diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..f47cd622d66 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "TMessagesProj/jni/libtgvoip"] + path = TMessagesProj/jni/libtgvoip + url = https://github.com/grishka/libtgvoip diff --git a/TMessagesProj/build.gradle b/TMessagesProj/build.gradle index 1dc16dbf90c..e1837d117d9 100644 --- a/TMessagesProj/build.gradle +++ b/TMessagesProj/build.gradle @@ -9,19 +9,22 @@ configurations { } dependencies { - compile 'com.google.android.gms:play-services-gcm:9.6.1' - compile 'com.google.android.gms:play-services-maps:9.6.1' - compile 'com.google.android.gms:play-services-vision:9.6.1' - compile 'com.android.support:support-core-ui:24.2.1' - compile 'com.android.support:support-compat:24.2.1' - compile 'com.android.support:support-core-utils:24.2.1' - compile 'net.hockeyapp.android:HockeySDK:4.0.1' + compile 'com.google.android.gms:play-services-gcm:10.2.0' + compile 'com.google.android.gms:play-services-maps:10.2.0' + compile 'com.google.android.gms:play-services-vision:10.2.0' + compile 'com.android.support:support-core-ui:25.3.0' + compile 'com.android.support:support-compat:25.3.0' + compile 'com.android.support:support-core-utils:25.3.0' + compile 'com.android.support:support-v13:25.3.0' + compile 'com.android.support:palette-v7:25.3.0' + compile 'net.hockeyapp.android:HockeySDK:4.1.2' compile 'com.googlecode.mp4parser:isoparser:1.0.6' + compile 'com.stripe:stripe-android:2.0.2' } android { - compileSdkVersion 24 - buildToolsVersion '24.0.2' + compileSdkVersion 25 + buildToolsVersion '25.0.2' useLibrary 'org.apache.http.legacy' defaultConfig.applicationId = "org.telegram.messenger" @@ -34,6 +37,10 @@ android { } } + dexOptions { + jumboMode = true + } + compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 targetCompatibility JavaVersion.VERSION_1_7 @@ -70,6 +77,7 @@ android { jniDebuggable false signingConfig signingConfigs.release minifyEnabled false + shrinkResources false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } @@ -80,7 +88,7 @@ android { } } - defaultConfig.versionCode = 851 + defaultConfig.versionCode = 957 sourceSets.debug { manifest.srcFile 'config/debug/AndroidManifest.xml' @@ -107,20 +115,52 @@ android { } versionCode = 1 } - fat { + x86_SDK23 { + ndk { + abiFilter "x86" + } + sourceSets.debug { + manifest.srcFile 'config/debug/AndroidManifest_SDK23.xml' + } + sourceSets.release { + manifest.srcFile 'config/release/AndroidManifest_SDK23.xml' + } + minSdkVersion 23 + versionCode = 4 + } + armv7_SDK23 { + ndk { + abiFilter "armeabi-v7a" + } + sourceSets.debug { + manifest.srcFile 'config/debug/AndroidManifest_SDK23.xml' + } + sourceSets.release { + manifest.srcFile 'config/release/AndroidManifest_SDK23.xml' + } + minSdkVersion 23 versionCode = 3 } + fat { + sourceSets.debug { + manifest.srcFile 'config/debug/AndroidManifest_SDK23.xml' + } + sourceSets.release { + manifest.srcFile 'config/release/AndroidManifest_SDK23.xml' + } + versionCode = 5 + } } applicationVariants.all { variant -> def abiVersion = variant.productFlavors.get(0).versionCode - variant.mergedFlavor.versionCode = defaultConfig.versionCode * 10 + abiVersion; + variant.mergedFlavor.versionCode = defaultConfig.versionCode * 10 + abiVersion } defaultConfig { minSdkVersion 14 - targetSdkVersion 24 - versionName "3.13.1" + targetSdkVersion 25 + versionName "3.18.0" externalNativeBuild { ndkBuild { diff --git a/TMessagesProj/config/debug/AndroidManifest_SDK23.xml b/TMessagesProj/config/debug/AndroidManifest_SDK23.xml new file mode 100644 index 00000000000..c5e0d86a002 --- /dev/null +++ b/TMessagesProj/config/debug/AndroidManifest_SDK23.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TMessagesProj/config/release/AndroidManifest_SDK23.xml b/TMessagesProj/config/release/AndroidManifest_SDK23.xml new file mode 100644 index 00000000000..e5642aa2561 --- /dev/null +++ b/TMessagesProj/config/release/AndroidManifest_SDK23.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TMessagesProj/jni/Android.mk b/TMessagesProj/jni/Android.mk index 6e9f977fb30..181e3ac6a7f 100755 --- a/TMessagesProj/jni/Android.mk +++ b/TMessagesProj/jni/Android.mk @@ -108,6 +108,64 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) +LOCAL_MODULE := WebRtcAec + +LOCAL_SRC_FILES := ./libtgvoip/external/libWebRtcAec_android_$(TARGET_ARCH_ABI).a + +include $(PREBUILT_STATIC_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := voip +LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -finline-functions -ffast-math -Os -fno-strict-aliasing -O3 +LOCAL_CFLAGS := -O3 -DUSE_KISS_FFT -fexceptions + +ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) +# LOCAL_CPPFLAGS += -mfloat-abi=softfp -mfpu=neon +# LOCAL_CFLAGS += -mfloat-abi=softfp -mfpu=neon -DFLOATING_POINT +# LOCAL_ARM_NEON := true +else + LOCAL_CFLAGS += -DFIXED_POINT + ifeq ($(TARGET_ARCH_ABI),armeabi) +# LOCAL_CPPFLAGS += -mfloat-abi=softfp -mfpu=neon +# LOCAL_CFLAGS += -mfloat-abi=softfp -mfpu=neon + else + ifeq ($(TARGET_ARCH_ABI),x86) + + endif + endif +endif + +MY_DIR := libtgvoip + +LOCAL_C_INCLUDES := jni/opus/include jni/boringssl/include/ + +LOCAL_SRC_FILES := \ +./libtgvoip/logging.cpp \ +./libtgvoip/VoIPController.cpp \ +./libtgvoip/BufferInputStream.cpp \ +./libtgvoip/BufferOutputStream.cpp \ +./libtgvoip/BlockingQueue.cpp \ +./libtgvoip/audio/AudioInput.cpp \ +./libtgvoip/os/android/AudioInputOpenSLES.cpp \ +./libtgvoip/MediaStreamItf.cpp \ +./libtgvoip/audio/AudioOutput.cpp \ +./libtgvoip/OpusEncoder.cpp \ +./libtgvoip/os/android/AudioOutputOpenSLES.cpp \ +./libtgvoip/JitterBuffer.cpp \ +./libtgvoip/OpusDecoder.cpp \ +./libtgvoip/BufferPool.cpp \ +./libtgvoip/os/android/OpenSLEngineWrapper.cpp \ +./libtgvoip/os/android/AudioInputAndroid.cpp \ +./libtgvoip/os/android/AudioOutputAndroid.cpp \ +./libtgvoip/EchoCanceller.cpp \ +./libtgvoip/CongestionControl.cpp \ +./libtgvoip/VoIPServerConfig.cpp + +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) + LOCAL_CPPFLAGS := -Wall -std=c++11 -DANDROID -frtti -DHAVE_PTHREAD -finline-functions -ffast-math -O0 LOCAL_C_INCLUDES += ./jni/boringssl/include/ LOCAL_ARM_MODE := arm @@ -237,13 +295,13 @@ include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) LOCAL_PRELINK_MODULE := false -LOCAL_MODULE := tmessages.24 +LOCAL_MODULE := tmessages.26 LOCAL_CFLAGS := -w -std=c11 -Os -DNULL=0 -DSOCKLEN_T=socklen_t -DLOCALE_NOT_USED -D_LARGEFILE_SOURCE=1 -D_FILE_OFFSET_BITS=64 LOCAL_CFLAGS += -Drestrict='' -D__EMX__ -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT -DHAVE_LRINTF -fno-math-errno LOCAL_CFLAGS += -DANDROID_NDK -DDISABLE_IMPORTGL -fno-strict-aliasing -fprefetch-loop-arrays -DAVOID_TABLES -DANDROID_TILE_BASED_DECODE -DANDROID_ARMV6_IDCT -ffast-math -D__STDC_CONSTANT_MACROS LOCAL_CPPFLAGS := -DBSD=1 -ffast-math -Os -funroll-loops -std=c++11 -LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad avformat avcodec avutil +LOCAL_LDLIBS := -ljnigraphics -llog -lz -latomic -lOpenSLES +LOCAL_STATIC_LIBRARIES := webp sqlite tgnet breakpad avformat avcodec avutil voip WebRtcAec LOCAL_SRC_FILES := \ ./opus/src/opus.c \ @@ -261,6 +319,14 @@ ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) LOCAL_ARM_MODE := arm LOCAL_CPPFLAGS += -DLIBYUV_NEON LOCAL_CFLAGS += -DLIBYUV_NEON + LOCAL_CFLAGS += -DOPUS_HAVE_RTCD -DOPUS_ARM_ASM + LOCAL_SRC_FILES += \ +# ./opus/celt/arm/celt_neon_intr.c \ +# ./opus/silk/arm/NSQ_neon.c \ + ./opus/silk/arm/arm_silk_map.c + +# LOCAL_SRC_FILES += ./opus/celt/arm/celt_pitch_xcorr_arm-gnu.S + else ifeq ($(TARGET_ARCH_ABI),armeabi) LOCAL_ARM_MODE := arm @@ -270,8 +336,25 @@ else LOCAL_CFLAGS += -Dx86fix LOCAL_CPPFLAGS += -Dx86fix LOCAL_ARM_MODE := arm - LOCAL_SRC_FILE += \ - ./libyuv/source/row_x86.asm +# LOCAL_SRC_FILES += \ +# ./libyuv/source/row_x86.asm + +# LOCAL_SRC_FILES += \ +# ./opus/celt/x86/celt_lpc_sse.c \ +# ./opus/celt/x86/pitch_sse.c \ +# ./opus/celt/x86/pitch_sse2.c \ +# ./opus/celt/x86/pitch_sse4_1.c \ +# ./opus/celt/x86/vq_sse2.c \ +# ./opus/celt/x86/x86_celt_map.c \ +# ./opus/celt/x86/x86cpu.c \ +# ./opus/silk/fixed/x86/burg_modified_FIX_sse.c \ +# ./opus/silk/fixed/x86/vector_ops_FIX_sse.c \ +# ./opus/silk/x86/NSQ_del_dec_sse.c \ +# ./opus/silk/x86/NSQ_sse.c \ +# ./opus/silk/x86/VAD_sse.c \ +# ./opus/silk/x86/VQ_WMat_sse.c \ +# ./opus/silk/x86/x86_silk_map.c + endif endif endif @@ -352,7 +435,8 @@ LOCAL_SRC_FILES += \ ./opus/silk/stereo_decode_pred.c \ ./opus/silk/stereo_encode_pred.c \ ./opus/silk/stereo_find_predictor.c \ -./opus/silk/stereo_quant_pred.c +./opus/silk/stereo_quant_pred.c \ +./opus/silk/LPC_fit.c LOCAL_SRC_FILES += \ ./opus/silk/fixed/LTP_analysis_filter_FIX.c \ @@ -364,12 +448,10 @@ LOCAL_SRC_FILES += \ ./opus/silk/fixed/find_pitch_lags_FIX.c \ ./opus/silk/fixed/find_pred_coefs_FIX.c \ ./opus/silk/fixed/noise_shape_analysis_FIX.c \ -./opus/silk/fixed/prefilter_FIX.c \ ./opus/silk/fixed/process_gains_FIX.c \ ./opus/silk/fixed/regularize_correlations_FIX.c \ ./opus/silk/fixed/residual_energy16_FIX.c \ ./opus/silk/fixed/residual_energy_FIX.c \ -./opus/silk/fixed/solve_LS_FIX.c \ ./opus/silk/fixed/warped_autocorrelation_FIX.c \ ./opus/silk/fixed/apply_sine_window_FIX.c \ ./opus/silk/fixed/autocorr_FIX.c \ @@ -483,7 +565,8 @@ LOCAL_SRC_FILES += \ ./gifvideo.cpp \ ./SqliteWrapper.cpp \ ./TgNetWrapper.cpp \ -./NativeLoader.cpp +./NativeLoader.cpp \ +./libtgvoip/client/android/tg_voip_jni.cpp include $(BUILD_SHARED_LIBRARY) diff --git a/TMessagesProj/jni/Application.mk b/TMessagesProj/jni/Application.mk index cbbd91a7272..9504557f5f6 100644 --- a/TMessagesProj/jni/Application.mk +++ b/TMessagesProj/jni/Application.mk @@ -1,4 +1,4 @@ -APP_PLATFORM := android-9 +APP_PLATFORM := android-14 APP_ABI := armeabi armeabi-v7a NDK_TOOLCHAIN_VERSION := 4.9 APP_STL := gnustl_static \ No newline at end of file diff --git a/TMessagesProj/jni/TgNetWrapper.cpp b/TMessagesProj/jni/TgNetWrapper.cpp index c08185a7785..63728d15ac8 100644 --- a/TMessagesProj/jni/TgNetWrapper.cpp +++ b/TMessagesProj/jni/TgNetWrapper.cpp @@ -26,6 +26,8 @@ jmethodID jclass_ConnectionsManager_onLogout; jmethodID jclass_ConnectionsManager_onConnectionStateChanged; jmethodID jclass_ConnectionsManager_onInternalPushReceived; jmethodID jclass_ConnectionsManager_onUpdateConfig; +jmethodID jclass_ConnectionsManager_onBytesSent; +jmethodID jclass_ConnectionsManager_onBytesReceived; jint createLoadOpetation(JNIEnv *env, jclass c, jint dc_id, jlong id, jlong volume_id, jlong access_hash, jint local_id, jbyteArray encKey, jbyteArray encIv, jstring extension, jint version, jint size, jstring dest, jstring temp, jobject delegate) { if (encKey != nullptr && encIv == nullptr || encKey == nullptr && encIv != nullptr || extension == nullptr || dest == nullptr || temp == nullptr) { @@ -168,7 +170,7 @@ void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject if (onQuickAck != nullptr) { onQuickAck = env->NewGlobalRef(onQuickAck); } - ConnectionsManager::getInstance().sendRequest(request, ([onComplete](TLObject *response, TL_error *error) { + ConnectionsManager::getInstance().sendRequest(request, ([onComplete](TLObject *response, TL_error *error, int32_t networkType) { TL_api_response *resp = (TL_api_response *) response; jint ptr = 0; jint errorCode = 0; @@ -180,7 +182,7 @@ void sendRequest(JNIEnv *env, jclass c, jint object, jobject onComplete, jobject errorText = jniEnv->NewStringUTF(error->text.c_str()); } if (onComplete != nullptr) { - jniEnv->CallVoidMethod(onComplete, jclass_RequestDelegateInternal_run, ptr, errorCode, errorText); + jniEnv->CallVoidMethod(onComplete, jclass_RequestDelegateInternal_run, ptr, errorCode, errorText, networkType); } if (errorText != nullptr) { jniEnv->DeleteLocalRef(errorText); @@ -246,8 +248,8 @@ void setUseIpv6(JNIEnv *env, jclass c, bool value) { ConnectionsManager::getInstance().setUseIpv6(value); } -void setNetworkAvailable(JNIEnv *env, jclass c, jboolean value) { - ConnectionsManager::getInstance().setNetworkAvailable(value); +void setNetworkAvailable(JNIEnv *env, jclass c, jboolean value, jint networkType) { + ConnectionsManager::getInstance().setNetworkAvailable(value, networkType); } void setPushConnectionEnabled(JNIEnv *env, jclass c, jboolean value) { @@ -289,9 +291,17 @@ class Delegate : public ConnectiosManagerDelegate { void onInternalPushReceived() { jniEnv->CallStaticVoidMethod(jclass_ConnectionsManager, jclass_ConnectionsManager_onInternalPushReceived); } + + void onBytesReceived(int32_t amount, int32_t networkType) { + jniEnv->CallStaticVoidMethod(jclass_ConnectionsManager, jclass_ConnectionsManager_onBytesReceived, amount, networkType); + } + + void onBytesSent(int32_t amount, int32_t networkType) { + jniEnv->CallStaticVoidMethod(jclass_ConnectionsManager, jclass_ConnectionsManager_onBytesSent, amount, networkType); + } }; -void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring configPath, jstring logPath, jint userId, jboolean enablePushConnection) { +void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring deviceModel, jstring systemVersion, jstring appVersion, jstring langCode, jstring configPath, jstring logPath, jint userId, jboolean enablePushConnection, jboolean hasNetwork, jint networkType) { const char *deviceModelStr = env->GetStringUTFChars(deviceModel, 0); const char *systemVersionStr = env->GetStringUTFChars(systemVersion, 0); const char *appVersionStr = env->GetStringUTFChars(appVersion, 0); @@ -299,7 +309,7 @@ void init(JNIEnv *env, jclass c, jint version, jint layer, jint apiId, jstring d const char *configPathStr = env->GetStringUTFChars(configPath, 0); const char *logPathStr = env->GetStringUTFChars(logPath, 0); - ConnectionsManager::getInstance().init(version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(configPathStr), std::string(logPathStr), userId, true, enablePushConnection); + ConnectionsManager::getInstance().init(version, layer, apiId, std::string(deviceModelStr), std::string(systemVersionStr), std::string(appVersionStr), std::string(langCodeStr), std::string(configPathStr), std::string(logPathStr), userId, true, enablePushConnection, hasNetwork, networkType); if (deviceModelStr != 0) { env->ReleaseStringUTFChars(deviceModel, deviceModelStr); @@ -339,13 +349,13 @@ static JNINativeMethod ConnectionsManagerMethods[] = { {"native_applyDatacenterAddress", "(ILjava/lang/String;I)V", (void *) applyDatacenterAddress}, {"native_getConnectionState", "()I", (void *) getConnectionState}, {"native_setUserId", "(I)V", (void *) setUserId}, - {"native_init", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZ)V", (void *) init}, + {"native_init", "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IZZI)V", (void *) init}, {"native_switchBackend", "()V", (void *) switchBackend}, {"native_pauseNetwork", "()V", (void *) pauseNetwork}, {"native_resumeNetwork", "(Z)V", (void *) resumeNetwork}, {"native_updateDcSettings", "()V", (void *) updateDcSettings}, {"native_setUseIpv6", "(Z)V", (void *) setUseIpv6}, - {"native_setNetworkAvailable", "(Z)V", (void *) setNetworkAvailable}, + {"native_setNetworkAvailable", "(ZI)V", (void *) setNetworkAvailable}, {"native_setPushConnectionEnabled", "(Z)V", (void *) setPushConnectionEnabled}, {"native_setJava", "(Z)V", (void *) setJava} }; @@ -381,7 +391,7 @@ extern "C" int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env) { if (jclass_RequestDelegateInternal == 0) { return JNI_FALSE; } - jclass_RequestDelegateInternal_run = env->GetMethodID(jclass_RequestDelegateInternal, "run", "(IILjava/lang/String;)V"); + jclass_RequestDelegateInternal_run = env->GetMethodID(jclass_RequestDelegateInternal, "run", "(IILjava/lang/String;I)V"); if (jclass_RequestDelegateInternal_run == 0) { return JNI_FALSE; } @@ -447,6 +457,14 @@ extern "C" int registerNativeTgNetFunctions(JavaVM *vm, JNIEnv *env) { if (jclass_ConnectionsManager_onUpdateConfig == 0) { return JNI_FALSE; } + jclass_ConnectionsManager_onBytesSent = env->GetStaticMethodID(jclass_ConnectionsManager, "onBytesSent", "(II)V"); + if (jclass_ConnectionsManager_onBytesSent == 0) { + return JNI_FALSE; + } + jclass_ConnectionsManager_onBytesReceived = env->GetStaticMethodID(jclass_ConnectionsManager, "onBytesReceived", "(II)V"); + if (jclass_ConnectionsManager_onBytesReceived == 0) { + return JNI_FALSE; + } ConnectionsManager::getInstance().setDelegate(new Delegate()); return JNI_TRUE; diff --git a/TMessagesProj/jni/boringssl/crypto/ec/util-64.c b/TMessagesProj/jni/boringssl/crypto/ec/util-64.c index 171b0631b69..829cf6d73af 100644 --- a/TMessagesProj/jni/boringssl/crypto/ec/util-64.c +++ b/TMessagesProj/jni/boringssl/crypto/ec/util-64.c @@ -127,7 +127,7 @@ void ec_GFp_nistp_points_make_affine_internal( * * A left-shift followed by subtraction of the original value yields a new * representation of the same value, using signed bits s_i = b_(i+1) - b_i. - * This representation from Booth's paper has since appeared in the + * This representation from Booth's paper has since onAppeared in the * literature under a variety of different names including "reversed binary * form", "alternating greedy expansion", "mutual opposite form", and * "sign-alternating {+-1}-representation". diff --git a/TMessagesProj/jni/gifvideo.cpp b/TMessagesProj/jni/gifvideo.cpp index b30d3f0b481..06532917cde 100644 --- a/TMessagesProj/jni/gifvideo.cpp +++ b/TMessagesProj/jni/gifvideo.cpp @@ -273,6 +273,5 @@ jint Java_org_telegram_ui_Components_AnimatedFileDrawable_getVideoFrame(JNIEnv * return 1; } } - return 0; } } diff --git a/TMessagesProj/jni/libtgvoip b/TMessagesProj/jni/libtgvoip new file mode 160000 index 00000000000..eb813e1d133 --- /dev/null +++ b/TMessagesProj/jni/libtgvoip @@ -0,0 +1 @@ +Subproject commit eb813e1d133fdfb96f5efe9c767e86136aab1133 diff --git a/TMessagesProj/jni/opus/celt/_kiss_fft_guts.h b/TMessagesProj/jni/opus/celt/_kiss_fft_guts.h index aefe490e117..17392b3e909 100644 --- a/TMessagesProj/jni/opus/celt/_kiss_fft_guts.h +++ b/TMessagesProj/jni/opus/celt/_kiss_fft_guts.h @@ -58,16 +58,12 @@ # define S_MUL(a,b) MULT16_32_Q15(b, a) # define C_MUL(m,a,b) \ - do{ (m).r = SUB32(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ - (m).i = ADD32(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0) + do{ (m).r = SUB32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = ADD32_ovflw(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)); }while(0) # define C_MULC(m,a,b) \ - do{ (m).r = ADD32(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ - (m).i = SUB32(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0) - -# define C_MUL4(m,a,b) \ - do{ (m).r = SHR32(SUB32(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)),2); \ - (m).i = SHR32(ADD32(S_MUL((a).r,(b).i) , S_MUL((a).i,(b).r)),2); }while(0) + do{ (m).r = ADD32_ovflw(S_MUL((a).r,(b).r) , S_MUL((a).i,(b).i)); \ + (m).i = SUB32_ovflw(S_MUL((a).i,(b).r) , S_MUL((a).r,(b).i)); }while(0) # define C_MULBYSCALAR( c, s ) \ do{ (c).r = S_MUL( (c).r , s ) ;\ @@ -81,17 +77,17 @@ DIVSCALAR( (c).i , div); }while (0) #define C_ADD( res, a,b)\ - do {(res).r=ADD32((a).r,(b).r); (res).i=ADD32((a).i,(b).i); \ + do {(res).r=ADD32_ovflw((a).r,(b).r); (res).i=ADD32_ovflw((a).i,(b).i); \ }while(0) #define C_SUB( res, a,b)\ - do {(res).r=SUB32((a).r,(b).r); (res).i=SUB32((a).i,(b).i); \ + do {(res).r=SUB32_ovflw((a).r,(b).r); (res).i=SUB32_ovflw((a).i,(b).i); \ }while(0) #define C_ADDTO( res , a)\ - do {(res).r = ADD32((res).r, (a).r); (res).i = ADD32((res).i,(a).i);\ + do {(res).r = ADD32_ovflw((res).r, (a).r); (res).i = ADD32_ovflw((res).i,(a).i);\ }while(0) #define C_SUBFROM( res , a)\ - do {(res).r = ADD32((res).r,(a).r); (res).i = SUB32((res).i,(a).i); \ + do {(res).r = ADD32_ovflw((res).r,(a).r); (res).i = SUB32_ovflw((res).i,(a).i); \ }while(0) #if defined(OPUS_ARM_INLINE_ASM) @@ -101,6 +97,9 @@ #if defined(OPUS_ARM_INLINE_EDSP) #include "arm/kiss_fft_armv5e.h" #endif +#if defined(MIPSr1_ASM) +#include "mips/kiss_fft_mipsr1.h" +#endif #else /* not FIXED_POINT*/ diff --git a/TMessagesProj/jni/opus/celt/arch.h b/TMessagesProj/jni/opus/celt/arch.h index 3bbcd3663a2..9eb37d8f779 100644 --- a/TMessagesProj/jni/opus/celt/arch.h +++ b/TMessagesProj/jni/opus/celt/arch.h @@ -46,6 +46,14 @@ # endif # endif +#if OPUS_GNUC_PREREQ(3, 0) +#define opus_likely(x) (__builtin_expect(!!(x), 1)) +#define opus_unlikely(x) (__builtin_expect(!!(x), 0)) +#else +#define opus_likely(x) (!!(x)) +#define opus_unlikely(x) (!!(x)) +#endif + #define CELT_SIG_SCALE 32768.f #define celt_fatal(str) _celt_fatal(str, __FILE__, __LINE__); @@ -69,11 +77,8 @@ static OPUS_INLINE void _celt_fatal(const char *str, const char *file, int line) #define IMUL32(a,b) ((a)*(b)) -#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ -#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ #define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 16-bit value. */ #define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ -#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ #define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum 32-bit value. */ #define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ #define IMIN(a,b) ((a) < (b) ? (a) : (b)) /**< Minimum int value. */ @@ -81,6 +86,15 @@ static OPUS_INLINE void _celt_fatal(const char *str, const char *file, int line) #define UADD32(a,b) ((a)+(b)) #define USUB32(a,b) ((a)-(b)) +/* Set this if opus_int64 is a native type of the CPU. */ +/* Assume that all LP64 architectures have fast 64-bit types; also x86_64 + (which can be ILP32 for x32) and Win64 (which is LLP64). */ +#if defined(__x86_64__) || defined(__LP64__) || defined(_WIN64) +#define OPUS_FAST_INT64 1 +#else +#define OPUS_FAST_INT64 0 +#endif + #define PRINT_MIPS(file) #ifdef FIXED_POINT @@ -95,6 +109,9 @@ typedef opus_val32 celt_ener; #define Q15ONE 32767 #define SIG_SHIFT 12 +/* Safe saturation value for 32-bit signals. Should be less than + 2^31*(1-0.85) to avoid blowing up on DC at deemphasis.*/ +#define SIG_SAT (300000000) #define NORM_SCALING 16384 @@ -108,13 +125,22 @@ typedef opus_val32 celt_ener; #define SCALEIN(a) (a) #define SCALEOUT(a) (a) +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) + +static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { + return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x; +} + #ifdef FIXED_DEBUG #include "fixed_debug.h" #else #include "fixed_generic.h" -#ifdef OPUS_ARM_INLINE_EDSP +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/fixed_arm64.h" +#elif OPUS_ARM_INLINE_EDSP #include "arm/fixed_armv5e.h" #elif defined (OPUS_ARM_INLINE_ASM) #include "arm/fixed_armv4.h" @@ -137,6 +163,22 @@ typedef float celt_sig; typedef float celt_norm; typedef float celt_ener; +#ifdef FLOAT_APPROX +/* This code should reliably detect NaN/inf even when -ffast-math is used. + Assumes IEEE 754 format. */ +static OPUS_INLINE int celt_isnan(float x) +{ + union {float f; opus_uint32 i;} in; + in.f = x; + return ((in.i>>23)&0xFF)==0xFF && (in.i&0x007FFFFF)!=0; +} +#else +#ifdef __FAST_MATH__ +#error Cannot build libopus with -ffast-math unless FLOAT_APPROX is defined. This could result in crashes on extreme (e.g. NaN) input +#endif +#define celt_isnan(x) ((x)!=(x)) +#endif + #define Q15ONE 1.0f #define NORM_SCALING 1.f @@ -146,11 +188,16 @@ typedef float celt_ener; #define VERY_LARGE16 1e15f #define Q15_ONE ((opus_val16)1.f) +/* This appears to be the same speed as C99's fabsf() but it's more portable. */ +#define ABS16(x) ((float)fabs(x)) +#define ABS32(x) ((float)fabs(x)) + #define QCONST16(x,bits) (x) #define QCONST32(x,bits) (x) #define NEG16(x) (-(x)) #define NEG32(x) (-(x)) +#define NEG32_ovflw(x) (-(x)) #define EXTRACT16(x) (x) #define EXTEND32(x) (x) #define SHR16(a,shift) (a) @@ -167,6 +214,7 @@ typedef float celt_ener; #define SATURATE16(x) (x) #define ROUND16(a,shift) (a) +#define SROUND16(a,shift) (a) #define HALF16(x) (.5f*(x)) #define HALF32(x) (.5f*(x)) @@ -174,6 +222,8 @@ typedef float celt_ener; #define SUB16(a,b) ((a)-(b)) #define ADD32(a,b) ((a)+(b)) #define SUB32(a,b) ((a)-(b)) +#define ADD32_ovflw(a,b) ((a)+(b)) +#define SUB32_ovflw(a,b) ((a)-(b)) #define MULT16_16_16(a,b) ((a)*(b)) #define MULT16_16(a,b) ((opus_val32)(a)*(opus_val32)(b)) #define MAC16_16(c,a,b) ((c)+(opus_val32)(a)*(opus_val32)(b)) @@ -184,6 +234,7 @@ typedef float celt_ener; #define MULT32_32_Q31(a,b) ((a)*(b)) #define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q16(c,a,b) ((c)+(a)*(b)) #define MULT16_16_Q11_32(a,b) ((a)*(b)) #define MULT16_16_Q11(a,b) ((a)*(b)) @@ -201,6 +252,8 @@ typedef float celt_ener; #define SCALEIN(a) ((a)*CELT_SIG_SCALE) #define SCALEOUT(a) ((a)*(1/CELT_SIG_SCALE)) +#define SIG2WORD16(x) (x) + #endif /* !FIXED_POINT */ #ifndef GLOBAL_STACK_SIZE diff --git a/TMessagesProj/jni/opus/celt/arm/arm2gnu.pl b/TMessagesProj/jni/opus/celt/arm/arm2gnu.pl index eab42efa2bc..6c922ac819d 100755 --- a/TMessagesProj/jni/opus/celt/arm/arm2gnu.pl +++ b/TMessagesProj/jni/opus/celt/arm/arm2gnu.pl @@ -1,7 +1,33 @@ #!/usr/bin/perl +# Copyright (C) 2002-2013 Xiph.org Foundation +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# - Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. my $bigend; # little/big endian my $nxstack; +my $apple = 0; +my $symprefix = ""; $nxstack = 0; @@ -10,11 +36,16 @@ while ($ARGV[0] =~ /^-/) { $_ = shift; - last if /^--/; - if (/^-n/) { + last if /^--$/; + if (/^-n$/) { $nflag++; next; } + if (/^--apple$/) { + $apple = 1; + $symprefix = "_"; + next; + } die "I don't recognize this switch: $_\\n"; } $printit++ unless $nflag; @@ -25,6 +56,8 @@ $thumb = 0; # ARM mode by default, not Thumb. @proc_stack = (); +printf (" .syntax unified\n"); + LINE: while (<>) { @@ -53,7 +86,7 @@ s/\bINCLUDE[ \t]*([^ \t\n]+)/.include \"$1\"/; s/\bGET[ \t]*([^ \t\n]+)/.include \"${ my $x=$1; $x =~ s|\.s|-gnu.S|; \$x }\"/; s/\bIMPORT\b/.extern/; - s/\bEXPORT\b/.global/; + s/\bEXPORT\b\s*/.global $symprefix/; s/^(\s+)\[/$1IF/; s/^(\s+)\|/$1ELSE/; s/^(\s+)\]/$1ENDIF/; @@ -109,7 +142,7 @@ # won't match the original source file (we could use the .line # directive, which is documented to be obsolete, but then gdb will # show the wrong line in the translated source file). - s/$/; .arch armv7-a\n .fpu neon\n .object_arch armv4t/; + s/$/; .arch armv7-a\n .fpu neon\n .object_arch armv4t/ unless ($apple); } } @@ -131,9 +164,13 @@ $prefix = ""; if ($proc) { - $prefix = $prefix.sprintf("\t.type\t%s, %%function; ",$proc); + $prefix = $prefix.sprintf("\t.type\t%s, %%function; ",$proc) unless ($apple); + # Make sure we $prefix isn't empty here (for the $apple case). + # We handle mangling the label here, make sure it doesn't match + # the label handling below (if $prefix would be empty). + $prefix = "; "; push(@proc_stack, $proc); - s/^[A-Za-z_\.]\w+/$&:/; + s/^[A-Za-z_\.]\w+/$symprefix$&:/; } $prefix = $prefix."\t.thumb_func; " if ($thumb); s/\bPROC\b/@ $&/; @@ -146,7 +183,7 @@ my $proc; s/\bENDP\b/@ $&/; $proc = pop(@proc_stack); - $_ = "\t.size $proc, .-$proc".$_ if ($proc); + $_ = "\t.size $proc, .-$proc".$_ if ($proc && !$apple); } s/\bSUBT\b/@ $&/; s/\bDATA\b/@ $&/; # DATA directive is deprecated -- Asm guide, p.7-25 @@ -311,6 +348,6 @@ } #If we had a code section, mark that this object doesn't need an executable # stack. -if ($nxstack) { +if ($nxstack && !$apple) { printf (" .section\t.note.GNU-stack,\"\",\%\%progbits\n"); } diff --git a/TMessagesProj/jni/opus/celt/arm/arm_celt_map.c b/TMessagesProj/jni/opus/celt/arm/arm_celt_map.c index 547a84d1495..4d4d069a86f 100644 --- a/TMessagesProj/jni/opus/celt/arm/arm_celt_map.c +++ b/TMessagesProj/jni/opus/celt/arm/arm_celt_map.c @@ -30,10 +30,15 @@ #endif #include "pitch.h" +#include "kiss_fft.h" +#include "mdct.h" #if defined(OPUS_HAVE_RTCD) # if defined(FIXED_POINT) +# if ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \ + (defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \ + (defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP))) opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, const opus_val16 *, opus_val32 *, int , int) = { celt_pitch_xcorr_c, /* ARMv4 */ @@ -41,9 +46,98 @@ opus_val32 (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, MAY_HAVE_MEDIA(celt_pitch_xcorr), /* Media */ MAY_HAVE_NEON(celt_pitch_xcorr) /* NEON */ }; -# else -# error "Floating-point implementation is not supported by ARM asm yet." \ - "Reconfigure with --disable-rtcd or send patches." -# endif + +# endif +# else /* !FIXED_POINT */ +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) +void (*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int) = { + celt_pitch_xcorr_c, /* ARMv4 */ + celt_pitch_xcorr_c, /* EDSP */ + celt_pitch_xcorr_c, /* Media */ + celt_pitch_xcorr_float_neon /* Neon */ +}; +# endif +# endif /* FIXED_POINT */ + +#if defined(FIXED_POINT) && defined(OPUS_HAVE_RTCD) && \ + defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR) + +void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len +) = { + xcorr_kernel_c, /* ARMv4 */ + xcorr_kernel_c, /* EDSP */ + xcorr_kernel_c, /* Media */ + xcorr_kernel_neon_fixed, /* Neon */ +}; #endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +# if defined(HAVE_ARM_NE10) +# if defined(CUSTOM_MODES) +int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = { + opus_fft_alloc_arch_c, /* ARMv4 */ + opus_fft_alloc_arch_c, /* EDSP */ + opus_fft_alloc_arch_c, /* Media */ + opus_fft_alloc_arm_neon /* Neon with NE10 library support */ +}; + +void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])(kiss_fft_state *st) = { + opus_fft_free_arch_c, /* ARMv4 */ + opus_fft_free_arch_c, /* EDSP */ + opus_fft_free_arch_c, /* Media */ + opus_fft_free_arm_neon /* Neon with NE10 */ +}; +# endif /* CUSTOM_MODES */ + +void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) = { + opus_fft_c, /* ARMv4 */ + opus_fft_c, /* EDSP */ + opus_fft_c, /* Media */ + opus_fft_neon /* Neon with NE10 */ +}; + +void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) = { + opus_ifft_c, /* ARMv4 */ + opus_ifft_c, /* EDSP */ + opus_ifft_c, /* Media */ + opus_ifft_neon /* Neon with NE10 */ +}; + +void (*const CLT_MDCT_FORWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, + int stride, int arch) = { + clt_mdct_forward_c, /* ARMv4 */ + clt_mdct_forward_c, /* EDSP */ + clt_mdct_forward_c, /* Media */ + clt_mdct_forward_neon /* Neon with NE10 */ +}; + +void (*const CLT_MDCT_BACKWARD_IMPL[OPUS_ARCHMASK+1])(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, + int stride, int arch) = { + clt_mdct_backward_c, /* ARMv4 */ + clt_mdct_backward_c, /* EDSP */ + clt_mdct_backward_c, /* Media */ + clt_mdct_backward_neon /* Neon with NE10 */ +}; + +# endif /* HAVE_ARM_NE10 */ +# endif /* OPUS_ARM_MAY_HAVE_NEON_INTR */ + +#endif /* OPUS_HAVE_RTCD */ diff --git a/TMessagesProj/jni/opus/celt/arm/armcpu.c b/TMessagesProj/jni/opus/celt/arm/armcpu.c index 17685258b1e..694a63b78e6 100644 --- a/TMessagesProj/jni/opus/celt/arm/armcpu.c +++ b/TMessagesProj/jni/opus/celt/arm/armcpu.c @@ -37,11 +37,12 @@ #include "cpu_support.h" #include "os_support.h" #include "opus_types.h" +#include "arch.h" -#define OPUS_CPU_ARM_V4 (1) -#define OPUS_CPU_ARM_EDSP (1<<1) -#define OPUS_CPU_ARM_MEDIA (1<<2) -#define OPUS_CPU_ARM_NEON (1<<3) +#define OPUS_CPU_ARM_V4_FLAG (1<= ARMv6) */ if(memcmp(buf, "CPU architecture:", 17) == 0) { @@ -134,7 +137,7 @@ opus_uint32 opus_cpu_capabilities(void) version = atoi(buf+17); if(version >= 6) - flags |= OPUS_CPU_ARM_MEDIA; + flags |= OPUS_CPU_ARM_MEDIA_FLAG; } # endif } @@ -156,18 +159,26 @@ int opus_select_arch(void) opus_uint32 flags = opus_cpu_capabilities(); int arch = 0; - if(!(flags & OPUS_CPU_ARM_EDSP)) + if(!(flags & OPUS_CPU_ARM_EDSP_FLAG)) { + /* Asserts ensure arch values are sequential */ + celt_assert(arch == OPUS_ARCH_ARM_V4); return arch; + } arch++; - if(!(flags & OPUS_CPU_ARM_MEDIA)) + if(!(flags & OPUS_CPU_ARM_MEDIA_FLAG)) { + celt_assert(arch == OPUS_ARCH_ARM_EDSP); return arch; + } arch++; - if(!(flags & OPUS_CPU_ARM_NEON)) + if(!(flags & OPUS_CPU_ARM_NEON_FLAG)) { + celt_assert(arch == OPUS_ARCH_ARM_MEDIA); return arch; + } arch++; + celt_assert(arch == OPUS_ARCH_ARM_NEON); return arch; } diff --git a/TMessagesProj/jni/opus/celt/arm/armcpu.h b/TMessagesProj/jni/opus/celt/arm/armcpu.h index ac5744606e0..820262ff5f8 100644 --- a/TMessagesProj/jni/opus/celt/arm/armcpu.h +++ b/TMessagesProj/jni/opus/celt/arm/armcpu.h @@ -66,6 +66,12 @@ # if defined(OPUS_HAVE_RTCD) int opus_select_arch(void); + +#define OPUS_ARCH_ARM_V4 (0) +#define OPUS_ARCH_ARM_EDSP (1) +#define OPUS_ARCH_ARM_MEDIA (2) +#define OPUS_ARCH_ARM_NEON (3) + # endif #endif diff --git a/TMessagesProj/jni/opus/celt/arm/celt_ne10_fft.c b/TMessagesProj/jni/opus/celt/arm/celt_ne10_fft.c new file mode 100644 index 00000000000..42d96a71176 --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/celt_ne10_fft.c @@ -0,0 +1,174 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_ne10_fft.c + @brief ARM Neon optimizations for fft using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include +#include +#include "os_support.h" +#include "kiss_fft.h" +#include "stack_alloc.h" + +#if !defined(FIXED_POINT) +# define NE10_FFT_ALLOC_C2C_TYPE_NEON ne10_fft_alloc_c2c_float32_neon +# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_float32_t +# define NE10_FFT_STATE_TYPE_T ne10_fft_state_float32_t +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_float32 +# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_float32_t +# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_float32_neon +#else +# define NE10_FFT_ALLOC_C2C_TYPE_NEON(nfft) ne10_fft_alloc_c2c_int32_neon(nfft) +# define NE10_FFT_CFG_TYPE_T ne10_fft_cfg_int32_t +# define NE10_FFT_STATE_TYPE_T ne10_fft_state_int32_t +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32 +# define NE10_FFT_DESTROY_C2C_TYPE ne10_fft_destroy_c2c_int32 +# define NE10_FFT_CPX_TYPE_T ne10_fft_cpx_int32_t +# define NE10_FFT_C2C_1D_TYPE_NEON ne10_fft_c2c_1d_int32_neon +#endif + +#if defined(CUSTOM_MODES) + +/* nfft lengths in NE10 that support scaled fft */ +# define NE10_FFTSCALED_SUPPORT_MAX 4 +static const int ne10_fft_scaled_support[NE10_FFTSCALED_SUPPORT_MAX] = { + 480, 240, 120, 60 +}; + +int opus_fft_alloc_arm_neon(kiss_fft_state *st) +{ + int i; + size_t memneeded = sizeof(struct arch_fft_state); + + st->arch_fft = (arch_fft_state *)opus_alloc(memneeded); + if (!st->arch_fft) + return -1; + + for (i = 0; i < NE10_FFTSCALED_SUPPORT_MAX; i++) { + if(st->nfft == ne10_fft_scaled_support[i]) + break; + } + if (i == NE10_FFTSCALED_SUPPORT_MAX) { + /* This nfft length (scaled fft) is not supported in NE10 */ + st->arch_fft->is_supported = 0; + st->arch_fft->priv = NULL; + } + else { + st->arch_fft->is_supported = 1; + st->arch_fft->priv = (void *)NE10_FFT_ALLOC_C2C_TYPE_NEON(st->nfft); + if (st->arch_fft->priv == NULL) { + return -1; + } + } + return 0; +} + +void opus_fft_free_arm_neon(kiss_fft_state *st) +{ + NE10_FFT_CFG_TYPE_T cfg; + + if (!st->arch_fft) + return; + + cfg = (NE10_FFT_CFG_TYPE_T)st->arch_fft->priv; + if (cfg) + NE10_FFT_DESTROY_C2C_TYPE(cfg); + opus_free(st->arch_fft); +} +#endif + +void opus_fft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) +{ + NE10_FFT_STATE_TYPE_T state; + NE10_FFT_CFG_TYPE_T cfg = &state; + VARDECL(NE10_FFT_CPX_TYPE_T, buffer); + SAVE_STACK; + ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T); + + if (!st->arch_fft->is_supported) { + /* This nfft length (scaled fft) not supported in NE10 */ + opus_fft_c(st, fin, fout); + } + else { + memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T)); + state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0]; +#if !defined(FIXED_POINT) + state.is_forward_scaled = 1; + + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 0); +#else + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 0, 1); +#endif + } + RESTORE_STACK; +} + +void opus_ifft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout) +{ + NE10_FFT_STATE_TYPE_T state; + NE10_FFT_CFG_TYPE_T cfg = &state; + VARDECL(NE10_FFT_CPX_TYPE_T, buffer); + SAVE_STACK; + ALLOC(buffer, st->nfft, NE10_FFT_CPX_TYPE_T); + + if (!st->arch_fft->is_supported) { + /* This nfft length (scaled fft) not supported in NE10 */ + opus_ifft_c(st, fin, fout); + } + else { + memcpy((void *)cfg, st->arch_fft->priv, sizeof(NE10_FFT_STATE_TYPE_T)); + state.buffer = (NE10_FFT_CPX_TYPE_T *)&buffer[0]; +#if !defined(FIXED_POINT) + state.is_backward_scaled = 0; + + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 1); +#else + NE10_FFT_C2C_1D_TYPE_NEON((NE10_FFT_CPX_TYPE_T *)fout, + (NE10_FFT_CPX_TYPE_T *)fin, + cfg, 1, 0); +#endif + } + RESTORE_STACK; +} diff --git a/TMessagesProj/jni/opus/celt/arm/celt_ne10_mdct.c b/TMessagesProj/jni/opus/celt/arm/celt_ne10_mdct.c new file mode 100644 index 00000000000..293c3efd7a2 --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/celt_ne10_mdct.c @@ -0,0 +1,258 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_ne10_mdct.c + @brief ARM Neon optimizations for mdct using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SKIP_CONFIG_H +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#endif + +#include "kiss_fft.h" +#include "_kiss_fft_guts.h" +#include "mdct.h" +#include "stack_alloc.h" + +void clt_mdct_forward_neon(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, + int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + + SAVE_STACK; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + ALLOC(f2, N4, kiss_fft_cpx); + + /* Consider the input to be composed of four blocks: [a, b, c, d] */ + /* Window, shuffle, fold */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in+(overlap>>1); + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+N2-1+(overlap>>1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const opus_val16 * OPUS_RESTRICT wp1 = window+(overlap>>1); + const opus_val16 * OPUS_RESTRICT wp2 = window+(overlap>>1)-1; + for(i=0;i<((overlap+3)>>2);i++) + { + /* Real part arranged as -d-cR, Imag part arranged as -b+aR*/ + *yp++ = MULT16_32_Q15(*wp2, xp1[N2]) + MULT16_32_Q15(*wp1,*xp2); + *yp++ = MULT16_32_Q15(*wp1, *xp1) - MULT16_32_Q15(*wp2, xp2[-N2]); + xp1+=2; + xp2-=2; + wp1+=2; + wp2-=2; + } + wp1 = window; + wp2 = window+overlap-1; + for(;i>2);i++) + { + /* Real part arranged as a-bR, Imag part arranged as -c-dR */ + *yp++ = *xp2; + *yp++ = *xp1; + xp1+=2; + xp2-=2; + } + for(;ii,t[N4+i]) - S_MUL(fp->r,t[i]); + yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; + yp1 += 2*stride; + yp2 -= 2*stride; + } + } + RESTORE_STACK; +} + +void clt_mdct_backward_neon(const mdct_lookup *l, + kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, + int overlap, int shift, int stride, int arch) +{ + int i; + int N, N2, N4; + VARDECL(kiss_fft_scalar, f); + const kiss_twiddle_scalar *trig; + const kiss_fft_state *st = l->kfft[shift]; + + N = l->n; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } + N2 = N>>1; + N4 = N>>2; + + ALLOC(f, N2, kiss_fft_scalar); + + /* Pre-rotate */ + { + /* Temp pointers to make it really clear to the compiler what we're doing */ + const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; + const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); + kiss_fft_scalar * OPUS_RESTRICT yp = f; + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + for(i=0;i>1)), arch); + + /* Post-rotate and de-shuffle from both ends of the buffer at once to make + it in-place. */ + { + kiss_fft_scalar * yp0 = out+(overlap>>1); + kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; + /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the + middle pair will be computed twice. */ + for(i=0;i<(N4+1)>>1;i++) + { + kiss_fft_scalar re, im, yr, yi; + kiss_twiddle_scalar t0, t1; + re = yp0[0]; + im = yp0[1]; + t0 = t[i]; + t1 = t[N4+i]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL(re,t0) + S_MUL(im,t1); + yi = S_MUL(re,t1) - S_MUL(im,t0); + re = yp1[0]; + im = yp1[1]; + yp0[0] = yr; + yp1[1] = yi; + + t0 = t[(N4-i-1)]; + t1 = t[(N2-i-1)]; + /* We'd scale up by 2 here, but instead it's done when mixing the windows */ + yr = S_MUL(re,t0) + S_MUL(im,t1); + yi = S_MUL(re,t1) - S_MUL(im,t0); + yp1[0] = yr; + yp0[1] = yi; + yp0 += 2; + yp1 -= 2; + } + } + + /* Mirror on both sides for TDAC */ + { + kiss_fft_scalar * OPUS_RESTRICT xp1 = out+overlap-1; + kiss_fft_scalar * OPUS_RESTRICT yp1 = out; + const opus_val16 * OPUS_RESTRICT wp1 = window; + const opus_val16 * OPUS_RESTRICT wp2 = window+overlap-1; + + for(i = 0; i < overlap/2; i++) + { + kiss_fft_scalar x1, x2; + x1 = *xp1; + x2 = *yp1; + *yp1++ = MULT16_32_Q15(*wp2, x2) - MULT16_32_Q15(*wp1, x1); + *xp1-- = MULT16_32_Q15(*wp1, x2) + MULT16_32_Q15(*wp2, x1); + wp1++; + wp2--; + } + } + RESTORE_STACK; +} diff --git a/TMessagesProj/jni/opus/celt/arm/celt_neon_intr.c b/TMessagesProj/jni/opus/celt/arm/celt_neon_intr.c new file mode 100644 index 00000000000..47bbe3dc22e --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/celt_neon_intr.c @@ -0,0 +1,311 @@ +/* Copyright (c) 2014-2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file celt_neon_intr.c + @brief ARM Neon Intrinsic optimizations for celt + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "../pitch.h" + +#if defined(FIXED_POINT) +void xcorr_kernel_neon_fixed(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len) +{ + int j; + int32x4_t a = vld1q_s32(sum); + /* Load y[0...3] */ + /* This requires len>0 to always be valid (which we assert in the C code). */ + int16x4_t y0 = vld1_s16(y); + y += 4; + + for (j = 0; j + 8 <= len; j += 8) + { + /* Load x[0...7] */ + int16x8_t xx = vld1q_s16(x); + int16x4_t x0 = vget_low_s16(xx); + int16x4_t x4 = vget_high_s16(xx); + /* Load y[4...11] */ + int16x8_t yy = vld1q_s16(y); + int16x4_t y4 = vget_low_s16(yy); + int16x4_t y8 = vget_high_s16(yy); + int32x4_t a0 = vmlal_lane_s16(a, y0, x0, 0); + int32x4_t a1 = vmlal_lane_s16(a0, y4, x4, 0); + + int16x4_t y1 = vext_s16(y0, y4, 1); + int16x4_t y5 = vext_s16(y4, y8, 1); + int32x4_t a2 = vmlal_lane_s16(a1, y1, x0, 1); + int32x4_t a3 = vmlal_lane_s16(a2, y5, x4, 1); + + int16x4_t y2 = vext_s16(y0, y4, 2); + int16x4_t y6 = vext_s16(y4, y8, 2); + int32x4_t a4 = vmlal_lane_s16(a3, y2, x0, 2); + int32x4_t a5 = vmlal_lane_s16(a4, y6, x4, 2); + + int16x4_t y3 = vext_s16(y0, y4, 3); + int16x4_t y7 = vext_s16(y4, y8, 3); + int32x4_t a6 = vmlal_lane_s16(a5, y3, x0, 3); + int32x4_t a7 = vmlal_lane_s16(a6, y7, x4, 3); + + y0 = y8; + a = a7; + x += 8; + y += 8; + } + + for (; j < len; j++) + { + int16x4_t x0 = vld1_dup_s16(x); /* load next x */ + int32x4_t a0 = vmlal_s16(a, y0, x0); + + int16x4_t y4 = vld1_dup_s16(y); /* load next y */ + y0 = vext_s16(y0, y4, 1); + a = a0; + x++; + y++; + } + + vst1q_s32(sum, a); +} + +#else +/* + * Function: xcorr_kernel_neon_float + * --------------------------------- + * Computes 4 correlation values and stores them in sum[4] + */ +static void xcorr_kernel_neon_float(const float32_t *x, const float32_t *y, + float32_t sum[4], int len) { + float32x4_t YY[3]; + float32x4_t YEXT[3]; + float32x4_t XX[2]; + float32x2_t XX_2; + float32x4_t SUMM; + const float32_t *xi = x; + const float32_t *yi = y; + + celt_assert(len>0); + + YY[0] = vld1q_f32(yi); + SUMM = vdupq_n_f32(0); + + /* Consume 8 elements in x vector and 12 elements in y + * vector. However, the 12'th element never really gets + * touched in this loop. So, if len == 8, then we only + * must access y[0] to y[10]. y[11] must not be accessed + * hence make sure len > 8 and not len >= 8 + */ + while (len > 8) { + yi += 4; + YY[1] = vld1q_f32(yi); + yi += 4; + YY[2] = vld1q_f32(yi); + + XX[0] = vld1q_f32(xi); + xi += 4; + XX[1] = vld1q_f32(xi); + xi += 4; + + SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0); + YEXT[0] = vextq_f32(YY[0], YY[1], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1); + YEXT[1] = vextq_f32(YY[0], YY[1], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0); + YEXT[2] = vextq_f32(YY[0], YY[1], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1); + + SUMM = vmlaq_lane_f32(SUMM, YY[1], vget_low_f32(XX[1]), 0); + YEXT[0] = vextq_f32(YY[1], YY[2], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[1]), 1); + YEXT[1] = vextq_f32(YY[1], YY[2], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[1]), 0); + YEXT[2] = vextq_f32(YY[1], YY[2], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[1]), 1); + + YY[0] = YY[2]; + len -= 8; + } + + /* Consume 4 elements in x vector and 8 elements in y + * vector. However, the 8'th element in y never really gets + * touched in this loop. So, if len == 4, then we only + * must access y[0] to y[6]. y[7] must not be accessed + * hence make sure len>4 and not len>=4 + */ + if (len > 4) { + yi += 4; + YY[1] = vld1q_f32(yi); + + XX[0] = vld1q_f32(xi); + xi += 4; + + SUMM = vmlaq_lane_f32(SUMM, YY[0], vget_low_f32(XX[0]), 0); + YEXT[0] = vextq_f32(YY[0], YY[1], 1); + SUMM = vmlaq_lane_f32(SUMM, YEXT[0], vget_low_f32(XX[0]), 1); + YEXT[1] = vextq_f32(YY[0], YY[1], 2); + SUMM = vmlaq_lane_f32(SUMM, YEXT[1], vget_high_f32(XX[0]), 0); + YEXT[2] = vextq_f32(YY[0], YY[1], 3); + SUMM = vmlaq_lane_f32(SUMM, YEXT[2], vget_high_f32(XX[0]), 1); + + YY[0] = YY[1]; + len -= 4; + } + + while (--len > 0) { + XX_2 = vld1_dup_f32(xi++); + SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0); + YY[0]= vld1q_f32(++yi); + } + + XX_2 = vld1_dup_f32(xi); + SUMM = vmlaq_lane_f32(SUMM, YY[0], XX_2, 0); + + vst1q_f32(sum, SUMM); +} + +/* + * Function: xcorr_kernel_neon_float_process1 + * --------------------------------- + * Computes single correlation values and stores in *sum + */ +static void xcorr_kernel_neon_float_process1(const float32_t *x, + const float32_t *y, float32_t *sum, int len) { + float32x4_t XX[4]; + float32x4_t YY[4]; + float32x2_t XX_2; + float32x2_t YY_2; + float32x4_t SUMM; + float32x2_t SUMM_2[2]; + const float32_t *xi = x; + const float32_t *yi = y; + + SUMM = vdupq_n_f32(0); + + /* Work on 16 values per iteration */ + while (len >= 16) { + XX[0] = vld1q_f32(xi); + xi += 4; + XX[1] = vld1q_f32(xi); + xi += 4; + XX[2] = vld1q_f32(xi); + xi += 4; + XX[3] = vld1q_f32(xi); + xi += 4; + + YY[0] = vld1q_f32(yi); + yi += 4; + YY[1] = vld1q_f32(yi); + yi += 4; + YY[2] = vld1q_f32(yi); + yi += 4; + YY[3] = vld1q_f32(yi); + yi += 4; + + SUMM = vmlaq_f32(SUMM, YY[0], XX[0]); + SUMM = vmlaq_f32(SUMM, YY[1], XX[1]); + SUMM = vmlaq_f32(SUMM, YY[2], XX[2]); + SUMM = vmlaq_f32(SUMM, YY[3], XX[3]); + len -= 16; + } + + /* Work on 8 values */ + if (len >= 8) { + XX[0] = vld1q_f32(xi); + xi += 4; + XX[1] = vld1q_f32(xi); + xi += 4; + + YY[0] = vld1q_f32(yi); + yi += 4; + YY[1] = vld1q_f32(yi); + yi += 4; + + SUMM = vmlaq_f32(SUMM, YY[0], XX[0]); + SUMM = vmlaq_f32(SUMM, YY[1], XX[1]); + len -= 8; + } + + /* Work on 4 values */ + if (len >= 4) { + XX[0] = vld1q_f32(xi); + xi += 4; + YY[0] = vld1q_f32(yi); + yi += 4; + SUMM = vmlaq_f32(SUMM, YY[0], XX[0]); + len -= 4; + } + + /* Start accumulating results */ + SUMM_2[0] = vget_low_f32(SUMM); + if (len >= 2) { + /* While at it, consume 2 more values if available */ + XX_2 = vld1_f32(xi); + xi += 2; + YY_2 = vld1_f32(yi); + yi += 2; + SUMM_2[0] = vmla_f32(SUMM_2[0], YY_2, XX_2); + len -= 2; + } + SUMM_2[1] = vget_high_f32(SUMM); + SUMM_2[0] = vadd_f32(SUMM_2[0], SUMM_2[1]); + SUMM_2[0] = vpadd_f32(SUMM_2[0], SUMM_2[0]); + /* Ok, now we have result accumulated in SUMM_2[0].0 */ + + if (len > 0) { + /* Case when you have one value left */ + XX_2 = vld1_dup_f32(xi); + YY_2 = vld1_dup_f32(yi); + SUMM_2[0] = vmla_f32(SUMM_2[0], XX_2, YY_2); + } + + vst1_lane_f32(sum, SUMM_2[0], 0); +} + +void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch) { + int i; + celt_assert(max_pitch > 0); + celt_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0); + + for (i = 0; i < (max_pitch-3); i += 4) { + xcorr_kernel_neon_float((const float32_t *)_x, (const float32_t *)_y+i, + (float32_t *)xcorr+i, len); + } + + /* In case max_pitch isn't multiple of 4 + * compute single correlation value per iteration + */ + for (; i < max_pitch; i++) { + xcorr_kernel_neon_float_process1((const float32_t *)_x, + (const float32_t *)_y+i, (float32_t *)xcorr+i, len); + } +} +#endif diff --git a/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm-gnu.S b/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm-gnu.S new file mode 100644 index 00000000000..5b2ee55a10c --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm-gnu.S @@ -0,0 +1,551 @@ + .syntax unified +@ Copyright (c) 2007-2008 CSIRO +@ Copyright (c) 2007-2009 Xiph.Org Foundation +@ Copyright (c) 2013 Parrot +@ Written by Aurélien Zanelli +@ +@ Redistribution and use in source and binary forms, with or without +@ modification, are permitted provided that the following conditions +@ are met: +@ +@ - Redistributions of source code must retain the above copyright +@ notice, this list of conditions and the following disclaimer. +@ +@ - Redistributions in binary form must reproduce the above copyright +@ notice, this list of conditions and the following disclaimer in the +@ documentation and/or other materials provided with the distribution. +@ +@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +@ ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +@ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +@ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +@ OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +@ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +@ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +@ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + .text; .p2align 2; .arch armv7-a + .fpu neon + .object_arch armv4t + + .include "celt/arm/armopts-gnu.S" + + .if OPUS_ARM_MAY_HAVE_EDSP + .global celt_pitch_xcorr_edsp + .endif + + .if OPUS_ARM_MAY_HAVE_NEON + .global celt_pitch_xcorr_neon + .endif + + .if OPUS_ARM_MAY_HAVE_NEON + +@ Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3 +; xcorr_kernel_neon: @ PROC +xcorr_kernel_neon_start: + @ input: + @ r3 = int len + @ r4 = opus_val16 *x + @ r5 = opus_val16 *y + @ q0 = opus_val32 sum[4] + @ output: + @ q0 = opus_val32 sum[4] + @ preserved: r0-r3, r6-r11, d2, q4-q7, q9-q15 + @ internal usage: + @ r12 = int j + @ d3 = y_3|y_2|y_1|y_0 + @ q2 = y_B|y_A|y_9|y_8|y_7|y_6|y_5|y_4 + @ q3 = x_7|x_6|x_5|x_4|x_3|x_2|x_1|x_0 + @ q8 = scratch + @ + @ Load y[0...3] + @ This requires len>0 to always be valid (which we assert in the C code). + VLD1.16 {d5}, [r5]! + SUBS r12, r3, #8 + BLE xcorr_kernel_neon_process4 +@ Process 8 samples at a time. +@ This loop loads one y value more than we actually need. Therefore we have to +@ stop as soon as there are 8 or fewer samples left (instead of 7), to avoid +@ reading past the end of the array. +xcorr_kernel_neon_process8: + @ This loop has 19 total instructions (10 cycles to issue, minimum), with + @ - 2 cycles of ARM insrtuctions, + @ - 10 cycles of load/store/byte permute instructions, and + @ - 9 cycles of data processing instructions. + @ On a Cortex A8, we dual-issue the maximum amount (9 cycles) between the + @ latter two categories, meaning the whole loop should run in 10 cycles per + @ iteration, barring cache misses. + @ + @ Load x[0...7] + VLD1.16 {d6, d7}, [r4]! + @ Unlike VMOV, VAND is a data processsing instruction (and doesn't get + @ assembled to VMOV, like VORR would), so it dual-issues with the prior VLD1. + VAND d3, d5, d5 + SUBS r12, r12, #8 + @ Load y[4...11] + VLD1.16 {d4, d5}, [r5]! + VMLAL.S16 q0, d3, d6[0] + VEXT.16 d16, d3, d4, #1 + VMLAL.S16 q0, d4, d7[0] + VEXT.16 d17, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d3, d4, #2 + VMLAL.S16 q0, d17, d7[1] + VEXT.16 d17, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d3, d4, #3 + VMLAL.S16 q0, d17, d7[2] + VEXT.16 d17, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] + VMLAL.S16 q0, d17, d7[3] + BGT xcorr_kernel_neon_process8 +@ Process 4 samples here if we have > 4 left (still reading one extra y value). +xcorr_kernel_neon_process4: + ADDS r12, r12, #4 + BLE xcorr_kernel_neon_process2 + @ Load x[0...3] + VLD1.16 d6, [r4]! + @ Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #4 + @ Load y[4...7] + VLD1.16 d5, [r5]! + VMLAL.S16 q0, d4, d6[0] + VEXT.16 d16, d4, d5, #1 + VMLAL.S16 q0, d16, d6[1] + VEXT.16 d16, d4, d5, #2 + VMLAL.S16 q0, d16, d6[2] + VEXT.16 d16, d4, d5, #3 + VMLAL.S16 q0, d16, d6[3] +@ Process 2 samples here if we have > 2 left (still reading one extra y value). +xcorr_kernel_neon_process2: + ADDS r12, r12, #2 + BLE xcorr_kernel_neon_process1 + @ Load x[0...1] + VLD2.16 {d6[],d7[]}, [r4]! + @ Use VAND since it's a data processing instruction again. + VAND d4, d5, d5 + SUB r12, r12, #2 + @ Load y[4...5] + VLD1.32 {d5[]}, [r5]! + VMLAL.S16 q0, d4, d6 + VEXT.16 d16, d4, d5, #1 + @ Replace bottom copy of {y5,y4} in d5 with {y3,y2} from d4, using VSRI + @ instead of VEXT, since it's a data-processing instruction. + VSRI.64 d5, d4, #32 + VMLAL.S16 q0, d16, d7 +@ Process 1 sample using the extra y value we loaded above. +xcorr_kernel_neon_process1: + @ Load next *x + VLD1.16 {d6[]}, [r4]! + ADDS r12, r12, #1 + @ y[0...3] are left in d5 from prior iteration(s) (if any) + VMLAL.S16 q0, d5, d6 + MOVLE pc, lr +@ Now process 1 last sample, not reading ahead. + @ Load last *y + VLD1.16 {d4[]}, [r5]! + VSRI.64 d4, d5, #16 + @ Load last *x + VLD1.16 {d6[]}, [r4]! + VMLAL.S16 q0, d4, d6 + MOV pc, lr + .size xcorr_kernel_neon, .-xcorr_kernel_neon @ ENDP + +@ opus_val32 celt_pitch_xcorr_neon(opus_val16 *_x, opus_val16 *_y, +@ opus_val32 *xcorr, int len, int max_pitch) +; celt_pitch_xcorr_neon: @ PROC + @ input: + @ r0 = opus_val16 *_x + @ r1 = opus_val16 *_y + @ r2 = opus_val32 *xcorr + @ r3 = int len + @ output: + @ r0 = int maxcorr + @ internal usage: + @ r4 = opus_val16 *x (for xcorr_kernel_neon()) + @ r5 = opus_val16 *y (for xcorr_kernel_neon()) + @ r6 = int max_pitch + @ r12 = int j + @ q15 = int maxcorr[4] (q15 is not used by xcorr_kernel_neon()) + STMFD sp!, {r4-r6, lr} + LDR r6, [sp, #16] + VMOV.S32 q15, #1 + @ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + SUBS r6, r6, #4 + BLT celt_pitch_xcorr_neon_process4_done +celt_pitch_xcorr_neon_process4: + @ xcorr_kernel_neon parameters: + @ r3 = len, r4 = _x, r5 = _y, q0 = {0, 0, 0, 0} + MOV r4, r0 + MOV r5, r1 + VEOR q0, q0, q0 + @ xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3. + @ So we don't save/restore any other registers. + BL xcorr_kernel_neon_start + SUBS r6, r6, #4 + VST1.32 {q0}, [r2]! + @ _y += 4 + ADD r1, r1, #8 + VMAX.S32 q15, q15, q0 + @ if (max_pitch < 4) goto celt_pitch_xcorr_neon_process4_done + BGE celt_pitch_xcorr_neon_process4 +@ We have less than 4 sums left to compute. +celt_pitch_xcorr_neon_process4_done: + ADDS r6, r6, #4 + @ Reduce maxcorr to a single value + VMAX.S32 d30, d30, d31 + VPMAX.S32 d30, d30, d30 + @ if (max_pitch <= 0) goto celt_pitch_xcorr_neon_done + BLE celt_pitch_xcorr_neon_done +@ Now compute each remaining sum one at a time. +celt_pitch_xcorr_neon_process_remaining: + MOV r4, r0 + MOV r5, r1 + VMOV.I32 q0, #0 + SUBS r12, r3, #8 + BLT celt_pitch_xcorr_neon_process_remaining4 +@ Sum terms 8 at a time. +celt_pitch_xcorr_neon_process_remaining_loop8: + @ Load x[0...7] + VLD1.16 {q1}, [r4]! + @ Load y[0...7] + VLD1.16 {q2}, [r5]! + SUBS r12, r12, #8 + VMLAL.S16 q0, d4, d2 + VMLAL.S16 q0, d5, d3 + BGE celt_pitch_xcorr_neon_process_remaining_loop8 +@ Sum terms 4 at a time. +celt_pitch_xcorr_neon_process_remaining4: + ADDS r12, r12, #4 + BLT celt_pitch_xcorr_neon_process_remaining4_done + @ Load x[0...3] + VLD1.16 {d2}, [r4]! + @ Load y[0...3] + VLD1.16 {d3}, [r5]! + SUB r12, r12, #4 + VMLAL.S16 q0, d3, d2 +celt_pitch_xcorr_neon_process_remaining4_done: + @ Reduce the sum to a single value. + VADD.S32 d0, d0, d1 + VPADDL.S32 d0, d0 + ADDS r12, r12, #4 + BLE celt_pitch_xcorr_neon_process_remaining_loop_done +@ Sum terms 1 at a time. +celt_pitch_xcorr_neon_process_remaining_loop1: + VLD1.16 {d2[]}, [r4]! + VLD1.16 {d3[]}, [r5]! + SUBS r12, r12, #1 + VMLAL.S16 q0, d2, d3 + BGT celt_pitch_xcorr_neon_process_remaining_loop1 +celt_pitch_xcorr_neon_process_remaining_loop_done: + VST1.32 {d0[0]}, [r2]! + VMAX.S32 d30, d30, d0 + SUBS r6, r6, #1 + @ _y++ + ADD r1, r1, #2 + @ if (--max_pitch > 0) goto celt_pitch_xcorr_neon_process_remaining + BGT celt_pitch_xcorr_neon_process_remaining +celt_pitch_xcorr_neon_done: + VMOV.32 r0, d30[0] + LDMFD sp!, {r4-r6, pc} + .size celt_pitch_xcorr_neon, .-celt_pitch_xcorr_neon @ ENDP + + .endif + + .if OPUS_ARM_MAY_HAVE_EDSP + +@ This will get used on ARMv7 devices without NEON, so it has been optimized +@ to take advantage of dual-issuing where possible. +; xcorr_kernel_edsp: @ PROC +xcorr_kernel_edsp_start: + @ input: + @ r3 = int len + @ r4 = opus_val16 *_x (must be 32-bit aligned) + @ r5 = opus_val16 *_y (must be 32-bit aligned) + @ r6...r9 = opus_val32 sum[4] + @ output: + @ r6...r9 = opus_val32 sum[4] + @ preserved: r0-r5 + @ internal usage + @ r2 = int j + @ r12,r14 = opus_val16 x[4] + @ r10,r11 = opus_val16 y[4] + STMFD sp!, {r2,r4,r5,lr} + LDR r10, [r5], #4 @ Load y[0...1] + SUBS r2, r3, #4 @ j = len-4 + LDR r11, [r5], #4 @ Load y[2...3] + BLE xcorr_kernel_edsp_process4_done + LDR r12, [r4], #4 @ Load x[0...1] + @ Stall +xcorr_kernel_edsp_process4: + @ The multiplies must issue from pipeline 0, and can't dual-issue with each + @ other. Every other instruction here dual-issues with a multiply, and is + @ thus "free". There should be no stalls in the body of the loop. + SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_0,y_0) + LDR r14, [r4], #4 @ Load x[2...3] + SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x_0,y_1) + SUBS r2, r2, #4 @ j-=4 + SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_0,y_2) + SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x_0,y_3) + SMLATT r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x_1,y_1) + LDR r10, [r5], #4 @ Load y[4...5] + SMLATB r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],x_1,y_2) + SMLATT r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x_1,y_3) + SMLATB r9, r12, r10, r9 @ sum[3] = MAC16_16(sum[3],x_1,y_4) + LDRGT r12, [r4], #4 @ Load x[0...1] + SMLABB r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_2,y_2) + SMLABT r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x_2,y_3) + SMLABB r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_2,y_4) + SMLABT r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x_2,y_5) + SMLATT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],x_3,y_3) + LDR r11, [r5], #4 @ Load y[6...7] + SMLATB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],x_3,y_4) + SMLATT r8, r14, r10, r8 @ sum[2] = MAC16_16(sum[2],x_3,y_5) + SMLATB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],x_3,y_6) + BGT xcorr_kernel_edsp_process4 +xcorr_kernel_edsp_process4_done: + ADDS r2, r2, #4 + BLE xcorr_kernel_edsp_done + LDRH r12, [r4], #2 @ r12 = *x++ + SUBS r2, r2, #1 @ j-- + @ Stall + SMLABB r6, r12, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_0) + LDRHGT r14, [r4], #2 @ r14 = *x++ + SMLABT r7, r12, r10, r7 @ sum[1] = MAC16_16(sum[1],x,y_1) + SMLABB r8, r12, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_2) + SMLABT r9, r12, r11, r9 @ sum[3] = MAC16_16(sum[3],x,y_3) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r10, r6 @ sum[0] = MAC16_16(sum[0],x,y_1) + SUBS r2, r2, #1 @ j-- + SMLABB r7, r14, r11, r7 @ sum[1] = MAC16_16(sum[1],x,y_2) + LDRH r10, [r5], #2 @ r10 = y_4 = *y++ + SMLABT r8, r14, r11, r8 @ sum[2] = MAC16_16(sum[2],x,y_3) + LDRHGT r12, [r4], #2 @ r12 = *x++ + SMLABB r9, r14, r10, r9 @ sum[3] = MAC16_16(sum[3],x,y_4) + BLE xcorr_kernel_edsp_done + SMLABB r6, r12, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_2) + CMP r2, #1 @ j-- + SMLABT r7, r12, r11, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_3) + LDRH r2, [r5], #2 @ r2 = y_5 = *y++ + SMLABB r8, r12, r10, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_4) + LDRHGT r14, [r4] @ r14 = *x + SMLABB r9, r12, r2, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_5) + BLE xcorr_kernel_edsp_done + SMLABT r6, r14, r11, r6 @ sum[0] = MAC16_16(sum[0],tmp,y_3) + LDRH r11, [r5] @ r11 = y_6 = *y + SMLABB r7, r14, r10, r7 @ sum[1] = MAC16_16(sum[1],tmp,y_4) + SMLABB r8, r14, r2, r8 @ sum[2] = MAC16_16(sum[2],tmp,y_5) + SMLABB r9, r14, r11, r9 @ sum[3] = MAC16_16(sum[3],tmp,y_6) +xcorr_kernel_edsp_done: + LDMFD sp!, {r2,r4,r5,pc} + .size xcorr_kernel_edsp, .-xcorr_kernel_edsp @ ENDP + +; celt_pitch_xcorr_edsp: @ PROC + @ input: + @ r0 = opus_val16 *_x (must be 32-bit aligned) + @ r1 = opus_val16 *_y (only needs to be 16-bit aligned) + @ r2 = opus_val32 *xcorr + @ r3 = int len + @ output: + @ r0 = maxcorr + @ internal usage + @ r4 = opus_val16 *x + @ r5 = opus_val16 *y + @ r6 = opus_val32 sum0 + @ r7 = opus_val32 sum1 + @ r8 = opus_val32 sum2 + @ r9 = opus_val32 sum3 + @ r1 = int max_pitch + @ r12 = int j + STMFD sp!, {r4-r11, lr} + MOV r5, r1 + LDR r1, [sp, #36] + MOV r4, r0 + TST r5, #3 + @ maxcorr = 1 + MOV r0, #1 + BEQ celt_pitch_xcorr_edsp_process1u_done +@ Compute one sum at the start to make y 32-bit aligned. + SUBS r12, r3, #4 + @ r14 = sum = 0 + MOV r14, #0 + LDRH r8, [r5], #2 + BLE celt_pitch_xcorr_edsp_process1u_loop4_done + LDR r6, [r4], #4 + MOV r8, r8, LSL #16 +celt_pitch_xcorr_edsp_process1u_loop4: + LDR r9, [r5], #4 + SMLABT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + LDR r7, [r4], #4 + SMLATB r14, r6, r9, r14 @ sum = MAC16_16(sum, x_1, y_1) + LDR r8, [r5], #4 + SMLABT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2) + SUBS r12, r12, #4 @ j-=4 + SMLATB r14, r7, r8, r14 @ sum = MAC16_16(sum, x_3, y_3) + LDRGT r6, [r4], #4 + BGT celt_pitch_xcorr_edsp_process1u_loop4 + MOV r8, r8, LSR #16 +celt_pitch_xcorr_edsp_process1u_loop4_done: + ADDS r12, r12, #4 +celt_pitch_xcorr_edsp_process1u_loop1: + LDRHGE r6, [r4], #2 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y) + SUBSGE r12, r12, #1 + LDRHGT r8, [r5], #2 + BGT celt_pitch_xcorr_edsp_process1u_loop1 + @ Restore _x + SUB r4, r4, r3, LSL #1 + @ Restore and advance _y + SUB r5, r5, r3, LSL #1 + @ maxcorr = max(maxcorr, sum) + CMP r0, r14 + ADD r5, r5, #2 + MOVLT r0, r14 + SUBS r1, r1, #1 + @ xcorr[i] = sum + STR r14, [r2], #4 + BLE celt_pitch_xcorr_edsp_done +celt_pitch_xcorr_edsp_process1u_done: + @ if (max_pitch < 4) goto celt_pitch_xcorr_edsp_process2 + SUBS r1, r1, #4 + BLT celt_pitch_xcorr_edsp_process2 +celt_pitch_xcorr_edsp_process4: + @ xcorr_kernel_edsp parameters: + @ r3 = len, r4 = _x, r5 = _y, r6...r9 = sum[4] = {0, 0, 0, 0} + MOV r6, #0 + MOV r7, #0 + MOV r8, #0 + MOV r9, #0 + BL xcorr_kernel_edsp_start @ xcorr_kernel_edsp(_x, _y+i, xcorr+i, len) + @ maxcorr = max(maxcorr, sum0, sum1, sum2, sum3) + CMP r0, r6 + @ _y+=4 + ADD r5, r5, #8 + MOVLT r0, r6 + CMP r0, r7 + MOVLT r0, r7 + CMP r0, r8 + MOVLT r0, r8 + CMP r0, r9 + MOVLT r0, r9 + STMIA r2!, {r6-r9} + SUBS r1, r1, #4 + BGE celt_pitch_xcorr_edsp_process4 +celt_pitch_xcorr_edsp_process2: + ADDS r1, r1, #2 + BLT celt_pitch_xcorr_edsp_process1a + SUBS r12, r3, #4 + @ {r10, r11} = {sum0, sum1} = {0, 0} + MOV r10, #0 + MOV r11, #0 + LDR r8, [r5], #4 + BLE celt_pitch_xcorr_edsp_process2_loop_done + LDR r6, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process2_loop4: + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDR r7, [r4], #4 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + SUBS r12, r12, #4 @ j-=4 + SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1) + LDR r8, [r5], #4 + SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2) + LDRGT r6, [r4], #4 + SMLABB r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_2, y_2) + SMLABT r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_2, y_3) + SMLATT r10, r7, r9, r10 @ sum0 = MAC16_16(sum0, x_3, y_3) + LDRGT r9, [r5], #4 + SMLATB r11, r7, r8, r11 @ sum1 = MAC16_16(sum1, x_3, y_4) + BGT celt_pitch_xcorr_edsp_process2_loop4 +celt_pitch_xcorr_edsp_process2_loop_done: + ADDS r12, r12, #2 + BLE celt_pitch_xcorr_edsp_process2_1 + LDR r6, [r4], #4 + @ Stall + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDR r9, [r5], #4 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + SUB r12, r12, #2 + SMLATT r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_1, y_1) + MOV r8, r9 + SMLATB r11, r6, r9, r11 @ sum1 = MAC16_16(sum1, x_1, y_2) +celt_pitch_xcorr_edsp_process2_1: + LDRH r6, [r4], #2 + ADDS r12, r12, #1 + @ Stall + SMLABB r10, r6, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_0) + LDRHGT r7, [r4], #2 + SMLABT r11, r6, r8, r11 @ sum1 = MAC16_16(sum1, x_0, y_1) + BLE celt_pitch_xcorr_edsp_process2_done + LDRH r9, [r5], #2 + SMLABT r10, r7, r8, r10 @ sum0 = MAC16_16(sum0, x_0, y_1) + SMLABB r11, r7, r9, r11 @ sum1 = MAC16_16(sum1, x_0, y_2) +celt_pitch_xcorr_edsp_process2_done: + @ Restore _x + SUB r4, r4, r3, LSL #1 + @ Restore and advance _y + SUB r5, r5, r3, LSL #1 + @ maxcorr = max(maxcorr, sum0) + CMP r0, r10 + ADD r5, r5, #2 + MOVLT r0, r10 + SUB r1, r1, #2 + @ maxcorr = max(maxcorr, sum1) + CMP r0, r11 + @ xcorr[i] = sum + STR r10, [r2], #4 + MOVLT r0, r11 + STR r11, [r2], #4 +celt_pitch_xcorr_edsp_process1a: + ADDS r1, r1, #1 + BLT celt_pitch_xcorr_edsp_done + SUBS r12, r3, #4 + @ r14 = sum = 0 + MOV r14, #0 + BLT celt_pitch_xcorr_edsp_process1a_loop_done + LDR r6, [r4], #4 + LDR r8, [r5], #4 + LDR r7, [r4], #4 + LDR r9, [r5], #4 +celt_pitch_xcorr_edsp_process1a_loop4: + SMLABB r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + SUBS r12, r12, #4 @ j-=4 + SMLATT r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1) + LDRGE r6, [r4], #4 + SMLABB r14, r7, r9, r14 @ sum = MAC16_16(sum, x_2, y_2) + LDRGE r8, [r5], #4 + SMLATT r14, r7, r9, r14 @ sum = MAC16_16(sum, x_3, y_3) + LDRGE r7, [r4], #4 + LDRGE r9, [r5], #4 + BGE celt_pitch_xcorr_edsp_process1a_loop4 +celt_pitch_xcorr_edsp_process1a_loop_done: + ADDS r12, r12, #2 + LDRGE r6, [r4], #4 + LDRGE r8, [r5], #4 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_0, y_0) + SUBGE r12, r12, #2 + SMLATTGE r14, r6, r8, r14 @ sum = MAC16_16(sum, x_1, y_1) + ADDS r12, r12, #1 + LDRHGE r6, [r4], #2 + LDRHGE r8, [r5], #2 + @ Stall + SMLABBGE r14, r6, r8, r14 @ sum = MAC16_16(sum, *x, *y) + @ maxcorr = max(maxcorr, sum) + CMP r0, r14 + @ xcorr[i] = sum + STR r14, [r2], #4 + MOVLT r0, r14 +celt_pitch_xcorr_edsp_done: + LDMFD sp!, {r4-r11, pc} + .size celt_pitch_xcorr_edsp, .-celt_pitch_xcorr_edsp @ ENDP + + .endif + +@ END: + .section .note.GNU-stack,"",%progbits diff --git a/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm.s b/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm.s index 09917b16bf2..f96e0a88bbe 100644 --- a/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm.s +++ b/TMessagesProj/jni/opus/celt/arm/celt_pitch_xcorr_arm.s @@ -42,6 +42,7 @@ IF OPUS_ARM_MAY_HAVE_NEON ; Compute sum[k]=sum(x[j]*y[j+k],j=0...len-1), k=0...3 xcorr_kernel_neon PROC +xcorr_kernel_neon_start ; input: ; r3 = int len ; r4 = opus_val16 *x @@ -181,7 +182,7 @@ celt_pitch_xcorr_neon_process4 VEOR q0, q0, q0 ; xcorr_kernel_neon only modifies r4, r5, r12, and q0...q3. ; So we don't save/restore any other registers. - BL xcorr_kernel_neon + BL xcorr_kernel_neon_start SUBS r6, r6, #4 VST1.32 {q0}, [r2]! ; _y += 4 @@ -257,6 +258,7 @@ IF OPUS_ARM_MAY_HAVE_EDSP ; This will get used on ARMv7 devices without NEON, so it has been optimized ; to take advantage of dual-issuing where possible. xcorr_kernel_edsp PROC +xcorr_kernel_edsp_start ; input: ; r3 = int len ; r4 = opus_val16 *_x (must be 32-bit aligned) @@ -309,7 +311,7 @@ xcorr_kernel_edsp_process4_done SUBS r2, r2, #1 ; j-- ; Stall SMLABB r6, r12, r10, r6 ; sum[0] = MAC16_16(sum[0],x,y_0) - LDRGTH r14, [r4], #2 ; r14 = *x++ + LDRHGT r14, [r4], #2 ; r14 = *x++ SMLABT r7, r12, r10, r7 ; sum[1] = MAC16_16(sum[1],x,y_1) SMLABB r8, r12, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_2) SMLABT r9, r12, r11, r9 ; sum[3] = MAC16_16(sum[3],x,y_3) @@ -319,7 +321,7 @@ xcorr_kernel_edsp_process4_done SMLABB r7, r14, r11, r7 ; sum[1] = MAC16_16(sum[1],x,y_2) LDRH r10, [r5], #2 ; r10 = y_4 = *y++ SMLABT r8, r14, r11, r8 ; sum[2] = MAC16_16(sum[2],x,y_3) - LDRGTH r12, [r4], #2 ; r12 = *x++ + LDRHGT r12, [r4], #2 ; r12 = *x++ SMLABB r9, r14, r10, r9 ; sum[3] = MAC16_16(sum[3],x,y_4) BLE xcorr_kernel_edsp_done SMLABB r6, r12, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_2) @@ -327,7 +329,7 @@ xcorr_kernel_edsp_process4_done SMLABT r7, r12, r11, r7 ; sum[1] = MAC16_16(sum[1],tmp,y_3) LDRH r2, [r5], #2 ; r2 = y_5 = *y++ SMLABB r8, r12, r10, r8 ; sum[2] = MAC16_16(sum[2],tmp,y_4) - LDRGTH r14, [r4] ; r14 = *x + LDRHGT r14, [r4] ; r14 = *x SMLABB r9, r12, r2, r9 ; sum[3] = MAC16_16(sum[3],tmp,y_5) BLE xcorr_kernel_edsp_done SMLABT r6, r14, r11, r6 ; sum[0] = MAC16_16(sum[0],tmp,y_3) @@ -387,11 +389,11 @@ celt_pitch_xcorr_edsp_process1u_loop4 celt_pitch_xcorr_edsp_process1u_loop4_done ADDS r12, r12, #4 celt_pitch_xcorr_edsp_process1u_loop1 - LDRGEH r6, [r4], #2 + LDRHGE r6, [r4], #2 ; Stall SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y) - SUBGES r12, r12, #1 - LDRGTH r8, [r5], #2 + SUBSGE r12, r12, #1 + LDRHGT r8, [r5], #2 BGT celt_pitch_xcorr_edsp_process1u_loop1 ; Restore _x SUB r4, r4, r3, LSL #1 @@ -416,7 +418,7 @@ celt_pitch_xcorr_edsp_process4 MOV r7, #0 MOV r8, #0 MOV r9, #0 - BL xcorr_kernel_edsp ; xcorr_kernel_edsp(_x, _y+i, xcorr+i, len) + BL xcorr_kernel_edsp_start ; xcorr_kernel_edsp(_x, _y+i, xcorr+i, len) ; maxcorr = max(maxcorr, sum0, sum1, sum2, sum3) CMP r0, r6 ; _y+=4 @@ -474,7 +476,7 @@ celt_pitch_xcorr_edsp_process2_1 ADDS r12, r12, #1 ; Stall SMLABB r10, r6, r8, r10 ; sum0 = MAC16_16(sum0, x_0, y_0) - LDRGTH r7, [r4], #2 + LDRHGT r7, [r4], #2 SMLABT r11, r6, r8, r11 ; sum1 = MAC16_16(sum1, x_0, y_1) BLE celt_pitch_xcorr_edsp_process2_done LDRH r9, [r5], #2 @@ -527,8 +529,8 @@ celt_pitch_xcorr_edsp_process1a_loop_done SUBGE r12, r12, #2 SMLATTGE r14, r6, r8, r14 ; sum = MAC16_16(sum, x_1, y_1) ADDS r12, r12, #1 - LDRGEH r6, [r4], #2 - LDRGEH r8, [r5], #2 + LDRHGE r6, [r4], #2 + LDRHGE r8, [r5], #2 ; Stall SMLABBGE r14, r6, r8, r14 ; sum = MAC16_16(sum, *x, *y) ; maxcorr = max(maxcorr, sum) diff --git a/TMessagesProj/jni/opus/celt/arm/fft_arm.h b/TMessagesProj/jni/opus/celt/arm/fft_arm.h new file mode 100644 index 00000000000..0cb55d8e22d --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/fft_arm.h @@ -0,0 +1,72 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file fft_arm.h + @brief ARM Neon Intrinsic optimizations for fft using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#if !defined(FFT_ARM_H) +#define FFT_ARM_H + +#include "config.h" +#include "kiss_fft.h" + +#if defined(HAVE_ARM_NE10) + +int opus_fft_alloc_arm_neon(kiss_fft_state *st); +void opus_fft_free_arm_neon(kiss_fft_state *st); + +void opus_fft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout); + +void opus_ifft_neon(const kiss_fft_state *st, + const kiss_fft_cpx *fin, + kiss_fft_cpx *fout); + +#if !defined(OPUS_HAVE_RTCD) +#define OVERRIDE_OPUS_FFT (1) + +#define opus_fft_alloc_arch(_st, arch) \ + ((void)(arch), opus_fft_alloc_arm_neon(_st)) + +#define opus_fft_free_arch(_st, arch) \ + ((void)(arch), opus_fft_free_arm_neon(_st)) + +#define opus_fft(_st, _fin, _fout, arch) \ + ((void)(arch), opus_fft_neon(_st, _fin, _fout)) + +#define opus_ifft(_st, _fin, _fout, arch) \ + ((void)(arch), opus_ifft_neon(_st, _fin, _fout)) + +#endif /* OPUS_HAVE_RTCD */ + +#endif /* HAVE_ARM_NE10 */ + +#endif diff --git a/TMessagesProj/jni/opus/celt/arm/fixed_arm64.h b/TMessagesProj/jni/opus/celt/arm/fixed_arm64.h new file mode 100644 index 00000000000..c6fbd3db2c1 --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/fixed_arm64.h @@ -0,0 +1,35 @@ +/* Copyright (C) 2015 Vidyo */ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_ARM64_H +#define FIXED_ARM64_H + +#include + +#undef SIG2WORD16 +#define SIG2WORD16(x) (vqmovns_s32(PSHR32((x), SIG_SHIFT))) + +#endif diff --git a/TMessagesProj/jni/opus/celt/arm/fixed_armv4.h b/TMessagesProj/jni/opus/celt/arm/fixed_armv4.h index b690bc8ceae..efb3b1896a8 100644 --- a/TMessagesProj/jni/opus/celt/arm/fixed_armv4.h +++ b/TMessagesProj/jni/opus/celt/arm/fixed_armv4.h @@ -68,6 +68,10 @@ static OPUS_INLINE opus_val32 MULT16_32_Q15_armv4(opus_val16 a, opus_val32 b) #undef MAC16_32_Q15 #define MAC16_32_Q15(c, a, b) ADD32(c, MULT16_32_Q15(a, b)) +/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add. + Result fits in 32 bits. */ +#undef MAC16_32_Q16 +#define MAC16_32_Q16(c, a, b) ADD32(c, MULT16_32_Q16(a, b)) /** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ #undef MULT32_32_Q31 diff --git a/TMessagesProj/jni/opus/celt/arm/fixed_armv5e.h b/TMessagesProj/jni/opus/celt/arm/fixed_armv5e.h index 1194a7d3ecb..36a63211013 100644 --- a/TMessagesProj/jni/opus/celt/arm/fixed_armv5e.h +++ b/TMessagesProj/jni/opus/celt/arm/fixed_armv5e.h @@ -82,6 +82,23 @@ static OPUS_INLINE opus_val32 MAC16_32_Q15_armv5e(opus_val32 c, opus_val16 a, } #define MAC16_32_Q15(c, a, b) (MAC16_32_Q15_armv5e(c, a, b)) +/** 16x32 multiply, followed by a 16-bit shift right and 32-bit add. + Result fits in 32 bits. */ +#undef MAC16_32_Q16 +static OPUS_INLINE opus_val32 MAC16_32_Q16_armv5e(opus_val32 c, opus_val16 a, + opus_val32 b) +{ + int res; + __asm__( + "#MAC16_32_Q16\n\t" + "smlawb %0, %1, %2, %3;\n" + : "=r"(res) + : "r"(b), "r"(a), "r"(c) + ); + return res; +} +#define MAC16_32_Q16(c, a, b) (MAC16_32_Q16_armv5e(c, a, b)) + /** 16x16 multiply-add where the result fits in 32 bits */ #undef MAC16_16 static OPUS_INLINE opus_val32 MAC16_16_armv5e(opus_val32 c, opus_val16 a, @@ -113,4 +130,22 @@ static OPUS_INLINE opus_val32 MULT16_16_armv5e(opus_val16 a, opus_val16 b) } #define MULT16_16(a, b) (MULT16_16_armv5e(a, b)) +#ifdef OPUS_ARM_INLINE_MEDIA + +#undef SIG2WORD16 +static OPUS_INLINE opus_val16 SIG2WORD16_armv6(opus_val32 x) +{ + celt_sig res; + __asm__( + "#SIG2WORD16\n\t" + "ssat %0, #16, %1, ASR #12\n\t" + : "=r"(res) + : "r"(x+2048) + ); + return EXTRACT16(res); +} +#define SIG2WORD16(x) (SIG2WORD16_armv6(x)) + +#endif /* OPUS_ARM_INLINE_MEDIA */ + #endif diff --git a/TMessagesProj/jni/opus/celt/arm/mdct_arm.h b/TMessagesProj/jni/opus/celt/arm/mdct_arm.h new file mode 100644 index 00000000000..49cbb445760 --- /dev/null +++ b/TMessagesProj/jni/opus/celt/arm/mdct_arm.h @@ -0,0 +1,60 @@ +/* Copyright (c) 2015 Xiph.Org Foundation + Written by Viswanath Puttagunta */ +/** + @file arm_mdct.h + @brief ARM Neon Intrinsic optimizations for mdct using NE10 library + */ + +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(MDCT_ARM_H) +#define MDCT_ARM_H + +#include "config.h" +#include "mdct.h" + +#if defined(HAVE_ARM_NE10) +/** Compute a forward MDCT and scale by 4/N, trashes the input array */ +void clt_mdct_forward_neon(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +void clt_mdct_backward_neon(const mdct_lookup *l, kiss_fft_scalar *in, + kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, + int shift, int stride, int arch); + +#if !defined(OPUS_HAVE_RTCD) +#define OVERRIDE_OPUS_MDCT (1) +#define clt_mdct_forward(_l, _in, _out, _window, _int, _shift, _stride, _arch) \ + clt_mdct_forward_neon(_l, _in, _out, _window, _int, _shift, _stride, _arch) +#define clt_mdct_backward(_l, _in, _out, _window, _int, _shift, _stride, _arch) \ + clt_mdct_backward_neon(_l, _in, _out, _window, _int, _shift, _stride, _arch) +#endif /* OPUS_HAVE_RTCD */ +#endif /* HAVE_ARM_NE10 */ + +#endif diff --git a/TMessagesProj/jni/opus/celt/arm/pitch_arm.h b/TMessagesProj/jni/opus/celt/arm/pitch_arm.h index a07f8ac2fa8..14331169eeb 100644 --- a/TMessagesProj/jni/opus/celt/arm/pitch_arm.h +++ b/TMessagesProj/jni/opus/celt/arm/pitch_arm.h @@ -46,12 +46,81 @@ opus_val32 celt_pitch_xcorr_edsp(const opus_val16 *_x, const opus_val16 *_y, opus_val32 *xcorr, int len, int max_pitch); # endif -# if !defined(OPUS_HAVE_RTCD) +# if defined(OPUS_HAVE_RTCD) && \ + ((defined(OPUS_ARM_MAY_HAVE_NEON) && !defined(OPUS_ARM_PRESUME_NEON)) || \ + (defined(OPUS_ARM_MAY_HAVE_MEDIA) && !defined(OPUS_ARM_PRESUME_MEDIA)) || \ + (defined(OPUS_ARM_MAY_HAVE_EDSP) && !defined(OPUS_ARM_PRESUME_EDSP))) +extern opus_val32 +(*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int); +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ + ((*CELT_PITCH_XCORR_IMPL[(arch)&OPUS_ARCHMASK])(_x, _y, \ + xcorr, len, max_pitch)) + +# elif defined(OPUS_ARM_PRESUME_EDSP) || \ + defined(OPUS_ARM_PRESUME_MEDIA) || \ + defined(OPUS_ARM_PRESUME_NEON) # define OVERRIDE_PITCH_XCORR (1) # define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ ((void)(arch),PRESUME_NEON(celt_pitch_xcorr)(_x, _y, xcorr, len, max_pitch)) + +# endif + +# if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void xcorr_kernel_neon_fixed( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len); +# endif + +# if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) + +extern void (*const XCORR_KERNEL_IMPL[OPUS_ARCHMASK + 1])( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len); + +# define OVERRIDE_XCORR_KERNEL (1) +# define xcorr_kernel(x, y, sum, len, arch) \ + ((*XCORR_KERNEL_IMPL[(arch) & OPUS_ARCHMASK])(x, y, sum, len)) + +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) +# define OVERRIDE_XCORR_KERNEL (1) +# define xcorr_kernel(x, y, sum, len, arch) \ + ((void)arch, xcorr_kernel_neon_fixed(x, y, sum, len)) + +# endif + +#else /* Start !FIXED_POINT */ +/* Float case */ +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +void celt_pitch_xcorr_float_neon(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch); +#endif + +# if defined(OPUS_HAVE_RTCD) && \ + (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && !defined(OPUS_ARM_PRESUME_NEON_INTR)) +extern void +(*const CELT_PITCH_XCORR_IMPL[OPUS_ARCHMASK+1])(const opus_val16 *, + const opus_val16 *, opus_val32 *, int, int); + +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ + ((*CELT_PITCH_XCORR_IMPL[(arch)&OPUS_ARCHMASK])(_x, _y, \ + xcorr, len, max_pitch)) + +# elif defined(OPUS_ARM_PRESUME_NEON_INTR) + +# define OVERRIDE_PITCH_XCORR (1) +# define celt_pitch_xcorr(_x, _y, xcorr, len, max_pitch, arch) \ + ((void)(arch),celt_pitch_xcorr_float_neon(_x, _y, xcorr, len, max_pitch)) + # endif -# endif +#endif /* end !FIXED_POINT */ #endif diff --git a/TMessagesProj/jni/opus/celt/bands.c b/TMessagesProj/jni/opus/celt/bands.c index cce56e2f6eb..05205554fe7 100644 --- a/TMessagesProj/jni/opus/celt/bands.c +++ b/TMessagesProj/jni/opus/celt/bands.c @@ -92,11 +92,11 @@ static int bitexact_log2tan(int isin,int icos) #ifdef FIXED_POINT /* Compute the amplitude (sqrt energy) in each of the bands */ -void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int M) +void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM) { int i, c, N; const opus_int16 *eBands = m->eBands; - N = M*m->shortMdctSize; + N = m->shortMdctSize< 0) { - int shift = celt_ilog2(maxval)-10; - j=M*eBands[i]; do { - sum = MAC16_16(sum, EXTRACT16(VSHR32(X[j+c*N],shift)), - EXTRACT16(VSHR32(X[j+c*N],shift))); - } while (++jlogN[i]>>BITRES)+LM+1)>>1); + j=eBands[i]<0) + { + do { + sum = MAC16_16(sum, EXTRACT16(SHR32(X[j+c*N],shift)), + EXTRACT16(SHR32(X[j+c*N],shift))); + } while (++jnbEBands] = EPSILON+VSHR32(EXTEND32(celt_sqrt(sum)),-shift); } else { @@ -150,18 +155,16 @@ void normalise_bands(const CELTMode *m, const celt_sig * OPUS_RESTRICT freq, cel #else /* FIXED_POINT */ /* Compute the amplitude (sqrt energy) in each of the bands */ -void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int M) +void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM) { int i, c, N; const opus_int16 *eBands = m->eBands; - N = M*m->shortMdctSize; + N = m->shortMdctSize<nbEBands] = celt_sqrt(sum); /*printf ("%f ", bandE[i+c*m->nbEBands]);*/ } @@ -190,74 +193,80 @@ void normalise_bands(const CELTMode *m, const celt_sig * OPUS_RESTRICT freq, cel /* De-normalise the energy to produce the synthesis from the unit-energy bands */ void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, - celt_sig * OPUS_RESTRICT freq, const opus_val16 *bandLogE, int start, int end, int C, int M) + celt_sig * OPUS_RESTRICT freq, const opus_val16 *bandLogE, int start, + int end, int M, int downsample, int silence) { - int i, c, N; + int i, N; + int bound; + celt_sig * OPUS_RESTRICT f; + const celt_norm * OPUS_RESTRICT x; const opus_int16 *eBands = m->eBands; N = M*m->shortMdctSize; - celt_assert2(C<=2, "denormalise_bands() not implemented for >2 channels"); - c=0; do { - celt_sig * OPUS_RESTRICT f; - const celt_norm * OPUS_RESTRICT x; - f = freq+c*N; - x = X+c*N+M*eBands[start]; - for (i=0;inbEBands], SHL16((opus_val16)eMeans[i],6)); + j=M*eBands[i]; + band_end = M*eBands[i+1]; + lg = SATURATE16(ADD32(bandLogE[i], SHL32((opus_val32)eMeans[i],6))); #ifndef FIXED_POINT - g = celt_exp2(lg); + g = celt_exp2(MIN32(32.f, lg)); #else - /* Handle the integer part of the log energy */ - shift = 16-(lg>>DB_SHIFT); - if (shift>31) + /* Handle the integer part of the log energy */ + shift = 16-(lg>>DB_SHIFT); + if (shift>31) + { + shift=0; + g=0; + } else { + /* Handle the fractional part. */ + g = celt_exp2_frac(lg&((1< 16384 we'd be likely to overflow, so we're + capping the gain here, which is equivalent to a cap of 18 on lg. + This shouldn't trigger unless the bitstream is already corrupted. */ + if (shift <= -2) { - shift=0; - g=0; - } else { - /* Handle the fractional part. */ - g = celt_exp2_frac(lg&((1<eBands[i+1]-m->eBands[i]; /* depth in 1/8 bits */ - depth = (1+pulses[i])/((m->eBands[i+1]-m->eBands[i])<=0); + depth = celt_udiv(1+pulses[i], (m->eBands[i+1]-m->eBands[i]))>>LM; #ifdef FIXED_POINT thresh32 = SHR32(celt_exp2(-SHL16(depth, 10-BITRES)),1); @@ -345,12 +355,36 @@ void anti_collapse(const CELTMode *m, celt_norm *X_, unsigned char *collapse_mas } /* We just added some energy, so we need to renormalise */ if (renormalize) - renormalise_vector(X, N0<m->nbEBands-4) - hf_sum += 32*(tcount[1]+tcount[0])/N; + hf_sum += celt_udiv(32*(tcount[1]+tcount[0]), N); tmp = (2*tcount[2] >= N) + (2*tcount[1] >= N) + (2*tcount[0] >= N); sum += tmp*256; nbBands++; @@ -493,7 +526,7 @@ int spreading_decision(const CELTMode *m, celt_norm *X, int *average, if (update_hf) { if (hf_sum) - hf_sum /= C*(4-m->nbEBands+end); + hf_sum = celt_udiv(hf_sum, C*(4-m->nbEBands+end)); *hf_average = (*hf_average+hf_sum)>>1; hf_sum = *hf_average; if (*tapset_decision==2) @@ -509,7 +542,8 @@ int spreading_decision(const CELTMode *m, celt_norm *X, int *average, } /*printf("%d %d %d\n", hf_sum, *hf_average, *tapset_decision);*/ celt_assert(nbBands>0); /* end has to be non-zero */ - sum /= nbBands; + celt_assert(sum>=0); + sum = celt_udiv(sum, nbBands); /* Recursive averaging */ sum = (sum+*average)>>1; *average = sum; @@ -567,8 +601,7 @@ static void deinterleave_hadamard(celt_norm *X, int N0, int stride, int hadamard for (j=0;jarch); } tell = ec_tell_frac(ec); if (qn!=1) { if (encode) - itheta = (itheta*qn+8192)>>14; - + { + if (!stereo || ctx->theta_round == 0) + { + itheta = (itheta*(opus_int32)qn+8192)>>14; + } else { + int down; + /* Bias quantization towards itheta=0 and itheta=16384. */ + int bias = itheta > 8192 ? 32767/qn : -32767/qn; + down = IMIN(qn-1, IMAX(0, (itheta*(opus_int32)qn + bias)>>14)); + if (ctx->theta_round < 0) + itheta = down; + else + itheta = down+1; + } + } /* Entropy coding of the angle. We use a uniform pdf for the time split, a step for stereo, and a triangular one for the rest. */ if (stereo && N>2) @@ -769,7 +819,8 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, ec_dec_update(ec, fl, fl+fs, ft); } } - itheta = (opus_int32)itheta*16384/qn; + celt_assert(itheta>=0); + itheta = celt_udiv((opus_int32)itheta*16384, qn); if (encode && stereo) { if (itheta==0) @@ -782,7 +833,7 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, } else if (stereo) { if (encode) { - inv = itheta > 8192; + inv = itheta > 8192 && !ctx->disable_inv; if (inv) { int j; @@ -799,6 +850,9 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, inv = ec_dec_bit_logp(ec, 2); } else inv = 0; + /* inv flag override to avoid problems with downmixing. */ + if (ctx->disable_inv) + inv = 0; itheta = 0; } qalloc = ec_tell_frac(ec) - tell; @@ -834,11 +888,6 @@ static void compute_theta(struct band_ctx *ctx, struct split_ctx *sctx, static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, int b, celt_norm *lowband_out) { -#ifdef RESYNTH - int resynth = 1; -#else - int resynth = !ctx->encode; -#endif int c; int stereo; celt_norm *x = X; @@ -863,7 +912,7 @@ static unsigned quant_band_n1(struct band_ctx *ctx, celt_norm *X, celt_norm *Y, ctx->remaining_bits -= 1<resynth) x[0] = sign ? -NORM_SCALING : NORM_SCALING; x = Y; } while (++c<1+stereo); @@ -888,11 +937,6 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, int B0=B; opus_val16 mid=0, side=0; unsigned cm=0; -#ifdef RESYNTH - int resynth = 1; -#else - int resynth = !ctx->encode; -#endif celt_norm *Y=NULL; int encode; const CELTMode *m; @@ -924,8 +968,7 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, fill = (fill&1)|(fill<<1); B = (B+1)>>1; - compute_theta(ctx, &sctx, X, Y, N, &b, B, B0, - LM, 0, &fill); + compute_theta(ctx, &sctx, X, Y, N, &b, B, B0, LM, 0, &fill); imid = sctx.imid; iside = sctx.iside; delta = sctx.delta; @@ -959,24 +1002,20 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, rebalance = ctx->remaining_bits; if (mbits >= sbits) { - cm = quant_partition(ctx, X, N, mbits, B, - lowband, LM, + cm = quant_partition(ctx, X, N, mbits, B, lowband, LM, MULT16_16_P15(gain,mid), fill); rebalance = mbits - (rebalance-ctx->remaining_bits); if (rebalance > 3<>B)<<(B0>>1); } else { - cm = quant_partition(ctx, Y, N, sbits, B, - next_lowband2, LM, + cm = quant_partition(ctx, Y, N, sbits, B, next_lowband2, LM, MULT16_16_P15(gain,side), fill>>B)<<(B0>>1); rebalance = sbits - (rebalance-ctx->remaining_bits); if (rebalance > 3<resynth, ctx->arch); } else { cm = alg_unquant(X, N, K, spread, B, ec, gain); } } else { /* If there's no pulse, fill the band anyway */ int j; - if (resynth) + if (ctx->resynth) { unsigned cm_mask; /* B can be as large as 16, so this shift might overflow an int on a @@ -1021,8 +1056,7 @@ static unsigned quant_partition(struct band_ctx *ctx, celt_norm *X, fill &= cm_mask; if (!fill) { - for (j=0;jarch); } } } @@ -1070,11 +1104,6 @@ static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, int recombine=0; int longBlocks; unsigned cm=0; -#ifdef RESYNTH - int resynth = 1; -#else - int resynth = !ctx->encode; -#endif int k; int encode; int tf_change; @@ -1084,7 +1113,7 @@ static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, longBlocks = B0==1; - N_B /= B; + N_B = celt_udiv(N_B, B); /* Special case for one sample */ if (N==1) @@ -1098,9 +1127,7 @@ static unsigned quant_band(struct band_ctx *ctx, celt_norm *X, if (lowband_scratch && lowband && (recombine || ((N_B&1) == 0 && tf_change<0) || B0>1)) { - int j; - for (j=0;j>recombine, B0<resynth) { /* Undo the sample reorganization going from time order to frequency order */ if (B0>1) @@ -1200,11 +1226,6 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm int inv = 0; opus_val16 mid=0, side=0; unsigned cm=0; -#ifdef RESYNTH - int resynth = 1; -#else - int resynth = !ctx->encode; -#endif int mbits, sbits, delta; int itheta; int qalloc; @@ -1224,8 +1245,7 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm orig_fill = fill; - compute_theta(ctx, &sctx, X, Y, N, &b, B, B, - LM, 1, &fill); + compute_theta(ctx, &sctx, X, Y, N, &b, B, B, LM, 1, &fill); inv = sctx.inv; imid = sctx.imid; iside = sctx.iside; @@ -1273,13 +1293,13 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm sign = 1-2*sign; /* We use orig_fill here because we want to fold the side, but if itheta==16384, we'll have cleared the low bits of fill. */ - cm = quant_band(ctx, x2, N, mbits, B, lowband, - LM, lowband_out, Q15ONE, lowband_scratch, orig_fill); + cm = quant_band(ctx, x2, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, orig_fill); /* We don't split N=2 bands, so cm is either 1 or 0 (for a fold-collapse), and there's no need to worry about mixing with the other channel. */ y2[0] = -sign*x2[1]; y2[1] = sign*x2[0]; - if (resynth) + if (ctx->resynth) { celt_norm tmp; X[0] = MULT16_16_Q15(mid, X[0]); @@ -1306,41 +1326,35 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm { /* In stereo mode, we do not apply a scaling to the mid because we need the normalized mid for folding later. */ - cm = quant_band(ctx, X, N, mbits, B, - lowband, LM, lowband_out, - Q15ONE, lowband_scratch, fill); + cm = quant_band(ctx, X, N, mbits, B, lowband, LM, lowband_out, Q15ONE, + lowband_scratch, fill); rebalance = mbits - (rebalance-ctx->remaining_bits); if (rebalance > 3<>B); + cm |= quant_band(ctx, Y, N, sbits, B, NULL, LM, NULL, side, NULL, fill>>B); } else { /* For a stereo split, the high bits of fill are always zero, so no folding will be done to the side. */ - cm = quant_band(ctx, Y, N, sbits, B, - NULL, LM, NULL, - side, NULL, fill>>B); + cm = quant_band(ctx, Y, N, sbits, B, NULL, LM, NULL, side, NULL, fill>>B); rebalance = sbits - (rebalance-ctx->remaining_bits); if (rebalance > 3<resynth) { if (N!=2) - stereo_merge(X, Y, mid, N); + stereo_merge(X, Y, mid, N, ctx->arch); if (inv) { int j; @@ -1351,17 +1365,38 @@ static unsigned quant_band_stereo(struct band_ctx *ctx, celt_norm *X, celt_norm return cm; } +static void special_hybrid_folding(const CELTMode *m, celt_norm *norm, celt_norm *norm2, int start, int M, int dual_stereo) +{ + int n1, n2; + const opus_int16 * OPUS_RESTRICT eBands = m->eBands; + n1 = M*(eBands[start+1]-eBands[start]); + n2 = M*(eBands[start+2]-eBands[start+1]); + /* Duplicate enough of the first band folding data to be able to fold the second band. + Copies no data for CELT-only mode. */ + OPUS_COPY(&norm[n1], &norm[2*n1 - n2], n2-n1); + if (dual_stereo) + OPUS_COPY(&norm2[n1], &norm2[2*n1 - n2], n2-n1); +} void quant_all_bands(int encode, const CELTMode *m, int start, int end, - celt_norm *X_, celt_norm *Y_, unsigned char *collapse_masks, const celt_ener *bandE, int *pulses, - int shortBlocks, int spread, int dual_stereo, int intensity, int *tf_res, - opus_int32 total_bits, opus_int32 balance, ec_ctx *ec, int LM, int codedBands, opus_uint32 *seed) + celt_norm *X_, celt_norm *Y_, unsigned char *collapse_masks, + const celt_ener *bandE, int *pulses, int shortBlocks, int spread, + int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, + opus_int32 balance, ec_ctx *ec, int LM, int codedBands, + opus_uint32 *seed, int complexity, int arch, int disable_inv) { int i; opus_int32 remaining_bits; const opus_int16 * OPUS_RESTRICT eBands = m->eBands; celt_norm * OPUS_RESTRICT norm, * OPUS_RESTRICT norm2; VARDECL(celt_norm, _norm); + VARDECL(celt_norm, _lowband_scratch); + VARDECL(celt_norm, X_save); + VARDECL(celt_norm, Y_save); + VARDECL(celt_norm, X_save2); + VARDECL(celt_norm, Y_save2); + VARDECL(celt_norm, norm_save2); + int resynth_alloc; celt_norm *lowband_scratch; int B; int M; @@ -1369,10 +1404,11 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, int update_lowband = 1; int C = Y_ != NULL ? 2 : 1; int norm_offset; + int theta_rdo = encode && Y_!=NULL && !dual_stereo && complexity>=8; #ifdef RESYNTH int resynth = 1; #else - int resynth = !encode; + int resynth = !encode || theta_rdo; #endif struct band_ctx ctx; SAVE_STACK; @@ -1385,9 +1421,24 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, ALLOC(_norm, C*(M*eBands[m->nbEBands-1]-norm_offset), celt_norm); norm = _norm; norm2 = norm + M*eBands[m->nbEBands-1]-norm_offset; - /* We can use the last band as scratch space because we don't need that - scratch space for the last band. */ - lowband_scratch = X_+M*eBands[m->nbEBands-1]; + + /* For decoding, we can use the last band as scratch space because we don't need that + scratch space for the last band and we don't care about the data there until we're + decoding the last band. */ + if (encode && resynth) + resynth_alloc = M*(eBands[m->nbEBands]-eBands[m->nbEBands-1]); + else + resynth_alloc = ALLOC_NONE; + ALLOC(_lowband_scratch, resynth_alloc, celt_norm); + if (encode && resynth) + lowband_scratch = _lowband_scratch; + else + lowband_scratch = X_+M*eBands[m->nbEBands-1]; + ALLOC(X_save, resynth_alloc, celt_norm); + ALLOC(Y_save, resynth_alloc, celt_norm); + ALLOC(X_save2, resynth_alloc, celt_norm); + ALLOC(Y_save2, resynth_alloc, celt_norm); + ALLOC(norm_save2, resynth_alloc, celt_norm); lowband_offset = 0; ctx.bandE = bandE; @@ -1397,6 +1448,10 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, ctx.m = m; ctx.seed = *seed; ctx.spread = spread; + ctx.arch = arch; + ctx.disable_inv = disable_inv; + ctx.resynth = resynth; + ctx.theta_round = 0; for (i=start;i= M*eBands[start] || i==start+1) && (update_lowband || lowband_offset==0)) + lowband_offset = i; + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); +#else if (resynth && M*eBands[i]-N >= M*eBands[start] && (update_lowband || lowband_offset==0)) lowband_offset = i; +#endif tf_change = tf_res[i]; ctx.tf_change = tf_change; @@ -1446,7 +1508,7 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, Y = norm; lowband_scratch = NULL; } - if (i==end-1) + if (last && !theta_rdo) lowband_scratch = NULL; /* Get a conservative estimate of the collapse_mask's for the bands we're @@ -1461,7 +1523,11 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, fold_start = lowband_offset; while(M*eBands[--fold_start] > effective_lowband+norm_offset); fold_end = lowband_offset-1; +#ifdef ENABLE_UPDATE_DRAFT + while(++fold_end < i && M*eBands[fold_end] < effective_lowband+norm_offset+N); +#else while(M*eBands[++fold_end] < effective_lowband+norm_offset+N); +#endif x_cm = y_cm = 0; fold_i = fold_start; do { x_cm |= collapse_masks[fold_i*C+0]; @@ -1494,13 +1560,77 @@ void quant_all_bands(int encode, const CELTMode *m, int start, int end, } else { if (Y!=NULL) { - x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, - effective_lowband != -1 ? norm+effective_lowband : NULL, LM, - last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, x_cm|y_cm); + if (theta_rdo && i < intensity) + { + ec_ctx ec_save, ec_save2; + struct band_ctx ctx_save, ctx_save2; + opus_val32 dist0, dist1; + unsigned cm, cm2; + int nstart_bytes, nend_bytes, save_bytes; + unsigned char *bytes_buf; + unsigned char bytes_save[1275]; + opus_val16 w[2]; + compute_channel_weights(bandE[i], bandE[i+m->nbEBands], w); + /* Make a copy. */ + cm = x_cm|y_cm; + ec_save = *ec; + ctx_save = ctx; + OPUS_COPY(X_save, X, N); + OPUS_COPY(Y_save, Y, N); + /* Encode and round down. */ + ctx.theta_round = -1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist0 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + + /* Save first result. */ + cm2 = x_cm; + ec_save2 = *ec; + ctx_save2 = ctx; + OPUS_COPY(X_save2, X, N); + OPUS_COPY(Y_save2, Y, N); + if (!last) + OPUS_COPY(norm_save2, norm+M*eBands[i]-norm_offset, N); + nstart_bytes = ec_save.offs; + nend_bytes = ec_save.storage; + bytes_buf = ec_save.buf+nstart_bytes; + save_bytes = nend_bytes-nstart_bytes; + OPUS_COPY(bytes_save, bytes_buf, save_bytes); + + /* Restore */ + *ec = ec_save; + ctx = ctx_save; + OPUS_COPY(X, X_save, N); + OPUS_COPY(Y, Y_save, N); + if (i == start+1) + special_hybrid_folding(m, norm, norm2, start, M, dual_stereo); + /* Encode and round up. */ + ctx.theta_round = 1; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, cm); + dist1 = MULT16_32_Q15(w[0], celt_inner_prod(X_save, X, N, arch)) + MULT16_32_Q15(w[1], celt_inner_prod(Y_save, Y, N, arch)); + if (dist0 >= dist1) { + x_cm = cm2; + *ec = ec_save2; + ctx = ctx_save2; + OPUS_COPY(X, X_save2, N); + OPUS_COPY(Y, Y_save2, N); + if (!last) + OPUS_COPY(norm+M*eBands[i]-norm_offset, norm_save2, N); + OPUS_COPY(bytes_buf, bytes_save, save_bytes); + } + } else { + ctx.theta_round = 0; + x_cm = quant_band_stereo(&ctx, X, Y, N, b, B, + effective_lowband != -1 ? norm+effective_lowband : NULL, LM, + last?NULL:norm+M*eBands[i]-norm_offset, lowband_scratch, x_cm|y_cm); + } } else { x_cm = quant_band(&ctx, X, N, b, B, effective_lowband != -1 ? norm+effective_lowband : NULL, LM, - last?NULL:norm+M*eBands[i]-norm_offset, Q15ONE, lowband_scratch, x_cm|y_cm); + last?NULL:norm+M*eBands[i]-norm_offset, Q15ONE, lowband_scratch, x_cm|y_cm); } y_cm = x_cm; } diff --git a/TMessagesProj/jni/opus/celt/bands.h b/TMessagesProj/jni/opus/celt/bands.h index 96ba52a649f..c040c7f745d 100644 --- a/TMessagesProj/jni/opus/celt/bands.h +++ b/TMessagesProj/jni/opus/celt/bands.h @@ -41,7 +41,7 @@ * @param X Spectrum * @param bandE Square root of the energy for each band (returned) */ -void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int M); +void compute_band_energies(const CELTMode *m, const celt_sig *X, celt_ener *bandE, int end, int C, int LM); /*void compute_noise_energies(const CELTMode *m, const celt_sig *X, const opus_val16 *tonality, celt_ener *bandE);*/ @@ -59,14 +59,15 @@ void normalise_bands(const CELTMode *m, const celt_sig * OPUS_RESTRICT freq, cel * @param bandE Square root of the energy for each band */ void denormalise_bands(const CELTMode *m, const celt_norm * OPUS_RESTRICT X, - celt_sig * OPUS_RESTRICT freq, const opus_val16 *bandE, int start, int end, int C, int M); + celt_sig * OPUS_RESTRICT freq, const opus_val16 *bandE, int start, + int end, int M, int downsample, int silence); #define SPREAD_NONE (0) #define SPREAD_LIGHT (1) #define SPREAD_NORMAL (2) #define SPREAD_AGGRESSIVE (3) -int spreading_decision(const CELTMode *m, celt_norm *X, int *average, +int spreading_decision(const CELTMode *m, const celt_norm *X, int *average, int last_decision, int *hf_average, int *tapset_decision, int update_hf, int end, int C, int M); @@ -97,15 +98,20 @@ void haar1(celt_norm *X, int N0, int stride); * @param LM log2() of the number of 2.5 subframes in the frame * @param codedBands Last band to receive bits + 1 * @param seed Random generator seed + * @param arch Run-time architecture (see opus_select_arch()) */ void quant_all_bands(int encode, const CELTMode *m, int start, int end, - celt_norm * X, celt_norm * Y, unsigned char *collapse_masks, const celt_ener *bandE, int *pulses, - int shortBlocks, int spread, int dual_stereo, int intensity, int *tf_res, - opus_int32 total_bits, opus_int32 balance, ec_ctx *ec, int M, int codedBands, opus_uint32 *seed); - -void anti_collapse(const CELTMode *m, celt_norm *X_, unsigned char *collapse_masks, int LM, int C, int size, - int start, int end, opus_val16 *logE, opus_val16 *prev1logE, - opus_val16 *prev2logE, int *pulses, opus_uint32 seed); + celt_norm * X, celt_norm * Y, unsigned char *collapse_masks, + const celt_ener *bandE, int *pulses, int shortBlocks, int spread, + int dual_stereo, int intensity, int *tf_res, opus_int32 total_bits, + opus_int32 balance, ec_ctx *ec, int M, int codedBands, opus_uint32 *seed, + int complexity, int arch, int disable_inv); + +void anti_collapse(const CELTMode *m, celt_norm *X_, + unsigned char *collapse_masks, int LM, int C, int size, int start, + int end, const opus_val16 *logE, const opus_val16 *prev1logE, + const opus_val16 *prev2logE, const int *pulses, opus_uint32 seed, + int arch); opus_uint32 celt_lcg_rand(opus_uint32 seed); diff --git a/TMessagesProj/jni/opus/celt/celt.c b/TMessagesProj/jni/opus/celt/celt.c index 3e0ce6e6a55..9ce234695ce 100644 --- a/TMessagesProj/jni/opus/celt/celt.c +++ b/TMessagesProj/jni/opus/celt/celt.c @@ -54,6 +54,10 @@ #define PACKAGE_VERSION "unknown" #endif +#if defined(MIPSr1_ASM) +#include "mips/celt_mipsr1.h" +#endif + int resampling_factor(opus_int32 rate) { @@ -85,8 +89,77 @@ int resampling_factor(opus_int32 rate) return ret; } -#ifndef OVERRIDE_COMB_FILTER_CONST -static void comb_filter_const(opus_val32 *y, opus_val32 *x, int T, int N, +#if !defined(OVERRIDE_COMB_FILTER_CONST) || defined(NON_STATIC_COMB_FILTER_CONST_C) +/* This version should be faster on ARM */ +#ifdef OPUS_ARM_ASM +#ifndef NON_STATIC_COMB_FILTER_CONST_C +static +#endif +void comb_filter_const_c(opus_val32 *y, opus_val32 *x, int T, int N, + opus_val16 g10, opus_val16 g11, opus_val16 g12) +{ + opus_val32 x0, x1, x2, x3, x4; + int i; + x4 = SHL32(x[-T-2], 1); + x3 = SHL32(x[-T-1], 1); + x2 = SHL32(x[-T], 1); + x1 = SHL32(x[-T+1], 1); + for (i=0;istart = 0; st->end = st->mode->effEBands; st->signalling = 1; +#ifdef ENABLE_UPDATE_DRAFT + st->disable_inv = channels == 1; +#else + st->disable_inv = 0; +#endif st->arch = opus_select_arch(); - st->loss_count = 0; - opus_custom_decoder_ctl(st, OPUS_RESET_STATE); return OPUS_OK; @@ -175,28 +183,62 @@ void opus_custom_decoder_destroy(CELTDecoder *st) } #endif /* CUSTOM_MODES */ -static OPUS_INLINE opus_val16 SIG2WORD16(celt_sig x) +#ifndef CUSTOM_MODES +/* Special case for stereo with no downsampling and no accumulation. This is + quite common and we can make it faster by processing both channels in the + same loop, reducing overhead due to the dependency loop in the IIR filter. */ +static void deemphasis_stereo_simple(celt_sig *in[], opus_val16 *pcm, int N, const opus_val16 coef0, + celt_sig *mem) { -#ifdef FIXED_POINT - x = PSHR32(x, SIG_SHIFT); - x = MAX32(x, -32768); - x = MIN32(x, 32767); - return EXTRACT16(x); -#else - return (opus_val16)x; -#endif + celt_sig * OPUS_RESTRICT x0; + celt_sig * OPUS_RESTRICT x1; + celt_sig m0, m1; + int j; + x0=in[0]; + x1=in[1]; + m0 = mem[0]; + m1 = mem[1]; + for (j=0;joverlap; + nbEBands = mode->nbEBands; + N = mode->shortMdctSize<shortMdctSize; + B = M; + NB = mode->shortMdctSize; shift = mode->maxLM; } else { B = 1; - N = mode->shortMdctSize<shortMdctSize<maxLM-LM; } - c=0; do { - /* IMDCT on the interleaved the sub-frames, overlap-add is performed by the IMDCT */ + + if (CC==2&&C==1) + { + /* Copying a mono streams to two channels */ + celt_sig *freq2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Store a temporary copy in the output buffer because the IMDCT destroys its input. */ + freq2 = out_syn[1]+overlap/2; + OPUS_COPY(freq2, freq, N); for (b=0;bmdct, &X[b+c*N*B], out_mem[c]+N*b, mode->window, overlap, shift, B); - } while (++cmdct, &freq2[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + for (b=0;bmdct, &freq[b], out_syn[1]+NB*b, mode->window, overlap, shift, B, arch); + } else if (CC==1&&C==2) + { + /* Downmixing a stereo stream to mono */ + celt_sig *freq2; + freq2 = out_syn[0]+overlap/2; + denormalise_bands(mode, X, freq, oldBandE, start, effEnd, M, + downsample, silence); + /* Use the output buffer as temp array before downmixing. */ + denormalise_bands(mode, X+N, freq2, oldBandE+nbEBands, start, effEnd, M, + downsample, silence); + for (i=0;imdct, &freq[b], out_syn[0]+NB*b, mode->window, overlap, shift, B, arch); + } else { + /* Normal case (mono or stereo) */ + c=0; do { + denormalise_bands(mode, X+c*N, freq, oldBandE+c*nbEBands, start, effEnd, M, + downsample, silence); + for (b=0;bmdct, &freq[b], out_syn[c]+NB*b, mode->window, overlap, shift, B, arch); + } while (++c>1, opus_val16 ); + pitch_downsample(decode_mem, lp_pitch_buf, + DECODE_BUFFER_SIZE, C, arch); + pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, + DECODE_BUFFER_SIZE-PLC_PITCH_LAG_MAX, + PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, arch); + pitch_index = PLC_PITCH_LAG_MAX-pitch_index; + RESTORE_STACK; + return pitch_index; +} + +static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, int N, int LM) { int c; int i; @@ -343,11 +474,9 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R int nbEBands; int overlap; int start; - int downsample; int loss_count; int noise_based; const opus_int16 *eBands; - VARDECL(celt_sig, scratch); SAVE_STACK; mode = st->mode; @@ -367,40 +496,37 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R loss_count = st->loss_count; start = st->start; - downsample = st->downsample; - noise_based = loss_count >= 5 || start != 0; - ALLOC(scratch, noise_based?N*C:N, celt_sig); + noise_based = loss_count >= 5 || start != 0 || st->skip_plc; if (noise_based) { /* Noise-based PLC/CNG */ - celt_sig *freq; +#ifdef NORM_ALIASING_HACK + celt_norm *X; +#else VARDECL(celt_norm, X); +#endif opus_uint32 seed; - opus_val16 *plcLogE; int end; int effEnd; - + opus_val16 decay; end = st->end; effEnd = IMAX(start, IMIN(end, mode->effEBands)); - /* Share the interleaved signal MDCT coefficient buffer with the - deemphasis scratch buffer. */ - freq = scratch; +#ifdef NORM_ALIASING_HACK + /* This is an ugly hack that breaks aliasing rules and would be easily broken, + but it saves almost 4kB of stack. */ + X = (celt_norm*)(out_syn[C-1]+overlap/2); +#else ALLOC(X, C*N, celt_norm); /**< Interleaved normalised MDCTs */ +#endif - if (loss_count >= 5) - plcLogE = backgroundLogE; - else { - /* Energy decay */ - opus_val16 decay = loss_count==0 ? - QCONST16(1.5f, DB_SHIFT) : QCONST16(.5f, DB_SHIFT); - c=0; do - { - for (i=start;irng; for (c=0;c>20); } - renormalise_vector(X+boffs, blen, Q15ONE); + renormalise_vector(X+boffs, blen, Q15ONE, st->arch); } } st->rng = seed; - denormalise_bands(mode, X, freq, plcLogE, start, effEnd, C, 1<>1)); } while (++cdownsample, 0, st->arch); } else { /* Pitch-based PLC */ const opus_val16 *window; @@ -445,15 +563,7 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R if (loss_count == 0) { - VARDECL( opus_val16, lp_pitch_buf ); - ALLOC( lp_pitch_buf, DECODE_BUFFER_SIZE>>1, opus_val16 ); - pitch_downsample(decode_mem, lp_pitch_buf, - DECODE_BUFFER_SIZE, C, st->arch); - pitch_search(lp_pitch_buf+(PLC_PITCH_LAG_MAX>>1), lp_pitch_buf, - DECODE_BUFFER_SIZE-PLC_PITCH_LAG_MAX, - PLC_PITCH_LAG_MAX-PLC_PITCH_LAG_MIN, &pitch_index, st->arch); - pitch_index = PLC_PITCH_LAG_MAX-pitch_index; - st->last_pitch_index = pitch_index; + st->last_pitch_index = pitch_index = celt_plc_pitch_search(decode_mem, C, st->arch); } else { pitch_index = st->last_pitch_index; fade = QCONST16(.8f,15); @@ -501,6 +611,23 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R #endif } _celt_lpc(lpc+c*LPC_ORDER, ac, LPC_ORDER); +#ifdef FIXED_POINT + /* For fixed-point, apply bandwidth expansion until we can guarantee that + no overflow can happen in the IIR filter. This means: + 32768*sum(abs(filter)) < 2^31 */ + while (1) { + opus_val16 tmp=Q15ONE; + opus_val32 sum=QCONST16(1., SIG_SHIFT); + for (i=0;iarch); } /* Check if the waveform is decaying, and if so how fast. @@ -570,9 +697,8 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R tmp = ROUND16( buf[DECODE_BUFFER_SIZE-MAX_PERIOD-N+extrapolation_offset+j], SIG_SHIFT); - S1 += SHR32(MULT16_16(tmp, tmp), 8); + S1 += SHR32(MULT16_16(tmp, tmp), 10); } - { opus_val16 lpc_mem[LPC_ORDER]; /* Copy the last decoded samples (prior to the overlap region) to @@ -583,7 +709,11 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R the signal domain. */ celt_iir(buf+DECODE_BUFFER_SIZE-N, lpc+c*LPC_ORDER, buf+DECODE_BUFFER_SIZE-N, extrapolation_len, LPC_ORDER, - lpc_mem); + lpc_mem, st->arch); +#ifdef FIXED_POINT + for (i=0; i < extrapolation_len; i++) + buf[DECODE_BUFFER_SIZE-N+i] = SATURATE(buf[DECODE_BUFFER_SIZE-N+i], SIG_SAT); +#endif } /* Check if the synthesis energy is higher than expected, which can @@ -594,7 +724,7 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R for (i=0;ipostfilter_period, st->postfilter_period, overlap, -st->postfilter_gain, -st->postfilter_gain, - st->postfilter_tapset, st->postfilter_tapset, NULL, 0); + st->postfilter_tapset, st->postfilter_tapset, NULL, 0, st->arch); /* Simulate TDAC on the concealed audio so that it blends with the MDCT of the next frame. */ @@ -644,22 +774,23 @@ static void celt_decode_lost(CELTDecoder * OPUS_RESTRICT st, opus_val16 * OPUS_R } while (++cpreemph, st->preemph_memD, scratch); - st->loss_count = loss_count+1; RESTORE_STACK; } -int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec) +int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, + int len, opus_val16 * OPUS_RESTRICT pcm, int frame_size, ec_dec *dec, int accum) { int c, i, N; int spread_decision; opus_int32 bits; ec_dec _dec; - VARDECL(celt_sig, freq); +#ifdef NORM_ALIASING_HACK + celt_norm *X; +#else VARDECL(celt_norm, X); +#endif VARDECL(int, fine_quant); VARDECL(int, pulses); VARDECL(int, cap); @@ -677,6 +808,8 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat int intra_ener; const int CC = st->channels; int LM, M; + int start; + int end; int effEnd; int codedBands; int alloc_trim; @@ -703,11 +836,10 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat nbEBands = mode->nbEBands; overlap = mode->overlap; eBands = mode->eBands; + start = st->start; + end = st->end; frame_size *= st->downsample; - c=0; do { - decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); - } while (++c_decode_mem+(DECODE_BUFFER_SIZE+overlap)*CC); oldBandE = lpc+CC*LPC_ORDER; oldLogE = oldBandE + 2*nbEBands; @@ -725,7 +857,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat if (data0<0) return OPUS_INVALID_PACKET; } - st->end = IMAX(1, mode->effEBands-2*(data0>>5)); + st->end = end = IMAX(1, mode->effEBands-2*(data0>>5)); LM = (data0>>3)&0x3; C = 1 + ((data0>>2)&0x1); data++; @@ -752,18 +884,27 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat return OPUS_BAD_ARG; N = M*mode->shortMdctSize; + c=0; do { + decode_mem[c] = st->_decode_mem + c*(DECODE_BUFFER_SIZE+overlap); + out_syn[c] = decode_mem[c]+DECODE_BUFFER_SIZE-N; + } while (++cend; + effEnd = end; if (effEnd > mode->effEBands) effEnd = mode->effEBands; if (data == NULL || len<=1) { - celt_decode_lost(st, pcm, N, LM); + celt_decode_lost(st, N, LM); + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); RESTORE_STACK; return frame_size/st->downsample; } + /* Check if there are at least two packets received consecutively before + * turning on the pitch-based PLC */ + st->skip_plc = st->loss_count != 0; + if (dec == NULL) { ec_dec_init(&_dec,(unsigned char*)data,len); @@ -795,7 +936,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat postfilter_gain = 0; postfilter_pitch = 0; postfilter_tapset = 0; - if (st->start==0 && tell+16 <= total_bits) + if (start==0 && tell+16 <= total_bits) { if(ec_dec_bit_logp(dec, 1)) { @@ -826,11 +967,11 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat /* Decode the global flags (first symbols in the stream) */ intra_ener = tell+3<=total_bits ? ec_dec_bit_logp(dec, 3) : 0; /* Get band energies */ - unquant_coarse_energy(mode, st->start, st->end, oldBandE, + unquant_coarse_energy(mode, start, end, oldBandE, intra_ener, dec, C, LM); ALLOC(tf_res, nbEBands, int); - tf_decode(st->start, st->end, isTransient, tf_res, LM, dec); + tf_decode(start, end, isTransient, tf_res, LM, dec); tell = ec_tell(dec); spread_decision = SPREAD_NORMAL; @@ -846,7 +987,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat dynalloc_logp = 6; total_bits<<=BITRES; tell = ec_tell_frac(dec); - for (i=st->start;iend;i++) + for (i=start;istart, st->end, offsets, cap, + codedBands = compute_allocation(mode, start, end, offsets, cap, alloc_trim, &intensity, &dual_stereo, bits, &balance, pulses, fine_quant, fine_priority, C, LM, dec, 0, 0, 0); - unquant_fine_energy(mode, st->start, st->end, oldBandE, fine_quant, dec, C); + unquant_fine_energy(mode, start, end, oldBandE, fine_quant, dec, C); + + c=0; do { + OPUS_MOVE(decode_mem[c], decode_mem[c]+N, DECODE_BUFFER_SIZE-N+overlap/2); + } while (++cstart, st->end, X, C==2 ? X+N : NULL, collapse_masks, + quant_all_bands(0, mode, start, end, X, C==2 ? X+N : NULL, collapse_masks, NULL, pulses, shortBlocks, spread_decision, dual_stereo, intensity, tf_res, - len*(8<rng); + len*(8<rng, 0, + st->arch, st->disable_inv); if (anti_collapse_rsv > 0) { anti_collapse_on = ec_dec_bits(dec, 1); } - unquant_energy_finalise(mode, st->start, st->end, oldBandE, + unquant_energy_finalise(mode, start, end, oldBandE, fine_quant, fine_priority, len*8-ec_tell(dec), dec, C); if (anti_collapse_on) anti_collapse(mode, X, collapse_masks, LM, C, N, - st->start, st->end, oldBandE, oldLogE, oldLogE2, pulses, st->rng); - - ALLOC(freq, IMAX(CC,C)*N, celt_sig); /**< Interleaved signal MDCTs */ + start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng, st->arch); if (silence) { for (i=0;istart, effEnd, C, M); } - c=0; do { - OPUS_MOVE(decode_mem[c], decode_mem[c]+N, DECODE_BUFFER_SIZE-N+overlap/2); - } while (++cdownsample!=1) - bound = IMIN(bound, N/st->downsample); - for (i=bound;idownsample, silence, st->arch); c=0; do { st->postfilter_period=IMAX(st->postfilter_period, COMBFILTER_MINPERIOD); st->postfilter_period_old=IMAX(st->postfilter_period_old, COMBFILTER_MINPERIOD); comb_filter(out_syn[c], out_syn[c], st->postfilter_period_old, st->postfilter_period, mode->shortMdctSize, st->postfilter_gain_old, st->postfilter_gain, st->postfilter_tapset_old, st->postfilter_tapset, - mode->window, overlap); + mode->window, overlap, st->arch); if (LM!=0) comb_filter(out_syn[c]+mode->shortMdctSize, out_syn[c]+mode->shortMdctSize, st->postfilter_period, postfilter_pitch, N-mode->shortMdctSize, st->postfilter_gain, postfilter_gain, st->postfilter_tapset, postfilter_tapset, - mode->window, overlap); + mode->window, overlap, st->arch); } while (++cpostfilter_period_old = st->postfilter_period; @@ -978,32 +1098,36 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat st->postfilter_tapset_old = st->postfilter_tapset; } - if (C==1) { - for (i=0;iloss_count < 10) + max_background_increase = M*QCONST16(0.001f,DB_SHIFT); + else + max_background_increase = QCONST16(1.f,DB_SHIFT); for (i=0;i<2*nbEBands;i++) - oldLogE2[i] = oldLogE[i]; - for (i=0;i<2*nbEBands;i++) - oldLogE[i] = oldBandE[i]; - for (i=0;i<2*nbEBands;i++) - backgroundLogE[i] = MIN16(backgroundLogE[i] + M*QCONST16(0.001f,DB_SHIFT), oldBandE[i]); + backgroundLogE[i] = MIN16(backgroundLogE[i] + max_background_increase, oldBandE[i]); } else { for (i=0;i<2*nbEBands;i++) oldLogE[i] = MIN16(oldLogE[i], oldBandE[i]); } c=0; do { - for (i=0;istart;i++) + for (i=0;iend;irng = dec->rng; - /* We reuse freq[] as scratch space for the de-emphasis */ - deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, freq); + deemphasis(out_syn, pcm, N, CC, st->downsample, mode->preemph, st->preemph_memD, accum); st->loss_count = 0; RESTORE_STACK; if (ec_tell(dec) > 8*len) @@ -1028,7 +1151,7 @@ int celt_decode_with_ec(CELTDecoder * OPUS_RESTRICT st, const unsigned char *dat #ifdef FIXED_POINT int opus_custom_decode(CELTDecoder * OPUS_RESTRICT st, const unsigned char *data, int len, opus_int16 * OPUS_RESTRICT pcm, int frame_size) { - return celt_decode_with_ec(st, data, len, pcm, frame_size, NULL); + return celt_decode_with_ec(st, data, len, pcm, frame_size, NULL, 0); } #ifndef DISABLE_FLOAT_API @@ -1045,7 +1168,7 @@ int opus_custom_decode_float(CELTDecoder * OPUS_RESTRICT st, const unsigned char N = frame_size; ALLOC(out, C*N, opus_int16); - ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL); + ret=celt_decode_with_ec(st, data, len, out, frame_size, NULL, 0); if (ret>0) for (j=0;j0) for (j=0;jDECODER_RESET_START - (char*)st)); for (i=0;i<2*st->mode->nbEBands;i++) oldLogE[i]=oldLogE2[i]=-QCONST16(28.f,DB_SHIFT); + st->skip_plc = 1; } break; case OPUS_GET_PITCH_REQUEST: @@ -1181,6 +1305,26 @@ int opus_custom_decoder_ctl(CELTDecoder * OPUS_RESTRICT st, int request, ...) *value=st->rng; } break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; default: goto bad_request; } diff --git a/TMessagesProj/jni/opus/celt/celt_encoder.c b/TMessagesProj/jni/opus/celt/celt_encoder.c index ffff0775dfd..de3053aa3e6 100644 --- a/TMessagesProj/jni/opus/celt/celt_encoder.c +++ b/TMessagesProj/jni/opus/celt/celt_encoder.c @@ -57,7 +57,6 @@ */ struct OpusCustomEncoder { const OpusCustomMode *mode; /**< Mode used by the encoder */ - int overlap; int channels; int stream_channels; @@ -76,6 +75,7 @@ struct OpusCustomEncoder { int lsb_depth; int variable_duration; int lfe; + int disable_inv; int arch; /* Everything beyond this point gets cleared on a reset */ @@ -99,6 +99,7 @@ struct OpusCustomEncoder { #endif int consec_transient; AnalysisInfo analysis; + SILKInfo silk_info; opus_val32 preemph_memE[2]; opus_val32 preemph_memD[2]; @@ -124,6 +125,7 @@ struct OpusCustomEncoder { /* opus_val16 oldBandE[], Size = channels*mode->nbEBands */ /* opus_val16 oldLogE[], Size = channels*mode->nbEBands */ /* opus_val16 oldLogE2[], Size = channels*mode->nbEBands */ + /* opus_val16 energyError[], Size = channels*mode->nbEBands */ }; int celt_encoder_get_size(int channels) @@ -137,9 +139,10 @@ OPUS_CUSTOM_NOSTATIC int opus_custom_encoder_get_size(const CELTMode *mode, int int size = sizeof(struct CELTEncoder) + (channels*mode->overlap-1)*sizeof(celt_sig) /* celt_sig in_mem[channels*mode->overlap]; */ + channels*COMBFILTER_MAXPERIOD*sizeof(celt_sig) /* celt_sig prefilter_mem[channels*COMBFILTER_MAXPERIOD]; */ - + 3*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */ + + 4*channels*mode->nbEBands*sizeof(opus_val16); /* opus_val16 oldBandE[channels*mode->nbEBands]; */ /* opus_val16 oldLogE[channels*mode->nbEBands]; */ /* opus_val16 oldLogE2[channels*mode->nbEBands]; */ + /* opus_val16 energyError[channels*mode->nbEBands]; */ return size; } @@ -173,14 +176,12 @@ static int opus_custom_encoder_init_arch(CELTEncoder *st, const CELTMode *mode, OPUS_CLEAR((char*)st, opus_custom_encoder_get_size(mode, channels)); st->mode = mode; - st->overlap = mode->overlap; st->stream_channels = st->channels = channels; st->upsample = 1; st->start = 0; st->end = st->mode->effEBands; st->signalling = 1; - st->arch = arch; st->constrained_vbr = 1; @@ -225,7 +226,8 @@ void opus_custom_encoder_destroy(CELTEncoder *st) static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int C, - opus_val16 *tf_estimate, int *tf_chan) + opus_val16 *tf_estimate, int *tf_chan, int allow_weak_transients, + int *weak_transient) { int i; VARDECL(opus_val16, tmp); @@ -235,6 +237,12 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int int c; opus_val16 tf_max; int len2; + /* Forward masking: 6.7 dB/ms. */ +#ifdef FIXED_POINT + int forward_shift = 4; +#else + opus_val16 forward_decay = QCONST16(.0625f,15); +#endif /* Table of 6*64/x, trained on real data to minimize the average error */ static const unsigned char inv_table[128] = { 255,255,156,110, 86, 70, 59, 51, 45, 40, 37, 33, 31, 28, 26, 25, @@ -249,6 +257,19 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int SAVE_STACK; ALLOC(tmp, len, opus_val16); + *weak_transient = 0; + /* For lower bitrates, let's be more conservative and have a forward masking + decay of 3.3 dB/ms. This avoids having to code transients at very low + bitrate (mostly for hybrid), which can result in unstable energy and/or + partial collapse. */ + if (allow_weak_transients) + { +#ifdef FIXED_POINT + forward_shift = 5; +#else + forward_decay = QCONST16(.03125f,15); +#endif + } len2=len/2; for (c=0;c=0;i--) { + /* Backward masking: 13.9 dB/ms. */ #ifdef FIXED_POINT /* FIXME: Use PSHR16() instead */ tmp[i] = mem0 + PSHR32(tmp[i]-mem0,3); @@ -346,9 +367,9 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int { int id; #ifdef FIXED_POINT - id = IMAX(0,IMIN(127,MULT16_32_Q15(tmp[i],norm))); /* Do not round to nearest */ + id = MAX32(0,MIN32(127,MULT16_32_Q15(tmp[i]+EPSILON,norm))); /* Do not round to nearest */ #else - id = IMAX(0,IMIN(127,(int)floor(64*norm*tmp[i]))); /* Do not round to nearest */ + id = (int)MAX32(0,MIN32(127,floor(64*norm*(tmp[i]+EPSILON)))); /* Do not round to nearest */ #endif unmask += inv_table[id]; } @@ -362,11 +383,16 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int } } is_transient = mask_metric>200; - + /* For low bitrates, define "weak transients" that need to be + handled differently to avoid partial collapse. */ + if (allow_weak_transients && is_transient && mask_metric<600) { + is_transient = 0; + *weak_transient = 1; + } /* Arbitrary metric for VBR boost */ tf_max = MAX16(0,celt_sqrt(27*mask_metric)-42); /* *tf_estimate = 1 + MIN16(1, sqrt(MAX16(0, tf_max-30))/20); */ - *tf_estimate = celt_sqrt(MAX16(0, SHL32(MULT16_16(QCONST16(0.0069,14),MIN16(163,tf_max)),14)-QCONST32(0.139,28))); + *tf_estimate = celt_sqrt(MAX32(0, SHL32(MULT16_16(QCONST16(0.0069,14),MIN16(163,tf_max)),14)-QCONST32(0.139,28))); /*printf("%d %f\n", tf_max, mask_metric);*/ RESTORE_STACK; #ifdef FUZZING @@ -378,8 +404,8 @@ static int transient_analysis(const opus_val32 * OPUS_RESTRICT in, int len, int /* Looks for sudden increases of energy to decide whether we need to patch the transient decision */ -int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, - int end, int C) +static int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, + int start, int end, int C) { int i, c; opus_val32 mean_diff=0; @@ -388,28 +414,28 @@ int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, avoid false detection caused by irrelevant bands */ if (C==1) { - spread_old[0] = oldE[0]; - for (i=1;i=0;i--) + for (i=end-2;i>=start;i--) spread_old[i] = MAX16(spread_old[i], spread_old[i+1]-QCONST16(1.0f, DB_SHIFT)); /* Compute mean increase */ c=0; do { - for (i=2;i QCONST16(1.f, DB_SHIFT); } @@ -417,9 +443,10 @@ int patch_transient_decision(opus_val16 *newE, opus_val16 *oldE, int nbEBands, /** Apply window and compute the MDCT for all sub-frames and all channels in a frame */ static void compute_mdcts(const CELTMode *mode, int shortBlocks, celt_sig * OPUS_RESTRICT in, - celt_sig * OPUS_RESTRICT out, int C, int CC, int LM, int upsample) + celt_sig * OPUS_RESTRICT out, int C, int CC, int LM, int upsample, + int arch) { - const int overlap = OVERLAP(mode); + const int overlap = mode->overlap; int N; int B; int shift; @@ -438,7 +465,9 @@ static void compute_mdcts(const CELTMode *mode, int shortBlocks, celt_sig * OPUS for (b=0;bmdct, in+c*(B*N+overlap)+b*N, &out[b+c*N*B], mode->window, overlap, shift, B); + clt_mdct_forward(&mode->mdct, in+c*(B*N+overlap)+b*N, + &out[b+c*N*B], mode->window, overlap, shift, B, + arch); } } while (++ceBands[i+1]-m->eBands[i])<eBands[i+1]-m->eBands[i])==1; - for (j=0;jeBands[i]<eBands[i]<>LM, 1<eBands[i]<eBands[i+1]<eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])<eBands[i]<eBands[i+1]<eBands[i]<eBands[i]<eBands[i+1]-m->eBands[i])< QCONST16(.995f,10)) - trim_index-=4; - else if (sum > QCONST16(.92f,10)) - trim_index-=3; - else if (sum > QCONST16(.85f,10)) - trim_index-=2; - else if (sum > QCONST16(.8f,10)) - trim_index-=1; /* mid-side savings estimations based on the LF average*/ logXC = celt_log2(QCONST32(1.001f, 20)-MULT16_16(sum, sum)); /* mid-side savings estimations based on min correlation */ @@ -819,14 +836,6 @@ static int alloc_trim_analysis(const CELTMode *m, const celt_norm *X, } while (++c QCONST16(2.f, DB_SHIFT)) - trim_index--; - if (diff > QCONST16(8.f, DB_SHIFT)) - trim_index--; - if (diff < -QCONST16(4.f, DB_SHIFT)) - trim_index++; - if (diff < -QCONST16(10.f, DB_SHIFT)) - trim_index++; trim -= MAX16(-QCONST16(2.f, 8), MIN16(QCONST16(2.f, 8), SHR16(diff+QCONST16(1.f, DB_SHIFT),DB_SHIFT-8)/6 )); trim -= SHR16(surround_trim, DB_SHIFT-8); trim -= 2*SHR16(tf_estimate, 14-8); @@ -836,6 +845,8 @@ static int alloc_trim_analysis(const CELTMode *m, const celt_norm *X, trim -= MAX16(-QCONST16(2.f, 8), MIN16(QCONST16(2.f, 8), (opus_val16)(QCONST16(2.f, 8)*(analysis->tonality_slope+.05f)))); } +#else + (void)analysis; #endif #ifdef FIXED_POINT @@ -843,10 +854,7 @@ static int alloc_trim_analysis(const CELTMode *m, const celt_norm *X, #else trim_index = (int)floor(.5f+trim); #endif - if (trim_index<0) - trim_index = 0; - if (trim_index>10) - trim_index = 10; + trim_index = IMAX(0, IMIN(10, trim_index)); /*printf("%d\n", trim_index);*/ #ifdef FUZZING trim_index = rand()%11; @@ -886,6 +894,66 @@ static int stereo_analysis(const CELTMode *m, const celt_norm *X, > MULT16_32_Q15(m->eBands[13]<<(LM+1), sumLR); } +#define MSWAP(a,b) do {opus_val16 tmp = a;a=b;b=tmp;} while(0) +static opus_val16 median_of_5(const opus_val16 *x) +{ + opus_val16 t0, t1, t2, t3, t4; + t2 = x[2]; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + if (x[3] > x[4]) + { + t3 = x[4]; + t4 = x[3]; + } else { + t3 = x[3]; + t4 = x[4]; + } + if (t0 > t3) + { + MSWAP(t0, t3); + MSWAP(t1, t4); + } + if (t2 > t1) + { + if (t1 < t3) + return MIN16(t2, t3); + else + return MIN16(t4, t1); + } else { + if (t2 < t3) + return MIN16(t1, t3); + else + return MIN16(t2, t4); + } +} + +static opus_val16 median_of_3(const opus_val16 *x) +{ + opus_val16 t0, t1, t2; + if (x[0] > x[1]) + { + t0 = x[1]; + t1 = x[0]; + } else { + t0 = x[0]; + t1 = x[1]; + } + t2 = x[2]; + if (t1 < t2) + return t1; + else if (t0 < t2) + return t2; + else + return t0; +} + static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 *bandLogE2, int nbEBands, int start, int end, int C, int *offsets, int lsb_depth, const opus_int16 *logN, int isTransient, int vbr, int constrained_vbr, const opus_int16 *eBands, int LM, @@ -899,8 +967,7 @@ static opus_val16 dynalloc_analysis(const opus_val16 *bandLogE, const opus_val16 SAVE_STACK; ALLOC(follower, C*nbEBands, opus_val16); ALLOC(noise_floor, C*nbEBands, opus_val16); - for (i=0;i bandLogE2[c*nbEBands+i-1]+QCONST16(.5f,DB_SHIFT)) last=i; - follower[c*nbEBands+i] = MIN16(follower[c*nbEBands+i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE2[c*nbEBands+i]); + f[i] = MIN16(f[i-1]+QCONST16(1.5f,DB_SHIFT), bandLogE2[c*nbEBands+i]); } for (i=last-1;i>=0;i--) - follower[c*nbEBands+i] = MIN16(follower[c*nbEBands+i], MIN16(follower[c*nbEBands+i+1]+QCONST16(2.f,DB_SHIFT), bandLogE2[c*nbEBands+i])); + f[i] = MIN16(f[i], MIN16(f[i+1]+QCONST16(2.f,DB_SHIFT), bandLogE2[c*nbEBands+i])); + + /* Combine with a median filter to avoid dynalloc triggering unnecessarily. + The "offset" value controls how conservative we are -- a higher offset + reduces the impact of the median filter and makes dynalloc use more bits. */ + offset = QCONST16(1.f, DB_SHIFT); + for (i=2;imode; + overlap = mode->overlap; ALLOC(_pre, CC*(N+COMBFILTER_MAXPERIOD), celt_sig); pre[0] = _pre; @@ -1027,7 +1114,7 @@ static int run_prefilter(CELTEncoder *st, celt_sig *in, celt_sig *prefilter_mem, c=0; do { OPUS_COPY(pre[c], prefilter_mem+c*COMBFILTER_MAXPERIOD, COMBFILTER_MAXPERIOD); - OPUS_COPY(pre[c]+COMBFILTER_MAXPERIOD, in+c*(N+st->overlap)+st->overlap, N); + OPUS_COPY(pre[c]+COMBFILTER_MAXPERIOD, in+c*(N+overlap)+overlap, N); } while (++cprefilter_period, st->prefilter_gain); + N, &pitch_index, st->prefilter_period, st->prefilter_gain, st->arch); if (pitch_index > COMBFILTER_MAXPERIOD-2) pitch_index = COMBFILTER_MAXPERIOD-2; gain1 = MULT16_16_Q15(QCONST16(.7f,15),gain1); @@ -1100,25 +1187,25 @@ static int run_prefilter(CELTEncoder *st, celt_sig *in, celt_sig *prefilter_mem, /*printf("%d %f\n", pitch_index, gain1);*/ c=0; do { - int offset = mode->shortMdctSize-st->overlap; + int offset = mode->shortMdctSize-overlap; st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); - OPUS_COPY(in+c*(N+st->overlap), st->in_mem+c*(st->overlap), st->overlap); + OPUS_COPY(in+c*(N+overlap), st->in_mem+c*(overlap), overlap); if (offset) - comb_filter(in+c*(N+st->overlap)+st->overlap, pre[c]+COMBFILTER_MAXPERIOD, + comb_filter(in+c*(N+overlap)+overlap, pre[c]+COMBFILTER_MAXPERIOD, st->prefilter_period, st->prefilter_period, offset, -st->prefilter_gain, -st->prefilter_gain, - st->prefilter_tapset, st->prefilter_tapset, NULL, 0); + st->prefilter_tapset, st->prefilter_tapset, NULL, 0, st->arch); - comb_filter(in+c*(N+st->overlap)+st->overlap+offset, pre[c]+COMBFILTER_MAXPERIOD+offset, + comb_filter(in+c*(N+overlap)+overlap+offset, pre[c]+COMBFILTER_MAXPERIOD+offset, st->prefilter_period, pitch_index, N-offset, -st->prefilter_gain, -gain1, - st->prefilter_tapset, prefilter_tapset, mode->window, st->overlap); - OPUS_COPY(st->in_mem+c*(st->overlap), in+c*(N+st->overlap)+N, st->overlap); + st->prefilter_tapset, prefilter_tapset, mode->window, overlap, st->arch); + OPUS_COPY(st->in_mem+c*(overlap), in+c*(N+overlap)+N, overlap); if (N>COMBFILTER_MAXPERIOD) { - OPUS_MOVE(prefilter_mem+c*COMBFILTER_MAXPERIOD, pre[c]+N, COMBFILTER_MAXPERIOD); + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD, pre[c]+N, COMBFILTER_MAXPERIOD); } else { OPUS_MOVE(prefilter_mem+c*COMBFILTER_MAXPERIOD, prefilter_mem+c*COMBFILTER_MAXPERIOD+N, COMBFILTER_MAXPERIOD-N); - OPUS_MOVE(prefilter_mem+c*COMBFILTER_MAXPERIOD+COMBFILTER_MAXPERIOD-N, pre[c]+COMBFILTER_MAXPERIOD, N); + OPUS_COPY(prefilter_mem+c*COMBFILTER_MAXPERIOD+COMBFILTER_MAXPERIOD-N, pre[c]+COMBFILTER_MAXPERIOD, N); } } while (++ctonality, tonal);*/ target = tonal_target; } +#else + (void)analysis; + (void)pitch_change; #endif if (has_surround_mask&&!lfe) @@ -1216,18 +1306,11 @@ static int compute_vbr(const CELTMode *mode, AnalysisInfo *analysis, opus_int32 /*printf("%f %d\n", maxDepth, floor_depth);*/ } - if ((!has_surround_mask||lfe) && (constrained_vbr || bitrate<64000)) + /* Make VBR less aggressive for constrained VBR because we can't keep a higher bitrate + for long. Needs tuning. */ + if ((!has_surround_mask||lfe) && constrained_vbr) { - opus_val16 rate_factor; -#ifdef FIXED_POINT - rate_factor = MAX16(0,(bitrate-32000)); -#else - rate_factor = MAX16(0,(1.f/32768)*(bitrate-32000)); -#endif - if (constrained_vbr) - rate_factor = MIN16(rate_factor, QCONST16(0.67f, 15)); - target = base_target + (opus_int32)MULT16_32_Q15(rate_factor, target-base_target); - + target = base_target + (opus_int32)MULT16_32_Q15(QCONST16(0.67f, 15), target-base_target); } if (!has_surround_mask && tf_estimate < QCONST16(.2f, 14)) @@ -1265,7 +1348,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, VARDECL(int, tf_res); VARDECL(unsigned char, collapse_masks); celt_sig *prefilter_mem; - opus_val16 *oldBandE, *oldLogE, *oldLogE2; + opus_val16 *oldBandE, *oldLogE, *oldLogE2, *energyError; int shortBlocks=0; int isTransient=0; const int CC = st->channels; @@ -1273,9 +1356,10 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, int LM, M; int tf_select; int nbFilledBytes, nbAvailableBytes; + int start; + int end; int effEnd; int codedBands; - int tf_sum; int alloc_trim; int pitch_index=COMBFILTER_MINPERIOD; opus_val16 gain1 = 0; @@ -1287,6 +1371,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, opus_int32 total_boost; opus_int32 balance; opus_int32 tell; + opus_int32 tell0_frac; int prefilter_tapset=0; int pf_on; int anti_collapse_rsv; @@ -1308,7 +1393,9 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, opus_val16 surround_masking=0; opus_val16 temporal_vbr=0; opus_val16 surround_trim = 0; - opus_int32 equiv_rate = 510000; + opus_int32 equiv_rate; + int hybrid; + int weak_transient = 0; VARDECL(opus_val16, surround_dynalloc); ALLOC_STACK; @@ -1316,6 +1403,9 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, nbEBands = mode->nbEBands; overlap = mode->overlap; eBands = mode->eBands; + start = st->start; + end = st->end; + hybrid = start != 0; tf_estimate = 0; if (nbCompressedBytes<2 || pcm==NULL) { @@ -1335,16 +1425,18 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, M=1<shortMdctSize; - prefilter_mem = st->in_mem+CC*(st->overlap); - oldBandE = (opus_val16*)(st->in_mem+CC*(st->overlap+COMBFILTER_MAXPERIOD)); + prefilter_mem = st->in_mem+CC*(overlap); + oldBandE = (opus_val16*)(st->in_mem+CC*(overlap+COMBFILTER_MAXPERIOD)); oldLogE = oldBandE + CC*nbEBands; oldLogE2 = oldLogE + CC*nbEBands; + energyError = oldLogE2 + CC*nbEBands; if (enc==NULL) { - tell=1; + tell0_frac=tell=1; nbFilledBytes=0; } else { + tell0_frac=tell=ec_tell_frac(enc); tell=ec_tell(enc); nbFilledBytes=(tell+4)>>3; } @@ -1352,8 +1444,8 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, #ifdef CUSTOM_MODES if (st->signalling && enc==NULL) { - int tmp = (mode->effEBands-st->end)>>1; - st->end = IMAX(1, mode->effEBands-tmp); + int tmp = (mode->effEBands-end)>>1; + end = st->end = IMAX(1, mode->effEBands-tmp); compressed[0] = tmp<<5; compressed[0] |= LM<<3; compressed[0] |= (C==2)<<2; @@ -1397,10 +1489,11 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, if (st->bitrate!=OPUS_BITRATE_MAX) nbCompressedBytes = IMAX(2, IMIN(nbCompressedBytes, (tmp+4*mode->Fs)/(8*mode->Fs)-!!st->signalling)); - effectiveBytes = nbCompressedBytes; + effectiveBytes = nbCompressedBytes - nbFilledBytes; } + equiv_rate = ((opus_int32)nbCompressedBytes*8*50 >> (3-LM)) - (40*C+20)*((400>>LM) - 50); if (st->bitrate != OPUS_BITRATE_MAX) - equiv_rate = st->bitrate - (40*C+20)*((400>>LM) - 50); + equiv_rate = IMIN(equiv_rate, st->bitrate - (40*C+20)*((400>>LM) - 50)); if (enc==NULL) { @@ -1436,11 +1529,11 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, } total_bits = nbCompressedBytes*8; - effEnd = st->end; + effEnd = end; if (effEnd > mode->effEBands) effEnd = mode->effEBands; - ALLOC(in, CC*(N+st->overlap), celt_sig); + ALLOC(in, CC*(N+overlap), celt_sig); sample_max=MAX32(st->overlap_max, celt_maxabs16(pcm, C*(N-overlap)/st->upsample)); st->overlap_max=celt_maxabs16(pcm+C*(N-overlap)/st->upsample, C*overlap/st->upsample); @@ -1474,8 +1567,12 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, enc->nbits_total+=tell-ec_tell(enc); } c=0; do { - celt_preemphasis(pcm+c, in+c*(N+st->overlap)+st->overlap, N, CC, st->upsample, - mode->preemph, st->preemph_memE+c, st->clip); + int need_clip=0; +#ifndef FIXED_POINT + need_clip = st->clip && sample_max>65536.f; +#endif + celt_preemphasis(pcm+c, in+c*(N+overlap)+overlap, N, CC, st->upsample, + mode->preemph, st->preemph_memE+c, need_clip); } while (++clfe&&nbAvailableBytes>3) || nbAvailableBytes>12*C) && st->start==0 && !silence && !st->disable_pf + enabled = ((st->lfe&&nbAvailableBytes>3) || nbAvailableBytes>12*C) && !hybrid && !silence && !st->disable_pf && st->complexity >= 5 && !(st->consec_transient && LM!=3 && st->variable_duration==OPUS_FRAMESIZE_VARIABLE); prefilter_tapset = st->tapset_decision; @@ -1494,7 +1591,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, pitch_change = 1; if (pf_on==0) { - if(st->start==0 && tell+16<=total_bits) + if(!hybrid && tell+16<=total_bits) ec_enc_bit_logp(enc, 0, 1); } else { /*This block is not gated by a total bits check only because @@ -1515,8 +1612,12 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, shortBlocks = 0; if (st->complexity >= 1 && !st->lfe) { - isTransient = transient_analysis(in, N+st->overlap, CC, - &tf_estimate, &tf_chan); + /* Reduces the likelihood of energy instability on fricatives at low bitrate + in hybrid mode. It seems like we still want to have real transients on vowels + though (small SILK quantization offset value). */ + int allow_weak_transients = hybrid && effectiveBytes<15 && st->silk_info.offset >= 100; + isTransient = transient_analysis(in, N+overlap, CC, + &tf_estimate, &tf_chan, allow_weak_transients, &weak_transient); } if (LM>0 && ec_tell(enc)+3<=total_bits) { @@ -1535,33 +1636,32 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, ALLOC(bandLogE2, C*nbEBands, opus_val16); if (secondMdct) { - compute_mdcts(mode, 0, in, freq, C, CC, LM, st->upsample); - compute_band_energies(mode, freq, bandE, effEnd, C, M); - amp2Log2(mode, effEnd, st->end, bandE, bandLogE2, C); + compute_mdcts(mode, 0, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM); + amp2Log2(mode, effEnd, end, bandE, bandLogE2, C); for (i=0;iupsample); + compute_mdcts(mode, shortBlocks, in, freq, C, CC, LM, st->upsample, st->arch); if (CC==2&&C==1) tf_chan = 0; - compute_band_energies(mode, freq, bandE, effEnd, C, M); + compute_band_energies(mode, freq, bandE, effEnd, C, LM); if (st->lfe) { - for (i=2;iend;i++) + for (i=2;iend, bandE, bandLogE, C); + amp2Log2(mode, effEnd, end, bandE, bandLogE, C); ALLOC(surround_dynalloc, C*nbEBands, opus_val16); - for(i=0;iend;i++) - surround_dynalloc[i] = 0; + OPUS_CLEAR(surround_dynalloc, end); /* This computes how much masking takes place between surround channels */ - if (st->start==0&&st->energy_mask&&!st->lfe) + if (!hybrid&&st->energy_mask&&!st->lfe) { int mask_end; int midband; @@ -1584,6 +1684,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, diff += MULT16_16(mask, 1+2*i-mask_end); } } + celt_assert(count>0); mask_avg = DIV32_16(mask_avg,count); mask_avg += QCONST16(.2f, DB_SHIFT); diff = diff*6/(C*(mask_end-1)*(mask_end+1)*mask_end); @@ -1621,8 +1722,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, disabling masking. */ mask_avg = 0; diff = 0; - for(i=0;istart;iend;i++) + for(i=start;iend-st->start); + frame_avg /= (end-start); temporal_vbr = SUB16(frame_avg,st->spec_avg); temporal_vbr = MIN16(QCONST16(3.f, DB_SHIFT), MAX16(-QCONST16(1.5f, DB_SHIFT), temporal_vbr)); st->spec_avg += MULT16_16_Q15(QCONST16(.02f, 15), temporal_vbr); @@ -1658,21 +1758,20 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, if (!secondMdct) { - for (i=0;i0 && ec_tell(enc)+3<=total_bits && !isTransient && st->complexity>=5 && !st->lfe) + if (LM>0 && ec_tell(enc)+3<=total_bits && !isTransient && st->complexity>=5 && !st->lfe && !hybrid) { - if (patch_transient_decision(bandLogE, oldBandE, nbEBands, st->end, C)) + if (patch_transient_decision(bandLogE, oldBandE, nbEBands, start, end, C)) { isTransient = 1; shortBlocks = M; - compute_mdcts(mode, shortBlocks, in, freq, C, CC, LM, st->upsample); - compute_band_energies(mode, freq, bandE, effEnd, C, M); - amp2Log2(mode, effEnd, st->end, bandE, bandLogE, C); + compute_mdcts(mode, shortBlocks, in, freq, C, CC, LM, st->upsample, st->arch); + compute_band_energies(mode, freq, bandE, effEnd, C, LM); + amp2Log2(mode, effEnd, end, bandE, bandLogE, C); /* Compensate for the scaling of short vs long mdcts */ for (i=0;i=15*C && st->start==0 && st->complexity>=2 && !st->lfe) + if (effectiveBytes>=15*C && !hybrid && st->complexity>=2 && !st->lfe) { int lambda; - if (effectiveBytes<40) - lambda = 12; - else if (effectiveBytes<60) - lambda = 6; - else if (effectiveBytes<100) - lambda = 4; - else - lambda = 3; - lambda*=2; - tf_select = tf_analysis(mode, effEnd, isTransient, tf_res, lambda, X, N, LM, &tf_sum, tf_estimate, tf_chan); - for (i=effEnd;iend;i++) + lambda = IMAX(5, 1280/effectiveBytes + 2); + tf_select = tf_analysis(mode, effEnd, isTransient, tf_res, lambda, X, N, LM, tf_estimate, tf_chan); + for (i=effEnd;iend;i++) + for (i=0;istart, st->end, effEnd, bandLogE, + c=0; + do { + for (i=start;iforce_intra, &st->delayedIntra, st->complexity >= 4, st->loss_rate, st->lfe); - tf_encode(st->start, st->end, isTransient, tf_res, LM, tf_select, enc); + tf_encode(start, end, isTransient, tf_res, LM, tf_select, enc); if (ec_tell(enc)+4<=total_bits) { @@ -1726,7 +1843,15 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, { st->tapset_decision = 0; st->spread_decision = SPREAD_NORMAL; - } else if (shortBlocks || st->complexity < 3 || nbAvailableBytes < 10*C || st->start != 0) + } else if (hybrid) + { + if (st->complexity == 0) + st->spread_decision = SPREAD_NONE; + else if (isTransient) + st->spread_decision = SPREAD_NORMAL; + else + st->spread_decision = SPREAD_AGGRESSIVE; + } else if (shortBlocks || st->complexity < 3 || nbAvailableBytes < 10*C) { if (st->complexity == 0) st->spread_decision = SPREAD_NONE; @@ -1760,7 +1885,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, ALLOC(offsets, nbEBands, int); - maxDepth = dynalloc_analysis(bandLogE, bandLogE2, nbEBands, st->start, st->end, C, offsets, + maxDepth = dynalloc_analysis(bandLogE, bandLogE2, nbEBands, start, end, C, offsets, st->lsb_depth, mode->logN, isTransient, st->vbr, st->constrained_vbr, eBands, LM, effectiveBytes, &tot_boost, st->lfe, surround_dynalloc); /* For LFE, everything interesting is in the first band */ @@ -1773,7 +1898,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, total_bits<<=BITRES; total_boost = 0; tell = ec_tell_frac(enc); - for (i=st->start;iend;i++) + for (i=start;iintensity = hysteresis_decision((opus_val16)(equiv_rate/1000), intensity_thresholds, intensity_histeresis, 21, st->intensity); - st->intensity = IMIN(st->end,IMAX(st->start, st->intensity)); + st->intensity = IMIN(end,IMAX(start, st->intensity)); } alloc_trim = 5; if (tell+(6<lfe) + if (start > 0 || st->lfe) + { + st->stereo_saving = 0; alloc_trim = 5; - else + } else { alloc_trim = alloc_trim_analysis(mode, X, bandLogE, - st->end, LM, C, N, &st->analysis, &st->stereo_saving, tf_estimate, st->intensity, surround_trim); + end, LM, C, N, &st->analysis, &st->stereo_saving, tf_estimate, + st->intensity, surround_trim, st->arch); + } ec_enc_icdf(enc, alloc_trim, trim_icdf, 7); tell = ec_tell_frac(enc); } @@ -1846,17 +1975,36 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, /* Don't attempt to use more than 510 kb/s, even for frames smaller than 20 ms. The CELT allocator will just not be able to use more than that anyway. */ nbCompressedBytes = IMIN(nbCompressedBytes,1275>>(3-LM)); - base_target = vbr_rate - ((40*C+20)<constrained_vbr) base_target += (st->vbr_offset>>lm_diff); - target = compute_vbr(mode, &st->analysis, base_target, LM, equiv_rate, + if (!hybrid) + { + target = compute_vbr(mode, &st->analysis, base_target, LM, equiv_rate, st->lastCodedBands, C, st->intensity, st->constrained_vbr, st->stereo_saving, tot_boost, tf_estimate, pitch_change, maxDepth, st->variable_duration, st->lfe, st->energy_mask!=NULL, surround_masking, temporal_vbr); - + } else { + target = base_target; + /* Tonal frames (offset<100) need more bits than noisy (offset>100) ones. */ + if (st->silk_info.offset < 100) target += 12 << BITRES >> (3-LM); + if (st->silk_info.offset > 100) target -= 18 << BITRES >> (3-LM); + /* Boosting bitrate on transients and vowels with significant temporal + spikes. */ + target += MULT16_16_Q14(tf_estimate-QCONST16(.25f,14), (50< QCONST16(.7f,14)) + target = IMAX(target, 50<>(BITRES+3)) + 2 - nbFilledBytes; + min_allowed = ((tell+total_boost+(1<<(BITRES+3))-1)>>(BITRES+3)) + 2; + /* Take into account the 37 bits we need to have left in the packet to + signal a redundant frame in hybrid mode. Creating a shorter packet would + create an entropy coder desync. */ + if (hybrid) + min_allowed = IMAX(min_allowed, (tell0_frac+(37<>(BITRES+3)); nbAvailableBytes = (target+(1<<(BITRES+2)))>>(BITRES+3); nbAvailableBytes = IMAX(min_allowed,nbAvailableBytes); - nbAvailableBytes = IMIN(nbCompressedBytes,nbAvailableBytes+nbFilledBytes) - nbFilledBytes; + nbAvailableBytes = IMIN(nbCompressedBytes,nbAvailableBytes); /* By how much did we "miss" the target on that frame */ delta = target - vbr_rate; @@ -1915,7 +2068,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, st->vbr_reservoir = 0; /*printf ("+%d\n", adjust);*/ } - nbCompressedBytes = IMIN(nbCompressedBytes,nbAvailableBytes+nbFilledBytes); + nbCompressedBytes = IMIN(nbCompressedBytes,nbAvailableBytes); /*printf("%d\n", nbCompressedBytes*50*8);*/ /* This moves the raw bits to take into account the new compressed size */ ec_enc_shrink(enc, nbCompressedBytes); @@ -1930,7 +2083,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, bits = (((opus_int32)nbCompressedBytes*8)<=2&&bits>=((LM+2)<end-1; + signalBandwidth = end-1; #ifndef DISABLE_FLOAT_API if (st->analysis.valid) { @@ -1950,7 +2103,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, #endif if (st->lfe) signalBandwidth = 1; - codedBands = compute_allocation(mode, st->start, st->end, offsets, cap, + codedBands = compute_allocation(mode, start, end, offsets, cap, alloc_trim, &st->intensity, &dual_stereo, bits, &balance, pulses, fine_quant, fine_priority, C, LM, enc, 1, st->lastCodedBands, signalBandwidth); if (st->lastCodedBands) @@ -1958,13 +2111,14 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, else st->lastCodedBands = codedBands; - quant_fine_energy(mode, st->start, st->end, oldBandE, error, fine_quant, enc, C); + quant_fine_energy(mode, start, end, oldBandE, error, fine_quant, enc, C); /* Residual quantisation */ ALLOC(collapse_masks, C*nbEBands, unsigned char); - quant_all_bands(1, mode, st->start, st->end, X, C==2 ? X+N : NULL, collapse_masks, - bandE, pulses, shortBlocks, st->spread_decision, dual_stereo, st->intensity, tf_res, - nbCompressedBytes*(8<rng); + quant_all_bands(1, mode, start, end, X, C==2 ? X+N : NULL, collapse_masks, + bandE, pulses, shortBlocks, st->spread_decision, + dual_stereo, st->intensity, tf_res, nbCompressedBytes*(8<rng, st->complexity, st->arch, st->disable_inv); if (anti_collapse_rsv > 0) { @@ -1974,7 +2128,15 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, #endif ec_enc_bits(enc, anti_collapse_on, 1); } - quant_energy_finalise(mode, st->start, st->end, oldBandE, error, fine_quant, fine_priority, nbCompressedBytes*8-ec_tell(enc), enc, C); + quant_energy_finalise(mode, start, end, oldBandE, error, fine_quant, fine_priority, nbCompressedBytes*8-ec_tell(enc), enc, C); + OPUS_CLEAR(energyError, nbEBands*CC); + c=0; + do { + for (i=start;istart, st->end, oldBandE, oldLogE, oldLogE2, pulses, st->rng); - } - - if (silence) - { - for (i=0;istart, effEnd, C, M); + start, end, oldBandE, oldLogE, oldLogE2, pulses, st->rng); } c=0; do { OPUS_MOVE(st->syn_mem[c], st->syn_mem[c]+N, 2*MAX_PERIOD-N+overlap/2); } while (++csyn_mem[c]+2*MAX_PERIOD-N; } while (++cupsample, silence, st->arch); c=0; do { st->prefilter_period=IMAX(st->prefilter_period, COMBFILTER_MINPERIOD); st->prefilter_period_old=IMAX(st->prefilter_period_old, COMBFILTER_MINPERIOD); comb_filter(out_mem[c], out_mem[c], st->prefilter_period_old, st->prefilter_period, mode->shortMdctSize, st->prefilter_gain_old, st->prefilter_gain, st->prefilter_tapset_old, st->prefilter_tapset, - mode->window, st->overlap); + mode->window, overlap); if (LM!=0) comb_filter(out_mem[c]+mode->shortMdctSize, out_mem[c]+mode->shortMdctSize, st->prefilter_period, pitch_index, N-mode->shortMdctSize, st->prefilter_gain, gain1, st->prefilter_tapset, prefilter_tapset, @@ -2031,7 +2179,7 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, } while (++cupsample, mode->preemph, st->preemph_memD, freq); + deemphasis(out_mem, (opus_val16*)pcm, N, CC, st->upsample, mode->preemph, st->preemph_memD); st->prefilter_period_old = st->prefilter_period; st->prefilter_gain_old = st->prefilter_gain; st->prefilter_tapset_old = st->prefilter_tapset; @@ -2051,16 +2199,13 @@ int celt_encode_with_ec(CELTEncoder * OPUS_RESTRICT st, const opus_val16 * pcm, #endif if (CC==2&&C==1) { - for (i=0;istart;i++) + for (i=0;iend;ivariable_duration = value; } break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + st->disable_inv = value; + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + *value = st->disable_inv; + } + break; case OPUS_RESET_STATE: { int i; opus_val16 *oldBandE, *oldLogE, *oldLogE2; - oldBandE = (opus_val16*)(st->in_mem+st->channels*(st->overlap+COMBFILTER_MAXPERIOD)); + oldBandE = (opus_val16*)(st->in_mem+st->channels*(st->mode->overlap+COMBFILTER_MAXPERIOD)); oldLogE = oldBandE + st->channels*st->mode->nbEBands; oldLogE2 = oldLogE + st->channels*st->mode->nbEBands; OPUS_CLEAR((char*)&st->ENCODER_RESET_START, @@ -2311,6 +2476,13 @@ int opus_custom_encoder_ctl(CELTEncoder * OPUS_RESTRICT st, int request, ...) OPUS_COPY(&st->analysis, info, 1); } break; + case CELT_SET_SILK_INFO_REQUEST: + { + SILKInfo *info = va_arg(ap, SILKInfo *); + if (info) + OPUS_COPY(&st->silk_info, info, 1); + } + break; case CELT_GET_MODE_REQUEST: { const CELTMode ** value = va_arg(ap, const CELTMode**); diff --git a/TMessagesProj/jni/opus/celt/celt_lpc.c b/TMessagesProj/jni/opus/celt/celt_lpc.c index fa29d626eaf..bc9eb2c82b6 100644 --- a/TMessagesProj/jni/opus/celt/celt_lpc.c +++ b/TMessagesProj/jni/opus/celt/celt_lpc.c @@ -49,8 +49,7 @@ int p float *lpc = _lpc; #endif - for (i = 0; i < p; i++) - lpc[i] = 0; + OPUS_CLEAR(lpc, p); if (ac[0] != 0) { for (i = 0; i < p; i++) { @@ -88,12 +87,15 @@ int p #endif } -void celt_fir(const opus_val16 *_x, + +void celt_fir_c( + const opus_val16 *_x, const opus_val16 *num, opus_val16 *_y, int N, int ord, - opus_val16 *mem) + opus_val16 *mem, + int arch) { int i,j; VARDECL(opus_val16, rnum); @@ -111,6 +113,7 @@ void celt_fir(const opus_val16 *_x, for(i=0;i non-sse + * arch[1] -> sse + * arch[2] -> sse2 + * arch[3] -> sse4.1 + * arch[4] -> avx + */ +#define OPUS_ARCHMASK 7 +int opus_select_arch(void); + #else #define OPUS_ARCHMASK 0 @@ -50,5 +67,4 @@ static OPUS_INLINE int opus_select_arch(void) return 0; } #endif - #endif diff --git a/TMessagesProj/jni/opus/celt/cwrs.c b/TMessagesProj/jni/opus/celt/cwrs.c index ad980cc7d81..9722f0ac86c 100644 --- a/TMessagesProj/jni/opus/celt/cwrs.c +++ b/TMessagesProj/jni/opus/celt/cwrs.c @@ -74,7 +74,7 @@ int log2_frac(opus_uint32 val, int frac) /*Although derived separately, the pulse vector coding scheme is equivalent to a Pyramid Vector Quantizer \cite{Fis86}. Some additional notes about an early version appear at - http://people.xiph.org/~tterribe/notes/cwrs.html, but the codebook ordering + https://people.xiph.org/~tterribe/notes/cwrs.html, but the codebook ordering and the definitions of some terms have evolved since that was written. The conversion from a pulse vector to an integer index (encoding) and back @@ -460,10 +460,12 @@ void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ ec_enc_uint(_enc,icwrs(_n,_y),CELT_PVQ_V(_n,_k)); } -static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ +static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ opus_uint32 p; int s; int k0; + opus_int16 val; + opus_val32 yy=0; celt_assert(_k>0); celt_assert(_n>1); while(_n>2){ @@ -487,7 +489,9 @@ static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ } else for(p=row[_k];p>_i;p=row[_k])_k--; _i-=p; - *_y++=(k0-_k+s)^s; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); } /*Lots of dimensions case:*/ else{ @@ -507,7 +511,9 @@ static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ do p=CELT_PVQ_U_ROW[--_k][_n]; while(p>_i); _i-=p; - *_y++=(k0-_k+s)^s; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); } } _n--; @@ -519,14 +525,19 @@ static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y){ k0=_k; _k=(_i+1)>>1; if(_k)_i-=2*_k-1; - *_y++=(k0-_k+s)^s; + val=(k0-_k+s)^s; + *_y++=val; + yy=MAC16_16(yy,val,val); /*_n==1*/ s=-(int)_i; - *_y=(_k+s)^s; + val=(_k+s)^s; + *_y=val; + yy=MAC16_16(yy,val,val); + return yy; } -void decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ - cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ + return cwrsi(_n,_k,ec_dec_uint(_dec,CELT_PVQ_V(_n,_k)),_y); } #else /* SMALL_FOOTPRINT */ @@ -591,8 +602,10 @@ static opus_uint32 ncwrs_urow(unsigned _n,unsigned _k,opus_uint32 *_u){ _y: Returns the vector of pulses. _u: Must contain entries [0..._k+1] of row _n of U() on input. Its contents will be destructively modified.*/ -static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y,opus_uint32 *_u){ +static opus_val32 cwrsi(int _n,int _k,opus_uint32 _i,int *_y,opus_uint32 *_u){ int j; + opus_int16 val; + opus_val32 yy=0; celt_assert(_n>0); j=0; do{ @@ -607,10 +620,13 @@ static void cwrsi(int _n,int _k,opus_uint32 _i,int *_y,opus_uint32 *_u){ while(p>_i)p=_u[--_k]; _i-=p; yj-=_k; - _y[j]=(yj+s)^s; + val=(yj+s)^s; + _y[j]=val; + yy=MAC16_16(yy,val,val); uprev(_u,_k+2,0); } while(++j<_n); + return yy; } /*Returns the index of the given combination of K elements chosen from a set @@ -685,13 +701,15 @@ void encode_pulses(const int *_y,int _n,int _k,ec_enc *_enc){ RESTORE_STACK; } -void decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ +opus_val32 decode_pulses(int *_y,int _n,int _k,ec_dec *_dec){ VARDECL(opus_uint32,u); + int ret; SAVE_STACK; celt_assert(_k>0); ALLOC(u,_k+2U,opus_uint32); - cwrsi(_n,_k,ec_dec_uint(_dec,ncwrs_urow(_n,_k,u)),_y,u); + ret = cwrsi(_n,_k,ec_dec_uint(_dec,ncwrs_urow(_n,_k,u)),_y,u); RESTORE_STACK; + return ret; } #endif /* SMALL_FOOTPRINT */ diff --git a/TMessagesProj/jni/opus/celt/cwrs.h b/TMessagesProj/jni/opus/celt/cwrs.h index 7dfbd076d16..7cd47174593 100644 --- a/TMessagesProj/jni/opus/celt/cwrs.h +++ b/TMessagesProj/jni/opus/celt/cwrs.h @@ -43,6 +43,6 @@ void get_required_bits(opus_int16 *bits, int N, int K, int frac); void encode_pulses(const int *_y, int N, int K, ec_enc *enc); -void decode_pulses(int *_y, int N, int K, ec_dec *dec); +opus_val32 decode_pulses(int *_y, int N, int K, ec_dec *dec); #endif /* CWRS_H */ diff --git a/TMessagesProj/jni/opus/celt/entcode.c b/TMessagesProj/jni/opus/celt/entcode.c index fa5d7c7c2c9..70f32016ece 100644 --- a/TMessagesProj/jni/opus/celt/entcode.c +++ b/TMessagesProj/jni/opus/celt/entcode.c @@ -62,6 +62,27 @@ int ec_ilog(opus_uint32 _v){ } #endif +#if 1 +/* This is a faster version of ec_tell_frac() that takes advantage + of the low (1/8 bit) resolution to use just a linear function + followed by a lookup to determine the exact transition thresholds. */ +opus_uint32 ec_tell_frac(ec_ctx *_this){ + static const unsigned correction[8] = + {35733, 38967, 42495, 46340, + 50535, 55109, 60097, 65535}; + opus_uint32 nbits; + opus_uint32 r; + int l; + unsigned b; + nbits=_this->nbits_total<rng); + r=_this->rng>>(l-16); + b = (r>>12)-8; + b += r>correction[b]; + l = (l<<3)+b; + return nbits-l; +} +#else opus_uint32 ec_tell_frac(ec_ctx *_this){ opus_uint32 nbits; opus_uint32 r; @@ -91,3 +112,42 @@ opus_uint32 ec_tell_frac(ec_ctx *_this){ } return nbits-l; } +#endif + +#ifdef USE_SMALL_DIV_TABLE +/* Result of 2^32/(2*i+1), except for i=0. */ +const opus_uint32 SMALL_DIV_TABLE[129] = { + 0xFFFFFFFF, 0x55555555, 0x33333333, 0x24924924, + 0x1C71C71C, 0x1745D174, 0x13B13B13, 0x11111111, + 0x0F0F0F0F, 0x0D79435E, 0x0C30C30C, 0x0B21642C, + 0x0A3D70A3, 0x097B425E, 0x08D3DCB0, 0x08421084, + 0x07C1F07C, 0x07507507, 0x06EB3E45, 0x06906906, + 0x063E7063, 0x05F417D0, 0x05B05B05, 0x0572620A, + 0x05397829, 0x05050505, 0x04D4873E, 0x04A7904A, + 0x047DC11F, 0x0456C797, 0x04325C53, 0x04104104, + 0x03F03F03, 0x03D22635, 0x03B5CC0E, 0x039B0AD1, + 0x0381C0E0, 0x0369D036, 0x03531DEC, 0x033D91D2, + 0x0329161F, 0x03159721, 0x03030303, 0x02F14990, + 0x02E05C0B, 0x02D02D02, 0x02C0B02C, 0x02B1DA46, + 0x02A3A0FD, 0x0295FAD4, 0x0288DF0C, 0x027C4597, + 0x02702702, 0x02647C69, 0x02593F69, 0x024E6A17, + 0x0243F6F0, 0x0239E0D5, 0x02302302, 0x0226B902, + 0x021D9EAD, 0x0214D021, 0x020C49BA, 0x02040810, + 0x01FC07F0, 0x01F44659, 0x01ECC07B, 0x01E573AC, + 0x01DE5D6E, 0x01D77B65, 0x01D0CB58, 0x01CA4B30, + 0x01C3F8F0, 0x01BDD2B8, 0x01B7D6C3, 0x01B20364, + 0x01AC5701, 0x01A6D01A, 0x01A16D3F, 0x019C2D14, + 0x01970E4F, 0x01920FB4, 0x018D3018, 0x01886E5F, + 0x0183C977, 0x017F405F, 0x017AD220, 0x01767DCE, + 0x01724287, 0x016E1F76, 0x016A13CD, 0x01661EC6, + 0x01623FA7, 0x015E75BB, 0x015AC056, 0x01571ED3, + 0x01539094, 0x01501501, 0x014CAB88, 0x0149539E, + 0x01460CBC, 0x0142D662, 0x013FB013, 0x013C995A, + 0x013991C2, 0x013698DF, 0x0133AE45, 0x0130D190, + 0x012E025C, 0x012B404A, 0x01288B01, 0x0125E227, + 0x01234567, 0x0120B470, 0x011E2EF3, 0x011BB4A4, + 0x01194538, 0x0116E068, 0x011485F0, 0x0112358E, + 0x010FEF01, 0x010DB20A, 0x010B7E6E, 0x010953F3, + 0x01073260, 0x0105197F, 0x0103091B, 0x01010101 +}; +#endif diff --git a/TMessagesProj/jni/opus/celt/entcode.h b/TMessagesProj/jni/opus/celt/entcode.h index dd13e49e504..13d6c84ef0f 100644 --- a/TMessagesProj/jni/opus/celt/entcode.h +++ b/TMessagesProj/jni/opus/celt/entcode.h @@ -34,6 +34,12 @@ # include # include "ecintrin.h" +extern const opus_uint32 SMALL_DIV_TABLE[129]; + +#ifdef OPUS_ARM_ASM +#define USE_SMALL_DIV_TABLE +#endif + /*OPT: ec_window must be at least 32 bits, but if you have fast arithmetic on a larger type, you can speed up the decoder by using it here.*/ typedef opus_uint32 ec_window; @@ -114,4 +120,33 @@ static OPUS_INLINE int ec_tell(ec_ctx *_this){ rounding error is in the positive direction).*/ opus_uint32 ec_tell_frac(ec_ctx *_this); +/* Tested exhaustively for all n and for 1<=d<=256 */ +static OPUS_INLINE opus_uint32 celt_udiv(opus_uint32 n, opus_uint32 d) { + celt_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (d>256) + return n/d; + else { + opus_uint32 t, q; + t = EC_ILOG(d&-d); + q = (opus_uint64)SMALL_DIV_TABLE[d>>t]*(n>>(t-1))>>32; + return q+(n-q*d >= d); + } +#else + return n/d; +#endif +} + +static OPUS_INLINE opus_int32 celt_sudiv(opus_int32 n, opus_int32 d) { + celt_assert(d>0); +#ifdef USE_SMALL_DIV_TABLE + if (n<0) + return -(opus_int32)celt_udiv(-n, d); + else + return celt_udiv(n, d); +#else + return n/d; +#endif +} + #endif diff --git a/TMessagesProj/jni/opus/celt/entdec.c b/TMessagesProj/jni/opus/celt/entdec.c index 3c264685c26..0b3433ed8b9 100644 --- a/TMessagesProj/jni/opus/celt/entdec.c +++ b/TMessagesProj/jni/opus/celt/entdec.c @@ -138,7 +138,7 @@ void ec_dec_init(ec_dec *_this,unsigned char *_buf,opus_uint32 _storage){ unsigned ec_decode(ec_dec *_this,unsigned _ft){ unsigned s; - _this->ext=_this->rng/_ft; + _this->ext=celt_udiv(_this->rng,_ft); s=(unsigned)(_this->val/_this->ext); return _ft-EC_MINI(s+1,_ft); } diff --git a/TMessagesProj/jni/opus/celt/entenc.c b/TMessagesProj/jni/opus/celt/entenc.c index a7e34ecef97..f1750d25b84 100644 --- a/TMessagesProj/jni/opus/celt/entenc.c +++ b/TMessagesProj/jni/opus/celt/entenc.c @@ -98,7 +98,7 @@ static void ec_enc_carry_out(ec_enc *_this,int _c){ else _this->ext++; } -static void ec_enc_normalize(ec_enc *_this){ +static OPUS_INLINE void ec_enc_normalize(ec_enc *_this){ /*If the range is too small, output some bits and rescale it.*/ while(_this->rng<=EC_CODE_BOT){ ec_enc_carry_out(_this,(int)(_this->val>>EC_CODE_SHIFT)); @@ -127,7 +127,7 @@ void ec_enc_init(ec_enc *_this,unsigned char *_buf,opus_uint32 _size){ void ec_encode(ec_enc *_this,unsigned _fl,unsigned _fh,unsigned _ft){ opus_uint32 r; - r=_this->rng/_ft; + r=celt_udiv(_this->rng,_ft); if(_fl>0){ _this->val+=_this->rng-IMUL32(r,(_ft-_fl)); _this->rng=IMUL32(r,(_fh-_fl)); diff --git a/TMessagesProj/jni/opus/celt/fixed_debug.h b/TMessagesProj/jni/opus/celt/fixed_debug.h index 80bc94910fa..c9546a7d6ed 100644 --- a/TMessagesProj/jni/opus/celt/fixed_debug.h +++ b/TMessagesProj/jni/opus/celt/fixed_debug.h @@ -59,6 +59,12 @@ extern opus_int64 celt_mips; #define SHR(a,b) SHR32(a,b) #define PSHR(a,b) PSHR32(a,b) +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) (celt_mips+=2,(opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +#define NEG32_ovflw(a) (celt_mips+=2,(opus_val32)(-(opus_uint32)(a))) + static OPUS_INLINE short NEG16(int x) { int res; @@ -227,6 +233,8 @@ static OPUS_INLINE int SHL32_(opus_int64 a, int shift, char *file, int line) #define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) #define ROUND16(x,a) (celt_mips--,EXTRACT16(PSHR32((x),(a)))) +#define SROUND16(x,a) (celt_mips--,EXTRACT16(SATURATE(PSHR32(x,a), 32767))); + #define HALF16(x) (SHR16(x,1)) #define HALF32(x) (SHR32(x,1)) @@ -496,6 +504,7 @@ static OPUS_INLINE int MULT16_32_PX_(int a, opus_int64 b, int Q, char *file, int #define MULT16_32_Q15(a,b) MULT16_32_QX(a,b,15) #define MAC16_32_Q15(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q15((a),(b)))) +#define MAC16_32_Q16(c,a,b) (celt_mips-=2,ADD32((c),MULT16_32_Q16((a),(b)))) static OPUS_INLINE int SATURATE(int a, int b) { @@ -767,6 +776,16 @@ static OPUS_INLINE int DIV32_(opus_int64 a, opus_int64 b, char *file, int line) return res; } +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + + #undef PRINT_MIPS #define PRINT_MIPS(file) do {fprintf (file, "total complexity = %llu MIPS\n", celt_mips);} while (0); diff --git a/TMessagesProj/jni/opus/celt/fixed_generic.h b/TMessagesProj/jni/opus/celt/fixed_generic.h index ecf018a2443..3561b93c30f 100644 --- a/TMessagesProj/jni/opus/celt/fixed_generic.h +++ b/TMessagesProj/jni/opus/celt/fixed_generic.h @@ -37,16 +37,32 @@ #define MULT16_16SU(a,b) ((opus_val32)(opus_val16)(a)*(opus_val32)(opus_uint16)(b)) /** 16x32 multiplication, followed by a 16-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q16(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),16)) +#else #define MULT16_32_Q16(a,b) ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif /** 16x32 multiplication, followed by a 16-bit shift right (round-to-nearest). Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_P16(a,b) ((opus_val32)PSHR((opus_int64)((opus_val16)(a))*(b),16)) +#else #define MULT16_32_P16(a,b) ADD32(MULT16_16((a),SHR((b),16)), PSHR(MULT16_16SU((a),((b)&0x0000ffff)),16)) +#endif /** 16x32 multiplication, followed by a 15-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT16_32_Q15(a,b) ((opus_val32)SHR((opus_int64)((opus_val16)(a))*(b),15)) +#else #define MULT16_32_Q15(a,b) ADD32(SHL(MULT16_16((a),SHR((b),16)),1), SHR(MULT16_16SU((a),((b)&0x0000ffff)),15)) +#endif /** 32x32 multiplication, followed by a 31-bit shift right. Results fits in 32 bits */ +#if OPUS_FAST_INT64 +#define MULT32_32_Q31(a,b) ((opus_val32)SHR((opus_int64)(a)*(opus_int64)(b),31)) +#else #define MULT32_32_Q31(a,b) ADD32(ADD32(SHL(MULT16_16(SHR((a),16),SHR((b),16)),1), SHR(MULT16_16SU(SHR((a),16),((b)&0x0000ffff)),15)), SHR(MULT16_16SU(SHR((b),16),((a)&0x0000ffff)),15)) +#endif /** Compile-time conversion of float constant to 16-bit value */ #define QCONST16(x,bits) ((opus_val16)(.5+(x)*(((opus_val32)1)<<(bits)))) @@ -88,6 +104,9 @@ /** Shift by a and round-to-neareast 32-bit value. Result is a 16-bit value */ #define ROUND16(x,a) (EXTRACT16(PSHR32((x),(a)))) +/** Shift by a and round-to-neareast 32-bit value. Result is a saturated 16-bit value */ +#define SROUND16(x,a) EXTRACT16(SATURATE(PSHR32(x,a), 32767)); + /** Divide by two */ #define HALF16(x) (SHR16(x,1)) #define HALF32(x) (SHR32(x,1)) @@ -101,6 +120,12 @@ /** Subtract two 32-bit values */ #define SUB32(a,b) ((opus_val32)(a)-(opus_val32)(b)) +/** Add two 32-bit values, ignore any overflows */ +#define ADD32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)+(opus_uint32)(b))) +/** Subtract two 32-bit values, ignore any overflows */ +#define SUB32_ovflw(a,b) ((opus_val32)((opus_uint32)(a)-(opus_uint32)(b))) +#define NEG32_ovflw(a) ((opus_val32)(-(opus_uint32)(a))) + /** 16x16 multiplication where the result fits in 16 bits */ #define MULT16_16_16(a,b) ((((opus_val16)(a))*((opus_val16)(b)))) @@ -113,7 +138,11 @@ /** 16x32 multiply, followed by a 15-bit shift right and 32-bit add. b must fit in 31 bits. Result fits in 32 bits. */ -#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) +#define MAC16_32_Q15(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + +/** 16x32 multiplication, followed by a 16-bit shift right and 32-bit add. + Results fits in 32 bits */ +#define MAC16_32_Q16(c,a,b) ADD32((c),ADD32(MULT16_16((a),SHR((b),16)), SHR(MULT16_16SU((a),((b)&0x0000ffff)),16))) #define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) #define MULT16_16_Q11(a,b) (SHR(MULT16_16((a),(b)),11)) @@ -131,4 +160,17 @@ /** Divide a 32-bit value by a 32-bit value. Result fits in 32 bits */ #define DIV32(a,b) (((opus_val32)(a))/((opus_val32)(b))) +#if defined(MIPSr1_ASM) +#include "mips/fixed_generic_mipsr1.h" +#endif + +static OPUS_INLINE opus_val16 SIG2WORD16_generic(celt_sig x) +{ + x = PSHR32(x, SIG_SHIFT); + x = MAX32(x, -32768); + x = MIN32(x, 32767); + return EXTRACT16(x); +} +#define SIG2WORD16(x) (SIG2WORD16_generic(x)) + #endif diff --git a/TMessagesProj/jni/opus/celt/float_cast.h b/TMessagesProj/jni/opus/celt/float_cast.h index ede6574860c..98b40abcf65 100644 --- a/TMessagesProj/jni/opus/celt/float_cast.h +++ b/TMessagesProj/jni/opus/celt/float_cast.h @@ -61,7 +61,13 @@ ** the config.h file. */ -#if (HAVE_LRINTF) +/* With GCC, when SSE is available, the fastest conversion is cvtss2si. */ +#if defined(__GNUC__) && defined(__SSE__) + +#include +static OPUS_INLINE opus_int32 float2int(float x) {return _mm_cvt_ss2si(_mm_set_ss(x));} + +#elif defined(HAVE_LRINTF) /* These defines enable functionality introduced with the 1999 ISO C ** standard. They must be defined before the inclusion of math.h to @@ -90,14 +96,14 @@ #include #define float2int(x) lrint(x) -#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && (defined (WIN64) || defined (_WIN64)) +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && defined (_M_X64) #include __inline long int float2int(float value) { return _mm_cvtss_si32(_mm_load_ss(&value)); } -#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && (defined (WIN32) || defined (_WIN32)) +#elif (defined(_MSC_VER) && _MSC_VER >= 1400) && defined (_M_IX86) #include /* Win32 doesn't seem to have these functions. diff --git a/TMessagesProj/jni/opus/celt/kiss_fft.c b/TMessagesProj/jni/opus/celt/kiss_fft.c index ad706c73971..83775165d86 100644 --- a/TMessagesProj/jni/opus/celt/kiss_fft.c +++ b/TMessagesProj/jni/opus/celt/kiss_fft.c @@ -47,64 +47,56 @@ static void kf_bfly2( kiss_fft_cpx * Fout, - const size_t fstride, - const kiss_fft_state *st, int m, - int N, - int mm + int N ) { kiss_fft_cpx * Fout2; - const kiss_twiddle_cpx * tw1; - int i,j; - kiss_fft_cpx * Fout_beg = Fout; - for (i=0;itwiddles; - for(j=0;jr = SHR32(Fout->r, 1);Fout->i = SHR32(Fout->i, 1); - Fout2->r = SHR32(Fout2->r, 1);Fout2->i = SHR32(Fout2->i, 1); - C_MUL (t, *Fout2 , *tw1); - tw1 += fstride; + Fout2 = Fout + 1; + t = *Fout2; C_SUB( *Fout2 , *Fout , t ); C_ADDTO( *Fout , t ); - ++Fout2; - ++Fout; + Fout += 2; } - } -} - -static void ki_bfly2( - kiss_fft_cpx * Fout, - const size_t fstride, - const kiss_fft_state *st, - int m, - int N, - int mm - ) -{ - kiss_fft_cpx * Fout2; - const kiss_twiddle_cpx * tw1; - kiss_fft_cpx t; - int i,j; - kiss_fft_cpx * Fout_beg = Fout; - for (i=0;itwiddles; - for(j=0;jtwiddles; - for (j=0;jr = PSHR32(Fout->r, 2); - Fout->i = PSHR32(Fout->i, 2); - C_SUB( scratch[5] , *Fout, scratch[1] ); - C_ADDTO(*Fout, scratch[1]); - C_ADD( scratch[3] , scratch[0] , scratch[2] ); - C_SUB( scratch[4] , scratch[0] , scratch[2] ); - C_SUB( Fout[m2], *Fout, scratch[3] ); - tw1 += fstride; - tw2 += fstride*2; - tw3 += fstride*3; - C_ADDTO( *Fout , scratch[3] ); - - Fout[m].r = scratch[5].r + scratch[4].i; - Fout[m].i = scratch[5].i - scratch[4].r; - Fout[m3].r = scratch[5].r - scratch[4].i; - Fout[m3].i = scratch[5].i + scratch[4].r; - ++Fout; + kiss_fft_cpx scratch0, scratch1; + + C_SUB( scratch0 , *Fout, Fout[2] ); + C_ADDTO(*Fout, Fout[2]); + C_ADD( scratch1 , Fout[1] , Fout[3] ); + C_SUB( Fout[2], *Fout, scratch1 ); + C_ADDTO( *Fout , scratch1 ); + C_SUB( scratch1 , Fout[1] , Fout[3] ); + + Fout[1].r = ADD32_ovflw(scratch0.r, scratch1.i); + Fout[1].i = SUB32_ovflw(scratch0.i, scratch1.r); + Fout[3].r = SUB32_ovflw(scratch0.r, scratch1.i); + Fout[3].i = ADD32_ovflw(scratch0.i, scratch1.r); + Fout+=4; } - } -} - -static void ki_bfly4( - kiss_fft_cpx * Fout, - const size_t fstride, - const kiss_fft_state *st, - int m, - int N, - int mm - ) -{ - const kiss_twiddle_cpx *tw1,*tw2,*tw3; - kiss_fft_cpx scratch[6]; - const size_t m2=2*m; - const size_t m3=3*m; - int i, j; - - kiss_fft_cpx * Fout_beg = Fout; - for (i=0;itwiddles; - for (j=0;jtwiddles; + /* m is guaranteed to be a multiple of 4. */ + for (j=0;jtwiddles[fstride*m]; +#endif for (i=0;itwiddles; + /* For non-custom modes, m is guaranteed to be a multiple of 4. */ k=m; do { - C_FIXDIV(*Fout,3); C_FIXDIV(Fout[m],3); C_FIXDIV(Fout[m2],3); C_MUL(scratch[1],Fout[m] , *tw1); C_MUL(scratch[2],Fout[m2] , *tw2); @@ -237,74 +212,26 @@ static void kf_bfly3( tw1 += fstride; tw2 += fstride*2; - Fout[m].r = Fout->r - HALF_OF(scratch[3].r); - Fout[m].i = Fout->i - HALF_OF(scratch[3].i); + Fout[m].r = SUB32_ovflw(Fout->r, HALF_OF(scratch[3].r)); + Fout[m].i = SUB32_ovflw(Fout->i, HALF_OF(scratch[3].i)); C_MULBYSCALAR( scratch[0] , epi3.i ); C_ADDTO(*Fout,scratch[3]); - Fout[m2].r = Fout[m].r + scratch[0].i; - Fout[m2].i = Fout[m].i - scratch[0].r; + Fout[m2].r = ADD32_ovflw(Fout[m].r, scratch[0].i); + Fout[m2].i = SUB32_ovflw(Fout[m].i, scratch[0].r); - Fout[m].r -= scratch[0].i; - Fout[m].i += scratch[0].r; + Fout[m].r = SUB32_ovflw(Fout[m].r, scratch[0].i); + Fout[m].i = ADD32_ovflw(Fout[m].i, scratch[0].r); ++Fout; } while(--k); } } -static void ki_bfly3( - kiss_fft_cpx * Fout, - const size_t fstride, - const kiss_fft_state *st, - int m, - int N, - int mm - ) -{ - int i, k; - const size_t m2 = 2*m; - const kiss_twiddle_cpx *tw1,*tw2; - kiss_fft_cpx scratch[5]; - kiss_twiddle_cpx epi3; - - kiss_fft_cpx * Fout_beg = Fout; - epi3 = st->twiddles[fstride*m]; - for (i=0;itwiddles; - k=m; - do{ - - C_MULC(scratch[1],Fout[m] , *tw1); - C_MULC(scratch[2],Fout[m2] , *tw2); - - C_ADD(scratch[3],scratch[1],scratch[2]); - C_SUB(scratch[0],scratch[1],scratch[2]); - tw1 += fstride; - tw2 += fstride*2; - - Fout[m].r = Fout->r - HALF_OF(scratch[3].r); - Fout[m].i = Fout->i - HALF_OF(scratch[3].i); - - C_MULBYSCALAR( scratch[0] , -epi3.i ); - - C_ADDTO(*Fout,scratch[3]); - - Fout[m2].r = Fout[m].r + scratch[0].i; - Fout[m2].i = Fout[m].i - scratch[0].r; - - Fout[m].r -= scratch[0].i; - Fout[m].i += scratch[0].r; - - ++Fout; - }while(--k); - } -} +#ifndef OVERRIDE_kf_bfly5 static void kf_bfly5( kiss_fft_cpx * Fout, const size_t fstride, @@ -317,13 +244,19 @@ static void kf_bfly5( kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; int i, u; kiss_fft_cpx scratch[13]; - const kiss_twiddle_cpx * twiddles = st->twiddles; const kiss_twiddle_cpx *tw; kiss_twiddle_cpx ya,yb; kiss_fft_cpx * Fout_beg = Fout; - ya = twiddles[fstride*m]; - yb = twiddles[fstride*2*m]; +#ifdef FIXED_POINT + ya.r = 10126; + ya.i = -31164; + yb.r = -26510; + yb.i = -19261; +#else + ya = st->twiddles[fstride*m]; + yb = st->twiddles[fstride*2*m]; +#endif tw=st->twiddles; for (i=0;ir += scratch[7].r + scratch[8].r; - Fout0->i += scratch[7].i + scratch[8].i; + Fout0->r = ADD32_ovflw(Fout0->r, ADD32_ovflw(scratch[7].r, scratch[8].r)); + Fout0->i = ADD32_ovflw(Fout0->i, ADD32_ovflw(scratch[7].i, scratch[8].i)); - scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); - scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); + scratch[5].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,ya.r), S_MUL(scratch[8].r,yb.r))); + scratch[5].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,ya.r), S_MUL(scratch[8].i,yb.r))); - scratch[6].r = S_MUL(scratch[10].i,ya.i) + S_MUL(scratch[9].i,yb.i); - scratch[6].i = -S_MUL(scratch[10].r,ya.i) - S_MUL(scratch[9].r,yb.i); + scratch[6].r = ADD32_ovflw(S_MUL(scratch[10].i,ya.i), S_MUL(scratch[9].i,yb.i)); + scratch[6].i = NEG32_ovflw(ADD32_ovflw(S_MUL(scratch[10].r,ya.i), S_MUL(scratch[9].r,yb.i))); C_SUB(*Fout1,scratch[5],scratch[6]); C_ADD(*Fout4,scratch[5],scratch[6]); - scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); - scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); - scratch[12].r = - S_MUL(scratch[10].i,yb.i) + S_MUL(scratch[9].i,ya.i); - scratch[12].i = S_MUL(scratch[10].r,yb.i) - S_MUL(scratch[9].r,ya.i); + scratch[11].r = ADD32_ovflw(scratch[0].r, ADD32_ovflw(S_MUL(scratch[7].r,yb.r), S_MUL(scratch[8].r,ya.r))); + scratch[11].i = ADD32_ovflw(scratch[0].i, ADD32_ovflw(S_MUL(scratch[7].i,yb.r), S_MUL(scratch[8].i,ya.r))); + scratch[12].r = SUB32_ovflw(S_MUL(scratch[9].i,ya.i), S_MUL(scratch[10].i,yb.i)); + scratch[12].i = SUB32_ovflw(S_MUL(scratch[10].r,yb.i), S_MUL(scratch[9].r,ya.i)); C_ADD(*Fout2,scratch[11],scratch[12]); C_SUB(*Fout3,scratch[11],scratch[12]); @@ -373,74 +306,8 @@ static void kf_bfly5( } } } +#endif /* OVERRIDE_kf_bfly5 */ -static void ki_bfly5( - kiss_fft_cpx * Fout, - const size_t fstride, - const kiss_fft_state *st, - int m, - int N, - int mm - ) -{ - kiss_fft_cpx *Fout0,*Fout1,*Fout2,*Fout3,*Fout4; - int i, u; - kiss_fft_cpx scratch[13]; - const kiss_twiddle_cpx * twiddles = st->twiddles; - const kiss_twiddle_cpx *tw; - kiss_twiddle_cpx ya,yb; - kiss_fft_cpx * Fout_beg = Fout; - - ya = twiddles[fstride*m]; - yb = twiddles[fstride*2*m]; - tw=st->twiddles; - - for (i=0;ir += scratch[7].r + scratch[8].r; - Fout0->i += scratch[7].i + scratch[8].i; - - scratch[5].r = scratch[0].r + S_MUL(scratch[7].r,ya.r) + S_MUL(scratch[8].r,yb.r); - scratch[5].i = scratch[0].i + S_MUL(scratch[7].i,ya.r) + S_MUL(scratch[8].i,yb.r); - - scratch[6].r = -S_MUL(scratch[10].i,ya.i) - S_MUL(scratch[9].i,yb.i); - scratch[6].i = S_MUL(scratch[10].r,ya.i) + S_MUL(scratch[9].r,yb.i); - - C_SUB(*Fout1,scratch[5],scratch[6]); - C_ADD(*Fout4,scratch[5],scratch[6]); - - scratch[11].r = scratch[0].r + S_MUL(scratch[7].r,yb.r) + S_MUL(scratch[8].r,ya.r); - scratch[11].i = scratch[0].i + S_MUL(scratch[7].i,yb.r) + S_MUL(scratch[8].i,ya.r); - scratch[12].r = S_MUL(scratch[10].i,yb.i) - S_MUL(scratch[9].i,ya.i); - scratch[12].i = -S_MUL(scratch[10].r,yb.i) + S_MUL(scratch[9].r,ya.i); - - C_ADD(*Fout2,scratch[11],scratch[12]); - C_SUB(*Fout3,scratch[11],scratch[12]); - - ++Fout0;++Fout1;++Fout2;++Fout3;++Fout4; - } - } -} #endif @@ -488,6 +355,9 @@ static int kf_factor(int n,opus_int16 * facbuf) { int p=4; + int i; + int stages=0; + int nbak = n; /*factor out powers of 4, powers of 2, then any remaining primes */ do { @@ -509,9 +379,30 @@ int kf_factor(int n,opus_int16 * facbuf) { return 0; } - *facbuf++ = p; - *facbuf++ = n; + facbuf[2*stages] = p; + if (p==2 && stages > 1) + { + facbuf[2*stages] = 4; + facbuf[2] = 2; + } + stages++; } while (n > 1); + n = nbak; + /* Reverse the order to get the radix 4 at the end, so we can use the + fast degenerate case. It turns out that reversing the order also + improves the noise behaviour. */ + for (i=0;infft=nfft; -#ifndef FIXED_POINT +#ifdef FIXED_POINT + st->scale_shift = celt_ilog2(st->nfft); + if (st->nfft == 1<scale_shift) + st->scale = Q15ONE; + else + st->scale = (1073741824+st->nfft/2)/st->nfft>>(15-st->scale_shift); +#else st->scale = 1.f/nfft; #endif if (base != NULL) { st->twiddles = base->twiddles; st->shift = 0; - while (nfft<shift != base->nfft && st->shift < 32) + while (st->shift < 32 && nfft<shift != base->nfft) st->shift++; if (st->shift>=32) goto fail; @@ -581,22 +484,31 @@ kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, co if (st->bitrev==NULL) goto fail; compute_bitrev_table(0, bitrev, 1,1, st->factors,st); + + /* Initialize architecture specific fft parameters */ + if (opus_fft_alloc_arch(st, arch)) + goto fail; } return st; fail: - opus_fft_free(st); + opus_fft_free(st, arch); return NULL; } -kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem ) +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch) { - return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL); + return opus_fft_alloc_twiddles(nfft, mem, lenmem, NULL, arch); } -void opus_fft_free(const kiss_fft_state *cfg) +void opus_fft_free_arch_c(kiss_fft_state *st) { + (void)st; +} + +void opus_fft_free(const kiss_fft_state *cfg, int arch) { if (cfg) { + opus_fft_free_arch((kiss_fft_state *)cfg, arch); opus_free((opus_int16*)cfg->bitrev); if (cfg->shift < 0) opus_free((kiss_twiddle_cpx*)cfg->twiddles); @@ -606,7 +518,7 @@ void opus_fft_free(const kiss_fft_state *cfg) #endif /* CUSTOM_MODES */ -void opus_fft(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout) { int m2, m; int p; @@ -618,17 +530,6 @@ void opus_fft(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fou /* st->shift can be -1 */ shift = st->shift>0 ? st->shift : 0; - celt_assert2 (fin != fout, "In-place FFT not supported"); - /* Bit-reverse the input */ - for (i=0;infft;i++) - { - fout[st->bitrev[i]] = fin[i]; -#ifndef FIXED_POINT - fout[st->bitrev[i]].r *= st->scale; - fout[st->bitrev[i]].i *= st->scale; -#endif - } - fstride[0] = 1; L=0; do { @@ -647,7 +548,7 @@ void opus_fft(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fou switch (st->factors[2*i]) { case 2: - kf_bfly2(fout,fstride[i]<scale_shift-1; +#endif + scale = st->scale; - /* st->shift can be -1 */ - shift = st->shift>0 ? st->shift : 0; celt_assert2 (fin != fout, "In-place FFT not supported"); /* Bit-reverse the input */ for (i=0;infft;i++) - fout[st->bitrev[i]] = fin[i]; - - fstride[0] = 1; - L=0; - do { - p = st->factors[2*L]; - m = st->factors[2*L+1]; - fstride[L+1] = fstride[L]*p; - L++; - } while(m!=1); - m = st->factors[2*L-1]; - for (i=L-1;i>=0;i--) { - if (i!=0) - m2 = st->factors[2*i-1]; - else - m2 = 1; - switch (st->factors[2*i]) - { - case 2: - ki_bfly2(fout,fstride[i]<bitrev[i]].r = SHR32(MULT16_32_Q16(scale, x.r), scale_shift); + fout[st->bitrev[i]].i = SHR32(MULT16_32_Q16(scale, x.i), scale_shift); } + opus_fft_impl(st, fout); } + +void opus_ifft_c(const kiss_fft_state *st,const kiss_fft_cpx *fin,kiss_fft_cpx *fout) +{ + int i; + celt_assert2 (fin != fout, "In-place FFT not supported"); + /* Bit-reverse the input */ + for (i=0;infft;i++) + fout[st->bitrev[i]] = fin[i]; + for (i=0;infft;i++) + fout[i].i = -fout[i].i; + opus_fft_impl(st, fout); + for (i=0;infft;i++) + fout[i].i = -fout[i].i; +} diff --git a/TMessagesProj/jni/opus/celt/kiss_fft.h b/TMessagesProj/jni/opus/celt/kiss_fft.h index 66332e3bb95..bffa2bfad63 100644 --- a/TMessagesProj/jni/opus/celt/kiss_fft.h +++ b/TMessagesProj/jni/opus/celt/kiss_fft.h @@ -32,6 +32,7 @@ #include #include #include "arch.h" +#include "cpu_support.h" #ifdef __cplusplus extern "C" { @@ -77,17 +78,28 @@ typedef struct { 4*4*4*2 */ +typedef struct arch_fft_state{ + int is_supported; + void *priv; +} arch_fft_state; + typedef struct kiss_fft_state{ int nfft; -#ifndef FIXED_POINT - kiss_fft_scalar scale; + opus_val16 scale; +#ifdef FIXED_POINT + int scale_shift; #endif int shift; opus_int16 factors[2*MAXFACTORS]; const opus_int16 *bitrev; const kiss_twiddle_cpx *twiddles; + arch_fft_state *arch_fft; } kiss_fft_state; +#if defined(HAVE_ARM_NE10) +#include "arm/fft_arm.h" +#endif + /*typedef struct kiss_fft_state* kiss_fft_cfg;*/ /** @@ -113,9 +125,9 @@ typedef struct kiss_fft_state{ * buffer size in *lenmem. * */ -kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base); +kiss_fft_state *opus_fft_alloc_twiddles(int nfft,void * mem,size_t * lenmem, const kiss_fft_state *base, int arch); -kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem); +kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem, int arch); /** * opus_fft(cfg,in_out_buf) @@ -127,10 +139,59 @@ kiss_fft_state *opus_fft_alloc(int nfft,void * mem,size_t * lenmem); * Note that each element is complex and can be accessed like f[k].r and f[k].i * */ -void opus_fft(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); -void opus_ifft(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); +void opus_fft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); +void opus_ifft_c(const kiss_fft_state *cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); + +void opus_fft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); +void opus_ifft_impl(const kiss_fft_state *st,kiss_fft_cpx *fout); + +void opus_fft_free(const kiss_fft_state *cfg, int arch); + + +void opus_fft_free_arch_c(kiss_fft_state *st); +int opus_fft_alloc_arch_c(kiss_fft_state *st); + +#if !defined(OVERRIDE_OPUS_FFT) +/* Is run-time CPU detection enabled on this platform? */ +#if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) + +extern int (*const OPUS_FFT_ALLOC_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); + +#define opus_fft_alloc_arch(_st, arch) \ + ((*OPUS_FFT_ALLOC_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT_FREE_ARCH_IMPL[OPUS_ARCHMASK+1])( + kiss_fft_state *st); +#define opus_fft_free_arch(_st, arch) \ + ((*OPUS_FFT_FREE_ARCH_IMPL[(arch)&OPUS_ARCHMASK])(_st)) + +extern void (*const OPUS_FFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((*OPUS_FFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +extern void (*const OPUS_IFFT[OPUS_ARCHMASK+1])(const kiss_fft_state *cfg, + const kiss_fft_cpx *fin, kiss_fft_cpx *fout); +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((*OPUS_IFFT[(arch)&OPUS_ARCHMASK])(_cfg, _fin, _fout)) + +#else /* else for if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ + +#define opus_fft_alloc_arch(_st, arch) \ + ((void)(arch), opus_fft_alloc_arch_c(_st)) + +#define opus_fft_free_arch(_st, arch) \ + ((void)(arch), opus_fft_free_arch_c(_st)) + +#define opus_fft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_fft_c(_cfg, _fin, _fout)) + +#define opus_ifft(_cfg, _fin, _fout, arch) \ + ((void)(arch), opus_ifft_c(_cfg, _fin, _fout)) -void opus_fft_free(const kiss_fft_state *cfg); +#endif /* end if defined(OPUS_HAVE_RTCD) && (defined(HAVE_ARM_NE10)) */ +#endif /* end if !defined(OVERRIDE_OPUS_FFT) */ #ifdef __cplusplus } diff --git a/TMessagesProj/jni/opus/celt/mathops.c b/TMessagesProj/jni/opus/celt/mathops.c index 3f8c5dcc0e1..21a01f52e43 100644 --- a/TMessagesProj/jni/opus/celt/mathops.c +++ b/TMessagesProj/jni/opus/celt/mathops.c @@ -164,7 +164,7 @@ opus_val16 celt_cos_norm(opus_val32 x) { return _celt_cos_pi_2(EXTRACT16(x)); } else { - return NEG32(_celt_cos_pi_2(EXTRACT16(65536-x))); + return NEG16(_celt_cos_pi_2(EXTRACT16(65536-x))); } } else { if (x&0x0000ffff) diff --git a/TMessagesProj/jni/opus/celt/mathops.h b/TMessagesProj/jni/opus/celt/mathops.h index a0525a96103..1f8a20cb454 100644 --- a/TMessagesProj/jni/opus/celt/mathops.h +++ b/TMessagesProj/jni/opus/celt/mathops.h @@ -38,11 +38,44 @@ #include "entcode.h" #include "os_support.h" +#define PI 3.141592653f + /* Multiplies two 16-bit fractional values. Bit-exactness of this macro is important */ #define FRAC_MUL16(a,b) ((16384+((opus_int32)(opus_int16)(a)*(opus_int16)(b)))>>15) unsigned isqrt32(opus_uint32 _val); +/* CELT doesn't need it for fixed-point, by analysis.c does. */ +#if !defined(FIXED_POINT) || defined(ANALYSIS_C) +#define cA 0.43157974f +#define cB 0.67848403f +#define cC 0.08595542f +#define cE ((float)PI/2) +static OPUS_INLINE float fast_atan2f(float y, float x) { + float x2, y2; + x2 = x*x; + y2 = y*y; + /* For very small values, we don't care about the answer, so + we can just return 0. */ + if (x2 + y2 < 1e-18f) + { + return 0; + } + if(x2>1; -#endif l->n = N; - N4 = N>>2; l->maxshift = maxshift; for (i=0;i<=maxshift;i++) { if (i==0) - l->kfft[i] = opus_fft_alloc(N>>2>>i, 0, 0); + l->kfft[i] = opus_fft_alloc(N>>2>>i, 0, 0, arch); else - l->kfft[i] = opus_fft_alloc_twiddles(N>>2>>i, 0, 0, l->kfft[0]); + l->kfft[i] = opus_fft_alloc_twiddles(N>>2>>i, 0, 0, l->kfft[0], arch); #ifndef ENABLE_TI_DSPLIB55 if (l->kfft[i]==NULL) return 0; #endif } - l->trig = trig = (kiss_twiddle_scalar*)opus_alloc((N4+1)*sizeof(kiss_twiddle_scalar)); + l->trig = trig = (kiss_twiddle_scalar*)opus_alloc((N-(N2>>maxshift))*sizeof(kiss_twiddle_scalar)); if (l->trig==NULL) return 0; - /* We have enough points that sine isn't necessary */ + for (shift=0;shift<=maxshift;shift++) + { + /* We have enough points that sine isn't necessary */ #if defined(FIXED_POINT) - for (i=0;i<=N4;i++) - trig[i] = TRIG_UPSCALE*celt_cos_norm(DIV32(ADD32(SHL32(EXTEND32(i),17),N2),N)); +#if 1 + for (i=0;i>= 1; + N >>= 1; + } return 1; } -void clt_mdct_clear(mdct_lookup *l) +void clt_mdct_clear(mdct_lookup *l, int arch) { int i; for (i=0;i<=l->maxshift;i++) - opus_fft_free(l->kfft[i]); + opus_fft_free(l->kfft[i], arch); opus_free((kiss_twiddle_scalar*)l->trig); } #endif /* CUSTOM_MODES */ /* Forward MDCT trashes the input array */ -void clt_mdct_forward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, - const opus_val16 *window, int overlap, int shift, int stride) +#ifndef OVERRIDE_clt_mdct_forward +void clt_mdct_forward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 *window, int overlap, int shift, int stride, int arch) { int i; int N, N2, N4; - kiss_twiddle_scalar sine; VARDECL(kiss_fft_scalar, f); - VARDECL(kiss_fft_scalar, f2); + VARDECL(kiss_fft_cpx, f2); + const kiss_fft_state *st = l->kfft[shift]; + const kiss_twiddle_scalar *trig; + opus_val16 scale; +#ifdef FIXED_POINT + /* Allows us to scale with MULT16_32_Q16(), which is faster than + MULT16_32_Q15() on ARM. */ + int scale_shift = st->scale_shift-1; +#endif SAVE_STACK; + (void)arch; + scale = st->scale; + N = l->n; - N >>= shift; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } N2 = N>>1; N4 = N>>2; + ALLOC(f, N2, kiss_fft_scalar); - ALLOC(f2, N2, kiss_fft_scalar); - /* sin(x) ~= x here */ -#ifdef FIXED_POINT - sine = TRIG_UPSCALE*(QCONST16(0.7853981f, 15)+N2)/N; -#else - sine = (kiss_twiddle_scalar)2*PI*(.125f)/N; -#endif + ALLOC(f2, N4, kiss_fft_cpx); /* Consider the input to be composed of four blocks: [a, b, c, d] */ /* Window, shuffle, fold */ @@ -167,123 +191,131 @@ void clt_mdct_forward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar /* Pre-rotation */ { kiss_fft_scalar * OPUS_RESTRICT yp = f; - const kiss_twiddle_scalar *t = &l->trig[0]; + const kiss_twiddle_scalar *t = &trig[0]; for(i=0;ibitrev[i]] = yc; } } - /* N/4 complex FFT, down-scales by 4/N */ - opus_fft(l->kfft[shift], (kiss_fft_cpx *)f, (kiss_fft_cpx *)f2); + /* N/4 complex FFT, does not downscale anymore */ + opus_fft_impl(st, f2); /* Post-rotate */ { /* Temp pointers to make it really clear to the compiler what we're doing */ - const kiss_fft_scalar * OPUS_RESTRICT fp = f2; + const kiss_fft_cpx * OPUS_RESTRICT fp = f2; kiss_fft_scalar * OPUS_RESTRICT yp1 = out; kiss_fft_scalar * OPUS_RESTRICT yp2 = out+stride*(N2-1); - const kiss_twiddle_scalar *t = &l->trig[0]; + const kiss_twiddle_scalar *t = &trig[0]; /* Temp pointers to make it really clear to the compiler what we're doing */ for(i=0;ii,t[N4+i]) - S_MUL(fp->r,t[i]); + yi = S_MUL(fp->r,t[N4+i]) + S_MUL(fp->i,t[i]); + *yp1 = yr; + *yp2 = yi; + fp++; yp1 += 2*stride; yp2 -= 2*stride; } } RESTORE_STACK; } +#endif /* OVERRIDE_clt_mdct_forward */ -void clt_mdct_backward(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, - const opus_val16 * OPUS_RESTRICT window, int overlap, int shift, int stride) +#ifndef OVERRIDE_clt_mdct_backward +void clt_mdct_backward_c(const mdct_lookup *l, kiss_fft_scalar *in, kiss_fft_scalar * OPUS_RESTRICT out, + const opus_val16 * OPUS_RESTRICT window, int overlap, int shift, int stride, int arch) { int i; int N, N2, N4; - kiss_twiddle_scalar sine; - VARDECL(kiss_fft_scalar, f2); - SAVE_STACK; + const kiss_twiddle_scalar *trig; + (void) arch; + N = l->n; - N >>= shift; + trig = l->trig; + for (i=0;i>= 1; + trig += N; + } N2 = N>>1; N4 = N>>2; - ALLOC(f2, N2, kiss_fft_scalar); - /* sin(x) ~= x here */ -#ifdef FIXED_POINT - sine = TRIG_UPSCALE*(QCONST16(0.7853981f, 15)+N2)/N; -#else - sine = (kiss_twiddle_scalar)2*PI*(.125f)/N; -#endif /* Pre-rotate */ { /* Temp pointers to make it really clear to the compiler what we're doing */ const kiss_fft_scalar * OPUS_RESTRICT xp1 = in; const kiss_fft_scalar * OPUS_RESTRICT xp2 = in+stride*(N2-1); - kiss_fft_scalar * OPUS_RESTRICT yp = f2; - const kiss_twiddle_scalar *t = &l->trig[0]; + kiss_fft_scalar * OPUS_RESTRICT yp = out+(overlap>>1); + const kiss_twiddle_scalar * OPUS_RESTRICT t = &trig[0]; + const opus_int16 * OPUS_RESTRICT bitrev = l->kfft[shift]->bitrev; for(i=0;ikfft[shift], (kiss_fft_cpx *)f2, (kiss_fft_cpx *)(out+(overlap>>1))); + opus_fft_impl(l->kfft[shift], (kiss_fft_cpx*)(out+(overlap>>1))); /* Post-rotate and de-shuffle from both ends of the buffer at once to make it in-place. */ { - kiss_fft_scalar * OPUS_RESTRICT yp0 = out+(overlap>>1); - kiss_fft_scalar * OPUS_RESTRICT yp1 = out+(overlap>>1)+N2-2; - const kiss_twiddle_scalar *t = &l->trig[0]; + kiss_fft_scalar * yp0 = out+(overlap>>1); + kiss_fft_scalar * yp1 = out+(overlap>>1)+N2-2; + const kiss_twiddle_scalar *t = &trig[0]; /* Loop to (N4+1)>>1 to handle odd N4. When N4 is odd, the middle pair will be computed twice. */ for(i=0;i<(N4+1)>>1;i++) { kiss_fft_scalar re, im, yr, yi; kiss_twiddle_scalar t0, t1; - re = yp0[0]; - im = yp0[1]; - t0 = t[i<maxLM); if (clt_mdct_init(&mode->mdct, 2*mode->shortMdctSize*mode->nbShortMdcts, - mode->maxLM) == 0) + mode->maxLM, arch) == 0) goto failure; if (error) @@ -408,6 +410,8 @@ CELTMode *opus_custom_mode_create(opus_int32 Fs, int frame_size, int *error) #ifdef CUSTOM_MODES void opus_custom_mode_destroy(CELTMode *mode) { + int arch = opus_select_arch(); + if (mode == NULL) return; #ifndef CUSTOM_MODES_ONLY @@ -431,7 +435,7 @@ void opus_custom_mode_destroy(CELTMode *mode) opus_free((opus_int16*)mode->cache.index); opus_free((unsigned char*)mode->cache.bits); opus_free((unsigned char*)mode->cache.caps); - clt_mdct_clear(&mode->mdct); + clt_mdct_clear(&mode->mdct, arch); opus_free((CELTMode *)mode); } diff --git a/TMessagesProj/jni/opus/celt/modes.h b/TMessagesProj/jni/opus/celt/modes.h index c8340f9875c..be813ccc8b4 100644 --- a/TMessagesProj/jni/opus/celt/modes.h +++ b/TMessagesProj/jni/opus/celt/modes.h @@ -39,14 +39,6 @@ #define MAX_PERIOD 1024 -#ifndef OVERLAP -#define OVERLAP(mode) ((mode)->overlap) -#endif - -#ifndef FRAMESIZE -#define FRAMESIZE(mode) ((mode)->mdctSize) -#endif - typedef struct { int size; const opus_int16 *index; diff --git a/TMessagesProj/jni/opus/celt/os_support.h b/TMessagesProj/jni/opus/celt/os_support.h index 5e47e3cff9a..a2171971e9d 100644 --- a/TMessagesProj/jni/opus/celt/os_support.h +++ b/TMessagesProj/jni/opus/celt/os_support.h @@ -67,18 +67,18 @@ static OPUS_INLINE void opus_free (void *ptr) } #endif -/** Copy n bytes of memory from src to dst. The 0* term provides compile-time type checking */ +/** Copy n elements from src to dst. The 0* term provides compile-time type checking */ #ifndef OVERRIDE_OPUS_COPY #define OPUS_COPY(dst, src, n) (memcpy((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) #endif -/** Copy n bytes of memory from src to dst, allowing overlapping regions. The 0* term +/** Copy n elements from src to dst, allowing overlapping regions. The 0* term provides compile-time type checking */ #ifndef OVERRIDE_OPUS_MOVE #define OPUS_MOVE(dst, src, n) (memmove((dst), (src), (n)*sizeof(*(dst)) + 0*((dst)-(src)) )) #endif -/** Set n elements of dst to zero, starting at address s */ +/** Set n elements of dst to zero */ #ifndef OVERRIDE_OPUS_CLEAR #define OPUS_CLEAR(dst, n) (memset((dst), 0, (n)*sizeof(*(dst)))) #endif diff --git a/TMessagesProj/jni/opus/celt/pitch.c b/TMessagesProj/jni/opus/celt/pitch.c index d2b305441da..bf46e7d562b 100644 --- a/TMessagesProj/jni/opus/celt/pitch.c +++ b/TMessagesProj/jni/opus/celt/pitch.c @@ -214,25 +214,35 @@ void pitch_downsample(celt_sig * OPUS_RESTRICT x[], opus_val16 * OPUS_RESTRICT x celt_fir5(x_lp, lpc2, x_lp, len>>1, mem); } -#if 0 /* This is a simple version of the pitch correlation that should work - well on DSPs like Blackfin and TI C5x/C6x */ - +/* Pure C implementation. */ #ifdef FIXED_POINT opus_val32 #else void #endif -celt_pitch_xcorr(opus_val16 *x, opus_val16 *y, opus_val32 *xcorr, int len, int max_pitch) +#if defined(OVERRIDE_PITCH_XCORR) +celt_pitch_xcorr_c(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch) +#else +celt_pitch_xcorr(const opus_val16 *_x, const opus_val16 *_y, + opus_val32 *xcorr, int len, int max_pitch, int arch) +#endif { + +#if 0 /* This is a simple version of the pitch correlation that should work + well on DSPs like Blackfin and TI C5x/C6x */ int i, j; #ifdef FIXED_POINT opus_val32 maxcorr=1; +#endif +#if !defined(OVERRIDE_PITCH_XCORR) + (void)arch; #endif for (i=0;i0); - celt_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0); #ifdef FIXED_POINT opus_val32 maxcorr=1; #endif + celt_assert(max_pitch>0); + celt_assert((((unsigned char *)_x-(unsigned char *)NULL)&3)==0); for (i=0;i>1;i++) { - opus_val32 sum=0; + opus_val32 sum; xcorr[i] = 0; if (abs(i-2*best_pitch[0])>2 && abs(i-2*best_pitch[1])>2) continue; +#ifdef FIXED_POINT + sum = 0; for (j=0;j>1;j++) sum += SHR32(MULT16_16(x_lp[j],y[i+j]), shift); +#else + sum = celt_inner_prod_c(x_lp, y+i, len>>1); +#endif xcorr[i] = MAX32(-1, sum); #ifdef FIXED_POINT maxcorr = MAX32(maxcorr, sum); @@ -399,9 +412,44 @@ void pitch_search(const opus_val16 * OPUS_RESTRICT x_lp, opus_val16 * OPUS_RESTR RESTORE_STACK; } +#ifdef FIXED_POINT +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + opus_val32 x2y2; + int sx, sy, shift; + opus_val32 g; + opus_val16 den; + if (xy == 0 || xx == 0 || yy == 0) + return 0; + sx = celt_ilog2(xx)-14; + sy = celt_ilog2(yy)-14; + shift = sx + sy; + x2y2 = MULT16_16_Q14(VSHR32(xx, sx), VSHR32(yy, sy)); + if (shift & 1) { + if (x2y2 < 32768) + { + x2y2 <<= 1; + shift--; + } else { + x2y2 >>= 1; + shift++; + } + } + den = celt_rsqrt_norm(x2y2); + g = MULT16_32_Q15(den, xy); + g = VSHR32(g, (shift>>1)-1); + return EXTRACT16(MIN32(g, Q15ONE)); +} +#else +static opus_val16 compute_pitch_gain(opus_val32 xy, opus_val32 xx, opus_val32 yy) +{ + return xy/celt_sqrt(1+xx*yy); +} +#endif + static const int second_check[16] = {0, 0, 3, 2, 3, 2, 5, 2, 3, 2, 3, 2, 5, 2, 3, 2}; opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, - int N, int *T0_, int prev_period, opus_val16 prev_gain) + int N, int *T0_, int prev_period, opus_val16 prev_gain, int arch) { int k, i, T, T0; opus_val16 g, g0; @@ -426,7 +474,7 @@ opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, T = T0 = *T0_; ALLOC(yy_lookup, maxperiod+1, opus_val32); - dual_inner_prod(x, x, x-T0, N, &xx, &xy); + dual_inner_prod(x, x, x-T0, N, &xx, &xy, arch); yy_lookup[0] = xx; yy=xx; for (i=1;i<=maxperiod;i++) @@ -437,18 +485,7 @@ opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, yy = yy_lookup[T0]; best_xy = xy; best_yy = yy; -#ifdef FIXED_POINT - { - opus_val32 x2y2; - int sh, t; - x2y2 = 1+HALF32(MULT32_32_Q31(xx,yy)); - sh = celt_ilog2(x2y2)>>1; - t = VSHR32(x2y2, 2*(sh-7)); - g = g0 = VSHR32(MULT16_32_Q15(celt_rsqrt_norm(t), xy),sh+1); - } -#else - g = g0 = xy/celt_sqrt(1+xx*yy); -#endif + g = g0 = compute_pitch_gain(xy, xx, yy); /* Look for any pitch at T/k */ for (k=2;k<=15;k++) { @@ -456,7 +493,7 @@ opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, opus_val16 g1; opus_val16 cont=0; opus_val16 thresh; - T1 = (2*T0+k)/(2*k); + T1 = celt_udiv(2*T0+k, 2*k); if (T1 < minperiod) break; /* Look for another strong correlation at T1b */ @@ -468,27 +505,16 @@ opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, T1b = T0+T1; } else { - T1b = (2*second_check[k]*T0+k)/(2*k); + T1b = celt_udiv(2*second_check[k]*T0+k, 2*k); } - dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2); - xy += xy2; - yy = yy_lookup[T1] + yy_lookup[T1b]; -#ifdef FIXED_POINT - { - opus_val32 x2y2; - int sh, t; - x2y2 = 1+MULT32_32_Q31(xx,yy); - sh = celt_ilog2(x2y2)>>1; - t = VSHR32(x2y2, 2*(sh-7)); - g1 = VSHR32(MULT16_32_Q15(celt_rsqrt_norm(t), xy),sh+1); - } -#else - g1 = xy/celt_sqrt(1+2.f*xx*1.f*yy); -#endif + dual_inner_prod(x, &x[-T1], &x[-T1b], N, &xy, &xy2, arch); + xy = HALF32(xy + xy2); + yy = HALF32(yy_lookup[T1] + yy_lookup[T1b]); + g1 = compute_pitch_gain(xy, xx, yy); if (abs(T1-prev_period)<=1) cont = prev_gain; else if (abs(T1-prev_period)<=2 && 5*k*k < T0) - cont = HALF32(prev_gain); + cont = HALF16(prev_gain); else cont = 0; thresh = MAX16(QCONST16(.3f,15), MULT16_16_Q15(QCONST16(.7f,15),g0)-cont); @@ -513,13 +539,7 @@ opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, pg = SHR32(frac_div32(best_xy,best_yy+1),16); for (k=0;k<3;k++) - { - int T1 = T+k-1; - xy = 0; - for (i=0;i MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[0])) offset = 1; else if ((xcorr[0]-xcorr[2]) > MULT16_32_Q15(QCONST16(.7f,15),xcorr[1]-xcorr[2])) diff --git a/TMessagesProj/jni/opus/celt/pitch.h b/TMessagesProj/jni/opus/celt/pitch.h index df317ecc1de..d3503532a08 100644 --- a/TMessagesProj/jni/opus/celt/pitch.h +++ b/TMessagesProj/jni/opus/celt/pitch.h @@ -37,11 +37,17 @@ #include "modes.h" #include "cpu_support.h" -#if defined(__SSE__) && !defined(FIXED_POINT) +#if (defined(OPUS_X86_MAY_HAVE_SSE) && !defined(FIXED_POINT)) \ + || ((defined(OPUS_X86_MAY_HAVE_SSE4_1) || defined(OPUS_X86_MAY_HAVE_SSE2)) && defined(FIXED_POINT)) #include "x86/pitch_sse.h" #endif -#if defined(OPUS_ARM_ASM) && defined(FIXED_POINT) +#if defined(MIPSr1_ASM) +#include "mips/pitch_mipsr1.h" +#endif + +#if ((defined(OPUS_ARM_ASM) && defined(FIXED_POINT)) \ + || defined(OPUS_ARM_MAY_HAVE_NEON_INTR)) # include "arm/pitch_arm.h" #endif @@ -52,12 +58,12 @@ void pitch_search(const opus_val16 * OPUS_RESTRICT x_lp, opus_val16 * OPUS_RESTR int len, int max_pitch, int *pitch, int arch); opus_val16 remove_doubling(opus_val16 *x, int maxperiod, int minperiod, - int N, int *T0, int prev_period, opus_val16 prev_gain); + int N, int *T0, int prev_period, opus_val16 prev_gain, int arch); + /* OPT: This is the kernel you really want to optimize. It gets used a lot by the prefilter and by the PLC. */ -#ifndef OVERRIDE_XCORR_KERNEL -static OPUS_INLINE void xcorr_kernel(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len) +static OPUS_INLINE void xcorr_kernel_c(const opus_val16 * x, const opus_val16 * y, opus_val32 sum[4], int len) { int j; opus_val16 y_0, y_1, y_2, y_3; @@ -122,10 +128,14 @@ static OPUS_INLINE void xcorr_kernel(const opus_val16 * x, const opus_val16 * y, sum[3] = MAC16_16(sum[3],tmp,y_1); } } + +#ifndef OVERRIDE_XCORR_KERNEL +#define xcorr_kernel(x, y, sum, len, arch) \ + ((void)(arch),xcorr_kernel_c(x, y, sum, len)) #endif /* OVERRIDE_XCORR_KERNEL */ -#ifndef OVERRIDE_DUAL_INNER_PROD -static OPUS_INLINE void dual_inner_prod(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + +static OPUS_INLINE void dual_inner_prod_c(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, int N, opus_val32 *xy1, opus_val32 *xy2) { int i; @@ -139,8 +149,35 @@ static OPUS_INLINE void dual_inner_prod(const opus_val16 *x, const opus_val16 *y *xy1 = xy01; *xy2 = xy02; } + +#ifndef OVERRIDE_DUAL_INNER_PROD +# define dual_inner_prod(x, y01, y02, N, xy1, xy2, arch) \ + ((void)(arch),dual_inner_prod_c(x, y01, y02, N, xy1, xy2)) +#endif + +/*We make sure a C version is always available for cases where the overhead of + vectorization and passing around an arch flag aren't worth it.*/ +static OPUS_INLINE opus_val32 celt_inner_prod_c(const opus_val16 *x, + const opus_val16 *y, int N) +{ + int i; + opus_val32 xy=0; + for (i=0;inbEBands, opus_val16); @@ -418,6 +418,7 @@ void quant_energy_finalise(const CELTMode *m, int start, int end, opus_val16 *ol offset = (q2-.5f)*(1<<(14-fine_quant[i]-1))*(1.f/16384); #endif oldEBands[i+c*m->nbEBands] += offset; + error[i+c*m->nbEBands] -= offset; bits_left--; } while (++c < C); } @@ -547,9 +548,15 @@ void amp2Log2(const CELTMode *m, int effEnd, int end, c=0; do { for (i=0;inbEBands] = - celt_log2(SHL32(bandE[i+c*m->nbEBands],2)) + celt_log2(bandE[i+c*m->nbEBands]) - SHL16((opus_val16)eMeans[i],6); +#ifdef FIXED_POINT + /* Compensate for bandE[] being Q12 but celt_log2() taking a Q14 input. */ + bandLogE[i+c*m->nbEBands] += QCONST16(2.f, DB_SHIFT); +#endif + } for (i=effEnd;inbEBands+i] = -QCONST16(14.f,DB_SHIFT); } while (++c < C); diff --git a/TMessagesProj/jni/opus/celt/rate.c b/TMessagesProj/jni/opus/celt/rate.c index e13d839d63b..ca4cc870eaa 100644 --- a/TMessagesProj/jni/opus/celt/rate.c +++ b/TMessagesProj/jni/opus/celt/rate.c @@ -131,7 +131,7 @@ void compute_pulse_cache(CELTMode *m, int LM) for (i=0;istart;) { - int tmp = bits1[j] + (lo*bits2[j]>>ALLOC_STEPS); + int tmp = bits1[j] + ((opus_int32)lo*bits2[j]>>ALLOC_STEPS); if (tmp < thresh[j] && !done) { if (tmp >= alloc_floor) @@ -333,7 +333,7 @@ static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, /*Figure out how many left-over bits we would be adding to this band. This can include bits we've stolen back from higher, skipped bands.*/ left = total-psum; - percoeff = left/(m->eBands[codedBands]-m->eBands[start]); + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; rem = IMAX(left-(m->eBands[j]-m->eBands[start]),0); band_width = m->eBands[codedBands]-m->eBands[j]; @@ -348,12 +348,17 @@ static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, /*This if() block is the only part of the allocation function that is not a mandatory part of the bitstream: any bands we choose to skip here must be explicitly signaled.*/ - /*Choose a threshold with some hysteresis to keep bands from - fluctuating in and out.*/ + int depth_threshold; + /*We choose a threshold with some hysteresis to keep bands from + fluctuating in and out, but we try not to fold below a certain point. */ + if (codedBands > 17) + depth_threshold = j ((j>4 && j<=signalBandwidth)) + if (codedBands<=start+2 || (band_bits > (depth_threshold*band_width<>4 && j<=signalBandwidth)) #endif { ec_enc_bit_logp(ec, 1, 1); @@ -414,7 +419,7 @@ static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, /* Allocate the remaining bits */ left = total-psum; - percoeff = left/(m->eBands[codedBands]-m->eBands[start]); + percoeff = celt_udiv(left, m->eBands[codedBands]-m->eBands[start]); left -= (m->eBands[codedBands]-m->eBands[start])*percoeff; for (j=start;jeBands[j+1]-m->eBands[j])); @@ -465,7 +470,8 @@ static OPUS_INLINE int interp_bits2pulses(const CELTMode *m, int start, int end, offset += NClogN>>3; /* Divide with rounding */ - ebits[j] = IMAX(0, (bits[j] + offset + (den<<(BITRES-1))) / (den<>BITRES; /* Make sure not to bust */ if (C*ebits[j] > (bits[j]>>BITRES)) diff --git a/TMessagesProj/jni/opus/celt/rate.h b/TMessagesProj/jni/opus/celt/rate.h index f1e06611299..515f7687cec 100644 --- a/TMessagesProj/jni/opus/celt/rate.h +++ b/TMessagesProj/jni/opus/celt/rate.h @@ -32,7 +32,7 @@ #define MAX_PSEUDO 40 #define LOG_MAX_PSEUDO 6 -#define MAX_PULSES 128 +#define CELT_MAX_PULSES 128 #define MAX_FINE_BITS 8 diff --git a/TMessagesProj/jni/opus/celt/stack_alloc.h b/TMessagesProj/jni/opus/celt/stack_alloc.h index 316a6ce12c0..2b51c8d80cc 100644 --- a/TMessagesProj/jni/opus/celt/stack_alloc.h +++ b/TMessagesProj/jni/opus/celt/stack_alloc.h @@ -116,9 +116,11 @@ #else #ifdef CELT_C +char *scratch_ptr=0; char *global_stack=0; #else extern char *global_stack; +extern char *scratch_ptr; #endif /* CELT_C */ #ifdef ENABLE_VALGRIND @@ -140,8 +142,12 @@ extern char *global_stack_top; #define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1)) #define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)/sizeof(char)),(stack)+=(size)*(sizeof(type)/sizeof(char)),(type*)((stack)-(size)*(sizeof(type)/sizeof(char)))) +#if 0 /* Set this to 1 to instrument pseudostack usage */ +#define RESTORE_STACK (printf("%ld %s:%d\n", global_stack-scratch_ptr, __FILE__, __LINE__),global_stack = _saved_stack) +#else #define RESTORE_STACK (global_stack = _saved_stack) -#define ALLOC_STACK char *_saved_stack; (global_stack = (global_stack==0) ? opus_alloc_scratch(GLOBAL_STACK_SIZE) : global_stack); _saved_stack = global_stack; +#endif +#define ALLOC_STACK char *_saved_stack; (global_stack = (global_stack==0) ? (scratch_ptr=opus_alloc_scratch(GLOBAL_STACK_SIZE)) : global_stack); _saved_stack = global_stack; #endif /* ENABLE_VALGRIND */ diff --git a/TMessagesProj/jni/opus/celt/static_modes_fixed.h b/TMessagesProj/jni/opus/celt/static_modes_fixed.h index 216df9e605f..8717d626cbe 100644 --- a/TMessagesProj/jni/opus/celt/static_modes_fixed.h +++ b/TMessagesProj/jni/opus/celt/static_modes_fixed.h @@ -4,6 +4,11 @@ #include "modes.h" #include "rate.h" +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_fixed_arm_ne10.h" +#endif + #ifndef DEF_WINDOW120 #define DEF_WINDOW120 static const opus_val16 window120[120] = { @@ -341,84 +346,84 @@ static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { #ifndef FFT_BITREV480 #define FFT_BITREV480 static const opus_int16 fft_bitrev480[480] = { -0, 120, 240, 360, 30, 150, 270, 390, 60, 180, 300, 420, 90, 210, 330, -450, 15, 135, 255, 375, 45, 165, 285, 405, 75, 195, 315, 435, 105, 225, -345, 465, 5, 125, 245, 365, 35, 155, 275, 395, 65, 185, 305, 425, 95, -215, 335, 455, 20, 140, 260, 380, 50, 170, 290, 410, 80, 200, 320, 440, -110, 230, 350, 470, 10, 130, 250, 370, 40, 160, 280, 400, 70, 190, 310, -430, 100, 220, 340, 460, 25, 145, 265, 385, 55, 175, 295, 415, 85, 205, -325, 445, 115, 235, 355, 475, 1, 121, 241, 361, 31, 151, 271, 391, 61, -181, 301, 421, 91, 211, 331, 451, 16, 136, 256, 376, 46, 166, 286, 406, -76, 196, 316, 436, 106, 226, 346, 466, 6, 126, 246, 366, 36, 156, 276, -396, 66, 186, 306, 426, 96, 216, 336, 456, 21, 141, 261, 381, 51, 171, -291, 411, 81, 201, 321, 441, 111, 231, 351, 471, 11, 131, 251, 371, 41, -161, 281, 401, 71, 191, 311, 431, 101, 221, 341, 461, 26, 146, 266, 386, -56, 176, 296, 416, 86, 206, 326, 446, 116, 236, 356, 476, 2, 122, 242, -362, 32, 152, 272, 392, 62, 182, 302, 422, 92, 212, 332, 452, 17, 137, -257, 377, 47, 167, 287, 407, 77, 197, 317, 437, 107, 227, 347, 467, 7, -127, 247, 367, 37, 157, 277, 397, 67, 187, 307, 427, 97, 217, 337, 457, -22, 142, 262, 382, 52, 172, 292, 412, 82, 202, 322, 442, 112, 232, 352, -472, 12, 132, 252, 372, 42, 162, 282, 402, 72, 192, 312, 432, 102, 222, -342, 462, 27, 147, 267, 387, 57, 177, 297, 417, 87, 207, 327, 447, 117, -237, 357, 477, 3, 123, 243, 363, 33, 153, 273, 393, 63, 183, 303, 423, -93, 213, 333, 453, 18, 138, 258, 378, 48, 168, 288, 408, 78, 198, 318, -438, 108, 228, 348, 468, 8, 128, 248, 368, 38, 158, 278, 398, 68, 188, -308, 428, 98, 218, 338, 458, 23, 143, 263, 383, 53, 173, 293, 413, 83, -203, 323, 443, 113, 233, 353, 473, 13, 133, 253, 373, 43, 163, 283, 403, -73, 193, 313, 433, 103, 223, 343, 463, 28, 148, 268, 388, 58, 178, 298, -418, 88, 208, 328, 448, 118, 238, 358, 478, 4, 124, 244, 364, 34, 154, -274, 394, 64, 184, 304, 424, 94, 214, 334, 454, 19, 139, 259, 379, 49, -169, 289, 409, 79, 199, 319, 439, 109, 229, 349, 469, 9, 129, 249, 369, -39, 159, 279, 399, 69, 189, 309, 429, 99, 219, 339, 459, 24, 144, 264, -384, 54, 174, 294, 414, 84, 204, 324, 444, 114, 234, 354, 474, 14, 134, -254, 374, 44, 164, 284, 404, 74, 194, 314, 434, 104, 224, 344, 464, 29, -149, 269, 389, 59, 179, 299, 419, 89, 209, 329, 449, 119, 239, 359, 479, +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, }; #endif #ifndef FFT_BITREV240 #define FFT_BITREV240 static const opus_int16 fft_bitrev240[240] = { -0, 60, 120, 180, 15, 75, 135, 195, 30, 90, 150, 210, 45, 105, 165, -225, 5, 65, 125, 185, 20, 80, 140, 200, 35, 95, 155, 215, 50, 110, -170, 230, 10, 70, 130, 190, 25, 85, 145, 205, 40, 100, 160, 220, 55, -115, 175, 235, 1, 61, 121, 181, 16, 76, 136, 196, 31, 91, 151, 211, -46, 106, 166, 226, 6, 66, 126, 186, 21, 81, 141, 201, 36, 96, 156, -216, 51, 111, 171, 231, 11, 71, 131, 191, 26, 86, 146, 206, 41, 101, -161, 221, 56, 116, 176, 236, 2, 62, 122, 182, 17, 77, 137, 197, 32, -92, 152, 212, 47, 107, 167, 227, 7, 67, 127, 187, 22, 82, 142, 202, -37, 97, 157, 217, 52, 112, 172, 232, 12, 72, 132, 192, 27, 87, 147, -207, 42, 102, 162, 222, 57, 117, 177, 237, 3, 63, 123, 183, 18, 78, -138, 198, 33, 93, 153, 213, 48, 108, 168, 228, 8, 68, 128, 188, 23, -83, 143, 203, 38, 98, 158, 218, 53, 113, 173, 233, 13, 73, 133, 193, -28, 88, 148, 208, 43, 103, 163, 223, 58, 118, 178, 238, 4, 64, 124, -184, 19, 79, 139, 199, 34, 94, 154, 214, 49, 109, 169, 229, 9, 69, -129, 189, 24, 84, 144, 204, 39, 99, 159, 219, 54, 114, 174, 234, 14, -74, 134, 194, 29, 89, 149, 209, 44, 104, 164, 224, 59, 119, 179, 239, +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, }; #endif #ifndef FFT_BITREV120 #define FFT_BITREV120 static const opus_int16 fft_bitrev120[120] = { -0, 30, 60, 90, 15, 45, 75, 105, 5, 35, 65, 95, 20, 50, 80, -110, 10, 40, 70, 100, 25, 55, 85, 115, 1, 31, 61, 91, 16, 46, -76, 106, 6, 36, 66, 96, 21, 51, 81, 111, 11, 41, 71, 101, 26, -56, 86, 116, 2, 32, 62, 92, 17, 47, 77, 107, 7, 37, 67, 97, -22, 52, 82, 112, 12, 42, 72, 102, 27, 57, 87, 117, 3, 33, 63, -93, 18, 48, 78, 108, 8, 38, 68, 98, 23, 53, 83, 113, 13, 43, -73, 103, 28, 58, 88, 118, 4, 34, 64, 94, 19, 49, 79, 109, 9, -39, 69, 99, 24, 54, 84, 114, 14, 44, 74, 104, 29, 59, 89, 119, +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, }; #endif #ifndef FFT_BITREV60 #define FFT_BITREV60 static const opus_int16 fft_bitrev60[60] = { -0, 15, 30, 45, 5, 20, 35, 50, 10, 25, 40, 55, 1, 16, 31, -46, 6, 21, 36, 51, 11, 26, 41, 56, 2, 17, 32, 47, 7, 22, -37, 52, 12, 27, 42, 57, 3, 18, 33, 48, 8, 23, 38, 53, 13, -28, 43, 58, 4, 19, 34, 49, 9, 24, 39, 54, 14, 29, 44, 59, +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, }; #endif @@ -426,10 +431,17 @@ static const opus_int16 fft_bitrev60[60] = { #define FFT_STATE48000_960_0 static const kiss_fft_state fft_state48000_960_0 = { 480, /* nfft */ +17476, /* scale */ +8, /* scale_shift */ -1, /* shift */ -{4, 120, 4, 30, 2, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev480, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif }; #endif @@ -437,10 +449,17 @@ fft_twiddles48000_960, /* bitrev */ #define FFT_STATE48000_960_1 static const kiss_fft_state fft_state48000_960_1 = { 240, /* nfft */ +17476, /* scale */ +7, /* scale_shift */ 1, /* shift */ -{4, 60, 4, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev240, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif }; #endif @@ -448,10 +467,17 @@ fft_twiddles48000_960, /* bitrev */ #define FFT_STATE48000_960_2 static const kiss_fft_state fft_state48000_960_2 = { 120, /* nfft */ +17476, /* scale */ +6, /* scale_shift */ 2, /* shift */ -{4, 30, 2, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev120, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif }; #endif @@ -459,10 +485,17 @@ fft_twiddles48000_960, /* bitrev */ #define FFT_STATE48000_960_3 static const kiss_fft_state fft_state48000_960_3 = { 60, /* nfft */ +17476, /* scale */ +5, /* scale_shift */ 3, /* shift */ -{4, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev60, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif }; #endif @@ -470,104 +503,368 @@ fft_twiddles48000_960, /* bitrev */ #ifndef MDCT_TWIDDLES960 #define MDCT_TWIDDLES960 -static const opus_val16 mdct_twiddles960[481] = { -32767, 32767, 32767, 32767, 32766, -32763, 32762, 32759, 32757, 32753, -32751, 32747, 32743, 32738, 32733, -32729, 32724, 32717, 32711, 32705, -32698, 32690, 32683, 32676, 32667, -32658, 32650, 32640, 32631, 32620, -32610, 32599, 32588, 32577, 32566, -32554, 32541, 32528, 32515, 32502, -32487, 32474, 32459, 32444, 32429, -32413, 32397, 32381, 32364, 32348, -32331, 32313, 32294, 32277, 32257, -32239, 32219, 32200, 32180, 32159, -32138, 32118, 32096, 32074, 32051, -32029, 32006, 31984, 31960, 31936, -31912, 31888, 31863, 31837, 31812, -31786, 31760, 31734, 31707, 31679, -31652, 31624, 31596, 31567, 31539, -31508, 31479, 31450, 31419, 31388, -31357, 31326, 31294, 31262, 31230, -31198, 31164, 31131, 31097, 31063, -31030, 30994, 30959, 30924, 30889, -30853, 30816, 30779, 30743, 30705, -30668, 30629, 30592, 30553, 30515, -30475, 30435, 30396, 30356, 30315, -30274, 30233, 30191, 30149, 30107, -30065, 30022, 29979, 29936, 29891, -29847, 29803, 29758, 29713, 29668, -29622, 29577, 29529, 29483, 29436, -29390, 29341, 29293, 29246, 29197, -29148, 29098, 29050, 29000, 28949, -28899, 28848, 28797, 28746, 28694, -28642, 28590, 28537, 28485, 28432, -28378, 28324, 28271, 28217, 28162, -28106, 28051, 27995, 27940, 27884, -27827, 27770, 27713, 27657, 27598, -27540, 27481, 27423, 27365, 27305, -27246, 27187, 27126, 27066, 27006, -26945, 26883, 26822, 26760, 26698, -26636, 26574, 26510, 26448, 26383, -26320, 26257, 26191, 26127, 26062, -25997, 25931, 25866, 25800, 25734, -25667, 25601, 25533, 25466, 25398, -25330, 25262, 25194, 25125, 25056, -24987, 24917, 24848, 24778, 24707, -24636, 24566, 24495, 24424, 24352, -24280, 24208, 24135, 24063, 23990, -23917, 23842, 23769, 23695, 23622, -23546, 23472, 23398, 23322, 23246, -23171, 23095, 23018, 22942, 22866, -22788, 22711, 22634, 22557, 22478, -22400, 22322, 22244, 22165, 22085, -22006, 21927, 21846, 21766, 21687, -21606, 21524, 21443, 21363, 21282, -21199, 21118, 21035, 20954, 20870, -20788, 20705, 20621, 20538, 20455, -20371, 20286, 20202, 20118, 20034, -19947, 19863, 19777, 19692, 19606, -19520, 19434, 19347, 19260, 19174, -19088, 18999, 18911, 18825, 18737, -18648, 18560, 18472, 18384, 18294, -18205, 18116, 18025, 17936, 17846, -17757, 17666, 17576, 17485, 17395, -17303, 17212, 17122, 17030, 16937, -16846, 16755, 16662, 16569, 16477, -16385, 16291, 16198, 16105, 16012, -15917, 15824, 15730, 15636, 15541, -15447, 15352, 15257, 15162, 15067, -14973, 14875, 14781, 14685, 14589, -14493, 14396, 14300, 14204, 14107, -14010, 13914, 13815, 13718, 13621, -13524, 13425, 13328, 13230, 13133, -13033, 12935, 12836, 12738, 12638, -12540, 12441, 12341, 12241, 12142, -12044, 11943, 11843, 11744, 11643, -11542, 11442, 11342, 11241, 11139, -11039, 10939, 10836, 10736, 10635, -10534, 10431, 10330, 10228, 10127, -10024, 9921, 9820, 9718, 9614, -9512, 9410, 9306, 9204, 9101, -8998, 8895, 8791, 8689, 8585, -8481, 8377, 8274, 8171, 8067, -7962, 7858, 7753, 7650, 7545, -7441, 7336, 7231, 7129, 7023, -6917, 6813, 6709, 6604, 6498, -6393, 6288, 6182, 6077, 5973, -5867, 5760, 5656, 5549, 5445, -5339, 5232, 5127, 5022, 4914, -4809, 4703, 4596, 4490, 4384, -4278, 4171, 4065, 3958, 3852, -3745, 3640, 3532, 3426, 3318, -3212, 3106, 2998, 2891, 2786, -2679, 2570, 2465, 2358, 2251, -2143, 2037, 1929, 1823, 1715, -1609, 1501, 1393, 1287, 1180, -1073, 964, 858, 751, 644, -535, 429, 322, 214, 107, -0, }; +static const opus_val16 mdct_twiddles960[1800] = { +32767, 32767, 32767, 32766, 32765, +32763, 32761, 32759, 32756, 32753, +32750, 32746, 32742, 32738, 32733, +32728, 32722, 32717, 32710, 32704, +32697, 32690, 32682, 32674, 32666, +32657, 32648, 32639, 32629, 32619, +32609, 32598, 32587, 32576, 32564, +32552, 32539, 32526, 32513, 32500, +32486, 32472, 32457, 32442, 32427, +32411, 32395, 32379, 32362, 32345, +32328, 32310, 32292, 32274, 32255, +32236, 32217, 32197, 32177, 32157, +32136, 32115, 32093, 32071, 32049, +32027, 32004, 31981, 31957, 31933, +31909, 31884, 31859, 31834, 31809, +31783, 31756, 31730, 31703, 31676, +31648, 31620, 31592, 31563, 31534, +31505, 31475, 31445, 31415, 31384, +31353, 31322, 31290, 31258, 31226, +31193, 31160, 31127, 31093, 31059, +31025, 30990, 30955, 30920, 30884, +30848, 30812, 30775, 30738, 30701, +30663, 30625, 30587, 30548, 30509, +30470, 30430, 30390, 30350, 30309, +30269, 30227, 30186, 30144, 30102, +30059, 30016, 29973, 29930, 29886, +29842, 29797, 29752, 29707, 29662, +29616, 29570, 29524, 29477, 29430, +29383, 29335, 29287, 29239, 29190, +29142, 29092, 29043, 28993, 28943, +28892, 28842, 28791, 28739, 28688, +28636, 28583, 28531, 28478, 28425, +28371, 28317, 28263, 28209, 28154, +28099, 28044, 27988, 27932, 27876, +27820, 27763, 27706, 27648, 27591, +27533, 27474, 27416, 27357, 27298, +27238, 27178, 27118, 27058, 26997, +26936, 26875, 26814, 26752, 26690, +26628, 26565, 26502, 26439, 26375, +26312, 26247, 26183, 26119, 26054, +25988, 25923, 25857, 25791, 25725, +25658, 25592, 25524, 25457, 25389, +25322, 25253, 25185, 25116, 25047, +24978, 24908, 24838, 24768, 24698, +24627, 24557, 24485, 24414, 24342, +24270, 24198, 24126, 24053, 23980, +23907, 23834, 23760, 23686, 23612, +23537, 23462, 23387, 23312, 23237, +23161, 23085, 23009, 22932, 22856, +22779, 22701, 22624, 22546, 22468, +22390, 22312, 22233, 22154, 22075, +21996, 21916, 21836, 21756, 21676, +21595, 21515, 21434, 21352, 21271, +21189, 21107, 21025, 20943, 20860, +20777, 20694, 20611, 20528, 20444, +20360, 20276, 20192, 20107, 20022, +19937, 19852, 19767, 19681, 19595, +19509, 19423, 19336, 19250, 19163, +19076, 18988, 18901, 18813, 18725, +18637, 18549, 18460, 18372, 18283, +18194, 18104, 18015, 17925, 17835, +17745, 17655, 17565, 17474, 17383, +17292, 17201, 17110, 17018, 16927, +16835, 16743, 16650, 16558, 16465, +16372, 16279, 16186, 16093, 15999, +15906, 15812, 15718, 15624, 15529, +15435, 15340, 15245, 15150, 15055, +14960, 14864, 14769, 14673, 14577, +14481, 14385, 14288, 14192, 14095, +13998, 13901, 13804, 13706, 13609, +13511, 13414, 13316, 13218, 13119, +13021, 12923, 12824, 12725, 12626, +12527, 12428, 12329, 12230, 12130, +12030, 11930, 11831, 11730, 11630, +11530, 11430, 11329, 11228, 11128, +11027, 10926, 10824, 10723, 10622, +10520, 10419, 10317, 10215, 10113, +10011, 9909, 9807, 9704, 9602, +9499, 9397, 9294, 9191, 9088, +8985, 8882, 8778, 8675, 8572, +8468, 8364, 8261, 8157, 8053, +7949, 7845, 7741, 7637, 7532, +7428, 7323, 7219, 7114, 7009, +6905, 6800, 6695, 6590, 6485, +6380, 6274, 6169, 6064, 5958, +5853, 5747, 5642, 5536, 5430, +5325, 5219, 5113, 5007, 4901, +4795, 4689, 4583, 4476, 4370, +4264, 4157, 4051, 3945, 3838, +3732, 3625, 3518, 3412, 3305, +3198, 3092, 2985, 2878, 2771, +2664, 2558, 2451, 2344, 2237, +2130, 2023, 1916, 1809, 1702, +1594, 1487, 1380, 1273, 1166, +1059, 952, 844, 737, 630, +523, 416, 308, 201, 94, +-13, -121, -228, -335, -442, +-550, -657, -764, -871, -978, +-1086, -1193, -1300, -1407, -1514, +-1621, -1728, -1835, -1942, -2049, +-2157, -2263, -2370, -2477, -2584, +-2691, -2798, -2905, -3012, -3118, +-3225, -3332, -3439, -3545, -3652, +-3758, -3865, -3971, -4078, -4184, +-4290, -4397, -4503, -4609, -4715, +-4821, -4927, -5033, -5139, -5245, +-5351, -5457, -5562, -5668, -5774, +-5879, -5985, -6090, -6195, -6301, +-6406, -6511, -6616, -6721, -6826, +-6931, -7036, -7140, -7245, -7349, +-7454, -7558, -7663, -7767, -7871, +-7975, -8079, -8183, -8287, -8390, +-8494, -8597, -8701, -8804, -8907, +-9011, -9114, -9217, -9319, -9422, +-9525, -9627, -9730, -9832, -9934, +-10037, -10139, -10241, -10342, -10444, +-10546, -10647, -10748, -10850, -10951, +-11052, -11153, -11253, -11354, -11455, +-11555, -11655, -11756, -11856, -11955, +-12055, -12155, -12254, -12354, -12453, +-12552, -12651, -12750, -12849, -12947, +-13046, -13144, -13242, -13340, -13438, +-13536, -13633, -13731, -13828, -13925, +-14022, -14119, -14216, -14312, -14409, +-14505, -14601, -14697, -14793, -14888, +-14984, -15079, -15174, -15269, -15364, +-15459, -15553, -15647, -15741, -15835, +-15929, -16023, -16116, -16210, -16303, +-16396, -16488, -16581, -16673, -16766, +-16858, -16949, -17041, -17133, -17224, +-17315, -17406, -17497, -17587, -17678, +-17768, -17858, -17948, -18037, -18127, +-18216, -18305, -18394, -18483, -18571, +-18659, -18747, -18835, -18923, -19010, +-19098, -19185, -19271, -19358, -19444, +-19531, -19617, -19702, -19788, -19873, +-19959, -20043, -20128, -20213, -20297, +-20381, -20465, -20549, -20632, -20715, +-20798, -20881, -20963, -21046, -21128, +-21210, -21291, -21373, -21454, -21535, +-21616, -21696, -21776, -21856, -21936, +-22016, -22095, -22174, -22253, -22331, +-22410, -22488, -22566, -22643, -22721, +-22798, -22875, -22951, -23028, -23104, +-23180, -23256, -23331, -23406, -23481, +-23556, -23630, -23704, -23778, -23852, +-23925, -23998, -24071, -24144, -24216, +-24288, -24360, -24432, -24503, -24574, +-24645, -24716, -24786, -24856, -24926, +-24995, -25064, -25133, -25202, -25270, +-25339, -25406, -25474, -25541, -25608, +-25675, -25742, -25808, -25874, -25939, +-26005, -26070, -26135, -26199, -26264, +-26327, -26391, -26455, -26518, -26581, +-26643, -26705, -26767, -26829, -26891, +-26952, -27013, -27073, -27133, -27193, +-27253, -27312, -27372, -27430, -27489, +-27547, -27605, -27663, -27720, -27777, +-27834, -27890, -27946, -28002, -28058, +-28113, -28168, -28223, -28277, -28331, +-28385, -28438, -28491, -28544, -28596, +-28649, -28701, -28752, -28803, -28854, +-28905, -28955, -29006, -29055, -29105, +-29154, -29203, -29251, -29299, -29347, +-29395, -29442, -29489, -29535, -29582, +-29628, -29673, -29719, -29764, -29808, +-29853, -29897, -29941, -29984, -30027, +-30070, -30112, -30154, -30196, -30238, +-30279, -30320, -30360, -30400, -30440, +-30480, -30519, -30558, -30596, -30635, +-30672, -30710, -30747, -30784, -30821, +-30857, -30893, -30929, -30964, -30999, +-31033, -31068, -31102, -31135, -31168, +-31201, -31234, -31266, -31298, -31330, +-31361, -31392, -31422, -31453, -31483, +-31512, -31541, -31570, -31599, -31627, +-31655, -31682, -31710, -31737, -31763, +-31789, -31815, -31841, -31866, -31891, +-31915, -31939, -31963, -31986, -32010, +-32032, -32055, -32077, -32099, -32120, +-32141, -32162, -32182, -32202, -32222, +-32241, -32260, -32279, -32297, -32315, +-32333, -32350, -32367, -32383, -32399, +-32415, -32431, -32446, -32461, -32475, +-32489, -32503, -32517, -32530, -32542, +-32555, -32567, -32579, -32590, -32601, +-32612, -32622, -32632, -32641, -32651, +-32659, -32668, -32676, -32684, -32692, +-32699, -32706, -32712, -32718, -32724, +-32729, -32734, -32739, -32743, -32747, +-32751, -32754, -32757, -32760, -32762, +-32764, -32765, -32767, -32767, -32767, +32767, 32767, 32765, 32761, 32756, +32750, 32742, 32732, 32722, 32710, +32696, 32681, 32665, 32647, 32628, +32608, 32586, 32562, 32538, 32512, +32484, 32455, 32425, 32393, 32360, +32326, 32290, 32253, 32214, 32174, +32133, 32090, 32046, 32001, 31954, +31906, 31856, 31805, 31753, 31700, +31645, 31588, 31530, 31471, 31411, +31349, 31286, 31222, 31156, 31089, +31020, 30951, 30880, 30807, 30733, +30658, 30582, 30504, 30425, 30345, +30263, 30181, 30096, 30011, 29924, +29836, 29747, 29656, 29564, 29471, +29377, 29281, 29184, 29086, 28987, +28886, 28784, 28681, 28577, 28471, +28365, 28257, 28147, 28037, 27925, +27812, 27698, 27583, 27467, 27349, +27231, 27111, 26990, 26868, 26744, +26620, 26494, 26367, 26239, 26110, +25980, 25849, 25717, 25583, 25449, +25313, 25176, 25038, 24900, 24760, +24619, 24477, 24333, 24189, 24044, +23898, 23751, 23602, 23453, 23303, +23152, 22999, 22846, 22692, 22537, +22380, 22223, 22065, 21906, 21746, +21585, 21423, 21261, 21097, 20933, +20767, 20601, 20434, 20265, 20096, +19927, 19756, 19584, 19412, 19239, +19065, 18890, 18714, 18538, 18361, +18183, 18004, 17824, 17644, 17463, +17281, 17098, 16915, 16731, 16546, +16361, 16175, 15988, 15800, 15612, +15423, 15234, 15043, 14852, 14661, +14469, 14276, 14083, 13889, 13694, +13499, 13303, 13107, 12910, 12713, +12515, 12317, 12118, 11918, 11718, +11517, 11316, 11115, 10913, 10710, +10508, 10304, 10100, 9896, 9691, +9486, 9281, 9075, 8869, 8662, +8455, 8248, 8040, 7832, 7623, +7415, 7206, 6996, 6787, 6577, +6366, 6156, 5945, 5734, 5523, +5311, 5100, 4888, 4675, 4463, +4251, 4038, 3825, 3612, 3399, +3185, 2972, 2758, 2544, 2330, +2116, 1902, 1688, 1474, 1260, +1045, 831, 617, 402, 188, +-27, -241, -456, -670, -885, +-1099, -1313, -1528, -1742, -1956, +-2170, -2384, -2598, -2811, -3025, +-3239, -3452, -3665, -3878, -4091, +-4304, -4516, -4728, -4941, -5153, +-5364, -5576, -5787, -5998, -6209, +-6419, -6629, -6839, -7049, -7258, +-7467, -7676, -7884, -8092, -8300, +-8507, -8714, -8920, -9127, -9332, +-9538, -9743, -9947, -10151, -10355, +-10558, -10761, -10963, -11165, -11367, +-11568, -11768, -11968, -12167, -12366, +-12565, -12762, -12960, -13156, -13352, +-13548, -13743, -13937, -14131, -14324, +-14517, -14709, -14900, -15091, -15281, +-15470, -15659, -15847, -16035, -16221, +-16407, -16593, -16777, -16961, -17144, +-17326, -17508, -17689, -17869, -18049, +-18227, -18405, -18582, -18758, -18934, +-19108, -19282, -19455, -19627, -19799, +-19969, -20139, -20308, -20475, -20642, +-20809, -20974, -21138, -21301, -21464, +-21626, -21786, -21946, -22105, -22263, +-22420, -22575, -22730, -22884, -23037, +-23189, -23340, -23490, -23640, -23788, +-23935, -24080, -24225, -24369, -24512, +-24654, -24795, -24934, -25073, -25211, +-25347, -25482, -25617, -25750, -25882, +-26013, -26143, -26272, -26399, -26526, +-26651, -26775, -26898, -27020, -27141, +-27260, -27379, -27496, -27612, -27727, +-27841, -27953, -28065, -28175, -28284, +-28391, -28498, -28603, -28707, -28810, +-28911, -29012, -29111, -29209, -29305, +-29401, -29495, -29587, -29679, -29769, +-29858, -29946, -30032, -30118, -30201, +-30284, -30365, -30445, -30524, -30601, +-30677, -30752, -30825, -30897, -30968, +-31038, -31106, -31172, -31238, -31302, +-31365, -31426, -31486, -31545, -31602, +-31658, -31713, -31766, -31818, -31869, +-31918, -31966, -32012, -32058, -32101, +-32144, -32185, -32224, -32262, -32299, +-32335, -32369, -32401, -32433, -32463, +-32491, -32518, -32544, -32568, -32591, +-32613, -32633, -32652, -32669, -32685, +-32700, -32713, -32724, -32735, -32744, +-32751, -32757, -32762, -32766, -32767, +32767, 32764, 32755, 32741, 32720, +32694, 32663, 32626, 32583, 32535, +32481, 32421, 32356, 32286, 32209, +32128, 32041, 31948, 31850, 31747, +31638, 31523, 31403, 31278, 31148, +31012, 30871, 30724, 30572, 30415, +30253, 30086, 29913, 29736, 29553, +29365, 29172, 28974, 28771, 28564, +28351, 28134, 27911, 27684, 27452, +27216, 26975, 26729, 26478, 26223, +25964, 25700, 25432, 25159, 24882, +24601, 24315, 24026, 23732, 23434, +23133, 22827, 22517, 22204, 21886, +21565, 21240, 20912, 20580, 20244, +19905, 19563, 19217, 18868, 18516, +18160, 17802, 17440, 17075, 16708, +16338, 15964, 15588, 15210, 14829, +14445, 14059, 13670, 13279, 12886, +12490, 12093, 11693, 11291, 10888, +10482, 10075, 9666, 9255, 8843, +8429, 8014, 7597, 7180, 6760, +6340, 5919, 5496, 5073, 4649, +4224, 3798, 3372, 2945, 2517, +2090, 1661, 1233, 804, 375, +-54, -483, -911, -1340, -1768, +-2197, -2624, -3052, -3479, -3905, +-4330, -4755, -5179, -5602, -6024, +-6445, -6865, -7284, -7702, -8118, +-8533, -8946, -9358, -9768, -10177, +-10584, -10989, -11392, -11793, -12192, +-12589, -12984, -13377, -13767, -14155, +-14541, -14924, -15305, -15683, -16058, +-16430, -16800, -17167, -17531, -17892, +-18249, -18604, -18956, -19304, -19649, +-19990, -20329, -20663, -20994, -21322, +-21646, -21966, -22282, -22595, -22904, +-23208, -23509, -23806, -24099, -24387, +-24672, -24952, -25228, -25499, -25766, +-26029, -26288, -26541, -26791, -27035, +-27275, -27511, -27741, -27967, -28188, +-28405, -28616, -28823, -29024, -29221, +-29412, -29599, -29780, -29957, -30128, +-30294, -30455, -30611, -30761, -30906, +-31046, -31181, -31310, -31434, -31552, +-31665, -31773, -31875, -31972, -32063, +-32149, -32229, -32304, -32373, -32437, +-32495, -32547, -32594, -32635, -32671, +-32701, -32726, -32745, -32758, -32766, +32767, 32754, 32717, 32658, 32577, +32473, 32348, 32200, 32029, 31837, +31624, 31388, 31131, 30853, 30553, +30232, 29891, 29530, 29148, 28746, +28324, 27883, 27423, 26944, 26447, +25931, 25398, 24847, 24279, 23695, +23095, 22478, 21846, 21199, 20538, +19863, 19174, 18472, 17757, 17030, +16291, 15541, 14781, 14010, 13230, +12441, 11643, 10837, 10024, 9204, +8377, 7545, 6708, 5866, 5020, +4171, 3319, 2464, 1608, 751, +-107, -965, -1822, -2678, -3532, +-4383, -5232, -6077, -6918, -7754, +-8585, -9409, -10228, -11039, -11843, +-12639, -13426, -14204, -14972, -15730, +-16477, -17213, -17937, -18648, -19347, +-20033, -20705, -21363, -22006, -22634, +-23246, -23843, -24423, -24986, -25533, +-26062, -26573, -27066, -27540, -27995, +-28431, -28848, -29245, -29622, -29979, +-30315, -30630, -30924, -31197, -31449, +-31679, -31887, -32074, -32239, -32381, +-32501, -32600, -32675, -32729, -32759, +}; #endif static const CELTMode mode48000_960_120 = { diff --git a/TMessagesProj/jni/opus/celt/static_modes_fixed_arm_ne10.h b/TMessagesProj/jni/opus/celt/static_modes_fixed_arm_ne10.h new file mode 100644 index 00000000000..b8ef0cee983 --- /dev/null +++ b/TMessagesProj/jni/opus/celt/static_modes_fixed_arm_ne10.h @@ -0,0 +1,388 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_int32_t ne10_twiddles_480[480] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +{2147483647,0}, {2147299667,-28109693}, {2146747758,-56214570}, +{2145828015,-84309815}, {2144540595,-112390613}, {2142885719,-140452154}, +{2140863671,-168489630}, {2138474797,-196498235}, {2135719506,-224473172}, +{2132598271,-252409646}, {2129111626,-280302871}, {2125260168,-308148068}, +{2121044558,-335940465}, {2116465518,-363675300}, {2111523833,-391347822}, +{2106220349,-418953288}, {2100555974,-446486968}, {2094531681,-473944146}, +{2088148500,-501320115}, {2081407525,-528610186}, {2074309912,-555809682}, +{2066856885,-582913912}, {2059049696,-609918325}, {2050889698,-636818231}, +{2042378310,-663608960}, {2033516972,-690285983}, {2024307180,-716844791}, +{2014750533,-743280770}, {2004848691,-769589332}, {1994603329,-795766029}, +{1984016179,-821806435}, {1973089077,-847706028}, {1961823921,-873460313}, +{1950222618,-899064934}, {1938287127,-924515564}, {1926019520,-949807783}, +{1913421927,-974937199}, {1900496481,-999899565}, {1887245364,-1024690661}, +{1873670877,-1049306180}, {1859775377,-1073741851}, {1845561215,-1097993541}, +{1831030826,-1122057097}, {1816186632,-1145928502}, {1801031311,-1169603450}, +{1785567394,-1193077993}, {1769797456,-1216348214}, {1753724345,-1239409914}, +{1737350743,-1262259248}, {1720679456,-1284892300}, {1703713340,-1307305194}, +{1686455222,-1329494189}, {1668908218,-1351455280}, {1651075255,-1373184807}, +{1632959307,-1394679144}, {1614563642,-1415934412}, {1595891331,-1436947067}, +{1576945572,-1457713510}, {1557729613,-1478230181}, {1538246655,-1498493658}, +{1518500216,-1518500282}, {1498493590,-1538246721}, {1478230113,-1557729677}, +{1457713441,-1576945636}, {1436946998,-1595891394}, {1415934341,-1614563704}, +{1394679073,-1632959368}, {1373184735,-1651075315}, {1351455207,-1668908277}, +{1329494115,-1686455280}, {1307305120,-1703713397}, {1284892225,-1720679512}, +{1262259172,-1737350799}, {1239409837,-1753724400}, {1216348136,-1769797510}, +{1193077915,-1785567446}, {1169603371,-1801031362}, {1145928423,-1816186682}, +{1122057017,-1831030875}, {1097993571,-1845561197}, {1073741769,-1859775424}, +{1049305987,-1873670985}, {1024690635,-1887245378}, {999899482,-1900496524}, +{974937230,-1913421912}, {949807699,-1926019561}, {924515422,-1938287195}, +{899064965,-1950222603}, {873460227,-1961823959}, {847705824,-1973089164}, +{821806407,-1984016190}, {795765941,-1994603364}, {769589125,-2004848771}, +{743280682,-2014750566}, {716844642,-2024307233}, {690286016,-2033516961}, +{663608871,-2042378339}, {636818019,-2050889764}, {609918296,-2059049705}, +{582913822,-2066856911}, {555809715,-2074309903}, {528610126,-2081407540}, +{501319962,-2088148536}, {473944148,-2094531680}, {446486876,-2100555994}, +{418953102,-2106220386}, {391347792,-2111523838}, {363675176,-2116465540}, +{335940246,-2121044593}, {308148006,-2125260177}, {280302715,-2129111646}, +{252409648,-2132598271}, {224473078,-2135719516}, {196498046,-2138474814}, +{168489600,-2140863674}, {140452029,-2142885728}, {112390647,-2144540593}, +{84309753,-2145828017}, {56214412,-2146747762}, {28109695,-2147299667}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{-94,-2147483647}, {-56214600,-2146747757}, {-112390835,-2144540584}, +{-168489787,-2140863659}, {-224473265,-2135719496}, {-280302901,-2129111622}, +{-335940431,-2121044564}, {-391347977,-2111523804}, {-446487060,-2100555955}, +{-501320144,-2088148493}, {-555809896,-2074309855}, {-609918476,-2059049651}, +{-663609049,-2042378281}, {-716844819,-2024307170}, {-769589300,-2004848703}, +{-821806581,-1984016118}, {-873460398,-1961823883}, {-924515591,-1938287114}, +{-974937397,-1913421827}, {-1024690575,-1887245411}, {-1073741932,-1859775330}, +{-1122057395,-1831030643}, {-1169603421,-1801031330}, {-1216348291,-1769797403}, +{-1262259116,-1737350839}, {-1307305268,-1703713283}, {-1351455453,-1668908078}, +{-1394679021,-1632959413}, {-1436947137,-1595891268}, {-1478230435,-1557729372}, +{-1518500258,-1518500240}, {-1557729742,-1478230045}, {-1595891628,-1436946738}, +{-1632959429,-1394679001}, {-1668908417,-1351455035}, {-1703713298,-1307305248}, +{-1737350854,-1262259096}, {-1769797708,-1216347848}, {-1801031344,-1169603400}, +{-1831030924,-1122056937}, {-1859775343,-1073741910}, {-1887245423,-1024690552}, +{-1913422071,-974936918}, {-1938287125,-924515568}, {-1961823997,-873460141}, +{-1984016324,-821806084}, {-2004848713,-769589276}, {-2024307264,-716844553}, +{-2042378447,-663608538}, {-2059049731,-609918206}, {-2074309994,-555809377}, +{-2088148499,-501320119}, {-2100556013,-446486785}, {-2111523902,-391347448}, +{-2121044568,-335940406}, {-2129111659,-280302621}, {-2135719499,-224473240}, +{-2140863681,-168489506}, {-2144540612,-112390298}, {-2146747758,-56214574}, +{2147483647,0}, {2145828015,-84309815}, {2140863671,-168489630}, +{2132598271,-252409646}, {2121044558,-335940465}, {2106220349,-418953288}, +{2088148500,-501320115}, {2066856885,-582913912}, {2042378310,-663608960}, +{2014750533,-743280770}, {1984016179,-821806435}, {1950222618,-899064934}, +{1913421927,-974937199}, {1873670877,-1049306180}, {1831030826,-1122057097}, +{1785567394,-1193077993}, {1737350743,-1262259248}, {1686455222,-1329494189}, +{1632959307,-1394679144}, {1576945572,-1457713510}, {1518500216,-1518500282}, +{1457713441,-1576945636}, {1394679073,-1632959368}, {1329494115,-1686455280}, +{1262259172,-1737350799}, {1193077915,-1785567446}, {1122057017,-1831030875}, +{1049305987,-1873670985}, {974937230,-1913421912}, {899064965,-1950222603}, +{821806407,-1984016190}, {743280682,-2014750566}, {663608871,-2042378339}, +{582913822,-2066856911}, {501319962,-2088148536}, {418953102,-2106220386}, +{335940246,-2121044593}, {252409648,-2132598271}, {168489600,-2140863674}, +{84309753,-2145828017}, {-94,-2147483647}, {-84309940,-2145828010}, +{-168489787,-2140863659}, {-252409834,-2132598249}, {-335940431,-2121044564}, +{-418953286,-2106220349}, {-501320144,-2088148493}, {-582914003,-2066856860}, +{-663609049,-2042378281}, {-743280858,-2014750501}, {-821806581,-1984016118}, +{-899065136,-1950222525}, {-974937397,-1913421827}, {-1049306374,-1873670768}, +{-1122057395,-1831030643}, {-1193078284,-1785567199}, {-1262259116,-1737350839}, +{-1329494061,-1686455323}, {-1394679021,-1632959413}, {-1457713485,-1576945595}, +{-1518500258,-1518500240}, {-1576945613,-1457713466}, {-1632959429,-1394679001}, +{-1686455338,-1329494041}, {-1737350854,-1262259096}, {-1785567498,-1193077837}, +{-1831030924,-1122056937}, {-1873671031,-1049305905}, {-1913422071,-974936918}, +{-1950222750,-899064648}, {-1984016324,-821806084}, {-2014750687,-743280354}, +{-2042378447,-663608538}, {-2066856867,-582913978}, {-2088148499,-501320119}, +{-2106220354,-418953261}, {-2121044568,-335940406}, {-2132598282,-252409555}, +{-2140863681,-168489506}, {-2145828021,-84309659}, {-2147483647,188}, +{-2145828006,84310034}, {-2140863651,168489881}, {-2132598237,252409928}, +{-2121044509,335940777}, {-2106220281,418953629}, {-2088148411,501320484}, +{-2066856765,582914339}, {-2042378331,663608895}, {-2014750557,743280706}, +{-1984016181,821806431}, {-1950222593,899064989}, {-1913421900,974937252}, +{-1873670848,1049306232}, {-1831030728,1122057257}, {-1785567289,1193078149}, +{-1737350633,1262259400}, {-1686455106,1329494336}, {-1632959185,1394679287}, +{-1576945358,1457713742}, {-1518499993,1518500506}, {-1457713209,1576945850}, +{-1394678735,1632959656}, {-1329493766,1686455555}, {-1262258813,1737351059}, +{-1193077546,1785567692}, {-1122056638,1831031107}, {-1049305599,1873671202}, +{-974936606,1913422229}, {-899064330,1950222896}, {-821805761,1984016458}, +{-743280025,2014750808}, {-663609179,2042378239}, {-582914134,2066856823}, +{-501320277,2088148461}, {-418953420,2106220322}, {-335940566,2121044542}, +{-252409716,2132598263}, {-168489668,2140863668}, {-84309821,2145828015}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_240[240] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +{2147483647,0}, {2146747758,-56214570}, {2144540595,-112390613}, +{2140863671,-168489630}, {2135719506,-224473172}, {2129111626,-280302871}, +{2121044558,-335940465}, {2111523833,-391347822}, {2100555974,-446486968}, +{2088148500,-501320115}, {2074309912,-555809682}, {2059049696,-609918325}, +{2042378310,-663608960}, {2024307180,-716844791}, {2004848691,-769589332}, +{1984016179,-821806435}, {1961823921,-873460313}, {1938287127,-924515564}, +{1913421927,-974937199}, {1887245364,-1024690661}, {1859775377,-1073741851}, +{1831030826,-1122057097}, {1801031311,-1169603450}, {1769797456,-1216348214}, +{1737350743,-1262259248}, {1703713340,-1307305194}, {1668908218,-1351455280}, +{1632959307,-1394679144}, {1595891331,-1436947067}, {1557729613,-1478230181}, +{1518500216,-1518500282}, {1478230113,-1557729677}, {1436946998,-1595891394}, +{1394679073,-1632959368}, {1351455207,-1668908277}, {1307305120,-1703713397}, +{1262259172,-1737350799}, {1216348136,-1769797510}, {1169603371,-1801031362}, +{1122057017,-1831030875}, {1073741769,-1859775424}, {1024690635,-1887245378}, +{974937230,-1913421912}, {924515422,-1938287195}, {873460227,-1961823959}, +{821806407,-1984016190}, {769589125,-2004848771}, {716844642,-2024307233}, +{663608871,-2042378339}, {609918296,-2059049705}, {555809715,-2074309903}, +{501319962,-2088148536}, {446486876,-2100555994}, {391347792,-2111523838}, +{335940246,-2121044593}, {280302715,-2129111646}, {224473078,-2135719516}, +{168489600,-2140863674}, {112390647,-2144540593}, {56214412,-2146747762}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{-94,-2147483647}, {-112390835,-2144540584}, {-224473265,-2135719496}, +{-335940431,-2121044564}, {-446487060,-2100555955}, {-555809896,-2074309855}, +{-663609049,-2042378281}, {-769589300,-2004848703}, {-873460398,-1961823883}, +{-974937397,-1913421827}, {-1073741932,-1859775330}, {-1169603421,-1801031330}, +{-1262259116,-1737350839}, {-1351455453,-1668908078}, {-1436947137,-1595891268}, +{-1518500258,-1518500240}, {-1595891628,-1436946738}, {-1668908417,-1351455035}, +{-1737350854,-1262259096}, {-1801031344,-1169603400}, {-1859775343,-1073741910}, +{-1913422071,-974936918}, {-1961823997,-873460141}, {-2004848713,-769589276}, +{-2042378447,-663608538}, {-2074309994,-555809377}, {-2100556013,-446486785}, +{-2121044568,-335940406}, {-2135719499,-224473240}, {-2144540612,-112390298}, +{2147483647,0}, {2140863671,-168489630}, {2121044558,-335940465}, +{2088148500,-501320115}, {2042378310,-663608960}, {1984016179,-821806435}, +{1913421927,-974937199}, {1831030826,-1122057097}, {1737350743,-1262259248}, +{1632959307,-1394679144}, {1518500216,-1518500282}, {1394679073,-1632959368}, +{1262259172,-1737350799}, {1122057017,-1831030875}, {974937230,-1913421912}, +{821806407,-1984016190}, {663608871,-2042378339}, {501319962,-2088148536}, +{335940246,-2121044593}, {168489600,-2140863674}, {-94,-2147483647}, +{-168489787,-2140863659}, {-335940431,-2121044564}, {-501320144,-2088148493}, +{-663609049,-2042378281}, {-821806581,-1984016118}, {-974937397,-1913421827}, +{-1122057395,-1831030643}, {-1262259116,-1737350839}, {-1394679021,-1632959413}, +{-1518500258,-1518500240}, {-1632959429,-1394679001}, {-1737350854,-1262259096}, +{-1831030924,-1122056937}, {-1913422071,-974936918}, {-1984016324,-821806084}, +{-2042378447,-663608538}, {-2088148499,-501320119}, {-2121044568,-335940406}, +{-2140863681,-168489506}, {-2147483647,188}, {-2140863651,168489881}, +{-2121044509,335940777}, {-2088148411,501320484}, {-2042378331,663608895}, +{-1984016181,821806431}, {-1913421900,974937252}, {-1831030728,1122057257}, +{-1737350633,1262259400}, {-1632959185,1394679287}, {-1518499993,1518500506}, +{-1394678735,1632959656}, {-1262258813,1737351059}, {-1122056638,1831031107}, +{-974936606,1913422229}, {-821805761,1984016458}, {-663609179,2042378239}, +{-501320277,2088148461}, {-335940566,2121044542}, {-168489668,2140863668}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_120[120] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2144540595,-112390613}, {2135719506,-224473172}, +{2121044558,-335940465}, {2100555974,-446486968}, {2074309912,-555809682}, +{2042378310,-663608960}, {2004848691,-769589332}, {1961823921,-873460313}, +{1913421927,-974937199}, {1859775377,-1073741851}, {1801031311,-1169603450}, +{1737350743,-1262259248}, {1668908218,-1351455280}, {1595891331,-1436947067}, +{1518500216,-1518500282}, {1436946998,-1595891394}, {1351455207,-1668908277}, +{1262259172,-1737350799}, {1169603371,-1801031362}, {1073741769,-1859775424}, +{974937230,-1913421912}, {873460227,-1961823959}, {769589125,-2004848771}, +{663608871,-2042378339}, {555809715,-2074309903}, {446486876,-2100555994}, +{335940246,-2121044593}, {224473078,-2135719516}, {112390647,-2144540593}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{-94,-2147483647}, {-224473265,-2135719496}, {-446487060,-2100555955}, +{-663609049,-2042378281}, {-873460398,-1961823883}, {-1073741932,-1859775330}, +{-1262259116,-1737350839}, {-1436947137,-1595891268}, {-1595891628,-1436946738}, +{-1737350854,-1262259096}, {-1859775343,-1073741910}, {-1961823997,-873460141}, +{-2042378447,-663608538}, {-2100556013,-446486785}, {-2135719499,-224473240}, +{2147483647,0}, {2121044558,-335940465}, {2042378310,-663608960}, +{1913421927,-974937199}, {1737350743,-1262259248}, {1518500216,-1518500282}, +{1262259172,-1737350799}, {974937230,-1913421912}, {663608871,-2042378339}, +{335940246,-2121044593}, {-94,-2147483647}, {-335940431,-2121044564}, +{-663609049,-2042378281}, {-974937397,-1913421827}, {-1262259116,-1737350839}, +{-1518500258,-1518500240}, {-1737350854,-1262259096}, {-1913422071,-974936918}, +{-2042378447,-663608538}, {-2121044568,-335940406}, {-2147483647,188}, +{-2121044509,335940777}, {-2042378331,663608895}, {-1913421900,974937252}, +{-1737350633,1262259400}, {-1518499993,1518500506}, {-1262258813,1737351059}, +{-974936606,1913422229}, {-663609179,2042378239}, {-335940566,2121044542}, +}; +static const ne10_fft_cpx_int32_t ne10_twiddles_60[60] = { +{0,0}, {2147483647,0}, {2147483647,0}, +{2147483647,0}, {1961823921,-873460313}, {1436946998,-1595891394}, +{2147483647,0}, {1436946998,-1595891394}, {-224473265,-2135719496}, +{2147483647,0}, {663608871,-2042378339}, {-1737350854,-1262259096}, +{2147483647,0}, {-224473265,-2135719496}, {-2100555935,446487152}, +{2147483647,0}, {2135719506,-224473172}, {2100555974,-446486968}, +{2042378310,-663608960}, {1961823921,-873460313}, {1859775377,-1073741851}, +{1737350743,-1262259248}, {1595891331,-1436947067}, {1436946998,-1595891394}, +{1262259172,-1737350799}, {1073741769,-1859775424}, {873460227,-1961823959}, +{663608871,-2042378339}, {446486876,-2100555994}, {224473078,-2135719516}, +{2147483647,0}, {2100555974,-446486968}, {1961823921,-873460313}, +{1737350743,-1262259248}, {1436946998,-1595891394}, {1073741769,-1859775424}, +{663608871,-2042378339}, {224473078,-2135719516}, {-224473265,-2135719496}, +{-663609049,-2042378281}, {-1073741932,-1859775330}, {-1436947137,-1595891268}, +{-1737350854,-1262259096}, {-1961823997,-873460141}, {-2100556013,-446486785}, +{2147483647,0}, {2042378310,-663608960}, {1737350743,-1262259248}, +{1262259172,-1737350799}, {663608871,-2042378339}, {-94,-2147483647}, +{-663609049,-2042378281}, {-1262259116,-1737350839}, {-1737350854,-1262259096}, +{-2042378447,-663608538}, {-2147483647,188}, {-2042378331,663608895}, +{-1737350633,1262259400}, {-1262258813,1737351059}, {-663609179,2042378239}, +}; +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_int32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_480[120], +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_int32_t_480, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_int32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_240[60], +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_int32_t_240, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_int32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_120[30], +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_int32_t_120, +}; + +static const ne10_fft_state_int32_t ne10_fft_state_int32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_int32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_int32_t *)&ne10_twiddles_60[15], +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_int32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/TMessagesProj/jni/opus/celt/static_modes_float.h b/TMessagesProj/jni/opus/celt/static_modes_float.h index 5d7e7b8e684..e102a383918 100644 --- a/TMessagesProj/jni/opus/celt/static_modes_float.h +++ b/TMessagesProj/jni/opus/celt/static_modes_float.h @@ -4,6 +4,11 @@ #include "modes.h" #include "rate.h" +#ifdef HAVE_ARM_NE10 +#define OVERRIDE_FFT 1 +#include "static_modes_float_arm_ne10.h" +#endif + #ifndef DEF_WINDOW120 #define DEF_WINDOW120 static const opus_val16 window120[120] = { @@ -341,84 +346,84 @@ static const kiss_twiddle_cpx fft_twiddles48000_960[480] = { #ifndef FFT_BITREV480 #define FFT_BITREV480 static const opus_int16 fft_bitrev480[480] = { -0, 120, 240, 360, 30, 150, 270, 390, 60, 180, 300, 420, 90, 210, 330, -450, 15, 135, 255, 375, 45, 165, 285, 405, 75, 195, 315, 435, 105, 225, -345, 465, 5, 125, 245, 365, 35, 155, 275, 395, 65, 185, 305, 425, 95, -215, 335, 455, 20, 140, 260, 380, 50, 170, 290, 410, 80, 200, 320, 440, -110, 230, 350, 470, 10, 130, 250, 370, 40, 160, 280, 400, 70, 190, 310, -430, 100, 220, 340, 460, 25, 145, 265, 385, 55, 175, 295, 415, 85, 205, -325, 445, 115, 235, 355, 475, 1, 121, 241, 361, 31, 151, 271, 391, 61, -181, 301, 421, 91, 211, 331, 451, 16, 136, 256, 376, 46, 166, 286, 406, -76, 196, 316, 436, 106, 226, 346, 466, 6, 126, 246, 366, 36, 156, 276, -396, 66, 186, 306, 426, 96, 216, 336, 456, 21, 141, 261, 381, 51, 171, -291, 411, 81, 201, 321, 441, 111, 231, 351, 471, 11, 131, 251, 371, 41, -161, 281, 401, 71, 191, 311, 431, 101, 221, 341, 461, 26, 146, 266, 386, -56, 176, 296, 416, 86, 206, 326, 446, 116, 236, 356, 476, 2, 122, 242, -362, 32, 152, 272, 392, 62, 182, 302, 422, 92, 212, 332, 452, 17, 137, -257, 377, 47, 167, 287, 407, 77, 197, 317, 437, 107, 227, 347, 467, 7, -127, 247, 367, 37, 157, 277, 397, 67, 187, 307, 427, 97, 217, 337, 457, -22, 142, 262, 382, 52, 172, 292, 412, 82, 202, 322, 442, 112, 232, 352, -472, 12, 132, 252, 372, 42, 162, 282, 402, 72, 192, 312, 432, 102, 222, -342, 462, 27, 147, 267, 387, 57, 177, 297, 417, 87, 207, 327, 447, 117, -237, 357, 477, 3, 123, 243, 363, 33, 153, 273, 393, 63, 183, 303, 423, -93, 213, 333, 453, 18, 138, 258, 378, 48, 168, 288, 408, 78, 198, 318, -438, 108, 228, 348, 468, 8, 128, 248, 368, 38, 158, 278, 398, 68, 188, -308, 428, 98, 218, 338, 458, 23, 143, 263, 383, 53, 173, 293, 413, 83, -203, 323, 443, 113, 233, 353, 473, 13, 133, 253, 373, 43, 163, 283, 403, -73, 193, 313, 433, 103, 223, 343, 463, 28, 148, 268, 388, 58, 178, 298, -418, 88, 208, 328, 448, 118, 238, 358, 478, 4, 124, 244, 364, 34, 154, -274, 394, 64, 184, 304, 424, 94, 214, 334, 454, 19, 139, 259, 379, 49, -169, 289, 409, 79, 199, 319, 439, 109, 229, 349, 469, 9, 129, 249, 369, -39, 159, 279, 399, 69, 189, 309, 429, 99, 219, 339, 459, 24, 144, 264, -384, 54, 174, 294, 414, 84, 204, 324, 444, 114, 234, 354, 474, 14, 134, -254, 374, 44, 164, 284, 404, 74, 194, 314, 434, 104, 224, 344, 464, 29, -149, 269, 389, 59, 179, 299, 419, 89, 209, 329, 449, 119, 239, 359, 479, +0, 96, 192, 288, 384, 32, 128, 224, 320, 416, 64, 160, 256, 352, 448, +8, 104, 200, 296, 392, 40, 136, 232, 328, 424, 72, 168, 264, 360, 456, +16, 112, 208, 304, 400, 48, 144, 240, 336, 432, 80, 176, 272, 368, 464, +24, 120, 216, 312, 408, 56, 152, 248, 344, 440, 88, 184, 280, 376, 472, +4, 100, 196, 292, 388, 36, 132, 228, 324, 420, 68, 164, 260, 356, 452, +12, 108, 204, 300, 396, 44, 140, 236, 332, 428, 76, 172, 268, 364, 460, +20, 116, 212, 308, 404, 52, 148, 244, 340, 436, 84, 180, 276, 372, 468, +28, 124, 220, 316, 412, 60, 156, 252, 348, 444, 92, 188, 284, 380, 476, +1, 97, 193, 289, 385, 33, 129, 225, 321, 417, 65, 161, 257, 353, 449, +9, 105, 201, 297, 393, 41, 137, 233, 329, 425, 73, 169, 265, 361, 457, +17, 113, 209, 305, 401, 49, 145, 241, 337, 433, 81, 177, 273, 369, 465, +25, 121, 217, 313, 409, 57, 153, 249, 345, 441, 89, 185, 281, 377, 473, +5, 101, 197, 293, 389, 37, 133, 229, 325, 421, 69, 165, 261, 357, 453, +13, 109, 205, 301, 397, 45, 141, 237, 333, 429, 77, 173, 269, 365, 461, +21, 117, 213, 309, 405, 53, 149, 245, 341, 437, 85, 181, 277, 373, 469, +29, 125, 221, 317, 413, 61, 157, 253, 349, 445, 93, 189, 285, 381, 477, +2, 98, 194, 290, 386, 34, 130, 226, 322, 418, 66, 162, 258, 354, 450, +10, 106, 202, 298, 394, 42, 138, 234, 330, 426, 74, 170, 266, 362, 458, +18, 114, 210, 306, 402, 50, 146, 242, 338, 434, 82, 178, 274, 370, 466, +26, 122, 218, 314, 410, 58, 154, 250, 346, 442, 90, 186, 282, 378, 474, +6, 102, 198, 294, 390, 38, 134, 230, 326, 422, 70, 166, 262, 358, 454, +14, 110, 206, 302, 398, 46, 142, 238, 334, 430, 78, 174, 270, 366, 462, +22, 118, 214, 310, 406, 54, 150, 246, 342, 438, 86, 182, 278, 374, 470, +30, 126, 222, 318, 414, 62, 158, 254, 350, 446, 94, 190, 286, 382, 478, +3, 99, 195, 291, 387, 35, 131, 227, 323, 419, 67, 163, 259, 355, 451, +11, 107, 203, 299, 395, 43, 139, 235, 331, 427, 75, 171, 267, 363, 459, +19, 115, 211, 307, 403, 51, 147, 243, 339, 435, 83, 179, 275, 371, 467, +27, 123, 219, 315, 411, 59, 155, 251, 347, 443, 91, 187, 283, 379, 475, +7, 103, 199, 295, 391, 39, 135, 231, 327, 423, 71, 167, 263, 359, 455, +15, 111, 207, 303, 399, 47, 143, 239, 335, 431, 79, 175, 271, 367, 463, +23, 119, 215, 311, 407, 55, 151, 247, 343, 439, 87, 183, 279, 375, 471, +31, 127, 223, 319, 415, 63, 159, 255, 351, 447, 95, 191, 287, 383, 479, }; #endif #ifndef FFT_BITREV240 #define FFT_BITREV240 static const opus_int16 fft_bitrev240[240] = { -0, 60, 120, 180, 15, 75, 135, 195, 30, 90, 150, 210, 45, 105, 165, -225, 5, 65, 125, 185, 20, 80, 140, 200, 35, 95, 155, 215, 50, 110, -170, 230, 10, 70, 130, 190, 25, 85, 145, 205, 40, 100, 160, 220, 55, -115, 175, 235, 1, 61, 121, 181, 16, 76, 136, 196, 31, 91, 151, 211, -46, 106, 166, 226, 6, 66, 126, 186, 21, 81, 141, 201, 36, 96, 156, -216, 51, 111, 171, 231, 11, 71, 131, 191, 26, 86, 146, 206, 41, 101, -161, 221, 56, 116, 176, 236, 2, 62, 122, 182, 17, 77, 137, 197, 32, -92, 152, 212, 47, 107, 167, 227, 7, 67, 127, 187, 22, 82, 142, 202, -37, 97, 157, 217, 52, 112, 172, 232, 12, 72, 132, 192, 27, 87, 147, -207, 42, 102, 162, 222, 57, 117, 177, 237, 3, 63, 123, 183, 18, 78, -138, 198, 33, 93, 153, 213, 48, 108, 168, 228, 8, 68, 128, 188, 23, -83, 143, 203, 38, 98, 158, 218, 53, 113, 173, 233, 13, 73, 133, 193, -28, 88, 148, 208, 43, 103, 163, 223, 58, 118, 178, 238, 4, 64, 124, -184, 19, 79, 139, 199, 34, 94, 154, 214, 49, 109, 169, 229, 9, 69, -129, 189, 24, 84, 144, 204, 39, 99, 159, 219, 54, 114, 174, 234, 14, -74, 134, 194, 29, 89, 149, 209, 44, 104, 164, 224, 59, 119, 179, 239, +0, 48, 96, 144, 192, 16, 64, 112, 160, 208, 32, 80, 128, 176, 224, +4, 52, 100, 148, 196, 20, 68, 116, 164, 212, 36, 84, 132, 180, 228, +8, 56, 104, 152, 200, 24, 72, 120, 168, 216, 40, 88, 136, 184, 232, +12, 60, 108, 156, 204, 28, 76, 124, 172, 220, 44, 92, 140, 188, 236, +1, 49, 97, 145, 193, 17, 65, 113, 161, 209, 33, 81, 129, 177, 225, +5, 53, 101, 149, 197, 21, 69, 117, 165, 213, 37, 85, 133, 181, 229, +9, 57, 105, 153, 201, 25, 73, 121, 169, 217, 41, 89, 137, 185, 233, +13, 61, 109, 157, 205, 29, 77, 125, 173, 221, 45, 93, 141, 189, 237, +2, 50, 98, 146, 194, 18, 66, 114, 162, 210, 34, 82, 130, 178, 226, +6, 54, 102, 150, 198, 22, 70, 118, 166, 214, 38, 86, 134, 182, 230, +10, 58, 106, 154, 202, 26, 74, 122, 170, 218, 42, 90, 138, 186, 234, +14, 62, 110, 158, 206, 30, 78, 126, 174, 222, 46, 94, 142, 190, 238, +3, 51, 99, 147, 195, 19, 67, 115, 163, 211, 35, 83, 131, 179, 227, +7, 55, 103, 151, 199, 23, 71, 119, 167, 215, 39, 87, 135, 183, 231, +11, 59, 107, 155, 203, 27, 75, 123, 171, 219, 43, 91, 139, 187, 235, +15, 63, 111, 159, 207, 31, 79, 127, 175, 223, 47, 95, 143, 191, 239, }; #endif #ifndef FFT_BITREV120 #define FFT_BITREV120 static const opus_int16 fft_bitrev120[120] = { -0, 30, 60, 90, 15, 45, 75, 105, 5, 35, 65, 95, 20, 50, 80, -110, 10, 40, 70, 100, 25, 55, 85, 115, 1, 31, 61, 91, 16, 46, -76, 106, 6, 36, 66, 96, 21, 51, 81, 111, 11, 41, 71, 101, 26, -56, 86, 116, 2, 32, 62, 92, 17, 47, 77, 107, 7, 37, 67, 97, -22, 52, 82, 112, 12, 42, 72, 102, 27, 57, 87, 117, 3, 33, 63, -93, 18, 48, 78, 108, 8, 38, 68, 98, 23, 53, 83, 113, 13, 43, -73, 103, 28, 58, 88, 118, 4, 34, 64, 94, 19, 49, 79, 109, 9, -39, 69, 99, 24, 54, 84, 114, 14, 44, 74, 104, 29, 59, 89, 119, +0, 24, 48, 72, 96, 8, 32, 56, 80, 104, 16, 40, 64, 88, 112, +4, 28, 52, 76, 100, 12, 36, 60, 84, 108, 20, 44, 68, 92, 116, +1, 25, 49, 73, 97, 9, 33, 57, 81, 105, 17, 41, 65, 89, 113, +5, 29, 53, 77, 101, 13, 37, 61, 85, 109, 21, 45, 69, 93, 117, +2, 26, 50, 74, 98, 10, 34, 58, 82, 106, 18, 42, 66, 90, 114, +6, 30, 54, 78, 102, 14, 38, 62, 86, 110, 22, 46, 70, 94, 118, +3, 27, 51, 75, 99, 11, 35, 59, 83, 107, 19, 43, 67, 91, 115, +7, 31, 55, 79, 103, 15, 39, 63, 87, 111, 23, 47, 71, 95, 119, }; #endif #ifndef FFT_BITREV60 #define FFT_BITREV60 static const opus_int16 fft_bitrev60[60] = { -0, 15, 30, 45, 5, 20, 35, 50, 10, 25, 40, 55, 1, 16, 31, -46, 6, 21, 36, 51, 11, 26, 41, 56, 2, 17, 32, 47, 7, 22, -37, 52, 12, 27, 42, 57, 3, 18, 33, 48, 8, 23, 38, 53, 13, -28, 43, 58, 4, 19, 34, 49, 9, 24, 39, 54, 14, 29, 44, 59, +0, 12, 24, 36, 48, 4, 16, 28, 40, 52, 8, 20, 32, 44, 56, +1, 13, 25, 37, 49, 5, 17, 29, 41, 53, 9, 21, 33, 45, 57, +2, 14, 26, 38, 50, 6, 18, 30, 42, 54, 10, 22, 34, 46, 58, +3, 15, 27, 39, 51, 7, 19, 31, 43, 55, 11, 23, 35, 47, 59, }; #endif @@ -428,9 +433,14 @@ static const kiss_fft_state fft_state48000_960_0 = { 480, /* nfft */ 0.002083333f, /* scale */ -1, /* shift */ -{4, 120, 4, 30, 2, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 96, 3, 32, 4, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev480, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_480, +#else +NULL, +#endif }; #endif @@ -440,9 +450,14 @@ static const kiss_fft_state fft_state48000_960_1 = { 240, /* nfft */ 0.004166667f, /* scale */ 1, /* shift */ -{4, 60, 4, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 48, 3, 16, 4, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev240, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_240, +#else +NULL, +#endif }; #endif @@ -452,9 +467,14 @@ static const kiss_fft_state fft_state48000_960_2 = { 120, /* nfft */ 0.008333333f, /* scale */ 2, /* shift */ -{4, 30, 2, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 24, 3, 8, 2, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev120, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_120, +#else +NULL, +#endif }; #endif @@ -464,9 +484,14 @@ static const kiss_fft_state fft_state48000_960_3 = { 60, /* nfft */ 0.016666667f, /* scale */ 3, /* shift */ -{4, 15, 3, 5, 5, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ +{5, 12, 3, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }, /* factors */ fft_bitrev60, /* bitrev */ fft_twiddles48000_960, /* bitrev */ +#ifdef OVERRIDE_FFT +(arch_fft_state *)&cfg_arch_60, +#else +NULL, +#endif }; #endif @@ -474,104 +499,368 @@ fft_twiddles48000_960, /* bitrev */ #ifndef MDCT_TWIDDLES960 #define MDCT_TWIDDLES960 -static const opus_val16 mdct_twiddles960[481] = { -1.0000000f, 0.99999465f, 0.99997858f, 0.99995181f, 0.99991433f, -0.99986614f, 0.99980724f, 0.99973764f, 0.99965732f, 0.99956631f, -0.99946459f, 0.99935216f, 0.99922904f, 0.99909521f, 0.99895068f, -0.99879546f, 0.99862953f, 0.99845292f, 0.99826561f, 0.99806761f, -0.99785892f, 0.99763955f, 0.99740949f, 0.99716875f, 0.99691733f, -0.99665524f, 0.99638247f, 0.99609903f, 0.99580493f, 0.99550016f, -0.99518473f, 0.99485864f, 0.99452190f, 0.99417450f, 0.99381646f, -0.99344778f, 0.99306846f, 0.99267850f, 0.99227791f, 0.99186670f, -0.99144486f, 0.99101241f, 0.99056934f, 0.99011566f, 0.98965139f, -0.98917651f, 0.98869104f, 0.98819498f, 0.98768834f, 0.98717112f, -0.98664333f, 0.98610497f, 0.98555606f, 0.98499659f, 0.98442657f, -0.98384600f, 0.98325491f, 0.98265328f, 0.98204113f, 0.98141846f, -0.98078528f, 0.98014159f, 0.97948742f, 0.97882275f, 0.97814760f, -0.97746197f, 0.97676588f, 0.97605933f, 0.97534232f, 0.97461487f, -0.97387698f, 0.97312866f, 0.97236992f, 0.97160077f, 0.97082121f, -0.97003125f, 0.96923091f, 0.96842019f, 0.96759909f, 0.96676764f, -0.96592582f, 0.96507367f, 0.96421118f, 0.96333837f, 0.96245523f, -0.96156180f, 0.96065806f, 0.95974403f, 0.95881973f, 0.95788517f, -0.95694034f, 0.95598526f, 0.95501995f, 0.95404440f, 0.95305864f, -0.95206267f, 0.95105651f, 0.95004016f, 0.94901364f, 0.94797697f, -0.94693013f, 0.94587315f, 0.94480604f, 0.94372882f, 0.94264149f, -0.94154406f, 0.94043656f, 0.93931897f, 0.93819133f, 0.93705365f, -0.93590592f, 0.93474818f, 0.93358042f, 0.93240268f, 0.93121493f, -0.93001722f, 0.92880955f, 0.92759193f, 0.92636438f, 0.92512690f, -0.92387953f, 0.92262225f, 0.92135509f, 0.92007809f, 0.91879121f, -0.91749449f, 0.91618795f, 0.91487161f, 0.91354545f, 0.91220952f, -0.91086382f, 0.90950836f, 0.90814316f, 0.90676824f, 0.90538363f, -0.90398929f, 0.90258528f, 0.90117161f, 0.89974828f, 0.89831532f, -0.89687273f, 0.89542055f, 0.89395877f, 0.89248742f, 0.89100652f, -0.88951606f, 0.88801610f, 0.88650661f, 0.88498764f, 0.88345918f, -0.88192125f, 0.88037390f, 0.87881711f, 0.87725090f, 0.87567531f, -0.87409035f, 0.87249599f, 0.87089232f, 0.86927933f, 0.86765699f, -0.86602540f, 0.86438453f, 0.86273437f, 0.86107503f, 0.85940641f, -0.85772862f, 0.85604161f, 0.85434547f, 0.85264014f, 0.85092572f, -0.84920218f, 0.84746955f, 0.84572781f, 0.84397704f, 0.84221721f, -0.84044838f, 0.83867056f, 0.83688375f, 0.83508799f, 0.83328325f, -0.83146961f, 0.82964704f, 0.82781562f, 0.82597530f, 0.82412620f, -0.82226820f, 0.82040144f, 0.81852589f, 0.81664154f, 0.81474847f, -0.81284665f, 0.81093620f, 0.80901698f, 0.80708914f, 0.80515262f, -0.80320752f, 0.80125378f, 0.79929149f, 0.79732067f, 0.79534125f, -0.79335335f, 0.79135691f, 0.78935204f, 0.78733867f, 0.78531691f, -0.78328674f, 0.78124818f, 0.77920122f, 0.77714595f, 0.77508232f, -0.77301043f, 0.77093026f, 0.76884183f, 0.76674517f, 0.76464026f, -0.76252720f, 0.76040593f, 0.75827656f, 0.75613907f, 0.75399349f, -0.75183978f, 0.74967807f, 0.74750833f, 0.74533054f, 0.74314481f, -0.74095112f, 0.73874950f, 0.73653993f, 0.73432251f, 0.73209718f, -0.72986405f, 0.72762307f, 0.72537438f, 0.72311787f, 0.72085359f, -0.71858162f, 0.71630192f, 0.71401459f, 0.71171956f, 0.70941701f, -0.70710677f, 0.70478900f, 0.70246363f, 0.70013079f, 0.69779041f, -0.69544260f, 0.69308738f, 0.69072466f, 0.68835458f, 0.68597709f, -0.68359229f, 0.68120013f, 0.67880072f, 0.67639404f, 0.67398011f, -0.67155892f, 0.66913059f, 0.66669509f, 0.66425240f, 0.66180265f, -0.65934581f, 0.65688191f, 0.65441092f, 0.65193298f, 0.64944801f, -0.64695613f, 0.64445727f, 0.64195160f, 0.63943902f, 0.63691954f, -0.63439328f, 0.63186019f, 0.62932037f, 0.62677377f, 0.62422055f, -0.62166055f, 0.61909394f, 0.61652065f, 0.61394081f, 0.61135435f, -0.60876139f, 0.60616195f, 0.60355593f, 0.60094349f, 0.59832457f, -0.59569929f, 0.59306758f, 0.59042957f, 0.58778523f, 0.58513460f, -0.58247766f, 0.57981452f, 0.57714518f, 0.57446961f, 0.57178793f, -0.56910013f, 0.56640624f, 0.56370623f, 0.56100023f, 0.55828818f, -0.55557020f, 0.55284627f, 0.55011641f, 0.54738067f, 0.54463901f, -0.54189157f, 0.53913828f, 0.53637921f, 0.53361450f, 0.53084398f, -0.52806787f, 0.52528601f, 0.52249852f, 0.51970543f, 0.51690688f, -0.51410279f, 0.51129310f, 0.50847793f, 0.50565732f, 0.50283139f, -0.49999997f, 0.49716321f, 0.49432122f, 0.49147383f, 0.48862118f, -0.48576340f, 0.48290042f, 0.48003216f, 0.47715876f, 0.47428025f, -0.47139677f, 0.46850813f, 0.46561448f, 0.46271584f, 0.45981235f, -0.45690383f, 0.45399042f, 0.45107214f, 0.44814915f, 0.44522124f, -0.44228868f, 0.43935137f, 0.43640926f, 0.43346247f, 0.43051104f, -0.42755511f, 0.42459449f, 0.42162932f, 0.41865964f, 0.41568558f, -0.41270697f, 0.40972393f, 0.40673661f, 0.40374494f, 0.40074884f, -0.39774844f, 0.39474390f, 0.39173501f, 0.38872193f, 0.38570469f, -0.38268343f, 0.37965796f, 0.37662842f, 0.37359496f, 0.37055739f, -0.36751585f, 0.36447038f, 0.36142122f, 0.35836797f, 0.35531089f, -0.35225000f, 0.34918544f, 0.34611704f, 0.34304493f, 0.33996926f, -0.33688983f, 0.33380680f, 0.33072019f, 0.32763015f, 0.32453650f, -0.32143936f, 0.31833890f, 0.31523503f, 0.31212767f, 0.30901696f, -0.30590306f, 0.30278577f, 0.29966524f, 0.29654150f, 0.29341470f, -0.29028464f, 0.28715147f, 0.28401522f, 0.28087605f, 0.27773376f, -0.27458861f, 0.27144052f, 0.26828940f, 0.26513541f, 0.26197859f, -0.25881907f, 0.25565666f, 0.25249152f, 0.24932367f, 0.24615327f, -0.24298012f, 0.23980436f, 0.23662604f, 0.23344530f, 0.23026206f, -0.22707623f, 0.22388809f, 0.22069744f, 0.21750443f, 0.21430908f, -0.21111156f, 0.20791165f, 0.20470953f, 0.20150520f, 0.19829884f, -0.19509024f, 0.19187955f, 0.18866692f, 0.18545227f, 0.18223552f, -0.17901681f, 0.17579631f, 0.17257380f, 0.16934945f, 0.16612328f, -0.16289546f, 0.15966577f, 0.15643437f, 0.15320141f, 0.14996669f, -0.14673037f, 0.14349260f, 0.14025329f, 0.13701235f, 0.13376995f, -0.13052612f, 0.12728101f, 0.12403442f, 0.12078650f, 0.11753740f, -0.11428693f, 0.11103523f, 0.10778234f, 0.10452842f, 0.10127326f, -0.098017137f, 0.094759842f, 0.091501652f, 0.088242363f, 0.084982129f, -0.081721103f, 0.078459084f, 0.075196224f, 0.071932560f, 0.068668243f, -0.065403073f, 0.062137201f, 0.058870665f, 0.055603617f, 0.052335974f, -0.049067651f, 0.045798921f, 0.042529582f, 0.039259788f, 0.035989573f, -0.032719092f, 0.029448142f, 0.026176876f, 0.022905329f, 0.019633657f, -0.016361655f, 0.013089478f, 0.0098171604f, 0.0065449764f, 0.0032724839f, --4.3711390e-08f, }; +static const opus_val16 mdct_twiddles960[1800] = { +0.99999994f, 0.99999321f, 0.99997580f, 0.99994773f, 0.99990886f, +0.99985933f, 0.99979913f, 0.99972820f, 0.99964654f, 0.99955416f, +0.99945110f, 0.99933738f, 0.99921292f, 0.99907774f, 0.99893188f, +0.99877530f, 0.99860805f, 0.99843007f, 0.99824142f, 0.99804211f, +0.99783206f, 0.99761140f, 0.99737996f, 0.99713790f, 0.99688518f, +0.99662173f, 0.99634761f, 0.99606287f, 0.99576741f, 0.99546129f, +0.99514455f, 0.99481714f, 0.99447906f, 0.99413031f, 0.99377096f, +0.99340093f, 0.99302030f, 0.99262899f, 0.99222708f, 0.99181455f, +0.99139136f, 0.99095762f, 0.99051321f, 0.99005818f, 0.98959261f, +0.98911643f, 0.98862964f, 0.98813224f, 0.98762429f, 0.98710573f, +0.98657662f, 0.98603696f, 0.98548669f, 0.98492593f, 0.98435456f, +0.98377270f, 0.98318028f, 0.98257732f, 0.98196387f, 0.98133987f, +0.98070538f, 0.98006040f, 0.97940493f, 0.97873890f, 0.97806245f, +0.97737551f, 0.97667813f, 0.97597027f, 0.97525197f, 0.97452319f, +0.97378403f, 0.97303438f, 0.97227436f, 0.97150391f, 0.97072303f, +0.96993178f, 0.96913016f, 0.96831810f, 0.96749574f, 0.96666300f, +0.96581990f, 0.96496642f, 0.96410263f, 0.96322852f, 0.96234411f, +0.96144938f, 0.96054435f, 0.95962906f, 0.95870346f, 0.95776761f, +0.95682150f, 0.95586514f, 0.95489854f, 0.95392174f, 0.95293468f, +0.95193744f, 0.95093000f, 0.94991243f, 0.94888461f, 0.94784665f, +0.94679856f, 0.94574034f, 0.94467193f, 0.94359344f, 0.94250488f, +0.94140619f, 0.94029742f, 0.93917859f, 0.93804967f, 0.93691075f, +0.93576175f, 0.93460274f, 0.93343377f, 0.93225473f, 0.93106574f, +0.92986679f, 0.92865789f, 0.92743903f, 0.92621022f, 0.92497152f, +0.92372292f, 0.92246443f, 0.92119598f, 0.91991776f, 0.91862965f, +0.91733170f, 0.91602397f, 0.91470635f, 0.91337901f, 0.91204184f, +0.91069490f, 0.90933824f, 0.90797186f, 0.90659571f, 0.90520984f, +0.90381432f, 0.90240908f, 0.90099424f, 0.89956969f, 0.89813554f, +0.89669174f, 0.89523834f, 0.89377540f, 0.89230281f, 0.89082074f, +0.88932908f, 0.88782793f, 0.88631725f, 0.88479710f, 0.88326746f, +0.88172835f, 0.88017982f, 0.87862182f, 0.87705445f, 0.87547767f, +0.87389153f, 0.87229604f, 0.87069118f, 0.86907703f, 0.86745358f, +0.86582077f, 0.86417878f, 0.86252749f, 0.86086690f, 0.85919720f, +0.85751826f, 0.85583007f, 0.85413277f, 0.85242635f, 0.85071075f, +0.84898609f, 0.84725231f, 0.84550947f, 0.84375757f, 0.84199661f, +0.84022665f, 0.83844769f, 0.83665979f, 0.83486289f, 0.83305705f, +0.83124226f, 0.82941860f, 0.82758605f, 0.82574469f, 0.82389444f, +0.82203537f, 0.82016748f, 0.81829083f, 0.81640542f, 0.81451124f, +0.81260836f, 0.81069672f, 0.80877650f, 0.80684757f, 0.80490994f, +0.80296379f, 0.80100900f, 0.79904562f, 0.79707366f, 0.79509324f, +0.79310423f, 0.79110676f, 0.78910083f, 0.78708643f, 0.78506362f, +0.78303236f, 0.78099275f, 0.77894479f, 0.77688843f, 0.77482378f, +0.77275085f, 0.77066964f, 0.76858020f, 0.76648247f, 0.76437658f, +0.76226246f, 0.76014024f, 0.75800985f, 0.75587130f, 0.75372469f, +0.75157005f, 0.74940729f, 0.74723655f, 0.74505776f, 0.74287105f, +0.74067634f, 0.73847371f, 0.73626316f, 0.73404479f, 0.73181850f, +0.72958434f, 0.72734243f, 0.72509271f, 0.72283524f, 0.72057003f, +0.71829706f, 0.71601641f, 0.71372813f, 0.71143216f, 0.70912862f, +0.70681745f, 0.70449871f, 0.70217246f, 0.69983864f, 0.69749737f, +0.69514859f, 0.69279242f, 0.69042879f, 0.68805778f, 0.68567938f, +0.68329364f, 0.68090063f, 0.67850029f, 0.67609268f, 0.67367786f, +0.67125577f, 0.66882652f, 0.66639012f, 0.66394657f, 0.66149592f, +0.65903819f, 0.65657341f, 0.65410155f, 0.65162271f, 0.64913690f, +0.64664418f, 0.64414448f, 0.64163786f, 0.63912445f, 0.63660413f, +0.63407701f, 0.63154310f, 0.62900239f, 0.62645501f, 0.62390089f, +0.62134010f, 0.61877263f, 0.61619854f, 0.61361790f, 0.61103064f, +0.60843682f, 0.60583651f, 0.60322970f, 0.60061646f, 0.59799677f, +0.59537065f, 0.59273821f, 0.59009939f, 0.58745426f, 0.58480281f, +0.58214509f, 0.57948118f, 0.57681108f, 0.57413477f, 0.57145232f, +0.56876373f, 0.56606907f, 0.56336832f, 0.56066155f, 0.55794877f, +0.55523002f, 0.55250537f, 0.54977477f, 0.54703826f, 0.54429591f, +0.54154772f, 0.53879374f, 0.53603399f, 0.53326851f, 0.53049731f, +0.52772039f, 0.52493787f, 0.52214974f, 0.51935595f, 0.51655668f, +0.51375180f, 0.51094145f, 0.50812566f, 0.50530440f, 0.50247771f, +0.49964568f, 0.49680826f, 0.49396557f, 0.49111754f, 0.48826426f, +0.48540577f, 0.48254207f, 0.47967321f, 0.47679919f, 0.47392011f, +0.47103590f, 0.46814668f, 0.46525243f, 0.46235323f, 0.45944905f, +0.45653993f, 0.45362595f, 0.45070711f, 0.44778344f, 0.44485497f, +0.44192174f, 0.43898380f, 0.43604112f, 0.43309379f, 0.43014181f, +0.42718524f, 0.42422408f, 0.42125839f, 0.41828820f, 0.41531351f, +0.41233435f, 0.40935081f, 0.40636289f, 0.40337059f, 0.40037400f, +0.39737311f, 0.39436796f, 0.39135858f, 0.38834500f, 0.38532731f, +0.38230544f, 0.37927949f, 0.37624949f, 0.37321547f, 0.37017745f, +0.36713544f, 0.36408952f, 0.36103970f, 0.35798600f, 0.35492846f, +0.35186714f, 0.34880206f, 0.34573323f, 0.34266070f, 0.33958447f, +0.33650464f, 0.33342120f, 0.33033419f, 0.32724363f, 0.32414958f, +0.32105204f, 0.31795108f, 0.31484672f, 0.31173897f, 0.30862790f, +0.30551350f, 0.30239585f, 0.29927495f, 0.29615086f, 0.29302359f, +0.28989318f, 0.28675964f, 0.28362307f, 0.28048345f, 0.27734083f, +0.27419522f, 0.27104670f, 0.26789525f, 0.26474094f, 0.26158381f, +0.25842386f, 0.25526115f, 0.25209570f, 0.24892756f, 0.24575676f, +0.24258332f, 0.23940729f, 0.23622867f, 0.23304754f, 0.22986393f, +0.22667783f, 0.22348931f, 0.22029841f, 0.21710514f, 0.21390954f, +0.21071166f, 0.20751151f, 0.20430915f, 0.20110460f, 0.19789790f, +0.19468907f, 0.19147816f, 0.18826519f, 0.18505022f, 0.18183327f, +0.17861435f, 0.17539354f, 0.17217083f, 0.16894630f, 0.16571994f, +0.16249183f, 0.15926196f, 0.15603039f, 0.15279715f, 0.14956227f, +0.14632578f, 0.14308774f, 0.13984816f, 0.13660708f, 0.13336454f, +0.13012058f, 0.12687522f, 0.12362850f, 0.12038045f, 0.11713112f, +0.11388054f, 0.11062872f, 0.10737573f, 0.10412160f, 0.10086634f, +0.097609997f, 0.094352618f, 0.091094226f, 0.087834857f, 0.084574550f, +0.081313334f, 0.078051247f, 0.074788325f, 0.071524605f, 0.068260118f, +0.064994894f, 0.061728980f, 0.058462404f, 0.055195201f, 0.051927410f, +0.048659060f, 0.045390189f, 0.042120833f, 0.038851023f, 0.035580799f, +0.032310195f, 0.029039243f, 0.025767982f, 0.022496443f, 0.019224664f, +0.015952680f, 0.012680525f, 0.0094082337f, 0.0061358409f, 0.0028633832f, +-0.00040910527f, -0.0036815894f, -0.0069540343f, -0.010226404f, -0.013498665f, +-0.016770782f, -0.020042717f, -0.023314439f, -0.026585912f, -0.029857099f, +-0.033127967f, -0.036398482f, -0.039668605f, -0.042938303f, -0.046207540f, +-0.049476285f, -0.052744497f, -0.056012146f, -0.059279196f, -0.062545612f, +-0.065811358f, -0.069076397f, -0.072340697f, -0.075604223f, -0.078866936f, +-0.082128808f, -0.085389800f, -0.088649876f, -0.091909006f, -0.095167145f, +-0.098424271f, -0.10168034f, -0.10493532f, -0.10818918f, -0.11144188f, +-0.11469338f, -0.11794366f, -0.12119267f, -0.12444039f, -0.12768677f, +-0.13093179f, -0.13417540f, -0.13741758f, -0.14065829f, -0.14389749f, +-0.14713514f, -0.15037122f, -0.15360570f, -0.15683852f, -0.16006967f, +-0.16329910f, -0.16652679f, -0.16975269f, -0.17297678f, -0.17619900f, +-0.17941935f, -0.18263777f, -0.18585424f, -0.18906870f, -0.19228116f, +-0.19549155f, -0.19869985f, -0.20190603f, -0.20511003f, -0.20831184f, +-0.21151142f, -0.21470875f, -0.21790376f, -0.22109644f, -0.22428675f, +-0.22747467f, -0.23066014f, -0.23384315f, -0.23702365f, -0.24020162f, +-0.24337701f, -0.24654980f, -0.24971995f, -0.25288740f, -0.25605217f, +-0.25921419f, -0.26237345f, -0.26552987f, -0.26868346f, -0.27183419f, +-0.27498198f, -0.27812684f, -0.28126872f, -0.28440759f, -0.28754342f, +-0.29067615f, -0.29380578f, -0.29693225f, -0.30005556f, -0.30317566f, +-0.30629250f, -0.30940607f, -0.31251630f, -0.31562322f, -0.31872672f, +-0.32182685f, -0.32492352f, -0.32801670f, -0.33110636f, -0.33419248f, +-0.33727503f, -0.34035397f, -0.34342924f, -0.34650084f, -0.34956875f, +-0.35263291f, -0.35569328f, -0.35874987f, -0.36180258f, -0.36485144f, +-0.36789638f, -0.37093741f, -0.37397444f, -0.37700745f, -0.38003644f, +-0.38306138f, -0.38608220f, -0.38909888f, -0.39211139f, -0.39511973f, +-0.39812380f, -0.40112361f, -0.40411916f, -0.40711036f, -0.41009718f, +-0.41307965f, -0.41605768f, -0.41903123f, -0.42200032f, -0.42496487f, +-0.42792490f, -0.43088034f, -0.43383113f, -0.43677729f, -0.43971881f, +-0.44265559f, -0.44558764f, -0.44851488f, -0.45143735f, -0.45435500f, +-0.45726776f, -0.46017563f, -0.46307856f, -0.46597654f, -0.46886954f, +-0.47175750f, -0.47464043f, -0.47751826f, -0.48039100f, -0.48325855f, +-0.48612097f, -0.48897815f, -0.49183011f, -0.49467680f, -0.49751821f, +-0.50035429f, -0.50318497f, -0.50601029f, -0.50883019f, -0.51164466f, +-0.51445359f, -0.51725709f, -0.52005500f, -0.52284735f, -0.52563411f, +-0.52841520f, -0.53119069f, -0.53396046f, -0.53672451f, -0.53948283f, +-0.54223537f, -0.54498214f, -0.54772300f, -0.55045801f, -0.55318713f, +-0.55591035f, -0.55862761f, -0.56133890f, -0.56404412f, -0.56674337f, +-0.56943649f, -0.57212353f, -0.57480448f, -0.57747924f, -0.58014780f, +-0.58281022f, -0.58546633f, -0.58811617f, -0.59075975f, -0.59339696f, +-0.59602785f, -0.59865236f, -0.60127044f, -0.60388207f, -0.60648727f, +-0.60908598f, -0.61167812f, -0.61426371f, -0.61684275f, -0.61941516f, +-0.62198097f, -0.62454009f, -0.62709254f, -0.62963831f, -0.63217729f, +-0.63470948f, -0.63723493f, -0.63975352f, -0.64226526f, -0.64477009f, +-0.64726806f, -0.64975911f, -0.65224314f, -0.65472025f, -0.65719032f, +-0.65965337f, -0.66210932f, -0.66455823f, -0.66700000f, -0.66943461f, +-0.67186207f, -0.67428231f, -0.67669535f, -0.67910111f, -0.68149966f, +-0.68389088f, -0.68627477f, -0.68865126f, -0.69102043f, -0.69338220f, +-0.69573659f, -0.69808346f, -0.70042288f, -0.70275480f, -0.70507920f, +-0.70739603f, -0.70970529f, -0.71200693f, -0.71430099f, -0.71658736f, +-0.71886611f, -0.72113711f, -0.72340041f, -0.72565591f, -0.72790372f, +-0.73014367f, -0.73237586f, -0.73460019f, -0.73681659f, -0.73902518f, +-0.74122584f, -0.74341851f, -0.74560326f, -0.74778003f, -0.74994880f, +-0.75210953f, -0.75426215f, -0.75640678f, -0.75854325f, -0.76067162f, +-0.76279181f, -0.76490390f, -0.76700771f, -0.76910341f, -0.77119076f, +-0.77326995f, -0.77534080f, -0.77740335f, -0.77945763f, -0.78150350f, +-0.78354102f, -0.78557014f, -0.78759086f, -0.78960317f, -0.79160696f, +-0.79360235f, -0.79558921f, -0.79756755f, -0.79953730f, -0.80149853f, +-0.80345118f, -0.80539525f, -0.80733067f, -0.80925739f, -0.81117553f, +-0.81308490f, -0.81498563f, -0.81687760f, -0.81876087f, -0.82063532f, +-0.82250100f, -0.82435787f, -0.82620591f, -0.82804507f, -0.82987541f, +-0.83169687f, -0.83350939f, -0.83531296f, -0.83710766f, -0.83889335f, +-0.84067005f, -0.84243774f, -0.84419644f, -0.84594607f, -0.84768665f, +-0.84941816f, -0.85114056f, -0.85285389f, -0.85455805f, -0.85625303f, +-0.85793889f, -0.85961550f, -0.86128294f, -0.86294121f, -0.86459017f, +-0.86622989f, -0.86786032f, -0.86948150f, -0.87109333f, -0.87269586f, +-0.87428904f, -0.87587279f, -0.87744725f, -0.87901229f, -0.88056785f, +-0.88211405f, -0.88365078f, -0.88517809f, -0.88669586f, -0.88820416f, +-0.88970292f, -0.89119220f, -0.89267188f, -0.89414203f, -0.89560264f, +-0.89705360f, -0.89849502f, -0.89992678f, -0.90134889f, -0.90276134f, +-0.90416414f, -0.90555727f, -0.90694070f, -0.90831441f, -0.90967834f, +-0.91103262f, -0.91237706f, -0.91371179f, -0.91503674f, -0.91635185f, +-0.91765714f, -0.91895264f, -0.92023826f, -0.92151409f, -0.92277998f, +-0.92403603f, -0.92528218f, -0.92651838f, -0.92774469f, -0.92896110f, +-0.93016750f, -0.93136400f, -0.93255049f, -0.93372697f, -0.93489349f, +-0.93604994f, -0.93719643f, -0.93833286f, -0.93945926f, -0.94057560f, +-0.94168180f, -0.94277799f, -0.94386405f, -0.94494003f, -0.94600588f, +-0.94706154f, -0.94810712f, -0.94914252f, -0.95016778f, -0.95118284f, +-0.95218778f, -0.95318246f, -0.95416695f, -0.95514119f, -0.95610523f, +-0.95705903f, -0.95800257f, -0.95893586f, -0.95985889f, -0.96077162f, +-0.96167403f, -0.96256620f, -0.96344805f, -0.96431959f, -0.96518075f, +-0.96603161f, -0.96687216f, -0.96770233f, -0.96852213f, -0.96933156f, +-0.97013056f, -0.97091925f, -0.97169751f, -0.97246534f, -0.97322279f, +-0.97396982f, -0.97470641f, -0.97543252f, -0.97614825f, -0.97685349f, +-0.97754824f, -0.97823256f, -0.97890645f, -0.97956979f, -0.98022264f, +-0.98086500f, -0.98149687f, -0.98211825f, -0.98272908f, -0.98332942f, +-0.98391914f, -0.98449844f, -0.98506713f, -0.98562527f, -0.98617285f, +-0.98670989f, -0.98723638f, -0.98775226f, -0.98825759f, -0.98875231f, +-0.98923647f, -0.98971003f, -0.99017298f, -0.99062532f, -0.99106705f, +-0.99149817f, -0.99191868f, -0.99232858f, -0.99272782f, -0.99311644f, +-0.99349445f, -0.99386179f, -0.99421853f, -0.99456459f, -0.99489999f, +-0.99522477f, -0.99553883f, -0.99584228f, -0.99613506f, -0.99641716f, +-0.99668860f, -0.99694937f, -0.99719942f, -0.99743885f, -0.99766755f, +-0.99788558f, -0.99809295f, -0.99828959f, -0.99847561f, -0.99865085f, +-0.99881548f, -0.99896932f, -0.99911255f, -0.99924499f, -0.99936682f, +-0.99947786f, -0.99957830f, -0.99966794f, -0.99974692f, -0.99981517f, +-0.99987274f, -0.99991959f, -0.99995571f, -0.99998116f, -0.99999589f, +0.99999964f, 0.99997288f, 0.99990326f, 0.99979085f, 0.99963558f, +0.99943751f, 0.99919659f, 0.99891287f, 0.99858636f, 0.99821711f, +0.99780506f, 0.99735034f, 0.99685282f, 0.99631262f, 0.99572974f, +0.99510419f, 0.99443603f, 0.99372530f, 0.99297196f, 0.99217612f, +0.99133772f, 0.99045694f, 0.98953366f, 0.98856801f, 0.98756003f, +0.98650974f, 0.98541719f, 0.98428243f, 0.98310548f, 0.98188645f, +0.98062533f, 0.97932225f, 0.97797716f, 0.97659022f, 0.97516143f, +0.97369087f, 0.97217858f, 0.97062469f, 0.96902919f, 0.96739221f, +0.96571374f, 0.96399397f, 0.96223283f, 0.96043050f, 0.95858705f, +0.95670253f, 0.95477700f, 0.95281059f, 0.95080340f, 0.94875544f, +0.94666684f, 0.94453770f, 0.94236809f, 0.94015813f, 0.93790787f, +0.93561745f, 0.93328691f, 0.93091643f, 0.92850608f, 0.92605597f, +0.92356616f, 0.92103678f, 0.91846794f, 0.91585976f, 0.91321236f, +0.91052586f, 0.90780038f, 0.90503591f, 0.90223277f, 0.89939094f, +0.89651060f, 0.89359182f, 0.89063478f, 0.88763964f, 0.88460642f, +0.88153529f, 0.87842643f, 0.87527996f, 0.87209594f, 0.86887461f, +0.86561602f, 0.86232042f, 0.85898781f, 0.85561842f, 0.85221243f, +0.84876984f, 0.84529096f, 0.84177583f, 0.83822471f, 0.83463764f, +0.83101481f, 0.82735640f, 0.82366252f, 0.81993335f, 0.81616908f, +0.81236988f, 0.80853581f, 0.80466717f, 0.80076402f, 0.79682660f, +0.79285502f, 0.78884947f, 0.78481019f, 0.78073722f, 0.77663082f, +0.77249116f, 0.76831841f, 0.76411277f, 0.75987434f, 0.75560343f, +0.75130010f, 0.74696463f, 0.74259710f, 0.73819780f, 0.73376691f, +0.72930455f, 0.72481096f, 0.72028631f, 0.71573079f, 0.71114463f, +0.70652801f, 0.70188117f, 0.69720417f, 0.69249737f, 0.68776089f, +0.68299496f, 0.67819971f, 0.67337549f, 0.66852236f, 0.66364062f, +0.65873051f, 0.65379208f, 0.64882571f, 0.64383155f, 0.63880974f, +0.63376063f, 0.62868434f, 0.62358117f, 0.61845124f, 0.61329484f, +0.60811216f, 0.60290343f, 0.59766883f, 0.59240872f, 0.58712316f, +0.58181250f, 0.57647687f, 0.57111657f, 0.56573176f, 0.56032276f, +0.55488980f, 0.54943299f, 0.54395270f, 0.53844911f, 0.53292239f, +0.52737290f, 0.52180082f, 0.51620632f, 0.51058978f, 0.50495136f, +0.49929130f, 0.49360985f, 0.48790723f, 0.48218375f, 0.47643960f, +0.47067502f, 0.46489030f, 0.45908567f, 0.45326138f, 0.44741765f, +0.44155475f, 0.43567297f, 0.42977250f, 0.42385364f, 0.41791660f, +0.41196167f, 0.40598908f, 0.39999911f, 0.39399201f, 0.38796803f, +0.38192743f, 0.37587047f, 0.36979741f, 0.36370850f, 0.35760403f, +0.35148421f, 0.34534934f, 0.33919969f, 0.33303553f, 0.32685706f, +0.32066461f, 0.31445843f, 0.30823877f, 0.30200592f, 0.29576012f, +0.28950164f, 0.28323078f, 0.27694780f, 0.27065292f, 0.26434645f, +0.25802869f, 0.25169984f, 0.24536023f, 0.23901010f, 0.23264973f, +0.22627939f, 0.21989937f, 0.21350993f, 0.20711134f, 0.20070387f, +0.19428782f, 0.18786344f, 0.18143101f, 0.17499080f, 0.16854310f, +0.16208819f, 0.15562633f, 0.14915779f, 0.14268288f, 0.13620184f, +0.12971498f, 0.12322257f, 0.11672486f, 0.11022217f, 0.10371475f, +0.097202882f, 0.090686858f, 0.084166944f, 0.077643424f, 0.071116582f, +0.064586692f, 0.058054037f, 0.051518895f, 0.044981543f, 0.038442269f, +0.031901345f, 0.025359053f, 0.018815678f, 0.012271495f, 0.0057267868f, +-0.00081816671f, -0.0073630852f, -0.013907688f, -0.020451695f, -0.026994826f, +-0.033536803f, -0.040077340f, -0.046616159f, -0.053152986f, -0.059687532f, +-0.066219524f, -0.072748676f, -0.079274714f, -0.085797355f, -0.092316322f, +-0.098831341f, -0.10534211f, -0.11184838f, -0.11834986f, -0.12484626f, +-0.13133731f, -0.13782275f, -0.14430228f, -0.15077563f, -0.15724251f, +-0.16370267f, -0.17015581f, -0.17660165f, -0.18303993f, -0.18947038f, +-0.19589271f, -0.20230664f, -0.20871192f, -0.21510825f, -0.22149536f, +-0.22787298f, -0.23424086f, -0.24059868f, -0.24694622f, -0.25328314f, +-0.25960925f, -0.26592422f, -0.27222782f, -0.27851975f, -0.28479972f, +-0.29106751f, -0.29732284f, -0.30356544f, -0.30979502f, -0.31601134f, +-0.32221413f, -0.32840309f, -0.33457801f, -0.34073856f, -0.34688455f, +-0.35301566f, -0.35913166f, -0.36523229f, -0.37131724f, -0.37738630f, +-0.38343921f, -0.38947567f, -0.39549544f, -0.40149832f, -0.40748394f, +-0.41345215f, -0.41940263f, -0.42533514f, -0.43124944f, -0.43714526f, +-0.44302234f, -0.44888046f, -0.45471936f, -0.46053877f, -0.46633846f, +-0.47211814f, -0.47787762f, -0.48361665f, -0.48933494f, -0.49503228f, +-0.50070840f, -0.50636309f, -0.51199609f, -0.51760709f, -0.52319598f, +-0.52876246f, -0.53430629f, -0.53982723f, -0.54532504f, -0.55079949f, +-0.55625033f, -0.56167740f, -0.56708032f, -0.57245898f, -0.57781315f, +-0.58314258f, -0.58844697f, -0.59372622f, -0.59897995f, -0.60420811f, +-0.60941035f, -0.61458647f, -0.61973625f, -0.62485951f, -0.62995601f, +-0.63502556f, -0.64006782f, -0.64508271f, -0.65007001f, -0.65502942f, +-0.65996075f, -0.66486382f, -0.66973841f, -0.67458433f, -0.67940134f, +-0.68418926f, -0.68894786f, -0.69367695f, -0.69837630f, -0.70304573f, +-0.70768511f, -0.71229410f, -0.71687263f, -0.72142041f, -0.72593731f, +-0.73042315f, -0.73487765f, -0.73930067f, -0.74369204f, -0.74805158f, +-0.75237900f, -0.75667429f, -0.76093709f, -0.76516730f, -0.76936477f, +-0.77352923f, -0.77766061f, -0.78175867f, -0.78582323f, -0.78985411f, +-0.79385114f, -0.79781419f, -0.80174309f, -0.80563760f, -0.80949765f, +-0.81332302f, -0.81711352f, -0.82086903f, -0.82458937f, -0.82827437f, +-0.83192390f, -0.83553779f, -0.83911592f, -0.84265804f, -0.84616417f, +-0.84963393f, -0.85306740f, -0.85646427f, -0.85982448f, -0.86314780f, +-0.86643422f, -0.86968350f, -0.87289548f, -0.87607014f, -0.87920725f, +-0.88230664f, -0.88536829f, -0.88839203f, -0.89137769f, -0.89432514f, +-0.89723432f, -0.90010506f, -0.90293723f, -0.90573072f, -0.90848541f, +-0.91120118f, -0.91387796f, -0.91651553f, -0.91911387f, -0.92167282f, +-0.92419231f, -0.92667222f, -0.92911243f, -0.93151283f, -0.93387336f, +-0.93619382f, -0.93847424f, -0.94071442f, -0.94291431f, -0.94507378f, +-0.94719279f, -0.94927126f, -0.95130903f, -0.95330608f, -0.95526224f, +-0.95717752f, -0.95905179f, -0.96088499f, -0.96267700f, -0.96442777f, +-0.96613729f, -0.96780539f, -0.96943200f, -0.97101706f, -0.97256058f, +-0.97406244f, -0.97552258f, -0.97694093f, -0.97831738f, -0.97965199f, +-0.98094457f, -0.98219514f, -0.98340368f, -0.98457009f, -0.98569429f, +-0.98677629f, -0.98781598f, -0.98881340f, -0.98976845f, -0.99068111f, +-0.99155134f, -0.99237907f, -0.99316430f, -0.99390697f, -0.99460709f, +-0.99526459f, -0.99587947f, -0.99645168f, -0.99698120f, -0.99746799f, +-0.99791211f, -0.99831343f, -0.99867201f, -0.99898779f, -0.99926084f, +-0.99949104f, -0.99967843f, -0.99982297f, -0.99992472f, -0.99998361f, +0.99999869f, 0.99989158f, 0.99961317f, 0.99916345f, 0.99854255f, +0.99775058f, 0.99678761f, 0.99565387f, 0.99434954f, 0.99287480f, +0.99122995f, 0.98941529f, 0.98743105f, 0.98527765f, 0.98295540f, +0.98046476f, 0.97780609f, 0.97497988f, 0.97198665f, 0.96882683f, +0.96550101f, 0.96200979f, 0.95835376f, 0.95453346f, 0.95054960f, +0.94640291f, 0.94209403f, 0.93762374f, 0.93299282f, 0.92820197f, +0.92325211f, 0.91814411f, 0.91287869f, 0.90745693f, 0.90187967f, +0.89614785f, 0.89026248f, 0.88422459f, 0.87803519f, 0.87169534f, +0.86520612f, 0.85856867f, 0.85178405f, 0.84485358f, 0.83777827f, +0.83055943f, 0.82319832f, 0.81569612f, 0.80805415f, 0.80027372f, +0.79235619f, 0.78430289f, 0.77611518f, 0.76779449f, 0.75934225f, +0.75075996f, 0.74204898f, 0.73321080f, 0.72424710f, 0.71515924f, +0.70594883f, 0.69661748f, 0.68716675f, 0.67759830f, 0.66791373f, +0.65811473f, 0.64820296f, 0.63818014f, 0.62804794f, 0.61780810f, +0.60746247f, 0.59701276f, 0.58646071f, 0.57580817f, 0.56505698f, +0.55420899f, 0.54326600f, 0.53222996f, 0.52110273f, 0.50988621f, +0.49858227f, 0.48719296f, 0.47572014f, 0.46416581f, 0.45253196f, +0.44082057f, 0.42903364f, 0.41717321f, 0.40524128f, 0.39323992f, +0.38117120f, 0.36903715f, 0.35683987f, 0.34458145f, 0.33226398f, +0.31988961f, 0.30746040f, 0.29497850f, 0.28244606f, 0.26986524f, +0.25723818f, 0.24456702f, 0.23185398f, 0.21910121f, 0.20631088f, +0.19348522f, 0.18062639f, 0.16773662f, 0.15481812f, 0.14187308f, +0.12890373f, 0.11591230f, 0.10290100f, 0.089872077f, 0.076827750f, +0.063770257f, 0.050701842f, 0.037624735f, 0.024541186f, 0.011453429f, +-0.0016362892f, -0.014725727f, -0.027812643f, -0.040894791f, -0.053969935f, +-0.067035832f, -0.080090240f, -0.093130924f, -0.10615565f, -0.11916219f, +-0.13214831f, -0.14511178f, -0.15805040f, -0.17096193f, -0.18384418f, +-0.19669491f, -0.20951195f, -0.22229309f, -0.23503613f, -0.24773891f, +-0.26039925f, -0.27301496f, -0.28558388f, -0.29810387f, -0.31057280f, +-0.32298848f, -0.33534884f, -0.34765175f, -0.35989508f, -0.37207675f, +-0.38419467f, -0.39624676f, -0.40823093f, -0.42014518f, -0.43198743f, +-0.44375566f, -0.45544785f, -0.46706200f, -0.47859612f, -0.49004826f, +-0.50141639f, -0.51269865f, -0.52389306f, -0.53499764f, -0.54601061f, +-0.55693001f, -0.56775403f, -0.57848072f, -0.58910829f, -0.59963489f, +-0.61005878f, -0.62037814f, -0.63059121f, -0.64069623f, -0.65069145f, +-0.66057515f, -0.67034572f, -0.68000144f, -0.68954057f, -0.69896162f, +-0.70826286f, -0.71744281f, -0.72649974f, -0.73543227f, -0.74423873f, +-0.75291771f, -0.76146764f, -0.76988715f, -0.77817470f, -0.78632891f, +-0.79434842f, -0.80223179f, -0.80997771f, -0.81758487f, -0.82505190f, +-0.83237761f, -0.83956063f, -0.84659988f, -0.85349399f, -0.86024189f, +-0.86684239f, -0.87329435f, -0.87959671f, -0.88574833f, -0.89174819f, +-0.89759529f, -0.90328854f, -0.90882701f, -0.91420978f, -0.91943592f, +-0.92450452f, -0.92941469f, -0.93416560f, -0.93875647f, -0.94318646f, +-0.94745487f, -0.95156091f, -0.95550388f, -0.95928317f, -0.96289814f, +-0.96634805f, -0.96963239f, -0.97275060f, -0.97570217f, -0.97848648f, +-0.98110318f, -0.98355180f, -0.98583186f, -0.98794299f, -0.98988485f, +-0.99165714f, -0.99325943f, -0.99469161f, -0.99595332f, -0.99704438f, +-0.99796462f, -0.99871385f, -0.99929196f, -0.99969882f, -0.99993443f, +0.99999464f, 0.99956632f, 0.99845290f, 0.99665523f, 0.99417448f, +0.99101239f, 0.98717111f, 0.98265326f, 0.97746199f, 0.97160077f, +0.96507365f, 0.95788515f, 0.95004016f, 0.94154406f, 0.93240267f, +0.92262226f, 0.91220951f, 0.90117162f, 0.88951606f, 0.87725091f, +0.86438453f, 0.85092574f, 0.83688372f, 0.82226819f, 0.80708915f, +0.79135692f, 0.77508235f, 0.75827658f, 0.74095112f, 0.72311783f, +0.70478898f, 0.68597710f, 0.66669506f, 0.64695615f, 0.62677377f, +0.60616189f, 0.58513457f, 0.56370622f, 0.54189157f, 0.51970547f, +0.49716324f, 0.47428027f, 0.45107225f, 0.42755505f, 0.40374488f, +0.37965798f, 0.35531086f, 0.33072025f, 0.30590299f, 0.28087607f, +0.25565663f, 0.23026201f, 0.20470956f, 0.17901683f, 0.15320139f, +0.12728097f, 0.10127331f, 0.075196236f, 0.049067631f, 0.022905400f, +-0.0032725304f, -0.029448219f, -0.055603724f, -0.081721120f, -0.10778251f, +-0.13377003f, -0.15966587f, -0.18545228f, -0.21111161f, -0.23662624f, +-0.26197869f, -0.28715160f, -0.31212771f, -0.33688989f, -0.36142120f, +-0.38570482f, -0.40972409f, -0.43346253f, -0.45690393f, -0.48003218f, +-0.50283146f, -0.52528608f, -0.54738069f, -0.56910020f, -0.59042966f, +-0.61135447f, -0.63186026f, -0.65193301f, -0.67155898f, -0.69072473f, +-0.70941705f, -0.72762316f, -0.74533063f, -0.76252723f, -0.77920127f, +-0.79534131f, -0.81093621f, -0.82597536f, -0.84044844f, -0.85434550f, +-0.86765707f, -0.88037395f, -0.89248747f, -0.90398932f, -0.91487163f, +-0.92512697f, -0.93474823f, -0.94372886f, -0.95206273f, -0.95974404f, +-0.96676767f, -0.97312868f, -0.97882277f, -0.98384601f, -0.98819500f, +-0.99186671f, -0.99485862f, -0.99716878f, -0.99879545f, -0.99973762f, +}; #endif static const CELTMode mode48000_960_120 = { diff --git a/TMessagesProj/jni/opus/celt/static_modes_float_arm_ne10.h b/TMessagesProj/jni/opus/celt/static_modes_float_arm_ne10.h new file mode 100644 index 00000000000..934a82a420a --- /dev/null +++ b/TMessagesProj/jni/opus/celt/static_modes_float_arm_ne10.h @@ -0,0 +1,404 @@ +/* The contents of this file was automatically generated by + * dump_mode_arm_ne10.c with arguments: 48000 960 + * It contains static definitions for some pre-defined modes. */ +#include + +#ifndef NE10_FFT_PARAMS48000_960 +#define NE10_FFT_PARAMS48000_960 +static const ne10_int32_t ne10_factors_480[64] = { +4, 40, 4, 30, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_240[64] = { +3, 20, 4, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_120[64] = { +3, 10, 2, 15, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_int32_t ne10_factors_60[64] = { +2, 5, 5, 3, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, }; +static const ne10_fft_cpx_float32_t ne10_twiddles_480[480] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +{1.0000000f,-0.0000000f}, {0.99991435f,-0.013089596f}, {0.99965733f,-0.026176950f}, +{0.99922901f,-0.039259817f}, {0.99862951f,-0.052335959f}, {0.99785894f,-0.065403134f}, +{0.99691731f,-0.078459099f}, {0.99580491f,-0.091501623f}, {0.99452192f,-0.10452846f}, +{0.99306846f,-0.11753740f}, {0.99144489f,-0.13052620f}, {0.98965138f,-0.14349262f}, +{0.98768836f,-0.15643448f}, {0.98555607f,-0.16934951f}, {0.98325491f,-0.18223552f}, +{0.98078525f,-0.19509032f}, {0.97814763f,-0.20791170f}, {0.97534233f,-0.22069745f}, +{0.97236991f,-0.23344538f}, {0.96923089f,-0.24615330f}, {0.96592581f,-0.25881904f}, +{0.96245521f,-0.27144045f}, {0.95881975f,-0.28401536f}, {0.95501995f,-0.29654160f}, +{0.95105648f,-0.30901700f}, {0.94693011f,-0.32143945f}, {0.94264150f,-0.33380687f}, +{0.93819129f,-0.34611708f}, {0.93358040f,-0.35836795f}, {0.92880952f,-0.37055743f}, +{0.92387956f,-0.38268346f}, {0.91879117f,-0.39474389f}, {0.91354543f,-0.40673664f}, +{0.90814316f,-0.41865975f}, {0.90258527f,-0.43051112f}, {0.89687270f,-0.44228873f}, +{0.89100653f,-0.45399052f}, {0.88498765f,-0.46561453f}, {0.87881708f,-0.47715878f}, +{0.87249601f,-0.48862126f}, {0.86602545f,-0.50000000f}, {0.85940641f,-0.51129311f}, +{0.85264015f,-0.52249855f}, {0.84572786f,-0.53361452f}, {0.83867055f,-0.54463905f}, +{0.83146960f,-0.55557024f}, {0.82412618f,-0.56640625f}, {0.81664151f,-0.57714522f}, +{0.80901700f,-0.58778524f}, {0.80125380f,-0.59832460f}, {0.79335332f,-0.60876143f}, +{0.78531694f,-0.61909395f}, {0.77714598f,-0.62932038f}, {0.76884180f,-0.63943899f}, +{0.76040596f,-0.64944810f}, {0.75183982f,-0.65934587f}, {0.74314475f,-0.66913062f}, +{0.73432249f,-0.67880076f}, {0.72537434f,-0.68835455f}, {0.71630192f,-0.69779050f}, +{0.70710677f,-0.70710683f}, {0.69779044f,-0.71630198f}, {0.68835455f,-0.72537440f}, +{0.67880070f,-0.73432255f}, {0.66913056f,-0.74314487f}, {0.65934581f,-0.75183982f}, +{0.64944804f,-0.76040596f}, {0.63943899f,-0.76884186f}, {0.62932038f,-0.77714598f}, +{0.61909395f,-0.78531694f}, {0.60876137f,-0.79335338f}, {0.59832460f,-0.80125386f}, +{0.58778524f,-0.80901700f}, {0.57714516f,-0.81664151f}, {0.56640625f,-0.82412618f}, +{0.55557019f,-0.83146960f}, {0.54463899f,-0.83867055f}, {0.53361452f,-0.84572786f}, +{0.52249849f,-0.85264015f}, {0.51129311f,-0.85940641f}, {0.49999997f,-0.86602545f}, +{0.48862118f,-0.87249601f}, {0.47715876f,-0.87881708f}, {0.46561447f,-0.88498765f}, +{0.45399052f,-0.89100653f}, {0.44228867f,-0.89687276f}, {0.43051103f,-0.90258533f}, +{0.41865975f,-0.90814316f}, {0.40673661f,-0.91354549f}, {0.39474380f,-0.91879129f}, +{0.38268343f,-0.92387956f}, {0.37055740f,-0.92880958f}, {0.35836786f,-0.93358046f}, +{0.34611705f,-0.93819135f}, {0.33380681f,-0.94264150f}, {0.32143947f,-0.94693011f}, +{0.30901697f,-0.95105654f}, {0.29654151f,-0.95501995f}, {0.28401533f,-0.95881975f}, +{0.27144039f,-0.96245527f}, {0.25881907f,-0.96592581f}, {0.24615327f,-0.96923089f}, +{0.23344530f,-0.97236991f}, {0.22069745f,-0.97534233f}, {0.20791166f,-0.97814763f}, +{0.19509023f,-0.98078531f}, {0.18223552f,-0.98325491f}, {0.16934945f,-0.98555607f}, +{0.15643437f,-0.98768836f}, {0.14349259f,-0.98965138f}, {0.13052613f,-0.99144489f}, +{0.11753740f,-0.99306846f}, {0.10452842f,-0.99452192f}, {0.091501534f,-0.99580491f}, +{0.078459084f,-0.99691731f}, {0.065403074f,-0.99785894f}, {0.052335974f,-0.99862951f}, +{0.039259788f,-0.99922901f}, {0.026176875f,-0.99965733f}, {0.013089597f,-0.99991435f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{-4.3711388e-08f,-1.0000000f}, {-0.026176963f,-0.99965733f}, {-0.052336060f,-0.99862951f}, +{-0.078459173f,-0.99691731f}, {-0.10452851f,-0.99452192f}, {-0.13052621f,-0.99144489f}, +{-0.15643445f,-0.98768836f}, {-0.18223560f,-0.98325491f}, {-0.20791174f,-0.97814757f}, +{-0.23344538f,-0.97236991f}, {-0.25881916f,-0.96592581f}, {-0.28401542f,-0.95881969f}, +{-0.30901703f,-0.95105648f}, {-0.33380687f,-0.94264150f}, {-0.35836795f,-0.93358040f}, +{-0.38268352f,-0.92387950f}, {-0.40673670f,-0.91354543f}, {-0.43051112f,-0.90258527f}, +{-0.45399061f,-0.89100647f}, {-0.47715873f,-0.87881708f}, {-0.50000006f,-0.86602533f}, +{-0.52249867f,-0.85264009f}, {-0.54463905f,-0.83867055f}, {-0.56640631f,-0.82412612f}, +{-0.58778518f,-0.80901700f}, {-0.60876143f,-0.79335332f}, {-0.62932050f,-0.77714586f}, +{-0.64944804f,-0.76040596f}, {-0.66913068f,-0.74314475f}, {-0.68835467f,-0.72537428f}, +{-0.70710677f,-0.70710677f}, {-0.72537446f,-0.68835449f}, {-0.74314493f,-0.66913044f}, +{-0.76040596f,-0.64944804f}, {-0.77714604f,-0.62932026f}, {-0.79335332f,-0.60876143f}, +{-0.80901700f,-0.58778518f}, {-0.82412624f,-0.56640613f}, {-0.83867055f,-0.54463899f}, +{-0.85264021f,-0.52249849f}, {-0.86602539f,-0.50000006f}, {-0.87881714f,-0.47715873f}, +{-0.89100659f,-0.45399037f}, {-0.90258527f,-0.43051112f}, {-0.91354549f,-0.40673658f}, +{-0.92387956f,-0.38268328f}, {-0.93358040f,-0.35836792f}, {-0.94264150f,-0.33380675f}, +{-0.95105654f,-0.30901679f}, {-0.95881975f,-0.28401530f}, {-0.96592587f,-0.25881892f}, +{-0.97236991f,-0.23344538f}, {-0.97814763f,-0.20791161f}, {-0.98325491f,-0.18223536f}, +{-0.98768836f,-0.15643445f}, {-0.99144489f,-0.13052608f}, {-0.99452192f,-0.10452849f}, +{-0.99691737f,-0.078459039f}, {-0.99862957f,-0.052335810f}, {-0.99965733f,-0.026176952f}, +{1.0000000f,-0.0000000f}, {0.99922901f,-0.039259817f}, {0.99691731f,-0.078459099f}, +{0.99306846f,-0.11753740f}, {0.98768836f,-0.15643448f}, {0.98078525f,-0.19509032f}, +{0.97236991f,-0.23344538f}, {0.96245521f,-0.27144045f}, {0.95105648f,-0.30901700f}, +{0.93819129f,-0.34611708f}, {0.92387956f,-0.38268346f}, {0.90814316f,-0.41865975f}, +{0.89100653f,-0.45399052f}, {0.87249601f,-0.48862126f}, {0.85264015f,-0.52249855f}, +{0.83146960f,-0.55557024f}, {0.80901700f,-0.58778524f}, {0.78531694f,-0.61909395f}, +{0.76040596f,-0.64944810f}, {0.73432249f,-0.67880076f}, {0.70710677f,-0.70710683f}, +{0.67880070f,-0.73432255f}, {0.64944804f,-0.76040596f}, {0.61909395f,-0.78531694f}, +{0.58778524f,-0.80901700f}, {0.55557019f,-0.83146960f}, {0.52249849f,-0.85264015f}, +{0.48862118f,-0.87249601f}, {0.45399052f,-0.89100653f}, {0.41865975f,-0.90814316f}, +{0.38268343f,-0.92387956f}, {0.34611705f,-0.93819135f}, {0.30901697f,-0.95105654f}, +{0.27144039f,-0.96245527f}, {0.23344530f,-0.97236991f}, {0.19509023f,-0.98078531f}, +{0.15643437f,-0.98768836f}, {0.11753740f,-0.99306846f}, {0.078459084f,-0.99691731f}, +{0.039259788f,-0.99922901f}, {-4.3711388e-08f,-1.0000000f}, {-0.039259877f,-0.99922901f}, +{-0.078459173f,-0.99691731f}, {-0.11753749f,-0.99306846f}, {-0.15643445f,-0.98768836f}, +{-0.19509032f,-0.98078525f}, {-0.23344538f,-0.97236991f}, {-0.27144048f,-0.96245521f}, +{-0.30901703f,-0.95105648f}, {-0.34611711f,-0.93819129f}, {-0.38268352f,-0.92387950f}, +{-0.41865984f,-0.90814310f}, {-0.45399061f,-0.89100647f}, {-0.48862135f,-0.87249595f}, +{-0.52249867f,-0.85264009f}, {-0.55557036f,-0.83146954f}, {-0.58778518f,-0.80901700f}, +{-0.61909389f,-0.78531694f}, {-0.64944804f,-0.76040596f}, {-0.67880076f,-0.73432249f}, +{-0.70710677f,-0.70710677f}, {-0.73432249f,-0.67880070f}, {-0.76040596f,-0.64944804f}, +{-0.78531694f,-0.61909389f}, {-0.80901700f,-0.58778518f}, {-0.83146966f,-0.55557019f}, +{-0.85264021f,-0.52249849f}, {-0.87249607f,-0.48862115f}, {-0.89100659f,-0.45399037f}, +{-0.90814322f,-0.41865960f}, {-0.92387956f,-0.38268328f}, {-0.93819135f,-0.34611690f}, +{-0.95105654f,-0.30901679f}, {-0.96245521f,-0.27144048f}, {-0.97236991f,-0.23344538f}, +{-0.98078531f,-0.19509031f}, {-0.98768836f,-0.15643445f}, {-0.99306846f,-0.11753736f}, +{-0.99691737f,-0.078459039f}, {-0.99922901f,-0.039259743f}, {-1.0000000f,8.7422777e-08f}, +{-0.99922901f,0.039259918f}, {-0.99691731f,0.078459218f}, {-0.99306846f,0.11753753f}, +{-0.98768830f,0.15643461f}, {-0.98078525f,0.19509049f}, {-0.97236985f,0.23344554f}, +{-0.96245515f,0.27144065f}, {-0.95105654f,0.30901697f}, {-0.93819135f,0.34611705f}, +{-0.92387956f,0.38268346f}, {-0.90814316f,0.41865975f}, {-0.89100653f,0.45399055f}, +{-0.87249601f,0.48862129f}, {-0.85264015f,0.52249861f}, {-0.83146960f,0.55557030f}, +{-0.80901694f,0.58778536f}, {-0.78531688f,0.61909401f}, {-0.76040590f,0.64944816f}, +{-0.73432243f,0.67880082f}, {-0.70710665f,0.70710689f}, {-0.67880058f,0.73432261f}, +{-0.64944792f,0.76040608f}, {-0.61909378f,0.78531706f}, {-0.58778507f,0.80901712f}, +{-0.55557001f,0.83146977f}, {-0.52249837f,0.85264033f}, {-0.48862100f,0.87249613f}, +{-0.45399022f,0.89100665f}, {-0.41865945f,0.90814328f}, {-0.38268313f,0.92387968f}, +{-0.34611672f,0.93819147f}, {-0.30901709f,0.95105648f}, {-0.27144054f,0.96245521f}, +{-0.23344545f,0.97236991f}, {-0.19509038f,0.98078525f}, {-0.15643452f,0.98768830f}, +{-0.11753743f,0.99306846f}, {-0.078459114f,0.99691731f}, {-0.039259821f,0.99922901f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_240[240] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +{1.0000000f,-0.0000000f}, {0.99965733f,-0.026176950f}, {0.99862951f,-0.052335959f}, +{0.99691731f,-0.078459099f}, {0.99452192f,-0.10452846f}, {0.99144489f,-0.13052620f}, +{0.98768836f,-0.15643448f}, {0.98325491f,-0.18223552f}, {0.97814763f,-0.20791170f}, +{0.97236991f,-0.23344538f}, {0.96592581f,-0.25881904f}, {0.95881975f,-0.28401536f}, +{0.95105648f,-0.30901700f}, {0.94264150f,-0.33380687f}, {0.93358040f,-0.35836795f}, +{0.92387956f,-0.38268346f}, {0.91354543f,-0.40673664f}, {0.90258527f,-0.43051112f}, +{0.89100653f,-0.45399052f}, {0.87881708f,-0.47715878f}, {0.86602545f,-0.50000000f}, +{0.85264015f,-0.52249855f}, {0.83867055f,-0.54463905f}, {0.82412618f,-0.56640625f}, +{0.80901700f,-0.58778524f}, {0.79335332f,-0.60876143f}, {0.77714598f,-0.62932038f}, +{0.76040596f,-0.64944810f}, {0.74314475f,-0.66913062f}, {0.72537434f,-0.68835455f}, +{0.70710677f,-0.70710683f}, {0.68835455f,-0.72537440f}, {0.66913056f,-0.74314487f}, +{0.64944804f,-0.76040596f}, {0.62932038f,-0.77714598f}, {0.60876137f,-0.79335338f}, +{0.58778524f,-0.80901700f}, {0.56640625f,-0.82412618f}, {0.54463899f,-0.83867055f}, +{0.52249849f,-0.85264015f}, {0.49999997f,-0.86602545f}, {0.47715876f,-0.87881708f}, +{0.45399052f,-0.89100653f}, {0.43051103f,-0.90258533f}, {0.40673661f,-0.91354549f}, +{0.38268343f,-0.92387956f}, {0.35836786f,-0.93358046f}, {0.33380681f,-0.94264150f}, +{0.30901697f,-0.95105654f}, {0.28401533f,-0.95881975f}, {0.25881907f,-0.96592581f}, +{0.23344530f,-0.97236991f}, {0.20791166f,-0.97814763f}, {0.18223552f,-0.98325491f}, +{0.15643437f,-0.98768836f}, {0.13052613f,-0.99144489f}, {0.10452842f,-0.99452192f}, +{0.078459084f,-0.99691731f}, {0.052335974f,-0.99862951f}, {0.026176875f,-0.99965733f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{-4.3711388e-08f,-1.0000000f}, {-0.052336060f,-0.99862951f}, {-0.10452851f,-0.99452192f}, +{-0.15643445f,-0.98768836f}, {-0.20791174f,-0.97814757f}, {-0.25881916f,-0.96592581f}, +{-0.30901703f,-0.95105648f}, {-0.35836795f,-0.93358040f}, {-0.40673670f,-0.91354543f}, +{-0.45399061f,-0.89100647f}, {-0.50000006f,-0.86602533f}, {-0.54463905f,-0.83867055f}, +{-0.58778518f,-0.80901700f}, {-0.62932050f,-0.77714586f}, {-0.66913068f,-0.74314475f}, +{-0.70710677f,-0.70710677f}, {-0.74314493f,-0.66913044f}, {-0.77714604f,-0.62932026f}, +{-0.80901700f,-0.58778518f}, {-0.83867055f,-0.54463899f}, {-0.86602539f,-0.50000006f}, +{-0.89100659f,-0.45399037f}, {-0.91354549f,-0.40673658f}, {-0.93358040f,-0.35836792f}, +{-0.95105654f,-0.30901679f}, {-0.96592587f,-0.25881892f}, {-0.97814763f,-0.20791161f}, +{-0.98768836f,-0.15643445f}, {-0.99452192f,-0.10452849f}, {-0.99862957f,-0.052335810f}, +{1.0000000f,-0.0000000f}, {0.99691731f,-0.078459099f}, {0.98768836f,-0.15643448f}, +{0.97236991f,-0.23344538f}, {0.95105648f,-0.30901700f}, {0.92387956f,-0.38268346f}, +{0.89100653f,-0.45399052f}, {0.85264015f,-0.52249855f}, {0.80901700f,-0.58778524f}, +{0.76040596f,-0.64944810f}, {0.70710677f,-0.70710683f}, {0.64944804f,-0.76040596f}, +{0.58778524f,-0.80901700f}, {0.52249849f,-0.85264015f}, {0.45399052f,-0.89100653f}, +{0.38268343f,-0.92387956f}, {0.30901697f,-0.95105654f}, {0.23344530f,-0.97236991f}, +{0.15643437f,-0.98768836f}, {0.078459084f,-0.99691731f}, {-4.3711388e-08f,-1.0000000f}, +{-0.078459173f,-0.99691731f}, {-0.15643445f,-0.98768836f}, {-0.23344538f,-0.97236991f}, +{-0.30901703f,-0.95105648f}, {-0.38268352f,-0.92387950f}, {-0.45399061f,-0.89100647f}, +{-0.52249867f,-0.85264009f}, {-0.58778518f,-0.80901700f}, {-0.64944804f,-0.76040596f}, +{-0.70710677f,-0.70710677f}, {-0.76040596f,-0.64944804f}, {-0.80901700f,-0.58778518f}, +{-0.85264021f,-0.52249849f}, {-0.89100659f,-0.45399037f}, {-0.92387956f,-0.38268328f}, +{-0.95105654f,-0.30901679f}, {-0.97236991f,-0.23344538f}, {-0.98768836f,-0.15643445f}, +{-0.99691737f,-0.078459039f}, {-1.0000000f,8.7422777e-08f}, {-0.99691731f,0.078459218f}, +{-0.98768830f,0.15643461f}, {-0.97236985f,0.23344554f}, {-0.95105654f,0.30901697f}, +{-0.92387956f,0.38268346f}, {-0.89100653f,0.45399055f}, {-0.85264015f,0.52249861f}, +{-0.80901694f,0.58778536f}, {-0.76040590f,0.64944816f}, {-0.70710665f,0.70710689f}, +{-0.64944792f,0.76040608f}, {-0.58778507f,0.80901712f}, {-0.52249837f,0.85264033f}, +{-0.45399022f,0.89100665f}, {-0.38268313f,0.92387968f}, {-0.30901709f,0.95105648f}, +{-0.23344545f,0.97236991f}, {-0.15643452f,0.98768830f}, {-0.078459114f,0.99691731f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_120[120] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.99862951f,-0.052335959f}, {0.99452192f,-0.10452846f}, +{0.98768836f,-0.15643448f}, {0.97814763f,-0.20791170f}, {0.96592581f,-0.25881904f}, +{0.95105648f,-0.30901700f}, {0.93358040f,-0.35836795f}, {0.91354543f,-0.40673664f}, +{0.89100653f,-0.45399052f}, {0.86602545f,-0.50000000f}, {0.83867055f,-0.54463905f}, +{0.80901700f,-0.58778524f}, {0.77714598f,-0.62932038f}, {0.74314475f,-0.66913062f}, +{0.70710677f,-0.70710683f}, {0.66913056f,-0.74314487f}, {0.62932038f,-0.77714598f}, +{0.58778524f,-0.80901700f}, {0.54463899f,-0.83867055f}, {0.49999997f,-0.86602545f}, +{0.45399052f,-0.89100653f}, {0.40673661f,-0.91354549f}, {0.35836786f,-0.93358046f}, +{0.30901697f,-0.95105654f}, {0.25881907f,-0.96592581f}, {0.20791166f,-0.97814763f}, +{0.15643437f,-0.98768836f}, {0.10452842f,-0.99452192f}, {0.052335974f,-0.99862951f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{-4.3711388e-08f,-1.0000000f}, {-0.10452851f,-0.99452192f}, {-0.20791174f,-0.97814757f}, +{-0.30901703f,-0.95105648f}, {-0.40673670f,-0.91354543f}, {-0.50000006f,-0.86602533f}, +{-0.58778518f,-0.80901700f}, {-0.66913068f,-0.74314475f}, {-0.74314493f,-0.66913044f}, +{-0.80901700f,-0.58778518f}, {-0.86602539f,-0.50000006f}, {-0.91354549f,-0.40673658f}, +{-0.95105654f,-0.30901679f}, {-0.97814763f,-0.20791161f}, {-0.99452192f,-0.10452849f}, +{1.0000000f,-0.0000000f}, {0.98768836f,-0.15643448f}, {0.95105648f,-0.30901700f}, +{0.89100653f,-0.45399052f}, {0.80901700f,-0.58778524f}, {0.70710677f,-0.70710683f}, +{0.58778524f,-0.80901700f}, {0.45399052f,-0.89100653f}, {0.30901697f,-0.95105654f}, +{0.15643437f,-0.98768836f}, {-4.3711388e-08f,-1.0000000f}, {-0.15643445f,-0.98768836f}, +{-0.30901703f,-0.95105648f}, {-0.45399061f,-0.89100647f}, {-0.58778518f,-0.80901700f}, +{-0.70710677f,-0.70710677f}, {-0.80901700f,-0.58778518f}, {-0.89100659f,-0.45399037f}, +{-0.95105654f,-0.30901679f}, {-0.98768836f,-0.15643445f}, {-1.0000000f,8.7422777e-08f}, +{-0.98768830f,0.15643461f}, {-0.95105654f,0.30901697f}, {-0.89100653f,0.45399055f}, +{-0.80901694f,0.58778536f}, {-0.70710665f,0.70710689f}, {-0.58778507f,0.80901712f}, +{-0.45399022f,0.89100665f}, {-0.30901709f,0.95105648f}, {-0.15643452f,0.98768830f}, +}; +static const ne10_fft_cpx_float32_t ne10_twiddles_60[60] = { +{1.0000000f,0.0000000f}, {1.0000000f,-0.0000000f}, {1.0000000f,-0.0000000f}, +{1.0000000f,-0.0000000f}, {0.91354543f,-0.40673664f}, {0.66913056f,-0.74314487f}, +{1.0000000f,-0.0000000f}, {0.66913056f,-0.74314487f}, {-0.10452851f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.30901697f,-0.95105654f}, {-0.80901700f,-0.58778518f}, +{1.0000000f,-0.0000000f}, {-0.10452851f,-0.99452192f}, {-0.97814757f,0.20791179f}, +{1.0000000f,-0.0000000f}, {0.99452192f,-0.10452846f}, {0.97814763f,-0.20791170f}, +{0.95105648f,-0.30901700f}, {0.91354543f,-0.40673664f}, {0.86602545f,-0.50000000f}, +{0.80901700f,-0.58778524f}, {0.74314475f,-0.66913062f}, {0.66913056f,-0.74314487f}, +{0.58778524f,-0.80901700f}, {0.49999997f,-0.86602545f}, {0.40673661f,-0.91354549f}, +{0.30901697f,-0.95105654f}, {0.20791166f,-0.97814763f}, {0.10452842f,-0.99452192f}, +{1.0000000f,-0.0000000f}, {0.97814763f,-0.20791170f}, {0.91354543f,-0.40673664f}, +{0.80901700f,-0.58778524f}, {0.66913056f,-0.74314487f}, {0.49999997f,-0.86602545f}, +{0.30901697f,-0.95105654f}, {0.10452842f,-0.99452192f}, {-0.10452851f,-0.99452192f}, +{-0.30901703f,-0.95105648f}, {-0.50000006f,-0.86602533f}, {-0.66913068f,-0.74314475f}, +{-0.80901700f,-0.58778518f}, {-0.91354549f,-0.40673658f}, {-0.97814763f,-0.20791161f}, +{1.0000000f,-0.0000000f}, {0.95105648f,-0.30901700f}, {0.80901700f,-0.58778524f}, +{0.58778524f,-0.80901700f}, {0.30901697f,-0.95105654f}, {-4.3711388e-08f,-1.0000000f}, +{-0.30901703f,-0.95105648f}, {-0.58778518f,-0.80901700f}, {-0.80901700f,-0.58778518f}, +{-0.95105654f,-0.30901679f}, {-1.0000000f,8.7422777e-08f}, {-0.95105654f,0.30901697f}, +{-0.80901694f,0.58778536f}, {-0.58778507f,0.80901712f}, {-0.30901709f,0.95105648f}, +}; +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_480 = { +120, +(ne10_int32_t *)ne10_factors_480, +(ne10_fft_cpx_float32_t *)ne10_twiddles_480, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_480[120], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_480 = { +1, +(void *)&ne10_fft_state_float32_t_480, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_240 = { +60, +(ne10_int32_t *)ne10_factors_240, +(ne10_fft_cpx_float32_t *)ne10_twiddles_240, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_240[60], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_240 = { +1, +(void *)&ne10_fft_state_float32_t_240, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_120 = { +30, +(ne10_int32_t *)ne10_factors_120, +(ne10_fft_cpx_float32_t *)ne10_twiddles_120, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_120[30], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_120 = { +1, +(void *)&ne10_fft_state_float32_t_120, +}; + +static const ne10_fft_state_float32_t ne10_fft_state_float32_t_60 = { +15, +(ne10_int32_t *)ne10_factors_60, +(ne10_fft_cpx_float32_t *)ne10_twiddles_60, +NULL, +(ne10_fft_cpx_float32_t *)&ne10_twiddles_60[15], +/* is_forward_scaled = true */ +(ne10_int32_t) 1, +/* is_backward_scaled = false */ +(ne10_int32_t) 0, +}; +static const arch_fft_state cfg_arch_60 = { +1, +(void *)&ne10_fft_state_float32_t_60, +}; + +#endif /* end NE10_FFT_PARAMS48000_960 */ diff --git a/TMessagesProj/jni/opus/celt/vq.c b/TMessagesProj/jni/opus/celt/vq.c index 98a0f36c9f0..1fac70e2b44 100644 --- a/TMessagesProj/jni/opus/celt/vq.c +++ b/TMessagesProj/jni/opus/celt/vq.c @@ -37,19 +37,23 @@ #include "os_support.h" #include "bands.h" #include "rate.h" +#include "pitch.h" +#ifndef OVERRIDE_vq_exp_rotation1 static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_val16 s) { int i; + opus_val16 ms; celt_norm *Xptr; Xptr = X; + ms = NEG16(s); for (i=0;i=0;i--) @@ -57,10 +61,11 @@ static void exp_rotation1(celt_norm *X, int len, int stride, opus_val16 c, opus_ celt_norm x1, x2; x1 = Xptr[0]; x2 = Xptr[stride]; - Xptr[stride] = EXTRACT16(SHR32(MULT16_16(c,x2) + MULT16_16(s,x1), 15)); - *Xptr-- = EXTRACT16(SHR32(MULT16_16(c,x1) - MULT16_16(s,x2), 15)); + Xptr[stride] = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x2), s, x1), 15)); + *Xptr-- = EXTRACT16(PSHR32(MAC16_16(MULT16_16(c, x1), ms, x2), 15)); } } +#endif /* OVERRIDE_vq_exp_rotation1 */ static void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int spread) { @@ -91,7 +96,7 @@ static void exp_rotation(celt_norm *X, int len, int dir, int stride, int K, int } /*NOTE: As a minor optimization, we could be passing around log2(B), not B, for both this and for extract_collapse_mask().*/ - len /= stride; + len = celt_udiv(len, stride); for (i=0;i0, "alg_quant() needs at least one pulse"); - celt_assert2(N>1, "alg_quant() needs at least two dimensions"); - + (void)arch; ALLOC(y, N, celt_norm); - ALLOC(iy, N, int); - ALLOC(signx, N, opus_val16); - - exp_rotation(X, N, 1, B, K, spread); + ALLOC(signx, N, int); /* Get rid of the sign */ sum = 0; j=0; do { - if (X[j]>0) - signx[j]=1; - else { - signx[j]=-1; - X[j]=-X[j]; - } + signx[j] = X[j]<0; + /* OPT: Make sure the compiler doesn't use a branch on ABS16(). */ + X[j] = ABS16(X[j]); iy[j] = 0; y[j] = 0; } while (++j=1, "Allocated too many pulses in the quick pass"); + celt_assert2(pulsesLeft>=0, "Allocated too many pulses in the quick pass"); /* This should never happen, but just in case it does (e.g. on silence) we fill the first bin with pulses. */ @@ -249,12 +246,12 @@ unsigned alg_quant(celt_norm *X, int N, int K, int spread, int B, ec_enc *enc pulsesLeft=0; } - s = 1; for (i=0;i= best_num/best_den, but that way we can do it without any division */ - /* OPT: Make sure to use conditional moves here */ - if (MULT16_16(best_den, Rxy) > MULT16_16(Ryy, best_num)) + /* OPT: It's not clear whether a cmov is faster than a branch here + since the condition is more often false than true and using + a cmov introduces data dependencies across iterations. The optimal + choice may be architecture-dependent. */ + if (opus_unlikely(MULT16_16(best_den, Rxy) > MULT16_16(Ryy, best_num))) { best_den = Ryy; best_num = Rxy; @@ -294,23 +307,47 @@ unsigned alg_quant(celt_norm *X, int N, int K, int spread, int B, ec_enc *enc /* Only now that we've made the final choice, update y/iy */ /* Multiplying y[j] by 2 so we don't have to do it everywhere else */ - y[best_id] += 2*s; + y[best_id] += 2; iy[best_id]++; } /* Put the original sign back */ j=0; do { - X[j] = MULT16_16(signx[j],X[j]); - if (signx[j] < 0) - iy[j] = -iy[j]; + /*iy[j] = signx[j] ? -iy[j] : iy[j];*/ + /* OPT: The is more likely to be compiled without a branch than the code above + but has the same performance otherwise. */ + iy[j] = (iy[j]^-signx[j]) + signx[j]; } while (++j0, "alg_quant() needs at least one pulse"); + celt_assert2(N>1, "alg_quant() needs at least two dimensions"); + + /* Covers vectorization by up to 4. */ + ALLOC(iy, N+3, int); + + exp_rotation(X, N, 1, B, K, spread); + + yy = op_pvq_search(X, iy, K, N, arch); + encode_pulses(iy, N, K, enc); -#ifdef RESYNTH - normalise_residual(iy, X, N, yy, gain); - exp_rotation(X, N, -1, B, K, spread); -#endif + if (resynth) + { + normalise_residual(iy, X, N, yy, gain); + exp_rotation(X, N, -1, B, K, spread); + } collapse_mask = extract_collapse_mask(iy, N, B); RESTORE_STACK; @@ -322,7 +359,6 @@ unsigned alg_quant(celt_norm *X, int N, int K, int spread, int B, ec_enc *enc unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, ec_dec *dec, opus_val16 gain) { - int i; opus_val32 Ryy; unsigned collapse_mask; VARDECL(int, iy); @@ -331,12 +367,7 @@ unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, celt_assert2(K>0, "alg_unquant() needs at least one pulse"); celt_assert2(N>1, "alg_unquant() needs at least two dimensions"); ALLOC(iy, N, int); - decode_pulses(iy, N, K, dec); - Ryy = 0; - i=0; - do { - Ryy = MAC16_16(Ryy, iy[i], iy[i]); - } while (++i < N); + Ryy = decode_pulses(iy, N, K, dec); normalise_residual(iy, X, N, Ryy, gain); exp_rotation(X, N, -1, B, K, spread); collapse_mask = extract_collapse_mask(iy, N, B); @@ -344,21 +375,18 @@ unsigned alg_unquant(celt_norm *X, int N, int K, int spread, int B, return collapse_mask; } -void renormalise_vector(celt_norm *X, int N, opus_val16 gain) +#ifndef OVERRIDE_renormalise_vector +void renormalise_vector(celt_norm *X, int N, opus_val16 gain, int arch) { int i; #ifdef FIXED_POINT int k; #endif - opus_val32 E = EPSILON; + opus_val32 E; opus_val16 g; opus_val32 t; - celt_norm *xptr = X; - for (i=0;i>1; #endif @@ -373,8 +401,9 @@ void renormalise_vector(celt_norm *X, int N, opus_val16 gain) } /*return celt_sqrt(E);*/ } +#endif /* OVERRIDE_renormalise_vector */ -int stereo_itheta(celt_norm *X, celt_norm *Y, int stereo, int N) +int stereo_itheta(const celt_norm *X, const celt_norm *Y, int stereo, int N, int arch) { int i; int itheta; @@ -393,14 +422,8 @@ int stereo_itheta(celt_norm *X, celt_norm *Y, int stereo, int N) Eside = MAC16_16(Eside, s, s); } } else { - for (i=0;i +#include +#include +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" +#include "x86cpu.h" + +#if defined(FIXED_POINT) + +void celt_fir_sse4_1(const opus_val16 *_x, + const opus_val16 *num, + opus_val16 *_y, + int N, + int ord, + opus_val16 *mem, + int arch) +{ + int i,j; + VARDECL(opus_val16, rnum); + VARDECL(opus_val16, x); + + __m128i vecNoA; + opus_int32 noA ; + SAVE_STACK; + + ALLOC(rnum, ord, opus_val16); + ALLOC(x, N+ord, opus_val16); + for(i=0;i> 1; + vecNoA = _mm_set_epi32(noA, noA, noA, noA); + + for (i=0;i +#include "arch.h" + +void xcorr_kernel_sse(const opus_val16 *x, const opus_val16 *y, opus_val32 sum[4], int len) +{ + int j; + __m128 xsum1, xsum2; + xsum1 = _mm_loadu_ps(sum); + xsum2 = _mm_setzero_ps(); + + for (j = 0; j < len-3; j += 4) + { + __m128 x0 = _mm_loadu_ps(x+j); + __m128 yj = _mm_loadu_ps(y+j); + __m128 y3 = _mm_loadu_ps(y+j+3); + + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x00),yj)); + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x55), + _mm_shuffle_ps(yj,y3,0x49))); + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xaa), + _mm_shuffle_ps(yj,y3,0x9e))); + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xff),y3)); + } + if (j < len) + { + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + if (++j < len) + { + xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + if (++j < len) + { + xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); + } + } + } + _mm_storeu_ps(sum,_mm_add_ps(xsum1,xsum2)); +} + + +void dual_inner_prod_sse(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, + int N, opus_val32 *xy1, opus_val32 *xy2) +{ + int i; + __m128 xsum1, xsum2; + xsum1 = _mm_setzero_ps(); + xsum2 = _mm_setzero_ps(); + for (i=0;i -#include "arch.h" +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) && defined(FIXED_POINT) +void xcorr_kernel_sse4_1( + const opus_int16 *x, + const opus_int16 *y, + opus_val32 sum[4], + int len); +#endif +#if defined(OPUS_X86_MAY_HAVE_SSE) && !defined(FIXED_POINT) +void xcorr_kernel_sse( + const opus_val16 *x, + const opus_val16 *y, + opus_val32 sum[4], + int len); +#endif + +#if defined(OPUS_X86_PRESUME_SSE4_1) && defined(FIXED_POINT) #define OVERRIDE_XCORR_KERNEL -static OPUS_INLINE void xcorr_kernel(const opus_val16 *x, const opus_val16 *y, opus_val32 sum[4], int len) -{ - int j; - __m128 xsum1, xsum2; - xsum1 = _mm_loadu_ps(sum); - xsum2 = _mm_setzero_ps(); - - for (j = 0; j < len-3; j += 4) - { - __m128 x0 = _mm_loadu_ps(x+j); - __m128 yj = _mm_loadu_ps(y+j); - __m128 y3 = _mm_loadu_ps(y+j+3); - - xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x00),yj)); - xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0x55), - _mm_shuffle_ps(yj,y3,0x49))); - xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xaa), - _mm_shuffle_ps(yj,y3,0x9e))); - xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_shuffle_ps(x0,x0,0xff),y3)); - } - if (j < len) - { - xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); - if (++j < len) - { - xsum2 = _mm_add_ps(xsum2,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); - if (++j < len) - { - xsum1 = _mm_add_ps(xsum1,_mm_mul_ps(_mm_load1_ps(x+j),_mm_loadu_ps(y+j))); - } - } - } - _mm_storeu_ps(sum,_mm_add_ps(xsum1,xsum2)); -} +#define xcorr_kernel(x, y, sum, len, arch) \ + ((void)arch, xcorr_kernel_sse4_1(x, y, sum, len)) -#define OVERRIDE_DUAL_INNER_PROD -static OPUS_INLINE void dual_inner_prod(const opus_val16 *x, const opus_val16 *y01, const opus_val16 *y02, - int N, opus_val32 *xy1, opus_val32 *xy2) -{ - int i; - __m128 xsum1, xsum2; - xsum1 = _mm_setzero_ps(); - xsum2 = _mm_setzero_ps(); - for (i=0;i +#include + +#include "macros.h" +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE2) && defined(FIXED_POINT) +opus_val32 celt_inner_prod_sse2(const opus_val16 *x, const opus_val16 *y, + int N) +{ + opus_int i, dataSize16; + opus_int32 sum; + + __m128i inVec1_76543210, inVec1_FEDCBA98, acc1; + __m128i inVec2_76543210, inVec2_FEDCBA98, acc2; + + sum = 0; + dataSize16 = N & ~15; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for (i=0;i= 8) + { + inVec1_76543210 = _mm_loadu_si128((__m128i *)(&x[i + 0])); + inVec2_76543210 = _mm_loadu_si128((__m128i *)(&y[i + 0])); + + inVec1_76543210 = _mm_madd_epi16(inVec1_76543210, inVec2_76543210); + + acc1 = _mm_add_epi32(acc1, inVec1_76543210); + i += 8; + } + + acc1 = _mm_add_epi32(acc1, _mm_unpackhi_epi64( acc1, acc1)); + acc1 = _mm_add_epi32(acc1, _mm_shufflelo_epi16( acc1, 0x0E)); + sum += _mm_cvtsi128_si32(acc1); + + for (;i +#include + +#include "macros.h" +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "pitch.h" + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) && defined(FIXED_POINT) +#include +#include "x86cpu.h" + +opus_val32 celt_inner_prod_sse4_1(const opus_val16 *x, const opus_val16 *y, + int N) +{ + opus_int i, dataSize16; + opus_int32 sum; + __m128i inVec1_76543210, inVec1_FEDCBA98, acc1; + __m128i inVec2_76543210, inVec2_FEDCBA98, acc2; + __m128i inVec1_3210, inVec2_3210; + + sum = 0; + dataSize16 = N & ~15; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for (i=0;i= 8) + { + inVec1_76543210 = _mm_loadu_si128((__m128i *)(&x[i + 0])); + inVec2_76543210 = _mm_loadu_si128((__m128i *)(&y[i + 0])); + + inVec1_76543210 = _mm_madd_epi16(inVec1_76543210, inVec2_76543210); + + acc1 = _mm_add_epi32(acc1, inVec1_76543210); + i += 8; + } + + if (N - i >= 4) + { + inVec1_3210 = OP_CVTEPI16_EPI32_M64(&x[i + 0]); + inVec2_3210 = OP_CVTEPI16_EPI32_M64(&y[i + 0]); + + inVec1_3210 = _mm_mullo_epi32(inVec1_3210, inVec2_3210); + + acc1 = _mm_add_epi32(acc1, inVec1_3210); + i += 4; + } + + acc1 = _mm_add_epi32(acc1, _mm_unpackhi_epi64(acc1, acc1)); + acc1 = _mm_add_epi32(acc1, _mm_shufflelo_epi16(acc1, 0x0E)); + + sum += _mm_cvtsi128_si32(acc1); + + for (;i= 3); + + sum0 = _mm_setzero_si128(); + sum1 = _mm_setzero_si128(); + sum2 = _mm_setzero_si128(); + sum3 = _mm_setzero_si128(); + + for (j=0;j<(len-7);j+=8) + { + vecX = _mm_loadu_si128((__m128i *)(&x[j + 0])); + vecY0 = _mm_loadu_si128((__m128i *)(&y[j + 0])); + vecY1 = _mm_loadu_si128((__m128i *)(&y[j + 1])); + vecY2 = _mm_loadu_si128((__m128i *)(&y[j + 2])); + vecY3 = _mm_loadu_si128((__m128i *)(&y[j + 3])); + + sum0 = _mm_add_epi32(sum0, _mm_madd_epi16(vecX, vecY0)); + sum1 = _mm_add_epi32(sum1, _mm_madd_epi16(vecX, vecY1)); + sum2 = _mm_add_epi32(sum2, _mm_madd_epi16(vecX, vecY2)); + sum3 = _mm_add_epi32(sum3, _mm_madd_epi16(vecX, vecY3)); + } + + sum0 = _mm_add_epi32(sum0, _mm_unpackhi_epi64( sum0, sum0)); + sum0 = _mm_add_epi32(sum0, _mm_shufflelo_epi16( sum0, 0x0E)); + + sum1 = _mm_add_epi32(sum1, _mm_unpackhi_epi64( sum1, sum1)); + sum1 = _mm_add_epi32(sum1, _mm_shufflelo_epi16( sum1, 0x0E)); + + sum2 = _mm_add_epi32(sum2, _mm_unpackhi_epi64( sum2, sum2)); + sum2 = _mm_add_epi32(sum2, _mm_shufflelo_epi16( sum2, 0x0E)); + + sum3 = _mm_add_epi32(sum3, _mm_unpackhi_epi64( sum3, sum3)); + sum3 = _mm_add_epi32(sum3, _mm_shufflelo_epi16( sum3, 0x0E)); + + vecSum = _mm_unpacklo_epi64(_mm_unpacklo_epi32(sum0, sum1), + _mm_unpacklo_epi32(sum2, sum3)); + + for (;j<(len-3);j+=4) + { + vecX = OP_CVTEPI16_EPI32_M64(&x[j + 0]); + vecX0 = _mm_shuffle_epi32(vecX, 0x00); + vecX1 = _mm_shuffle_epi32(vecX, 0x55); + vecX2 = _mm_shuffle_epi32(vecX, 0xaa); + vecX3 = _mm_shuffle_epi32(vecX, 0xff); + + vecY0 = OP_CVTEPI16_EPI32_M64(&y[j + 0]); + vecY1 = OP_CVTEPI16_EPI32_M64(&y[j + 1]); + vecY2 = OP_CVTEPI16_EPI32_M64(&y[j + 2]); + vecY3 = OP_CVTEPI16_EPI32_M64(&y[j + 3]); + + sum0 = _mm_mullo_epi32(vecX0, vecY0); + sum1 = _mm_mullo_epi32(vecX1, vecY1); + sum2 = _mm_mullo_epi32(vecX2, vecY2); + sum3 = _mm_mullo_epi32(vecX3, vecY3); + + sum0 = _mm_add_epi32(sum0, sum1); + sum2 = _mm_add_epi32(sum2, sum3); + vecSum = _mm_add_epi32(vecSum, sum0); + vecSum = _mm_add_epi32(vecSum, sum2); + } + + for (;j +#include +#include "celt_lpc.h" +#include "stack_alloc.h" +#include "mathops.h" +#include "vq.h" +#include "x86cpu.h" + + +#ifndef FIXED_POINT + +opus_val16 op_pvq_search_sse2(celt_norm *_X, int *iy, int K, int N, int arch) +{ + int i, j; + int pulsesLeft; + float xy, yy; + VARDECL(celt_norm, y); + VARDECL(celt_norm, X); + VARDECL(float, signy); + __m128 signmask; + __m128 sums; + __m128i fours; + SAVE_STACK; + + (void)arch; + /* All bits set to zero, except for the sign bit. */ + signmask = _mm_set_ps1(-0.f); + fours = _mm_set_epi32(4, 4, 4, 4); + ALLOC(y, N+3, celt_norm); + ALLOC(X, N+3, celt_norm); + ALLOC(signy, N+3, float); + + OPUS_COPY(X, _X, N); + X[N] = X[N+1] = X[N+2] = 0; + sums = _mm_setzero_ps(); + for (j=0;j (N>>1)) + { + __m128i pulses_sum; + __m128 yy4, xy4; + __m128 rcp4; + opus_val32 sum = _mm_cvtss_f32(sums); + /* If X is too small, just replace it with a pulse at 0 */ + /* Prevents infinities and NaNs from causing too many pulses + to be allocated. 64 is an approximation of infinity here. */ + if (!(sum > EPSILON && sum < 64)) + { + X[0] = QCONST16(1.f,14); + j=1; do + X[j]=0; + while (++j=0, "Allocated too many pulses in the quick pass"); + + /* This should never happen, but just in case it does (e.g. on silence) + we fill the first bin with pulses. */ + if (pulsesLeft > N+3) + { + opus_val16 tmp = (opus_val16)pulsesLeft; + yy = MAC16_16(yy, tmp, tmp); + yy = MAC16_16(yy, tmp, y[0]); + iy[0] += pulsesLeft; + pulsesLeft=0; + } + + for (i=0;i +static _inline void cpuid(unsigned int CPUInfo[4], unsigned int InfoType) +{ + __cpuid((int*)CPUInfo, InfoType); +} + +#else + +#if defined(CPU_INFO_BY_C) +#include +#endif + +static void cpuid(unsigned int CPUInfo[4], unsigned int InfoType) +{ +#if defined(CPU_INFO_BY_ASM) +#if defined(__i386__) && defined(__PIC__) +/* %ebx is PIC register in 32-bit, so mustn't clobber it. */ + __asm__ __volatile__ ( + "xchg %%ebx, %1\n" + "cpuid\n" + "xchg %%ebx, %1\n": + "=a" (CPUInfo[0]), + "=r" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) : + "0" (InfoType) + ); +#else + __asm__ __volatile__ ( + "cpuid": + "=a" (CPUInfo[0]), + "=b" (CPUInfo[1]), + "=c" (CPUInfo[2]), + "=d" (CPUInfo[3]) : + "0" (InfoType) + ); +#endif +#elif defined(CPU_INFO_BY_C) + __get_cpuid(InfoType, &(CPUInfo[0]), &(CPUInfo[1]), &(CPUInfo[2]), &(CPUInfo[3])); +#endif +} + +#endif + +typedef struct CPU_Feature{ + /* SIMD: 128-bit */ + int HW_SSE; + int HW_SSE2; + int HW_SSE41; + /* SIMD: 256-bit */ + int HW_AVX; +} CPU_Feature; + +static void opus_cpu_feature_check(CPU_Feature *cpu_feature) +{ + unsigned int info[4] = {0}; + unsigned int nIds = 0; + + cpuid(info, 0); + nIds = info[0]; + + if (nIds >= 1){ + cpuid(info, 1); + cpu_feature->HW_SSE = (info[3] & (1 << 25)) != 0; + cpu_feature->HW_SSE2 = (info[3] & (1 << 26)) != 0; + cpu_feature->HW_SSE41 = (info[2] & (1 << 19)) != 0; + cpu_feature->HW_AVX = (info[2] & (1 << 28)) != 0; + } + else { + cpu_feature->HW_SSE = 0; + cpu_feature->HW_SSE2 = 0; + cpu_feature->HW_SSE41 = 0; + cpu_feature->HW_AVX = 0; + } +} + +int opus_select_arch(void) +{ + CPU_Feature cpu_feature; + int arch; + + opus_cpu_feature_check(&cpu_feature); + + arch = 0; + if (!cpu_feature.HW_SSE) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_SSE2) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_SSE41) + { + return arch; + } + arch++; + + if (!cpu_feature.HW_AVX) + { + return arch; + } + arch++; + + return arch; +} + +#endif diff --git a/TMessagesProj/jni/opus/celt/x86/x86cpu.h b/TMessagesProj/jni/opus/celt/x86/x86cpu.h new file mode 100644 index 00000000000..04fd48aac4b --- /dev/null +++ b/TMessagesProj/jni/opus/celt/x86/x86cpu.h @@ -0,0 +1,93 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if !defined(X86CPU_H) +# define X86CPU_H + +# if defined(OPUS_X86_MAY_HAVE_SSE) +# define MAY_HAVE_SSE(name) name ## _sse +# else +# define MAY_HAVE_SSE(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE2) +# define MAY_HAVE_SSE2(name) name ## _sse2 +# else +# define MAY_HAVE_SSE2(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_SSE4_1) +# define MAY_HAVE_SSE4_1(name) name ## _sse4_1 +# else +# define MAY_HAVE_SSE4_1(name) name ## _c +# endif + +# if defined(OPUS_X86_MAY_HAVE_AVX) +# define MAY_HAVE_AVX(name) name ## _avx +# else +# define MAY_HAVE_AVX(name) name ## _c +# endif + +# if defined(OPUS_HAVE_RTCD) +int opus_select_arch(void); +# endif + +/*gcc appears to emit MOVDQA's to load the argument of an _mm_cvtepi8_epi32() + or _mm_cvtepi16_epi32() when optimizations are disabled, even though the + actual PMOVSXWD instruction takes an m32 or m64. Unlike a normal memory + reference, these require 16-byte alignment and load a full 16 bytes (instead + of 4 or 8), possibly reading out of bounds. + + We can insert an explicit MOVD or MOVQ using _mm_cvtsi32_si128() or + _mm_loadl_epi64(), which should have the same semantics as an m32 or m64 + reference in the PMOVSXWD instruction itself, but gcc is not smart enough to + optimize this out when optimizations ARE enabled. + + Clang, in contrast, requires us to do this always for _mm_cvtepi8_epi32 + (which is fair, since technically the compiler is always allowed to do the + dereference before invoking the function implementing the intrinsic). + However, it is smart enough to eliminate the extra MOVD instruction. + For _mm_cvtepi16_epi32, it does the right thing, though does *not* optimize out + the extra MOVQ if it's specified explicitly */ + +# if defined(__clang__) || !defined(__OPTIMIZE__) +# define OP_CVTEPI8_EPI32_M32(x) \ + (_mm_cvtepi8_epi32(_mm_cvtsi32_si128(*(int *)(x)))) +# else +# define OP_CVTEPI8_EPI32_M32(x) \ + (_mm_cvtepi8_epi32(*(__m128i *)(x))) +#endif + +# if !defined(__OPTIMIZE__) +# define OP_CVTEPI16_EPI32_M64(x) \ + (_mm_cvtepi16_epi32(_mm_loadl_epi64((__m128i *)(x)))) +# else +# define OP_CVTEPI16_EPI32_M64(x) \ + (_mm_cvtepi16_epi32(*(__m128i *)(x))) +# endif + +#endif diff --git a/TMessagesProj/jni/opus/include/opus.h b/TMessagesProj/jni/opus/include/opus.h index 93a53a2ffcd..5be73ddf4e5 100644 --- a/TMessagesProj/jni/opus/include/opus.h +++ b/TMessagesProj/jni/opus/include/opus.h @@ -142,7 +142,7 @@ extern "C" { * * opus_encode() and opus_encode_float() return the number of bytes actually written to the packet. * The return value can be negative, which indicates that an error has occurred. If the return value - * is 1 byte, then the packet does not need to be transmitted (DTX). + * is 2 bytes or less, then the packet does not need to be transmitted (DTX). * * Once the encoder state if no longer needed, it can be destroyed with * @@ -616,7 +616,10 @@ OPUS_EXPORT void opus_pcm_soft_clip(float *pcm, int frame_size, int channels, fl * merged. Splitting valid Opus packets is always guaranteed to succeed, * whereas merging valid packets only succeeds if all frames have the same * mode, bandwidth, and frame size, and when the total duration of the merged - * packet is no more than 120 ms. + * packet is no more than 120 ms. The 120 ms limit comes from the + * specification and limits decoder memory requirements at a point where + * framing overhead becomes negligible. + * * The repacketizer currently only operates on elementary Opus * streams. It will not manipualte multistream packets successfully, except in * the degenerate case where they consist of data from a single stream. diff --git a/TMessagesProj/jni/opus/include/opus_defines.h b/TMessagesProj/jni/opus/include/opus_defines.h index 265089f65e3..38a81432a3a 100644 --- a/TMessagesProj/jni/opus/include/opus_defines.h +++ b/TMessagesProj/jni/opus/include/opus_defines.h @@ -46,7 +46,7 @@ extern "C" { #define OPUS_OK 0 /** One or more invalid/out of range arguments @hideinitializer*/ #define OPUS_BAD_ARG -1 -/** The mode struct passed is invalid @hideinitializer*/ +/** Not enough bytes allocated in the buffer @hideinitializer*/ #define OPUS_BUFFER_TOO_SMALL -2 /** An internal error was detected @hideinitializer*/ #define OPUS_INTERNAL_ERROR -3 @@ -65,7 +65,7 @@ extern "C" { #ifndef OPUS_EXPORT # if defined(WIN32) -# ifdef OPUS_BUILD +# if defined(OPUS_BUILD) && defined(DLL_EXPORT) # define OPUS_EXPORT __declspec(dllexport) # else # define OPUS_EXPORT @@ -165,8 +165,9 @@ extern "C" { #define OPUS_GET_EXPERT_FRAME_DURATION_REQUEST 4041 #define OPUS_SET_PREDICTION_DISABLED_REQUEST 4042 #define OPUS_GET_PREDICTION_DISABLED_REQUEST 4043 - /* Don't use 4045, it's already taken by OPUS_GET_GAIN_REQUEST */ +#define OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST 4046 +#define OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST 4047 /* Macros to trigger compilation errors when the wrong types are provided to a CTL */ #define __opus_check_int(x) (((void)((x) == (opus_int32)0)), (opus_int32)(x)) @@ -208,6 +209,9 @@ extern "C" { #define OPUS_FRAMESIZE_20_MS 5004 /**< Use 20 ms frames */ #define OPUS_FRAMESIZE_40_MS 5005 /**< Use 40 ms frames */ #define OPUS_FRAMESIZE_60_MS 5006 /**< Use 60 ms frames */ +#define OPUS_FRAMESIZE_80_MS 5007 /**< Use 80 ms frames */ +#define OPUS_FRAMESIZE_100_MS 5008 /**< Use 100 ms frames */ +#define OPUS_FRAMESIZE_120_MS 5009 /**< Use 120 ms frames */ /**@}*/ @@ -274,7 +278,6 @@ extern "C" { /** Enables or disables variable bitrate (VBR) in the encoder. * The configured bitrate may not be met exactly because frames must * be an integer number of bytes in length. - * @warning Only the MDCT mode of Opus can provide hard CBR behavior. * @see OPUS_GET_VBR * @see OPUS_SET_VBR_CONSTRAINT * @param[in] x opus_int32: Allowed values: @@ -454,14 +457,6 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_APPLICATION(x) OPUS_GET_APPLICATION_REQUEST, __opus_check_int_ptr(x) -/** Gets the sampling rate the encoder or decoder was initialized with. - * This simply returns the Fs value passed to opus_encoder_init() - * or opus_decoder_init(). - * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. - * @hideinitializer - */ -#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) - /** Gets the total samples of delay added by the entire codec. * This can be queried by the encoder and then the provided number of samples can be * skipped on from the start of the decoder's output to provide time aligned input @@ -498,9 +493,9 @@ extern "C" { #define OPUS_GET_INBAND_FEC(x) OPUS_GET_INBAND_FEC_REQUEST, __opus_check_int_ptr(x) /** Configures the encoder's expected packet loss percentage. - * Higher values with trigger progressively more loss resistant behavior in the encoder - * at the expense of quality at a given bitrate in the lossless case, but greater quality - * under loss. + * Higher values trigger progressively more loss resistant behavior in the encoder + * at the expense of quality at a given bitrate in the absence of packet loss, but + * greater quality under loss. * @see OPUS_GET_PACKET_LOSS_PERC * @param[in] x opus_int32: Loss percentage in the range 0-100, inclusive (default: 0). * @hideinitializer */ @@ -532,7 +527,19 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_DTX(x) OPUS_GET_DTX_REQUEST, __opus_check_int_ptr(x) /** Configures the depth of signal being encoded. + * * This is a hint which helps the encoder identify silence and near-silence. + * It represents the number of significant bits of linear intensity below + * which the signal contains ignorable quantization or other noise. + * + * For example, OPUS_SET_LSB_DEPTH(14) would be an appropriate setting + * for G.711 u-law input. OPUS_SET_LSB_DEPTH(16) would be appropriate + * for 16-bit linear pcm input with opus_encode_float(). + * + * When using opus_encode() instead of opus_encode_float(), or when libopus + * is compiled for fixed-point, the encoder uses the minimum of the value + * set here and the value 16. + * * @see OPUS_GET_LSB_DEPTH * @param[in] x opus_int32: Input precision in bits, between 8 and 24 * (default: 24). @@ -545,11 +552,6 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_LSB_DEPTH(x) OPUS_GET_LSB_DEPTH_REQUEST, __opus_check_int_ptr(x) -/** Gets the duration (in samples) of the last packet successfully decoded or concealed. - * @param[out] x opus_int32 *: Number of samples (at current sampling rate). - * @hideinitializer */ -#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) - /** Configures the encoder's use of variable duration frames. * When variable duration is enabled, the encoder is free to use a shorter frame * size than the one requested in the opus_encode*() call. @@ -558,41 +560,59 @@ extern "C" { * packet. The part of the audio that was not encoded needs to be resent to the * encoder for the next call. Do not use this option unless you really * know what you are doing. - * @see OPUS_GET_EXPERT_VARIABLE_DURATION + * @see OPUS_GET_EXPERT_FRAME_DURATION * @param[in] x opus_int32: Allowed values: *
*
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
*
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
- *
OPUS_FRAMESIZE_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
*
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
*
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
*
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
*
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
*
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
*
* @hideinitializer */ #define OPUS_SET_EXPERT_FRAME_DURATION(x) OPUS_SET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int(x) /** Gets the encoder's configured use of variable duration frames. - * @see OPUS_SET_EXPERT_VARIABLE_DURATION + * @see OPUS_SET_EXPERT_FRAME_DURATION * @param[out] x opus_int32 *: Returns one of the following values: *
*
OPUS_FRAMESIZE_ARG
Select frame size from the argument (default).
*
OPUS_FRAMESIZE_2_5_MS
Use 2.5 ms frames.
- *
OPUS_FRAMESIZE_5_MS
Use 2.5 ms frames.
+ *
OPUS_FRAMESIZE_5_MS
Use 5 ms frames.
*
OPUS_FRAMESIZE_10_MS
Use 10 ms frames.
*
OPUS_FRAMESIZE_20_MS
Use 20 ms frames.
*
OPUS_FRAMESIZE_40_MS
Use 40 ms frames.
*
OPUS_FRAMESIZE_60_MS
Use 60 ms frames.
+ *
OPUS_FRAMESIZE_80_MS
Use 80 ms frames.
+ *
OPUS_FRAMESIZE_100_MS
Use 100 ms frames.
+ *
OPUS_FRAMESIZE_120_MS
Use 120 ms frames.
*
OPUS_FRAMESIZE_VARIABLE
Optimize the frame size dynamically.
*
* @hideinitializer */ #define OPUS_GET_EXPERT_FRAME_DURATION(x) OPUS_GET_EXPERT_FRAME_DURATION_REQUEST, __opus_check_int_ptr(x) /** If set to 1, disables almost all use of prediction, making frames almost - completely independent. This reduces quality. (default : 0) + * completely independent. This reduces quality. + * @see OPUS_GET_PREDICTION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable prediction (default).
+ *
1
Disable prediction.
+ *
* @hideinitializer */ #define OPUS_SET_PREDICTION_DISABLED(x) OPUS_SET_PREDICTION_DISABLED_REQUEST, __opus_check_int(x) /** Gets the encoder's configured prediction status. + * @see OPUS_SET_PREDICTION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Prediction enabled (default).
+ *
1
Prediction disabled.
+ *
* @hideinitializer */ #define OPUS_GET_PREDICTION_DISABLED(x) OPUS_GET_PREDICTION_DISABLED_REQUEST, __opus_check_int_ptr(x) @@ -649,18 +669,6 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_FINAL_RANGE(x) OPUS_GET_FINAL_RANGE_REQUEST, __opus_check_uint_ptr(x) -/** Gets the pitch of the last decoded frame, if available. - * This can be used for any post-processing algorithm requiring the use of pitch, - * e.g. time stretching/shortening. If the last frame was not voiced, or if the - * pitch was not coded in the frame, then zero is returned. - * - * This CTL is only implemented for decoder instances. - * - * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) - * - * @hideinitializer */ -#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) - /** Gets the encoder's configured bandpass or the decoder's last bandpass. * @see OPUS_SET_BANDWIDTH * @param[out] x opus_int32 *: Returns one of the following values: @@ -675,6 +683,38 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_BANDWIDTH(x) OPUS_GET_BANDWIDTH_REQUEST, __opus_check_int_ptr(x) +/** Gets the sampling rate the encoder or decoder was initialized with. + * This simply returns the Fs value passed to opus_encoder_init() + * or opus_decoder_init(). + * @param[out] x opus_int32 *: Sampling rate of encoder or decoder. + * @hideinitializer + */ +#define OPUS_GET_SAMPLE_RATE(x) OPUS_GET_SAMPLE_RATE_REQUEST, __opus_check_int_ptr(x) + +/** If set to 1, disables the use of phase inversion for intensity stereo, + * improving the quality of mono downmixes, but slightly reducing normal + * stereo quality. Disabling phase inversion in the decoder does not comply + * with RFC 6716, although it does not cause any interoperability issue and + * is expected to become part of the Opus standard once RFC 6716 is updated + * by draft-ietf-codec-opus-update. + * @see OPUS_GET_PHASE_INVERSION_DISABLED + * @param[in] x opus_int32: Allowed values: + *
+ *
0
Enable phase inversion (default).
+ *
1
Disable phase inversion.
+ *
+ * @hideinitializer */ +#define OPUS_SET_PHASE_INVERSION_DISABLED(x) OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int(x) +/** Gets the encoder's configured phase inversion status. + * @see OPUS_SET_PHASE_INVERSION_DISABLED + * @param[out] x opus_int32 *: Returns one of the following values: + *
+ *
0
Stereo phase inversion enabled (default).
+ *
1
Stereo phase inversion disabled.
+ *
+ * @hideinitializer */ +#define OPUS_GET_PHASE_INVERSION_DISABLED(x) OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST, __opus_check_int_ptr(x) + /**@}*/ /** @defgroup opus_decoderctls Decoder related CTLs @@ -699,6 +739,23 @@ extern "C" { * @hideinitializer */ #define OPUS_GET_GAIN(x) OPUS_GET_GAIN_REQUEST, __opus_check_int_ptr(x) +/** Gets the duration (in samples) of the last packet successfully decoded or concealed. + * @param[out] x opus_int32 *: Number of samples (at current sampling rate). + * @hideinitializer */ +#define OPUS_GET_LAST_PACKET_DURATION(x) OPUS_GET_LAST_PACKET_DURATION_REQUEST, __opus_check_int_ptr(x) + +/** Gets the pitch of the last decoded frame, if available. + * This can be used for any post-processing algorithm requiring the use of pitch, + * e.g. time stretching/shortening. If the last frame was not voiced, or if the + * pitch was not coded in the frame, then zero is returned. + * + * This CTL is only implemented for decoder instances. + * + * @param[out] x opus_int32 *: pitch period at 48 kHz (or 0 if not available) + * + * @hideinitializer */ +#define OPUS_GET_PITCH(x) OPUS_GET_PITCH_REQUEST, __opus_check_int_ptr(x) + /**@}*/ /** @defgroup opus_libinfo Opus library information functions @@ -713,6 +770,10 @@ extern "C" { OPUS_EXPORT const char *opus_strerror(int error); /** Gets the libopus version string. + * + * Applications may look for the substring "-fixed" in the version string to + * determine whether they have a fixed-point or floating-point build at + * runtime. * * @returns Version string */ diff --git a/TMessagesProj/jni/opus/include/opus_multistream.h b/TMessagesProj/jni/opus/include/opus_multistream.h index ae5997934ae..3622e009fb5 100644 --- a/TMessagesProj/jni/opus/include/opus_multistream.h +++ b/TMessagesProj/jni/opus/include/opus_multistream.h @@ -110,10 +110,10 @@ extern "C" { * packets produced by the encoder. Some basic information, such as packet * duration, can be computed without any special negotiation. * - * The format for multistream Opus packets is defined in the - * Ogg - * encapsulation specification and is based on the self-delimited Opus - * framing described in Appendix B of RFC 6716. + * The format for multistream Opus packets is defined in + * RFC 7845 + * and is based on the self-delimited Opus framing described in Appendix B of + * RFC 6716. * Normal Opus packets are just a degenerate case of multistream Opus packets, * and can be encoded or decoded with the multistream API by setting * streams to 1 when initializing the encoder or @@ -140,7 +140,7 @@ extern "C" { * * The output channels specified by the encoder * should use the - * Vorbis + * Vorbis * channel ordering. A decoder may wish to apply an additional permutation * to the mapping the encoder used to achieve a different output channel * order (e.g. for outputing in WAV order). diff --git a/TMessagesProj/jni/opus/silk/A2NLSF.c b/TMessagesProj/jni/opus/silk/A2NLSF.c index 74b1b95d6f3..b487686ff9c 100644 --- a/TMessagesProj/jni/opus/silk/A2NLSF.c +++ b/TMessagesProj/jni/opus/silk/A2NLSF.c @@ -40,7 +40,7 @@ POSSIBILITY OF SUCH DAMAGE. /* Number of binary divisions, when not in low complexity mode */ #define BIN_DIV_STEPS_A2NLSF_FIX 3 /* must be no higher than 16 - log2( LSF_COS_TAB_SZ_FIX ) */ -#define MAX_ITERATIONS_A2NLSF_FIX 30 +#define MAX_ITERATIONS_A2NLSF_FIX 16 /* Helper function for A2NLSF(..) */ /* Transforms polynomials from cos(n*f) to cos(f)^n */ @@ -71,8 +71,23 @@ static OPUS_INLINE opus_int32 silk_A2NLSF_eval_poly( /* return the polynomial ev y32 = p[ dd ]; /* Q16 */ x_Q16 = silk_LSHIFT( x, 4 ); - for( n = dd - 1; n >= 0; n-- ) { - y32 = silk_SMLAWW( p[ n ], y32, x_Q16 ); /* Q16 */ + + if ( opus_likely( 8 == dd ) ) + { + y32 = silk_SMLAWW( p[ 7 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 6 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 5 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 4 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 3 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 2 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 1 ], y32, x_Q16 ); + y32 = silk_SMLAWW( p[ 0 ], y32, x_Q16 ); + } + else + { + for( n = dd - 1; n >= 0; n-- ) { + y32 = silk_SMLAWW( p[ n ], y32, x_Q16 ); /* Q16 */ + } } return y32; } @@ -115,7 +130,7 @@ void silk_A2NLSF( const opus_int d /* I Filter order (must be even) */ ) { - opus_int i, k, m, dd, root_ix, ffrac; + opus_int i, k, m, dd, root_ix, ffrac; opus_int32 xlo, xhi, xmid; opus_int32 ylo, yhi, ymid, thr; opus_int32 nom, den; @@ -224,13 +239,13 @@ void silk_A2NLSF( /* Set NLSFs to white spectrum and exit */ NLSF[ 0 ] = (opus_int16)silk_DIV32_16( 1 << 15, d + 1 ); for( k = 1; k < d; k++ ) { - NLSF[ k ] = (opus_int16)silk_SMULBB( k + 1, NLSF[ 0 ] ); + NLSF[ k ] = (opus_int16)silk_ADD16( NLSF[ k-1 ], NLSF[ 0 ] ); } return; } /* Error: Apply progressively more bandwidth expansion and run again */ - silk_bwexpander_32( a_Q16, d, 65536 - silk_SMULBB( 10 + i, i ) ); /* 10_Q16 = 0.00015*/ + silk_bwexpander_32( a_Q16, d, 65536 - silk_LSHIFT( 1, i ) ); silk_A2NLSF_init( a_Q16, P, Q, dd ); p = P; /* Pointer to polynomial */ diff --git a/TMessagesProj/jni/opus/silk/API.h b/TMessagesProj/jni/opus/silk/API.h index f0601bcf6b1..0131acbb08f 100644 --- a/TMessagesProj/jni/opus/silk/API.h +++ b/TMessagesProj/jni/opus/silk/API.h @@ -111,7 +111,8 @@ opus_int silk_Decode( /* O Returns error co opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ ec_dec *psRangeDec, /* I/O Compressor data structure */ opus_int16 *samplesOut, /* O Decoded output speech vector */ - opus_int32 *nSamplesOut /* O Number of samples decoded */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ + int arch /* I Run-time architecture */ ); #if 0 diff --git a/TMessagesProj/jni/opus/silk/CNG.c b/TMessagesProj/jni/opus/silk/CNG.c index 8481d95dbe3..d140db7d473 100644 --- a/TMessagesProj/jni/opus/silk/CNG.c +++ b/TMessagesProj/jni/opus/silk/CNG.c @@ -34,9 +34,8 @@ POSSIBILITY OF SUCH DAMAGE. /* Generates excitation for CNG LPC synthesis */ static OPUS_INLINE void silk_CNG_exc( - opus_int32 residual_Q10[], /* O CNG residual signal Q10 */ + opus_int32 exc_Q14[], /* O CNG excitation signal Q10 */ opus_int32 exc_buf_Q14[], /* I Random samples buffer Q10 */ - opus_int32 Gain_Q16, /* I Gain to apply */ opus_int length, /* I Length */ opus_int32 *rand_seed /* I/O Seed to random index generator */ ) @@ -55,7 +54,7 @@ static OPUS_INLINE void silk_CNG_exc( idx = (opus_int)( silk_RSHIFT( seed, 24 ) & exc_mask ); silk_assert( idx >= 0 ); silk_assert( idx <= CNG_BUF_MASK_MAX ); - residual_Q10[ i ] = (opus_int16)silk_SAT16( silk_SMULWW( exc_buf_Q14[ idx ], Gain_Q16 >> 4 ) ); + exc_Q14[ i ] = exc_buf_Q14[ idx ]; } *rand_seed = seed; } @@ -85,7 +84,7 @@ void silk_CNG( ) { opus_int i, subfr; - opus_int32 sum_Q6, max_Gain_Q16; + opus_int32 LPC_pred_Q10, max_Gain_Q16, gain_Q16, gain_Q10; opus_int16 A_Q12[ MAX_LPC_ORDER ]; silk_CNG_struct *psCNG = &psDec->sCNG; SAVE_STACK; @@ -124,47 +123,60 @@ void silk_CNG( /* Add CNG when packet is lost or during DTX */ if( psDec->lossCnt ) { - VARDECL( opus_int32, CNG_sig_Q10 ); - - ALLOC( CNG_sig_Q10, length + MAX_LPC_ORDER, opus_int32 ); + VARDECL( opus_int32, CNG_sig_Q14 ); + ALLOC( CNG_sig_Q14, length + MAX_LPC_ORDER, opus_int32 ); /* Generate CNG excitation */ - silk_CNG_exc( CNG_sig_Q10 + MAX_LPC_ORDER, psCNG->CNG_exc_buf_Q14, psCNG->CNG_smth_Gain_Q16, length, &psCNG->rand_seed ); + gain_Q16 = silk_SMULWW( psDec->sPLC.randScale_Q14, psDec->sPLC.prevGain_Q16[1] ); + if( gain_Q16 >= (1 << 21) || psCNG->CNG_smth_Gain_Q16 > (1 << 23) ) { + gain_Q16 = silk_SMULTT( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULTT( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 16 ); + } else { + gain_Q16 = silk_SMULWW( gain_Q16, gain_Q16 ); + gain_Q16 = silk_SUB_LSHIFT32(silk_SMULWW( psCNG->CNG_smth_Gain_Q16, psCNG->CNG_smth_Gain_Q16 ), gain_Q16, 5 ); + gain_Q16 = silk_LSHIFT32( silk_SQRT_APPROX( gain_Q16 ), 8 ); + } + gain_Q10 = silk_RSHIFT( gain_Q16, 6 ); + + silk_CNG_exc( CNG_sig_Q14 + MAX_LPC_ORDER, psCNG->CNG_exc_buf_Q14, length, &psCNG->rand_seed ); /* Convert CNG NLSF to filter representation */ silk_NLSF2A( A_Q12, psCNG->CNG_smth_NLSF_Q15, psDec->LPC_order ); /* Generate CNG signal, by synthesis filtering */ - silk_memcpy( CNG_sig_Q10, psCNG->CNG_synth_state, MAX_LPC_ORDER * sizeof( opus_int32 ) ); + silk_memcpy( CNG_sig_Q14, psCNG->CNG_synth_state, MAX_LPC_ORDER * sizeof( opus_int32 ) ); for( i = 0; i < length; i++ ) { silk_assert( psDec->LPC_order == 10 || psDec->LPC_order == 16 ); /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ - sum_Q6 = silk_RSHIFT( psDec->LPC_order, 1 ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); + LPC_pred_Q10 = silk_RSHIFT( psDec->LPC_order, 1 ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 1 ], A_Q12[ 0 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 2 ], A_Q12[ 1 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 3 ], A_Q12[ 2 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 4 ], A_Q12[ 3 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 5 ], A_Q12[ 4 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 6 ], A_Q12[ 5 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 7 ], A_Q12[ 6 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 8 ], A_Q12[ 7 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 9 ], A_Q12[ 8 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 10 ], A_Q12[ 9 ] ); if( psDec->LPC_order == 16 ) { - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 11 ], A_Q12[ 10 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 12 ], A_Q12[ 11 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 13 ], A_Q12[ 12 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 14 ], A_Q12[ 13 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 15 ], A_Q12[ 14 ] ); - sum_Q6 = silk_SMLAWB( sum_Q6, CNG_sig_Q10[ MAX_LPC_ORDER + i - 16 ], A_Q12[ 15 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 11 ], A_Q12[ 10 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 12 ], A_Q12[ 11 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 13 ], A_Q12[ 12 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 14 ], A_Q12[ 13 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 15 ], A_Q12[ 14 ] ); + LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, CNG_sig_Q14[ MAX_LPC_ORDER + i - 16 ], A_Q12[ 15 ] ); } /* Update states */ - CNG_sig_Q10[ MAX_LPC_ORDER + i ] = silk_ADD_LSHIFT( CNG_sig_Q10[ MAX_LPC_ORDER + i ], sum_Q6, 4 ); + CNG_sig_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( CNG_sig_Q14[ MAX_LPC_ORDER + i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); + + /* Scale with Gain and add to input signal */ + frame[ i ] = (opus_int16)silk_ADD_SAT16( frame[ i ], silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( CNG_sig_Q14[ MAX_LPC_ORDER + i ], gain_Q10 ), 8 ) ) ); - frame[ i ] = silk_ADD_SAT16( frame[ i ], silk_RSHIFT_ROUND( sum_Q6, 6 ) ); } - silk_memcpy( psCNG->CNG_synth_state, &CNG_sig_Q10[ length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); + silk_memcpy( psCNG->CNG_synth_state, &CNG_sig_Q14[ length ], MAX_LPC_ORDER * sizeof( opus_int32 ) ); } else { silk_memset( psCNG->CNG_synth_state, 0, psDec->LPC_order * sizeof( opus_int32 ) ); } diff --git a/TMessagesProj/jni/opus/silk/LPC_analysis_filter.c b/TMessagesProj/jni/opus/silk/LPC_analysis_filter.c index 9d1f16cb7d2..3c616d7be2a 100644 --- a/TMessagesProj/jni/opus/silk/LPC_analysis_filter.c +++ b/TMessagesProj/jni/opus/silk/LPC_analysis_filter.c @@ -39,16 +39,24 @@ POSSIBILITY OF SUCH DAMAGE. /* first d output samples are set to zero */ /*******************************************/ +/* OPT: Using celt_fir() for this function should be faster, but it may cause + integer overflows in intermediate values (not final results), which the + current implementation silences by casting to unsigned. Enabling + this should be safe in pretty much all cases, even though it is not technically + C89-compliant. */ +#define USE_CELT_FIR 0 + void silk_LPC_analysis_filter( opus_int16 *out, /* O Output signal */ const opus_int16 *in, /* I Input signal */ const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ const opus_int32 len, /* I Signal length */ - const opus_int32 d /* I Filter order */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ ) { opus_int j; -#ifdef FIXED_POINT +#if USE_CELT_FIR opus_int16 mem[SILK_MAX_ORDER_LPC]; opus_int16 num[SILK_MAX_ORDER_LPC]; #else @@ -61,7 +69,7 @@ void silk_LPC_analysis_filter( silk_assert( (d & 1) == 0 ); silk_assert( d <= len ); -#ifdef FIXED_POINT +#if USE_CELT_FIR silk_assert( d <= SILK_MAX_ORDER_LPC ); for ( j = 0; j < d; j++ ) { num[ j ] = -B[ j ]; @@ -69,11 +77,12 @@ void silk_LPC_analysis_filter( for (j=0;j maxabs ) { + maxabs = absval; + idx = k; + } + } + maxabs = silk_RSHIFT_ROUND( maxabs, QIN - QOUT ); + + if( maxabs > silk_int16_MAX ) { + /* Reduce magnitude of prediction coefficients */ + maxabs = silk_min( maxabs, 163838 ); /* ( silk_int32_MAX >> 14 ) + silk_int16_MAX = 163838 */ + chirp_Q16 = SILK_FIX_CONST( 0.999, 16 ) - silk_DIV32( silk_LSHIFT( maxabs - silk_int16_MAX, 14 ), + silk_RSHIFT32( silk_MUL( maxabs, idx + 1), 2 ) ); + silk_bwexpander_32( a_QIN, d, chirp_Q16 ); + } else { + break; + } + } + + if( i == 10 ) { + /* Reached the last iteration, clip the coefficients */ + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ) ); + a_QIN[ k ] = silk_LSHIFT( (opus_int32)a_QOUT[ k ], QIN - QOUT ); + } + } else { + for( k = 0; k < d; k++ ) { + a_QOUT[ k ] = (opus_int16)silk_RSHIFT_ROUND( a_QIN[ k ], QIN - QOUT ); + } + } +} diff --git a/TMessagesProj/jni/opus/silk/LPC_inv_pred_gain.c b/TMessagesProj/jni/opus/silk/LPC_inv_pred_gain.c index 4af89aa5fad..87d9a499273 100644 --- a/TMessagesProj/jni/opus/silk/LPC_inv_pred_gain.c +++ b/TMessagesProj/jni/opus/silk/LPC_inv_pred_gain.c @@ -30,6 +30,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "SigProc_FIX.h" +#include "define.h" #define QA 24 #define A_LIMIT SILK_FIX_CONST( 0.99975, QA ) @@ -39,68 +40,80 @@ POSSIBILITY OF SUCH DAMAGE. /* Compute inverse of LPC prediction gain, and */ /* test if LPC coefficients are stable (all poles within unit circle) */ static opus_int32 LPC_inverse_pred_gain_QA( /* O Returns inverse prediction gain in energy domain, Q30 */ - opus_int32 A_QA[ 2 ][ SILK_MAX_ORDER_LPC ], /* I Prediction coefficients */ + opus_int32 A_QA[ SILK_MAX_ORDER_LPC ], /* I Prediction coefficients */ const opus_int order /* I Prediction order */ ) { opus_int k, n, mult2Q; - opus_int32 invGain_Q30, rc_Q31, rc_mult1_Q30, rc_mult2, tmp_QA; - opus_int32 *Aold_QA, *Anew_QA; + opus_int32 invGain_Q30, rc_Q31, rc_mult1_Q30, rc_mult2, tmp1, tmp2; - Anew_QA = A_QA[ order & 1 ]; - - invGain_Q30 = (opus_int32)1 << 30; + invGain_Q30 = SILK_FIX_CONST( 1, 30 ); for( k = order - 1; k > 0; k-- ) { /* Check for stability */ - if( ( Anew_QA[ k ] > A_LIMIT ) || ( Anew_QA[ k ] < -A_LIMIT ) ) { + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { return 0; } /* Set RC equal to negated AR coef */ - rc_Q31 = -silk_LSHIFT( Anew_QA[ k ], 31 - QA ); + rc_Q31 = -silk_LSHIFT( A_QA[ k ], 31 - QA ); /* rc_mult1_Q30 range: [ 1 : 2^30 ] */ - rc_mult1_Q30 = ( (opus_int32)1 << 30 ) - silk_SMMUL( rc_Q31, rc_Q31 ); + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); silk_assert( rc_mult1_Q30 > ( 1 << 15 ) ); /* reduce A_LIMIT if fails */ silk_assert( rc_mult1_Q30 <= ( 1 << 30 ) ); - /* rc_mult2 range: [ 2^30 : silk_int32_MAX ] */ - mult2Q = 32 - silk_CLZ32( silk_abs( rc_mult1_Q30 ) ); - rc_mult2 = silk_INVERSE32_varQ( rc_mult1_Q30, mult2Q + 30 ); - /* Update inverse gain */ /* invGain_Q30 range: [ 0 : 2^30 ] */ invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); silk_assert( invGain_Q30 >= 0 ); silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } - /* Swap pointers */ - Aold_QA = Anew_QA; - Anew_QA = A_QA[ k & 1 ]; + /* rc_mult2 range: [ 2^30 : silk_int32_MAX ] */ + mult2Q = 32 - silk_CLZ32( silk_abs( rc_mult1_Q30 ) ); + rc_mult2 = silk_INVERSE32_varQ( rc_mult1_Q30, mult2Q + 30 ); /* Update AR coefficient */ - for( n = 0; n < k; n++ ) { - tmp_QA = Aold_QA[ n ] - MUL32_FRAC_Q( Aold_QA[ k - n - 1 ], rc_Q31, 31 ); - Anew_QA[ n ] = MUL32_FRAC_Q( tmp_QA, rc_mult2 , mult2Q ); + for( n = 0; n < (k + 1) >> 1; n++ ) { + opus_int64 tmp64; + tmp1 = A_QA[ n ]; + tmp2 = A_QA[ k - n - 1 ]; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp1, + MUL32_FRAC_Q( tmp2, rc_Q31, 31 ) ), rc_mult2 ), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ n ] = ( opus_int32 )tmp64; + tmp64 = silk_RSHIFT_ROUND64( silk_SMULL( silk_SUB_SAT32(tmp2, + MUL32_FRAC_Q( tmp1, rc_Q31, 31 ) ), rc_mult2), mult2Q); + if( tmp64 > silk_int32_MAX || tmp64 < silk_int32_MIN ) { + return 0; + } + A_QA[ k - n - 1 ] = ( opus_int32 )tmp64; } } /* Check for stability */ - if( ( Anew_QA[ 0 ] > A_LIMIT ) || ( Anew_QA[ 0 ] < -A_LIMIT ) ) { + if( ( A_QA[ k ] > A_LIMIT ) || ( A_QA[ k ] < -A_LIMIT ) ) { return 0; } /* Set RC equal to negated AR coef */ - rc_Q31 = -silk_LSHIFT( Anew_QA[ 0 ], 31 - QA ); + rc_Q31 = -silk_LSHIFT( A_QA[ 0 ], 31 - QA ); /* Range: [ 1 : 2^30 ] */ - rc_mult1_Q30 = ( (opus_int32)1 << 30 ) - silk_SMMUL( rc_Q31, rc_Q31 ); + rc_mult1_Q30 = silk_SUB32( SILK_FIX_CONST( 1, 30 ), silk_SMMUL( rc_Q31, rc_Q31 ) ); /* Update inverse gain */ /* Range: [ 0 : 2^30 ] */ invGain_Q30 = silk_LSHIFT( silk_SMMUL( invGain_Q30, rc_mult1_Q30 ), 2 ); - silk_assert( invGain_Q30 >= 0 ); - silk_assert( invGain_Q30 <= 1<<30 ); + silk_assert( invGain_Q30 >= 0 ); + silk_assert( invGain_Q30 <= ( 1 << 30 ) ); + if( invGain_Q30 < SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN, 30 ) ) { + return 0; + } return invGain_Q30; } @@ -112,16 +125,13 @@ opus_int32 silk_LPC_inverse_pred_gain( /* O Returns inverse predi ) { opus_int k; - opus_int32 Atmp_QA[ 2 ][ SILK_MAX_ORDER_LPC ]; - opus_int32 *Anew_QA; + opus_int32 Atmp_QA[ SILK_MAX_ORDER_LPC ]; opus_int32 DC_resp = 0; - Anew_QA = Atmp_QA[ order & 1 ]; - /* Increase Q domain of the AR coefficients */ for( k = 0; k < order; k++ ) { DC_resp += (opus_int32)A_Q12[ k ]; - Anew_QA[ k ] = silk_LSHIFT32( (opus_int32)A_Q12[ k ], QA - 12 ); + Atmp_QA[ k ] = silk_LSHIFT32( (opus_int32)A_Q12[ k ], QA - 12 ); } /* If the DC is unstable, we don't even need to do the full calculations */ if( DC_resp >= 4096 ) { @@ -139,14 +149,11 @@ opus_int32 silk_LPC_inverse_pred_gain_Q24( /* O Returns inverse pred ) { opus_int k; - opus_int32 Atmp_QA[ 2 ][ SILK_MAX_ORDER_LPC ]; - opus_int32 *Anew_QA; - - Anew_QA = Atmp_QA[ order & 1 ]; + opus_int32 Atmp_QA[ SILK_MAX_ORDER_LPC ]; /* Increase Q domain of the AR coefficients */ for( k = 0; k < order; k++ ) { - Anew_QA[ k ] = silk_RSHIFT32( A_Q24[ k ], 24 - QA ); + Atmp_QA[ k ] = silk_RSHIFT32( A_Q24[ k ], 24 - QA ); } return LPC_inverse_pred_gain_QA( Atmp_QA, order ); diff --git a/TMessagesProj/jni/opus/silk/MacroCount.h b/TMessagesProj/jni/opus/silk/MacroCount.h index 834817d058b..54dbdbd983e 100644 --- a/TMessagesProj/jni/opus/silk/MacroCount.h +++ b/TMessagesProj/jni/opus/silk/MacroCount.h @@ -319,14 +319,6 @@ static OPUS_INLINE opus_int32 silk_ADD_POS_SAT32(opus_int64 a, opus_int64 b){ return(tmp); } -#undef silk_ADD_POS_SAT64 -static OPUS_INLINE opus_int64 silk_ADD_POS_SAT64(opus_int64 a, opus_int64 b){ - opus_int64 tmp; - ops_count += 1; - tmp = ((((a)+(b)) & 0x8000000000000000LL) ? silk_int64_MAX : ((a)+(b))); - return(tmp); -} - #undef silk_LSHIFT8 static OPUS_INLINE opus_int8 silk_LSHIFT8(opus_int8 a, opus_int32 shift){ opus_int8 ret; diff --git a/TMessagesProj/jni/opus/silk/MacroDebug.h b/TMessagesProj/jni/opus/silk/MacroDebug.h index 35aedc5c5fa..8dd4ce2ee27 100644 --- a/TMessagesProj/jni/opus/silk/MacroDebug.h +++ b/TMessagesProj/jni/opus/silk/MacroDebug.h @@ -539,8 +539,7 @@ static OPUS_INLINE opus_int32 silk_DIV32_16_(opus_int32 a32, opus_int32 b32, cha no checking needed for silk_POS_SAT32 no checking needed for silk_ADD_POS_SAT8 no checking needed for silk_ADD_POS_SAT16 - no checking needed for silk_ADD_POS_SAT32 - no checking needed for silk_ADD_POS_SAT64 */ + no checking needed for silk_ADD_POS_SAT32 */ #undef silk_LSHIFT8 #define silk_LSHIFT8(a,b) silk_LSHIFT8_((a), (b), __FILE__, __LINE__) diff --git a/TMessagesProj/jni/opus/silk/NLSF2A.c b/TMessagesProj/jni/opus/silk/NLSF2A.c index b1c559ea682..0ea5c17e2e9 100644 --- a/TMessagesProj/jni/opus/silk/NLSF2A.c +++ b/TMessagesProj/jni/opus/silk/NLSF2A.c @@ -83,15 +83,14 @@ void silk_NLSF2A( opus_int32 P[ SILK_MAX_ORDER_LPC / 2 + 1 ], Q[ SILK_MAX_ORDER_LPC / 2 + 1 ]; opus_int32 Ptmp, Qtmp, f_int, f_frac, cos_val, delta; opus_int32 a32_QA1[ SILK_MAX_ORDER_LPC ]; - opus_int32 maxabs, absval, idx=0, sc_Q16; silk_assert( LSF_COS_TAB_SZ_FIX == 128 ); - silk_assert( d==10||d==16 ); + silk_assert( d==10 || d==16 ); /* convert LSFs to 2*cos(LSF), using piecewise linear curve from table */ ordering = d == 16 ? ordering16 : ordering10; for( k = 0; k < d; k++ ) { - silk_assert(NLSF[k] >= 0 ); + silk_assert( NLSF[k] >= 0 ); /* f_int on a scale 0-127 (rounded down) */ f_int = silk_RSHIFT( NLSF[k], 15 - 7 ); @@ -126,52 +125,15 @@ void silk_NLSF2A( a32_QA1[ d-k-1 ] = Qtmp - Ptmp; /* QA+1 */ } - /* Limit the maximum absolute value of the prediction coefficients, so that they'll fit in int16 */ - for( i = 0; i < 10; i++ ) { - /* Find maximum absolute value and its index */ - maxabs = 0; - for( k = 0; k < d; k++ ) { - absval = silk_abs( a32_QA1[k] ); - if( absval > maxabs ) { - maxabs = absval; - idx = k; - } - } - maxabs = silk_RSHIFT_ROUND( maxabs, QA + 1 - 12 ); /* QA+1 -> Q12 */ - - if( maxabs > silk_int16_MAX ) { - /* Reduce magnitude of prediction coefficients */ - maxabs = silk_min( maxabs, 163838 ); /* ( silk_int32_MAX >> 14 ) + silk_int16_MAX = 163838 */ - sc_Q16 = SILK_FIX_CONST( 0.999, 16 ) - silk_DIV32( silk_LSHIFT( maxabs - silk_int16_MAX, 14 ), - silk_RSHIFT32( silk_MUL( maxabs, idx + 1), 2 ) ); - silk_bwexpander_32( a32_QA1, d, sc_Q16 ); - } else { - break; - } - } + /* Convert int32 coefficients to Q12 int16 coefs */ + silk_LPC_fit( a_Q12, a32_QA1, 12, QA + 1, d ); - if( i == 10 ) { - /* Reached the last iteration, clip the coefficients */ + for( i = 0; silk_LPC_inverse_pred_gain( a_Q12, d ) == 0 && i < MAX_LPC_STABILIZE_ITERATIONS; i++ ) { + /* Prediction coefficients are (too close to) unstable; apply bandwidth expansion */ + /* on the unscaled coefficients, convert to Q12 and measure again */ + silk_bwexpander_32( a32_QA1, d, 65536 - silk_LSHIFT( 2, i ) ); for( k = 0; k < d; k++ ) { - a_Q12[ k ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ) ); /* QA+1 -> Q12 */ - a32_QA1[ k ] = silk_LSHIFT( (opus_int32)a_Q12[ k ], QA + 1 - 12 ); - } - } else { - for( k = 0; k < d; k++ ) { - a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ - } - } - - for( i = 0; i < MAX_LPC_STABILIZE_ITERATIONS; i++ ) { - if( silk_LPC_inverse_pred_gain( a_Q12, d ) < SILK_FIX_CONST( 1.0 / MAX_PREDICTION_POWER_GAIN, 30 ) ) { - /* Prediction coefficients are (too close to) unstable; apply bandwidth expansion */ - /* on the unscaled coefficients, convert to Q12 and measure again */ - silk_bwexpander_32( a32_QA1, d, 65536 - silk_LSHIFT( 2, i ) ); - for( k = 0; k < d; k++ ) { - a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ - } - } else { - break; + a_Q12[ k ] = (opus_int16)silk_RSHIFT_ROUND( a32_QA1[ k ], QA + 1 - 12 ); /* QA+1 -> Q12 */ } } } diff --git a/TMessagesProj/jni/opus/silk/NLSF_VQ.c b/TMessagesProj/jni/opus/silk/NLSF_VQ.c index 69b6e22e189..452f3dcb7dd 100644 --- a/TMessagesProj/jni/opus/silk/NLSF_VQ.c +++ b/TMessagesProj/jni/opus/silk/NLSF_VQ.c @@ -33,36 +33,44 @@ POSSIBILITY OF SUCH DAMAGE. /* Compute quantization errors for an LPC_order element input vector for a VQ codebook */ void silk_NLSF_VQ( - opus_int32 err_Q26[], /* O Quantization errors [K] */ + opus_int32 err_Q24[], /* O Quantization errors [K] */ const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ const opus_int K, /* I Number of codebook vectors */ const opus_int LPC_order /* I Number of LPCs */ ) { - opus_int i, m; - opus_int32 diff_Q15, sum_error_Q30, sum_error_Q26; + opus_int i, m; + opus_int32 diff_Q15, diffw_Q24, sum_error_Q24, pred_Q24; + const opus_int16 *w_Q9_ptr; + const opus_uint8 *cb_Q8_ptr; - silk_assert( LPC_order <= 16 ); silk_assert( ( LPC_order & 1 ) == 0 ); /* Loop over codebook */ + cb_Q8_ptr = pCB_Q8; + w_Q9_ptr = pWght_Q9; for( i = 0; i < K; i++ ) { - sum_error_Q26 = 0; - for( m = 0; m < LPC_order; m += 2 ) { - /* Compute weighted squared quantization error for index m */ - diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m ], (opus_int32)*pCB_Q8++, 7 ); /* range: [ -32767 : 32767 ]*/ - sum_error_Q30 = silk_SMULBB( diff_Q15, diff_Q15 ); + sum_error_Q24 = 0; + pred_Q24 = 0; + for( m = LPC_order-2; m >= 0; m -= 2 ) { + /* Compute weighted absolute predictive quantization error for index m + 1 */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m + 1 ], (opus_int32)cb_Q8_ptr[ m + 1 ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m + 1 ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; - /* Compute weighted squared quantization error for index m + 1 */ - diff_Q15 = silk_SUB_LSHIFT32( in_Q15[m + 1], (opus_int32)*pCB_Q8++, 7 ); /* range: [ -32767 : 32767 ]*/ - sum_error_Q30 = silk_SMLABB( sum_error_Q30, diff_Q15, diff_Q15 ); + /* Compute weighted absolute predictive quantization error for index m */ + diff_Q15 = silk_SUB_LSHIFT32( in_Q15[ m ], (opus_int32)cb_Q8_ptr[ m ], 7 ); /* range: [ -32767 : 32767 ]*/ + diffw_Q24 = silk_SMULBB( diff_Q15, w_Q9_ptr[ m ] ); + sum_error_Q24 = silk_ADD32( sum_error_Q24, silk_abs( silk_SUB_RSHIFT32( diffw_Q24, pred_Q24, 1 ) ) ); + pred_Q24 = diffw_Q24; - sum_error_Q26 = silk_ADD_RSHIFT32( sum_error_Q26, sum_error_Q30, 4 ); - - silk_assert( sum_error_Q26 >= 0 ); - silk_assert( sum_error_Q30 >= 0 ); + silk_assert( sum_error_Q24 >= 0 ); } - err_Q26[ i ] = sum_error_Q26; + err_Q24[ i ] = sum_error_Q24; + cb_Q8_ptr += LPC_order; + w_Q9_ptr += LPC_order; } } diff --git a/TMessagesProj/jni/opus/silk/NLSF_decode.c b/TMessagesProj/jni/opus/silk/NLSF_decode.c index 9f715060b8e..eeb0ba8c92c 100644 --- a/TMessagesProj/jni/opus/silk/NLSF_decode.c +++ b/TMessagesProj/jni/opus/silk/NLSF_decode.c @@ -32,7 +32,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "main.h" /* Predictive dequantizer for NLSF residuals */ -static OPUS_INLINE void silk_NLSF_residual_dequant( /* O Returns RD value in Q30 */ +static OPUS_INLINE void silk_NLSF_residual_dequant( /* O Returns RD value in Q30 */ opus_int16 x_Q10[], /* O Output [ order ] */ const opus_int8 indices[], /* I Quantization indices [ order ] */ const opus_uint8 pred_coef_Q8[], /* I Backward predictor coefs [ order ] */ @@ -70,15 +70,9 @@ void silk_NLSF_decode( opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; opus_int16 ec_ix[ MAX_LPC_ORDER ]; opus_int16 res_Q10[ MAX_LPC_ORDER ]; - opus_int16 W_tmp_QW[ MAX_LPC_ORDER ]; - opus_int32 W_tmp_Q9, NLSF_Q15_tmp; + opus_int32 NLSF_Q15_tmp; const opus_uint8 *pCB_element; - - /* Decode first stage */ - pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; - for( i = 0; i < psNLSF_CB->order; i++ ) { - pNLSF_Q15[ i ] = silk_LSHIFT( (opus_int16)pCB_element[ i ], 7 ); - } + const opus_int16 *pCB_Wght_Q9; /* Unpack entropy table indices and predictor for current CB1 index */ silk_NLSF_unpack( ec_ix, pred_Q8, psNLSF_CB, NLSFIndices[ 0 ] ); @@ -86,13 +80,11 @@ void silk_NLSF_decode( /* Predictive residual dequantizer */ silk_NLSF_residual_dequant( res_Q10, &NLSFIndices[ 1 ], pred_Q8, psNLSF_CB->quantStepSize_Q16, psNLSF_CB->order ); - /* Weights from codebook vector */ - silk_NLSF_VQ_weights_laroia( W_tmp_QW, pNLSF_Q15, psNLSF_CB->order ); - - /* Apply inverse square-rooted weights and add to output */ + /* Apply inverse square-rooted weights to first stage and add to output */ + pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ NLSFIndices[ 0 ] * psNLSF_CB->order ]; for( i = 0; i < psNLSF_CB->order; i++ ) { - W_tmp_Q9 = silk_SQRT_APPROX( silk_LSHIFT( (opus_int32)W_tmp_QW[ i ], 18 - NLSF_W_Q ) ); - NLSF_Q15_tmp = silk_ADD32( pNLSF_Q15[ i ], silk_DIV32_16( silk_LSHIFT( (opus_int32)res_Q10[ i ], 14 ), W_tmp_Q9 ) ); + NLSF_Q15_tmp = silk_ADD_LSHIFT32( silk_DIV32_16( silk_LSHIFT( (opus_int32)res_Q10[ i ], 14 ), pCB_Wght_Q9[ i ] ), (opus_int16)pCB_element[ i ], 7 ); pNLSF_Q15[ i ] = (opus_int16)silk_LIMIT( NLSF_Q15_tmp, 0, 32767 ); } diff --git a/TMessagesProj/jni/opus/silk/NLSF_del_dec_quant.c b/TMessagesProj/jni/opus/silk/NLSF_del_dec_quant.c index 504dbbd040b..5155caeff41 100644 --- a/TMessagesProj/jni/opus/silk/NLSF_del_dec_quant.c +++ b/TMessagesProj/jni/opus/silk/NLSF_del_dec_quant.c @@ -46,8 +46,9 @@ opus_int32 silk_NLSF_del_dec_quant( /* O Returns ) { opus_int i, j, nStates, ind_tmp, ind_min_max, ind_max_min, in_Q10, res_Q10; - opus_int pred_Q10, diff_Q10, out0_Q10, out1_Q10, rate0_Q5, rate1_Q5; - opus_int32 RD_tmp_Q25, min_Q25, min_max_Q25, max_min_Q25, pred_coef_Q16; + opus_int pred_Q10, diff_Q10, rate0_Q5, rate1_Q5; + opus_int16 out0_Q10, out1_Q10; + opus_int32 RD_tmp_Q25, min_Q25, min_max_Q25, max_min_Q25; opus_int ind_sort[ NLSF_QUANT_DEL_DEC_STATES ]; opus_int8 ind[ NLSF_QUANT_DEL_DEC_STATES ][ MAX_LPC_ORDER ]; opus_int16 prev_out_Q10[ 2 * NLSF_QUANT_DEL_DEC_STATES ]; @@ -56,38 +57,47 @@ opus_int32 silk_NLSF_del_dec_quant( /* O Returns opus_int32 RD_max_Q25[ NLSF_QUANT_DEL_DEC_STATES ]; const opus_uint8 *rates_Q5; + opus_int out0_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + opus_int out1_Q10_table[2 * NLSF_QUANT_MAX_AMPLITUDE_EXT]; + + for (i = -NLSF_QUANT_MAX_AMPLITUDE_EXT; i <= NLSF_QUANT_MAX_AMPLITUDE_EXT-1; i++) + { + out0_Q10 = silk_LSHIFT( i, 10 ); + out1_Q10 = silk_ADD16( out0_Q10, 1024 ); + if( i > 0 ) { + out0_Q10 = silk_SUB16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == 0 ) { + out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else if( i == -1 ) { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } else { + out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + out1_Q10 = silk_ADD16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); + } + out0_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out0_Q10, quant_step_size_Q16 ), 16 ); + out1_Q10_table[ i + NLSF_QUANT_MAX_AMPLITUDE_EXT ] = silk_RSHIFT( silk_SMULBB( out1_Q10, quant_step_size_Q16 ), 16 ); + } + silk_assert( (NLSF_QUANT_DEL_DEC_STATES & (NLSF_QUANT_DEL_DEC_STATES-1)) == 0 ); /* must be power of two */ nStates = 1; RD_Q25[ 0 ] = 0; prev_out_Q10[ 0 ] = 0; - for( i = order - 1; ; i-- ) { + for( i = order - 1; i >= 0; i-- ) { rates_Q5 = &ec_rates_Q5[ ec_ix[ i ] ]; - pred_coef_Q16 = silk_LSHIFT( (opus_int32)pred_coef_Q8[ i ], 8 ); in_Q10 = x_Q10[ i ]; for( j = 0; j < nStates; j++ ) { - pred_Q10 = silk_SMULWB( pred_coef_Q16, prev_out_Q10[ j ] ); + pred_Q10 = silk_RSHIFT( silk_SMULBB( (opus_int16)pred_coef_Q8[ i ], prev_out_Q10[ j ] ), 8 ); res_Q10 = silk_SUB16( in_Q10, pred_Q10 ); - ind_tmp = silk_SMULWB( (opus_int32)inv_quant_step_size_Q6, res_Q10 ); + ind_tmp = silk_RSHIFT( silk_SMULBB( inv_quant_step_size_Q6, res_Q10 ), 16 ); ind_tmp = silk_LIMIT( ind_tmp, -NLSF_QUANT_MAX_AMPLITUDE_EXT, NLSF_QUANT_MAX_AMPLITUDE_EXT-1 ); ind[ j ][ i ] = (opus_int8)ind_tmp; /* compute outputs for ind_tmp and ind_tmp + 1 */ - out0_Q10 = silk_LSHIFT( ind_tmp, 10 ); - out1_Q10 = silk_ADD16( out0_Q10, 1024 ); - if( ind_tmp > 0 ) { - out0_Q10 = silk_SUB16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - } else if( ind_tmp == 0 ) { - out1_Q10 = silk_SUB16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - } else if( ind_tmp == -1 ) { - out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - } else { - out0_Q10 = silk_ADD16( out0_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - out1_Q10 = silk_ADD16( out1_Q10, SILK_FIX_CONST( NLSF_QUANT_LEVEL_ADJ, 10 ) ); - } - out0_Q10 = silk_SMULWB( (opus_int32)out0_Q10, quant_step_size_Q16 ); - out1_Q10 = silk_SMULWB( (opus_int32)out1_Q10, quant_step_size_Q16 ); + out0_Q10 = out0_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + out1_Q10 = out1_Q10_table[ ind_tmp + NLSF_QUANT_MAX_AMPLITUDE_EXT ]; + out0_Q10 = silk_ADD16( out0_Q10, pred_Q10 ); out1_Q10 = silk_ADD16( out1_Q10, pred_Q10 ); prev_out_Q10[ j ] = out0_Q10; @@ -121,7 +131,7 @@ opus_int32 silk_NLSF_del_dec_quant( /* O Returns RD_Q25[ j + nStates ] = silk_SMLABB( silk_MLA( RD_tmp_Q25, silk_SMULBB( diff_Q10, diff_Q10 ), w_Q5[ i ] ), mu_Q20, rate1_Q5 ); } - if( nStates <= ( NLSF_QUANT_DEL_DEC_STATES >> 1 ) ) { + if( silk_LSHIFT( nStates, 1 ) <= NLSF_QUANT_DEL_DEC_STATES ) { /* double number of states and copy */ for( j = 0; j < nStates; j++ ) { ind[ j + nStates ][ i ] = ind[ j ][ i ] + 1; @@ -130,7 +140,7 @@ opus_int32 silk_NLSF_del_dec_quant( /* O Returns for( j = nStates; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { ind[ j ][ i ] = ind[ j - nStates ][ i ]; } - } else if( i > 0 ) { + } else { /* sort lower and upper half of RD_Q25, pairwise */ for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { if( RD_Q25[ j ] > RD_Q25[ j + NLSF_QUANT_DEL_DEC_STATES ] ) { @@ -181,8 +191,6 @@ opus_int32 silk_NLSF_del_dec_quant( /* O Returns for( j = 0; j < NLSF_QUANT_DEL_DEC_STATES; j++ ) { ind[ j ][ i ] += silk_RSHIFT( ind_sort[ j ], NLSF_QUANT_DEL_DEC_STATES_LOG2 ); } - } else { /* i == 0 */ - break; } } diff --git a/TMessagesProj/jni/opus/silk/NLSF_encode.c b/TMessagesProj/jni/opus/silk/NLSF_encode.c index 03a036fda2f..268b9a195b5 100644 --- a/TMessagesProj/jni/opus/silk/NLSF_encode.c +++ b/TMessagesProj/jni/opus/silk/NLSF_encode.c @@ -37,31 +37,29 @@ POSSIBILITY OF SUCH DAMAGE. /***********************/ opus_int32 silk_NLSF_encode( /* O Returns RD value in Q25 */ opus_int8 *NLSFIndices, /* I Codebook path vector [ LPC_ORDER + 1 ] */ - opus_int16 *pNLSF_Q15, /* I/O Quantized NLSF vector [ LPC_ORDER ] */ + opus_int16 *pNLSF_Q15, /* I/O (Un)quantized NLSF vector [ LPC_ORDER ] */ const silk_NLSF_CB_struct *psNLSF_CB, /* I Codebook object */ - const opus_int16 *pW_QW, /* I NLSF weight vector [ LPC_ORDER ] */ + const opus_int16 *pW_Q2, /* I NLSF weight vector [ LPC_ORDER ] */ const opus_int NLSF_mu_Q20, /* I Rate weight for the RD optimization */ const opus_int nSurvivors, /* I Max survivors after first stage */ const opus_int signalType /* I Signal type: 0/1/2 */ ) { opus_int i, s, ind1, bestIndex, prob_Q8, bits_q7; - opus_int32 W_tmp_Q9; - VARDECL( opus_int32, err_Q26 ); + opus_int32 W_tmp_Q9, ret; + VARDECL( opus_int32, err_Q24 ); VARDECL( opus_int32, RD_Q25 ); VARDECL( opus_int, tempIndices1 ); VARDECL( opus_int8, tempIndices2 ); - opus_int16 res_Q15[ MAX_LPC_ORDER ]; opus_int16 res_Q10[ MAX_LPC_ORDER ]; opus_int16 NLSF_tmp_Q15[ MAX_LPC_ORDER ]; - opus_int16 W_tmp_QW[ MAX_LPC_ORDER ]; opus_int16 W_adj_Q5[ MAX_LPC_ORDER ]; opus_uint8 pred_Q8[ MAX_LPC_ORDER ]; opus_int16 ec_ix[ MAX_LPC_ORDER ]; const opus_uint8 *pCB_element, *iCDF_ptr; + const opus_int16 *pCB_Wght_Q9; SAVE_STACK; - silk_assert( nSurvivors <= NLSF_VQ_MAX_SURVIVORS ); silk_assert( signalType >= 0 && signalType <= 2 ); silk_assert( NLSF_mu_Q20 <= 32767 && NLSF_mu_Q20 >= 0 ); @@ -69,12 +67,12 @@ opus_int32 silk_NLSF_encode( /* O Returns silk_NLSF_stabilize( pNLSF_Q15, psNLSF_CB->deltaMin_Q15, psNLSF_CB->order ); /* First stage: VQ */ - ALLOC( err_Q26, psNLSF_CB->nVectors, opus_int32 ); - silk_NLSF_VQ( err_Q26, pNLSF_Q15, psNLSF_CB->CB1_NLSF_Q8, psNLSF_CB->nVectors, psNLSF_CB->order ); + ALLOC( err_Q24, psNLSF_CB->nVectors, opus_int32 ); + silk_NLSF_VQ( err_Q24, pNLSF_Q15, psNLSF_CB->CB1_NLSF_Q8, psNLSF_CB->CB1_Wght_Q9, psNLSF_CB->nVectors, psNLSF_CB->order ); /* Sort the quantization errors */ ALLOC( tempIndices1, nSurvivors, opus_int ); - silk_insertion_sort_increasing( err_Q26, tempIndices1, psNLSF_CB->nVectors, nSurvivors ); + silk_insertion_sort_increasing( err_Q24, tempIndices1, psNLSF_CB->nVectors, nSurvivors ); ALLOC( RD_Q25, nSurvivors, opus_int32 ); ALLOC( tempIndices2, nSurvivors * MAX_LPC_ORDER, opus_int8 ); @@ -85,23 +83,12 @@ opus_int32 silk_NLSF_encode( /* O Returns /* Residual after first stage */ pCB_element = &psNLSF_CB->CB1_NLSF_Q8[ ind1 * psNLSF_CB->order ]; + pCB_Wght_Q9 = &psNLSF_CB->CB1_Wght_Q9[ ind1 * psNLSF_CB->order ]; for( i = 0; i < psNLSF_CB->order; i++ ) { NLSF_tmp_Q15[ i ] = silk_LSHIFT16( (opus_int16)pCB_element[ i ], 7 ); - res_Q15[ i ] = pNLSF_Q15[ i ] - NLSF_tmp_Q15[ i ]; - } - - /* Weights from codebook vector */ - silk_NLSF_VQ_weights_laroia( W_tmp_QW, NLSF_tmp_Q15, psNLSF_CB->order ); - - /* Apply square-rooted weights */ - for( i = 0; i < psNLSF_CB->order; i++ ) { - W_tmp_Q9 = silk_SQRT_APPROX( silk_LSHIFT( (opus_int32)W_tmp_QW[ i ], 18 - NLSF_W_Q ) ); - res_Q10[ i ] = (opus_int16)silk_RSHIFT( silk_SMULBB( res_Q15[ i ], W_tmp_Q9 ), 14 ); - } - - /* Modify input weights accordingly */ - for( i = 0; i < psNLSF_CB->order; i++ ) { - W_adj_Q5[ i ] = silk_DIV32_16( silk_LSHIFT( (opus_int32)pW_QW[ i ], 5 ), W_tmp_QW[ i ] ); + W_tmp_Q9 = pCB_Wght_Q9[ i ]; + res_Q10[ i ] = (opus_int16)silk_RSHIFT( silk_SMULBB( pNLSF_Q15[ i ] - NLSF_tmp_Q15[ i ], W_tmp_Q9 ), 14 ); + W_adj_Q5[ i ] = silk_DIV32_varQ( (opus_int32)pW_Q2[ i ], silk_SMULBB( W_tmp_Q9, W_tmp_Q9 ), 21 ); } /* Unpack entropy table indices and predictor for current CB1 index */ @@ -131,6 +118,7 @@ opus_int32 silk_NLSF_encode( /* O Returns /* Decode */ silk_NLSF_decode( pNLSF_Q15, NLSFIndices, psNLSF_CB ); + ret = RD_Q25[ 0 ]; RESTORE_STACK; - return RD_Q25[ 0 ]; + return ret; } diff --git a/TMessagesProj/jni/opus/silk/NLSF_stabilize.c b/TMessagesProj/jni/opus/silk/NLSF_stabilize.c index 1fa1ea379bd..8f3426b91e6 100644 --- a/TMessagesProj/jni/opus/silk/NLSF_stabilize.c +++ b/TMessagesProj/jni/opus/silk/NLSF_stabilize.c @@ -130,7 +130,7 @@ void silk_NLSF_stabilize( /* Keep delta_min distance between the NLSFs */ for( i = 1; i < L; i++ ) - NLSF_Q15[i] = silk_max_int( NLSF_Q15[i], NLSF_Q15[i-1] + NDeltaMin_Q15[i] ); + NLSF_Q15[i] = silk_max_int( NLSF_Q15[i], silk_ADD_SAT16( NLSF_Q15[i-1], NDeltaMin_Q15[i] ) ); /* Last NLSF should be no higher than 1 - NDeltaMin[L] */ NLSF_Q15[L-1] = silk_min_int( NLSF_Q15[L-1], (1<<15) - NDeltaMin_Q15[L] ); diff --git a/TMessagesProj/jni/opus/silk/NSQ.c b/TMessagesProj/jni/opus/silk/NSQ.c index cf5b3fd5476..0e2ea504980 100644 --- a/TMessagesProj/jni/opus/silk/NSQ.c +++ b/TMessagesProj/jni/opus/silk/NSQ.c @@ -31,11 +31,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "main.h" #include "stack_alloc.h" +#include "NSQ.h" + static OPUS_INLINE void silk_nsq_scale_states( const silk_encoder_state *psEncC, /* I Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ - const opus_int32 x_Q3[], /* I input in Q3 */ + const opus_int16 x16[], /* I input */ opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ @@ -46,6 +48,7 @@ static OPUS_INLINE void silk_nsq_scale_states( const opus_int signal_type /* I Signal type */ ); +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) static OPUS_INLINE void silk_noise_shape_quantizer( silk_nsq_state *NSQ, /* I/O NSQ state */ opus_int signalType, /* I Signal type */ @@ -65,18 +68,21 @@ static OPUS_INLINE void silk_noise_shape_quantizer( opus_int offset_Q10, /* I */ opus_int length, /* I Input length */ opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ - opus_int predictLPCOrder /* I Prediction filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ ); +#endif -void silk_NSQ( +void silk_NSQ_c +( const silk_encoder_state *psEncC, /* I/O Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ SideInfoIndices *psIndices, /* I/O Quantization Indices */ - const opus_int32 x_Q3[], /* I Prefiltered input signal */ + const opus_int16 x16[], /* I Input */ opus_int8 pulses[], /* O Quantized pulse signal */ const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ - const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ @@ -111,8 +117,7 @@ void silk_NSQ( LSF_interpolation_flag = 1; } - ALLOC( sLTP_Q15, - psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); /* Set up pointers to start of sub frame */ @@ -122,7 +127,7 @@ void silk_NSQ( for( k = 0; k < psEncC->nb_subfr; k++ ) { A_Q12 = &PredCoef_Q12[ (( k >> 1 ) | ( 1 - LSF_interpolation_flag )) * MAX_LPC_ORDER ]; B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; - AR_shp_Q13 = &AR2_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; /* Noise shape parameters */ silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); @@ -141,20 +146,20 @@ void silk_NSQ( silk_assert( start_idx > 0 ); silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], - A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder ); + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); NSQ->rewhite_flag = 1; NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; } } - silk_nsq_scale_states( psEncC, NSQ, x_Q3, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); + silk_nsq_scale_states( psEncC, NSQ, x16, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); silk_noise_shape_quantizer( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, - offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder ); + offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder, psEncC->arch ); - x_Q3 += psEncC->subfr_length; + x16 += psEncC->subfr_length; pulses += psEncC->subfr_length; pxq += psEncC->subfr_length; } @@ -172,7 +177,11 @@ void silk_NSQ( /***********************************/ /* silk_noise_shape_quantizer */ /***********************************/ -static OPUS_INLINE void silk_noise_shape_quantizer( + +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_noise_shape_quantizer( silk_nsq_state *NSQ, /* I/O NSQ state */ opus_int signalType, /* I Signal type */ const opus_int32 x_sc_Q10[], /* I */ @@ -191,15 +200,19 @@ static OPUS_INLINE void silk_noise_shape_quantizer( opus_int offset_Q10, /* I */ opus_int length, /* I Input length */ opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ - opus_int predictLPCOrder /* I Prediction filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ ) { - opus_int i, j; + opus_int i; opus_int32 LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13; opus_int32 n_LF_Q12, r_Q10, rr_Q10, q1_Q0, q1_Q10, q2_Q10, rd1_Q20, rd2_Q20; opus_int32 exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; opus_int32 *psLPC_Q14, *shp_lag_ptr, *pred_lag_ptr; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; @@ -208,32 +221,16 @@ static OPUS_INLINE void silk_noise_shape_quantizer( /* Set up short term AR state */ psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 ]; +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + for( i = 0; i < length; i++ ) { /* Generate dither */ NSQ->rand_seed = silk_RAND( NSQ->rand_seed ); /* Short-term prediction */ - silk_assert( predictLPCOrder == 10 || predictLPCOrder == 16 ); - /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ - LPC_pred_Q10 = silk_RSHIFT( predictLPCOrder, 1 ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ 0 ], a_Q12[ 0 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -1 ], a_Q12[ 1 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -2 ], a_Q12[ 2 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -3 ], a_Q12[ 3 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -4 ], a_Q12[ 4 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -5 ], a_Q12[ 5 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -6 ], a_Q12[ 6 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -7 ], a_Q12[ 7 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -8 ], a_Q12[ 8 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -9 ], a_Q12[ 9 ] ); - if( predictLPCOrder == 16 ) { - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -10 ], a_Q12[ 10 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -11 ], a_Q12[ 11 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -12 ], a_Q12[ 12 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -13 ], a_Q12[ 13 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -14 ], a_Q12[ 14 ] ); - LPC_pred_Q10 = silk_SMLAWB( LPC_pred_Q10, psLPC_Q14[ -15 ], a_Q12[ 15 ] ); - } + LPC_pred_Q10 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); /* Long-term prediction */ if( signalType == TYPE_VOICED ) { @@ -252,23 +249,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer( /* Noise shape feedback */ silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ - tmp2 = psLPC_Q14[ 0 ]; - tmp1 = NSQ->sAR2_Q14[ 0 ]; - NSQ->sAR2_Q14[ 0 ] = tmp2; - n_AR_Q12 = silk_RSHIFT( shapingLPCOrder, 1 ); - n_AR_Q12 = silk_SMLAWB( n_AR_Q12, tmp2, AR_shp_Q13[ 0 ] ); - for( j = 2; j < shapingLPCOrder; j += 2 ) { - tmp2 = NSQ->sAR2_Q14[ j - 1 ]; - NSQ->sAR2_Q14[ j - 1 ] = tmp1; - n_AR_Q12 = silk_SMLAWB( n_AR_Q12, tmp1, AR_shp_Q13[ j - 1 ] ); - tmp1 = NSQ->sAR2_Q14[ j + 0 ]; - NSQ->sAR2_Q14[ j + 0 ] = tmp2; - n_AR_Q12 = silk_SMLAWB( n_AR_Q12, tmp2, AR_shp_Q13[ j ] ); - } - NSQ->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; - n_AR_Q12 = silk_SMLAWB( n_AR_Q12, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + n_AR_Q12 = silk_NSQ_noise_shape_feedback_loop(&NSQ->sDiff_shp_Q14, NSQ->sAR2_Q14, AR_shp_Q13, shapingLPCOrder, arch); - n_AR_Q12 = silk_LSHIFT32( n_AR_Q12, 1 ); /* Q11 -> Q12 */ n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sLF_AR_shp_Q14, Tilt_Q14 ); n_LF_Q12 = silk_SMULWB( NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - 1 ], LF_shp_Q14 ); @@ -296,14 +278,27 @@ static OPUS_INLINE void silk_noise_shape_quantizer( r_Q10 = silk_SUB32( x_sc_Q10[ i ], tmp1 ); /* residual error Q10 */ /* Flip sign depending on dither */ - if ( NSQ->rand_seed < 0 ) { - r_Q10 = -r_Q10; + if( NSQ->rand_seed < 0 ) { + r_Q10 = -r_Q10; } r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); /* Find two quantization level candidates and measure their rate-distortion */ q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } if( q1_Q0 > 0 ) { q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); @@ -354,7 +349,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer( /* Update states */ psLPC_Q14++; *psLPC_Q14 = xq_Q14; - sLF_AR_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, n_AR_Q12, 2 ); + NSQ->sDiff_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_sc_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB_LSHIFT32( NSQ->sDiff_shp_Q14, n_AR_Q12, 2 ); NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14; NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB_LSHIFT32( sLF_AR_shp_Q14, n_LF_Q12, 2 ); @@ -373,7 +369,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer( static OPUS_INLINE void silk_nsq_scale_states( const silk_encoder_state *psEncC, /* I Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ - const opus_int32 x_Q3[], /* I input in Q3 */ + const opus_int16 x16[], /* I input */ opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ @@ -385,28 +381,18 @@ static OPUS_INLINE void silk_nsq_scale_states( ) { opus_int i, lag; - opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q23; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; lag = pitchL[ subfr ]; inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); silk_assert( inv_gain_Q31 != 0 ); - /* Calculate gain adjustment factor */ - if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { - gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); - } else { - gain_adj_Q16 = (opus_int32)1 << 16; - } - /* Scale input */ - inv_gain_Q23 = silk_RSHIFT_ROUND( inv_gain_Q31, 8 ); + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); for( i = 0; i < psEncC->subfr_length; i++ ) { - x_sc_Q10[ i ] = silk_SMULWW( x_Q3[ i ], inv_gain_Q23 ); + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); } - /* Save inverse gain */ - NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; - /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ if( NSQ->rewhite_flag ) { if( subfr == 0 ) { @@ -420,7 +406,9 @@ static OPUS_INLINE void silk_nsq_scale_states( } /* Adjust for changing gain */ - if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + /* Scale long-term shaping state */ for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); @@ -434,6 +422,7 @@ static OPUS_INLINE void silk_nsq_scale_states( } NSQ->sLF_AR_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sLF_AR_shp_Q14 ); + NSQ->sDiff_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sDiff_shp_Q14 ); /* Scale short-term prediction and shaping states */ for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { @@ -442,5 +431,8 @@ static OPUS_INLINE void silk_nsq_scale_states( for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { NSQ->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sAR2_Q14[ i ] ); } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; } } diff --git a/TMessagesProj/jni/opus/silk/NSQ.h b/TMessagesProj/jni/opus/silk/NSQ.h new file mode 100644 index 00000000000..971832f660e --- /dev/null +++ b/TMessagesProj/jni/opus/silk/NSQ.h @@ -0,0 +1,101 @@ +/*********************************************************************** +Copyright (c) 2014 Vidyo. +Copyright (c) 2006-2011, Skype Limited. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifndef SILK_NSQ_H +#define SILK_NSQ_H + +#include "SigProc_FIX.h" + +#undef silk_short_prediction_create_arch_coef + +static OPUS_INLINE opus_int32 silk_noise_shape_quantizer_short_prediction_c(const opus_int32 *buf32, const opus_int16 *coef16, opus_int order) +{ + opus_int32 out; + silk_assert( order == 10 || order == 16 ); + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + out = silk_RSHIFT( order, 1 ); + out = silk_SMLAWB( out, buf32[ 0 ], coef16[ 0 ] ); + out = silk_SMLAWB( out, buf32[ -1 ], coef16[ 1 ] ); + out = silk_SMLAWB( out, buf32[ -2 ], coef16[ 2 ] ); + out = silk_SMLAWB( out, buf32[ -3 ], coef16[ 3 ] ); + out = silk_SMLAWB( out, buf32[ -4 ], coef16[ 4 ] ); + out = silk_SMLAWB( out, buf32[ -5 ], coef16[ 5 ] ); + out = silk_SMLAWB( out, buf32[ -6 ], coef16[ 6 ] ); + out = silk_SMLAWB( out, buf32[ -7 ], coef16[ 7 ] ); + out = silk_SMLAWB( out, buf32[ -8 ], coef16[ 8 ] ); + out = silk_SMLAWB( out, buf32[ -9 ], coef16[ 9 ] ); + + if( order == 16 ) + { + out = silk_SMLAWB( out, buf32[ -10 ], coef16[ 10 ] ); + out = silk_SMLAWB( out, buf32[ -11 ], coef16[ 11 ] ); + out = silk_SMLAWB( out, buf32[ -12 ], coef16[ 12 ] ); + out = silk_SMLAWB( out, buf32[ -13 ], coef16[ 13 ] ); + out = silk_SMLAWB( out, buf32[ -14 ], coef16[ 14 ] ); + out = silk_SMLAWB( out, buf32[ -15 ], coef16[ 15 ] ); + } + return out; +} + +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) ((void)arch,silk_noise_shape_quantizer_short_prediction_c(in, coef, order)) + +static OPUS_INLINE opus_int32 silk_NSQ_noise_shape_feedback_loop_c(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order) +{ + opus_int32 out; + opus_int32 tmp1, tmp2; + opus_int j; + + tmp2 = data0[0]; + tmp1 = data1[0]; + data1[0] = tmp2; + + out = silk_RSHIFT(order, 1); + out = silk_SMLAWB(out, tmp2, coef[0]); + + for (j = 2; j < order; j += 2) { + tmp2 = data1[j - 1]; + data1[j - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[j - 1]); + tmp1 = data1[j + 0]; + data1[j + 0] = tmp2; + out = silk_SMLAWB(out, tmp2, coef[j]); + } + data1[order - 1] = tmp1; + out = silk_SMLAWB(out, tmp1, coef[order - 1]); + /* Q11 -> Q12 */ + out = silk_LSHIFT32( out, 1 ); + return out; +} + +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) ((void)arch,silk_NSQ_noise_shape_feedback_loop_c(data0, data1, coef, order)) + +#if defined(OPUS_ARM_MAY_HAVE_NEON_INTR) +#include "arm/NSQ_neon.h" +#endif + +#endif /* SILK_NSQ_H */ diff --git a/TMessagesProj/jni/opus/silk/NSQ_del_dec.c b/TMessagesProj/jni/opus/silk/NSQ_del_dec.c index 522be406639..3495613b2b6 100644 --- a/TMessagesProj/jni/opus/silk/NSQ_del_dec.c +++ b/TMessagesProj/jni/opus/silk/NSQ_del_dec.c @@ -31,6 +31,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "main.h" #include "stack_alloc.h" +#include "NSQ.h" + typedef struct { opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; @@ -41,6 +43,7 @@ typedef struct { opus_int32 Shape_Q14[ DECISION_DELAY ]; opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; opus_int32 Seed; opus_int32 SeedInit; opus_int32 RD_Q10; @@ -51,17 +54,21 @@ typedef struct { opus_int32 RD_Q10; opus_int32 xq_Q14; opus_int32 LF_AR_Q14; + opus_int32 Diff_Q14; opus_int32 sLTP_shp_Q14; opus_int32 LPC_exc_Q14; } NSQ_sample_struct; typedef NSQ_sample_struct NSQ_sample_pair[ 2 ]; +#if defined(MIPSr1_ASM) +#include "mips/NSQ_del_dec_mipsr1.h" +#endif static OPUS_INLINE void silk_nsq_del_dec_scale_states( const silk_encoder_state *psEncC, /* I Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ - const opus_int32 x_Q3[], /* I Input in Q3 */ + const opus_int16 x16[], /* I Input */ opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ @@ -103,18 +110,19 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( opus_int warping_Q16, /* I */ opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ opus_int *smpl_buf_idx, /* I Index to newest samples in buffers */ - opus_int decisionDelay /* I */ + opus_int decisionDelay, /* I */ + int arch /* I */ ); -void silk_NSQ_del_dec( +void silk_NSQ_del_dec_c( const silk_encoder_state *psEncC, /* I/O Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ SideInfoIndices *psIndices, /* I/O Quantization Indices */ - const opus_int32 x_Q3[], /* I Prefiltered input signal */ + const opus_int16 x16[], /* I Input */ opus_int8 pulses[], /* O Quantized pulse signal */ const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ - const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ @@ -153,6 +161,7 @@ void silk_NSQ_del_dec( psDD->SeedInit = psDD->Seed; psDD->RD_Q10 = 0; psDD->LF_AR_Q14 = NSQ->sLF_AR_shp_Q14; + psDD->Diff_Q14 = NSQ->sDiff_shp_Q14; psDD->Shape_Q14[ 0 ] = NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ]; silk_memcpy( psDD->sLPC_Q14, NSQ->sLPC_Q14, NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); silk_memcpy( psDD->sAR2_Q14, NSQ->sAR2_Q14, sizeof( NSQ->sAR2_Q14 ) ); @@ -180,8 +189,7 @@ void silk_NSQ_del_dec( LSF_interpolation_flag = 1; } - ALLOC( sLTP_Q15, - psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP_Q15, psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); @@ -193,7 +201,7 @@ void silk_NSQ_del_dec( for( k = 0; k < psEncC->nb_subfr; k++ ) { A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; - AR_shp_Q13 = &AR2_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + AR_shp_Q13 = &AR_Q13[ k * MAX_SHAPE_LPC_ORDER ]; /* Noise shape parameters */ silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); @@ -229,7 +237,8 @@ void silk_NSQ_del_dec( psDD = &psDelDec[ Winner_ind ]; last_smple_idx = smpl_buf_idx + decisionDelay; for( i = 0; i < decisionDelay; i++ ) { - last_smple_idx = ( last_smple_idx - 1 ) & DECISION_DELAY_MASK; + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gains_Q16[ 1 ] ), 14 ) ); @@ -244,22 +253,22 @@ void silk_NSQ_del_dec( silk_assert( start_idx > 0 ); silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], - A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder ); + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; NSQ->rewhite_flag = 1; } } - silk_nsq_del_dec_scale_states( psEncC, NSQ, psDelDec, x_Q3, x_sc_Q10, sLTP, sLTP_Q15, k, + silk_nsq_del_dec_scale_states( psEncC, NSQ, psDelDec, x16, x_sc_Q10, sLTP, sLTP_Q15, k, psEncC->nStatesDelayedDecision, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); silk_noise_shape_quantizer_del_dec( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, - psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay ); + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay, psEncC->arch ); - x_Q3 += psEncC->subfr_length; + x16 += psEncC->subfr_length; pulses += psEncC->subfr_length; pxq += psEncC->subfr_length; } @@ -280,7 +289,9 @@ void silk_NSQ_del_dec( last_smple_idx = smpl_buf_idx + decisionDelay; Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); for( i = 0; i < decisionDelay; i++ ) { - last_smple_idx = ( last_smple_idx - 1 ) & DECISION_DELAY_MASK; + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gain_Q10 ), 8 ) ); @@ -291,6 +302,7 @@ void silk_NSQ_del_dec( /* Update states */ NSQ->sLF_AR_shp_Q14 = psDD->LF_AR_Q14; + NSQ->sDiff_shp_Q14 = psDD->Diff_Q14; NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; /* Save quantized speech signal */ @@ -303,6 +315,7 @@ void silk_NSQ_del_dec( /******************************************/ /* Noise shape quantizer for one subframe */ /******************************************/ +#ifndef OVERRIDE_silk_noise_shape_quantizer_del_dec static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( silk_nsq_state *NSQ, /* I/O NSQ state */ NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ @@ -329,7 +342,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( opus_int warping_Q16, /* I */ opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ opus_int *smpl_buf_idx, /* I Index to newest samples in buffers */ - opus_int decisionDelay /* I */ + opus_int decisionDelay, /* I */ + int arch /* I */ ) { opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; @@ -339,6 +353,10 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; +#ifdef silk_short_prediction_create_arch_coef + opus_int32 a_Q12_arch[MAX_LPC_ORDER]; +#endif + VARDECL( NSQ_sample_pair, psSampleState ); NSQ_del_dec_struct *psDD; NSQ_sample_struct *psSS; @@ -351,6 +369,10 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); +#ifdef silk_short_prediction_create_arch_coef + silk_short_prediction_create_arch_coef(a_Q12_arch, a_Q12, predictLPCOrder); +#endif + for( i = 0; i < length; i++ ) { /* Perform common calculations used in all states */ @@ -394,33 +416,13 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( /* Pointer used in short term prediction and shaping */ psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; /* Short-term prediction */ - silk_assert( predictLPCOrder == 10 || predictLPCOrder == 16 ); - /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ - LPC_pred_Q14 = silk_RSHIFT( predictLPCOrder, 1 ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ 0 ], a_Q12[ 0 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -1 ], a_Q12[ 1 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -2 ], a_Q12[ 2 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -3 ], a_Q12[ 3 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -4 ], a_Q12[ 4 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -5 ], a_Q12[ 5 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -6 ], a_Q12[ 6 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -7 ], a_Q12[ 7 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -8 ], a_Q12[ 8 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -9 ], a_Q12[ 9 ] ); - if( predictLPCOrder == 16 ) { - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -10 ], a_Q12[ 10 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -11 ], a_Q12[ 11 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -12 ], a_Q12[ 12 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -13 ], a_Q12[ 13 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -14 ], a_Q12[ 14 ] ); - LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -15 ], a_Q12[ 15 ] ); - } + LPC_pred_Q14 = silk_noise_shape_quantizer_short_prediction(psLPC_Q14, a_Q12, a_Q12_arch, predictLPCOrder, arch); LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ /* Noise shape feedback */ silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ /* Output of lowpass section */ - tmp2 = silk_SMLAWB( psLPC_Q14[ 0 ], psDD->sAR2_Q14[ 0 ], warping_Q16 ); + tmp2 = silk_SMLAWB( psDD->Diff_Q14, psDD->sAR2_Q14[ 0 ], warping_Q16 ); /* Output of allpass section */ tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], psDD->sAR2_Q14[ 1 ] - tmp2, warping_Q16 ); psDD->sAR2_Q14[ 0 ] = tmp2; @@ -466,6 +468,19 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( /* Find two quantization level candidates and measure their rate-distortion */ q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if (Lambda_Q10 > 2048) { + /* For aggressive RDO, the bias becomes more than one pulse. */ + int rdo_offset = Lambda_Q10/2 - 512; + if (q1_Q10 > rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 - rdo_offset, 10 ); + } else if (q1_Q10 < -rdo_offset) { + q1_Q0 = silk_RSHIFT( q1_Q10 + rdo_offset, 10 ); + } else if (q1_Q10 < 0) { + q1_Q0 = -1; + } else { + q1_Q0 = 0; + } + } if( q1_Q0 > 0 ) { q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); @@ -519,7 +534,8 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); /* Update states */ - sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 0 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB32( psSS[ 0 ].Diff_Q14, n_AR_Q14 ); psSS[ 0 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; @@ -533,21 +549,22 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( exc_Q14 = -exc_Q14; } - /* Add predictions */ LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); /* Update states */ - sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 1 ].Diff_Q14 = silk_SUB_LSHIFT32( xq_Q14, x_Q10[ i ], 4 ); + sLF_AR_shp_Q14 = silk_SUB32( psSS[ 1 ].Diff_Q14, n_AR_Q14 ); psSS[ 1 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; psSS[ 1 ].xq_Q14 = xq_Q14; } - *smpl_buf_idx = ( *smpl_buf_idx - 1 ) & DECISION_DELAY_MASK; /* Index to newest samples */ - last_smple_idx = ( *smpl_buf_idx + decisionDelay ) & DECISION_DELAY_MASK; /* Index to decisionDelay old samples */ + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; /* Find winner */ RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; @@ -611,6 +628,7 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( psDD = &psDelDec[ k ]; psSS = &psSampleState[ k ][ 0 ]; psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->Diff_Q14 = psSS->Diff_Q14; psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; @@ -629,12 +647,13 @@ static OPUS_INLINE void silk_noise_shape_quantizer_del_dec( } RESTORE_STACK; } +#endif /* OVERRIDE_silk_noise_shape_quantizer_del_dec */ static OPUS_INLINE void silk_nsq_del_dec_scale_states( const silk_encoder_state *psEncC, /* I Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ - const opus_int32 x_Q3[], /* I Input in Q3 */ + const opus_int16 x16[], /* I Input */ opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ @@ -648,29 +667,19 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states( ) { opus_int i, k, lag; - opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q23; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q26; NSQ_del_dec_struct *psDD; lag = pitchL[ subfr ]; inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); silk_assert( inv_gain_Q31 != 0 ); - /* Calculate gain adjustment factor */ - if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { - gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); - } else { - gain_adj_Q16 = (opus_int32)1 << 16; - } - /* Scale input */ - inv_gain_Q23 = silk_RSHIFT_ROUND( inv_gain_Q31, 8 ); + inv_gain_Q26 = silk_RSHIFT_ROUND( inv_gain_Q31, 5 ); for( i = 0; i < psEncC->subfr_length; i++ ) { - x_sc_Q10[ i ] = silk_SMULWW( x_Q3[ i ], inv_gain_Q23 ); + x_sc_Q10[ i ] = silk_SMULWW( x16[ i ], inv_gain_Q26 ); } - /* Save inverse gain */ - NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; - /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ if( NSQ->rewhite_flag ) { if( subfr == 0 ) { @@ -684,7 +693,9 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states( } /* Adjust for changing gain */ - if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + /* Scale long-term shaping state */ for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx; i++ ) { NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); @@ -702,6 +713,7 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states( /* Scale scalar states */ psDD->LF_AR_Q14 = silk_SMULWW( gain_adj_Q16, psDD->LF_AR_Q14 ); + psDD->Diff_Q14 = silk_SMULWW( gain_adj_Q16, psDD->Diff_Q14 ); /* Scale short-term prediction and shaping states */ for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { @@ -715,5 +727,8 @@ static OPUS_INLINE void silk_nsq_del_dec_scale_states( psDD->Shape_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Shape_Q14[ i ] ); } } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; } } diff --git a/TMessagesProj/jni/opus/silk/PLC.c b/TMessagesProj/jni/opus/silk/PLC.c index 01f40014c46..277037a943b 100644 --- a/TMessagesProj/jni/opus/silk/PLC.c +++ b/TMessagesProj/jni/opus/silk/PLC.c @@ -46,7 +46,8 @@ static OPUS_INLINE void silk_PLC_update( static OPUS_INLINE void silk_PLC_conceal( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I/O Decoder control */ - opus_int16 frame[] /* O LPC residual signal */ + opus_int16 frame[], /* O LPC residual signal */ + int arch /* I Run-time architecture */ ); @@ -65,7 +66,8 @@ void silk_PLC( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I/O Decoder control */ opus_int16 frame[], /* I/O signal */ - opus_int lost /* I Loss flag */ + opus_int lost, /* I Loss flag */ + int arch /* I Run-time architecture */ ) { /* PLC control function */ @@ -78,7 +80,7 @@ void silk_PLC( /****************************/ /* Generate Signal */ /****************************/ - silk_PLC_conceal( psDec, psDecCtrl, frame ); + silk_PLC_conceal( psDec, psDecCtrl, frame, arch ); psDec->lossCnt++; } else { @@ -165,10 +167,35 @@ static OPUS_INLINE void silk_PLC_update( psPLC->nb_subfr = psDec->nb_subfr; } +static OPUS_INLINE void silk_PLC_energy(opus_int32 *energy1, opus_int *shift1, opus_int32 *energy2, opus_int *shift2, + const opus_int32 *exc_Q14, const opus_int32 *prevGain_Q10, int subfr_length, int nb_subfr) +{ + int i, k; + VARDECL( opus_int16, exc_buf ); + opus_int16 *exc_buf_ptr; + SAVE_STACK; + ALLOC( exc_buf, 2*subfr_length, opus_int16 ); + /* Find random noise component */ + /* Scale previous excitation signal */ + exc_buf_ptr = exc_buf; + for( k = 0; k < 2; k++ ) { + for( i = 0; i < subfr_length; i++ ) { + exc_buf_ptr[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT( + silk_SMULWW( exc_Q14[ i + ( k + nb_subfr - 2 ) * subfr_length ], prevGain_Q10[ k ] ), 8 ) ); + } + exc_buf_ptr += subfr_length; + } + /* Find the subframe with lowest energy of the last two and use that as random noise generator */ + silk_sum_sqr_shift( energy1, shift1, exc_buf, subfr_length ); + silk_sum_sqr_shift( energy2, shift2, &exc_buf[ subfr_length ], subfr_length ); + RESTORE_STACK; +} + static OPUS_INLINE void silk_PLC_conceal( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I/O Decoder control */ - opus_int16 frame[] /* O LPC residual signal */ + opus_int16 frame[], /* O LPC residual signal */ + int arch /* I Run-time architecture */ ) { opus_int i, j, k; @@ -177,19 +204,26 @@ static OPUS_INLINE void silk_PLC_conceal( opus_int32 energy1, energy2, *rand_ptr, *pred_lag_ptr; opus_int32 LPC_pred_Q10, LTP_pred_Q12; opus_int16 rand_scale_Q14; - opus_int16 *B_Q14, *exc_buf_ptr; + opus_int16 *B_Q14; opus_int32 *sLPC_Q14_ptr; - VARDECL( opus_int16, exc_buf ); opus_int16 A_Q12[ MAX_LPC_ORDER ]; +#ifdef SMALL_FOOTPRINT + opus_int16 *sLTP; +#else VARDECL( opus_int16, sLTP ); +#endif VARDECL( opus_int32, sLTP_Q14 ); silk_PLC_struct *psPLC = &psDec->sPLC; opus_int32 prevGain_Q10[2]; SAVE_STACK; - ALLOC( exc_buf, 2*psPLC->subfr_length, opus_int16 ); - ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); ALLOC( sLTP_Q14, psDec->ltp_mem_length + psDec->frame_length, opus_int32 ); +#ifdef SMALL_FOOTPRINT + /* Ugly hack that breaks aliasing rules to save stack: put sLTP at the very end of sLTP_Q14. */ + sLTP = ((opus_int16*)&sLTP_Q14[psDec->ltp_mem_length + psDec->frame_length])-psDec->ltp_mem_length; +#else + ALLOC( sLTP, psDec->ltp_mem_length, opus_int16 ); +#endif prevGain_Q10[0] = silk_RSHIFT( psPLC->prevGain_Q16[ 0 ], 6); prevGain_Q10[1] = silk_RSHIFT( psPLC->prevGain_Q16[ 1 ], 6); @@ -198,19 +232,7 @@ static OPUS_INLINE void silk_PLC_conceal( silk_memset( psPLC->prevLPC_Q12, 0, sizeof( psPLC->prevLPC_Q12 ) ); } - /* Find random noise component */ - /* Scale previous excitation signal */ - exc_buf_ptr = exc_buf; - for( k = 0; k < 2; k++ ) { - for( i = 0; i < psPLC->subfr_length; i++ ) { - exc_buf_ptr[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT( - silk_SMULWW( psDec->exc_Q14[ i + ( k + psPLC->nb_subfr - 2 ) * psPLC->subfr_length ], prevGain_Q10[ k ] ), 8 ) ); - } - exc_buf_ptr += psPLC->subfr_length; - } - /* Find the subframe with lowest energy of the last two and use that as random noise generator */ - silk_sum_sqr_shift( &energy1, &shift1, exc_buf, psPLC->subfr_length ); - silk_sum_sqr_shift( &energy2, &shift2, &exc_buf[ psPLC->subfr_length ], psPLC->subfr_length ); + silk_PLC_energy(&energy1, &shift1, &energy2, &shift2, psDec->exc_Q14, prevGain_Q10, psDec->subfr_length, psDec->nb_subfr); if( silk_RSHIFT( energy1, shift2 ) < silk_RSHIFT( energy2, shift1 ) ) { /* First sub-frame has lowest energy */ @@ -270,7 +292,7 @@ static OPUS_INLINE void silk_PLC_conceal( /* Rewhiten LTP state */ idx = psDec->ltp_mem_length - lag - psDec->LPC_order - LTP_ORDER / 2; silk_assert( idx > 0 ); - silk_LPC_analysis_filter( &sLTP[ idx ], &psDec->outBuf[ idx ], A_Q12, psDec->ltp_mem_length - idx, psDec->LPC_order ); + silk_LPC_analysis_filter( &sLTP[ idx ], &psDec->outBuf[ idx ], A_Q12, psDec->ltp_mem_length - idx, psDec->LPC_order, arch ); /* Scale LTP state */ inv_gain_Q30 = silk_INVERSE32_varQ( psPLC->prevGain_Q16[ 1 ], 46 ); inv_gain_Q30 = silk_min( inv_gain_Q30, silk_int32_MAX >> 1 ); @@ -306,8 +328,10 @@ static OPUS_INLINE void silk_PLC_conceal( for( j = 0; j < LTP_ORDER; j++ ) { B_Q14[ j ] = silk_RSHIFT( silk_SMULBB( harm_Gain_Q15, B_Q14[ j ] ), 15 ); } - /* Gradually reduce excitation gain */ - rand_scale_Q14 = silk_RSHIFT( silk_SMULBB( rand_scale_Q14, rand_Gain_Q15 ), 15 ); + if ( psDec->indices.signalType != TYPE_NO_VOICE_ACTIVITY ) { + /* Gradually reduce excitation gain */ + rand_scale_Q14 = silk_RSHIFT( silk_SMULBB( rand_scale_Q14, rand_Gain_Q15 ), 15 ); + } /* Slowly increase pitch lag */ psPLC->pitchL_Q8 = silk_SMLAWB( psPLC->pitchL_Q8, psPLC->pitchL_Q8, PITCH_DRIFT_FAC_Q16 ); @@ -343,7 +367,8 @@ static OPUS_INLINE void silk_PLC_conceal( } /* Add prediction to LPC excitation */ - sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = silk_ADD_LSHIFT32( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], LPC_pred_Q10, 4 ); + sLPC_Q14_ptr[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], + silk_LSHIFT_SAT32( LPC_pred_Q10, 4 )); /* Scale with Gain */ frame[ i ] = (opus_int16)silk_SAT16( silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14_ptr[ MAX_LPC_ORDER + i ], prevGain_Q10[ 1 ] ), 8 ) ) ); diff --git a/TMessagesProj/jni/opus/silk/PLC.h b/TMessagesProj/jni/opus/silk/PLC.h index f1e2eccc69a..6438f516330 100644 --- a/TMessagesProj/jni/opus/silk/PLC.h +++ b/TMessagesProj/jni/opus/silk/PLC.h @@ -48,7 +48,8 @@ void silk_PLC( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I/O Decoder control */ opus_int16 frame[], /* I/O signal */ - opus_int lost /* I Loss flag */ + opus_int lost, /* I Loss flag */ + int arch /* I Run-time architecture */ ); void silk_PLC_glue_frames( diff --git a/TMessagesProj/jni/opus/silk/SigProc_FIX.h b/TMessagesProj/jni/opus/silk/SigProc_FIX.h index 1b580579104..0f260176b3e 100644 --- a/TMessagesProj/jni/opus/silk/SigProc_FIX.h +++ b/TMessagesProj/jni/opus/silk/SigProc_FIX.h @@ -35,13 +35,17 @@ extern "C" /*#define silk_MACRO_COUNT */ /* Used to enable WMOPS counting */ -#define SILK_MAX_ORDER_LPC 16 /* max order of the LPC analysis in schur() and k2a() */ +#define SILK_MAX_ORDER_LPC 24 /* max order of the LPC analysis in schur() and k2a() */ #include /* for memset(), memcpy(), memmove() */ #include "typedef.h" #include "resampler_structs.h" #include "macros.h" +#include "cpu_support.h" +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/SigProc_FIX_sse.h" +#endif /********************************************************************/ /* SIGNAL PROCESSING FUNCTIONS */ @@ -108,7 +112,8 @@ void silk_LPC_analysis_filter( const opus_int16 *in, /* I Input signal */ const opus_int16 *B, /* I MA prediction coefficients, Q12 [order] */ const opus_int32 len, /* I Signal length */ - const opus_int32 d /* I Filter order */ + const opus_int32 d, /* I Filter order */ + int arch /* I Run-time architecture */ ); /* Chirp (bandwidth expand) LP AR filter */ @@ -269,6 +274,15 @@ void silk_NLSF2A( const opus_int d /* I filter order (should be even) */ ); +/* Convert int32 coefficients to int16 coefs and make sure there's no wrap-around */ +void silk_LPC_fit( + opus_int16 *a_QOUT, /* O Output signal */ + opus_int32 *a_QIN, /* I/O Input signal */ + const opus_int QOUT, /* I Input Q domain */ + const opus_int QIN, /* I Input Q domain */ + const opus_int d /* I Filter order */ +); + void silk_insertion_sort_increasing( opus_int32 *a, /* I/O Unsorted / Sorted vector */ opus_int *idx, /* O Index vector for the sorted elements */ @@ -303,7 +317,7 @@ void silk_NLSF_VQ_weights_laroia( ); /* Compute reflection coefficients from input signal */ -void silk_burg_modified( +void silk_burg_modified_c( opus_int32 *res_nrg, /* O Residual energy */ opus_int *res_nrg_Q, /* O Residual energy Q value */ opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ @@ -335,12 +349,15 @@ void silk_scale_vector32_Q26_lshift_18( /********************************************************************/ /* return sum( inVec1[i] * inVec2[i] ) */ + opus_int32 silk_inner_prod_aligned( const opus_int16 *const inVec1, /* I input vector 1 */ const opus_int16 *const inVec2, /* I input vector 2 */ - const opus_int len /* I vector lengths */ + const opus_int len, /* I vector lengths */ + int arch /* I Run-time architecture */ ); + opus_int32 silk_inner_prod_aligned_scale( const opus_int16 *const inVec1, /* I input vector 1 */ const opus_int16 *const inVec2, /* I input vector 2 */ @@ -348,7 +365,7 @@ opus_int32 silk_inner_prod_aligned_scale( const opus_int len /* I vector lengths */ ); -opus_int64 silk_inner_prod16_aligned_64( +opus_int64 silk_inner_prod16_aligned_64_c( const opus_int16 *inVec1, /* I input vector 1 */ const opus_int16 *inVec2, /* I input vector 2 */ const opus_int len /* I vector lengths */ @@ -463,8 +480,7 @@ static OPUS_INLINE opus_int32 silk_ROR32( opus_int32 a32, opus_int rot ) /* Add with saturation for positive input values */ #define silk_ADD_POS_SAT8(a, b) ((((a)+(b)) & 0x80) ? silk_int8_MAX : ((a)+(b))) #define silk_ADD_POS_SAT16(a, b) ((((a)+(b)) & 0x8000) ? silk_int16_MAX : ((a)+(b))) -#define silk_ADD_POS_SAT32(a, b) ((((a)+(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))) -#define silk_ADD_POS_SAT64(a, b) ((((a)+(b)) & 0x8000000000000000LL) ? silk_int64_MAX : ((a)+(b))) +#define silk_ADD_POS_SAT32(a, b) ((((opus_uint32)(a)+(opus_uint32)(b)) & 0x80000000) ? silk_int32_MAX : ((a)+(b))) #define silk_LSHIFT8(a, shift) ((opus_int8)((opus_uint8)(a)<<(shift))) /* shift >= 0, shift < 8 */ #define silk_LSHIFT16(a, shift) ((opus_int16)((opus_uint16)(a)<<(shift))) /* shift >= 0, shift < 16 */ @@ -575,6 +591,14 @@ static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) /* the following seems faster on x86 */ #define silk_SMMUL(a32, b32) (opus_int32)silk_RSHIFT64(silk_SMULL((a32), (b32)), 32) +#if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +#define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((void)(arch), silk_burg_modified_c(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) + +#define silk_inner_prod16_aligned_64(inVec1, inVec2, len, arch) \ + ((void)(arch),silk_inner_prod16_aligned_64_c(inVec1, inVec2, len)) +#endif + #include "Inlines.h" #include "MacroCount.h" #include "MacroDebug.h" @@ -587,6 +611,11 @@ static OPUS_INLINE opus_int64 silk_max_64(opus_int64 a, opus_int64 b) #include "arm/SigProc_FIX_armv5e.h" #endif +#if defined(MIPSr1_ASM) +#include "mips/sigproc_fix_mipsr1.h" +#endif + + #ifdef __cplusplus } #endif diff --git a/TMessagesProj/jni/opus/silk/VAD.c b/TMessagesProj/jni/opus/silk/VAD.c index a8090981434..0a782af2f13 100644 --- a/TMessagesProj/jni/opus/silk/VAD.c +++ b/TMessagesProj/jni/opus/silk/VAD.c @@ -33,10 +33,12 @@ POSSIBILITY OF SUCH DAMAGE. #include "stack_alloc.h" /* Silk VAD noise level estimation */ +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) static OPUS_INLINE void silk_VAD_GetNoiseLevels( const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ ); +#endif /**********************************/ /* Initialization of the Silk VAD */ @@ -77,7 +79,7 @@ static const opus_int32 tiltWeights[ VAD_N_BANDS ] = { 30000, 6000, -12000, -120 /***************************************/ /* Get the speech activity level in Q8 */ /***************************************/ -opus_int silk_VAD_GetSA_Q8( /* O Return value, 0 if success */ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ silk_encoder_state *psEncC, /* I/O Encoder state */ const opus_int16 pIn[] /* I PCM input */ ) @@ -296,7 +298,10 @@ opus_int silk_VAD_GetSA_Q8( /* O Return v /**************************/ /* Noise level estimation */ /**************************/ -static OPUS_INLINE void silk_VAD_GetNoiseLevels( +# if !defined(OPUS_X86_MAY_HAVE_SSE4_1) +static OPUS_INLINE +#endif +void silk_VAD_GetNoiseLevels( const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ ) diff --git a/TMessagesProj/jni/opus/silk/VQ_WMat_EC.c b/TMessagesProj/jni/opus/silk/VQ_WMat_EC.c index 13d5d34eddb..0f3d545c4ef 100644 --- a/TMessagesProj/jni/opus/silk/VQ_WMat_EC.c +++ b/TMessagesProj/jni/opus/silk/VQ_WMat_EC.c @@ -32,86 +32,97 @@ POSSIBILITY OF SUCH DAMAGE. #include "main.h" /* Entropy constrained matrix-weighted VQ, hard-coded to 5-element vectors, for a single input data vector */ -void silk_VQ_WMat_EC( +void silk_VQ_WMat_EC_c( opus_int8 *ind, /* O index of best codebook vector */ - opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ - const opus_int16 *in_Q14, /* I input vector to be quantized */ - const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ const opus_int8 *cb_Q7, /* I codebook */ const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ - const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int subfr_len, /* I number of samples per subframe */ const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ - opus_int L /* I number of vectors in codebook */ + const opus_int L /* I number of vectors in codebook */ ) { opus_int k, gain_tmp_Q7; const opus_int8 *cb_row_Q7; - opus_int16 diff_Q14[ 5 ]; - opus_int32 sum1_Q14, sum2_Q16; + opus_int32 neg_xX_Q24[ 5 ]; + opus_int32 sum1_Q15, sum2_Q24; + opus_int32 bits_res_Q8, bits_tot_Q8; + + /* Negate and convert to new Q domain */ + neg_xX_Q24[ 0 ] = -silk_LSHIFT32( xX_Q17[ 0 ], 7 ); + neg_xX_Q24[ 1 ] = -silk_LSHIFT32( xX_Q17[ 1 ], 7 ); + neg_xX_Q24[ 2 ] = -silk_LSHIFT32( xX_Q17[ 2 ], 7 ); + neg_xX_Q24[ 3 ] = -silk_LSHIFT32( xX_Q17[ 3 ], 7 ); + neg_xX_Q24[ 4 ] = -silk_LSHIFT32( xX_Q17[ 4 ], 7 ); /* Loop over codebook */ - *rate_dist_Q14 = silk_int32_MAX; + *rate_dist_Q8 = silk_int32_MAX; + *res_nrg_Q15 = silk_int32_MAX; cb_row_Q7 = cb_Q7; + /* In things go really bad, at least *ind is set to something safe. */ + *ind = 0; for( k = 0; k < L; k++ ) { - gain_tmp_Q7 = cb_gain_Q7[k]; - - diff_Q14[ 0 ] = in_Q14[ 0 ] - silk_LSHIFT( cb_row_Q7[ 0 ], 7 ); - diff_Q14[ 1 ] = in_Q14[ 1 ] - silk_LSHIFT( cb_row_Q7[ 1 ], 7 ); - diff_Q14[ 2 ] = in_Q14[ 2 ] - silk_LSHIFT( cb_row_Q7[ 2 ], 7 ); - diff_Q14[ 3 ] = in_Q14[ 3 ] - silk_LSHIFT( cb_row_Q7[ 3 ], 7 ); - diff_Q14[ 4 ] = in_Q14[ 4 ] - silk_LSHIFT( cb_row_Q7[ 4 ], 7 ); - + opus_int32 penalty; + gain_tmp_Q7 = cb_gain_Q7[k]; /* Weighted rate */ - sum1_Q14 = silk_SMULBB( mu_Q9, cl_Q5[ k ] ); - - /* Penalty for too large gain */ - sum1_Q14 = silk_ADD_LSHIFT32( sum1_Q14, silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 10 ); - - silk_assert( sum1_Q14 >= 0 ); - - /* first row of W_Q18 */ - sum2_Q16 = silk_SMULWB( W_Q18[ 1 ], diff_Q14[ 1 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 2 ], diff_Q14[ 2 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 3 ], diff_Q14[ 3 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 4 ], diff_Q14[ 4 ] ); - sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 0 ], diff_Q14[ 0 ] ); - sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 0 ] ); - - /* second row of W_Q18 */ - sum2_Q16 = silk_SMULWB( W_Q18[ 7 ], diff_Q14[ 2 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 8 ], diff_Q14[ 3 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 9 ], diff_Q14[ 4 ] ); - sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 6 ], diff_Q14[ 1 ] ); - sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 1 ] ); - - /* third row of W_Q18 */ - sum2_Q16 = silk_SMULWB( W_Q18[ 13 ], diff_Q14[ 3 ] ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 14 ], diff_Q14[ 4 ] ); - sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 12 ], diff_Q14[ 2 ] ); - sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 2 ] ); - - /* fourth row of W_Q18 */ - sum2_Q16 = silk_SMULWB( W_Q18[ 19 ], diff_Q14[ 4 ] ); - sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); - sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 18 ], diff_Q14[ 3 ] ); - sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 3 ] ); - - /* last row of W_Q18 */ - sum2_Q16 = silk_SMULWB( W_Q18[ 24 ], diff_Q14[ 4 ] ); - sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 4 ] ); - - silk_assert( sum1_Q14 >= 0 ); + /* Quantization error: 1 - 2 * xX * cb + cb' * XX * cb */ + sum1_Q15 = SILK_FIX_CONST( 1.001, 15 ); + + /* Penalty for too large gain */ + penalty = silk_LSHIFT32( silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 11 ); + + /* first row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 0 ], XX_Q17[ 1 ], cb_row_Q7[ 1 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 2 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 3 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 4 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 0 ], cb_row_Q7[ 0 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 0 ] ); + + /* second row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 1 ], XX_Q17[ 7 ], cb_row_Q7[ 2 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 8 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 9 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 6 ], cb_row_Q7[ 1 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 1 ] ); + + /* third row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 2 ], XX_Q17[ 13 ], cb_row_Q7[ 3 ] ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 14 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 12 ], cb_row_Q7[ 2 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 2 ] ); + + /* fourth row of XX_Q17 */ + sum2_Q24 = silk_MLA( neg_xX_Q24[ 3 ], XX_Q17[ 19 ], cb_row_Q7[ 4 ] ); + sum2_Q24 = silk_LSHIFT32( sum2_Q24, 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 18 ], cb_row_Q7[ 3 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 3 ] ); + + /* last row of XX_Q17 */ + sum2_Q24 = silk_LSHIFT32( neg_xX_Q24[ 4 ], 1 ); + sum2_Q24 = silk_MLA( sum2_Q24, XX_Q17[ 24 ], cb_row_Q7[ 4 ] ); + sum1_Q15 = silk_SMLAWB( sum1_Q15, sum2_Q24, cb_row_Q7[ 4 ] ); /* find best */ - if( sum1_Q14 < *rate_dist_Q14 ) { - *rate_dist_Q14 = sum1_Q14; - *ind = (opus_int8)k; - *gain_Q7 = gain_tmp_Q7; + if( sum1_Q15 >= 0 ) { + /* Translate residual energy to bits using high-rate assumption (6 dB ==> 1 bit/sample) */ + bits_res_Q8 = silk_SMULBB( subfr_len, silk_lin2log( sum1_Q15 + penalty) - (15 << 7) ); + /* In the following line we reduce the codelength component by half ("-1"); seems to slghtly improve quality */ + bits_tot_Q8 = silk_ADD_LSHIFT32( bits_res_Q8, cl_Q5[ k ], 3-1 ); + if( bits_tot_Q8 <= *rate_dist_Q8 ) { + *rate_dist_Q8 = bits_tot_Q8; + *res_nrg_Q15 = sum1_Q15 + penalty; + *ind = (opus_int8)k; + *gain_Q7 = gain_tmp_Q7; + } } /* Go to next cbk vector */ diff --git a/TMessagesProj/jni/opus/silk/arm/NSQ_neon.c b/TMessagesProj/jni/opus/silk/arm/NSQ_neon.c new file mode 100644 index 00000000000..96425299736 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/arm/NSQ_neon.c @@ -0,0 +1,112 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "main.h" +#include "stack_alloc.h" +#include "NSQ.h" +#include "celt/cpu_support.h" +#include "celt/arm/armcpu.h" + +opus_int32 silk_noise_shape_quantizer_short_prediction_neon(const opus_int32 *buf32, const opus_int32 *coef32, opus_int order) +{ + int32x4_t coef0 = vld1q_s32(coef32); + int32x4_t coef1 = vld1q_s32(coef32 + 4); + int32x4_t coef2 = vld1q_s32(coef32 + 8); + int32x4_t coef3 = vld1q_s32(coef32 + 12); + + int32x4_t a0 = vld1q_s32(buf32 - 15); + int32x4_t a1 = vld1q_s32(buf32 - 11); + int32x4_t a2 = vld1q_s32(buf32 - 7); + int32x4_t a3 = vld1q_s32(buf32 - 3); + + int32x4_t b0 = vqdmulhq_s32(coef0, a0); + int32x4_t b1 = vqdmulhq_s32(coef1, a1); + int32x4_t b2 = vqdmulhq_s32(coef2, a2); + int32x4_t b3 = vqdmulhq_s32(coef3, a3); + + int32x4_t c0 = vaddq_s32(b0, b1); + int32x4_t c1 = vaddq_s32(b2, b3); + + int32x4_t d = vaddq_s32(c0, c1); + + int64x2_t e = vpaddlq_s32(d); + + int64x1_t f = vadd_s64(vget_low_s64(e), vget_high_s64(e)); + + opus_int32 out = vget_lane_s32(vreinterpret_s32_s64(f), 0); + + out += silk_RSHIFT( order, 1 ); + + return out; +} + + +opus_int32 silk_NSQ_noise_shape_feedback_loop_neon(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order) +{ + opus_int32 out; + if (order == 8) + { + int32x4_t a00 = vdupq_n_s32(data0[0]); + int32x4_t a01 = vld1q_s32(data1); /* data1[0] ... [3] */ + + int32x4_t a0 = vextq_s32 (a00, a01, 3); /* data0[0] data1[0] ...[2] */ + int32x4_t a1 = vld1q_s32(data1 + 3); /* data1[3] ... [6] */ + + /*TODO: Convert these once in advance instead of once per sample, like + silk_noise_shape_quantizer_short_prediction_neon() does.*/ + int16x8_t coef16 = vld1q_s16(coef); + int32x4_t coef0 = vmovl_s16(vget_low_s16(coef16)); + int32x4_t coef1 = vmovl_s16(vget_high_s16(coef16)); + + /*This is not bit-exact with the C version, since we do not drop the + lower 16 bits of each multiply, but wait until the end to truncate + precision. This is an encoder-specific calculation (and unlike + silk_noise_shape_quantizer_short_prediction_neon(), is not meant to + simulate what the decoder will do). We still could use vqdmulhq_s32() + like silk_noise_shape_quantizer_short_prediction_neon() and save + half the multiplies, but the speed difference is not large, since we + then need two extra adds.*/ + int64x2_t b0 = vmull_s32(vget_low_s32(a0), vget_low_s32(coef0)); + int64x2_t b1 = vmlal_s32(b0, vget_high_s32(a0), vget_high_s32(coef0)); + int64x2_t b2 = vmlal_s32(b1, vget_low_s32(a1), vget_low_s32(coef1)); + int64x2_t b3 = vmlal_s32(b2, vget_high_s32(a1), vget_high_s32(coef1)); + + int64x1_t c = vadd_s64(vget_low_s64(b3), vget_high_s64(b3)); + int64x1_t cS = vrshr_n_s64(c, 15); + int32x2_t d = vreinterpret_s32_s64(cS); + + out = vget_lane_s32(d, 0); + vst1q_s32(data1, a0); + vst1q_s32(data1 + 4, a1); + return out; + } + return silk_NSQ_noise_shape_feedback_loop_c(data0, data1, coef, order); +} diff --git a/TMessagesProj/jni/opus/silk/arm/NSQ_neon.h b/TMessagesProj/jni/opus/silk/arm/NSQ_neon.h new file mode 100644 index 00000000000..77c946af854 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/arm/NSQ_neon.h @@ -0,0 +1,113 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifndef SILK_NSQ_NEON_H +#define SILK_NSQ_NEON_H + +#include "cpu_support.h" + +#undef silk_short_prediction_create_arch_coef +/* For vectorized calc, reverse a_Q12 coefs, convert to 32-bit, and shift for vqdmulhq_s32. */ +static OPUS_INLINE void silk_short_prediction_create_arch_coef_neon(opus_int32 *out, const opus_int16 *in, opus_int order) +{ + out[15] = in[0] << 15; + out[14] = in[1] << 15; + out[13] = in[2] << 15; + out[12] = in[3] << 15; + out[11] = in[4] << 15; + out[10] = in[5] << 15; + out[9] = in[6] << 15; + out[8] = in[7] << 15; + out[7] = in[8] << 15; + out[6] = in[9] << 15; + + if (order == 16) + { + out[5] = in[10] << 15; + out[4] = in[11] << 15; + out[3] = in[12] << 15; + out[2] = in[13] << 15; + out[1] = in[14] << 15; + out[0] = in[15] << 15; + } + else + { + out[5] = 0; + out[4] = 0; + out[3] = 0; + out[2] = 0; + out[1] = 0; + out[0] = 0; + } +} + +#if defined(OPUS_ARM_PRESUME_NEON_INTR) + +#define silk_short_prediction_create_arch_coef(out, in, order) \ + (silk_short_prediction_create_arch_coef_neon(out, in, order)) + +#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + +#define silk_short_prediction_create_arch_coef(out, in, order) \ + do { if (arch == OPUS_ARCH_ARM_NEON) { silk_short_prediction_create_arch_coef_neon(out, in, order); } } while (0) + +#endif + +opus_int32 silk_noise_shape_quantizer_short_prediction_neon(const opus_int32 *buf32, const opus_int32 *coef32, opus_int order); + +opus_int32 silk_NSQ_noise_shape_feedback_loop_neon(const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, opus_int order); + +#if defined(OPUS_ARM_PRESUME_NEON_INTR) +#undef silk_noise_shape_quantizer_short_prediction +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) \ + ((void)arch,silk_noise_shape_quantizer_short_prediction_neon(in, coefRev, order)) + +#undef silk_NSQ_noise_shape_feedback_loop +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) ((void)arch,silk_NSQ_noise_shape_feedback_loop_neon(data0, data1, coef, order)) + +#elif defined(OPUS_HAVE_RTCD) && defined(OPUS_ARM_MAY_HAVE_NEON_INTR) + +/* silk_noise_shape_quantizer_short_prediction implementations take different parameters based on arch + (coef vs. coefRev) so can't use the usual IMPL table implementation */ +#undef silk_noise_shape_quantizer_short_prediction +#define silk_noise_shape_quantizer_short_prediction(in, coef, coefRev, order, arch) \ + (arch == OPUS_ARCH_ARM_NEON ? \ + silk_noise_shape_quantizer_short_prediction_neon(in, coefRev, order) : \ + silk_noise_shape_quantizer_short_prediction_c(in, coef, order)) + +extern opus_int32 + (*const SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[OPUS_ARCHMASK+1])( + const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, + opus_int order); + +#undef silk_NSQ_noise_shape_feedback_loop +#define silk_NSQ_noise_shape_feedback_loop(data0, data1, coef, order, arch) \ + (SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[(arch)&OPUS_ARCHMASK](data0, data1, \ + coef, order)) + +#endif + +#endif /* SILK_NSQ_NEON_H */ diff --git a/TMessagesProj/jni/opus/silk/arm/arm_silk_map.c b/TMessagesProj/jni/opus/silk/arm/arm_silk_map.c new file mode 100644 index 00000000000..9bd86a7b215 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/arm/arm_silk_map.c @@ -0,0 +1,55 @@ +/*********************************************************************** +Copyright (C) 2014 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "NSQ.h" + +#if defined(OPUS_HAVE_RTCD) + +# if (defined(OPUS_ARM_MAY_HAVE_NEON_INTR) && \ + !defined(OPUS_ARM_PRESUME_NEON_INTR)) + +/*There is no table for silk_noise_shape_quantizer_short_prediction because the + NEON version takes different parameters than the C version. + Instead RTCD is done via if statements at the call sites. + See NSQ_neon.h for details.*/ + +opus_int32 + (*const SILK_NSQ_NOISE_SHAPE_FEEDBACK_LOOP_IMPL[OPUS_ARCHMASK+1])( + const opus_int32 *data0, opus_int32 *data1, const opus_int16 *coef, + opus_int order) = { + silk_NSQ_noise_shape_feedback_loop_c, /* ARMv4 */ + silk_NSQ_noise_shape_feedback_loop_c, /* EDSP */ + silk_NSQ_noise_shape_feedback_loop_c, /* Media */ + silk_NSQ_noise_shape_feedback_loop_neon, /* NEON */ +}; + +# endif + +#endif /* OPUS_HAVE_RTCD */ diff --git a/TMessagesProj/jni/opus/silk/arm/macros_arm64.h b/TMessagesProj/jni/opus/silk/arm/macros_arm64.h new file mode 100644 index 00000000000..ed030413c5c --- /dev/null +++ b/TMessagesProj/jni/opus/silk/arm/macros_arm64.h @@ -0,0 +1,39 @@ +/*********************************************************************** +Copyright (C) 2015 Vidyo +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +- Neither the name of Internet Society, IETF or IETF Trust, nor the +names of specific contributors, may be used to endorse or promote +products derived from this software without specific prior written +permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +***********************************************************************/ + +#ifndef SILK_MACROS_ARM64_H +#define SILK_MACROS_ARM64_H + +#include + +#undef silk_ADD_SAT32 +#define silk_ADD_SAT32(a, b) (vqadds_s32((a), (b))) + +#undef silk_SUB_SAT32 +#define silk_SUB_SAT32(a, b) (vqsubs_s32((a), (b))) + +#endif /* SILK_MACROS_ARM64_H */ diff --git a/TMessagesProj/jni/opus/silk/bwexpander.c b/TMessagesProj/jni/opus/silk/bwexpander.c index 2eb4456695e..afa97907ec8 100644 --- a/TMessagesProj/jni/opus/silk/bwexpander.c +++ b/TMessagesProj/jni/opus/silk/bwexpander.c @@ -45,7 +45,7 @@ void silk_bwexpander( /* Bias in silk_SMULWB can lead to unstable filters */ for( i = 0; i < d - 1; i++ ) { ar[ i ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ i ] ), 16 ); - chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); + chirp_Q16 += silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, chirp_minus_one_Q16 ), 16 ); } ar[ d - 1 ] = (opus_int16)silk_RSHIFT_ROUND( silk_MUL( chirp_Q16, ar[ d - 1 ] ), 16 ); } diff --git a/TMessagesProj/jni/opus/silk/code_signs.c b/TMessagesProj/jni/opus/silk/code_signs.c index 0419ea2626a..dfd1dca9a18 100644 --- a/TMessagesProj/jni/opus/silk/code_signs.c +++ b/TMessagesProj/jni/opus/silk/code_signs.c @@ -74,7 +74,7 @@ void silk_encode_signs( /* Decodes signs of excitation */ void silk_decode_signs( ec_dec *psRangeDec, /* I/O Compressor data structure */ - opus_int pulses[], /* I/O pulse signal */ + opus_int16 pulses[], /* I/O pulse signal */ opus_int length, /* I length of input */ const opus_int signalType, /* I Signal type */ const opus_int quantOffsetType, /* I Quantization offset type */ @@ -83,7 +83,7 @@ void silk_decode_signs( { opus_int i, j, p; opus_uint8 icdf[ 2 ]; - opus_int *q_ptr; + opus_int16 *q_ptr; const opus_uint8 *icdf_ptr; icdf[ 1 ] = 0; diff --git a/TMessagesProj/jni/opus/silk/control.h b/TMessagesProj/jni/opus/silk/control.h index 747e5426a0c..b76ec33cd6d 100644 --- a/TMessagesProj/jni/opus/silk/control.h +++ b/TMessagesProj/jni/opus/silk/control.h @@ -77,6 +77,9 @@ typedef struct { /* I: Flag to enable in-band Forward Error Correction (FEC); 0/1 */ opus_int useInBandFEC; + /* I: Flag to actually code in-band Forward Error Correction (FEC) in the current packet; 0/1 */ + opus_int LBRR_coded; + /* I: Flag to enable discontinuous transmission (DTX); 0/1 */ opus_int useDTX; @@ -110,6 +113,11 @@ typedef struct { /* O: Tells the Opus encoder we're ready to switch */ opus_int switchReady; + /* O: SILK Signal type */ + opus_int signalType; + + /* O: SILK offset (dithering) */ + opus_int offset; } silk_EncControlStruct; /**************************************************************************/ diff --git a/TMessagesProj/jni/opus/silk/control_SNR.c b/TMessagesProj/jni/opus/silk/control_SNR.c index f04e69fce24..464c1acfe88 100644 --- a/TMessagesProj/jni/opus/silk/control_SNR.c +++ b/TMessagesProj/jni/opus/silk/control_SNR.c @@ -64,17 +64,11 @@ opus_int silk_control_SNR( /* Find bitrate interval in table and interpolate */ for( k = 1; k < TARGET_RATE_TAB_SZ; k++ ) { if( TargetRate_bps <= rateTable[ k ] ) { - frac_Q6 = silk_DIV32( silk_LSHIFT( TargetRate_bps - rateTable[ k - 1 ], 6 ), - rateTable[ k ] - rateTable[ k - 1 ] ); + frac_Q6 = silk_DIV32( silk_LSHIFT( TargetRate_bps - rateTable[ k - 1 ], 6 ), rateTable[ k ] - rateTable[ k - 1 ] ); psEncC->SNR_dB_Q7 = silk_LSHIFT( silk_SNR_table_Q1[ k - 1 ], 6 ) + silk_MUL( frac_Q6, silk_SNR_table_Q1[ k ] - silk_SNR_table_Q1[ k - 1 ] ); break; } } - - /* Reduce coding quality whenever LBRR is enabled, to free up some bits */ - if( psEncC->LBRR_enabled ) { - psEncC->SNR_dB_Q7 = silk_SMLABB( psEncC->SNR_dB_Q7, 12 - psEncC->LBRR_GainIncreases, SILK_FIX_CONST( -0.25, 7 ) ); - } } return ret; diff --git a/TMessagesProj/jni/opus/silk/control_codec.c b/TMessagesProj/jni/opus/silk/control_codec.c index 1f674bddb64..9350fd4b2b4 100644 --- a/TMessagesProj/jni/opus/silk/control_codec.c +++ b/TMessagesProj/jni/opus/silk/control_codec.c @@ -57,7 +57,7 @@ static opus_int silk_setup_complexity( static OPUS_INLINE opus_int silk_setup_LBRR( silk_encoder_state *psEncC, /* I/O */ - const opus_int32 TargetRate_bps /* I */ + const silk_EncControlStruct *encControl /* I */ ); @@ -65,7 +65,6 @@ static OPUS_INLINE opus_int silk_setup_LBRR( opus_int silk_control_encoder( silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk encoder state */ silk_EncControlStruct *encControl, /* I Control structure */ - const opus_int32 TargetRate_bps, /* I Target max bitrate (bps) */ const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ const opus_int channelNb, /* I Channel number */ const opus_int force_fs_kHz @@ -125,7 +124,7 @@ opus_int silk_control_encoder( /********************************************/ /* Set LBRR usage */ /********************************************/ - ret += silk_setup_LBRR( &psEnc->sCmn, TargetRate_bps ); + ret += silk_setup_LBRR( &psEnc->sCmn, encControl ); psEnc->sCmn.controlled_since_last_payload = 1; @@ -244,7 +243,6 @@ static opus_int silk_setup_fs( if( psEnc->sCmn.fs_kHz != fs_kHz ) { /* reset part of the state */ silk_memset( &psEnc->sShape, 0, sizeof( psEnc->sShape ) ); - silk_memset( &psEnc->sPrefilt, 0, sizeof( psEnc->sPrefilt ) ); silk_memset( &psEnc->sCmn.sNSQ, 0, sizeof( psEnc->sCmn.sNSQ ) ); silk_memset( psEnc->sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); silk_memset( &psEnc->sCmn.sLP.In_LP_State, 0, sizeof( psEnc->sCmn.sLP.In_LP_State ) ); @@ -255,7 +253,6 @@ static opus_int silk_setup_fs( /* Initialize non-zero parameters */ psEnc->sCmn.prevLag = 100; psEnc->sCmn.first_frame_after_reset = 1; - psEnc->sPrefilt.lagPrev = 100; psEnc->sShape.LastGainIndex = 10; psEnc->sCmn.sNSQ.lagPrev = 100; psEnc->sCmn.sNSQ.prev_gain_Q16 = 65536; @@ -293,13 +290,10 @@ static opus_int silk_setup_fs( psEnc->sCmn.pitch_LPC_win_length = silk_SMULBB( FIND_PITCH_LPC_WIN_MS_2_SF, fs_kHz ); } if( psEnc->sCmn.fs_kHz == 16 ) { - psEnc->sCmn.mu_LTP_Q9 = SILK_FIX_CONST( MU_LTP_QUANT_WB, 9 ); psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform8_iCDF; } else if( psEnc->sCmn.fs_kHz == 12 ) { - psEnc->sCmn.mu_LTP_Q9 = SILK_FIX_CONST( MU_LTP_QUANT_MB, 9 ); psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform6_iCDF; } else { - psEnc->sCmn.mu_LTP_Q9 = SILK_FIX_CONST( MU_LTP_QUANT_NB, 9 ); psEnc->sCmn.pitch_lag_low_bits_iCDF = silk_uniform4_iCDF; } } @@ -319,60 +313,75 @@ static opus_int silk_setup_complexity( /* Set encoding complexity */ silk_assert( Complexity >= 0 && Complexity <= 10 ); - if( Complexity < 2 ) { + if( Complexity < 1 ) { psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); psEncC->pitchEstimationLPCOrder = 6; - psEncC->shapingLPCOrder = 8; + psEncC->shapingLPCOrder = 12; psEncC->la_shape = 3 * psEncC->fs_kHz; psEncC->nStatesDelayedDecision = 1; psEncC->useInterpolatedNLSFs = 0; - psEncC->LTPQuantLowComplexity = 1; psEncC->NLSF_MSVQ_Survivors = 2; psEncC->warping_Q16 = 0; - } else if( Complexity < 4 ) { + } else if( Complexity < 2 ) { psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); psEncC->pitchEstimationLPCOrder = 8; - psEncC->shapingLPCOrder = 10; + psEncC->shapingLPCOrder = 14; psEncC->la_shape = 5 * psEncC->fs_kHz; psEncC->nStatesDelayedDecision = 1; psEncC->useInterpolatedNLSFs = 0; - psEncC->LTPQuantLowComplexity = 0; + psEncC->NLSF_MSVQ_Survivors = 3; + psEncC->warping_Q16 = 0; + } else if( Complexity < 3 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MIN_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.8, 16 ); + psEncC->pitchEstimationLPCOrder = 6; + psEncC->shapingLPCOrder = 12; + psEncC->la_shape = 3 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; + psEncC->NLSF_MSVQ_Survivors = 2; + psEncC->warping_Q16 = 0; + } else if( Complexity < 4 ) { + psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; + psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.76, 16 ); + psEncC->pitchEstimationLPCOrder = 8; + psEncC->shapingLPCOrder = 14; + psEncC->la_shape = 5 * psEncC->fs_kHz; + psEncC->nStatesDelayedDecision = 2; + psEncC->useInterpolatedNLSFs = 0; psEncC->NLSF_MSVQ_Survivors = 4; psEncC->warping_Q16 = 0; } else if( Complexity < 6 ) { psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.74, 16 ); psEncC->pitchEstimationLPCOrder = 10; - psEncC->shapingLPCOrder = 12; + psEncC->shapingLPCOrder = 16; psEncC->la_shape = 5 * psEncC->fs_kHz; psEncC->nStatesDelayedDecision = 2; psEncC->useInterpolatedNLSFs = 1; - psEncC->LTPQuantLowComplexity = 0; - psEncC->NLSF_MSVQ_Survivors = 8; + psEncC->NLSF_MSVQ_Survivors = 6; psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); } else if( Complexity < 8 ) { psEncC->pitchEstimationComplexity = SILK_PE_MID_COMPLEX; psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.72, 16 ); psEncC->pitchEstimationLPCOrder = 12; - psEncC->shapingLPCOrder = 14; + psEncC->shapingLPCOrder = 20; psEncC->la_shape = 5 * psEncC->fs_kHz; psEncC->nStatesDelayedDecision = 3; psEncC->useInterpolatedNLSFs = 1; - psEncC->LTPQuantLowComplexity = 0; - psEncC->NLSF_MSVQ_Survivors = 16; + psEncC->NLSF_MSVQ_Survivors = 8; psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); } else { psEncC->pitchEstimationComplexity = SILK_PE_MAX_COMPLEX; psEncC->pitchEstimationThreshold_Q16 = SILK_FIX_CONST( 0.7, 16 ); psEncC->pitchEstimationLPCOrder = 16; - psEncC->shapingLPCOrder = 16; + psEncC->shapingLPCOrder = 24; psEncC->la_shape = 5 * psEncC->fs_kHz; psEncC->nStatesDelayedDecision = MAX_DEL_DEC_STATES; psEncC->useInterpolatedNLSFs = 1; - psEncC->LTPQuantLowComplexity = 0; - psEncC->NLSF_MSVQ_Survivors = 32; + psEncC->NLSF_MSVQ_Survivors = 16; psEncC->warping_Q16 = psEncC->fs_kHz * SILK_FIX_CONST( WARPING_MULTIPLIER, 16 ); } @@ -387,33 +396,25 @@ static opus_int silk_setup_complexity( silk_assert( psEncC->warping_Q16 <= 32767 ); silk_assert( psEncC->la_shape <= LA_SHAPE_MAX ); silk_assert( psEncC->shapeWinLength <= SHAPE_LPC_WIN_MAX ); - silk_assert( psEncC->NLSF_MSVQ_Survivors <= NLSF_VQ_MAX_SURVIVORS ); return ret; } static OPUS_INLINE opus_int silk_setup_LBRR( silk_encoder_state *psEncC, /* I/O */ - const opus_int32 TargetRate_bps /* I */ + const silk_EncControlStruct *encControl /* I */ ) { - opus_int ret = SILK_NO_ERROR; - opus_int32 LBRR_rate_thres_bps; - - psEncC->LBRR_enabled = 0; - if( psEncC->useInBandFEC && psEncC->PacketLoss_perc > 0 ) { - if( psEncC->fs_kHz == 8 ) { - LBRR_rate_thres_bps = LBRR_NB_MIN_RATE_BPS; - } else if( psEncC->fs_kHz == 12 ) { - LBRR_rate_thres_bps = LBRR_MB_MIN_RATE_BPS; + opus_int LBRR_in_previous_packet, ret = SILK_NO_ERROR; + + LBRR_in_previous_packet = psEncC->LBRR_enabled; + psEncC->LBRR_enabled = encControl->LBRR_coded; + if( psEncC->LBRR_enabled ) { + /* Set gain increase for coding LBRR excitation */ + if( LBRR_in_previous_packet == 0 ) { + /* Previous packet did not have LBRR, and was therefore coded at a higher bitrate */ + psEncC->LBRR_GainIncreases = 7; } else { - LBRR_rate_thres_bps = LBRR_WB_MIN_RATE_BPS; - } - LBRR_rate_thres_bps = silk_SMULWB( silk_MUL( LBRR_rate_thres_bps, 125 - silk_min( psEncC->PacketLoss_perc, 25 ) ), SILK_FIX_CONST( 0.01, 16 ) ); - - if( TargetRate_bps > LBRR_rate_thres_bps ) { - /* Set gain increase for coding LBRR excitation */ - psEncC->LBRR_enabled = 1; psEncC->LBRR_GainIncreases = silk_max_int( 7 - silk_SMULWB( (opus_int32)psEncC->PacketLoss_perc, SILK_FIX_CONST( 0.4, 16 ) ), 2 ); } } diff --git a/TMessagesProj/jni/opus/silk/dec_API.c b/TMessagesProj/jni/opus/silk/dec_API.c index 4cbcf71514d..b7d8ed48d88 100644 --- a/TMessagesProj/jni/opus/silk/dec_API.c +++ b/TMessagesProj/jni/opus/silk/dec_API.c @@ -31,6 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "API.h" #include "main.h" #include "stack_alloc.h" +#include "os_support.h" /************************/ /* Decoder Super Struct */ @@ -84,13 +85,15 @@ opus_int silk_Decode( /* O Returns error co opus_int newPacketFlag, /* I Indicates first decoder call for this packet */ ec_dec *psRangeDec, /* I/O Compressor data structure */ opus_int16 *samplesOut, /* O Decoded output speech vector */ - opus_int32 *nSamplesOut /* O Number of samples decoded */ + opus_int32 *nSamplesOut, /* O Number of samples decoded */ + int arch /* I Run-time architecture */ ) { opus_int i, n, decode_only_middle = 0, ret = SILK_NO_ERROR; opus_int32 nSamplesOutDec, LBRR_symbol; opus_int16 *samplesOut1_tmp[ 2 ]; - VARDECL( opus_int16, samplesOut1_tmp_storage ); + VARDECL( opus_int16, samplesOut1_tmp_storage1 ); + VARDECL( opus_int16, samplesOut1_tmp_storage2 ); VARDECL( opus_int16, samplesOut2_tmp ); opus_int32 MS_pred_Q13[ 2 ] = { 0 }; opus_int16 *resample_out_ptr; @@ -98,6 +101,7 @@ opus_int silk_Decode( /* O Returns error co silk_decoder_state *channel_state = psDec->channel_state; opus_int has_side; opus_int stereo_to_mono; + int delay_stack_alloc; SAVE_STACK; silk_assert( decControl->nChannelsInternal == 1 || decControl->nChannelsInternal == 2 ); @@ -196,7 +200,7 @@ opus_int silk_Decode( /* O Returns error co for( i = 0; i < channel_state[ 0 ].nFramesPerPacket; i++ ) { for( n = 0; n < decControl->nChannelsInternal; n++ ) { if( channel_state[ n ].LBRR_flags[ i ] ) { - opus_int pulses[ MAX_FRAME_LENGTH ]; + opus_int16 pulses[ MAX_FRAME_LENGTH ]; opus_int condCoding; if( decControl->nChannelsInternal == 2 && n == 0 ) { @@ -251,13 +255,22 @@ opus_int silk_Decode( /* O Returns error co psDec->channel_state[ 1 ].first_frame_after_reset = 1; } - ALLOC( samplesOut1_tmp_storage, - decControl->nChannelsInternal*( - channel_state[ 0 ].frame_length + 2 ), + /* Check if the temp buffer fits into the output PCM buffer. If it fits, + we can delay allocating the temp buffer until after the SILK peak stack + usage. We need to use a < and not a <= because of the two extra samples. */ + delay_stack_alloc = decControl->internalSampleRate*decControl->nChannelsInternal + < decControl->API_sampleRate*decControl->nChannelsAPI; + ALLOC( samplesOut1_tmp_storage1, delay_stack_alloc ? ALLOC_NONE + : decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ), opus_int16 ); - samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage; - samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage - + channel_state[ 0 ].frame_length + 2; + if ( delay_stack_alloc ) + { + samplesOut1_tmp[ 0 ] = samplesOut; + samplesOut1_tmp[ 1 ] = samplesOut + channel_state[ 0 ].frame_length + 2; + } else { + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage1; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage1 + channel_state[ 0 ].frame_length + 2; + } if( lostFlag == FLAG_DECODE_NORMAL ) { has_side = !decode_only_middle; @@ -284,7 +297,7 @@ opus_int silk_Decode( /* O Returns error co } else { condCoding = CODE_CONDITIONALLY; } - ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding); + ret += silk_decode_frame( &channel_state[ n ], psRangeDec, &samplesOut1_tmp[ n ][ 2 ], &nSamplesOutDec, lostFlag, condCoding, arch); } else { silk_memset( &samplesOut1_tmp[ n ][ 2 ], 0, nSamplesOutDec * sizeof( opus_int16 ) ); } @@ -312,6 +325,15 @@ opus_int silk_Decode( /* O Returns error co resample_out_ptr = samplesOut; } + ALLOC( samplesOut1_tmp_storage2, delay_stack_alloc + ? decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2 ) + : ALLOC_NONE, + opus_int16 ); + if ( delay_stack_alloc ) { + OPUS_COPY(samplesOut1_tmp_storage2, samplesOut, decControl->nChannelsInternal*(channel_state[ 0 ].frame_length + 2)); + samplesOut1_tmp[ 0 ] = samplesOut1_tmp_storage2; + samplesOut1_tmp[ 1 ] = samplesOut1_tmp_storage2 + channel_state[ 0 ].frame_length + 2; + } for( n = 0; n < silk_min( decControl->nChannelsAPI, decControl->nChannelsInternal ); n++ ) { /* Resample decoded signal to API_sampleRate */ diff --git a/TMessagesProj/jni/opus/silk/decode_core.c b/TMessagesProj/jni/opus/silk/decode_core.c index a820bf11d6a..e569c0e72b3 100644 --- a/TMessagesProj/jni/opus/silk/decode_core.c +++ b/TMessagesProj/jni/opus/silk/decode_core.c @@ -39,7 +39,8 @@ void silk_decode_core( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I Decoder control */ opus_int16 xq[], /* O Decoded speech */ - const opus_int pulses[ MAX_FRAME_LENGTH ] /* I Pulse signal */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ ) { opus_int i, k, lag = 0, start_idx, sLTP_buf_idx, NLSF_interpolation_flag, signalType; @@ -147,7 +148,7 @@ void silk_decode_core( } silk_LPC_analysis_filter( &sLTP[ start_idx ], &psDec->outBuf[ start_idx + k * psDec->subfr_length ], - A_Q12, psDec->ltp_mem_length - start_idx, psDec->LPC_order ); + A_Q12, psDec->ltp_mem_length - start_idx, psDec->LPC_order, arch ); /* After rewhitening the LTP state is unscaled */ if( k == 0 ) { @@ -218,7 +219,7 @@ void silk_decode_core( } /* Add prediction to LPC excitation */ - sLPC_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_LSHIFT32( pres_Q14[ i ], LPC_pred_Q10, 4 ); + sLPC_Q14[ MAX_LPC_ORDER + i ] = silk_ADD_SAT32( pres_Q14[ i ], silk_LSHIFT_SAT32( LPC_pred_Q10, 4 ) ); /* Scale with gain */ pxq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( sLPC_Q14[ MAX_LPC_ORDER + i ], Gain_Q10 ), 8 ) ); diff --git a/TMessagesProj/jni/opus/silk/decode_frame.c b/TMessagesProj/jni/opus/silk/decode_frame.c index abc00a3d545..dfa73c4a296 100644 --- a/TMessagesProj/jni/opus/silk/decode_frame.c +++ b/TMessagesProj/jni/opus/silk/decode_frame.c @@ -42,18 +42,16 @@ opus_int silk_decode_frame( opus_int16 pOut[], /* O Pointer to output speech frame */ opus_int32 *pN, /* O Pointer to size of output frame */ opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ - opus_int condCoding /* I The type of conditional coding to use */ + opus_int condCoding, /* I The type of conditional coding to use */ + int arch /* I Run-time architecture */ ) { VARDECL( silk_decoder_control, psDecCtrl ); opus_int L, mv_len, ret = 0; - VARDECL( opus_int, pulses ); SAVE_STACK; L = psDec->frame_length; ALLOC( psDecCtrl, 1, silk_decoder_control ); - ALLOC( pulses, (L + SHELL_CODEC_FRAME_LENGTH - 1) & - ~(SHELL_CODEC_FRAME_LENGTH - 1), opus_int ); psDecCtrl->LTP_scale_Q14 = 0; /* Safety checks */ @@ -62,6 +60,9 @@ opus_int silk_decode_frame( if( lostFlag == FLAG_DECODE_NORMAL || ( lostFlag == FLAG_DECODE_LBRR && psDec->LBRR_flags[ psDec->nFramesDecoded ] == 1 ) ) { + VARDECL( opus_int16, pulses ); + ALLOC( pulses, (L + SHELL_CODEC_FRAME_LENGTH - 1) & + ~(SHELL_CODEC_FRAME_LENGTH - 1), opus_int16 ); /*********************************************/ /* Decode quantization indices of side info */ /*********************************************/ @@ -81,12 +82,12 @@ opus_int silk_decode_frame( /********************************************************/ /* Run inverse NSQ */ /********************************************************/ - silk_decode_core( psDec, psDecCtrl, pOut, pulses ); + silk_decode_core( psDec, psDecCtrl, pOut, pulses, arch ); /********************************************************/ /* Update PLC state */ /********************************************************/ - silk_PLC( psDec, psDecCtrl, pOut, 0 ); + silk_PLC( psDec, psDecCtrl, pOut, 0, arch ); psDec->lossCnt = 0; psDec->prevSignalType = psDec->indices.signalType; @@ -96,7 +97,8 @@ opus_int silk_decode_frame( psDec->first_frame_after_reset = 0; } else { /* Handle packet loss by extrapolation */ - silk_PLC( psDec, psDecCtrl, pOut, 1 ); + psDec->indices.signalType = psDec->prevSignalType; + silk_PLC( psDec, psDecCtrl, pOut, 1, arch ); } /*************************/ @@ -107,16 +109,16 @@ opus_int silk_decode_frame( silk_memmove( psDec->outBuf, &psDec->outBuf[ psDec->frame_length ], mv_len * sizeof(opus_int16) ); silk_memcpy( &psDec->outBuf[ mv_len ], pOut, psDec->frame_length * sizeof( opus_int16 ) ); - /****************************************************************/ - /* Ensure smooth connection of extrapolated and good frames */ - /****************************************************************/ - silk_PLC_glue_frames( psDec, pOut, L ); - /************************************************/ /* Comfort noise generation / estimation */ /************************************************/ silk_CNG( psDec, psDecCtrl, pOut, L ); + /****************************************************************/ + /* Ensure smooth connection of extrapolated and good frames */ + /****************************************************************/ + silk_PLC_glue_frames( psDec, pOut, L ); + /* Update some decoder state variables */ psDec->lagPrev = psDecCtrl->pitchL[ psDec->nb_subfr - 1 ]; diff --git a/TMessagesProj/jni/opus/silk/decode_pulses.c b/TMessagesProj/jni/opus/silk/decode_pulses.c index e8a87c2ab75..d6bbec92252 100644 --- a/TMessagesProj/jni/opus/silk/decode_pulses.c +++ b/TMessagesProj/jni/opus/silk/decode_pulses.c @@ -36,7 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. /*********************************************/ void silk_decode_pulses( ec_dec *psRangeDec, /* I/O Compressor data structure */ - opus_int pulses[], /* O Excitation signal */ + opus_int16 pulses[], /* O Excitation signal */ const opus_int signalType, /* I Sigtype */ const opus_int quantOffsetType, /* I quantOffsetType */ const opus_int frame_length /* I Frame length */ @@ -44,7 +44,7 @@ void silk_decode_pulses( { opus_int i, j, k, iter, abs_q, nLS, RateLevelIndex; opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ], nLshifts[ MAX_NB_SHELL_BLOCKS ]; - opus_int *pulses_ptr; + opus_int16 *pulses_ptr; const opus_uint8 *cdf_ptr; /*********************/ @@ -69,9 +69,9 @@ void silk_decode_pulses( sum_pulses[ i ] = ec_dec_icdf( psRangeDec, cdf_ptr, 8 ); /* LSB indication */ - while( sum_pulses[ i ] == MAX_PULSES + 1 ) { + while( sum_pulses[ i ] == SILK_MAX_PULSES + 1 ) { nLshifts[ i ]++; - /* When we've already got 10 LSBs, we shift the table to not allow (MAX_PULSES + 1) */ + /* When we've already got 10 LSBs, we shift the table to not allow (SILK_MAX_PULSES + 1) */ sum_pulses[ i ] = ec_dec_icdf( psRangeDec, silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1] + ( nLshifts[ i ] == 10 ), 8 ); } @@ -84,7 +84,7 @@ void silk_decode_pulses( if( sum_pulses[ i ] > 0 ) { silk_shell_decoder( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], psRangeDec, sum_pulses[ i ] ); } else { - silk_memset( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof( opus_int ) ); + silk_memset( &pulses[ silk_SMULBB( i, SHELL_CODEC_FRAME_LENGTH ) ], 0, SHELL_CODEC_FRAME_LENGTH * sizeof( pulses[0] ) ); } } diff --git a/TMessagesProj/jni/opus/silk/define.h b/TMessagesProj/jni/opus/silk/define.h index c47aca9f58f..781cfdccf22 100644 --- a/TMessagesProj/jni/opus/silk/define.h +++ b/TMessagesProj/jni/opus/silk/define.h @@ -56,6 +56,7 @@ extern "C" /* DTX settings */ #define NB_SPEECH_FRAMES_BEFORE_DTX 10 /* eq 200 ms */ #define MAX_CONSECUTIVE_DTX 20 /* eq 400 ms */ +#define DTX_ACTIVITY_THRESHOLD 0.1f /* Maximum sampling frequency */ #define MAX_FS_KHZ 16 @@ -147,7 +148,7 @@ extern "C" #define USE_HARM_SHAPING 1 /* Max LPC order of noise shaping filters */ -#define MAX_SHAPE_LPC_ORDER 16 +#define MAX_SHAPE_LPC_ORDER 24 #define HARM_SHAPE_FIR_TAPS 3 @@ -157,8 +158,7 @@ extern "C" #define LTP_BUF_LENGTH 512 #define LTP_MASK ( LTP_BUF_LENGTH - 1 ) -#define DECISION_DELAY 32 -#define DECISION_DELAY_MASK ( DECISION_DELAY - 1 ) +#define DECISION_DELAY 40 /* Number of subframes for excitation entropy coding */ #define SHELL_CODEC_FRAME_LENGTH 16 @@ -169,7 +169,7 @@ extern "C" #define N_RATE_LEVELS 10 /* Maximum sum of pulses per shell coding frame */ -#define MAX_PULSES 16 +#define SILK_MAX_PULSES 16 #define MAX_MATRIX_SIZE MAX_LPC_ORDER /* Max of LPC Order and LTP order */ @@ -205,7 +205,6 @@ extern "C" /******************/ #define NLSF_W_Q 2 #define NLSF_VQ_MAX_VECTORS 32 -#define NLSF_VQ_MAX_SURVIVORS 32 #define NLSF_QUANT_MAX_AMPLITUDE 4 #define NLSF_QUANT_MAX_AMPLITUDE_EXT 10 #define NLSF_QUANT_LEVEL_ADJ 0.1 diff --git a/TMessagesProj/jni/opus/silk/enc_API.c b/TMessagesProj/jni/opus/silk/enc_API.c index 43739efc24e..ba3db06044b 100644 --- a/TMessagesProj/jni/opus/silk/enc_API.c +++ b/TMessagesProj/jni/opus/silk/enc_API.c @@ -165,7 +165,7 @@ opus_int silk_Encode( /* O Returns error co psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded = psEnc->state_Fxx[ 1 ].sCmn.nFramesEncoded = 0; /* Check values in encoder control structure */ - if( ( ret = check_control_input( encControl ) != 0 ) ) { + if( ( ret = check_control_input( encControl ) ) != 0 ) { silk_assert( 0 ); RESTORE_STACK; return ret; @@ -237,7 +237,7 @@ opus_int silk_Encode( /* O Returns error co for( n = 0; n < encControl->nChannelsInternal; n++ ) { /* Force the side channel to the same rate as the mid */ opus_int force_fs_kHz = (n==1) ? psEnc->state_Fxx[0].sCmn.fs_kHz : 0; - if( ( ret = silk_control_encoder( &psEnc->state_Fxx[ n ], encControl, TargetRate_bps, psEnc->allowBandwidthSwitch, n, force_fs_kHz ) ) != 0 ) { + if( ( ret = silk_control_encoder( &psEnc->state_Fxx[ n ], encControl, psEnc->allowBandwidthSwitch, n, force_fs_kHz ) ) != 0 ) { silk_assert( 0 ); RESTORE_STACK; return ret; @@ -376,26 +376,33 @@ opus_int silk_Encode( /* O Returns error co for( n = 0; n < encControl->nChannelsInternal; n++ ) { silk_memset( psEnc->state_Fxx[ n ].sCmn.LBRR_flags, 0, sizeof( psEnc->state_Fxx[ n ].sCmn.LBRR_flags ) ); } + + psEnc->nBitsUsedLBRR = ec_tell( psRangeEnc ); } silk_HP_variable_cutoff( psEnc->state_Fxx ); /* Total target bits for packet */ nBits = silk_DIV32_16( silk_MUL( encControl->bitRate, encControl->payloadSize_ms ), 1000 ); - /* Subtract half of the bits already used */ + /* Subtract bits used for LBRR */ if( !prefillFlag ) { - nBits -= ec_tell( psRangeEnc ) >> 1; + nBits -= psEnc->nBitsUsedLBRR; } /* Divide by number of uncoded frames left in packet */ - nBits = silk_DIV32_16( nBits, psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket - psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded ); + nBits = silk_DIV32_16( nBits, psEnc->state_Fxx[ 0 ].sCmn.nFramesPerPacket ); /* Convert to bits/second */ if( encControl->payloadSize_ms == 10 ) { TargetRate_bps = silk_SMULBB( nBits, 100 ); } else { TargetRate_bps = silk_SMULBB( nBits, 50 ); } - /* Subtract fraction of bits in excess of target in previous packets */ + /* Subtract fraction of bits in excess of target in previous frames and packets */ TargetRate_bps -= silk_DIV32_16( silk_MUL( psEnc->nBitsExceeded, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + if( !prefillFlag && psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded > 0 ) { + /* Compare actual vs target bits so far in this packet */ + opus_int32 bitsBalance = ec_tell( psRangeEnc ) - psEnc->nBitsUsedLBRR - nBits * psEnc->state_Fxx[ 0 ].sCmn.nFramesEncoded; + TargetRate_bps -= silk_DIV32_16( silk_MUL( bitsBalance, 1000 ), BITRESERVOIR_DECAY_TIME_MS ); + } /* Never exceed input bitrate */ TargetRate_bps = silk_LIMIT( TargetRate_bps, encControl->bitRate, 5000 ); @@ -409,7 +416,6 @@ opus_int silk_Encode( /* O Returns error co /* Reset side channel encoder memory for first frame with side coding */ if( psEnc->prev_decode_only_middle == 1 ) { silk_memset( &psEnc->state_Fxx[ 1 ].sShape, 0, sizeof( psEnc->state_Fxx[ 1 ].sShape ) ); - silk_memset( &psEnc->state_Fxx[ 1 ].sPrefilt, 0, sizeof( psEnc->state_Fxx[ 1 ].sPrefilt ) ); silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sNSQ, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sNSQ ) ); silk_memset( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.prev_NLSFq_Q15 ) ); silk_memset( &psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State, 0, sizeof( psEnc->state_Fxx[ 1 ].sCmn.sLP.In_LP_State ) ); @@ -550,6 +556,10 @@ opus_int silk_Encode( /* O Returns error co } } + encControl->signalType = psEnc->state_Fxx[0].sCmn.indices.signalType; + encControl->offset = silk_Quantization_Offsets_Q10 + [ psEnc->state_Fxx[0].sCmn.indices.signalType >> 1 ] + [ psEnc->state_Fxx[0].sCmn.indices.quantOffsetType ]; RESTORE_STACK; return ret; } diff --git a/TMessagesProj/jni/opus/silk/encode_pulses.c b/TMessagesProj/jni/opus/silk/encode_pulses.c index a4501438d12..ab00264f991 100644 --- a/TMessagesProj/jni/opus/silk/encode_pulses.c +++ b/TMessagesProj/jni/opus/silk/encode_pulses.c @@ -142,7 +142,7 @@ void silk_encode_pulses( sumBits_Q5 = silk_rate_levels_BITS_Q5[ signalType >> 1 ][ k ]; for( i = 0; i < iter; i++ ) { if( nRshifts[ i ] > 0 ) { - sumBits_Q5 += nBits_ptr[ MAX_PULSES + 1 ]; + sumBits_Q5 += nBits_ptr[ SILK_MAX_PULSES + 1 ]; } else { sumBits_Q5 += nBits_ptr[ sum_pulses[ i ] ]; } @@ -162,9 +162,9 @@ void silk_encode_pulses( if( nRshifts[ i ] == 0 ) { ec_enc_icdf( psRangeEnc, sum_pulses[ i ], cdf_ptr, 8 ); } else { - ec_enc_icdf( psRangeEnc, MAX_PULSES + 1, cdf_ptr, 8 ); + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, cdf_ptr, 8 ); for( k = 0; k < nRshifts[ i ] - 1; k++ ) { - ec_enc_icdf( psRangeEnc, MAX_PULSES + 1, silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); + ec_enc_icdf( psRangeEnc, SILK_MAX_PULSES + 1, silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); } ec_enc_icdf( psRangeEnc, sum_pulses[ i ], silk_pulses_per_block_iCDF[ N_RATE_LEVELS - 1 ], 8 ); } diff --git a/TMessagesProj/jni/opus/silk/fixed/LTP_analysis_filter_FIX.c b/TMessagesProj/jni/opus/silk/fixed/LTP_analysis_filter_FIX.c index a94190808e9..5574e7069fe 100644 --- a/TMessagesProj/jni/opus/silk/fixed/LTP_analysis_filter_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/LTP_analysis_filter_FIX.c @@ -45,7 +45,7 @@ void silk_LTP_analysis_filter_FIX( const opus_int16 *x_ptr, *x_lag_ptr; opus_int16 Btmp_Q14[ LTP_ORDER ]; opus_int16 *LTP_res_ptr; - opus_int k, i, j; + opus_int k, i; opus_int32 LTP_est; x_ptr = x; @@ -53,9 +53,12 @@ void silk_LTP_analysis_filter_FIX( for( k = 0; k < nb_subfr; k++ ) { x_lag_ptr = x_ptr - pitchL[ k ]; - for( i = 0; i < LTP_ORDER; i++ ) { - Btmp_Q14[ i ] = LTPCoef_Q14[ k * LTP_ORDER + i ]; - } + + Btmp_Q14[ 0 ] = LTPCoef_Q14[ k * LTP_ORDER ]; + Btmp_Q14[ 1 ] = LTPCoef_Q14[ k * LTP_ORDER + 1 ]; + Btmp_Q14[ 2 ] = LTPCoef_Q14[ k * LTP_ORDER + 2 ]; + Btmp_Q14[ 3 ] = LTPCoef_Q14[ k * LTP_ORDER + 3 ]; + Btmp_Q14[ 4 ] = LTPCoef_Q14[ k * LTP_ORDER + 4 ]; /* LTP analysis FIR filter */ for( i = 0; i < subfr_length + pre_length; i++ ) { @@ -63,9 +66,11 @@ void silk_LTP_analysis_filter_FIX( /* Long-term prediction */ LTP_est = silk_SMULBB( x_lag_ptr[ LTP_ORDER / 2 ], Btmp_Q14[ 0 ] ); - for( j = 1; j < LTP_ORDER; j++ ) { - LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ LTP_ORDER / 2 - j ], Btmp_Q14[ j ] ); - } + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ 1 ], Btmp_Q14[ 1 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ 0 ], Btmp_Q14[ 2 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ -1 ], Btmp_Q14[ 3 ] ); + LTP_est = silk_SMLABB_ovflw( LTP_est, x_lag_ptr[ -2 ], Btmp_Q14[ 4 ] ); + LTP_est = silk_RSHIFT_ROUND( LTP_est, 14 ); /* round and -> Q0*/ /* Subtract long-term prediction */ diff --git a/TMessagesProj/jni/opus/silk/fixed/burg_modified_FIX.c b/TMessagesProj/jni/opus/silk/fixed/burg_modified_FIX.c index db348295bf8..80edbdfa8a6 100644 --- a/TMessagesProj/jni/opus/silk/fixed/burg_modified_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/burg_modified_FIX.c @@ -37,12 +37,12 @@ POSSIBILITY OF SUCH DAMAGE. #define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384 */ #define QA 25 -#define N_BITS_HEAD_ROOM 2 +#define N_BITS_HEAD_ROOM 3 #define MIN_RSHIFTS -16 #define MAX_RSHIFTS (32 - QA) /* Compute reflection coefficients from input signal */ -void silk_burg_modified( +void silk_burg_modified_c( opus_int32 *res_nrg, /* O Residual energy */ opus_int *res_nrg_Q, /* O Residual energy Q value */ opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ @@ -54,7 +54,7 @@ void silk_burg_modified( int arch /* I Run-time architecture */ ) { - opus_int k, n, s, lz, rshifts, rshifts_extra, reached_max_gain; + opus_int k, n, s, lz, rshifts, reached_max_gain; opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2; const opus_int16 *x_ptr; opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; @@ -63,27 +63,23 @@ void silk_burg_modified( opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; + opus_int64 C0_64; silk_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); /* Compute autocorrelations, added over subframes */ - silk_sum_sqr_shift( &C0, &rshifts, x, nb_subfr * subfr_length ); - if( rshifts > MAX_RSHIFTS ) { - C0 = silk_LSHIFT32( C0, rshifts - MAX_RSHIFTS ); - silk_assert( C0 > 0 ); - rshifts = MAX_RSHIFTS; + C0_64 = silk_inner_prod16_aligned_64( x, x, subfr_length*nb_subfr, arch ); + lz = silk_CLZ64(C0_64); + rshifts = 32 + 1 + N_BITS_HEAD_ROOM - lz; + if (rshifts > MAX_RSHIFTS) rshifts = MAX_RSHIFTS; + if (rshifts < MIN_RSHIFTS) rshifts = MIN_RSHIFTS; + + if (rshifts > 0) { + C0 = (opus_int32)silk_RSHIFT64(C0_64, rshifts ); } else { - lz = silk_CLZ32( C0 ) - 1; - rshifts_extra = N_BITS_HEAD_ROOM - lz; - if( rshifts_extra > 0 ) { - rshifts_extra = silk_min( rshifts_extra, MAX_RSHIFTS - rshifts ); - C0 = silk_RSHIFT32( C0, rshifts_extra ); - } else { - rshifts_extra = silk_max( rshifts_extra, MIN_RSHIFTS - rshifts ); - C0 = silk_LSHIFT32( C0, -rshifts_extra ); - } - rshifts += rshifts_extra; + C0 = silk_LSHIFT32((opus_int32)C0_64, -rshifts ); } + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); if( rshifts > 0 ) { @@ -91,7 +87,7 @@ void silk_burg_modified( x_ptr = x + s * subfr_length; for( n = 1; n < D + 1; n++ ) { C_first_row[ n - 1 ] += (opus_int32)silk_RSHIFT64( - silk_inner_prod16_aligned_64( x_ptr, x_ptr + n, subfr_length - n ), rshifts ); + silk_inner_prod16_aligned_64( x_ptr, x_ptr + n, subfr_length - n, arch ), rshifts ); } } } else { @@ -154,8 +150,11 @@ void silk_burg_modified( C_first_row[ k ] = silk_MLA( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ C_last_row[ k ] = silk_MLA( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 17 ); /* Q17 */ - tmp1 = silk_MLA( tmp1, x_ptr[ n - k - 1 ], Atmp1 ); /* Q17 */ - tmp2 = silk_MLA( tmp2, x_ptr[ subfr_length - n + k ], Atmp1 ); /* Q17 */ + /* We sometimes have get overflows in the multiplications (even beyond +/- 2^32), + but they cancel each other and the real result seems to always fit in a 32-bit + signed integer. This was determined experimentally, not theoretically (unfortunately). */ + tmp1 = silk_MLA_ovflw( tmp1, x_ptr[ n - k - 1 ], Atmp1 ); /* Q17 */ + tmp2 = silk_MLA_ovflw( tmp2, x_ptr[ subfr_length - n + k ], Atmp1 ); /* Q17 */ } tmp1 = -tmp1; /* Q17 */ tmp2 = -tmp2; /* Q17 */ @@ -204,12 +203,14 @@ void silk_burg_modified( /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ tmp2 = ( (opus_int32)1 << 30 ) - silk_DIV32_varQ( minInvGain_Q30, invGain_Q30, 30 ); /* Q30 */ rc_Q31 = silk_SQRT_APPROX( tmp2 ); /* Q15 */ - /* Newton-Raphson iteration */ - rc_Q31 = silk_RSHIFT32( rc_Q31 + silk_DIV32( tmp2, rc_Q31 ), 1 ); /* Q15 */ - rc_Q31 = silk_LSHIFT32( rc_Q31, 16 ); /* Q31 */ - if( num < 0 ) { - /* Ensure adjusted reflection coefficients has the original sign */ - rc_Q31 = -rc_Q31; + if( rc_Q31 > 0 ) { + /* Newton-Raphson iteration */ + rc_Q31 = silk_RSHIFT32( rc_Q31 + silk_DIV32( tmp2, rc_Q31 ), 1 ); /* Q15 */ + rc_Q31 = silk_LSHIFT32( rc_Q31, 16 ); /* Q31 */ + if( num < 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc_Q31 = -rc_Q31; + } } invGain_Q30 = minInvGain_Q30; reached_max_gain = 1; @@ -252,12 +253,12 @@ void silk_burg_modified( if( rshifts > 0 ) { for( s = 0; s < nb_subfr; s++ ) { x_ptr = x + s * subfr_length; - C0 -= (opus_int32)silk_RSHIFT64( silk_inner_prod16_aligned_64( x_ptr, x_ptr, D ), rshifts ); + C0 -= (opus_int32)silk_RSHIFT64( silk_inner_prod16_aligned_64( x_ptr, x_ptr, D, arch ), rshifts ); } } else { for( s = 0; s < nb_subfr; s++ ) { x_ptr = x + s * subfr_length; - C0 -= silk_LSHIFT32( silk_inner_prod_aligned( x_ptr, x_ptr, D ), -rshifts ); + C0 -= silk_LSHIFT32( silk_inner_prod_aligned( x_ptr, x_ptr, D, arch), -rshifts); } } /* Approximate residual energy */ diff --git a/TMessagesProj/jni/opus/silk/fixed/corrMatrix_FIX.c b/TMessagesProj/jni/opus/silk/fixed/corrMatrix_FIX.c index c6172705369..1b4a29c232b 100644 --- a/TMessagesProj/jni/opus/silk/fixed/corrMatrix_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/corrMatrix_FIX.c @@ -42,7 +42,8 @@ void silk_corrVector_FIX( const opus_int L, /* I Length of vectors */ const opus_int order, /* I Max lag for correlation */ opus_int32 *Xt, /* O Pointer to X'*t correlation vector [order] */ - const opus_int rshifts /* I Right shifts of correlations */ + const opus_int rshifts, /* I Right shifts of correlations */ + int arch /* I Run-time architecture */ ) { opus_int lag, i; @@ -57,7 +58,7 @@ void silk_corrVector_FIX( for( lag = 0; lag < order; lag++ ) { inner_prod = 0; for( i = 0; i < L; i++ ) { - inner_prod += silk_RSHIFT32( silk_SMULBB( ptr1[ i ], ptr2[i] ), rshifts ); + inner_prod = silk_ADD_RSHIFT32( inner_prod, silk_SMULBB( ptr1[ i ], ptr2[i] ), rshifts ); } Xt[ lag ] = inner_prod; /* X[:,lag]'*t */ ptr1--; /* Go to next column of X */ @@ -65,7 +66,7 @@ void silk_corrVector_FIX( } else { silk_assert( rshifts == 0 ); for( lag = 0; lag < order; lag++ ) { - Xt[ lag ] = silk_inner_prod_aligned( ptr1, ptr2, L ); /* X[:,lag]'*t */ + Xt[ lag ] = silk_inner_prod_aligned( ptr1, ptr2, L, arch ); /* X[:,lag]'*t */ ptr1--; /* Go to next column of X */ } } @@ -76,60 +77,54 @@ void silk_corrMatrix_FIX( const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ const opus_int L, /* I Length of vectors */ const opus_int order, /* I Max lag for correlation */ - const opus_int head_room, /* I Desired headroom */ opus_int32 *XX, /* O Pointer to X'*X correlation matrix [ order x order ] */ - opus_int *rshifts /* I/O Right shifts of correlations */ + opus_int32 *nrg, /* O Energy of x vector */ + opus_int *rshifts, /* O Right shifts of correlations and energy */ + int arch /* I Run-time architecture */ ) { - opus_int i, j, lag, rshifts_local, head_room_rshifts; + opus_int i, j, lag; opus_int32 energy; const opus_int16 *ptr1, *ptr2; /* Calculate energy to find shift used to fit in 32 bits */ - silk_sum_sqr_shift( &energy, &rshifts_local, x, L + order - 1 ); - /* Add shifts to get the desired head room */ - head_room_rshifts = silk_max( head_room - silk_CLZ32( energy ), 0 ); - - energy = silk_RSHIFT32( energy, head_room_rshifts ); - rshifts_local += head_room_rshifts; + silk_sum_sqr_shift( nrg, rshifts, x, L + order - 1 ); + energy = *nrg; /* Calculate energy of first column (0) of X: X[:,0]'*X[:,0] */ /* Remove contribution of first order - 1 samples */ for( i = 0; i < order - 1; i++ ) { - energy -= silk_RSHIFT32( silk_SMULBB( x[ i ], x[ i ] ), rshifts_local ); - } - if( rshifts_local < *rshifts ) { - /* Adjust energy */ - energy = silk_RSHIFT32( energy, *rshifts - rshifts_local ); - rshifts_local = *rshifts; + energy -= silk_RSHIFT32( silk_SMULBB( x[ i ], x[ i ] ), *rshifts ); } /* Calculate energy of remaining columns of X: X[:,j]'*X[:,j] */ /* Fill out the diagonal of the correlation matrix */ matrix_ptr( XX, 0, 0, order ) = energy; + silk_assert( energy >= 0 ); ptr1 = &x[ order - 1 ]; /* First sample of column 0 of X */ for( j = 1; j < order; j++ ) { - energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr1[ L - j ] ), rshifts_local ) ); - energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr1[ -j ] ), rshifts_local ) ); + energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr1[ L - j ] ), *rshifts ) ); + energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr1[ -j ] ), *rshifts ) ); matrix_ptr( XX, j, j, order ) = energy; + silk_assert( energy >= 0 ); } ptr2 = &x[ order - 2 ]; /* First sample of column 1 of X */ /* Calculate the remaining elements of the correlation matrix */ - if( rshifts_local > 0 ) { + if( *rshifts > 0 ) { /* Right shifting used */ for( lag = 1; lag < order; lag++ ) { /* Inner product of column 0 and column lag: X[:,0]'*X[:,lag] */ energy = 0; for( i = 0; i < L; i++ ) { - energy += silk_RSHIFT32( silk_SMULBB( ptr1[ i ], ptr2[i] ), rshifts_local ); + energy += silk_RSHIFT32( silk_SMULBB( ptr1[ i ], ptr2[i] ), *rshifts ); } /* Calculate remaining off diagonal: X[:,j]'*X[:,j + lag] */ matrix_ptr( XX, lag, 0, order ) = energy; matrix_ptr( XX, 0, lag, order ) = energy; for( j = 1; j < ( order - lag ); j++ ) { - energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr2[ L - j ] ), rshifts_local ) ); - energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr2[ -j ] ), rshifts_local ) ); + energy = silk_SUB32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ L - j ], ptr2[ L - j ] ), *rshifts ) ); + energy = silk_ADD32( energy, silk_RSHIFT32( silk_SMULBB( ptr1[ -j ], ptr2[ -j ] ), *rshifts ) ); matrix_ptr( XX, lag + j, j, order ) = energy; matrix_ptr( XX, j, lag + j, order ) = energy; } @@ -138,7 +133,7 @@ void silk_corrMatrix_FIX( } else { for( lag = 1; lag < order; lag++ ) { /* Inner product of column 0 and column lag: X[:,0]'*X[:,lag] */ - energy = silk_inner_prod_aligned( ptr1, ptr2, L ); + energy = silk_inner_prod_aligned( ptr1, ptr2, L, arch ); matrix_ptr( XX, lag, 0, order ) = energy; matrix_ptr( XX, 0, lag, order ) = energy; /* Calculate remaining off diagonal: X[:,j]'*X[:,j + lag] */ @@ -151,6 +146,5 @@ void silk_corrMatrix_FIX( ptr2--;/* Update pointer to first sample of next column (lag) in X */ } } - *rshifts = rshifts_local; } diff --git a/TMessagesProj/jni/opus/silk/fixed/encode_frame_FIX.c b/TMessagesProj/jni/opus/silk/fixed/encode_frame_FIX.c index b490986b978..4f9e08610ef 100644 --- a/TMessagesProj/jni/opus/silk/fixed/encode_frame_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/encode_frame_FIX.c @@ -29,6 +29,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "config.h" #endif +#include #include "main_FIX.h" #include "stack_alloc.h" #include "tuning_parameters.h" @@ -37,7 +38,7 @@ POSSIBILITY OF SUCH DAMAGE. static OPUS_INLINE void silk_LBRR_encode_FIX( silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ silk_encoder_control_FIX *psEncCtrl, /* I/O Pointer to Silk FIX encoder control struct */ - const opus_int32 xfw_Q3[], /* I Input signal */ + const opus_int16 x16[], /* I Input signal */ opus_int condCoding /* I The type of conditional coding used so far for this frame */ ); @@ -48,7 +49,7 @@ void silk_encode_do_VAD_FIX( /****************************/ /* Voice Activity Detection */ /****************************/ - silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1 ); + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.arch ); /**************************************************/ /* Convert speech activity into VAD and DTX flags */ @@ -94,6 +95,9 @@ opus_int silk_encode_frame_FIX( opus_int16 ec_prevLagIndex_copy; opus_int ec_prevSignalType_copy; opus_int8 LastGainIndex_copy2; + opus_int gain_lock[ MAX_NB_SUBFR ] = {0}; + opus_int16 best_gain_mult[ MAX_NB_SUBFR ]; + opus_int best_sum[ MAX_NB_SUBFR ]; SAVE_STACK; /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ @@ -118,7 +122,6 @@ opus_int silk_encode_frame_FIX( silk_memcpy( x_frame + LA_SHAPE_MS * psEnc->sCmn.fs_kHz, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.frame_length * sizeof( opus_int16 ) ); if( !psEnc->sCmn.prefillFlag ) { - VARDECL( opus_int32, xfw_Q3 ); VARDECL( opus_int16, res_pitch ); VARDECL( opus_uint8, ec_buf_copy ); opus_int16 *res_pitch_frame; @@ -132,7 +135,7 @@ opus_int silk_encode_frame_FIX( /*****************************************/ /* Find pitch lags, initial LPC analysis */ /*****************************************/ - silk_find_pitch_lags_FIX( psEnc, &sEncCtrl, res_pitch, x_frame, psEnc->sCmn.arch ); + silk_find_pitch_lags_FIX( psEnc, &sEncCtrl, res_pitch, x_frame - psEnc->sCmn.ltp_mem_length, psEnc->sCmn.arch ); /************************/ /* Noise shape analysis */ @@ -142,23 +145,17 @@ opus_int silk_encode_frame_FIX( /***************************************************/ /* Find linear prediction coefficients (LPC + LTP) */ /***************************************************/ - silk_find_pred_coefs_FIX( psEnc, &sEncCtrl, res_pitch, x_frame, condCoding ); + silk_find_pred_coefs_FIX( psEnc, &sEncCtrl, res_pitch_frame, x_frame, condCoding ); /****************************************/ /* Process gains */ /****************************************/ silk_process_gains_FIX( psEnc, &sEncCtrl, condCoding ); - /*****************************************/ - /* Prefiltering for noise shaper */ - /*****************************************/ - ALLOC( xfw_Q3, psEnc->sCmn.frame_length, opus_int32 ); - silk_prefilter_FIX( psEnc, &sEncCtrl, xfw_Q3, x_frame ); - /****************************************/ /* Low Bitrate Redundant Encoding */ /****************************************/ - silk_LBRR_encode_FIX( psEnc, &sEncCtrl, xfw_Q3, condCoding ); + silk_LBRR_encode_FIX( psEnc, &sEncCtrl, x_frame, condCoding ); /* Loop over quantizer and entropy coding to control bitrate */ maxIter = 6; @@ -194,13 +191,19 @@ opus_int silk_encode_frame_FIX( /* Noise shaping quantization */ /*****************************************/ if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { - silk_NSQ_del_dec( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, xfw_Q3, psEnc->sCmn.pulses, - sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR2_Q13, sEncCtrl.HarmShapeGain_Q14, - sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14 ); + silk_NSQ_del_dec( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, x_frame, psEnc->sCmn.pulses, + sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR_Q13, sEncCtrl.HarmShapeGain_Q14, + sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14, + psEnc->sCmn.arch ); } else { - silk_NSQ( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, xfw_Q3, psEnc->sCmn.pulses, - sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR2_Q13, sEncCtrl.HarmShapeGain_Q14, - sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14 ); + silk_NSQ( &psEnc->sCmn, &psEnc->sCmn.sNSQ, &psEnc->sCmn.indices, x_frame, psEnc->sCmn.pulses, + sEncCtrl.PredCoef_Q12[ 0 ], sEncCtrl.LTPCoef_Q14, sEncCtrl.AR_Q13, sEncCtrl.HarmShapeGain_Q14, + sEncCtrl.Tilt_Q14, sEncCtrl.LF_shp_Q14, sEncCtrl.Gains_Q16, sEncCtrl.pitchL, sEncCtrl.Lambda_Q10, sEncCtrl.LTP_scale_Q14, + psEnc->sCmn.arch); + } + + if ( iter == maxIter && !found_lower ) { + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); } /****************************************/ @@ -216,6 +219,33 @@ opus_int silk_encode_frame_FIX( nBits = ec_tell( psRangeEnc ); + /* If we still bust after the last iteration, do some damage control. */ + if ( iter == maxIter && !found_lower && nBits > maxBits ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + + /* Keep gains the same as the last frame. */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + psEnc->sCmn.indices.GainsIndices[ i ] = 4; + } + if (condCoding != CODE_CONDITIONALLY) { + psEnc->sCmn.indices.GainsIndices[ 0 ] = sEncCtrl.lastGainIndexPrev; + } + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + /* Clear all pulses. */ + for ( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + psEnc->sCmn.pulses[ i ] = 0; + } + + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + } + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { break; } @@ -263,15 +293,35 @@ opus_int silk_encode_frame_FIX( break; } + if ( !found_lower && nBits > maxBits ) { + int j; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + int sum=0; + for ( j = i*psEnc->sCmn.subfr_length; j < (i+1)*psEnc->sCmn.subfr_length; j++ ) { + sum += abs( psEnc->sCmn.pulses[j] ); + } + if ( iter == 0 || (sum < best_sum[i] && !gain_lock[i]) ) { + best_sum[i] = sum; + best_gain_mult[i] = gainMult_Q8; + } else { + gain_lock[i] = 1; + } + } + } if( ( found_lower & found_upper ) == 0 ) { /* Adjust gain according to high-rate rate/distortion curve */ - opus_int32 gain_factor_Q16; - gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) ); - gain_factor_Q16 = silk_min_32( gain_factor_Q16, SILK_FIX_CONST( 2, 16 ) ); if( nBits > maxBits ) { - gain_factor_Q16 = silk_max_32( gain_factor_Q16, SILK_FIX_CONST( 1.3, 16 ) ); + if (gainMult_Q8 < 16384) { + gainMult_Q8 *= 2; + } else { + gainMult_Q8 = 32767; + } + } else { + opus_int32 gain_factor_Q16; + gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) ); + gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 ); } - gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 ); + } else { /* Adjust gain by interpolating */ gainMult_Q8 = gainMult_lower + silk_DIV32_16( silk_MUL( gainMult_upper - gainMult_lower, maxBits - nBits_lower ), nBits_upper - nBits_lower ); @@ -285,9 +335,15 @@ opus_int silk_encode_frame_FIX( } for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { - sEncCtrl.Gains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], gainMult_Q8 ), 8 ); + opus_int16 tmp; + if ( gain_lock[i] ) { + tmp = best_gain_mult[i]; + } else { + tmp = gainMult_Q8; + } + sEncCtrl.Gains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], tmp ), 8 ); } - + /* Quantize gains */ psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; silk_gains_quant( psEnc->sCmn.indices.GainsIndices, sEncCtrl.Gains_Q16, @@ -329,7 +385,7 @@ opus_int silk_encode_frame_FIX( static OPUS_INLINE void silk_LBRR_encode_FIX( silk_encoder_state_FIX *psEnc, /* I/O Pointer to Silk FIX encoder state */ silk_encoder_control_FIX *psEncCtrl, /* I/O Pointer to Silk FIX encoder control struct */ - const opus_int32 xfw_Q3[], /* I Input signal */ + const opus_int16 x16[], /* I Input signal */ opus_int condCoding /* I The type of conditional coding used so far for this frame */ ) { @@ -368,15 +424,15 @@ static OPUS_INLINE void silk_LBRR_encode_FIX( /* Noise shaping quantization */ /*****************************************/ if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { - silk_NSQ_del_dec( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, xfw_Q3, + silk_NSQ_del_dec( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, x16, psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], psEncCtrl->PredCoef_Q12[ 0 ], psEncCtrl->LTPCoef_Q14, - psEncCtrl->AR2_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, - psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14 ); + psEncCtrl->AR_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, + psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14, psEnc->sCmn.arch ); } else { - silk_NSQ( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, xfw_Q3, + silk_NSQ( &psEnc->sCmn, &sNSQ_LBRR, psIndices_LBRR, x16, psEnc->sCmn.pulses_LBRR[ psEnc->sCmn.nFramesEncoded ], psEncCtrl->PredCoef_Q12[ 0 ], psEncCtrl->LTPCoef_Q14, - psEncCtrl->AR2_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, - psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14 ); + psEncCtrl->AR_Q13, psEncCtrl->HarmShapeGain_Q14, psEncCtrl->Tilt_Q14, psEncCtrl->LF_shp_Q14, + psEncCtrl->Gains_Q16, psEncCtrl->pitchL, psEncCtrl->Lambda_Q10, psEncCtrl->LTP_scale_Q14, psEnc->sCmn.arch ); } /* Restore original gains */ diff --git a/TMessagesProj/jni/opus/silk/fixed/find_LPC_FIX.c b/TMessagesProj/jni/opus/silk/fixed/find_LPC_FIX.c index 783d32e20fa..e11cdc86e67 100644 --- a/TMessagesProj/jni/opus/silk/fixed/find_LPC_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/find_LPC_FIX.c @@ -95,7 +95,7 @@ void silk_find_LPC_FIX( silk_NLSF2A( a_tmp_Q12, NLSF0_Q15, psEncC->predictLPCOrder ); /* Calculate residual energy with NLSF interpolation */ - silk_LPC_analysis_filter( LPC_res, x, a_tmp_Q12, 2 * subfr_length, psEncC->predictLPCOrder ); + silk_LPC_analysis_filter( LPC_res, x, a_tmp_Q12, 2 * subfr_length, psEncC->predictLPCOrder, psEncC->arch ); silk_sum_sqr_shift( &res_nrg0, &rshift0, LPC_res + psEncC->predictLPCOrder, subfr_length - psEncC->predictLPCOrder ); silk_sum_sqr_shift( &res_nrg1, &rshift1, LPC_res + psEncC->predictLPCOrder + subfr_length, subfr_length - psEncC->predictLPCOrder ); diff --git a/TMessagesProj/jni/opus/silk/fixed/find_LTP_FIX.c b/TMessagesProj/jni/opus/silk/fixed/find_LTP_FIX.c index 8c4d70376cb..62d4afb2507 100644 --- a/TMessagesProj/jni/opus/silk/fixed/find_LTP_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/find_LTP_FIX.c @@ -32,213 +32,68 @@ POSSIBILITY OF SUCH DAMAGE. #include "main_FIX.h" #include "tuning_parameters.h" -/* Head room for correlations */ -#define LTP_CORRS_HEAD_ROOM 2 - -void silk_fit_LTP( - opus_int32 LTP_coefs_Q16[ LTP_ORDER ], - opus_int16 LTP_coefs_Q14[ LTP_ORDER ] -); - void silk_find_LTP_FIX( - opus_int16 b_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O LTP coefs */ - opus_int32 WLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ - opus_int *LTPredCodGain_Q7, /* O LTP coding gain */ - const opus_int16 r_lpc[], /* I residual signal after LPC signal + state for first 10 ms */ + opus_int32 XXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Correlation matrix */ + opus_int32 xXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER ], /* O Correlation vector */ + const opus_int16 r_ptr[], /* I Residual signal after LPC */ const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ - const opus_int32 Wght_Q15[ MAX_NB_SUBFR ], /* I weights */ - const opus_int subfr_length, /* I subframe length */ - const opus_int nb_subfr, /* I number of subframes */ - const opus_int mem_offset, /* I number of samples in LTP memory */ - opus_int corr_rshifts[ MAX_NB_SUBFR ] /* O right shifts applied to correlations */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ) { - opus_int i, k, lshift; - const opus_int16 *r_ptr, *lag_ptr; - opus_int16 *b_Q14_ptr; - - opus_int32 regu; - opus_int32 *WLTP_ptr; - opus_int32 b_Q16[ LTP_ORDER ], delta_b_Q14[ LTP_ORDER ], d_Q14[ MAX_NB_SUBFR ], nrg[ MAX_NB_SUBFR ], g_Q26; - opus_int32 w[ MAX_NB_SUBFR ], WLTP_max, max_abs_d_Q14, max_w_bits; - - opus_int32 temp32, denom32; - opus_int extra_shifts; - opus_int rr_shifts, maxRshifts, maxRshifts_wxtra, LZs; - opus_int32 LPC_res_nrg, LPC_LTP_res_nrg, div_Q16; - opus_int32 Rr[ LTP_ORDER ], rr[ MAX_NB_SUBFR ]; - opus_int32 wd, m_Q12; - - b_Q14_ptr = b_Q14; - WLTP_ptr = WLTP; - r_ptr = &r_lpc[ mem_offset ]; + opus_int i, k, extra_shifts; + opus_int xx_shifts, xX_shifts, XX_shifts; + const opus_int16 *lag_ptr; + opus_int32 *XXLTP_Q17_ptr, *xXLTP_Q17_ptr; + opus_int32 xx, nrg, temp; + + xXLTP_Q17_ptr = xXLTP_Q17; + XXLTP_Q17_ptr = XXLTP_Q17; for( k = 0; k < nb_subfr; k++ ) { lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 ); - silk_sum_sqr_shift( &rr[ k ], &rr_shifts, r_ptr, subfr_length ); /* rr[ k ] in Q( -rr_shifts ) */ - - /* Assure headroom */ - LZs = silk_CLZ32( rr[k] ); - if( LZs < LTP_CORRS_HEAD_ROOM ) { - rr[ k ] = silk_RSHIFT_ROUND( rr[ k ], LTP_CORRS_HEAD_ROOM - LZs ); - rr_shifts += ( LTP_CORRS_HEAD_ROOM - LZs ); - } - corr_rshifts[ k ] = rr_shifts; - silk_corrMatrix_FIX( lag_ptr, subfr_length, LTP_ORDER, LTP_CORRS_HEAD_ROOM, WLTP_ptr, &corr_rshifts[ k ] ); /* WLTP_fix_ptr in Q( -corr_rshifts[ k ] ) */ - - /* The correlation vector always has lower max abs value than rr and/or RR so head room is assured */ - silk_corrVector_FIX( lag_ptr, r_ptr, subfr_length, LTP_ORDER, Rr, corr_rshifts[ k ] ); /* Rr_fix_ptr in Q( -corr_rshifts[ k ] ) */ - if( corr_rshifts[ k ] > rr_shifts ) { - rr[ k ] = silk_RSHIFT( rr[ k ], corr_rshifts[ k ] - rr_shifts ); /* rr[ k ] in Q( -corr_rshifts[ k ] ) */ + silk_sum_sqr_shift( &xx, &xx_shifts, r_ptr, subfr_length + LTP_ORDER ); /* xx in Q( -xx_shifts ) */ + silk_corrMatrix_FIX( lag_ptr, subfr_length, LTP_ORDER, XXLTP_Q17_ptr, &nrg, &XX_shifts, arch ); /* XXLTP_Q17_ptr and nrg in Q( -XX_shifts ) */ + extra_shifts = xx_shifts - XX_shifts; + if( extra_shifts > 0 ) { + /* Shift XX */ + xX_shifts = xx_shifts; + for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { + XXLTP_Q17_ptr[ i ] = silk_RSHIFT32( XXLTP_Q17_ptr[ i ], extra_shifts ); /* Q( -xX_shifts ) */ + } + nrg = silk_RSHIFT32( nrg, extra_shifts ); /* Q( -xX_shifts ) */ + } else if( extra_shifts < 0 ) { + /* Shift xx */ + xX_shifts = XX_shifts; + xx = silk_RSHIFT32( xx, -extra_shifts ); /* Q( -xX_shifts ) */ + } else { + xX_shifts = xx_shifts; } - silk_assert( rr[ k ] >= 0 ); - - regu = 1; - regu = silk_SMLAWB( regu, rr[ k ], SILK_FIX_CONST( LTP_DAMPING/3, 16 ) ); - regu = silk_SMLAWB( regu, matrix_ptr( WLTP_ptr, 0, 0, LTP_ORDER ), SILK_FIX_CONST( LTP_DAMPING/3, 16 ) ); - regu = silk_SMLAWB( regu, matrix_ptr( WLTP_ptr, LTP_ORDER-1, LTP_ORDER-1, LTP_ORDER ), SILK_FIX_CONST( LTP_DAMPING/3, 16 ) ); - silk_regularize_correlations_FIX( WLTP_ptr, &rr[k], regu, LTP_ORDER ); - - silk_solve_LDL_FIX( WLTP_ptr, LTP_ORDER, Rr, b_Q16 ); /* WLTP_fix_ptr and Rr_fix_ptr both in Q(-corr_rshifts[k]) */ - - /* Limit and store in Q14 */ - silk_fit_LTP( b_Q16, b_Q14_ptr ); - - /* Calculate residual energy */ - nrg[ k ] = silk_residual_energy16_covar_FIX( b_Q14_ptr, WLTP_ptr, Rr, rr[ k ], LTP_ORDER, 14 ); /* nrg_fix in Q( -corr_rshifts[ k ] ) */ - - /* temp = Wght[ k ] / ( nrg[ k ] * Wght[ k ] + 0.01f * subfr_length ); */ - extra_shifts = silk_min_int( corr_rshifts[ k ], LTP_CORRS_HEAD_ROOM ); - denom32 = silk_LSHIFT_SAT32( silk_SMULWB( nrg[ k ], Wght_Q15[ k ] ), 1 + extra_shifts ) + /* Q( -corr_rshifts[ k ] + extra_shifts ) */ - silk_RSHIFT( silk_SMULWB( (opus_int32)subfr_length, 655 ), corr_rshifts[ k ] - extra_shifts ); /* Q( -corr_rshifts[ k ] + extra_shifts ) */ - denom32 = silk_max( denom32, 1 ); - silk_assert( ((opus_int64)Wght_Q15[ k ] << 16 ) < silk_int32_MAX ); /* Wght always < 0.5 in Q0 */ - temp32 = silk_DIV32( silk_LSHIFT( (opus_int32)Wght_Q15[ k ], 16 ), denom32 ); /* Q( 15 + 16 + corr_rshifts[k] - extra_shifts ) */ - temp32 = silk_RSHIFT( temp32, 31 + corr_rshifts[ k ] - extra_shifts - 26 ); /* Q26 */ + silk_corrVector_FIX( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xXLTP_Q17_ptr, xX_shifts, arch ); /* xXLTP_Q17_ptr in Q( -xX_shifts ) */ - /* Limit temp such that the below scaling never wraps around */ - WLTP_max = 0; + /* At this point all correlations are in Q(-xX_shifts) */ + temp = silk_SMLAWB( 1, nrg, SILK_FIX_CONST( LTP_CORR_INV_MAX, 16 ) ); + temp = silk_max( temp, xx ); +TIC(div) +#if 0 for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { - WLTP_max = silk_max( WLTP_ptr[ i ], WLTP_max ); + XXLTP_Q17_ptr[ i ] = silk_DIV32_varQ( XXLTP_Q17_ptr[ i ], temp, 17 ); } - lshift = silk_CLZ32( WLTP_max ) - 1 - 3; /* keep 3 bits free for vq_nearest_neighbor_fix */ - silk_assert( 26 - 18 + lshift >= 0 ); - if( 26 - 18 + lshift < 31 ) { - temp32 = silk_min_32( temp32, silk_LSHIFT( (opus_int32)1, 26 - 18 + lshift ) ); - } - - silk_scale_vector32_Q26_lshift_18( WLTP_ptr, temp32, LTP_ORDER * LTP_ORDER ); /* WLTP_ptr in Q( 18 - corr_rshifts[ k ] ) */ - - w[ k ] = matrix_ptr( WLTP_ptr, LTP_ORDER/2, LTP_ORDER/2, LTP_ORDER ); /* w in Q( 18 - corr_rshifts[ k ] ) */ - silk_assert( w[k] >= 0 ); - - r_ptr += subfr_length; - b_Q14_ptr += LTP_ORDER; - WLTP_ptr += LTP_ORDER * LTP_ORDER; - } - - maxRshifts = 0; - for( k = 0; k < nb_subfr; k++ ) { - maxRshifts = silk_max_int( corr_rshifts[ k ], maxRshifts ); - } - - /* Compute LTP coding gain */ - if( LTPredCodGain_Q7 != NULL ) { - LPC_LTP_res_nrg = 0; - LPC_res_nrg = 0; - silk_assert( LTP_CORRS_HEAD_ROOM >= 2 ); /* Check that no overflow will happen when adding */ - for( k = 0; k < nb_subfr; k++ ) { - LPC_res_nrg = silk_ADD32( LPC_res_nrg, silk_RSHIFT( silk_ADD32( silk_SMULWB( rr[ k ], Wght_Q15[ k ] ), 1 ), 1 + ( maxRshifts - corr_rshifts[ k ] ) ) ); /* Q( -maxRshifts ) */ - LPC_LTP_res_nrg = silk_ADD32( LPC_LTP_res_nrg, silk_RSHIFT( silk_ADD32( silk_SMULWB( nrg[ k ], Wght_Q15[ k ] ), 1 ), 1 + ( maxRshifts - corr_rshifts[ k ] ) ) ); /* Q( -maxRshifts ) */ - } - LPC_LTP_res_nrg = silk_max( LPC_LTP_res_nrg, 1 ); /* avoid division by zero */ - - div_Q16 = silk_DIV32_varQ( LPC_res_nrg, LPC_LTP_res_nrg, 16 ); - *LTPredCodGain_Q7 = ( opus_int )silk_SMULBB( 3, silk_lin2log( div_Q16 ) - ( 16 << 7 ) ); - - silk_assert( *LTPredCodGain_Q7 == ( opus_int )silk_SAT16( silk_MUL( 3, silk_lin2log( div_Q16 ) - ( 16 << 7 ) ) ) ); - } - - /* smoothing */ - /* d = sum( B, 1 ); */ - b_Q14_ptr = b_Q14; - for( k = 0; k < nb_subfr; k++ ) { - d_Q14[ k ] = 0; for( i = 0; i < LTP_ORDER; i++ ) { - d_Q14[ k ] += b_Q14_ptr[ i ]; - } - b_Q14_ptr += LTP_ORDER; - } - - /* m = ( w * d' ) / ( sum( w ) + 1e-3 ); */ - - /* Find maximum absolute value of d_Q14 and the bits used by w in Q0 */ - max_abs_d_Q14 = 0; - max_w_bits = 0; - for( k = 0; k < nb_subfr; k++ ) { - max_abs_d_Q14 = silk_max_32( max_abs_d_Q14, silk_abs( d_Q14[ k ] ) ); - /* w[ k ] is in Q( 18 - corr_rshifts[ k ] ) */ - /* Find bits needed in Q( 18 - maxRshifts ) */ - max_w_bits = silk_max_32( max_w_bits, 32 - silk_CLZ32( w[ k ] ) + corr_rshifts[ k ] - maxRshifts ); - } - - /* max_abs_d_Q14 = (5 << 15); worst case, i.e. LTP_ORDER * -silk_int16_MIN */ - silk_assert( max_abs_d_Q14 <= ( 5 << 15 ) ); - - /* How many bits is needed for w*d' in Q( 18 - maxRshifts ) in the worst case, of all d_Q14's being equal to max_abs_d_Q14 */ - extra_shifts = max_w_bits + 32 - silk_CLZ32( max_abs_d_Q14 ) - 14; - - /* Subtract what we got available; bits in output var plus maxRshifts */ - extra_shifts -= ( 32 - 1 - 2 + maxRshifts ); /* Keep sign bit free as well as 2 bits for accumulation */ - extra_shifts = silk_max_int( extra_shifts, 0 ); - - maxRshifts_wxtra = maxRshifts + extra_shifts; - - temp32 = silk_RSHIFT( 262, maxRshifts + extra_shifts ) + 1; /* 1e-3f in Q( 18 - (maxRshifts + extra_shifts) ) */ - wd = 0; - for( k = 0; k < nb_subfr; k++ ) { - /* w has at least 2 bits of headroom so no overflow should happen */ - temp32 = silk_ADD32( temp32, silk_RSHIFT( w[ k ], maxRshifts_wxtra - corr_rshifts[ k ] ) ); /* Q( 18 - maxRshifts_wxtra ) */ - wd = silk_ADD32( wd, silk_LSHIFT( silk_SMULWW( silk_RSHIFT( w[ k ], maxRshifts_wxtra - corr_rshifts[ k ] ), d_Q14[ k ] ), 2 ) ); /* Q( 18 - maxRshifts_wxtra ) */ - } - m_Q12 = silk_DIV32_varQ( wd, temp32, 12 ); - - b_Q14_ptr = b_Q14; - for( k = 0; k < nb_subfr; k++ ) { - /* w_fix[ k ] from Q( 18 - corr_rshifts[ k ] ) to Q( 16 ) */ - if( 2 - corr_rshifts[k] > 0 ) { - temp32 = silk_RSHIFT( w[ k ], 2 - corr_rshifts[ k ] ); - } else { - temp32 = silk_LSHIFT_SAT32( w[ k ], corr_rshifts[ k ] - 2 ); + xXLTP_Q17_ptr[ i ] = silk_DIV32_varQ( xXLTP_Q17_ptr[ i ], temp, 17 ); } - - g_Q26 = silk_MUL( - silk_DIV32( - SILK_FIX_CONST( LTP_SMOOTHING, 26 ), - silk_RSHIFT( SILK_FIX_CONST( LTP_SMOOTHING, 26 ), 10 ) + temp32 ), /* Q10 */ - silk_LSHIFT_SAT32( silk_SUB_SAT32( (opus_int32)m_Q12, silk_RSHIFT( d_Q14[ k ], 2 ) ), 4 ) ); /* Q16 */ - - temp32 = 0; - for( i = 0; i < LTP_ORDER; i++ ) { - delta_b_Q14[ i ] = silk_max_16( b_Q14_ptr[ i ], 1638 ); /* 1638_Q14 = 0.1_Q0 */ - temp32 += delta_b_Q14[ i ]; /* Q14 */ +#else + for( i = 0; i < LTP_ORDER * LTP_ORDER; i++ ) { + XXLTP_Q17_ptr[ i ] = (opus_int32)( silk_LSHIFT64( (opus_int64)XXLTP_Q17_ptr[ i ], 17 ) / temp ); } - temp32 = silk_DIV32( g_Q26, temp32 ); /* Q14 -> Q12 */ for( i = 0; i < LTP_ORDER; i++ ) { - b_Q14_ptr[ i ] = silk_LIMIT_32( (opus_int32)b_Q14_ptr[ i ] + silk_SMULWB( silk_LSHIFT_SAT32( temp32, 4 ), delta_b_Q14[ i ] ), -16000, 28000 ); + xXLTP_Q17_ptr[ i ] = (opus_int32)( silk_LSHIFT64( (opus_int64)xXLTP_Q17_ptr[ i ], 17 ) / temp ); } - b_Q14_ptr += LTP_ORDER; - } -} - -void silk_fit_LTP( - opus_int32 LTP_coefs_Q16[ LTP_ORDER ], - opus_int16 LTP_coefs_Q14[ LTP_ORDER ] -) -{ - opus_int i; - - for( i = 0; i < LTP_ORDER; i++ ) { - LTP_coefs_Q14[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( LTP_coefs_Q16[ i ], 2 ) ); +#endif +TOC(div) + r_ptr += subfr_length; + XXLTP_Q17_ptr += LTP_ORDER * LTP_ORDER; + xXLTP_Q17_ptr += LTP_ORDER; } } diff --git a/TMessagesProj/jni/opus/silk/fixed/find_pitch_lags_FIX.c b/TMessagesProj/jni/opus/silk/fixed/find_pitch_lags_FIX.c index 620f8dcd2c1..9303e9db1f3 100644 --- a/TMessagesProj/jni/opus/silk/fixed/find_pitch_lags_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/find_pitch_lags_FIX.c @@ -44,7 +44,7 @@ void silk_find_pitch_lags_FIX( { opus_int buf_len, i, scale; opus_int32 thrhld_Q13, res_nrg; - const opus_int16 *x_buf, *x_buf_ptr; + const opus_int16 *x_ptr; VARDECL( opus_int16, Wsig ); opus_int16 *Wsig_ptr; opus_int32 auto_corr[ MAX_FIND_PITCH_LPC_ORDER + 1 ]; @@ -61,8 +61,6 @@ void silk_find_pitch_lags_FIX( /* Safety check */ silk_assert( buf_len >= psEnc->sCmn.pitch_LPC_win_length ); - x_buf = x - psEnc->sCmn.ltp_mem_length; - /*************************************/ /* Estimate LPC AR coefficients */ /*************************************/ @@ -72,19 +70,19 @@ void silk_find_pitch_lags_FIX( ALLOC( Wsig, psEnc->sCmn.pitch_LPC_win_length, opus_int16 ); /* First LA_LTP samples */ - x_buf_ptr = x_buf + buf_len - psEnc->sCmn.pitch_LPC_win_length; + x_ptr = x + buf_len - psEnc->sCmn.pitch_LPC_win_length; Wsig_ptr = Wsig; - silk_apply_sine_window( Wsig_ptr, x_buf_ptr, 1, psEnc->sCmn.la_pitch ); + silk_apply_sine_window( Wsig_ptr, x_ptr, 1, psEnc->sCmn.la_pitch ); /* Middle un - windowed samples */ Wsig_ptr += psEnc->sCmn.la_pitch; - x_buf_ptr += psEnc->sCmn.la_pitch; - silk_memcpy( Wsig_ptr, x_buf_ptr, ( psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ) ) * sizeof( opus_int16 ) ); + x_ptr += psEnc->sCmn.la_pitch; + silk_memcpy( Wsig_ptr, x_ptr, ( psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ) ) * sizeof( opus_int16 ) ); /* Last LA_LTP samples */ Wsig_ptr += psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ); - x_buf_ptr += psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ); - silk_apply_sine_window( Wsig_ptr, x_buf_ptr, 2, psEnc->sCmn.la_pitch ); + x_ptr += psEnc->sCmn.pitch_LPC_win_length - silk_LSHIFT( psEnc->sCmn.la_pitch, 1 ); + silk_apply_sine_window( Wsig_ptr, x_ptr, 2, psEnc->sCmn.la_pitch ); /* Calculate autocorrelation sequence */ silk_autocorr( auto_corr, &scale, Wsig, psEnc->sCmn.pitch_LPC_win_length, psEnc->sCmn.pitchEstimationLPCOrder + 1, arch ); @@ -112,7 +110,7 @@ void silk_find_pitch_lags_FIX( /*****************************************/ /* LPC analysis filtering */ /*****************************************/ - silk_LPC_analysis_filter( res, x_buf, A_Q12, buf_len, psEnc->sCmn.pitchEstimationLPCOrder ); + silk_LPC_analysis_filter( res, x, A_Q12, buf_len, psEnc->sCmn.pitchEstimationLPCOrder, psEnc->sCmn.arch ); if( psEnc->sCmn.indices.signalType != TYPE_NO_VOICE_ACTIVITY && psEnc->sCmn.first_frame_after_reset == 0 ) { /* Threshold for pitch estimator */ diff --git a/TMessagesProj/jni/opus/silk/fixed/find_pred_coefs_FIX.c b/TMessagesProj/jni/opus/silk/fixed/find_pred_coefs_FIX.c index 5c22f8288b5..24c6aab3a77 100644 --- a/TMessagesProj/jni/opus/silk/fixed/find_pred_coefs_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/find_pred_coefs_FIX.c @@ -41,13 +41,12 @@ void silk_find_pred_coefs_FIX( ) { opus_int i; - opus_int32 invGains_Q16[ MAX_NB_SUBFR ], local_gains[ MAX_NB_SUBFR ], Wght_Q15[ MAX_NB_SUBFR ]; + opus_int32 invGains_Q16[ MAX_NB_SUBFR ], local_gains[ MAX_NB_SUBFR ]; opus_int16 NLSF_Q15[ MAX_LPC_ORDER ]; const opus_int16 *x_ptr; opus_int16 *x_pre_ptr; VARDECL( opus_int16, LPC_in_pre ); - opus_int32 tmp, min_gain_Q16, minInvGain_Q30; - opus_int LTP_corrs_rshift[ MAX_NB_SUBFR ]; + opus_int32 min_gain_Q16, minInvGain_Q30; SAVE_STACK; /* weighting for weighted least squares */ @@ -61,13 +60,11 @@ void silk_find_pred_coefs_FIX( /* Invert and normalize gains, and ensure that maximum invGains_Q16 is within range of a 16 bit int */ invGains_Q16[ i ] = silk_DIV32_varQ( min_gain_Q16, psEncCtrl->Gains_Q16[ i ], 16 - 2 ); - /* Ensure Wght_Q15 a minimum value 1 */ - invGains_Q16[ i ] = silk_max( invGains_Q16[ i ], 363 ); + /* Limit inverse */ + invGains_Q16[ i ] = silk_max( invGains_Q16[ i ], 100 ); /* Square the inverted gains */ silk_assert( invGains_Q16[ i ] == silk_SAT16( invGains_Q16[ i ] ) ); - tmp = silk_SMULWB( invGains_Q16[ i ], invGains_Q16[ i ] ); - Wght_Q15[ i ] = silk_RSHIFT( tmp, 1 ); /* Invert the inverted and normalized gains */ local_gains[ i ] = silk_DIV32( ( (opus_int32)1 << 16 ), invGains_Q16[ i ] ); @@ -77,23 +74,24 @@ void silk_find_pred_coefs_FIX( psEnc->sCmn.nb_subfr * psEnc->sCmn.predictLPCOrder + psEnc->sCmn.frame_length, opus_int16 ); if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { - VARDECL( opus_int32, WLTP ); + VARDECL( opus_int32, xXLTP_Q17 ); + VARDECL( opus_int32, XXLTP_Q17 ); /**********/ /* VOICED */ /**********/ silk_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 ); - ALLOC( WLTP, psEnc->sCmn.nb_subfr * LTP_ORDER * LTP_ORDER, opus_int32 ); + ALLOC( xXLTP_Q17, psEnc->sCmn.nb_subfr * LTP_ORDER, opus_int32 ); + ALLOC( XXLTP_Q17, psEnc->sCmn.nb_subfr * LTP_ORDER * LTP_ORDER, opus_int32 ); /* LTP analysis */ - silk_find_LTP_FIX( psEncCtrl->LTPCoef_Q14, WLTP, &psEncCtrl->LTPredCodGain_Q7, - res_pitch, psEncCtrl->pitchL, Wght_Q15, psEnc->sCmn.subfr_length, - psEnc->sCmn.nb_subfr, psEnc->sCmn.ltp_mem_length, LTP_corrs_rshift ); + silk_find_LTP_FIX( XXLTP_Q17, xXLTP_Q17, res_pitch, + psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); /* Quantize LTP gain parameters */ silk_quant_LTP_gains( psEncCtrl->LTPCoef_Q14, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex, - &psEnc->sCmn.sum_log_gain_Q7, WLTP, psEnc->sCmn.mu_LTP_Q9, psEnc->sCmn.LTPQuantLowComplexity, psEnc->sCmn.nb_subfr); + &psEnc->sCmn.sum_log_gain_Q7, &psEncCtrl->LTPredCodGain_Q7, XXLTP_Q17, xXLTP_Q17, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); /* Control LTP scaling */ silk_LTP_scale_ctrl_FIX( psEnc, psEncCtrl, condCoding ); @@ -118,16 +116,16 @@ void silk_find_pred_coefs_FIX( silk_memset( psEncCtrl->LTPCoef_Q14, 0, psEnc->sCmn.nb_subfr * LTP_ORDER * sizeof( opus_int16 ) ); psEncCtrl->LTPredCodGain_Q7 = 0; - psEnc->sCmn.sum_log_gain_Q7 = 0; + psEnc->sCmn.sum_log_gain_Q7 = 0; } /* Limit on total predictive coding gain */ if( psEnc->sCmn.first_frame_after_reset ) { minInvGain_Q30 = SILK_FIX_CONST( 1.0f / MAX_PREDICTION_POWER_GAIN_AFTER_RESET, 30 ); - } else { + } else { minInvGain_Q30 = silk_log2lin( silk_SMLAWB( 16 << 7, (opus_int32)psEncCtrl->LTPredCodGain_Q7, SILK_FIX_CONST( 1.0 / 3, 16 ) ) ); /* Q16 */ - minInvGain_Q30 = silk_DIV32_varQ( minInvGain_Q30, - silk_SMULWW( SILK_FIX_CONST( MAX_PREDICTION_POWER_GAIN, 0 ), + minInvGain_Q30 = silk_DIV32_varQ( minInvGain_Q30, + silk_SMULWW( SILK_FIX_CONST( MAX_PREDICTION_POWER_GAIN, 0 ), silk_SMLAWB( SILK_FIX_CONST( 0.25, 18 ), SILK_FIX_CONST( 0.75, 18 ), psEncCtrl->coding_quality_Q14 ) ), 14 ); } @@ -139,7 +137,7 @@ void silk_find_pred_coefs_FIX( /* Calculate residual energy using quantized LPC coefficients */ silk_residual_energy_FIX( psEncCtrl->ResNrg, psEncCtrl->ResNrgQ, LPC_in_pre, psEncCtrl->PredCoef_Q12, local_gains, - psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder ); + psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.predictLPCOrder, psEnc->sCmn.arch ); /* Copy to prediction struct for use in next frame for interpolation */ silk_memcpy( psEnc->sCmn.prev_NLSFq_Q15, NLSF_Q15, sizeof( psEnc->sCmn.prev_NLSFq_Q15 ) ); diff --git a/TMessagesProj/jni/opus/silk/fixed/k2a_FIX.c b/TMessagesProj/jni/opus/silk/fixed/k2a_FIX.c index 5fee599bcb7..549f6eadaa2 100644 --- a/TMessagesProj/jni/opus/silk/fixed/k2a_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/k2a_FIX.c @@ -39,14 +39,15 @@ void silk_k2a( ) { opus_int k, n; - opus_int32 Atmp[ SILK_MAX_ORDER_LPC ]; + opus_int32 rc, tmp1, tmp2; for( k = 0; k < order; k++ ) { - for( n = 0; n < k; n++ ) { - Atmp[ n ] = A_Q24[ n ]; - } - for( n = 0; n < k; n++ ) { - A_Q24[ n ] = silk_SMLAWB( A_Q24[ n ], silk_LSHIFT( Atmp[ k - n - 1 ], 1 ), rc_Q15[ k ] ); + rc = rc_Q15[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A_Q24[ n ]; + tmp2 = A_Q24[ k - n - 1 ]; + A_Q24[ n ] = silk_SMLAWB( tmp1, silk_LSHIFT( tmp2, 1 ), rc ); + A_Q24[ k - n - 1 ] = silk_SMLAWB( tmp2, silk_LSHIFT( tmp1, 1 ), rc ); } A_Q24[ k ] = -silk_LSHIFT( (opus_int32)rc_Q15[ k ], 9 ); } diff --git a/TMessagesProj/jni/opus/silk/fixed/k2a_Q16_FIX.c b/TMessagesProj/jni/opus/silk/fixed/k2a_Q16_FIX.c index 3b039875446..1595aa62126 100644 --- a/TMessagesProj/jni/opus/silk/fixed/k2a_Q16_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/k2a_Q16_FIX.c @@ -39,15 +39,16 @@ void silk_k2a_Q16( ) { opus_int k, n; - opus_int32 Atmp[ SILK_MAX_ORDER_LPC ]; + opus_int32 rc, tmp1, tmp2; for( k = 0; k < order; k++ ) { - for( n = 0; n < k; n++ ) { - Atmp[ n ] = A_Q24[ n ]; + rc = rc_Q16[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A_Q24[ n ]; + tmp2 = A_Q24[ k - n - 1 ]; + A_Q24[ n ] = silk_SMLAWW( tmp1, tmp2, rc ); + A_Q24[ k - n - 1 ] = silk_SMLAWW( tmp2, tmp1, rc ); } - for( n = 0; n < k; n++ ) { - A_Q24[ n ] = silk_SMLAWW( A_Q24[ n ], Atmp[ k - n - 1 ], rc_Q16[ k ] ); - } - A_Q24[ k ] = -silk_LSHIFT( rc_Q16[ k ], 8 ); + A_Q24[ k ] = -silk_LSHIFT( rc, 8 ); } } diff --git a/TMessagesProj/jni/opus/silk/fixed/main_FIX.h b/TMessagesProj/jni/opus/silk/fixed/main_FIX.h index a56ca07a224..ddbf37723b2 100644 --- a/TMessagesProj/jni/opus/silk/fixed/main_FIX.h +++ b/TMessagesProj/jni/opus/silk/fixed/main_FIX.h @@ -81,22 +81,11 @@ opus_int silk_init_encoder( opus_int silk_control_encoder( silk_encoder_state_Fxx *psEnc, /* I/O Pointer to Silk encoder state */ silk_EncControlStruct *encControl, /* I Control structure */ - const opus_int32 TargetRate_bps, /* I Target max bitrate (bps) */ const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ const opus_int channelNb, /* I Channel number */ const opus_int force_fs_kHz ); -/****************/ -/* Prefiltering */ -/****************/ -void silk_prefilter_FIX( - silk_encoder_state_FIX *psEnc, /* I/O Encoder state */ - const silk_encoder_control_FIX *psEncCtrl, /* I Encoder control */ - opus_int32 xw_Q10[], /* O Weighted signal */ - const opus_int16 x[] /* I Speech signal */ -); - /**************************/ /* Noise shaping analysis */ /**************************/ @@ -157,16 +146,13 @@ void silk_find_LPC_FIX( /* LTP analysis */ void silk_find_LTP_FIX( - opus_int16 b_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O LTP coefs */ - opus_int32 WLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ - opus_int *LTPredCodGain_Q7, /* O LTP coding gain */ - const opus_int16 r_lpc[], /* I residual signal after LPC signal + state for first 10 ms */ + opus_int32 XXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Correlation matrix */ + opus_int32 xXLTP_Q17[ MAX_NB_SUBFR * LTP_ORDER ], /* O Correlation vector */ + const opus_int16 r_lpc[], /* I Residual signal after LPC */ const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ - const opus_int32 Wght_Q15[ MAX_NB_SUBFR ], /* I weights */ - const opus_int subfr_length, /* I subframe length */ - const opus_int nb_subfr, /* I number of subframes */ - const opus_int mem_offset, /* I number of samples in LTP memory */ - opus_int corr_rshifts[ MAX_NB_SUBFR ] /* O right shifts applied to correlations */ + const opus_int subfr_length, /* I Subframe length */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ); void silk_LTP_analysis_filter_FIX( @@ -190,7 +176,8 @@ void silk_residual_energy_FIX( const opus_int32 gains[ MAX_NB_SUBFR ], /* I Quantization gains */ const opus_int subfr_length, /* I Subframe length */ const opus_int nb_subfr, /* I Number of subframes */ - const opus_int LPC_order /* I LPC order */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ ); /* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ @@ -218,9 +205,10 @@ void silk_corrMatrix_FIX( const opus_int16 *x, /* I x vector [L + order - 1] used to form data matrix X */ const opus_int L, /* I Length of vectors */ const opus_int order, /* I Max lag for correlation */ - const opus_int head_room, /* I Desired headroom */ opus_int32 *XX, /* O Pointer to X'*X correlation matrix [ order x order ] */ - opus_int *rshifts /* I/O Right shifts of correlations */ + opus_int32 *nrg, /* O Energy of x vector */ + opus_int *rshifts, /* O Right shifts of correlations */ + int arch /* I Run-time architecture */ ); /* Calculates correlation vector X'*t */ @@ -230,23 +218,8 @@ void silk_corrVector_FIX( const opus_int L, /* I Length of vectors */ const opus_int order, /* I Max lag for correlation */ opus_int32 *Xt, /* O Pointer to X'*t correlation vector [order] */ - const opus_int rshifts /* I Right shifts of correlations */ -); - -/* Add noise to matrix diagonal */ -void silk_regularize_correlations_FIX( - opus_int32 *XX, /* I/O Correlation matrices */ - opus_int32 *xx, /* I/O Correlation values */ - opus_int32 noise, /* I Noise to add */ - opus_int D /* I Dimension of XX */ -); - -/* Solves Ax = b, assuming A is symmetric */ -void silk_solve_LDL_FIX( - opus_int32 *A, /* I Pointer to symetric square matrix A */ - opus_int M, /* I Size of matrix */ - const opus_int32 *b, /* I Pointer to b vector */ - opus_int32 *x_Q16 /* O Pointer to x solution vector */ + const opus_int rshifts, /* I Right shifts of correlations */ + int arch /* I Run-time architecture */ ); #ifndef FORCE_CPP_BUILD diff --git a/TMessagesProj/jni/opus/silk/fixed/noise_shape_analysis_FIX.c b/TMessagesProj/jni/opus/silk/fixed/noise_shape_analysis_FIX.c index e24d2e9d338..8fe23777f6d 100644 --- a/TMessagesProj/jni/opus/silk/fixed/noise_shape_analysis_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/noise_shape_analysis_FIX.c @@ -57,90 +57,86 @@ static OPUS_INLINE opus_int32 warped_gain( /* gain in Q16*/ /* Convert warped filter coefficients to monic pseudo-warped coefficients and limit maximum */ /* amplitude of monic warped coefficients by using bandwidth expansion on the true coefficients */ static OPUS_INLINE void limit_warped_coefs( - opus_int32 *coefs_syn_Q24, - opus_int32 *coefs_ana_Q24, + opus_int32 *coefs_Q24, opus_int lambda_Q16, opus_int32 limit_Q24, opus_int order ) { opus_int i, iter, ind = 0; - opus_int32 tmp, maxabs_Q24, chirp_Q16, gain_syn_Q16, gain_ana_Q16; + opus_int32 tmp, maxabs_Q24, chirp_Q16, gain_Q16; opus_int32 nom_Q16, den_Q24; + opus_int32 limit_Q20, maxabs_Q20; /* Convert to monic coefficients */ lambda_Q16 = -lambda_Q16; for( i = order - 1; i > 0; i-- ) { - coefs_syn_Q24[ i - 1 ] = silk_SMLAWB( coefs_syn_Q24[ i - 1 ], coefs_syn_Q24[ i ], lambda_Q16 ); - coefs_ana_Q24[ i - 1 ] = silk_SMLAWB( coefs_ana_Q24[ i - 1 ], coefs_ana_Q24[ i ], lambda_Q16 ); + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); } lambda_Q16 = -lambda_Q16; - nom_Q16 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 16 ), -(opus_int32)lambda_Q16, lambda_Q16 ); - den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_syn_Q24[ 0 ], lambda_Q16 ); - gain_syn_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); - den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_ana_Q24[ 0 ], lambda_Q16 ); - gain_ana_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); + nom_Q16 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 16 ), -(opus_int32)lambda_Q16, lambda_Q16 ); + den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_Q24[ 0 ], lambda_Q16 ); + gain_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); for( i = 0; i < order; i++ ) { - coefs_syn_Q24[ i ] = silk_SMULWW( gain_syn_Q16, coefs_syn_Q24[ i ] ); - coefs_ana_Q24[ i ] = silk_SMULWW( gain_ana_Q16, coefs_ana_Q24[ i ] ); + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); } - + limit_Q20 = silk_RSHIFT(limit_Q24, 4); for( iter = 0; iter < 10; iter++ ) { /* Find maximum absolute value */ maxabs_Q24 = -1; for( i = 0; i < order; i++ ) { - tmp = silk_max( silk_abs_int32( coefs_syn_Q24[ i ] ), silk_abs_int32( coefs_ana_Q24[ i ] ) ); + tmp = silk_abs_int32( coefs_Q24[ i ] ); if( tmp > maxabs_Q24 ) { maxabs_Q24 = tmp; ind = i; } } - if( maxabs_Q24 <= limit_Q24 ) { + /* Use Q20 to avoid any overflow when multiplying by (ind + 1) later. */ + maxabs_Q20 = silk_RSHIFT(maxabs_Q24, 4); + if( maxabs_Q20 <= limit_Q20 ) { /* Coefficients are within range - done */ return; } /* Convert back to true warped coefficients */ for( i = 1; i < order; i++ ) { - coefs_syn_Q24[ i - 1 ] = silk_SMLAWB( coefs_syn_Q24[ i - 1 ], coefs_syn_Q24[ i ], lambda_Q16 ); - coefs_ana_Q24[ i - 1 ] = silk_SMLAWB( coefs_ana_Q24[ i - 1 ], coefs_ana_Q24[ i ], lambda_Q16 ); + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); } - gain_syn_Q16 = silk_INVERSE32_varQ( gain_syn_Q16, 32 ); - gain_ana_Q16 = silk_INVERSE32_varQ( gain_ana_Q16, 32 ); + gain_Q16 = silk_INVERSE32_varQ( gain_Q16, 32 ); for( i = 0; i < order; i++ ) { - coefs_syn_Q24[ i ] = silk_SMULWW( gain_syn_Q16, coefs_syn_Q24[ i ] ); - coefs_ana_Q24[ i ] = silk_SMULWW( gain_ana_Q16, coefs_ana_Q24[ i ] ); + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); } /* Apply bandwidth expansion */ chirp_Q16 = SILK_FIX_CONST( 0.99, 16 ) - silk_DIV32_varQ( - silk_SMULWB( maxabs_Q24 - limit_Q24, silk_SMLABB( SILK_FIX_CONST( 0.8, 10 ), SILK_FIX_CONST( 0.1, 10 ), iter ) ), - silk_MUL( maxabs_Q24, ind + 1 ), 22 ); - silk_bwexpander_32( coefs_syn_Q24, order, chirp_Q16 ); - silk_bwexpander_32( coefs_ana_Q24, order, chirp_Q16 ); + silk_SMULWB( maxabs_Q20 - limit_Q20, silk_SMLABB( SILK_FIX_CONST( 0.8, 10 ), SILK_FIX_CONST( 0.1, 10 ), iter ) ), + silk_MUL( maxabs_Q20, ind + 1 ), 22 ); + silk_bwexpander_32( coefs_Q24, order, chirp_Q16 ); /* Convert to monic warped coefficients */ lambda_Q16 = -lambda_Q16; for( i = order - 1; i > 0; i-- ) { - coefs_syn_Q24[ i - 1 ] = silk_SMLAWB( coefs_syn_Q24[ i - 1 ], coefs_syn_Q24[ i ], lambda_Q16 ); - coefs_ana_Q24[ i - 1 ] = silk_SMLAWB( coefs_ana_Q24[ i - 1 ], coefs_ana_Q24[ i ], lambda_Q16 ); + coefs_Q24[ i - 1 ] = silk_SMLAWB( coefs_Q24[ i - 1 ], coefs_Q24[ i ], lambda_Q16 ); } lambda_Q16 = -lambda_Q16; nom_Q16 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 16 ), -(opus_int32)lambda_Q16, lambda_Q16 ); - den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_syn_Q24[ 0 ], lambda_Q16 ); - gain_syn_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); - den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_ana_Q24[ 0 ], lambda_Q16 ); - gain_ana_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); + den_Q24 = silk_SMLAWB( SILK_FIX_CONST( 1.0, 24 ), coefs_Q24[ 0 ], lambda_Q16 ); + gain_Q16 = silk_DIV32_varQ( nom_Q16, den_Q24, 24 ); for( i = 0; i < order; i++ ) { - coefs_syn_Q24[ i ] = silk_SMULWW( gain_syn_Q16, coefs_syn_Q24[ i ] ); - coefs_ana_Q24[ i ] = silk_SMULWW( gain_ana_Q16, coefs_ana_Q24[ i ] ); + coefs_Q24[ i ] = silk_SMULWW( gain_Q16, coefs_Q24[ i ] ); } } silk_assert( 0 ); } +/* Disable MIPS version until it's updated. */ +#if 0 && defined(MIPSr1_ASM) +#include "mips/noise_shape_analysis_FIX_mipsr1.h" +#endif + /**************************************************************/ /* Compute noise shaping coefficients and initial gain values */ /**************************************************************/ +#ifndef OVERRIDE_silk_noise_shape_analysis_FIX void silk_noise_shape_analysis_FIX( silk_encoder_state_FIX *psEnc, /* I/O Encoder state FIX */ silk_encoder_control_FIX *psEncCtrl, /* I/O Encoder control FIX */ @@ -150,14 +146,13 @@ void silk_noise_shape_analysis_FIX( ) { silk_shape_state_FIX *psShapeSt = &psEnc->sShape; - opus_int k, i, nSamples, Qnrg, b_Q14, warping_Q16, scale = 0; - opus_int32 SNR_adj_dB_Q7, HarmBoost_Q16, HarmShapeGain_Q16, Tilt_Q16, tmp32; - opus_int32 nrg, pre_nrg_Q30, log_energy_Q7, log_energy_prev_Q7, energy_variation_Q7; - opus_int32 delta_Q16, BWExp1_Q16, BWExp2_Q16, gain_mult_Q16, gain_add_Q16, strength_Q16, b_Q8; + opus_int k, i, nSamples, nSegs, Qnrg, b_Q14, warping_Q16, scale = 0; + opus_int32 SNR_adj_dB_Q7, HarmShapeGain_Q16, Tilt_Q16, tmp32; + opus_int32 nrg, log_energy_Q7, log_energy_prev_Q7, energy_variation_Q7; + opus_int32 BWExp_Q16, gain_mult_Q16, gain_add_Q16, strength_Q16, b_Q8; opus_int32 auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; opus_int32 refl_coef_Q16[ MAX_SHAPE_LPC_ORDER ]; - opus_int32 AR1_Q24[ MAX_SHAPE_LPC_ORDER ]; - opus_int32 AR2_Q24[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 AR_Q24[ MAX_SHAPE_LPC_ORDER ]; VARDECL( opus_int16, x_windowed ); const opus_int16 *x_ptr, *pitch_res_ptr; SAVE_STACK; @@ -204,14 +199,14 @@ void silk_noise_shape_analysis_FIX( if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { /* Initially set to 0; may be overruled in process_gains(..) */ psEnc->sCmn.indices.quantOffsetType = 0; - psEncCtrl->sparseness_Q8 = 0; } else { /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ nSamples = silk_LSHIFT( psEnc->sCmn.fs_kHz, 1 ); energy_variation_Q7 = 0; log_energy_prev_Q7 = 0; pitch_res_ptr = pitch_res; - for( k = 0; k < silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; k++ ) { + nSegs = silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; + for( k = 0; k < nSegs; k++ ) { silk_sum_sqr_shift( &nrg, &scale, pitch_res_ptr, nSamples ); nrg += silk_RSHIFT( nSamples, scale ); /* Q(-scale)*/ @@ -223,18 +218,12 @@ void silk_noise_shape_analysis_FIX( pitch_res_ptr += nSamples; } - psEncCtrl->sparseness_Q8 = silk_RSHIFT( silk_sigm_Q15( silk_SMULWB( energy_variation_Q7 - - SILK_FIX_CONST( 5.0, 7 ), SILK_FIX_CONST( 0.1, 16 ) ) ), 7 ); - /* Set quantization offset depending on sparseness measure */ - if( psEncCtrl->sparseness_Q8 > SILK_FIX_CONST( SPARSENESS_THRESHOLD_QNT_OFFSET, 8 ) ) { + if( energy_variation_Q7 > SILK_FIX_CONST( ENERGY_VARIATION_THRESHOLD_QNT_OFFSET, 7 ) * (nSegs-1) ) { psEnc->sCmn.indices.quantOffsetType = 0; } else { psEnc->sCmn.indices.quantOffsetType = 1; } - - /* Increase coding SNR for sparse signals */ - SNR_adj_dB_Q7 = silk_SMLAWB( SNR_adj_dB_Q7, SILK_FIX_CONST( SPARSE_SNR_INCR_dB, 15 ), psEncCtrl->sparseness_Q8 - SILK_FIX_CONST( 0.5, 8 ) ); } /*******************************/ @@ -242,14 +231,8 @@ void silk_noise_shape_analysis_FIX( /*******************************/ /* More BWE for signals with high prediction gain */ strength_Q16 = silk_SMULWB( psEncCtrl->predGain_Q16, SILK_FIX_CONST( FIND_PITCH_WHITE_NOISE_FRACTION, 16 ) ); - BWExp1_Q16 = BWExp2_Q16 = silk_DIV32_varQ( SILK_FIX_CONST( BANDWIDTH_EXPANSION, 16 ), + BWExp_Q16 = silk_DIV32_varQ( SILK_FIX_CONST( BANDWIDTH_EXPANSION, 16 ), silk_SMLAWW( SILK_FIX_CONST( 1.0, 16 ), strength_Q16, strength_Q16 ), 16 ); - delta_Q16 = silk_SMULWB( SILK_FIX_CONST( 1.0, 16 ) - silk_SMULBB( 3, psEncCtrl->coding_quality_Q14 ), - SILK_FIX_CONST( LOW_RATE_BANDWIDTH_EXPANSION_DELTA, 16 ) ); - BWExp1_Q16 = silk_SUB32( BWExp1_Q16, delta_Q16 ); - BWExp2_Q16 = silk_ADD32( BWExp2_Q16, delta_Q16 ); - /* BWExp1 will be applied after BWExp2, so make it relative */ - BWExp1_Q16 = silk_DIV32_16( silk_LSHIFT( BWExp1_Q16, 14 ), silk_RSHIFT( BWExp2_Q16, 2 ) ); if( psEnc->sCmn.warping_Q16 > 0 ) { /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ @@ -294,7 +277,7 @@ void silk_noise_shape_analysis_FIX( silk_assert( nrg >= 0 ); /* Convert reflection coefficients to prediction coefficients */ - silk_k2a_Q16( AR2_Q24, refl_coef_Q16, psEnc->sCmn.shapingLPCOrder ); + silk_k2a_Q16( AR_Q24, refl_coef_Q16, psEnc->sCmn.shapingLPCOrder ); Qnrg = -scale; /* range: -12...30*/ silk_assert( Qnrg >= -12 ); @@ -313,40 +296,34 @@ void silk_noise_shape_analysis_FIX( if( psEnc->sCmn.warping_Q16 > 0 ) { /* Adjust gain for warping */ - gain_mult_Q16 = warped_gain( AR2_Q24, warping_Q16, psEnc->sCmn.shapingLPCOrder ); - silk_assert( psEncCtrl->Gains_Q16[ k ] >= 0 ); - if ( silk_SMULWW( silk_RSHIFT_ROUND( psEncCtrl->Gains_Q16[ k ], 1 ), gain_mult_Q16 ) >= ( silk_int32_MAX >> 1 ) ) { - psEncCtrl->Gains_Q16[ k ] = silk_int32_MAX; + gain_mult_Q16 = warped_gain( AR_Q24, warping_Q16, psEnc->sCmn.shapingLPCOrder ); + silk_assert( psEncCtrl->Gains_Q16[ k ] > 0 ); + if( psEncCtrl->Gains_Q16[ k ] < SILK_FIX_CONST( 0.25, 16 ) ) { + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); } else { - psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( psEncCtrl->Gains_Q16[ k ], gain_mult_Q16 ); + psEncCtrl->Gains_Q16[ k ] = silk_SMULWW( silk_RSHIFT_ROUND( psEncCtrl->Gains_Q16[ k ], 1 ), gain_mult_Q16 ); + if ( psEncCtrl->Gains_Q16[ k ] >= ( silk_int32_MAX >> 1 ) ) { + psEncCtrl->Gains_Q16[ k ] = silk_int32_MAX; + } else { + psEncCtrl->Gains_Q16[ k ] = silk_LSHIFT32( psEncCtrl->Gains_Q16[ k ], 1 ); + } } + silk_assert( psEncCtrl->Gains_Q16[ k ] > 0 ); } - /* Bandwidth expansion for synthesis filter shaping */ - silk_bwexpander_32( AR2_Q24, psEnc->sCmn.shapingLPCOrder, BWExp2_Q16 ); - - /* Compute noise shaping filter coefficients */ - silk_memcpy( AR1_Q24, AR2_Q24, psEnc->sCmn.shapingLPCOrder * sizeof( opus_int32 ) ); - - /* Bandwidth expansion for analysis filter shaping */ - silk_assert( BWExp1_Q16 <= SILK_FIX_CONST( 1.0, 16 ) ); - silk_bwexpander_32( AR1_Q24, psEnc->sCmn.shapingLPCOrder, BWExp1_Q16 ); - - /* Ratio of prediction gains, in energy domain */ - pre_nrg_Q30 = silk_LPC_inverse_pred_gain_Q24( AR2_Q24, psEnc->sCmn.shapingLPCOrder ); - nrg = silk_LPC_inverse_pred_gain_Q24( AR1_Q24, psEnc->sCmn.shapingLPCOrder ); + /* Bandwidth expansion */ + silk_bwexpander_32( AR_Q24, psEnc->sCmn.shapingLPCOrder, BWExp_Q16 ); - /*psEncCtrl->GainsPre[ k ] = 1.0f - 0.7f * ( 1.0f - pre_nrg / nrg ) = 0.3f + 0.7f * pre_nrg / nrg;*/ - pre_nrg_Q30 = silk_LSHIFT32( silk_SMULWB( pre_nrg_Q30, SILK_FIX_CONST( 0.7, 15 ) ), 1 ); - psEncCtrl->GainsPre_Q14[ k ] = ( opus_int ) SILK_FIX_CONST( 0.3, 14 ) + silk_DIV32_varQ( pre_nrg_Q30, nrg, 14 ); - - /* Convert to monic warped prediction coefficients and limit absolute values */ - limit_warped_coefs( AR2_Q24, AR1_Q24, warping_Q16, SILK_FIX_CONST( 3.999, 24 ), psEnc->sCmn.shapingLPCOrder ); + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Convert to monic warped prediction coefficients and limit absolute values */ + limit_warped_coefs( AR_Q24, warping_Q16, SILK_FIX_CONST( 3.999, 24 ), psEnc->sCmn.shapingLPCOrder ); - /* Convert from Q24 to Q13 and store in int16 */ - for( i = 0; i < psEnc->sCmn.shapingLPCOrder; i++ ) { - psEncCtrl->AR1_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR1_Q24[ i ], 11 ) ); - psEncCtrl->AR2_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR2_Q24[ i ], 11 ) ); + /* Convert from Q24 to Q13 and store in int16 */ + for( i = 0; i < psEnc->sCmn.shapingLPCOrder; i++ ) { + psEncCtrl->AR_Q13[ k * MAX_SHAPE_LPC_ORDER + i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( AR_Q24[ i ], 11 ) ); + } + } else { + silk_LPC_fit( &psEncCtrl->AR_Q13[ k * MAX_SHAPE_LPC_ORDER ], AR_Q24, 13, 24, psEnc->sCmn.shapingLPCOrder ); } } @@ -363,11 +340,6 @@ void silk_noise_shape_analysis_FIX( psEncCtrl->Gains_Q16[ k ] = silk_ADD_POS_SAT32( psEncCtrl->Gains_Q16[ k ], gain_add_Q16 ); } - gain_mult_Q16 = SILK_FIX_CONST( 1.0, 16 ) + silk_RSHIFT_ROUND( silk_MLA( SILK_FIX_CONST( INPUT_TILT, 26 ), - psEncCtrl->coding_quality_Q14, SILK_FIX_CONST( HIGH_RATE_INPUT_TILT, 12 ) ), 10 ); - for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { - psEncCtrl->GainsPre_Q14[ k ] = silk_SMULWB( gain_mult_Q16, psEncCtrl->GainsPre_Q14[ k ] ); - } /************************************************/ /* Control low-frequency shaping and noise tilt */ @@ -405,14 +377,6 @@ void silk_noise_shape_analysis_FIX( /****************************/ /* HARMONIC SHAPING CONTROL */ /****************************/ - /* Control boosting of harmonic frequencies */ - HarmBoost_Q16 = silk_SMULWB( silk_SMULWB( SILK_FIX_CONST( 1.0, 17 ) - silk_LSHIFT( psEncCtrl->coding_quality_Q14, 3 ), - psEnc->LTPCorr_Q15 ), SILK_FIX_CONST( LOW_RATE_HARMONIC_BOOST, 16 ) ); - - /* More harmonic boost for noisy input signals */ - HarmBoost_Q16 = silk_SMLAWB( HarmBoost_Q16, - SILK_FIX_CONST( 1.0, 16 ) - silk_LSHIFT( psEncCtrl->input_quality_Q14, 2 ), SILK_FIX_CONST( LOW_INPUT_QUALITY_HARMONIC_BOOST, 16 ) ); - if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { /* More harmonic noise shaping for high bitrates or noisy input */ HarmShapeGain_Q16 = silk_SMLAWB( SILK_FIX_CONST( HARMONIC_SHAPING, 16 ), @@ -430,16 +394,14 @@ void silk_noise_shape_analysis_FIX( /* Smooth over subframes */ /*************************/ for( k = 0; k < MAX_NB_SUBFR; k++ ) { - psShapeSt->HarmBoost_smth_Q16 = - silk_SMLAWB( psShapeSt->HarmBoost_smth_Q16, HarmBoost_Q16 - psShapeSt->HarmBoost_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); psShapeSt->HarmShapeGain_smth_Q16 = silk_SMLAWB( psShapeSt->HarmShapeGain_smth_Q16, HarmShapeGain_Q16 - psShapeSt->HarmShapeGain_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); psShapeSt->Tilt_smth_Q16 = silk_SMLAWB( psShapeSt->Tilt_smth_Q16, Tilt_Q16 - psShapeSt->Tilt_smth_Q16, SILK_FIX_CONST( SUBFR_SMTH_COEF, 16 ) ); - psEncCtrl->HarmBoost_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->HarmBoost_smth_Q16, 2 ); psEncCtrl->HarmShapeGain_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->HarmShapeGain_smth_Q16, 2 ); psEncCtrl->Tilt_Q14[ k ] = ( opus_int )silk_RSHIFT_ROUND( psShapeSt->Tilt_smth_Q16, 2 ); } RESTORE_STACK; } +#endif /* OVERRIDE_silk_noise_shape_analysis_FIX */ diff --git a/TMessagesProj/jni/opus/silk/fixed/pitch_analysis_core_FIX.c b/TMessagesProj/jni/opus/silk/fixed/pitch_analysis_core_FIX.c index 1641a0fbcda..8df109e6cf5 100644 --- a/TMessagesProj/jni/opus/silk/fixed/pitch_analysis_core_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/pitch_analysis_core_FIX.c @@ -72,14 +72,15 @@ static void silk_P_Ana_calc_energy_st3( opus_int start_lag, /* I lag offset to search around */ opus_int sf_length, /* I length of one 5 ms subframe */ opus_int nb_subfr, /* I number of subframes */ - opus_int complexity /* I Complexity setting */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ ); /*************************************************************/ /* FIXED POINT CORE PITCH ANALYSIS FUNCTION */ /*************************************************************/ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 voiced, 1 unvoiced */ - const opus_int16 *frame, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ + const opus_int16 *frame_unscaled, /* I Signal of length PE_FRAME_LENGTH_MS*Fs_kHz */ opus_int *pitch_out, /* O 4 pitch lag values */ opus_int16 *lagIndex, /* O Lag Index */ opus_int8 *contourIndex, /* O Pitch contour Index */ @@ -93,16 +94,17 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 int arch /* I Run-time architecture */ ) { - VARDECL( opus_int16, frame_8kHz ); + VARDECL( opus_int16, frame_8kHz_buf ); VARDECL( opus_int16, frame_4kHz ); + VARDECL( opus_int16, frame_scaled ); opus_int32 filt_state[ 6 ]; - const opus_int16 *input_frame_ptr; + const opus_int16 *frame, *frame_8kHz; opus_int i, k, d, j; VARDECL( opus_int16, C ); VARDECL( opus_int32, xcorr32 ); const opus_int16 *target_ptr, *basis_ptr; - opus_int32 cross_corr, normalizer, energy, shift, energy_basis, energy_target; - opus_int d_srch[ PE_D_SRCH_LENGTH ], Cmax, length_d_srch, length_d_comp; + opus_int32 cross_corr, normalizer, energy, energy_basis, energy_target; + opus_int d_srch[ PE_D_SRCH_LENGTH ], Cmax, length_d_srch, length_d_comp, shift; VARDECL( opus_int16, d_comp ); opus_int32 sum, threshold, lag_counter; opus_int CBimax, CBimax_new, CBimax_old, lag, start_lag, end_lag, lag_new; @@ -118,6 +120,7 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 opus_int32 delta_lag_log2_sqr_Q7, lag_log2_Q7, prevLag_log2_Q7, prev_lag_bias_Q13; const opus_int8 *Lag_CB_ptr; SAVE_STACK; + /* Check for valid sampling frequency */ silk_assert( Fs_kHz == 8 || Fs_kHz == 12 || Fs_kHz == 16 ); @@ -136,17 +139,33 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 min_lag = PE_MIN_LAG_MS * Fs_kHz; max_lag = PE_MAX_LAG_MS * Fs_kHz - 1; + /* Downscale input if necessary */ + silk_sum_sqr_shift( &energy, &shift, frame_unscaled, frame_length ); + shift += 3 - silk_CLZ32( energy ); /* at least two bits headroom */ + ALLOC( frame_scaled, frame_length, opus_int16 ); + if( shift > 0 ) { + shift = silk_RSHIFT( shift + 1, 1 ); + for( i = 0; i < frame_length; i++ ) { + frame_scaled[ i ] = silk_RSHIFT( frame_unscaled[ i ], shift ); + } + frame = frame_scaled; + } else { + frame = frame_unscaled; + } + + ALLOC( frame_8kHz_buf, ( Fs_kHz == 8 ) ? 1 : frame_length_8kHz, opus_int16 ); /* Resample from input sampled at Fs_kHz to 8 kHz */ - ALLOC( frame_8kHz, frame_length_8kHz, opus_int16 ); if( Fs_kHz == 16 ) { silk_memset( filt_state, 0, 2 * sizeof( opus_int32 ) ); - silk_resampler_down2( filt_state, frame_8kHz, frame, frame_length ); + silk_resampler_down2( filt_state, frame_8kHz_buf, frame, frame_length ); + frame_8kHz = frame_8kHz_buf; } else if( Fs_kHz == 12 ) { silk_memset( filt_state, 0, 6 * sizeof( opus_int32 ) ); - silk_resampler_down2_3( filt_state, frame_8kHz, frame, frame_length ); + silk_resampler_down2_3( filt_state, frame_8kHz_buf, frame, frame_length ); + frame_8kHz = frame_8kHz_buf; } else { silk_assert( Fs_kHz == 8 ); - silk_memcpy( frame_8kHz, frame, frame_length_8kHz * sizeof(opus_int16) ); + frame_8kHz = frame; } /* Decimate again to 4 kHz */ @@ -159,19 +178,6 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 frame_4kHz[ i ] = silk_ADD_SAT16( frame_4kHz[ i ], frame_4kHz[ i - 1 ] ); } - /******************************************************************************* - ** Scale 4 kHz signal down to prevent correlations measures from overflowing - ** find scaling as max scaling for each 8kHz(?) subframe - *******************************************************************************/ - - /* Inner product is calculated with different lengths, so scale for the worst case */ - silk_sum_sqr_shift( &energy, &shift, frame_4kHz, frame_length_4kHz ); - if( shift > 0 ) { - shift = silk_RSHIFT( shift, 1 ); - for( i = 0; i < frame_length_4kHz; i++ ) { - frame_4kHz[ i ] = silk_RSHIFT( frame_4kHz[ i ], shift ); - } - } /****************************************************************************** * FIRST STAGE, operating in 4 khz @@ -195,8 +201,8 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 /* Calculate first vector products before loop */ cross_corr = xcorr32[ MAX_LAG_4KHZ - MIN_LAG_4KHZ ]; - normalizer = silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ ); - normalizer = silk_ADD32( normalizer, silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ ) ); + normalizer = silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ, arch ); + normalizer = silk_ADD32( normalizer, silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ) ); normalizer = silk_ADD32( normalizer, silk_SMULBB( SF_LENGTH_8KHZ, 4000 ) ); matrix_ptr( C, k, 0, CSTRIDE_4KHZ ) = @@ -310,18 +316,6 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 ** SECOND STAGE, operating at 8 kHz, on lag sections with high correlation *************************************************************************************/ - /****************************************************************************** - ** Scale signal down to avoid correlations measures from overflowing - *******************************************************************************/ - /* find scaling as max scaling for each subframe */ - silk_sum_sqr_shift( &energy, &shift, frame_8kHz, frame_length_8kHz ); - if( shift > 0 ) { - shift = silk_RSHIFT( shift, 1 ); - for( i = 0; i < frame_length_8kHz; i++ ) { - frame_8kHz[ i ] = silk_RSHIFT( frame_8kHz[ i ], shift ); - } - } - /********************************************************************************* * Find energy of each subframe projected onto its history, for a range of delays *********************************************************************************/ @@ -334,7 +328,7 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 silk_assert( target_ptr >= frame_8kHz ); silk_assert( target_ptr + SF_LENGTH_8KHZ <= frame_8kHz + frame_length_8kHz ); - energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ ), 1 ); + energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, SF_LENGTH_8KHZ, arch ), 1 ); for( j = 0; j < length_d_comp; j++ ) { d = d_comp[ j ]; basis_ptr = target_ptr - d; @@ -343,9 +337,9 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 silk_assert( basis_ptr >= frame_8kHz ); silk_assert( basis_ptr + SF_LENGTH_8KHZ <= frame_8kHz + frame_length_8kHz ); - cross_corr = silk_inner_prod_aligned( target_ptr, basis_ptr, SF_LENGTH_8KHZ ); + cross_corr = silk_inner_prod_aligned( target_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ); if( cross_corr > 0 ) { - energy_basis = silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ ); + energy_basis = silk_inner_prod_aligned( basis_ptr, basis_ptr, SF_LENGTH_8KHZ, arch ); matrix_ptr( C, k, d - ( MIN_LAG_8KHZ - 2 ), CSTRIDE_8KHZ ) = (opus_int16)silk_DIV32_varQ( cross_corr, silk_ADD32( energy_target, @@ -461,24 +455,6 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 silk_assert( *LTPCorr_Q15 >= 0 ); if( Fs_kHz > 8 ) { - VARDECL( opus_int16, scratch_mem ); - /***************************************************************************/ - /* Scale input signal down to avoid correlations measures from overflowing */ - /***************************************************************************/ - /* find scaling as max scaling for each subframe */ - silk_sum_sqr_shift( &energy, &shift, frame, frame_length ); - ALLOC( scratch_mem, shift > 0 ? frame_length : ALLOC_NONE, opus_int16 ); - if( shift > 0 ) { - /* Move signal to scratch mem because the input signal should be unchanged */ - shift = silk_RSHIFT( shift, 1 ); - for( i = 0; i < frame_length; i++ ) { - scratch_mem[ i ] = silk_RSHIFT( frame[ i ], shift ); - } - input_frame_ptr = scratch_mem; - } else { - input_frame_ptr = frame; - } - /* Search in original signal */ CBimax_old = CBimax; @@ -518,15 +494,15 @@ opus_int silk_pitch_analysis_core( /* O Voicing estimate: 0 /* Calculate the correlations and energies needed in stage 3 */ ALLOC( energies_st3, nb_subfr * nb_cbk_search, silk_pe_stage3_vals ); ALLOC( cross_corr_st3, nb_subfr * nb_cbk_search, silk_pe_stage3_vals ); - silk_P_Ana_calc_corr_st3( cross_corr_st3, input_frame_ptr, start_lag, sf_length, nb_subfr, complexity, arch ); - silk_P_Ana_calc_energy_st3( energies_st3, input_frame_ptr, start_lag, sf_length, nb_subfr, complexity ); + silk_P_Ana_calc_corr_st3( cross_corr_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); + silk_P_Ana_calc_energy_st3( energies_st3, frame, start_lag, sf_length, nb_subfr, complexity, arch ); lag_counter = 0; silk_assert( lag == silk_SAT16( lag ) ); contour_bias_Q15 = silk_DIV32_16( SILK_FIX_CONST( PE_FLATCONTOUR_BIAS, 15 ), lag ); - target_ptr = &input_frame_ptr[ PE_LTP_MEM_LENGTH_MS * Fs_kHz ]; - energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, nb_subfr * sf_length ), 1 ); + target_ptr = &frame[ PE_LTP_MEM_LENGTH_MS * Fs_kHz ]; + energy_target = silk_ADD32( silk_inner_prod_aligned( target_ptr, target_ptr, nb_subfr * sf_length, arch ), 1 ); for( d = start_lag; d <= end_lag; d++ ) { for( j = 0; j < nb_cbk_search; j++ ) { cross_corr = 0; @@ -671,7 +647,8 @@ static void silk_P_Ana_calc_energy_st3( opus_int start_lag, /* I lag offset to search around */ opus_int sf_length, /* I length of one 5 ms subframe */ opus_int nb_subfr, /* I number of subframes */ - opus_int complexity /* I Complexity setting */ + opus_int complexity, /* I Complexity setting */ + int arch /* I Run-time architecture */ ) { const opus_int16 *target_ptr, *basis_ptr; @@ -705,7 +682,7 @@ static void silk_P_Ana_calc_energy_st3( /* Calculate the energy for first lag */ basis_ptr = target_ptr - ( start_lag + matrix_ptr( Lag_range_ptr, k, 0, 2 ) ); - energy = silk_inner_prod_aligned( basis_ptr, basis_ptr, sf_length ); + energy = silk_inner_prod_aligned( basis_ptr, basis_ptr, sf_length, arch ); silk_assert( energy >= 0 ); scratch_mem[ lag_counter ] = energy; lag_counter++; diff --git a/TMessagesProj/jni/opus/silk/fixed/prefilter_FIX.c b/TMessagesProj/jni/opus/silk/fixed/prefilter_FIX.c deleted file mode 100644 index d381730c28f..00000000000 --- a/TMessagesProj/jni/opus/silk/fixed/prefilter_FIX.c +++ /dev/null @@ -1,209 +0,0 @@ -/*********************************************************************** -Copyright (c) 2006-2011, Skype Limited. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -***********************************************************************/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "main_FIX.h" -#include "stack_alloc.h" -#include "tuning_parameters.h" - -/* Prefilter for finding Quantizer input signal */ -static OPUS_INLINE void silk_prefilt_FIX( - silk_prefilter_state_FIX *P, /* I/O state */ - opus_int32 st_res_Q12[], /* I short term residual signal */ - opus_int32 xw_Q3[], /* O prefiltered signal */ - opus_int32 HarmShapeFIRPacked_Q12, /* I Harmonic shaping coeficients */ - opus_int Tilt_Q14, /* I Tilt shaping coeficient */ - opus_int32 LF_shp_Q14, /* I Low-frequancy shaping coeficients */ - opus_int lag, /* I Lag for harmonic shaping */ - opus_int length /* I Length of signals */ -); - -void silk_warped_LPC_analysis_filter_FIX( - opus_int32 state[], /* I/O State [order + 1] */ - opus_int32 res_Q2[], /* O Residual signal [length] */ - const opus_int16 coef_Q13[], /* I Coefficients [order] */ - const opus_int16 input[], /* I Input signal [length] */ - const opus_int16 lambda_Q16, /* I Warping factor */ - const opus_int length, /* I Length of input signal */ - const opus_int order /* I Filter order (even) */ -) -{ - opus_int n, i; - opus_int32 acc_Q11, tmp1, tmp2; - - /* Order must be even */ - silk_assert( ( order & 1 ) == 0 ); - - for( n = 0; n < length; n++ ) { - /* Output of lowpass section */ - tmp2 = silk_SMLAWB( state[ 0 ], state[ 1 ], lambda_Q16 ); - state[ 0 ] = silk_LSHIFT( input[ n ], 14 ); - /* Output of allpass section */ - tmp1 = silk_SMLAWB( state[ 1 ], state[ 2 ] - tmp2, lambda_Q16 ); - state[ 1 ] = tmp2; - acc_Q11 = silk_RSHIFT( order, 1 ); - acc_Q11 = silk_SMLAWB( acc_Q11, tmp2, coef_Q13[ 0 ] ); - /* Loop over allpass sections */ - for( i = 2; i < order; i += 2 ) { - /* Output of allpass section */ - tmp2 = silk_SMLAWB( state[ i ], state[ i + 1 ] - tmp1, lambda_Q16 ); - state[ i ] = tmp1; - acc_Q11 = silk_SMLAWB( acc_Q11, tmp1, coef_Q13[ i - 1 ] ); - /* Output of allpass section */ - tmp1 = silk_SMLAWB( state[ i + 1 ], state[ i + 2 ] - tmp2, lambda_Q16 ); - state[ i + 1 ] = tmp2; - acc_Q11 = silk_SMLAWB( acc_Q11, tmp2, coef_Q13[ i ] ); - } - state[ order ] = tmp1; - acc_Q11 = silk_SMLAWB( acc_Q11, tmp1, coef_Q13[ order - 1 ] ); - res_Q2[ n ] = silk_LSHIFT( (opus_int32)input[ n ], 2 ) - silk_RSHIFT_ROUND( acc_Q11, 9 ); - } -} - -void silk_prefilter_FIX( - silk_encoder_state_FIX *psEnc, /* I/O Encoder state */ - const silk_encoder_control_FIX *psEncCtrl, /* I Encoder control */ - opus_int32 xw_Q3[], /* O Weighted signal */ - const opus_int16 x[] /* I Speech signal */ -) -{ - silk_prefilter_state_FIX *P = &psEnc->sPrefilt; - opus_int j, k, lag; - opus_int32 tmp_32; - const opus_int16 *AR1_shp_Q13; - const opus_int16 *px; - opus_int32 *pxw_Q3; - opus_int HarmShapeGain_Q12, Tilt_Q14; - opus_int32 HarmShapeFIRPacked_Q12, LF_shp_Q14; - VARDECL( opus_int32, x_filt_Q12 ); - VARDECL( opus_int32, st_res_Q2 ); - opus_int16 B_Q10[ 2 ]; - SAVE_STACK; - - /* Set up pointers */ - px = x; - pxw_Q3 = xw_Q3; - lag = P->lagPrev; - ALLOC( x_filt_Q12, psEnc->sCmn.subfr_length, opus_int32 ); - ALLOC( st_res_Q2, psEnc->sCmn.subfr_length, opus_int32 ); - for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { - /* Update Variables that change per sub frame */ - if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { - lag = psEncCtrl->pitchL[ k ]; - } - - /* Noise shape parameters */ - HarmShapeGain_Q12 = silk_SMULWB( (opus_int32)psEncCtrl->HarmShapeGain_Q14[ k ], 16384 - psEncCtrl->HarmBoost_Q14[ k ] ); - silk_assert( HarmShapeGain_Q12 >= 0 ); - HarmShapeFIRPacked_Q12 = silk_RSHIFT( HarmShapeGain_Q12, 2 ); - HarmShapeFIRPacked_Q12 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q12, 1 ), 16 ); - Tilt_Q14 = psEncCtrl->Tilt_Q14[ k ]; - LF_shp_Q14 = psEncCtrl->LF_shp_Q14[ k ]; - AR1_shp_Q13 = &psEncCtrl->AR1_Q13[ k * MAX_SHAPE_LPC_ORDER ]; - - /* Short term FIR filtering*/ - silk_warped_LPC_analysis_filter_FIX( P->sAR_shp, st_res_Q2, AR1_shp_Q13, px, - psEnc->sCmn.warping_Q16, psEnc->sCmn.subfr_length, psEnc->sCmn.shapingLPCOrder ); - - /* Reduce (mainly) low frequencies during harmonic emphasis */ - B_Q10[ 0 ] = silk_RSHIFT_ROUND( psEncCtrl->GainsPre_Q14[ k ], 4 ); - tmp_32 = silk_SMLABB( SILK_FIX_CONST( INPUT_TILT, 26 ), psEncCtrl->HarmBoost_Q14[ k ], HarmShapeGain_Q12 ); /* Q26 */ - tmp_32 = silk_SMLABB( tmp_32, psEncCtrl->coding_quality_Q14, SILK_FIX_CONST( HIGH_RATE_INPUT_TILT, 12 ) ); /* Q26 */ - tmp_32 = silk_SMULWB( tmp_32, -psEncCtrl->GainsPre_Q14[ k ] ); /* Q24 */ - tmp_32 = silk_RSHIFT_ROUND( tmp_32, 14 ); /* Q10 */ - B_Q10[ 1 ]= silk_SAT16( tmp_32 ); - x_filt_Q12[ 0 ] = silk_MLA( silk_MUL( st_res_Q2[ 0 ], B_Q10[ 0 ] ), P->sHarmHP_Q2, B_Q10[ 1 ] ); - for( j = 1; j < psEnc->sCmn.subfr_length; j++ ) { - x_filt_Q12[ j ] = silk_MLA( silk_MUL( st_res_Q2[ j ], B_Q10[ 0 ] ), st_res_Q2[ j - 1 ], B_Q10[ 1 ] ); - } - P->sHarmHP_Q2 = st_res_Q2[ psEnc->sCmn.subfr_length - 1 ]; - - silk_prefilt_FIX( P, x_filt_Q12, pxw_Q3, HarmShapeFIRPacked_Q12, Tilt_Q14, LF_shp_Q14, lag, psEnc->sCmn.subfr_length ); - - px += psEnc->sCmn.subfr_length; - pxw_Q3 += psEnc->sCmn.subfr_length; - } - - P->lagPrev = psEncCtrl->pitchL[ psEnc->sCmn.nb_subfr - 1 ]; - RESTORE_STACK; -} - -/* Prefilter for finding Quantizer input signal */ -static OPUS_INLINE void silk_prefilt_FIX( - silk_prefilter_state_FIX *P, /* I/O state */ - opus_int32 st_res_Q12[], /* I short term residual signal */ - opus_int32 xw_Q3[], /* O prefiltered signal */ - opus_int32 HarmShapeFIRPacked_Q12, /* I Harmonic shaping coeficients */ - opus_int Tilt_Q14, /* I Tilt shaping coeficient */ - opus_int32 LF_shp_Q14, /* I Low-frequancy shaping coeficients */ - opus_int lag, /* I Lag for harmonic shaping */ - opus_int length /* I Length of signals */ -) -{ - opus_int i, idx, LTP_shp_buf_idx; - opus_int32 n_LTP_Q12, n_Tilt_Q10, n_LF_Q10; - opus_int32 sLF_MA_shp_Q12, sLF_AR_shp_Q12; - opus_int16 *LTP_shp_buf; - - /* To speed up use temp variables instead of using the struct */ - LTP_shp_buf = P->sLTP_shp; - LTP_shp_buf_idx = P->sLTP_shp_buf_idx; - sLF_AR_shp_Q12 = P->sLF_AR_shp_Q12; - sLF_MA_shp_Q12 = P->sLF_MA_shp_Q12; - - for( i = 0; i < length; i++ ) { - if( lag > 0 ) { - /* unrolled loop */ - silk_assert( HARM_SHAPE_FIR_TAPS == 3 ); - idx = lag + LTP_shp_buf_idx; - n_LTP_Q12 = silk_SMULBB( LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 - 1) & LTP_MASK ], HarmShapeFIRPacked_Q12 ); - n_LTP_Q12 = silk_SMLABT( n_LTP_Q12, LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 ) & LTP_MASK ], HarmShapeFIRPacked_Q12 ); - n_LTP_Q12 = silk_SMLABB( n_LTP_Q12, LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 + 1) & LTP_MASK ], HarmShapeFIRPacked_Q12 ); - } else { - n_LTP_Q12 = 0; - } - - n_Tilt_Q10 = silk_SMULWB( sLF_AR_shp_Q12, Tilt_Q14 ); - n_LF_Q10 = silk_SMLAWB( silk_SMULWT( sLF_AR_shp_Q12, LF_shp_Q14 ), sLF_MA_shp_Q12, LF_shp_Q14 ); - - sLF_AR_shp_Q12 = silk_SUB32( st_res_Q12[ i ], silk_LSHIFT( n_Tilt_Q10, 2 ) ); - sLF_MA_shp_Q12 = silk_SUB32( sLF_AR_shp_Q12, silk_LSHIFT( n_LF_Q10, 2 ) ); - - LTP_shp_buf_idx = ( LTP_shp_buf_idx - 1 ) & LTP_MASK; - LTP_shp_buf[ LTP_shp_buf_idx ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sLF_MA_shp_Q12, 12 ) ); - - xw_Q3[i] = silk_RSHIFT_ROUND( silk_SUB32( sLF_MA_shp_Q12, n_LTP_Q12 ), 9 ); - } - - /* Copy temp variable back to state */ - P->sLF_AR_shp_Q12 = sLF_AR_shp_Q12; - P->sLF_MA_shp_Q12 = sLF_MA_shp_Q12; - P->sLTP_shp_buf_idx = LTP_shp_buf_idx; -} diff --git a/TMessagesProj/jni/opus/silk/fixed/residual_energy_FIX.c b/TMessagesProj/jni/opus/silk/fixed/residual_energy_FIX.c index 105ae31807b..41f74778e82 100644 --- a/TMessagesProj/jni/opus/silk/fixed/residual_energy_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/residual_energy_FIX.c @@ -42,7 +42,8 @@ void silk_residual_energy_FIX( const opus_int32 gains[ MAX_NB_SUBFR ], /* I Quantization gains */ const opus_int subfr_length, /* I Subframe length */ const opus_int nb_subfr, /* I Number of subframes */ - const opus_int LPC_order /* I LPC order */ + const opus_int LPC_order, /* I LPC order */ + int arch /* I Run-time architecture */ ) { opus_int offset, i, j, rshift, lz1, lz2; @@ -60,7 +61,7 @@ void silk_residual_energy_FIX( silk_assert( ( nb_subfr >> 1 ) * ( MAX_NB_SUBFR >> 1 ) == nb_subfr ); for( i = 0; i < nb_subfr >> 1; i++ ) { /* Calculate half frame LPC residual signal including preceding samples */ - silk_LPC_analysis_filter( LPC_res, x_ptr, a_Q12[ i ], ( MAX_NB_SUBFR >> 1 ) * offset, LPC_order ); + silk_LPC_analysis_filter( LPC_res, x_ptr, a_Q12[ i ], ( MAX_NB_SUBFR >> 1 ) * offset, LPC_order, arch ); /* Point to first subframe of the just calculated LPC residual signal */ LPC_res_ptr = LPC_res + LPC_order; diff --git a/TMessagesProj/jni/opus/silk/fixed/schur64_FIX.c b/TMessagesProj/jni/opus/silk/fixed/schur64_FIX.c index 764a10ef3ea..4d3b0932a64 100644 --- a/TMessagesProj/jni/opus/silk/fixed/schur64_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/schur64_FIX.c @@ -43,7 +43,7 @@ opus_int32 silk_schur64( /* O returns residual ene opus_int32 C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; opus_int32 Ctmp1_Q30, Ctmp2_Q30, rc_tmp_Q31; - silk_assert( order==6||order==8||order==10||order==12||order==14||order==16 ); + silk_assert( order <= SILK_MAX_ORDER_LPC ); /* Check for invalid input */ if( c[ 0 ] <= 0 ) { diff --git a/TMessagesProj/jni/opus/silk/fixed/schur_FIX.c b/TMessagesProj/jni/opus/silk/fixed/schur_FIX.c index c4c0ef23b47..9fe7f4198c6 100644 --- a/TMessagesProj/jni/opus/silk/fixed/schur_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/schur_FIX.c @@ -43,7 +43,7 @@ opus_int32 silk_schur( /* O Returns residual ene opus_int32 C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; opus_int32 Ctmp1, Ctmp2, rc_tmp_Q15; - silk_assert( order==6||order==8||order==10||order==12||order==14||order==16 ); + silk_assert( order <= SILK_MAX_ORDER_LPC ); /* Get number of leading zeros */ lz = silk_CLZ32( c[ 0 ] ); diff --git a/TMessagesProj/jni/opus/silk/fixed/solve_LS_FIX.c b/TMessagesProj/jni/opus/silk/fixed/solve_LS_FIX.c deleted file mode 100644 index 51d7d49d02a..00000000000 --- a/TMessagesProj/jni/opus/silk/fixed/solve_LS_FIX.c +++ /dev/null @@ -1,249 +0,0 @@ -/*********************************************************************** -Copyright (c) 2006-2011, Skype Limited. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -***********************************************************************/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "main_FIX.h" -#include "stack_alloc.h" -#include "tuning_parameters.h" - -/*****************************/ -/* Internal function headers */ -/*****************************/ - -typedef struct { - opus_int32 Q36_part; - opus_int32 Q48_part; -} inv_D_t; - -/* Factorize square matrix A into LDL form */ -static OPUS_INLINE void silk_LDL_factorize_FIX( - opus_int32 *A, /* I/O Pointer to Symetric Square Matrix */ - opus_int M, /* I Size of Matrix */ - opus_int32 *L_Q16, /* I/O Pointer to Square Upper triangular Matrix */ - inv_D_t *inv_D /* I/O Pointer to vector holding inverted diagonal elements of D */ -); - -/* Solve Lx = b, when L is lower triangular and has ones on the diagonal */ -static OPUS_INLINE void silk_LS_SolveFirst_FIX( - const opus_int32 *L_Q16, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const opus_int32 *b, /* I b Vector */ - opus_int32 *x_Q16 /* O x Vector */ -); - -/* Solve L^t*x = b, where L is lower triangular with ones on the diagonal */ -static OPUS_INLINE void silk_LS_SolveLast_FIX( - const opus_int32 *L_Q16, /* I Pointer to Lower Triangular Matrix */ - const opus_int M, /* I Dim of Matrix equation */ - const opus_int32 *b, /* I b Vector */ - opus_int32 *x_Q16 /* O x Vector */ -); - -static OPUS_INLINE void silk_LS_divide_Q16_FIX( - opus_int32 T[], /* I/O Numenator vector */ - inv_D_t *inv_D, /* I 1 / D vector */ - opus_int M /* I dimension */ -); - -/* Solves Ax = b, assuming A is symmetric */ -void silk_solve_LDL_FIX( - opus_int32 *A, /* I Pointer to symetric square matrix A */ - opus_int M, /* I Size of matrix */ - const opus_int32 *b, /* I Pointer to b vector */ - opus_int32 *x_Q16 /* O Pointer to x solution vector */ -) -{ - VARDECL( opus_int32, L_Q16 ); - opus_int32 Y[ MAX_MATRIX_SIZE ]; - inv_D_t inv_D[ MAX_MATRIX_SIZE ]; - SAVE_STACK; - - silk_assert( M <= MAX_MATRIX_SIZE ); - ALLOC( L_Q16, M * M, opus_int32 ); - - /*************************************************** - Factorize A by LDL such that A = L*D*L', - where L is lower triangular with ones on diagonal - ****************************************************/ - silk_LDL_factorize_FIX( A, M, L_Q16, inv_D ); - - /**************************************************** - * substitute D*L'*x = Y. ie: - L*D*L'*x = b => L*Y = b <=> Y = inv(L)*b - ******************************************************/ - silk_LS_SolveFirst_FIX( L_Q16, M, b, Y ); - - /**************************************************** - D*L'*x = Y <=> L'*x = inv(D)*Y, because D is - diagonal just multiply with 1/d_i - ****************************************************/ - silk_LS_divide_Q16_FIX( Y, inv_D, M ); - - /**************************************************** - x = inv(L') * inv(D) * Y - *****************************************************/ - silk_LS_SolveLast_FIX( L_Q16, M, Y, x_Q16 ); - RESTORE_STACK; -} - -static OPUS_INLINE void silk_LDL_factorize_FIX( - opus_int32 *A, /* I/O Pointer to Symetric Square Matrix */ - opus_int M, /* I Size of Matrix */ - opus_int32 *L_Q16, /* I/O Pointer to Square Upper triangular Matrix */ - inv_D_t *inv_D /* I/O Pointer to vector holding inverted diagonal elements of D */ -) -{ - opus_int i, j, k, status, loop_count; - const opus_int32 *ptr1, *ptr2; - opus_int32 diag_min_value, tmp_32, err; - opus_int32 v_Q0[ MAX_MATRIX_SIZE ], D_Q0[ MAX_MATRIX_SIZE ]; - opus_int32 one_div_diag_Q36, one_div_diag_Q40, one_div_diag_Q48; - - silk_assert( M <= MAX_MATRIX_SIZE ); - - status = 1; - diag_min_value = silk_max_32( silk_SMMUL( silk_ADD_SAT32( A[ 0 ], A[ silk_SMULBB( M, M ) - 1 ] ), SILK_FIX_CONST( FIND_LTP_COND_FAC, 31 ) ), 1 << 9 ); - for( loop_count = 0; loop_count < M && status == 1; loop_count++ ) { - status = 0; - for( j = 0; j < M; j++ ) { - ptr1 = matrix_adr( L_Q16, j, 0, M ); - tmp_32 = 0; - for( i = 0; i < j; i++ ) { - v_Q0[ i ] = silk_SMULWW( D_Q0[ i ], ptr1[ i ] ); /* Q0 */ - tmp_32 = silk_SMLAWW( tmp_32, v_Q0[ i ], ptr1[ i ] ); /* Q0 */ - } - tmp_32 = silk_SUB32( matrix_ptr( A, j, j, M ), tmp_32 ); - - if( tmp_32 < diag_min_value ) { - tmp_32 = silk_SUB32( silk_SMULBB( loop_count + 1, diag_min_value ), tmp_32 ); - /* Matrix not positive semi-definite, or ill conditioned */ - for( i = 0; i < M; i++ ) { - matrix_ptr( A, i, i, M ) = silk_ADD32( matrix_ptr( A, i, i, M ), tmp_32 ); - } - status = 1; - break; - } - D_Q0[ j ] = tmp_32; /* always < max(Correlation) */ - - /* two-step division */ - one_div_diag_Q36 = silk_INVERSE32_varQ( tmp_32, 36 ); /* Q36 */ - one_div_diag_Q40 = silk_LSHIFT( one_div_diag_Q36, 4 ); /* Q40 */ - err = silk_SUB32( (opus_int32)1 << 24, silk_SMULWW( tmp_32, one_div_diag_Q40 ) ); /* Q24 */ - one_div_diag_Q48 = silk_SMULWW( err, one_div_diag_Q40 ); /* Q48 */ - - /* Save 1/Ds */ - inv_D[ j ].Q36_part = one_div_diag_Q36; - inv_D[ j ].Q48_part = one_div_diag_Q48; - - matrix_ptr( L_Q16, j, j, M ) = 65536; /* 1.0 in Q16 */ - ptr1 = matrix_adr( A, j, 0, M ); - ptr2 = matrix_adr( L_Q16, j + 1, 0, M ); - for( i = j + 1; i < M; i++ ) { - tmp_32 = 0; - for( k = 0; k < j; k++ ) { - tmp_32 = silk_SMLAWW( tmp_32, v_Q0[ k ], ptr2[ k ] ); /* Q0 */ - } - tmp_32 = silk_SUB32( ptr1[ i ], tmp_32 ); /* always < max(Correlation) */ - - /* tmp_32 / D_Q0[j] : Divide to Q16 */ - matrix_ptr( L_Q16, i, j, M ) = silk_ADD32( silk_SMMUL( tmp_32, one_div_diag_Q48 ), - silk_RSHIFT( silk_SMULWW( tmp_32, one_div_diag_Q36 ), 4 ) ); - - /* go to next column */ - ptr2 += M; - } - } - } - - silk_assert( status == 0 ); -} - -static OPUS_INLINE void silk_LS_divide_Q16_FIX( - opus_int32 T[], /* I/O Numenator vector */ - inv_D_t *inv_D, /* I 1 / D vector */ - opus_int M /* I dimension */ -) -{ - opus_int i; - opus_int32 tmp_32; - opus_int32 one_div_diag_Q36, one_div_diag_Q48; - - for( i = 0; i < M; i++ ) { - one_div_diag_Q36 = inv_D[ i ].Q36_part; - one_div_diag_Q48 = inv_D[ i ].Q48_part; - - tmp_32 = T[ i ]; - T[ i ] = silk_ADD32( silk_SMMUL( tmp_32, one_div_diag_Q48 ), silk_RSHIFT( silk_SMULWW( tmp_32, one_div_diag_Q36 ), 4 ) ); - } -} - -/* Solve Lx = b, when L is lower triangular and has ones on the diagonal */ -static OPUS_INLINE void silk_LS_SolveFirst_FIX( - const opus_int32 *L_Q16, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const opus_int32 *b, /* I b Vector */ - opus_int32 *x_Q16 /* O x Vector */ -) -{ - opus_int i, j; - const opus_int32 *ptr32; - opus_int32 tmp_32; - - for( i = 0; i < M; i++ ) { - ptr32 = matrix_adr( L_Q16, i, 0, M ); - tmp_32 = 0; - for( j = 0; j < i; j++ ) { - tmp_32 = silk_SMLAWW( tmp_32, ptr32[ j ], x_Q16[ j ] ); - } - x_Q16[ i ] = silk_SUB32( b[ i ], tmp_32 ); - } -} - -/* Solve L^t*x = b, where L is lower triangular with ones on the diagonal */ -static OPUS_INLINE void silk_LS_SolveLast_FIX( - const opus_int32 *L_Q16, /* I Pointer to Lower Triangular Matrix */ - const opus_int M, /* I Dim of Matrix equation */ - const opus_int32 *b, /* I b Vector */ - opus_int32 *x_Q16 /* O x Vector */ -) -{ - opus_int i, j; - const opus_int32 *ptr32; - opus_int32 tmp_32; - - for( i = M - 1; i >= 0; i-- ) { - ptr32 = matrix_adr( L_Q16, 0, i, M ); - tmp_32 = 0; - for( j = M - 1; j > i; j-- ) { - tmp_32 = silk_SMLAWW( tmp_32, ptr32[ silk_SMULBB( j, M ) ], x_Q16[ j ] ); - } - x_Q16[ i ] = silk_SUB32( b[ i ], tmp_32 ); - } -} diff --git a/TMessagesProj/jni/opus/silk/fixed/structs_FIX.h b/TMessagesProj/jni/opus/silk/fixed/structs_FIX.h index 244b4793445..2774a97b24f 100644 --- a/TMessagesProj/jni/opus/silk/fixed/structs_FIX.h +++ b/TMessagesProj/jni/opus/silk/fixed/structs_FIX.h @@ -47,31 +47,17 @@ typedef struct { opus_int32 Tilt_smth_Q16; } silk_shape_state_FIX; -/********************************/ -/* Prefilter state */ -/********************************/ -typedef struct { - opus_int16 sLTP_shp[ LTP_BUF_LENGTH ]; - opus_int32 sAR_shp[ MAX_SHAPE_LPC_ORDER + 1 ]; - opus_int sLTP_shp_buf_idx; - opus_int32 sLF_AR_shp_Q12; - opus_int32 sLF_MA_shp_Q12; - opus_int32 sHarmHP_Q2; - opus_int32 rand_seed; - opus_int lagPrev; -} silk_prefilter_state_FIX; - /********************************/ /* Encoder state FIX */ /********************************/ typedef struct { silk_encoder_state sCmn; /* Common struct, shared with floating-point code */ silk_shape_state_FIX sShape; /* Shape state */ - silk_prefilter_state_FIX sPrefilt; /* Prefilter State */ /* Buffer for find pitch and noise shape analysis */ silk_DWORD_ALIGN opus_int16 x_buf[ 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX ];/* Buffer for find pitch and noise shape analysis */ opus_int LTPCorr_Q15; /* Normalized correlation from pitch lag estimator */ + opus_int32 resNrgSmth; } silk_encoder_state_FIX; /************************/ @@ -87,11 +73,8 @@ typedef struct { /* Noise shaping parameters */ /* Testing */ - silk_DWORD_ALIGN opus_int16 AR1_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; - silk_DWORD_ALIGN opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + silk_DWORD_ALIGN opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ]; /* Packs two int16 coefficients per int32 value */ - opus_int GainsPre_Q14[ MAX_NB_SUBFR ]; - opus_int HarmBoost_Q14[ MAX_NB_SUBFR ]; opus_int Tilt_Q14[ MAX_NB_SUBFR ]; opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ]; opus_int Lambda_Q10; @@ -99,7 +82,6 @@ typedef struct { opus_int coding_quality_Q14; /* measures */ - opus_int sparseness_Q8; opus_int32 predGain_Q16; opus_int LTPredCodGain_Q7; opus_int32 ResNrg[ MAX_NB_SUBFR ]; /* Residual energy per subframe */ @@ -116,6 +98,7 @@ typedef struct { typedef struct { silk_encoder_state_FIX state_Fxx[ ENCODER_NUM_CHANNELS ]; stereo_enc_state sStereo; + opus_int32 nBitsUsedLBRR; opus_int32 nBitsExceeded; opus_int nChannelsAPI; opus_int nChannelsInternal; diff --git a/TMessagesProj/jni/opus/silk/fixed/vector_ops_FIX.c b/TMessagesProj/jni/opus/silk/fixed/vector_ops_FIX.c index 509c8b35a11..d94980014f6 100644 --- a/TMessagesProj/jni/opus/silk/fixed/vector_ops_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/vector_ops_FIX.c @@ -30,6 +30,7 @@ POSSIBILITY OF SUCH DAMAGE. #endif #include "SigProc_FIX.h" +#include "pitch.h" /* Copy and multiply a vector by a constant */ void silk_scale_copy_vector16( @@ -70,18 +71,23 @@ void silk_scale_vector32_Q26_lshift_18( opus_int32 silk_inner_prod_aligned( const opus_int16 *const inVec1, /* I input vector 1 */ const opus_int16 *const inVec2, /* I input vector 2 */ - const opus_int len /* I vector lengths */ + const opus_int len, /* I vector lengths */ + int arch /* I Run-time architecture */ ) { +#ifdef FIXED_POINT + return celt_inner_prod(inVec1, inVec2, len, arch); +#else opus_int i; opus_int32 sum = 0; for( i = 0; i < len; i++ ) { sum = silk_SMLABB( sum, inVec1[ i ], inVec2[ i ] ); } return sum; +#endif } -opus_int64 silk_inner_prod16_aligned_64( +opus_int64 silk_inner_prod16_aligned_64_c( const opus_int16 *inVec1, /* I input vector 1 */ const opus_int16 *inVec2, /* I input vector 2 */ const opus_int len /* I vector lengths */ diff --git a/TMessagesProj/jni/opus/silk/fixed/warped_autocorrelation_FIX.c b/TMessagesProj/jni/opus/silk/fixed/warped_autocorrelation_FIX.c index a4a579b10d7..af164029756 100644 --- a/TMessagesProj/jni/opus/silk/fixed/warped_autocorrelation_FIX.c +++ b/TMessagesProj/jni/opus/silk/fixed/warped_autocorrelation_FIX.c @@ -32,8 +32,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "main_FIX.h" #define QC 10 -#define QS 14 +#define QS 13 +#if defined(MIPSr1_ASM) +#include "mips/warped_autocorrelation_FIX_mipsr1.h" +#endif + + +#ifndef OVERRIDE_silk_warped_autocorrelation_FIX /* Autocorrelations for a warped frequency axis */ void silk_warped_autocorrelation_FIX( opus_int32 *corr, /* O Result [order + 1] */ @@ -86,3 +92,4 @@ void silk_warped_autocorrelation_FIX( } silk_assert( corr_QC[ 0 ] >= 0 ); /* If breaking, decrease QC*/ } +#endif /* OVERRIDE_silk_warped_autocorrelation_FIX */ diff --git a/TMessagesProj/jni/opus/silk/fixed/x86/burg_modified_FIX_sse.c b/TMessagesProj/jni/opus/silk/fixed/x86/burg_modified_FIX_sse.c new file mode 100644 index 00000000000..3c3583c5fc3 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/fixed/x86/burg_modified_FIX_sse.c @@ -0,0 +1,377 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "SigProc_FIX.h" +#include "define.h" +#include "tuning_parameters.h" +#include "pitch.h" +#include "celt/x86/x86cpu.h" + +#define MAX_FRAME_SIZE 384 /* subfr_length * nb_subfr = ( 0.005 * 16000 + 16 ) * 4 = 384 */ + +#define QA 25 +#define N_BITS_HEAD_ROOM 2 +#define MIN_RSHIFTS -16 +#define MAX_RSHIFTS (32 - QA) + +/* Compute reflection coefficients from input signal */ +void silk_burg_modified_sse4_1( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +) +{ + opus_int k, n, s, lz, rshifts, rshifts_extra, reached_max_gain; + opus_int32 C0, num, nrg, rc_Q31, invGain_Q30, Atmp_QA, Atmp1, tmp1, tmp2, x1, x2; + const opus_int16 *x_ptr; + opus_int32 C_first_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 C_last_row[ SILK_MAX_ORDER_LPC ]; + opus_int32 Af_QA[ SILK_MAX_ORDER_LPC ]; + opus_int32 CAf[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 CAb[ SILK_MAX_ORDER_LPC + 1 ]; + opus_int32 xcorr[ SILK_MAX_ORDER_LPC ]; + + __m128i FIRST_3210, LAST_3210, ATMP_3210, TMP1_3210, TMP2_3210, T1_3210, T2_3210, PTR_3210, SUBFR_3210, X1_3210, X2_3210; + __m128i CONST1 = _mm_set1_epi32(1); + + silk_assert( subfr_length * nb_subfr <= MAX_FRAME_SIZE ); + + /* Compute autocorrelations, added over subframes */ + silk_sum_sqr_shift( &C0, &rshifts, x, nb_subfr * subfr_length ); + if( rshifts > MAX_RSHIFTS ) { + C0 = silk_LSHIFT32( C0, rshifts - MAX_RSHIFTS ); + silk_assert( C0 > 0 ); + rshifts = MAX_RSHIFTS; + } else { + lz = silk_CLZ32( C0 ) - 1; + rshifts_extra = N_BITS_HEAD_ROOM - lz; + if( rshifts_extra > 0 ) { + rshifts_extra = silk_min( rshifts_extra, MAX_RSHIFTS - rshifts ); + C0 = silk_RSHIFT32( C0, rshifts_extra ); + } else { + rshifts_extra = silk_max( rshifts_extra, MIN_RSHIFTS - rshifts ); + C0 = silk_LSHIFT32( C0, -rshifts_extra ); + } + rshifts += rshifts_extra; + } + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + silk_memset( C_first_row, 0, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += (opus_int32)silk_RSHIFT64( + silk_inner_prod16_aligned_64( x_ptr, x_ptr + n, subfr_length - n, arch ), rshifts ); + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + int i; + opus_int32 d; + x_ptr = x + s * subfr_length; + celt_pitch_xcorr(x_ptr, x_ptr + 1, xcorr, subfr_length - D, D, arch ); + for( n = 1; n < D + 1; n++ ) { + for ( i = n + subfr_length - D, d = 0; i < subfr_length; i++ ) + d = MAC16_16( d, x_ptr[ i ], x_ptr[ i - n ] ); + xcorr[ n - 1 ] += d; + } + for( n = 1; n < D + 1; n++ ) { + C_first_row[ n - 1 ] += silk_LSHIFT32( xcorr[ n - 1 ], -rshifts ); + } + } + } + silk_memcpy( C_last_row, C_first_row, SILK_MAX_ORDER_LPC * sizeof( opus_int32 ) ); + + /* Initialize */ + CAb[ 0 ] = CAf[ 0 ] = C0 + silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ) + 1; /* Q(-rshifts) */ + + invGain_Q30 = (opus_int32)1 << 30; + reached_max_gain = 0; + for( n = 0; n < D; n++ ) { + /* Update first row of correlation matrix (without first element) */ + /* Update last row of correlation matrix (without last element, stored in reversed order) */ + /* Update C * Af */ + /* Update C * flipud(Af) (stored in reversed order) */ + if( rshifts > -2 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], 16 - rshifts ); /* Q(16-rshifts) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 16 - rshifts ); /* Q(16-rshifts) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], QA - 16 ); /* Q(QA-16) */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], QA - 16 ); /* Q(QA-16) */ + for( k = 0; k < n; k++ ) { + C_first_row[ k ] = silk_SMLAWB( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_SMLAWB( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp_QA = Af_QA[ k ]; + tmp1 = silk_SMLAWB( tmp1, Atmp_QA, x_ptr[ n - k - 1 ] ); /* Q(QA-16) */ + tmp2 = silk_SMLAWB( tmp2, Atmp_QA, x_ptr[ subfr_length - n + k ] ); /* Q(QA-16) */ + } + tmp1 = silk_LSHIFT32( -tmp1, 32 - QA - rshifts ); /* Q(16-rshifts) */ + tmp2 = silk_LSHIFT32( -tmp2, 32 - QA - rshifts ); /* Q(16-rshifts) */ + for( k = 0; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWB( CAf[ k ], tmp1, x_ptr[ n - k ] ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWB( CAb[ k ], tmp2, x_ptr[ subfr_length - n + k - 1 ] ); /* Q( -rshift ) */ + } + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + x1 = -silk_LSHIFT32( (opus_int32)x_ptr[ n ], -rshifts ); /* Q( -rshifts ) */ + x2 = -silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], -rshifts ); /* Q( -rshifts ) */ + tmp1 = silk_LSHIFT32( (opus_int32)x_ptr[ n ], 17 ); /* Q17 */ + tmp2 = silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n - 1 ], 17 ); /* Q17 */ + + X1_3210 = _mm_set1_epi32( x1 ); + X2_3210 = _mm_set1_epi32( x2 ); + TMP1_3210 = _mm_setzero_si128(); + TMP2_3210 = _mm_setzero_si128(); + for( k = 0; k < n - 3; k += 4 ) { + PTR_3210 = OP_CVTEPI16_EPI32_M64( &x_ptr[ n - k - 1 - 3 ] ); + SUBFR_3210 = OP_CVTEPI16_EPI32_M64( &x_ptr[ subfr_length - n + k ] ); + FIRST_3210 = _mm_loadu_si128( (__m128i *)&C_first_row[ k ] ); + PTR_3210 = _mm_shuffle_epi32( PTR_3210, _MM_SHUFFLE( 0, 1, 2, 3 ) ); + LAST_3210 = _mm_loadu_si128( (__m128i *)&C_last_row[ k ] ); + ATMP_3210 = _mm_loadu_si128( (__m128i *)&Af_QA[ k ] ); + + T1_3210 = _mm_mullo_epi32( PTR_3210, X1_3210 ); + T2_3210 = _mm_mullo_epi32( SUBFR_3210, X2_3210 ); + + ATMP_3210 = _mm_srai_epi32( ATMP_3210, 7 ); + ATMP_3210 = _mm_add_epi32( ATMP_3210, CONST1 ); + ATMP_3210 = _mm_srai_epi32( ATMP_3210, 1 ); + + FIRST_3210 = _mm_add_epi32( FIRST_3210, T1_3210 ); + LAST_3210 = _mm_add_epi32( LAST_3210, T2_3210 ); + + PTR_3210 = _mm_mullo_epi32( ATMP_3210, PTR_3210 ); + SUBFR_3210 = _mm_mullo_epi32( ATMP_3210, SUBFR_3210 ); + + _mm_storeu_si128( (__m128i *)&C_first_row[ k ], FIRST_3210 ); + _mm_storeu_si128( (__m128i *)&C_last_row[ k ], LAST_3210 ); + + TMP1_3210 = _mm_add_epi32( TMP1_3210, PTR_3210 ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, SUBFR_3210 ); + } + + TMP1_3210 = _mm_add_epi32( TMP1_3210, _mm_unpackhi_epi64(TMP1_3210, TMP1_3210 ) ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, _mm_unpackhi_epi64(TMP2_3210, TMP2_3210 ) ); + TMP1_3210 = _mm_add_epi32( TMP1_3210, _mm_shufflelo_epi16(TMP1_3210, 0x0E ) ); + TMP2_3210 = _mm_add_epi32( TMP2_3210, _mm_shufflelo_epi16(TMP2_3210, 0x0E ) ); + + tmp1 += _mm_cvtsi128_si32( TMP1_3210 ); + tmp2 += _mm_cvtsi128_si32( TMP2_3210 ); + + for( ; k < n; k++ ) { + C_first_row[ k ] = silk_MLA( C_first_row[ k ], x1, x_ptr[ n - k - 1 ] ); /* Q( -rshifts ) */ + C_last_row[ k ] = silk_MLA( C_last_row[ k ], x2, x_ptr[ subfr_length - n + k ] ); /* Q( -rshifts ) */ + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 17 ); /* Q17 */ + tmp1 = silk_MLA( tmp1, x_ptr[ n - k - 1 ], Atmp1 ); /* Q17 */ + tmp2 = silk_MLA( tmp2, x_ptr[ subfr_length - n + k ], Atmp1 ); /* Q17 */ + } + + tmp1 = -tmp1; /* Q17 */ + tmp2 = -tmp2; /* Q17 */ + + { + __m128i xmm_tmp1, xmm_tmp2; + __m128i xmm_x_ptr_n_k_x2x0, xmm_x_ptr_n_k_x3x1; + __m128i xmm_x_ptr_sub_x2x0, xmm_x_ptr_sub_x3x1; + + xmm_tmp1 = _mm_set1_epi32( tmp1 ); + xmm_tmp2 = _mm_set1_epi32( tmp2 ); + + for( k = 0; k <= n - 3; k += 4 ) { + xmm_x_ptr_n_k_x2x0 = OP_CVTEPI16_EPI32_M64( &x_ptr[ n - k - 3 ] ); + xmm_x_ptr_sub_x2x0 = OP_CVTEPI16_EPI32_M64( &x_ptr[ subfr_length - n + k - 1 ] ); + + xmm_x_ptr_n_k_x2x0 = _mm_shuffle_epi32( xmm_x_ptr_n_k_x2x0, _MM_SHUFFLE( 0, 1, 2, 3 ) ); + + xmm_x_ptr_n_k_x2x0 = _mm_slli_epi32( xmm_x_ptr_n_k_x2x0, -rshifts - 1 ); + xmm_x_ptr_sub_x2x0 = _mm_slli_epi32( xmm_x_ptr_sub_x2x0, -rshifts - 1 ); + + /* equal shift right 4 bytes, xmm_x_ptr_n_k_x3x1 = _mm_srli_si128(xmm_x_ptr_n_k_x2x0, 4)*/ + xmm_x_ptr_n_k_x3x1 = _mm_shuffle_epi32( xmm_x_ptr_n_k_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + xmm_x_ptr_sub_x3x1 = _mm_shuffle_epi32( xmm_x_ptr_sub_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x_ptr_n_k_x2x0 = _mm_mul_epi32( xmm_x_ptr_n_k_x2x0, xmm_tmp1 ); + xmm_x_ptr_n_k_x3x1 = _mm_mul_epi32( xmm_x_ptr_n_k_x3x1, xmm_tmp1 ); + xmm_x_ptr_sub_x2x0 = _mm_mul_epi32( xmm_x_ptr_sub_x2x0, xmm_tmp2 ); + xmm_x_ptr_sub_x3x1 = _mm_mul_epi32( xmm_x_ptr_sub_x3x1, xmm_tmp2 ); + + xmm_x_ptr_n_k_x2x0 = _mm_srli_epi64( xmm_x_ptr_n_k_x2x0, 16 ); + xmm_x_ptr_n_k_x3x1 = _mm_slli_epi64( xmm_x_ptr_n_k_x3x1, 16 ); + xmm_x_ptr_sub_x2x0 = _mm_srli_epi64( xmm_x_ptr_sub_x2x0, 16 ); + xmm_x_ptr_sub_x3x1 = _mm_slli_epi64( xmm_x_ptr_sub_x3x1, 16 ); + + xmm_x_ptr_n_k_x2x0 = _mm_blend_epi16( xmm_x_ptr_n_k_x2x0, xmm_x_ptr_n_k_x3x1, 0xCC ); + xmm_x_ptr_sub_x2x0 = _mm_blend_epi16( xmm_x_ptr_sub_x2x0, xmm_x_ptr_sub_x3x1, 0xCC ); + + X1_3210 = _mm_loadu_si128( (__m128i *)&CAf[ k ] ); + PTR_3210 = _mm_loadu_si128( (__m128i *)&CAb[ k ] ); + + X1_3210 = _mm_add_epi32( X1_3210, xmm_x_ptr_n_k_x2x0 ); + PTR_3210 = _mm_add_epi32( PTR_3210, xmm_x_ptr_sub_x2x0 ); + + _mm_storeu_si128( (__m128i *)&CAf[ k ], X1_3210 ); + _mm_storeu_si128( (__m128i *)&CAb[ k ], PTR_3210 ); + } + + for( ; k <= n; k++ ) { + CAf[ k ] = silk_SMLAWW( CAf[ k ], tmp1, + silk_LSHIFT32( (opus_int32)x_ptr[ n - k ], -rshifts - 1 ) ); /* Q( -rshift ) */ + CAb[ k ] = silk_SMLAWW( CAb[ k ], tmp2, + silk_LSHIFT32( (opus_int32)x_ptr[ subfr_length - n + k - 1 ], -rshifts - 1 ) ); /* Q( -rshift ) */ + } + } + } + } + + /* Calculate nominator and denominator for the next order reflection (parcor) coefficient */ + tmp1 = C_first_row[ n ]; /* Q( -rshifts ) */ + tmp2 = C_last_row[ n ]; /* Q( -rshifts ) */ + num = 0; /* Q( -rshifts ) */ + nrg = silk_ADD32( CAb[ 0 ], CAf[ 0 ] ); /* Q( 1-rshifts ) */ + for( k = 0; k < n; k++ ) { + Atmp_QA = Af_QA[ k ]; + lz = silk_CLZ32( silk_abs( Atmp_QA ) ) - 1; + lz = silk_min( 32 - QA, lz ); + Atmp1 = silk_LSHIFT32( Atmp_QA, lz ); /* Q( QA + lz ) */ + + tmp1 = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( C_last_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + tmp2 = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( C_first_row[ n - k - 1 ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + num = silk_ADD_LSHIFT32( num, silk_SMMUL( CAb[ n - k ], Atmp1 ), 32 - QA - lz ); /* Q( -rshifts ) */ + nrg = silk_ADD_LSHIFT32( nrg, silk_SMMUL( silk_ADD32( CAb[ k + 1 ], CAf[ k + 1 ] ), + Atmp1 ), 32 - QA - lz ); /* Q( 1-rshifts ) */ + } + CAf[ n + 1 ] = tmp1; /* Q( -rshifts ) */ + CAb[ n + 1 ] = tmp2; /* Q( -rshifts ) */ + num = silk_ADD32( num, tmp2 ); /* Q( -rshifts ) */ + num = silk_LSHIFT32( -num, 1 ); /* Q( 1-rshifts ) */ + + /* Calculate the next order reflection (parcor) coefficient */ + if( silk_abs( num ) < nrg ) { + rc_Q31 = silk_DIV32_varQ( num, nrg, 31 ); + } else { + rc_Q31 = ( num > 0 ) ? silk_int32_MAX : silk_int32_MIN; + } + + /* Update inverse prediction gain */ + tmp1 = ( (opus_int32)1 << 30 ) - silk_SMMUL( rc_Q31, rc_Q31 ); + tmp1 = silk_LSHIFT( silk_SMMUL( invGain_Q30, tmp1 ), 2 ); + if( tmp1 <= minInvGain_Q30 ) { + /* Max prediction gain exceeded; set reflection coefficient such that max prediction gain is exactly hit */ + tmp2 = ( (opus_int32)1 << 30 ) - silk_DIV32_varQ( minInvGain_Q30, invGain_Q30, 30 ); /* Q30 */ + rc_Q31 = silk_SQRT_APPROX( tmp2 ); /* Q15 */ + if( rc_Q31 > 0 ) { + /* Newton-Raphson iteration */ + rc_Q31 = silk_RSHIFT32( rc_Q31 + silk_DIV32( tmp2, rc_Q31 ), 1 ); /* Q15 */ + rc_Q31 = silk_LSHIFT32( rc_Q31, 16 ); /* Q31 */ + if( num < 0 ) { + /* Ensure adjusted reflection coefficients has the original sign */ + rc_Q31 = -rc_Q31; + } + } + invGain_Q30 = minInvGain_Q30; + reached_max_gain = 1; + } else { + invGain_Q30 = tmp1; + } + + /* Update the AR coefficients */ + for( k = 0; k < (n + 1) >> 1; k++ ) { + tmp1 = Af_QA[ k ]; /* QA */ + tmp2 = Af_QA[ n - k - 1 ]; /* QA */ + Af_QA[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* QA */ + Af_QA[ n - k - 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* QA */ + } + Af_QA[ n ] = silk_RSHIFT32( rc_Q31, 31 - QA ); /* QA */ + + if( reached_max_gain ) { + /* Reached max prediction gain; set remaining coefficients to zero and exit loop */ + for( k = n + 1; k < D; k++ ) { + Af_QA[ k ] = 0; + } + break; + } + + /* Update C * Af and C * Ab */ + for( k = 0; k <= n + 1; k++ ) { + tmp1 = CAf[ k ]; /* Q( -rshifts ) */ + tmp2 = CAb[ n - k + 1 ]; /* Q( -rshifts ) */ + CAf[ k ] = silk_ADD_LSHIFT32( tmp1, silk_SMMUL( tmp2, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + CAb[ n - k + 1 ] = silk_ADD_LSHIFT32( tmp2, silk_SMMUL( tmp1, rc_Q31 ), 1 ); /* Q( -rshifts ) */ + } + } + + if( reached_max_gain ) { + for( k = 0; k < D; k++ ) { + /* Scale coefficients */ + A_Q16[ k ] = -silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); + } + /* Subtract energy of preceding samples from C0 */ + if( rshifts > 0 ) { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= (opus_int32)silk_RSHIFT64( silk_inner_prod16_aligned_64( x_ptr, x_ptr, D, arch ), rshifts ); + } + } else { + for( s = 0; s < nb_subfr; s++ ) { + x_ptr = x + s * subfr_length; + C0 -= silk_LSHIFT32( silk_inner_prod_aligned( x_ptr, x_ptr, D, arch ), -rshifts ); + } + } + /* Approximate residual energy */ + *res_nrg = silk_LSHIFT( silk_SMMUL( invGain_Q30, C0 ), 2 ); + *res_nrg_Q = -rshifts; + } else { + /* Return residual energy */ + nrg = CAf[ 0 ]; /* Q( -rshifts ) */ + tmp1 = (opus_int32)1 << 16; /* Q16 */ + for( k = 0; k < D; k++ ) { + Atmp1 = silk_RSHIFT_ROUND( Af_QA[ k ], QA - 16 ); /* Q16 */ + nrg = silk_SMLAWW( nrg, CAf[ k + 1 ], Atmp1 ); /* Q( -rshifts ) */ + tmp1 = silk_SMLAWW( tmp1, Atmp1, Atmp1 ); /* Q16 */ + A_Q16[ k ] = -Atmp1; + } + *res_nrg = silk_SMLAWW( nrg, silk_SMMUL( SILK_FIX_CONST( FIND_LPC_COND_FAC, 32 ), C0 ), -tmp1 );/* Q( -rshifts ) */ + *res_nrg_Q = -rshifts; + } +} diff --git a/TMessagesProj/jni/opus/silk/fixed/x86/vector_ops_FIX_sse.c b/TMessagesProj/jni/opus/silk/fixed/x86/vector_ops_FIX_sse.c new file mode 100644 index 00000000000..c1e90564d0e --- /dev/null +++ b/TMessagesProj/jni/opus/silk/fixed/x86/vector_ops_FIX_sse.c @@ -0,0 +1,88 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" + +#include "SigProc_FIX.h" +#include "pitch.h" + +opus_int64 silk_inner_prod16_aligned_64_sse4_1( + const opus_int16 *inVec1, /* I input vector 1 */ + const opus_int16 *inVec2, /* I input vector 2 */ + const opus_int len /* I vector lengths */ +) +{ + opus_int i, dataSize8; + opus_int64 sum; + + __m128i xmm_tempa; + __m128i inVec1_76543210, acc1; + __m128i inVec2_76543210, acc2; + + sum = 0; + dataSize8 = len & ~7; + + acc1 = _mm_setzero_si128(); + acc2 = _mm_setzero_si128(); + + for( i = 0; i < dataSize8; i += 8 ) { + inVec1_76543210 = _mm_loadu_si128( (__m128i *)(&inVec1[i + 0] ) ); + inVec2_76543210 = _mm_loadu_si128( (__m128i *)(&inVec2[i + 0] ) ); + + /* only when all 4 operands are -32768 (0x8000), this results in wrap around */ + inVec1_76543210 = _mm_madd_epi16( inVec1_76543210, inVec2_76543210 ); + + xmm_tempa = _mm_cvtepi32_epi64( inVec1_76543210 ); + /* equal shift right 8 bytes */ + inVec1_76543210 = _mm_shuffle_epi32( inVec1_76543210, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + inVec1_76543210 = _mm_cvtepi32_epi64( inVec1_76543210 ); + + acc1 = _mm_add_epi64( acc1, xmm_tempa ); + acc2 = _mm_add_epi64( acc2, inVec1_76543210 ); + } + + acc1 = _mm_add_epi64( acc1, acc2 ); + + /* equal shift right 8 bytes */ + acc2 = _mm_shuffle_epi32( acc1, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + acc1 = _mm_add_epi64( acc1, acc2 ); + + _mm_storel_epi64( (__m128i *)&sum, acc1 ); + + for( ; i < len; i++ ) { + sum = silk_SMLABB( sum, inVec1[ i ], inVec2[ i ] ); + } + + return sum; +} diff --git a/TMessagesProj/jni/opus/silk/float/LPC_inv_pred_gain_FLP.c b/TMessagesProj/jni/opus/silk/float/LPC_inv_pred_gain_FLP.c index 25178bacdde..2be2122d614 100644 --- a/TMessagesProj/jni/opus/silk/float/LPC_inv_pred_gain_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/LPC_inv_pred_gain_FLP.c @@ -31,8 +31,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "SigProc_FIX.h" #include "SigProc_FLP.h" - -#define RC_THRESHOLD 0.9999f +#include "define.h" /* compute inverse of LPC prediction gain, and */ /* test if LPC coefficients are stable (all poles within unit circle) */ @@ -43,34 +42,32 @@ silk_float silk_LPC_inverse_pred_gain_FLP( /* O return inverse prediction ga ) { opus_int k, n; - double invGain, rc, rc_mult1, rc_mult2; - silk_float Atmp[ 2 ][ SILK_MAX_ORDER_LPC ]; - silk_float *Aold, *Anew; + double invGain, rc, rc_mult1, rc_mult2, tmp1, tmp2; + silk_float Atmp[ SILK_MAX_ORDER_LPC ]; - Anew = Atmp[ order & 1 ]; - silk_memcpy( Anew, A, order * sizeof(silk_float) ); + silk_memcpy( Atmp, A, order * sizeof(silk_float) ); invGain = 1.0; for( k = order - 1; k > 0; k-- ) { - rc = -Anew[ k ]; - if( rc > RC_THRESHOLD || rc < -RC_THRESHOLD ) { + rc = -Atmp[ k ]; + rc_mult1 = 1.0f - rc * rc; + invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { return 0.0f; } - rc_mult1 = 1.0f - rc * rc; rc_mult2 = 1.0f / rc_mult1; - invGain *= rc_mult1; - /* swap pointers */ - Aold = Anew; - Anew = Atmp[ k & 1 ]; - for( n = 0; n < k; n++ ) { - Anew[ n ] = (silk_float)( ( Aold[ n ] - Aold[ k - n - 1 ] * rc ) * rc_mult2 ); + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = Atmp[ n ]; + tmp2 = Atmp[ k - n - 1 ]; + Atmp[ n ] = (silk_float)( ( tmp1 - tmp2 * rc ) * rc_mult2 ); + Atmp[ k - n - 1 ] = (silk_float)( ( tmp2 - tmp1 * rc ) * rc_mult2 ); } } - rc = -Anew[ 0 ]; - if( rc > RC_THRESHOLD || rc < -RC_THRESHOLD ) { - return 0.0f; - } + rc = -Atmp[ 0 ]; rc_mult1 = 1.0f - rc * rc; invGain *= rc_mult1; + if( invGain * MAX_PREDICTION_POWER_GAIN < 1.0f ) { + return 0.0f; + } return (silk_float)invGain; } diff --git a/TMessagesProj/jni/opus/silk/float/SigProc_FLP.h b/TMessagesProj/jni/opus/silk/float/SigProc_FLP.h index f0cb3733be6..953de8b09e3 100644 --- a/TMessagesProj/jni/opus/silk/float/SigProc_FLP.h +++ b/TMessagesProj/jni/opus/silk/float/SigProc_FLP.h @@ -68,13 +68,6 @@ void silk_k2a_FLP( opus_int32 order /* I prediction order */ ); -/* Solve the normal equations using the Levinson-Durbin recursion */ -silk_float silk_levinsondurbin_FLP( /* O prediction error energy */ - silk_float A[], /* O prediction coefficients [order] */ - const silk_float corr[], /* I input auto-correlations [order + 1] */ - const opus_int order /* I prediction order */ -); - /* compute autocorrelation */ void silk_autocorrelation_FLP( silk_float *results, /* O result (length correlationCount) */ diff --git a/TMessagesProj/jni/opus/silk/float/encode_frame_FLP.c b/TMessagesProj/jni/opus/silk/float/encode_frame_FLP.c index d54e2686e55..c3ad50a9f41 100644 --- a/TMessagesProj/jni/opus/silk/float/encode_frame_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/encode_frame_FLP.c @@ -29,6 +29,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "config.h" #endif +#include #include "main_FLP.h" #include "tuning_parameters.h" @@ -47,7 +48,7 @@ void silk_encode_do_VAD_FLP( /****************************/ /* Voice Activity Detection */ /****************************/ - silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1 ); + silk_VAD_GetSA_Q8( &psEnc->sCmn, psEnc->sCmn.inputBuf + 1, psEnc->sCmn.arch ); /**************************************************/ /* Convert speech activity into VAD and DTX flags */ @@ -85,7 +86,6 @@ opus_int silk_encode_frame_FLP( silk_encoder_control_FLP sEncCtrl; opus_int i, iter, maxIter, found_upper, found_lower, ret = 0; silk_float *x_frame, *res_pitch_frame; - silk_float xfw[ MAX_FRAME_LENGTH ]; silk_float res_pitch[ 2 * MAX_FRAME_LENGTH + LA_PITCH_MAX ]; ec_enc sRangeEnc_copy, sRangeEnc_copy2; silk_nsq_state sNSQ_copy, sNSQ_copy2; @@ -97,6 +97,9 @@ opus_int silk_encode_frame_FLP( opus_int8 LastGainIndex_copy2; opus_int32 pGains_Q16[ MAX_NB_SUBFR ]; opus_uint8 ec_buf_copy[ 1275 ]; + opus_int gain_lock[ MAX_NB_SUBFR ] = {0}; + opus_int16 best_gain_mult[ MAX_NB_SUBFR ]; + opus_int best_sum[ MAX_NB_SUBFR ]; /* This is totally unnecessary but many compilers (including gcc) are too dumb to realise it */ LastGainIndex_copy2 = nBits_lower = nBits_upper = gainMult_lower = gainMult_upper = 0; @@ -139,22 +142,17 @@ opus_int silk_encode_frame_FLP( /***************************************************/ /* Find linear prediction coefficients (LPC + LTP) */ /***************************************************/ - silk_find_pred_coefs_FLP( psEnc, &sEncCtrl, res_pitch, x_frame, condCoding ); + silk_find_pred_coefs_FLP( psEnc, &sEncCtrl, res_pitch_frame, x_frame, condCoding ); /****************************************/ /* Process gains */ /****************************************/ silk_process_gains_FLP( psEnc, &sEncCtrl, condCoding ); - /*****************************************/ - /* Prefiltering for noise shaper */ - /*****************************************/ - silk_prefilter_FLP( psEnc, &sEncCtrl, xfw, x_frame ); - /****************************************/ /* Low Bitrate Redundant Encoding */ /****************************************/ - silk_LBRR_encode_FLP( psEnc, &sEncCtrl, xfw, condCoding ); + silk_LBRR_encode_FLP( psEnc, &sEncCtrl, x_frame, condCoding ); /* Loop over quantizer and entroy coding to control bitrate */ maxIter = 6; @@ -188,7 +186,11 @@ opus_int silk_encode_frame_FLP( /*****************************************/ /* Noise shaping quantization */ /*****************************************/ - silk_NSQ_wrapper_FLP( psEnc, &sEncCtrl, &psEnc->sCmn.indices, &psEnc->sCmn.sNSQ, psEnc->sCmn.pulses, xfw ); + silk_NSQ_wrapper_FLP( psEnc, &sEncCtrl, &psEnc->sCmn.indices, &psEnc->sCmn.sNSQ, psEnc->sCmn.pulses, x_frame ); + + if ( iter == maxIter && !found_lower ) { + silk_memcpy( &sRangeEnc_copy2, psRangeEnc, sizeof( ec_enc ) ); + } /****************************************/ /* Encode Parameters */ @@ -203,6 +205,33 @@ opus_int silk_encode_frame_FLP( nBits = ec_tell( psRangeEnc ); + /* If we still bust after the last iteration, do some damage control. */ + if ( iter == maxIter && !found_lower && nBits > maxBits ) { + silk_memcpy( psRangeEnc, &sRangeEnc_copy2, sizeof( ec_enc ) ); + + /* Keep gains the same as the last frame. */ + psEnc->sShape.LastGainIndex = sEncCtrl.lastGainIndexPrev; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + psEnc->sCmn.indices.GainsIndices[ i ] = 4; + } + if (condCoding != CODE_CONDITIONALLY) { + psEnc->sCmn.indices.GainsIndices[ 0 ] = sEncCtrl.lastGainIndexPrev; + } + psEnc->sCmn.ec_prevLagIndex = ec_prevLagIndex_copy; + psEnc->sCmn.ec_prevSignalType = ec_prevSignalType_copy; + /* Clear all pulses. */ + for ( i = 0; i < psEnc->sCmn.frame_length; i++ ) { + psEnc->sCmn.pulses[ i ] = 0; + } + + silk_encode_indices( &psEnc->sCmn, psRangeEnc, psEnc->sCmn.nFramesEncoded, 0, condCoding ); + + silk_encode_pulses( psRangeEnc, psEnc->sCmn.indices.signalType, psEnc->sCmn.indices.quantOffsetType, + psEnc->sCmn.pulses, psEnc->sCmn.frame_length ); + + nBits = ec_tell( psRangeEnc ); + } + if( useCBR == 0 && iter == 0 && nBits <= maxBits ) { break; } @@ -223,7 +252,9 @@ opus_int silk_encode_frame_FLP( if( nBits > maxBits ) { if( found_lower == 0 && iter >= 2 ) { /* Adjust the quantizer's rate/distortion tradeoff and discard previous "upper" results */ - sEncCtrl.Lambda *= 1.5f; + sEncCtrl.Lambda = silk_max_float(sEncCtrl.Lambda*1.5f, 1.5f); + /* Reducing dithering can help us hit the target. */ + psEnc->sCmn.indices.quantOffsetType = 0; found_upper = 0; gainsID_upper = -1; } else { @@ -250,15 +281,34 @@ opus_int silk_encode_frame_FLP( break; } + if ( !found_lower && nBits > maxBits ) { + int j; + for ( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { + int sum=0; + for ( j = i*psEnc->sCmn.subfr_length; j < (i+1)*psEnc->sCmn.subfr_length; j++ ) { + sum += abs( psEnc->sCmn.pulses[j] ); + } + if ( iter == 0 || (sum < best_sum[i] && !gain_lock[i]) ) { + best_sum[i] = sum; + best_gain_mult[i] = gainMult_Q8; + } else { + gain_lock[i] = 1; + } + } + } if( ( found_lower & found_upper ) == 0 ) { /* Adjust gain according to high-rate rate/distortion curve */ - opus_int32 gain_factor_Q16; - gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) ); - gain_factor_Q16 = silk_min_32( gain_factor_Q16, SILK_FIX_CONST( 2, 16 ) ); if( nBits > maxBits ) { - gain_factor_Q16 = silk_max_32( gain_factor_Q16, SILK_FIX_CONST( 1.3, 16 ) ); + if (gainMult_Q8 < 16384) { + gainMult_Q8 *= 2; + } else { + gainMult_Q8 = 32767; + } + } else { + opus_int32 gain_factor_Q16; + gain_factor_Q16 = silk_log2lin( silk_LSHIFT( nBits - maxBits, 7 ) / psEnc->sCmn.frame_length + SILK_FIX_CONST( 16, 7 ) ); + gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 ); } - gainMult_Q8 = silk_SMULWB( gain_factor_Q16, gainMult_Q8 ); } else { /* Adjust gain by interpolating */ gainMult_Q8 = gainMult_lower + ( ( gainMult_upper - gainMult_lower ) * ( maxBits - nBits_lower ) ) / ( nBits_upper - nBits_lower ); @@ -272,7 +322,13 @@ opus_int silk_encode_frame_FLP( } for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { - pGains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], gainMult_Q8 ), 8 ); + opus_int16 tmp; + if ( gain_lock[i] ) { + tmp = best_gain_mult[i]; + } else { + tmp = gainMult_Q8; + } + pGains_Q16[ i ] = silk_LSHIFT_SAT32( silk_SMULWB( sEncCtrl.GainsUnq_Q16[ i ], tmp ), 8 ); } /* Quantize gains */ diff --git a/TMessagesProj/jni/opus/silk/float/energy_FLP.c b/TMessagesProj/jni/opus/silk/float/energy_FLP.c index 24b8179f9e3..7bc7173c9cf 100644 --- a/TMessagesProj/jni/opus/silk/float/energy_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/energy_FLP.c @@ -37,13 +37,12 @@ double silk_energy_FLP( opus_int dataSize ) { - opus_int i, dataSize4; + opus_int i; double result; /* 4x unrolled loop */ result = 0.0; - dataSize4 = dataSize & 0xFFFC; - for( i = 0; i < dataSize4; i += 4 ) { + for( i = 0; i < dataSize - 3; i += 4 ) { result += data[ i + 0 ] * (double)data[ i + 0 ] + data[ i + 1 ] * (double)data[ i + 1 ] + data[ i + 2 ] * (double)data[ i + 2 ] + diff --git a/TMessagesProj/jni/opus/silk/float/find_LPC_FLP.c b/TMessagesProj/jni/opus/silk/float/find_LPC_FLP.c index 61c1ad9554b..fcfe1c3681e 100644 --- a/TMessagesProj/jni/opus/silk/float/find_LPC_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/find_LPC_FLP.c @@ -99,6 +99,6 @@ void silk_find_LPC_FLP( silk_A2NLSF_FLP( NLSF_Q15, a, psEncC->predictLPCOrder ); } - silk_assert( psEncC->indices.NLSFInterpCoef_Q2 == 4 || + silk_assert( psEncC->indices.NLSFInterpCoef_Q2 == 4 || ( psEncC->useInterpolatedNLSFs && !psEncC->first_frame_after_reset && psEncC->nb_subfr == MAX_NB_SUBFR ) ); } diff --git a/TMessagesProj/jni/opus/silk/float/find_LTP_FLP.c b/TMessagesProj/jni/opus/silk/float/find_LTP_FLP.c index 72299960140..f97064930e8 100644 --- a/TMessagesProj/jni/opus/silk/float/find_LTP_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/find_LTP_FLP.c @@ -33,100 +33,32 @@ POSSIBILITY OF SUCH DAMAGE. #include "tuning_parameters.h" void silk_find_LTP_FLP( - silk_float b[ MAX_NB_SUBFR * LTP_ORDER ], /* O LTP coefs */ - silk_float WLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ - silk_float *LTPredCodGain, /* O LTP coding gain */ - const silk_float r_lpc[], /* I LPC residual */ - const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ - const silk_float Wght[ MAX_NB_SUBFR ], /* I Weights */ + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ + const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ const opus_int subfr_length, /* I Subframe length */ - const opus_int nb_subfr, /* I number of subframes */ - const opus_int mem_offset /* I Number of samples in LTP memory */ + const opus_int nb_subfr /* I number of subframes */ ) { - opus_int i, k; - silk_float *b_ptr, temp, *WLTP_ptr; - silk_float LPC_res_nrg, LPC_LTP_res_nrg; - silk_float d[ MAX_NB_SUBFR ], m, g, delta_b[ LTP_ORDER ]; - silk_float w[ MAX_NB_SUBFR ], nrg[ MAX_NB_SUBFR ], regu; - silk_float Rr[ LTP_ORDER ], rr[ MAX_NB_SUBFR ]; - const silk_float *r_ptr, *lag_ptr; + opus_int k; + silk_float *xX_ptr, *XX_ptr; + const silk_float *lag_ptr; + silk_float xx, temp; - b_ptr = b; - WLTP_ptr = WLTP; - r_ptr = &r_lpc[ mem_offset ]; + xX_ptr = xX; + XX_ptr = XX; for( k = 0; k < nb_subfr; k++ ) { lag_ptr = r_ptr - ( lag[ k ] + LTP_ORDER / 2 ); + silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, XX_ptr ); + silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, xX_ptr ); + xx = ( silk_float )silk_energy_FLP( r_ptr, subfr_length + LTP_ORDER ); + temp = 1.0f / silk_max( xx, LTP_CORR_INV_MAX * 0.5f * ( XX_ptr[ 0 ] + XX_ptr[ 24 ] ) + 1.0f ); + silk_scale_vector_FLP( XX_ptr, temp, LTP_ORDER * LTP_ORDER ); + silk_scale_vector_FLP( xX_ptr, temp, LTP_ORDER ); - silk_corrMatrix_FLP( lag_ptr, subfr_length, LTP_ORDER, WLTP_ptr ); - silk_corrVector_FLP( lag_ptr, r_ptr, subfr_length, LTP_ORDER, Rr ); - - rr[ k ] = ( silk_float )silk_energy_FLP( r_ptr, subfr_length ); - regu = 1.0f + rr[ k ] + - matrix_ptr( WLTP_ptr, 0, 0, LTP_ORDER ) + - matrix_ptr( WLTP_ptr, LTP_ORDER-1, LTP_ORDER-1, LTP_ORDER ); - regu *= LTP_DAMPING / 3; - silk_regularize_correlations_FLP( WLTP_ptr, &rr[ k ], regu, LTP_ORDER ); - silk_solve_LDL_FLP( WLTP_ptr, LTP_ORDER, Rr, b_ptr ); - - /* Calculate residual energy */ - nrg[ k ] = silk_residual_energy_covar_FLP( b_ptr, WLTP_ptr, Rr, rr[ k ], LTP_ORDER ); - - temp = Wght[ k ] / ( nrg[ k ] * Wght[ k ] + 0.01f * subfr_length ); - silk_scale_vector_FLP( WLTP_ptr, temp, LTP_ORDER * LTP_ORDER ); - w[ k ] = matrix_ptr( WLTP_ptr, LTP_ORDER / 2, LTP_ORDER / 2, LTP_ORDER ); - - r_ptr += subfr_length; - b_ptr += LTP_ORDER; - WLTP_ptr += LTP_ORDER * LTP_ORDER; - } - - /* Compute LTP coding gain */ - if( LTPredCodGain != NULL ) { - LPC_LTP_res_nrg = 1e-6f; - LPC_res_nrg = 0.0f; - for( k = 0; k < nb_subfr; k++ ) { - LPC_res_nrg += rr[ k ] * Wght[ k ]; - LPC_LTP_res_nrg += nrg[ k ] * Wght[ k ]; - } - - silk_assert( LPC_LTP_res_nrg > 0 ); - *LTPredCodGain = 3.0f * silk_log2( LPC_res_nrg / LPC_LTP_res_nrg ); - } - - /* Smoothing */ - /* d = sum( B, 1 ); */ - b_ptr = b; - for( k = 0; k < nb_subfr; k++ ) { - d[ k ] = 0; - for( i = 0; i < LTP_ORDER; i++ ) { - d[ k ] += b_ptr[ i ]; - } - b_ptr += LTP_ORDER; - } - /* m = ( w * d' ) / ( sum( w ) + 1e-3 ); */ - temp = 1e-3f; - for( k = 0; k < nb_subfr; k++ ) { - temp += w[ k ]; - } - m = 0; - for( k = 0; k < nb_subfr; k++ ) { - m += d[ k ] * w[ k ]; - } - m = m / temp; - - b_ptr = b; - for( k = 0; k < nb_subfr; k++ ) { - g = LTP_SMOOTHING / ( LTP_SMOOTHING + w[ k ] ) * ( m - d[ k ] ); - temp = 0; - for( i = 0; i < LTP_ORDER; i++ ) { - delta_b[ i ] = silk_max_float( b_ptr[ i ], 0.1f ); - temp += delta_b[ i ]; - } - temp = g / temp; - for( i = 0; i < LTP_ORDER; i++ ) { - b_ptr[ i ] = b_ptr[ i ] + delta_b[ i ] * temp; - } - b_ptr += LTP_ORDER; + r_ptr += subfr_length; + XX_ptr += LTP_ORDER * LTP_ORDER; + xX_ptr += LTP_ORDER; } } diff --git a/TMessagesProj/jni/opus/silk/float/find_pred_coefs_FLP.c b/TMessagesProj/jni/opus/silk/float/find_pred_coefs_FLP.c index ea2c6c432ab..cb2e763b1db 100644 --- a/TMessagesProj/jni/opus/silk/float/find_pred_coefs_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/find_pred_coefs_FLP.c @@ -41,8 +41,9 @@ void silk_find_pred_coefs_FLP( ) { opus_int i; - silk_float WLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; - silk_float invGains[ MAX_NB_SUBFR ], Wght[ MAX_NB_SUBFR ]; + silk_float XXLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + silk_float xXLTP[ MAX_NB_SUBFR * LTP_ORDER ]; + silk_float invGains[ MAX_NB_SUBFR ]; opus_int16 NLSF_Q15[ MAX_LPC_ORDER ]; const silk_float *x_ptr; silk_float *x_pre_ptr, LPC_in_pre[ MAX_NB_SUBFR * MAX_LPC_ORDER + MAX_FRAME_LENGTH ]; @@ -52,7 +53,6 @@ void silk_find_pred_coefs_FLP( for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { silk_assert( psEncCtrl->Gains[ i ] > 0.0f ); invGains[ i ] = 1.0f / psEncCtrl->Gains[ i ]; - Wght[ i ] = invGains[ i ] * invGains[ i ]; } if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { @@ -62,12 +62,11 @@ void silk_find_pred_coefs_FLP( silk_assert( psEnc->sCmn.ltp_mem_length - psEnc->sCmn.predictLPCOrder >= psEncCtrl->pitchL[ 0 ] + LTP_ORDER / 2 ); /* LTP analysis */ - silk_find_LTP_FLP( psEncCtrl->LTPCoef, WLTP, &psEncCtrl->LTPredCodGain, res_pitch, - psEncCtrl->pitchL, Wght, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.ltp_mem_length ); + silk_find_LTP_FLP( XXLTP, xXLTP, res_pitch, psEncCtrl->pitchL, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr ); /* Quantize LTP gain parameters */ silk_quant_LTP_gains_FLP( psEncCtrl->LTPCoef, psEnc->sCmn.indices.LTPIndex, &psEnc->sCmn.indices.PERIndex, - &psEnc->sCmn.sum_log_gain_Q7, WLTP, psEnc->sCmn.mu_LTP_Q9, psEnc->sCmn.LTPQuantLowComplexity, psEnc->sCmn.nb_subfr ); + &psEnc->sCmn.sum_log_gain_Q7, &psEncCtrl->LTPredCodGain, XXLTP, xXLTP, psEnc->sCmn.subfr_length, psEnc->sCmn.nb_subfr, psEnc->sCmn.arch ); /* Control LTP scaling */ silk_LTP_scale_ctrl_FLP( psEnc, psEncCtrl, condCoding ); @@ -90,13 +89,13 @@ void silk_find_pred_coefs_FLP( } silk_memset( psEncCtrl->LTPCoef, 0, psEnc->sCmn.nb_subfr * LTP_ORDER * sizeof( silk_float ) ); psEncCtrl->LTPredCodGain = 0.0f; - psEnc->sCmn.sum_log_gain_Q7 = 0; + psEnc->sCmn.sum_log_gain_Q7 = 0; } /* Limit on total predictive coding gain */ if( psEnc->sCmn.first_frame_after_reset ) { minInvGain = 1.0f / MAX_PREDICTION_POWER_GAIN_AFTER_RESET; - } else { + } else { minInvGain = (silk_float)pow( 2, psEncCtrl->LTPredCodGain / 3 ) / MAX_PREDICTION_POWER_GAIN; minInvGain /= 0.25f + 0.75f * psEncCtrl->coding_quality; } diff --git a/TMessagesProj/jni/opus/silk/float/inner_product_FLP.c b/TMessagesProj/jni/opus/silk/float/inner_product_FLP.c index 029c012911d..cdd39d24ce9 100644 --- a/TMessagesProj/jni/opus/silk/float/inner_product_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/inner_product_FLP.c @@ -38,13 +38,12 @@ double silk_inner_product_FLP( opus_int dataSize ) { - opus_int i, dataSize4; + opus_int i; double result; /* 4x unrolled loop */ result = 0.0; - dataSize4 = dataSize & 0xFFFC; - for( i = 0; i < dataSize4; i += 4 ) { + for( i = 0; i < dataSize - 3; i += 4 ) { result += data1[ i + 0 ] * (double)data2[ i + 0 ] + data1[ i + 1 ] * (double)data2[ i + 1 ] + data1[ i + 2 ] * (double)data2[ i + 2 ] + diff --git a/TMessagesProj/jni/opus/silk/float/k2a_FLP.c b/TMessagesProj/jni/opus/silk/float/k2a_FLP.c index 12af4e76697..1448008dbbe 100644 --- a/TMessagesProj/jni/opus/silk/float/k2a_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/k2a_FLP.c @@ -39,15 +39,16 @@ void silk_k2a_FLP( ) { opus_int k, n; - silk_float Atmp[ SILK_MAX_ORDER_LPC ]; + silk_float rck, tmp1, tmp2; for( k = 0; k < order; k++ ) { - for( n = 0; n < k; n++ ) { - Atmp[ n ] = A[ n ]; + rck = rc[ k ]; + for( n = 0; n < (k + 1) >> 1; n++ ) { + tmp1 = A[ n ]; + tmp2 = A[ k - n - 1 ]; + A[ n ] = tmp1 + tmp2 * rck; + A[ k - n - 1 ] = tmp2 + tmp1 * rck; } - for( n = 0; n < k; n++ ) { - A[ n ] += Atmp[ k - n - 1 ] * rc[ k ]; - } - A[ k ] = -rc[ k ]; + A[ k ] = -rck; } } diff --git a/TMessagesProj/jni/opus/silk/float/levinsondurbin_FLP.c b/TMessagesProj/jni/opus/silk/float/levinsondurbin_FLP.c deleted file mode 100644 index f0ba6069812..00000000000 --- a/TMessagesProj/jni/opus/silk/float/levinsondurbin_FLP.c +++ /dev/null @@ -1,81 +0,0 @@ -/*********************************************************************** -Copyright (c) 2006-2011, Skype Limited. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -***********************************************************************/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "SigProc_FLP.h" - -/* Solve the normal equations using the Levinson-Durbin recursion */ -silk_float silk_levinsondurbin_FLP( /* O prediction error energy */ - silk_float A[], /* O prediction coefficients [order] */ - const silk_float corr[], /* I input auto-correlations [order + 1] */ - const opus_int order /* I prediction order */ -) -{ - opus_int i, mHalf, m; - silk_float min_nrg, nrg, t, km, Atmp1, Atmp2; - - min_nrg = 1e-12f * corr[ 0 ] + 1e-9f; - nrg = corr[ 0 ]; - nrg = silk_max_float(min_nrg, nrg); - A[ 0 ] = corr[ 1 ] / nrg; - nrg -= A[ 0 ] * corr[ 1 ]; - nrg = silk_max_float(min_nrg, nrg); - - for( m = 1; m < order; m++ ) - { - t = corr[ m + 1 ]; - for( i = 0; i < m; i++ ) { - t -= A[ i ] * corr[ m - i ]; - } - - /* reflection coefficient */ - km = t / nrg; - - /* residual energy */ - nrg -= km * t; - nrg = silk_max_float(min_nrg, nrg); - - mHalf = m >> 1; - for( i = 0; i < mHalf; i++ ) { - Atmp1 = A[ i ]; - Atmp2 = A[ m - i - 1 ]; - A[ m - i - 1 ] -= km * Atmp1; - A[ i ] -= km * Atmp2; - } - if( m & 1 ) { - A[ mHalf ] -= km * A[ mHalf ]; - } - A[ m ] = km; - } - - /* return the residual energy */ - return nrg; -} - diff --git a/TMessagesProj/jni/opus/silk/float/main_FLP.h b/TMessagesProj/jni/opus/silk/float/main_FLP.h index fb553b61aa4..8d1d2a8a27b 100644 --- a/TMessagesProj/jni/opus/silk/float/main_FLP.h +++ b/TMessagesProj/jni/opus/silk/float/main_FLP.h @@ -79,22 +79,11 @@ opus_int silk_init_encoder( opus_int silk_control_encoder( silk_encoder_state_FLP *psEnc, /* I/O Pointer to Silk encoder state FLP */ silk_EncControlStruct *encControl, /* I Control structure */ - const opus_int32 TargetRate_bps, /* I Target max bitrate (bps) */ const opus_int allow_bw_switch, /* I Flag to allow switching audio bandwidth */ const opus_int channelNb, /* I Channel number */ const opus_int force_fs_kHz ); -/****************/ -/* Prefiltering */ -/****************/ -void silk_prefilter_FLP( - silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ - const silk_encoder_control_FLP *psEncCtrl, /* I Encoder control FLP */ - silk_float xw[], /* O Weighted signal */ - const silk_float x[] /* I Speech signal */ -); - /**************************/ /* Noise shaping analysis */ /**************************/ @@ -153,15 +142,12 @@ void silk_find_LPC_FLP( /* LTP analysis */ void silk_find_LTP_FLP( - silk_float b[ MAX_NB_SUBFR * LTP_ORDER ], /* O LTP coefs */ - silk_float WLTP[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ - silk_float *LTPredCodGain, /* O LTP coding gain */ - const silk_float r_lpc[], /* I LPC residual */ + silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* O Weight for LTP quantization */ + silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* O Weight for LTP quantization */ + const silk_float r_ptr[], /* I LPC residual */ const opus_int lag[ MAX_NB_SUBFR ], /* I LTP lags */ - const silk_float Wght[ MAX_NB_SUBFR ], /* I Weights */ const opus_int subfr_length, /* I Subframe length */ - const opus_int nb_subfr, /* I number of subframes */ - const opus_int mem_offset /* I Number of samples in LTP memory */ + const opus_int nb_subfr /* I number of subframes */ ); void silk_LTP_analysis_filter_FLP( @@ -198,14 +184,16 @@ void silk_LPC_analysis_filter_FLP( /* LTP tap quantizer */ void silk_quant_LTP_gains_FLP( - silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* I/O (Un-)quantized LTP gains */ + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ opus_int8 *periodicity_index, /* O Periodicity index */ opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ - const silk_float W[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Error weights */ - const opus_int mu_Q10, /* I Mu value (R/D tradeoff) */ - const opus_int lowComplexity, /* I Flag for low complexity */ - const opus_int nb_subfr /* I number of subframes */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ); /* Residual energy: nrg = wxx - 2 * wXx * c + c' * wXX * c */ @@ -244,22 +232,6 @@ void silk_corrVector_FLP( silk_float *Xt /* O X'*t correlation vector [order] */ ); -/* Add noise to matrix diagonal */ -void silk_regularize_correlations_FLP( - silk_float *XX, /* I/O Correlation matrices */ - silk_float *xx, /* I/O Correlation values */ - const silk_float noise, /* I Noise energy to add */ - const opus_int D /* I Dimension of XX */ -); - -/* Function to solve linear equation Ax = b, where A is an MxM symmetric matrix */ -void silk_solve_LDL_FLP( - silk_float *A, /* I/O Symmetric square matrix, out: reg. */ - const opus_int M, /* I Size of matrix */ - const silk_float *b, /* I Pointer to b vector */ - silk_float *x /* O Pointer to x solution vector */ -); - /* Apply sine window to signal vector. */ /* Window types: */ /* 1 -> sine window from 0 to pi/2 */ diff --git a/TMessagesProj/jni/opus/silk/float/noise_shape_analysis_FLP.c b/TMessagesProj/jni/opus/silk/float/noise_shape_analysis_FLP.c index 65f6ea58705..cb3d8a50b7c 100644 --- a/TMessagesProj/jni/opus/silk/float/noise_shape_analysis_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/noise_shape_analysis_FLP.c @@ -55,25 +55,21 @@ static OPUS_INLINE silk_float warped_gain( /* Convert warped filter coefficients to monic pseudo-warped coefficients and limit maximum */ /* amplitude of monic warped coefficients by using bandwidth expansion on the true coefficients */ static OPUS_INLINE void warped_true2monic_coefs( - silk_float *coefs_syn, - silk_float *coefs_ana, + silk_float *coefs, silk_float lambda, silk_float limit, opus_int order ) { opus_int i, iter, ind = 0; - silk_float tmp, maxabs, chirp, gain_syn, gain_ana; + silk_float tmp, maxabs, chirp, gain; /* Convert to monic coefficients */ for( i = order - 1; i > 0; i-- ) { - coefs_syn[ i - 1 ] -= lambda * coefs_syn[ i ]; - coefs_ana[ i - 1 ] -= lambda * coefs_ana[ i ]; + coefs[ i - 1 ] -= lambda * coefs[ i ]; } - gain_syn = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs_syn[ 0 ] ); - gain_ana = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs_ana[ 0 ] ); + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); for( i = 0; i < order; i++ ) { - coefs_syn[ i ] *= gain_syn; - coefs_ana[ i ] *= gain_ana; + coefs[ i ] *= gain; } /* Limit */ @@ -81,7 +77,7 @@ static OPUS_INLINE void warped_true2monic_coefs( /* Find maximum absolute value */ maxabs = -1.0f; for( i = 0; i < order; i++ ) { - tmp = silk_max( silk_abs_float( coefs_syn[ i ] ), silk_abs_float( coefs_ana[ i ] ) ); + tmp = silk_abs_float( coefs[ i ] ); if( tmp > maxabs ) { maxabs = tmp; ind = i; @@ -94,36 +90,59 @@ static OPUS_INLINE void warped_true2monic_coefs( /* Convert back to true warped coefficients */ for( i = 1; i < order; i++ ) { - coefs_syn[ i - 1 ] += lambda * coefs_syn[ i ]; - coefs_ana[ i - 1 ] += lambda * coefs_ana[ i ]; + coefs[ i - 1 ] += lambda * coefs[ i ]; } - gain_syn = 1.0f / gain_syn; - gain_ana = 1.0f / gain_ana; + gain = 1.0f / gain; for( i = 0; i < order; i++ ) { - coefs_syn[ i ] *= gain_syn; - coefs_ana[ i ] *= gain_ana; + coefs[ i ] *= gain; } /* Apply bandwidth expansion */ chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); - silk_bwexpander_FLP( coefs_syn, order, chirp ); - silk_bwexpander_FLP( coefs_ana, order, chirp ); + silk_bwexpander_FLP( coefs, order, chirp ); /* Convert to monic warped coefficients */ for( i = order - 1; i > 0; i-- ) { - coefs_syn[ i - 1 ] -= lambda * coefs_syn[ i ]; - coefs_ana[ i - 1 ] -= lambda * coefs_ana[ i ]; + coefs[ i - 1 ] -= lambda * coefs[ i ]; } - gain_syn = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs_syn[ 0 ] ); - gain_ana = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs_ana[ 0 ] ); + gain = ( 1.0f - lambda * lambda ) / ( 1.0f + lambda * coefs[ 0 ] ); for( i = 0; i < order; i++ ) { - coefs_syn[ i ] *= gain_syn; - coefs_ana[ i ] *= gain_ana; + coefs[ i ] *= gain; } } silk_assert( 0 ); } +static OPUS_INLINE void limit_coefs( + silk_float *coefs, + silk_float limit, + opus_int order +) { + opus_int i, iter, ind = 0; + silk_float tmp, maxabs, chirp; + + for( iter = 0; iter < 10; iter++ ) { + /* Find maximum absolute value */ + maxabs = -1.0f; + for( i = 0; i < order; i++ ) { + tmp = silk_abs_float( coefs[ i ] ); + if( tmp > maxabs ) { + maxabs = tmp; + ind = i; + } + } + if( maxabs <= limit ) { + /* Coefficients are within range - done */ + return; + } + + /* Apply bandwidth expansion */ + chirp = 0.99f - ( 0.8f + 0.1f * iter ) * ( maxabs - limit ) / ( maxabs * ( ind + 1 ) ); + silk_bwexpander_FLP( coefs, order, chirp ); + } + silk_assert( 0 ); +} + /* Compute noise shaping coefficients and initial gain values */ void silk_noise_shape_analysis_FLP( silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ @@ -133,12 +152,13 @@ void silk_noise_shape_analysis_FLP( ) { silk_shape_state_FLP *psShapeSt = &psEnc->sShape; - opus_int k, nSamples; - silk_float SNR_adj_dB, HarmBoost, HarmShapeGain, Tilt; - silk_float nrg, pre_nrg, log_energy, log_energy_prev, energy_variation; - silk_float delta, BWExp1, BWExp2, gain_mult, gain_add, strength, b, warping; + opus_int k, nSamples, nSegs; + silk_float SNR_adj_dB, HarmShapeGain, Tilt; + silk_float nrg, log_energy, log_energy_prev, energy_variation; + silk_float BWExp, gain_mult, gain_add, strength, b, warping; silk_float x_windowed[ SHAPE_LPC_WIN_MAX ]; silk_float auto_corr[ MAX_SHAPE_LPC_ORDER + 1 ]; + silk_float rc[ MAX_SHAPE_LPC_ORDER + 1 ]; const silk_float *x_ptr, *pitch_res_ptr; /* Point to start of first LPC analysis block */ @@ -176,14 +196,14 @@ void silk_noise_shape_analysis_FLP( if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { /* Initially set to 0; may be overruled in process_gains(..) */ psEnc->sCmn.indices.quantOffsetType = 0; - psEncCtrl->sparseness = 0.0f; } else { /* Sparseness measure, based on relative fluctuations of energy per 2 milliseconds */ nSamples = 2 * psEnc->sCmn.fs_kHz; energy_variation = 0.0f; log_energy_prev = 0.0f; pitch_res_ptr = pitch_res; - for( k = 0; k < silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; k++ ) { + nSegs = silk_SMULBB( SUB_FRAME_LENGTH_MS, psEnc->sCmn.nb_subfr ) / 2; + for( k = 0; k < nSegs; k++ ) { nrg = ( silk_float )nSamples + ( silk_float )silk_energy_FLP( pitch_res_ptr, nSamples ); log_energy = silk_log2( nrg ); if( k > 0 ) { @@ -192,17 +212,13 @@ void silk_noise_shape_analysis_FLP( log_energy_prev = log_energy; pitch_res_ptr += nSamples; } - psEncCtrl->sparseness = silk_sigmoid( 0.4f * ( energy_variation - 5.0f ) ); /* Set quantization offset depending on sparseness measure */ - if( psEncCtrl->sparseness > SPARSENESS_THRESHOLD_QNT_OFFSET ) { + if( energy_variation > ENERGY_VARIATION_THRESHOLD_QNT_OFFSET * (nSegs-1) ) { psEnc->sCmn.indices.quantOffsetType = 0; } else { psEnc->sCmn.indices.quantOffsetType = 1; } - - /* Increase coding SNR for sparse signals */ - SNR_adj_dB += SPARSE_SNR_INCR_dB * ( psEncCtrl->sparseness - 0.5f ); } /*******************************/ @@ -210,19 +226,10 @@ void silk_noise_shape_analysis_FLP( /*******************************/ /* More BWE for signals with high prediction gain */ strength = FIND_PITCH_WHITE_NOISE_FRACTION * psEncCtrl->predGain; /* between 0.0 and 1.0 */ - BWExp1 = BWExp2 = BANDWIDTH_EXPANSION / ( 1.0f + strength * strength ); - delta = LOW_RATE_BANDWIDTH_EXPANSION_DELTA * ( 1.0f - 0.75f * psEncCtrl->coding_quality ); - BWExp1 -= delta; - BWExp2 += delta; - /* BWExp1 will be applied after BWExp2, so make it relative */ - BWExp1 /= BWExp2; - - if( psEnc->sCmn.warping_Q16 > 0 ) { - /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ - warping = (silk_float)psEnc->sCmn.warping_Q16 / 65536.0f + 0.01f * psEncCtrl->coding_quality; - } else { - warping = 0.0f; - } + BWExp = BANDWIDTH_EXPANSION / ( 1.0f + strength * strength ); + + /* Slightly more warping in analysis will move quantization noise up in frequency, where it's better masked */ + warping = (silk_float)psEnc->sCmn.warping_Q16 / 65536.0f + 0.01f * psEncCtrl->coding_quality; /********************************************/ /* Compute noise shaping AR coefs and gains */ @@ -252,37 +259,28 @@ void silk_noise_shape_analysis_FLP( } /* Add white noise, as a fraction of energy */ - auto_corr[ 0 ] += auto_corr[ 0 ] * SHAPE_WHITE_NOISE_FRACTION; + auto_corr[ 0 ] += auto_corr[ 0 ] * SHAPE_WHITE_NOISE_FRACTION + 1.0f; /* Convert correlations to prediction coefficients, and compute residual energy */ - nrg = silk_levinsondurbin_FLP( &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], auto_corr, psEnc->sCmn.shapingLPCOrder ); + nrg = silk_schur_FLP( rc, auto_corr, psEnc->sCmn.shapingLPCOrder ); + silk_k2a_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], rc, psEnc->sCmn.shapingLPCOrder ); psEncCtrl->Gains[ k ] = ( silk_float )sqrt( nrg ); if( psEnc->sCmn.warping_Q16 > 0 ) { /* Adjust gain for warping */ - psEncCtrl->Gains[ k ] *= warped_gain( &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], warping, psEnc->sCmn.shapingLPCOrder ); + psEncCtrl->Gains[ k ] *= warped_gain( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, psEnc->sCmn.shapingLPCOrder ); } /* Bandwidth expansion for synthesis filter shaping */ - silk_bwexpander_FLP( &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder, BWExp2 ); - - /* Compute noise shaping filter coefficients */ - silk_memcpy( - &psEncCtrl->AR1[ k * MAX_SHAPE_LPC_ORDER ], - &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], - psEnc->sCmn.shapingLPCOrder * sizeof( silk_float ) ); + silk_bwexpander_FLP( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder, BWExp ); - /* Bandwidth expansion for analysis filter shaping */ - silk_bwexpander_FLP( &psEncCtrl->AR1[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder, BWExp1 ); - - /* Ratio of prediction gains, in energy domain */ - pre_nrg = silk_LPC_inverse_pred_gain_FLP( &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder ); - nrg = silk_LPC_inverse_pred_gain_FLP( &psEncCtrl->AR1[ k * MAX_SHAPE_LPC_ORDER ], psEnc->sCmn.shapingLPCOrder ); - psEncCtrl->GainsPre[ k ] = 1.0f - 0.7f * ( 1.0f - pre_nrg / nrg ); - - /* Convert to monic warped prediction coefficients and limit absolute values */ - warped_true2monic_coefs( &psEncCtrl->AR2[ k * MAX_SHAPE_LPC_ORDER ], &psEncCtrl->AR1[ k * MAX_SHAPE_LPC_ORDER ], - warping, 3.999f, psEnc->sCmn.shapingLPCOrder ); + if( psEnc->sCmn.warping_Q16 > 0 ) { + /* Convert to monic warped prediction coefficients and limit absolute values */ + warped_true2monic_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], warping, 3.999f, psEnc->sCmn.shapingLPCOrder ); + } else { + /* Limit absolute values */ + limit_coefs( &psEncCtrl->AR[ k * MAX_SHAPE_LPC_ORDER ], 3.999f, psEnc->sCmn.shapingLPCOrder ); + } } /*****************/ @@ -296,11 +294,6 @@ void silk_noise_shape_analysis_FLP( psEncCtrl->Gains[ k ] += gain_add; } - gain_mult = 1.0f + INPUT_TILT + psEncCtrl->coding_quality * HIGH_RATE_INPUT_TILT; - for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { - psEncCtrl->GainsPre[ k ] *= gain_mult; - } - /************************************************/ /* Control low-frequency shaping and noise tilt */ /************************************************/ @@ -331,12 +324,6 @@ void silk_noise_shape_analysis_FLP( /****************************/ /* HARMONIC SHAPING CONTROL */ /****************************/ - /* Control boosting of harmonic frequencies */ - HarmBoost = LOW_RATE_HARMONIC_BOOST * ( 1.0f - psEncCtrl->coding_quality ) * psEnc->LTPCorr; - - /* More harmonic boost for noisy input signals */ - HarmBoost += LOW_INPUT_QUALITY_HARMONIC_BOOST * ( 1.0f - psEncCtrl->input_quality ); - if( USE_HARM_SHAPING && psEnc->sCmn.indices.signalType == TYPE_VOICED ) { /* Harmonic noise shaping */ HarmShapeGain = HARMONIC_SHAPING; @@ -355,8 +342,6 @@ void silk_noise_shape_analysis_FLP( /* Smooth over subframes */ /*************************/ for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { - psShapeSt->HarmBoost_smth += SUBFR_SMTH_COEF * ( HarmBoost - psShapeSt->HarmBoost_smth ); - psEncCtrl->HarmBoost[ k ] = psShapeSt->HarmBoost_smth; psShapeSt->HarmShapeGain_smth += SUBFR_SMTH_COEF * ( HarmShapeGain - psShapeSt->HarmShapeGain_smth ); psEncCtrl->HarmShapeGain[ k ] = psShapeSt->HarmShapeGain_smth; psShapeSt->Tilt_smth += SUBFR_SMTH_COEF * ( Tilt - psShapeSt->Tilt_smth ); diff --git a/TMessagesProj/jni/opus/silk/float/pitch_analysis_core_FLP.c b/TMessagesProj/jni/opus/silk/float/pitch_analysis_core_FLP.c index e58f041bd64..b37169378b1 100644 --- a/TMessagesProj/jni/opus/silk/float/pitch_analysis_core_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/pitch_analysis_core_FLP.c @@ -159,7 +159,7 @@ opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, /* Low-pass filter */ for( i = frame_length_4kHz - 1; i > 0; i-- ) { - frame_4kHz[ i ] += frame_4kHz[ i - 1 ]; + frame_4kHz[ i ] = silk_ADD_SAT16( frame_4kHz[ i ], frame_4kHz[ i - 1 ] ); } /****************************************************************************** @@ -182,8 +182,8 @@ opus_int silk_pitch_analysis_core_FLP( /* O Voicing estimate: 0 voiced, /* Calculate first vector products before loop */ cross_corr = xcorr[ max_lag_4kHz - min_lag_4kHz ]; - normalizer = silk_energy_FLP( target_ptr, sf_length_8kHz ) + - silk_energy_FLP( basis_ptr, sf_length_8kHz ) + + normalizer = silk_energy_FLP( target_ptr, sf_length_8kHz ) + + silk_energy_FLP( basis_ptr, sf_length_8kHz ) + sf_length_8kHz * 4000.0f; C[ 0 ][ min_lag_4kHz ] += (silk_float)( 2 * cross_corr / normalizer ); diff --git a/TMessagesProj/jni/opus/silk/float/prefilter_FLP.c b/TMessagesProj/jni/opus/silk/float/prefilter_FLP.c deleted file mode 100644 index 8bc32fb4104..00000000000 --- a/TMessagesProj/jni/opus/silk/float/prefilter_FLP.c +++ /dev/null @@ -1,206 +0,0 @@ -/*********************************************************************** -Copyright (c) 2006-2011, Skype Limited. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -***********************************************************************/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "main_FLP.h" -#include "tuning_parameters.h" - -/* -* Prefilter for finding Quantizer input signal -*/ -static OPUS_INLINE void silk_prefilt_FLP( - silk_prefilter_state_FLP *P, /* I/O state */ - silk_float st_res[], /* I */ - silk_float xw[], /* O */ - silk_float *HarmShapeFIR, /* I */ - silk_float Tilt, /* I */ - silk_float LF_MA_shp, /* I */ - silk_float LF_AR_shp, /* I */ - opus_int lag, /* I */ - opus_int length /* I */ -); - -static void silk_warped_LPC_analysis_filter_FLP( - silk_float state[], /* I/O State [order + 1] */ - silk_float res[], /* O Residual signal [length] */ - const silk_float coef[], /* I Coefficients [order] */ - const silk_float input[], /* I Input signal [length] */ - const silk_float lambda, /* I Warping factor */ - const opus_int length, /* I Length of input signal */ - const opus_int order /* I Filter order (even) */ -) -{ - opus_int n, i; - silk_float acc, tmp1, tmp2; - - /* Order must be even */ - silk_assert( ( order & 1 ) == 0 ); - - for( n = 0; n < length; n++ ) { - /* Output of lowpass section */ - tmp2 = state[ 0 ] + lambda * state[ 1 ]; - state[ 0 ] = input[ n ]; - /* Output of allpass section */ - tmp1 = state[ 1 ] + lambda * ( state[ 2 ] - tmp2 ); - state[ 1 ] = tmp2; - acc = coef[ 0 ] * tmp2; - /* Loop over allpass sections */ - for( i = 2; i < order; i += 2 ) { - /* Output of allpass section */ - tmp2 = state[ i ] + lambda * ( state[ i + 1 ] - tmp1 ); - state[ i ] = tmp1; - acc += coef[ i - 1 ] * tmp1; - /* Output of allpass section */ - tmp1 = state[ i + 1 ] + lambda * ( state[ i + 2 ] - tmp2 ); - state[ i + 1 ] = tmp2; - acc += coef[ i ] * tmp2; - } - state[ order ] = tmp1; - acc += coef[ order - 1 ] * tmp1; - res[ n ] = input[ n ] - acc; - } -} - -/* -* silk_prefilter. Main prefilter function -*/ -void silk_prefilter_FLP( - silk_encoder_state_FLP *psEnc, /* I/O Encoder state FLP */ - const silk_encoder_control_FLP *psEncCtrl, /* I Encoder control FLP */ - silk_float xw[], /* O Weighted signal */ - const silk_float x[] /* I Speech signal */ -) -{ - silk_prefilter_state_FLP *P = &psEnc->sPrefilt; - opus_int j, k, lag; - silk_float HarmShapeGain, Tilt, LF_MA_shp, LF_AR_shp; - silk_float B[ 2 ]; - const silk_float *AR1_shp; - const silk_float *px; - silk_float *pxw; - silk_float HarmShapeFIR[ 3 ]; - silk_float st_res[ MAX_SUB_FRAME_LENGTH + MAX_LPC_ORDER ]; - - /* Set up pointers */ - px = x; - pxw = xw; - lag = P->lagPrev; - for( k = 0; k < psEnc->sCmn.nb_subfr; k++ ) { - /* Update Variables that change per sub frame */ - if( psEnc->sCmn.indices.signalType == TYPE_VOICED ) { - lag = psEncCtrl->pitchL[ k ]; - } - - /* Noise shape parameters */ - HarmShapeGain = psEncCtrl->HarmShapeGain[ k ] * ( 1.0f - psEncCtrl->HarmBoost[ k ] ); - HarmShapeFIR[ 0 ] = 0.25f * HarmShapeGain; - HarmShapeFIR[ 1 ] = 32767.0f / 65536.0f * HarmShapeGain; - HarmShapeFIR[ 2 ] = 0.25f * HarmShapeGain; - Tilt = psEncCtrl->Tilt[ k ]; - LF_MA_shp = psEncCtrl->LF_MA_shp[ k ]; - LF_AR_shp = psEncCtrl->LF_AR_shp[ k ]; - AR1_shp = &psEncCtrl->AR1[ k * MAX_SHAPE_LPC_ORDER ]; - - /* Short term FIR filtering */ - silk_warped_LPC_analysis_filter_FLP( P->sAR_shp, st_res, AR1_shp, px, - (silk_float)psEnc->sCmn.warping_Q16 / 65536.0f, psEnc->sCmn.subfr_length, psEnc->sCmn.shapingLPCOrder ); - - /* Reduce (mainly) low frequencies during harmonic emphasis */ - B[ 0 ] = psEncCtrl->GainsPre[ k ]; - B[ 1 ] = -psEncCtrl->GainsPre[ k ] * - ( psEncCtrl->HarmBoost[ k ] * HarmShapeGain + INPUT_TILT + psEncCtrl->coding_quality * HIGH_RATE_INPUT_TILT ); - pxw[ 0 ] = B[ 0 ] * st_res[ 0 ] + B[ 1 ] * P->sHarmHP; - for( j = 1; j < psEnc->sCmn.subfr_length; j++ ) { - pxw[ j ] = B[ 0 ] * st_res[ j ] + B[ 1 ] * st_res[ j - 1 ]; - } - P->sHarmHP = st_res[ psEnc->sCmn.subfr_length - 1 ]; - - silk_prefilt_FLP( P, pxw, pxw, HarmShapeFIR, Tilt, LF_MA_shp, LF_AR_shp, lag, psEnc->sCmn.subfr_length ); - - px += psEnc->sCmn.subfr_length; - pxw += psEnc->sCmn.subfr_length; - } - P->lagPrev = psEncCtrl->pitchL[ psEnc->sCmn.nb_subfr - 1 ]; -} - -/* -* Prefilter for finding Quantizer input signal -*/ -static OPUS_INLINE void silk_prefilt_FLP( - silk_prefilter_state_FLP *P, /* I/O state */ - silk_float st_res[], /* I */ - silk_float xw[], /* O */ - silk_float *HarmShapeFIR, /* I */ - silk_float Tilt, /* I */ - silk_float LF_MA_shp, /* I */ - silk_float LF_AR_shp, /* I */ - opus_int lag, /* I */ - opus_int length /* I */ -) -{ - opus_int i; - opus_int idx, LTP_shp_buf_idx; - silk_float n_Tilt, n_LF, n_LTP; - silk_float sLF_AR_shp, sLF_MA_shp; - silk_float *LTP_shp_buf; - - /* To speed up use temp variables instead of using the struct */ - LTP_shp_buf = P->sLTP_shp; - LTP_shp_buf_idx = P->sLTP_shp_buf_idx; - sLF_AR_shp = P->sLF_AR_shp; - sLF_MA_shp = P->sLF_MA_shp; - - for( i = 0; i < length; i++ ) { - if( lag > 0 ) { - silk_assert( HARM_SHAPE_FIR_TAPS == 3 ); - idx = lag + LTP_shp_buf_idx; - n_LTP = LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 - 1) & LTP_MASK ] * HarmShapeFIR[ 0 ]; - n_LTP += LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 ) & LTP_MASK ] * HarmShapeFIR[ 1 ]; - n_LTP += LTP_shp_buf[ ( idx - HARM_SHAPE_FIR_TAPS / 2 + 1) & LTP_MASK ] * HarmShapeFIR[ 2 ]; - } else { - n_LTP = 0; - } - - n_Tilt = sLF_AR_shp * Tilt; - n_LF = sLF_AR_shp * LF_AR_shp + sLF_MA_shp * LF_MA_shp; - - sLF_AR_shp = st_res[ i ] - n_Tilt; - sLF_MA_shp = sLF_AR_shp - n_LF; - - LTP_shp_buf_idx = ( LTP_shp_buf_idx - 1 ) & LTP_MASK; - LTP_shp_buf[ LTP_shp_buf_idx ] = sLF_MA_shp; - - xw[ i ] = sLF_MA_shp - n_LTP; - } - /* Copy temp variable back to state */ - P->sLF_AR_shp = sLF_AR_shp; - P->sLF_MA_shp = sLF_MA_shp; - P->sLTP_shp_buf_idx = LTP_shp_buf_idx; -} diff --git a/TMessagesProj/jni/opus/silk/float/schur_FLP.c b/TMessagesProj/jni/opus/silk/float/schur_FLP.c index ee436f8351c..f4b4072f6b3 100644 --- a/TMessagesProj/jni/opus/silk/float/schur_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/schur_FLP.c @@ -38,10 +38,10 @@ silk_float silk_schur_FLP( /* O returns residual energy ) { opus_int k, n; - silk_float C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; - silk_float Ctmp1, Ctmp2, rc_tmp; + double C[ SILK_MAX_ORDER_LPC + 1 ][ 2 ]; + double Ctmp1, Ctmp2, rc_tmp; - silk_assert( order==6||order==8||order==10||order==12||order==14||order==16 ); + silk_assert( order <= SILK_MAX_ORDER_LPC ); /* Copy correlations */ for( k = 0; k < order+1; k++ ) { @@ -53,7 +53,7 @@ silk_float silk_schur_FLP( /* O returns residual energy rc_tmp = -C[ k + 1 ][ 0 ] / silk_max_float( C[ 0 ][ 1 ], 1e-9f ); /* Save the output */ - refl_coef[ k ] = rc_tmp; + refl_coef[ k ] = (silk_float)rc_tmp; /* Update correlations */ for( n = 0; n < order - k; n++ ) { @@ -65,6 +65,5 @@ silk_float silk_schur_FLP( /* O returns residual energy } /* Return residual energy */ - return C[ 0 ][ 1 ]; + return (silk_float)C[ 0 ][ 1 ]; } - diff --git a/TMessagesProj/jni/opus/silk/float/solve_LS_FLP.c b/TMessagesProj/jni/opus/silk/float/solve_LS_FLP.c deleted file mode 100644 index 7c90d665a0f..00000000000 --- a/TMessagesProj/jni/opus/silk/float/solve_LS_FLP.c +++ /dev/null @@ -1,207 +0,0 @@ -/*********************************************************************** -Copyright (c) 2006-2011, Skype Limited. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -- Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. -- Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. -- Neither the name of Internet Society, IETF or IETF Trust, nor the -names of specific contributors, may be used to endorse or promote -products derived from this software without specific prior written -permission. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. -***********************************************************************/ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "main_FLP.h" -#include "tuning_parameters.h" - -/********************************************************************** - * LDL Factorisation. Finds the upper triangular matrix L and the diagonal - * Matrix D (only the diagonal elements returned in a vector)such that - * the symmetric matric A is given by A = L*D*L'. - **********************************************************************/ -static OPUS_INLINE void silk_LDL_FLP( - silk_float *A, /* I/O Pointer to Symetric Square Matrix */ - opus_int M, /* I Size of Matrix */ - silk_float *L, /* I/O Pointer to Square Upper triangular Matrix */ - silk_float *Dinv /* I/O Pointer to vector holding the inverse diagonal elements of D */ -); - -/********************************************************************** - * Function to solve linear equation Ax = b, when A is a MxM lower - * triangular matrix, with ones on the diagonal. - **********************************************************************/ -static OPUS_INLINE void silk_SolveWithLowerTriangularWdiagOnes_FLP( - const silk_float *L, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const silk_float *b, /* I b Vector */ - silk_float *x /* O x Vector */ -); - -/********************************************************************** - * Function to solve linear equation (A^T)x = b, when A is a MxM lower - * triangular, with ones on the diagonal. (ie then A^T is upper triangular) - **********************************************************************/ -static OPUS_INLINE void silk_SolveWithUpperTriangularFromLowerWdiagOnes_FLP( - const silk_float *L, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const silk_float *b, /* I b Vector */ - silk_float *x /* O x Vector */ -); - -/********************************************************************** - * Function to solve linear equation Ax = b, when A is a MxM - * symmetric square matrix - using LDL factorisation - **********************************************************************/ -void silk_solve_LDL_FLP( - silk_float *A, /* I/O Symmetric square matrix, out: reg. */ - const opus_int M, /* I Size of matrix */ - const silk_float *b, /* I Pointer to b vector */ - silk_float *x /* O Pointer to x solution vector */ -) -{ - opus_int i; - silk_float L[ MAX_MATRIX_SIZE ][ MAX_MATRIX_SIZE ]; - silk_float T[ MAX_MATRIX_SIZE ]; - silk_float Dinv[ MAX_MATRIX_SIZE ]; /* inverse diagonal elements of D*/ - - silk_assert( M <= MAX_MATRIX_SIZE ); - - /*************************************************** - Factorize A by LDL such that A = L*D*(L^T), - where L is lower triangular with ones on diagonal - ****************************************************/ - silk_LDL_FLP( A, M, &L[ 0 ][ 0 ], Dinv ); - - /**************************************************** - * substitute D*(L^T) = T. ie: - L*D*(L^T)*x = b => L*T = b <=> T = inv(L)*b - ******************************************************/ - silk_SolveWithLowerTriangularWdiagOnes_FLP( &L[ 0 ][ 0 ], M, b, T ); - - /**************************************************** - D*(L^T)*x = T <=> (L^T)*x = inv(D)*T, because D is - diagonal just multiply with 1/d_i - ****************************************************/ - for( i = 0; i < M; i++ ) { - T[ i ] = T[ i ] * Dinv[ i ]; - } - /**************************************************** - x = inv(L') * inv(D) * T - *****************************************************/ - silk_SolveWithUpperTriangularFromLowerWdiagOnes_FLP( &L[ 0 ][ 0 ], M, T, x ); -} - -static OPUS_INLINE void silk_SolveWithUpperTriangularFromLowerWdiagOnes_FLP( - const silk_float *L, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const silk_float *b, /* I b Vector */ - silk_float *x /* O x Vector */ -) -{ - opus_int i, j; - silk_float temp; - const silk_float *ptr1; - - for( i = M - 1; i >= 0; i-- ) { - ptr1 = matrix_adr( L, 0, i, M ); - temp = 0; - for( j = M - 1; j > i ; j-- ) { - temp += ptr1[ j * M ] * x[ j ]; - } - temp = b[ i ] - temp; - x[ i ] = temp; - } -} - -static OPUS_INLINE void silk_SolveWithLowerTriangularWdiagOnes_FLP( - const silk_float *L, /* I Pointer to Lower Triangular Matrix */ - opus_int M, /* I Dim of Matrix equation */ - const silk_float *b, /* I b Vector */ - silk_float *x /* O x Vector */ -) -{ - opus_int i, j; - silk_float temp; - const silk_float *ptr1; - - for( i = 0; i < M; i++ ) { - ptr1 = matrix_adr( L, i, 0, M ); - temp = 0; - for( j = 0; j < i; j++ ) { - temp += ptr1[ j ] * x[ j ]; - } - temp = b[ i ] - temp; - x[ i ] = temp; - } -} - -static OPUS_INLINE void silk_LDL_FLP( - silk_float *A, /* I/O Pointer to Symetric Square Matrix */ - opus_int M, /* I Size of Matrix */ - silk_float *L, /* I/O Pointer to Square Upper triangular Matrix */ - silk_float *Dinv /* I/O Pointer to vector holding the inverse diagonal elements of D */ -) -{ - opus_int i, j, k, loop_count, err = 1; - silk_float *ptr1, *ptr2; - double temp, diag_min_value; - silk_float v[ MAX_MATRIX_SIZE ], D[ MAX_MATRIX_SIZE ]; /* temp arrays*/ - - silk_assert( M <= MAX_MATRIX_SIZE ); - - diag_min_value = FIND_LTP_COND_FAC * 0.5f * ( A[ 0 ] + A[ M * M - 1 ] ); - for( loop_count = 0; loop_count < M && err == 1; loop_count++ ) { - err = 0; - for( j = 0; j < M; j++ ) { - ptr1 = matrix_adr( L, j, 0, M ); - temp = matrix_ptr( A, j, j, M ); /* element in row j column j*/ - for( i = 0; i < j; i++ ) { - v[ i ] = ptr1[ i ] * D[ i ]; - temp -= ptr1[ i ] * v[ i ]; - } - if( temp < diag_min_value ) { - /* Badly conditioned matrix: add white noise and run again */ - temp = ( loop_count + 1 ) * diag_min_value - temp; - for( i = 0; i < M; i++ ) { - matrix_ptr( A, i, i, M ) += ( silk_float )temp; - } - err = 1; - break; - } - D[ j ] = ( silk_float )temp; - Dinv[ j ] = ( silk_float )( 1.0f / temp ); - matrix_ptr( L, j, j, M ) = 1.0f; - - ptr1 = matrix_adr( A, j, 0, M ); - ptr2 = matrix_adr( L, j + 1, 0, M); - for( i = j + 1; i < M; i++ ) { - temp = 0.0; - for( k = 0; k < j; k++ ) { - temp += ptr2[ k ] * v[ k ]; - } - matrix_ptr( L, i, j, M ) = ( silk_float )( ( ptr1[ i ] - temp ) * Dinv[ j ] ); - ptr2 += M; /* go to next column*/ - } - } - } - silk_assert( err == 0 ); -} - diff --git a/TMessagesProj/jni/opus/silk/float/structs_FLP.h b/TMessagesProj/jni/opus/silk/float/structs_FLP.h index bb529e71a44..3150b386e47 100644 --- a/TMessagesProj/jni/opus/silk/float/structs_FLP.h +++ b/TMessagesProj/jni/opus/silk/float/structs_FLP.h @@ -42,32 +42,16 @@ extern "C" /********************************/ typedef struct { opus_int8 LastGainIndex; - silk_float HarmBoost_smth; silk_float HarmShapeGain_smth; silk_float Tilt_smth; } silk_shape_state_FLP; -/********************************/ -/* Prefilter state */ -/********************************/ -typedef struct { - silk_float sLTP_shp[ LTP_BUF_LENGTH ]; - silk_float sAR_shp[ MAX_SHAPE_LPC_ORDER + 1 ]; - opus_int sLTP_shp_buf_idx; - silk_float sLF_AR_shp; - silk_float sLF_MA_shp; - silk_float sHarmHP; - opus_int32 rand_seed; - opus_int lagPrev; -} silk_prefilter_state_FLP; - /********************************/ /* Encoder state FLP */ /********************************/ typedef struct { silk_encoder_state sCmn; /* Common struct, shared with fixed-point code */ silk_shape_state_FLP sShape; /* Noise shaping state */ - silk_prefilter_state_FLP sPrefilt; /* Prefilter State */ /* Buffer for find pitch and noise shape analysis */ silk_float x_buf[ 2 * MAX_FRAME_LENGTH + LA_SHAPE_MAX ];/* Buffer for find pitch and noise shape analysis */ @@ -86,12 +70,9 @@ typedef struct { opus_int pitchL[ MAX_NB_SUBFR ]; /* Noise shaping parameters */ - silk_float AR1[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; - silk_float AR2[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + silk_float AR[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; silk_float LF_MA_shp[ MAX_NB_SUBFR ]; silk_float LF_AR_shp[ MAX_NB_SUBFR ]; - silk_float GainsPre[ MAX_NB_SUBFR ]; - silk_float HarmBoost[ MAX_NB_SUBFR ]; silk_float Tilt[ MAX_NB_SUBFR ]; silk_float HarmShapeGain[ MAX_NB_SUBFR ]; silk_float Lambda; @@ -99,7 +80,6 @@ typedef struct { silk_float coding_quality; /* Measures */ - silk_float sparseness; silk_float predGain; silk_float LTPredCodGain; silk_float ResNrg[ MAX_NB_SUBFR ]; /* Residual energy per subframe */ @@ -115,6 +95,7 @@ typedef struct { typedef struct { silk_encoder_state_FLP state_Fxx[ ENCODER_NUM_CHANNELS ]; stereo_enc_state sStereo; + opus_int32 nBitsUsedLBRR; opus_int32 nBitsExceeded; opus_int nChannelsAPI; opus_int nChannelsInternal; diff --git a/TMessagesProj/jni/opus/silk/float/wrappers_FLP.c b/TMessagesProj/jni/opus/silk/float/wrappers_FLP.c index 350599b20c5..e8f4383120d 100644 --- a/TMessagesProj/jni/opus/silk/float/wrappers_FLP.c +++ b/TMessagesProj/jni/opus/silk/float/wrappers_FLP.c @@ -102,14 +102,14 @@ void silk_NSQ_wrapper_FLP( ) { opus_int i, j; - opus_int32 x_Q3[ MAX_FRAME_LENGTH ]; + opus_int16 x16[ MAX_FRAME_LENGTH ]; opus_int32 Gains_Q16[ MAX_NB_SUBFR ]; silk_DWORD_ALIGN opus_int16 PredCoef_Q12[ 2 ][ MAX_LPC_ORDER ]; opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ]; opus_int LTP_scale_Q14; /* Noise shaping parameters */ - opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; + opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ]; opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ]; /* Packs two int16 coefficients per int32 value */ opus_int Lambda_Q10; opus_int Tilt_Q14[ MAX_NB_SUBFR ]; @@ -119,7 +119,7 @@ void silk_NSQ_wrapper_FLP( /* Noise shape parameters */ for( i = 0; i < psEnc->sCmn.nb_subfr; i++ ) { for( j = 0; j < psEnc->sCmn.shapingLPCOrder; j++ ) { - AR2_Q13[ i * MAX_SHAPE_LPC_ORDER + j ] = silk_float2int( psEncCtrl->AR2[ i * MAX_SHAPE_LPC_ORDER + j ] * 8192.0f ); + AR_Q13[ i * MAX_SHAPE_LPC_ORDER + j ] = silk_float2int( psEncCtrl->AR[ i * MAX_SHAPE_LPC_ORDER + j ] * 8192.0f ); } } @@ -155,16 +155,16 @@ void silk_NSQ_wrapper_FLP( /* Convert input to fix */ for( i = 0; i < psEnc->sCmn.frame_length; i++ ) { - x_Q3[ i ] = silk_float2int( 8.0f * x[ i ] ); + x16[ i ] = silk_float2int( x[ i ] ); } /* Call NSQ */ if( psEnc->sCmn.nStatesDelayedDecision > 1 || psEnc->sCmn.warping_Q16 > 0 ) { - silk_NSQ_del_dec( &psEnc->sCmn, psNSQ, psIndices, x_Q3, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, - AR2_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14 ); + silk_NSQ_del_dec( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); } else { - silk_NSQ( &psEnc->sCmn, psNSQ, psIndices, x_Q3, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, - AR2_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14 ); + silk_NSQ( &psEnc->sCmn, psNSQ, psIndices, x16, pulses, PredCoef_Q12[ 0 ], LTPCoef_Q14, + AR_Q13, HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, psEncCtrl->pitchL, Lambda_Q10, LTP_scale_Q14, psEnc->sCmn.arch ); } } @@ -172,30 +172,35 @@ void silk_NSQ_wrapper_FLP( /* Floating-point Silk LTP quantiation wrapper */ /***********************************************/ void silk_quant_LTP_gains_FLP( - silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* I/O (Un-)quantized LTP gains */ + silk_float B[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook index */ opus_int8 *periodicity_index, /* O Periodicity index */ opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ - const silk_float W[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Error weights */ - const opus_int mu_Q10, /* I Mu value (R/D tradeoff) */ - const opus_int lowComplexity, /* I Flag for low complexity */ - const opus_int nb_subfr /* I number of subframes */ + silk_float *pred_gain_dB, /* O LTP prediction gain */ + const silk_float XX[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ], /* I Correlation matrix */ + const silk_float xX[ MAX_NB_SUBFR * LTP_ORDER ], /* I Correlation vector */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ) { - opus_int i; + opus_int i, pred_gain_dB_Q7; opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ]; - opus_int32 W_Q18[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ]; + opus_int32 XX_Q17[ MAX_NB_SUBFR * LTP_ORDER * LTP_ORDER ]; + opus_int32 xX_Q17[ MAX_NB_SUBFR * LTP_ORDER ]; - for( i = 0; i < nb_subfr * LTP_ORDER; i++ ) { - B_Q14[ i ] = (opus_int16)silk_float2int( B[ i ] * 16384.0f ); - } for( i = 0; i < nb_subfr * LTP_ORDER * LTP_ORDER; i++ ) { - W_Q18[ i ] = (opus_int32)silk_float2int( W[ i ] * 262144.0f ); + XX_Q17[ i ] = (opus_int32)silk_float2int( XX[ i ] * 131072.0f ); + } + for( i = 0; i < nb_subfr * LTP_ORDER; i++ ) { + xX_Q17[ i ] = (opus_int32)silk_float2int( xX[ i ] * 131072.0f ); } - silk_quant_LTP_gains( B_Q14, cbk_index, periodicity_index, sum_log_gain_Q7, W_Q18, mu_Q10, lowComplexity, nb_subfr ); + silk_quant_LTP_gains( B_Q14, cbk_index, periodicity_index, sum_log_gain_Q7, &pred_gain_dB_Q7, XX_Q17, xX_Q17, subfr_len, nb_subfr, arch ); for( i = 0; i < nb_subfr * LTP_ORDER; i++ ) { B[ i ] = (silk_float)B_Q14[ i ] * ( 1.0f / 16384.0f ); } + + *pred_gain_dB = (silk_float)pred_gain_dB_Q7 * ( 1.0f / 128.0f ); } diff --git a/TMessagesProj/jni/opus/silk/lin2log.c b/TMessagesProj/jni/opus/silk/lin2log.c index d4fe515321f..0d5155aa86e 100644 --- a/TMessagesProj/jni/opus/silk/lin2log.c +++ b/TMessagesProj/jni/opus/silk/lin2log.c @@ -41,6 +41,6 @@ opus_int32 silk_lin2log( silk_CLZ_FRAC( inLin, &lz, &frac_Q7 ); /* Piece-wise parabolic approximation */ - return silk_LSHIFT( 31 - lz, 7 ) + silk_SMLAWB( frac_Q7, silk_MUL( frac_Q7, 128 - frac_Q7 ), 179 ); + return silk_ADD_LSHIFT32( silk_SMLAWB( frac_Q7, silk_MUL( frac_Q7, 128 - frac_Q7 ), 179 ), 31 - lz, 7 ); } diff --git a/TMessagesProj/jni/opus/silk/log2lin.c b/TMessagesProj/jni/opus/silk/log2lin.c index a692e009dbb..b7c48e47407 100644 --- a/TMessagesProj/jni/opus/silk/log2lin.c +++ b/TMessagesProj/jni/opus/silk/log2lin.c @@ -33,7 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. /* Approximation of 2^() (very close inverse of silk_lin2log()) */ /* Convert input to a linear scale */ -opus_int32 silk_log2lin( +opus_int32 silk_log2lin( const opus_int32 inLog_Q7 /* I input on log scale */ ) { @@ -42,8 +42,8 @@ opus_int32 silk_log2lin( if( inLog_Q7 < 0 ) { return 0; } else if ( inLog_Q7 >= 3967 ) { - return silk_int32_MAX; - } + return silk_int32_MAX; + } out = silk_LSHIFT( 1, silk_RSHIFT( inLog_Q7, 7 ) ); frac_Q7 = inLog_Q7 & 0x7F; diff --git a/TMessagesProj/jni/opus/silk/macros.h b/TMessagesProj/jni/opus/silk/macros.h index a84e5a5d3ef..3c67b6e5d97 100644 --- a/TMessagesProj/jni/opus/silk/macros.h +++ b/TMessagesProj/jni/opus/silk/macros.h @@ -34,20 +34,37 @@ POSSIBILITY OF SUCH DAMAGE. #include "opus_types.h" #include "opus_defines.h" +#include "arch.h" /* This is an OPUS_INLINE header file for general platform. */ /* (a32 * (opus_int32)((opus_int16)(b32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMULWB(a32, b32) ((opus_int32)(((a32) * (opus_int64)((opus_int16)(b32))) >> 16)) +#else #define silk_SMULWB(a32, b32) ((((a32) >> 16) * (opus_int32)((opus_int16)(b32))) + ((((a32) & 0x0000FFFF) * (opus_int32)((opus_int16)(b32))) >> 16)) +#endif /* a32 + (b32 * (opus_int32)((opus_int16)(c32))) >> 16 output have to be 32bit int */ +#if OPUS_FAST_INT64 +#define silk_SMLAWB(a32, b32, c32) ((opus_int32)((a32) + (((b32) * (opus_int64)((opus_int16)(c32))) >> 16))) +#else #define silk_SMLAWB(a32, b32, c32) ((a32) + ((((b32) >> 16) * (opus_int32)((opus_int16)(c32))) + ((((b32) & 0x0000FFFF) * (opus_int32)((opus_int16)(c32))) >> 16))) +#endif /* (a32 * (b32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWT(a32, b32) ((opus_int32)(((a32) * (opus_int64)((b32) >> 16)) >> 16)) +#else #define silk_SMULWT(a32, b32) (((a32) >> 16) * ((b32) >> 16) + ((((a32) & 0x0000FFFF) * ((b32) >> 16)) >> 16)) +#endif /* a32 + (b32 * (c32 >> 16)) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMLAWT(a32, b32, c32) ((opus_int32)((a32) + (((b32) * ((opus_int64)(c32) >> 16)) >> 16))) +#else #define silk_SMLAWT(a32, b32, c32) ((a32) + (((b32) >> 16) * ((c32) >> 16)) + ((((b32) & 0x0000FFFF) * ((c32) >> 16)) >> 16)) +#endif /* (opus_int32)((opus_int16)(a3))) * (opus_int32)((opus_int16)(b32)) output have to be 32bit int */ #define silk_SMULBB(a32, b32) ((opus_int32)((opus_int16)(a32)) * (opus_int32)((opus_int16)(b32))) @@ -65,10 +82,18 @@ POSSIBILITY OF SUCH DAMAGE. #define silk_SMLAL(a64, b32, c32) (silk_ADD64((a64), ((opus_int64)(b32) * (opus_int64)(c32)))) /* (a32 * b32) >> 16 */ +#if OPUS_FAST_INT64 +#define silk_SMULWW(a32, b32) ((opus_int32)(((opus_int64)(a32) * (b32)) >> 16)) +#else #define silk_SMULWW(a32, b32) silk_MLA(silk_SMULWB((a32), (b32)), (a32), silk_RSHIFT_ROUND((b32), 16)) +#endif /* a32 + ((b32 * c32) >> 16) */ +#if OPUS_FAST_INT64 +#define silk_SMLAWW(a32, b32, c32) ((opus_int32)((a32) + (((opus_int64)(b32) * (c32)) >> 16))) +#else #define silk_SMLAWW(a32, b32, c32) silk_MLA(silk_SMLAWB((a32), (b32), (c32)), (b32), silk_RSHIFT_ROUND((c32), 16)) +#endif /* add/subtract with output saturated */ #define silk_ADD_SAT32(a, b) ((((opus_uint32)(a) + (opus_uint32)(b)) & 0x80000000) == 0 ? \ @@ -79,17 +104,24 @@ POSSIBILITY OF SUCH DAMAGE. (( (a) & ((b)^0x80000000) & 0x80000000) ? silk_int32_MIN : (a)-(b)) : \ ((((a)^0x80000000) & (b) & 0x80000000) ? silk_int32_MAX : (a)-(b)) ) -#include "ecintrin.h" +#if defined(MIPSr1_ASM) +#include "mips/macros_mipsr1.h" +#endif +#include "ecintrin.h" +#ifndef OVERRIDE_silk_CLZ16 static OPUS_INLINE opus_int32 silk_CLZ16(opus_int16 in16) { return 32 - EC_ILOG(in16<<16|0x8000); } +#endif +#ifndef OVERRIDE_silk_CLZ32 static OPUS_INLINE opus_int32 silk_CLZ32(opus_int32 in32) { return in32 ? 32 - EC_ILOG(in32) : 32; } +#endif /* Row based */ #define matrix_ptr(Matrix_base_adr, row, column, N) \ @@ -111,5 +143,9 @@ static OPUS_INLINE opus_int32 silk_CLZ32(opus_int32 in32) #include "arm/macros_armv5e.h" #endif +#ifdef OPUS_ARM_PRESUME_AARCH64_NEON_INTR +#include "arm/macros_arm64.h" +#endif + #endif /* SILK_MACROS_H */ diff --git a/TMessagesProj/jni/opus/silk/main.h b/TMessagesProj/jni/opus/silk/main.h index 2bdf89784da..13d42419fcc 100644 --- a/TMessagesProj/jni/opus/silk/main.h +++ b/TMessagesProj/jni/opus/silk/main.h @@ -38,6 +38,10 @@ POSSIBILITY OF SUCH DAMAGE. #include "entenc.h" #include "entdec.h" +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +#include "x86/main_sse.h" +#endif + /* Convert Left/Right stereo signal to adaptive Mid/Side representation */ void silk_stereo_LR_to_MS( stereo_enc_state *state, /* I/O State */ @@ -105,22 +109,22 @@ void silk_stereo_decode_mid_only( /* Encodes signs of excitation */ void silk_encode_signs( - ec_enc *psRangeEnc, /* I/O Compressor data structure */ - const opus_int8 pulses[], /* I pulse signal */ - opus_int length, /* I length of input */ - const opus_int signalType, /* I Signal type */ - const opus_int quantOffsetType, /* I Quantization offset type */ - const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ + ec_enc *psRangeEnc, /* I/O Compressor data structure */ + const opus_int8 pulses[], /* I pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ ); /* Decodes signs of excitation */ void silk_decode_signs( - ec_dec *psRangeDec, /* I/O Compressor data structure */ - opus_int pulses[], /* I/O pulse signal */ - opus_int length, /* I length of input */ - const opus_int signalType, /* I Signal type */ - const opus_int quantOffsetType, /* I Quantization offset type */ - const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ + ec_dec *psRangeDec, /* I/O Compressor data structure */ + opus_int16 pulses[], /* I/O pulse signal */ + opus_int length, /* I length of input */ + const opus_int signalType, /* I Signal type */ + const opus_int quantOffsetType, /* I Quantization offset type */ + const opus_int sum_pulses[ MAX_NB_SHELL_BLOCKS ] /* I Sum of absolute pulses per block */ ); /* Check encoder control struct */ @@ -161,7 +165,7 @@ void silk_shell_encoder( /* Shell decoder, operates on one shell code frame of 16 pulses */ void silk_shell_decoder( - opus_int *pulses0, /* O data: nonnegative pulse amplitudes */ + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ ec_dec *psRangeDec, /* I/O Compressor data structure */ const opus_int pulses4 /* I number of pulses per pulse-subframe */ ); @@ -201,43 +205,52 @@ void silk_interpolate( /* LTP tap quantizer */ void silk_quant_LTP_gains( - opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* I/O (un)quantized LTP gains */ + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ opus_int8 *periodicity_index, /* O Periodicity Index */ - opus_int32 *sum_gain_dB_Q7, /* I/O Cumulative max prediction gain */ - const opus_int32 W_Q18[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Error Weights in Q18 */ - opus_int mu_Q9, /* I Mu value (R/D tradeoff) */ - opus_int lowComplexity, /* I Flag for low complexity */ - const opus_int nb_subfr /* I number of subframes */ + opus_int32 *sum_gain_dB_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ); /* Entropy constrained matrix-weighted VQ, for a single input data vector */ -void silk_VQ_WMat_EC( +void silk_VQ_WMat_EC_c( opus_int8 *ind, /* O index of best codebook vector */ - opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int32 *res_nrg_Q15, /* O best residual energy */ + opus_int32 *rate_dist_Q8, /* O best total bitrate */ opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ - const opus_int16 *in_Q14, /* I input vector to be quantized */ - const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int32 *XX_Q17, /* I correlation matrix */ + const opus_int32 *xX_Q17, /* I correlation vector */ const opus_int8 *cb_Q7, /* I codebook */ const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ - const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int subfr_len, /* I number of samples per subframe */ const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ - opus_int L /* I number of vectors in codebook */ + const opus_int L /* I number of vectors in codebook */ ); +#if !defined(OVERRIDE_silk_VQ_WMat_EC) +#define silk_VQ_WMat_EC(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L, arch) \ + ((void)(arch),silk_VQ_WMat_EC_c(ind, res_nrg_Q15, rate_dist_Q8, gain_Q7, XX_Q17, xX_Q17, cb_Q7, cb_gain_Q7, cl_Q5, subfr_len, max_gain_Q7, L)) +#endif + /************************************/ /* Noise shaping quantization (NSQ) */ /************************************/ -void silk_NSQ( + +void silk_NSQ_c( const silk_encoder_state *psEncC, /* I/O Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ SideInfoIndices *psIndices, /* I/O Quantization Indices */ - const opus_int32 x_Q3[], /* I Prefiltered input signal */ + const opus_int16 x16[], /* I Input */ opus_int8 pulses[], /* O Quantized pulse signal */ const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ - const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ @@ -247,16 +260,23 @@ void silk_NSQ( const opus_int LTP_scale_Q14 /* I LTP state scaling */ ); +#if !defined(OVERRIDE_silk_NSQ) +#define silk_NSQ(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + /* Noise shaping using delayed decision */ -void silk_NSQ_del_dec( +void silk_NSQ_del_dec_c( const silk_encoder_state *psEncC, /* I/O Encoder State */ silk_nsq_state *NSQ, /* I/O NSQ state */ SideInfoIndices *psIndices, /* I/O Quantization Indices */ - const opus_int32 x_Q3[], /* I Prefiltered input signal */ + const opus_int16 x16[], /* I Input */ opus_int8 pulses[], /* O Quantized pulse signal */ const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ - const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int16 AR_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ @@ -266,6 +286,13 @@ void silk_NSQ_del_dec( const opus_int LTP_scale_Q14 /* I LTP state scaling */ ); +#if !defined(OVERRIDE_silk_NSQ_del_dec) +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_c(psEncC, NSQ, psIndices, x16, pulses, PredCoef_Q12, LTPCoef_Q14, AR_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) +#endif + /************/ /* Silk VAD */ /************/ @@ -275,11 +302,15 @@ opus_int silk_VAD_Init( /* O Return v ); /* Get speech activity level in Q8 */ -opus_int silk_VAD_GetSA_Q8( /* O Return value, 0 if success */ +opus_int silk_VAD_GetSA_Q8_c( /* O Return value, 0 if success */ silk_encoder_state *psEncC, /* I/O Encoder state */ const opus_int16 pIn[] /* I PCM input */ ); +#if !defined(OVERRIDE_silk_VAD_GetSA_Q8) +#define silk_VAD_GetSA_Q8(psEnC, pIn, arch) ((void)(arch),silk_VAD_GetSA_Q8_c(psEnC, pIn)) +#endif + /* Low-pass filter with variable cutoff frequency based on */ /* piece-wise linear interpolation between elliptic filters */ /* Start by setting transition_frame_no = 1; */ @@ -315,6 +346,7 @@ void silk_NLSF_VQ( opus_int32 err_Q26[], /* O Quantization errors [K] */ const opus_int16 in_Q15[], /* I Input vectors to be quantized [LPC_order] */ const opus_uint8 pCB_Q8[], /* I Codebook vectors [K*LPC_order] */ + const opus_int16 pWght_Q9[], /* I Codebook weights [K*LPC_order] */ const opus_int K, /* I Number of codebook vectors */ const opus_int LPC_order /* I Number of LPCs */ ); @@ -373,7 +405,8 @@ opus_int silk_decode_frame( opus_int16 pOut[], /* O Pointer to output speech frame */ opus_int32 *pN, /* O Pointer to size of output frame */ opus_int lostFlag, /* I 0: no loss, 1 loss, 2 decode fec */ - opus_int condCoding /* I The type of conditional coding to use */ + opus_int condCoding, /* I The type of conditional coding to use */ + int arch /* I Run-time architecture */ ); /* Decode indices from bitstream */ @@ -397,13 +430,14 @@ void silk_decode_core( silk_decoder_state *psDec, /* I/O Decoder state */ silk_decoder_control *psDecCtrl, /* I Decoder control */ opus_int16 xq[], /* O Decoded speech */ - const opus_int pulses[ MAX_FRAME_LENGTH ] /* I Pulse signal */ + const opus_int16 pulses[ MAX_FRAME_LENGTH ], /* I Pulse signal */ + int arch /* I Run-time architecture */ ); /* Decode quantization indices of excitation (Shell coding) */ void silk_decode_pulses( ec_dec *psRangeDec, /* I/O Compressor data structure */ - opus_int pulses[], /* O Excitation signal */ + opus_int16 pulses[], /* O Excitation signal */ const opus_int signalType, /* I Sigtype */ const opus_int quantOffsetType, /* I quantOffsetType */ const opus_int frame_length /* I Frame length */ diff --git a/TMessagesProj/jni/opus/silk/process_NLSFs.c b/TMessagesProj/jni/opus/silk/process_NLSFs.c index c27cf030469..0ab71f01634 100644 --- a/TMessagesProj/jni/opus/silk/process_NLSFs.c +++ b/TMessagesProj/jni/opus/silk/process_NLSFs.c @@ -41,7 +41,7 @@ void silk_process_NLSFs( { opus_int i, doInterpolate; opus_int NLSF_mu_Q20; - opus_int32 i_sqr_Q15; + opus_int16 i_sqr_Q15; opus_int16 pNLSF0_temp_Q15[ MAX_LPC_ORDER ]; opus_int16 pNLSFW_QW[ MAX_LPC_ORDER ]; opus_int16 pNLSFW0_temp_QW[ MAX_LPC_ORDER ]; @@ -79,7 +79,8 @@ void silk_process_NLSFs( /* Update NLSF weights with contribution from first half */ i_sqr_Q15 = silk_LSHIFT( silk_SMULBB( psEncC->indices.NLSFInterpCoef_Q2, psEncC->indices.NLSFInterpCoef_Q2 ), 11 ); for( i = 0; i < psEncC->predictLPCOrder; i++ ) { - pNLSFW_QW[ i ] = silk_SMLAWB( silk_RSHIFT( pNLSFW_QW[ i ], 1 ), (opus_int32)pNLSFW0_temp_QW[ i ], i_sqr_Q15 ); + pNLSFW_QW[ i ] = silk_ADD16( silk_RSHIFT( pNLSFW_QW[ i ], 1 ), silk_RSHIFT( + silk_SMULBB( pNLSFW0_temp_QW[ i ], i_sqr_Q15 ), 16) ); silk_assert( pNLSFW_QW[ i ] >= 1 ); } } @@ -100,6 +101,7 @@ void silk_process_NLSFs( } else { /* Copy LPC coefficients for first half from second half */ + silk_assert( psEncC->predictLPCOrder <= MAX_LPC_ORDER ); silk_memcpy( PredCoef_Q12[ 0 ], PredCoef_Q12[ 1 ], psEncC->predictLPCOrder * sizeof( opus_int16 ) ); } } diff --git a/TMessagesProj/jni/opus/silk/quant_LTP_gains.c b/TMessagesProj/jni/opus/silk/quant_LTP_gains.c index fd0870da194..d6b8eff8d18 100644 --- a/TMessagesProj/jni/opus/silk/quant_LTP_gains.c +++ b/TMessagesProj/jni/opus/silk/quant_LTP_gains.c @@ -33,14 +33,16 @@ POSSIBILITY OF SUCH DAMAGE. #include "tuning_parameters.h" void silk_quant_LTP_gains( - opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* I/O (un)quantized LTP gains */ + opus_int16 B_Q14[ MAX_NB_SUBFR * LTP_ORDER ], /* O Quantized LTP gains */ opus_int8 cbk_index[ MAX_NB_SUBFR ], /* O Codebook Index */ opus_int8 *periodicity_index, /* O Periodicity Index */ - opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ - const opus_int32 W_Q18[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Error Weights in Q18 */ - opus_int mu_Q9, /* I Mu value (R/D tradeoff) */ - opus_int lowComplexity, /* I Flag for low complexity */ - const opus_int nb_subfr /* I number of subframes */ + opus_int32 *sum_log_gain_Q7, /* I/O Cumulative max prediction gain */ + opus_int *pred_gain_dB_Q7, /* O LTP prediction gain */ + const opus_int32 XX_Q17[ MAX_NB_SUBFR*LTP_ORDER*LTP_ORDER ], /* I Correlation matrix in Q18 */ + const opus_int32 xX_Q17[ MAX_NB_SUBFR*LTP_ORDER ], /* I Correlation vector in Q18 */ + const opus_int subfr_len, /* I Number of samples per subframe */ + const opus_int nb_subfr, /* I Number of subframes */ + int arch /* I Run-time architecture */ ) { opus_int j, k, cbk_size; @@ -48,16 +50,16 @@ void silk_quant_LTP_gains( const opus_uint8 *cl_ptr_Q5; const opus_int8 *cbk_ptr_Q7; const opus_uint8 *cbk_gain_ptr_Q7; - const opus_int16 *b_Q14_ptr; - const opus_int32 *W_Q18_ptr; - opus_int32 rate_dist_Q14_subfr, rate_dist_Q14, min_rate_dist_Q14; - opus_int32 sum_log_gain_tmp_Q7, best_sum_log_gain_Q7, max_gain_Q7, gain_Q7; + const opus_int32 *XX_Q17_ptr, *xX_Q17_ptr; + opus_int32 res_nrg_Q15_subfr, res_nrg_Q15, rate_dist_Q7_subfr, rate_dist_Q7, min_rate_dist_Q7; + opus_int32 sum_log_gain_tmp_Q7, best_sum_log_gain_Q7, max_gain_Q7; + opus_int gain_Q7; /***************************************************/ /* iterate over different codebooks with different */ /* rates/distortions, and choose best */ /***************************************************/ - min_rate_dist_Q14 = silk_int32_MAX; + min_rate_dist_Q7 = silk_int32_MAX; best_sum_log_gain_Q7 = 0; for( k = 0; k < 3; k++ ) { /* Safety margin for pitch gain control, to take into account factors @@ -69,51 +71,46 @@ void silk_quant_LTP_gains( cbk_gain_ptr_Q7 = silk_LTP_vq_gain_ptrs_Q7[ k ]; cbk_size = silk_LTP_vq_sizes[ k ]; - /* Set up pointer to first subframe */ - W_Q18_ptr = W_Q18; - b_Q14_ptr = B_Q14; + /* Set up pointers to first subframe */ + XX_Q17_ptr = XX_Q17; + xX_Q17_ptr = xX_Q17; - rate_dist_Q14 = 0; - sum_log_gain_tmp_Q7 = *sum_log_gain_Q7; + res_nrg_Q15 = 0; + rate_dist_Q7 = 0; + sum_log_gain_tmp_Q7 = *sum_log_gain_Q7; for( j = 0; j < nb_subfr; j++ ) { - max_gain_Q7 = silk_log2lin( ( SILK_FIX_CONST( MAX_SUM_LOG_GAIN_DB / 6.0, 7 ) - sum_log_gain_tmp_Q7 ) - + SILK_FIX_CONST( 7, 7 ) ) - gain_safety; - + max_gain_Q7 = silk_log2lin( ( SILK_FIX_CONST( MAX_SUM_LOG_GAIN_DB / 6.0, 7 ) - sum_log_gain_tmp_Q7 ) + + SILK_FIX_CONST( 7, 7 ) ) - gain_safety; silk_VQ_WMat_EC( &temp_idx[ j ], /* O index of best codebook vector */ - &rate_dist_Q14_subfr, /* O best weighted quantization error + mu * rate */ - &gain_Q7, /* O sum of absolute LTP coefficients */ - b_Q14_ptr, /* I input vector to be quantized */ - W_Q18_ptr, /* I weighting matrix */ + &res_nrg_Q15_subfr, /* O residual energy */ + &rate_dist_Q7_subfr, /* O best weighted quantization error + mu * rate */ + &gain_Q7, /* O sum of absolute LTP coefficients */ + XX_Q17_ptr, /* I correlation matrix */ + xX_Q17_ptr, /* I correlation vector */ cbk_ptr_Q7, /* I codebook */ cbk_gain_ptr_Q7, /* I codebook effective gains */ cl_ptr_Q5, /* I code length for each codebook vector */ - mu_Q9, /* I tradeoff between weighted error and rate */ - max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ - cbk_size /* I number of vectors in codebook */ + subfr_len, /* I number of samples per subframe */ + max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + cbk_size, /* I number of vectors in codebook */ + arch /* I Run-time architecture */ ); - rate_dist_Q14 = silk_ADD_POS_SAT32( rate_dist_Q14, rate_dist_Q14_subfr ); + res_nrg_Q15 = silk_ADD_POS_SAT32( res_nrg_Q15, res_nrg_Q15_subfr ); + rate_dist_Q7 = silk_ADD_POS_SAT32( rate_dist_Q7, rate_dist_Q7_subfr ); sum_log_gain_tmp_Q7 = silk_max(0, sum_log_gain_tmp_Q7 + silk_lin2log( gain_safety + gain_Q7 ) - SILK_FIX_CONST( 7, 7 )); - b_Q14_ptr += LTP_ORDER; - W_Q18_ptr += LTP_ORDER * LTP_ORDER; + XX_Q17_ptr += LTP_ORDER * LTP_ORDER; + xX_Q17_ptr += LTP_ORDER; } - /* Avoid never finding a codebook */ - rate_dist_Q14 = silk_min( silk_int32_MAX - 1, rate_dist_Q14 ); - - if( rate_dist_Q14 < min_rate_dist_Q14 ) { - min_rate_dist_Q14 = rate_dist_Q14; + if( rate_dist_Q7 <= min_rate_dist_Q7 ) { + min_rate_dist_Q7 = rate_dist_Q7; *periodicity_index = (opus_int8)k; silk_memcpy( cbk_index, temp_idx, nb_subfr * sizeof( opus_int8 ) ); - best_sum_log_gain_Q7 = sum_log_gain_tmp_Q7; - } - - /* Break early in low-complexity mode if rate distortion is below threshold */ - if( lowComplexity && ( rate_dist_Q14 < silk_LTP_gain_middle_avg_RD_Q14 ) ) { - break; + best_sum_log_gain_Q7 = sum_log_gain_tmp_Q7; } } @@ -123,6 +120,13 @@ void silk_quant_LTP_gains( B_Q14[ j * LTP_ORDER + k ] = silk_LSHIFT( cbk_ptr_Q7[ cbk_index[ j ] * LTP_ORDER + k ], 7 ); } } - *sum_log_gain_Q7 = best_sum_log_gain_Q7; -} + if( nb_subfr == 2 ) { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 1 ); + } else { + res_nrg_Q15 = silk_RSHIFT32( res_nrg_Q15, 2 ); + } + + *sum_log_gain_Q7 = best_sum_log_gain_Q7; + *pred_gain_dB_Q7 = (opus_int)silk_SMULBB( -3, silk_lin2log( res_nrg_Q15 ) - ( 15 << 7 ) ); +} diff --git a/TMessagesProj/jni/opus/silk/resampler_rom.c b/TMessagesProj/jni/opus/silk/resampler_rom.c index 2d502706f9f..5e6b04476aa 100644 --- a/TMessagesProj/jni/opus/silk/resampler_rom.c +++ b/TMessagesProj/jni/opus/silk/resampler_rom.c @@ -41,36 +41,36 @@ POSSIBILITY OF SUCH DAMAGE. /* Tables with IIR and FIR coefficients for fractional downsamplers (123 Words) */ silk_DWORD_ALIGN const opus_int16 silk_Resampler_3_4_COEFS[ 2 + 3 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { - -20694, -13867, - -49, 64, 17, -157, 353, -496, 163, 11047, 22205, - -39, 6, 91, -170, 186, 23, -896, 6336, 19928, - -19, -36, 102, -89, -24, 328, -951, 2568, 15909, + -20694, -13867, + -49, 64, 17, -157, 353, -496, 163, 11047, 22205, + -39, 6, 91, -170, 186, 23, -896, 6336, 19928, + -19, -36, 102, -89, -24, 328, -951, 2568, 15909, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS[ 2 + 2 * RESAMPLER_DOWN_ORDER_FIR0 / 2 ] = { - -14457, -14019, - 64, 128, -122, 36, 310, -768, 584, 9267, 17733, - 12, 128, 18, -142, 288, -117, -865, 4123, 14459, + -14457, -14019, + 64, 128, -122, 36, 310, -768, 584, 9267, 17733, + 12, 128, 18, -142, 288, -117, -865, 4123, 14459, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_2_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR1 / 2 ] = { - 616, -14323, - -10, 39, 58, -46, -84, 120, 184, -315, -541, 1284, 5380, 9024, + 616, -14323, + -10, 39, 58, -46, -84, 120, 184, -315, -541, 1284, 5380, 9024, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_3_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { - 16102, -15162, - -13, 0, 20, 26, 5, -31, -43, -4, 65, 90, 7, -157, -248, -44, 593, 1583, 2612, 3271, + 16102, -15162, + -13, 0, 20, 26, 5, -31, -43, -4, 65, 90, 7, -157, -248, -44, 593, 1583, 2612, 3271, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_4_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { - 22500, -15099, - 3, -14, -20, -15, 2, 25, 37, 25, -16, -71, -107, -79, 50, 292, 623, 982, 1288, 1464, + 22500, -15099, + 3, -14, -20, -15, 2, 25, 37, 25, -16, -71, -107, -79, 50, 292, 623, 982, 1288, 1464, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_1_6_COEFS[ 2 + RESAMPLER_DOWN_ORDER_FIR2 / 2 ] = { - 27540, -15257, - 17, 12, 8, 1, -10, -22, -30, -32, -22, 3, 44, 100, 168, 243, 317, 381, 429, 455, + 27540, -15257, + 17, 12, 8, 1, -10, -22, -30, -32, -22, 3, 44, 100, 168, 243, 317, 381, 429, 455, }; silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ] = { @@ -81,16 +81,16 @@ silk_DWORD_ALIGN const opus_int16 silk_Resampler_2_3_COEFS_LQ[ 2 + 2 * 2 ] = { /* Table with interplation fractions of 1/24, 3/24, 5/24, ... , 23/24 : 23/24 (46 Words) */ silk_DWORD_ALIGN const opus_int16 silk_resampler_frac_FIR_12[ 12 ][ RESAMPLER_ORDER_FIR_12 / 2 ] = { - { 189, -600, 617, 30567 }, - { 117, -159, -1070, 29704 }, - { 52, 221, -2392, 28276 }, - { -4, 529, -3350, 26341 }, - { -48, 758, -3956, 23973 }, - { -80, 905, -4235, 21254 }, - { -99, 972, -4222, 18278 }, - { -107, 967, -3957, 15143 }, - { -103, 896, -3487, 11950 }, - { -91, 773, -2865, 8798 }, - { -71, 611, -2143, 5784 }, - { -46, 425, -1375, 2996 }, + { 189, -600, 617, 30567 }, + { 117, -159, -1070, 29704 }, + { 52, 221, -2392, 28276 }, + { -4, 529, -3350, 26341 }, + { -48, 758, -3956, 23973 }, + { -80, 905, -4235, 21254 }, + { -99, 972, -4222, 18278 }, + { -107, 967, -3957, 15143 }, + { -103, 896, -3487, 11950 }, + { -91, 773, -2865, 8798 }, + { -71, 611, -2143, 5784 }, + { -46, 425, -1375, 2996 }, }; diff --git a/TMessagesProj/jni/opus/silk/shell_coder.c b/TMessagesProj/jni/opus/silk/shell_coder.c index 796f57d6c20..4af341474bc 100644 --- a/TMessagesProj/jni/opus/silk/shell_coder.c +++ b/TMessagesProj/jni/opus/silk/shell_coder.c @@ -58,8 +58,8 @@ static OPUS_INLINE void encode_split( } static OPUS_INLINE void decode_split( - opus_int *p_child1, /* O pulse amplitude of first child subframe */ - opus_int *p_child2, /* O pulse amplitude of second child subframe */ + opus_int16 *p_child1, /* O pulse amplitude of first child subframe */ + opus_int16 *p_child2, /* O pulse amplitude of second child subframe */ ec_dec *psRangeDec, /* I/O Compressor data structure */ const opus_int p, /* I pulse amplitude of current subframe */ const opus_uint8 *shell_table /* I table of shell cdfs */ @@ -117,12 +117,12 @@ void silk_shell_encoder( /* Shell decoder, operates on one shell code frame of 16 pulses */ void silk_shell_decoder( - opus_int *pulses0, /* O data: nonnegative pulse amplitudes */ + opus_int16 *pulses0, /* O data: nonnegative pulse amplitudes */ ec_dec *psRangeDec, /* I/O Compressor data structure */ const opus_int pulses4 /* I number of pulses per pulse-subframe */ ) { - opus_int pulses3[ 2 ], pulses2[ 4 ], pulses1[ 8 ]; + opus_int16 pulses3[ 2 ], pulses2[ 4 ], pulses1[ 8 ]; /* this function operates on one shell code frame of 16 pulses */ silk_assert( SHELL_CODEC_FRAME_LENGTH == 16 ); diff --git a/TMessagesProj/jni/opus/silk/sort.c b/TMessagesProj/jni/opus/silk/sort.c index 8670dbdd02c..7187c9efb11 100644 --- a/TMessagesProj/jni/opus/silk/sort.c +++ b/TMessagesProj/jni/opus/silk/sort.c @@ -33,7 +33,7 @@ POSSIBILITY OF SUCH DAMAGE. /* Best case: O(n) for an already sorted array */ /* Worst case: O(n^2) for an inversely sorted array */ /* */ -/* Shell short: http://en.wikipedia.org/wiki/Shell_sort */ +/* Shell short: https://en.wikipedia.org/wiki/Shell_sort */ #include "SigProc_FIX.h" diff --git a/TMessagesProj/jni/opus/silk/stereo_LR_to_MS.c b/TMessagesProj/jni/opus/silk/stereo_LR_to_MS.c index 42906e6f676..dda0298de27 100644 --- a/TMessagesProj/jni/opus/silk/stereo_LR_to_MS.c +++ b/TMessagesProj/jni/opus/silk/stereo_LR_to_MS.c @@ -77,7 +77,7 @@ void silk_stereo_LR_to_MS( ALLOC( LP_mid, frame_length, opus_int16 ); ALLOC( HP_mid, frame_length, opus_int16 ); for( n = 0; n < frame_length; n++ ) { - sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT( mid[ n ] + mid[ n + 2 ], mid[ n + 1 ], 1 ), 2 ); + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 2 ); LP_mid[ n ] = sum; HP_mid[ n ] = mid[ n + 1 ] - sum; } @@ -86,7 +86,7 @@ void silk_stereo_LR_to_MS( ALLOC( LP_side, frame_length, opus_int16 ); ALLOC( HP_side, frame_length, opus_int16 ); for( n = 0; n < frame_length; n++ ) { - sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT( side[ n ] + side[ n + 2 ], side[ n + 1 ], 1 ), 2 ); + sum = silk_RSHIFT_ROUND( silk_ADD_LSHIFT( side[ n ] + (opus_int32)side[ n + 2 ], side[ n + 1 ], 1 ), 2 ); LP_side[ n ] = sum; HP_side[ n ] = side[ n + 1 ] - sum; } @@ -207,7 +207,7 @@ void silk_stereo_LR_to_MS( pred0_Q13 += delta0_Q13; pred1_Q13 += delta1_Q13; w_Q24 += deltaw_Q24; - sum = silk_LSHIFT( silk_ADD_LSHIFT( mid[ n ] + mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_LSHIFT( silk_ADD_LSHIFT( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); @@ -217,7 +217,7 @@ void silk_stereo_LR_to_MS( pred1_Q13 = -pred_Q13[ 1 ]; w_Q24 = silk_LSHIFT( width_Q14, 10 ); for( n = STEREO_INTERP_LEN_MS * fs_kHz; n < frame_length; n++ ) { - sum = silk_LSHIFT( silk_ADD_LSHIFT( mid[ n ] + mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ + sum = silk_LSHIFT( silk_ADD_LSHIFT( mid[ n ] + (opus_int32)mid[ n + 2 ], mid[ n + 1 ], 1 ), 9 ); /* Q11 */ sum = silk_SMLAWB( silk_SMULWB( w_Q24, side[ n + 1 ] ), sum, pred0_Q13 ); /* Q8 */ sum = silk_SMLAWB( sum, silk_LSHIFT( (opus_int32)mid[ n + 1 ], 11 ), pred1_Q13 ); /* Q8 */ x2[ n - 1 ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( sum, 8 ) ); diff --git a/TMessagesProj/jni/opus/silk/structs.h b/TMessagesProj/jni/opus/silk/structs.h index 1826b36a805..f7c9652fe66 100644 --- a/TMessagesProj/jni/opus/silk/structs.h +++ b/TMessagesProj/jni/opus/silk/structs.h @@ -48,6 +48,7 @@ typedef struct { opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; opus_int32 sLF_AR_shp_Q14; + opus_int32 sDiff_shp_Q14; opus_int lagPrev; opus_int sLTP_buf_idx; opus_int sLTP_shp_buf_idx; @@ -86,6 +87,7 @@ typedef struct { const opus_int16 quantStepSize_Q16; const opus_int16 invQuantStepSize_Q6; const opus_uint8 *CB1_NLSF_Q8; + const opus_int16 *CB1_Wght_Q9; const opus_uint8 *CB1_iCDF; const opus_uint8 *pred_Q8; const opus_uint8 *ec_sel; @@ -169,9 +171,7 @@ typedef struct { opus_int pitchEstimationComplexity; /* Complexity level for pitch estimator */ opus_int pitchEstimationLPCOrder; /* Whitening filter order for pitch estimator */ opus_int32 pitchEstimationThreshold_Q16; /* Threshold for pitch estimator */ - opus_int LTPQuantLowComplexity; /* Flag for low complexity LTP quantization */ - opus_int mu_LTP_Q9; /* Rate-distortion tradeoff in LTP quantization */ - opus_int32 sum_log_gain_Q7; /* Cumulative max prediction gain */ + opus_int32 sum_log_gain_Q7; /* Cumulative max prediction gain */ opus_int NLSF_MSVQ_Survivors; /* Number of survivors in NLSF MSVQ */ opus_int first_frame_after_reset; /* Flag for deactivating NLSF interpolation, pitch prediction */ opus_int controlled_since_last_payload; /* Flag for ensuring codec_control only runs once per packet */ diff --git a/TMessagesProj/jni/opus/silk/sum_sqr_shift.c b/TMessagesProj/jni/opus/silk/sum_sqr_shift.c index 12514c9917a..4fd0c3d7d53 100644 --- a/TMessagesProj/jni/opus/silk/sum_sqr_shift.c +++ b/TMessagesProj/jni/opus/silk/sum_sqr_shift.c @@ -41,42 +41,40 @@ void silk_sum_sqr_shift( ) { opus_int i, shft; - opus_int32 nrg_tmp, nrg; + opus_uint32 nrg_tmp; + opus_int32 nrg; - nrg = 0; - shft = 0; - len--; - for( i = 0; i < len; i += 2 ) { - nrg = silk_SMLABB_ovflw( nrg, x[ i ], x[ i ] ); - nrg = silk_SMLABB_ovflw( nrg, x[ i + 1 ], x[ i + 1 ] ); - if( nrg < 0 ) { - /* Scale down */ - nrg = (opus_int32)silk_RSHIFT_uint( (opus_uint32)nrg, 2 ); - shft = 2; - break; - } + /* Do a first run with the maximum shift we could have. */ + shft = 31-silk_CLZ32(len); + /* Let's be conservative with rounding and start with nrg=len. */ + nrg = len; + for( i = 0; i < len - 1; i += 2 ) { + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); } - for( ; i < len; i += 2 ) { + if( i < len ) { + /* One sample left to process */ + nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); + } + silk_assert( nrg >= 0 ); + /* Make sure the result will fit in a 32-bit signed integer with two bits + of headroom. */ + shft = silk_max_32(0, shft+3 - silk_CLZ32(nrg)); + nrg = 0; + for( i = 0 ; i < len - 1; i += 2 ) { nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); nrg_tmp = silk_SMLABB_ovflw( nrg_tmp, x[ i + 1 ], x[ i + 1 ] ); - nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, (opus_uint32)nrg_tmp, shft ); - if( nrg < 0 ) { - /* Scale down */ - nrg = (opus_int32)silk_RSHIFT_uint( (opus_uint32)nrg, 2 ); - shft += 2; - } + nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); } - if( i == len ) { + if( i < len ) { /* One sample left to process */ nrg_tmp = silk_SMULBB( x[ i ], x[ i ] ); nrg = (opus_int32)silk_ADD_RSHIFT_uint( nrg, nrg_tmp, shft ); } - /* Make sure to have at least one extra leading zero (two leading zeros in total) */ - if( nrg & 0xC0000000 ) { - nrg = silk_RSHIFT_uint( (opus_uint32)nrg, 2 ); - shft += 2; - } + silk_assert( nrg >= 0 ); /* Output arguments */ *shift = shft; diff --git a/TMessagesProj/jni/opus/silk/tables.h b/TMessagesProj/jni/opus/silk/tables.h index a91431e8543..8b0380eeb09 100644 --- a/TMessagesProj/jni/opus/silk/tables.h +++ b/TMessagesProj/jni/opus/silk/tables.h @@ -47,8 +47,8 @@ extern const opus_uint8 silk_pitch_contour_NB_iCDF[ 11 ]; extern const opus_uint8 silk_pitch_contour_10_ms_iCDF[ 12 ]; /* 12 */ extern const opus_uint8 silk_pitch_contour_10_ms_NB_iCDF[ 3 ]; /* 3 */ -extern const opus_uint8 silk_pulses_per_block_iCDF[ N_RATE_LEVELS ][ MAX_PULSES + 2 ]; /* 180 */ -extern const opus_uint8 silk_pulses_per_block_BITS_Q5[ N_RATE_LEVELS - 1 ][ MAX_PULSES + 2 ]; /* 162 */ +extern const opus_uint8 silk_pulses_per_block_iCDF[ N_RATE_LEVELS ][ SILK_MAX_PULSES + 2 ]; /* 180 */ +extern const opus_uint8 silk_pulses_per_block_BITS_Q5[ N_RATE_LEVELS - 1 ][ SILK_MAX_PULSES + 2 ]; /* 162 */ extern const opus_uint8 silk_rate_levels_iCDF[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ extern const opus_uint8 silk_rate_levels_BITS_Q5[ 2 ][ N_RATE_LEVELS - 1 ]; /* 18 */ @@ -59,7 +59,7 @@ extern const opus_uint8 silk_shell_code_table0[ 152 ]; extern const opus_uint8 silk_shell_code_table1[ 152 ]; /* 152 */ extern const opus_uint8 silk_shell_code_table2[ 152 ]; /* 152 */ extern const opus_uint8 silk_shell_code_table3[ 152 ]; /* 152 */ -extern const opus_uint8 silk_shell_code_table_offsets[ MAX_PULSES + 1 ]; /* 17 */ +extern const opus_uint8 silk_shell_code_table_offsets[ SILK_MAX_PULSES + 1 ]; /* 17 */ extern const opus_uint8 silk_lsb_iCDF[ 2 ]; /* 2 */ @@ -76,10 +76,8 @@ extern const opus_uint8 silk_NLSF_EXT_iCDF[ 7 ]; extern const opus_uint8 silk_LTP_per_index_iCDF[ 3 ]; /* 3 */ extern const opus_uint8 * const silk_LTP_gain_iCDF_ptrs[ NB_LTP_CBKS ]; /* 3 */ extern const opus_uint8 * const silk_LTP_gain_BITS_Q5_ptrs[ NB_LTP_CBKS ]; /* 3 */ -extern const opus_int16 silk_LTP_gain_middle_avg_RD_Q14; extern const opus_int8 * const silk_LTP_vq_ptrs_Q7[ NB_LTP_CBKS ]; /* 168 */ extern const opus_uint8 * const silk_LTP_vq_gain_ptrs_Q7[NB_LTP_CBKS]; - extern const opus_int8 silk_LTP_vq_sizes[ NB_LTP_CBKS ]; /* 3 */ extern const opus_uint8 silk_LTPscale_iCDF[ 3 ]; /* 4 */ diff --git a/TMessagesProj/jni/opus/silk/tables_LTP.c b/TMessagesProj/jni/opus/silk/tables_LTP.c index 0e6a0254d5d..5e12c8643e5 100644 --- a/TMessagesProj/jni/opus/silk/tables_LTP.c +++ b/TMessagesProj/jni/opus/silk/tables_LTP.c @@ -51,8 +51,6 @@ static const opus_uint8 silk_LTP_gain_iCDF_2[32] = { 24, 20, 16, 12, 9, 5, 2, 0 }; -const opus_int16 silk_LTP_gain_middle_avg_RD_Q14 = 12304; - static const opus_uint8 silk_LTP_gain_BITS_Q5_0[8] = { 15, 131, 138, 138, 155, 155, 173, 173 }; diff --git a/TMessagesProj/jni/opus/silk/tables_NLSF_CB_NB_MB.c b/TMessagesProj/jni/opus/silk/tables_NLSF_CB_NB_MB.c index 8c59d207aa0..195d5b95bd1 100644 --- a/TMessagesProj/jni/opus/silk/tables_NLSF_CB_NB_MB.c +++ b/TMessagesProj/jni/opus/silk/tables_NLSF_CB_NB_MB.c @@ -74,6 +74,41 @@ static const opus_uint8 silk_NLSF_CB1_NB_MB_Q8[ 320 ] = { 64, 84, 104, 118, 156, 177, 201, 230 }; +static const opus_int16 silk_NLSF_CB1_Wght_Q9[ 320 ] = { + 2897, 2314, 2314, 2314, 2287, 2287, 2314, 2300, 2327, 2287, + 2888, 2580, 2394, 2367, 2314, 2274, 2274, 2274, 2274, 2194, + 2487, 2340, 2340, 2314, 2314, 2314, 2340, 2340, 2367, 2354, + 3216, 2766, 2340, 2340, 2314, 2274, 2221, 2207, 2261, 2194, + 2460, 2474, 2367, 2394, 2394, 2394, 2394, 2367, 2407, 2314, + 3479, 3056, 2127, 2207, 2274, 2274, 2274, 2287, 2314, 2261, + 3282, 3141, 2580, 2394, 2247, 2221, 2207, 2194, 2194, 2114, + 4096, 3845, 2221, 2620, 2620, 2407, 2314, 2394, 2367, 2074, + 3178, 3244, 2367, 2221, 2553, 2434, 2340, 2314, 2167, 2221, + 3338, 3488, 2726, 2194, 2261, 2460, 2354, 2367, 2207, 2101, + 2354, 2420, 2327, 2367, 2394, 2420, 2420, 2420, 2460, 2367, + 3779, 3629, 2434, 2527, 2367, 2274, 2274, 2300, 2207, 2048, + 3254, 3225, 2713, 2846, 2447, 2327, 2300, 2300, 2274, 2127, + 3263, 3300, 2753, 2806, 2447, 2261, 2261, 2247, 2127, 2101, + 2873, 2981, 2633, 2367, 2407, 2354, 2194, 2247, 2247, 2114, + 3225, 3197, 2633, 2580, 2274, 2181, 2247, 2221, 2221, 2141, + 3178, 3310, 2740, 2407, 2274, 2274, 2274, 2287, 2194, 2114, + 3141, 3272, 2460, 2061, 2287, 2500, 2367, 2487, 2434, 2181, + 3507, 3282, 2314, 2700, 2647, 2474, 2367, 2394, 2340, 2127, + 3423, 3535, 3038, 3056, 2300, 1950, 2221, 2274, 2274, 2274, + 3404, 3366, 2087, 2687, 2873, 2354, 2420, 2274, 2474, 2540, + 3760, 3488, 1950, 2660, 2897, 2527, 2394, 2367, 2460, 2261, + 3028, 3272, 2740, 2888, 2740, 2154, 2127, 2287, 2234, 2247, + 3695, 3657, 2025, 1969, 2660, 2700, 2580, 2500, 2327, 2367, + 3207, 3413, 2354, 2074, 2888, 2888, 2340, 2487, 2247, 2167, + 3338, 3366, 2846, 2780, 2327, 2154, 2274, 2287, 2114, 2061, + 2327, 2300, 2181, 2167, 2181, 2367, 2633, 2700, 2700, 2553, + 2407, 2434, 2221, 2261, 2221, 2221, 2340, 2420, 2607, 2700, + 3038, 3244, 2806, 2888, 2474, 2074, 2300, 2314, 2354, 2380, + 2221, 2154, 2127, 2287, 2500, 2793, 2793, 2620, 2580, 2367, + 3676, 3713, 2234, 1838, 2181, 2753, 2726, 2673, 2513, 2207, + 2793, 3160, 2726, 2553, 2846, 2513, 2181, 2394, 2221, 2181 +}; + static const opus_uint8 silk_NLSF_CB1_iCDF_NB_MB[ 64 ] = { 212, 178, 148, 129, 108, 96, 85, 82, 79, 77, 61, 59, 57, 56, 51, 49, @@ -150,6 +185,7 @@ const silk_NLSF_CB_struct silk_NLSF_CB_NB_MB = SILK_FIX_CONST( 0.18, 16 ), SILK_FIX_CONST( 1.0 / 0.18, 6 ), silk_NLSF_CB1_NB_MB_Q8, + silk_NLSF_CB1_Wght_Q9, silk_NLSF_CB1_iCDF_NB_MB, silk_NLSF_PRED_NB_MB_Q8, silk_NLSF_CB2_SELECT_NB_MB, diff --git a/TMessagesProj/jni/opus/silk/tables_NLSF_CB_WB.c b/TMessagesProj/jni/opus/silk/tables_NLSF_CB_WB.c index 50af87eb2e1..a15f7eb423d 100644 --- a/TMessagesProj/jni/opus/silk/tables_NLSF_CB_WB.c +++ b/TMessagesProj/jni/opus/silk/tables_NLSF_CB_WB.c @@ -98,6 +98,41 @@ static const opus_uint8 silk_NLSF_CB1_WB_Q8[ 512 ] = { 110, 119, 129, 141, 175, 198, 218, 237 }; +static const opus_int16 silk_NLSF_CB1_Wght_Q9[ 512 ] = { + 3657, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2963, 2963, 2925, 2846, + 3216, 3085, 2972, 3056, 3056, 3010, 3010, 3010, 2963, 2963, 3010, 2972, 2888, 2846, 2846, 2726, + 3920, 4014, 2981, 3207, 3207, 2934, 3056, 2846, 3122, 3244, 2925, 2846, 2620, 2553, 2780, 2925, + 3516, 3197, 3010, 3103, 3019, 2888, 2925, 2925, 2925, 2925, 2888, 2888, 2888, 2888, 2888, 2753, + 5054, 5054, 2934, 3573, 3385, 3056, 3085, 2793, 3160, 3160, 2972, 2846, 2513, 2540, 2753, 2888, + 4428, 4149, 2700, 2753, 2972, 3010, 2925, 2846, 2981, 3019, 2925, 2925, 2925, 2925, 2888, 2726, + 3620, 3019, 2972, 3056, 3056, 2873, 2806, 3056, 3216, 3047, 2981, 3291, 3291, 2981, 3310, 2991, + 5227, 5014, 2540, 3338, 3526, 3385, 3197, 3094, 3376, 2981, 2700, 2647, 2687, 2793, 2846, 2673, + 5081, 5174, 4615, 4428, 2460, 2897, 3047, 3207, 3169, 2687, 2740, 2888, 2846, 2793, 2846, 2700, + 3122, 2888, 2963, 2925, 2925, 2925, 2925, 2963, 2963, 2963, 2963, 2925, 2925, 2963, 2963, 2963, + 4202, 3207, 2981, 3103, 3010, 2888, 2888, 2925, 2972, 2873, 2916, 3019, 2972, 3010, 3197, 2873, + 3760, 3760, 3244, 3103, 2981, 2888, 2925, 2888, 2972, 2934, 2793, 2793, 2846, 2888, 2888, 2660, + 3854, 4014, 3207, 3122, 3244, 2934, 3047, 2963, 2963, 3085, 2846, 2793, 2793, 2793, 2793, 2580, + 3845, 4080, 3357, 3516, 3094, 2740, 3010, 2934, 3122, 3085, 2846, 2846, 2647, 2647, 2846, 2806, + 5147, 4894, 3225, 3845, 3441, 3169, 2897, 3413, 3451, 2700, 2580, 2673, 2740, 2846, 2806, 2753, + 4109, 3789, 3291, 3160, 2925, 2888, 2888, 2925, 2793, 2740, 2793, 2740, 2793, 2846, 2888, 2806, + 5081, 5054, 3047, 3545, 3244, 3056, 3085, 2944, 3103, 2897, 2740, 2740, 2740, 2846, 2793, 2620, + 4309, 4309, 2860, 2527, 3207, 3376, 3376, 3075, 3075, 3376, 3056, 2846, 2647, 2580, 2726, 2753, + 3056, 2916, 2806, 2888, 2740, 2687, 2897, 3103, 3150, 3150, 3216, 3169, 3056, 3010, 2963, 2846, + 4375, 3882, 2925, 2888, 2846, 2888, 2846, 2846, 2888, 2888, 2888, 2846, 2888, 2925, 2888, 2846, + 2981, 2916, 2916, 2981, 2981, 3056, 3122, 3216, 3150, 3056, 3010, 2972, 2972, 2972, 2925, 2740, + 4229, 4149, 3310, 3347, 2925, 2963, 2888, 2981, 2981, 2846, 2793, 2740, 2846, 2846, 2846, 2793, + 4080, 4014, 3103, 3010, 2925, 2925, 2925, 2888, 2925, 2925, 2846, 2846, 2846, 2793, 2888, 2780, + 4615, 4575, 3169, 3441, 3207, 2981, 2897, 3038, 3122, 2740, 2687, 2687, 2687, 2740, 2793, 2700, + 4149, 4269, 3789, 3657, 2726, 2780, 2888, 2888, 3010, 2972, 2925, 2846, 2687, 2687, 2793, 2888, + 4215, 3554, 2753, 2846, 2846, 2888, 2888, 2888, 2925, 2925, 2888, 2925, 2925, 2925, 2963, 2888, + 5174, 4921, 2261, 3432, 3789, 3479, 3347, 2846, 3310, 3479, 3150, 2897, 2460, 2487, 2753, 2925, + 3451, 3685, 3122, 3197, 3357, 3047, 3207, 3207, 2981, 3216, 3085, 2925, 2925, 2687, 2540, 2434, + 2981, 3010, 2793, 2793, 2740, 2793, 2846, 2972, 3056, 3103, 3150, 3150, 3150, 3103, 3010, 3010, + 2944, 2873, 2687, 2726, 2780, 3010, 3432, 3545, 3357, 3244, 3056, 3010, 2963, 2925, 2888, 2846, + 3019, 2944, 2897, 3010, 3010, 2972, 3019, 3103, 3056, 3056, 3010, 2888, 2846, 2925, 2925, 2888, + 3920, 3967, 3010, 3197, 3357, 3216, 3291, 3291, 3479, 3704, 3441, 2726, 2181, 2460, 2580, 2607 +}; + static const opus_uint8 silk_NLSF_CB1_iCDF_WB[ 64 ] = { 225, 204, 201, 184, 183, 175, 158, 154, 153, 135, 119, 115, 113, 110, 109, 99, @@ -188,6 +223,7 @@ const silk_NLSF_CB_struct silk_NLSF_CB_WB = SILK_FIX_CONST( 0.15, 16 ), SILK_FIX_CONST( 1.0 / 0.15, 6 ), silk_NLSF_CB1_WB_Q8, + silk_NLSF_CB1_Wght_Q9, silk_NLSF_CB1_iCDF_WB, silk_NLSF_PRED_WB_Q8, silk_NLSF_CB2_SELECT_WB, diff --git a/TMessagesProj/jni/opus/silk/tuning_parameters.h b/TMessagesProj/jni/opus/silk/tuning_parameters.h index e1057bbaae3..d70275fd8f5 100644 --- a/TMessagesProj/jni/opus/silk/tuning_parameters.h +++ b/TMessagesProj/jni/opus/silk/tuning_parameters.h @@ -53,18 +53,11 @@ extern "C" /* LPC analysis regularization */ #define FIND_LPC_COND_FAC 1e-5f -/* LTP analysis defines */ -#define FIND_LTP_COND_FAC 1e-5f -#define LTP_DAMPING 0.05f -#define LTP_SMOOTHING 0.1f - -/* LTP quantization settings */ -#define MU_LTP_QUANT_NB 0.03f -#define MU_LTP_QUANT_MB 0.025f -#define MU_LTP_QUANT_WB 0.02f - /* Max cumulative LTP gain */ -#define MAX_SUM_LOG_GAIN_DB 250.0f +#define MAX_SUM_LOG_GAIN_DB 250.0f + +/* LTP analysis defines */ +#define LTP_CORR_INV_MAX 0.03f /***********************/ /* High pass filtering */ @@ -103,25 +96,16 @@ extern "C" #define SPARSE_SNR_INCR_dB 2.0f /* threshold for sparseness measure above which to use lower quantization offset during unvoiced */ -#define SPARSENESS_THRESHOLD_QNT_OFFSET 0.75f +#define ENERGY_VARIATION_THRESHOLD_QNT_OFFSET 0.6f /* warping control */ #define WARPING_MULTIPLIER 0.015f /* fraction added to first autocorrelation value */ -#define SHAPE_WHITE_NOISE_FRACTION 5e-5f +#define SHAPE_WHITE_NOISE_FRACTION 3e-5f /* noise shaping filter chirp factor */ -#define BANDWIDTH_EXPANSION 0.95f - -/* difference between chirp factors for analysis and synthesis noise shaping filters at low bitrates */ -#define LOW_RATE_BANDWIDTH_EXPANSION_DELTA 0.01f - -/* extra harmonic boosting (signal shaping) at low bitrates */ -#define LOW_RATE_HARMONIC_BOOST 0.1f - -/* extra harmonic boosting (signal shaping) for noisy input signals */ -#define LOW_INPUT_QUALITY_HARMONIC_BOOST 0.1f +#define BANDWIDTH_EXPANSION 0.94f /* harmonic noise shaping */ #define HARMONIC_SHAPING 0.3f diff --git a/TMessagesProj/jni/opus/silk/x86/NSQ_del_dec_sse.c b/TMessagesProj/jni/opus/silk/x86/NSQ_del_dec_sse.c new file mode 100644 index 00000000000..a6f84e1c0c2 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/NSQ_del_dec_sse.c @@ -0,0 +1,860 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" + +#include "stack_alloc.h" + +typedef struct { + opus_int32 sLPC_Q14[ MAX_SUB_FRAME_LENGTH + NSQ_LPC_BUF_LENGTH ]; + opus_int32 RandState[ DECISION_DELAY ]; + opus_int32 Q_Q10[ DECISION_DELAY ]; + opus_int32 Xq_Q14[ DECISION_DELAY ]; + opus_int32 Pred_Q15[ DECISION_DELAY ]; + opus_int32 Shape_Q14[ DECISION_DELAY ]; + opus_int32 sAR2_Q14[ MAX_SHAPE_LPC_ORDER ]; + opus_int32 LF_AR_Q14; + opus_int32 Seed; + opus_int32 SeedInit; + opus_int32 RD_Q10; +} NSQ_del_dec_struct; + +typedef struct { + opus_int32 Q_Q10; + opus_int32 RD_Q10; + opus_int32 xq_Q14; + opus_int32 LF_AR_Q14; + opus_int32 sLTP_shp_Q14; + opus_int32 LPC_exc_Q14; +} NSQ_sample_struct; + +typedef NSQ_sample_struct NSQ_sample_pair[ 2 ]; + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int32 x_Q3[], /* I Input in Q3 */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +); + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +); + +void silk_NSQ_del_dec_sse4_1( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int i, k, lag, start_idx, LSF_interpolation_flag, Winner_ind, subfr; + opus_int last_smple_idx, smpl_buf_idx, decisionDelay; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + opus_int32 RDmin_Q10, Gain_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + VARDECL( opus_int32, delayedGain_Q10 ); + VARDECL( NSQ_del_dec_struct, psDelDec ); + NSQ_del_dec_struct *psDD; + SAVE_STACK; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + /* Initialize delayed decision states */ + ALLOC( psDelDec, psEncC->nStatesDelayedDecision, NSQ_del_dec_struct ); + silk_memset( psDelDec, 0, psEncC->nStatesDelayedDecision * sizeof( NSQ_del_dec_struct ) ); + for( k = 0; k < psEncC->nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psDD->Seed = ( k + psIndices->Seed ) & 3; + psDD->SeedInit = psDD->Seed; + psDD->RD_Q10 = 0; + psDD->LF_AR_Q14 = NSQ->sLF_AR_shp_Q14; + psDD->Shape_Q14[ 0 ] = NSQ->sLTP_shp_Q14[ psEncC->ltp_mem_length - 1 ]; + silk_memcpy( psDD->sLPC_Q14, NSQ->sLPC_Q14, NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( psDD->sAR2_Q14, NSQ->sAR2_Q14, sizeof( NSQ->sAR2_Q14 ) ); + } + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + smpl_buf_idx = 0; /* index of oldest samples */ + + decisionDelay = silk_min_int( DECISION_DELAY, psEncC->subfr_length ); + + /* For voiced frames limit the decision delay to lower than the pitch lag */ + if( psIndices->signalType == TYPE_VOICED ) { + for( k = 0; k < psEncC->nb_subfr; k++ ) { + decisionDelay = silk_min_int( decisionDelay, pitchL[ k ] - LTP_ORDER / 2 - 1 ); + } + } else { + if( lag > 0 ) { + decisionDelay = silk_min_int( decisionDelay, lag - LTP_ORDER / 2 - 1 ); + } + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, + psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + ALLOC( delayedGain_Q10, DECISION_DELAY, opus_int32 ); + /* Set up pointers to start of sub frame */ + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + subfr = 0; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ ( ( k >> 1 ) | ( 1 - LSF_interpolation_flag ) ) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR2_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + if( k == 2 ) { + /* RESET DELAYED DECISIONS */ + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( i = 1; i < psEncC->nStatesDelayedDecision; i++ ) { + if( psDelDec[ i ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ i ].RD_Q10; + Winner_ind = i; + } + } + for( i = 0; i < psEncC->nStatesDelayedDecision; i++ ) { + if( i != Winner_ind ) { + psDelDec[ i ].RD_Q10 += ( silk_int32_MAX >> 4 ); + silk_assert( psDelDec[ i ].RD_Q10 >= 0 ); + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + last_smple_idx = smpl_buf_idx + decisionDelay; + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gains_Q16[ 1 ] ), 14 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + + subfr = 0; + } + + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + silk_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + NSQ->rewhite_flag = 1; + } + } + + silk_nsq_del_dec_scale_states_sse4_1( psEncC, NSQ, psDelDec, x_Q3, x_sc_Q10, sLTP, sLTP_Q15, k, + psEncC->nStatesDelayedDecision, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType, decisionDelay ); + + silk_noise_shape_quantizer_del_dec_sse4_1( NSQ, psDelDec, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, + delayedGain_Q10, A_Q12, B_Q14, AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], + Gains_Q16[ k ], Lambda_Q10, offset_Q10, psEncC->subfr_length, subfr++, psEncC->shapingLPCOrder, + psEncC->predictLPCOrder, psEncC->warping_Q16, psEncC->nStatesDelayedDecision, &smpl_buf_idx, decisionDelay ); + + x_Q3 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Find winner */ + RDmin_Q10 = psDelDec[ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < psEncC->nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psDelDec[ k ].RD_Q10; + Winner_ind = k; + } + } + + /* Copy final part of signals from winner state to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + psIndices->Seed = psDD->SeedInit; + last_smple_idx = smpl_buf_idx + decisionDelay; + Gain_Q10 = silk_RSHIFT32( Gains_Q16[ psEncC->nb_subfr - 1 ], 6 ); + for( i = 0; i < decisionDelay; i++ ) { + last_smple_idx = ( last_smple_idx - 1 ) % DECISION_DELAY; + if( last_smple_idx < 0 ) last_smple_idx += DECISION_DELAY; + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + pxq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], Gain_Q10 ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay + i ] = psDD->Shape_Q14[ last_smple_idx ]; + } + silk_memcpy( NSQ->sLPC_Q14, &psDD->sLPC_Q14[ psEncC->subfr_length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + silk_memcpy( NSQ->sAR2_Q14, psDD->sAR2_Q14, sizeof( psDD->sAR2_Q14 ) ); + + /* Update states */ + NSQ->sLF_AR_shp_Q14 = psDD->LF_AR_Q14; + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech signal */ + /* DEBUG_STORE_DATA( enc.pcm, &NSQ->xq[psEncC->ltp_mem_length], psEncC->frame_length * sizeof( opus_int16 ) ) */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/******************************************/ +/* Noise shape quantizer for one subframe */ +/******************************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_del_dec_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP filter state */ + opus_int32 delayedGain_Q10[], /* I/O Gain delay buffer */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int subfr, /* I Subframe number */ + opus_int shapingLPCOrder, /* I Shaping LPC filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + opus_int warping_Q16, /* I */ + opus_int nStatesDelayedDecision, /* I Number of states in decision tree */ + opus_int *smpl_buf_idx, /* I Index to newest samples in buffers */ + opus_int decisionDelay /* I */ +) +{ + opus_int i, j, k, Winner_ind, RDmin_ind, RDmax_ind, last_smple_idx; + opus_int32 Winner_rand_state; + opus_int32 LTP_pred_Q14, LPC_pred_Q14, n_AR_Q14, n_LTP_Q14; + opus_int32 n_LF_Q14, r_Q10, rr_Q10, rd1_Q10, rd2_Q10, RDmin_Q10, RDmax_Q10; + opus_int32 q1_Q0, q1_Q10, q2_Q10, exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *pred_lag_ptr, *shp_lag_ptr, *psLPC_Q14; + VARDECL( NSQ_sample_pair, psSampleState ); + NSQ_del_dec_struct *psDD; + NSQ_sample_struct *psSS; + + __m128i a_Q12_0123, a_Q12_4567, a_Q12_89AB, a_Q12_CDEF; + __m128i b_Q12_0123, b_sr_Q12_0123; + SAVE_STACK; + + silk_assert( nStatesDelayedDecision > 0 ); + ALLOC( psSampleState, nStatesDelayedDecision, NSQ_sample_pair ); + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + a_Q12_0123 = OP_CVTEPI16_EPI32_M64( a_Q12 ); + a_Q12_4567 = OP_CVTEPI16_EPI32_M64( a_Q12 + 4 ); + + if( opus_likely( predictLPCOrder == 16 ) ) { + a_Q12_89AB = OP_CVTEPI16_EPI32_M64( a_Q12 + 8 ); + a_Q12_CDEF = OP_CVTEPI16_EPI32_M64( a_Q12 + 12 ); + } + + if( signalType == TYPE_VOICED ){ + b_Q12_0123 = OP_CVTEPI16_EPI32_M64( b_Q14 ); + b_sr_Q12_0123 = _mm_shuffle_epi32( b_Q12_0123, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + } + for( i = 0; i < length; i++ ) { + /* Perform common calculations used in all states */ + + /* Long-term prediction */ + if( signalType == TYPE_VOICED ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q14 = 2; + { + __m128i tmpa, tmpb, pred_lag_ptr_tmp; + pred_lag_ptr_tmp = _mm_loadu_si128( (__m128i *)(&pred_lag_ptr[ -3 ] ) ); + pred_lag_ptr_tmp = _mm_shuffle_epi32( pred_lag_ptr_tmp, 0x1B ); + tmpa = _mm_mul_epi32( pred_lag_ptr_tmp, b_Q12_0123 ); + tmpa = _mm_srli_si128( tmpa, 2 ); + + pred_lag_ptr_tmp = _mm_shuffle_epi32( pred_lag_ptr_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) );/* equal shift right 4 bytes */ + pred_lag_ptr_tmp = _mm_mul_epi32( pred_lag_ptr_tmp, b_sr_Q12_0123 ); + pred_lag_ptr_tmp = _mm_srli_si128( pred_lag_ptr_tmp, 2 ); + pred_lag_ptr_tmp = _mm_add_epi32( pred_lag_ptr_tmp, tmpa ); + + tmpb = _mm_shuffle_epi32( pred_lag_ptr_tmp, _MM_SHUFFLE( 0, 0, 3, 2 ) );/* equal shift right 8 bytes */ + pred_lag_ptr_tmp = _mm_add_epi32( pred_lag_ptr_tmp, tmpb ); + LTP_pred_Q14 += _mm_cvtsi128_si32( pred_lag_ptr_tmp ); + + LTP_pred_Q14 = silk_SMLAWB( LTP_pred_Q14, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + LTP_pred_Q14 = silk_LSHIFT( LTP_pred_Q14, 1 ); /* Q13 -> Q14 */ + pred_lag_ptr++; + } + } else { + LTP_pred_Q14 = 0; + } + + /* Long-term shaping */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q14 = silk_SMULWB( silk_ADD32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SMLAWT( n_LTP_Q14, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q14 = silk_SUB_LSHIFT32( LTP_pred_Q14, n_LTP_Q14, 2 ); /* Q12 -> Q14 */ + shp_lag_ptr++; + } else { + n_LTP_Q14 = 0; + } + { + __m128i tmpa, tmpb, psLPC_Q14_tmp, a_Q12_tmp; + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + /* Delayed decision state */ + psDD = &psDelDec[ k ]; + + /* Sample state */ + psSS = psSampleState[ k ]; + + /* Generate dither */ + psDD->Seed = silk_RAND( psDD->Seed ); + + /* Pointer used in short term prediction and shaping */ + psLPC_Q14 = &psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 + i ]; + /* Short-term prediction */ + silk_assert( predictLPCOrder == 10 || predictLPCOrder == 16 ); + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q14 = silk_RSHIFT( predictLPCOrder, 1 ); + + tmpb = _mm_setzero_si128(); + + /* step 1 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -3 ] ) ); /* -3, -2 , -1, 0 */ + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); /* 0, -1, -2, -3 */ + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_0123 ); /* 0, -1, -2, -3 * 0123 -> 0*0, 2*-2 */ + + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_0123, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); /* 1*-1, 3*-3 */ + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* step 2 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -7 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_4567 ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_4567, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + if ( opus_likely( predictLPCOrder == 16 ) ) + { + /* step 3 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -11 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_89AB ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_89AB, _MM_SHUFFLE(0, 3, 2, 1 ) );/* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* setp 4 */ + psLPC_Q14_tmp = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -15 ] ) ); + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, 0x1B ); + tmpa = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_CDEF ); + tmpa = _mm_srli_epi64( tmpa, 16 ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + + psLPC_Q14_tmp = _mm_shuffle_epi32( psLPC_Q14_tmp, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + a_Q12_tmp = _mm_shuffle_epi32( a_Q12_CDEF, _MM_SHUFFLE(0, 3, 2, 1 ) ); /* equal shift right 4 bytes */ + psLPC_Q14_tmp = _mm_mul_epi32( psLPC_Q14_tmp, a_Q12_tmp ); + psLPC_Q14_tmp = _mm_srli_epi64( psLPC_Q14_tmp, 16 ); + tmpb = _mm_add_epi32( tmpb, psLPC_Q14_tmp ); + + /* add at last */ + /* equal shift right 8 bytes*/ + tmpa = _mm_shuffle_epi32( tmpb, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + tmpb = _mm_add_epi32( tmpb, tmpa ); + LPC_pred_Q14 += _mm_cvtsi128_si32( tmpb ); + } + else + { + /* add at last */ + tmpa = _mm_shuffle_epi32( tmpb, _MM_SHUFFLE( 0, 0, 3, 2 ) ); /* equal shift right 8 bytes*/ + tmpb = _mm_add_epi32( tmpb, tmpa ); + LPC_pred_Q14 += _mm_cvtsi128_si32( tmpb ); + + LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -8 ], a_Q12[ 8 ] ); + LPC_pred_Q14 = silk_SMLAWB( LPC_pred_Q14, psLPC_Q14[ -9 ], a_Q12[ 9 ] ); + } + + LPC_pred_Q14 = silk_LSHIFT( LPC_pred_Q14, 4 ); /* Q10 -> Q14 */ + + /* Noise shape feedback */ + silk_assert( ( shapingLPCOrder & 1 ) == 0 ); /* check that order is even */ + /* Output of lowpass section */ + tmp2 = silk_SMLAWB( psLPC_Q14[ 0 ], psDD->sAR2_Q14[ 0 ], warping_Q16 ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ 0 ], psDD->sAR2_Q14[ 1 ] - tmp2, warping_Q16 ); + psDD->sAR2_Q14[ 0 ] = tmp2; + n_AR_Q14 = silk_RSHIFT( shapingLPCOrder, 1 ); + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ 0 ] ); + /* Loop over allpass sections */ + for( j = 2; j < shapingLPCOrder; j += 2 ) { + /* Output of allpass section */ + tmp2 = silk_SMLAWB( psDD->sAR2_Q14[ j - 1 ], psDD->sAR2_Q14[ j + 0 ] - tmp1, warping_Q16 ); + psDD->sAR2_Q14[ j - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ j - 1 ] ); + /* Output of allpass section */ + tmp1 = silk_SMLAWB( psDD->sAR2_Q14[ j + 0 ], psDD->sAR2_Q14[ j + 1 ] - tmp2, warping_Q16 ); + psDD->sAR2_Q14[ j + 0 ] = tmp2; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp2, AR_shp_Q13[ j ] ); + } + psDD->sAR2_Q14[ shapingLPCOrder - 1 ] = tmp1; + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, tmp1, AR_shp_Q13[ shapingLPCOrder - 1 ] ); + + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 1 ); /* Q11 -> Q12 */ + n_AR_Q14 = silk_SMLAWB( n_AR_Q14, psDD->LF_AR_Q14, Tilt_Q14 ); /* Q12 */ + n_AR_Q14 = silk_LSHIFT( n_AR_Q14, 2 ); /* Q12 -> Q14 */ + + n_LF_Q14 = silk_SMULWB( psDD->Shape_Q14[ *smpl_buf_idx ], LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_SMLAWT( n_LF_Q14, psDD->LF_AR_Q14, LF_shp_Q14 ); /* Q12 */ + n_LF_Q14 = silk_LSHIFT( n_LF_Q14, 2 ); /* Q12 -> Q14 */ + + /* Input minus prediction plus noise feedback */ + /* r = x[ i ] - LTP_pred - LPC_pred + n_AR + n_Tilt + n_LF + n_LTP */ + tmp1 = silk_ADD32( n_AR_Q14, n_LF_Q14 ); /* Q14 */ + tmp2 = silk_ADD32( n_LTP_Q14, LPC_pred_Q14 ); /* Q13 */ + tmp1 = silk_SUB32( tmp2, tmp1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 4 ); /* Q10 */ + + r_Q10 = silk_SUB32( x_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Flip sign depending on dither */ + if ( psDD->Seed < 0 ) { + r_Q10 = -r_Q10; + } + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + if( q1_Q0 > 0 ) { + q1_Q10 = silk_SUB32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == 0 ) { + q1_Q10 = offset_Q10; + q2_Q10 = silk_ADD32( q1_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else if( q1_Q0 == -1 ) { + q2_Q10 = offset_Q10; + q1_Q10 = silk_SUB32( q2_Q10, 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( q2_Q10, Lambda_Q10 ); + } else { /* q1_Q0 < -1 */ + q1_Q10 = silk_ADD32( silk_LSHIFT( q1_Q0, 10 ), QUANT_LEVEL_ADJUST_Q10 ); + q1_Q10 = silk_ADD32( q1_Q10, offset_Q10 ); + q2_Q10 = silk_ADD32( q1_Q10, 1024 ); + rd1_Q10 = silk_SMULBB( -q1_Q10, Lambda_Q10 ); + rd2_Q10 = silk_SMULBB( -q2_Q10, Lambda_Q10 ); + } + rr_Q10 = silk_SUB32( r_Q10, q1_Q10 ); + rd1_Q10 = silk_RSHIFT( silk_SMLABB( rd1_Q10, rr_Q10, rr_Q10 ), 10 ); + rr_Q10 = silk_SUB32( r_Q10, q2_Q10 ); + rd2_Q10 = silk_RSHIFT( silk_SMLABB( rd2_Q10, rr_Q10, rr_Q10 ), 10 ); + + if( rd1_Q10 < rd2_Q10 ) { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 0 ].Q_Q10 = q1_Q10; + psSS[ 1 ].Q_Q10 = q2_Q10; + } else { + psSS[ 0 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd2_Q10 ); + psSS[ 1 ].RD_Q10 = silk_ADD32( psDD->RD_Q10, rd1_Q10 ); + psSS[ 0 ].Q_Q10 = q2_Q10; + psSS[ 1 ].Q_Q10 = q1_Q10; + } + + /* Update states for best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 0 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 0 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 0 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 0 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 0 ].xq_Q14 = xq_Q14; + + /* Update states for second best quantization */ + + /* Quantized excitation */ + exc_Q14 = silk_LSHIFT32( psSS[ 1 ].Q_Q10, 4 ); + if ( psDD->Seed < 0 ) { + exc_Q14 = -exc_Q14; + } + + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD32( exc_Q14, LTP_pred_Q14 ); + xq_Q14 = silk_ADD32( LPC_exc_Q14, LPC_pred_Q14 ); + + /* Update states */ + sLF_AR_shp_Q14 = silk_SUB32( xq_Q14, n_AR_Q14 ); + psSS[ 1 ].sLTP_shp_Q14 = silk_SUB32( sLF_AR_shp_Q14, n_LF_Q14 ); + psSS[ 1 ].LF_AR_Q14 = sLF_AR_shp_Q14; + psSS[ 1 ].LPC_exc_Q14 = LPC_exc_Q14; + psSS[ 1 ].xq_Q14 = xq_Q14; + } + } + *smpl_buf_idx = ( *smpl_buf_idx - 1 ) % DECISION_DELAY; + if( *smpl_buf_idx < 0 ) *smpl_buf_idx += DECISION_DELAY; + last_smple_idx = ( *smpl_buf_idx + decisionDelay ) % DECISION_DELAY; + + /* Find winner */ + RDmin_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + Winner_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + if( psSampleState[ k ][ 0 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + Winner_ind = k; + } + } + + /* Increase RD values of expired states */ + Winner_rand_state = psDelDec[ Winner_ind ].RandState[ last_smple_idx ]; + for( k = 0; k < nStatesDelayedDecision; k++ ) { + if( psDelDec[ k ].RandState[ last_smple_idx ] != Winner_rand_state ) { + psSampleState[ k ][ 0 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 0 ].RD_Q10, silk_int32_MAX >> 4 ); + psSampleState[ k ][ 1 ].RD_Q10 = silk_ADD32( psSampleState[ k ][ 1 ].RD_Q10, silk_int32_MAX >> 4 ); + silk_assert( psSampleState[ k ][ 0 ].RD_Q10 >= 0 ); + } + } + + /* Find worst in first set and best in second set */ + RDmax_Q10 = psSampleState[ 0 ][ 0 ].RD_Q10; + RDmin_Q10 = psSampleState[ 0 ][ 1 ].RD_Q10; + RDmax_ind = 0; + RDmin_ind = 0; + for( k = 1; k < nStatesDelayedDecision; k++ ) { + /* find worst in first set */ + if( psSampleState[ k ][ 0 ].RD_Q10 > RDmax_Q10 ) { + RDmax_Q10 = psSampleState[ k ][ 0 ].RD_Q10; + RDmax_ind = k; + } + /* find best in second set */ + if( psSampleState[ k ][ 1 ].RD_Q10 < RDmin_Q10 ) { + RDmin_Q10 = psSampleState[ k ][ 1 ].RD_Q10; + RDmin_ind = k; + } + } + + /* Replace a state if best from second set outperforms worst in first set */ + if( RDmin_Q10 < RDmax_Q10 ) { + silk_memcpy( ( (opus_int32 *)&psDelDec[ RDmax_ind ] ) + i, + ( (opus_int32 *)&psDelDec[ RDmin_ind ] ) + i, sizeof( NSQ_del_dec_struct ) - i * sizeof( opus_int32) ); + silk_memcpy( &psSampleState[ RDmax_ind ][ 0 ], &psSampleState[ RDmin_ind ][ 1 ], sizeof( NSQ_sample_struct ) ); + } + + /* Write samples from winner to output and long-term filter states */ + psDD = &psDelDec[ Winner_ind ]; + if( subfr > 0 || i >= decisionDelay ) { + pulses[ i - decisionDelay ] = (opus_int8)silk_RSHIFT_ROUND( psDD->Q_Q10[ last_smple_idx ], 10 ); + xq[ i - decisionDelay ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( + silk_SMULWW( psDD->Xq_Q14[ last_smple_idx ], delayedGain_Q10[ last_smple_idx ] ), 8 ) ); + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - decisionDelay ] = psDD->Shape_Q14[ last_smple_idx ]; + sLTP_Q15[ NSQ->sLTP_buf_idx - decisionDelay ] = psDD->Pred_Q15[ last_smple_idx ]; + } + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Update states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + psSS = &psSampleState[ k ][ 0 ]; + psDD->LF_AR_Q14 = psSS->LF_AR_Q14; + psDD->sLPC_Q14[ NSQ_LPC_BUF_LENGTH + i ] = psSS->xq_Q14; + psDD->Xq_Q14[ *smpl_buf_idx ] = psSS->xq_Q14; + psDD->Q_Q10[ *smpl_buf_idx ] = psSS->Q_Q10; + psDD->Pred_Q15[ *smpl_buf_idx ] = silk_LSHIFT32( psSS->LPC_exc_Q14, 1 ); + psDD->Shape_Q14[ *smpl_buf_idx ] = psSS->sLTP_shp_Q14; + psDD->Seed = silk_ADD32_ovflw( psDD->Seed, silk_RSHIFT_ROUND( psSS->Q_Q10, 10 ) ); + psDD->RandState[ *smpl_buf_idx ] = psDD->Seed; + psDD->RD_Q10 = psSS->RD_Q10; + } + delayedGain_Q10[ *smpl_buf_idx ] = Gain_Q10; + } + /* Update LPC states */ + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + silk_memcpy( psDD->sLPC_Q14, &psDD->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); + } + RESTORE_STACK; +} + +static OPUS_INLINE void silk_nsq_del_dec_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + NSQ_del_dec_struct psDelDec[], /* I/O Delayed decision states */ + const opus_int32 x_Q3[], /* I Input in Q3 */ + opus_int32 x_sc_Q10[], /* O Input scaled with 1/Gain in Q10 */ + const opus_int16 sLTP[], /* I Re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I Subframe number */ + opus_int nStatesDelayedDecision, /* I Number of del dec states */ + const opus_int LTP_scale_Q14, /* I LTP state scaling */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type, /* I Signal type */ + const opus_int decisionDelay /* I Decision delay */ +) +{ + opus_int i, k, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q23; + NSQ_del_dec_struct *psDD; + __m128i xmm_inv_gain_Q23, xmm_x_Q3_x2x0, xmm_x_Q3_x3x1; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + + silk_assert( inv_gain_Q31 != 0 ); + + /* Calculate gain adjustment factor */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + } else { + gain_adj_Q16 = (opus_int32)1 << 16; + } + + /* Scale input */ + inv_gain_Q23 = silk_RSHIFT_ROUND( inv_gain_Q31, 8 ); + + /* prepare inv_gain_Q23 in packed 4 32-bits */ + xmm_inv_gain_Q23 = _mm_set1_epi32(inv_gain_Q23); + + for( i = 0; i < psEncC->subfr_length - 3; i += 4 ) { + xmm_x_Q3_x2x0 = _mm_loadu_si128( (__m128i *)(&(x_Q3[ i ] ) ) ); + /* equal shift right 4 bytes*/ + xmm_x_Q3_x3x1 = _mm_shuffle_epi32( xmm_x_Q3_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x_Q3_x2x0 = _mm_mul_epi32( xmm_x_Q3_x2x0, xmm_inv_gain_Q23 ); + xmm_x_Q3_x3x1 = _mm_mul_epi32( xmm_x_Q3_x3x1, xmm_inv_gain_Q23 ); + + xmm_x_Q3_x2x0 = _mm_srli_epi64( xmm_x_Q3_x2x0, 16 ); + xmm_x_Q3_x3x1 = _mm_slli_epi64( xmm_x_Q3_x3x1, 16 ); + + xmm_x_Q3_x2x0 = _mm_blend_epi16( xmm_x_Q3_x2x0, xmm_x_Q3_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(&(x_sc_Q10[ i ])), xmm_x_Q3_x2x0 ); + } + + for( ; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x_Q3[ i ], inv_gain_Q23 ); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + /* Scale long-term shaping state */ + { + __m128i xmm_gain_adj_Q16, xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1; + + /* prepare gain_adj_Q16 in packed 4 32-bits */ + xmm_gain_adj_Q16 = _mm_set1_epi32( gain_adj_Q16 ); + + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 ) + { + xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ) ); + /* equal shift right 4 bytes*/ + xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x2x0, xmm_gain_adj_Q16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x3x1, xmm_gain_adj_Q16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_srli_epi64( xmm_sLTP_shp_Q14_x2x0, 16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_slli_epi64( xmm_sLTP_shp_Q14_x3x1, 16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 ); + } + + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx - decisionDelay; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + for( k = 0; k < nStatesDelayedDecision; k++ ) { + psDD = &psDelDec[ k ]; + + /* Scale scalar states */ + psDD->LF_AR_Q14 = silk_SMULWW( gain_adj_Q16, psDD->LF_AR_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + psDD->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + psDD->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->sAR2_Q14[ i ] ); + } + for( i = 0; i < DECISION_DELAY; i++ ) { + psDD->Pred_Q15[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Pred_Q15[ i ] ); + psDD->Shape_Q14[ i ] = silk_SMULWW( gain_adj_Q16, psDD->Shape_Q14[ i ] ); + } + } + } + } +} diff --git a/TMessagesProj/jni/opus/silk/x86/NSQ_sse.c b/TMessagesProj/jni/opus/silk/x86/NSQ_sse.c new file mode 100644 index 00000000000..bb3c5f19557 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/NSQ_sse.c @@ -0,0 +1,720 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" +#include "stack_alloc.h" + +static OPUS_INLINE void silk_nsq_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int32 x_Q3[], /* I input in Q3 */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +); + +static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int32 table[][4] /* I */ +); + +void silk_NSQ_sse4_1( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) +{ + opus_int k, lag, start_idx, LSF_interpolation_flag; + const opus_int16 *A_Q12, *B_Q14, *AR_shp_Q13; + opus_int16 *pxq; + VARDECL( opus_int32, sLTP_Q15 ); + VARDECL( opus_int16, sLTP ); + opus_int32 HarmShapeFIRPacked_Q14; + opus_int offset_Q10; + VARDECL( opus_int32, x_sc_Q10 ); + + opus_int32 table[ 64 ][ 4 ]; + opus_int32 tmp1; + opus_int32 q1_Q10, q2_Q10, rd1_Q20, rd2_Q20; + + SAVE_STACK; + + NSQ->rand_seed = psIndices->Seed; + + /* Set unvoiced lag to the previous one, overwrite later for voiced */ + lag = NSQ->lagPrev; + + silk_assert( NSQ->prev_gain_Q16 != 0 ); + + offset_Q10 = silk_Quantization_Offsets_Q10[ psIndices->signalType >> 1 ][ psIndices->quantOffsetType ]; + + /* 0 */ + q1_Q10 = offset_Q10; + q2_Q10 = offset_Q10 + ( 1024 - QUANT_LEVEL_ADJUST_Q10 ); + rd1_Q20 = q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 32 ][ 0 ] = q1_Q10; + table[ 32 ][ 1 ] = q2_Q10; + table[ 32 ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + + /* -1 */ + q1_Q10 = offset_Q10 - ( 1024 - QUANT_LEVEL_ADJUST_Q10 ); + q2_Q10 = offset_Q10; + rd1_Q20 = - q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 31 ][ 0 ] = q1_Q10; + table[ 31 ][ 1 ] = q2_Q10; + table[ 31 ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 31 ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + + /* > 0 */ + for (k = 1; k <= 31; k++) + { + tmp1 = offset_Q10 + silk_LSHIFT( k, 10 ); + + q1_Q10 = tmp1 - QUANT_LEVEL_ADJUST_Q10; + q2_Q10 = tmp1 - QUANT_LEVEL_ADJUST_Q10 + 1024; + rd1_Q20 = q1_Q10 * Lambda_Q10; + rd2_Q20 = q2_Q10 * Lambda_Q10; + + table[ 32 + k ][ 0 ] = q1_Q10; + table[ 32 + k ][ 1 ] = q2_Q10; + table[ 32 + k ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 + k ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + } + + /* < -1 */ + for (k = -32; k <= -2; k++) + { + tmp1 = offset_Q10 + silk_LSHIFT( k, 10 ); + + q1_Q10 = tmp1 + QUANT_LEVEL_ADJUST_Q10; + q2_Q10 = tmp1 + QUANT_LEVEL_ADJUST_Q10 + 1024; + rd1_Q20 = - q1_Q10 * Lambda_Q10; + rd2_Q20 = - q2_Q10 * Lambda_Q10; + + table[ 32 + k ][ 0 ] = q1_Q10; + table[ 32 + k ][ 1 ] = q2_Q10; + table[ 32 + k ][ 2 ] = 2 * (q1_Q10 - q2_Q10); + table[ 32 + k ][ 3 ] = (rd1_Q20 - rd2_Q20) + (q1_Q10 * q1_Q10 - q2_Q10 * q2_Q10); + } + + if( psIndices->NLSFInterpCoef_Q2 == 4 ) { + LSF_interpolation_flag = 0; + } else { + LSF_interpolation_flag = 1; + } + + ALLOC( sLTP_Q15, + psEncC->ltp_mem_length + psEncC->frame_length, opus_int32 ); + ALLOC( sLTP, psEncC->ltp_mem_length + psEncC->frame_length, opus_int16 ); + ALLOC( x_sc_Q10, psEncC->subfr_length, opus_int32 ); + /* Set up pointers to start of sub frame */ + NSQ->sLTP_shp_buf_idx = psEncC->ltp_mem_length; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + pxq = &NSQ->xq[ psEncC->ltp_mem_length ]; + for( k = 0; k < psEncC->nb_subfr; k++ ) { + A_Q12 = &PredCoef_Q12[ (( k >> 1 ) | ( 1 - LSF_interpolation_flag )) * MAX_LPC_ORDER ]; + B_Q14 = <PCoef_Q14[ k * LTP_ORDER ]; + AR_shp_Q13 = &AR2_Q13[ k * MAX_SHAPE_LPC_ORDER ]; + + /* Noise shape parameters */ + silk_assert( HarmShapeGain_Q14[ k ] >= 0 ); + HarmShapeFIRPacked_Q14 = silk_RSHIFT( HarmShapeGain_Q14[ k ], 2 ); + HarmShapeFIRPacked_Q14 |= silk_LSHIFT( (opus_int32)silk_RSHIFT( HarmShapeGain_Q14[ k ], 1 ), 16 ); + + NSQ->rewhite_flag = 0; + if( psIndices->signalType == TYPE_VOICED ) { + /* Voiced */ + lag = pitchL[ k ]; + + /* Re-whitening */ + if( ( k & ( 3 - silk_LSHIFT( LSF_interpolation_flag, 1 ) ) ) == 0 ) { + /* Rewhiten with new A coefs */ + start_idx = psEncC->ltp_mem_length - lag - psEncC->predictLPCOrder - LTP_ORDER / 2; + silk_assert( start_idx > 0 ); + + silk_LPC_analysis_filter( &sLTP[ start_idx ], &NSQ->xq[ start_idx + k * psEncC->subfr_length ], + A_Q12, psEncC->ltp_mem_length - start_idx, psEncC->predictLPCOrder, psEncC->arch ); + + NSQ->rewhite_flag = 1; + NSQ->sLTP_buf_idx = psEncC->ltp_mem_length; + } + } + + silk_nsq_scale_states_sse4_1( psEncC, NSQ, x_Q3, x_sc_Q10, sLTP, sLTP_Q15, k, LTP_scale_Q14, Gains_Q16, pitchL, psIndices->signalType ); + + if ( opus_likely( ( 10 == psEncC->shapingLPCOrder ) && ( 16 == psEncC->predictLPCOrder) ) ) + { + silk_noise_shape_quantizer_10_16_sse4_1( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], + offset_Q10, psEncC->subfr_length, &(table[32]) ); + } + else + { + silk_noise_shape_quantizer( NSQ, psIndices->signalType, x_sc_Q10, pulses, pxq, sLTP_Q15, A_Q12, B_Q14, + AR_shp_Q13, lag, HarmShapeFIRPacked_Q14, Tilt_Q14[ k ], LF_shp_Q14[ k ], Gains_Q16[ k ], Lambda_Q10, + offset_Q10, psEncC->subfr_length, psEncC->shapingLPCOrder, psEncC->predictLPCOrder, psEncC->arch ); + } + + x_Q3 += psEncC->subfr_length; + pulses += psEncC->subfr_length; + pxq += psEncC->subfr_length; + } + + /* Update lagPrev for next frame */ + NSQ->lagPrev = pitchL[ psEncC->nb_subfr - 1 ]; + + /* Save quantized speech and noise shaping signals */ + /* DEBUG_STORE_DATA( enc.pcm, &NSQ->xq[ psEncC->ltp_mem_length ], psEncC->frame_length * sizeof( opus_int16 ) ) */ + silk_memmove( NSQ->xq, &NSQ->xq[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int16 ) ); + silk_memmove( NSQ->sLTP_shp_Q14, &NSQ->sLTP_shp_Q14[ psEncC->frame_length ], psEncC->ltp_mem_length * sizeof( opus_int32 ) ); + RESTORE_STACK; +} + +/***********************************/ +/* silk_noise_shape_quantizer_10_16 */ +/***********************************/ +static OPUS_INLINE void silk_noise_shape_quantizer_10_16_sse4_1( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int32 table[][4] /* I */ +) +{ + opus_int i; + opus_int32 LTP_pred_Q13, LPC_pred_Q10, n_AR_Q12, n_LTP_Q13; + opus_int32 n_LF_Q12, r_Q10, q1_Q0, q1_Q10, q2_Q10; + opus_int32 exc_Q14, LPC_exc_Q14, xq_Q14, Gain_Q10; + opus_int32 tmp1, tmp2, sLF_AR_shp_Q14; + opus_int32 *psLPC_Q14, *shp_lag_ptr, *pred_lag_ptr; + + __m128i xmm_tempa, xmm_tempb; + + __m128i xmm_one; + + __m128i psLPC_Q14_hi_01234567, psLPC_Q14_hi_89ABCDEF; + __m128i psLPC_Q14_lo_01234567, psLPC_Q14_lo_89ABCDEF; + __m128i a_Q12_01234567, a_Q12_89ABCDEF; + + __m128i sAR2_Q14_hi_76543210, sAR2_Q14_lo_76543210; + __m128i AR_shp_Q13_76543210; + + shp_lag_ptr = &NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - lag + HARM_SHAPE_FIR_TAPS / 2 ]; + pred_lag_ptr = &sLTP_Q15[ NSQ->sLTP_buf_idx - lag + LTP_ORDER / 2 ]; + Gain_Q10 = silk_RSHIFT( Gain_Q16, 6 ); + + /* Set up short term AR state */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH - 1 ]; + + sLF_AR_shp_Q14 = NSQ->sLF_AR_shp_Q14; + xq_Q14 = psLPC_Q14[ 0 ]; + LTP_pred_Q13 = 0; + + /* load a_Q12 */ + xmm_one = _mm_set_epi8( 1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14 ); + + /* load a_Q12[0] - a_Q12[7] */ + a_Q12_01234567 = _mm_loadu_si128( (__m128i *)(&a_Q12[ 0 ] ) ); + /* load a_Q12[ 8 ] - a_Q12[ 15 ] */ + a_Q12_89ABCDEF = _mm_loadu_si128( (__m128i *)(&a_Q12[ 8 ] ) ); + + a_Q12_01234567 = _mm_shuffle_epi8( a_Q12_01234567, xmm_one ); + a_Q12_89ABCDEF = _mm_shuffle_epi8( a_Q12_89ABCDEF, xmm_one ); + + /* load AR_shp_Q13 */ + AR_shp_Q13_76543210 = _mm_loadu_si128( (__m128i *)(&AR_shp_Q13[0] ) ); + + /* load psLPC_Q14 */ + xmm_one = _mm_set_epi8(15, 14, 11, 10, 7, 6, 3, 2, 13, 12, 9, 8, 5, 4, 1, 0 ); + + xmm_tempa = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[-16]) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[-12]) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + psLPC_Q14_hi_89ABCDEF = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + psLPC_Q14_lo_89ABCDEF = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + xmm_tempa = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -8 ]) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(&psLPC_Q14[ -4 ]) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + psLPC_Q14_hi_01234567 = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + psLPC_Q14_lo_01234567 = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + /* load sAR2_Q14 */ + xmm_tempa = _mm_loadu_si128( (__m128i *)(&(NSQ->sAR2_Q14[ 0 ]) ) ); + xmm_tempb = _mm_loadu_si128( (__m128i *)(&(NSQ->sAR2_Q14[ 4 ]) ) ); + + xmm_tempa = _mm_shuffle_epi8( xmm_tempa, xmm_one ); + xmm_tempb = _mm_shuffle_epi8( xmm_tempb, xmm_one ); + + sAR2_Q14_hi_76543210 = _mm_unpackhi_epi64( xmm_tempa, xmm_tempb ); + sAR2_Q14_lo_76543210 = _mm_unpacklo_epi64( xmm_tempa, xmm_tempb ); + + /* prepare 1 in 8 * 16bit */ + xmm_one = _mm_set1_epi16(1); + + for( i = 0; i < length; i++ ) + { + /* Short-term prediction */ + __m128i xmm_hi_07, xmm_hi_8F, xmm_lo_07, xmm_lo_8F; + + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LPC_pred_Q10 = 8; /* silk_RSHIFT( predictLPCOrder, 1 ); */ + + /* shift psLPC_Q14 */ + psLPC_Q14_hi_89ABCDEF = _mm_alignr_epi8( psLPC_Q14_hi_01234567, psLPC_Q14_hi_89ABCDEF, 2 ); + psLPC_Q14_lo_89ABCDEF = _mm_alignr_epi8( psLPC_Q14_lo_01234567, psLPC_Q14_lo_89ABCDEF, 2 ); + + psLPC_Q14_hi_01234567 = _mm_srli_si128( psLPC_Q14_hi_01234567, 2 ); + psLPC_Q14_lo_01234567 = _mm_srli_si128( psLPC_Q14_lo_01234567, 2 ); + + psLPC_Q14_hi_01234567 = _mm_insert_epi16( psLPC_Q14_hi_01234567, (xq_Q14 >> 16), 7 ); + psLPC_Q14_lo_01234567 = _mm_insert_epi16( psLPC_Q14_lo_01234567, (xq_Q14), 7 ); + + /* high part, use pmaddwd, results in 4 32-bit */ + xmm_hi_07 = _mm_madd_epi16( psLPC_Q14_hi_01234567, a_Q12_01234567 ); + xmm_hi_8F = _mm_madd_epi16( psLPC_Q14_hi_89ABCDEF, a_Q12_89ABCDEF ); + + /* low part, use pmulhw, results in 8 16-bit, note we need simulate unsigned * signed, _mm_srai_epi16(psLPC_Q14_lo_01234567, 15) */ + xmm_tempa = _mm_cmpgt_epi16( _mm_setzero_si128(), psLPC_Q14_lo_01234567 ); + xmm_tempb = _mm_cmpgt_epi16( _mm_setzero_si128(), psLPC_Q14_lo_89ABCDEF ); + + xmm_tempa = _mm_and_si128( xmm_tempa, a_Q12_01234567 ); + xmm_tempb = _mm_and_si128( xmm_tempb, a_Q12_89ABCDEF ); + + xmm_lo_07 = _mm_mulhi_epi16( psLPC_Q14_lo_01234567, a_Q12_01234567 ); + xmm_lo_8F = _mm_mulhi_epi16( psLPC_Q14_lo_89ABCDEF, a_Q12_89ABCDEF ); + + xmm_lo_07 = _mm_add_epi16( xmm_lo_07, xmm_tempa ); + xmm_lo_8F = _mm_add_epi16( xmm_lo_8F, xmm_tempb ); + + xmm_lo_07 = _mm_madd_epi16( xmm_lo_07, xmm_one ); + xmm_lo_8F = _mm_madd_epi16( xmm_lo_8F, xmm_one ); + + /* accumulate */ + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_hi_8F ); + xmm_lo_07 = _mm_add_epi32( xmm_lo_07, xmm_lo_8F ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_lo_07 ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_unpackhi_epi64(xmm_hi_07, xmm_hi_07 ) ); + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_shufflelo_epi16(xmm_hi_07, 0x0E ) ); + + LPC_pred_Q10 += _mm_cvtsi128_si32( xmm_hi_07 ); + + /* Long-term prediction */ + if ( opus_likely( signalType == TYPE_VOICED ) ) { + /* Unrolled loop */ + /* Avoids introducing a bias because silk_SMLAWB() always rounds to -inf */ + LTP_pred_Q13 = 2; + { + __m128i b_Q14_3210, b_Q14_0123, pred_lag_ptr_0123; + + b_Q14_3210 = OP_CVTEPI16_EPI32_M64( b_Q14 ); + b_Q14_0123 = _mm_shuffle_epi32( b_Q14_3210, 0x1B ); + + /* loaded: [0] [-1] [-2] [-3] */ + pred_lag_ptr_0123 = _mm_loadu_si128( (__m128i *)(&pred_lag_ptr[ -3 ] ) ); + /* shuffle to [-3] [-2] [-1] [0] and to new xmm */ + xmm_tempa = _mm_shuffle_epi32( pred_lag_ptr_0123, 0x1B ); + /*64-bit multiply, a[2] * b[-2], a[0] * b[0] */ + xmm_tempa = _mm_mul_epi32( xmm_tempa, b_Q14_3210 ); + /* right shift 2 bytes (16 bits), zero extended */ + xmm_tempa = _mm_srli_si128( xmm_tempa, 2 ); + + /* a[1] * b[-1], a[3] * b[-3] */ + pred_lag_ptr_0123 = _mm_mul_epi32( pred_lag_ptr_0123, b_Q14_0123 ); + pred_lag_ptr_0123 = _mm_srli_si128( pred_lag_ptr_0123, 2 ); + + pred_lag_ptr_0123 = _mm_add_epi32( pred_lag_ptr_0123, xmm_tempa ); + /* equal shift right 8 bytes*/ + xmm_tempa = _mm_shuffle_epi32( pred_lag_ptr_0123, _MM_SHUFFLE( 0, 0, 3, 2 ) ); + xmm_tempa = _mm_add_epi32( xmm_tempa, pred_lag_ptr_0123 ); + + LTP_pred_Q13 += _mm_cvtsi128_si32( xmm_tempa ); + + LTP_pred_Q13 = silk_SMLAWB( LTP_pred_Q13, pred_lag_ptr[ -4 ], b_Q14[ 4 ] ); + pred_lag_ptr++; + } + } + + /* Noise shape feedback */ + NSQ->sAR2_Q14[ 9 ] = NSQ->sAR2_Q14[ 8 ]; + NSQ->sAR2_Q14[ 8 ] = _mm_cvtsi128_si32( _mm_srli_si128(_mm_unpackhi_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ), 12 ) ); + + sAR2_Q14_hi_76543210 = _mm_slli_si128( sAR2_Q14_hi_76543210, 2 ); + sAR2_Q14_lo_76543210 = _mm_slli_si128( sAR2_Q14_lo_76543210, 2 ); + + sAR2_Q14_hi_76543210 = _mm_insert_epi16( sAR2_Q14_hi_76543210, (xq_Q14 >> 16), 0 ); + sAR2_Q14_lo_76543210 = _mm_insert_epi16( sAR2_Q14_lo_76543210, (xq_Q14), 0 ); + + /* high part, use pmaddwd, results in 4 32-bit */ + xmm_hi_07 = _mm_madd_epi16( sAR2_Q14_hi_76543210, AR_shp_Q13_76543210 ); + + /* low part, use pmulhw, results in 8 16-bit, note we need simulate unsigned * signed,_mm_srai_epi16(sAR2_Q14_lo_76543210, 15) */ + xmm_tempa = _mm_cmpgt_epi16( _mm_setzero_si128(), sAR2_Q14_lo_76543210 ); + xmm_tempa = _mm_and_si128( xmm_tempa, AR_shp_Q13_76543210 ); + + xmm_lo_07 = _mm_mulhi_epi16( sAR2_Q14_lo_76543210, AR_shp_Q13_76543210 ); + xmm_lo_07 = _mm_add_epi16( xmm_lo_07, xmm_tempa ); + + xmm_lo_07 = _mm_madd_epi16( xmm_lo_07, xmm_one ); + + /* accumulate */ + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, xmm_lo_07 ); + + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_unpackhi_epi64(xmm_hi_07, xmm_hi_07 ) ); + xmm_hi_07 = _mm_add_epi32( xmm_hi_07, _mm_shufflelo_epi16(xmm_hi_07, 0x0E ) ); + + n_AR_Q12 = 5 + _mm_cvtsi128_si32( xmm_hi_07 ); + + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sAR2_Q14[ 8 ], AR_shp_Q13[ 8 ] ); + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, NSQ->sAR2_Q14[ 9 ], AR_shp_Q13[ 9 ] ); + + n_AR_Q12 = silk_LSHIFT32( n_AR_Q12, 1 ); /* Q11 -> Q12 */ + n_AR_Q12 = silk_SMLAWB( n_AR_Q12, sLF_AR_shp_Q14, Tilt_Q14 ); + + n_LF_Q12 = silk_SMULWB( NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx - 1 ], LF_shp_Q14 ); + n_LF_Q12 = silk_SMLAWT( n_LF_Q12, sLF_AR_shp_Q14, LF_shp_Q14 ); + + silk_assert( lag > 0 || signalType != TYPE_VOICED ); + + /* Combine prediction and noise shaping signals */ + tmp1 = silk_SUB32( silk_LSHIFT32( LPC_pred_Q10, 2 ), n_AR_Q12 ); /* Q12 */ + tmp1 = silk_SUB32( tmp1, n_LF_Q12 ); /* Q12 */ + if( lag > 0 ) { + /* Symmetric, packed FIR coefficients */ + n_LTP_Q13 = silk_SMULWB( silk_ADD32( shp_lag_ptr[ 0 ], shp_lag_ptr[ -2 ] ), HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_SMLAWT( n_LTP_Q13, shp_lag_ptr[ -1 ], HarmShapeFIRPacked_Q14 ); + n_LTP_Q13 = silk_LSHIFT( n_LTP_Q13, 1 ); + shp_lag_ptr++; + + tmp2 = silk_SUB32( LTP_pred_Q13, n_LTP_Q13 ); /* Q13 */ + tmp1 = silk_ADD_LSHIFT32( tmp2, tmp1, 1 ); /* Q13 */ + tmp1 = silk_RSHIFT_ROUND( tmp1, 3 ); /* Q10 */ + } else { + tmp1 = silk_RSHIFT_ROUND( tmp1, 2 ); /* Q10 */ + } + + r_Q10 = silk_SUB32( x_sc_Q10[ i ], tmp1 ); /* residual error Q10 */ + + /* Generate dither */ + NSQ->rand_seed = silk_RAND( NSQ->rand_seed ); + + /* Flip sign depending on dither */ + tmp2 = -r_Q10; + if ( NSQ->rand_seed < 0 ) r_Q10 = tmp2; + + r_Q10 = silk_LIMIT_32( r_Q10, -(31 << 10), 30 << 10 ); + + /* Find two quantization level candidates and measure their rate-distortion */ + q1_Q10 = silk_SUB32( r_Q10, offset_Q10 ); + q1_Q0 = silk_RSHIFT( q1_Q10, 10 ); + + q1_Q10 = table[q1_Q0][0]; + q2_Q10 = table[q1_Q0][1]; + + if (r_Q10 * table[q1_Q0][2] - table[q1_Q0][3] < 0) + { + q1_Q10 = q2_Q10; + } + + pulses[ i ] = (opus_int8)silk_RSHIFT_ROUND( q1_Q10, 10 ); + + /* Excitation */ + exc_Q14 = silk_LSHIFT( q1_Q10, 4 ); + + tmp2 = -exc_Q14; + if ( NSQ->rand_seed < 0 ) exc_Q14 = tmp2; + + /* Add predictions */ + LPC_exc_Q14 = silk_ADD_LSHIFT32( exc_Q14, LTP_pred_Q13, 1 ); + xq_Q14 = silk_ADD_LSHIFT32( LPC_exc_Q14, LPC_pred_Q10, 4 ); + + /* Update states */ + psLPC_Q14++; + *psLPC_Q14 = xq_Q14; + sLF_AR_shp_Q14 = silk_SUB_LSHIFT32( xq_Q14, n_AR_Q12, 2 ); + + NSQ->sLTP_shp_Q14[ NSQ->sLTP_shp_buf_idx ] = silk_SUB_LSHIFT32( sLF_AR_shp_Q14, n_LF_Q12, 2 ); + sLTP_Q15[ NSQ->sLTP_buf_idx ] = silk_LSHIFT( LPC_exc_Q14, 1 ); + NSQ->sLTP_shp_buf_idx++; + NSQ->sLTP_buf_idx++; + + /* Make dither dependent on quantized signal */ + NSQ->rand_seed = silk_ADD32_ovflw( NSQ->rand_seed, pulses[ i ] ); + } + + NSQ->sLF_AR_shp_Q14 = sLF_AR_shp_Q14; + + /* Scale XQ back to normal level before saving */ + psLPC_Q14 = &NSQ->sLPC_Q14[ NSQ_LPC_BUF_LENGTH ]; + + /* write back sAR2_Q14 */ + xmm_tempa = _mm_unpackhi_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ); + xmm_tempb = _mm_unpacklo_epi16( sAR2_Q14_lo_76543210, sAR2_Q14_hi_76543210 ); + _mm_storeu_si128( (__m128i *)(&NSQ->sAR2_Q14[ 4 ]), xmm_tempa ); + _mm_storeu_si128( (__m128i *)(&NSQ->sAR2_Q14[ 0 ]), xmm_tempb ); + + /* xq[ i ] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psLPC_Q14[ i ], Gain_Q10 ), 8 ) ); */ + { + __m128i xmm_Gain_Q10; + __m128i xmm_xq_Q14_3210, xmm_xq_Q14_x3x1, xmm_xq_Q14_7654, xmm_xq_Q14_x7x5; + + /* prepare (1 << 7) in packed 4 32-bits */ + xmm_tempa = _mm_set1_epi32( (1 << 7) ); + + /* prepare Gain_Q10 in packed 4 32-bits */ + xmm_Gain_Q10 = _mm_set1_epi32( Gain_Q10 ); + + /* process xq */ + for (i = 0; i < length - 7; i += 8) + { + xmm_xq_Q14_3210 = _mm_loadu_si128( (__m128i *)(&(psLPC_Q14[ i + 0 ] ) ) ); + xmm_xq_Q14_7654 = _mm_loadu_si128( (__m128i *)(&(psLPC_Q14[ i + 4 ] ) ) ); + + /* equal shift right 4 bytes*/ + xmm_xq_Q14_x3x1 = _mm_shuffle_epi32( xmm_xq_Q14_3210, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + /* equal shift right 4 bytes*/ + xmm_xq_Q14_x7x5 = _mm_shuffle_epi32( xmm_xq_Q14_7654, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_xq_Q14_3210 = _mm_mul_epi32( xmm_xq_Q14_3210, xmm_Gain_Q10 ); + xmm_xq_Q14_x3x1 = _mm_mul_epi32( xmm_xq_Q14_x3x1, xmm_Gain_Q10 ); + xmm_xq_Q14_7654 = _mm_mul_epi32( xmm_xq_Q14_7654, xmm_Gain_Q10 ); + xmm_xq_Q14_x7x5 = _mm_mul_epi32( xmm_xq_Q14_x7x5, xmm_Gain_Q10 ); + + xmm_xq_Q14_3210 = _mm_srli_epi64( xmm_xq_Q14_3210, 16 ); + xmm_xq_Q14_x3x1 = _mm_slli_epi64( xmm_xq_Q14_x3x1, 16 ); + xmm_xq_Q14_7654 = _mm_srli_epi64( xmm_xq_Q14_7654, 16 ); + xmm_xq_Q14_x7x5 = _mm_slli_epi64( xmm_xq_Q14_x7x5, 16 ); + + xmm_xq_Q14_3210 = _mm_blend_epi16( xmm_xq_Q14_3210, xmm_xq_Q14_x3x1, 0xCC ); + xmm_xq_Q14_7654 = _mm_blend_epi16( xmm_xq_Q14_7654, xmm_xq_Q14_x7x5, 0xCC ); + + /* silk_RSHIFT_ROUND(xq, 8) */ + xmm_xq_Q14_3210 = _mm_add_epi32( xmm_xq_Q14_3210, xmm_tempa ); + xmm_xq_Q14_7654 = _mm_add_epi32( xmm_xq_Q14_7654, xmm_tempa ); + + xmm_xq_Q14_3210 = _mm_srai_epi32( xmm_xq_Q14_3210, 8 ); + xmm_xq_Q14_7654 = _mm_srai_epi32( xmm_xq_Q14_7654, 8 ); + + /* silk_SAT16 */ + xmm_xq_Q14_3210 = _mm_packs_epi32( xmm_xq_Q14_3210, xmm_xq_Q14_7654 ); + + /* save to xq */ + _mm_storeu_si128( (__m128i *)(&xq[ i ] ), xmm_xq_Q14_3210 ); + } + } + for ( ; i < length; i++) + { + xq[i] = (opus_int16)silk_SAT16( silk_RSHIFT_ROUND( silk_SMULWW( psLPC_Q14[ i ], Gain_Q10 ), 8 ) ); + } + + /* Update LPC synth buffer */ + silk_memcpy( NSQ->sLPC_Q14, &NSQ->sLPC_Q14[ length ], NSQ_LPC_BUF_LENGTH * sizeof( opus_int32 ) ); +} + +static OPUS_INLINE void silk_nsq_scale_states_sse4_1( + const silk_encoder_state *psEncC, /* I Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + const opus_int32 x_Q3[], /* I input in Q3 */ + opus_int32 x_sc_Q10[], /* O input scaled with 1/Gain */ + const opus_int16 sLTP[], /* I re-whitened LTP state in Q0 */ + opus_int32 sLTP_Q15[], /* O LTP state matching scaled input */ + opus_int subfr, /* I subframe number */ + const opus_int LTP_scale_Q14, /* I */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lag */ + const opus_int signal_type /* I Signal type */ +) +{ + opus_int i, lag; + opus_int32 gain_adj_Q16, inv_gain_Q31, inv_gain_Q23; + __m128i xmm_inv_gain_Q23, xmm_x_Q3_x2x0, xmm_x_Q3_x3x1; + + lag = pitchL[ subfr ]; + inv_gain_Q31 = silk_INVERSE32_varQ( silk_max( Gains_Q16[ subfr ], 1 ), 47 ); + silk_assert( inv_gain_Q31 != 0 ); + + /* Calculate gain adjustment factor */ + if( Gains_Q16[ subfr ] != NSQ->prev_gain_Q16 ) { + gain_adj_Q16 = silk_DIV32_varQ( NSQ->prev_gain_Q16, Gains_Q16[ subfr ], 16 ); + } else { + gain_adj_Q16 = (opus_int32)1 << 16; + } + + /* Scale input */ + inv_gain_Q23 = silk_RSHIFT_ROUND( inv_gain_Q31, 8 ); + + /* prepare inv_gain_Q23 in packed 4 32-bits */ + xmm_inv_gain_Q23 = _mm_set1_epi32(inv_gain_Q23); + + for( i = 0; i < psEncC->subfr_length - 3; i += 4 ) { + xmm_x_Q3_x2x0 = _mm_loadu_si128( (__m128i *)(&(x_Q3[ i ] ) ) ); + + /* equal shift right 4 bytes*/ + xmm_x_Q3_x3x1 = _mm_shuffle_epi32( xmm_x_Q3_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_x_Q3_x2x0 = _mm_mul_epi32( xmm_x_Q3_x2x0, xmm_inv_gain_Q23 ); + xmm_x_Q3_x3x1 = _mm_mul_epi32( xmm_x_Q3_x3x1, xmm_inv_gain_Q23 ); + + xmm_x_Q3_x2x0 = _mm_srli_epi64( xmm_x_Q3_x2x0, 16 ); + xmm_x_Q3_x3x1 = _mm_slli_epi64( xmm_x_Q3_x3x1, 16 ); + + xmm_x_Q3_x2x0 = _mm_blend_epi16( xmm_x_Q3_x2x0, xmm_x_Q3_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(&(x_sc_Q10[ i ] ) ), xmm_x_Q3_x2x0 ); + } + + for( ; i < psEncC->subfr_length; i++ ) { + x_sc_Q10[ i ] = silk_SMULWW( x_Q3[ i ], inv_gain_Q23 ); + } + + /* Save inverse gain */ + NSQ->prev_gain_Q16 = Gains_Q16[ subfr ]; + + /* After rewhitening the LTP state is un-scaled, so scale with inv_gain_Q16 */ + if( NSQ->rewhite_flag ) { + if( subfr == 0 ) { + /* Do LTP downscaling */ + inv_gain_Q31 = silk_LSHIFT( silk_SMULWB( inv_gain_Q31, LTP_scale_Q14 ), 2 ); + } + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + silk_assert( i < MAX_FRAME_LENGTH ); + sLTP_Q15[ i ] = silk_SMULWB( inv_gain_Q31, sLTP[ i ] ); + } + } + + /* Adjust for changing gain */ + if( gain_adj_Q16 != (opus_int32)1 << 16 ) { + /* Scale long-term shaping state */ + __m128i xmm_gain_adj_Q16, xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1; + + /* prepare gain_adj_Q16 in packed 4 32-bits */ + xmm_gain_adj_Q16 = _mm_set1_epi32(gain_adj_Q16); + + for( i = NSQ->sLTP_shp_buf_idx - psEncC->ltp_mem_length; i < NSQ->sLTP_shp_buf_idx - 3; i += 4 ) + { + xmm_sLTP_shp_Q14_x2x0 = _mm_loadu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ) ); + /* equal shift right 4 bytes*/ + xmm_sLTP_shp_Q14_x3x1 = _mm_shuffle_epi32( xmm_sLTP_shp_Q14_x2x0, _MM_SHUFFLE( 0, 3, 2, 1 ) ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x2x0, xmm_gain_adj_Q16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_mul_epi32( xmm_sLTP_shp_Q14_x3x1, xmm_gain_adj_Q16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_srli_epi64( xmm_sLTP_shp_Q14_x2x0, 16 ); + xmm_sLTP_shp_Q14_x3x1 = _mm_slli_epi64( xmm_sLTP_shp_Q14_x3x1, 16 ); + + xmm_sLTP_shp_Q14_x2x0 = _mm_blend_epi16( xmm_sLTP_shp_Q14_x2x0, xmm_sLTP_shp_Q14_x3x1, 0xCC ); + + _mm_storeu_si128( (__m128i *)(&(NSQ->sLTP_shp_Q14[ i ] ) ), xmm_sLTP_shp_Q14_x2x0 ); + } + + for( ; i < NSQ->sLTP_shp_buf_idx; i++ ) { + NSQ->sLTP_shp_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLTP_shp_Q14[ i ] ); + } + + /* Scale long-term prediction state */ + if( signal_type == TYPE_VOICED && NSQ->rewhite_flag == 0 ) { + for( i = NSQ->sLTP_buf_idx - lag - LTP_ORDER / 2; i < NSQ->sLTP_buf_idx; i++ ) { + sLTP_Q15[ i ] = silk_SMULWW( gain_adj_Q16, sLTP_Q15[ i ] ); + } + } + + NSQ->sLF_AR_shp_Q14 = silk_SMULWW( gain_adj_Q16, NSQ->sLF_AR_shp_Q14 ); + + /* Scale short-term prediction and shaping states */ + for( i = 0; i < NSQ_LPC_BUF_LENGTH; i++ ) { + NSQ->sLPC_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sLPC_Q14[ i ] ); + } + for( i = 0; i < MAX_SHAPE_LPC_ORDER; i++ ) { + NSQ->sAR2_Q14[ i ] = silk_SMULWW( gain_adj_Q16, NSQ->sAR2_Q14[ i ] ); + } + } +} diff --git a/TMessagesProj/jni/opus/silk/x86/SigProc_FIX_sse.h b/TMessagesProj/jni/opus/silk/x86/SigProc_FIX_sse.h new file mode 100644 index 00000000000..61efa8da415 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/SigProc_FIX_sse.h @@ -0,0 +1,94 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef SIGPROC_FIX_SSE_H +#define SIGPROC_FIX_SSE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#if defined(OPUS_X86_MAY_HAVE_SSE4_1) +void silk_burg_modified_sse4_1( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +); + +#if defined(OPUS_X86_PRESUME_SSE4_1) +#define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((void)(arch), silk_burg_modified_sse4_1(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) + +#else + +extern void (*const SILK_BURG_MODIFIED_IMPL[OPUS_ARCHMASK + 1])( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */); + +# define silk_burg_modified(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch) \ + ((*SILK_BURG_MODIFIED_IMPL[(arch) & OPUS_ARCHMASK])(res_nrg, res_nrg_Q, A_Q16, x, minInvGain_Q30, subfr_length, nb_subfr, D, arch)) + +#endif + +opus_int64 silk_inner_prod16_aligned_64_sse4_1( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len +); + + +#if defined(OPUS_X86_PRESUME_SSE4_1) + +#define silk_inner_prod16_aligned_64(inVec1, inVec2, len, arch) \ + ((void)(arch),silk_inner_prod16_aligned_64_sse4_1(inVec1, inVec2, len)) + +#else + +extern opus_int64 (*const SILK_INNER_PROD16_ALIGNED_64_IMPL[OPUS_ARCHMASK + 1])( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len); + +# define silk_inner_prod16_aligned_64(inVec1, inVec2, len, arch) \ + ((*SILK_INNER_PROD16_ALIGNED_64_IMPL[(arch) & OPUS_ARCHMASK])(inVec1, inVec2, len)) + +#endif +#endif +#endif diff --git a/TMessagesProj/jni/opus/silk/x86/VAD_sse.c b/TMessagesProj/jni/opus/silk/x86/VAD_sse.c new file mode 100644 index 00000000000..4e90f4410d8 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/VAD_sse.c @@ -0,0 +1,277 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "main.h" +#include "stack_alloc.h" + +/* Weighting factors for tilt measure */ +static const opus_int32 tiltWeights[ VAD_N_BANDS ] = { 30000, 6000, -12000, -12000 }; + +/***************************************/ +/* Get the speech activity level in Q8 */ +/***************************************/ +opus_int silk_VAD_GetSA_Q8_sse4_1( /* O Return value, 0 if success */ + silk_encoder_state *psEncC, /* I/O Encoder state */ + const opus_int16 pIn[] /* I PCM input */ +) +{ + opus_int SA_Q15, pSNR_dB_Q7, input_tilt; + opus_int decimated_framelength1, decimated_framelength2; + opus_int decimated_framelength; + opus_int dec_subframe_length, dec_subframe_offset, SNR_Q7, i, b, s; + opus_int32 sumSquared, smooth_coef_Q16; + opus_int16 HPstateTmp; + VARDECL( opus_int16, X ); + opus_int32 Xnrg[ VAD_N_BANDS ]; + opus_int32 NrgToNoiseRatio_Q8[ VAD_N_BANDS ]; + opus_int32 speech_nrg, x_tmp; + opus_int X_offset[ VAD_N_BANDS ]; + opus_int ret = 0; + silk_VAD_state *psSilk_VAD = &psEncC->sVAD; + + SAVE_STACK; + + /* Safety checks */ + silk_assert( VAD_N_BANDS == 4 ); + silk_assert( MAX_FRAME_LENGTH >= psEncC->frame_length ); + silk_assert( psEncC->frame_length <= 512 ); + silk_assert( psEncC->frame_length == 8 * silk_RSHIFT( psEncC->frame_length, 3 ) ); + + /***********************/ + /* Filter and Decimate */ + /***********************/ + decimated_framelength1 = silk_RSHIFT( psEncC->frame_length, 1 ); + decimated_framelength2 = silk_RSHIFT( psEncC->frame_length, 2 ); + decimated_framelength = silk_RSHIFT( psEncC->frame_length, 3 ); + /* Decimate into 4 bands: + 0 L 3L L 3L 5L + - -- - -- -- + 8 8 2 4 4 + + [0-1 kHz| temp. |1-2 kHz| 2-4 kHz | 4-8 kHz | + + They're arranged to allow the minimal ( frame_length / 4 ) extra + scratch space during the downsampling process */ + X_offset[ 0 ] = 0; + X_offset[ 1 ] = decimated_framelength + decimated_framelength2; + X_offset[ 2 ] = X_offset[ 1 ] + decimated_framelength; + X_offset[ 3 ] = X_offset[ 2 ] + decimated_framelength2; + ALLOC( X, X_offset[ 3 ] + decimated_framelength1, opus_int16 ); + + /* 0-8 kHz to 0-4 kHz and 4-8 kHz */ + silk_ana_filt_bank_1( pIn, &psSilk_VAD->AnaState[ 0 ], + X, &X[ X_offset[ 3 ] ], psEncC->frame_length ); + + /* 0-4 kHz to 0-2 kHz and 2-4 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState1[ 0 ], + X, &X[ X_offset[ 2 ] ], decimated_framelength1 ); + + /* 0-2 kHz to 0-1 kHz and 1-2 kHz */ + silk_ana_filt_bank_1( X, &psSilk_VAD->AnaState2[ 0 ], + X, &X[ X_offset[ 1 ] ], decimated_framelength2 ); + + /*********************************************/ + /* HP filter on lowest band (differentiator) */ + /*********************************************/ + X[ decimated_framelength - 1 ] = silk_RSHIFT( X[ decimated_framelength - 1 ], 1 ); + HPstateTmp = X[ decimated_framelength - 1 ]; + for( i = decimated_framelength - 1; i > 0; i-- ) { + X[ i - 1 ] = silk_RSHIFT( X[ i - 1 ], 1 ); + X[ i ] -= X[ i - 1 ]; + } + X[ 0 ] -= psSilk_VAD->HPstate; + psSilk_VAD->HPstate = HPstateTmp; + + /*************************************/ + /* Calculate the energy in each band */ + /*************************************/ + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Find the decimated framelength in the non-uniformly divided bands */ + decimated_framelength = silk_RSHIFT( psEncC->frame_length, silk_min_int( VAD_N_BANDS - b, VAD_N_BANDS - 1 ) ); + + /* Split length into subframe lengths */ + dec_subframe_length = silk_RSHIFT( decimated_framelength, VAD_INTERNAL_SUBFRAMES_LOG2 ); + dec_subframe_offset = 0; + + /* Compute energy per sub-frame */ + /* initialize with summed energy of last subframe */ + Xnrg[ b ] = psSilk_VAD->XnrgSubfr[ b ]; + for( s = 0; s < VAD_INTERNAL_SUBFRAMES; s++ ) { + __m128i xmm_X, xmm_acc; + sumSquared = 0; + + xmm_acc = _mm_setzero_si128(); + + for( i = 0; i < dec_subframe_length - 7; i += 8 ) + { + xmm_X = _mm_loadu_si128( (__m128i *)&(X[ X_offset[ b ] + i + dec_subframe_offset ] ) ); + xmm_X = _mm_srai_epi16( xmm_X, 3 ); + xmm_X = _mm_madd_epi16( xmm_X, xmm_X ); + xmm_acc = _mm_add_epi32( xmm_acc, xmm_X ); + } + + xmm_acc = _mm_add_epi32( xmm_acc, _mm_unpackhi_epi64( xmm_acc, xmm_acc ) ); + xmm_acc = _mm_add_epi32( xmm_acc, _mm_shufflelo_epi16( xmm_acc, 0x0E ) ); + + sumSquared += _mm_cvtsi128_si32( xmm_acc ); + + for( ; i < dec_subframe_length; i++ ) { + /* The energy will be less than dec_subframe_length * ( silk_int16_MIN / 8 ) ^ 2. */ + /* Therefore we can accumulate with no risk of overflow (unless dec_subframe_length > 128) */ + x_tmp = silk_RSHIFT( + X[ X_offset[ b ] + i + dec_subframe_offset ], 3 ); + sumSquared = silk_SMLABB( sumSquared, x_tmp, x_tmp ); + + /* Safety check */ + silk_assert( sumSquared >= 0 ); + } + + /* Add/saturate summed energy of current subframe */ + if( s < VAD_INTERNAL_SUBFRAMES - 1 ) { + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], sumSquared ); + } else { + /* Look-ahead subframe */ + Xnrg[ b ] = silk_ADD_POS_SAT32( Xnrg[ b ], silk_RSHIFT( sumSquared, 1 ) ); + } + + dec_subframe_offset += dec_subframe_length; + } + psSilk_VAD->XnrgSubfr[ b ] = sumSquared; + } + + /********************/ + /* Noise estimation */ + /********************/ + silk_VAD_GetNoiseLevels( &Xnrg[ 0 ], psSilk_VAD ); + + /***********************************************/ + /* Signal-plus-noise to noise ratio estimation */ + /***********************************************/ + sumSquared = 0; + input_tilt = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + speech_nrg = Xnrg[ b ] - psSilk_VAD->NL[ b ]; + if( speech_nrg > 0 ) { + /* Divide, with sufficient resolution */ + if( ( Xnrg[ b ] & 0xFF800000 ) == 0 ) { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( silk_LSHIFT( Xnrg[ b ], 8 ), psSilk_VAD->NL[ b ] + 1 ); + } else { + NrgToNoiseRatio_Q8[ b ] = silk_DIV32( Xnrg[ b ], silk_RSHIFT( psSilk_VAD->NL[ b ], 8 ) + 1 ); + } + + /* Convert to log domain */ + SNR_Q7 = silk_lin2log( NrgToNoiseRatio_Q8[ b ] ) - 8 * 128; + + /* Sum-of-squares */ + sumSquared = silk_SMLABB( sumSquared, SNR_Q7, SNR_Q7 ); /* Q14 */ + + /* Tilt measure */ + if( speech_nrg < ( (opus_int32)1 << 20 ) ) { + /* Scale down SNR value for small subband speech energies */ + SNR_Q7 = silk_SMULWB( silk_LSHIFT( silk_SQRT_APPROX( speech_nrg ), 6 ), SNR_Q7 ); + } + input_tilt = silk_SMLAWB( input_tilt, tiltWeights[ b ], SNR_Q7 ); + } else { + NrgToNoiseRatio_Q8[ b ] = 256; + } + } + + /* Mean-of-squares */ + sumSquared = silk_DIV32_16( sumSquared, VAD_N_BANDS ); /* Q14 */ + + /* Root-mean-square approximation, scale to dBs, and write to output pointer */ + pSNR_dB_Q7 = (opus_int16)( 3 * silk_SQRT_APPROX( sumSquared ) ); /* Q7 */ + + /*********************************/ + /* Speech Probability Estimation */ + /*********************************/ + SA_Q15 = silk_sigm_Q15( silk_SMULWB( VAD_SNR_FACTOR_Q16, pSNR_dB_Q7 ) - VAD_NEGATIVE_OFFSET_Q5 ); + + /**************************/ + /* Frequency Tilt Measure */ + /**************************/ + psEncC->input_tilt_Q15 = silk_LSHIFT( silk_sigm_Q15( input_tilt ) - 16384, 1 ); + + /**************************************************/ + /* Scale the sigmoid output based on power levels */ + /**************************************************/ + speech_nrg = 0; + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* Accumulate signal-without-noise energies, higher frequency bands have more weight */ + speech_nrg += ( b + 1 ) * silk_RSHIFT( Xnrg[ b ] - psSilk_VAD->NL[ b ], 4 ); + } + + /* Power scaling */ + if( speech_nrg <= 0 ) { + SA_Q15 = silk_RSHIFT( SA_Q15, 1 ); + } else if( speech_nrg < 32768 ) { + if( psEncC->frame_length == 10 * psEncC->fs_kHz ) { + speech_nrg = silk_LSHIFT_SAT32( speech_nrg, 16 ); + } else { + speech_nrg = silk_LSHIFT_SAT32( speech_nrg, 15 ); + } + + /* square-root */ + speech_nrg = silk_SQRT_APPROX( speech_nrg ); + SA_Q15 = silk_SMULWB( 32768 + speech_nrg, SA_Q15 ); + } + + /* Copy the resulting speech activity in Q8 */ + psEncC->speech_activity_Q8 = silk_min_int( silk_RSHIFT( SA_Q15, 7 ), silk_uint8_MAX ); + + /***********************************/ + /* Energy Level and SNR estimation */ + /***********************************/ + /* Smoothing coefficient */ + smooth_coef_Q16 = silk_SMULWB( VAD_SNR_SMOOTH_COEF_Q18, silk_SMULWB( (opus_int32)SA_Q15, SA_Q15 ) ); + + if( psEncC->frame_length == 10 * psEncC->fs_kHz ) { + smooth_coef_Q16 >>= 1; + } + + for( b = 0; b < VAD_N_BANDS; b++ ) { + /* compute smoothed energy-to-noise ratio per band */ + psSilk_VAD->NrgRatioSmth_Q8[ b ] = silk_SMLAWB( psSilk_VAD->NrgRatioSmth_Q8[ b ], + NrgToNoiseRatio_Q8[ b ] - psSilk_VAD->NrgRatioSmth_Q8[ b ], smooth_coef_Q16 ); + + /* signal to noise ratio in dB per band */ + SNR_Q7 = 3 * ( silk_lin2log( psSilk_VAD->NrgRatioSmth_Q8[b] ) - 8 * 128 ); + /* quality = sigmoid( 0.25 * ( SNR_dB - 16 ) ); */ + psEncC->input_quality_bands_Q15[ b ] = silk_sigm_Q15( silk_RSHIFT( SNR_Q7 - 16 * 128, 4 ) ); + } + + RESTORE_STACK; + return( ret ); +} diff --git a/TMessagesProj/jni/opus/silk/x86/VQ_WMat_EC_sse.c b/TMessagesProj/jni/opus/silk/x86/VQ_WMat_EC_sse.c new file mode 100644 index 00000000000..74d6c6d0ec6 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/VQ_WMat_EC_sse.c @@ -0,0 +1,142 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "main.h" +#include "celt/x86/x86cpu.h" + +/* Entropy constrained matrix-weighted VQ, hard-coded to 5-element vectors, for a single input data vector */ +void silk_VQ_WMat_EC_sse4_1( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int16 *in_Q14, /* I input vector to be quantized */ + const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + opus_int L /* I number of vectors in codebook */ +) +{ + opus_int k, gain_tmp_Q7; + const opus_int8 *cb_row_Q7; + opus_int16 diff_Q14[ 5 ]; + opus_int32 sum1_Q14, sum2_Q16; + + __m128i C_tmp1, C_tmp2, C_tmp3, C_tmp4, C_tmp5; + /* Loop over codebook */ + *rate_dist_Q14 = silk_int32_MAX; + cb_row_Q7 = cb_Q7; + for( k = 0; k < L; k++ ) { + gain_tmp_Q7 = cb_gain_Q7[k]; + + diff_Q14[ 0 ] = in_Q14[ 0 ] - silk_LSHIFT( cb_row_Q7[ 0 ], 7 ); + + C_tmp1 = OP_CVTEPI16_EPI32_M64( &in_Q14[ 1 ] ); + C_tmp2 = OP_CVTEPI8_EPI32_M32( &cb_row_Q7[ 1 ] ); + C_tmp2 = _mm_slli_epi32( C_tmp2, 7 ); + C_tmp1 = _mm_sub_epi32( C_tmp1, C_tmp2 ); + + diff_Q14[ 1 ] = _mm_extract_epi16( C_tmp1, 0 ); + diff_Q14[ 2 ] = _mm_extract_epi16( C_tmp1, 2 ); + diff_Q14[ 3 ] = _mm_extract_epi16( C_tmp1, 4 ); + diff_Q14[ 4 ] = _mm_extract_epi16( C_tmp1, 6 ); + + /* Weighted rate */ + sum1_Q14 = silk_SMULBB( mu_Q9, cl_Q5[ k ] ); + + /* Penalty for too large gain */ + sum1_Q14 = silk_ADD_LSHIFT32( sum1_Q14, silk_max( silk_SUB32( gain_tmp_Q7, max_gain_Q7 ), 0 ), 10 ); + + silk_assert( sum1_Q14 >= 0 ); + + /* first row of W_Q18 */ + C_tmp3 = _mm_loadu_si128( (__m128i *)(&W_Q18[ 1 ] ) ); + C_tmp4 = _mm_mul_epi32( C_tmp3, C_tmp1 ); + C_tmp4 = _mm_srli_si128( C_tmp4, 2 ); + + C_tmp1 = _mm_shuffle_epi32( C_tmp1, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* shift right 4 bytes */ + C_tmp3 = _mm_shuffle_epi32( C_tmp3, _MM_SHUFFLE( 0, 3, 2, 1 ) ); /* shift right 4 bytes */ + + C_tmp5 = _mm_mul_epi32( C_tmp3, C_tmp1 ); + C_tmp5 = _mm_srli_si128( C_tmp5, 2 ); + + C_tmp5 = _mm_add_epi32( C_tmp4, C_tmp5 ); + C_tmp5 = _mm_slli_epi32( C_tmp5, 1 ); + + C_tmp5 = _mm_add_epi32( C_tmp5, _mm_shuffle_epi32( C_tmp5, _MM_SHUFFLE( 0, 0, 0, 2 ) ) ); + sum2_Q16 = _mm_cvtsi128_si32( C_tmp5 ); + + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 0 ], diff_Q14[ 0 ] ); + sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 0 ] ); + + /* second row of W_Q18 */ + sum2_Q16 = silk_SMULWB( W_Q18[ 7 ], diff_Q14[ 2 ] ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 8 ], diff_Q14[ 3 ] ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 9 ], diff_Q14[ 4 ] ); + sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 6 ], diff_Q14[ 1 ] ); + sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 1 ] ); + + /* third row of W_Q18 */ + sum2_Q16 = silk_SMULWB( W_Q18[ 13 ], diff_Q14[ 3 ] ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 14 ], diff_Q14[ 4 ] ); + sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 12 ], diff_Q14[ 2 ] ); + sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 2 ] ); + + /* fourth row of W_Q18 */ + sum2_Q16 = silk_SMULWB( W_Q18[ 19 ], diff_Q14[ 4 ] ); + sum2_Q16 = silk_LSHIFT( sum2_Q16, 1 ); + sum2_Q16 = silk_SMLAWB( sum2_Q16, W_Q18[ 18 ], diff_Q14[ 3 ] ); + sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 3 ] ); + + /* last row of W_Q18 */ + sum2_Q16 = silk_SMULWB( W_Q18[ 24 ], diff_Q14[ 4 ] ); + sum1_Q14 = silk_SMLAWB( sum1_Q14, sum2_Q16, diff_Q14[ 4 ] ); + + silk_assert( sum1_Q14 >= 0 ); + + /* find best */ + if( sum1_Q14 < *rate_dist_Q14 ) { + *rate_dist_Q14 = sum1_Q14; + *ind = (opus_int8)k; + *gain_Q7 = gain_tmp_Q7; + } + + /* Go to next cbk vector */ + cb_row_Q7 += LTP_ORDER; + } +} diff --git a/TMessagesProj/jni/opus/silk/x86/main_sse.h b/TMessagesProj/jni/opus/silk/x86/main_sse.h new file mode 100644 index 00000000000..a221f3178c1 --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/main_sse.h @@ -0,0 +1,248 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef MAIN_SSE_H +#define MAIN_SSE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +# if defined(OPUS_X86_MAY_HAVE_SSE4_1) + +#if 0 /* FIXME: SSE disabled until silk_VQ_WMat_EC_sse4_1() gets updated. */ +# define OVERRIDE_silk_VQ_WMat_EC + +void silk_VQ_WMat_EC_sse4_1( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int16 *in_Q14, /* I input vector to be quantized */ + const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + opus_int L /* I number of vectors in codebook */ +); + +#if defined OPUS_X86_PRESUME_SSE4_1 + +#define silk_VQ_WMat_EC(ind, rate_dist_Q14, gain_Q7, in_Q14, W_Q18, cb_Q7, cb_gain_Q7, cl_Q5, \ + mu_Q9, max_gain_Q7, L, arch) \ + ((void)(arch),silk_VQ_WMat_EC_sse4_1(ind, rate_dist_Q14, gain_Q7, in_Q14, W_Q18, cb_Q7, cb_gain_Q7, cl_Q5, \ + mu_Q9, max_gain_Q7, L)) + +#else + +extern void (*const SILK_VQ_WMAT_EC_IMPL[OPUS_ARCHMASK + 1])( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int16 *in_Q14, /* I input vector to be quantized */ + const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + opus_int L /* I number of vectors in codebook */ +); + +# define silk_VQ_WMat_EC(ind, rate_dist_Q14, gain_Q7, in_Q14, W_Q18, cb_Q7, cb_gain_Q7, cl_Q5, \ + mu_Q9, max_gain_Q7, L, arch) \ + ((*SILK_VQ_WMAT_EC_IMPL[(arch) & OPUS_ARCHMASK])(ind, rate_dist_Q14, gain_Q7, in_Q14, W_Q18, cb_Q7, cb_gain_Q7, cl_Q5, \ + mu_Q9, max_gain_Q7, L)) + +#endif +#endif + +#if 0 /* FIXME: SSE disabled until the NSQ code gets updated. */ +# define OVERRIDE_silk_NSQ + +void silk_NSQ_sse4_1( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if defined OPUS_X86_PRESUME_SSE4_1 + +#define silk_NSQ(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_sse4_1(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +#else + +extern void (*const SILK_NSQ_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# define silk_NSQ(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((*SILK_NSQ_IMPL[(arch) & OPUS_ARCHMASK])(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +#endif + +# define OVERRIDE_silk_NSQ_del_dec + +void silk_NSQ_del_dec_sse4_1( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +#if defined OPUS_X86_PRESUME_SSE4_1 + +#define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((void)(arch),silk_NSQ_del_dec_sse4_1(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +#else + +extern void (*const SILK_NSQ_DEL_DEC_IMPL[OPUS_ARCHMASK + 1])( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +); + +# define silk_NSQ_del_dec(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14, arch) \ + ((*SILK_NSQ_DEL_DEC_IMPL[(arch) & OPUS_ARCHMASK])(psEncC, NSQ, psIndices, x_Q3, pulses, PredCoef_Q12, LTPCoef_Q14, AR2_Q13, \ + HarmShapeGain_Q14, Tilt_Q14, LF_shp_Q14, Gains_Q16, pitchL, Lambda_Q10, LTP_scale_Q14)) + +#endif +#endif + +void silk_noise_shape_quantizer( + silk_nsq_state *NSQ, /* I/O NSQ state */ + opus_int signalType, /* I Signal type */ + const opus_int32 x_sc_Q10[], /* I */ + opus_int8 pulses[], /* O */ + opus_int16 xq[], /* O */ + opus_int32 sLTP_Q15[], /* I/O LTP state */ + const opus_int16 a_Q12[], /* I Short term prediction coefs */ + const opus_int16 b_Q14[], /* I Long term prediction coefs */ + const opus_int16 AR_shp_Q13[], /* I Noise shaping AR coefs */ + opus_int lag, /* I Pitch lag */ + opus_int32 HarmShapeFIRPacked_Q14, /* I */ + opus_int Tilt_Q14, /* I Spectral tilt */ + opus_int32 LF_shp_Q14, /* I */ + opus_int32 Gain_Q16, /* I */ + opus_int Lambda_Q10, /* I */ + opus_int offset_Q10, /* I */ + opus_int length, /* I Input length */ + opus_int shapingLPCOrder, /* I Noise shaping AR filter order */ + opus_int predictLPCOrder, /* I Prediction filter order */ + int arch /* I Architecture */ +); + +/**************************/ +/* Noise level estimation */ +/**************************/ +void silk_VAD_GetNoiseLevels( + const opus_int32 pX[ VAD_N_BANDS ], /* I subband energies */ + silk_VAD_state *psSilk_VAD /* I/O Pointer to Silk VAD state */ +); + +# define OVERRIDE_silk_VAD_GetSA_Q8 + +opus_int silk_VAD_GetSA_Q8_sse4_1( + silk_encoder_state *psEnC, + const opus_int16 pIn[] +); + +#if defined(OPUS_X86_PRESUME_SSE4_1) +#define silk_VAD_GetSA_Q8(psEnC, pIn, arch) ((void)(arch),silk_VAD_GetSA_Q8_sse4_1(psEnC, pIn)) + +#else + +# define silk_VAD_GetSA_Q8(psEnC, pIn, arch) \ + ((*SILK_VAD_GETSA_Q8_IMPL[(arch) & OPUS_ARCHMASK])(psEnC, pIn)) + +extern opus_int (*const SILK_VAD_GETSA_Q8_IMPL[OPUS_ARCHMASK + 1])( + silk_encoder_state *psEnC, + const opus_int16 pIn[]); + +#endif + +# endif +#endif diff --git a/TMessagesProj/jni/opus/silk/x86/x86_silk_map.c b/TMessagesProj/jni/opus/silk/x86/x86_silk_map.c new file mode 100644 index 00000000000..6a1d75c426a --- /dev/null +++ b/TMessagesProj/jni/opus/silk/x86/x86_silk_map.c @@ -0,0 +1,164 @@ +/* Copyright (c) 2014, Cisco Systems, INC + Written by XiangMingZhu WeiZhou MinPeng YanWang + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include "celt/x86/x86cpu.h" +#include "structs.h" +#include "SigProc_FIX.h" +#include "pitch.h" +#include "main.h" + +#if !defined(OPUS_X86_PRESUME_SSE4_1) + +#if defined(FIXED_POINT) + +#include "fixed/main_FIX.h" + +opus_int64 (*const SILK_INNER_PROD16_ALIGNED_64_IMPL[ OPUS_ARCHMASK + 1 ] )( + const opus_int16 *inVec1, + const opus_int16 *inVec2, + const opus_int len +) = { + silk_inner_prod16_aligned_64_c, /* non-sse */ + silk_inner_prod16_aligned_64_c, + silk_inner_prod16_aligned_64_c, + MAY_HAVE_SSE4_1( silk_inner_prod16_aligned_64 ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_inner_prod16_aligned_64 ) /* avx */ +}; + +#endif + +opus_int (*const SILK_VAD_GETSA_Q8_IMPL[ OPUS_ARCHMASK + 1 ] )( + silk_encoder_state *psEncC, + const opus_int16 pIn[] +) = { + silk_VAD_GetSA_Q8_c, /* non-sse */ + silk_VAD_GetSA_Q8_c, + silk_VAD_GetSA_Q8_c, + MAY_HAVE_SSE4_1( silk_VAD_GetSA_Q8 ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_VAD_GetSA_Q8 ) /* avx */ +}; + +#if 0 /* FIXME: SSE disabled until the NSQ code gets updated. */ +void (*const SILK_NSQ_IMPL[ OPUS_ARCHMASK + 1 ] )( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) = { + silk_NSQ_c, /* non-sse */ + silk_NSQ_c, + silk_NSQ_c, + MAY_HAVE_SSE4_1( silk_NSQ ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_NSQ ) /* avx */ +}; +#endif + +#if 0 /* FIXME: SSE disabled until silk_VQ_WMat_EC_sse4_1() gets updated. */ +void (*const SILK_VQ_WMAT_EC_IMPL[ OPUS_ARCHMASK + 1 ] )( + opus_int8 *ind, /* O index of best codebook vector */ + opus_int32 *rate_dist_Q14, /* O best weighted quant error + mu * rate */ + opus_int *gain_Q7, /* O sum of absolute LTP coefficients */ + const opus_int16 *in_Q14, /* I input vector to be quantized */ + const opus_int32 *W_Q18, /* I weighting matrix */ + const opus_int8 *cb_Q7, /* I codebook */ + const opus_uint8 *cb_gain_Q7, /* I codebook effective gain */ + const opus_uint8 *cl_Q5, /* I code length for each codebook vector */ + const opus_int mu_Q9, /* I tradeoff betw. weighted error and rate */ + const opus_int32 max_gain_Q7, /* I maximum sum of absolute LTP coefficients */ + opus_int L /* I number of vectors in codebook */ +) = { + silk_VQ_WMat_EC_c, /* non-sse */ + silk_VQ_WMat_EC_c, + silk_VQ_WMat_EC_c, + MAY_HAVE_SSE4_1( silk_VQ_WMat_EC ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_VQ_WMat_EC ) /* avx */ +}; +#endif + +#if 0 /* FIXME: SSE disabled until the NSQ code gets updated. */ +void (*const SILK_NSQ_DEL_DEC_IMPL[ OPUS_ARCHMASK + 1 ] )( + const silk_encoder_state *psEncC, /* I/O Encoder State */ + silk_nsq_state *NSQ, /* I/O NSQ state */ + SideInfoIndices *psIndices, /* I/O Quantization Indices */ + const opus_int32 x_Q3[], /* I Prefiltered input signal */ + opus_int8 pulses[], /* O Quantized pulse signal */ + const opus_int16 PredCoef_Q12[ 2 * MAX_LPC_ORDER ], /* I Short term prediction coefs */ + const opus_int16 LTPCoef_Q14[ LTP_ORDER * MAX_NB_SUBFR ], /* I Long term prediction coefs */ + const opus_int16 AR2_Q13[ MAX_NB_SUBFR * MAX_SHAPE_LPC_ORDER ], /* I Noise shaping coefs */ + const opus_int HarmShapeGain_Q14[ MAX_NB_SUBFR ], /* I Long term shaping coefs */ + const opus_int Tilt_Q14[ MAX_NB_SUBFR ], /* I Spectral tilt */ + const opus_int32 LF_shp_Q14[ MAX_NB_SUBFR ], /* I Low frequency shaping coefs */ + const opus_int32 Gains_Q16[ MAX_NB_SUBFR ], /* I Quantization step sizes */ + const opus_int pitchL[ MAX_NB_SUBFR ], /* I Pitch lags */ + const opus_int Lambda_Q10, /* I Rate/distortion tradeoff */ + const opus_int LTP_scale_Q14 /* I LTP state scaling */ +) = { + silk_NSQ_del_dec_c, /* non-sse */ + silk_NSQ_del_dec_c, + silk_NSQ_del_dec_c, + MAY_HAVE_SSE4_1( silk_NSQ_del_dec ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_NSQ_del_dec ) /* avx */ +}; +#endif + +#if defined(FIXED_POINT) + +void (*const SILK_BURG_MODIFIED_IMPL[ OPUS_ARCHMASK + 1 ] )( + opus_int32 *res_nrg, /* O Residual energy */ + opus_int *res_nrg_Q, /* O Residual energy Q value */ + opus_int32 A_Q16[], /* O Prediction coefficients (length order) */ + const opus_int16 x[], /* I Input signal, length: nb_subfr * ( D + subfr_length ) */ + const opus_int32 minInvGain_Q30, /* I Inverse of max prediction gain */ + const opus_int subfr_length, /* I Input signal subframe length (incl. D preceding samples) */ + const opus_int nb_subfr, /* I Number of subframes stacked in x */ + const opus_int D, /* I Order */ + int arch /* I Run-time architecture */ +) = { + silk_burg_modified_c, /* non-sse */ + silk_burg_modified_c, + silk_burg_modified_c, + MAY_HAVE_SSE4_1( silk_burg_modified ), /* sse4.1 */ + MAY_HAVE_SSE4_1( silk_burg_modified ) /* avx */ +}; + +#endif +#endif diff --git a/TMessagesProj/jni/opus/src/analysis.c b/TMessagesProj/jni/opus/src/analysis.c index 778a62aabfc..b704fb4cf05 100644 --- a/TMessagesProj/jni/opus/src/analysis.c +++ b/TMessagesProj/jni/opus/src/analysis.c @@ -29,18 +29,20 @@ #include "config.h" #endif +#define ANALYSIS_C + +#include + +#include "mathops.h" #include "kiss_fft.h" #include "celt.h" #include "modes.h" #include "arch.h" #include "quant_bands.h" -#include #include "analysis.h" #include "mlp.h" #include "stack_alloc.h" -extern const MLP net; - #ifndef M_PI #define M_PI 3.141592653 #endif @@ -111,33 +113,20 @@ static const int extra_bands[NB_TOT_BANDS+1] = { #define NB_TONAL_SKIP_BANDS 9 -#define cA 0.43157974f -#define cB 0.67848403f -#define cC 0.08595542f -#define cE ((float)M_PI/2) -static OPUS_INLINE float fast_atan2f(float y, float x) { - float x2, y2; - /* Should avoid underflow on the values we'll get */ - if (ABS16(x)+ABS16(y)<1e-9f) - { - x*=1e12f; - y*=1e12f; - } - x2 = x*x; - y2 = y*y; - if(x2arch = opus_select_arch(); + /* Clear remaining fields. */ + tonality_analysis_reset(tonal); +} + +void tonality_analysis_reset(TonalityAnalysisState *tonal) +{ + /* Clear non-reusable fields. */ + char *start = (char*)&tonal->TONALITY_ANALYSIS_RESET_START; + OPUS_CLEAR(start, sizeof(TonalityAnalysisState) - (start - (char*)tonal)); } void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len) @@ -189,7 +178,12 @@ void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int info_out->music_prob = psum; } -void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info_out, const CELTMode *celt_mode, const void *x, int len, int offset, int c1, int c2, int C, int lsb_depth, downmix_func downmix) +static const float std_feature_bias[9] = { + 5.684947, 3.475288, 1.770634, 1.599784, 3.773215, + 2.163313, 1.260756, 1.116868, 1.918795 +}; + +static void tonality_analysis(TonalityAnalysisState *tonal, const CELTMode *celt_mode, const void *x, int len, int offset, int c1, int c2, int C, int lsb_depth, downmix_func downmix) { int i, b; const kiss_fft_state *kfft; @@ -262,7 +256,16 @@ void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info_out, con remaining = len - (ANALYSIS_BUF_SIZE-tonal->mem_fill); downmix(x, &tonal->inmem[240], remaining, offset+ANALYSIS_BUF_SIZE-tonal->mem_fill, c1, c2, C); tonal->mem_fill = 240 + remaining; - opus_fft(kfft, in, out); + opus_fft(kfft, in, out, tonal->arch); +#ifndef FIXED_POINT + /* If there's any NaN on the input, the entire output will be NaN, so we only need to check one value. */ + if (celt_isnan(out[0].r)) + { + info->valid = 0; + RESTORE_STACK; + return; + } +#endif for (i=1;ivalid = 0; + RESTORE_STACK; + return; + } +#endif + tonal->E[tonal->E_count][b] = E; frame_noisiness += nE/(1e-15f+E); @@ -475,24 +488,28 @@ void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info_out, con tonal->mem[i] = BFCC[i]; } for (i=0;i<9;i++) - features[11+i] = (float)sqrt(tonal->std[i]); - features[20] = info->tonality; - features[21] = info->activity; - features[22] = frame_stationarity; - features[23] = info->tonality_slope; - features[24] = tonal->lowECount; + features[11+i] = (float)sqrt(tonal->std[i]) - std_feature_bias[i]; + features[20] = info->tonality - 0.154723; + features[21] = info->activity - 0.724643; + features[22] = frame_stationarity - 0.743717; + features[23] = info->tonality_slope + 0.069216; + features[24] = tonal->lowECount - 0.067930; #ifndef DISABLE_FLOAT_API mlp_process(&net, features, frame_probs); frame_probs[0] = .5f*(frame_probs[0]+1); /* Curve fitting between the MLP probability and the actual probability */ - frame_probs[0] = .01f + 1.21f*frame_probs[0]*frame_probs[0] - .23f*(float)pow(frame_probs[0], 10); + /*frame_probs[0] = .01f + 1.21f*frame_probs[0]*frame_probs[0] - .23f*(float)pow(frame_probs[0], 10);*/ /* Probability of active audio (as opposed to silence) */ frame_probs[1] = .5f*frame_probs[1]+.5f; + frame_probs[1] *= frame_probs[1]; /* Consider that silence has a 50-50 probability. */ frame_probs[0] = frame_probs[1]*frame_probs[0] + (1-frame_probs[1])*.5f; - /*printf("%f %f ", frame_probs[0], frame_probs[1]);*/ + /* Probability of speech or music vs noise */ + info->activity_probability = frame_probs[1]; + + /*printf("%f %f\n", frame_probs[0], frame_probs[1]);*/ { /* Probability of state transition */ float tau; @@ -508,17 +525,14 @@ void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info_out, con /* Instantaneous probability of speech and music, with beta pre-applied. */ float speech0; float music0; + float p, q; /* One transition every 3 minutes of active audio */ tau = .00005f*frame_probs[1]; - beta = .05f; - if (1) { - /* Adapt beta based on how "unexpected" the new prob is */ - float p, q; - p = MAX16(.05f,MIN16(.95f,frame_probs[0])); - q = MAX16(.05f,MIN16(.95f,tonal->music_prob)); - beta = .01f+.05f*ABS16(p-q)/(p*(1-q)+q*(1-p)); - } + /* Adapt beta based on how "unexpected" the new prob is */ + p = MAX16(.05f,MIN16(.95f,frame_probs[0])); + q = MAX16(.05f,MIN16(.95f,tonal->music_prob)); + beta = .01f+.05f*ABS16(p-q)/(p*(1-q)+q*(1-p)); /* p0 and p1 are the probabilities of speech and music at this frame using only information from previous frame and applying the state transition model */ @@ -611,8 +625,6 @@ void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info_out, con /*printf("%d %d\n", info->bandwidth, info->opus_bandwidth);*/ info->noisiness = frame_noisiness; info->valid = 1; - if (info_out!=NULL) - OPUS_COPY(info_out, info, 1); RESTORE_STACK; } @@ -630,11 +642,11 @@ void run_analysis(TonalityAnalysisState *analysis, const CELTMode *celt_mode, co pcm_len = analysis_frame_size - analysis->analysis_offset; offset = analysis->analysis_offset; - do { - tonality_analysis(analysis, NULL, celt_mode, analysis_pcm, IMIN(480, pcm_len), offset, c1, c2, C, lsb_depth, downmix); + while (pcm_len>0) { + tonality_analysis(analysis, celt_mode, analysis_pcm, IMIN(480, pcm_len), offset, c1, c2, C, lsb_depth, downmix); offset += 480; pcm_len -= 480; - } while (pcm_len>0); + } analysis->analysis_offset = analysis_frame_size; analysis->analysis_offset -= frame_size; diff --git a/TMessagesProj/jni/opus/src/analysis.h b/TMessagesProj/jni/opus/src/analysis.h index be0388faa39..9eae56a5253 100644 --- a/TMessagesProj/jni/opus/src/analysis.h +++ b/TMessagesProj/jni/opus/src/analysis.h @@ -39,6 +39,8 @@ #define DETECT_SIZE 200 typedef struct { + int arch; +#define TONALITY_ANALYSIS_RESET_START angle float angle[240]; float d_angle[240]; float d2_angle[240]; @@ -78,8 +80,19 @@ typedef struct { AnalysisInfo info[DETECT_SIZE]; } TonalityAnalysisState; -void tonality_analysis(TonalityAnalysisState *tonal, AnalysisInfo *info, - const CELTMode *celt_mode, const void *x, int len, int offset, int c1, int c2, int C, int lsb_depth, downmix_func downmix); +/** Initialize a TonalityAnalysisState struct. + * + * This performs some possibly slow initialization steps which should + * not be repeated every analysis step. No allocated memory is retained + * by the state struct, so no cleanup call is required. + */ +void tonality_analysis_init(TonalityAnalysisState *analysis); + +/** Reset a TonalityAnalysisState stuct. + * + * Call this when there's a discontinuity in the data. + */ +void tonality_analysis_reset(TonalityAnalysisState *analysis); void tonality_get_info(TonalityAnalysisState *tonal, AnalysisInfo *info_out, int len); diff --git a/TMessagesProj/jni/opus/src/mlp.c b/TMessagesProj/jni/opus/src/mlp.c index 46386026674..ff9e50df472 100644 --- a/TMessagesProj/jni/opus/src/mlp.c +++ b/TMessagesProj/jni/opus/src/mlp.c @@ -41,77 +41,82 @@ #if 0 static OPUS_INLINE opus_val16 tansig_approx(opus_val32 _x) /* Q19 */ { - int i; - opus_val16 xx; /* Q11 */ - /*double x, y;*/ - opus_val16 dy, yy; /* Q14 */ - /*x = 1.9073e-06*_x;*/ - if (_x>=QCONST32(8,19)) - return QCONST32(1.,14); - if (_x<=-QCONST32(8,19)) - return -QCONST32(1.,14); - xx = EXTRACT16(SHR32(_x, 8)); - /*i = lrint(25*x);*/ - i = SHR32(ADD32(1024,MULT16_16(25, xx)),11); - /*x -= .04*i;*/ - xx -= EXTRACT16(SHR32(MULT16_16(20972,i),8)); - /*x = xx*(1./2048);*/ - /*y = tansig_table[250+i];*/ - yy = tansig_table[250+i]; - /*y = yy*(1./16384);*/ - dy = 16384-MULT16_16_Q14(yy,yy); - yy = yy + MULT16_16_Q14(MULT16_16_Q11(xx,dy),(16384 - MULT16_16_Q11(yy,xx))); - return yy; + int i; + opus_val16 xx; /* Q11 */ + /*double x, y;*/ + opus_val16 dy, yy; /* Q14 */ + /*x = 1.9073e-06*_x;*/ + if (_x>=QCONST32(8,19)) + return QCONST32(1.,14); + if (_x<=-QCONST32(8,19)) + return -QCONST32(1.,14); + xx = EXTRACT16(SHR32(_x, 8)); + /*i = lrint(25*x);*/ + i = SHR32(ADD32(1024,MULT16_16(25, xx)),11); + /*x -= .04*i;*/ + xx -= EXTRACT16(SHR32(MULT16_16(20972,i),8)); + /*x = xx*(1./2048);*/ + /*y = tansig_table[250+i];*/ + yy = tansig_table[250+i]; + /*y = yy*(1./16384);*/ + dy = 16384-MULT16_16_Q14(yy,yy); + yy = yy + MULT16_16_Q14(MULT16_16_Q11(xx,dy),(16384 - MULT16_16_Q11(yy,xx))); + return yy; } #else /*extern const float tansig_table[501];*/ static OPUS_INLINE float tansig_approx(float x) { - int i; - float y, dy; - float sign=1; - /* Tests are reversed to catch NaNs */ + int i; + float y, dy; + float sign=1; + /* Tests are reversed to catch NaNs */ if (!(x<8)) return 1; if (!(x>-8)) return -1; - if (x<0) - { - x=-x; - sign=-1; - } - i = (int)floor(.5f+25*x); - x -= .04f*i; - y = tansig_table[i]; - dy = 1-y*y; - y = y + x*dy*(1 - y*x); - return sign*y; +#ifndef FIXED_POINT + /* Another check in case of -ffast-math */ + if (celt_isnan(x)) + return 0; +#endif + if (x<0) + { + x=-x; + sign=-1; + } + i = (int)floor(.5f+25*x); + x -= .04f*i; + y = tansig_table[i]; + dy = 1-y*y; + y = y + x*dy*(1 - y*x); + return sign*y; } #endif #if 0 void mlp_process(const MLP *m, const opus_val16 *in, opus_val16 *out) { - int j; - opus_val16 hidden[MAX_NEURONS]; - const opus_val16 *W = m->weights; - /* Copy to tmp_in */ - for (j=0;jtopo[1];j++) - { - int k; - opus_val32 sum = SHL32(EXTEND32(*W++),8); - for (k=0;ktopo[0];k++) - sum = MAC16_16(sum, in[k],*W++); - hidden[j] = tansig_approx(sum); - } - for (j=0;jtopo[2];j++) - { - int k; - opus_val32 sum = SHL32(EXTEND32(*W++),14); - for (k=0;ktopo[1];k++) - sum = MAC16_16(sum, hidden[k], *W++); - out[j] = tansig_approx(EXTRACT16(PSHR32(sum,17))); - } + int j; + opus_val16 hidden[MAX_NEURONS]; + const opus_val16 *W = m->weights; + /* Copy to tmp_in */ + for (j=0;jtopo[1];j++) + { + int k; + opus_val32 sum = SHL32(EXTEND32(*W++),8); + for (k=0;ktopo[0];k++) + sum = MAC16_16(sum, in[k],*W++); + hidden[j] = tansig_approx(sum); + } + for (j=0;jtopo[2];j++) + { + int k; + opus_val32 sum = SHL32(EXTEND32(*W++),14); + for (k=0;ktopo[1];k++) + sum = MAC16_16(sum, hidden[k], *W++); + out[j] = tansig_approx(EXTRACT16(PSHR32(sum,17))); + } } #else void mlp_process(const MLP *m, const float *in, float *out) diff --git a/TMessagesProj/jni/opus/src/mlp.h b/TMessagesProj/jni/opus/src/mlp.h index 86c8e0617d0..618e246e2c4 100644 --- a/TMessagesProj/jni/opus/src/mlp.h +++ b/TMessagesProj/jni/opus/src/mlp.h @@ -31,11 +31,13 @@ #include "arch.h" typedef struct { - int layers; - const int *topo; - const float *weights; + int layers; + const int *topo; + const float *weights; } MLP; +extern const MLP net; + void mlp_process(const MLP *m, const float *in, float *out); #endif /* _MLP_H_ */ diff --git a/TMessagesProj/jni/opus/src/mlp_data.c b/TMessagesProj/jni/opus/src/mlp_data.c index 401c4c02501..3222bece33a 100644 --- a/TMessagesProj/jni/opus/src/mlp_data.c +++ b/TMessagesProj/jni/opus/src/mlp_data.c @@ -1,105 +1,112 @@ -/* The contents of this file was automatically generated by mlp_train.c - It contains multi-layer perceptron (MLP) weights. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif #include "mlp.h" -/* RMS error was 0.138320, seed was 1361535663 */ +/* RMS error was 0.230027, seed was 1452289367 */ +/* 0.009100 0.069938 (0.230027 0.230027) 1.24058e-07 5543 */ -static const float weights[422] = { +static const float weights[450] = { /* hidden layer */ --0.0941125f, -0.302976f, -0.603555f, -0.19393f, -0.185983f, --0.601617f, -0.0465317f, -0.114563f, -0.103599f, -0.618938f, --0.317859f, -0.169949f, -0.0702885f, 0.148065f, 0.409524f, -0.548432f, 0.367649f, -0.494393f, 0.764306f, -1.83957f, -0.170849f, 12.786f, -1.08848f, -1.27284f, -16.2606f, -24.1773f, -5.57454f, -0.17276f, -0.163388f, -0.224421f, --0.0948944f, -0.0728695f, -0.26557f, -0.100283f, -0.0515459f, --0.146142f, -0.120674f, -0.180655f, 0.12857f, 0.442138f, --0.493735f, 0.167767f, 0.206699f, -0.197567f, 0.417999f, -1.50364f, -0.773341f, -10.0401f, 0.401872f, 2.97966f, -15.2165f, -1.88905f, -1.19254f, 0.0285397f, -0.00405139f, -0.0707565f, 0.00825699f, -0.0927269f, -0.010393f, -0.00428882f, --0.00489743f, -0.0709731f, -0.00255992f, 0.0395619f, 0.226424f, -0.0325231f, 0.162175f, -0.100118f, 0.485789f, 0.12697f, -0.285937f, 0.0155637f, 0.10546f, 3.05558f, 1.15059f, --1.00904f, -1.83088f, 3.31766f, -3.42516f, -0.119135f, --0.0405654f, 0.00690068f, 0.0179877f, -0.0382487f, 0.00597941f, --0.0183611f, 0.00190395f, -0.144322f, -0.0435671f, 0.000990594f, -0.221087f, 0.142405f, 0.484066f, 0.404395f, 0.511955f, --0.237255f, 0.241742f, 0.35045f, -0.699428f, 10.3993f, -2.6507f, -2.43459f, -4.18838f, 1.05928f, 1.71067f, -0.00667811f, -0.0721335f, -0.0397346f, 0.0362704f, -0.11496f, --0.0235776f, 0.0082161f, -0.0141741f, -0.0329699f, -0.0354253f, -0.00277404f, -0.290654f, -1.14767f, -0.319157f, -0.686544f, -0.36897f, 0.478899f, 0.182579f, -0.411069f, 0.881104f, --4.60683f, 1.4697f, 0.335845f, -1.81905f, -30.1699f, -5.55225f, 0.0019508f, -0.123576f, -0.0727332f, -0.0641597f, --0.0534458f, -0.108166f, -0.0937368f, -0.0697883f, -0.0275475f, --0.192309f, -0.110074f, 0.285375f, -0.405597f, 0.0926724f, --0.287881f, -0.851193f, -0.099493f, -0.233764f, -1.2852f, -1.13611f, 3.12168f, -0.0699f, -1.86216f, 2.65292f, --7.31036f, 2.44776f, -0.00111802f, -0.0632786f, -0.0376296f, --0.149851f, 0.142963f, 0.184368f, 0.123433f, 0.0756158f, -0.117312f, 0.0933395f, 0.0692163f, 0.0842592f, 0.0704683f, -0.0589963f, 0.0942205f, -0.448862f, 0.0262677f, 0.270352f, --0.262317f, 0.172586f, 2.00227f, -0.159216f, 0.038422f, -10.2073f, 4.15536f, -2.3407f, -0.0550265f, 0.00964792f, --0.141336f, 0.0274501f, 0.0343921f, -0.0487428f, 0.0950172f, --0.00775017f, -0.0372492f, -0.00548121f, -0.0663695f, 0.0960506f, --0.200008f, -0.0412827f, 0.58728f, 0.0515787f, 0.337254f, -0.855024f, 0.668371f, -0.114904f, -3.62962f, -0.467477f, --0.215472f, 2.61537f, 0.406117f, -1.36373f, 0.0425394f, -0.12208f, 0.0934502f, 0.123055f, 0.0340935f, -0.142466f, -0.035037f, -0.0490666f, 0.0733208f, 0.0576672f, 0.123984f, --0.0517194f, -0.253018f, 0.590565f, 0.145849f, 0.315185f, -0.221534f, -0.149081f, 0.216161f, -0.349575f, 24.5664f, --0.994196f, 0.614289f, -18.7905f, -2.83277f, -0.716801f, --0.347201f, 0.479515f, -0.246027f, 0.0758683f, 0.137293f, --0.17781f, 0.118751f, -0.00108329f, -0.237334f, 0.355732f, --0.12991f, -0.0547627f, -0.318576f, -0.325524f, 0.180494f, --0.0625604f, 0.141219f, 0.344064f, 0.37658f, -0.591772f, -5.8427f, -0.38075f, 0.221894f, -1.41934f, -1.87943e+06f, -1.34114f, 0.0283355f, -0.0447856f, -0.0211466f, -0.0256927f, -0.0139618f, 0.0207934f, -0.0107666f, 0.0110969f, 0.0586069f, --0.0253545f, -0.0328433f, 0.11872f, -0.216943f, 0.145748f, -0.119808f, -0.0915211f, -0.120647f, -0.0787719f, -0.143644f, --0.595116f, -1.152f, -1.25335f, -1.17092f, 4.34023f, --975268.f, -1.37033f, -0.0401123f, 0.210602f, -0.136656f, -0.135962f, -0.0523293f, 0.0444604f, 0.0143928f, 0.00412666f, --0.0193003f, 0.218452f, -0.110204f, -2.02563f, 0.918238f, --2.45362f, 1.19542f, -0.061362f, -1.92243f, 0.308111f, -0.49764f, 0.912356f, 0.209272f, -2.34525f, 2.19326f, --6.47121f, 1.69771f, -0.725123f, 0.0118929f, 0.0377944f, -0.0554003f, 0.0226452f, -0.0704421f, -0.0300309f, 0.0122978f, --0.0041782f, -0.0686612f, 0.0313115f, 0.039111f, 0.364111f, --0.0945548f, 0.0229876f, -0.17414f, 0.329795f, 0.114714f, -0.30022f, 0.106997f, 0.132355f, 5.79932f, 0.908058f, --0.905324f, -3.3561f, 0.190647f, 0.184211f, -0.673648f, -0.231807f, -0.0586222f, 0.230752f, -0.438277f, 0.245857f, --0.17215f, 0.0876383f, -0.720512f, 0.162515f, 0.0170571f, -0.101781f, 0.388477f, 1.32931f, 1.08548f, -0.936301f, --2.36958f, -6.71988f, -3.44376f, 2.13818f, 14.2318f, -4.91459f, -3.09052f, -9.69191f, -0.768234f, 1.79604f, -0.0549653f, 0.163399f, 0.0797025f, 0.0343933f, -0.0555876f, --0.00505673f, 0.0187258f, 0.0326628f, 0.0231486f, 0.15573f, -0.0476223f, -0.254824f, 1.60155f, -0.801221f, 2.55496f, -0.737629f, -1.36249f, -0.695463f, -2.44301f, -1.73188f, -3.95279f, 1.89068f, 0.486087f, -11.3343f, 3.9416e+06f, - +-1.20927f, -0.0275523f, 0.0304442f, -0.071791f, -0.0897356f, +0.100996f, -0.0492634f, 0.070213f, 0.0187071f, 0.0042668f, +0.0644589f, -0.10967f, -0.119688f, -0.00888386f, 0.170952f, +0.174562f, -0.265435f, -0.0635892f, -0.284755f, -1.06453f, +0.202855f, 2.31084f, -2.763f, -0.420894f, 0.698811f, +6.46418f, 0.0662341f, 0.0758173f, 0.0511722f, 0.0426484f, +0.115711f, -0.263815f, -0.0113386f, -0.189737f, -0.0929912f, +-0.287827f, 0.0925463f, 0.0286792f, -0.0199793f, -0.193071f, +0.258586f, 0.018504f, 0.116125f, 0.099269f, -0.00781962f, +-0.266017f, 0.283733f, 10.5488f, -0.658286f, 0.836758f, +13.1168f, -5.02553f, -1.0969f, -0.0738116f, 0.0204736f, +0.0110775f, -0.00198985f, 0.00426824f, 0.148998f, 0.0755275f, +0.112213f, -0.0518501f, 0.028398f, 0.0240943f, -0.0503666f, +-0.149506f, -0.133575f, -0.137328f, 0.116275f, 0.238077f, +0.080265f, 0.0387349f, 0.09185f, 4.04867f, 3.2435f, +-0.7155f, 8.14792f, -29.8969f, 1.1575f, -0.124794f, +0.0226943f, -0.0470538f, -0.0334476f, 0.0360859f, 0.0447789f, +-0.00258532f, -0.0192054f, -0.113082f, 0.109513f, -0.0437787f, +0.0382349f, -0.00994462f, -0.155653f, 0.171922f, -0.222151f, +-0.523565f, -0.0454432f, -0.556888f, 0.761537f, -2.70075f, +-0.883015f, 0.887168f, 0.746329f, -0.363477f, 0.360424f, +0.034755f, -0.015404f, 0.00688472f, -0.00949269f, 0.0625642f, +-0.050711f, 0.0370223f, 0.0149561f, 0.060385f, -0.0709806f, +-0.036509f, 0.099007f, -0.0397276f, 0.285237f, 0.127836f, +-0.15154f, 0.265848f, -0.0832318f, 0.0520659f, 0.897805f, +0.439215f, -3.00803f, 1.93755f, -0.408725f, 0.300142f, +-1.42001f, 0.118794f, -0.04621f, 0.050757f, -0.0239654f, +-0.0629488f, -0.0083243f, -0.108989f, -0.0326831f, 0.104277f, +-0.0667274f, 0.0475941f, 0.069182f, -0.0574944f, -0.137823f, +-0.206978f, -0.162035f, -0.208444f, 0.141751f, -0.289377f, +-0.7875f, 0.0911f, 0.174999f, -2.03406f, 3.06743f, +1.22255f, 2.10659f, 0.0779022f, -0.220946f, 0.137124f, +-0.0625512f, -0.073468f, 0.174861f, -0.139417f, 0.0967417f, +0.0830658f, -0.223662f, 0.103016f, -0.102317f, 0.225611f, +0.154375f, 0.187856f, -0.00878193f, 0.128648f, -0.371477f, +-0.479037f, 0.156541f, 1.10304f, -1.26162f, 0.086939f, +-0.143269f, 2.18318f, -2.88831f, 0.101126f, -0.308315f, +0.222068f, -0.227709f, -0.00855236f, 0.0107035f, 0.00774349f, +-0.0185316f, 0.0306039f, -0.233612f, 0.0807309f, -0.029933f, +0.151942f, -0.267724f, 0.0484763f, 0.132192f, -0.230059f, +0.357879f, 0.075414f, 0.110637f, -1.27818f, 3.3101f, +0.831064f, -0.212367f, -20.704f, -1.1492f, 0.0312941f, +-0.0208507f, -0.00804196f, 0.0110407f, 0.027599f, 0.00193594f, +-0.0135057f, -0.00614977f, 0.0505432f, -0.0108098f, 0.000826042f, +-0.0243765f, -0.323055f, 0.0682748f, -0.55873f, -0.103042f, +0.174935f, -0.126558f, -0.104518f, 0.422479f, -0.0683178f, +-1.44811f, 0.702109f, 0.712138f, -0.420112f, 2.59746f, +-0.0297689f, -0.0453044f, -0.0330312f, -0.0344518f, -0.0260442f, +-0.0610515f, 0.0916816f, 0.0256295f, -0.105187f, 0.0771212f, +-0.0898792f, -0.186163f, -0.321019f, -0.225689f, 0.175825f, +0.252939f, 0.738898f, 2.41919f, 0.114505f, -0.314026f, +0.607983f, 1.73201f, -2.09609f, -0.609339f, 1.18997f, +0.113871f, -0.177673f, -0.0785783f, -0.348033f, -0.0949274f, +-0.0191062f, 0.335823f, -0.0578655f, 0.131259f, -0.118687f, +-0.132123f, -0.239624f, 0.000738732f, -0.185936f, -0.13077f, +-0.436439f, -0.141664f, 0.0353391f, -0.0536557f, -0.0964537f, +0.221853f, 1.94264f, -1.78544f, 3.8254f, 3.74598f, +2.37071f, -1.42709f, 0.0463179f, -0.0568602f, 0.0529534f, +-0.103245f, -0.340972f, 0.101934f, -0.810811f, 0.176158f, +0.469658f, 0.0248864f, -0.10734f, -0.143827f, -0.0457131f, +0.779219f, -0.142152f, 0.0394297f, 0.160772f, -0.707623f, +-0.608236f, 1.07106f, -1.27037f, 2.27722f, 6.3688f, +0.519837f, -3.33262f, -0.126443f, -0.0943922f, 0.0265837f, +0.0620709f, 0.0113266f, -0.255811f, -0.0735781f, -0.0638952f, +-0.09543f, -0.204965f, 0.00454999f, 0.0554974f, -0.16251f, +-0.573836f, 0.258764f, 0.19895f, 0.0219289f, -0.376757f, +-0.508578f, -0.0767061f, -0.654512f, 4.48901f, 3.38949f, +-2.34533f, -11.0766f, 4.35799f, 1.66794f, -0.0513934f, +-0.0685787f, -0.0112154f, 0.000464661f, -0.234848f, -0.338596f, +-0.142242f, -0.167476f, -0.140324f, -0.104829f, -0.104195f, +0.0110351f, -0.112668f, 0.0872292f, -0.170777f, -0.0876985f, +0.123348f, -0.156758f, 0.199038f, -0.056107f, 0.899269f, +0.0820197f, -1.295f, 0.0295294f, 2.27577f, -0.940993f, +-0.0100104f, -0.111541f, -0.132193f, -0.11037f, 0.0371375f, +-0.0180172f, -0.0105591f, 0.0197043f, 0.04099f, -0.0538671f, +-0.102347f, -0.0470742f, 0.178034f, -0.267772f, -0.105789f, +-0.105376f, 0.0623262f, -0.042906f, 0.176528f, -0.160076f, +-2.28483f, -1.92619f, 0.218149f, 9.67107f, 3.30399f, +-1.75951f, 0.129671f, 0.118305f, 0.140766f, 0.0678099f, +0.00313175f, -0.0144533f, -0.0310217f, -0.0245139f, 0.136948f, +0.150137f, 0.112326f, -0.0755033f, -0.280984f, -0.249342f, +-0.681657f, 0.0315246f, 0.294968f, 0.0407062f, 0.282759f, +-0.344185f, -7.32828f, -0.220036f, -0.560418f, -1.87191f, +-7.10132f, /* output layer */ --0.381439f, 0.12115f, -0.906927f, 2.93878f, 1.6388f, -0.882811f, 0.874344f, 1.21726f, -0.874545f, 0.321706f, -0.785055f, 0.946558f, -0.575066f, -3.46553f, 0.884905f, -0.0924047f, -9.90712f, 0.391338f, 0.160103f, -2.04954f, -4.1455f, 0.0684029f, -0.144761f, -0.285282f, 0.379244f, --1.1584f, -0.0277241f, -9.85f, -4.82386f, 3.71333f, -3.87308f, 3.52558f}; +8.55144, 2.0822, 0.240592, 1.26638, 0.0309585, +-1.09841, 0.861549, -1.53704, 1.07356, 4.39194, +-2.60476, 0.375094, 0.122941, 0.00326393, 0.777163, +-2.03171, -0.944556, 4.02958, -0.260741, 0.556385, +-0.220568, -1.77121, -0.858706, -1.52023, -0.784162, +0.345948, -0.0488489, -0.323381, -0.752573, 0.517346, +0.876475, -1.44056, -0.382276, -1.55409, }; -static const int topo[3] = {25, 15, 2}; +static const int topo[3] = {25, 16, 2}; const MLP net = { - 3, - topo, - weights + 3, + topo, + weights }; diff --git a/TMessagesProj/jni/opus/src/opus.c b/TMessagesProj/jni/opus/src/opus.c index 30890b9cbfd..f76f125cfa3 100644 --- a/TMessagesProj/jni/opus/src/opus.c +++ b/TMessagesProj/jni/opus/src/opus.c @@ -104,6 +104,10 @@ OPUS_EXPORT void opus_pcm_soft_clip(float *_x, int N, int C, float *declip_mem) /* Compute a such that maxval + a*maxval^2 = 1 */ a=(maxval-1)/(maxval*maxval); + /* Slightly boost "a" by 2^-22. This is just enough to ensure -ffast-math + does not cause output values larger than +/-1, but small enough not + to matter even for 24-bit output. */ + a += a*2.4e-7; if (x[i*C]>0) a = -a; /* Apply soft clipping */ @@ -166,6 +170,27 @@ static int parse_size(const unsigned char *data, opus_int32 len, opus_int16 *siz } } +int opus_packet_get_samples_per_frame(const unsigned char *data, + opus_int32 Fs) +{ + int audiosize; + if (data[0]&0x80) + { + audiosize = ((data[0]>>3)&0x3); + audiosize = (Fs<>3)&0x3); + if (audiosize == 3) + audiosize = Fs*60/1000; + else + audiosize = (Fs<= 2) && !defined(__OPTIMIZE__) +#if defined(__GNUC__) && (__GNUC__ >= 2) && !defined(__OPTIMIZE__) && !defined(OPUS_WILL_BE_SLOW) # pragma message "You appear to be compiling without optimization, if so opus will be very slow." #endif @@ -59,6 +59,7 @@ struct OpusDecoder { opus_int32 Fs; /** Sampling rate (at the API level) */ silk_DecControlStruct DecControl; int decode_gain; + int arch; /* Everything beyond this point gets cleared on a reset */ #define OPUS_DECODER_RESET_START stream_channels @@ -77,12 +78,6 @@ struct OpusDecoder { opus_uint32 rangeFinal; }; -#ifdef FIXED_POINT -static OPUS_INLINE opus_int16 SAT16(opus_int32 x) { - return x > 32767 ? 32767 : x < -32768 ? -32768 : (opus_int16)x; -} -#endif - int opus_decoder_get_size(int channels) { @@ -137,6 +132,7 @@ int opus_decoder_init(OpusDecoder *st, opus_int32 Fs, int channels) st->prev_mode = 0; st->frame_size = Fs/400; + st->arch = opus_select_arch(); return OPUS_OK; } @@ -215,7 +211,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, VARDECL(opus_val16, pcm_transition_silk); int pcm_transition_celt_size; VARDECL(opus_val16, pcm_transition_celt); - opus_val16 *pcm_transition; + opus_val16 *pcm_transition=NULL; int redundant_audio_size; VARDECL(opus_val16, redundant_audio); @@ -230,6 +226,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, int F2_5, F5, F10, F20; const opus_val16 *window; opus_uint32 redundant_rng = 0; + int celt_accum; ALLOC_STACK; silk_dec = (char*)st+st->silk_dec_offset; @@ -295,6 +292,14 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, } } + /* In fixed-point, we can tell CELT to do the accumulation on top of the + SILK PCM buffer. This saves some stack space. */ +#ifdef FIXED_POINT + celt_accum = (mode != MODE_CELT_ONLY) && (frame_size >= F10); +#else + celt_accum = 0; +#endif + pcm_transition_silk_size = ALLOC_NONE; pcm_transition_celt_size = ALLOC_NONE; if (data!=NULL && st->prev_mode > 0 && ( @@ -325,14 +330,20 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, } /* Don't allocate any memory when in CELT-only mode */ - pcm_silk_size = (mode != MODE_CELT_ONLY) ? IMAX(F10, frame_size)*st->channels : ALLOC_NONE; + pcm_silk_size = (mode != MODE_CELT_ONLY && !celt_accum) ? IMAX(F10, frame_size)*st->channels : ALLOC_NONE; ALLOC(pcm_silk, pcm_silk_size, opus_int16); /* SILK processing */ if (mode != MODE_CELT_ONLY) { int lost_flag, decoded_samples; - opus_int16 *pcm_ptr = pcm_silk; + opus_int16 *pcm_ptr; +#ifdef FIXED_POINT + if (celt_accum) + pcm_ptr = pcm; + else +#endif + pcm_ptr = pcm_silk; if (st->prev_mode==MODE_CELT_ONLY) silk_InitDecoder( silk_dec ); @@ -366,7 +377,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, /* Call SILK decoder */ int first_frame = decoded_samples == 0; silk_ret = silk_Decode( silk_dec, &st->DecControl, - lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size ); + lost_flag, first_frame, &dec, pcm_ptr, &silk_frame_size, st->arch ); if( silk_ret ) { if (lost_flag) { /* PLC failure should not be fatal */ @@ -462,7 +473,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, { celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0)); celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, - redundant_audio, F5, NULL); + redundant_audio, F5, NULL, 0); celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng)); } @@ -477,25 +488,28 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, celt_decoder_ctl(celt_dec, OPUS_RESET_STATE); /* Decode CELT */ celt_ret = celt_decode_with_ec(celt_dec, decode_fec ? NULL : data, - len, pcm, celt_frame_size, &dec); + len, pcm, celt_frame_size, &dec, celt_accum); } else { unsigned char silence[2] = {0xFF, 0xFF}; - for (i=0;ichannels;i++) - pcm[i] = 0; + if (!celt_accum) + { + for (i=0;ichannels;i++) + pcm[i] = 0; + } /* For hybrid -> SILK transitions, we let the CELT MDCT do a fade-out by decoding a silence frame */ if (st->prev_mode == MODE_HYBRID && !(redundancy && celt_to_silk && st->prev_redundancy) ) { celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0)); - celt_decode_with_ec(celt_dec, silence, 2, pcm, F2_5, NULL); + celt_decode_with_ec(celt_dec, silence, 2, pcm, F2_5, NULL, celt_accum); } } - if (mode != MODE_CELT_ONLY) + if (mode != MODE_CELT_ONLY && !celt_accum) { #ifdef FIXED_POINT for (i=0;ichannels;i++) - pcm[i] = SAT16(pcm[i] + pcm_silk[i]); + pcm[i] = SAT16(ADD32(pcm[i], pcm_silk[i])); #else for (i=0;ichannels;i++) pcm[i] = pcm[i] + (opus_val16)((1.f/32768.f)*pcm_silk[i]); @@ -514,7 +528,7 @@ static int opus_decode_frame(OpusDecoder *st, const unsigned char *data, celt_decoder_ctl(celt_dec, OPUS_RESET_STATE); celt_decoder_ctl(celt_dec, CELT_SET_START_BAND(0)); - celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, redundant_audio, F5, NULL); + celt_decode_with_ec(celt_dec, data+len, redundancy_bytes, redundant_audio, F5, NULL, 0); celt_decoder_ctl(celt_dec, OPUS_GET_FINAL_RANGE(&redundant_rng)); smooth_fade(pcm+st->channels*(frame_size-F2_5), redundant_audio+st->channels*F2_5, pcm+st->channels*(frame_size-F2_5), F2_5, st->channels, window, st->Fs); @@ -710,6 +724,7 @@ int opus_decode_float(OpusDecoder *st, const unsigned char *data, { VARDECL(opus_int16, out); int ret, i; + int nb_samples; ALLOC_STACK; if(frame_size<=0) @@ -717,6 +732,14 @@ int opus_decode_float(OpusDecoder *st, const unsigned char *data, RESTORE_STACK; return OPUS_BAD_ARG; } + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } ALLOC(out, frame_size*st->channels, opus_int16); ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 0); @@ -737,6 +760,7 @@ int opus_decode(OpusDecoder *st, const unsigned char *data, { VARDECL(float, out); int ret, i; + int nb_samples; ALLOC_STACK; if(frame_size<=0) @@ -745,6 +769,14 @@ int opus_decode(OpusDecoder *st, const unsigned char *data, return OPUS_BAD_ARG; } + if (data != NULL && len > 0 && !decode_fec) + { + nb_samples = opus_decoder_get_nb_samples(st, data, len); + if (nb_samples>0) + frame_size = IMIN(frame_size, nb_samples); + else + return OPUS_INVALID_PACKET; + } ALLOC(out, frame_size*st->channels, float); ret = opus_decode_native(st, data, len, out, frame_size, decode_fec, 0, NULL, 1); @@ -859,7 +891,7 @@ int opus_decoder_ctl(OpusDecoder *st, int request, ...) break; case OPUS_GET_LAST_PACKET_DURATION_REQUEST: { - opus_uint32 *value = va_arg(ap, opus_uint32*); + opus_int32 *value = va_arg(ap, opus_int32*); if (!value) { goto bad_arg; @@ -867,6 +899,26 @@ int opus_decoder_ctl(OpusDecoder *st, int request, ...) *value = st->last_packet_duration; } break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + celt_decoder_ctl(celt_dec, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + celt_decoder_ctl(celt_dec, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; default: /*fprintf(stderr, "unknown opus_decoder_ctl() request: %d", request);*/ ret = OPUS_UNIMPLEMENTED; @@ -904,27 +956,6 @@ int opus_packet_get_bandwidth(const unsigned char *data) return bandwidth; } -int opus_packet_get_samples_per_frame(const unsigned char *data, - opus_int32 Fs) -{ - int audiosize; - if (data[0]&0x80) - { - audiosize = ((data[0]>>3)&0x3); - audiosize = (Fs<>3)&0x3); - if (audiosize == 3) - audiosize = Fs*60/1000; - else - audiosize = (Fs<MB */ - 14000, 1000, /* MB<->WB */ - 17000, 1000, /* WB<->SWB */ - 21000, 2000, /* SWB<->FB */ + 10000, 1000, /* NB<->MB */ + 11000, 1000, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ }; static const opus_int32 mono_music_bandwidth_thresholds[8] = { - 12000, 1000, /* NB<->MB */ - 15000, 1000, /* MB<->WB */ - 18000, 2000, /* WB<->SWB */ - 22000, 2000, /* SWB<->FB */ + 10000, 1000, /* NB<->MB */ + 11000, 1000, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ }; static const opus_int32 stereo_voice_bandwidth_thresholds[8] = { - 11000, 1000, /* NB<->MB */ - 14000, 1000, /* MB<->WB */ - 21000, 2000, /* WB<->SWB */ - 28000, 2000, /* SWB<->FB */ + 10000, 1000, /* NB<->MB */ + 11000, 1000, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ }; static const opus_int32 stereo_music_bandwidth_thresholds[8] = { - 12000, 1000, /* NB<->MB */ - 18000, 2000, /* MB<->WB */ - 21000, 2000, /* WB<->SWB */ - 30000, 2000, /* SWB<->FB */ + 10000, 1000, /* NB<->MB */ + 11000, 1000, /* MB<->WB */ + 13500, 1000, /* WB<->SWB */ + 14000, 2000, /* SWB<->FB */ }; /* Threshold bit-rates for switching between mono and stereo */ -static const opus_int32 stereo_voice_threshold = 30000; -static const opus_int32 stereo_music_threshold = 30000; +static const opus_int32 stereo_voice_threshold = 24000; +static const opus_int32 stereo_music_threshold = 24000; /* Threshold bit-rate for switching between SILK/hybrid and CELT-only */ static const opus_int32 mode_thresholds[2][2] = { @@ -145,6 +157,14 @@ static const opus_int32 mode_thresholds[2][2] = { { 36000, 16000}, /* stereo */ }; +static const opus_int32 fec_thresholds[] = { + 12000, 1000, /* NB */ + 14000, 1000, /* MB */ + 16000, 1000, /* WB */ + 20000, 1000, /* SWB */ + 22000, 1000, /* FB */ +}; + int opus_encoder_get_size(int channels) { int silkEncSizeBytes, celtEncSizeBytes; @@ -231,7 +251,7 @@ int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int applicat st->lsb_depth = 24; st->variable_duration = OPUS_FRAMESIZE_ARG; - /* Delay compensation of 4 ms (2.5 ms for SILK's extra look-ahead + /* Delay compensation of 4 ms (2.5 ms for SILK's extra look-ahead + 1.5 ms for SILK resamplers and stereo prediction) */ st->delay_compensation = st->Fs/250; @@ -242,6 +262,10 @@ int opus_encoder_init(OpusEncoder* st, opus_int32 Fs, int channels, int applicat st->mode = MODE_HYBRID; st->bandwidth = OPUS_BANDWIDTH_FULLBAND; +#ifndef DISABLE_FLOAT_API + tonality_analysis_init(&st->analysis); +#endif + return OPUS_OK; } @@ -365,14 +389,14 @@ static void dc_reject(const opus_val16 *in, opus_int32 cutoff_Hz, opus_val16 *ou for (i=0;i=0 && offset <= subframe); - x += C*offset; len -= offset; e[1]=mem[1]; e_1[1]=1.f/(EPSILON+mem[1]); @@ -681,6 +738,7 @@ int optimize_framesize(const opus_val16 *x, int len, int C, opus_int32 Fs, pos = 3; } else { pos=1; + offset=0; } N=IMIN(len/subframe, MAX_DYNAMIC_FRAMESIZE); /* Just silencing a warning, it's really initialized later */ @@ -692,7 +750,7 @@ int optimize_framesize(const opus_val16 *x, int len, int C, opus_int32 Fs, int j; tmp=EPSILON; - downmix(x, sub, subframe, i*subframe, 0, -2, C); + downmix(x, sub, subframe, i*subframe+offset, 0, -2, C); if (i==0) memx = sub[0]; for (j=0;j-1) scale /= 2; for (j=0;j-1) scale /= 2; for (j=0;j= OPUS_FRAMESIZE_2_5_MS && variable_duration <= OPUS_FRAMESIZE_60_MS) - new_size = IMIN(3*Fs/50, (Fs/400)<<(variable_duration-OPUS_FRAMESIZE_2_5_MS)); + else if (variable_duration >= OPUS_FRAMESIZE_2_5_MS && variable_duration <= OPUS_FRAMESIZE_120_MS) + { + if (variable_duration <= OPUS_FRAMESIZE_40_MS) + new_size = (Fs/400)<<(variable_duration-OPUS_FRAMESIZE_2_5_MS); + else + new_size = (variable_duration-OPUS_FRAMESIZE_2_5_MS-2)*Fs/50; + } else return -1; if (new_size>frame_size) return -1; - if (400*new_size!=Fs && 200*new_size!=Fs && 100*new_size!=Fs && - 50*new_size!=Fs && 25*new_size!=Fs && 50*new_size!=3*Fs) + if (400*new_size!=Fs && 200*new_size!=Fs && 100*new_size!=Fs && + 50*new_size!=Fs && 25*new_size!=Fs && 50*new_size!=3*Fs && + 50*new_size!=4*Fs && 50*new_size!=5*Fs && 50*new_size!=6*Fs) return -1; return new_size; } @@ -836,6 +900,12 @@ opus_int32 compute_frame_size(const void *analysis_pcm, int frame_size, LM--; frame_size = (Fs/400<YY = MAX32(0, mem->YY); if (MAX32(mem->XX, mem->YY)>QCONST16(8e-4f, 18)) { + opus_val16 corr; + opus_val16 ldiff; + opus_val16 width; sqrt_xx = celt_sqrt(mem->XX); sqrt_yy = celt_sqrt(mem->YY); qrrt_xx = celt_sqrt(sqrt_xx); @@ -907,24 +980,333 @@ opus_val16 compute_stereo_width(const opus_val16 *pcm, int frame_size, opus_int3 mem->XY = MIN32(mem->XY, sqrt_xx*sqrt_yy); corr = SHR32(frac_div32(mem->XY,EPSILON+MULT16_16(sqrt_xx,sqrt_yy)),16); /* Approximate loudness difference */ - ldiff = Q15ONE*ABS16(qrrt_xx-qrrt_yy)/(EPSILON+qrrt_xx+qrrt_yy); + ldiff = MULT16_16(Q15ONE, ABS16(qrrt_xx-qrrt_yy))/(EPSILON+qrrt_xx+qrrt_yy); width = MULT16_16_Q15(celt_sqrt(QCONST32(1.f,30)-MULT16_16(corr,corr)), ldiff); /* Smoothing over one second */ mem->smoothed_width += (width-mem->smoothed_width)/frame_rate; /* Peak follower */ mem->max_follower = MAX16(mem->max_follower-QCONST16(.02f,15)/frame_rate, mem->smoothed_width); - } else { - width = 0; - corr=Q15ONE; - ldiff=0; } /*printf("%f %f %f %f %f ", corr/(float)Q15ONE, ldiff/(float)Q15ONE, width/(float)Q15ONE, mem->smoothed_width/(float)Q15ONE, mem->max_follower/(float)Q15ONE);*/ - return EXTRACT16(MIN32(Q15ONE,20*mem->max_follower)); + return EXTRACT16(MIN32(Q15ONE, MULT16_16(20, mem->max_follower))); +} + +static int decide_fec(int useInBandFEC, int PacketLoss_perc, int last_fec, int mode, int *bandwidth, opus_int32 rate) +{ + int orig_bandwidth; + if (!useInBandFEC || PacketLoss_perc == 0 || mode == MODE_CELT_ONLY) + return 0; + orig_bandwidth = *bandwidth; + for (;;) + { + opus_int32 hysteresis; + opus_int32 LBRR_rate_thres_bps; + /* Compute threshold for using FEC at the current bandwidth setting */ + LBRR_rate_thres_bps = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND)]; + hysteresis = fec_thresholds[2*(*bandwidth - OPUS_BANDWIDTH_NARROWBAND) + 1]; + if (last_fec == 1) LBRR_rate_thres_bps -= hysteresis; + if (last_fec == 0) LBRR_rate_thres_bps += hysteresis; + LBRR_rate_thres_bps = silk_SMULWB( silk_MUL( LBRR_rate_thres_bps, + 125 - silk_min( PacketLoss_perc, 25 ) ), SILK_FIX_CONST( 0.01, 16 ) ); + /* If loss <= 5%, we look at whether we have enough rate to enable FEC. + If loss > 5%, we decrease the bandwidth until we can enable FEC. */ + if (rate > LBRR_rate_thres_bps) + return 1; + else if (PacketLoss_perc <= 5) + return 0; + else if (*bandwidth > OPUS_BANDWIDTH_NARROWBAND) + (*bandwidth)--; + else + break; + } + /* Couldn't find any bandwidth to enable FEC, keep original bandwidth. */ + *bandwidth = orig_bandwidth; + return 0; +} + +static int compute_silk_rate_for_hybrid(int rate, int bandwidth, int frame20ms, int vbr, int fec) { + int entry; + int i; + int N; + int silk_rate; + static int rate_table[][5] = { + /* |total| |-------- SILK------------| + |-- No FEC -| |--- FEC ---| + 10ms 20ms 10ms 20ms */ + { 0, 0, 0, 0, 0}, + {12000, 10000, 10000, 11000, 11000}, + {16000, 13500, 13500, 15000, 15000}, + {20000, 16000, 16000, 18000, 18000}, + {24000, 18000, 18000, 21000, 21000}, + {32000, 22000, 22000, 28000, 28000}, + {64000, 38000, 38000, 50000, 50000} + }; + entry = 1 + frame20ms + 2*fec; + N = sizeof(rate_table)/sizeof(rate_table[0]); + for (i=1;i rate) break; + } + if (i == N) + { + silk_rate = rate_table[i-1][entry]; + /* For now, just give 50% of the extra bits to SILK. */ + silk_rate += (rate-rate_table[i-1][0])/2; + } else { + opus_int32 lo, hi, x0, x1; + lo = rate_table[i-1][entry]; + hi = rate_table[i][entry]; + x0 = rate_table[i-1][0]; + x1 = rate_table[i][0]; + silk_rate = (lo*(x1-rate) + hi*(rate-x0))/(x1-x0); + } + if (!vbr) + { + /* Tiny boost to SILK for CBR. We should probably tune this better. */ + silk_rate += 100; + } + if (bandwidth==OPUS_BANDWIDTH_SUPERWIDEBAND) + silk_rate += 300; + return silk_rate; +} + +/* Returns the equivalent bitrate corresponding to 20 ms frames, + complexity 10 VBR operation. */ +static opus_int32 compute_equiv_rate(opus_int32 bitrate, int channels, + int frame_rate, int vbr, int mode, int complexity, int loss) +{ + opus_int32 equiv; + equiv = bitrate; + /* Take into account overhead from smaller frames. */ + equiv -= (40*channels+20)*(frame_rate - 50); + /* CBR is about a 8% penalty for both SILK and CELT. */ + if (!vbr) + equiv -= equiv/12; + /* Complexity makes about 10% difference (from 0 to 10) in general. */ + equiv = equiv * (90+complexity)/100; + if (mode == MODE_SILK_ONLY || mode == MODE_HYBRID) + { + /* SILK complexity 0-1 uses the non-delayed-decision NSQ, which + costs about 20%. */ + if (complexity<2) + equiv = equiv*4/5; + equiv -= equiv*loss/(6*loss + 10); + } else if (mode == MODE_CELT_ONLY) { + /* CELT complexity 0-4 doesn't have the pitch filter, which costs + about 10%. */ + if (complexity<5) + equiv = equiv*9/10; + } else { + /* Mode not known yet */ + /* Half the SILK loss*/ + equiv -= equiv*loss/(12*loss + 20); + } + return equiv; +} + +#ifndef DISABLE_FLOAT_API + +static int is_digital_silence(const opus_val16* pcm, int frame_size, int channels, int lsb_depth) +{ + int silence = 0; + opus_val32 sample_max = 0; + + sample_max = celt_maxabs16(pcm, frame_size*channels); + +#ifdef FIXED_POINT + silence = (sample_max == 0); + (void)lsb_depth; +#else + silence = (sample_max <= (opus_val16) 1 / (1 << lsb_depth)); +#endif + + return silence; +} + +#ifdef FIXED_POINT +static opus_val32 compute_frame_energy(const opus_val16 *pcm, int frame_size, int channels, int arch) +{ + int i; + opus_val32 sample_max; + int max_shift; + int shift; + opus_val32 energy = 0; + int len = frame_size*channels; + (void)arch; + /* Max amplitude in the signal */ + sample_max = celt_maxabs16(pcm, len); + + /* Compute the right shift required in the MAC to avoid an overflow */ + max_shift = celt_ilog2(len); + shift = IMAX(0, (celt_ilog2(sample_max) << 1) + max_shift - 28); + + /* Compute the energy */ + for (i=0; i= (PSEUDO_SNR_THRESHOLD * noise_energy); + } + } + + if (is_silence || (is_noise && is_sufficiently_quiet)) + { + /* The number of consecutive DTX frames should be within the allowed bounds */ + (*nb_no_activity_frames)++; + + if (*nb_no_activity_frames > NB_SPEECH_FRAMES_BEFORE_DTX) + { + if (*nb_no_activity_frames <= (NB_SPEECH_FRAMES_BEFORE_DTX + MAX_CONSECUTIVE_DTX)) + /* Valid frame for DTX! */ + return 1; + else + (*nb_no_activity_frames) = NB_SPEECH_FRAMES_BEFORE_DTX; + } + } else + (*nb_no_activity_frames) = 0; + + return 0; +} + +#endif + +static opus_int32 encode_multiframe_packet(OpusEncoder *st, + const opus_val16 *pcm, + int nb_frames, + int frame_size, + unsigned char *data, + opus_int32 out_data_bytes, + int to_celt, + int lsb_depth, + int float_api) +{ + int i; + int ret = 0; + VARDECL(unsigned char, tmp_data); + int bak_mode, bak_bandwidth, bak_channels, bak_to_mono; + VARDECL(OpusRepacketizer, rp); + int max_header_bytes; + opus_int32 bytes_per_frame; + opus_int32 cbr_bytes; + opus_int32 repacketize_len; + int tmp_len; + ALLOC_STACK; + + /* Worst cases: + * 2 frames: Code 2 with different compressed sizes + * >2 frames: Code 3 VBR */ + max_header_bytes = nb_frames == 2 ? 3 : (2+(nb_frames-1)*2); + + if (st->use_vbr || st->user_bitrate_bps==OPUS_BITRATE_MAX) + repacketize_len = out_data_bytes; + else { + cbr_bytes = 3*st->bitrate_bps/(3*8*st->Fs/(frame_size*nb_frames)); + repacketize_len = IMIN(cbr_bytes, out_data_bytes); + } + bytes_per_frame = IMIN(1276, 1+(repacketize_len-max_header_bytes)/nb_frames); + + ALLOC(tmp_data, nb_frames*bytes_per_frame, unsigned char); + ALLOC(rp, 1, OpusRepacketizer); + opus_repacketizer_init(rp); + + bak_mode = st->user_forced_mode; + bak_bandwidth = st->user_bandwidth; + bak_channels = st->force_channels; + + st->user_forced_mode = st->mode; + st->user_bandwidth = st->bandwidth; + st->force_channels = st->stream_channels; + + bak_to_mono = st->silk_mode.toMono; + if (bak_to_mono) + st->force_channels = 1; + else + st->prev_channels = st->stream_channels; + + for (i=0;isilk_mode.toMono = 0; + st->nonfinal_frame = i<(nb_frames-1); + + /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */ + if (to_celt && i==nb_frames-1) + st->user_forced_mode = MODE_CELT_ONLY; + + tmp_len = opus_encode_native(st, pcm+i*(st->channels*frame_size), frame_size, + tmp_data+i*bytes_per_frame, bytes_per_frame, lsb_depth, NULL, 0, 0, 0, 0, + NULL, float_api); + + if (tmp_len<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + + ret = opus_repacketizer_cat(rp, tmp_data+i*bytes_per_frame, tmp_len); + + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + } + + ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr); + + if (ret<0) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } + + /* Discard configs that were forced locally for the purpose of repacketization */ + st->user_forced_mode = bak_mode; + st->user_bandwidth = bak_bandwidth; + st->force_channels = bak_channels; + st->silk_mode.toMono = bak_to_mono; + + RESTORE_STACK; + return ret; } opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, - const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, int analysis_channels, downmix_func downmix) + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api) { void *silk_enc; CELTEncoder *celt_enc; @@ -954,9 +1336,12 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ int total_buffer; opus_val16 stereo_width; const CELTMode *celt_mode; +#ifndef DISABLE_FLOAT_API AnalysisInfo analysis_info; int analysis_read_pos_bak=-1; int analysis_read_subframe_bak=-1; + int is_silence = 0; +#endif VARDECL(opus_val16, tmp_prefill); ALLOC_STACK; @@ -965,7 +1350,8 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->rangeFinal = 0; if ((!st->variable_duration && 400*frame_size != st->Fs && 200*frame_size != st->Fs && 100*frame_size != st->Fs && - 50*frame_size != st->Fs && 25*frame_size != st->Fs && 50*frame_size != 3*st->Fs) + 50*frame_size != st->Fs && 25*frame_size != st->Fs && 50*frame_size != 3*st->Fs && 50*frame_size != 4*st->Fs && + 50*frame_size != 5*st->Fs && 50*frame_size != 6*st->Fs) || (400*frame_size < st->Fs) || max_data_bytes<=0 ) @@ -973,6 +1359,14 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ RESTORE_STACK; return OPUS_BAD_ARG; } + + /* Cannot encode 100 ms in 1 byte */ + if (max_data_bytes==1 && st->Fs==(frame_size*10)) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } + silk_enc = (char*)st+st->silk_enc_offset; celt_enc = (CELTEncoder*)((char*)st+st->celt_enc_offset); if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) @@ -982,26 +1376,42 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ lsb_depth = IMIN(lsb_depth, st->lsb_depth); - analysis_info.valid = 0; celt_encoder_ctl(celt_enc, CELT_GET_MODE(&celt_mode)); #ifndef DISABLE_FLOAT_API + analysis_info.valid = 0; #ifdef FIXED_POINT if (st->silk_mode.complexity >= 10 && st->Fs==48000) #else if (st->silk_mode.complexity >= 7 && st->Fs==48000) #endif { - analysis_read_pos_bak = st->analysis.read_pos; - analysis_read_subframe_bak = st->analysis.read_subframe; - run_analysis(&st->analysis, celt_mode, analysis_pcm, analysis_size, frame_size, - c1, c2, analysis_channels, st->Fs, - lsb_depth, downmix, &analysis_info); + if (is_digital_silence(pcm, frame_size, st->channels, lsb_depth)) + { + is_silence = 1; + } else { + analysis_read_pos_bak = st->analysis.read_pos; + analysis_read_subframe_bak = st->analysis.read_subframe; + run_analysis(&st->analysis, celt_mode, analysis_pcm, analysis_size, frame_size, + c1, c2, analysis_channels, st->Fs, + lsb_depth, downmix, &analysis_info); + } + + /* Track the peak signal energy */ + if (!is_silence && analysis_info.activity_probability > DTX_ACTIVITY_THRESHOLD) + st->peak_signal_energy = MAX32(MULT16_32_Q15(QCONST16(0.999, 15), st->peak_signal_energy), + compute_frame_energy(pcm, frame_size, st->channels, st->arch)); } +#else + (void)analysis_pcm; + (void)analysis_size; #endif - st->voice_ratio = -1; - #ifndef DISABLE_FLOAT_API + /* Reset voice_ratio if this frame is not silent or if analysis is disabled. + * Otherwise, preserve voice_ratio from the last non-silent frame */ + if (!is_silence) + st->voice_ratio = -1; + st->detected_bandwidth = 0; if (analysis_info.valid) { @@ -1021,6 +1431,8 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ else st->detected_bandwidth = OPUS_BANDWIDTH_FULLBAND; } +#else + st->voice_ratio = -1; #endif if (st->channels==2 && st->force_channels!=1) @@ -1031,39 +1443,89 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->bitrate_bps = user_bitrate_to_bitrate(st, frame_size, max_data_bytes); frame_rate = st->Fs/frame_size; + if (!st->use_vbr) + { + int cbrBytes; + /* Multiply by 12 to make sure the division is exact. */ + int frame_rate12 = 12*st->Fs/frame_size; + /* We need to make sure that "int" values always fit in 16 bits. */ + cbrBytes = IMIN( (12*st->bitrate_bps/8 + frame_rate12/2)/frame_rate12, max_data_bytes); + st->bitrate_bps = cbrBytes*(opus_int32)frame_rate12*8/12; + /* Make sure we provide at least one byte to avoid failing. */ + max_data_bytes = IMAX(1, cbrBytes); + } if (max_data_bytes<3 || st->bitrate_bps < 3*frame_rate*8 || (frame_rate<50 && (max_data_bytes*frame_rate<300 || st->bitrate_bps < 2400))) { /*If the space is too low to do something useful, emit 'PLC' frames.*/ int tocmode = st->mode; int bw = st->bandwidth == 0 ? OPUS_BANDWIDTH_NARROWBAND : st->bandwidth; + int packet_code = 0; + int num_multiframes = 0; + if (tocmode==0) tocmode = MODE_SILK_ONLY; if (frame_rate>100) tocmode = MODE_CELT_ONLY; - if (frame_rate < 50) - tocmode = MODE_SILK_ONLY; + /* 40 ms -> 2 x 20 ms if in CELT_ONLY or HYBRID mode */ + if (frame_rate==25 && tocmode!=MODE_SILK_ONLY) + { + frame_rate = 50; + packet_code = 1; + } + + /* >= 60 ms frames */ + if (frame_rate<=16) + { + /* 1 x 60 ms, 2 x 40 ms, 2 x 60 ms */ + if (out_data_bytes==1 || (tocmode==MODE_SILK_ONLY && frame_rate!=10)) + { + tocmode = MODE_SILK_ONLY; + + packet_code = frame_rate <= 12; + frame_rate = frame_rate == 12 ? 25 : 16; + } + else + { + num_multiframes = 50/frame_rate; + frame_rate = 50; + packet_code = 3; + } + } + if(tocmode==MODE_SILK_ONLY&&bw>OPUS_BANDWIDTH_WIDEBAND) bw=OPUS_BANDWIDTH_WIDEBAND; else if (tocmode==MODE_CELT_ONLY&&bw==OPUS_BANDWIDTH_MEDIUMBAND) bw=OPUS_BANDWIDTH_NARROWBAND; - else if (bw<=OPUS_BANDWIDTH_SUPERWIDEBAND) + else if (tocmode==MODE_HYBRID&&bw<=OPUS_BANDWIDTH_SUPERWIDEBAND) bw=OPUS_BANDWIDTH_SUPERWIDEBAND; + data[0] = gen_toc(tocmode, frame_rate, bw, st->stream_channels); + data[0] |= packet_code; + + ret = packet_code <= 1 ? 1 : 2; + + max_data_bytes = IMAX(max_data_bytes, ret); + + if (packet_code==3) + data[1] = num_multiframes; + + if (!st->use_vbr) + { + ret = opus_packet_pad(data, ret, max_data_bytes); + if (ret == OPUS_OK) + ret = max_data_bytes; + else + ret = OPUS_INTERNAL_ERROR; + } RESTORE_STACK; - return 1; - } - if (!st->use_vbr) - { - int cbrBytes; - cbrBytes = IMIN( (st->bitrate_bps + 4*frame_rate)/(8*frame_rate) , max_data_bytes); - st->bitrate_bps = cbrBytes * (8*frame_rate); - max_data_bytes = cbrBytes; + return ret; } max_rate = frame_rate*max_data_bytes*8; /* Equivalent 20-ms rate for mode/channel/bandwidth decisions */ - equiv_rate = st->bitrate_bps - (40*st->channels+20)*(st->Fs/frame_size - 50); + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); if (st->signal_type == OPUS_SIGNAL_VOICE) voice_est = 127; @@ -1104,7 +1566,9 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ } #endif } - equiv_rate = st->bitrate_bps - (40*st->stream_channels+20)*(st->Fs/frame_size - 50); + /* Update equivalent rate for channels decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, 0, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); /* Mode selection depending on application and signal type */ if (st->application == OPUS_APPLICATION_RESTRICTED_LOWDELAY) @@ -1153,10 +1617,21 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ /* When FEC is enabled and there's enough packet loss, use SILK */ if (st->silk_mode.useInBandFEC && st->silk_mode.packetLossPercentage > (128-voice_est)>>4) st->mode = MODE_SILK_ONLY; - /* When encoding voice and DTX is enabled, set the encoder to SILK mode (at least for now) */ + /* When encoding voice and DTX is enabled but the generalized DTX cannot be used, + because of complexity and sampling frequency settings, switch to SILK DTX and + set the encoder to SILK mode */ +#ifndef DISABLE_FLOAT_API + st->silk_mode.useDTX = st->use_dtx && !(analysis_info.valid || is_silence); +#else + st->silk_mode.useDTX = st->use_dtx; +#endif if (st->silk_mode.useDTX && voice_est > 100) st->mode = MODE_SILK_ONLY; #endif + + /* If max_data_bytes represents less than 6 kb/s, switch to CELT-only mode */ + if (max_data_bytes < (frame_rate > 50 ? 9000 : 6000)*frame_size / (st->Fs * 8)) + st->mode = MODE_CELT_ONLY; } else { st->mode = st->user_forced_mode; } @@ -1166,19 +1641,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->mode = MODE_CELT_ONLY; if (st->lfe) st->mode = MODE_CELT_ONLY; - /* If max_data_bytes represents less than 8 kb/s, switch to CELT-only mode */ - if (max_data_bytes < (frame_rate > 50 ? 12000 : 8000)*frame_size / (st->Fs * 8)) - st->mode = MODE_CELT_ONLY; - - if (st->stream_channels == 1 && st->prev_channels ==2 && st->silk_mode.toMono==0 - && st->mode != MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY) - { - /* Delay stereo->mono transition by two frames so that SILK can do a smooth downmix */ - st->silk_mode.toMono = 1; - st->stream_channels = 2; - } else { - st->silk_mode.toMono = 0; - } if (st->prev_mode > 0 && ((st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) || @@ -1198,24 +1660,23 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ } } } - /* For the first frame at a new SILK bandwidth */ - if (st->silk_bw_switch) - { - redundancy = 1; - celt_to_silk = 1; - st->silk_bw_switch = 0; - prefill=1; - } - if (redundancy) + /* When encoding multiframes, we can ask for a switch to CELT only in the last frame. This switch + * is processed above as the requested mode shouldn't interrupt stereo->mono transition. */ + if (st->stream_channels == 1 && st->prev_channels ==2 && st->silk_mode.toMono==0 + && st->mode != MODE_CELT_ONLY && st->prev_mode != MODE_CELT_ONLY) { - /* Fair share of the max size allowed */ - redundancy_bytes = IMIN(257, max_data_bytes*(opus_int32)(st->Fs/200)/(frame_size+st->Fs/200)); - /* For VBR, target the actual bitrate (subject to the limit above) */ - if (st->use_vbr) - redundancy_bytes = IMIN(redundancy_bytes, st->bitrate_bps/1600); + /* Delay stereo->mono transition by two frames so that SILK can do a smooth downmix */ + st->silk_mode.toMono = 1; + st->stream_channels = 2; + } else { + st->silk_mode.toMono = 0; } + /* Update equivalent rate with mode decision. */ + equiv_rate = compute_equiv_rate(st->bitrate_bps, st->stream_channels, st->Fs/frame_size, + st->use_vbr, st->mode, st->silk_mode.complexity, st->silk_mode.packetLossPercentage); + if (st->mode != MODE_CELT_ONLY && st->prev_mode == MODE_CELT_ONLY) { silk_EncControlStruct dummy; @@ -1229,17 +1690,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ const opus_int32 *voice_bandwidth_thresholds, *music_bandwidth_thresholds; opus_int32 bandwidth_thresholds[8]; int bandwidth = OPUS_BANDWIDTH_FULLBAND; - opus_int32 equiv_rate2; - equiv_rate2 = equiv_rate; - if (st->mode != MODE_CELT_ONLY) - { - /* Adjust the threshold +/- 10% depending on complexity */ - equiv_rate2 = equiv_rate2 * (45+st->silk_mode.complexity)/50; - /* CBR is less efficient by ~1 kb/s */ - if (!st->use_vbr) - equiv_rate2 -= 1000; - } if (st->channels==2 && st->force_channels!=1) { voice_bandwidth_thresholds = stereo_voice_bandwidth_thresholds; @@ -1260,15 +1711,15 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ hysteresis = bandwidth_thresholds[2*(bandwidth-OPUS_BANDWIDTH_MEDIUMBAND)+1]; if (!st->first) { - if (st->bandwidth >= bandwidth) + if (st->auto_bandwidth >= bandwidth) threshold -= hysteresis; else threshold += hysteresis; } - if (equiv_rate2 >= threshold) + if (equiv_rate >= threshold) break; } while (--bandwidth>OPUS_BANDWIDTH_NARROWBAND); - st->bandwidth = bandwidth; + st->bandwidth = st->auto_bandwidth = bandwidth; /* Prevents any transition to SWB/FB until the SILK layer has fully switched to WB mode and turned the variable LP filter off */ if (!st->first && st->mode != MODE_CELT_ONLY && !st->silk_mode.inWBmodeWithoutVariableLP && st->bandwidth > OPUS_BANDWIDTH_WIDEBAND) @@ -1321,6 +1772,8 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->bandwidth = IMIN(st->bandwidth, st->detected_bandwidth); } #endif + st->silk_mode.LBRR_coded = decide_fec(st->silk_mode.useInBandFEC, st->silk_mode.packetLossPercentage, + st->silk_mode.LBRR_coded, st->mode, &st->bandwidth, equiv_rate); celt_encoder_ctl(celt_enc, OPUS_SET_LSB_DEPTH(lsb_depth)); /* CELT mode doesn't support mediumband, use wideband instead */ @@ -1329,15 +1782,34 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ if (st->lfe) st->bandwidth = OPUS_BANDWIDTH_NARROWBAND; - /* Can't support higher than wideband for >20 ms frames */ - if (frame_size > st->Fs/50 && (st->mode == MODE_CELT_ONLY || st->bandwidth > OPUS_BANDWIDTH_WIDEBAND)) + curr_bandwidth = st->bandwidth; + + /* Chooses the appropriate mode for speech + *NEVER* switch to/from CELT-only mode here as this will invalidate some assumptions */ + if (st->mode == MODE_SILK_ONLY && curr_bandwidth > OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_HYBRID; + if (st->mode == MODE_HYBRID && curr_bandwidth <= OPUS_BANDWIDTH_WIDEBAND) + st->mode = MODE_SILK_ONLY; + + /* Can't support higher than >60 ms frames, and >20 ms when in Hybrid or CELT-only modes */ + if ((frame_size > st->Fs/50 && (st->mode != MODE_SILK_ONLY)) || frame_size > 3*st->Fs/50) { - VARDECL(unsigned char, tmp_data); + int enc_frame_size; int nb_frames; - int bak_mode, bak_bandwidth, bak_channels, bak_to_mono; - VARDECL(OpusRepacketizer, rp); - opus_int32 bytes_per_frame; - opus_int32 repacketize_len; + + if (st->mode == MODE_SILK_ONLY) + { + if (frame_size == 2*st->Fs/25) /* 80 ms -> 2x 40 ms */ + enc_frame_size = st->Fs/25; + if (frame_size == 3*st->Fs/25) /* 120 ms -> 2x 60 ms */ + enc_frame_size = 3*st->Fs/50; + else /* 100 ms -> 5x 20 ms */ + enc_frame_size = st->Fs/50; + } + else + enc_frame_size = st->Fs/50; + + nb_frames = frame_size/enc_frame_size; #ifndef DISABLE_FLOAT_API if (analysis_read_pos_bak!= -1) @@ -1347,74 +1819,35 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ } #endif - nb_frames = frame_size > st->Fs/25 ? 3 : 2; - bytes_per_frame = IMIN(1276,(out_data_bytes-3)/nb_frames); + ret = encode_multiframe_packet(st, pcm, nb_frames, enc_frame_size, data, + out_data_bytes, to_celt, lsb_depth, float_api); - ALLOC(tmp_data, nb_frames*bytes_per_frame, unsigned char); - - ALLOC(rp, 1, OpusRepacketizer); - opus_repacketizer_init(rp); + RESTORE_STACK; + return ret; + } - bak_mode = st->user_forced_mode; - bak_bandwidth = st->user_bandwidth; - bak_channels = st->force_channels; + /* For the first frame at a new SILK bandwidth */ + if (st->silk_bw_switch) + { + redundancy = 1; + celt_to_silk = 1; + st->silk_bw_switch = 0; + prefill=1; + } - st->user_forced_mode = st->mode; - st->user_bandwidth = st->bandwidth; - st->force_channels = st->stream_channels; - bak_to_mono = st->silk_mode.toMono; + /* If we decided to go with CELT, make sure redundancy is off, no matter what + we decided earlier. */ + if (st->mode == MODE_CELT_ONLY) + redundancy = 0; - if (bak_to_mono) - st->force_channels = 1; - else - st->prev_channels = st->stream_channels; - for (i=0;isilk_mode.toMono = 0; - /* When switching from SILK/Hybrid to CELT, only ask for a switch at the last frame */ - if (to_celt && i==nb_frames-1) - st->user_forced_mode = MODE_CELT_ONLY; - tmp_len = opus_encode_native(st, pcm+i*(st->channels*st->Fs/50), st->Fs/50, - tmp_data+i*bytes_per_frame, bytes_per_frame, lsb_depth, - NULL, 0, c1, c2, analysis_channels, downmix); - if (tmp_len<0) - { - RESTORE_STACK; - return OPUS_INTERNAL_ERROR; - } - ret = opus_repacketizer_cat(rp, tmp_data+i*bytes_per_frame, tmp_len); - if (ret<0) - { - RESTORE_STACK; - return OPUS_INTERNAL_ERROR; - } - } + if (redundancy) + { + /* Fair share of the max size allowed */ + redundancy_bytes = IMIN(257, max_data_bytes*(opus_int32)(st->Fs/200)/(frame_size+st->Fs/200)); + /* For VBR, target the actual bitrate (subject to the limit above) */ if (st->use_vbr) - repacketize_len = out_data_bytes; - else - repacketize_len = IMIN(3*st->bitrate_bps/(3*8*50/nb_frames), out_data_bytes); - ret = opus_repacketizer_out_range_impl(rp, 0, nb_frames, data, repacketize_len, 0, !st->use_vbr); - if (ret<0) - { - RESTORE_STACK; - return OPUS_INTERNAL_ERROR; - } - st->user_forced_mode = bak_mode; - st->user_bandwidth = bak_bandwidth; - st->force_channels = bak_channels; - st->silk_mode.toMono = bak_to_mono; - RESTORE_STACK; - return ret; + redundancy_bytes = IMIN(redundancy_bytes, st->bitrate_bps/1600); } - curr_bandwidth = st->bandwidth; - - /* Chooses the appropriate mode for speech - *NEVER* switch to/from CELT-only mode here as this will invalidate some assumptions */ - if (st->mode == MODE_SILK_ONLY && curr_bandwidth > OPUS_BANDWIDTH_WIDEBAND) - st->mode = MODE_HYBRID; - if (st->mode == MODE_HYBRID && curr_bandwidth <= OPUS_BANDWIDTH_WIDEBAND) - st->mode = MODE_SILK_ONLY; /* printf("%d %d %d %d\n", st->bitrate_bps, st->stream_channels, st->mode, curr_bandwidth); */ bytes_target = IMIN(max_data_bytes-redundancy_bytes, st->bitrate_bps * frame_size / (st->Fs * 8)) - 1; @@ -1424,8 +1857,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ ec_enc_init(&enc, data, max_data_bytes-1); ALLOC(pcm_buf, (total_buffer+frame_size)*st->channels, opus_val16); - for (i=0;ichannels;i++) - pcm_buf[i] = st->delay_buffer[(st->encoder_buffer-total_buffer)*st->channels+i]; + OPUS_COPY(pcm_buf, &st->delay_buffer[(st->encoder_buffer-total_buffer)*st->channels], total_buffer*st->channels); if (st->mode == MODE_CELT_ONLY) hp_freq_smth1 = silk_LSHIFT( silk_lin2log( VARIABLE_HP_MIN_CUTOFF_HZ ), 8 ); @@ -1444,7 +1876,20 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ } else { dc_reject(pcm, 3, &pcm_buf[total_buffer*st->channels], st->hp_mem, frame_size, st->channels, st->Fs); } - +#ifndef FIXED_POINT + if (float_api) + { + opus_val32 sum; + sum = celt_inner_prod(&pcm_buf[total_buffer*st->channels], &pcm_buf[total_buffer*st->channels], frame_size*st->channels, st->arch); + /* This should filter out both NaNs and ridiculous signals that could + cause NaNs further down. */ + if (!(sum < 1e9f) || celt_isnan(sum)) + { + OPUS_CLEAR(&pcm_buf[total_buffer*st->channels], frame_size*st->channels); + st->hp_mem[0] = st->hp_mem[1] = st->hp_mem[2] = st->hp_mem[3] = 0; + } + } +#endif /* SILK processing */ @@ -1462,27 +1907,14 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ /* Distribute bits between SILK and CELT */ total_bitRate = 8 * bytes_target * frame_rate; if( st->mode == MODE_HYBRID ) { - int HB_gain_ref; /* Base rate for SILK */ - st->silk_mode.bitRate = st->stream_channels * ( 5000 + 1000 * ( st->Fs == 100 * frame_size ) ); - if( curr_bandwidth == OPUS_BANDWIDTH_SUPERWIDEBAND ) { - /* SILK gets 2/3 of the remaining bits */ - st->silk_mode.bitRate += ( total_bitRate - st->silk_mode.bitRate ) * 2 / 3; - } else { /* FULLBAND */ - /* SILK gets 3/5 of the remaining bits */ - st->silk_mode.bitRate += ( total_bitRate - st->silk_mode.bitRate ) * 3 / 5; - } - /* Don't let SILK use more than 80% */ - if( st->silk_mode.bitRate > total_bitRate * 4/5 ) { - st->silk_mode.bitRate = total_bitRate * 4/5; - } + st->silk_mode.bitRate = compute_silk_rate_for_hybrid(total_bitRate, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded); if (!st->energy_masking) { /* Increasingly attenuate high band when it gets allocated fewer bits */ celt_rate = total_bitRate - st->silk_mode.bitRate; - HB_gain_ref = (curr_bandwidth == OPUS_BANDWIDTH_SUPERWIDEBAND) ? 3000 : 3600; - HB_gain = SHL32((opus_val32)celt_rate, 9) / SHR32((opus_val32)celt_rate + st->stream_channels * HB_gain_ref, 6); - HB_gain = HB_gain < Q15ONE*6/7 ? HB_gain + Q15ONE/7 : Q15ONE; + HB_gain = Q15ONE - SHR32(celt_exp2(-celt_rate * QCONST16(1.f/1024, 10)), 1); } } else { /* SILK gets all bits */ @@ -1529,7 +1961,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->silk_mode.bitRate += 3*rate_offset/5; else st->silk_mode.bitRate += rate_offset; - bytes_target += rate_offset * frame_size / (8 * st->Fs); } st->silk_mode.payloadSize_ms = 1000 * frame_size / st->Fs; @@ -1550,40 +1981,52 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->silk_mode.minInternalSampleRate = 8000; } + st->silk_mode.maxInternalSampleRate = 16000; if (st->mode == MODE_SILK_ONLY) { opus_int32 effective_max_rate = max_rate; - st->silk_mode.maxInternalSampleRate = 16000; if (frame_rate > 50) effective_max_rate = effective_max_rate*2/3; - if (effective_max_rate < 13000) + if (effective_max_rate < 8000) { st->silk_mode.maxInternalSampleRate = 12000; st->silk_mode.desiredInternalSampleRate = IMIN(12000, st->silk_mode.desiredInternalSampleRate); } - if (effective_max_rate < 9600) + if (effective_max_rate < 7000) { st->silk_mode.maxInternalSampleRate = 8000; st->silk_mode.desiredInternalSampleRate = IMIN(8000, st->silk_mode.desiredInternalSampleRate); } - } else { - st->silk_mode.maxInternalSampleRate = 16000; } st->silk_mode.useCBR = !st->use_vbr; /* Call SILK encoder for the low band */ - nBytes = IMIN(1275, max_data_bytes-1-redundancy_bytes); - st->silk_mode.maxBits = nBytes*8; - /* Only allow up to 90% of the bits for hybrid mode*/ - if (st->mode == MODE_HYBRID) - st->silk_mode.maxBits = (opus_int32)st->silk_mode.maxBits*9/10; + /* Max bits for SILK, counting ToC, redundancy bytes, and optionally redundancy. */ + st->silk_mode.maxBits = (max_data_bytes-1)*8; + if (redundancy && redundancy_bytes >= 2) + { + /* Counting 1 bit for redundancy position and 20 bits for flag+size (only for hybrid). */ + st->silk_mode.maxBits -= redundancy_bytes*8 + 1; + if (st->mode == MODE_HYBRID) + st->silk_mode.maxBits -= 20; + } if (st->silk_mode.useCBR) { - st->silk_mode.maxBits = (st->silk_mode.bitRate * frame_size / (st->Fs * 8))*8; - /* Reduce the initial target to make it easier to reach the CBR rate */ - st->silk_mode.bitRate = IMAX(1, st->silk_mode.bitRate-2000); + if (st->mode == MODE_HYBRID) + { + st->silk_mode.maxBits = IMIN(st->silk_mode.maxBits, st->silk_mode.bitRate * frame_size / st->Fs); + } + } else { + /* Constrained VBR. */ + if (st->mode == MODE_HYBRID) + { + /* Compute SILK bitrate corresponding to the max total bits available */ + opus_int32 maxBitRate = compute_silk_rate_for_hybrid(st->silk_mode.maxBits*st->Fs / frame_size, + curr_bandwidth, st->Fs == 50 * frame_size, st->use_vbr, st->silk_mode.LBRR_coded); + st->silk_mode.maxBits = maxBitRate * frame_size / st->Fs; + } } if (prefill) @@ -1599,8 +2042,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ prefill_offset = st->channels*(st->encoder_buffer-st->delay_compensation-st->Fs/400); gain_fade(st->delay_buffer+prefill_offset, st->delay_buffer+prefill_offset, 0, Q15ONE, celt_mode->overlap, st->Fs/400, st->channels, celt_mode->window, st->Fs); - for(i=0;idelay_buffer[i]=0; + OPUS_CLEAR(st->delay_buffer, prefill_offset); #ifdef FIXED_POINT pcm_silk = st->delay_buffer; #else @@ -1623,13 +2065,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ RESTORE_STACK; return OPUS_INTERNAL_ERROR; } - if (nBytes==0) - { - st->rangeFinal = 0; - data[-1] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); - RESTORE_STACK; - return 1; - } + /* Extract SILK internal bandwidth for signaling in first byte */ if( st->mode == MODE_SILK_ONLY ) { if( st->silk_mode.internalSampleRate == 8000 ) { @@ -1643,7 +2079,16 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ silk_assert( st->silk_mode.internalSampleRate == 16000 ); } - st->silk_mode.opusCanSwitch = st->silk_mode.switchReady; + st->silk_mode.opusCanSwitch = st->silk_mode.switchReady && !st->nonfinal_frame; + + if (nBytes==0) + { + st->rangeFinal = 0; + data[-1] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + /* FIXME: How do we allocate the redundancy for CBR? */ if (st->silk_mode.opusCanSwitch) { @@ -1688,16 +2133,9 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ if (st->mode == MODE_HYBRID) { - int len; - - len = (ec_tell(&enc)+7)>>3; - if (redundancy) - len += st->mode == MODE_HYBRID ? 3 : 1; if( st->use_vbr ) { - nb_compr_bytes = len + bytes_target - (st->silk_mode.bitRate * frame_size) / (8 * st->Fs); - } else { - /* check if SILK used up too much */ - nb_compr_bytes = len > bytes_target ? len : bytes_target; + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(0)); } } else { if (st->use_vbr) @@ -1714,28 +2152,25 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ celt_encoder_ctl(celt_enc, OPUS_SET_VBR(1)); celt_encoder_ctl(celt_enc, OPUS_SET_VBR_CONSTRAINT(st->vbr_constraint)); celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps+bonus)); - nb_compr_bytes = max_data_bytes-1-redundancy_bytes; - } else { - nb_compr_bytes = bytes_target; } } - - } else { - nb_compr_bytes = 0; } ALLOC(tmp_prefill, st->channels*st->Fs/400, opus_val16); if (st->mode != MODE_SILK_ONLY && st->mode != st->prev_mode && st->prev_mode > 0) { - for (i=0;ichannels*st->Fs/400;i++) - tmp_prefill[i] = st->delay_buffer[(st->encoder_buffer-total_buffer-st->Fs/400)*st->channels + i]; + OPUS_COPY(tmp_prefill, &st->delay_buffer[(st->encoder_buffer-total_buffer-st->Fs/400)*st->channels], st->channels*st->Fs/400); } - for (i=0;ichannels*(st->encoder_buffer-(frame_size+total_buffer));i++) - st->delay_buffer[i] = st->delay_buffer[i+st->channels*frame_size]; - for (;iencoder_buffer*st->channels;i++) - st->delay_buffer[i] = pcm_buf[(frame_size+total_buffer-st->encoder_buffer)*st->channels+i]; - + if (st->channels*(st->encoder_buffer-(frame_size+total_buffer)) > 0) + { + OPUS_MOVE(st->delay_buffer, &st->delay_buffer[st->channels*frame_size], st->channels*(st->encoder_buffer-frame_size-total_buffer)); + OPUS_COPY(&st->delay_buffer[st->channels*(st->encoder_buffer-frame_size-total_buffer)], + &pcm_buf[0], + (frame_size+total_buffer)*st->channels); + } else { + OPUS_COPY(st->delay_buffer, &pcm_buf[(frame_size+total_buffer-st->encoder_buffer)*st->channels], st->encoder_buffer*st->channels); + } /* gain_fade() and stereo_fade() need to be after the buffer copying because we don't want any of this to affect the SILK part */ if( st->prev_HB_gain < Q15ONE || HB_gain < Q15ONE ) { @@ -1744,7 +2179,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ } st->prev_HB_gain = HB_gain; if (st->mode != MODE_HYBRID || st->stream_channels==1) - st->silk_mode.stereoWidth_Q14 = IMIN((1<<14),2*IMAX(0,equiv_rate-30000)); + st->silk_mode.stereoWidth_Q14 = IMIN((1<<14),2*IMAX(0,equiv_rate-24000)); if( !st->energy_masking && st->channels == 2 ) { /* Apply stereo width reduction (at low bitrates) */ if( st->hybrid_stereo_width_Q14 < (1 << 14) || st->silk_mode.stereoWidth_Q14 < (1 << 14) ) { @@ -1767,14 +2202,19 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ if ( st->mode != MODE_CELT_ONLY && ec_tell(&enc)+17+20*(st->mode == MODE_HYBRID) <= 8*(max_data_bytes-1)) { /* For SILK mode, the redundancy is inferred from the length */ - if (st->mode == MODE_HYBRID && (redundancy || ec_tell(&enc)+37 <= 8*nb_compr_bytes)) + if (st->mode == MODE_HYBRID) ec_enc_bit_logp(&enc, redundancy, 12); if (redundancy) { int max_redundancy; ec_enc_bit_logp(&enc, celt_to_silk, 1); if (st->mode == MODE_HYBRID) - max_redundancy = (max_data_bytes-1)-nb_compr_bytes; + { + /* Reserve the 8 bits needed for the redundancy length, + and at least a few bits for CELT if possible */ + max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+8+3+7)>>3); + max_redundancy = IMIN(max_redundancy, redundancy_bytes); + } else max_redundancy = (max_data_bytes-1)-((ec_tell(&enc)+7)>>3); /* Target the same bit-rate for redundancy as for the rest, @@ -1801,7 +2241,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ ec_enc_done(&enc); nb_compr_bytes = ret; } else { - nb_compr_bytes = IMIN((max_data_bytes-1)-redundancy_bytes, nb_compr_bytes); + nb_compr_bytes = (max_data_bytes-1)-redundancy_bytes; ec_enc_shrink(&enc, nb_compr_bytes); } @@ -1809,6 +2249,14 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ if (redundancy || st->mode != MODE_SILK_ONLY) celt_encoder_ctl(celt_enc, CELT_SET_ANALYSIS(&analysis_info)); #endif + if (st->mode == MODE_HYBRID) { + SILKInfo info; + info.signalType = st->silk_mode.signalType; + info.offset = st->silk_mode.offset; + celt_encoder_ctl(celt_enc, CELT_SET_SILK_INFO(&info)); + } else { + celt_encoder_ctl(celt_enc, CELT_SET_SILK_INFO((SILKInfo*)NULL)); + } /* 5 ms redundant frame for CELT->SILK */ if (redundancy && celt_to_silk) @@ -1816,6 +2264,7 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ int err; celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); err = celt_encode_with_ec(celt_enc, pcm_buf, st->Fs/200, data+nb_compr_bytes, redundancy_bytes, NULL); if (err < 0) { @@ -1839,15 +2288,25 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ celt_encode_with_ec(celt_enc, tmp_prefill, st->Fs/400, dummy, 2, NULL); celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); } - /* If false, we already busted the budget and we'll end up with a "PLC packet" */ + /* If false, we already busted the budget and we'll end up with a "PLC frame" */ if (ec_tell(&enc) <= 8*nb_compr_bytes) { + /* Set the bitrate again if it was overridden in the redundancy code above*/ + if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && st->use_vbr) + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(st->bitrate_bps-st->silk_mode.bitRate)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(st->use_vbr)); ret = celt_encode_with_ec(celt_enc, pcm_buf, frame_size, NULL, nb_compr_bytes, &enc); if (ret < 0) { RESTORE_STACK; return OPUS_INTERNAL_ERROR; } + /* Put CELT->SILK redundancy data in the right place. */ + if (redundancy && celt_to_silk && st->mode==MODE_HYBRID && st->use_vbr) + { + OPUS_MOVE(data+ret, data+nb_compr_bytes, redundancy_bytes); + nb_compr_bytes = nb_compr_bytes+redundancy_bytes; + } } } @@ -1863,7 +2322,15 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); celt_encoder_ctl(celt_enc, CELT_SET_START_BAND(0)); celt_encoder_ctl(celt_enc, CELT_SET_PREDICTION(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_VBR(0)); + celt_encoder_ctl(celt_enc, OPUS_SET_BITRATE(OPUS_BITRATE_MAX)); + if (st->mode == MODE_HYBRID) + { + /* Shrink packet to what the encoder actually used. */ + nb_compr_bytes = ret; + ec_enc_shrink(&enc, nb_compr_bytes); + } /* NOTE: We could speed this up slightly (at the expense of code size) by just adding a function that prefills the buffer */ celt_encode_with_ec(celt_enc, pcm_buf+st->channels*(frame_size-N2-N4), N4, dummy, 2, NULL); @@ -1893,6 +2360,21 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ st->first = 0; + /* DTX decision */ +#ifndef DISABLE_FLOAT_API + if (st->use_dtx && (analysis_info.valid || is_silence)) + { + if (decide_dtx_mode(analysis_info.activity_probability, &st->nb_no_activity_frames, + st->peak_signal_energy, pcm, frame_size, st->channels, is_silence, st->arch)) + { + st->rangeFinal = 0; + data[0] = gen_toc(st->mode, st->Fs/frame_size, curr_bandwidth, st->stream_channels); + RESTORE_STACK; + return 1; + } + } +#endif + /* In the unlikely case that the SILK encoder busted its target, tell the decoder to call the PLC */ if (ec_tell(&enc) > (max_data_bytes-1)*8) @@ -1920,7 +2402,6 @@ opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_ if (!st->use_vbr) { if (opus_packet_pad(data, ret, max_data_bytes) != OPUS_OK) - { RESTORE_STACK; return OPUS_INTERNAL_ERROR; @@ -1955,7 +2436,8 @@ opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_fra for (i=0;ichannels;i++) in[i] = FLOAT2INT16(pcm[i]); - ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, pcm, analysis_frame_size, 0, -2, st->channels, downmix_float); + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); RESTORE_STACK; return ret; } @@ -1977,7 +2459,8 @@ opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_fram , st->analysis.subframe_mem #endif ); - return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 16, pcm, analysis_frame_size, 0, -2, st->channels, downmix_int); + return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); } #else @@ -2002,7 +2485,8 @@ opus_int32 opus_encode(OpusEncoder *st, const opus_int16 *pcm, int analysis_fram for (i=0;ichannels;i++) in[i] = (1.0f/32768)*pcm[i]; - ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, pcm, analysis_frame_size, 0, -2, st->channels, downmix_int); + ret = opus_encode_native(st, in, frame_size, data, max_data_bytes, 16, + pcm, analysis_frame_size, 0, -2, st->channels, downmix_int, 0); RESTORE_STACK; return ret; } @@ -2019,7 +2503,7 @@ opus_int32 opus_encode_float(OpusEncoder *st, const float *pcm, int analysis_fra st->variable_duration, st->channels, st->Fs, st->bitrate_bps, delay_compensation, downmix_float, st->analysis.subframe_mem); return opus_encode_native(st, pcm, frame_size, data, out_data_bytes, 24, - pcm, analysis_frame_size, 0, -2, st->channels, downmix_float); + pcm, analysis_frame_size, 0, -2, st->channels, downmix_float, 1); } #endif @@ -2108,7 +2592,7 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...) case OPUS_SET_MAX_BANDWIDTH_REQUEST: { opus_int32 value = va_arg(ap, opus_int32); - if (value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) + if (value < OPUS_BANDWIDTH_NARROWBAND || value > OPUS_BANDWIDTH_FULLBAND) { goto bad_arg; } @@ -2166,7 +2650,7 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...) { goto bad_arg; } - st->silk_mode.useDTX = value; + st->use_dtx = value; } break; case OPUS_GET_DTX_REQUEST: @@ -2176,7 +2660,7 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...) { goto bad_arg; } - *value = st->silk_mode.useDTX; + *value = st->use_dtx; } break; case OPUS_SET_COMPLEXITY_REQUEST: @@ -2377,10 +2861,12 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...) case OPUS_SET_EXPERT_FRAME_DURATION_REQUEST: { opus_int32 value = va_arg(ap, opus_int32); - if (value != OPUS_FRAMESIZE_ARG && value != OPUS_FRAMESIZE_2_5_MS && - value != OPUS_FRAMESIZE_5_MS && value != OPUS_FRAMESIZE_10_MS && - value != OPUS_FRAMESIZE_20_MS && value != OPUS_FRAMESIZE_40_MS && - value != OPUS_FRAMESIZE_60_MS && value != OPUS_FRAMESIZE_VARIABLE) + if (value != OPUS_FRAMESIZE_ARG && value != OPUS_FRAMESIZE_2_5_MS && + value != OPUS_FRAMESIZE_5_MS && value != OPUS_FRAMESIZE_10_MS && + value != OPUS_FRAMESIZE_20_MS && value != OPUS_FRAMESIZE_40_MS && + value != OPUS_FRAMESIZE_60_MS && value != OPUS_FRAMESIZE_80_MS && + value != OPUS_FRAMESIZE_100_MS && value != OPUS_FRAMESIZE_120_MS && + value != OPUS_FRAMESIZE_VARIABLE) { goto bad_arg; } @@ -2414,15 +2900,38 @@ int opus_encoder_ctl(OpusEncoder *st, int request, ...) *value = st->silk_mode.reducedDependency; } break; + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 value = va_arg(ap, opus_int32); + if(value<0 || value>1) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_SET_PHASE_INVERSION_DISABLED(value)); + } + break; + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: + { + opus_int32 *value = va_arg(ap, opus_int32*); + if (!value) + { + goto bad_arg; + } + celt_encoder_ctl(celt_enc, OPUS_GET_PHASE_INVERSION_DISABLED(value)); + } + break; case OPUS_RESET_STATE: { void *silk_enc; silk_EncControlStruct dummy; + char *start; silk_enc = (char*)st+st->silk_enc_offset; +#ifndef DISABLE_FLOAT_API + tonality_analysis_reset(&st->analysis); +#endif - OPUS_CLEAR((char*)&st->OPUS_ENCODER_RESET_START, - sizeof(OpusEncoder)- - ((char*)&st->OPUS_ENCODER_RESET_START - (char*)st)); + start = (char*)&st->OPUS_ENCODER_RESET_START; + OPUS_CLEAR(start, sizeof(OpusEncoder) - (start - (char*)st)); celt_encoder_ctl(celt_enc, OPUS_RESET_STATE); silk_InitEncoder( silk_enc, st->arch, &dummy ); diff --git a/TMessagesProj/jni/opus/src/opus_multistream_decoder.c b/TMessagesProj/jni/opus/src/opus_multistream_decoder.c index a05fa1e765b..e421726f2b4 100644 --- a/TMessagesProj/jni/opus/src/opus_multistream_decoder.c +++ b/TMessagesProj/jni/opus/src/opus_multistream_decoder.c @@ -75,7 +75,7 @@ int opus_multistream_decoder_init( char *ptr; if ((channels>255) || (channels<1) || (coupled_streams>streams) || - (coupled_streams+streams>255) || (streams<1) || (coupled_streams<0)) + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) return OPUS_BAD_ARG; st->layout.nb_channels = channels; @@ -119,7 +119,7 @@ OpusMSDecoder *opus_multistream_decoder_create( int ret; OpusMSDecoder *st; if ((channels>255) || (channels<1) || (coupled_streams>streams) || - (coupled_streams+streams>255) || (streams<1) || (coupled_streams<0)) + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) { if (error) *error = OPUS_BAD_ARG; @@ -237,7 +237,8 @@ static int opus_multistream_decode_native( for (s=0;slayout.nb_streams;s++) { OpusDecoder *dec; - int packet_offset, ret; + opus_int32 packet_offset; + int ret; dec = (OpusDecoder*)ptr; ptr += (s < st->layout.nb_coupled_streams) ? align(coupled_size) : align(mono_size); @@ -425,6 +426,7 @@ int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) case OPUS_GET_SAMPLE_RATE_REQUEST: case OPUS_GET_GAIN_REQUEST: case OPUS_GET_LAST_PACKET_DURATION_REQUEST: + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: { OpusDecoder *dec; /* For int32* GET params, just query the first stream */ @@ -499,6 +501,7 @@ int opus_multistream_decoder_ctl(OpusMSDecoder *st, int request, ...) } break; case OPUS_SET_GAIN_REQUEST: + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: { int s; /* This works for int32 params */ diff --git a/TMessagesProj/jni/opus/src/opus_multistream_encoder.c b/TMessagesProj/jni/opus/src/opus_multistream_encoder.c index 49e27913ee0..f0b9ae42d00 100644 --- a/TMessagesProj/jni/opus/src/opus_multistream_encoder.c +++ b/TMessagesProj/jni/opus/src/opus_multistream_encoder.c @@ -41,6 +41,7 @@ #include "modes.h" #include "bands.h" #include "quant_bands.h" +#include "pitch.h" typedef struct { int nb_streams; @@ -69,12 +70,22 @@ typedef void (*opus_copy_channel_in_func)( int frame_size ); +typedef enum { + MAPPING_TYPE_NONE, + MAPPING_TYPE_SURROUND +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS + , /* Do not include comma at end of enumerator list */ + MAPPING_TYPE_AMBISONICS +#endif +} MappingType; + struct OpusMSEncoder { ChannelLayout layout; + int arch; int lfe_stream; int application; int variable_duration; - int surround; + MappingType mapping_type; opus_int32 bitrate_bps; float subframe_mem[3]; /* Encoder states go here */ @@ -98,7 +109,8 @@ static opus_val32 *ms_get_preemph_mem(OpusMSEncoder *st) else ptr += align(mono_size); } - return (opus_val32*)(ptr+st->layout.nb_channels*120*sizeof(opus_val32)); + /* void* cast avoids clang -Wcast-align warning */ + return (opus_val32*)(void*)(ptr+st->layout.nb_channels*120*sizeof(opus_val32)); } static opus_val32 *ms_get_window_mem(OpusMSEncoder *st) @@ -117,7 +129,8 @@ static opus_val32 *ms_get_window_mem(OpusMSEncoder *st) else ptr += align(mono_size); } - return (opus_val32*)ptr; + /* void* cast avoids clang -Wcast-align warning */ + return (opus_val32*)(void*)ptr; } static int validate_encoder_layout(const ChannelLayout *layout) @@ -199,7 +212,7 @@ static opus_val16 logSum(opus_val16 a, opus_val16 b) max = b; diff = SUB32(EXTEND32(b),EXTEND32(a)); } - if (diff >= QCONST16(8.f, DB_SHIFT)) + if (!(diff < QCONST16(8.f, DB_SHIFT))) /* inverted to catch NaNs */ return max; #ifdef FIXED_POINT low = SHR32(diff, DB_SHIFT-1); @@ -218,7 +231,7 @@ opus_val16 logSum(opus_val16 a, opus_val16 b) #endif void surround_analysis(const CELTMode *celt_mode, const void *pcm, opus_val16 *bandLogE, opus_val32 *mem, opus_val32 *preemph_mem, - int len, int overlap, int channels, int rate, opus_copy_channel_in_func copy_channel_in + int len, int overlap, int channels, int rate, opus_copy_channel_in_func copy_channel_in, int arch ) { int c; @@ -227,6 +240,7 @@ void surround_analysis(const CELTMode *celt_mode, const void *pcm, opus_val16 *b int pos[8] = {0}; int upsample; int frame_size; + int freq_size; opus_val16 channel_offset; opus_val32 bandE[21]; opus_val16 maskLogE[3][21]; @@ -237,14 +251,16 @@ void surround_analysis(const CELTMode *celt_mode, const void *pcm, opus_val16 *b upsample = resampling_factor(rate); frame_size = len*upsample; + freq_size = IMIN(960, frame_size); + /* LM = log2(frame_size / 120) */ for (LM=0;LMmaxLM;LM++) if (celt_mode->shortMdctSize<preemph, preemph_mem+c, 0); - clt_mdct_forward(&celt_mode->mdct, in, freq, celt_mode->window, overlap, celt_mode->maxLM-LM, 1); - if (upsample != 1) +#ifndef FIXED_POINT { - int bound = len; - for (i=0;imdct, in+960*frame, freq, celt_mode->window, + overlap, celt_mode->maxLM-LM, 1, arch); + if (upsample != 1) + { + int bound = freq_size/upsample; + for (i=0;i255) || (channels<1) || (coupled_streams>streams) || - (coupled_streams+streams>255) || (streams<1) || (coupled_streams<0)) + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) return OPUS_BAD_ARG; + st->arch = opus_select_arch(); st->layout.nb_channels = channels; st->layout.nb_streams = streams; st->layout.nb_coupled_streams = coupled_streams; st->subframe_mem[0]=st->subframe_mem[1]=st->subframe_mem[2]=0; - if (!surround) + if (mapping_type != MAPPING_TYPE_SURROUND) st->lfe_stream = -1; st->bitrate_bps = OPUS_AUTO; st->application = application; @@ -444,12 +491,12 @@ static int opus_multistream_encoder_init_impl( if(ret!=OPUS_OK)return ret; ptr += align(mono_size); } - if (surround) + if (mapping_type == MAPPING_TYPE_SURROUND) { OPUS_CLEAR(ms_get_preemph_mem(st), channels); OPUS_CLEAR(ms_get_window_mem(st), channels*120); } - st->surround = surround; + st->mapping_type = mapping_type; return OPUS_OK; } @@ -463,7 +510,9 @@ int opus_multistream_encoder_init( int application ) { - return opus_multistream_encoder_init_impl(st, Fs, channels, streams, coupled_streams, mapping, application, 0); + return opus_multistream_encoder_init_impl(st, Fs, channels, streams, + coupled_streams, mapping, + application, MAPPING_TYPE_NONE); } int opus_multistream_surround_encoder_init( @@ -477,6 +526,8 @@ int opus_multistream_surround_encoder_init( int application ) { + MappingType mapping_type; + if ((channels>255) || (channels<1)) return OPUS_BAD_ARG; st->lfe_stream = -1; @@ -511,10 +562,32 @@ int opus_multistream_surround_encoder_init( *coupled_streams=0; for(i=0;i2&&mapping_family==1); + + if (channels>2 && mapping_family==1) { + mapping_type = MAPPING_TYPE_SURROUND; +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS + } else if (mapping_family==254) + { + mapping_type = MAPPING_TYPE_AMBISONICS; +#endif + } else + { + mapping_type = MAPPING_TYPE_NONE; + } + return opus_multistream_encoder_init_impl(st, Fs, channels, *streams, + *coupled_streams, mapping, + application, mapping_type); } OpusMSEncoder *opus_multistream_encoder_create( @@ -530,7 +603,7 @@ OpusMSEncoder *opus_multistream_encoder_create( int ret; OpusMSEncoder *st; if ((channels>255) || (channels<1) || (coupled_streams>streams) || - (coupled_streams+streams>255) || (streams<1) || (coupled_streams<0)) + (streams<1) || (coupled_streams<0) || (streams>255-coupled_streams)) { if (error) *error = OPUS_BAD_ARG; @@ -566,6 +639,7 @@ OpusMSEncoder *opus_multistream_surround_encoder_create( ) { int ret; + opus_int32 size; OpusMSEncoder *st; if ((channels>255) || (channels<1)) { @@ -573,7 +647,14 @@ OpusMSEncoder *opus_multistream_surround_encoder_create( *error = OPUS_BAD_ARG; return NULL; } - st = (OpusMSEncoder *)opus_alloc(opus_multistream_surround_encoder_get_size(channels, mapping_family)); + size = opus_multistream_surround_encoder_get_size(channels, mapping_family); + if (!size) + { + if (error) + *error = OPUS_UNIMPLEMENTED; + return NULL; + } + st = (OpusMSEncoder *)opus_alloc(size); if (st==NULL) { if (error) @@ -594,55 +675,60 @@ OpusMSEncoder *opus_multistream_surround_encoder_create( static void surround_rate_allocation( OpusMSEncoder *st, opus_int32 *rate, - int frame_size + int frame_size, + opus_int32 Fs ) { int i; opus_int32 channel_rate; - opus_int32 Fs; - char *ptr; int stream_offset; int lfe_offset; int coupled_ratio; /* Q8 */ int lfe_ratio; /* Q8 */ + int nb_lfe; + int nb_uncoupled; + int nb_coupled; + int nb_normal; + opus_int32 channel_offset; + opus_int32 bitrate; + int total; + + nb_lfe = (st->lfe_stream!=-1); + nb_coupled = st->layout.nb_coupled_streams; + nb_uncoupled = st->layout.nb_streams-nb_coupled-nb_lfe; + nb_normal = 2*nb_coupled + nb_uncoupled; + + /* Give each non-LFE channel enough bits per channel for coding band energy. */ + channel_offset = 40*IMAX(50, Fs/frame_size); - ptr = (char*)st + align(sizeof(OpusMSEncoder)); - opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_SAMPLE_RATE(&Fs)); - - if (st->bitrate_bps > st->layout.nb_channels*40000) - stream_offset = 20000; - else - stream_offset = st->bitrate_bps/st->layout.nb_channels/2; - stream_offset += 60*(Fs/frame_size-50); - /* We start by giving each stream (coupled or uncoupled) the same bitrate. - This models the main saving of coupled channels over uncoupled. */ - /* The LFE stream is an exception to the above and gets fewer bits. */ - lfe_offset = 3500 + 60*(Fs/frame_size-50); - /* Coupled streams get twice the mono rate after the first 20 kb/s. */ - coupled_ratio = 512; - /* Should depend on the bitrate, for now we assume LFE gets 1/8 the bits of mono */ - lfe_ratio = 32; - - /* Compute bitrate allocation between streams */ if (st->bitrate_bps==OPUS_AUTO) { - channel_rate = Fs+60*Fs/frame_size; + bitrate = nb_normal*(channel_offset + Fs + 10000) + 8000*nb_lfe; } else if (st->bitrate_bps==OPUS_BITRATE_MAX) { - channel_rate = 300000; + bitrate = nb_normal*300000 + nb_lfe*128000; } else { - int nb_lfe; - int nb_uncoupled; - int nb_coupled; - int total; - nb_lfe = (st->lfe_stream!=-1); - nb_coupled = st->layout.nb_coupled_streams; - nb_uncoupled = st->layout.nb_streams-nb_coupled-nb_lfe; - total = (nb_uncoupled<<8) /* mono */ - + coupled_ratio*nb_coupled /* stereo */ - + nb_lfe*lfe_ratio; - channel_rate = 256*(st->bitrate_bps-lfe_offset*nb_lfe-stream_offset*(nb_coupled+nb_uncoupled))/total; + bitrate = st->bitrate_bps; } + + /* Give LFE some basic stream_channel allocation but never exceed 1/20 of the + total rate for the non-energy part to avoid problems at really low rate. */ + lfe_offset = IMIN(bitrate/20, 3000) + 15*IMAX(50, Fs/frame_size); + + /* We give each stream (coupled or uncoupled) a starting bitrate. + This models the main saving of coupled channels over uncoupled. */ + stream_offset = (bitrate - channel_offset*nb_normal - lfe_offset*nb_lfe)/nb_normal/2; + stream_offset = IMAX(0, IMIN(20000, stream_offset)); + + /* Coupled streams get twice the mono rate after the offset is allocated. */ + coupled_ratio = 512; + /* Should depend on the bitrate, for now we assume LFE gets 1/8 the bits of mono */ + lfe_ratio = 32; + + total = (nb_uncoupled<<8) /* mono */ + + coupled_ratio*nb_coupled /* stereo */ + + nb_lfe*lfe_ratio; + channel_rate = 256*(opus_int64)(bitrate - lfe_offset*nb_lfe - stream_offset*(nb_coupled+nb_uncoupled) - channel_offset*nb_normal)/total; #ifndef FIXED_POINT if (st->variable_duration==OPUS_FRAMESIZE_VARIABLE && frame_size != Fs/50) { @@ -655,16 +741,102 @@ static void surround_rate_allocation( for (i=0;ilayout.nb_streams;i++) { if (ilayout.nb_coupled_streams) - rate[i] = stream_offset+(channel_rate*coupled_ratio>>8); + rate[i] = 2*channel_offset + IMAX(0, stream_offset+(channel_rate*coupled_ratio>>8)); else if (i!=st->lfe_stream) - rate[i] = stream_offset+channel_rate; + rate[i] = channel_offset + IMAX(0, stream_offset + channel_rate); else - rate[i] = lfe_offset+(channel_rate*lfe_ratio>>8); + rate[i] = IMAX(0, lfe_offset+(channel_rate*lfe_ratio>>8)); } } -/* Max size in case the encoder decides to return three frames */ -#define MS_FRAME_TMP (3*1275+7) +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS +static void ambisonics_rate_allocation( + OpusMSEncoder *st, + opus_int32 *rate, + int frame_size, + opus_int32 Fs + ) +{ + int i; + int non_mono_rate; + int total_rate; + + /* The mono channel gets (rate_ratio_num / rate_ratio_den) times as many bits + * as all other channels */ + const int rate_ratio_num = 4; + const int rate_ratio_den = 3; + const int num_channels = st->layout.nb_streams; + + if (st->bitrate_bps==OPUS_AUTO) + { + total_rate = (st->layout.nb_coupled_streams + st->layout.nb_streams) * + (Fs+60*Fs/frame_size) + st->layout.nb_streams * 15000; + } else if (st->bitrate_bps==OPUS_BITRATE_MAX) + { + total_rate = num_channels * 320000; + } else { + total_rate = st->bitrate_bps; + } + + /* Let y be the non-mono rate and let p, q be integers such that the mono + * channel rate is (p/q) * y. + * Also let T be the total bitrate to allocate. Then + * (n - 1) y + (p/q) y = T + * y = (T q) / (qn - q + p) + */ + non_mono_rate = + total_rate * rate_ratio_den + / (rate_ratio_den*num_channels + rate_ratio_num - rate_ratio_den); + +#ifndef FIXED_POINT + if (st->variable_duration==OPUS_FRAMESIZE_VARIABLE && frame_size != Fs/50) + { + opus_int32 bonus = 60*(Fs/frame_size-50); + non_mono_rate += bonus; + } +#endif + + rate[0] = total_rate - (num_channels - 1) * non_mono_rate; + for (i=1;ilayout.nb_streams;i++) + { + rate[i] = non_mono_rate; + } +} +#endif /* ENABLE_EXPERIMENTAL_AMBISONICS */ + +static opus_int32 rate_allocation( + OpusMSEncoder *st, + opus_int32 *rate, + int frame_size + ) +{ + int i; + opus_int32 rate_sum=0; + opus_int32 Fs; + char *ptr; + + ptr = (char*)st + align(sizeof(OpusMSEncoder)); + opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_SAMPLE_RATE(&Fs)); + +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS + if (st->mapping_type == MAPPING_TYPE_AMBISONICS) { + ambisonics_rate_allocation(st, rate, frame_size, Fs); + } else +#endif + { + surround_rate_allocation(st, rate, frame_size, Fs); + } + + for (i=0;ilayout.nb_streams;i++) + { + rate[i] = IMAX(rate[i], 500); + rate_sum += rate[i]; + } + return rate_sum; +} + +/* Max size in case the encoder decides to return six frames (6 x 20 ms = 120 ms) */ +#define MS_FRAME_TMP (6*1275+12) static int opus_multistream_encode_native ( OpusMSEncoder *st, @@ -674,7 +846,8 @@ static int opus_multistream_encode_native unsigned char *data, opus_int32 max_data_bytes, int lsb_depth, - downmix_func downmix + downmix_func downmix, + int float_api ) { opus_int32 Fs; @@ -694,9 +867,11 @@ static int opus_multistream_encode_native opus_val32 *mem = NULL; opus_val32 *preemph_mem=NULL; int frame_size; + opus_int32 rate_sum; + opus_int32 smallest_packet; ALLOC_STACK; - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { preemph_mem = ms_get_preemph_mem(st); mem = ms_get_window_mem(st); @@ -709,13 +884,11 @@ static int opus_multistream_encode_native { opus_int32 delay_compensation; - int channels; - channels = st->layout.nb_streams + st->layout.nb_coupled_streams; opus_encoder_ctl((OpusEncoder*)ptr, OPUS_GET_LOOKAHEAD(&delay_compensation)); delay_compensation -= Fs/400; frame_size = compute_frame_size(pcm, analysis_frame_size, - st->variable_duration, channels, Fs, st->bitrate_bps, + st->variable_duration, st->layout.nb_channels, Fs, st->bitrate_bps, delay_compensation, downmix #ifndef DISABLE_FLOAT_API , st->subframe_mem @@ -730,35 +903,50 @@ static int opus_multistream_encode_native } /* Validate frame_size before using it to allocate stack space. This mirrors the checks in opus_encode[_float](). */ - if (400*frame_size != Fs && 200*frame_size != Fs && - 100*frame_size != Fs && 50*frame_size != Fs && - 25*frame_size != Fs && 50*frame_size != 3*Fs) + if (400*frame_size != Fs && 200*frame_size != Fs && + 100*frame_size != Fs && 50*frame_size != Fs && + 25*frame_size != Fs && 50*frame_size != 3*Fs && + 50*frame_size != 4*Fs && 50*frame_size != 5*Fs && + 50*frame_size != 6*Fs) { RESTORE_STACK; return OPUS_BAD_ARG; } + + /* Smallest packet the encoder can produce. */ + smallest_packet = st->layout.nb_streams*2-1; + /* 100 ms needs an extra byte per stream for the ToC. */ + if (Fs/frame_size == 10) + smallest_packet += st->layout.nb_streams; + if (max_data_bytes < smallest_packet) + { + RESTORE_STACK; + return OPUS_BUFFER_TOO_SMALL; + } ALLOC(buf, 2*frame_size, opus_val16); coupled_size = opus_encoder_get_size(2); mono_size = opus_encoder_get_size(1); ALLOC(bandSMR, 21*st->layout.nb_channels, opus_val16); - if (st->surround) - { - surround_analysis(celt_mode, pcm, bandSMR, mem, preemph_mem, frame_size, 120, st->layout.nb_channels, Fs, copy_channel_in); - } - - if (max_data_bytes < 4*st->layout.nb_streams-1) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { - RESTORE_STACK; - return OPUS_BUFFER_TOO_SMALL; + surround_analysis(celt_mode, pcm, bandSMR, mem, preemph_mem, frame_size, 120, st->layout.nb_channels, Fs, copy_channel_in, st->arch); } /* Compute bitrate allocation between streams (this could be a lot better) */ - surround_rate_allocation(st, bitrates, frame_size); + rate_sum = rate_allocation(st, bitrates, frame_size); if (!vbr) - max_data_bytes = IMIN(max_data_bytes, 3*st->bitrate_bps/(3*8*Fs/frame_size)); - + { + if (st->bitrate_bps == OPUS_AUTO) + { + max_data_bytes = IMIN(max_data_bytes, 3*rate_sum/(3*8*Fs/frame_size)); + } else if (st->bitrate_bps != OPUS_BITRATE_MAX) + { + max_data_bytes = IMIN(max_data_bytes, IMAX(smallest_packet, + 3*st->bitrate_bps/(3*8*Fs/frame_size))); + } + } ptr = (char*)st + align(sizeof(OpusMSEncoder)); for (s=0;slayout.nb_streams;s++) { @@ -769,7 +957,7 @@ static int opus_multistream_encode_native else ptr += align(mono_size); opus_encoder_ctl(enc, OPUS_SET_BITRATE(bitrates[s])); - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { opus_int32 equiv_rate; equiv_rate = st->bitrate_bps; @@ -790,6 +978,11 @@ static int opus_multistream_encode_native opus_encoder_ctl(enc, OPUS_SET_FORCE_CHANNELS(2)); } } +#ifdef ENABLE_EXPERIMENTAL_AMBISONICS + else if (st->mapping_type == MAPPING_TYPE_AMBISONICS) { + opus_encoder_ctl(enc, OPUS_SET_FORCE_MODE(MODE_CELT_ONLY)); + } +#endif } ptr = (char*)st + align(sizeof(OpusMSEncoder)); @@ -801,6 +994,7 @@ static int opus_multistream_encode_native int len; int curr_max; int c1, c2; + int ret; opus_repacketizer_init(&rp); enc = (OpusEncoder*)ptr; @@ -815,7 +1009,7 @@ static int opus_multistream_encode_native (*copy_channel_in)(buf+1, 2, pcm, st->layout.nb_channels, right, frame_size); ptr += align(coupled_size); - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { for (i=0;i<21;i++) { @@ -831,7 +1025,7 @@ static int opus_multistream_encode_native (*copy_channel_in)(buf, 1, pcm, st->layout.nb_channels, chan, frame_size); ptr += align(mono_size); - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { for (i=0;i<21;i++) bandLogE[i] = bandSMR[21*chan+i]; @@ -839,17 +1033,22 @@ static int opus_multistream_encode_native c1 = chan; c2 = -1; } - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) opus_encoder_ctl(enc, OPUS_SET_ENERGY_MASK(bandLogE)); /* number of bytes left (+Toc) */ curr_max = max_data_bytes - tot_size; - /* Reserve three bytes for the last stream and four for the others */ - curr_max -= IMAX(0,4*(st->layout.nb_streams-s-1)-1); + /* Reserve one byte for the last stream and two for the others */ + curr_max -= IMAX(0,2*(st->layout.nb_streams-s-1)-1); + /* For 100 ms, reserve an extra byte per stream for the ToC */ + if (Fs/frame_size == 10) + curr_max -= st->layout.nb_streams-s-1; curr_max = IMIN(curr_max,MS_FRAME_TMP); + /* Repacketizer will add one or two bytes for self-delimited frames */ + if (s != st->layout.nb_streams-1) curr_max -= curr_max>253 ? 2 : 1; if (!vbr && s == st->layout.nb_streams-1) opus_encoder_ctl(enc, OPUS_SET_BITRATE(curr_max*(8*Fs/frame_size))); len = opus_encode_native(enc, buf, frame_size, tmp_data, curr_max, lsb_depth, - pcm, analysis_frame_size, c1, c2, st->layout.nb_channels, downmix); + pcm, analysis_frame_size, c1, c2, st->layout.nb_channels, downmix, float_api); if (len<0) { RESTORE_STACK; @@ -858,7 +1057,14 @@ static int opus_multistream_encode_native /* We need to use the repacketizer to add the self-delimiting lengths while taking into account the fact that the encoder can now return more than one frame at a time (e.g. 60 ms CELT-only) */ - opus_repacketizer_cat(&rp, tmp_data, len); + ret = opus_repacketizer_cat(&rp, tmp_data, len); + /* If the opus_repacketizer_cat() fails, then something's seriously wrong + with the encoder. */ + if (ret != OPUS_OK) + { + RESTORE_STACK; + return OPUS_INTERNAL_ERROR; + } len = opus_repacketizer_out_range_impl(&rp, 0, opus_repacketizer_get_nb_frames(&rp), data, max_data_bytes-tot_size, s != st->layout.nb_streams-1, !vbr && s == st->layout.nb_streams-1); data += len; @@ -922,7 +1128,7 @@ int opus_multistream_encode( ) { return opus_multistream_encode_native(st, opus_copy_channel_in_short, - pcm, frame_size, data, max_data_bytes, 16, downmix_int); + pcm, frame_size, data, max_data_bytes, 16, downmix_int, 0); } #ifndef DISABLE_FLOAT_API @@ -935,7 +1141,7 @@ int opus_multistream_encode_float( ) { return opus_multistream_encode_native(st, opus_copy_channel_in_float, - pcm, frame_size, data, max_data_bytes, 16, downmix_float); + pcm, frame_size, data, max_data_bytes, 16, downmix_float, 1); } #endif @@ -951,7 +1157,7 @@ int opus_multistream_encode_float ) { return opus_multistream_encode_native(st, opus_copy_channel_in_float, - pcm, frame_size, data, max_data_bytes, 24, downmix_float); + pcm, frame_size, data, max_data_bytes, 24, downmix_float, 1); } int opus_multistream_encode( @@ -963,7 +1169,7 @@ int opus_multistream_encode( ) { return opus_multistream_encode_native(st, opus_copy_channel_in_short, - pcm, frame_size, data, max_data_bytes, 16, downmix_int); + pcm, frame_size, data, max_data_bytes, 16, downmix_int, 0); } #endif @@ -984,9 +1190,11 @@ int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) case OPUS_SET_BITRATE_REQUEST: { opus_int32 value = va_arg(ap, opus_int32); - if (value<0 && value!=OPUS_AUTO && value!=OPUS_BITRATE_MAX) + if (value != OPUS_AUTO && value != OPUS_BITRATE_MAX) { - goto bad_arg; + if (value <= 0) + goto bad_arg; + value = IMIN(300000*st->layout.nb_channels, IMAX(500*st->layout.nb_channels, value)); } st->bitrate_bps = value; } @@ -1029,6 +1237,7 @@ int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) case OPUS_GET_INBAND_FEC_REQUEST: case OPUS_GET_FORCE_CHANNELS_REQUEST: case OPUS_GET_PREDICTION_DISABLED_REQUEST: + case OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST: { OpusEncoder *enc; /* For int32* GET params, just query the first stream */ @@ -1075,6 +1284,7 @@ int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) case OPUS_SET_FORCE_MODE_REQUEST: case OPUS_SET_FORCE_CHANNELS_REQUEST: case OPUS_SET_PREDICTION_DISABLED_REQUEST: + case OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST: { int s; /* This works for int32 params */ @@ -1137,7 +1347,7 @@ int opus_multistream_encoder_ctl(OpusMSEncoder *st, int request, ...) { int s; st->subframe_mem[0] = st->subframe_mem[1] = st->subframe_mem[2] = 0; - if (st->surround) + if (st->mapping_type == MAPPING_TYPE_SURROUND) { OPUS_CLEAR(ms_get_preemph_mem(st), st->layout.nb_channels); OPUS_CLEAR(ms_get_window_mem(st), st->layout.nb_channels*120); diff --git a/TMessagesProj/jni/opus/src/opus_private.h b/TMessagesProj/jni/opus/src/opus_private.h index 83225f2b6c1..3b62eed0964 100644 --- a/TMessagesProj/jni/opus/src/opus_private.h +++ b/TMessagesProj/jni/opus/src/opus_private.h @@ -33,6 +33,8 @@ #include "opus.h" #include "celt.h" +#include /* offsetof */ + struct OpusRepacketizer { unsigned char toc; int nb_frames; @@ -86,10 +88,6 @@ typedef void (*downmix_func)(const void *, opus_val32 *, int, int, int, int, int void downmix_float(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); void downmix_int(const void *_x, opus_val32 *sub, int subframe, int offset, int c1, int c2, int C); -int optimize_framesize(const opus_val16 *x, int len, int C, opus_int32 Fs, - int bitrate, opus_val16 tonality, float *mem, int buffering, - downmix_func downmix); - int encode_size(int size, unsigned char *data); opus_int32 frame_size_select(opus_int32 frame_size, int variable_duration, opus_int32 Fs); @@ -104,16 +102,23 @@ opus_int32 compute_frame_size(const void *analysis_pcm, int frame_size, opus_int32 opus_encode_native(OpusEncoder *st, const opus_val16 *pcm, int frame_size, unsigned char *data, opus_int32 out_data_bytes, int lsb_depth, - const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, int analysis_channels, downmix_func downmix); + const void *analysis_pcm, opus_int32 analysis_size, int c1, int c2, + int analysis_channels, downmix_func downmix, int float_api); int opus_decode_native(OpusDecoder *st, const unsigned char *data, opus_int32 len, opus_val16 *pcm, int frame_size, int decode_fec, int self_delimited, opus_int32 *packet_offset, int soft_clip); -/* Make sure everything's aligned to sizeof(void *) bytes */ +/* Make sure everything is properly aligned. */ static OPUS_INLINE int align(int i) { - return (i+(int)sizeof(void *)-1)&-(int)sizeof(void *); + struct foo {char c; union { void* p; opus_int32 i; opus_val32 v; } u;}; + + unsigned int alignment = offsetof(struct foo, u); + + /* Optimizing compilers should optimize div and multiply into and + for all sensible alignment values. */ + return ((i + alignment - 1) / alignment) * alignment; } int opus_packet_parse_impl(const unsigned char *data, opus_int32 len, diff --git a/TMessagesProj/jni/opus/src/repacketizer.c b/TMessagesProj/jni/opus/src/repacketizer.c index a62675ce940..c80ee7f0014 100644 --- a/TMessagesProj/jni/opus/src/repacketizer.c +++ b/TMessagesProj/jni/opus/src/repacketizer.c @@ -219,8 +219,9 @@ opus_int32 opus_repacketizer_out_range_impl(OpusRepacketizer *rp, int begin, int } if (pad) { - for (i=ptr-data;i 0) return OPUS_OK; diff --git a/TMessagesProj/jni/sqlite/sqlite3.c b/TMessagesProj/jni/sqlite/sqlite3.c index 9e962ce13d0..af83f89f3aa 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.c +++ b/TMessagesProj/jni/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.14.2. By combining all the individual C code files into this +** version 3.18.0. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -204,12 +204,29 @@ # define _LARGEFILE_SOURCE 1 #endif -/* What version of GCC is being used. 0 means GCC is not being used */ -#ifdef __GNUC__ +/* The GCC_VERSION and MSVC_VERSION macros are used to +** conditionally include optimizations for each of these compilers. A +** value of 0 means that compiler is not being used. The +** SQLITE_DISABLE_INTRINSIC macro means do not use any compiler-specific +** optimizations, and hence set all compiler macros to 0 +** +** There was once also a CLANG_VERSION macro. However, we learn that the +** version numbers in clang are for "marketing" only and are inconsistent +** and unreliable. Fortunately, all versions of clang also recognize the +** gcc version numbers and have reasonable settings for gcc version numbers, +** so the GCC_VERSION macro will be set to a correct non-zero value even +** when compiling with clang. +*/ +#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) # define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__) #else # define GCC_VERSION 0 #endif +#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC) +# define MSVC_VERSION _MSC_VER +#else +# define MSVC_VERSION 0 +#endif /* Needed for various definitions... */ #if defined(__GNUC__) && !defined(_GNU_SOURCE) @@ -368,25 +385,26 @@ extern "C" { ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** -** Since version 3.6.18, SQLite source code has been stored in the +** Since [version 3.6.18] ([dateof:3.6.18]), +** SQLite source code has been stored in the ** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID -** string contains the date and time of the check-in (UTC) and an SHA1 -** hash of the entire source tree. +** string contains the date and time of the check-in (UTC) and a SHA1 +** or SHA3-256 hash of the entire source tree. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.14.2" -#define SQLITE_VERSION_NUMBER 3014002 -#define SQLITE_SOURCE_ID "2016-09-12 18:50:49 29dbef4b8585f753861a36d6dd102ca634197bd6" +#define SQLITE_VERSION "3.18.0" +#define SQLITE_VERSION_NUMBER 3018000 +#define SQLITE_SOURCE_ID "2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37" /* ** CAPI3REF: Run-Time Library Version Numbers -** KEYWORDS: sqlite3_version, sqlite3_sourceid +** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros @@ -518,7 +536,11 @@ typedef struct sqlite3 sqlite3; */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; - typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# ifdef SQLITE_UINT64_TYPE + typedef SQLITE_UINT64_TYPE sqlite_uint64; +# else + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# endif #elif defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 sqlite_int64; typedef unsigned __int64 sqlite_uint64; @@ -712,7 +734,8 @@ SQLITE_API int sqlite3_exec( ** [result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to -** address this, newer versions of SQLite (version 3.3.8 and later) include +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include ** support for additional result codes that provide more detailed information ** about errors. These [extended result codes] are enabled or disabled ** on a per database connection basis using the @@ -830,7 +853,7 @@ SQLITE_API int sqlite3_exec( ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are ** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN -** flag indicate that a file cannot be deleted when open. The +** flag indicates that a file cannot be deleted when open. The ** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on ** read-only media and cannot be changed even by processes with ** elevated privileges. @@ -980,6 +1003,9 @@ struct sqlite3_file { **
  • [SQLITE_IOCAP_ATOMIC64K] **
  • [SQLITE_IOCAP_SAFE_APPEND] **
  • [SQLITE_IOCAP_SEQUENTIAL] +**
  • [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] +**
  • [SQLITE_IOCAP_POWERSAFE_OVERWRITE] +**
  • [SQLITE_IOCAP_IMMUTABLE] ** ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of @@ -1236,6 +1262,12 @@ struct sqlite3_io_methods { ** on whether or not the file has been renamed, moved, or deleted since it ** was first opened. ** +**
  • [[SQLITE_FCNTL_WIN32_GET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_GET_HANDLE] opcode can be used to obtain the +** underlying native file handle associated with a file handle. This file +** control interprets its argument as a pointer to a native file handle and +** writes the resulting value there. +** **
  • [[SQLITE_FCNTL_WIN32_SET_HANDLE]] ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This ** opcode causes the xFileControl method to swap the file handle with the one @@ -1286,6 +1318,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_RBU 26 #define SQLITE_FCNTL_VFS_POINTER 27 #define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -2229,13 +2263,36 @@ struct sqlite3_mem_methods { ** be a NULL pointer, in which case the new setting is not reported back. ** ** +**
    SQLITE_DBCONFIG_MAINDBNAME
    +**
    ^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. +**
    +** +**
    SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
    +**
    Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint +** operation before closing the connection. This option may be used to +** override this behaviour. The first parameter passed to this operation +** is an integer - non-zero to disable checkpoints-on-close, or zero (the +** default) to enable them. The second parameter is a pointer to an integer +** into which is written 0 or 1 to indicate whether checkpoints-on-close +** have been disabled - 0 if they are not disabled, 1 if they are. +**
    +** ** */ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ /* @@ -2260,20 +2317,30 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); ** the table has a column of type [INTEGER PRIMARY KEY] then that column ** is another alias for the rowid. ** -** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the -** most recent successful [INSERT] into a rowid table or [virtual table] -** on database connection D. -** ^Inserts into [WITHOUT ROWID] tables are not recorded. -** ^If no successful [INSERT]s into rowid tables -** have ever occurred on the database connection D, -** then sqlite3_last_insert_rowid(D) returns zero. -** -** ^(If an [INSERT] occurs within a trigger or within a [virtual table] -** method, then this routine will return the [rowid] of the inserted -** row as long as the trigger or virtual table method is running. -** But once the trigger or virtual table method ends, the value returned -** by this routine reverts to what it was before the trigger or virtual -** table method began.)^ +** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of +** the most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** zero. +** +** As well as being set automatically as rows are inserted into database +** tables, the value returned by this function may be set explicitly by +** [sqlite3_set_last_insert_rowid()] +** +** Some virtual table implementations may INSERT rows into rowid tables as +** part of committing a transaction (e.g. to flush data accumulated in memory +** to disk). In this case subsequent calls to this function return the rowid +** associated with these internal INSERT operations, which leads to +** unintuitive results. Virtual table implementations that do write to rowid +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** control to the user. +** +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned +** by this routine reverts to what it was before the trigger was fired.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a ** successful [INSERT] and does not change the value returned by this @@ -2300,6 +2367,16 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); */ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); +/* +** CAPI3REF: Set the Last Insert Rowid value. +** METHOD: sqlite3 +** +** The sqlite3_set_last_insert_rowid(D, R) method allows the application to +** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** without inserting a row into the database. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); + /* ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 @@ -3624,9 +3701,9 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** [[SQLITE_LIMIT_VDBE_OP]] ^(
    SQLITE_LIMIT_VDBE_OP
    **
    The maximum number of instructions in a virtual machine program -** used to implement an SQL statement. This limit is not currently -** enforced, though that might be added in some future release of -** SQLite.
    )^ +** used to implement an SQL statement. If [sqlite3_prepare_v2()] or +** the equivalent tries to allocate space for more than this many opcodes +** in a single prepared statement, an SQLITE_NOMEM error is returned.)^ ** ** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(
    SQLITE_LIMIT_FUNCTION_ARG
    **
    The maximum number of arguments on a function.
    )^ @@ -3664,6 +3741,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 + /* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} @@ -3837,6 +3915,10 @@ SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. +** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since +** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and +** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so +** sqlite3_stmt_readonly() returns false for those commands. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -4119,8 +4201,12 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^This routine returns 0 if pStmt is an SQL -** statement that does not return data (for example an [UPDATE]). +** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement] returns no data (for example an [UPDATE]). +** ^However, just because this routine returns a positive number does not +** mean that one or more rows of data will be returned. ^A SELECT statement +** will always have a positive sqlite3_column_count() but depending on the +** WHERE clause constraints and the table content, it might return no rows. ** ** See also: [sqlite3_data_count()] */ @@ -4301,7 +4387,8 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** break because any application that ever receives an SQLITE_MISUSE error @@ -5628,7 +5715,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook -** is not invoked when duplication rows are deleted because of an +** is not invoked when conflicting rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. ** The exceptions defined in this paragraph might change in a future @@ -5664,7 +5751,8 @@ SQLITE_API void *sqlite3_update_hook( ** and disabled if the argument is false.)^ ** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent @@ -5758,7 +5846,8 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** from the heap. ** )^ ** -** Beginning with SQLite version 3.7.3, the soft heap limit is enforced +** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]), +** the soft heap limit is enforced ** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] ** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], ** the soft heap limit is enforced on every memory allocation. Without @@ -6152,13 +6241,15 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite version 3.8.2. If a virtual table extension is +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely ** to included crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for version 3.9.0. It may therefore only be used if +** was added for [version 3.9.0] ([dateof:3.9.0]). +** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. */ @@ -6406,6 +6497,12 @@ typedef struct sqlite3_blob sqlite3_blob; ** [database connection] error code and message accessible via ** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** +** A BLOB referenced by sqlite3_blob_open() may be read using the +** [sqlite3_blob_read()] interface and modified by using +** [sqlite3_blob_write()]. The [BLOB handle] can be moved to a +** different row of the same table using the [sqlite3_blob_reopen()] +** interface. However, the column, table, or database of a [BLOB handle] +** cannot be changed after the [BLOB handle] is opened. ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects @@ -6429,6 +6526,10 @@ typedef struct sqlite3_blob sqlite3_blob; ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. +** +** See also: [sqlite3_blob_close()], +** [sqlite3_blob_reopen()], [sqlite3_blob_read()], +** [sqlite3_blob_bytes()], [sqlite3_blob_write()]. */ SQLITE_API int sqlite3_blob_open( sqlite3*, @@ -6444,11 +6545,11 @@ SQLITE_API int sqlite3_blob_open( ** CAPI3REF: Move a BLOB Handle to a New Row ** METHOD: sqlite3_blob ** -** ^This function is used to move an existing blob handle so that it points +** ^This function is used to move an existing [BLOB handle] so that it points ** to a different row of the same database table. ^The new row is identified ** by the rowid value passed as the second argument. Only the row can be ** changed. ^The database, table and column on which the blob handle is open -** remain the same. Moving an existing blob handle to a new row can be +** remain the same. Moving an existing [BLOB handle] to a new row is ** faster than closing the existing handle and opening a new one. ** ** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - @@ -6856,7 +6957,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ -#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ @@ -6960,6 +7061,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 #define SQLITE_TESTCTRL_NEVER_CORRUPT 20 #define SQLITE_TESTCTRL_VDBE_COVERAGE 21 #define SQLITE_TESTCTRL_BYTEORDER 22 @@ -8376,7 +8478,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The [sqlite3_preupdate_hook()] interface registers a callback function ** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation -** on a [rowid table]. +** on a database table. ** ^At most one preupdate hook may be registered at a time on a single ** [database connection]; each call to [sqlite3_preupdate_hook()] overrides ** the previous setting. @@ -8385,9 +8487,9 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as ** the first parameter to callbacks. ** -** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate -** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID] -** tables. +** ^The preupdate hook only fires for changes to real database tables; the +** preupdate hook is not invoked for changes to [virtual tables] or to +** system tables like sqlite_master or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. @@ -8401,12 +8503,16 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. -** ^The sixth parameter to the preupdate callback is the initial [rowid] of the -** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is -** undefined for SQLITE_INSERT changes. -** ^The seventh parameter to the preupdate callback is the final [rowid] of -** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is -** undefined for SQLITE_DELETE changes. +** +** For an UPDATE or DELETE operation on a [rowid table], the sixth +** parameter passed to the preupdate callback is the initial [rowid] of the +** row being modified or deleted. For an INSERT operation on a rowid table, +** or any operation on a WITHOUT ROWID table, the value of the sixth +** parameter is undefined. For an INSERT or UPDATE on a rowid table the +** seventh parameter is the final rowid value of the row being inserted +** or updated. The value of the seventh parameter passed to the callback +** function is not defined for operations on WITHOUT ROWID tables, or for +** INSERT operations on rowid tables. ** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces @@ -8446,7 +8552,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** See also: [sqlite3_update_hook()] */ -SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +SQLITE_API void *sqlite3_preupdate_hook( sqlite3 *db, void(*xPreUpdate)( void *pCtx, /* Copy of third arg to preupdate_hook() */ @@ -8459,10 +8566,11 @@ SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( ), void* ); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_count(sqlite3 *); +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +#endif /* ** CAPI3REF: Low-level system error code @@ -8478,7 +8586,7 @@ SQLITE_API int sqlite3_system_errno(sqlite3*); /* ** CAPI3REF: Database Snapshot -** KEYWORDS: {snapshot} +** KEYWORDS: {snapshot} {sqlite3_snapshot} ** EXPERIMENTAL ** ** An instance of the snapshot object records the state of a [WAL mode] @@ -8502,7 +8610,9 @@ SQLITE_API int sqlite3_system_errno(sqlite3*); ** to an historical snapshot (if possible). The destructor for ** sqlite3_snapshot objects is [sqlite3_snapshot_free()]. */ -typedef struct sqlite3_snapshot sqlite3_snapshot; +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; /* ** CAPI3REF: Record A Database Snapshot @@ -8513,9 +8623,32 @@ typedef struct sqlite3_snapshot sqlite3_snapshot; ** schema S in database connection D. ^On success, the ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. -** ^If schema S of [database connection] D is not a [WAL mode] database -** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)] -** leaves the *P value unchanged and returns an appropriate [error code]. +** If there is not already a read-transaction open on schema S when +** this function is called, one is opened automatically. +** +** The following must be true for this function to succeed. If any of +** the following statements are false when sqlite3_snapshot_get() is +** called, SQLITE_ERROR is returned. The final value of *P is undefined +** in this case. +** +**
      +**
    • The database handle must be in [autocommit mode]. +** +**
    • Schema S of [database connection] D must be a [WAL mode] database. +** +**
    • There must not be a write transaction open on schema S of database +** connection D. +** +**
    • One or more transactions must have been written to the current wal +** file since it was created on disk (by any connection). This means +** that a snapshot cannot be taken on a wal mode database with no wal +** file immediately after it is first opened. At least one transaction +** must be written to it first. +**
    +** +** This function may also return SQLITE_NOMEM. If it is called with the +** database handle in autocommit mode but fails for some other reason, +** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to ** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()] @@ -8608,6 +8741,28 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( sqlite3_snapshot *p2 ); +/* +** CAPI3REF: Recover snapshots from a wal file +** EXPERIMENTAL +** +** If all connections disconnect from a database file but do not perform +** a checkpoint, the existing wal file is opened along with the database +** file the next time the database is opened. At this point it is only +** possible to successfully call sqlite3_snapshot_open() to open the most +** recent snapshot of the database (the one at the head of the wal file), +** even though the wal file may contain other valid snapshots for which +** clients have sqlite3_snapshot handles. +** +** This function attempts to scan the wal file associated with database zDb +** of database handle db and make all valid snapshots available to +** sqlite3_snapshot_open(). It is an error if there is already a read +** transaction open on the database, or if the database is not a wal mode +** database. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. @@ -8793,7 +8948,7 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; ** attached database. It is not an error if database zDb is not attached ** to the database when the session object is created. */ -int sqlite3session_create( +SQLITE_API int sqlite3session_create( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of db (e.g. "main") */ sqlite3_session **ppSession /* OUT: New session object */ @@ -8811,7 +8966,7 @@ int sqlite3session_create( ** are attached is closed. Refer to the documentation for ** [sqlite3session_create()] for details. */ -void sqlite3session_delete(sqlite3_session *pSession); +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); /* @@ -8831,7 +8986,7 @@ void sqlite3session_delete(sqlite3_session *pSession); ** The return value indicates the final state of the session object: 0 if ** the session is disabled, or 1 if it is enabled. */ -int sqlite3session_enable(sqlite3_session *pSession, int bEnable); +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); /* ** CAPI3REF: Set Or Clear the Indirect Change Flag @@ -8860,7 +9015,7 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable); ** The return value indicates the final state of the indirect flag: 0 if ** it is clear, or 1 if it is set. */ -int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); /* ** CAPI3REF: Attach A Table To A Session Object @@ -8890,7 +9045,7 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); ** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. */ -int sqlite3session_attach( +SQLITE_API int sqlite3session_attach( sqlite3_session *pSession, /* Session object */ const char *zTab /* Table name */ ); @@ -8899,12 +9054,12 @@ int sqlite3session_attach( ** CAPI3REF: Set a table filter on a Session Object. ** ** The second argument (xFilter) is the "filter callback". For changes to rows -** in tables that are not attached to the Session oject, the filter is called +** in tables that are not attached to the Session object, the filter is called ** to determine whether changes to the table's rows should be tracked or not. ** If xFilter returns 0, changes is not tracked. Note that once a table is ** attached, xFilter will not be called again. */ -void sqlite3session_table_filter( +SQLITE_API void sqlite3session_table_filter( sqlite3_session *pSession, /* Session object */ int(*xFilter)( void *pCtx, /* Copy of third arg to _filter_table() */ @@ -9017,7 +9172,7 @@ void sqlite3session_table_filter( ** another field of the same row is updated while the session is enabled, the ** resulting changeset will contain an UPDATE change that updates both fields. */ -int sqlite3session_changeset( +SQLITE_API int sqlite3session_changeset( sqlite3_session *pSession, /* Session object */ int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ @@ -9061,7 +9216,8 @@ int sqlite3session_changeset( ** the from-table, a DELETE record is added to the session object. ** **
  • For each row (primary key) that exists in both tables, but features -** different in each, an UPDATE record is added to the session. +** different non-PK values in each, an UPDATE record is added to the +** session. ** ** ** To clarify, if this function is called and then a changeset constructed @@ -9078,7 +9234,7 @@ int sqlite3session_changeset( ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ -int sqlite3session_diff( +SQLITE_API int sqlite3session_diff( sqlite3_session *pSession, const char *zFromDb, const char *zTbl, @@ -9114,7 +9270,7 @@ int sqlite3session_diff( ** a single table are grouped together, tables appear in the order in which ** they were attached to the session object). */ -int sqlite3session_patchset( +SQLITE_API int sqlite3session_patchset( sqlite3_session *pSession, /* Session object */ int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ void **ppPatchset /* OUT: Buffer containing changeset */ @@ -9135,7 +9291,7 @@ int sqlite3session_patchset( ** guaranteed that a call to sqlite3session_changeset() will return a ** changeset containing zero changes. */ -int sqlite3session_isempty(sqlite3_session *pSession); +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); /* ** CAPI3REF: Create An Iterator To Traverse A Changeset @@ -9165,12 +9321,12 @@ int sqlite3session_isempty(sqlite3_session *pSession); ** [sqlite3changeset_invert()] functions, all changes within the changeset ** that apply to a single table are grouped together. This means that when ** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visted +** this function, all changes that relate to a single table are visited ** consecutively. There is no chance that the iterator will visit a change ** the applies to table X, then one for table Y, and then later on visit ** another change for table X. */ -int sqlite3changeset_start( +SQLITE_API int sqlite3changeset_start( sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ int nChangeset, /* Size of changeset blob in bytes */ void *pChangeset /* Pointer to blob containing changeset */ @@ -9199,7 +9355,7 @@ int sqlite3changeset_start( ** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or ** SQLITE_NOMEM. */ -int sqlite3changeset_next(sqlite3_changeset_iter *pIter); +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); /* ** CAPI3REF: Obtain The Current Operation From A Changeset Iterator @@ -9227,7 +9383,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** SQLite error code is returned. The values of the output variables may not ** be trusted in this case. */ -int sqlite3changeset_op( +SQLITE_API int sqlite3changeset_op( sqlite3_changeset_iter *pIter, /* Iterator object */ const char **pzTab, /* OUT: Pointer to table name */ int *pnCol, /* OUT: Number of columns in table */ @@ -9252,7 +9408,7 @@ int sqlite3changeset_op( ** 0x01 if the corresponding column is part of the tables primary key, or ** 0x00 if it is not. ** -** If argumet pnCol is not NULL, then *pnCol is set to the number of columns +** If argument pnCol is not NULL, then *pnCol is set to the number of columns ** in the table. ** ** If this function is called when the iterator does not point to a valid @@ -9260,7 +9416,7 @@ int sqlite3changeset_op( ** SQLITE_OK is returned and the output variables populated as described ** above. */ -int sqlite3changeset_pk( +SQLITE_API int sqlite3changeset_pk( sqlite3_changeset_iter *pIter, /* Iterator object */ unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ int *pnCol /* OUT: Number of entries in output array */ @@ -9290,7 +9446,7 @@ int sqlite3changeset_pk( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_old( +SQLITE_API int sqlite3changeset_old( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ @@ -9323,7 +9479,7 @@ int sqlite3changeset_old( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_new( +SQLITE_API int sqlite3changeset_new( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ @@ -9350,7 +9506,7 @@ int sqlite3changeset_new( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_conflict( +SQLITE_API int sqlite3changeset_conflict( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: Value from conflicting row */ @@ -9366,7 +9522,7 @@ int sqlite3changeset_conflict( ** ** In all other cases this function returns SQLITE_MISUSE. */ -int sqlite3changeset_fk_conflicts( +SQLITE_API int sqlite3changeset_fk_conflicts( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int *pnOut /* OUT: Number of FK violations */ ); @@ -9399,7 +9555,7 @@ int sqlite3changeset_fk_conflicts( ** // An error has occurred ** } */ -int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); /* ** CAPI3REF: Invert A Changeset @@ -9429,7 +9585,7 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); ** WARNING/TODO: This function currently assumes that the input is a valid ** changeset. If it is not, the results are undefined. */ -int sqlite3changeset_invert( +SQLITE_API int sqlite3changeset_invert( int nIn, const void *pIn, /* Input changeset */ int *pnOut, void **ppOut /* OUT: Inverse of input */ ); @@ -9458,7 +9614,7 @@ int sqlite3changeset_invert( ** ** Refer to the sqlite3_changegroup documentation below for details. */ -int sqlite3changeset_concat( +SQLITE_API int sqlite3changeset_concat( int nA, /* Number of bytes in buffer pA */ void *pA, /* Pointer to buffer containing changeset A */ int nB, /* Number of bytes in buffer pB */ @@ -9469,12 +9625,12 @@ int sqlite3changeset_concat( /* -** Changegroup handle. +** CAPI3REF: Changegroup Handle */ typedef struct sqlite3_changegroup sqlite3_changegroup; /* -** CAPI3REF: Combine two or more changesets into a single changeset. +** CAPI3REF: Create A New Changegroup Object ** ** An sqlite3_changegroup object is used to combine two or more changesets ** (or patchsets) into a single changeset (or patchset). A single changegroup @@ -9511,6 +9667,8 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; int sqlite3changegroup_new(sqlite3_changegroup **pp); /* +** CAPI3REF: Add A Changeset To A Changegroup +** ** Add all changes within the changeset (or patchset) in buffer pData (size ** nData bytes) to the changegroup. ** @@ -9525,7 +9683,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** apply to the same row as a change already present in the changegroup if ** the two rows have the same primary key. ** -** Changes to rows that that do not already appear in the changegroup are +** Changes to rows that do not already appear in the changegroup are ** simply copied into it. Or, if both the new changeset and the changegroup ** contain changes that apply to a single row, the final contents of the ** changegroup depends on the type of each change, as follows: @@ -9586,6 +9744,8 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* +** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** ** Obtain a buffer containing a changeset (or patchset) representing the ** current contents of the changegroup. If the inputs to the changegroup ** were themselves changesets, the output is a changeset. Or, if the @@ -9614,7 +9774,7 @@ int sqlite3changegroup_output( ); /* -** Delete a changegroup object. +** CAPI3REF: Delete A Changegroup Object */ void sqlite3changegroup_delete(sqlite3_changegroup*); @@ -9642,7 +9802,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); **
      **
    • The table has the same name as the name recorded in the ** changeset, and -**
    • The table has the same number of columns as recorded in the +**
    • The table has at least as many columns as recorded in the ** changeset, and **
    • The table has primary key columns in the same position as ** recorded in the changeset. @@ -9687,7 +9847,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** If a row with matching primary key values is found, but one or more of ** the non-primary key fields contains a value different from the original ** row value stored in the changeset, the conflict-handler function is -** invoked with [SQLITE_CHANGESET_DATA] as the second argument. +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the +** database table has more columns than are recorded in the changeset, +** only the values of those non-primary key fields are compared against +** the current database contents - any trailing database table columns +** are ignored. ** ** If no row with matching primary key values is found in the database, ** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] @@ -9702,7 +9866,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** **
      INSERT Changes
      ** For each INSERT change, an attempt is made to insert the new row into -** the database. +** the database. If the changeset row contains fewer fields than the +** database table, the trailing fields are populated with their default +** values. ** ** If the attempt to insert the row fails because the database already ** contains a row with the same primary key values, the conflict handler @@ -9720,13 +9886,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** For each UPDATE change, this function checks if the target database ** contains a row with the same primary key value (or values) as the ** original row values stored in the changeset. If it does, and the values -** stored in all non-primary key columns also match the values stored in -** the changeset the row is updated within the target database. +** stored in all modified non-primary key columns also match the values +** stored in the changeset the row is updated within the target database. ** ** If a row with matching primary key values is found, but one or more of -** the non-primary key fields contains a value different from an original -** row value stored in the changeset, the conflict-handler function is -** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since +** the modified non-primary key fields contains a value different from an +** original row value stored in the changeset, the conflict-handler function +** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since ** UPDATE changes only contain values for non-primary key fields that are ** to be modified, only those fields need to match the original values to ** avoid the SQLITE_CHANGESET_DATA conflict-handler callback. @@ -9754,7 +9920,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** rolled back, restoring the target database to its original state, and an ** SQLite error code returned. */ -int sqlite3changeset_apply( +SQLITE_API int sqlite3changeset_apply( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ @@ -9955,7 +10121,7 @@ int sqlite3changeset_apply( ** parameter set to a value less than or equal to zero. Other than this, ** no guarantees are made as to the size of the chunks of data returned. */ -int sqlite3changeset_apply_strm( +SQLITE_API int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ void *pIn, /* First arg for xInput */ @@ -9970,7 +10136,7 @@ int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ); -int sqlite3changeset_concat_strm( +SQLITE_API int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, int (*xInputB)(void *pIn, void *pData, int *pnData), @@ -9978,23 +10144,23 @@ int sqlite3changeset_concat_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changeset_invert_strm( +SQLITE_API int sqlite3changeset_invert_strm( int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changeset_start_strm( +SQLITE_API int sqlite3changeset_start_strm( sqlite3_changeset_iter **pp, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); -int sqlite3session_changeset_strm( +SQLITE_API int sqlite3session_changeset_strm( sqlite3_session *pSession, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3session_patchset_strm( +SQLITE_API int sqlite3session_patchset_strm( sqlite3_session *pSession, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut @@ -10703,7 +10869,7 @@ struct fts5_api { ** Not currently enforced. */ #ifndef SQLITE_MAX_VDBE_OP -# define SQLITE_MAX_VDBE_OP 25000 +# define SQLITE_MAX_VDBE_OP 250000000 #endif /* @@ -10901,6 +11067,7 @@ struct fts5_api { # include # pragma intrinsic(_byteswap_ushort) # pragma intrinsic(_byteswap_ulong) +# pragma intrinsic(_byteswap_uint64) # pragma intrinsic(_ReadWriteBarrier) # else # include @@ -11391,9 +11558,9 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_LIMIT 129 #define TK_WHERE 130 #define TK_INTO 131 -#define TK_INTEGER 132 -#define TK_FLOAT 133 -#define TK_BLOB 134 +#define TK_FLOAT 132 +#define TK_BLOB 133 +#define TK_INTEGER 134 #define TK_VARIABLE 135 #define TK_CASE 136 #define TK_WHEN 137 @@ -11417,10 +11584,12 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_UMINUS 155 #define TK_UPLUS 156 #define TK_REGISTER 157 -#define TK_ASTERISK 158 -#define TK_SPAN 159 -#define TK_SPACE 160 -#define TK_ILLEGAL 161 +#define TK_VECTOR 158 +#define TK_SELECT_COLUMN 159 +#define TK_ASTERISK 160 +#define TK_SPAN 161 +#define TK_SPACE 162 +#define TK_ILLEGAL 163 /* The token codes above must all fit in 8 bits */ #define TKFLG_MASK 0xff @@ -11437,6 +11606,18 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #include #include +/* +** Use a macro to replace memcpy() if compiled with SQLITE_INLINE_MEMCPY. +** This allows better measurements of where memcpy() is used when running +** cachegrind. But this macro version of memcpy() is very slow so it +** should not be used in production. This is a performance measurement +** hack only. +*/ +#ifdef SQLITE_INLINE_MEMCPY +# define memcpy(D,S,N) {char*xxd=(char*)(D);const char*xxs=(const char*)(S);\ + int xxn=(N);while(xxn-->0)*(xxd++)=*(xxs++);} +#endif + /* ** If compiling for a processor that lacks floating point support, ** substitute integer for floating-point @@ -11521,9 +11702,12 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); ** pagecaches for each database connection. A positive number is the ** number of pages. A negative number N translations means that a buffer ** of -1024*N bytes is allocated and used for as many pages as it will hold. +** +** The default value of "20" was choosen to minimize the run-time of the +** speedtest1 test program with options: --shrink-memory --reprepare */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ -# define SQLITE_DEFAULT_PCACHE_INITSZ 100 +# define SQLITE_DEFAULT_PCACHE_INITSZ 20 #endif /* @@ -11698,32 +11882,35 @@ typedef INT16_TYPE LogEst; ** ** For best performance, an attempt is made to guess at the byte-order ** using C-preprocessor macros. If that is unsuccessful, or if -** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined +** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined ** at run-time. */ -#if (defined(i386) || defined(__i386__) || defined(_M_IX86) || \ +#ifndef SQLITE_BYTEORDER +# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ - defined(__arm__)) && !defined(SQLITE_RUNTIME_BYTEORDER) -# define SQLITE_BYTEORDER 1234 -# define SQLITE_BIGENDIAN 0 -# define SQLITE_LITTLEENDIAN 1 -# define SQLITE_UTF16NATIVE SQLITE_UTF16LE + defined(__arm__) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ppc__) +# define SQLITE_BYTEORDER 4321 +# else +# define SQLITE_BYTEORDER 0 +# endif #endif -#if (defined(sparc) || defined(__ppc__)) \ - && !defined(SQLITE_RUNTIME_BYTEORDER) -# define SQLITE_BYTEORDER 4321 +#if SQLITE_BYTEORDER==4321 # define SQLITE_BIGENDIAN 1 # define SQLITE_LITTLEENDIAN 0 # define SQLITE_UTF16NATIVE SQLITE_UTF16BE -#endif -#if !defined(SQLITE_BYTEORDER) +#elif SQLITE_BYTEORDER==1234 +# define SQLITE_BIGENDIAN 0 +# define SQLITE_LITTLEENDIAN 1 +# define SQLITE_UTF16NATIVE SQLITE_UTF16LE +#else # ifdef SQLITE_AMALGAMATION const int sqlite3one = 1; # else extern const int sqlite3one; # endif -# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ # define SQLITE_BIGENDIAN (*(char *)(&sqlite3one)==0) # define SQLITE_LITTLEENDIAN (*(char *)(&sqlite3one)==1) # define SQLITE_UTF16NATIVE (SQLITE_BIGENDIAN?SQLITE_UTF16BE:SQLITE_UTF16LE) @@ -11980,6 +12167,14 @@ typedef struct Walker Walker; typedef struct WhereInfo WhereInfo; typedef struct With With; +/* A VList object records a mapping between parameters/variables/wildcards +** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer +** variable number associated with that parameter. See the format description +** on the sqlite3VListAdd() routine for more information. A VList is really +** just an array of integers. +*/ +typedef int VList; + /* ** Defer sourcing vdbe.h and btree.h until after the "u8" and ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque @@ -12079,7 +12274,9 @@ SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree); +#ifndef SQLITE_OMIT_SHARED_CACHE SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock); +#endif SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int); SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); @@ -12236,9 +12433,10 @@ SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); -/* Allowed flags for the 2nd argument to sqlite3BtreeDelete() */ +/* Allowed flags for sqlite3BtreeDelete() and sqlite3BtreeInsert() */ #define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ #define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ +#define BTREE_APPEND 0x08 /* Insert is likely an append */ /* An instance of the BtreePayload object describes the content of a single ** entry in either an index or table btree. @@ -12262,28 +12460,33 @@ struct BtreePayload { const void *pKey; /* Key content for indexes. NULL for tables */ sqlite3_int64 nKey; /* Size of pKey for indexes. PRIMARY KEY for tabs */ const void *pData; /* Data for tables. NULL for indexes */ + struct Mem *aMem; /* First of nMem value in the unpacked pKey */ + u16 nMem; /* Number of aMem[] value. Might be zero */ int nData; /* Size of pData. 0 if none. */ int nZero; /* Extra zero data appended after pData,nData */ }; SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, - int bias, int seekResult); + int flags, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int *pRes); SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*); -SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*); +SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor*); -SQLITE_PRIVATE int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); +SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor*); +#ifndef SQLITE_OMIT_INCRBLOB +SQLITE_PRIVATE int sqlite3BtreePayloadChecked(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *); +#endif SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *); SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBt, int iVersion); SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); @@ -12293,6 +12496,7 @@ SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); #ifndef NDEBUG SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); #endif +SQLITE_PRIVATE int sqlite3BtreeCursorIsValidNN(BtCursor*); #ifndef SQLITE_OMIT_BTREECOUNT SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *, i64 *); @@ -12397,8 +12601,7 @@ typedef struct SubProgram SubProgram; struct VdbeOp { u8 opcode; /* What operation to perform */ signed char p4type; /* One of the P4_xxx constants for p4 */ - u8 notUsed1; - u8 p5; /* Fifth parameter is an unsigned character */ + u16 p5; /* Fifth parameter is an unsigned 16-bit integer */ int p1; /* First operand */ int p2; /* Second parameter (often the jump destination) */ int p3; /* The third parameter */ @@ -12444,7 +12647,7 @@ struct SubProgram { int nOp; /* Elements in aOp[] */ int nMem; /* Number of memory cells required */ int nCsr; /* Number of cursors required */ - int nOnce; /* Number of OP_Once instructions */ + u8 *aOnce; /* Array of OP_Once flags */ void *token; /* id that may be used to recursive triggers */ SubProgram *pNext; /* Next sub-program already visited */ }; @@ -12467,22 +12670,21 @@ typedef struct VdbeOpList VdbeOpList; #define P4_NOTUSED 0 /* The P4 parameter is not used */ #define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */ #define P4_STATIC (-2) /* Pointer to a static string */ -#define P4_COLLSEQ (-4) /* P4 is a pointer to a CollSeq structure */ -#define P4_FUNCDEF (-5) /* P4 is a pointer to a FuncDef structure */ -#define P4_KEYINFO (-6) /* P4 is a pointer to a KeyInfo structure */ -#define P4_EXPR (-7) /* P4 is a pointer to an Expr tree */ -#define P4_MEM (-8) /* P4 is a pointer to a Mem* structure */ +#define P4_COLLSEQ (-3) /* P4 is a pointer to a CollSeq structure */ +#define P4_FUNCDEF (-4) /* P4 is a pointer to a FuncDef structure */ +#define P4_KEYINFO (-5) /* P4 is a pointer to a KeyInfo structure */ +#define P4_EXPR (-6) /* P4 is a pointer to an Expr tree */ +#define P4_MEM (-7) /* P4 is a pointer to a Mem* structure */ #define P4_TRANSIENT 0 /* P4 is a pointer to a transient string */ -#define P4_VTAB (-10) /* P4 is a pointer to an sqlite3_vtab structure */ -#define P4_MPRINTF (-11) /* P4 is a string obtained from sqlite3_mprintf() */ -#define P4_REAL (-12) /* P4 is a 64-bit floating point value */ -#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */ -#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */ -#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ -#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-19) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-20) /* P4 is a pointer to a Table structure */ -#define P4_FUNCCTX (-21) /* P4 is a pointer to an sqlite3_context object */ +#define P4_VTAB (-8) /* P4 is a pointer to an sqlite3_vtab structure */ +#define P4_REAL (-9) /* P4 is a 64-bit floating point value */ +#define P4_INT64 (-10) /* P4 is a 64-bit signed integer */ +#define P4_INT32 (-11) /* P4 is a 32-bit signed integer */ +#define P4_INTARRAY (-12) /* P4 is a vector of 32-bit integers */ +#define P4_SUBPROGRAM (-13) /* P4 is a pointer to a SubProgram structure */ +#define P4_ADVANCE (-14) /* P4 is a pointer to BtreeNext() or BtreePrev() */ +#define P4_TABLE (-15) /* P4 is a pointer to a Table structure */ +#define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -12567,7 +12769,7 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Le 39 /* same as TK_LE, synopsis: IF r[P3]<=r[P1] */ #define OP_Lt 40 /* same as TK_LT, synopsis: IF r[P3]=r[P1] */ -#define OP_Last 42 +#define OP_ElseNotEq 42 /* same as TK_ESCAPE */ #define OP_BitAnd 43 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ #define OP_BitOr 44 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ #define OP_ShiftLeft 45 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<0 then r[P1]-=P3, goto P2 */ -#define OP_IfNotZero 66 /* synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 */ -#define OP_DecrJumpZero 67 /* synopsis: if (--r[P1])==0 goto P2 */ -#define OP_IncrVacuum 68 -#define OP_VNext 69 -#define OP_Init 70 /* synopsis: Start at P2 */ -#define OP_Return 71 -#define OP_EndCoroutine 72 -#define OP_HaltIfNull 73 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 74 -#define OP_Integer 75 /* synopsis: r[P2]=P1 */ -#define OP_Int64 76 /* synopsis: r[P2]=P4 */ -#define OP_String 77 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 78 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 79 /* synopsis: r[P1]=NULL */ -#define OP_Blob 80 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 81 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 82 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 83 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 84 /* synopsis: r[P2]=r[P1] */ -#define OP_IntCopy 85 /* synopsis: r[P2]=r[P1] */ -#define OP_ResultRow 86 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 87 -#define OP_Function0 88 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_Function 89 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_AddImm 90 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 91 -#define OP_Cast 92 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 93 -#define OP_Compare 94 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_Column 95 /* synopsis: r[P3]=PX */ -#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */ +#define OP_IfSmaller 55 +#define OP_SorterSort 56 +#define OP_Sort 57 +#define OP_Rewind 58 +#define OP_IdxLE 59 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGT 60 /* synopsis: key=r[P3@P4] */ +#define OP_IdxLT 61 /* synopsis: key=r[P3@P4] */ +#define OP_IdxGE 62 /* synopsis: key=r[P3@P4] */ +#define OP_RowSetRead 63 /* synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 64 /* synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 65 +#define OP_FkIfZero 66 /* synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IfPos 67 /* synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 68 /* synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 69 /* synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 70 +#define OP_VNext 71 +#define OP_Init 72 /* synopsis: Start at P2 */ +#define OP_Return 73 +#define OP_EndCoroutine 74 +#define OP_HaltIfNull 75 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 76 +#define OP_Integer 77 /* synopsis: r[P2]=P1 */ +#define OP_Int64 78 /* synopsis: r[P2]=P4 */ +#define OP_String 79 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_Null 80 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 81 /* synopsis: r[P1]=NULL */ +#define OP_Blob 82 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 83 /* synopsis: r[P2]=parameter(P1,P4) */ +#define OP_Move 84 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 85 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 86 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 87 /* synopsis: r[P2]=r[P1] */ +#define OP_ResultRow 88 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 89 +#define OP_Function0 90 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_Function 91 /* synopsis: r[P3]=func(r[P2@P5]) */ +#define OP_AddImm 92 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 93 +#define OP_Cast 94 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 95 +#define OP_Compare 96 /* synopsis: r[P1@P3] <-> r[P2@P3] */ #define OP_String8 97 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_MakeRecord 98 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 99 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 100 -#define OP_SetCookie 101 -#define OP_ReopenIdx 102 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenRead 103 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenWrite 104 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenAutoindex 105 /* synopsis: nColumn=P2 */ -#define OP_OpenEphemeral 106 /* synopsis: nColumn=P2 */ -#define OP_SorterOpen 107 -#define OP_SequenceTest 108 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ -#define OP_OpenPseudo 109 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 110 -#define OP_ColumnsUsed 111 -#define OP_Sequence 112 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NewRowid 113 /* synopsis: r[P2]=rowid */ -#define OP_Insert 114 /* synopsis: intkey=r[P3] data=r[P2] */ -#define OP_InsertInt 115 /* synopsis: intkey=P3 data=r[P2] */ -#define OP_Delete 116 -#define OP_ResetCount 117 -#define OP_SorterCompare 118 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 119 /* synopsis: r[P2]=data */ -#define OP_RowKey 120 /* synopsis: r[P2]=key */ -#define OP_RowData 121 /* synopsis: r[P2]=data */ -#define OP_Rowid 122 /* synopsis: r[P2]=rowid */ -#define OP_NullRow 123 -#define OP_SorterInsert 124 -#define OP_IdxInsert 125 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 126 /* synopsis: key=r[P2@P3] */ -#define OP_Seek 127 /* synopsis: Move P3 to P1.rowid */ -#define OP_IdxRowid 128 /* synopsis: r[P2]=rowid */ -#define OP_Destroy 129 -#define OP_Clear 130 -#define OP_ResetSorter 131 -#define OP_CreateIndex 132 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_Real 133 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_CreateTable 134 /* synopsis: r[P2]=root iDb=P1 */ -#define OP_ParseSchema 135 -#define OP_LoadAnalysis 136 -#define OP_DropTable 137 -#define OP_DropIndex 138 -#define OP_DropTrigger 139 -#define OP_IntegrityCk 140 -#define OP_RowSetAdd 141 /* synopsis: rowset(P1)=r[P2] */ -#define OP_Param 142 -#define OP_FkCounter 143 /* synopsis: fkctr[P1]+=P2 */ -#define OP_MemMax 144 /* synopsis: r[P1]=max(r[P1],r[P2]) */ -#define OP_OffsetLimit 145 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggStep0 146 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep 147 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggFinal 148 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 149 -#define OP_TableLock 150 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 151 -#define OP_VCreate 152 -#define OP_VDestroy 153 -#define OP_VOpen 154 -#define OP_VColumn 155 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VRename 156 -#define OP_Pagecount 157 -#define OP_MaxPgcnt 158 -#define OP_CursorHint 159 -#define OP_Noop 160 -#define OP_Explain 161 +#define OP_Column 98 /* synopsis: r[P3]=PX */ +#define OP_Affinity 99 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 100 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 101 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 102 +#define OP_SetCookie 103 +#define OP_ReopenIdx 104 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenRead 105 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 106 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenAutoindex 107 /* synopsis: nColumn=P2 */ +#define OP_OpenEphemeral 108 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 109 +#define OP_SequenceTest 110 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 111 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 112 +#define OP_ColumnsUsed 113 +#define OP_Sequence 114 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 115 /* synopsis: r[P2]=rowid */ +#define OP_Insert 116 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_InsertInt 117 /* synopsis: intkey=P3 data=r[P2] */ +#define OP_Delete 118 +#define OP_ResetCount 119 +#define OP_SorterCompare 120 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 121 /* synopsis: r[P2]=data */ +#define OP_RowData 122 /* synopsis: r[P2]=data */ +#define OP_Rowid 123 /* synopsis: r[P2]=rowid */ +#define OP_NullRow 124 +#define OP_SorterInsert 125 /* synopsis: key=r[P2] */ +#define OP_IdxInsert 126 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 127 /* synopsis: key=r[P2@P3] */ +#define OP_Seek 128 /* synopsis: Move P3 to P1.rowid */ +#define OP_IdxRowid 129 /* synopsis: r[P2]=rowid */ +#define OP_Destroy 130 +#define OP_Clear 131 +#define OP_Real 132 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ +#define OP_ResetSorter 133 +#define OP_CreateIndex 134 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_CreateTable 135 /* synopsis: r[P2]=root iDb=P1 */ +#define OP_SqlExec 136 +#define OP_ParseSchema 137 +#define OP_LoadAnalysis 138 +#define OP_DropTable 139 +#define OP_DropIndex 140 +#define OP_DropTrigger 141 +#define OP_IntegrityCk 142 +#define OP_RowSetAdd 143 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 144 +#define OP_FkCounter 145 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 146 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 147 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggStep0 148 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep 149 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggFinal 150 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 151 +#define OP_TableLock 152 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 153 +#define OP_VCreate 154 +#define OP_VDestroy 155 +#define OP_VOpen 156 +#define OP_VColumn 157 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 158 +#define OP_Pagecount 159 +#define OP_MaxPgcnt 160 +#define OP_CursorHint 161 +#define OP_Noop 162 +#define OP_Explain 163 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -12706,20 +12910,20 @@ typedef struct VdbeOpList VdbeOpList; /* 32 */ 0x09, 0x09, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ /* 40 */ 0x0b, 0x0b, 0x01, 0x26, 0x26, 0x26, 0x26, 0x26,\ /* 48 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x01, 0x12, 0x01,\ -/* 56 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x23, 0x0b, 0x01,\ -/* 64 */ 0x01, 0x03, 0x03, 0x03, 0x01, 0x01, 0x01, 0x02,\ -/* 72 */ 0x02, 0x08, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00,\ -/* 80 */ 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00,\ -/* 88 */ 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00,\ -/* 96 */ 0x00, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00,\ +/* 56 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x23,\ +/* 64 */ 0x0b, 0x01, 0x01, 0x03, 0x03, 0x03, 0x01, 0x01,\ +/* 72 */ 0x01, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10, 0x10,\ +/* 80 */ 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x10,\ +/* 88 */ 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00,\ +/* 96 */ 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\ /* 104 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 112 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 120 */ 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00, 0x00,\ -/* 128 */ 0x10, 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00,\ -/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x10, 0x00,\ -/* 144 */ 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 152 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\ -/* 160 */ 0x00, 0x00,} +/* 112 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,\ +/* 120 */ 0x00, 0x00, 0x00, 0x10, 0x00, 0x04, 0x04, 0x00,\ +/* 128 */ 0x00, 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10,\ +/* 136 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,\ +/* 144 */ 0x10, 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\ +/* 160 */ 0x10, 0x00, 0x00, 0x00,} /* The sqlite3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum @@ -12727,7 +12931,7 @@ typedef struct VdbeOpList VdbeOpList; ** generated this include file strives to group all JUMP opcodes ** together near the beginning of the list. */ -#define SQLITE_MX_JUMP_OPCODE 70 /* Maximum JUMP opcode */ +#define SQLITE_MX_JUMP_OPCODE 72 /* Maximum JUMP opcode */ /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ @@ -12750,8 +12954,10 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe*,int); #if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N); +SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p); #else # define sqlite3VdbeVerifyNoMallocRequired(A,B) +# define sqlite3VdbeVerifyNoResultRow(A) #endif SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp, int iLineno); SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); @@ -12759,11 +12965,12 @@ SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8); SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); -SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u8 P5); +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u16 P5); SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe*, int addr); SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); +SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); @@ -12799,7 +13006,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); -SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo *, char *, int, char **); +SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); typedef int (*RecordCompare)(int,const void*,UnpackedRecord*); SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); @@ -13004,7 +13211,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int, void(*)(DbPage*) ); -SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3*); SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); /* Functions used to configure a Pager object. */ @@ -13055,15 +13262,21 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); #ifndef SQLITE_OMIT_WAL -SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int, int*, int*); +SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager); SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); -SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3*); +# ifdef SQLITE_DIRECT_OVERFLOW_READ +SQLITE_PRIVATE int sqlite3PagerUseWal(Pager *pPager, Pgno); +# endif # ifdef SQLITE_ENABLE_SNAPSHOT SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot); SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager); # endif +#else +# define sqlite3PagerUseWal(x,y) 0 #endif #ifdef SQLITE_ENABLE_ZIPVFS @@ -13682,7 +13895,7 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); ** and the one-based values are used internally. */ #ifndef SQLITE_DEFAULT_SYNCHRONOUS -# define SQLITE_DEFAULT_SYNCHRONOUS (PAGER_SYNCHRONOUS_FULL-1) +# define SQLITE_DEFAULT_SYNCHRONOUS 2 #endif #ifndef SQLITE_DEFAULT_WAL_SYNCHRONOUS # define SQLITE_DEFAULT_WAL_SYNCHRONOUS SQLITE_DEFAULT_SYNCHRONOUS @@ -13696,7 +13909,7 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); ** databases may be attached. */ struct Db { - char *zName; /* Name of this database */ + char *zDbSName; /* Name of this database. (schema name, not filename) */ Btree *pBt; /* The B*Tree structure for this database file */ u8 safety_level; /* How aggressive at syncing data to disk */ u8 bSyncSet; /* True if "PRAGMA synchronous=N" has been run */ @@ -13887,6 +14100,8 @@ struct sqlite3 { u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ u8 mTrace; /* zero or more SQLITE_TRACE flags */ + u8 skipBtreeMutex; /* True if no shared-cache backends */ + u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ int nextPagesize; /* Pagesize after VACUUM if >0 */ u32 magic; /* Magic number for detect library misuse */ int nChange; /* Value returned by sqlite3_changes() */ @@ -14034,6 +14249,7 @@ struct sqlite3 { #define SQLITE_Vacuum 0x10000000 /* Currently in a VACUUM */ #define SQLITE_CellSizeCk 0x20000000 /* Check btree cell sizes on load */ #define SQLITE_Fts3Tokenizer 0x40000000 /* Enable fts3_tokenizer(2) */ +#define SQLITE_NoCkptOnClose 0x80000000 /* No checkpoint on close()/DETACH */ /* @@ -14059,13 +14275,8 @@ struct sqlite3 { /* ** Macros for testing whether or not optimizations are enabled or disabled. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST #define OptimizationDisabled(db, mask) (((db)->dbOptFlags&(mask))!=0) #define OptimizationEnabled(db, mask) (((db)->dbOptFlags&(mask))==0) -#else -#define OptimizationDisabled(db, mask) 0 -#define OptimizationEnabled(db, mask) 1 -#endif /* ** Return true if it OK to factor constant expressions into the initialization @@ -14156,6 +14367,7 @@ struct FuncDestructor { #define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ +#define SQLITE_FUNC_AFFINITY 0x4000 /* Built-in affinity() function */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are @@ -14332,6 +14544,7 @@ struct CollSeq { ** operator is NULL. It is added to certain comparison operators to ** prove that the operands are always NOT NULL. */ +#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */ #define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ #define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ @@ -14403,15 +14616,15 @@ struct Table { ExprList *pCheck; /* All CHECK constraints */ /* ... also used as column name list in a VIEW */ int tnum; /* Root BTree page for this table */ + u32 nTabRef; /* Number of pointers to this Table */ + u32 tabFlags; /* Mask of TF_* values */ i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */ i16 nCol; /* Number of columns in this table */ - u16 nRef; /* Number of pointers to this Table */ LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ LogEst szTabRow; /* Estimated size of each table row in bytes */ #ifdef SQLITE_ENABLE_COSTMULT LogEst costMult; /* Cost multiplier for using this table */ #endif - u8 tabFlags; /* Mask of TF_* values */ u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ #ifndef SQLITE_OMIT_ALTERTABLE int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ @@ -14435,15 +14648,17 @@ struct Table { ** the TF_OOOHidden attribute would apply in this case. Such tables require ** special handling during INSERT processing. */ -#define TF_Readonly 0x01 /* Read-only system table */ -#define TF_Ephemeral 0x02 /* An ephemeral table */ -#define TF_HasPrimaryKey 0x04 /* Table has a primary key */ -#define TF_Autoincrement 0x08 /* Integer primary key is autoincrement */ -#define TF_Virtual 0x10 /* Is a virtual table */ -#define TF_WithoutRowid 0x20 /* No rowid. PRIMARY KEY is the key */ -#define TF_NoVisibleRowid 0x40 /* No user-visible "rowid" column */ -#define TF_OOOHidden 0x80 /* Out-of-Order hidden columns */ - +#define TF_Readonly 0x0001 /* Read-only system table */ +#define TF_Ephemeral 0x0002 /* An ephemeral table */ +#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */ +#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */ +#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */ +#define TF_WithoutRowid 0x0020 /* No rowid. PRIMARY KEY is the key */ +#define TF_NoVisibleRowid 0x0040 /* No user-visible "rowid" column */ +#define TF_OOOHidden 0x0080 /* Out-of-Order hidden columns */ +#define TF_StatsUsed 0x0100 /* Query planner decisions affected by + ** Index.aiRowLogEst[] values */ +#define TF_HasNotNull 0x0200 /* Contains NOT NULL constraints */ /* ** Test to see whether or not a table is a virtual table. This is @@ -14451,7 +14666,7 @@ struct Table { ** table support is omitted from the build. */ #ifndef SQLITE_OMIT_VIRTUALTABLE -# define IsVirtual(X) (((X)->tabFlags & TF_Virtual)!=0) +# define IsVirtual(X) ((X)->nModuleArg) #else # define IsVirtual(X) 0 #endif @@ -14686,6 +14901,7 @@ struct Index { unsigned isResized:1; /* True if resizeIndexObject() has been called */ unsigned isCovering:1; /* True if this is a covering index */ unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ + unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int nSample; /* Number of elements in aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ @@ -14896,9 +15112,11 @@ struct Expr { int iTable; /* TK_COLUMN: cursor number of table holding column ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old - ** EP_Unlikely: 134217728 times likelihood */ + ** EP_Unlikely: 134217728 times likelihood + ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. - ** TK_VARIABLE: variable number (always >= 1). */ + ** TK_VARIABLE: variable number (always >= 1). + ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ u8 op2; /* TK_REGISTER: original value of Expr.op @@ -14934,6 +15152,7 @@ struct Expr { #define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ #define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ #define EP_Alias 0x400000 /* Is an alias for a result set column */ +#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ /* ** Combinations of two or more EP_* flags @@ -14993,7 +15212,7 @@ struct Expr { struct ExprList { int nExpr; /* Number of expressions on the list */ struct ExprList_item { /* For each expression in the list */ - Expr *pExpr; /* The list of expressions */ + Expr *pExpr; /* The parse tree for this expression */ char *zName; /* Token associated with this expression */ char *zSpan; /* Original text of the expression */ u8 sortOrder; /* 1 for DESC or 0 for ASC */ @@ -15158,7 +15377,7 @@ struct SrcList { #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ #define WHERE_SEEK_TABLE 0x0400 /* Do not defer seeks on main table */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ - /* 0x1000 not currently used */ +#define WHERE_SEEK_UNIQ_TABLE 0x1000 /* Do not defer seeks if unique */ /* 0x2000 not currently used */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ @@ -15379,7 +15598,7 @@ struct Select { */ struct SelectDest { u8 eDest; /* How to dispose of the results. On of SRT_* above. */ - char affSdst; /* Affinity used when eDest==SRT_Set */ + char *zAffSdst; /* Affinity used when eDest==SRT_Set */ int iSDParm; /* A parameter used by the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ @@ -15485,36 +15704,23 @@ struct Parse { u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 nColCache; /* Number of entries in aColCache[] */ - int aTempReg[8]; /* Holding area for temporary registers */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ int nTab; /* Number of previously allocated VDBE cursors */ int nMem; /* Number of memory cells used so far */ - int nSet; /* Number of sets used so far */ - int nOnce; /* Number of OP_Once instructions so far */ int nOpAlloc; /* Number of slots allocated for Vdbe.aOp[] */ int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ - int iFixedOp; /* Never back out opcodes iFixedOp-1 or earlier */ int ckBase; /* Base register of data during check constraints */ int iSelfTab; /* Table of an index whose exprs are being coded */ int iCacheLevel; /* ColCache valid when aColCache[].iLevel<=iCacheLevel */ int iCacheCnt; /* Counter used to generate aColCache[].lru values */ int nLabel; /* Number of labels used */ int *aLabel; /* Space to hold the labels */ - struct yColCache { - int iTable; /* Table cursor number */ - i16 iColumn; /* Table column number */ - u8 tempReg; /* iReg is a temp register that needs to be freed */ - int iLevel; /* Nesting level */ - int iReg; /* Reg with value of this column. 0 means none. */ - int lru; /* Least recently used entry has the smallest value */ - } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ ExprList *pConstExpr;/* Constant expressions */ Token constraintName;/* Name of the constraint currently being parsed */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ - int cookieValue[SQLITE_MAX_ATTACHED+2]; /* Values of cookies to verify */ int regRowid; /* Register holding rowid of CREATE TABLE entry */ int regRoot; /* Register holding root page number for new objects */ int nMaxArg; /* Max args passed to user function by sub-program */ @@ -15527,8 +15733,6 @@ struct Parse { TableLock *aTableLock; /* Required table locks for shared-cache mode */ #endif AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ - - /* Information used while coding trigger programs. */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ int addrCrTab; /* Address of OP_CreateTable opcode on CREATE TABLE */ @@ -15539,35 +15743,50 @@ struct Parse { u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ u8 disableTriggers; /* True to disable triggers */ + /************************************************************************** + ** Fields above must be initialized to zero. The fields that follow, + ** down to the beginning of the recursive section, do not need to be + ** initialized as they will be set before being used. The boundary is + ** determined by offsetof(Parse,aColCache). + **************************************************************************/ + + struct yColCache { + int iTable; /* Table cursor number */ + i16 iColumn; /* Table column number */ + u8 tempReg; /* iReg is a temp register that needs to be freed */ + int iLevel; /* Nesting level */ + int iReg; /* Reg with value of this column. 0 means none. */ + int lru; /* Least recently used entry has the smallest value */ + } aColCache[SQLITE_N_COLCACHE]; /* One for each column cache entry */ + int aTempReg[8]; /* Holding area for temporary registers */ + Token sNameToken; /* Token with unqualified schema object name */ + /************************************************************************ ** Above is constant between recursions. Below is reset before and after ** each recursion. The boundary between these two regions is determined - ** using offsetof(Parse,nVar) so the nVar field must be the first field - ** in the recursive region. + ** using offsetof(Parse,sLastToken) so the sLastToken field must be the + ** first field in the recursive region. ************************************************************************/ + Token sLastToken; /* The last token parsed */ ynVar nVar; /* Number of '?' variables seen in the SQL so far */ - int nzVar; /* Number of available slots in azVar[] */ u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ u8 explain; /* True if the EXPLAIN flag is found on the query */ #ifndef SQLITE_OMIT_VIRTUALTABLE u8 declareVtab; /* True if inside sqlite3_declare_vtab() */ int nVtabLock; /* Number of virtual tables to lock */ #endif - int nAlias; /* Number of aliased result set columns */ int nHeight; /* Expression tree height of current sub-select */ #ifndef SQLITE_OMIT_EXPLAIN int iSelectId; /* ID of current select for EXPLAIN output */ int iNextSelectId; /* Next available select ID for EXPLAIN output */ #endif - char **azVar; /* Pointers to names of parameters */ + VList *pVList; /* Mapping between variable names and numbers */ Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ const char *zTail; /* All SQL text past the last semicolon parsed */ Table *pNewTable; /* A table being constructed by CREATE TABLE */ Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */ const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */ - Token sNameToken; /* Token with unqualified schema object name */ - Token sLastToken; /* The last token parsed */ #ifndef SQLITE_OMIT_VIRTUALTABLE Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ @@ -15578,6 +15797,14 @@ struct Parse { With *pWithToFree; /* Free this WITH object at the end of the parse */ }; +/* +** Sizes and pointers of various parts of the Parse object. +*/ +#define PARSE_HDR_SZ offsetof(Parse,aColCache) /* Recursive part w/o aColCache*/ +#define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ +#define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ +#define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ + /* ** Return true if currently inside an sqlite3_declare_vtab() call. */ @@ -15611,13 +15838,11 @@ struct AuthContext { #define OPFLAG_NCHANGE 0x01 /* OP_Insert: Set to update db->nChange */ /* Also used in P2 (not P5) of OP_Delete */ #define OPFLAG_EPHEM 0x01 /* OP_Column: Ephemeral output is ok */ -#define OPFLAG_LASTROWID 0x02 /* Set to update db->lastRowid */ +#define OPFLAG_LASTROWID 0x20 /* Set to update db->lastRowid */ #define OPFLAG_ISUPDATE 0x04 /* This OP_Insert is an sql UPDATE */ #define OPFLAG_APPEND 0x08 /* This is likely to be an append */ #define OPFLAG_USESEEKRESULT 0x10 /* Try to avoid a seek in BtreeInsert() */ -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK #define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */ -#endif #define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ #define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ #define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ @@ -15625,7 +15850,7 @@ struct AuthContext { #define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ #define OPFLAG_P2ISREG 0x10 /* P2 to OP_Open** is a register number */ #define OPFLAG_PERMUTE 0x01 /* OP_Compare: use the permutation */ -#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete: keep cursor position */ +#define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ /* @@ -15822,10 +16047,11 @@ struct Sqlite3Config { void (*xVdbeBranch)(void*,int iSrcLine,u8 eThis,u8 eMx); /* Callback */ void *pVdbeBranchArg; /* 1st argument */ #endif -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ + int iOnceResetThreshold; /* When to reset OP_Once counters */ }; /* @@ -16025,7 +16251,7 @@ SQLITE_PRIVATE void sqlite3ScratchFree(void*); SQLITE_PRIVATE void *sqlite3PageMalloc(int); SQLITE_PRIVATE void sqlite3PageFree(void*); SQLITE_PRIVATE void sqlite3MemSetDefault(void); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE void sqlite3BenignMallocHooks(void (*)(void), void (*)(void)); #endif SQLITE_PRIVATE int sqlite3HeapNearlyFull(void); @@ -16111,6 +16337,7 @@ SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); #if defined(SQLITE_DEBUG) SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); +SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8); SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8); @@ -16135,13 +16362,14 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse*,int,int); SQLITE_PRIVATE Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); -SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*, const Token*); +SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*); SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3*,Expr*, Expr*); SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*); -SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); +SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int); SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,ExprSpan*); @@ -16150,6 +16378,9 @@ SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*); SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**); SQLITE_PRIVATE int sqlite3InitCallback(void*, int, char**, char**); SQLITE_PRIVATE void sqlite3Pragma(Parse*,Token*,Token*,Token*,int); +#ifndef SQLITE_OMIT_VIRTUALTABLE +SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3*,const char *zName); +#endif SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*); SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int); SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*); @@ -16177,9 +16408,8 @@ SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3*,const char*); -SQLITE_PRIVATE int sqlite3CodeOnce(Parse *); -#ifdef SQLITE_OMIT_BUILTIN_TEST +#ifdef SQLITE_UNTESTABLE # define sqlite3FaultSim(X) SQLITE_OK #else SQLITE_PRIVATE int sqlite3FaultSim(int); @@ -16192,7 +16422,7 @@ SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec*, u32); SQLITE_PRIVATE void sqlite3BitvecClear(Bitvec*, u32, void*); SQLITE_PRIVATE void sqlite3BitvecDestroy(Bitvec*); SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec*); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int,int*); #endif @@ -16281,7 +16511,7 @@ SQLITE_PRIVATE void sqlite3ExprCacheAffinityChange(Parse*, int, int); SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); -SQLITE_PRIVATE void sqlite3ExprCodeAtInit(Parse*, Expr*, int, u8); +SQLITE_PRIVATE int sqlite3ExprCodeAtInit(Parse*, Expr*, int); SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse*, Expr*, int); @@ -16289,6 +16519,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ #define SQLITE_ECEL_REF 0x04 /* Use ExprList.u.x.iOrderByCol */ +#define SQLITE_ECEL_OMITREF 0x08 /* Omit if ExprList.u.x.iOrderByCol */ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse*, Expr*, int, int); SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse*, Expr*, int, int); SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse*, Expr*, int, int); @@ -16300,10 +16531,11 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_ite SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); -SQLITE_PRIVATE void sqlite3Vacuum(Parse*); -SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*); +SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*); +SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int); SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*); SQLITE_PRIVATE int sqlite3ExprCompare(Expr*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int); SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int); SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); @@ -16311,7 +16543,7 @@ SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE void sqlite3PrngSaveState(void); SQLITE_PRIVATE void sqlite3PrngRestoreState(void); #endif @@ -16342,6 +16574,11 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int, int*,I SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse*,int); SQLITE_PRIVATE void sqlite3GenerateConstraintChecks(Parse*,Table*,int*,int,int,int,int, u8,u8,int,int*,int*); +#ifdef SQLITE_ENABLE_NULL_TRIM +SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe*,Table*); +#else +# define sqlite3SetMakeRecordP5(A,B) +#endif SQLITE_PRIVATE void sqlite3CompleteInsertion(Parse*,Table*,int,int,int,int*,int,int,int); SQLITE_PRIVATE int sqlite3OpenTableAndIndices(Parse*, Table*, int, u8, int, u8*, int*, int*); SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse*, int, int); @@ -16448,6 +16685,9 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); #endif +SQLITE_PRIVATE VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); +SQLITE_PRIVATE const char *sqlite3VListNumToName(VList*,int); +SQLITE_PRIVATE int sqlite3VListNameToNum(VList*,const char*,int); /* ** Routines to read and write variable-length integers. These used to @@ -16477,6 +16717,7 @@ SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*); SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int); SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2); SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); +SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int); SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr); SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); @@ -16542,7 +16783,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *); SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*); -SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *, Expr *, int, int); +SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr *, int, int); SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*); SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p); SQLITE_PRIVATE int sqlite3MatchSpanName(const char*, const char*, const char*, const char*); @@ -16597,19 +16838,29 @@ SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *); SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); +#ifndef SQLITE_OMIT_SUBQUERY +SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse*, Expr*); +#else +# define sqlite3ExprCheckIN(x,y) SQLITE_OK +#endif + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void); -SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue(Parse*,Index*,UnpackedRecord**,Expr*,u8,int,int*); +SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( + Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*); SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); SQLITE_PRIVATE void sqlite3Stat4ProbeFree(UnpackedRecord*); SQLITE_PRIVATE int sqlite3Stat4Column(sqlite3*, const void*, int, int, sqlite3_value**); +SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3*, Index*, int); #endif /* ** The interface to the LEMON-generated parser */ -SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(u64)); -SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*)); +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE void *sqlite3ParserAlloc(void*(*)(u64)); +SQLITE_PRIVATE void sqlite3ParserFree(void*, void(*)(void*)); +#endif SQLITE_PRIVATE void sqlite3Parser(void*, int, Token, Parse*); #ifdef YYTRACKMAXSTACKDEPTH SQLITE_PRIVATE int sqlite3ParserStackPeak(void*); @@ -16655,6 +16906,13 @@ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*); SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int); SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*); SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3*, Table*); +SQLITE_PRIVATE Module *sqlite3VtabCreateModule( + sqlite3*, + const char*, + const sqlite3_module*, + void*, + void(*)(void*) + ); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse*,Module*); @@ -16712,6 +16970,7 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); #define sqlite3FkDropTable(a,b,c) #define sqlite3FkOldmask(a,b) 0 #define sqlite3FkRequired(a,b,c,d) 0 + #define sqlite3FkReferences(a) 0 #endif #ifndef SQLITE_OMIT_FOREIGN_KEY SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*); @@ -16730,10 +16989,10 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex(Parse*,Table*,FKey*,Index**,int**); /* ** The interface to the code in fault.c used for identifying "benign" -** malloc failures. This is only present if SQLITE_OMIT_BUILTIN_TEST +** malloc failures. This is only present if SQLITE_UNTESTABLE ** is not defined. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE void sqlite3BeginBenignMalloc(void); SQLITE_PRIVATE void sqlite3EndBenignMalloc(void); #else @@ -16755,7 +17014,7 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void); #define IN_INDEX_NOOP_OK 0x0001 /* OK to return IN_INDEX_NOOP */ #define IN_INDEX_MEMBERSHIP 0x0002 /* IN operator used for membership test */ #define IN_INDEX_LOOP 0x0004 /* IN operator used as a loop */ -SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, int*); +SQLITE_PRIVATE int sqlite3FindInIndex(Parse *, Expr *, u32, int*, int*); SQLITE_PRIVATE int sqlite3JournalOpen(sqlite3_vfs *, const char *, sqlite3_file *, int, int); SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *); @@ -16860,6 +17119,12 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread*, void**); SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*); #endif +SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr); +SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int); +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); +SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*); + #endif /* SQLITEINT_H */ /************** End of sqliteInt.h *******************************************/ @@ -16945,16 +17210,13 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { ** ** (x & ~(map[x]&0x20)) ** -** Standard function tolower() is implemented using the sqlite3UpperToLower[] +** The equivalent of tolower() is implemented using the sqlite3UpperToLower[] ** array. tolower() is used more often than toupper() by SQLite. ** -** Bit 0x40 is set if the character non-alphanumeric and can be used in an +** Bit 0x40 is set if the character is non-alphanumeric and can be used in an ** SQLite identifier. Identifiers are alphanumerics, "_", "$", and any ** non-ASCII UTF character. Hence the test for whether or not a character is ** part of an identifier is 0x46. -** -** SQLite's versions are identical to the standard versions assuming a -** locale of "C". They are implemented as macros in sqliteInt.h. */ #ifdef SQLITE_ASCII SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { @@ -17027,7 +17289,7 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { #endif /* Statement journals spill to disk when their size exceeds the following -** threashold (in bytes). 0 means that statement journals are created and +** threshold (in bytes). 0 means that statement journals are created and ** written to disk immediately (the default behavior for SQLite versions ** before 3.12.0). -1 means always keep the entire statement journal in ** memory. (The statement journal is also always held entirely in memory @@ -17038,6 +17300,19 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { # define SQLITE_STMTJRNL_SPILL (64*1024) #endif +/* +** The default lookaside-configuration, the format "SZ,N". SZ is the +** number of bytes in each lookaside slot (should be a multiple of 8) +** and N is the number of slots. The lookaside-configuration can be +** changed as start-time using sqlite3_config(SQLITE_CONFIG_LOOKASIDE) +** or at run-time for an individual database connection using +** sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE); +*/ +#ifndef SQLITE_DEFAULT_LOOKASIDE +# define SQLITE_DEFAULT_LOOKASIDE 1200,100 +#endif + + /* ** The following singleton contains the global configuration for ** the SQLite library. @@ -17050,8 +17325,7 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ - 128, /* szLookaside */ - 500, /* nLookaside */ + SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ SQLITE_STMTJRNL_SPILL, /* nStmtSpill */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ @@ -17088,10 +17362,11 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* xVdbeBranch */ 0, /* pVbeBranchArg */ #endif -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ #endif - 0 /* bLocaltimeFault */ + 0, /* bLocaltimeFault */ + 0x7ffffffe /* iOnceResetThreshold */ }; /* @@ -17114,7 +17389,7 @@ SQLITE_PRIVATE const Token sqlite3IntTokens[] = { ** The value of the "pending" byte must be 0x40000000 (1 byte past the ** 1-gibabyte boundary) in a compatible database. SQLite never uses ** the database page that contains the pending byte. It never attempts -** to read or write that page. The pending byte page is set assign +** to read or write that page. The pending byte page is set aside ** for use by the VFS layers as space for managing file locks. ** ** During testing, it is often desirable to move the pending byte to @@ -17207,7 +17482,7 @@ static const char * const azCompileOpt[] = { #if SQLITE_COVERAGE_TEST "COVERAGE_TEST", #endif -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG "DEBUG", #endif #if SQLITE_DEFAULT_LOCKING_MODE @@ -17216,6 +17491,15 @@ static const char * const azCompileOpt[] = { #if defined(SQLITE_DEFAULT_MMAP_SIZE) && !defined(SQLITE_DEFAULT_MMAP_SIZE_xc) "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), #endif +#if SQLITE_DEFAULT_SYNCHRONOUS + "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), +#endif +#if SQLITE_DEFAULT_WAL_SYNCHRONOUS + "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), +#endif +#if SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif #if SQLITE_DISABLE_DIRSYNC "DISABLE_DIRSYNC", #endif @@ -17302,6 +17586,9 @@ static const char * const azCompileOpt[] = { #if SQLITE_ENABLE_UPDATE_DELETE_LIMIT "ENABLE_UPDATE_DELETE_LIMIT", #endif +#if defined(SQLITE_ENABLE_URI_00_ERROR) + "ENABLE_URI_00_ERROR", +#endif #if SQLITE_HAS_CODEC "HAS_CODEC", #endif @@ -17377,9 +17664,6 @@ static const char * const azCompileOpt[] = { #if SQLITE_OMIT_BTREECOUNT "OMIT_BTREECOUNT", #endif -#if SQLITE_OMIT_BUILTIN_TEST - "OMIT_BUILTIN_TEST", -#endif #if SQLITE_OMIT_CAST "OMIT_CAST", #endif @@ -17542,6 +17826,9 @@ static const char * const azCompileOpt[] = { #if defined(SQLITE_THREADSAFE) "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), #endif +#if SQLITE_UNTESTABLE + "UNTESTABLE" +#endif #if SQLITE_USE_ALLOCA "USE_ALLOCA", #endif @@ -17674,9 +17961,6 @@ typedef unsigned Bool; /* Opaque type used by code in vdbesort.c */ typedef struct VdbeSorter VdbeSorter; -/* Opaque type used by the explainer */ -typedef struct Explain Explain; - /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; @@ -17698,59 +17982,68 @@ typedef struct AuxData AuxData; */ typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { - u8 eCurType; /* One of the CURTYPE_* values above */ - i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ - u8 nullRow; /* True if pointing to a row with no data */ - u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ - u8 isTable; /* True for rowid tables. False for indexes */ + u8 eCurType; /* One of the CURTYPE_* values above */ + i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + u8 nullRow; /* True if pointing to a row with no data */ + u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ + u8 isTable; /* True for rowid tables. False for indexes */ #ifdef SQLITE_DEBUG - u8 seekOp; /* Most recent seek operation on this cursor */ - u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ -#endif - Bool isEphemeral:1; /* True for an ephemeral table */ - Bool useRandomRowid:1;/* Generate new record numbers semi-randomly */ - Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ - Pgno pgnoRoot; /* Root page of the open btree cursor */ - i16 nField; /* Number of fields in the header */ - u16 nHdrParsed; /* Number of header fields parsed so far */ + u8 seekOp; /* Most recent seek operation on this cursor */ + u8 wrFlag; /* The wrFlag argument to sqlite3BtreeCursor() */ +#endif + Bool isEphemeral:1; /* True for an ephemeral table */ + Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ + Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ + Btree *pBtx; /* Separate file holding temporary table */ + i64 seqCount; /* Sequence counter */ + int *aAltMap; /* Mapping from table to index column numbers */ + + /* Cached OP_Column parse information is only valid if cacheStatus matches + ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of + ** CACHE_STALE (0) and so setting cacheStatus=CACHE_STALE guarantees that + ** the cache is out of date. */ + u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ + int seekResult; /* Result of previous sqlite3BtreeMoveto() or 0 + ** if there have been no prior seeks on the cursor. */ + /* NB: seekResult does not distinguish between "no seeks have ever occurred + ** on this cursor" and "the most recent seek was an exact match". */ + + /* When a new VdbeCursor is allocated, only the fields above are zeroed. + ** The fields that follow are uninitialized, and must be individually + ** initialized prior to first use. */ + VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ union { BtCursor *pCursor; /* CURTYPE_BTREE. Btree cursor */ sqlite3_vtab_cursor *pVCur; /* CURTYPE_VTAB. Vtab cursor */ int pseudoTableReg; /* CURTYPE_PSEUDO. Reg holding content. */ VdbeSorter *pSorter; /* CURTYPE_SORTER. Sorter object */ } uc; - Btree *pBt; /* Separate file holding temporary table */ - KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ - int seekResult; /* Result of previous sqlite3BtreeMoveto() */ - i64 seqCount; /* Sequence counter */ - i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ - VdbeCursor *pAltCursor; /* Associated index cursor from which to read */ - int *aAltMap; /* Mapping from table to index column numbers */ + KeyInfo *pKeyInfo; /* Info about index keys needed by index cursors */ + u32 iHdrOffset; /* Offset to next unparsed byte of the header */ + Pgno pgnoRoot; /* Root page of the open btree cursor */ + i16 nField; /* Number of fields in the header */ + u16 nHdrParsed; /* Number of header fields parsed so far */ + i64 movetoTarget; /* Argument to the deferred sqlite3BtreeMoveto() */ + u32 *aOffset; /* Pointer to aType[nField] */ + const u8 *aRow; /* Data for the current row, if all on one page */ + u32 payloadSize; /* Total number of bytes in the record */ + u32 szRow; /* Byte available in aRow */ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK - u64 maskUsed; /* Mask of columns used by this cursor */ + u64 maskUsed; /* Mask of columns used by this cursor */ #endif - /* Cached information about the header for the data record that the - ** cursor is currently pointing to. Only valid if cacheStatus matches - ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of - ** CACHE_STALE and so setting cacheStatus=CACHE_STALE guarantees that - ** the cache is out of date. - ** - ** aRow might point to (ephemeral) data for the current row, or it might - ** be NULL. - */ - u32 cacheStatus; /* Cache is valid if this matches Vdbe.cacheCtr */ - u32 payloadSize; /* Total number of bytes in the record */ - u32 szRow; /* Byte available in aRow */ - u32 iHdrOffset; /* Offset to next unparsed byte of the header */ - const u8 *aRow; /* Data for the current row, if all on one page */ - u32 *aOffset; /* Pointer to aType[nField] */ - u32 aType[1]; /* Type values for all entries in the record */ /* 2*nField extra array elements allocated for aType[], beyond the one ** static element declared in the structure. nField total array slots for ** aType[] and nField+1 array slots for aOffset[] */ + u32 aType[1]; /* Type values record decode. MUST BE LAST */ }; + +/* +** A value for VdbeCursor.cacheStatus that means the cache is always invalid. +*/ +#define CACHE_STALE 0 + /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as @@ -17779,8 +18072,8 @@ struct VdbeFrame { Op *aOp; /* Program instructions for parent frame */ i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ - u8 *aOnceFlag; /* Array of OP_Once flags for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ + u8 *aOnce; /* Bitmask used by OP_Once */ void *token; /* Copy of SubProgram.token */ i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */ AuxData *pAuxData; /* Linked list of auxdata allocations */ @@ -17788,7 +18081,6 @@ struct VdbeFrame { int pc; /* Program Counter in parent (calling) frame */ int nOp; /* Size of aOp array */ int nMem; /* Number of entries in aMem */ - int nOnceFlag; /* Number of entries in aOnceFlag */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ int nChange; /* Statement changes (Vdbe.nChange) */ @@ -17797,11 +18089,6 @@ struct VdbeFrame { #define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))]) -/* -** A value for VdbeCursor.cacheValid that means the cache is always invalid. -*/ -#define CACHE_STALE 0 - /* ** Internally, the vdbe manipulates nearly all SQL values as Mem ** structures. Each Mem struct may cache multiple representations (string, @@ -17942,18 +18229,6 @@ struct sqlite3_context { sqlite3_value *argv[1]; /* Argument set */ }; -/* -** An Explain object accumulates indented output which is helpful -** in describing recursive data structures. -*/ -struct Explain { - Vdbe *pVdbe; /* Attach the explanation to this Vdbe */ - StrAccum str; /* The string being accumulated */ - int nIndent; /* Number of elements in aIndent */ - u16 aIndent[100]; /* Levels of indentation */ - char zBase[100]; /* Initial space */ -}; - /* A bitfield type for use inside of structures. Always follow with :N where ** N is the number of bits. */ @@ -17978,34 +18253,46 @@ struct ScanStatus { */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ + Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ + Parse *pParse; /* Parsing context used to create this Vdbe */ + ynVar nVar; /* Number of entries in aVar[] */ + u32 magic; /* Magic number for sanity checking */ + int nMem; /* Number of memory locations currently allocated */ + int nCursor; /* Number of slots in apCsr[] */ + u32 cacheCtr; /* VdbeCursor row cache generation counter */ + int pc; /* The program counter */ + int rc; /* Value to return */ + int nChange; /* Number of db changes made since last reset */ + int iStatement; /* Statement number (or 0 if has not opened stmt) */ + i64 iCurrentTime; /* Value of julianday('now') for this statement */ + i64 nFkConstraint; /* Number of imm. FK constraints this VM */ + i64 nStmtDefCons; /* Number of def. constraints when stmt started */ + i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ + + /* When allocating a new Vdbe object, all of the fields below should be + ** initialized to zero or NULL */ + Op *aOp; /* Space to hold the virtual machine's program */ Mem *aMem; /* The memory locations */ Mem **apArg; /* Arguments to currently executing user function */ Mem *aColName; /* Column names to return */ Mem *pResultSet; /* Pointer to an array of results */ - Parse *pParse; /* Parsing context used to create this Vdbe */ - int nMem; /* Number of memory locations currently allocated */ - int nOp; /* Number of instructions in the program */ - int nCursor; /* Number of slots in apCsr[] */ - u32 magic; /* Magic number for sanity checking */ char *zErrMsg; /* Error message written here */ - Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ VdbeCursor **apCsr; /* One element of this array for each open cursor */ Mem *aVar; /* Values for the OP_Variable opcode. */ - char **azVar; /* Name of variables */ - ynVar nVar; /* Number of entries in aVar[] */ - ynVar nzVar; /* Number of entries in azVar[] */ - u32 cacheCtr; /* VdbeCursor row cache generation counter */ - int pc; /* The program counter */ - int rc; /* Value to return */ + VList *pVList; /* Name of variables */ +#ifndef SQLITE_OMIT_TRACE + i64 startTime; /* Time when query started - used for profiling */ +#endif + int nOp; /* Number of instructions in the program */ #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ u8 errorAction; /* Recovery action to do in case of an error */ + u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft expired:1; /* True if the VM needs to be recompiled */ bft doingRerun:1; /* True if rerunning after an auto-reprepare */ - u8 minWriteFileFormat; /* Minimum file format for writable database files */ bft explain:2; /* True if EXPLAIN present on SQL command */ bft changeCntOn:1; /* True to update the change-counter */ bft runOnlyOnce:1; /* Automatically expire on reset */ @@ -18013,18 +18300,9 @@ struct Vdbe { bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ bft isPrepareV2:1; /* True if prepared with prepare_v2() */ - int nChange; /* Number of db changes made since last reset */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - int iStatement; /* Statement number (or 0 if has not opened stmt) */ u32 aCounter[5]; /* Counters used by sqlite3_stmt_status() */ -#ifndef SQLITE_OMIT_TRACE - i64 startTime; /* Time when query started - used for profiling */ -#endif - i64 iCurrentTime; /* Value of julianday('now') for this statement */ - i64 nFkConstraint; /* Number of imm. FK constraints this VM */ - i64 nStmtDefCons; /* Number of def. constraints when stmt started */ - i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ VdbeFrame *pFrame; /* Parent frame */ @@ -18032,8 +18310,6 @@ struct Vdbe { int nFrame; /* Number of frames in pFrame list */ u32 expmask; /* Binding to these vars invalidates VM */ SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ - int nOnceFlag; /* Size of array aOnceFlag[] */ - u8 *aOnceFlag; /* Flags for OP_Once */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS i64 *anExec; /* Number of times each op has been executed */ @@ -18045,10 +18321,11 @@ struct Vdbe { /* ** The following are allowed values for Vdbe.magic */ -#define VDBE_MAGIC_INIT 0x26bceaa5 /* Building a VDBE program */ -#define VDBE_MAGIC_RUN 0xbdf20da3 /* VDBE is ready to execute */ -#define VDBE_MAGIC_HALT 0x519c2973 /* VDBE has completed execution */ -#define VDBE_MAGIC_DEAD 0xb606c3c8 /* The VDBE has been deallocated */ +#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */ +#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */ +#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */ +#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */ +#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */ /* ** Structure used to store the context required by the @@ -18065,8 +18342,9 @@ struct PreUpdate { int iNewReg; /* Register for new.* values */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ - int iPKey; /* If not negative index of IPK column */ Mem *aNew; /* Array of new.* values */ + Table *pTab; /* Schema object being upated */ + Index *pPk; /* PK index if pTab is WITHOUT ROWID */ }; /* @@ -18119,7 +18397,7 @@ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*); SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem*,u8,u8); -SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,int,Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p); SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); SQLITE_PRIVATE const char *sqlite3OpcodeName(int); @@ -18588,16 +18866,18 @@ struct tm *__cdecl localtime(const time_t *); */ typedef struct DateTime DateTime; struct DateTime { - sqlite3_int64 iJD; /* The julian day number times 86400000 */ - int Y, M, D; /* Year, month, and day */ - int h, m; /* Hour and minutes */ - int tz; /* Timezone offset in minutes */ - double s; /* Seconds */ - char validYMD; /* True (1) if Y,M,D are valid */ - char validHMS; /* True (1) if h,m,s are valid */ - char validJD; /* True (1) if iJD is valid */ - char validTZ; /* True (1) if tz is valid */ - char tzSet; /* Timezone was set explicitly */ + sqlite3_int64 iJD; /* The julian day number times 86400000 */ + int Y, M, D; /* Year, month, and day */ + int h, m; /* Hour and minutes */ + int tz; /* Timezone offset in minutes */ + double s; /* Seconds */ + char validJD; /* True (1) if iJD is valid */ + char rawS; /* Raw numeric value stored in s */ + char validYMD; /* True (1) if Y,M,D are valid */ + char validHMS; /* True (1) if h,m,s are valid */ + char validTZ; /* True (1) if tz is valid */ + char tzSet; /* Timezone was set explicitly */ + char isError; /* An overflow has occurred */ }; @@ -18745,6 +19025,7 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ s = 0; } p->validJD = 0; + p->rawS = 0; p->validHMS = 1; p->h = h; p->m = m; @@ -18754,6 +19035,14 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ return 0; } +/* +** Put the DateTime object into its error state. +*/ +static void datetimeError(DateTime *p){ + memset(p, 0, sizeof(*p)); + p->isError = 1; +} + /* ** Convert from YYYY-MM-DD HH:MM:SS to julian day. We always assume ** that the YYYY-MM-DD is according to the Gregorian calendar. @@ -18773,6 +19062,10 @@ static void computeJD(DateTime *p){ M = 1; D = 1; } + if( Y<-4713 || Y>9999 || p->rawS ){ + datetimeError(p); + return; + } if( M<=2 ){ Y--; M += 12; @@ -18853,6 +19146,21 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ } } +/* +** Input "r" is a numeric quantity which might be a julian day number, +** or the number of seconds since 1970. If the value if r is within +** range of a julian day number, install it as such and set validJD. +** If the value is a valid unix timestamp, put it in p->s and set p->rawS. +*/ +static void setRawDateNumber(DateTime *p, double r){ + p->s = r; + p->rawS = 1; + if( r>=0.0 && r<5373484.5 ){ + p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); + p->validJD = 1; + } +} + /* ** Attempt to parse the given string into a julian day number. Return ** the number of errors. @@ -18882,13 +19190,30 @@ static int parseDateOrTime( }else if( sqlite3StrICmp(zDate,"now")==0){ return setDateTimeToCurrent(context, p); }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ - p->iJD = (sqlite3_int64)(r*86400000.0 + 0.5); - p->validJD = 1; + setRawDateNumber(p, r); return 0; } return 1; } +/* The julian day number for 9999-12-31 23:59:59.999 is 5373484.4999999. +** Multiplying this by 86400000 gives 464269060799999 as the maximum value +** for DateTime.iJD. +** +** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with +** such a large integer literal, so we have to encode it. +*/ +#define INT_464269060799999 ((((i64)0x1a640)<<32)|0x1072fdff) + +/* +** Return TRUE if the given julian day number is within range. +** +** The input is the JulianDay times 86400000. +*/ +static int validJulianDay(sqlite3_int64 iJD){ + return iJD>=0 && iJD<=INT_464269060799999; +} + /* ** Compute the Year, Month, and Day from the julian day number. */ @@ -18900,6 +19225,7 @@ static void computeYMD(DateTime *p){ p->M = 1; p->D = 1; }else{ + assert( validJulianDay(p->iJD) ); Z = (int)((p->iJD + 43200000)/86400000); A = (int)((Z - 1867216.25)/36524.25); A = Z + 1 + A - (A/4); @@ -18930,6 +19256,7 @@ static void computeHMS(DateTime *p){ s -= p->h*3600; p->m = s/60; p->s += s - p->m*60; + p->rawS = 0; p->validHMS = 1; } @@ -18991,14 +19318,14 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #endif sqlite3_mutex_enter(mutex); pX = localtime(t); -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0; #endif if( pX ) *pTm = *pX; sqlite3_mutex_leave(mutex); rc = pX==0; #else -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; #endif #if HAVE_LOCALTIME_R @@ -19069,13 +19396,38 @@ static sqlite3_int64 localtimeOffset( y.validYMD = 1; y.validHMS = 1; y.validJD = 0; + y.rawS = 0; y.validTZ = 0; + y.isError = 0; computeJD(&y); *pRc = SQLITE_OK; return y.iJD - x.iJD; } #endif /* SQLITE_OMIT_LOCALTIME */ +/* +** The following table defines various date transformations of the form +** +** 'NNN days' +** +** Where NNN is an arbitrary floating-point number and "days" can be one +** of several units of time. +*/ +static const struct { + u8 eType; /* Transformation type code */ + u8 nName; /* Length of th name */ + char *zName; /* Name of the transformation */ + double rLimit; /* Maximum NNN value for this transform */ + double rXform; /* Constant used for this transform */ +} aXformType[] = { + { 0, 6, "second", 464269060800.0, 86400000.0/(24.0*60.0*60.0) }, + { 0, 6, "minute", 7737817680.0, 86400000.0/(24.0*60.0) }, + { 0, 4, "hour", 128963628.0, 86400000.0/24.0 }, + { 0, 3, "day", 5373485.0, 86400000.0 }, + { 1, 5, "month", 176546.0, 30.0*86400000.0 }, + { 2, 4, "year", 14713.0, 365.0*86400000.0 }, +}; + /* ** Process a modifier to a date-time stamp. The modifiers are ** as follows: @@ -19100,17 +19452,15 @@ static sqlite3_int64 localtimeOffset( ** to context pCtx. If the error is an unrecognized modifier, no error is ** written to pCtx. */ -static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ +static int parseModifier( + sqlite3_context *pCtx, /* Function context */ + const char *z, /* The text of the modifier */ + int n, /* Length of zMod in bytes */ + DateTime *p /* The date/time value to be modified */ +){ int rc = 1; - int n; double r; - char *z, zBuf[30]; - z = zBuf; - for(n=0; niJD += localtimeOffset(p, pCtx, &rc); clearYMD_HMS_TZ(p); @@ -19130,16 +19480,21 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ /* ** unixepoch ** - ** Treat the current value of p->iJD as the number of + ** Treat the current value of p->s as the number of ** seconds since 1970. Convert to a real julian day number. */ - if( strcmp(z, "unixepoch")==0 && p->validJD ){ - p->iJD = (p->iJD + 43200)/86400 + 21086676*(i64)10000000; - clearYMD_HMS_TZ(p); - rc = 0; + if( sqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){ + r = p->s*1000.0 + 210866760000000.0; + if( r>=0.0 && r<464269060800000.0 ){ + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)r; + p->validJD = 1; + p->rawS = 0; + rc = 0; + } } #ifndef SQLITE_OMIT_LOCALTIME - else if( strcmp(z, "utc")==0 ){ + else if( sqlite3_stricmp(z, "utc")==0 ){ if( p->tzSet==0 ){ sqlite3_int64 c1; computeJD(p); @@ -19165,7 +19520,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ ** weekday N where 0==Sunday, 1==Monday, and so forth. If the ** date is already on the appropriate weekday, this is a no-op. */ - if( strncmp(z, "weekday ", 8)==0 + if( sqlite3_strnicmp(z, "weekday ", 8)==0 && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8) && (n=(int)r)==r && n>=0 && r<7 ){ sqlite3_int64 Z; @@ -19188,23 +19543,24 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ ** Move the date backwards to the beginning of the current day, ** or month or year. */ - if( strncmp(z, "start of ", 9)!=0 ) break; + if( sqlite3_strnicmp(z, "start of ", 9)!=0 ) break; + if( !p->validJD && !p->validYMD && !p->validHMS ) break; z += 9; computeYMD(p); p->validHMS = 1; p->h = p->m = 0; p->s = 0.0; + p->rawS = 0; p->validTZ = 0; p->validJD = 0; - if( strcmp(z,"month")==0 ){ + if( sqlite3_stricmp(z,"month")==0 ){ p->D = 1; rc = 0; - }else if( strcmp(z,"year")==0 ){ - computeYMD(p); + }else if( sqlite3_stricmp(z,"year")==0 ){ p->M = 1; p->D = 1; rc = 0; - }else if( strcmp(z,"day")==0 ){ + }else if( sqlite3_stricmp(z,"day")==0 ){ rc = 0; } break; @@ -19222,6 +19578,7 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ case '8': case '9': { double rRounder; + int i; for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){ rc = 1; @@ -19250,46 +19607,48 @@ static int parseModifier(sqlite3_context *pCtx, const char *zMod, DateTime *p){ rc = 0; break; } + + /* If control reaches this point, it means the transformation is + ** one of the forms like "+NNN days". */ z += n; while( sqlite3Isspace(*z) ) z++; n = sqlite3Strlen30(z); if( n>10 || n<3 ) break; - if( z[n-1]=='s' ){ z[n-1] = 0; n--; } + if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); - rc = 0; + rc = 1; rRounder = r<0 ? -0.5 : +0.5; - if( n==3 && strcmp(z,"day")==0 ){ - p->iJD += (sqlite3_int64)(r*86400000.0 + rRounder); - }else if( n==4 && strcmp(z,"hour")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/24.0) + rRounder); - }else if( n==6 && strcmp(z,"minute")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0)) + rRounder); - }else if( n==6 && strcmp(z,"second")==0 ){ - p->iJD += (sqlite3_int64)(r*(86400000.0/(24.0*60.0*60.0)) + rRounder); - }else if( n==5 && strcmp(z,"month")==0 ){ - int x, y; - computeYMD_HMS(p); - p->M += (int)r; - x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; - p->Y += x; - p->M -= x*12; - p->validJD = 0; - computeJD(p); - y = (int)r; - if( y!=r ){ - p->iJD += (sqlite3_int64)((r - y)*30.0*86400000.0 + rRounder); - } - }else if( n==4 && strcmp(z,"year")==0 ){ - int y = (int)r; - computeYMD_HMS(p); - p->Y += y; - p->validJD = 0; - computeJD(p); - if( y!=r ){ - p->iJD += (sqlite3_int64)((r - y)*365.0*86400000.0 + rRounder); + for(i=0; i-aXformType[i].rLimit && rM += (int)r; + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + p->validJD = 0; + r -= (int)r; + break; + } + case 2: { /* Special processing to add years */ + int y = (int)r; + computeYMD_HMS(p); + p->Y += y; + p->validJD = 0; + r -= (int)r; + break; + } + } + computeJD(p); + p->iJD += (sqlite3_int64)(r*aXformType[i].rXform + rRounder); + rc = 0; + break; } - }else{ - rc = 1; } clearYMD_HMS_TZ(p); break; @@ -19316,7 +19675,7 @@ static int isDate( sqlite3_value **argv, DateTime *p ){ - int i; + int i, n; const unsigned char *z; int eType; memset(p, 0, sizeof(*p)); @@ -19325,8 +19684,7 @@ static int isDate( } if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT || eType==SQLITE_INTEGER ){ - p->iJD = (sqlite3_int64)(sqlite3_value_double(argv[0])*86400000.0 + 0.5); - p->validJD = 1; + setRawDateNumber(p, sqlite3_value_double(argv[0])); }else{ z = sqlite3_value_text(argv[0]); if( !z || parseDateOrTime(context, (char*)z, p) ){ @@ -19335,8 +19693,11 @@ static int isDate( } for(i=1; iisError || !validJulianDay(p->iJD) ) return 1; return 0; } @@ -20134,7 +20495,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ /* #include "sqliteInt.h" */ -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** Global variables. @@ -20192,7 +20553,7 @@ SQLITE_PRIVATE void sqlite3EndBenignMalloc(void){ } } -#endif /* #ifndef SQLITE_OMIT_BUILTIN_TEST */ +#endif /* #ifndef SQLITE_UNTESTABLE */ /************** End of fault.c ***********************************************/ /************** Begin file mem0.c ********************************************/ @@ -20317,7 +20678,9 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){ */ #include #include +#ifdef SQLITE_MIGHT_BE_SINGLE_CORE #include +#endif /* SQLITE_MIGHT_BE_SINGLE_CORE */ static malloc_zone_t* _sqliteZone_; #define SQLITE_MALLOC(x) malloc_zone_malloc(_sqliteZone_, (x)) #define SQLITE_FREE(x) malloc_zone_free(_sqliteZone_, (x)); @@ -20385,7 +20748,9 @@ static malloc_zone_t* _sqliteZone_; */ static void *sqlite3MemMalloc(int nByte){ #ifdef SQLITE_MALLOCSIZE - void *p = SQLITE_MALLOC( nByte ); + void *p; + testcase( ROUND8(nByte)==nByte ); + p = SQLITE_MALLOC( nByte ); if( p==0 ){ testcase( sqlite3GlobalConfig.xLog!=0 ); sqlite3_log(SQLITE_NOMEM, "failed to allocate %u bytes of memory", nByte); @@ -20394,7 +20759,7 @@ static void *sqlite3MemMalloc(int nByte){ #else sqlite3_int64 *p; assert( nByte>0 ); - nByte = ROUND8(nByte); + testcase( ROUND8(nByte)!=nByte ); p = SQLITE_MALLOC( nByte+8 ); if( p ){ p[0] = nByte; @@ -20508,19 +20873,10 @@ static int sqlite3MemInit(void *NotUsed){ }else{ /* only 1 core, use our own zone to contention over global locks, ** e.g. we have our own dedicated locks */ - bool success; - malloc_zone_t* newzone = malloc_create_zone(4096, 0); - malloc_set_zone_name(newzone, "Sqlite_Heap"); - do{ - success = OSAtomicCompareAndSwapPtrBarrier(NULL, newzone, - (void * volatile *)&_sqliteZone_); - }while(!_sqliteZone_); - if( !success ){ - /* somebody registered a zone first */ - malloc_destroy_zone(newzone); - } + _sqliteZone_ = malloc_create_zone(4096, 0); + malloc_set_zone_name(_sqliteZone_, "Sqlite_Heap"); } -#endif +#endif /* defined(__APPLE__) && !defined(SQLITE_WITHOUT_ZONEMALLOC) */ UNUSED_PARAMETER(NotUsed); return SQLITE_OK; } @@ -23516,8 +23872,7 @@ SQLITE_PRIVATE void sqlite3MemoryBarrier(void){ SQLITE_MEMORY_BARRIER; #elif defined(__GNUC__) __sync_synchronize(); -#elif !defined(SQLITE_DISABLE_INTRINSIC) && \ - defined(_MSC_VER) && _MSC_VER>=1300 +#elif MSVC_VERSION>=1300 _ReadWriteBarrier(); #elif defined(MemoryBarrier) MemoryBarrier(); @@ -23728,8 +24083,8 @@ static void winMutexEnter(sqlite3_mutex *p){ p->owner = tid; p->nRef++; if( p->trace ){ - OSTRACE(("ENTER-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n", - tid, p, p->trace, p->nRef)); + OSTRACE(("ENTER-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n", + tid, p->id, p, p->trace, p->nRef)); } #endif } @@ -23771,8 +24126,8 @@ static int winMutexTry(sqlite3_mutex *p){ #endif #ifdef SQLITE_DEBUG if( p->trace ){ - OSTRACE(("TRY-MUTEX tid=%lu, mutex=%p (%d), owner=%lu, nRef=%d, rc=%s\n", - tid, p, p->trace, p->owner, p->nRef, sqlite3ErrName(rc))); + OSTRACE(("TRY-MUTEX tid=%lu, mutex(%d)=%p (%d), owner=%lu, nRef=%d, rc=%s\n", + tid, p->id, p, p->trace, p->owner, p->nRef, sqlite3ErrName(rc))); } #endif return rc; @@ -23800,8 +24155,8 @@ static void winMutexLeave(sqlite3_mutex *p){ LeaveCriticalSection(&p->mutex); #ifdef SQLITE_DEBUG if( p->trace ){ - OSTRACE(("LEAVE-MUTEX tid=%lu, mutex=%p (%d), nRef=%d\n", - tid, p, p->trace, p->nRef)); + OSTRACE(("LEAVE-MUTEX tid=%lu, mutex(%d)=%p (%d), nRef=%d\n", + tid, p->id, p, p->trace, p->nRef)); } #endif } @@ -24049,11 +24404,26 @@ static void sqlite3MallocAlarm(int nByte){ ** Do a memory allocation with statistics and alarms. Assume the ** lock is already held. */ -static int mallocWithAlarm(int n, void **pp){ - int nFull; +static void mallocWithAlarm(int n, void **pp){ void *p; + int nFull; assert( sqlite3_mutex_held(mem0.mutex) ); + assert( n>0 ); + + /* In Firefox (circa 2017-02-08), xRoundup() is remapped to an internal + ** implementation of malloc_good_size(), which must be called in debug + ** mode and specifically when the DMD "Dark Matter Detector" is enabled + ** or else a crash results. Hence, do not attempt to optimize out the + ** following xRoundup() call. */ nFull = sqlite3GlobalConfig.m.xRoundup(n); + +#ifdef SQLITE_MAX_MEMORY + if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)+nFull>SQLITE_MAX_MEMORY ){ + *pp = 0; + return; + } +#endif + sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n); if( mem0.alarmThreshold>0 ){ sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); @@ -24077,7 +24447,6 @@ static int mallocWithAlarm(int n, void **pp){ sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1); } *pp = p; - return nFull; } /* @@ -24243,7 +24612,7 @@ SQLITE_PRIVATE int sqlite3MallocSize(void *p){ SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ assert( p!=0 ); if( db==0 || !isLookaside(db,p) ){ -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG if( db==0 ){ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); @@ -24304,7 +24673,7 @@ SQLITE_PRIVATE void sqlite3DbFree(sqlite3 *db, void *p){ } if( isLookaside(db, p) ){ LookasideSlot *pBuf = (LookasideSlot*)p; -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG /* Trash all content in the buffer being freed */ memset(p, 0xaa, db->lookaside.sz); #endif @@ -24351,7 +24720,7 @@ SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ sqlite3_mutex_enter(mem0.mutex); sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); nDiff = nNew - nOld; - if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= + if( nDiff>0 && sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= mem0.alarmThreshold-nDiff ){ sqlite3MallocAlarm(nDiff); } @@ -24558,9 +24927,8 @@ SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){ if( z==0 ){ return 0; } - n = sqlite3Strlen30(z) + 1; - assert( (n&0x7fffffff)==n ); - zNew = sqlite3DbMallocRaw(db, (int)n); + n = strlen(z) + 1; + zNew = sqlite3DbMallocRaw(db, n); if( zNew ){ memcpy(zNew, z, n); } @@ -24674,7 +25042,7 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ ** Conversion types fall into various categories as defined by the ** following enumeration. */ -#define etRADIX 0 /* Integer types. %d, %x, %o, and so forth */ +#define etRADIX 0 /* non-decimal integer types. %x %o */ #define etFLOAT 1 /* Floating point. %f */ #define etEXP 2 /* Exponentional notation. %e and %E */ #define etGENERIC 3 /* Floating or exponential, depending on exponent. %g */ @@ -24692,8 +25060,9 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ #define etPOINTER 13 /* The %p conversion */ #define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */ #define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ +#define etDECIMAL 16 /* %d or %u, but not %x, %o */ -#define etINVALID 16 /* Any unrecognized conversion type */ +#define etINVALID 17 /* Any unrecognized conversion type */ /* @@ -24717,9 +25086,8 @@ typedef struct et_info { /* Information about each format field */ /* ** Allowed values for et_info.flags */ -#define FLAG_SIGNED 1 /* True if the value to convert is signed */ -#define FLAG_INTERN 2 /* True if for internal use only */ -#define FLAG_STRING 4 /* Allow infinity precision */ +#define FLAG_SIGNED 1 /* True if the value to convert is signed */ +#define FLAG_STRING 4 /* Allow infinite precision */ /* @@ -24729,7 +25097,7 @@ typedef struct et_info { /* Information about each format field */ static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; static const et_info fmtinfo[] = { - { 'd', 10, 1, etRADIX, 0, 0 }, + { 'd', 10, 1, etDECIMAL, 0, 0 }, { 's', 0, 4, etSTRING, 0, 0 }, { 'g', 0, 1, etGENERIC, 30, 0 }, { 'z', 0, 4, etDYNSTRING, 0, 0 }, @@ -24738,7 +25106,7 @@ static const et_info fmtinfo[] = { { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, { 'c', 0, 0, etCHARX, 0, 0 }, { 'o', 8, 0, etRADIX, 0, 2 }, - { 'u', 10, 0, etRADIX, 0, 0 }, + { 'u', 10, 0, etDECIMAL, 0, 0 }, { 'x', 16, 0, etRADIX, 16, 1 }, { 'X', 16, 0, etRADIX, 0, 4 }, #ifndef SQLITE_OMIT_FLOATING_POINT @@ -24747,16 +25115,15 @@ static const et_info fmtinfo[] = { { 'E', 0, 1, etEXP, 14, 0 }, { 'G', 0, 1, etGENERIC, 14, 0 }, #endif - { 'i', 10, 1, etRADIX, 0, 0 }, + { 'i', 10, 1, etDECIMAL, 0, 0 }, { 'n', 0, 0, etSIZE, 0, 0 }, { '%', 0, 0, etPERCENT, 0, 0 }, { 'p', 16, 0, etPOINTER, 0, 1 }, -/* All the rest have the FLAG_INTERN bit set and are thus for internal -** use only */ - { 'T', 0, 2, etTOKEN, 0, 0 }, - { 'S', 0, 2, etSRCLIST, 0, 0 }, - { 'r', 10, 3, etORDINAL, 0, 0 }, + /* All the rest are undocumented and are for internal use only */ + { 'T', 0, 0, etTOKEN, 0, 0 }, + { 'S', 0, 0, etSRCLIST, 0, 0 }, + { 'r', 10, 1, etORDINAL, 0, 0 }, }; /* @@ -24840,17 +25207,15 @@ SQLITE_PRIVATE void sqlite3VXPrintf( int idx; /* A general purpose loop counter */ int width; /* Width of the current field */ etByte flag_leftjustify; /* True if "-" flag is present */ - etByte flag_plussign; /* True if "+" flag is present */ - etByte flag_blanksign; /* True if " " flag is present */ + etByte flag_prefix; /* '+' or ' ' or 0 for prefix */ etByte flag_alternateform; /* True if "#" flag is present */ etByte flag_altform2; /* True if "!" flag is present */ etByte flag_zeropad; /* True if field width constant starts with zero */ - etByte flag_long; /* True if "l" flag is present */ - etByte flag_longlong; /* True if the "ll" flag is present */ + etByte flag_long; /* 1 for the "l" flag, 2 for "ll", 0 by default */ etByte done; /* Loop termination flag */ + etByte cThousand; /* Thousands separator for %d and %u */ etByte xtype = etINVALID; /* Conversion paradigm */ u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ - u8 useIntern; /* Ok to use internal conversions (ex: %T) */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ LONGDOUBLE_TYPE realvalue; /* Value for real types */ @@ -24869,13 +25234,11 @@ SQLITE_PRIVATE void sqlite3VXPrintf( char buf[etBUFSIZE]; /* Conversion buffer */ bufpt = 0; - if( pAccum->printfFlags ){ - if( (bArgList = (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC))!=0 ){ - pArgList = va_arg(ap, PrintfArguments*); - } - useIntern = pAccum->printfFlags & SQLITE_PRINTF_INTERNAL; + if( (pAccum->printfFlags & SQLITE_PRINTF_SQLFUNC)!=0 ){ + pArgList = va_arg(ap, PrintfArguments*); + bArgList = 1; }else{ - bArgList = useIntern = 0; + bArgList = 0; } for(; (c=(*fmt))!=0; ++fmt){ if( c!='%' ){ @@ -24893,17 +25256,18 @@ SQLITE_PRIVATE void sqlite3VXPrintf( break; } /* Find out what flags are present */ - flag_leftjustify = flag_plussign = flag_blanksign = + flag_leftjustify = flag_prefix = cThousand = flag_alternateform = flag_altform2 = flag_zeropad = 0; done = 0; do{ switch( c ){ case '-': flag_leftjustify = 1; break; - case '+': flag_plussign = 1; break; - case ' ': flag_blanksign = 1; break; + case '+': flag_prefix = '+'; break; + case ' ': flag_prefix = ' '; break; case '#': flag_alternateform = 1; break; case '!': flag_altform2 = 1; break; case '0': flag_zeropad = 1; break; + case ',': cThousand = ','; break; default: done = 1; break; } }while( !done && (c=(*++fmt))!=0 ); @@ -24973,13 +25337,11 @@ SQLITE_PRIVATE void sqlite3VXPrintf( flag_long = 1; c = *++fmt; if( c=='l' ){ - flag_longlong = 1; + flag_long = 2; c = *++fmt; - }else{ - flag_longlong = 0; } }else{ - flag_long = flag_longlong = 0; + flag_long = 0; } /* Fetch the info entry for the field */ infop = &fmtinfo[0]; @@ -24987,11 +25349,7 @@ SQLITE_PRIVATE void sqlite3VXPrintf( for(idx=0; idxflags & FLAG_INTERN)==0 ){ - xtype = infop->type; - }else{ - return; - } + xtype = infop->type; break; } } @@ -25001,15 +25359,11 @@ SQLITE_PRIVATE void sqlite3VXPrintf( ** ** flag_alternateform TRUE if a '#' is present. ** flag_altform2 TRUE if a '!' is present. - ** flag_plussign TRUE if a '+' is present. + ** flag_prefix '+' or ' ' or zero ** flag_leftjustify TRUE if a '-' is present or if the ** field width was negative. ** flag_zeropad TRUE if the width began with 0. - ** flag_long TRUE if the letter 'l' (ell) prefixed - ** the conversion character. - ** flag_longlong TRUE if the letter 'll' (ell ell) prefixed - ** the conversion character. - ** flag_blanksign TRUE if a ' ' is present. + ** flag_long 1 for "l", 2 for "ll" ** width The specified field width. This is ** always non-negative. Zero is the default. ** precision The specified precision. The default @@ -25019,19 +25373,24 @@ SQLITE_PRIVATE void sqlite3VXPrintf( */ switch( xtype ){ case etPOINTER: - flag_longlong = sizeof(char*)==sizeof(i64); - flag_long = sizeof(char*)==sizeof(long int); + flag_long = sizeof(char*)==sizeof(i64) ? 2 : + sizeof(char*)==sizeof(long int) ? 1 : 0; /* Fall through into the next case */ case etORDINAL: - case etRADIX: + case etRADIX: + cThousand = 0; + /* Fall through into the next case */ + case etDECIMAL: if( infop->flags & FLAG_SIGNED ){ i64 v; if( bArgList ){ v = getIntArg(pArgList); - }else if( flag_longlong ){ - v = va_arg(ap,i64); }else if( flag_long ){ - v = va_arg(ap,long int); + if( flag_long==2 ){ + v = va_arg(ap,i64) ; + }else{ + v = va_arg(ap,long int); + } }else{ v = va_arg(ap,int); } @@ -25044,17 +25403,17 @@ SQLITE_PRIVATE void sqlite3VXPrintf( prefix = '-'; }else{ longvalue = v; - if( flag_plussign ) prefix = '+'; - else if( flag_blanksign ) prefix = ' '; - else prefix = 0; + prefix = flag_prefix; } }else{ if( bArgList ){ longvalue = (u64)getIntArg(pArgList); - }else if( flag_longlong ){ - longvalue = va_arg(ap,u64); }else if( flag_long ){ - longvalue = va_arg(ap,unsigned long int); + if( flag_long==2 ){ + longvalue = va_arg(ap,u64); + }else{ + longvalue = va_arg(ap,unsigned long int); + } }else{ longvalue = va_arg(ap,unsigned int); } @@ -25064,16 +25423,17 @@ SQLITE_PRIVATE void sqlite3VXPrintf( if( flag_zeropad && precision0 ); } length = (int)(&zOut[nOut-1]-bufpt); - for(idx=precision-length; idx>0; idx--){ + while( precision>length ){ *(--bufpt) = '0'; /* Zero pad */ + length++; + } + if( cThousand ){ + int nn = (length - 1)/3; /* Number of "," to insert */ + int ix = (length - 1)%3 + 1; + bufpt -= nn; + for(idx=0; nn>0; idx++){ + bufpt[idx] = bufpt[idx+nn]; + ix--; + if( ix==0 ){ + bufpt[++idx] = cThousand; + nn--; + ix = 3; + } + } } if( prefix ) *(--bufpt) = prefix; /* Add sign */ if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ @@ -25122,9 +25497,7 @@ SQLITE_PRIVATE void sqlite3VXPrintf( realvalue = -realvalue; prefix = '-'; }else{ - if( flag_plussign ) prefix = '+'; - else if( flag_blanksign ) prefix = ' '; - else prefix = 0; + prefix = flag_prefix; } if( xtype==etGENERIC && precision>0 ) precision--; testcase( precision>0xfff ); @@ -25360,7 +25733,9 @@ SQLITE_PRIVATE void sqlite3VXPrintf( break; } case etTOKEN: { - Token *pToken = va_arg(ap, Token*); + Token *pToken; + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + pToken = va_arg(ap, Token*); assert( bArgList==0 ); if( pToken && pToken->n ){ sqlite3StrAccumAppend(pAccum, (const char*)pToken->z, pToken->n); @@ -25369,9 +25744,13 @@ SQLITE_PRIVATE void sqlite3VXPrintf( break; } case etSRCLIST: { - SrcList *pSrc = va_arg(ap, SrcList*); - int k = va_arg(ap, int); - struct SrcList_item *pItem = &pSrc->a[k]; + SrcList *pSrc; + int k; + struct SrcList_item *pItem; + if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; + pSrc = va_arg(ap, SrcList*); + k = va_arg(ap, int); + pItem = &pSrc->a[k]; assert( bArgList==0 ); assert( k>=0 && knSrc ); if( pItem->zDatabase ){ @@ -25393,9 +25772,13 @@ SQLITE_PRIVATE void sqlite3VXPrintf( ** the output. */ width -= length; - if( width>0 && !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); - sqlite3StrAccumAppend(pAccum, bufpt, length); - if( width>0 && flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + if( width>0 ){ + if( !flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + sqlite3StrAccumAppend(pAccum, bufpt, length); + if( flag_leftjustify ) sqlite3AppendChar(pAccum, width, ' '); + }else{ + sqlite3StrAccumAppend(pAccum, bufpt, length); + } if( zExtra ){ sqlite3DbFree(pAccum->db, zExtra); @@ -25500,7 +25883,7 @@ SQLITE_PRIVATE void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N){ assert( p->accError==0 || p->nAlloc==0 ); if( p->nChar+N >= p->nAlloc ){ enlargeAndAppend(p,z,N); - }else{ + }else if( N ){ assert( p->zText ); p->nChar += N; memcpy(&p->zText[p->nChar-N], z, N); @@ -25520,18 +25903,23 @@ SQLITE_PRIVATE void sqlite3StrAccumAppendAll(StrAccum *p, const char *z){ ** Return a pointer to the resulting string. Return a NULL ** pointer if any kind of error was encountered. */ +static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ + assert( p->mxAlloc>0 && !isMalloced(p) ); + p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); + if( p->zText ){ + memcpy(p->zText, p->zBase, p->nChar+1); + p->printfFlags |= SQLITE_PRINTF_MALLOCED; + }else{ + setStrAccumError(p, STRACCUM_NOMEM); + } + return p->zText; +} SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ if( p->zText ){ assert( (p->zText==p->zBase)==!isMalloced(p) ); p->zText[p->nChar] = 0; if( p->mxAlloc>0 && !isMalloced(p) ){ - p->zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); - if( p->zText ){ - memcpy(p->zText, p->zBase, p->nChar+1); - p->printfFlags |= SQLITE_PRINTF_MALLOCED; - }else{ - setStrAccumError(p, STRACCUM_NOMEM); - } + return strAccumFinishRealloc(p); } } return p->zText; @@ -25671,7 +26059,8 @@ SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_li #endif sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); sqlite3VXPrintf(&acc, zFormat, ap); - return sqlite3StrAccumFinish(&acc); + zBuf[acc.nChar] = 0; + return zBuf; } SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ char *z; @@ -25819,6 +26208,7 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_start(ap, zFormat); sqlite3VXPrintf(&acc, zFormat, ap); va_end(ap); + assert( acc.nChar>0 ); if( zBuf[acc.nChar-1]!='\n' ) sqlite3StrAccumAppend(&acc, "\n", 1); sqlite3StrAccumFinish(&acc); fprintf(stdout,"%s", zBuf); @@ -25874,11 +26264,15 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m /* -** Generate a human-readable description of a the Select object. +** Generate a human-readable description of a Select object. */ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 moreToFollow){ int n = 0; int cnt = 0; + if( p==0 ){ + sqlite3TreeViewLine(pView, "nil-SELECT"); + return; + } pView = sqlite3TreeViewPush(pView, moreToFollow); if( p->pWith ){ sqlite3TreeViewWith(pView, p->pWith, 1); @@ -26205,6 +26599,15 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewExpr(pView, pExpr->pRight, 0); break; } + case TK_VECTOR: { + sqlite3TreeViewBareExprList(pView, pExpr->x.pList, "VECTOR"); + break; + } + case TK_SELECT_COLUMN: { + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn); + sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; @@ -26221,21 +26624,20 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewPop(pView); } + /* ** Generate a human-readable explanation of an expression list. */ -SQLITE_PRIVATE void sqlite3TreeViewExprList( +SQLITE_PRIVATE void sqlite3TreeViewBareExprList( TreeView *pView, const ExprList *pList, - u8 moreToFollow, const char *zLabel ){ - int i; - pView = sqlite3TreeViewPush(pView, moreToFollow); if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; if( pList==0 ){ sqlite3TreeViewLine(pView, "%s (empty)", zLabel); }else{ + int i; sqlite3TreeViewLine(pView, "%s", zLabel); for(i=0; inExpr; i++){ int j = pList->a[i].u.x.iOrderByCol; @@ -26247,6 +26649,15 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList( if( j ) sqlite3TreeViewPop(pView); } } +} +SQLITE_PRIVATE void sqlite3TreeViewExprList( + TreeView *pView, + const ExprList *pList, + u8 moreToFollow, + const char *zLabel +){ + pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewBareExprList(pView, pList, zLabel); sqlite3TreeViewPop(pView); } @@ -26362,7 +26773,7 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){ sqlite3_mutex_leave(mutex); } -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** For testing purposes, we sometimes want to preserve the state of ** PRNG and restore the PRNG to its saved state at a later time, or @@ -26387,7 +26798,7 @@ SQLITE_PRIVATE void sqlite3PrngRestoreState(void){ sizeof(sqlite3Prng) ); } -#endif /* SQLITE_OMIT_BUILTIN_TEST */ +#endif /* SQLITE_UNTESTABLE */ /************** End of random.c **********************************************/ /************** Begin file threads.c *****************************************/ @@ -27245,7 +27656,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int x){ ** Return whatever integer value the test callback returns, or return ** SQLITE_OK if no test callback is installed. */ -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ int (*xCallback)(int) = sqlite3GlobalConfig.xTestCallback; return xCallback ? xCallback(iTest) : SQLITE_OK; @@ -28343,13 +28754,11 @@ SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){ u32 x; memcpy(&x,p,4); return x; -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(__GNUC__) && GCC_VERSION>=4003000 +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 u32 x; memcpy(&x,p,4); return __builtin_bswap32(x); -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(_MSC_VER) && _MSC_VER>=1300 +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 u32 x; memcpy(&x,p,4); return _byteswap_ulong(x); @@ -28361,12 +28770,10 @@ SQLITE_PRIVATE u32 sqlite3Get4byte(const u8 *p){ SQLITE_PRIVATE void sqlite3Put4byte(unsigned char *p, u32 v){ #if SQLITE_BYTEORDER==4321 memcpy(p,&v,4); -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(__GNUC__) && GCC_VERSION>=4003000 +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 u32 x = __builtin_bswap32(v); memcpy(p,&x,4); -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(_MSC_VER) && _MSC_VER>=1300 +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 u32 x = _byteswap_ulong(v); memcpy(p,&x,4); #else @@ -28482,6 +28889,9 @@ SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ ** overflow, leave *pA unchanged and return 1. */ SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 + return __builtin_add_overflow(*pA, iB, pA); +#else i64 iA = *pA; testcase( iA==0 ); testcase( iA==1 ); testcase( iB==-1 ); testcase( iB==0 ); @@ -28496,8 +28906,12 @@ SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){ } *pA += iB; return 0; +#endif } SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 + return __builtin_sub_overflow(*pA, iB, pA); +#else testcase( iB==SMALLEST_INT64+1 ); if( iB==SMALLEST_INT64 ){ testcase( (*pA)==(-1) ); testcase( (*pA)==0 ); @@ -28507,38 +28921,28 @@ SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){ }else{ return sqlite3AddInt64(pA, -iB); } +#endif } -#define TWOPOWER32 (((i64)1)<<32) -#define TWOPOWER31 (((i64)1)<<31) SQLITE_PRIVATE int sqlite3MulInt64(i64 *pA, i64 iB){ +#if GCC_VERSION>=5004000 + return __builtin_mul_overflow(*pA, iB, pA); +#else i64 iA = *pA; - i64 iA1, iA0, iB1, iB0, r; - - iA1 = iA/TWOPOWER32; - iA0 = iA % TWOPOWER32; - iB1 = iB/TWOPOWER32; - iB0 = iB % TWOPOWER32; - if( iA1==0 ){ - if( iB1==0 ){ - *pA *= iB; - return 0; - } - r = iA0*iB1; - }else if( iB1==0 ){ - r = iA1*iB0; - }else{ - /* If both iA1 and iB1 are non-zero, overflow will result */ - return 1; - } - testcase( r==(-TWOPOWER31)-1 ); - testcase( r==(-TWOPOWER31) ); - testcase( r==TWOPOWER31 ); - testcase( r==TWOPOWER31-1 ); - if( r<(-TWOPOWER31) || r>=TWOPOWER31 ) return 1; - r *= TWOPOWER32; - if( sqlite3AddInt64(&r, iA0*iB0) ) return 1; - *pA = r; + if( iB>0 ){ + if( iA>LARGEST_INT64/iB ) return 1; + if( iA0 ){ + if( iBLARGEST_INT64/-iB ) return 1; + } + } + *pA = iA*iB; return 0; +#endif } /* @@ -28672,6 +29076,109 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ } #endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ +/* +** Add a new name/number pair to a VList. This might require that the +** VList object be reallocated, so return the new VList. If an OOM +** error occurs, the original VList returned and the +** db->mallocFailed flag is set. +** +** A VList is really just an array of integers. To destroy a VList, +** simply pass it to sqlite3DbFree(). +** +** The first integer is the number of integers allocated for the whole +** VList. The second integer is the number of integers actually used. +** Each name/number pair is encoded by subsequent groups of 3 or more +** integers. +** +** Each name/number pair starts with two integers which are the numeric +** value for the pair and the size of the name/number pair, respectively. +** The text name overlays one or more following integers. The text name +** is always zero-terminated. +** +** Conceptually: +** +** struct VList { +** int nAlloc; // Number of allocated slots +** int nUsed; // Number of used slots +** struct VListEntry { +** int iValue; // Value for this entry +** int nSlot; // Slots used by this entry +** // ... variable name goes here +** } a[0]; +** } +** +** During code generation, pointers to the variable names within the +** VList are taken. When that happens, nAlloc is set to zero as an +** indication that the VList may never again be enlarged, since the +** accompanying realloc() would invalidate the pointers. +*/ +SQLITE_PRIVATE VList *sqlite3VListAdd( + sqlite3 *db, /* The database connection used for malloc() */ + VList *pIn, /* The input VList. Might be NULL */ + const char *zName, /* Name of symbol to add */ + int nName, /* Bytes of text in zName */ + int iVal /* Value to associate with zName */ +){ + int nInt; /* number of sizeof(int) objects needed for zName */ + char *z; /* Pointer to where zName will be stored */ + int i; /* Index in pIn[] where zName is stored */ + + nInt = nName/4 + 3; + assert( pIn==0 || pIn[0]>=3 ); /* Verify ok to add new elements */ + if( pIn==0 || pIn[1]+nInt > pIn[0] ){ + /* Enlarge the allocation */ + int nAlloc = (pIn ? pIn[0]*2 : 10) + nInt; + VList *pOut = sqlite3DbRealloc(db, pIn, nAlloc*sizeof(int)); + if( pOut==0 ) return pIn; + if( pIn==0 ) pOut[1] = 2; + pIn = pOut; + pIn[0] = nAlloc; + } + i = pIn[1]; + pIn[i] = iVal; + pIn[i+1] = nInt; + z = (char*)&pIn[i+2]; + pIn[1] = i+nInt; + assert( pIn[1]<=pIn[0] ); + memcpy(z, zName, nName); + z[nName] = 0; + return pIn; +} + +/* +** Return a pointer to the name of a variable in the given VList that +** has the value iVal. Or return a NULL if there is no such variable in +** the list +*/ +SQLITE_PRIVATE const char *sqlite3VListNumToName(VList *pIn, int iVal){ + int i, mx; + if( pIn==0 ) return 0; + mx = pIn[1]; + i = 2; + do{ + if( pIn[i]==iVal ) return (char*)&pIn[i+2]; + i += pIn[i+1]; + }while( i=r[P1]"), - /* 42 */ "Last" OpHelp(""), + /* 42 */ "ElseNotEq" OpHelp(""), /* 43 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), /* 44 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), /* 45 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<0 then r[P1]-=P3, goto P2"), - /* 66 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]-=P3, goto P2"), - /* 67 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 68 */ "IncrVacuum" OpHelp(""), - /* 69 */ "VNext" OpHelp(""), - /* 70 */ "Init" OpHelp("Start at P2"), - /* 71 */ "Return" OpHelp(""), - /* 72 */ "EndCoroutine" OpHelp(""), - /* 73 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 74 */ "Halt" OpHelp(""), - /* 75 */ "Integer" OpHelp("r[P2]=P1"), - /* 76 */ "Int64" OpHelp("r[P2]=P4"), - /* 77 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 78 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 79 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 80 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 81 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 82 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 83 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 84 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 85 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 86 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 87 */ "CollSeq" OpHelp(""), - /* 88 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), - /* 89 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), - /* 90 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 91 */ "RealAffinity" OpHelp(""), - /* 92 */ "Cast" OpHelp("affinity(r[P1])"), - /* 93 */ "Permutation" OpHelp(""), - /* 94 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 95 */ "Column" OpHelp("r[P3]=PX"), - /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 55 */ "IfSmaller" OpHelp(""), + /* 56 */ "SorterSort" OpHelp(""), + /* 57 */ "Sort" OpHelp(""), + /* 58 */ "Rewind" OpHelp(""), + /* 59 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 60 */ "IdxGT" OpHelp("key=r[P3@P4]"), + /* 61 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 62 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 63 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 64 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 65 */ "Program" OpHelp(""), + /* 66 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 67 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 68 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 69 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 70 */ "IncrVacuum" OpHelp(""), + /* 71 */ "VNext" OpHelp(""), + /* 72 */ "Init" OpHelp("Start at P2"), + /* 73 */ "Return" OpHelp(""), + /* 74 */ "EndCoroutine" OpHelp(""), + /* 75 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 76 */ "Halt" OpHelp(""), + /* 77 */ "Integer" OpHelp("r[P2]=P1"), + /* 78 */ "Int64" OpHelp("r[P2]=P4"), + /* 79 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 80 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 81 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 82 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 83 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), + /* 84 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 85 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 86 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 87 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 88 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 89 */ "CollSeq" OpHelp(""), + /* 90 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), + /* 91 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), + /* 92 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 93 */ "RealAffinity" OpHelp(""), + /* 94 */ "Cast" OpHelp("affinity(r[P1])"), + /* 95 */ "Permutation" OpHelp(""), + /* 96 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), /* 97 */ "String8" OpHelp("r[P2]='P4'"), - /* 98 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 99 */ "Count" OpHelp("r[P2]=count()"), - /* 100 */ "ReadCookie" OpHelp(""), - /* 101 */ "SetCookie" OpHelp(""), - /* 102 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 103 */ "OpenRead" OpHelp("root=P2 iDb=P3"), - /* 104 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), - /* 105 */ "OpenAutoindex" OpHelp("nColumn=P2"), - /* 106 */ "OpenEphemeral" OpHelp("nColumn=P2"), - /* 107 */ "SorterOpen" OpHelp(""), - /* 108 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), - /* 109 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 110 */ "Close" OpHelp(""), - /* 111 */ "ColumnsUsed" OpHelp(""), - /* 112 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 113 */ "NewRowid" OpHelp("r[P2]=rowid"), - /* 114 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), - /* 115 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), - /* 116 */ "Delete" OpHelp(""), - /* 117 */ "ResetCount" OpHelp(""), - /* 118 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 119 */ "SorterData" OpHelp("r[P2]=data"), - /* 120 */ "RowKey" OpHelp("r[P2]=key"), - /* 121 */ "RowData" OpHelp("r[P2]=data"), - /* 122 */ "Rowid" OpHelp("r[P2]=rowid"), - /* 123 */ "NullRow" OpHelp(""), - /* 124 */ "SorterInsert" OpHelp(""), - /* 125 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 126 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 127 */ "Seek" OpHelp("Move P3 to P1.rowid"), - /* 128 */ "IdxRowid" OpHelp("r[P2]=rowid"), - /* 129 */ "Destroy" OpHelp(""), - /* 130 */ "Clear" OpHelp(""), - /* 131 */ "ResetSorter" OpHelp(""), - /* 132 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), - /* 133 */ "Real" OpHelp("r[P2]=P4"), - /* 134 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), - /* 135 */ "ParseSchema" OpHelp(""), - /* 136 */ "LoadAnalysis" OpHelp(""), - /* 137 */ "DropTable" OpHelp(""), - /* 138 */ "DropIndex" OpHelp(""), - /* 139 */ "DropTrigger" OpHelp(""), - /* 140 */ "IntegrityCk" OpHelp(""), - /* 141 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), - /* 142 */ "Param" OpHelp(""), - /* 143 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 144 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), - /* 145 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 146 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 147 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 148 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 149 */ "Expire" OpHelp(""), - /* 150 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 151 */ "VBegin" OpHelp(""), - /* 152 */ "VCreate" OpHelp(""), - /* 153 */ "VDestroy" OpHelp(""), - /* 154 */ "VOpen" OpHelp(""), - /* 155 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 156 */ "VRename" OpHelp(""), - /* 157 */ "Pagecount" OpHelp(""), - /* 158 */ "MaxPgcnt" OpHelp(""), - /* 159 */ "CursorHint" OpHelp(""), - /* 160 */ "Noop" OpHelp(""), - /* 161 */ "Explain" OpHelp(""), + /* 98 */ "Column" OpHelp("r[P3]=PX"), + /* 99 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 100 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 101 */ "Count" OpHelp("r[P2]=count()"), + /* 102 */ "ReadCookie" OpHelp(""), + /* 103 */ "SetCookie" OpHelp(""), + /* 104 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 105 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 106 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 107 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 108 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 109 */ "SorterOpen" OpHelp(""), + /* 110 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 111 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 112 */ "Close" OpHelp(""), + /* 113 */ "ColumnsUsed" OpHelp(""), + /* 114 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 115 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 116 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 117 */ "InsertInt" OpHelp("intkey=P3 data=r[P2]"), + /* 118 */ "Delete" OpHelp(""), + /* 119 */ "ResetCount" OpHelp(""), + /* 120 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 121 */ "SorterData" OpHelp("r[P2]=data"), + /* 122 */ "RowData" OpHelp("r[P2]=data"), + /* 123 */ "Rowid" OpHelp("r[P2]=rowid"), + /* 124 */ "NullRow" OpHelp(""), + /* 125 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 126 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 127 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 128 */ "Seek" OpHelp("Move P3 to P1.rowid"), + /* 129 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 130 */ "Destroy" OpHelp(""), + /* 131 */ "Clear" OpHelp(""), + /* 132 */ "Real" OpHelp("r[P2]=P4"), + /* 133 */ "ResetSorter" OpHelp(""), + /* 134 */ "CreateIndex" OpHelp("r[P2]=root iDb=P1"), + /* 135 */ "CreateTable" OpHelp("r[P2]=root iDb=P1"), + /* 136 */ "SqlExec" OpHelp(""), + /* 137 */ "ParseSchema" OpHelp(""), + /* 138 */ "LoadAnalysis" OpHelp(""), + /* 139 */ "DropTable" OpHelp(""), + /* 140 */ "DropIndex" OpHelp(""), + /* 141 */ "DropTrigger" OpHelp(""), + /* 142 */ "IntegrityCk" OpHelp(""), + /* 143 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 144 */ "Param" OpHelp(""), + /* 145 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 146 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 147 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 148 */ "AggStep0" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 149 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 150 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 151 */ "Expire" OpHelp(""), + /* 152 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 153 */ "VBegin" OpHelp(""), + /* 154 */ "VCreate" OpHelp(""), + /* 155 */ "VDestroy" OpHelp(""), + /* 156 */ "VOpen" OpHelp(""), + /* 157 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 158 */ "VRename" OpHelp(""), + /* 159 */ "Pagecount" OpHelp(""), + /* 160 */ "MaxPgcnt" OpHelp(""), + /* 161 */ "CursorHint" OpHelp(""), + /* 162 */ "Noop" OpHelp(""), + /* 163 */ "Explain" OpHelp(""), }; return azName[i]; } @@ -30386,7 +30899,14 @@ struct unixFileId { #if OS_VXWORKS struct vxworksFileId *pId; /* Unique file ID for vxworks. */ #else - ino_t ino; /* Inode number */ + /* We are told that some versions of Android contain a bug that + ** sizes ino_t at only 32-bits instead of 64-bits. (See + ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c) + ** To work around this, always allocate 64-bits for the inode number. + ** On small machines that only have 32-bit inodes, this wastes 4 bytes, + ** but that should not be a big deal. */ + /* WAS: ino_t ino; */ + u64 ino; /* Inode number */ #endif }; @@ -30631,7 +31151,7 @@ static int findInodeInfo( #if OS_VXWORKS fileId.pId = pFile->pId; #else - fileId.ino = statbuf.st_ino; + fileId.ino = (u64)statbuf.st_ino; #endif pInode = inodeList; while( pInode && memcmp(&fileId, &pInode->fileId, sizeof(fileId)) ){ @@ -30665,7 +31185,8 @@ static int fileHasMoved(unixFile *pFile){ #else struct stat buf; return pFile->pInode!=0 && - (osStat(pFile->zPath, &buf)!=0 || buf.st_ino!=pFile->pInode->fileId.ino); + (osStat(pFile->zPath, &buf)!=0 + || (u64)buf.st_ino!=pFile->pInode->fileId.ino); #endif } @@ -34837,7 +35358,7 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ unixEnterMutex(); pInode = inodeList; while( pInode && (pInode->fileId.dev!=sStat.st_dev - || pInode->fileId.ino!=sStat.st_ino) ){ + || pInode->fileId.ino!=(u64)sStat.st_ino) ){ pInode = pInode->pNext; } if( pInode ){ @@ -34854,6 +35375,27 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ return pUnused; } +/* +** Find the mode, uid and gid of file zFile. +*/ +static int getFileMode( + const char *zFile, /* File name */ + mode_t *pMode, /* OUT: Permissions of zFile */ + uid_t *pUid, /* OUT: uid of zFile. */ + gid_t *pGid /* OUT: gid of zFile. */ +){ + struct stat sStat; /* Output of stat() on database file */ + int rc = SQLITE_OK; + if( 0==osStat(zFile, &sStat) ){ + *pMode = sStat.st_mode & 0777; + *pUid = sStat.st_uid; + *pGid = sStat.st_gid; + }else{ + rc = SQLITE_IOERR_FSTAT; + } + return rc; +} + /* ** This function is called by unixOpen() to determine the unix permissions ** to create new files with. If no error occurs, then SQLITE_OK is returned @@ -34889,7 +35431,6 @@ static int findCreateFileMode( if( flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL) ){ char zDb[MAX_PATHNAME+1]; /* Database file path */ int nDb; /* Number of valid bytes in zDb */ - struct stat sStat; /* Output of stat() on database file */ /* zPath is a path to a WAL or journal file. The following block derives ** the path to the associated database file from zPath. This block handles @@ -34920,15 +35461,18 @@ static int findCreateFileMode( memcpy(zDb, zPath, nDb); zDb[nDb] = '\0'; - if( 0==osStat(zDb, &sStat) ){ - *pMode = sStat.st_mode & 0777; - *pUid = sStat.st_uid; - *pGid = sStat.st_gid; - }else{ - rc = SQLITE_IOERR_FSTAT; - } + rc = getFileMode(zDb, pMode, pUid, pGid); }else if( flags & SQLITE_OPEN_DELETEONCLOSE ){ *pMode = 0600; + }else if( flags & SQLITE_OPEN_URI ){ + /* If this is a main database file and the file was opened using a URI + ** filename, check for the "modeof" parameter. If present, interpret + ** its value as a filename and try to copy the mode, uid and gid from + ** that file. */ + const char *z = sqlite3_uri_parameter(zPath, "modeof"); + if( z ){ + rc = getFileMode(z, pMode, pUid, pGid); + } } return rc; } @@ -37468,7 +38012,34 @@ struct winVfsAppData { ****************************************************************************** */ #ifndef SQLITE_WIN32_HEAP_CREATE -# define SQLITE_WIN32_HEAP_CREATE (TRUE) +# define SQLITE_WIN32_HEAP_CREATE (TRUE) +#endif + +/* + * This is the maximum possible initial size of the Win32-specific heap, in + * bytes. + */ +#ifndef SQLITE_WIN32_HEAP_MAX_INIT_SIZE +# define SQLITE_WIN32_HEAP_MAX_INIT_SIZE (4294967295U) +#endif + +/* + * This is the extra space for the initial size of the Win32-specific heap, + * in bytes. This value may be zero. + */ +#ifndef SQLITE_WIN32_HEAP_INIT_EXTRA +# define SQLITE_WIN32_HEAP_INIT_EXTRA (4194304) +#endif + +/* + * Calculate the maximum legal cache size, in pages, based on the maximum + * possible initial heap size and the default page size, setting aside the + * needed extra space. + */ +#ifndef SQLITE_WIN32_MAX_CACHE_SIZE +# define SQLITE_WIN32_MAX_CACHE_SIZE (((SQLITE_WIN32_HEAP_MAX_INIT_SIZE) - \ + (SQLITE_WIN32_HEAP_INIT_EXTRA)) / \ + (SQLITE_DEFAULT_PAGE_SIZE)) #endif /* @@ -37477,25 +38048,36 @@ struct winVfsAppData { */ #ifndef SQLITE_WIN32_CACHE_SIZE # if SQLITE_DEFAULT_CACHE_SIZE>=0 -# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE) +# define SQLITE_WIN32_CACHE_SIZE (SQLITE_DEFAULT_CACHE_SIZE) # else -# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE)) +# define SQLITE_WIN32_CACHE_SIZE (-(SQLITE_DEFAULT_CACHE_SIZE)) # endif #endif +/* + * Make sure that the calculated cache size, in pages, cannot cause the + * initial size of the Win32-specific heap to exceed the maximum amount + * of memory that can be specified in the call to HeapCreate. + */ +#if SQLITE_WIN32_CACHE_SIZE>SQLITE_WIN32_MAX_CACHE_SIZE +# undef SQLITE_WIN32_CACHE_SIZE +# define SQLITE_WIN32_CACHE_SIZE (2000) +#endif + /* * The initial size of the Win32-specific heap. This value may be zero. */ #ifndef SQLITE_WIN32_HEAP_INIT_SIZE -# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \ - (SQLITE_DEFAULT_PAGE_SIZE) + 4194304) +# define SQLITE_WIN32_HEAP_INIT_SIZE ((SQLITE_WIN32_CACHE_SIZE) * \ + (SQLITE_DEFAULT_PAGE_SIZE) + \ + (SQLITE_WIN32_HEAP_INIT_EXTRA)) #endif /* * The maximum size of the Win32-specific heap. This value may be zero. */ #ifndef SQLITE_WIN32_HEAP_MAX_SIZE -# define SQLITE_WIN32_HEAP_MAX_SIZE (0) +# define SQLITE_WIN32_HEAP_MAX_SIZE (0) #endif /* @@ -37503,7 +38085,7 @@ struct winVfsAppData { * zero for the default behavior. */ #ifndef SQLITE_WIN32_HEAP_FLAGS -# define SQLITE_WIN32_HEAP_FLAGS (0) +# define SQLITE_WIN32_HEAP_FLAGS (0) #endif @@ -40604,6 +41186,12 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); return SQLITE_OK; } + case SQLITE_FCNTL_WIN32_GET_HANDLE: { + LPHANDLE phFile = (LPHANDLE)pArg; + *phFile = pFile->h; + OSTRACE(("FCNTL file=%p, rc=SQLITE_OK\n", pFile->h)); + return SQLITE_OK; + } #ifdef SQLITE_TEST case SQLITE_FCNTL_WIN32_SET_HANDLE: { LPHANDLE phFile = (LPHANDLE)pArg; @@ -43369,7 +43957,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ return p->iSize; } -#ifndef SQLITE_OMIT_BUILTIN_TEST +#ifndef SQLITE_UNTESTABLE /* ** Let V[] be an array of unsigned characters sufficient to hold ** up to N bits. Let I be an integer between 0 and N. 0<=Ipgno>0 ); /* Page number is 1 or more */ + assert( pPg->pgno>0 || pPg->pPager==0 ); /* Page number is 1 or more */ pCache = pPg->pCache; assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ @@ -43774,6 +44362,12 @@ SQLITE_PRIVATE int sqlite3PcacheSize(void){ return sizeof(PCache); } ** has already been allocated and is passed in as the p pointer. ** The caller discovers how much space needs to be allocated by ** calling sqlite3PcacheSize(). +** +** szExtra is some extra space allocated for each page. The first +** 8 bytes of the extra space will be zeroed as the page is allocated, +** but remaining content will be uninitialized. Though it is opaque +** to this module, the extra space really ends up being the MemPage +** structure in the pager. */ SQLITE_PRIVATE int sqlite3PcacheOpen( int szPage, /* Size of every page */ @@ -43786,6 +44380,7 @@ SQLITE_PRIVATE int sqlite3PcacheOpen( memset(p, 0, sizeof(PCache)); p->szPage = 1; p->szExtra = szExtra; + assert( szExtra>=8 ); /* First 8 bytes will be zeroed */ p->bPurgeable = bPurgeable; p->eCreate = 2; p->xStress = xStress; @@ -43855,7 +44450,6 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch( assert( pCache!=0 ); assert( pCache->pCache!=0 ); assert( createFlag==3 || createFlag==0 ); - assert( pgno>0 ); assert( pCache->eCreate==((pCache->bPurgeable && pCache->pDirty) ? 1 : 2) ); /* eCreate defines what to do if the page does not exist. @@ -43951,11 +44545,11 @@ static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit( assert( pPage!=0 ); pPgHdr = (PgHdr*)pPage->pExtra; assert( pPgHdr->pPage==0 ); - memset(pPgHdr, 0, sizeof(PgHdr)); + memset(&pPgHdr->pDirty, 0, sizeof(PgHdr) - offsetof(PgHdr,pDirty)); pPgHdr->pPage = pPage; pPgHdr->pData = pPage->pBuf; pPgHdr->pExtra = (void *)&pPgHdr[1]; - memset(pPgHdr->pExtra, 0, pCache->szExtra); + memset(pPgHdr->pExtra, 0, 8); pPgHdr->pCache = pCache; pPgHdr->pgno = pgno; pPgHdr->flags = PGHDR_CLEAN; @@ -44645,7 +45239,7 @@ static int pcache1InitBulk(PCache1 *pCache){ szBulk = -1024 * (i64)pcache1.nInitPage; } if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ - szBulk = pCache->szAlloc*pCache->nMax; + szBulk = pCache->szAlloc*(i64)pCache->nMax; } zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); sqlite3EndBenignMalloc(); @@ -46179,7 +46773,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 #ifdef SQLITE_OMIT_WAL # define sqlite3WalOpen(x,y,z) 0 # define sqlite3WalLimit(x,y) -# define sqlite3WalClose(w,x,y,z) 0 +# define sqlite3WalClose(v,w,x,y,z) 0 # define sqlite3WalBeginReadTransaction(y,z) 0 # define sqlite3WalEndReadTransaction(z) # define sqlite3WalDbsize(y) 0 @@ -46189,7 +46783,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 # define sqlite3WalSavepoint(y,z) # define sqlite3WalSavepointUndo(y,z) 0 # define sqlite3WalFrames(u,v,w,x,y,z) 0 -# define sqlite3WalCheckpoint(r,s,t,u,v,w,x,y,z) 0 +# define sqlite3WalCheckpoint(q,r,s,t,u,v,w,x,y,z) 0 # define sqlite3WalCallback(z) 0 # define sqlite3WalExclusiveMode(y,z) 0 # define sqlite3WalHeapMemory(z) 0 @@ -46207,7 +46801,7 @@ typedef struct Wal Wal; /* Open and close a connection to a write-ahead log. */ SQLITE_PRIVATE int sqlite3WalOpen(sqlite3_vfs*, sqlite3_file*, const char *, int, i64, Wal**); -SQLITE_PRIVATE int sqlite3WalClose(Wal *pWal, int sync_flags, int, u8 *); +SQLITE_PRIVATE int sqlite3WalClose(Wal *pWal, sqlite3*, int sync_flags, int, u8 *); /* Set the limiting size of a WAL file. */ SQLITE_PRIVATE void sqlite3WalLimit(Wal*, i64); @@ -46250,6 +46844,7 @@ SQLITE_PRIVATE int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int); /* Copy pages from the log to the database file */ SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Write-ahead log connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ int eMode, /* One of PASSIVE, FULL and RESTART */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -46281,6 +46876,7 @@ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal); #ifdef SQLITE_ENABLE_SNAPSHOT SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot); SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal); #endif #ifdef SQLITE_ENABLE_ZIPVFS @@ -46970,6 +47566,7 @@ struct Pager { int nRead; /* Database pages read */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ + int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */ #ifdef SQLITE_HAS_CODEC void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ void (*xCodecSizeChng)(void*,int,int); /* Notify of page size changes */ @@ -47090,13 +47687,20 @@ static const unsigned char aJournalMagic[] = { #define isOpen(pFd) ((pFd)->pMethods!=0) /* -** Return true if this pager uses a write-ahead log instead of the usual -** rollback journal. Otherwise false. +** Return true if this pager uses a write-ahead log to read page pgno. +** Return false if the pager reads pgno directly from the database. */ -#ifndef SQLITE_OMIT_WAL -static int pagerUseWal(Pager *pPager){ - return (pPager->pWal!=0); +#if !defined(SQLITE_OMIT_WAL) && defined(SQLITE_DIRECT_OVERFLOW_READ) +SQLITE_PRIVATE int sqlite3PagerUseWal(Pager *pPager, Pgno pgno){ + u32 iRead = 0; + int rc; + if( pPager->pWal==0 ) return 0; + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); + return rc || iRead; } +#endif +#ifndef SQLITE_OMIT_WAL +# define pagerUseWal(x) ((x)->pWal!=0) #else # define pagerUseWal(x) 0 # define pagerRollbackWal(x) 0 @@ -47295,6 +47899,33 @@ static char *print_pager_state(Pager *p){ } #endif +/* Forward references to the various page getters */ +static int getPageNormal(Pager*,Pgno,DbPage**,int); +static int getPageError(Pager*,Pgno,DbPage**,int); +#if SQLITE_MAX_MMAP_SIZE>0 +static int getPageMMap(Pager*,Pgno,DbPage**,int); +#endif + +/* +** Set the Pager.xGet method for the appropriate routine used to fetch +** content from the pager. +*/ +static void setGetterMethod(Pager *pPager){ + if( pPager->errCode ){ + pPager->xGet = getPageError; +#if SQLITE_MAX_MMAP_SIZE>0 + }else if( USEFETCH(pPager) +#ifdef SQLITE_HAS_CODEC + && pPager->xCodec==0 +#endif + ){ + pPager->xGet = getPageMMap; +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + }else{ + pPager->xGet = getPageNormal; + } +} + /* ** Return true if it is necessary to write page *pPg into the sub-journal. ** A page needs to be written into the sub-journal if there exists one @@ -48109,6 +48740,7 @@ static void pager_unlock(Pager *pPager){ } if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0); pPager->errCode = SQLITE_OK; + setGetterMethod(pPager); } pPager->journalOff = 0; @@ -48146,6 +48778,7 @@ static int pager_error(Pager *pPager, int rc){ if( rc2==SQLITE_FULL || rc2==SQLITE_IOERR ){ pPager->errCode = rc; pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); } return rc; } @@ -48314,7 +48947,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ pPager->pInJournal = 0; pPager->nRec = 0; if( rc==SQLITE_OK ){ - if( pagerFlushOnCommit(pPager, bCommit) ){ + if( MEMDB || pagerFlushOnCommit(pPager, bCommit) ){ sqlite3PcacheCleanAll(pPager->pPCache); }else{ sqlite3PcacheClearWritable(pPager->pPCache); @@ -49713,6 +50346,7 @@ static void pagerFixMaplimit(Pager *pPager){ sqlite3_int64 sz; sz = pPager->szMmap; pPager->bUseFetch = (sz>0); + setGetterMethod(pPager); sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_MMAP_SIZE, &sz); } #endif @@ -50209,6 +50843,7 @@ static int pagerSyncHotJournal(Pager *pPager){ return rc; } +#if SQLITE_MAX_MMAP_SIZE>0 /* ** Obtain a reference to a memory mapped page object for page number pgno. ** The new object will use the pointer pData, obtained from xFetch(). @@ -50231,7 +50866,8 @@ static int pagerAcquireMapPage( *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; p->pDirty = 0; - memset(p->pExtra, 0, pPager->nExtra); + assert( pPager->nExtra>=8 ); + memset(p->pExtra, 0, 8); }else{ *ppPage = p = (PgHdr *)sqlite3MallocZero(sizeof(PgHdr) + pPager->nExtra); if( p==0 ){ @@ -50256,6 +50892,7 @@ static int pagerAcquireMapPage( return SQLITE_OK; } +#endif /* ** Release a reference to page pPg. pPg must have been returned by an @@ -50298,9 +50935,10 @@ static void pagerFreeMapHdrs(Pager *pPager){ ** a hot journal may be left in the filesystem but no error is returned ** to the caller. */ -SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){ +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ u8 *pTmp = (u8 *)pPager->pTmpSpace; + assert( db || pagerUseWal(pPager)==0 ); assert( assert_pager_state(pPager) ); disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); @@ -50308,7 +50946,10 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager){ /* pPager->errCode = 0; */ pPager->exclusiveMode = 0; #ifndef SQLITE_OMIT_WAL - sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, pPager->pageSize, pTmp); + assert( db || pPager->pWal==0 ); + sqlite3WalClose(pPager->pWal, db, pPager->ckptSyncFlags, pPager->pageSize, + (db && (db->flags & SQLITE_NoCkptOnClose) ? 0 : pTmp) + ); pPager->pWal = 0; #endif pager_reset(pPager); @@ -50827,7 +51468,9 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){ ** ** The nExtra parameter specifies the number of bytes of space allocated ** along with each page reference. This space is available to the user -** via the sqlite3PagerGetExtra() API. +** via the sqlite3PagerGetExtra() API. When a new page is allocated, the +** first 8 bytes of this space are zeroed but the remainder is uninitialized. +** (The extra space is used by btree as the MemPage object.) ** ** The flags argument is used to specify properties that affect the ** operation of the pager. It should be passed some bitwise combination @@ -51057,8 +51700,8 @@ SQLITE_PRIVATE int sqlite3PagerOpen( /* Initialize the PCache object. */ if( rc==SQLITE_OK ){ - assert( nExtra<1000 ); nExtra = ROUND8(nExtra); + assert( nExtra>=8 && nExtra<1000 ); rc = sqlite3PcacheOpen(szPageDflt, nExtra, !memDb, !memDb?pagerStress:0, (void *)pPager, pPager->pPCache); } @@ -51123,6 +51766,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( /* pPager->xBusyHandler = 0; */ /* pPager->pBusyHandlerArg = 0; */ pPager->xReiniter = xReinit; + setGetterMethod(pPager); /* memset(pPager->aHash, 0, sizeof(pPager->aHash)); */ /* pPager->szMmap = SQLITE_DEFAULT_MMAP_SIZE // will be set by btree.c */ @@ -51536,10 +52180,17 @@ static void pagerUnlockIfUnused(Pager *pPager){ } /* -** Acquire a reference to page number pgno in pager pPager (a page -** reference has type DbPage*). If the requested reference is +** The page getter methods each try to acquire a reference to a +** page with page number pgno. If the requested reference is ** successfully obtained, it is copied to *ppPage and SQLITE_OK returned. ** +** There are different implementations of the getter method depending +** on the current state of the pager. +** +** getPageNormal() -- The normal getter +** getPageError() -- Used if the pager is in an error state +** getPageMmap() -- Used if memory-mapped I/O is enabled +** ** If the requested page is already in the cache, it is returned. ** Otherwise, a new page object is allocated and populated with data ** read from the database file. In some cases, the pcache module may @@ -51551,14 +52202,14 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** already in the cache when this function is called, then the extra ** data is left as it was when the page object was last used. ** -** If the database image is smaller than the requested page or if a -** non-zero value is passed as the noContent parameter and the +** If the database image is smaller than the requested page or if +** the flags parameter contains the PAGER_GET_NOCONTENT bit and the ** requested page is not already stored in the cache, then no ** actual disk read occurs. In this case the memory image of the ** page is initialized to all zeros. ** -** If noContent is true, it means that we do not care about the contents -** of the page. This occurs in two scenarios: +** If PAGER_GET_NOCONTENT is true, it means that we do not care about +** the contents of the page. This occurs in two scenarios: ** ** a) When reading a free-list leaf page from the database, and ** @@ -51566,8 +52217,8 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** a new page into the cache to be filled with the data read ** from the savepoint journal. ** -** If noContent is true, then the data returned is zeroed instead of -** being read from the database. Additionally, the bits corresponding +** If PAGER_GET_NOCONTENT is true, then the data returned is zeroed instead +** of being read from the database. Additionally, the bits corresponding ** to pgno in Pager.pInJournal (bitvec of pages already written to the ** journal file) and the PagerSavepoint.pInSavepoint bitvecs of any open ** savepoints are set. This means if the page is made writable at any @@ -51585,106 +52236,39 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** Since Lookup() never goes to disk, it never has to deal with locks ** or journal files. */ -SQLITE_PRIVATE int sqlite3PagerGet( +static int getPageNormal( Pager *pPager, /* The pager open on the database file */ Pgno pgno, /* Page number to fetch */ DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ int rc = SQLITE_OK; - PgHdr *pPg = 0; - u32 iFrame = 0; /* Frame to read from WAL file */ - const int noContent = (flags & PAGER_GET_NOCONTENT); - - /* It is acceptable to use a read-only (mmap) page for any page except - ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY - ** flag was specified by the caller. And so long as the db is not a - ** temporary or in-memory database. */ - const int bMmapOk = (pgno>1 && USEFETCH(pPager) - && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) -#ifdef SQLITE_HAS_CODEC - && pPager->xCodec==0 -#endif - ); + PgHdr *pPg; + u8 noContent; /* True if PAGER_GET_NOCONTENT is set */ + sqlite3_pcache_page *pBase; - /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here - ** allows the compiler optimizer to reuse the results of the "pgno>1" - ** test in the previous statement, and avoid testing pgno==0 in the - ** common case where pgno is large. */ - if( pgno<=1 && pgno==0 ){ - return SQLITE_CORRUPT_BKPT; - } + assert( pPager->errCode==SQLITE_OK ); assert( pPager->eState>=PAGER_READER ); assert( assert_pager_state(pPager) ); - assert( noContent==0 || bMmapOk==0 ); - assert( pPager->hasHeldSharedLock==1 ); - /* If the pager is in the error state, return an error immediately. - ** Otherwise, request the page from the PCache layer. */ - if( pPager->errCode!=SQLITE_OK ){ - rc = pPager->errCode; - }else{ - if( bMmapOk && pagerUseWal(pPager) ){ - rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); - if( rc!=SQLITE_OK ) goto pager_acquire_err; - } - - if( bMmapOk && iFrame==0 ){ - void *pData = 0; - - rc = sqlite3OsFetch(pPager->fd, - (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData - ); - - if( rc==SQLITE_OK && pData ){ - if( pPager->eState>PAGER_READER || pPager->tempFile ){ - pPg = sqlite3PagerLookup(pPager, pgno); - } - if( pPg==0 ){ - rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); - }else{ - sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); - } - if( pPg ){ - assert( rc==SQLITE_OK ); - *ppPage = pPg; - return SQLITE_OK; - } - } - if( rc!=SQLITE_OK ){ - goto pager_acquire_err; - } - } - - { - sqlite3_pcache_page *pBase; - pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); - if( pBase==0 ){ - rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); - if( rc!=SQLITE_OK ) goto pager_acquire_err; - if( pBase==0 ){ - pPg = *ppPage = 0; - rc = SQLITE_NOMEM_BKPT; - goto pager_acquire_err; - } - } - pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); - assert( pPg!=0 ); - } - } - - if( rc!=SQLITE_OK ){ - /* Either the call to sqlite3PcacheFetch() returned an error or the - ** pager was already in the error-state when this function was called. - ** Set pPg to 0 and jump to the exception handler. */ + if( pgno==0 ) return SQLITE_CORRUPT_BKPT; + pBase = sqlite3PcacheFetch(pPager->pPCache, pgno, 3); + if( pBase==0 ){ pPg = 0; - goto pager_acquire_err; + rc = sqlite3PcacheFetchStress(pPager->pPCache, pgno, &pBase); + if( rc!=SQLITE_OK ) goto pager_acquire_err; + if( pBase==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto pager_acquire_err; + } } + pPg = *ppPage = sqlite3PcacheFetchFinish(pPager->pPCache, pgno, pBase); assert( pPg==(*ppPage) ); assert( pPg->pgno==pgno ); assert( pPg->pPager==pPager || pPg->pPager==0 ); + noContent = (flags & PAGER_GET_NOCONTENT)!=0; if( pPg->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ @@ -51694,17 +52278,18 @@ SQLITE_PRIVATE int sqlite3PagerGet( }else{ /* The pager cache has created a new page. Its content needs to - ** be initialized. */ - - pPg->pPager = pPager; - - /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page - ** number greater than this, or the unused locking-page, is requested. */ + ** be initialized. But first some error checks: + ** + ** (1) The maximum page number is 2^31 + ** (2) Never try to fetch the locking page + */ if( pgno>PAGER_MAX_PGNO || pgno==PAGER_MJ_PGNO(pPager) ){ rc = SQLITE_CORRUPT_BKPT; goto pager_acquire_err; } + pPg->pPager = pPager; + assert( !isOpen(pPager->fd) || !MEMDB ); if( !isOpen(pPager->fd) || pPager->dbSizepPager->mxPgno ){ @@ -51730,7 +52315,8 @@ SQLITE_PRIVATE int sqlite3PagerGet( memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ - if( pagerUseWal(pPager) && bMmapOk==0 ){ + u32 iFrame = 0; /* Frame to read from WAL file */ + if( pagerUseWal(pPager) ){ rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); if( rc!=SQLITE_OK ) goto pager_acquire_err; } @@ -51743,7 +52329,6 @@ SQLITE_PRIVATE int sqlite3PagerGet( } pager_set_pagehash(pPg); } - return SQLITE_OK; pager_acquire_err: @@ -51752,11 +52337,109 @@ SQLITE_PRIVATE int sqlite3PagerGet( sqlite3PcacheDrop(pPg); } pagerUnlockIfUnused(pPager); - *ppPage = 0; return rc; } +#if SQLITE_MAX_MMAP_SIZE>0 +/* The page getter for when memory-mapped I/O is enabled */ +static int getPageMMap( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + int rc = SQLITE_OK; + PgHdr *pPg = 0; + u32 iFrame = 0; /* Frame to read from WAL file */ + + /* It is acceptable to use a read-only (mmap) page for any page except + ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY + ** flag was specified by the caller. And so long as the db is not a + ** temporary or in-memory database. */ + const int bMmapOk = (pgno>1 + && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) + ); + + assert( USEFETCH(pPager) ); +#ifdef SQLITE_HAS_CODEC + assert( pPager->xCodec==0 ); +#endif + + /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here + ** allows the compiler optimizer to reuse the results of the "pgno>1" + ** test in the previous statement, and avoid testing pgno==0 in the + ** common case where pgno is large. */ + if( pgno<=1 && pgno==0 ){ + return SQLITE_CORRUPT_BKPT; + } + assert( pPager->eState>=PAGER_READER ); + assert( assert_pager_state(pPager) ); + assert( pPager->hasHeldSharedLock==1 ); + assert( pPager->errCode==SQLITE_OK ); + + if( bMmapOk && pagerUseWal(pPager) ){ + rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iFrame); + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + if( bMmapOk && iFrame==0 ){ + void *pData = 0; + rc = sqlite3OsFetch(pPager->fd, + (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData + ); + if( rc==SQLITE_OK && pData ){ + if( pPager->eState>PAGER_READER || pPager->tempFile ){ + pPg = sqlite3PagerLookup(pPager, pgno); + } + if( pPg==0 ){ + rc = pagerAcquireMapPage(pPager, pgno, pData, &pPg); + }else{ + sqlite3OsUnfetch(pPager->fd, (i64)(pgno-1)*pPager->pageSize, pData); + } + if( pPg ){ + assert( rc==SQLITE_OK ); + *ppPage = pPg; + return SQLITE_OK; + } + } + if( rc!=SQLITE_OK ){ + *ppPage = 0; + return rc; + } + } + return getPageNormal(pPager, pgno, ppPage, flags); +} +#endif /* SQLITE_MAX_MMAP_SIZE>0 */ + +/* The page getter method for when the pager is an error state */ +static int getPageError( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + UNUSED_PARAMETER(pgno); + UNUSED_PARAMETER(flags); + assert( pPager->errCode!=SQLITE_OK ); + *ppPage = 0; + return pPager->errCode; +} + + +/* Dispatch all page fetch requests to the appropriate getter method. +*/ +SQLITE_PRIVATE int sqlite3PagerGet( + Pager *pPager, /* The pager open on the database file */ + Pgno pgno, /* Page number to fetch */ + DbPage **ppPage, /* Write a pointer to the page here */ + int flags /* PAGER_GET_XXX flags */ +){ + return pPager->xGet(pPager, pgno, ppPage, flags); +} + /* ** Acquire a page if it is already in the in-memory cache. Do ** not read the page from disk. Return a pointer to the page, @@ -52230,11 +52913,11 @@ SQLITE_PRIVATE int sqlite3PagerWrite(PgHdr *pPg){ assert( (pPg->flags & PGHDR_MMAP)==0 ); assert( pPager->eState>=PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); - if( pPager->errCode ){ - return pPager->errCode; - }else if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ + if( (pPg->flags & PGHDR_WRITEABLE)!=0 && pPager->dbSize>=pPg->pgno ){ if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg); return SQLITE_OK; + }else if( pPager->errCode ){ + return pPager->errCode; }else if( pPager->sectorSize > (u32)pPager->pageSize ){ assert( pPager->tempFile==0 ); return pagerWriteLargeSector(pPg); @@ -52729,6 +53412,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){ */ pPager->errCode = SQLITE_ABORT; pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); return rc; } }else{ @@ -52933,7 +53617,11 @@ SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ ** savepoint. If no errors occur, SQLITE_OK is returned. */ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ - int rc = pPager->errCode; /* Return code */ + int rc = pPager->errCode; + +#ifdef SQLITE_ENABLE_ZIPVFS + if( op==SAVEPOINT_RELEASE ) rc = SQLITE_OK; +#endif assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || op==SAVEPOINT_ROLLBACK ); @@ -52974,6 +53662,21 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ rc = pagerPlaybackSavepoint(pPager, pSavepoint); assert(rc!=SQLITE_DONE); } + +#ifdef SQLITE_ENABLE_ZIPVFS + /* If the cache has been modified but the savepoint cannot be rolled + ** back journal_mode=off, put the pager in the error state. This way, + ** if the VFS used by this pager includes ZipVFS, the entire transaction + ** can be rolled back at the ZipVFS level. */ + else if( + pPager->journalMode==PAGER_JOURNALMODE_OFF + && pPager->eState>=PAGER_WRITER_CACHEMOD + ){ + pPager->errCode = SQLITE_ABORT; + pPager->eState = PAGER_ERROR; + setGetterMethod(pPager); + } +#endif } return rc; @@ -53044,6 +53747,7 @@ SQLITE_PRIVATE void sqlite3PagerSetCodec( pPager->xCodecSizeChng = xCodecSizeChng; pPager->xCodecFree = xCodecFree; pPager->pCodec = pCodec; + setGetterMethod(pPager); pagerReportSize(pPager); } SQLITE_PRIVATE void *sqlite3PagerGetCodec(Pager *pPager){ @@ -53453,10 +54157,16 @@ SQLITE_PRIVATE void sqlite3PagerClearCache(Pager *pPager){ ** ** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. */ -SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, int eMode, int *pnLog, int *pnCkpt){ +SQLITE_PRIVATE int sqlite3PagerCheckpoint( + Pager *pPager, /* Checkpoint on this pager */ + sqlite3 *db, /* Db handle used to check for interrupts */ + int eMode, /* Type of checkpoint */ + int *pnLog, /* OUT: Final number of frames in log */ + int *pnCkpt /* OUT: Final number of checkpointed frames */ +){ int rc = SQLITE_OK; if( pPager->pWal ){ - rc = sqlite3WalCheckpoint(pPager->pWal, eMode, + rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), pPager->pBusyHandlerArg, pPager->ckptSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, @@ -53588,7 +54298,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenWal( ** error (SQLITE_BUSY) is returned and the log connection is not closed. ** If successful, the EXCLUSIVE lock is not released before returning. */ -SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){ +SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ int rc = SQLITE_OK; assert( pPager->journalMode==PAGER_JOURNALMODE_WAL ); @@ -53616,7 +54326,7 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager){ if( rc==SQLITE_OK && pPager->pWal ){ rc = pagerExclusiveLock(pPager); if( rc==SQLITE_OK ){ - rc = sqlite3WalClose(pPager->pWal, pPager->ckptSyncFlags, + rc = sqlite3WalClose(pPager->pWal, db, pPager->ckptSyncFlags, pPager->pageSize, (u8*)pPager->pTmpSpace); pPager->pWal = 0; pagerFixMaplimit(pPager); @@ -53653,6 +54363,20 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSn } return rc; } + +/* +** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this +** is not a WAL database, return an error. +*/ +SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager){ + int rc; + if( pPager->pWal ){ + rc = sqlite3WalSnapshotRecover(pPager->pWal); + }else{ + rc = SQLITE_ERROR; + } + return rc; +} #endif /* SQLITE_ENABLE_SNAPSHOT */ #endif /* !SQLITE_OMIT_WAL */ @@ -55399,6 +56123,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ */ static int walCheckpoint( Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check for interrupts on this handle */ int eMode, /* One of PASSIVE, FULL or RESTART */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -55493,6 +56218,10 @@ static int walCheckpoint( while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ i64 iOffset; assert( walFramePgno(pWal, iFrame)==iDbpage ); + if( db->u1.isInterrupted ){ + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; + break; + } if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ continue; } @@ -55597,6 +56326,7 @@ static void walLimitSize(Wal *pWal, i64 nMax){ */ SQLITE_PRIVATE int sqlite3WalClose( Wal *pWal, /* Wal to close */ + sqlite3 *db, /* For interrupt flag */ int sync_flags, /* Flags to pass to OsSync() (or 0) */ int nBuf, u8 *zBuf /* Buffer of at least nBuf bytes */ @@ -55613,13 +56343,14 @@ SQLITE_PRIVATE int sqlite3WalClose( ** ** The EXCLUSIVE lock is not released before returning. */ - rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE); - if( rc==SQLITE_OK ){ + if( zBuf!=0 + && SQLITE_OK==(rc = sqlite3OsLock(pWal->pDbFd, SQLITE_LOCK_EXCLUSIVE)) + ){ if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint( - pWal, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 + rc = sqlite3WalCheckpoint(pWal, db, + SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 ); if( rc==SQLITE_OK ){ int bPersist = -1; @@ -56048,6 +56779,84 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ return rc; } +#ifdef SQLITE_ENABLE_SNAPSHOT +/* +** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted +** variable so that older snapshots can be accessed. To do this, loop +** through all wal frames from nBackfillAttempted to (nBackfill+1), +** comparing their content to the corresponding page with the database +** file, if any. Set nBackfillAttempted to the frame number of the +** first frame for which the wal file content matches the db file. +** +** This is only really safe if the file-system is such that any page +** writes made by earlier checkpointers were atomic operations, which +** is not always true. It is also possible that nBackfillAttempted +** may be left set to a value larger than expected, if a wal frame +** contains content that duplicate of an earlier version of the same +** page. +** +** SQLITE_OK is returned if successful, or an SQLite error code if an +** error occurs. It is not an error if nBackfillAttempted cannot be +** decreased at all. +*/ +SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ + int rc; + + assert( pWal->readLock>=0 ); + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + int szPage = (int)pWal->szPage; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + void *pBuf1 = sqlite3_malloc(szPage); + void *pBuf2 = sqlite3_malloc(szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){ + volatile ht_slot *dummy; + volatile u32 *aPgno; /* Array of page numbers */ + u32 iZero; /* Frame corresponding to aPgno[0] */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &dummy, &aPgno, &iZero); + if( rc!=SQLITE_OK ) break; + pgno = aPgno[i-iZero]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); + } + walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); + } + + return rc; +} +#endif /* SQLITE_ENABLE_SNAPSHOT */ + /* ** Begin a read transaction on the database. ** @@ -56110,7 +56919,11 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ ** has not yet set the pInfo->nBackfillAttempted variable to indicate ** its intent. To avoid the race condition this leads to, ensure that ** there is no checkpointer process by taking a shared CKPT lock - ** before checking pInfo->nBackfillAttempted. */ + ** before checking pInfo->nBackfillAttempted. + ** + ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing + ** this already? + */ rc = walLockShared(pWal, WAL_CKPT_LOCK); if( rc==SQLITE_OK ){ @@ -56867,6 +57680,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( */ SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Wal connection */ + sqlite3 *db, /* Check this handle's interrupt flag */ int eMode, /* PASSIVE, FULL, RESTART, or TRUNCATE */ int (*xBusy)(void*), /* Function to call when busy */ void *pBusyArg, /* Context argument for xBusyHandler */ @@ -56941,7 +57755,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; }else{ - rc = walCheckpoint(pWal, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); } /* If no error occurred, set the output variables. */ @@ -57061,9 +57875,14 @@ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){ SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapshot){ int rc = SQLITE_OK; WalIndexHdr *pRet; + static const u32 aZero[4] = { 0, 0, 0, 0 }; assert( pWal->readLock>=0 && pWal->writeLock==0 ); + if( memcmp(&pWal->hdr.aFrameCksum[0],aZero,16)==0 ){ + *ppSnapshot = 0; + return SQLITE_ERROR; + } pRet = (WalIndexHdr*)sqlite3_malloc(sizeof(WalIndexHdr)); if( pRet==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -57401,37 +58220,39 @@ typedef struct CellInfo CellInfo; #define PTF_LEAF 0x08 /* -** As each page of the file is loaded into memory, an instance of the following -** structure is appended and initialized to zero. This structure stores -** information about the page that is decoded from the raw file page. +** An instance of this object stores information about each a single database +** page that has been loaded into memory. The information in this object +** is derived from the raw on-disk page content. ** -** The pParent field points back to the parent page. This allows us to -** walk up the BTree from any leaf to the root. Care must be taken to -** unref() the parent page pointer when this page is no longer referenced. -** The pageDestructor() routine handles that chore. +** As each database page is loaded into memory, the pager allocats an +** instance of this object and zeros the first 8 bytes. (This is the +** "extra" information associated with each page of the pager.) ** ** Access to all fields of this structure is controlled by the mutex ** stored in MemPage.pBt->mutex. */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ - u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ + u8 bBusy; /* Prevent endless loops on corrupt database files */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ + Pgno pgno; /* Page number for this page */ + /* Only the first 8 bytes (above) are zeroed by pager.c when a new page + ** is allocated. All fields that follow must be initialized before use */ u8 leaf; /* True if a leaf page */ u8 hdrOffset; /* 100 for page 1. 0 otherwise */ u8 childPtrSize; /* 0 if leaf==1. 4 if leaf==0 */ u8 max1bytePayload; /* min(maxLocal,127) */ - u8 bBusy; /* Prevent endless loops on corrupt database files */ + u8 nOverflow; /* Number of overflow cell bodies in aCell[] */ u16 maxLocal; /* Copy of BtShared.maxLocal or BtShared.maxLeaf */ u16 minLocal; /* Copy of BtShared.minLocal or BtShared.minLeaf */ u16 cellOffset; /* Index in aData of first cell pointer */ u16 nFree; /* Number of free bytes on the page */ u16 nCell; /* Number of cells on this page, local and ovfl */ u16 maskPage; /* Mask for page offset */ - u16 aiOvfl[5]; /* Insert the i-th overflow cell before the aiOvfl-th + u16 aiOvfl[4]; /* Insert the i-th overflow cell before the aiOvfl-th ** non-overflow cell */ - u8 *apOvfl[5]; /* Pointers to the body of overflow cells */ + u8 *apOvfl[4]; /* Pointers to the body of overflow cells */ BtShared *pBt; /* Pointer to BtShared that this page is part of */ u8 *aData; /* Pointer to disk image of the page data */ u8 *aDataEnd; /* One byte past the end of usable data */ @@ -57440,16 +58261,8 @@ struct MemPage { DbPage *pDbPage; /* Pager page handle */ u16 (*xCellSize)(MemPage*,u8*); /* cellSizePtr method */ void (*xParseCell)(MemPage*,u8*,CellInfo*); /* btreeParseCell method */ - Pgno pgno; /* Page number for this page */ }; -/* -** The in-memory image of a disk page has the auxiliary information appended -** to the end. EXTRA_SIZE is the number of bytes of space needed to hold -** that extra information. -*/ -#define EXTRA_SIZE sizeof(MemPage) - /* ** A linked list of the following structures is stored at BtShared.pLock. ** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor @@ -57840,11 +58653,9 @@ struct IntegrityCk { */ #if SQLITE_BYTEORDER==4321 # define get2byteAligned(x) (*(u16*)(x)) -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && GCC_VERSION>=4008000 +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4008000 # define get2byteAligned(x) __builtin_bswap16(*(u16*)(x)) -#elif SQLITE_BYTEORDER==1234 && !defined(SQLITE_DISABLE_INTRINSIC) \ - && defined(_MSC_VER) && _MSC_VER>=1300 +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 # define get2byteAligned(x) _byteswap_ushort(*(u16*)(x)) #else # define get2byteAligned(x) ((x)[0]<<8 | (x)[1]) @@ -58019,16 +58830,24 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){ ** two or more btrees in common both try to lock all their btrees ** at the same instant. */ -SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ +static void SQLITE_NOINLINE btreeEnterAll(sqlite3 *db){ int i; + int skipOk = 1; Btree *p; assert( sqlite3_mutex_held(db->mutex) ); for(i=0; inDb; i++){ p = db->aDb[i].pBt; - if( p ) sqlite3BtreeEnter(p); + if( p && p->sharable ){ + sqlite3BtreeEnter(p); + skipOk = 0; + } } + db->skipBtreeMutex = skipOk; } -SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){ +SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ + if( db->skipBtreeMutex==0 ) btreeEnterAll(db); +} +static void SQLITE_NOINLINE btreeLeaveAll(sqlite3 *db){ int i; Btree *p; assert( sqlite3_mutex_held(db->mutex) ); @@ -58037,6 +58856,9 @@ SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){ if( p ) sqlite3BtreeLeave(p); } } +SQLITE_PRIVATE void sqlite3BtreeLeaveAll(sqlite3 *db){ + if( db->skipBtreeMutex==0 ) btreeLeaveAll(db); +} #ifndef NDEBUG /* @@ -58768,7 +59590,7 @@ static int saveCursorKey(BtCursor *pCur){ pCur->nKey = sqlite3BtreePayloadSize(pCur); pKey = sqlite3Malloc( pCur->nKey ); if( pKey ){ - rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey); + rc = sqlite3BtreePayload(pCur, 0, (int)pCur->nKey, pKey); if( rc==SQLITE_OK ){ pCur->pKey = pKey; }else{ @@ -58899,26 +59721,23 @@ static int btreeMoveto( ){ int rc; /* Status code */ UnpackedRecord *pIdxKey; /* Unpacked index key */ - char aSpace[200]; /* Temp space for pIdxKey - to avoid a malloc */ - char *pFree = 0; if( pKey ){ assert( nKey==(i64)(int)nKey ); - pIdxKey = sqlite3VdbeAllocUnpackedRecord( - pCur->pKeyInfo, aSpace, sizeof(aSpace), &pFree - ); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pCur->pKeyInfo); if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; sqlite3VdbeRecordUnpack(pCur->pKeyInfo, (int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 ){ - sqlite3DbFree(pCur->pKeyInfo->db, pFree); - return SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_BKPT; + goto moveto_done; } }else{ pIdxKey = 0; } rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); - if( pFree ){ - sqlite3DbFree(pCur->pKeyInfo->db, pFree); +moveto_done: + if( pIdxKey ){ + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); } return rc; } @@ -59456,17 +60275,18 @@ static void ptrmapPutOvflPtr(MemPage *pPage, u8 *pCell, int *pRC){ /* -** Defragment the page given. All Cells are moved to the -** end of the page and all free space is collected into one -** big FreeBlk that occurs in between the header and cell -** pointer array and the cell content area. +** Defragment the page given. This routine reorganizes cells within the +** page so that there are no free-blocks on the free-block list. +** +** Parameter nMaxFrag is the maximum amount of fragmented space that may be +** present in the page after this routine returns. ** ** EVIDENCE-OF: R-44582-60138 SQLite may from time to time reorganize a ** b-tree page so that there are no freeblocks or fragment bytes, all ** unused bytes are contained in the unallocated space region, and all ** cells are packed tightly at the end of the page. */ -static int defragmentPage(MemPage *pPage){ +static int defragmentPage(MemPage *pPage, int nMaxFrag){ int i; /* Loop counter */ int pc; /* Address of the i-th cell */ int hdr; /* Offset to the page header */ @@ -59481,7 +60301,6 @@ static int defragmentPage(MemPage *pPage){ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ - assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt!=0 ); assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); @@ -59493,9 +60312,56 @@ static int defragmentPage(MemPage *pPage){ cellOffset = pPage->cellOffset; nCell = pPage->nCell; assert( nCell==get2byte(&data[hdr+3]) ); + iCellFirst = cellOffset + 2*nCell; usableSize = pPage->pBt->usableSize; + + /* This block handles pages with two or fewer free blocks and nMaxFrag + ** or fewer fragmented bytes. In this case it is faster to move the + ** two (or one) blocks of cells using memmove() and add the required + ** offsets to each pointer in the cell-pointer array than it is to + ** reconstruct the entire page. */ + if( (int)data[hdr+7]<=nMaxFrag ){ + int iFree = get2byte(&data[hdr+1]); + if( iFree ){ + int iFree2 = get2byte(&data[iFree]); + + /* pageFindSlot() has already verified that free blocks are sorted + ** in order of offset within the page, and that no block extends + ** past the end of the page. Provided the two free slots do not + ** overlap, this guarantees that the memmove() calls below will not + ** overwrite the usableSize byte buffer, even if the database page + ** is corrupt. */ + assert( iFree2==0 || iFree2>iFree ); + assert( iFree+get2byte(&data[iFree+2]) <= usableSize ); + assert( iFree2==0 || iFree2+get2byte(&data[iFree2+2]) <= usableSize ); + + if( 0==iFree2 || (data[iFree2]==0 && data[iFree2+1]==0) ){ + u8 *pEnd = &data[cellOffset + nCell*2]; + u8 *pAddr; + int sz2 = 0; + int sz = get2byte(&data[iFree+2]); + int top = get2byte(&data[hdr+5]); + if( iFree2 ){ + if( iFree+sz>iFree2 ) return SQLITE_CORRUPT_BKPT; + sz2 = get2byte(&data[iFree2+2]); + assert( iFree+sz+sz2+iFree2-(iFree+sz) <= usableSize ); + memmove(&data[iFree+sz+sz2], &data[iFree+sz], iFree2-(iFree+sz)); + sz += sz2; + } + cbrk = top+sz; + assert( cbrk+(iFree-top) <= usableSize ); + memmove(&data[cbrk], &data[top], iFree-top); + for(pAddr=&data[cellOffset]; pAddrnFree ){ + return SQLITE_CORRUPT_BKPT; + } assert( cbrk>=iCellFirst ); put2byte(&data[hdr+5], cbrk); data[hdr+1] = 0; data[hdr+2] = 0; - data[hdr+7] = 0; memset(&data[iCellFirst], 0, cbrk-iCellFirst); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); - if( cbrk-iCellFirst!=pPage->nFree ){ - return SQLITE_CORRUPT_BKPT; - } return SQLITE_OK; } @@ -59676,10 +60544,10 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ testcase( gap+2+nByte==top ); if( gap+2+nByte>top ){ assert( pPage->nCell>0 || CORRUPT_DB ); - rc = defragmentPage(pPage); + rc = defragmentPage(pPage, MIN(4, pPage->nFree - (2+nByte))); if( rc ) return rc; top = get2byteNotZero(&data[hdr+5]); - assert( gap+nByte<=top ); + assert( gap+2+nByte<=top ); } @@ -59741,8 +60609,11 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ if( data[iPtr+1]==0 && data[iPtr]==0 ){ iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ }else{ - while( (iFreeBlk = get2byte(&data[iPtr]))>0 && iFreeBlkiLast ) return SQLITE_CORRUPT_BKPT; @@ -59876,7 +60747,7 @@ static int btreeInitPage(MemPage *pPage){ assert( pPage->aData == sqlite3PagerGetData(pPage->pDbPage) ); if( !pPage->isInit ){ - u16 pc; /* Address of a freeblock within pPage->aData[] */ + int pc; /* Address of a freeblock within pPage->aData[] */ u8 hdr; /* Offset to beginning of page header */ u8 *data; /* Equal to pPage->aData */ BtShared *pBt; /* The main btree structure */ @@ -59956,25 +60827,30 @@ static int btreeInitPage(MemPage *pPage){ ** freeblocks. */ pc = get2byte(&data[hdr+1]); nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ - while( pc>0 ){ - u16 next, size; - if( pciCellLast ){ + if( pc>0 ){ + u32 next, size; + if( pc0 && next<=pc+size+3) || pc+size>usableSize ){ - /* Free blocks must be in ascending order. And the last byte of - ** the free-block must lie on the database page. */ - return SQLITE_CORRUPT_BKPT; + while( 1 ){ + if( pc>iCellLast ){ + return SQLITE_CORRUPT_BKPT; /* Freeblock off the end of the page */ + } + next = get2byte(&data[pc]); + size = get2byte(&data[pc+2]); + nFree = nFree + size; + if( next<=pc+size+3 ) break; + pc = next; + } + if( next>0 ){ + return SQLITE_CORRUPT_BKPT; /* Freeblock not in ascending order */ + } + if( pc+size>(unsigned int)usableSize ){ + return SQLITE_CORRUPT_BKPT; /* Last freeblock extends past page end */ } - nFree = nFree + size; - pc = next; } /* At this point, nFree contains the sum of the offset to the start @@ -60415,7 +61291,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( goto btree_open_out; } rc = sqlite3PagerOpen(pVfs, &pBt->pPager, zFilename, - EXTRA_SIZE, flags, vfsFlags, pageReinit); + sizeof(MemPage), flags, vfsFlags, pageReinit); if( rc==SQLITE_OK ){ sqlite3PagerSetMmapLimit(pBt->pPager, db->szMmap); rc = sqlite3PagerReadFileheader(pBt->pPager,sizeof(zDbHeader),zDbHeader); @@ -60528,12 +61404,14 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( btree_open_out: if( rc!=SQLITE_OK ){ if( pBt && pBt->pPager ){ - sqlite3PagerClose(pBt->pPager); + sqlite3PagerClose(pBt->pPager, 0); } sqlite3_free(pBt); sqlite3_free(p); *ppBtree = 0; }else{ + sqlite3_file *pFile; + /* If the B-Tree was successfully opened, set the pager-cache size to the ** default value. Except, when opening on an existing shared pager-cache, ** do not change the pager-cache size. @@ -60541,6 +61419,11 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( if( sqlite3BtreeSchema(p, 0, 0)==0 ){ sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE); } + + pFile = sqlite3PagerFile(pBt->pPager); + if( pFile->pMethods ){ + sqlite3OsFileControlHint(pFile, SQLITE_FCNTL_PDB, (void*)&pBt->db); + } } if( mutexOpen ){ assert( sqlite3_mutex_held(mutexOpen) ); @@ -60670,7 +61553,7 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ ** Clean out and delete the BtShared object. */ assert( !pBt->pCursor ); - sqlite3PagerClose(pBt->pPager); + sqlite3PagerClose(pBt->pPager, p->db); if( pBt->xFreeSchema && pBt->pSchema ){ pBt->xFreeSchema(pBt->pSchema); } @@ -60936,6 +61819,31 @@ SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){ #endif } +/* +** If the user has not set the safety-level for this database connection +** using "PRAGMA synchronous", and if the safety-level is not already +** set to the value passed to this function as the second parameter, +** set it so. +*/ +#if SQLITE_DEFAULT_SYNCHRONOUS!=SQLITE_DEFAULT_WAL_SYNCHRONOUS +static void setDefaultSyncFlag(BtShared *pBt, u8 safety_level){ + sqlite3 *db; + Db *pDb; + if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){ + while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; } + if( pDb->bSyncSet==0 + && pDb->safety_level!=safety_level + && pDb!=&db->aDb[1] + ){ + pDb->safety_level = safety_level; + sqlite3PagerSetFlags(pBt->pPager, + pDb->safety_level | (db->flags & PAGER_FLAGS_MASK)); + } + } +} +#else +# define setDefaultSyncFlag(pBt,safety_level) +#endif /* ** Get a reference to pPage1 of the database file. This will @@ -61009,26 +61917,15 @@ static int lockBtree(BtShared *pBt){ if( rc!=SQLITE_OK ){ goto page1_init_failed; }else{ -#if SQLITE_DEFAULT_SYNCHRONOUS!=SQLITE_DEFAULT_WAL_SYNCHRONOUS - sqlite3 *db; - Db *pDb; - if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){ - while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; } - if( pDb->bSyncSet==0 - && pDb->safety_level==SQLITE_DEFAULT_SYNCHRONOUS+1 - ){ - pDb->safety_level = SQLITE_DEFAULT_WAL_SYNCHRONOUS+1; - sqlite3PagerSetFlags(pBt->pPager, - pDb->safety_level | (db->flags & PAGER_FLAGS_MASK)); - } - } -#endif + setDefaultSyncFlag(pBt, SQLITE_DEFAULT_WAL_SYNCHRONOUS+1); if( isOpen==0 ){ releasePage(pPage1); return SQLITE_OK; } } rc = SQLITE_NOTADB; + }else{ + setDefaultSyncFlag(pBt, SQLITE_DEFAULT_SYNCHRONOUS+1); } #endif @@ -61417,14 +62314,11 @@ static int setChildPtrmaps(MemPage *pPage){ int nCell; /* Number of cells in page pPage */ int rc; /* Return code */ BtShared *pBt = pPage->pBt; - u8 isInitOrig = pPage->isInit; Pgno pgno = pPage->pgno; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); rc = btreeInitPage(pPage); - if( rc!=SQLITE_OK ){ - goto set_child_ptrmaps_out; - } + if( rc!=SQLITE_OK ) return rc; nCell = pPage->nCell; for(i=0; iisInit = isInitOrig; return rc; } @@ -61472,7 +62364,6 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } put4byte(pPage->aData, iTo); }else{ - u8 isInitOrig = pPage->isInit; int i; int nCell; int rc; @@ -61486,12 +62377,14 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ if( eType==PTRMAP_OVERFLOW1 ){ CellInfo info; pPage->xParseCell(pPage, pCell, &info); - if( info.nLocalaData+pPage->maskPage - && iFrom==get4byte(pCell+info.nSize-4) - ){ - put4byte(pCell+info.nSize-4, iTo); - break; + if( info.nLocal pPage->aData+pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_BKPT; + } + if( iFrom==get4byte(pCell+info.nSize-4) ){ + put4byte(pCell+info.nSize-4, iTo); + break; + } } }else{ if( get4byte(pCell)==iFrom ){ @@ -61508,8 +62401,6 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } put4byte(&pPage->aData[pPage->hdrOffset+8], iTo); } - - pPage->isInit = isInitOrig; } return SQLITE_OK; } @@ -62168,7 +63059,12 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ assert( op==SAVEPOINT_RELEASE || op==SAVEPOINT_ROLLBACK ); assert( iSavepoint>=0 || (iSavepoint==-1 && op==SAVEPOINT_ROLLBACK) ); sqlite3BtreeEnter(p); - rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint); + if( op==SAVEPOINT_ROLLBACK ){ + rc = saveAllCursors(pBt, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSavepoint(pBt->pPager, op, iSavepoint); + } if( rc==SQLITE_OK ){ if( iSavepoint<0 && (pBt->btsFlags & BTS_INITIALLY_EMPTY)!=0 ){ pBt->nPage = 0; @@ -62404,6 +63300,10 @@ SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor *pCur){ return pCur && pCur->eState==CURSOR_VALID; } #endif /* NDEBUG */ +SQLITE_PRIVATE int sqlite3BtreeCursorIsValidNN(BtCursor *pCur){ + assert( pCur!=0 ); + return pCur->eState==CURSOR_VALID; +} /* ** Return the value of the integer key or "rowid" for a table btree. @@ -62550,7 +63450,6 @@ static int copyPayload( ** ** 0: The operation is a read. Populate the overflow cache. ** 1: The operation is a write. Populate the overflow cache. -** 2: The operation is a read. Do not populate the overflow cache. ** ** A total of "amt" bytes are read or written beginning at "offset". ** Data is read to or from the buffer pBuf. @@ -62558,13 +63457,13 @@ static int copyPayload( ** The content being read or written might appear on the main page ** or be scattered out on multiple overflow pages. ** -** If the current cursor entry uses one or more overflow pages and the -** eOp argument is not 2, this function may allocate space for and lazily -** populates the overflow page-list cache array (BtCursor.aOverflow). +** If the current cursor entry uses one or more overflow pages +** this function may allocate space for and lazily populate +** the overflow page-list cache array (BtCursor.aOverflow). ** Subsequent calls use this cache to make seeking to the supplied offset ** more efficient. ** -** Once an overflow page-list cache has been allocated, it may be +** Once an overflow page-list cache has been allocated, it must be ** invalidated if some other cursor writes to the same table, or if ** the cursor is moved to a different row. Additionally, in auto-vacuum ** mode, the following events may invalidate an overflow page-list cache. @@ -62586,21 +63485,17 @@ static int accessPayload( MemPage *pPage = pCur->apPage[pCur->iPage]; /* Btree page of current entry */ BtShared *pBt = pCur->pBt; /* Btree this cursor belongs to */ #ifdef SQLITE_DIRECT_OVERFLOW_READ - unsigned char * const pBufStart = pBuf; - int bEnd; /* True if reading to end of data */ + unsigned char * const pBufStart = pBuf; /* Start of original out buffer */ #endif assert( pPage ); + assert( eOp==0 || eOp==1 ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->aiIdx[pCur->iPage]nCell ); assert( cursorHoldsMutex(pCur) ); - assert( eOp!=2 || offset==0 ); /* Always start from beginning for eOp==2 */ getCellInfo(pCur); aPayload = pCur->info.pPayload; -#ifdef SQLITE_DIRECT_OVERFLOW_READ - bEnd = offset+amt==pCur->info.nPayload; -#endif assert( offset+amt <= pCur->info.nPayload ); assert( aPayload > pPage->aData ); @@ -62619,7 +63514,7 @@ static int accessPayload( if( a+offset>pCur->info.nLocal ){ a = pCur->info.nLocal - offset; } - rc = copyPayload(&aPayload[offset], pBuf, a, (eOp & 0x01), pPage->pDbPage); + rc = copyPayload(&aPayload[offset], pBuf, a, eOp, pPage->pDbPage); offset = 0; pBuf += a; amt -= a; @@ -62635,53 +63530,46 @@ static int accessPayload( nextPage = get4byte(&aPayload[pCur->info.nLocal]); /* If the BtCursor.aOverflow[] has not been allocated, allocate it now. - ** Except, do not allocate aOverflow[] for eOp==2. ** ** The aOverflow[] array is sized at one entry for each overflow page ** in the overflow chain. The page number of the first overflow page is ** stored in aOverflow[0], etc. A value of 0 in the aOverflow[] array ** means "not yet known" (the cache is lazily populated). */ - if( eOp!=2 && (pCur->curFlags & BTCF_ValidOvfl)==0 ){ + if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){ int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; if( nOvfl>pCur->nOvflAlloc ){ Pgno *aNew = (Pgno*)sqlite3Realloc( pCur->aOverflow, nOvfl*2*sizeof(Pgno) ); if( aNew==0 ){ - rc = SQLITE_NOMEM_BKPT; + return SQLITE_NOMEM_BKPT; }else{ pCur->nOvflAlloc = nOvfl*2; pCur->aOverflow = aNew; } } - if( rc==SQLITE_OK ){ - memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); - pCur->curFlags |= BTCF_ValidOvfl; + memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); + pCur->curFlags |= BTCF_ValidOvfl; + }else{ + /* If the overflow page-list cache has been allocated and the + ** entry for the first required overflow page is valid, skip + ** directly to it. + */ + if( pCur->aOverflow[offset/ovflSize] ){ + iIdx = (offset/ovflSize); + nextPage = pCur->aOverflow[iIdx]; + offset = (offset%ovflSize); } } - /* If the overflow page-list cache has been allocated and the - ** entry for the first required overflow page is valid, skip - ** directly to it. - */ - if( (pCur->curFlags & BTCF_ValidOvfl)!=0 - && pCur->aOverflow[offset/ovflSize] - ){ - iIdx = (offset/ovflSize); - nextPage = pCur->aOverflow[iIdx]; - offset = (offset%ovflSize); - } - - for( ; rc==SQLITE_OK && amt>0 && nextPage; iIdx++){ - + assert( rc==SQLITE_OK && amt>0 ); + while( nextPage ){ /* If required, populate the overflow page-list cache. */ - if( (pCur->curFlags & BTCF_ValidOvfl)!=0 ){ - assert( pCur->aOverflow[iIdx]==0 - || pCur->aOverflow[iIdx]==nextPage - || CORRUPT_DB ); - pCur->aOverflow[iIdx] = nextPage; - } + assert( pCur->aOverflow[iIdx]==0 + || pCur->aOverflow[iIdx]==nextPage + || CORRUPT_DB ); + pCur->aOverflow[iIdx] = nextPage; if( offset>=ovflSize ){ /* The only reason to read this page is to obtain the page @@ -62689,11 +63577,7 @@ static int accessPayload( ** data is not required. So first try to lookup the overflow ** page-list cache, if any, then fall back to the getOverflowPage() ** function. - ** - ** Note that the aOverflow[] array must be allocated because eOp!=2 - ** here. If eOp==2, then offset==0 and this branch is never taken. */ - assert( eOp!=2 ); assert( pCur->curFlags & BTCF_ValidOvfl ); assert( pCur->pBtree->db==pBt->db ); if( pCur->aOverflow[iIdx+1] ){ @@ -62707,7 +63591,7 @@ static int accessPayload( ** range of data that is being read (eOp==0) or written (eOp!=0). */ #ifdef SQLITE_DIRECT_OVERFLOW_READ - sqlite3_file *fd; + sqlite3_file *fd; /* File from which to do direct overflow read */ #endif int a = amt; if( a + offset > ovflSize ){ @@ -62719,27 +63603,25 @@ static int accessPayload( ** ** 1) this is a read operation, and ** 2) data is required from the start of this overflow page, and - ** 3) the database is file-backed, and - ** 4) there is no open write-transaction, and - ** 5) the database is not a WAL database, - ** 6) all data from the page is being read. - ** 7) at least 4 bytes have already been read into the output buffer + ** 3) there is no open write-transaction, and + ** 4) the database is file-backed, and + ** 5) the page is not in the WAL file + ** 6) at least 4 bytes have already been read into the output buffer ** ** then data can be read directly from the database file into the ** output buffer, bypassing the page-cache altogether. This speeds ** up loading large records that span many overflow pages. */ - if( (eOp&0x01)==0 /* (1) */ + if( eOp==0 /* (1) */ && offset==0 /* (2) */ - && (bEnd || a==ovflSize) /* (6) */ - && pBt->inTransaction==TRANS_READ /* (4) */ - && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (3) */ - && pBt->pPage1->aData[19]==0x01 /* (5) */ - && &pBuf[-4]>=pBufStart /* (7) */ + && pBt->inTransaction==TRANS_READ /* (3) */ + && (fd = sqlite3PagerFile(pBt->pPager))->pMethods /* (4) */ + && 0==sqlite3PagerUseWal(pBt->pPager, nextPage) /* (5) */ + && &pBuf[-4]>=pBufStart /* (6) */ ){ u8 aSave[4]; u8 *aWrite = &pBuf[-4]; - assert( aWrite>=pBufStart ); /* hence (7) */ + assert( aWrite>=pBufStart ); /* due to (6) */ memcpy(aSave, aWrite, 4); rc = sqlite3OsRead(fd, aWrite, a+4, (i64)pBt->pageSize*(nextPage-1)); nextPage = get4byte(aWrite); @@ -62750,41 +63632,49 @@ static int accessPayload( { DbPage *pDbPage; rc = sqlite3PagerGet(pBt->pPager, nextPage, &pDbPage, - ((eOp&0x01)==0 ? PAGER_GET_READONLY : 0) + (eOp==0 ? PAGER_GET_READONLY : 0) ); if( rc==SQLITE_OK ){ aPayload = sqlite3PagerGetData(pDbPage); nextPage = get4byte(aPayload); - rc = copyPayload(&aPayload[offset+4], pBuf, a, (eOp&0x01), pDbPage); + rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); sqlite3PagerUnref(pDbPage); offset = 0; } } amt -= a; + if( amt==0 ) return rc; pBuf += a; } + if( rc ) break; + iIdx++; } } if( rc==SQLITE_OK && amt>0 ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_BKPT; /* Overflow chain ends prematurely */ } return rc; } /* -** Read part of the key associated with cursor pCur. Exactly -** "amt" bytes will be transferred into pBuf[]. The transfer +** Read part of the payload for the row at which that cursor pCur is currently +** pointing. "amt" bytes will be transferred into pBuf[]. The transfer ** begins at "offset". ** -** The caller must ensure that pCur is pointing to a valid row -** in the table. +** pCur can be pointing to either a table or an index b-tree. +** If pointing to a table btree, then the content section is read. If +** pCur is pointing to an index b-tree then the key section is read. +** +** For sqlite3BtreePayload(), the caller must ensure that pCur is pointing +** to a valid row in the table. For sqlite3BtreePayloadChecked(), the +** cursor might be invalid or might need to be restored before being read. ** ** Return SQLITE_OK on success or an error code if anything goes ** wrong. An error is returned if "offset+amt" is larger than ** the available payload. */ -SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ +SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] ); @@ -62793,33 +63683,34 @@ SQLITE_PRIVATE int sqlite3BtreeKey(BtCursor *pCur, u32 offset, u32 amt, void *pB } /* -** Read part of the data associated with cursor pCur. Exactly -** "amt" bytes will be transfered into pBuf[]. The transfer -** begins at "offset". -** -** Return SQLITE_OK on success or an error code if anything goes -** wrong. An error is returned if "offset+amt" is larger than -** the available payload. +** This variant of sqlite3BtreePayload() works even if the cursor has not +** in the CURSOR_VALID state. It is only used by the sqlite3_blob_read() +** interface. */ -SQLITE_PRIVATE int sqlite3BtreeData(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ - int rc; - #ifndef SQLITE_OMIT_INCRBLOB +static SQLITE_NOINLINE int accessPayloadChecked( + BtCursor *pCur, + u32 offset, + u32 amt, + void *pBuf +){ + int rc; if ( pCur->eState==CURSOR_INVALID ){ return SQLITE_ABORT; } -#endif - assert( cursorOwnsBtShared(pCur) ); - rc = restoreCursorPosition(pCur); - if( rc==SQLITE_OK ){ - assert( pCur->eState==CURSOR_VALID ); - assert( pCur->iPage>=0 && pCur->apPage[pCur->iPage] ); - assert( pCur->aiIdx[pCur->iPage]apPage[pCur->iPage]->nCell ); - rc = accessPayload(pCur, offset, amt, pBuf, 0); + rc = btreeRestoreCursorPosition(pCur); + return rc ? rc : accessPayload(pCur, offset, amt, pBuf, 0); +} +SQLITE_PRIVATE int sqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 amt, void *pBuf){ + if( pCur->eState==CURSOR_VALID ){ + assert( cursorOwnsBtShared(pCur) ); + return accessPayload(pCur, offset, amt, pBuf, 0); + }else{ + return accessPayloadChecked(pCur, offset, amt, pBuf); } - return rc; } +#endif /* SQLITE_OMIT_INCRBLOB */ /* ** Return a pointer to payload information from the entry that the @@ -62906,7 +63797,7 @@ static int moveToChild(BtCursor *pCur, u32 newPgno){ pCur, pCur->curPagerFlags); } -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG /* ** Page pParent is an internal (non-leaf) tree page. This function ** asserts that page number iChild is the left-child if the iIdx'th @@ -62990,9 +63881,12 @@ static int moveToRoot(BtCursor *pCur){ } if( pCur->iPage>=0 ){ - while( pCur->iPage ){ - assert( pCur->apPage[pCur->iPage]!=0 ); - releasePageNotNull(pCur->apPage[pCur->iPage--]); + if( pCur->iPage ){ + do{ + assert( pCur->apPage[pCur->iPage]!=0 ); + releasePageNotNull(pCur->apPage[pCur->iPage--]); + }while( pCur->iPage); + goto skip_init; } }else if( pCur->pgnoRoot==0 ){ pCur->eState = CURSOR_INVALID; @@ -63003,7 +63897,7 @@ static int moveToRoot(BtCursor *pCur){ 0, pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; - return rc; + return rc; } pCur->iPage = 0; pCur->curIntKey = pCur->apPage[0]->intKey; @@ -63026,10 +63920,12 @@ static int moveToRoot(BtCursor *pCur){ return SQLITE_CORRUPT_BKPT; } +skip_init: pCur->aiIdx[0] = 0; pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); + pRoot = pCur->apPage[0]; if( pRoot->nCell>0 ){ pCur->eState = CURSOR_VALID; }else if( !pRoot->leaf ){ @@ -63218,9 +64114,26 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( *pRes = 0; return SQLITE_OK; } - if( (pCur->curFlags & BTCF_AtLast)!=0 && pCur->info.nKeyinfo.nKeycurFlags & BTCF_AtLast)!=0 ){ + *pRes = -1; + return SQLITE_OK; + } + /* If the requested key is one more than the previous key, then + ** try to get there using sqlite3BtreeNext() rather than a full + ** binary search. This is an optimization only. The correct answer + ** is still obtained without this ase, only a little more slowely */ + if( pCur->info.nKey+1==intKey && !pCur->skipNext ){ + *pRes = 0; + rc = sqlite3BtreeNext(pCur, pRes); + if( rc ) return rc; + if( *pRes==0 ){ + getCellInfo(pCur); + if( pCur->info.nKey==intKey ){ + return SQLITE_OK; + } + } + } } } @@ -63286,16 +64199,16 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( if( lwr>upr ){ c = +1; break; } }else{ assert( nCellKey==intKey ); - pCur->curFlags |= BTCF_ValidNKey; - pCur->info.nKey = nCellKey; pCur->aiIdx[pCur->iPage] = (u16)idx; if( !pPage->leaf ){ lwr = idx; goto moveto_next_layer; }else{ + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; *pRes = 0; - rc = SQLITE_OK; - goto moveto_finish; + return SQLITE_OK; } } assert( lwr+upr>=0 ); @@ -63356,7 +64269,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( goto moveto_finish; } pCur->aiIdx[pCur->iPage] = (u16)idx; - rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 2); + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + pCur->curFlags &= ~BTCF_ValidOvfl; if( rc ){ sqlite3_free(pCellKey); goto moveto_finish; @@ -63406,7 +64320,7 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( } moveto_finish: pCur->info.nSize = 0; - pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); return rc; } @@ -63426,6 +64340,30 @@ SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){ return (CURSOR_VALID!=pCur->eState); } +/* +** Return an estimate for the number of rows in the table that pCur is +** pointing to. Return a negative number if no estimate is currently +** available. +*/ +SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ + i64 n; + u8 i; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + + /* Currently this interface is only called by the OP_IfSmaller + ** opcode, and it that case the cursor will always be valid and + ** will always point to a leaf node. */ + if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1; + if( NEVER(pCur->apPage[pCur->iPage]->leaf==0) ) return -1; + + for(n=1, i=0; i<=pCur->iPage; i++){ + n *= pCur->apPage[i]->nCell; + } + return n; +} + /* ** Advance the cursor to the next entry in the database. If ** successful then set *pRes=0. If the cursor @@ -63604,7 +64542,7 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur, int *pRes){ moveToParent(pCur); } assert( pCur->info.nSize==0 ); - assert( (pCur->curFlags & (BTCF_ValidNKey|BTCF_ValidOvfl))==0 ); + assert( (pCur->curFlags & (BTCF_ValidOvfl))==0 ); pCur->aiIdx[pCur->iPage]--; pPage = pCur->apPage[pCur->iPage]; @@ -64120,30 +65058,28 @@ static void freePage(MemPage *pPage, int *pRC){ static int clearCell( MemPage *pPage, /* The page that contains the Cell */ unsigned char *pCell, /* First byte of the Cell */ - u16 *pnSize /* Write the size of the Cell here */ + CellInfo *pInfo /* Size information about the cell */ ){ BtShared *pBt = pPage->pBt; - CellInfo info; Pgno ovflPgno; int rc; int nOvfl; u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->xParseCell(pPage, pCell, &info); - *pnSize = info.nSize; - if( info.nLocal==info.nPayload ){ + pPage->xParseCell(pPage, pCell, pInfo); + if( pInfo->nLocal==pInfo->nPayload ){ return SQLITE_OK; /* No overflow pages. Return without doing anything */ } - if( pCell+info.nSize-1 > pPage->aData+pPage->maskPage ){ + if( pCell+pInfo->nSize-1 > pPage->aData+pPage->maskPage ){ return SQLITE_CORRUPT_BKPT; /* Cell extends past end of page */ } - ovflPgno = get4byte(pCell + info.nSize - 4); + ovflPgno = get4byte(pCell + pInfo->nSize - 4); assert( pBt->usableSize > 4 ); ovflPageSize = pBt->usableSize - 4; - nOvfl = (info.nPayload - info.nLocal + ovflPageSize - 1)/ovflPageSize; + nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize; assert( nOvfl>0 || - (CORRUPT_DB && (info.nPayload + ovflPageSize)nPayload + ovflPageSize)nKey); }else{ - assert( pX->nData==0 ); - assert( pX->nZero==0 ); assert( pX->nKey<=0x7fffffff && pX->pKey!=0 ); nSrc = nPayload = (int)pX->nKey; pSrc = pX->pKey; @@ -64274,7 +65208,7 @@ static int fillInCell( ** Use a call to btreeParseCellPtr() to verify that the values above ** were computed correctly. */ -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG { CellInfo info; pPage->xParseCell(pPage, pCell, &info); @@ -64385,7 +65319,6 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; - assert( idx>=0 && idxnCell ); assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -64469,7 +65402,10 @@ static void insertCell( put4byte(pCell, iChild); } j = pPage->nOverflow++; - assert( j<(int)(sizeof(pPage->apOvfl)/sizeof(pPage->apOvfl[0])) ); + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); pPage->apOvfl[j] = pCell; pPage->aiOvfl[j] = (u16)i; @@ -65209,7 +66145,7 @@ static int balance_nonroot( nMaxCells += 1+apOld[i]->nCell+apOld[i]->nOverflow; if( (i--)==0 ) break; - if( i+nxDiv==pParent->aiOvfl[0] && pParent->nOverflow ){ + if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ apDiv[i] = pParent->apOvfl[0]; pgno = get4byte(apDiv[i]); szNew[i] = pParent->xCellSize(pParent, apDiv[i]); @@ -65401,7 +66337,6 @@ static int balance_nonroot( for(i=0; inFree; - if( szNew[i]<0 ){ rc = SQLITE_CORRUPT_BKPT; goto balance_cleanup; } for(j=0; jnOverflow; j++){ szNew[i] += 2 + p->xCellSize(p, p->apOvfl[j]); } @@ -65799,7 +66734,7 @@ static int balance_nonroot( ** free space needs to be up front. */ assert( nNew==1 || CORRUPT_DB ); - rc = defragmentPage(apNew[0]); + rc = defragmentPage(apNew[0], -1); testcase( rc!=SQLITE_OK ); assert( apNew[0]->nFree == (get2byte(&apNew[0]->aData[5])-apNew[0]->cellOffset-apNew[0]->nCell*2) @@ -66063,22 +66998,24 @@ static int balance(BtCursor *pCur){ ** pX.pData,nData,nZero fields must be zero. ** ** If the seekResult parameter is non-zero, then a successful call to -** MovetoUnpacked() to seek cursor pCur to (pKey, nKey) has already -** been performed. seekResult is the search result returned (a negative -** number if pCur points at an entry that is smaller than (pKey, nKey), or -** a positive value if pCur points at an entry that is larger than -** (pKey, nKey)). -** -** If the seekResult parameter is non-zero, then the caller guarantees that -** cursor pCur is pointing at the existing copy of a row that is to be -** overwritten. If the seekResult parameter is 0, then cursor pCur may -** point to any entry or to no entry at all and so this function has to seek -** the cursor before the new key can be inserted. +** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already +** been performed. In other words, if seekResult!=0 then the cursor +** is currently pointing to a cell that will be adjacent to the cell +** to be inserted. If seekResult<0 then pCur points to a cell that is +** smaller then (pKey,nKey). If seekResult>0 then pCur points to a cell +** that is larger than (pKey,nKey). +** +** If seekResult==0, that means pCur is pointing at some unknown location. +** In that case, this routine must seek the cursor to the correct insertion +** point for (pKey,nKey) before doing the insertion. For index btrees, +** if pX->nMem is non-zero, then pX->aMem contains pointers to the unpacked +** key values and pX->aMem can be used instead of pX->pKey to avoid having +** to decode the key. */ SQLITE_PRIVATE int sqlite3BtreeInsert( BtCursor *pCur, /* Insert data into the table of this cursor */ const BtreePayload *pX, /* Content of the row to be inserted */ - int appendBias, /* True if this is likely an append */ + int flags, /* True if this is likely an append */ int seekResult /* Result of prior MovetoUnpacked() call */ ){ int rc; @@ -66091,6 +67028,8 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( unsigned char *oldCell; unsigned char *newCell = 0; + assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND))==flags ); + if( pCur->eState==CURSOR_FAULT ){ assert( pCur->skipNext!=SQLITE_OK ); return pCur->skipNext; @@ -66131,18 +67070,38 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** cursors open on the row being replaced */ invalidateIncrblobCursors(p, pX->nKey, 0); + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + ** to a row with the same key as the new entry being inserted. */ + assert( (flags & BTREE_SAVEPOSITION)==0 || + ((pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey) ); + /* If the cursor is currently on the last row and we are appending a ** new row onto the end, set the "loc" to avoid an unnecessary ** btreeMoveto() call */ - if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0 - && pCur->info.nKey==pX->nKey-1 ){ - loc = -1; + if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey==pCur->info.nKey ){ + loc = 0; + }else if( (pCur->curFlags&BTCF_ValidNKey)!=0 && pX->nKey>0 + && pCur->info.nKey==pX->nKey-1 ){ + loc = -1; }else if( loc==0 ){ - rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, appendBias, &loc); + rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc); if( rc ) return rc; } - }else if( loc==0 ){ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, appendBias, &loc); + }else if( loc==0 && (flags & BTREE_SAVEPOSITION)==0 ){ + if( pX->nMem ){ + UnpackedRecord r; + r.pKeyInfo = pCur->pKeyInfo; + r.aMem = pX->aMem; + r.nField = pX->nMem; + r.default_rc = 0; + r.errCode = 0; + r.r1 = 0; + r.r2 = 0; + r.eqSeen = 0; + rc = sqlite3BtreeMovetoUnpacked(pCur, &r, 0, flags!=0, &loc); + }else{ + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, flags!=0, &loc); + } if( rc ) return rc; } assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) ); @@ -66163,7 +67122,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( assert( szNew <= MX_CELL_SIZE(pBt) ); idx = pCur->aiIdx[pCur->iPage]; if( loc==0 ){ - u16 szOld; + CellInfo info; assert( idxnCell ); rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ @@ -66173,8 +67132,19 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - rc = clearCell(pPage, oldCell, &szOld); - dropCell(pPage, idx, szOld, &rc); + rc = clearCell(pPage, oldCell, &info); + if( info.nSize==szNew && info.nLocal==info.nPayload ){ + /* Overwrite the old cell with the new if they are the same size. + ** We could also try to do this if the old cell is smaller, then add + ** the leftover space to the free list. But experiments show that + ** doing that is no faster then skipping this optimization and just + ** calling dropCell() and insertCell(). */ + assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ + if( oldCell+szNew > pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; + memcpy(oldCell, newCell, szNew); + return SQLITE_OK; + } + dropCell(pPage, idx, info.nSize, &rc); if( rc ) goto end_insert; }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); @@ -66218,6 +67188,20 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** from trying to save the current position of the cursor. */ pCur->apPage[pCur->iPage]->nOverflow = 0; pCur->eState = CURSOR_INVALID; + if( (flags & BTREE_SAVEPOSITION) && rc==SQLITE_OK ){ + rc = moveToRoot(pCur); + if( pCur->pKeyInfo ){ + assert( pCur->pKey==0 ); + pCur->pKey = sqlite3Malloc( pX->nKey ); + if( pCur->pKey==0 ){ + rc = SQLITE_NOMEM; + }else{ + memcpy(pCur->pKey, pX->pKey, pX->nKey); + } + } + pCur->eState = CURSOR_REQUIRESEEK; + pCur->nKey = pX->nKey; + } } assert( pCur->apPage[pCur->iPage]->nOverflow==0 ); @@ -66250,7 +67234,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ unsigned char *pCell; /* Pointer to cell to delete */ int iCellIdx; /* Index of cell to delete */ int iCellDepth; /* Depth of node containing pCell */ - u16 szCell; /* Size of the cell being deleted */ + CellInfo info; /* Size of the cell being deleted */ int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */ u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */ @@ -66322,8 +67306,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** itself from within the page. */ rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell, &szCell); - dropCell(pPage, iCellIdx, szCell, &rc); + rc = clearCell(pPage, pCell, &info); + dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; /* If the cell deleted was not located on a leaf page, then the cursor @@ -66573,7 +67557,7 @@ static int clearDatabasePage( unsigned char *pCell; int i; int hdr; - u16 szCell; + CellInfo info; assert( sqlite3_mutex_held(pBt->mutex) ); if( pgno>btreePagecount(pBt) ){ @@ -66593,7 +67577,7 @@ static int clearDatabasePage( rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell, &szCell); + rc = clearCell(pPage, pCell, &info); if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ @@ -66684,27 +67668,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ assert( sqlite3BtreeHoldsMutex(p) ); assert( p->inTrans==TRANS_WRITE ); - - /* It is illegal to drop a table if any cursors are open on the - ** database. This is because in auto-vacuum mode the backend may - ** need to move another root-page to fill a gap left by the deleted - ** root page. If an open cursor was using this page a problem would - ** occur. - ** - ** This error is caught long before control reaches this point. - */ - if( NEVER(pBt->pCursor) ){ - sqlite3ConnectionBlocked(p->db, pBt->pCursor->pBtree->db); - return SQLITE_LOCKED_SHAREDCACHE; - } - - /* - ** It is illegal to drop the sqlite_master table on page 1. But again, - ** this error is caught long before reaching this point. - */ - if( NEVER(iTable<2) ){ - return SQLITE_CORRUPT_BKPT; - } + assert( iTable>=2 ); rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); if( rc ) return rc; @@ -67612,7 +68576,7 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int * if( pBt->inTransaction!=TRANS_NONE ){ rc = SQLITE_LOCKED; }else{ - rc = sqlite3PagerCheckpoint(pBt->pPager, eMode, pnLog, pnCkpt); + rc = sqlite3PagerCheckpoint(pBt->pPager, p->db, eMode, pnLog, pnCkpt); } sqlite3BtreeLeave(p); } @@ -67934,22 +68898,16 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ int i = sqlite3FindDbName(pDb, zDb); if( i==1 ){ - Parse *pParse; + Parse sParse; int rc = 0; - pParse = sqlite3StackAllocZero(pErrorDb, sizeof(*pParse)); - if( pParse==0 ){ - sqlite3ErrorWithMsg(pErrorDb, SQLITE_NOMEM, "out of memory"); - rc = SQLITE_NOMEM_BKPT; - }else{ - pParse->db = pDb; - if( sqlite3OpenTempDatabase(pParse) ){ - sqlite3ErrorWithMsg(pErrorDb, pParse->rc, "%s", pParse->zErrMsg); - rc = SQLITE_ERROR; - } - sqlite3DbFree(pErrorDb, pParse->zErrMsg); - sqlite3ParserReset(pParse); - sqlite3StackFree(pErrorDb, pParse); + memset(&sParse, 0, sizeof(sParse)); + sParse.db = pDb; + if( sqlite3OpenTempDatabase(&sParse) ){ + sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg); + rc = SQLITE_ERROR; } + sqlite3DbFree(pErrorDb, sParse.zErrMsg); + sqlite3ParserReset(&sParse); if( rc ){ return 0; } @@ -68047,7 +69005,6 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( p->isAttached = 0; if( 0==p->pSrc || 0==p->pDest - || setDestPgsz(p)==SQLITE_NOMEM || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK ){ /* One (or both) of the named databases did not exist or an OOM @@ -68235,14 +69192,6 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = SQLITE_OK; } - /* Lock the destination database, if it is not locked already. */ - if( SQLITE_OK==rc && p->bDestLocked==0 - && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2)) - ){ - p->bDestLocked = 1; - sqlite3BtreeGetMeta(p->pDest, BTREE_SCHEMA_VERSION, &p->iDestSchema); - } - /* If there is no open read-transaction on the source database, open ** one now. If a transaction is opened here, then it will be closed ** before this function exits. @@ -68252,6 +69201,24 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ bCloseTrans = 1; } + /* If the destination database has not yet been locked (i.e. if this + ** is the first call to backup_step() for the current backup operation), + ** try to set its page size to the same as the source database. This + ** is especially important on ZipVFS systems, as in that case it is + ** not possible to create a database file that uses one page size by + ** writing to it with another. */ + if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ + rc = SQLITE_NOMEM; + } + + /* Lock the destination database, if it is not locked already. */ + if( SQLITE_OK==rc && p->bDestLocked==0 + && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2)) + ){ + p->bDestLocked = 1; + sqlite3BtreeGetMeta(p->pDest, BTREE_SCHEMA_VERSION, &p->iDestSchema); + } + /* Do not allow backup if the destination database is in WAL mode ** and the page sizes are different between source and destination */ pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); @@ -68840,18 +69807,18 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ - int f; assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( (pMem->flags&MEM_RowSet)==0 ); - ExpandBlob(pMem); - f = pMem->flags; - if( (f&(MEM_Str|MEM_Blob)) && (pMem->szMalloc==0 || pMem->z!=pMem->zMalloc) ){ - if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ - return SQLITE_NOMEM_BKPT; + if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ + if( ExpandBlob(pMem) ) return SQLITE_NOMEM; + if( pMem->szMalloc==0 || pMem->z!=pMem->zMalloc ){ + if( sqlite3VdbeMemGrow(pMem, pMem->n + 2, 1) ){ + return SQLITE_NOMEM_BKPT; + } + pMem->z[pMem->n] = 0; + pMem->z[pMem->n+1] = 0; + pMem->flags |= MEM_Term; } - pMem->z[pMem->n] = 0; - pMem->z[pMem->n+1] = 0; - pMem->flags |= MEM_Term; } pMem->flags &= ~MEM_Ephem; #ifdef SQLITE_DEBUG @@ -68867,25 +69834,24 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ */ #ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ - if( pMem->flags & MEM_Zero ){ - int nByte; - assert( pMem->flags&MEM_Blob ); - assert( (pMem->flags&MEM_RowSet)==0 ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - - /* Set nByte to the number of bytes required to store the expanded blob. */ - nByte = pMem->n + pMem->u.nZero; - if( nByte<=0 ){ - nByte = 1; - } - if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ - return SQLITE_NOMEM_BKPT; - } + int nByte; + assert( pMem->flags & MEM_Zero ); + assert( pMem->flags&MEM_Blob ); + assert( (pMem->flags&MEM_RowSet)==0 ); + assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - memset(&pMem->z[pMem->n], 0, pMem->u.nZero); - pMem->n += pMem->u.nZero; - pMem->flags &= ~(MEM_Zero|MEM_Term); + /* Set nByte to the number of bytes required to store the expanded blob. */ + nByte = pMem->n + pMem->u.nZero; + if( nByte<=0 ){ + nByte = 1; + } + if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ + return SQLITE_NOMEM_BKPT; } + + memset(&pMem->z[pMem->n], 0, pMem->u.nZero); + pMem->n += pMem->u.nZero; + pMem->flags &= ~(MEM_Zero|MEM_Term); return SQLITE_OK; } #endif @@ -68945,6 +69911,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ if( sqlite3VdbeMemClearAndResize(pMem, nByte) ){ + pMem->enc = 0; return SQLITE_NOMEM_BKPT; } @@ -69226,7 +70193,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ } } assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 ); - pMem->flags &= ~(MEM_Str|MEM_Blob); + pMem->flags &= ~(MEM_Str|MEM_Blob|MEM_Zero); return SQLITE_OK; } @@ -69244,7 +70211,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ if( (pMem->flags & MEM_Blob)==0 ){ sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); - MemSetTypeFlag(pMem, MEM_Blob); + if( pMem->flags & MEM_Str ) MemSetTypeFlag(pMem, MEM_Blob); }else{ pMem->flags &= ~(MEM_TypeMask&~MEM_Blob); } @@ -69585,10 +70552,9 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( /* ** Move data out of a btree key or data field and into a Mem structure. -** The data or key is taken from the entry that pCur is currently pointing +** The data is payload from the entry that pCur is currently pointing ** to. offset and amt determine what portion of the data or key to retrieve. -** key is true to get the key or false to get data. The result is written -** into the pMem element. +** The result is written into the pMem element. ** ** The pMem object must have been initialized. This routine will use ** pMem->zMalloc to hold the content from the btree, if possible. New @@ -69603,17 +70569,12 @@ static SQLITE_NOINLINE int vdbeMemFromBtreeResize( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ - int key, /* If true, retrieve from the btree key, not data. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ int rc; pMem->flags = MEM_Null; if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+2)) ){ - if( key ){ - rc = sqlite3BtreeKey(pCur, offset, amt, pMem->z); - }else{ - rc = sqlite3BtreeData(pCur, offset, amt, pMem->z); - } + rc = sqlite3BtreePayload(pCur, offset, amt, pMem->z); if( rc==SQLITE_OK ){ pMem->z[amt] = 0; pMem->z[amt+1] = 0; @@ -69629,7 +70590,6 @@ SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ - int key, /* If true, retrieve from the btree key, not data. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ char *zData; /* Data from the btree layer */ @@ -69650,7 +70610,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( pMem->flags = MEM_Blob|MEM_Ephem; pMem->n = (int)amt; }else{ - rc = vdbeMemFromBtreeResize(pCur, offset, amt, key, pMem); + rc = vdbeMemFromBtreeResize(pCur, offset, amt, pMem); } return rc; @@ -69668,10 +70628,8 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ assert( (pVal->flags & MEM_RowSet)==0 ); assert( (pVal->flags & (MEM_Null))==0 ); if( pVal->flags & (MEM_Blob|MEM_Str) ){ + if( ExpandBlob(pVal) ) return 0; pVal->flags |= MEM_Str; - if( pVal->flags & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pVal); - } if( pVal->enc != (enc & ~SQLITE_UTF16_ALIGNED) ){ sqlite3VdbeChangeEncoding(pVal, enc & ~SQLITE_UTF16_ALIGNED); } @@ -69924,10 +70882,7 @@ static int valueFromExpr( const char *zNeg = ""; int rc = SQLITE_OK; - if( !pExpr ){ - *ppVal = 0; - return SQLITE_OK; - } + assert( pExpr!=0 ); while( (op = pExpr->op)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; @@ -69997,6 +70952,7 @@ static int valueFromExpr( }else if( op==TK_NULL ){ pVal = valueNew(db, pCtx); if( pVal==0 ) goto no_mem; + sqlite3VdbeMemNumerify(pVal); } #ifndef SQLITE_OMIT_BLOB_LITERAL else if( op==TK_BLOB ){ @@ -70051,7 +71007,7 @@ SQLITE_PRIVATE int sqlite3ValueFromExpr( u8 affinity, /* Affinity to use */ sqlite3_value **ppVal /* Write the new value here */ ){ - return valueFromExpr(db, pExpr, enc, affinity, ppVal, 0); + return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0; } #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 @@ -70171,9 +71127,9 @@ static int stat4ValueFromExpr( ** structures intended to be compared against sample index keys stored ** in the sqlite_stat4 table. ** -** A single call to this function attempts to populates field iVal (leftmost -** is 0 etc.) of the unpacked record with a value extracted from expression -** pExpr. Extraction of values is possible if: +** A single call to this function populates zero or more fields of the +** record starting with field iVal (fields are numbered from left to +** right starting with 0). A single field is populated if: ** ** * (pExpr==0). In this case the value is assumed to be an SQL NULL, ** @@ -70182,10 +71138,14 @@ static int stat4ValueFromExpr( ** * The sqlite3ValueFromExpr() function is able to extract a value ** from the expression (i.e. the expression is a literal value). ** -** If a value can be extracted, the affinity passed as the 5th argument -** is applied to it before it is copied into the UnpackedRecord. Output -** parameter *pbOk is set to true if a value is extracted, or false -** otherwise. +** Or, if pExpr is a TK_VECTOR, one field is populated for each of the +** vector components that match either of the two latter criteria listed +** above. +** +** Before any value is appended to the record, the affinity of the +** corresponding column within index pIdx is applied to it. Before +** this function returns, output parameter *pnExtract is set to the +** number of values appended to the record. ** ** When this function is called, *ppRec must either point to an object ** allocated by an earlier call to this function, or must be NULL. If it @@ -70201,22 +71161,33 @@ SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( Index *pIdx, /* Index being probed */ UnpackedRecord **ppRec, /* IN/OUT: Probe record */ Expr *pExpr, /* The expression to extract a value from */ - u8 affinity, /* Affinity to use */ + int nElem, /* Maximum number of values to append */ int iVal, /* Array element to populate */ - int *pbOk /* OUT: True if value was extracted */ + int *pnExtract /* OUT: Values appended to the record */ ){ - int rc; - sqlite3_value *pVal = 0; - struct ValueNewStat4Ctx alloc; + int rc = SQLITE_OK; + int nExtract = 0; - alloc.pParse = pParse; - alloc.pIdx = pIdx; - alloc.ppRec = ppRec; - alloc.iVal = iVal; + if( pExpr==0 || pExpr->op!=TK_SELECT ){ + int i; + struct ValueNewStat4Ctx alloc; - rc = stat4ValueFromExpr(pParse, pExpr, affinity, &alloc, &pVal); - assert( pVal==0 || pVal->db==pParse->db ); - *pbOk = (pVal!=0); + alloc.pParse = pParse; + alloc.pIdx = pIdx; + alloc.ppRec = ppRec; + + for(i=0; idb, pIdx, iVal+i); + alloc.iVal = iVal+i; + rc = stat4ValueFromExpr(pParse, pElem, aff, &alloc, &pVal); + if( !pVal ) break; + nExtract++; + } + } + + *pnExtract = nExtract; return rc; } @@ -70379,8 +71350,9 @@ SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ sqlite3 *db = pParse->db; Vdbe *p; - p = sqlite3DbMallocZero(db, sizeof(Vdbe) ); + p = sqlite3DbMallocRawNN(db, sizeof(Vdbe) ); if( p==0 ) return 0; + memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp)); p->db = db; if( db->pVdbe ){ db->pVdbe->pPrev = p; @@ -70414,6 +71386,7 @@ SQLITE_PRIVATE void sqlite3VdbeError(Vdbe *p, const char *zFormat, ...){ SQLITE_PRIVATE void sqlite3VdbeSetSql(Vdbe *p, const char *z, int n, int isPrepareV2){ assert( isPrepareV2==1 || isPrepareV2==0 ); if( p==0 ) return; + if( !isPrepareV2 ) p->expmask = 0; #if defined(SQLITE_OMIT_TRACE) && !defined(SQLITE_ENABLE_SQLLOG) if( !isPrepareV2 ) return; #endif @@ -70442,6 +71415,7 @@ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ pA->zSql = pB->zSql; pB->zSql = zTmp; pB->isPrepareV2 = pA->isPrepareV2; + pB->expmask = pA->expmask; } /* @@ -70472,6 +71446,12 @@ static int growOpArray(Vdbe *v, int nOp){ UNUSED_PARAMETER(nOp); #endif + /* Ensure that the size of a VDBE does not grow too large */ + if( nNew > p->db->aLimit[SQLITE_LIMIT_VDBE_OP] ){ + sqlite3OomFault(p->db); + return SQLITE_NOMEM; + } + assert( nOp<=(1024/sizeof(Op)) ); assert( nNew>=(p->nOpAlloc+nOp) ); pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); @@ -70542,9 +71522,8 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ if( p->db->flags & SQLITE_VdbeAddopTrace ){ int jj, kk; Parse *pParse = p->pParse; - for(jj=kk=0; jjnColCache; jj++){ struct yColCache *x = pParse->aColCache + jj; - if( x->iLevel>pParse->iCacheLevel || x->iReg==0 ) continue; printf(" r[%d]={%d:%d}", x->iReg, x->iTable, x->iColumn); kk++; } @@ -70671,7 +71650,11 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( int p4 /* The P4 operand as an integer */ ){ int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); - sqlite3VdbeChangeP4(p, addr, SQLITE_INT_TO_PTR(p4), P4_INT32); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } return addr; } @@ -70732,7 +71715,6 @@ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ if( p->aLabel ){ p->aLabel[j] = v->nOp; } - p->iFixedOp = v->nOp - 1; } /* @@ -71003,6 +71985,22 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N){ } #endif +/* +** Verify that the VM passed as the only argument does not contain +** an OP_ResultRow opcode. Fail an assert() if it does. This is used +** by code in pragma.c to ensure that the implementation of certain +** pragmas comports with the flags specified in the mkpragmatab.tcl +** script. +*/ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) +SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p){ + int i; + for(i=0; inOp; i++){ + assert( p->aOp[i].opcode!=OP_ResultRow ); + } +} +#endif + /* ** This function returns a pointer to the array of opcodes associated with ** the Vdbe passed as the first argument. It is the callers responsibility @@ -71122,8 +72120,9 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, u32 addr, int val){ SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){ sqlite3VdbeGetOp(p,addr)->p3 = val; } -SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ - if( !p->db->mallocFailed ) p->aOp[p->nOp-1].p5 = p5; +SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ + assert( p->nOp>0 || p->db->mallocFailed ); + if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; } /* @@ -71131,7 +72130,6 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u8 p5){ ** the address of the next instruction to be coded. */ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ - p->pParse->iFixedOp = p->nOp - 1; sqlite3VdbeChangeP2(p, addr, p->nOp); } @@ -71183,10 +72181,6 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ break; } #endif - case P4_MPRINTF: { - if( db->pnBytesFreed==0 ) sqlite3_free(p4); - break; - } case P4_FUNCDEF: { freeEphemeralFunction(db, (FuncDef*)p4); break; @@ -71254,7 +72248,7 @@ SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe *p, int addr){ ** then remove it. Return true if and only if an opcode was removed. */ SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ - if( (p->nOp-1)>(p->pParse->iFixedOp) && p->aOp[p->nOp-1].opcode==op ){ + if( p->nOp>0 && p->aOp[p->nOp-1].opcode==op ){ return sqlite3VdbeChangeToNoop(p, p->nOp-1); }else{ return 0; @@ -71331,16 +72325,42 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int } } +/* +** Change the P4 operand of the most recently coded instruction +** to the value defined by the arguments. This is a high-speed +** version of sqlite3VdbeChangeP4(). +** +** The P4 operand must not have been previously defined. And the new +** P4 must not be P4_INT32. Use sqlite3VdbeChangeP4() in either of +** those cases. +*/ +SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){ + VdbeOp *pOp; + assert( n!=P4_INT32 && n!=P4_VTAB ); + assert( n<=0 ); + if( p->db->mallocFailed ){ + freeP4(p->db, n, pP4); + }else{ + assert( pP4!=0 ); + assert( p->nOp>0 ); + pOp = &p->aOp[p->nOp-1]; + assert( pOp->p4type==P4_NOTUSED ); + pOp->p4type = n; + pOp->p4.p = pP4; + } +} + /* ** Set the P4 on the most recently added opcode to the KeyInfo for the ** index given. */ SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ Vdbe *v = pParse->pVdbe; + KeyInfo *pKeyInfo; assert( v!=0 ); assert( pIdx!=0 ); - sqlite3VdbeChangeP4(v, -1, (char*)sqlite3KeyInfoOfIndex(pParse, pIdx), - P4_KEYINFO); + pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pIdx); + if( pKeyInfo ) sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); } #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS @@ -71630,7 +72650,7 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); break; } -#ifdef SQLITE_DEBUG +#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) case P4_FUNCCTX: { FuncDef *pDef = pOp->p4.pCtx->pFunc; sqlite3XPrintf(&x, "%s(%d)", pDef->zName, pDef->nArg); @@ -71817,6 +72837,21 @@ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, Op *pOp){ } #endif +/* +** Initialize an array of N Mem element. +*/ +static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ + while( (N--)>0 ){ + p->db = db; + p->flags = flags; + p->szMalloc = 0; +#ifdef SQLITE_DEBUG + p->pScopyFrom = 0; +#endif + p++; + } +} + /* ** Release an array of N Mem elements */ @@ -72028,6 +73063,7 @@ SQLITE_PRIVATE int sqlite3VdbeList( pMem->flags = MEM_Str|MEM_Term; zP4 = displayP4(pOp, pMem->z, pMem->szMalloc); if( zP4!=pMem->z ){ + pMem->n = 0; sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0); }else{ assert( pMem->z!=0 ); @@ -72170,7 +73206,7 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ int i; #endif assert( p!=0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->magic==VDBE_MAGIC_INIT || p->magic==VDBE_MAGIC_RESET ); /* There should be at least one opcode. */ @@ -72227,7 +73263,6 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( int nMem; /* Number of VM memory registers */ int nCursor; /* Number of cursors required */ int nArg; /* Number of arguments in subprograms */ - int nOnce; /* Number of OP_Once instructions */ int n; /* Loop counter */ struct ReusableSpace x; /* Reusable bulk memory */ @@ -72242,8 +73277,6 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( nMem = pParse->nMem; nCursor = pParse->nTab; nArg = pParse->nMaxArg; - nOnce = pParse->nOnce; - if( nOnce==0 ) nOnce = 1; /* Ensure at least one byte in p->aOnceFlag[] */ /* Each cursor uses a memory cell. The first cursor (cursor 0) can ** use aMem[0] which is not otherwise used by the VDBE program. Allocate @@ -72262,10 +73295,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ assert( x.nFree>=0 ); - if( x.nFree>0 ){ - memset(x.pSpace, 0, x.nFree); - assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); - } + assert( EIGHT_BYTE_ALIGNMENT(&x.pSpace[x.nFree]) ); resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); @@ -72290,36 +73320,32 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); - p->aOnceFlag = allocSpace(&x, p->aOnceFlag, nOnce); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64)); #endif if( x.nNeeded==0 ) break; - x.pSpace = p->pFree = sqlite3DbMallocZero(db, x.nNeeded); + x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); x.nFree = x.nNeeded; }while( !db->mallocFailed ); - p->nCursor = nCursor; - p->nOnceFlag = nOnce; - if( p->aVar ){ + p->pVList = pParse->pVList; + pParse->pVList = 0; + p->explain = pParse->explain; + if( db->mallocFailed ){ + p->nVar = 0; + p->nCursor = 0; + p->nMem = 0; + }else{ + p->nCursor = nCursor; p->nVar = (ynVar)nVar; - for(n=0; naVar[n].flags = MEM_Null; - p->aVar[n].db = db; - } - } - p->nzVar = pParse->nzVar; - p->azVar = pParse->azVar; - pParse->nzVar = 0; - pParse->azVar = 0; - if( p->aMem ){ + initMemArray(p->aVar, nVar, db, MEM_Null); p->nMem = nMem; - for(n=0; naMem[n].flags = MEM_Undefined; - p->aMem[n].db = db; - } + initMemArray(p->aMem, nMem, db, MEM_Undefined); + memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + memset(p->anExec, 0, p->nOp*sizeof(i64)); +#endif } - p->explain = pParse->explain; sqlite3VdbeRewind(p); } @@ -72331,15 +73357,15 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ if( pCx==0 ){ return; } - assert( pCx->pBt==0 || pCx->eCurType==CURTYPE_BTREE ); + assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { - if( pCx->pBt ){ - sqlite3BtreeClose(pCx->pBt); + if( pCx->pBtx ){ + sqlite3BtreeClose(pCx->pBtx); /* The pCx->pCursor will be close automatically, if it exists, by ** the call above. */ }else{ @@ -72388,8 +73414,6 @@ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS v->anExec = pFrame->anExec; #endif - v->aOnceFlag = pFrame->aOnceFlag; - v->nOnceFlag = pFrame->nOnceFlag; v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; @@ -72473,13 +73497,9 @@ SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ sqlite3DbFree(db, p->aColName); n = nResColumn*COLNAME_N; p->nResColumn = (u16)nResColumn; - p->aColName = pColName = (Mem*)sqlite3DbMallocZero(db, sizeof(Mem)*n ); + p->aColName = pColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; - while( n-- > 0 ){ - pColName->flags = MEM_Null; - pColName->db = p->db; - pColName++; - } + initMemArray(p->aColName, n, p->db, MEM_Null); } /* @@ -72814,60 +73834,59 @@ static void checkActiveVdbeCnt(sqlite3 *db){ ** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. ** Otherwise SQLITE_OK. */ -SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ +static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ sqlite3 *const db = p->db; int rc = SQLITE_OK; + int i; + const int iSavepoint = p->iStatement-1; - /* If p->iStatement is greater than zero, then this Vdbe opened a - ** statement transaction that should be closed here. The only exception - ** is that an IO error may have occurred, causing an emergency rollback. - ** In this case (db->nStatement==0), and there is nothing to do. - */ - if( db->nStatement && p->iStatement ){ - int i; - const int iSavepoint = p->iStatement-1; - - assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE); - assert( db->nStatement>0 ); - assert( p->iStatement==(db->nStatement+db->nSavepoint) ); + assert( eOp==SAVEPOINT_ROLLBACK || eOp==SAVEPOINT_RELEASE); + assert( db->nStatement>0 ); + assert( p->iStatement==(db->nStatement+db->nSavepoint) ); - for(i=0; inDb; i++){ - int rc2 = SQLITE_OK; - Btree *pBt = db->aDb[i].pBt; - if( pBt ){ - if( eOp==SAVEPOINT_ROLLBACK ){ - rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint); - } - if( rc2==SQLITE_OK ){ - rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint); - } - if( rc==SQLITE_OK ){ - rc = rc2; - } - } - } - db->nStatement--; - p->iStatement = 0; - - if( rc==SQLITE_OK ){ + for(i=0; inDb; i++){ + int rc2 = SQLITE_OK; + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ if( eOp==SAVEPOINT_ROLLBACK ){ - rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint); + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_ROLLBACK, iSavepoint); + } + if( rc2==SQLITE_OK ){ + rc2 = sqlite3BtreeSavepoint(pBt, SAVEPOINT_RELEASE, iSavepoint); } if( rc==SQLITE_OK ){ - rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint); + rc = rc2; } } + } + db->nStatement--; + p->iStatement = 0; - /* If the statement transaction is being rolled back, also restore the - ** database handles deferred constraint counter to the value it had when - ** the statement transaction was opened. */ + if( rc==SQLITE_OK ){ if( eOp==SAVEPOINT_ROLLBACK ){ - db->nDeferredCons = p->nStmtDefCons; - db->nDeferredImmCons = p->nStmtDefImmCons; + rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint); + } + if( rc==SQLITE_OK ){ + rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint); } } + + /* If the statement transaction is being rolled back, also restore the + ** database handles deferred constraint counter to the value it had when + ** the statement transaction was opened. */ + if( eOp==SAVEPOINT_ROLLBACK ){ + db->nDeferredCons = p->nStmtDefCons; + db->nDeferredImmCons = p->nStmtDefImmCons; + } return rc; } +SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ + if( p->db->nStatement && p->iStatement ){ + return vdbeCloseStatement(p, eOp); + } + return SQLITE_OK; +} + /* ** This function is called when a transaction opened by the database @@ -72927,14 +73946,13 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ + if( p->magic!=VDBE_MAGIC_RUN ){ + return SQLITE_OK; + } if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } - if( p->aOnceFlag ) memset(p->aOnceFlag, 0, p->nOnceFlag); closeAllCursors(p); - if( p->magic!=VDBE_MAGIC_RUN ){ - return SQLITE_OK; - } checkActiveVdbeCnt(db); /* No commit or rollback needed if the program never started or if the @@ -73242,7 +74260,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ } #endif p->iCurrentTime = 0; - p->magic = VDBE_MAGIC_INIT; + p->magic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } @@ -73304,26 +74322,29 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, */ SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; - int i; assert( p->db==0 || p->db==db ); - releaseMemArray(p->aVar, p->nVar); releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - for(i=p->nzVar-1; i>=0; i--) sqlite3DbFree(db, p->azVar[i]); - sqlite3DbFree(db, p->azVar); + if( p->magic!=VDBE_MAGIC_INIT ){ + releaseMemArray(p->aVar, p->nVar); + sqlite3DbFree(db, p->pVList); + sqlite3DbFree(db, p->pFree); + } vdbeFreeOpArray(db, p->aOp, p->nOp); sqlite3DbFree(db, p->aColName); sqlite3DbFree(db, p->zSql); - sqlite3DbFree(db, p->pFree); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS - for(i=0; inScan; i++){ - sqlite3DbFree(db, p->aScan[i].zName); + { + int i; + for(i=0; inScan; i++){ + sqlite3DbFree(db, p->aScan[i].zName); + } + sqlite3DbFree(db, p->aScan); } - sqlite3DbFree(db, p->aScan); #endif } @@ -73824,30 +74845,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( ** If an OOM error occurs, NULL is returned. */ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( - KeyInfo *pKeyInfo, /* Description of the record */ - char *pSpace, /* Unaligned space available */ - int szSpace, /* Size of pSpace[] in bytes */ - char **ppFree /* OUT: Caller should free this pointer */ + KeyInfo *pKeyInfo /* Description of the record */ ){ UnpackedRecord *p; /* Unpacked record to return */ - int nOff; /* Increment pSpace by nOff to align it */ int nByte; /* Number of bytes required for *p */ - - /* We want to shift the pointer pSpace up such that it is 8-byte aligned. - ** Thus, we need to calculate a value, nOff, between 0 and 7, to shift - ** it by. If pSpace is already 8-byte aligned, nOff should be zero. - */ - nOff = (8 - (SQLITE_PTR_TO_INT(pSpace) & 7)) & 7; nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nField+1); - if( nByte>szSpace+nOff ){ - p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); - *ppFree = (char *)p; - if( !p ) return 0; - }else{ - p = (UnpackedRecord*)&pSpace[nOff]; - *ppFree = 0; - } - + p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); + if( !p ) return 0; p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))]; assert( pKeyInfo->aSortOrder!=0 ); p->pKeyInfo = pKeyInfo; @@ -73895,7 +74899,7 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( p->nField = u; } -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG /* ** This function compares two index or table record keys in the same way ** as the sqlite3VdbeRecordCompare() routine. Unlike VdbeRecordCompare(), @@ -74000,7 +75004,7 @@ static int vdbeRecordCompareDebug( } #endif -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG /* ** Count the number of fields (a.k.a. columns) in the record given by ** pKey,nKey. The verify that this count is less than or equal to the @@ -74074,15 +75078,49 @@ static int vdbeCompareMemString( } } +/* +** The input pBlob is guaranteed to be a Blob that is not marked +** with MEM_Zero. Return true if it could be a zero-blob. +*/ +static int isAllZero(const char *z, int n){ + int i; + for(i=0; iz, pB2->z, pB1->n>pB2->n ? pB2->n : pB1->n); + int c; + int n1 = pB1->n; + int n2 = pB2->n; + + /* It is possible to have a Blob value that has some non-zero content + ** followed by zero content. But that only comes up for Blobs formed + ** by the OP_MakeRecord opcode, and such Blobs never get passed into + ** sqlite3MemCompare(). */ + assert( (pB1->flags & MEM_Zero)==0 || n1==0 ); + assert( (pB2->flags & MEM_Zero)==0 || n2==0 ); + + if( (pB1->flags|pB2->flags) & MEM_Zero ){ + if( pB1->flags & pB2->flags & MEM_Zero ){ + return pB1->u.nZero - pB2->u.nZero; + }else if( pB1->flags & MEM_Zero ){ + if( !isAllZero(pB2->z, pB2->n) ) return -1; + return pB1->u.nZero - n2; + }else{ + if( !isAllZero(pB1->z, pB1->n) ) return +1; + return n1 - pB2->u.nZero; + } + } + c = memcmp(pB1->z, pB2->z, n1>n2 ? n2 : n1); if( c ) return c; - return pB1->n - pB2->n; + return n1 - n2; } /* @@ -74388,6 +75426,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* RHS is a blob */ else if( pRhs->flags & MEM_Blob ){ + assert( (pRhs->flags & MEM_Zero)==0 || pRhs->n==0 ); getVarint32(&aKey1[idx1], serial_type); testcase( serial_type==12 ); if( serial_type<12 || (serial_type & 0x01) ){ @@ -74399,6 +75438,12 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( if( (d1+nStr) > (unsigned)nKey1 ){ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ + }else if( pRhs->flags & MEM_Zero ){ + if( !isAllZero((const char*)&aKey1[d1],nStr) ){ + rc = 1; + }else{ + rc = nStr - pRhs->u.nZero; + } }else{ int nCmp = MIN(nStr, pRhs->n); rc = memcmp(&aKey1[d1], pRhs->z, nCmp); @@ -74469,7 +75514,7 @@ static int vdbeRecordCompareInt( int res; u32 y; u64 x; - i64 v = pPKey2->aMem[0].u.i; + i64 v; i64 lhs; vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); @@ -74528,6 +75573,7 @@ static int vdbeRecordCompareInt( return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); } + v = pPKey2->aMem[0].u.i; if( v>lhs ){ res = pPKey2->r1; }else if( v0 ); - if( iVar>32 ){ - v->expmask = 0xffffffff; + if( iVar>=32 ){ + v->expmask |= 0x80000000; }else{ v->expmask |= ((u32)1 << (iVar-1)); } @@ -74875,10 +75921,10 @@ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ ** This function is used to free UnpackedRecord structures allocated by ** the vdbeUnpackRecord() function found in vdbeapi.c. */ -static void vdbeFreeUnpacked(sqlite3 *db, UnpackedRecord *p){ +static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ if( p ){ int i; - for(i=0; inField; i++){ + for(i=0; iaMem[i]; if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); } @@ -74911,10 +75957,15 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( assert( db->pPreUpdate==0 ); memset(&preupdate, 0, sizeof(PreUpdate)); - if( op==SQLITE_UPDATE ){ - iKey2 = v->aMem[iReg].u.i; + if( HasRowid(pTab)==0 ){ + iKey1 = iKey2 = 0; + preupdate.pPk = sqlite3PrimaryKeyIndex(pTab); }else{ - iKey2 = iKey1; + if( op==SQLITE_UPDATE ){ + iKey2 = v->aMem[iReg].u.i; + }else{ + iKey2 = iKey1; + } } assert( pCsr->nField==pTab->nCol @@ -74931,14 +75982,14 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( preupdate.keyinfo.aSortOrder = (u8*)&fakeSortOrder; preupdate.iKey1 = iKey1; preupdate.iKey2 = iKey2; - preupdate.iPKey = pTab->iPKey; + preupdate.pTab = pTab; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); db->pPreUpdate = 0; sqlite3DbFree(db, preupdate.aRecord); - vdbeFreeUnpacked(db, preupdate.pUnpacked); - vdbeFreeUnpacked(db, preupdate.pNewUnpacked); + vdbeFreeUnpacked(db, preupdate.keyinfo.nField+1, preupdate.pUnpacked); + vdbeFreeUnpacked(db, preupdate.keyinfo.nField+1, preupdate.pNewUnpacked); if( preupdate.aNew ){ int i; for(i=0; inField; i++){ @@ -75107,7 +76158,8 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ sqlite3VdbeMemRelease(&p->aVar[i]); p->aVar[i].flags = MEM_Null; } - if( p->isPrepareV2 && p->expmask ){ + assert( p->isPrepareV2 || p->expmask==0 ); + if( p->expmask ){ p->expired = 1; } sqlite3_mutex_leave(mutex); @@ -75122,7 +76174,7 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ SQLITE_API const void *sqlite3_value_blob(sqlite3_value *pVal){ Mem *p = (Mem*)pVal; if( p->flags & (MEM_Blob|MEM_Str) ){ - if( sqlite3VdbeMemExpandBlob(p)!=SQLITE_OK ){ + if( ExpandBlob(p)!=SQLITE_OK ){ assert( p->flags==MEM_Null && p->z==0 ); return 0; } @@ -75452,7 +76504,7 @@ static int doWalCallbacks(sqlite3 *db){ nEntry = sqlite3PagerWalCallback(sqlite3BtreePager(pBt)); sqlite3BtreeLeave(pBt); if( db->xWalCallback && nEntry>0 && rc==SQLITE_OK ){ - rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zName, nEntry); + rc = db->xWalCallback(db->pWalArg, db, db->aDb[i].zDbSName, nEntry); } } } @@ -75905,14 +76957,13 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ Mem *pOut; pVm = (Vdbe *)pStmt; - if( pVm && pVm->pResultSet!=0 && inResColumn && i>=0 ){ - sqlite3_mutex_enter(pVm->db->mutex); + if( pVm==0 ) return (Mem*)columnNullValue(); + assert( pVm->db ); + sqlite3_mutex_enter(pVm->db->mutex); + if( pVm->pResultSet!=0 && inResColumn && i>=0 ){ pOut = &pVm->pResultSet[i]; }else{ - if( pVm && ALWAYS(pVm->db) ){ - sqlite3_mutex_enter(pVm->db->mutex); - sqlite3Error(pVm->db, SQLITE_RANGE); - } + sqlite3Error(pVm->db, SQLITE_RANGE); pOut = (Mem*)columnNullValue(); } return pOut; @@ -75945,6 +76996,8 @@ static void columnMallocFailure(sqlite3_stmt *pStmt) */ Vdbe *p = (Vdbe *)pStmt; if( p ){ + assert( p->db!=0 ); + assert( sqlite3_mutex_held(p->db->mutex) ); p->rc = sqlite3ApiExit(p->db, p->rc); sqlite3_mutex_leave(p->db->mutex); } @@ -76210,9 +77263,8 @@ static int vdbeUnbind(Vdbe *p, int i){ ** as if there had been a schema change, on the first sqlite3_step() call ** following any change to the bindings of that parameter. */ - if( p->isPrepareV2 && - ((i<32 && p->expmask & ((u32)1 << i)) || p->expmask==0xffffffff) - ){ + assert( p->isPrepareV2 || p->expmask==0 ); + if( p->expmask!=0 && (p->expmask & (i>=31 ? 0x80000000 : (u32)1<expired = 1; } return SQLITE_OK; @@ -76422,10 +77474,8 @@ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ */ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ Vdbe *p = (Vdbe*)pStmt; - if( p==0 || i<1 || i>p->nzVar ){ - return 0; - } - return p->azVar[i-1]; + if( p==0 ) return 0; + return sqlite3VListNumToName(p->pVList, i); } /* @@ -76434,19 +77484,8 @@ SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){ ** return 0. */ SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe *p, const char *zName, int nName){ - int i; - if( p==0 ){ - return 0; - } - if( zName ){ - for(i=0; inzVar; i++){ - const char *z = p->azVar[i]; - if( z && strncmp(z,zName,nName)==0 && z[nName]==0 ){ - return i+1; - } - } - } - return 0; + if( p==0 || zName==0 ) return 0; + return sqlite3VListNameToNum(p->pVList, zName, nName); } SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){ return sqlite3VdbeParameterIndex((Vdbe*)pStmt, zName, sqlite3Strlen30(zName)); @@ -76488,10 +77527,12 @@ SQLITE_API int sqlite3_transfer_bindings(sqlite3_stmt *pFromStmt, sqlite3_stmt * if( pFrom->nVar!=pTo->nVar ){ return SQLITE_ERROR; } - if( pTo->isPrepareV2 && pTo->expmask ){ + assert( pTo->isPrepareV2 || pTo->expmask==0 ); + if( pTo->expmask ){ pTo->expired = 1; } - if( pFrom->isPrepareV2 && pFrom->expmask ){ + assert( pFrom->isPrepareV2 || pFrom->expmask==0 ); + if( pFrom->expmask ){ pFrom->expired = 1; } return sqlite3TransferBindings(pFromStmt, pToStmt); @@ -76521,7 +77562,7 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt){ */ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->pc>=0 && v->magic==VDBE_MAGIC_RUN; + return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0; } /* @@ -76609,10 +77650,9 @@ static UnpackedRecord *vdbeUnpackRecord( int nKey, const void *pKey ){ - char *dummy; /* Dummy argument for AllocUnpackedRecord() */ UnpackedRecord *pRet; /* Return value */ - pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo, 0, 0, &dummy); + pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pRet ){ memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nField+1)); sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); @@ -76626,6 +77666,7 @@ static UnpackedRecord *vdbeUnpackRecord( */ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ PreUpdate *p = db->pPreUpdate; + Mem *pMem; int rc = SQLITE_OK; /* Test that this call is being made from within an SQLITE_DELETE or @@ -76634,6 +77675,9 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa rc = SQLITE_MISUSE_BKPT; goto preupdate_old_out; } + if( p->pPk ){ + iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; @@ -76647,7 +77691,7 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); aRec = sqlite3DbMallocRaw(db, nRec); if( !aRec ) goto preupdate_old_out; - rc = sqlite3BtreeData(p->pCsr->uc.pCursor, 0, nRec, aRec); + rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); if( rc==SQLITE_OK ){ p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); if( !p->pUnpacked ) rc = SQLITE_NOMEM; @@ -76659,12 +77703,14 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa p->aRecord = aRec; } - if( iIdx>=p->pUnpacked->nField ){ + pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey1); + }else if( iIdx>=p->pUnpacked->nField ){ *ppValue = (sqlite3_value *)columnNullValue(); - }else{ - *ppValue = &p->pUnpacked->aMem[iIdx]; - if( iIdx==p->iPKey ){ - sqlite3VdbeMemSetInt64(*ppValue, p->iKey1); + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ + if( pMem->flags & MEM_Int ){ + sqlite3VdbeMemRealify(pMem); } } @@ -76717,6 +77763,9 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa rc = SQLITE_MISUSE_BKPT; goto preupdate_new_out; } + if( p->pPk && p->op!=SQLITE_UPDATE ){ + iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + } if( iIdx>=p->pCsr->nField || iIdx<0 ){ rc = SQLITE_RANGE; goto preupdate_new_out; @@ -76728,7 +77777,7 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa UnpackedRecord *pUnpack = p->pNewUnpacked; if( !pUnpack ){ Mem *pData = &p->v->aMem[p->iNewReg]; - rc = sqlite3VdbeMemExpandBlob(pData); + rc = ExpandBlob(pData); if( rc!=SQLITE_OK ) goto preupdate_new_out; pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z); if( !pUnpack ){ @@ -76737,13 +77786,11 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa } p->pNewUnpacked = pUnpack; } - if( iIdx>=pUnpack->nField ){ + pMem = &pUnpack->aMem[iIdx]; + if( iIdx==p->pTab->iPKey ){ + sqlite3VdbeMemSetInt64(pMem, p->iKey2); + }else if( iIdx>=pUnpack->nField ){ pMem = (sqlite3_value *)columnNullValue(); - }else{ - pMem = &pUnpack->aMem[iIdx]; - if( iIdx==p->iPKey ){ - sqlite3VdbeMemSetInt64(pMem, p->iKey2); - } } }else{ /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required @@ -76762,7 +77809,7 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa assert( iIdx>=0 && iIdxpCsr->nField ); pMem = &p->aNew[iIdx]; if( pMem->flags==0 ){ - if( iIdx==p->iPKey ){ + if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey2); }else{ rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); @@ -77159,7 +78206,7 @@ SQLITE_API int sqlite3_found_count = 0; ** Test a register to see if it exceeds the current maximum blob size. ** If it does, record the new maximum blob size. */ -#if defined(SQLITE_TEST) && !defined(SQLITE_OMIT_BUILTIN_TEST) +#if defined(SQLITE_TEST) && !defined(SQLITE_UNTESTABLE) # define UPDATE_MAX_BLOBSIZE(P) updateMaxBlobsize(P) #else # define UPDATE_MAX_BLOBSIZE(P) @@ -77269,7 +78316,7 @@ static VdbeCursor *allocateCursor( } if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; - memset(pCx, 0, sizeof(VdbeCursor)); + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); pCx->eCurType = eCurType; pCx->iDb = iDb; pCx->nField = nField; @@ -77451,9 +78498,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){ }else{ c = 's'; } - - sqlite3_snprintf(100, zCsr, "%c", c); - zCsr += sqlite3Strlen30(zCsr); + *(zCsr++) = c; sqlite3_snprintf(100, zCsr, "%d[", pMem->n); zCsr += sqlite3Strlen30(zCsr); for(i=0; i<16 && in; i++){ @@ -77465,9 +78510,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){ if( z<32 || z>126 ) *zCsr++ = '.'; else *zCsr++ = z; } - - sqlite3_snprintf(100, zCsr, "]%s", encnames[pMem->enc]); - zCsr += sqlite3Strlen30(zCsr); + *(zCsr++) = ']'; if( f & MEM_Zero ){ sqlite3_snprintf(100, zCsr,"+%dz",pMem->u.nZero); zCsr += sqlite3Strlen30(zCsr); @@ -77710,7 +78753,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec( sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ u8 encoding = ENC(db); /* The database encoding */ - int iCompare = 0; /* Result of last OP_Compare operation */ + int iCompare = 0; /* Result of last comparison */ unsigned nVmStep = 0; /* Number of virtual machine steps */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK unsigned nProgressLimit = 0;/* Invoke xProgress() when nVmStep reaches this */ @@ -77720,8 +78763,6 @@ SQLITE_PRIVATE int sqlite3VdbeExec( Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ - int *aPermute = 0; /* Permutation of columns for OP_Compare */ - i64 lastRowid = db->lastRowid; /* Saved value of the last insert ROWID */ #ifdef VDBE_PROFILE u64 start; /* CPU clock count at start of opcode */ #endif @@ -77736,7 +78777,6 @@ SQLITE_PRIVATE int sqlite3VdbeExec( } assert( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_BUSY ); assert( p->bIsReader || p->readOnly!=0 ); - p->rc = SQLITE_OK; p->iCurrentTime = 0; assert( p->explain==0 ); p->pResultSet = 0; @@ -78042,7 +79082,7 @@ case OP_Yield: { /* in1, jump */ } /* Opcode: HaltIfNull P1 P2 P3 P4 P5 -** Synopsis: if r[P3]=null halt +** Synopsis: if r[P3]=null halt ** ** Check the value in register P3. If it is NULL then Halt using ** parameter P1, P2, and P4 as if this were a Halt instruction. If the @@ -78097,7 +79137,6 @@ case OP_Halt: { p->nFrame--; sqlite3VdbeSetChanges(db, p->nChange); pcx = sqlite3VdbeFrameRestore(pFrame); - lastRowid = db->lastRowid; if( pOp->p2==OE_Ignore ){ /* Instruction pcx is the OP_Program that invoked the sub-program ** currently being halted. If the p2 instruction of this OP_Halt @@ -78114,7 +79153,7 @@ case OP_Halt: { p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; p->pc = pcx; - assert( pOp->p5>=0 && pOp->p5<=4 ); + assert( pOp->p5<=4 ); if( p->rc ){ if( pOp->p5 ){ static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", @@ -78255,7 +79294,7 @@ case OP_String: { /* out2 */ } /* Opcode: Null P1 P2 P3 * * -** Synopsis: r[P2..P3]=NULL +** Synopsis: r[P2..P3]=NULL ** ** Write a NULL into registers P2. If P3 greater than P2, then also write ** NULL into register P3 and every register in between P2 and P3. If P3 @@ -78273,18 +79312,20 @@ case OP_Null: { /* out2 */ cnt = pOp->p3-pOp->p2; assert( pOp->p3<=(p->nMem+1 - p->nCursor) ); pOut->flags = nullFlag = pOp->p1 ? (MEM_Null|MEM_Cleared) : MEM_Null; + pOut->n = 0; while( cnt>0 ){ pOut++; memAboutToChange(p, pOut); sqlite3VdbeMemSetNull(pOut); pOut->flags = nullFlag; + pOut->n = 0; cnt--; } break; } /* Opcode: SoftNull P1 * * * * -** Synopsis: r[P1]=NULL +** Synopsis: r[P1]=NULL ** ** Set register P1 to have the value NULL as seen by the OP_MakeRecord ** instruction, but do not free any string or blob memory associated with @@ -78325,19 +79366,19 @@ case OP_Variable: { /* out2 */ Mem *pVar; /* Value being transferred */ assert( pOp->p1>0 && pOp->p1<=p->nVar ); - assert( pOp->p4.z==0 || pOp->p4.z==p->azVar[pOp->p1-1] ); + assert( pOp->p4.z==0 || pOp->p4.z==sqlite3VListNumToName(p->pVList,pOp->p1) ); pVar = &p->aVar[pOp->p1 - 1]; if( sqlite3VdbeMemTooBig(pVar) ){ goto too_big; } - pOut = out2Prerelease(p, pOp); + pOut = &aMem[pOp->p2]; sqlite3VdbeMemShallowCopy(pOut, pVar, MEM_Static); UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: Move P1 P2 P3 * * -** Synopsis: r[P2@P3]=r[P1@P3] +** Synopsis: r[P2@P3]=r[P1@P3] ** ** Move the P3 values in register P1..P1+P3-1 over into ** registers P2..P2+P3-1. Registers P1..P1+P3-1 are @@ -78447,7 +79488,7 @@ case OP_IntCopy: { /* out2 */ } /* Opcode: ResultRow P1 P2 * * * -** Synopsis: output=r[P1@P2] +** Synopsis: output=r[P1@P2] ** ** The registers P1 through P1+P2-1 contain a single row of ** results. This opcode causes the sqlite3_step() call to terminate @@ -78580,14 +79621,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ } /* Opcode: Add P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]+r[P2] +** Synopsis: r[P3]=r[P1]+r[P2] ** ** Add the value in register P1 to the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Multiply P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]*r[P2] +** Synopsis: r[P3]=r[P1]*r[P2] ** ** ** Multiply the value in register P1 by the value in register P2 @@ -78595,14 +79636,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** If either input is NULL, the result is NULL. */ /* Opcode: Subtract P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]-r[P1] +** Synopsis: r[P3]=r[P2]-r[P1] ** ** Subtract the value in register P1 from the value in register P2 ** and store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: Divide P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]/r[P1] +** Synopsis: r[P3]=r[P2]/r[P1] ** ** Divide the value in register P1 by the value in register P2 ** and store the result in register P3 (P3=P2/P1). If the value in @@ -78610,7 +79651,7 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** NULL, the result is NULL. */ /* Opcode: Remainder P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]%r[P1] +** Synopsis: r[P3]=r[P2]%r[P1] ** ** Compute the remainder after integer register P2 is divided by ** register P1 and store the result in register P3. @@ -78808,23 +79849,21 @@ case OP_Function: { for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; } - memAboutToChange(p, pCtx->pOut); + memAboutToChange(p, pOut); #ifdef SQLITE_DEBUG for(i=0; iargc; i++){ assert( memIsValid(pCtx->argv[i]) ); REGISTER_TRACE(pOp->p2+i, pCtx->argv[i]); } #endif - MemSetTypeFlag(pCtx->pOut, MEM_Null); + MemSetTypeFlag(pOut, MEM_Null); pCtx->fErrorOrAux = 0; - db->lastRowid = lastRowid; (*pCtx->pFunc->xSFunc)(pCtx, pCtx->argc, pCtx->argv);/* IMP: R-24505-23230 */ - lastRowid = db->lastRowid; /* Remember rowid changes made by xSFunc */ /* If the function returned an error, throw an exception */ if( pCtx->fErrorOrAux ){ if( pCtx->isError ){ - sqlite3VdbeError(p, "%s", sqlite3_value_text(pCtx->pOut)); + sqlite3VdbeError(p, "%s", sqlite3_value_text(pOut)); rc = pCtx->isError; } sqlite3VdbeDeleteAuxData(db, &p->pAuxData, pCtx->iOp, pOp->p1); @@ -78833,31 +79872,31 @@ case OP_Function: { /* Copy the result of the function into register P3 */ if( pOut->flags & (MEM_Str|MEM_Blob) ){ - sqlite3VdbeChangeEncoding(pCtx->pOut, encoding); - if( sqlite3VdbeMemTooBig(pCtx->pOut) ) goto too_big; + sqlite3VdbeChangeEncoding(pOut, encoding); + if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; } - REGISTER_TRACE(pOp->p3, pCtx->pOut); - UPDATE_MAX_BLOBSIZE(pCtx->pOut); + REGISTER_TRACE(pOp->p3, pOut); + UPDATE_MAX_BLOBSIZE(pOut); break; } /* Opcode: BitAnd P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]&r[P2] +** Synopsis: r[P3]=r[P1]&r[P2] ** ** Take the bit-wise AND of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: BitOr P1 P2 P3 * * -** Synopsis: r[P3]=r[P1]|r[P2] +** Synopsis: r[P3]=r[P1]|r[P2] ** ** Take the bit-wise OR of the values in register P1 and P2 and ** store the result in register P3. ** If either input is NULL, the result is NULL. */ /* Opcode: ShiftLeft P1 P2 P3 * * -** Synopsis: r[P3]=r[P2]<>r[P1] +** Synopsis: r[P3]=r[P2]>>r[P1] ** ** Shift the integer value in register P2 to the right by the ** number of bits specified by the integer in register P1. @@ -78925,7 +79964,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ } /* Opcode: AddImm P1 P2 * * * -** Synopsis: r[P1]=r[P1]+P2 +** Synopsis: r[P1]=r[P1]+P2 ** ** Add the constant P2 to the value in register P1. ** The result is always an integer. @@ -79017,15 +80056,12 @@ case OP_Cast: { /* in1 */ } #endif /* SQLITE_OMIT_CAST */ -/* Opcode: Lt P1 P2 P3 P4 P5 -** Synopsis: IF r[P3]flags */ u16 flags3; /* Copy of initial value of pIn3->flags */ @@ -79124,13 +80177,12 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( pOp->opcode==OP_Eq || pOp->opcode==OP_Ne ); assert( (flags1 & MEM_Cleared)==0 ); assert( (pOp->p5 & SQLITE_JUMPIFNULL)==0 ); - if( (flags1&MEM_Null)!=0 - && (flags3&MEM_Null)!=0 + if( (flags1&flags3&MEM_Null)!=0 && (flags3&MEM_Cleared)==0 ){ - res = 0; /* Results are equal */ + res = 0; /* Operands are equal */ }else{ - res = 1; /* Results are not equal */ + res = 1; /* Operands are not equal */ } }else{ /* SQLITE_NULLEQ is clear and at least one operand is NULL, @@ -79139,6 +80191,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + iCompare = 1; /* Operands are not equal */ memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Null); REGISTER_TRACE(pOp->p2, pOut); @@ -79157,12 +80210,21 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( (flags1 | flags3)&MEM_Str ){ if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); + testcase( flags3!=pIn3->flags ); /* Possible if pIn1==pIn3 */ flags3 = pIn3->flags; } if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } + /* Handle the common case of integer comparison here, as an + ** optimization, to avoid a call to sqlite3MemCompare() */ + if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){ + if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; } + if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; } + res = 0; + goto compare_op; + } }else if( affinity==SQLITE_AFF_TEXT ){ if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn1->flags & MEM_Int ); @@ -79170,7 +80232,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - flags3 = pIn3->flags; + assert( pIn1!=pIn3 ); } if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){ testcase( pIn3->flags & MEM_Int ); @@ -79181,23 +80243,16 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ } } assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); - if( flags1 & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pIn1); - flags1 &= ~MEM_Zero; - } - if( flags3 & MEM_Zero ){ - sqlite3VdbeMemExpandBlob(pIn3); - flags3 &= ~MEM_Zero; - } res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } +compare_op: switch( pOp->opcode ){ - case OP_Eq: res = res==0; break; - case OP_Ne: res = res!=0; break; - case OP_Lt: res = res<0; break; - case OP_Le: res = res<=0; break; - case OP_Gt: res = res>0; break; - default: res = res>=0; break; + case OP_Eq: res2 = res==0; break; + case OP_Ne: res2 = res; break; + case OP_Lt: res2 = res<0; break; + case OP_Le: res2 = res<=0; break; + case OP_Gt: res2 = res>0; break; + default: res2 = res>=0; break; } /* Undo any changes made by applyAffinity() to the input registers. */ @@ -79208,23 +80263,59 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ if( pOp->p5 & SQLITE_STOREP2 ){ pOut = &aMem[pOp->p2]; + iCompare = res; + res2 = res2!=0; /* For this path res2 must be exactly 0 or 1 */ + if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){ + /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1 + ** and prevents OP_Ne from overwriting NULL with 0. This flag + ** is only used in contexts where either: + ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0) + ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1) + ** Therefore it is not necessary to check the content of r[P2] for + ** NULL. */ + assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq ); + assert( res2==0 || res2==1 ); + testcase( res2==0 && pOp->opcode==OP_Eq ); + testcase( res2==1 && pOp->opcode==OP_Eq ); + testcase( res2==0 && pOp->opcode==OP_Ne ); + testcase( res2==1 && pOp->opcode==OP_Ne ); + if( (pOp->opcode==OP_Eq)==res2 ) break; + } memAboutToChange(p, pOut); MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = res; + pOut->u.i = res2; REGISTER_TRACE(pOp->p2, pOut); }else{ VdbeBranchTaken(res!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); - if( res ){ + if( res2 ){ goto jump_to_p2; } } break; } +/* Opcode: ElseNotEq * P2 * * * +** +** This opcode must immediately follow an OP_Lt or OP_Gt comparison operator. +** If result of an OP_Eq comparison on the same two operands +** would have be NULL or false (0), then then jump to P2. +** If the result of an OP_Eq comparison on the two previous operands +** would have been true (1), then fall through. +*/ +case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ + assert( pOp>aOp ); + assert( pOp[-1].opcode==OP_Lt || pOp[-1].opcode==OP_Gt ); + assert( pOp[-1].p5 & SQLITE_STOREP2 ); + VdbeBranchTaken(iCompare!=0, 2); + if( iCompare!=0 ) goto jump_to_p2; + break; +} + + /* Opcode: Permutation * * * P4 * ** -** Set the permutation used by the OP_Compare operator to be the array -** of integers in P4. +** Set the permutation used by the OP_Compare operator in the next +** instruction. The permutation is stored in the P4 operand. ** ** The permutation is only valid until the next OP_Compare that has ** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should @@ -79236,7 +80327,8 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ case OP_Permutation: { assert( pOp->p4type==P4_INTARRAY ); assert( pOp->p4.ai ); - aPermute = pOp->p4.ai + 1; + assert( pOp[1].opcode==OP_Compare ); + assert( pOp[1].p5 & OPFLAG_PERMUTE ); break; } @@ -79269,15 +80361,24 @@ case OP_Compare: { int idx; CollSeq *pColl; /* Collating sequence to use on this term */ int bRev; /* True for DESCENDING sort order */ + int *aPermute; /* The permutation */ - if( (pOp->p5 & OPFLAG_PERMUTE)==0 ) aPermute = 0; + if( (pOp->p5 & OPFLAG_PERMUTE)==0 ){ + aPermute = 0; + }else{ + assert( pOp>aOp ); + assert( pOp[-1].opcode==OP_Permutation ); + assert( pOp[-1].p4type==P4_INTARRAY ); + aPermute = pOp[-1].p4.ai + 1; + assert( aPermute!=0 ); + } n = pOp->p3; pKeyInfo = pOp->p4.pKeyInfo; assert( n>0 ); assert( pKeyInfo!=0 ); p1 = pOp->p1; p2 = pOp->p2; -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG if( aPermute ){ int k, mx = 0; for(k=0; kmx ) mx = aPermute[k]; @@ -79303,7 +80404,6 @@ case OP_Compare: { break; } } - aPermute = 0; break; } @@ -79416,23 +80516,39 @@ case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ /* Opcode: Once P1 P2 * * * ** -** Check the "once" flag number P1. If it is set, jump to instruction P2. -** Otherwise, set the flag and fall through to the next instruction. -** In other words, this opcode causes all following opcodes up through P2 -** (but not including P2) to run just once and to be skipped on subsequent -** times through the loop. +** Fall through to the next instruction the first time this opcode is +** encountered on each invocation of the byte-code program. Jump to P2 +** on the second and all subsequent encounters during the same invocation. ** -** All "once" flags are initially cleared whenever a prepared statement -** first begins to run. +** Top-level programs determine first invocation by comparing the P1 +** operand against the P1 operand on the OP_Init opcode at the beginning +** of the program. If the P1 values differ, then fall through and make +** the P1 of this opcode equal to the P1 of OP_Init. If P1 values are +** the same then take the jump. +** +** For subprograms, there is a bitmask in the VdbeFrame that determines +** whether or not the jump should be taken. The bitmask is necessary +** because the self-altering code trick does not work for recursive +** triggers. */ case OP_Once: { /* jump */ - assert( pOp->p1nOnceFlag ); - VdbeBranchTaken(p->aOnceFlag[pOp->p1]!=0, 2); - if( p->aOnceFlag[pOp->p1] ){ - goto jump_to_p2; + u32 iAddr; /* Address of this instruction */ + assert( p->aOp[0].opcode==OP_Init ); + if( p->pFrame ){ + iAddr = (int)(pOp - p->aOp); + if( (p->pFrame->aOnce[iAddr/8] & (1<<(iAddr & 7)))!=0 ){ + VdbeBranchTaken(1, 2); + goto jump_to_p2; + } + p->pFrame->aOnce[iAddr/8] |= 1<<(iAddr & 7); }else{ - p->aOnceFlag[pOp->p1] = 1; + if( p->aOp[0].p1==pOp->p1 ){ + VdbeBranchTaken(1, 2); + goto jump_to_p2; + } } + VdbeBranchTaken(0, 2); + pOp->p1 = p->aOp[0].p1; break; } @@ -79470,7 +80586,7 @@ case OP_IfNot: { /* jump, in1 */ } /* Opcode: IsNull P1 P2 * * * -** Synopsis: if r[P1]==NULL goto P2 +** Synopsis: if r[P1]==NULL goto P2 ** ** Jump to P2 if the value in register P1 is NULL. */ @@ -79498,7 +80614,7 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ } /* Opcode: Column P1 P2 P3 P4 P5 -** Synopsis: r[P3]=PX +** Synopsis: r[P3]=PX ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional @@ -79557,7 +80673,6 @@ case OP_Column: { assert( pC->eCurType!=CURTYPE_VTAB ); assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); assert( pC->eCurType!=CURTYPE_SORTER ); - pCrsr = pC->uc.pCursor; if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ if( pC->nullRow ){ @@ -79573,6 +80688,7 @@ case OP_Column: { goto op_column_out; } }else{ + pCrsr = pC->uc.pCursor; assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -79636,7 +80752,7 @@ case OP_Column: { /* Make sure zData points to enough of the record to cover the header. */ if( pC->aRow==0 ){ memset(&sMem, 0, sizeof(sMem)); - rc = sqlite3VdbeMemFromBtree(pCrsr, 0, aOffset[0], !pC->isTable, &sMem); + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, 0, aOffset[0], &sMem); if( rc!=SQLITE_OK ) goto abort_due_to_error; zData = (u8*)sMem.z; }else{ @@ -79745,12 +80861,16 @@ case OP_Column: { ** 2. the length(X) function if X is a blob, and ** 3. if the content length is zero. ** So we might as well use bogus content rather than reading - ** content from disk. */ - static u8 aZero[8]; /* This is the bogus content */ + ** content from disk. + ** + ** Although sqlite3VdbeSerialGet() may read at most 8 bytes from the + ** buffer passed to it, debugging function VdbeMemPrettyPrint() may + ** read up to 16. So 16 bytes of bogus content is supplied. + */ + static u8 aZero[16]; /* This is the bogus content */ sqlite3VdbeSerialGet(aZero, t, pDest); }else{ - rc = sqlite3VdbeMemFromBtree(pCrsr, aOffset[p2], len, !pC->isTable, - pDest); + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); if( rc!=SQLITE_OK ) goto abort_due_to_error; sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); pDest->flags &= ~MEM_Ephem; @@ -79865,6 +80985,20 @@ case OP_MakeRecord: { }while( zAffinity[0] ); } +#ifdef SQLITE_ENABLE_NULL_TRIM + /* NULLs can be safely trimmed from the end of the record, as long as + ** as the schema format is 2 or more and none of the omitted columns + ** have a non-NULL default value. Also, the record must be left with + ** at least one field. If P5>0 then it will be one more than the + ** index of the right-most column with a non-NULL default value */ + if( pOp->p5 ){ + while( (pLast->flags & MEM_Null)!=0 && nField>pOp->p5 ){ + pLast--; + nField--; + } + } +#endif + /* Loop through the elements that will make up the record to figure ** out how much space is required for the new record. */ @@ -80264,12 +81398,12 @@ case OP_Transaction: { rc = sqlite3BtreeBeginTrans(pBt, pOp->p2); testcase( rc==SQLITE_BUSY_SNAPSHOT ); testcase( rc==SQLITE_BUSY_RECOVERY ); - if( (rc&0xff)==SQLITE_BUSY ){ - p->pc = (int)(pOp - aOp); - p->rc = rc; - goto vdbe_return; - } if( rc!=SQLITE_OK ){ + if( (rc&0xff)==SQLITE_BUSY ){ + p->pc = (int)(pOp - aOp); + p->rc = rc; + goto vdbe_return; + } goto abort_due_to_error; } @@ -80296,10 +81430,9 @@ case OP_Transaction: { } /* Gather the schema version number for checking: - ** IMPLEMENTATION-OF: R-32195-19465 The schema version is used by SQLite - ** each time a query is executed to ensure that the internal cache of the - ** schema used when compiling the SQL query matches the schema of the - ** database against which the compiled query is actually executed. + ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema + ** version is checked to ensure that the schema has not changed since the + ** SQL statement was prepared. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&iMeta); iGen = db->aDb[pOp->p1].pSchema->iGeneration; @@ -80616,10 +81749,10 @@ case OP_OpenEphemeral: { if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBt, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBt, 1); + rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1); } if( rc==SQLITE_OK ){ /* If a transient index is required, create it by calling @@ -80627,21 +81760,20 @@ case OP_OpenEphemeral: { ** opening it. If a transient table is required, just use the ** automatically created table with root-page 1 (an BLOB_INTKEY table). */ - if( (pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ + if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ int pgno; assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBt, &pgno, BTREE_BLOBKEY | pOp->p5); + rc = sqlite3BtreeCreateTable(pCx->pBtx, &pgno, BTREE_BLOBKEY | pOp->p5); if( rc==SQLITE_OK ){ assert( pgno==MASTER_ROOT+1 ); assert( pKeyInfo->db==db ); assert( pKeyInfo->enc==ENC(db) ); - pCx->pKeyInfo = pKeyInfo; - rc = sqlite3BtreeCursor(pCx->pBt, pgno, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->pBtx, pgno, BTREE_WRCSR, pKeyInfo, pCx->uc.pCursor); } pCx->isTable = 0; }else{ - rc = sqlite3BtreeCursor(pCx->pBt, MASTER_ROOT, BTREE_WRCSR, + rc = sqlite3BtreeCursor(pCx->pBtx, MASTER_ROOT, BTREE_WRCSR, 0, pCx->uc.pCursor); pCx->isTable = 1; } @@ -80873,7 +82005,8 @@ case OP_SeekGT: { /* jump, in3 */ if( pC->isTable ){ /* The BTREE_SEEK_EQ flag is only set on index cursors */ - assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 ); + assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 + || CORRUPT_DB ); /* The input value in P3 might be of any type: integer, real, string, ** blob, or NULL. But it needs to be an integer before we can do @@ -80960,7 +82093,6 @@ case OP_SeekGT: { /* jump, in3 */ #ifdef SQLITE_DEBUG { int i; for(i=0; iuc.pCursor, &r, 0, 0, &res); if( rc!=SQLITE_OK ){ @@ -81008,7 +82140,6 @@ case OP_SeekGT: { /* jump, in3 */ } break; } - /* Opcode: Found P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] @@ -81077,10 +82208,9 @@ case OP_Found: { /* jump, in3 */ int ii; VdbeCursor *pC; int res; - char *pFree; + UnpackedRecord *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; - char aTempRec[ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*4 + 7]; #ifdef SQLITE_TEST if( pOp->opcode!=OP_NoConflict ) sqlite3_found_count++; @@ -81097,26 +82227,24 @@ case OP_Found: { /* jump, in3 */ assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); - pFree = 0; if( pOp->p4.i>0 ){ r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p4.i; r.aMem = pIn3; +#ifdef SQLITE_DEBUG for(ii=0; iip3+ii, &r.aMem[ii]); -#endif } +#endif pIdxKey = &r; + pFree = 0; }else{ - pIdxKey = sqlite3VdbeAllocUnpackedRecord( - pC->pKeyInfo, aTempRec, sizeof(aTempRec), &pFree - ); + pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; assert( pIn3->flags & MEM_Blob ); - ExpandBlob(pIn3); + (void)ExpandBlob(pIn3); sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); } pIdxKey->default_rc = 0; @@ -81133,7 +82261,7 @@ case OP_Found: { /* jump, in3 */ } } rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); - sqlite3DbFree(db, pFree); + if( pFree ) sqlite3DbFree(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -81360,7 +82488,7 @@ case OP_NewRowid: { /* out2 */ sqlite3VdbeMemIntegerify(pMem); assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */ if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){ - rc = SQLITE_FULL; /* IMP: R-12275-61338 */ + rc = SQLITE_FULL; /* IMP: R-17817-00630 */ goto abort_due_to_error; } if( vu.i+1 ){ @@ -81412,15 +82540,10 @@ case OP_NewRowid: { /* out2 */ ** then rowid is stored for subsequent return by the ** sqlite3_last_insert_rowid() function (otherwise it is unmodified). ** -** If the OPFLAG_USESEEKRESULT flag of P5 is set and if the result of -** the last seek operation (OP_NotExists or OP_SeekRowid) was a success, -** then this -** operation will not attempt to find the appropriate row before doing -** the insert but will instead overwrite the row that the cursor is -** currently pointing to. Presumably, the prior OP_NotExists or -** OP_SeekRowid opcode -** has already positioned the cursor correctly. This is an optimization -** that boosts performance by avoiding redundant seeks. +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equal to P3. ** ** If the OPFLAG_ISUPDATE flag is set, then this opcode is part of an ** UPDATE operation. Otherwise (if the flag is clear) then this opcode @@ -81441,7 +82564,7 @@ case OP_NewRowid: { /* out2 */ ** for indices is OP_IdxInsert. */ /* Opcode: InsertInt P1 P2 P3 P4 P5 -** Synopsis: intkey=P3 data=r[P2] +** Synopsis: intkey=P3 data=r[P2] ** ** This works exactly like OP_Insert except that the key is the ** integer value P3, not the value of the integer stored in register P3. @@ -81465,7 +82588,7 @@ case OP_InsertInt: { assert( pC!=0 ); assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); - assert( pC->isTable ); + assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable ); assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC ); REGISTER_TRACE(pOp->p2, pData); @@ -81481,14 +82604,13 @@ case OP_InsertInt: { } if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ - assert( pC->isTable ); assert( pC->iDb>=0 ); - zDb = db->aDb[pC->iDb].zName; + zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; - assert( HasRowid(pTab) ); + assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); op = ((pOp->p5 & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_INSERT); }else{ - pTab = 0; /* Not needed. Silence a comiler warning. */ + pTab = 0; /* Not needed. Silence a compiler warning. */ zDb = 0; /* Not needed. Silence a compiler warning. */ } @@ -81500,10 +82622,11 @@ case OP_InsertInt: { ){ sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey, pOp->p2); } + if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = lastRowid = x.nKey; + if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; if( pData->flags & MEM_Null ){ x.pData = 0; x.nData = 0; @@ -81520,7 +82643,7 @@ case OP_InsertInt: { } x.pKey = 0; rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & OPFLAG_APPEND)!=0, seekResult + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), seekResult ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -81557,7 +82680,7 @@ case OP_InsertInt: { ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL then it points to a Table struture. In this case either +** If P4 is not NULL then it points to a Table object. In this case either ** the update or pre-update hook, or both, may be invoked. The P1 cursor must ** have been positioned using OP_NotFound prior to invoking this opcode in ** this case. Specifically, if one is configured, the pre-update hook is @@ -81600,7 +82723,7 @@ case OP_Delete: { if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ assert( pC->iDb>=0 ); assert( pOp->p4.pTab!=0 ); - zDb = db->aDb[pC->iDb].zName; + zDb = db->aDb[pC->iDb].zDbSName; pTab = pOp->p4.pTab; if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){ pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); @@ -81612,8 +82735,11 @@ case OP_Delete: { #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update-hook if required. */ - if( db->xPreUpdateCallback && pOp->p4.pTab && HasRowid(pTab) ){ - assert( !(opflags & OPFLAG_ISUPDATE) || (aMem[pOp->p3].flags & MEM_Int) ); + if( db->xPreUpdateCallback && pOp->p4.pTab ){ + assert( !(opflags & OPFLAG_ISUPDATE) + || HasRowid(pTab)==0 + || (aMem[pOp->p3].flags & MEM_Int) + ); sqlite3VdbePreUpdateHook(p, pC, (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, @@ -81644,6 +82770,7 @@ case OP_Delete: { rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; if( rc ) goto abort_due_to_error; /* Invoke the update-hook if required. */ @@ -81672,7 +82799,7 @@ case OP_ResetCount: { } /* Opcode: SorterCompare P1 P2 P3 P4 -** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 +** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 ** ** P1 is a sorter cursor. This instruction compares a prefix of the ** record blob in register P3 against a prefix of the entry that @@ -81730,50 +82857,51 @@ case OP_SorterData: { break; } -/* Opcode: RowData P1 P2 * * * +/* Opcode: RowData P1 P2 P3 * * ** Synopsis: r[P2]=data ** -** Write into register P2 the complete row data for cursor P1. +** Write into register P2 the complete row content for the row at +** which cursor P1 is currently pointing. ** There is no interpretation of the data. ** It is just copied onto the P2 register exactly as ** it is found in the database file. ** +** If cursor P1 is an index, then the content is the key of the row. +** If cursor P2 is a table, then the content extracted is the data. +** ** If the P1 cursor must be pointing to a valid row (not a NULL row) ** of a real table, not a pseudo-table. -*/ -/* Opcode: RowKey P1 P2 * * * -** Synopsis: r[P2]=key ** -** Write into register P2 the complete row key for cursor P1. -** There is no interpretation of the data. -** The key is copied onto the P2 register exactly as -** it is found in the database file. +** If P3!=0 then this opcode is allowed to make an ephermeral pointer +** into the database page. That means that the content of the output +** register will be invalidated as soon as the cursor moves - including +** moves caused by other cursors that "save" the the current cursors +** position in order that they can write to the same table. If P3==0 +** then a copy of the data is made into memory. P3!=0 is faster, but +** P3==0 is safer. ** -** If the P1 cursor must be pointing to a valid row (not a NULL row) -** of a real table, not a pseudo-table. +** If P3!=0 then the content of the P2 register is unsuitable for use +** in OP_Result and any OP_Result will invalidate the P2 register content. +** The P2 register content is invalidated by opcodes like OP_Function or +** by any use of another cursor pointing to the same table. */ -case OP_RowKey: case OP_RowData: { VdbeCursor *pC; BtCursor *pCrsr; u32 n; - pOut = &aMem[pOp->p2]; - memAboutToChange(p, pOut); + pOut = out2Prerelease(p, pOp); - /* Note that RowKey and RowData are really exactly the same instruction */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->eCurType==CURTYPE_BTREE ); assert( isSorter(pC)==0 ); - assert( pC->isTable || pOp->opcode!=OP_RowData ); - assert( pC->isTable==0 || pOp->opcode==OP_RowData ); assert( pC->nullRow==0 ); assert( pC->uc.pCursor!=0 ); pCrsr = pC->uc.pCursor; - /* The OP_RowKey and OP_RowData opcodes always follow OP_NotExists or + /* The OP_RowData opcodes always follow OP_NotExists or ** OP_SeekRowid or OP_Rewind/Op_Next with no intervening instructions ** that might invalidate the cursor. ** If this where not the case, on of the following assert()s @@ -81793,18 +82921,9 @@ case OP_RowData: { goto too_big; } testcase( n==0 ); - if( sqlite3VdbeMemClearAndResize(pOut, MAX(n,32)) ){ - goto no_mem; - } - pOut->n = n; - MemSetTypeFlag(pOut, MEM_Blob); - if( pC->isTable==0 ){ - rc = sqlite3BtreeKey(pCrsr, 0, n, pOut->z); - }else{ - rc = sqlite3BtreeData(pCrsr, 0, n, pOut->z); - } + rc = sqlite3VdbeMemFromBtree(pCrsr, 0, n, pOut); if( rc ) goto abort_due_to_error; - pOut->enc = SQLITE_UTF8; /* In case the blob is ever cast to text */ + if( !pOp->p3 ) Deephemeralize(pOut); UPDATE_MAX_BLOBSIZE(pOut); REGISTER_TRACE(pOp->p2, pOut); break; @@ -81893,6 +83012,13 @@ case OP_NullRow: { ** This opcode leaves the cursor configured to move in reverse order, ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. +** +** If P3 is -1, then the cursor is positioned at the end of the btree +** for the purpose of appending a new entry onto the btree. In that +** case P2 must be 0. It is assumed that the cursor is used only for +** appending and so if the cursor is valid, then the cursor must already +** be pointing at the end of the btree and so no changes are made to +** the cursor. */ case OP_Last: { /* jump */ VdbeCursor *pC; @@ -81906,23 +83032,63 @@ case OP_Last: { /* jump */ pCrsr = pC->uc.pCursor; res = 0; assert( pCrsr!=0 ); - rc = sqlite3BtreeLast(pCrsr, &res); - pC->nullRow = (u8)res; - pC->deferredMoveto = 0; - pC->cacheStatus = CACHE_STALE; pC->seekResult = pOp->p3; #ifdef SQLITE_DEBUG pC->seekOp = OP_Last; #endif + if( pOp->p3==0 || !sqlite3BtreeCursorIsValidNN(pCrsr) ){ + rc = sqlite3BtreeLast(pCrsr, &res); + pC->nullRow = (u8)res; + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; + if( rc ) goto abort_due_to_error; + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } + }else{ + assert( pOp->p2==0 ); + } + break; +} + +/* Opcode: IfSmaller P1 P2 P3 * * +** +** Estimate the number of rows in the table P1. Jump to P2 if that +** estimate is less than approximately 2**(0.1*P3). +*/ +case OP_IfSmaller: { /* jump */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + i64 sz; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeFirst(pCrsr, &res); if( rc ) goto abort_due_to_error; - if( pOp->p2>0 ){ - VdbeBranchTaken(res!=0,2); - if( res ) goto jump_to_p2; + if( res==0 ){ + sz = sqlite3BtreeRowCountEst(pCrsr); + if( ALWAYS(sz>=0) && sqlite3LogEst((u64)sz)p3 ) res = 1; } + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; break; } +/* Opcode: SorterSort P1 P2 * * * +** +** After all records have been inserted into the Sorter object +** identified by P1, invoke this opcode to actually do the sorting. +** Jump to P2 if there are no records to be sorted. +** +** This opcode is an alias for OP_Sort and OP_Rewind that is used +** for Sorter objects. +*/ /* Opcode: Sort P1 P2 * * * ** ** This opcode does exactly the same thing as OP_Rewind except that @@ -82050,6 +83216,13 @@ case OP_Rewind: { /* jump */ ** This opcode works just like Prev except that if cursor P1 is not ** open it behaves a no-op. */ +/* Opcode: SorterNext P1 P2 * * P5 +** +** This opcode works just like OP_Next except that P1 must be a +** sorter object for which the OP_SorterSort opcode has been +** invoked. This opcode advances the cursor to the next sorted +** record, or jumps to P2 if there are no more sorted records. +*/ case OP_SorterNext: { /* jump */ VdbeCursor *pC; int res; @@ -82106,27 +83279,41 @@ case OP_Next: /* jump */ goto check_for_interrupt; } -/* Opcode: IdxInsert P1 P2 P3 * P5 +/* Opcode: IdxInsert P1 P2 P3 P4 P5 ** Synopsis: key=r[P2] ** ** Register P2 holds an SQL index key made using the ** MakeRecord instructions. This opcode writes that key ** into the index P1. Data for the entry is nil. ** -** P3 is a flag that provides a hint to the b-tree layer that this -** insert is likely to be an append. +** If P4 is not zero, then it is the number of values in the unpacked +** key of reg(P2). In that case, P3 is the index of the first register +** for the unpacked key. The availability of the unpacked key can sometimes +** be an optimization. +** +** If P5 has the OPFLAG_APPEND bit set, that is a hint to the b-tree layer +** that this insert is likely to be an append. ** ** If P5 has the OPFLAG_NCHANGE bit set, then the change counter is ** incremented by this instruction. If the OPFLAG_NCHANGE bit is clear, ** then the change counter is unchanged. ** -** If P5 has the OPFLAG_USESEEKRESULT bit set, then the cursor must have -** just done a seek to the spot where the new entry is to be inserted. -** This flag avoids doing an extra seek. +** If the OPFLAG_USESEEKRESULT flag of P5 is set, the implementation might +** run faster by avoiding an unnecessary seek on cursor P1. However, +** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior +** seeks on the cursor or if the most recent seek used a key equivalent +** to P2. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. */ +/* Opcode: SorterInsert P1 P2 * * * +** Synopsis: key=r[P2] +** +** Register P2 holds an SQL index key made using the +** MakeRecord instructions. This opcode writes that key +** into the sorter P1. Data for the entry is nil. +*/ case OP_SorterInsert: /* in2 */ case OP_IdxInsert: { /* in2 */ VdbeCursor *pC; @@ -82148,10 +83335,10 @@ case OP_IdxInsert: { /* in2 */ }else{ x.nKey = pIn2->n; x.pKey = pIn2->z; - x.nData = 0; - x.nZero = 0; - x.pData = 0; - rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, pOp->p3, + x.aMem = aMem + pOp->p3; + x.nMem = (u16)pOp->p4.i; + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) ); assert( pC->deferredMoveto==0 ); @@ -82195,11 +83382,12 @@ case OP_IdxDelete: { } assert( pC->deferredMoveto==0 ); pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; break; } /* Opcode: Seek P1 * P3 P4 * -** Synopsis: Move P3 to P1.rowid +** Synopsis: Move P3 to P1.rowid ** ** P1 is an open index cursor and P3 is a cursor on the corresponding ** table. This opcode does a deferred seek of the P3 table cursor @@ -82272,7 +83460,6 @@ case OP_IdxRowid: { /* out2 */ }else{ pOut = out2Prerelease(p, pOp); pOut->u.i = rowid; - pOut->flags = MEM_Int; } }else{ assert( pOp->opcode==OP_IdxRowid ); @@ -82536,6 +83723,18 @@ case OP_CreateTable: { /* out2 */ break; } +/* Opcode: SqlExec * * * P4 * +** +** Run the SQL statement or statements specified in the P4 string. +*/ +case OP_SqlExec: { + db->nSqlExec++; + rc = sqlite3_exec(db, pOp->p4.z, 0, 0, 0); + db->nSqlExec--; + if( rc ) goto abort_due_to_error; + break; +} + /* Opcode: ParseSchema P1 * * P4 * ** ** Read and parse all entries from the SQLITE_MASTER table of database P1 @@ -82564,13 +83763,13 @@ case OP_ParseSchema: { assert( iDb>=0 && iDbnDb ); assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); /* Used to be a conditional */ { - zMaster = SCHEMA_TABLE(iDb); + zMaster = MASTER_NAME; initData.db = db; initData.iDb = pOp->p1; initData.pzErrMsg = &p->zErrMsg; zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid", - db->aDb[iDb].zName, zMaster, pOp->p4.z); + db->aDb[iDb].zDbSName, zMaster, pOp->p4.z); if( zSql==0 ){ rc = SQLITE_NOMEM_BKPT; }else{ @@ -82656,7 +83855,7 @@ case OP_DropTrigger: { ** register P1 the text of an error message describing any problems. ** If no problems are found, store a NULL in register P1. ** -** The register P3 contains the maximum number of allowed errors. +** The register P3 contains one less than the maximum number of allowed errors. ** At most reg(P3) errors will be reported. ** In other words, the analysis stops as soon as reg(P1) errors are ** seen. Reg(P1) is updated with the number of errors remaining. @@ -82689,14 +83888,14 @@ case OP_IntegrityCk: { assert( pOp->p5nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, aRoot, nRoot, - (int)pnErr->u.i, &nErr); - pnErr->u.i -= nErr; + (int)pnErr->u.i+1, &nErr); sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ assert( z==0 ); }else if( z==0 ){ goto no_mem; }else{ + pnErr->u.i -= nErr-1; sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); } UPDATE_MAX_BLOBSIZE(pIn1); @@ -82706,7 +83905,7 @@ case OP_IntegrityCk: { #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ /* Opcode: RowSetAdd P1 P2 * * * -** Synopsis: rowset(P1)=r[P2] +** Synopsis: rowset(P1)=r[P2] ** ** Insert the integer value held by register P2 into a boolean index ** held in register P1. @@ -82726,7 +83925,7 @@ case OP_RowSetAdd: { /* in1, in2 */ } /* Opcode: RowSetRead P1 P2 P3 * * -** Synopsis: r[P3]=rowset(P1) +** Synopsis: r[P3]=rowset(P1) ** ** Extract the smallest value from boolean index P1 and put that value into ** register P3. Or, if boolean index P1 is initially empty, leave P3 @@ -82875,8 +84074,8 @@ case OP_Program: { /* jump */ if( pProgram->nCsr==0 ) nMem++; nByte = ROUND8(sizeof(VdbeFrame)) + nMem * sizeof(Mem) - + pProgram->nCsr * sizeof(VdbeCursor *) - + pProgram->nOnce * sizeof(u8); + + pProgram->nCsr * sizeof(VdbeCursor*) + + (pProgram->nOp + 7)/8; pFrame = sqlite3DbMallocZero(db, nByte); if( !pFrame ){ goto no_mem; @@ -82896,8 +84095,6 @@ case OP_Program: { /* jump */ pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; - pFrame->aOnceFlag = p->aOnceFlag; - pFrame->nOnceFlag = p->nOnceFlag; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS pFrame->anExec = p->anExec; #endif @@ -82917,7 +84114,7 @@ case OP_Program: { /* jump */ p->nFrame++; pFrame->pParent = p->pFrame; - pFrame->lastRowid = lastRowid; + pFrame->lastRowid = db->lastRowid; pFrame->nChange = p->nChange; pFrame->nDbChange = p->db->nChange; assert( pFrame->pAuxData==0 ); @@ -82929,15 +84126,14 @@ case OP_Program: { /* jump */ p->nMem = pFrame->nChildMem; p->nCursor = (u16)pFrame->nChildCsr; p->apCsr = (VdbeCursor **)&aMem[p->nMem]; + pFrame->aOnce = (u8*)&p->apCsr[pProgram->nCsr]; + memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; - p->aOnceFlag = (u8 *)&p->apCsr[p->nCursor]; - p->nOnceFlag = pProgram->nOnce; #ifdef SQLITE_ENABLE_STMT_SCANSTATUS p->anExec = 0; #endif pOp = &aOp[-1]; - memset(p->aOnceFlag, 0, p->nOnceFlag); break; } @@ -83081,29 +84277,42 @@ case OP_IfPos: { /* jump, in1 */ ** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. */ case OP_OffsetLimit: { /* in1, out2, in3 */ + i64 x; pIn1 = &aMem[pOp->p1]; pIn3 = &aMem[pOp->p3]; pOut = out2Prerelease(p, pOp); assert( pIn1->flags & MEM_Int ); assert( pIn3->flags & MEM_Int ); - pOut->u.i = pIn1->u.i<=0 ? -1 : pIn1->u.i+(pIn3->u.i>0?pIn3->u.i:0); + x = pIn1->u.i; + if( x<=0 || sqlite3AddInt64(&x, pIn3->u.i>0?pIn3->u.i:0) ){ + /* If the LIMIT is less than or equal to zero, loop forever. This + ** is documented. But also, if the LIMIT+OFFSET exceeds 2^63 then + ** also loop forever. This is undocumented. In fact, one could argue + ** that the loop should terminate. But assuming 1 billion iterations + ** per second (far exceeding the capabilities of any current hardware) + ** it would take nearly 300 years to actually reach the limit. So + ** looping forever is a reasonable approximation. */ + pOut->u.i = -1; + }else{ + pOut->u.i = x; + } break; } -/* Opcode: IfNotZero P1 P2 P3 * * -** Synopsis: if r[P1]!=0 then r[P1]-=P3, goto P2 +/* Opcode: IfNotZero P1 P2 * * * +** Synopsis: if r[P1]!=0 then r[P1]--, goto P2 ** ** Register P1 must contain an integer. If the content of register P1 is -** initially nonzero, then subtract P3 from the value in register P1 and -** jump to P2. If register P1 is initially zero, leave it unchanged -** and fall through. +** initially greater than zero, then decrement the value in register P1. +** If it is non-zero (negative or positive) and then also jump to P2. +** If register P1 is initially zero, leave it unchanged and fall through. */ case OP_IfNotZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); VdbeBranchTaken(pIn1->u.i<0, 2); if( pIn1->u.i ){ - pIn1->u.i -= pOp->p3; + if( pIn1->u.i>0 ) pIn1->u.i--; goto jump_to_p2; } break; @@ -83112,13 +84321,13 @@ case OP_IfNotZero: { /* jump, in1 */ /* Opcode: DecrJumpZero P1 P2 * * * ** Synopsis: if (--r[P1])==0 goto P2 ** -** Register P1 must hold an integer. Decrement the value in register P1 -** then jump to P2 if the new value is exactly zero. +** Register P1 must hold an integer. Decrement the value in P1 +** and jump to P2 if the new value is exactly zero. */ case OP_DecrJumpZero: { /* jump, in1 */ pIn1 = &aMem[pOp->p1]; assert( pIn1->flags&MEM_Int ); - pIn1->u.i--; + if( pIn1->u.i>SMALLEST_INT64 ) pIn1->u.i--; VdbeBranchTaken(pIn1->u.i==0, 2); if( pIn1->u.i==0 ) goto jump_to_p2; break; @@ -83364,7 +84573,7 @@ case OP_JournalMode: { /* out2 */ ** file. An EXCLUSIVE lock may still be held on the database file ** after a successful return. */ - rc = sqlite3PagerCloseWal(pPager); + rc = sqlite3PagerCloseWal(pPager, db); if( rc==SQLITE_OK ){ sqlite3PagerSetJournalMode(pPager, eNew); } @@ -83399,15 +84608,14 @@ case OP_JournalMode: { /* out2 */ #endif /* SQLITE_OMIT_PRAGMA */ #if !defined(SQLITE_OMIT_VACUUM) && !defined(SQLITE_OMIT_ATTACH) -/* Opcode: Vacuum * * * * * +/* Opcode: Vacuum P1 * * * * ** -** Vacuum the entire database. This opcode will cause other virtual -** machines to be created and run. It may not be called from within -** a transaction. +** Vacuum the entire database P1. P1 is 0 for "main", and 2 or more +** for an attached database. The "temp" database may not be vacuumed. */ case OP_Vacuum: { assert( p->readOnly==0 ); - rc = sqlite3RunVacuum(&p->zErrMsg, db); + rc = sqlite3RunVacuum(&p->zErrMsg, db, pOp->p1); if( rc ) goto abort_due_to_error; break; } @@ -83849,7 +85057,7 @@ case OP_VUpdate: { sqlite3VtabImportErrmsg(p, pVtab); if( rc==SQLITE_OK && pOp->p1 ){ assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) ); - db->lastRowid = lastRowid = rowid; + db->lastRowid = rowid; } if( (rc&0xff)==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){ if( pOp->p5==OE_Ignore ){ @@ -83905,8 +85113,8 @@ case OP_MaxPgcnt: { /* out2 */ #endif -/* Opcode: Init * P2 * P4 * -** Synopsis: Start at P2 +/* Opcode: Init P1 P2 * P4 * +** Synopsis: Start at P2 ** ** Programs contain a single instance of this opcode as the very first ** opcode. @@ -83916,9 +85124,13 @@ case OP_MaxPgcnt: { /* out2 */ ** Or if P4 is blank, use the string returned by sqlite3_sql(). ** ** If P2 is not zero, jump to instruction P2. +** +** Increment the value of P1 so that OP_Once opcodes will jump the +** first time they are evaluated for this run. */ case OP_Init: { /* jump */ char *zTrace; + int i; /* If the P4 argument is not NULL, then it must be an SQL comment string. ** The "--" string is broken up to prevent false-positives with srcck1.c. @@ -83930,6 +85142,7 @@ case OP_Init: { /* jump */ ** sqlite3_expanded_sql(P) otherwise. */ assert( pOp->p4.z==0 || strncmp(pOp->p4.z, "-" "- ", 3)==0 ); + assert( pOp==p->aOp ); /* Always instruction 0 */ #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 @@ -83944,17 +85157,21 @@ case OP_Init: { /* jump */ sqlite3_free(z); }else #endif - { + if( db->nVdbeExec>1 ){ + char *z = sqlite3MPrintf(db, "-- %s", zTrace); + (void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, z); + sqlite3DbFree(db, z); + }else{ (void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, zTrace); } } #ifdef SQLITE_USE_FCNTL_TRACE zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql); if( zTrace ){ - int i; - for(i=0; inDb; i++){ - if( DbMaskTest(p->btreeMask, i)==0 ) continue; - sqlite3_file_control(db, db->aDb[i].zName, SQLITE_FCNTL_TRACE, zTrace); + int j; + for(j=0; jnDb; j++){ + if( DbMaskTest(p->btreeMask, j)==0 ) continue; + sqlite3_file_control(db, db->aDb[j].zDbSName, SQLITE_FCNTL_TRACE, zTrace); } } #endif /* SQLITE_USE_FCNTL_TRACE */ @@ -83966,8 +85183,15 @@ case OP_Init: { /* jump */ } #endif /* SQLITE_DEBUG */ #endif /* SQLITE_OMIT_TRACE */ - if( pOp->p2 ) goto jump_to_p2; - break; + assert( pOp->p2>0 ); + if( pOp->p1>=sqlite3GlobalConfig.iOnceResetThreshold ){ + for(i=1; inOp; i++){ + if( p->aOp[i].opcode==OP_Once ) p->aOp[i].p1 = 0; + } + pOp->p1 = 0; + } + pOp->p1++; + goto jump_to_p2; } #ifdef SQLITE_ENABLE_CURSOR_HINTS @@ -84073,7 +85297,6 @@ default: { /* This is really OP_Noop and OP_Explain */ ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: - db->lastRowid = lastRowid; testcase( nVmStep>0 ); p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; sqlite3VdbeLeave(p); @@ -84137,10 +85360,9 @@ default: { /* This is really OP_Noop and OP_Explain */ */ typedef struct Incrblob Incrblob; struct Incrblob { - int flags; /* Copy of "flags" passed to sqlite3_blob_open() */ int nByte; /* Size of open blob, in bytes */ int iOffset; /* Byte offset of blob in cursor data */ - int iCol; /* Table column this handle is open on */ + u16 iCol; /* Table column this handle is open on */ BtCursor *pCsr; /* Cursor pointing at blob row */ sqlite3_stmt *pStmt; /* Statement holding cursor open */ sqlite3 *db; /* The associated database */ @@ -84171,17 +85393,27 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ char *zErr = 0; /* Error message */ Vdbe *v = (Vdbe *)p->pStmt; - /* Set the value of the SQL statements only variable to integer iRow. - ** This is done directly instead of using sqlite3_bind_int64() to avoid - ** triggering asserts related to mutexes. + /* Set the value of register r[1] in the SQL statement to integer iRow. + ** This is done directly as a performance optimization */ - assert( v->aVar[0].flags&MEM_Int ); - v->aVar[0].u.i = iRow; + v->aMem[1].flags = MEM_Int; + v->aMem[1].u.i = iRow; - rc = sqlite3_step(p->pStmt); + /* If the statement has been run before (and is paused at the OP_ResultRow) + ** then back it up to the point where it does the OP_SeekRowid. This could + ** have been down with an extra OP_Goto, but simply setting the program + ** counter is faster. */ + if( v->pc>3 ){ + v->pc = 3; + rc = sqlite3VdbeExec(v); + }else{ + rc = sqlite3_step(p->pStmt); + } if( rc==SQLITE_ROW ){ VdbeCursor *pC = v->apCsr[0]; - u32 type = pC->aType[p->iCol]; + u32 type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; + testcase( pC->nHdrParsed==p->iCol ); + testcase( pC->nHdrParsed==p->iCol+1 ); if( type<12 ){ zErr = sqlite3MPrintf(p->db, "cannot open value of type %s", type==0?"null": type==7?"real": "integer" @@ -84226,7 +85458,7 @@ SQLITE_API int sqlite3_blob_open( const char *zTable, /* The table containing the blob */ const char *zColumn, /* The column containing the blob */ sqlite_int64 iRow, /* The row containing the glob */ - int flags, /* True -> read/write access, false -> read-only */ + int wrFlag, /* True -> read/write access, false -> read-only */ sqlite3_blob **ppBlob /* Handle for accessing the blob returned here */ ){ int nAttempt = 0; @@ -84248,7 +85480,7 @@ SQLITE_API int sqlite3_blob_open( return SQLITE_MISUSE_BKPT; } #endif - flags = !!flags; /* flags = (flags ? 1 : 0); */ + wrFlag = !!wrFlag; /* wrFlag = (wrFlag ? 1 : 0); */ sqlite3_mutex_enter(db->mutex); @@ -84290,7 +85522,7 @@ SQLITE_API int sqlite3_blob_open( goto blob_open_out; } pBlob->pTab = pTab; - pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zName; + pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; /* Now search pTab for the exact column. */ for(iCol=0; iColnCol; iCol++) { @@ -84308,9 +85540,8 @@ SQLITE_API int sqlite3_blob_open( /* If the value is being opened for writing, check that the ** column is not indexed, and that it is not part of a foreign key. - ** It is against the rules to open a column to which either of these - ** descriptions applies for writing. */ - if( flags ){ + */ + if( wrFlag ){ const char *zFault = 0; Index *pIdx; #ifndef SQLITE_OMIT_FOREIGN_KEY @@ -84371,19 +85602,17 @@ SQLITE_API int sqlite3_blob_open( static const VdbeOpList openBlob[] = { {OP_TableLock, 0, 0, 0}, /* 0: Acquire a read or write lock */ {OP_OpenRead, 0, 0, 0}, /* 1: Open a cursor */ - {OP_Variable, 1, 1, 0}, /* 2: Move ?1 into reg[1] */ - {OP_NotExists, 0, 7, 1}, /* 3: Seek the cursor */ - {OP_Column, 0, 0, 1}, /* 4 */ - {OP_ResultRow, 1, 0, 0}, /* 5 */ - {OP_Goto, 0, 2, 0}, /* 6 */ - {OP_Close, 0, 0, 0}, /* 7 */ - {OP_Halt, 0, 0, 0}, /* 8 */ + /* blobSeekToRow() will initialize r[1] to the desired rowid */ + {OP_NotExists, 0, 5, 1}, /* 2: Seek the cursor to rowid=r[1] */ + {OP_Column, 0, 0, 1}, /* 3 */ + {OP_ResultRow, 1, 0, 0}, /* 4 */ + {OP_Halt, 0, 0, 0}, /* 5 */ }; Vdbe *v = (Vdbe *)pBlob->pStmt; int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); VdbeOp *aOp; - sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, flags, + sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag, pTab->pSchema->schema_cookie, pTab->pSchema->iGeneration); sqlite3VdbeChangeP5(v, 1); @@ -84400,7 +85629,7 @@ SQLITE_API int sqlite3_blob_open( #else aOp[0].p1 = iDb; aOp[0].p2 = pTab->tnum; - aOp[0].p3 = flags; + aOp[0].p3 = wrFlag; sqlite3VdbeChangeP4(v, 1, pTab->zName, P4_TRANSIENT); } if( db->mallocFailed==0 ){ @@ -84408,7 +85637,7 @@ SQLITE_API int sqlite3_blob_open( /* Remove either the OP_OpenWrite or OpenRead. Set the P2 ** parameter of the other to pTab->tnum. */ - if( flags ) aOp[1].opcode = OP_OpenWrite; + if( wrFlag ) aOp[1].opcode = OP_OpenWrite; aOp[1].p2 = pTab->tnum; aOp[1].p3 = iDb; @@ -84421,23 +85650,21 @@ SQLITE_API int sqlite3_blob_open( */ aOp[1].p4type = P4_INT32; aOp[1].p4.i = pTab->nCol+1; - aOp[4].p2 = pTab->nCol; + aOp[3].p2 = pTab->nCol; - pParse->nVar = 1; + pParse->nVar = 0; pParse->nMem = 1; pParse->nTab = 1; sqlite3VdbeMakeReady(v, pParse); } } - pBlob->flags = flags; pBlob->iCol = iCol; pBlob->db = db; sqlite3BtreeLeaveAll(db); if( db->mallocFailed ){ goto blob_open_out; } - sqlite3_bind_int64(pBlob->pStmt, 1, iRow); rc = blobSeekToRow(pBlob, iRow, &zErr); } while( (++nAttempt)pKeyInfo && pCsr->pBt==0 ); + assert( pCsr->pKeyInfo && pCsr->pBtx==0 ); assert( pCsr->eCurType==CURTYPE_SORTER ); szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nField-1)*sizeof(CollSeq*); sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); @@ -85949,12 +87176,8 @@ static int vdbeSorterOpenTempFile( */ static int vdbeSortAllocUnpacked(SortSubtask *pTask){ if( pTask->pUnpacked==0 ){ - char *pFree; - pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord( - pTask->pSorter->pKeyInfo, 0, 0, &pFree - ); - assert( pTask->pUnpacked==(UnpackedRecord*)pFree ); - if( pFree==0 ) return SQLITE_NOMEM_BKPT; + pTask->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pTask->pSorter->pKeyInfo); + if( pTask->pUnpacked==0 ) return SQLITE_NOMEM_BKPT; pTask->pUnpacked->nField = pTask->pSorter->pKeyInfo->nField; pTask->pUnpacked->errCode = 0; } @@ -87355,9 +88578,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( r2 = pSorter->pUnpacked; pKeyInfo = pCsr->pKeyInfo; if( r2==0 ){ - char *p; - r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo,0,0,&p); - assert( pSorter->pUnpacked==(UnpackedRecord*)p ); + r2 = pSorter->pUnpacked = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( r2==0 ) return SQLITE_NOMEM_BKPT; r2->nField = nKeyCol; } @@ -87828,11 +89049,11 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){ ** ** WRC_Continue Continue descending down the tree. ** -** WRC_Prune Do not descend into child nodes. But allow +** WRC_Prune Do not descend into child nodes, but allow ** the walk to continue with sibling nodes. ** ** WRC_Abort Do no more callbacks. Unwind the stack and -** return the top-level walk call. +** return from the top-level walk call. ** ** The return value from this routine is WRC_Abort to abandon the tree walk ** and WRC_Continue to continue. @@ -87842,17 +89063,17 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); rc = pWalker->xExprCallback(pWalker, pExpr); - if( rc==WRC_Continue - && !ExprHasProperty(pExpr,EP_TokenOnly) ){ - if( sqlite3WalkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; - if( sqlite3WalkExpr(pWalker, pExpr->pRight) ) return WRC_Abort; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; - }else{ - if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; - } + if( rc || ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + return rc & WRC_Abort; } - return rc & WRC_Abort; + if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; + if( pExpr->pRight && walkExpr(pWalker, pExpr->pRight) ) return WRC_Abort; + if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; + }else if( pExpr->x.pList ){ + if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; + } + return WRC_Continue; } SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue; @@ -87980,8 +89201,6 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ ** table and column. */ /* #include "sqliteInt.h" */ -/* #include */ -/* #include */ /* ** Walk the expression tree pExpr and increase the aggregate function @@ -88186,8 +89405,8 @@ static int lookupName( zDb = 0; }else{ for(i=0; inDb; i++){ - assert( db->aDb[i].zName ); - if( sqlite3StrICmp(db->aDb[i].zName,zDb)==0 ){ + assert( db->aDb[i].zDbSName ); + if( sqlite3StrICmp(db->aDb[i].zDbSName,zDb)==0 ){ pSchema = db->aDb[i].pSchema; break; } @@ -88196,7 +89415,8 @@ static int lookupName( } /* Start at the inner-most context and move outward until a match is found */ - while( pNC && cnt==0 ){ + assert( pNC && cnt==0 ); + do{ ExprList *pEList; SrcList *pSrcList = pNC->pSrcList; @@ -88365,6 +89585,10 @@ static int lookupName( sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); return WRC_Abort; } + if( sqlite3ExprVectorSize(pOrig)!=1 ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return WRC_Abort; + } resolveAlias(pParse, pEList, j, pExpr, "", nSubquery); cnt = 1; pMatch = 0; @@ -88377,11 +89601,11 @@ static int lookupName( /* Advance to the next name context. The loop will exit when either ** we have a match (cnt>0) or when we run out of name contexts. */ - if( cnt==0 ){ - pNC = pNC->pNext; - nSubquery++; - } - } + if( cnt ) break; + pNC = pNC->pNext; + nSubquery++; + }while( pNC ); + /* ** If X and Y are NULL (in other words if only the column name Z is @@ -88571,34 +89795,38 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) */ - /* A lone identifier is the name of a column. - */ - case TK_ID: { - return lookupName(pParse, 0, 0, pExpr->u.zToken, pNC, pExpr); - } - - /* A table name and column name: ID.ID + /* A column name: ID + ** Or table name and column name: ID.ID ** Or a database, table and column: ID.ID.ID + ** + ** The TK_ID and TK_OUT cases are combined so that there will only + ** be one call to lookupName(). Then the compiler will in-line + ** lookupName() for a size reduction and performance increase. */ + case TK_ID: case TK_DOT: { const char *zColumn; const char *zTable; const char *zDb; Expr *pRight; - /* if( pSrcList==0 ) break; */ - notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr); - /*notValid(pParse, pNC, "the \".\" operator", NC_PartIdx|NC_IsCheck, 1);*/ - pRight = pExpr->pRight; - if( pRight->op==TK_ID ){ + if( pExpr->op==TK_ID ){ zDb = 0; - zTable = pExpr->pLeft->u.zToken; - zColumn = pRight->u.zToken; + zTable = 0; + zColumn = pExpr->u.zToken; }else{ - assert( pRight->op==TK_DOT ); - zDb = pExpr->pLeft->u.zToken; - zTable = pRight->pLeft->u.zToken; - zColumn = pRight->pRight->u.zToken; + notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr); + pRight = pExpr->pRight; + if( pRight->op==TK_ID ){ + zDb = 0; + zTable = pExpr->pLeft->u.zToken; + zColumn = pRight->u.zToken; + }else{ + assert( pRight->op==TK_DOT ); + zDb = pExpr->pLeft->u.zToken; + zTable = pRight->pLeft->u.zToken; + zColumn = pRight->pRight->u.zToken; + } } return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr); } @@ -88611,14 +89839,12 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ int no_such_func = 0; /* True if no such function exists */ int wrong_num_args = 0; /* True if wrong number of arguments */ int is_agg = 0; /* True if is an aggregate function */ - int auth; /* Authorization to use the function */ int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ FuncDef *pDef; /* Information about the function */ u8 enc = ENC(pParse->db); /* The database encoding */ assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - notValid(pParse, pNC, "functions", NC_PartIdx); zId = pExpr->u.zToken; nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); @@ -88655,15 +89881,17 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ } } #ifndef SQLITE_OMIT_AUTHORIZATION - auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0, pDef->zName, 0); - if( auth!=SQLITE_OK ){ - if( auth==SQLITE_DENY ){ - sqlite3ErrorMsg(pParse, "not authorized to use function: %s", - pDef->zName); - pNC->nErr++; + { + int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0); + if( auth!=SQLITE_OK ){ + if( auth==SQLITE_DENY ){ + sqlite3ErrorMsg(pParse, "not authorized to use function: %s", + pDef->zName); + pNC->nErr++; + } + pExpr->op = TK_NULL; + return WRC_Prune; } - pExpr->op = TK_NULL; - return WRC_Prune; } #endif if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){ @@ -88676,7 +89904,8 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ /* Date/time functions that use 'now', and other functions like ** sqlite_version() that might change over time cannot be used ** in an index. */ - notValid(pParse, pNC, "non-deterministic functions", NC_IdxExpr); + notValid(pParse, pNC, "non-deterministic functions", + NC_IdxExpr|NC_PartIdx); } } if( is_agg && (pNC->ncFlags & NC_AllowAgg)==0 ){ @@ -88741,6 +89970,42 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr); break; } + case TK_BETWEEN: + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_IS: + case TK_ISNOT: { + int nLeft, nRight; + if( pParse->db->mallocFailed ) break; + assert( pExpr->pLeft!=0 ); + nLeft = sqlite3ExprVectorSize(pExpr->pLeft); + if( pExpr->op==TK_BETWEEN ){ + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); + if( nRight==nLeft ){ + nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); + } + }else{ + assert( pExpr->pRight!=0 ); + nRight = sqlite3ExprVectorSize(pExpr->pRight); + } + if( nLeft!=nRight ){ + testcase( pExpr->op==TK_EQ ); + testcase( pExpr->op==TK_NE ); + testcase( pExpr->op==TK_LT ); + testcase( pExpr->op==TK_LE ); + testcase( pExpr->op==TK_GT ); + testcase( pExpr->op==TK_GE ); + testcase( pExpr->op==TK_IS ); + testcase( pExpr->op==TK_ISNOT ); + testcase( pExpr->op==TK_BETWEEN ); + sqlite3ErrorMsg(pParse, "row value misused"); + } + break; + } } return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue; } @@ -89483,6 +90748,18 @@ SQLITE_PRIVATE void sqlite3ResolveSelfReference( */ /* #include "sqliteInt.h" */ +/* Forward declarations */ +static void exprCodeBetween(Parse*,Expr*,int,void(*)(Parse*,Expr*,int,int),int); +static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); + +/* +** Return the affinity character for a single column of a table. +*/ +SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){ + assert( iColnCol ); + return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; +} + /* ** Return the 'affinity' of the expression pExpr if any. ** @@ -89508,21 +90785,21 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ assert( pExpr->flags&EP_xIsSelect ); return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); } + if( op==TK_REGISTER ) op = pExpr->op2; #ifndef SQLITE_OMIT_CAST if( op==TK_CAST ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); return sqlite3AffinityType(pExpr->u.zToken, 0); } #endif - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER) - && pExpr->pTab!=0 - ){ - /* op==TK_REGISTER && pExpr->pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = pExpr->iColumn; - if( j<0 ) return SQLITE_AFF_INTEGER; - assert( pExpr->pTab && jpTab->nCol ); - return pExpr->pTab->aCol[j].affinity; + if( op==TK_AGG_COLUMN || op==TK_COLUMN ){ + return sqlite3TableColumnAffinity(pExpr->pTab, pExpr->iColumn); + } + if( op==TK_SELECT_COLUMN ){ + assert( pExpr->pLeft->flags&EP_xIsSelect ); + return sqlite3ExprAffinity( + pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr + ); } return pExpr->affinity; } @@ -89688,7 +90965,7 @@ static char comparisonAffinity(Expr *pExpr){ aff = sqlite3CompareAffinity(pExpr->pRight, aff); }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); - }else if( !aff ){ + }else if( aff==0 ){ aff = SQLITE_AFF_BLOB; } return aff; @@ -89778,6 +91055,274 @@ static int codeCompare( return addr; } +/* +** Return true if expression pExpr is a vector, or false otherwise. +** +** A vector is defined as any expression that results in two or more +** columns of result. Every TK_VECTOR node is an vector because the +** parser will not generate a TK_VECTOR with fewer than two entries. +** But a TK_SELECT might be either a vector or a scalar. It is only +** considered a vector if it has two or more result columns. +*/ +SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){ + return sqlite3ExprVectorSize(pExpr)>1; +} + +/* +** If the expression passed as the only argument is of type TK_VECTOR +** return the number of expressions in the vector. Or, if the expression +** is a sub-select, return the number of columns in the sub-select. For +** any other type of expression, return 1. +*/ +SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ + u8 op = pExpr->op; + if( op==TK_REGISTER ) op = pExpr->op2; + if( op==TK_VECTOR ){ + return pExpr->x.pList->nExpr; + }else if( op==TK_SELECT ){ + return pExpr->x.pSelect->pEList->nExpr; + }else{ + return 1; + } +} + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Return a pointer to a subexpression of pVector that is the i-th +** column of the vector (numbered starting with 0). The caller must +** ensure that i is within range. +** +** If pVector is really a scalar (and "scalar" here includes subqueries +** that return a single column!) then return pVector unmodified. +** +** pVector retains ownership of the returned subexpression. +** +** If the vector is a (SELECT ...) then the expression returned is +** just the expression for the i-th term of the result set, and may +** not be ready for evaluation because the table cursor has not yet +** been positioned. +*/ +SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ + assert( iop2==0 || pVector->op==TK_REGISTER ); + if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + return pVector->x.pSelect->pEList->a[i].pExpr; + }else{ + return pVector->x.pList->a[i].pExpr; + } + } + return pVector; +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) */ + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Compute and return a new Expr object which when passed to +** sqlite3ExprCode() will generate all necessary code to compute +** the iField-th column of the vector expression pVector. +** +** It is ok for pVector to be a scalar (as long as iField==0). +** In that case, this routine works like sqlite3ExprDup(). +** +** The caller owns the returned Expr object and is responsible for +** ensuring that the returned value eventually gets freed. +** +** The caller retains ownership of pVector. If pVector is a TK_SELECT, +** then the returned object will reference pVector and so pVector must remain +** valid for the life of the returned object. If pVector is a TK_VECTOR +** or a scalar expression, then it can be deleted as soon as this routine +** returns. +** +** A trick to cause a TK_SELECT pVector to be deleted together with +** the returned Expr object is to attach the pVector to the pRight field +** of the returned TK_SELECT_COLUMN Expr object. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( + Parse *pParse, /* Parsing context */ + Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ + int iField /* Which column of the vector to return */ +){ + Expr *pRet; + if( pVector->op==TK_SELECT ){ + assert( pVector->flags & EP_xIsSelect ); + /* The TK_SELECT_COLUMN Expr node: + ** + ** pLeft: pVector containing TK_SELECT. Not deleted. + ** pRight: not used. But recursively deleted. + ** iColumn: Index of a column in pVector + ** iTable: 0 or the number of columns on the LHS of an assignment + ** pLeft->iTable: First in an array of register holding result, or 0 + ** if the result is not yet computed. + ** + ** sqlite3ExprDelete() specifically skips the recursive delete of + ** pLeft on TK_SELECT_COLUMN nodes. But pRight is followed, so pVector + ** can be attached to pRight to cause this node to take ownership of + ** pVector. Typically there will be multiple TK_SELECT_COLUMN nodes + ** with the same pLeft pointer to the pVector, but only one of them + ** will own the pVector. + */ + pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); + if( pRet ){ + pRet->iColumn = iField; + pRet->pLeft = pVector; + } + assert( pRet==0 || pRet->iTable==0 ); + }else{ + if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr; + pRet = sqlite3ExprDup(pParse->db, pVector, 0); + } + return pRet; +} +#endif /* !define(SQLITE_OMIT_SUBQUERY) */ + +/* +** If expression pExpr is of type TK_SELECT, generate code to evaluate +** it. Return the register in which the result is stored (or, if the +** sub-select returns more than one column, the first in an array +** of registers in which the result is stored). +** +** If pExpr is not a TK_SELECT expression, return 0. +*/ +static int exprCodeSubselect(Parse *pParse, Expr *pExpr){ + int reg = 0; +#ifndef SQLITE_OMIT_SUBQUERY + if( pExpr->op==TK_SELECT ){ + reg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); + } +#endif + return reg; +} + +/* +** Argument pVector points to a vector expression - either a TK_VECTOR +** or TK_SELECT that returns more than one column. This function returns +** the register number of a register that contains the value of +** element iField of the vector. +** +** If pVector is a TK_SELECT expression, then code for it must have +** already been generated using the exprCodeSubselect() routine. In this +** case parameter regSelect should be the first in an array of registers +** containing the results of the sub-select. +** +** If pVector is of type TK_VECTOR, then code for the requested field +** is generated. In this case (*pRegFree) may be set to the number of +** a temporary register to be freed by the caller before returning. +** +** Before returning, output parameter (*ppExpr) is set to point to the +** Expr object corresponding to element iElem of the vector. +*/ +static int exprVectorRegister( + Parse *pParse, /* Parse context */ + Expr *pVector, /* Vector to extract element from */ + int iField, /* Field to extract from pVector */ + int regSelect, /* First in array of registers */ + Expr **ppExpr, /* OUT: Expression element */ + int *pRegFree /* OUT: Temp register to free */ +){ + u8 op = pVector->op; + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT ); + if( op==TK_REGISTER ){ + *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); + return pVector->iTable+iField; + } + if( op==TK_SELECT ){ + *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; + return regSelect+iField; + } + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); +} + +/* +** Expression pExpr is a comparison between two vector values. Compute +** the result of the comparison (1, 0, or NULL) and write that +** result into register dest. +** +** The caller must satisfy the following preconditions: +** +** if pExpr->op==TK_IS: op==TK_EQ and p5==SQLITE_NULLEQ +** if pExpr->op==TK_ISNOT: op==TK_NE and p5==SQLITE_NULLEQ +** otherwise: op==pExpr->op and p5==0 +*/ +static void codeVectorCompare( + Parse *pParse, /* Code generator context */ + Expr *pExpr, /* The comparison operation */ + int dest, /* Write results into this register */ + u8 op, /* Comparison operator */ + u8 p5 /* SQLITE_NULLEQ or zero */ +){ + Vdbe *v = pParse->pVdbe; + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; + int nLeft = sqlite3ExprVectorSize(pLeft); + int i; + int regLeft = 0; + int regRight = 0; + u8 opx = op; + int addrDone = sqlite3VdbeMakeLabel(v); + + if( nLeft!=sqlite3ExprVectorSize(pRight) ){ + sqlite3ErrorMsg(pParse, "row value misused"); + return; + } + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE + ); + assert( pExpr->op==op || (pExpr->op==TK_IS && op==TK_EQ) + || (pExpr->op==TK_ISNOT && op==TK_NE) ); + assert( p5==0 || pExpr->op!=op ); + assert( p5==SQLITE_NULLEQ || pExpr->op==op ); + + p5 |= SQLITE_STOREP2; + if( opx==TK_LE ) opx = TK_LT; + if( opx==TK_GE ) opx = TK_GT; + + regLeft = exprCodeSubselect(pParse, pLeft); + regRight = exprCodeSubselect(pParse, pRight); + + for(i=0; 1 /*Loop exits by "break"*/; i++){ + int regFree1 = 0, regFree2 = 0; + Expr *pL, *pR; + int r1, r2; + assert( i>=0 && i0 ) sqlite3ExprCachePush(pParse); + r1 = exprVectorRegister(pParse, pLeft, i, regLeft, &pL, ®Free1); + r2 = exprVectorRegister(pParse, pRight, i, regRight, &pR, ®Free2); + codeCompare(pParse, pL, pR, opx, r1, r2, dest, p5); + testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ReleaseTempReg(pParse, regFree2); + if( i>0 ) sqlite3ExprCachePop(pParse); + if( i==nLeft-1 ){ + break; + } + if( opx==TK_EQ ){ + sqlite3VdbeAddOp2(v, OP_IfNot, dest, addrDone); VdbeCoverage(v); + p5 |= SQLITE_KEEPNULL; + }else if( opx==TK_NE ){ + sqlite3VdbeAddOp2(v, OP_If, dest, addrDone); VdbeCoverage(v); + p5 |= SQLITE_KEEPNULL; + }else{ + assert( op==TK_LT || op==TK_GT || op==TK_LE || op==TK_GE ); + sqlite3VdbeAddOp2(v, OP_ElseNotEq, 0, addrDone); + VdbeCoverageIf(v, op==TK_LT); + VdbeCoverageIf(v, op==TK_GT); + VdbeCoverageIf(v, op==TK_LE); + VdbeCoverageIf(v, op==TK_GE); + if( i==nLeft-2 ) opx = op; + } + } + sqlite3VdbeResolveLabel(v, addrDone); +} + #if SQLITE_MAX_EXPR_DEPTH>0 /* ** Check that argument nHeight is less than or equal to the maximum @@ -89913,7 +91458,7 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** is allocated to hold the integer text and the dequote flag is ignored. */ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( - sqlite3 *db, /* Handle for sqlite3DbMallocZero() (may be null) */ + sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */ int op, /* Expression opcode */ const Token *pToken, /* Token argument. Might be NULL */ int dequote /* True to dequote */ @@ -90012,15 +91557,19 @@ SQLITE_PRIVATE Expr *sqlite3PExpr( Parse *pParse, /* Parsing context */ int op, /* Expression opcode */ Expr *pLeft, /* Left operand */ - Expr *pRight, /* Right operand */ - const Token *pToken /* Argument token */ + Expr *pRight /* Right operand */ ){ Expr *p; if( op==TK_AND && pParse->nErr==0 ){ /* Take advantage of short-circuit false optimization for AND */ p = sqlite3ExprAnd(pParse->db, pLeft, pRight); }else{ - p = sqlite3ExprAlloc(pParse->db, op & TKFLG_MASK, pToken, 1); + p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = op & TKFLG_MASK; + p->iAgg = -1; + } sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); } if( p ) { @@ -90123,7 +91672,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token * ** variable number. ** ** Wildcards of the form "?nnn" are assigned the number "nnn". We make -** sure "nnn" is not too be to avoid a denial of service attack when +** sure "nnn" is not too big to avoid a denial of service attack when ** the SQL statement comes from an external source. ** ** Wildcards of the form ":aaa", "@aaa", or "$aaa" are assigned the same number @@ -90131,28 +91680,34 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse *pParse, ExprList *pList, Token * ** instance of the wildcard, the next sequential variable number is ** assigned. */ -SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ +SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n){ sqlite3 *db = pParse->db; const char *z; + ynVar x; if( pExpr==0 ) return; assert( !ExprHasProperty(pExpr, EP_IntValue|EP_Reduced|EP_TokenOnly) ); z = pExpr->u.zToken; assert( z!=0 ); assert( z[0]!=0 ); + assert( n==sqlite3Strlen30(z) ); if( z[1]==0 ){ /* Wildcard of the form "?". Assign the next variable number */ assert( z[0]=='?' ); - pExpr->iColumn = (ynVar)(++pParse->nVar); + x = (ynVar)(++pParse->nVar); }else{ - ynVar x = 0; - u32 n = sqlite3Strlen30(z); + int doAdd = 0; if( z[0]=='?' ){ /* Wildcard of the form "?nnn". Convert "nnn" to an integer and ** use it as the variable number */ i64 i; - int bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8); - pExpr->iColumn = x = (ynVar)i; + int bOk; + if( n==2 ){ /*OPTIMIZATION-IF-TRUE*/ + i = z[1]-'0'; /* The common case of ?N for a single digit N */ + bOk = 1; + }else{ + bOk = 0==sqlite3Atoi64(&z[1], &i, n-1, SQLITE_UTF8); + } testcase( i==0 ); testcase( i==1 ); testcase( i==db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]-1 ); @@ -90160,44 +91715,32 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){ if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]); - x = 0; + return; } - if( i>pParse->nVar ){ - pParse->nVar = (int)i; + x = (ynVar)i; + if( x>pParse->nVar ){ + pParse->nVar = (int)x; + doAdd = 1; + }else if( sqlite3VListNumToName(pParse->pVList, x)==0 ){ + doAdd = 1; } }else{ /* Wildcards like ":aaa", "$aaa" or "@aaa". Reuse the same variable ** number as the prior appearance of the same name, or if the name ** has never appeared before, reuse the same variable number */ - ynVar i; - for(i=0; inzVar; i++){ - if( pParse->azVar[i] && strcmp(pParse->azVar[i],z)==0 ){ - pExpr->iColumn = x = (ynVar)i+1; - break; - } + x = (ynVar)sqlite3VListNameToNum(pParse->pVList, z, n); + if( x==0 ){ + x = (ynVar)(++pParse->nVar); + doAdd = 1; } - if( x==0 ) x = pExpr->iColumn = (ynVar)(++pParse->nVar); } - if( x>0 ){ - if( x>pParse->nzVar ){ - char **a; - a = sqlite3DbRealloc(db, pParse->azVar, x*sizeof(a[0])); - if( a==0 ){ - assert( db->mallocFailed ); /* Error reported through mallocFailed */ - return; - } - pParse->azVar = a; - memset(&a[pParse->nzVar], 0, (x-pParse->nzVar)*sizeof(a[0])); - pParse->nzVar = x; - } - if( z[0]!='?' || pParse->azVar[x-1]==0 ){ - sqlite3DbFree(db, pParse->azVar[x-1]); - pParse->azVar[x-1] = sqlite3DbStrNDup(db, z, n); - } + if( doAdd ){ + pParse->pVList = sqlite3VListAdd(db, pParse->pVList, z, n, x); } - } - if( !pParse->nErr && pParse->nVar>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ + } + pExpr->iColumn = x; + if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "too many SQL variables"); } } @@ -90209,18 +91752,25 @@ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); /* Sanity check: Assert that the IntValue is non-negative if it exists */ assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 ); - if( !ExprHasProperty(p, EP_TokenOnly) ){ +#ifdef SQLITE_DEBUG + if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ + assert( p->pLeft==0 ); + assert( p->pRight==0 ); + assert( p->x.pSelect==0 ); + } +#endif + if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ assert( p->x.pList==0 || p->pRight==0 ); - sqlite3ExprDelete(db, p->pLeft); + if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); sqlite3ExprDelete(db, p->pRight); - if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( ExprHasProperty(p, EP_xIsSelect) ){ sqlite3SelectDelete(db, p->x.pSelect); }else{ sqlite3ExprListDelete(db, p->x.pList); } } + if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( !ExprHasProperty(p, EP_Static) ){ sqlite3DbFree(db, p); } @@ -90279,7 +91829,7 @@ static int dupedExprStructSize(Expr *p, int flags){ assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags ){ + if( 0==flags || p->op==TK_SELECT_COLUMN ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); @@ -90397,7 +91947,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ memcpy(zToken, p->u.zToken, nToken); } - if( 0==((p->flags|pNew->flags) & EP_TokenOnly) ){ + if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ if( ExprHasProperty(p, EP_xIsSelect) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); @@ -90409,7 +91959,7 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ /* Fill in pNew->pLeft and pNew->pRight. */ if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly) ){ zAlloc += dupedExprNodeSize(p, dupFlags); - if( ExprHasProperty(pNew, EP_Reduced) ){ + if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){ pNew->pLeft = p->pLeft ? exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; pNew->pRight = p->pRight ? @@ -90419,8 +91969,14 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ *pzBuffer = zAlloc; } }else{ - if( !ExprHasProperty(p, EP_TokenOnly) ){ - pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + if( pNew->op==TK_SELECT_COLUMN ){ + pNew->pLeft = p->pLeft; + assert( p->iColumn==0 || p->pRight==0 ); + assert( p->pRight==0 || p->pRight==p->pLeft ); + }else{ + pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); + } pNew->pRight = sqlite3ExprDup(db, p->pRight, 0); } } @@ -90480,6 +92036,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) ExprList *pNew; struct ExprList_item *pItem, *pOldItem; int i; + Expr *pPriorSelectCol = 0; assert( db!=0 ); if( p==0 ) return 0; pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); @@ -90494,7 +92051,24 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ Expr *pOldExpr = pOldItem->pExpr; + Expr *pNewExpr; pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags); + if( pOldExpr + && pOldExpr->op==TK_SELECT_COLUMN + && (pNewExpr = pItem->pExpr)!=0 + ){ + assert( pNewExpr->iColumn==0 || i>0 ); + if( pNewExpr->iColumn==0 ){ + assert( pOldExpr->pLeft==pOldExpr->pRight ); + pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; + }else{ + assert( i>0 ); + assert( pItem[-1].pExpr!=0 ); + assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 ); + assert( pPriorSelectCol==pItem[-1].pExpr->pLeft ); + pNewExpr->pLeft = pPriorSelectCol; + } + } pItem->zName = sqlite3DbStrDup(db, pOldItem->zName); pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan); pItem->sortOrder = pOldItem->sortOrder; @@ -90545,7 +92119,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ } pTab = pNewItem->pTab = pOldItem->pTab; if( pTab ){ - pTab->nRef++; + pTab->nTabRef++; } pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags); @@ -90578,33 +92152,41 @@ SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ } return pNew; } -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ - Select *pNew, *pPrior; +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ + Select *pRet = 0; + Select *pNext = 0; + Select **pp = &pRet; + Select *p; + assert( db!=0 ); - if( p==0 ) return 0; - pNew = sqlite3DbMallocRawNN(db, sizeof(*p) ); - if( pNew==0 ) return 0; - pNew->pEList = sqlite3ExprListDup(db, p->pEList, flags); - pNew->pSrc = sqlite3SrcListDup(db, p->pSrc, flags); - pNew->pWhere = sqlite3ExprDup(db, p->pWhere, flags); - pNew->pGroupBy = sqlite3ExprListDup(db, p->pGroupBy, flags); - pNew->pHaving = sqlite3ExprDup(db, p->pHaving, flags); - pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, flags); - pNew->op = p->op; - pNew->pPrior = pPrior = sqlite3SelectDup(db, p->pPrior, flags); - if( pPrior ) pPrior->pNext = pNew; - pNew->pNext = 0; - pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); - pNew->pOffset = sqlite3ExprDup(db, p->pOffset, flags); - pNew->iLimit = 0; - pNew->iOffset = 0; - pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; - pNew->nSelectRow = p->nSelectRow; - pNew->pWith = withDup(db, p->pWith); - sqlite3SelectSetName(pNew, p->zSelName); - return pNew; + for(p=pDup; p; p=p->pPrior){ + Select *pNew = sqlite3DbMallocRawNN(db, sizeof(*p) ); + if( pNew==0 ) break; + pNew->pEList = sqlite3ExprListDup(db, p->pEList, flags); + pNew->pSrc = sqlite3SrcListDup(db, p->pSrc, flags); + pNew->pWhere = sqlite3ExprDup(db, p->pWhere, flags); + pNew->pGroupBy = sqlite3ExprListDup(db, p->pGroupBy, flags); + pNew->pHaving = sqlite3ExprDup(db, p->pHaving, flags); + pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, flags); + pNew->op = p->op; + pNew->pNext = pNext; + pNew->pPrior = 0; + pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); + pNew->pOffset = sqlite3ExprDup(db, p->pOffset, flags); + pNew->iLimit = 0; + pNew->iOffset = 0; + pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; + pNew->addrOpenEphm[0] = -1; + pNew->addrOpenEphm[1] = -1; + pNew->nSelectRow = p->nSelectRow; + pNew->pWith = withDup(db, p->pWith); + sqlite3SelectSetName(pNew, p->zSelName); + *pp = pNew; + pp = &pNew->pPrior; + pNext = pNew; + } + + return pRet; } #else SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ @@ -90661,6 +92243,75 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( return 0; } +/* +** pColumns and pExpr form a vector assignment which is part of the SET +** clause of an UPDATE statement. Like this: +** +** (a,b,c) = (expr1,expr2,expr3) +** Or: (a,b,c) = (SELECT x,y,z FROM ....) +** +** For each term of the vector assignment, append new entries to the +** expression list pList. In the case of a subquery on the RHS, append +** TK_SELECT_COLUMN expressions. +*/ +SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* List to which to append. Might be NULL */ + IdList *pColumns, /* List of names of LHS of the assignment */ + Expr *pExpr /* Vector expression to be appended. Might be NULL */ +){ + sqlite3 *db = pParse->db; + int n; + int i; + int iFirst = pList ? pList->nExpr : 0; + /* pColumns can only be NULL due to an OOM but an OOM will cause an + ** exit prior to this routine being invoked */ + if( NEVER(pColumns==0) ) goto vector_append_error; + if( pExpr==0 ) goto vector_append_error; + + /* If the RHS is a vector, then we can immediately check to see that + ** the size of the RHS and LHS match. But if the RHS is a SELECT, + ** wildcards ("*") in the result set of the SELECT must be expanded before + ** we can do the size check, so defer the size check until code generation. + */ + if( pExpr->op!=TK_SELECT && pColumns->nId!=(n=sqlite3ExprVectorSize(pExpr)) ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pColumns->nId, n); + goto vector_append_error; + } + + for(i=0; inId; i++){ + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i); + pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); + if( pList ){ + assert( pList->nExpr==iFirst+i+1 ); + pList->a[pList->nExpr-1].zName = pColumns->a[i].zName; + pColumns->a[i].zName = 0; + } + } + + if( pExpr->op==TK_SELECT ){ + if( pList && pList->a[iFirst].pExpr ){ + Expr *pFirst = pList->a[iFirst].pExpr; + assert( pFirst->op==TK_SELECT_COLUMN ); + + /* Store the SELECT statement in pRight so it will be deleted when + ** sqlite3ExprListDelete() is called */ + pFirst->pRight = pExpr; + pExpr = 0; + + /* Remember the size of the LHS in iTable so that we can check that + ** the RHS and LHS sizes match during code generation. */ + pFirst->iTable = pColumns->nId; + } + } + +vector_append_error: + sqlite3ExprDelete(db, pExpr); + sqlite3IdListDelete(db, pColumns); + return pList; +} + /* ** Set the sort order for the last element on the given ExprList. */ @@ -90943,6 +92594,7 @@ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){ */ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ int rc = 0; + if( p==0 ) return 0; /* Can only happen following on OOM */ /* If an expression is an integer literal that fits in a signed 32-bit ** integer, then the EP_IntValue flag will have already been set */ @@ -91068,8 +92720,8 @@ static Select *isCandidateForInOpt(Expr *pX){ Select *p; SrcList *pSrc; ExprList *pEList; - Expr *pRes; Table *pTab; + int i; if( !ExprHasProperty(pX, EP_xIsSelect) ) return 0; /* Not a subquery */ if( ExprHasProperty(pX, EP_VarSelect) ) return 0; /* Correlated subq */ p = pX->x.pSelect; @@ -91092,23 +92744,18 @@ static Select *isCandidateForInOpt(Expr *pX){ assert( pTab->pSelect==0 ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; - if( pEList->nExpr!=1 ) return 0; /* One column in the result set */ - pRes = pEList->a[0].pExpr; - if( pRes->op!=TK_COLUMN ) return 0; /* Result is a column */ - assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + assert( pEList!=0 ); + /* All SELECT results must be columns. */ + for(i=0; inExpr; i++){ + Expr *pRes = pEList->a[i].pExpr; + if( pRes->op!=TK_COLUMN ) return 0; + assert( pRes->iTable==pSrc->a[0].iCursor ); /* Not a correlated subquery */ + } return p; } #endif /* SQLITE_OMIT_SUBQUERY */ -/* -** Code an OP_Once instruction and allocate space for its flag. Return the -** address of the new instruction. -*/ -SQLITE_PRIVATE int sqlite3CodeOnce(Parse *pParse){ - Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ - return sqlite3VdbeAddOp1(v, OP_Once, pParse->nOnce++); -} - +#ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code that checks the left-most column of index table iCur to see if ** it contains any NULL entries. Cause the register at regHasNull to be set @@ -91124,6 +92771,7 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ VdbeComment((v, "first_entry_in(%d)", iCur)); sqlite3VdbeJumpHere(v, addr1); } +#endif #ifndef SQLITE_OMIT_SUBQUERY @@ -91168,7 +92816,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** An existing b-tree might be used if the RHS expression pX is a simple ** subquery such as: ** -** SELECT FROM +** SELECT , ... FROM
      ** ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then @@ -91184,14 +92832,14 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate ** through the set members) then the b-tree must not contain duplicates. -** An epheremal table must be used unless the selected is guaranteed -** to be unique - either because it is an INTEGER PRIMARY KEY or it -** has a UNIQUE constraint or UNIQUE index. +** An epheremal table must be used unless the selected columns are guaranteed +** to be unique - either because it is an INTEGER PRIMARY KEY or due to +** a UNIQUE constraint or index. ** ** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used ** for fast set membership tests) then an epheremal table must -** be used unless is an INTEGER PRIMARY KEY or an index can -** be found with as its left-most column. +** be used unless is a single INTEGER PRIMARY KEY column or an +** index can be found with the specified as its left-most. ** ** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and ** if the RHS of the IN operator is a list (not a subquery) then this @@ -91212,9 +92860,26 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** the value in that register will be NULL if the b-tree contains one or more ** NULL values, and it will be some non-NULL value if the b-tree contains no ** NULL values. +** +** If the aiMap parameter is not NULL, it must point to an array containing +** one element for each column returned by the SELECT statement on the RHS +** of the IN(...) operator. The i'th entry of the array is populated with the +** offset of the index column that matches the i'th column returned by the +** SELECT. For example, if the expression and selected index are: +** +** (?,?,?) IN (SELECT a, b, c FROM t1) +** CREATE INDEX i1 ON t1(b, c, a); +** +** then aiMap[] is populated with {2, 0, 1}. */ #ifndef SQLITE_OMIT_SUBQUERY -SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int *prRhsHasNull){ +SQLITE_PRIVATE int sqlite3FindInIndex( + Parse *pParse, /* Parsing context */ + Expr *pX, /* The right-hand side (RHS) of the IN operator */ + u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */ + int *prRhsHasNull, /* Register holding NULL status. See notes */ + int *aiMap /* Mapping from Index fields to RHS fields */ +){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ int iTab = pParse->nTab++; /* Cursor of the RHS table */ @@ -91224,36 +92889,46 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + /* If the RHS of this IN(...) operator is a SELECT, and if it matters + ** whether or not the SELECT result contains NULL values, check whether + ** or not NULL is actually possible (it may not be, for example, due + ** to NOT NULL constraints in the schema). If no NULL values are possible, + ** set prRhsHasNull to 0 before continuing. */ + if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + int i; + ExprList *pEList = pX->x.pSelect->pEList; + for(i=0; inExpr; i++){ + if( sqlite3ExprCanBeNull(pEList->a[i].pExpr) ) break; + } + if( i==pEList->nExpr ){ + prRhsHasNull = 0; + } + } + /* Check to see if an existing table or index can be used to ** satisfy the query. This is preferable to generating a new - ** ephemeral table. - */ + ** ephemeral table. */ if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table
      . */ - Expr *pExpr; /* Expression */ - i16 iCol; /* Index of column */ i16 iDb; /* Database idx for pTab */ + ExprList *pEList = p->pEList; + int nExpr = pEList->nExpr; assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ pTab = p->pSrc->a[0].pTab; - pExpr = p->pEList->a[0].pExpr; - iCol = (i16)pExpr->iColumn; - + /* Code an OP_Transaction and OP_TableLock for
      . */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); - /* This function is only called from two places. In both cases the vdbe - ** has already been allocated. So assume sqlite3GetVdbe() is always - ** successful here. - */ - assert(v); - if( iCol<0 ){ - int iAddr = sqlite3CodeOnce(pParse); + assert(v); /* sqlite3GetVdbe() has always been previously called */ + if( nExpr==1 && pEList->a[0].pExpr->iColumn<0 ){ + /* The "x IN (SELECT rowid FROM table)" case */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); sqlite3OpenTable(pParse, iTab, iDb, pTab, OP_OpenRead); @@ -91262,44 +92937,109 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int sqlite3VdbeJumpHere(v, iAddr); }else{ Index *pIdx; /* Iterator variable */ + int affinity_ok = 1; + int i; - /* The collation sequence used by the comparison. If an index is to - ** be used in place of a temp-table, it must be ordered according - ** to this collation sequence. */ - CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pExpr); - - /* Check that the affinity that will be used to perform the - ** comparison is the same as the affinity of the column. If - ** it is not, it is not possible to use any index. - */ - int affinity_ok = sqlite3IndexAffinityOk(pX, pTab->aCol[iCol].affinity); - - for(pIdx=pTab->pIndex; pIdx && eType==0 && affinity_ok; pIdx=pIdx->pNext){ - if( (pIdx->aiColumn[0]==iCol) - && sqlite3FindCollSeq(db, ENC(db), pIdx->azColl[0], 0)==pReq - && (!mustBeUnique || (pIdx->nKeyCol==1 && IsUniqueIndex(pIdx))) - ){ - int iAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); - sqlite3VdbeSetP4KeyInfo(pParse, pIdx); - VdbeComment((v, "%s", pIdx->zName)); - assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); - eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; - - if( prRhsHasNull && !pTab->aCol[iCol].notNull ){ + /* Check that the affinity that will be used to perform each + ** comparison is the same as the affinity of each column in table + ** on the RHS of the IN operator. If it not, it is not possible to + ** use any index of the RHS table. */ + for(i=0; ipLeft, i); + int iCol = pEList->a[i].pExpr->iColumn; + char idxaff = sqlite3TableColumnAffinity(pTab,iCol); /* RHS table */ + char cmpaff = sqlite3CompareAffinity(pLhs, idxaff); + testcase( cmpaff==SQLITE_AFF_BLOB ); + testcase( cmpaff==SQLITE_AFF_TEXT ); + switch( cmpaff ){ + case SQLITE_AFF_BLOB: + break; + case SQLITE_AFF_TEXT: + /* sqlite3CompareAffinity() only returns TEXT if one side or the + ** other has no affinity and the other side is TEXT. Hence, + ** the only way for cmpaff to be TEXT is for idxaff to be TEXT + ** and for the term on the LHS of the IN to have no affinity. */ + assert( idxaff==SQLITE_AFF_TEXT ); + break; + default: + affinity_ok = sqlite3IsNumericAffinity(idxaff); + } + } + + if( affinity_ok ){ + /* Search for an existing index that will work for this IN operator */ + for(pIdx=pTab->pIndex; pIdx && eType==0; pIdx=pIdx->pNext){ + Bitmask colUsed; /* Columns of the index used */ + Bitmask mCol; /* Mask for the current column */ + if( pIdx->nColumnnColumn==BMS-2 ); + testcase( pIdx->nColumn==BMS-1 ); + if( pIdx->nColumn>=BMS-1 ) continue; + if( mustBeUnique ){ + if( pIdx->nKeyCol>nExpr + ||(pIdx->nColumn>nExpr && !IsUniqueIndex(pIdx)) + ){ + continue; /* This index is not unique over the IN RHS columns */ + } + } + + colUsed = 0; /* Columns of index used so far */ + for(i=0; ipLeft, i); + Expr *pRhs = pEList->a[i].pExpr; + CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + int j; + + assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr ); + for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; + assert( pIdx->azColl[j] ); + if( pReq!=0 && sqlite3StrICmp(pReq->zName, pIdx->azColl[j])!=0 ){ + continue; + } + break; + } + if( j==nExpr ) break; + mCol = MASKBIT(j); + if( mCol & colUsed ) break; /* Each column used only once */ + colUsed |= mCol; + if( aiMap ) aiMap[i] = j; + } + + assert( i==nExpr || colUsed!=(MASKBIT(nExpr)-1) ); + if( colUsed==(MASKBIT(nExpr)-1) ){ + /* If we reach this point, that means the index pIdx is usable */ + int iAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); +#ifndef SQLITE_OMIT_EXPLAIN + sqlite3VdbeAddOp4(v, OP_Explain, 0, 0, 0, + sqlite3MPrintf(db, "USING INDEX %s FOR IN-OPERATOR",pIdx->zName), + P4_DYNAMIC); +#endif + sqlite3VdbeAddOp3(v, OP_OpenRead, iTab, pIdx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIdx); + VdbeComment((v, "%s", pIdx->zName)); + assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); + eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; + + if( prRhsHasNull ){ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK - const i64 sOne = 1; - sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, - iTab, 0, 0, (u8*)&sOne, P4_INT64); + i64 mask = (1<nMem; - sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + *prRhsHasNull = ++pParse->nMem; + if( nExpr==1 ){ + sqlite3SetHasNullFlag(v, iTab, *prRhsHasNull); + } + } + sqlite3VdbeJumpHere(v, iAddr); } - sqlite3VdbeJumpHere(v, iAddr); - } - } - } - } + } /* End loop over indexes */ + } /* End if( affinity_ok ) */ + } /* End if not an rowid index */ + } /* End attempt to optimize using an index */ /* If no preexisting index is available for the IN clause ** and IN_INDEX_NOOP is an allowed reply @@ -91315,7 +93055,6 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int ){ eType = IN_INDEX_NOOP; } - if( eType==0 ){ /* Could not find an existing table or index to use as the RHS b-tree. @@ -91337,10 +93076,85 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int }else{ pX->iTable = iTab; } + + if( aiMap && eType!=IN_INDEX_INDEX_ASC && eType!=IN_INDEX_INDEX_DESC ){ + int i, n; + n = sqlite3ExprVectorSize(pX->pLeft); + for(i=0; ipLeft; + int nVal = sqlite3ExprVectorSize(pLeft); + Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0; + char *zRet; + + assert( pExpr->op==TK_IN ); + zRet = sqlite3DbMallocRaw(pParse->db, nVal+1); + if( zRet ){ + int i; + for(i=0; ipEList->a[i].pExpr, a); + }else{ + zRet[i] = a; + } + } + zRet[nVal] = '\0'; + } + return zRet; +} +#endif + +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Load the Parse object passed as the first argument with an error +** message of the form: +** +** "sub-select returns N columns - expected M" +*/ +SQLITE_PRIVATE void sqlite3SubselectError(Parse *pParse, int nActual, int nExpect){ + const char *zFmt = "sub-select returns %d columns - expected %d"; + sqlite3ErrorMsg(pParse, zFmt, nActual, nExpect); +} +#endif + +/* +** Expression pExpr is a vector that has been used in a context where +** it is not permitted. If pExpr is a sub-select vector, this routine +** loads the Parse object with a message of the form: +** +** "sub-select returns N columns - expected 1" +** +** Or, if it is a regular scalar vector: +** +** "row value misused" +*/ +SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ +#ifndef SQLITE_OMIT_SUBQUERY + if( pExpr->flags & EP_xIsSelect ){ + sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); + }else +#endif + { + sqlite3ErrorMsg(pParse, "row value misused"); + } +} + /* ** Generate code for scalar subqueries used as a subquery expression, EXISTS, ** or IN operators. Examples: @@ -91366,7 +93180,9 @@ SQLITE_PRIVATE int sqlite3FindInIndex(Parse *pParse, Expr *pX, u32 inFlags, int ** value to non-NULL if the RHS is NULL-free. ** ** For a SELECT or EXISTS operator, return the register that holds the -** result. For IN operators or if an error occurs, the return value is 0. +** result. For a multi-column SELECT, the result is stored in a contiguous +** array of registers and the return value is the register of the left-most +** result column. Return 0 for IN operators or if an error occurs. */ #ifndef SQLITE_OMIT_SUBQUERY SQLITE_PRIVATE int sqlite3CodeSubselect( @@ -91381,8 +93197,8 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( if( NEVER(v==0) ) return 0; sqlite3ExprCachePush(pParse); - /* This code must be run in its entirety every time it is encountered - ** if any of the following is true: + /* The evaluation of the IN/EXISTS/SELECT must be repeated every time it + ** is encountered if any of the following is true: ** ** * The right-hand side is a correlated subquery ** * The right-hand side is an expression list containing variables @@ -91392,7 +93208,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - jmpIfDynamic = sqlite3CodeOnce(pParse); VdbeCoverage(v); + jmpIfDynamic = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } #ifndef SQLITE_OMIT_EXPLAIN @@ -91408,17 +93224,18 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( switch( pExpr->op ){ case TK_IN: { - char affinity; /* Affinity of the LHS of the IN */ int addr; /* Address of OP_OpenEphemeral instruction */ Expr *pLeft = pExpr->pLeft; /* the LHS of the IN operator */ KeyInfo *pKeyInfo = 0; /* Key information */ - - affinity = sqlite3ExprAffinity(pLeft); + int nVal; /* Size of vector pLeft */ + + nVal = sqlite3ExprVectorSize(pLeft); + assert( !isRowid || nVal==1 ); /* Whether this is an 'x IN(SELECT...)' or an 'x IN()' ** expression it is handled the same way. An ephemeral table is - ** filled with single-field index keys representing the results - ** from the SELECT or the . + ** filled with index keys representing the results from the + ** SELECT or the . ** ** If the 'x' expression is a column value, or the SELECT... ** statement returns a column value, then the affinity of that @@ -91429,8 +93246,9 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( ** is used. */ pExpr->iTable = pParse->nTab++; - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, !isRowid); - pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, 1, 1); + addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, + pExpr->iTable, (isRowid?0:nVal)); + pKeyInfo = isRowid ? 0 : sqlite3KeyInfoAlloc(pParse->db, nVal, 1); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* Case 1: expr IN (SELECT ...) @@ -91439,27 +93257,36 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( ** table allocated and opened above. */ Select *pSelect = pExpr->x.pSelect; - SelectDest dest; - ExprList *pEList; + ExprList *pEList = pSelect->pEList; assert( !isRowid ); - sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); - dest.affSdst = (u8)affinity; - assert( (pExpr->iTable&0x0000FFFF)==pExpr->iTable ); - pSelect->iLimit = 0; - testcase( pSelect->selFlags & SF_Distinct ); - testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3KeyInfoUnref(pKeyInfo); - return 0; + /* If the LHS and RHS of the IN operator do not match, that + ** error will have been caught long before we reach this point. */ + if( ALWAYS(pEList->nExpr==nVal) ){ + SelectDest dest; + int i; + sqlite3SelectDestInit(&dest, SRT_Set, pExpr->iTable); + dest.zAffSdst = exprINAffinity(pParse, pExpr); + pSelect->iLimit = 0; + testcase( pSelect->selFlags & SF_Distinct ); + testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ + if( sqlite3Select(pParse, pSelect, &dest) ){ + sqlite3DbFree(pParse->db, dest.zAffSdst); + sqlite3KeyInfoUnref(pKeyInfo); + return 0; + } + sqlite3DbFree(pParse->db, dest.zAffSdst); + assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ + assert( pEList!=0 ); + assert( pEList->nExpr>0 ); + assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); + for(i=0; iaColl[i] = sqlite3BinaryCompareCollSeq( + pParse, p, pEList->a[i].pExpr + ); + } } - pEList = pSelect->pEList; - assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ - assert( pEList!=0 ); - assert( pEList->nExpr>0 ); - assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); - pKeyInfo->aColl[0] = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, - pEList->a[0].pExpr); }else if( ALWAYS(pExpr->x.pList!=0) ){ /* Case 2: expr IN (exprlist) ** @@ -91468,11 +93295,13 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( ** that columns affinity when building index keys. If is not ** a column, use numeric affinity. */ + char affinity; /* Affinity of the LHS of the IN */ int i; ExprList *pList = pExpr->x.pList; struct ExprList_item *pItem; int r1, r2, r3; + affinity = sqlite3ExprAffinity(pLeft); if( !affinity ){ affinity = SQLITE_AFF_BLOB; } @@ -91512,7 +93341,7 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( }else{ sqlite3VdbeAddOp4(v, OP_MakeRecord, r3, 1, r2, &affinity, 1); sqlite3ExprCacheAffinityChange(pParse, r3, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, pExpr->iTable, r2); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pExpr->iTable, r2, r3, 1); } } } @@ -91528,26 +93357,37 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( case TK_EXISTS: case TK_SELECT: default: { - /* If this has to be a scalar SELECT. Generate code to put the - ** value of this select in a memory cell and record the number - ** of the memory cell in iColumn. If this is an EXISTS, write - ** an integer 0 (not exists) or 1 (exists) into a memory cell - ** and record that memory cell in iColumn. + /* Case 3: (SELECT ... FROM ...) + ** or: EXISTS(SELECT ... FROM ...) + ** + ** For a SELECT, generate code to put the values for all columns of + ** the first row into an array of registers and return the index of + ** the first register. + ** + ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) + ** into a register and return that register number. + ** + ** In both cases, the query is augmented with "LIMIT 1". Any + ** preexisting limit is discarded in place of the new LIMIT 1. */ Select *pSel; /* SELECT statement to encode */ - SelectDest dest; /* How to deal with SELECt result */ + SelectDest dest; /* How to deal with SELECT result */ + int nReg; /* Registers to allocate */ testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + pSel = pExpr->x.pSelect; - sqlite3SelectDestInit(&dest, 0, ++pParse->nMem); + nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; + sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); + pParse->nMem += nReg; if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; dest.iSdst = dest.iSDParm; - sqlite3VdbeAddOp2(v, OP_Null, 0, dest.iSDParm); + dest.nSdst = nReg; + sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1); VdbeComment((v, "Init subquery result")); }else{ dest.eDest = SRT_Exists; @@ -91555,8 +93395,8 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( VdbeComment((v, "Init EXISTS result")); } sqlite3ExprDelete(pParse->db, pSel->pLimit); - pSel->pLimit = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, - &sqlite3IntTokens[1]); + pSel->pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER, + &sqlite3IntTokens[1], 0); pSel->iLimit = 0; pSel->selFlags &= ~SF_MultiValue; if( sqlite3Select(pParse, pSel, &dest) ){ @@ -91581,6 +93421,28 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( } #endif /* SQLITE_OMIT_SUBQUERY */ +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** a sub-query, that the LHS is a vector of size 1. +*/ +SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ + int nVector = sqlite3ExprVectorSize(pIn->pLeft); + if( (pIn->flags & EP_xIsSelect) ){ + if( nVector!=pIn->x.pSelect->pEList->nExpr ){ + sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); + return 1; + } + }else if( nVector!=1 ){ + sqlite3VectorErrorMsg(pParse, pIn->pLeft); + return 1; + } + return 0; +} +#endif + #ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code for an IN expression. @@ -91588,16 +93450,24 @@ SQLITE_PRIVATE int sqlite3CodeSubselect( ** x IN (SELECT ...) ** x IN (value, value, ...) ** -** The left-hand side (LHS) is a scalar expression. The right-hand side (RHS) -** is an array of zero or more values. The expression is true if the LHS is -** contained within the RHS. The value of the expression is unknown (NULL) -** if the LHS is NULL or if the LHS is not contained within the RHS and the -** RHS contains one or more NULL values. +** The left-hand side (LHS) is a scalar or vector expression. The +** right-hand side (RHS) is an array of zero or more scalar values, or a +** subquery. If the RHS is a subquery, the number of result columns must +** match the number of columns in the vector on the LHS. If the RHS is +** a list of values, the LHS must be a scalar. +** +** The IN operator is true if the LHS value is contained within the RHS. +** The result is false if the LHS is definitely not in the RHS. The +** result is NULL if the presence of the LHS in the RHS cannot be +** determined due to NULLs. ** ** This routine generates code that jumps to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS ** is contained in the RHS then jump to destIfNull. If the LHS is contained ** within the RHS then fall through. +** +** See the separate in-operator.md documentation file in the canonical +** SQLite source tree for additional information. */ static void sqlite3ExprCodeIN( Parse *pParse, /* Parsing and code generating context */ @@ -91606,36 +93476,83 @@ static void sqlite3ExprCodeIN( int destIfNull /* Jump here if the results are unknown due to NULLs */ ){ int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ - char affinity; /* Comparison affinity to use */ int eType; /* Type of the RHS */ - int r1; /* Temporary use register */ + int rLhs; /* Register(s) holding the LHS values */ + int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ Vdbe *v; /* Statement under construction */ + int *aiMap = 0; /* Map from vector field to index column */ + char *zAff = 0; /* Affinity string for comparisons */ + int nVector; /* Size of vectors for this IN operator */ + int iDummy; /* Dummy parameter to exprCodeVector() */ + Expr *pLeft; /* The LHS of the IN operator */ + int i; /* loop counter */ + int destStep2; /* Where to jump when NULLs seen in step 2 */ + int destStep6 = 0; /* Start of code for Step 6 */ + int addrTruthOp; /* Address of opcode that determines the IN is true */ + int destNotNull; /* Jump here if a comparison is not true in step 6 */ + int addrTop; /* Top of the step-6 loop */ + + pLeft = pExpr->pLeft; + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; + zAff = exprINAffinity(pParse, pExpr); + nVector = sqlite3ExprVectorSize(pExpr->pLeft); + aiMap = (int*)sqlite3DbMallocZero( + pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1 + ); + if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error; - /* Compute the RHS. After this step, the table with cursor - ** pExpr->iTable will contains the values that make up the RHS. - */ + /* Attempt to compute the RHS. After this step, if anything other than + ** IN_INDEX_NOOP is returned, the table opened ith cursor pExpr->iTable + ** contains the values that make up the RHS. If IN_INDEX_NOOP is returned, + ** the RHS has not yet been coded. */ v = pParse->pVdbe; assert( v!=0 ); /* OOM detected prior to this routine */ VdbeNoopComment((v, "begin IN expr")); eType = sqlite3FindInIndex(pParse, pExpr, IN_INDEX_MEMBERSHIP | IN_INDEX_NOOP_OK, - destIfFalse==destIfNull ? 0 : &rRhsHasNull); + destIfFalse==destIfNull ? 0 : &rRhsHasNull, aiMap); - /* Figure out the affinity to use to create a key from the results - ** of the expression. affinityStr stores a static string suitable for - ** P4 of OP_MakeRecord. - */ - affinity = comparisonAffinity(pExpr); + assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH + || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC + ); +#ifdef SQLITE_DEBUG + /* Confirm that aiMap[] contains nVector integer values between 0 and + ** nVector-1. */ + for(i=0; i from " IN (...)". + /* Code the LHS, the from " IN (...)". If the LHS is a + ** vector, then it is stored in an array of nVector registers starting + ** at r1. + ** + ** sqlite3FindInIndex() might have reordered the fields of the LHS vector + ** so that the fields are in the same order as an existing index. The + ** aiMap[] array contains a mapping from the original LHS field order to + ** the field order that matches the RHS index. */ sqlite3ExprCachePush(pParse); - r1 = sqlite3GetTempReg(pParse); - sqlite3ExprCode(pParse, pExpr->pLeft, r1); + rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); + for(i=0; ix.pList; @@ -91647,7 +93564,7 @@ static void sqlite3ExprCodeIN( assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); if( destIfNull!=destIfFalse ){ regCkNull = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_BitAnd, r1, r1, regCkNull); + sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); } for(ii=0; iinExpr; ii++){ r2 = sqlite3ExprCodeTemp(pParse, pList->a[ii].pExpr, ®ToFree); @@ -91655,16 +93572,16 @@ static void sqlite3ExprCodeIN( sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull); } if( iinExpr-1 || destIfNull!=destIfFalse ){ - sqlite3VdbeAddOp4(v, OP_Eq, r1, labelOk, r2, + sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2, (void*)pColl, P4_COLLSEQ); VdbeCoverageIf(v, iinExpr-1); VdbeCoverageIf(v, ii==pList->nExpr-1); - sqlite3VdbeChangeP5(v, affinity); + sqlite3VdbeChangeP5(v, zAff[0]); }else{ assert( destIfNull==destIfFalse ); - sqlite3VdbeAddOp4(v, OP_Ne, r1, destIfFalse, r2, + sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse, r2, (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, affinity | SQLITE_JUMPIFNULL); + sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL); } sqlite3ReleaseTempReg(pParse, regToFree); } @@ -91674,77 +93591,113 @@ static void sqlite3ExprCodeIN( } sqlite3VdbeResolveLabel(v, labelOk); sqlite3ReleaseTempReg(pParse, regCkNull); + goto sqlite3ExprCodeIN_finished; + } + + /* Step 2: Check to see if the LHS contains any NULL columns. If the + ** LHS does contain NULLs then the result must be either FALSE or NULL. + ** We will then skip the binary search of the RHS. + */ + if( destIfNull==destIfFalse ){ + destStep2 = destIfFalse; }else{ - - /* If the LHS is NULL, then the result is either false or NULL depending - ** on whether the RHS is empty or not, respectively. - */ - if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ - if( destIfNull==destIfFalse ){ - /* Shortcut for the common case where the false and NULL outcomes are - ** the same. */ - sqlite3VdbeAddOp2(v, OP_IsNull, r1, destIfNull); VdbeCoverage(v); - }else{ - int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, r1); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); - VdbeCoverage(v); - sqlite3VdbeGoto(v, destIfNull); - sqlite3VdbeJumpHere(v, addr1); - } - } - - if( eType==IN_INDEX_ROWID ){ - /* In this case, the RHS is the ROWID of table b-tree - */ - sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, r1); + destStep2 = destStep6 = sqlite3VdbeMakeLabel(v); + } + for(i=0; ipLeft, i); + if( sqlite3ExprCanBeNull(p) ){ + sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); VdbeCoverage(v); - }else{ - /* In this case, the RHS is an index b-tree. - */ - sqlite3VdbeAddOp4(v, OP_Affinity, r1, 1, 0, &affinity, 1); - - /* If the set membership test fails, then the result of the - ** "x IN (...)" expression must be either 0 or NULL. If the set - ** contains no NULL values, then the result is 0. If the set - ** contains one or more NULL values, then the result of the - ** expression is also NULL. - */ - assert( destIfFalse!=destIfNull || rRhsHasNull==0 ); - if( rRhsHasNull==0 ){ - /* This branch runs if it is known at compile time that the RHS - ** cannot contain NULL values. This happens as the result - ** of a "NOT NULL" constraint in the database schema. - ** - ** Also run this branch if NULL is equivalent to FALSE - ** for this particular IN operator. - */ - sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, r1, 1); - VdbeCoverage(v); - }else{ - /* In this branch, the RHS of the IN might contain a NULL and - ** the presence of a NULL on the RHS makes a difference in the - ** outcome. - */ - int addr1; - - /* First check to see if the LHS is contained in the RHS. If so, - ** then the answer is TRUE the presence of NULLs in the RHS does - ** not matter. If the LHS is not contained in the RHS, then the - ** answer is NULL if the RHS contains NULLs and the answer is - ** FALSE if the RHS is NULL-free. - */ - addr1 = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, r1, 1); - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_IsNull, rRhsHasNull, destIfNull); - VdbeCoverage(v); - sqlite3VdbeGoto(v, destIfFalse); - sqlite3VdbeJumpHere(v, addr1); - } } } - sqlite3ReleaseTempReg(pParse, r1); + + /* Step 3. The LHS is now known to be non-NULL. Do the binary search + ** of the RHS using the LHS as a probe. If found, the result is + ** true. + */ + if( eType==IN_INDEX_ROWID ){ + /* In this case, the RHS is the ROWID of table b-tree and so we also + ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 + ** into a single opcode. */ + sqlite3VdbeAddOp3(v, OP_SeekRowid, pExpr->iTable, destIfFalse, rLhs); + VdbeCoverage(v); + addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ + }else{ + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); + if( destIfFalse==destIfNull ){ + /* Combine Step 3 and Step 5 into a single opcode */ + sqlite3VdbeAddOp4Int(v, OP_NotFound, pExpr->iTable, destIfFalse, + rLhs, nVector); VdbeCoverage(v); + goto sqlite3ExprCodeIN_finished; + } + /* Ordinary Step 3, for the case where FALSE and NULL are distinct */ + addrTruthOp = sqlite3VdbeAddOp4Int(v, OP_Found, pExpr->iTable, 0, + rLhs, nVector); VdbeCoverage(v); + } + + /* Step 4. If the RHS is known to be non-NULL and we did not find + ** an match on the search above, then the result must be FALSE. + */ + if( rRhsHasNull && nVector==1 ){ + sqlite3VdbeAddOp2(v, OP_NotNull, rRhsHasNull, destIfFalse); + VdbeCoverage(v); + } + + /* Step 5. If we do not care about the difference between NULL and + ** FALSE, then just return false. + */ + if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse); + + /* Step 6: Loop through rows of the RHS. Compare each row to the LHS. + ** If any comparison is NULL, then the result is NULL. If all + ** comparisons are FALSE then the final result is FALSE. + ** + ** For a scalar LHS, it is sufficient to check just the first row + ** of the RHS. + */ + if( destStep6 ) sqlite3VdbeResolveLabel(v, destStep6); + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind, pExpr->iTable, destIfFalse); + VdbeCoverage(v); + if( nVector>1 ){ + destNotNull = sqlite3VdbeMakeLabel(v); + }else{ + /* For nVector==1, combine steps 6 and 7 by immediately returning + ** FALSE if the first comparison is not NULL */ + destNotNull = destIfFalse; + } + for(i=0; iiTable, i, r3); + sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, + (void*)pColl, P4_COLLSEQ); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, r3); + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfNull); + if( nVector>1 ){ + sqlite3VdbeResolveLabel(v, destNotNull); + sqlite3VdbeAddOp2(v, OP_Next, pExpr->iTable, addrTop+1); + VdbeCoverage(v); + + /* Step 7: If we reach this point, we know that the result must + ** be false. */ + sqlite3VdbeAddOp2(v, OP_Goto, 0, destIfFalse); + } + + /* Jumps here in order to return true. */ + sqlite3VdbeJumpHere(v, addrTruthOp); + +sqlite3ExprCodeIN_finished: + if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); sqlite3ExprCachePop(pParse); VdbeComment((v, "end IN expr")); +sqlite3ExprCodeIN_oom_error: + sqlite3DbFree(pParse->db, aiMap); + sqlite3DbFree(pParse->db, zAff); } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -91788,52 +93741,39 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ const char *z = pExpr->u.zToken; assert( z!=0 ); c = sqlite3DecOrHexToI64(z, &value); - if( c==0 || (c==2 && negFlag) ){ - if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; } - sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64); - }else{ + if( c==1 || (c==2 && !negFlag) || (negFlag && value==SMALLEST_INT64)){ #ifdef SQLITE_OMIT_FLOATING_POINT sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z); #else #ifndef SQLITE_OMIT_HEX_INTEGER if( sqlite3_strnicmp(z,"0x",2)==0 ){ - sqlite3ErrorMsg(pParse, "hex literal too big: %s", z); + sqlite3ErrorMsg(pParse, "hex literal too big: %s%s", negFlag?"-":"",z); }else #endif { codeReal(v, z, negFlag, iMem); } #endif + }else{ + if( negFlag ){ value = c==2 ? SMALLEST_INT64 : -value; } + sqlite3VdbeAddOp4Dup8(v, OP_Int64, 0, iMem, 0, (u8*)&value, P4_INT64); } } } -#if defined(SQLITE_DEBUG) -/* -** Verify the consistency of the column cache -*/ -static int cacheIsValid(Parse *pParse){ - int i, n; - for(i=n=0; iaColCache[i].iReg>0 ) n++; - } - return n==pParse->nColCache; -} -#endif - /* -** Clear a cache entry. +** Erase column-cache entry number i */ -static void cacheEntryClear(Parse *pParse, struct yColCache *p){ - if( p->tempReg ){ +static void cacheEntryClear(Parse *pParse, int i){ + if( pParse->aColCache[i].tempReg ){ if( pParse->nTempRegaTempReg) ){ - pParse->aTempReg[pParse->nTempReg++] = p->iReg; + pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg; } - p->tempReg = 0; } - p->iReg = 0; pParse->nColCache--; - assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); + if( inColCache ){ + pParse->aColCache[i] = pParse->aColCache[pParse->nColCache]; + } } @@ -91863,46 +93803,33 @@ SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int ** that the object will never already be in cache. Verify this guarantee. */ #ifndef NDEBUG - for(i=0, p=pParse->aColCache; iiReg==0 || p->iTable!=iTab || p->iColumn!=iCol ); + for(i=0, p=pParse->aColCache; inColCache; i++, p++){ + assert( p->iTable!=iTab || p->iColumn!=iCol ); } #endif - /* Find an empty slot and replace it */ - for(i=0, p=pParse->aColCache; iiReg==0 ){ - p->iLevel = pParse->iCacheLevel; - p->iTable = iTab; - p->iColumn = iCol; - p->iReg = iReg; - p->tempReg = 0; - p->lru = pParse->iCacheCnt++; - pParse->nColCache++; - assert( pParse->db->mallocFailed || cacheIsValid(pParse) ); - return; - } - } - - /* Replace the last recently used */ - minLru = 0x7fffffff; - idxLru = -1; - for(i=0, p=pParse->aColCache; ilrulru; + /* If the cache is already full, delete the least recently used entry */ + if( pParse->nColCache>=SQLITE_N_COLCACHE ){ + minLru = 0x7fffffff; + idxLru = -1; + for(i=0, p=pParse->aColCache; ilrulru; + } } - } - if( ALWAYS(idxLru>=0) ){ p = &pParse->aColCache[idxLru]; - p->iLevel = pParse->iCacheLevel; - p->iTable = iTab; - p->iColumn = iCol; - p->iReg = iReg; - p->tempReg = 0; - p->lru = pParse->iCacheCnt++; - assert( cacheIsValid(pParse) ); - return; + }else{ + p = &pParse->aColCache[pParse->nColCache++]; } + + /* Add the new entry to the end of the cache */ + p->iLevel = pParse->iCacheLevel; + p->iTable = iTab; + p->iColumn = iCol; + p->iReg = iReg; + p->tempReg = 0; + p->lru = pParse->iCacheCnt++; } /* @@ -91910,13 +93837,14 @@ SQLITE_PRIVATE void sqlite3ExprCacheStore(Parse *pParse, int iTab, int iCol, int ** Purge the range of registers from the column cache. */ SQLITE_PRIVATE void sqlite3ExprCacheRemove(Parse *pParse, int iReg, int nReg){ - struct yColCache *p; - if( iReg<=0 || pParse->nColCache==0 ) return; - p = &pParse->aColCache[SQLITE_N_COLCACHE-1]; - while(1){ - if( p->iReg >= iReg && p->iReg < iReg+nReg ) cacheEntryClear(pParse, p); - if( p==pParse->aColCache ) break; - p--; + int i = 0; + while( inColCache ){ + struct yColCache *p = &pParse->aColCache[i]; + if( p->iReg >= iReg && p->iReg < iReg+nReg ){ + cacheEntryClear(pParse, i); + }else{ + i++; + } } } @@ -91940,8 +93868,7 @@ SQLITE_PRIVATE void sqlite3ExprCachePush(Parse *pParse){ ** the cache to the state it was in prior the most recent Push. */ SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){ - int i; - struct yColCache *p; + int i = 0; assert( pParse->iCacheLevel>=1 ); pParse->iCacheLevel--; #ifdef SQLITE_DEBUG @@ -91949,9 +93876,11 @@ SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){ printf("POP to %d\n", pParse->iCacheLevel); } #endif - for(i=0, p=pParse->aColCache; iiReg && p->iLevel>pParse->iCacheLevel ){ - cacheEntryClear(pParse, p); + while( inColCache ){ + if( pParse->aColCache[i].iLevel>pParse->iCacheLevel ){ + cacheEntryClear(pParse, i); + }else{ + i++; } } } @@ -91965,7 +93894,7 @@ SQLITE_PRIVATE void sqlite3ExprCachePop(Parse *pParse){ static void sqlite3ExprCachePinRegister(Parse *pParse, int iReg){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ if( p->iReg==iReg ){ p->tempReg = 0; } @@ -92043,8 +93972,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iiReg>0 && p->iTable==iTable && p->iColumn==iColumn ){ + for(i=0, p=pParse->aColCache; inColCache; i++, p++){ + if( p->iTable==iTable && p->iColumn==iColumn ){ p->lru = pParse->iCacheCnt++; sqlite3ExprCachePinRegister(pParse, p->iReg); return p->iReg; @@ -92076,18 +94005,20 @@ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnToReg( */ SQLITE_PRIVATE void sqlite3ExprCacheClear(Parse *pParse){ int i; - struct yColCache *p; -#if SQLITE_DEBUG +#ifdef SQLITE_DEBUG if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ printf("CLEAR\n"); } #endif - for(i=0, p=pParse->aColCache; iiReg ){ - cacheEntryClear(pParse, p); + for(i=0; inColCache; i++){ + if( pParse->aColCache[i].tempReg + && pParse->nTempRegaTempReg) + ){ + pParse->aTempReg[pParse->nTempReg++] = pParse->aColCache[i].iReg; } } + pParse->nColCache = 0; } /* @@ -92119,7 +94050,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int n static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ int r = p->iReg; if( r>=iFrom && r<=iTo ) return 1; /*NO_TEST*/ } @@ -92129,7 +94060,9 @@ static int usedAsColumnCache(Parse *pParse, int iFrom, int iTo){ /* -** Convert an expression node to a TK_REGISTER +** Convert a scalar expression node to a TK_REGISTER referencing +** register iReg. The caller must ensure that iReg already contains +** the correct value for the expression. */ static void exprToRegister(Expr *p, int iReg){ p->op2 = p->op; @@ -92138,6 +94071,38 @@ static void exprToRegister(Expr *p, int iReg){ ExprClearProperty(p, EP_Skip); } +/* +** Evaluate an expression (either a vector or a scalar expression) and store +** the result in continguous temporary registers. Return the index of +** the first register used to store the result. +** +** If the returned result register is a temporary scalar, then also write +** that register number into *piFreeable. If the returned result register +** is not a temporary or if the expression is a vector set *piFreeable +** to 0. +*/ +static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ + int iResult; + int nResult = sqlite3ExprVectorSize(p); + if( nResult==1 ){ + iResult = sqlite3ExprCodeTemp(pParse, p, piFreeable); + }else{ + *piFreeable = 0; + if( p->op==TK_SELECT ){ + iResult = sqlite3CodeSubselect(pParse, p, 0, 0); + }else{ + int i; + iResult = pParse->nMem+1; + pParse->nMem += nResult; + for(i=0; ix.pList->a[i].pExpr, i+iResult); + } + } + } + return iResult; +} + + /* ** Generate code into the current Vdbe to evaluate the given ** expression. Attempt to store the results in register "target". @@ -92155,9 +94120,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) int inReg = target; /* Results stored in register inReg */ int regFree1 = 0; /* If non-zero free this temporary register */ int regFree2 = 0; /* If non-zero free this temporary register */ - int r1, r2, r3, r4; /* Various register numbers */ - sqlite3 *db = pParse->db; /* The database connection */ + int r1, r2; /* Various register numbers */ Expr tempX; /* Temporary expression node */ + int p5 = 0; assert( target>0 && target<=pParse->nMem ); if( v==0 ){ @@ -92176,12 +94141,11 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ assert( pCol->iMem>0 ); - inReg = pCol->iMem; - break; + return pCol->iMem; }else if( pAggInfo->useSortingIdx ){ sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); - break; + return target; } /* Otherwise, fall thru into the TK_COLUMN case */ } @@ -92190,38 +94154,36 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) if( iTab<0 ){ if( pParse->ckBase>0 ){ /* Generating CHECK constraints or inserting into partial index */ - inReg = pExpr->iColumn + pParse->ckBase; - break; + return pExpr->iColumn + pParse->ckBase; }else{ /* Coding an expression that is part of an index where column names ** in the index refer to the table to which the index belongs */ iTab = pParse->iSelfTab; } } - inReg = sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, + return sqlite3ExprCodeGetColumn(pParse, pExpr->pTab, pExpr->iColumn, iTab, target, pExpr->op2); - break; } case TK_INTEGER: { codeInteger(pParse, pExpr, 0, target); - break; + return target; } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { assert( !ExprHasProperty(pExpr, EP_IntValue) ); codeReal(v, pExpr->u.zToken, 0, target); - break; + return target; } #endif case TK_STRING: { assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeLoadString(v, target, pExpr->u.zToken); - break; + return target; } case TK_NULL: { sqlite3VdbeAddOp2(v, OP_Null, 0, target); - break; + return target; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { @@ -92236,7 +94198,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( z[n]=='\'' ); zBlob = sqlite3HexToBlob(sqlite3VdbeDb(v), z, n); sqlite3VdbeAddOp4(v, OP_Blob, n/2, target, 0, zBlob, P4_DYNAMIC); - break; + return target; } #endif case TK_VARIABLE: { @@ -92245,15 +94207,15 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( pExpr->u.zToken[0]!=0 ); sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target); if( pExpr->u.zToken[1]!=0 ){ - assert( pExpr->u.zToken[0]=='?' - || strcmp(pExpr->u.zToken, pParse->azVar[pExpr->iColumn-1])==0 ); - sqlite3VdbeChangeP4(v, -1, pParse->azVar[pExpr->iColumn-1], P4_STATIC); + const char *z = sqlite3VListNumToName(pParse->pVList, pExpr->iColumn); + assert( pExpr->u.zToken[0]=='?' || strcmp(pExpr->u.zToken, z)==0 ); + pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */ + sqlite3VdbeAppendP4(v, (char*)z, P4_STATIC); } - break; + return target; } case TK_REGISTER: { - inReg = pExpr->iTable; - break; + return pExpr->iTable; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { @@ -92267,42 +94229,37 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) sqlite3AffinityType(pExpr->u.zToken, 0)); testcase( usedAsColumnCache(pParse, inReg, inReg) ); sqlite3ExprCacheAffinityChange(pParse, inReg, 1); - break; + return inReg; } #endif /* SQLITE_OMIT_CAST */ + case TK_IS: + case TK_ISNOT: + op = (op==TK_IS) ? TK_EQ : TK_NE; + p5 = SQLITE_NULLEQ; + /* fall-through */ case TK_LT: case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2); - assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); - assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); - assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); - assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); - assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); - assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - break; - } - case TK_IS: - case TK_ISNOT: { - testcase( op==TK_IS ); - testcase( op==TK_ISNOT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - op = (op==TK_IS) ? TK_EQ : TK_NE; - codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | SQLITE_NULLEQ); - VdbeCoverageIf(v, op==TK_EQ); - VdbeCoverageIf(v, op==TK_NE); - testcase( regFree1==0 ); - testcase( regFree2==0 ); + Expr *pLeft = pExpr->pLeft; + if( sqlite3ExprIsVector(pLeft) ){ + codeVectorCompare(pParse, pExpr, target, op, p5); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + codeCompare(pParse, pLeft, pExpr->pRight, op, + r1, r2, inReg, SQLITE_STOREP2 | p5); + assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); + assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); + assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); + assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); + assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); + assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + testcase( regFree1==0 ); + testcase( regFree2==0 ); + } break; } case TK_AND: @@ -92340,10 +94297,12 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( pLeft ); if( pLeft->op==TK_INTEGER ){ codeInteger(pParse, pLeft, 1, target); + return target; #ifndef SQLITE_OMIT_FLOATING_POINT }else if( pLeft->op==TK_FLOAT ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); codeReal(v, pLeft->u.zToken, 1, target); + return target; #endif }else{ tempX.op = TK_INTEGER; @@ -92354,7 +94313,6 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); testcase( regFree2==0 ); } - inReg = target; break; } case TK_BITNOT: @@ -92363,7 +94321,6 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( TK_NOT==OP_Not ); testcase( op==TK_NOT ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); testcase( regFree1==0 ); - inReg = target; sqlite3VdbeAddOp2(v, op, r1, inReg); break; } @@ -92388,7 +94345,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken); }else{ - inReg = pInfo->aFunc[pExpr->iAgg].iMem; + return pInfo->aFunc[pExpr->iAgg].iMem; } break; } @@ -92399,9 +94356,15 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) const char *zId; /* The function name */ u32 constMask = 0; /* Mask of function arguments that are constant */ int i; /* Loop counter */ + sqlite3 *db = pParse->db; /* The database connection */ u8 enc = ENC(db); /* The text encoding used by this database */ CollSeq *pColl = 0; /* A collating sequence */ + if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ + /* SQL functions can be expensive. So try to move constant functions + ** out of the inner loop, even if that means an extra OP_Copy. */ + return sqlite3ExprCodeAtInit(pParse, pExpr, -1); + } assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); if( ExprHasProperty(pExpr, EP_TokenOnly) ){ pFarg = 0; @@ -92447,9 +94410,24 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) */ if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ assert( nFarg>=1 ); - inReg = sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); - break; + return sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); + } + +#ifdef SQLITE_DEBUG + /* The AFFINITY() function evaluates to a string that describes + ** the type affinity of the argument. This is used for testing of + ** the SQLite type logic. + */ + if( pDef->funcFlags & SQLITE_FUNC_AFFINITY ){ + const char *azAff[] = { "blob", "text", "numeric", "integer", "real" }; + char aff; + assert( nFarg==1 ); + aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); + sqlite3VdbeLoadString(v, target, + aff ? azAff[aff-SQLITE_AFF_BLOB] : "none"); + return target; } +#endif for(i=0; ia[i].pExpr) ){ @@ -92523,16 +94501,35 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) if( nFarg && constMask==0 ){ sqlite3ReleaseTempRange(pParse, r1, nFarg); } - break; + return target; } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: case TK_SELECT: { + int nCol; testcase( op==TK_EXISTS ); testcase( op==TK_SELECT ); - inReg = sqlite3CodeSubselect(pParse, pExpr, 0, 0); + if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ + sqlite3SubselectError(pParse, nCol, 1); + }else{ + return sqlite3CodeSubselect(pParse, pExpr, 0, 0); + } break; } + case TK_SELECT_COLUMN: { + int n; + if( pExpr->pLeft->iTable==0 ){ + pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft, 0, 0); + } + assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT ); + if( pExpr->iTable + && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) + ){ + sqlite3ErrorMsg(pParse, "%d columns assigned %d values", + pExpr->iTable, n); + } + return pExpr->pLeft->iTable + pExpr->iColumn; + } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(v); int destIfNull = sqlite3VdbeMakeLabel(v); @@ -92542,7 +94539,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) sqlite3VdbeResolveLabel(v, destIfFalse); sqlite3VdbeAddOp2(v, OP_AddImm, target, 0); sqlite3VdbeResolveLabel(v, destIfNull); - break; + return target; } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -92559,35 +94556,13 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { - Expr *pLeft = pExpr->pLeft; - struct ExprList_item *pLItem = pExpr->x.pList->a; - Expr *pRight = pLItem->pExpr; - - r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree1==0 ); - testcase( regFree2==0 ); - r3 = sqlite3GetTempReg(pParse); - r4 = sqlite3GetTempReg(pParse); - codeCompare(pParse, pLeft, pRight, OP_Ge, - r1, r2, r3, SQLITE_STOREP2); VdbeCoverage(v); - pLItem++; - pRight = pLItem->pExpr; - sqlite3ReleaseTempReg(pParse, regFree2); - r2 = sqlite3ExprCodeTemp(pParse, pRight, ®Free2); - testcase( regFree2==0 ); - codeCompare(pParse, pLeft, pRight, OP_Le, r1, r2, r4, SQLITE_STOREP2); - VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_And, r3, r4, target); - sqlite3ReleaseTempReg(pParse, r3); - sqlite3ReleaseTempReg(pParse, r4); - break; + exprCodeBetween(pParse, pExpr, target, 0, 0); + return target; } case TK_SPAN: case TK_COLLATE: case TK_UPLUS: { - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - break; + return sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); } case TK_TRIGGER: { @@ -92646,6 +94621,10 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) break; } + case TK_VECTOR: { + sqlite3ErrorMsg(pParse, "row value misused"); + break; + } /* ** Form A: @@ -92689,8 +94668,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) if( (pX = pExpr->pLeft)!=0 ){ tempX = *pX; testcase( pX->op==TK_COLUMN ); - exprToRegister(&tempX, sqlite3ExprCodeTemp(pParse, pX, ®Free1)); + exprToRegister(&tempX, exprCodeVector(pParse, &tempX, ®Free1)); testcase( regFree1==0 ); + memset(&opCompare, 0, sizeof(opCompare)); opCompare.op = TK_EQ; opCompare.pLeft = &tempX; pTest = &opCompare; @@ -92724,7 +94704,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); } - assert( db->mallocFailed || pParse->nErr>0 + assert( pParse->db->mallocFailed || pParse->nErr>0 || pParse->iCacheLevel==iCacheLevel ); sqlite3VdbeResolveLabel(v, endLabel); break; @@ -92765,24 +94745,40 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) /* ** Factor out the code of the given expression to initialization time. +** +** If regDest>=0 then the result is always stored in that register and the +** result is not reusable. If regDest<0 then this routine is free to +** store the value whereever it wants. The register where the expression +** is stored is returned. When regDest<0, two identical expressions will +** code to the same register. */ -SQLITE_PRIVATE void sqlite3ExprCodeAtInit( +SQLITE_PRIVATE int sqlite3ExprCodeAtInit( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The expression to code when the VDBE initializes */ - int regDest, /* Store the value in this register */ - u8 reusable /* True if this expression is reusable */ + int regDest /* Store the value in this register */ ){ ExprList *p; assert( ConstFactorOk(pParse) ); p = pParse->pConstExpr; + if( regDest<0 && p ){ + struct ExprList_item *pItem; + int i; + for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ + if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ + return pItem->u.iConstExprReg; + } + } + } pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); p = sqlite3ExprListAppend(pParse, p, pExpr); if( p ){ struct ExprList_item *pItem = &p->a[p->nExpr-1]; + pItem->reusable = regDest<0; + if( regDest<0 ) regDest = ++pParse->nMem; pItem->u.iConstExprReg = regDest; - pItem->reusable = reusable; } pParse->pConstExpr = p; + return regDest; } /* @@ -92805,19 +94801,8 @@ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ && pExpr->op!=TK_REGISTER && sqlite3ExprIsConstantNotJoin(pExpr) ){ - ExprList *p = pParse->pConstExpr; - int i; *pReg = 0; - if( p ){ - struct ExprList_item *pItem; - for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(pItem->pExpr,pExpr,-1)==0 ){ - return pItem->u.iConstExprReg; - } - } - } - r2 = ++pParse->nMem; - sqlite3ExprCodeAtInit(pParse, pExpr, r2, 1); + r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1); }else{ int r1 = sqlite3GetTempReg(pParse); r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); @@ -92871,7 +94856,7 @@ SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){ */ SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ if( pParse->okConstFactor && sqlite3ExprIsConstant(pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target, 0); + sqlite3ExprCodeAtInit(pParse, pExpr, target); }else{ sqlite3ExprCode(pParse, pExpr, target); } @@ -92935,10 +94920,15 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( if( !ConstFactorOk(pParse) ) flags &= ~SQLITE_ECEL_FACTOR; for(pItem=pList->a, i=0; ipExpr; - if( (flags & SQLITE_ECEL_REF)!=0 && (j = pList->a[i].u.x.iOrderByCol)>0 ){ - sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); + if( (flags & SQLITE_ECEL_REF)!=0 && (j = pItem->u.x.iOrderByCol)>0 ){ + if( flags & SQLITE_ECEL_OMITREF ){ + i--; + n--; + }else{ + sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); + } }else if( (flags & SQLITE_ECEL_FACTOR)!=0 && sqlite3ExprIsConstant(pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target+i, 0); + sqlite3ExprCodeAtInit(pParse, pExpr, target+i); }else{ int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); if( inReg!=target+i ){ @@ -92969,20 +94959,33 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( ** ** Code it as such, taking care to do the common subexpression ** elimination of x. +** +** The xJumpIf parameter determines details: +** +** NULL: Store the boolean result in reg[dest] +** sqlite3ExprIfTrue: Jump to dest if true +** sqlite3ExprIfFalse: Jump to dest if false +** +** The jumpIfNull parameter is ignored if xJumpIf is NULL. */ static void exprCodeBetween( Parse *pParse, /* Parsing and code generating context */ Expr *pExpr, /* The BETWEEN expression */ - int dest, /* Jump here if the jump is taken */ - int jumpIfTrue, /* Take the jump if the BETWEEN is true */ + int dest, /* Jump destination or storage location */ + void (*xJump)(Parse*,Expr*,int,int), /* Action to take */ int jumpIfNull /* Take the jump if the BETWEEN is NULL */ ){ - Expr exprAnd; /* The AND operator in x>=y AND x<=z */ + Expr exprAnd; /* The AND operator in x>=y AND x<=z */ Expr compLeft; /* The x>=y term */ Expr compRight; /* The x<=z term */ Expr exprX; /* The x subexpression */ int regFree1 = 0; /* Temporary use register */ + + memset(&compLeft, 0, sizeof(Expr)); + memset(&compRight, 0, sizeof(Expr)); + memset(&exprAnd, 0, sizeof(Expr)); + assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); exprX = *pExpr->pLeft; exprAnd.op = TK_AND; @@ -92994,23 +94997,30 @@ static void exprCodeBetween( compRight.op = TK_LE; compRight.pLeft = &exprX; compRight.pRight = pExpr->x.pList->a[1].pExpr; - exprToRegister(&exprX, sqlite3ExprCodeTemp(pParse, &exprX, ®Free1)); - if( jumpIfTrue ){ - sqlite3ExprIfTrue(pParse, &exprAnd, dest, jumpIfNull); - }else{ - sqlite3ExprIfFalse(pParse, &exprAnd, dest, jumpIfNull); + exprToRegister(&exprX, exprCodeVector(pParse, &exprX, ®Free1)); + if( xJump ){ + xJump(pParse, &exprAnd, dest, jumpIfNull); + }else{ + /* Mark the expression is being from the ON or USING clause of a join + ** so that the sqlite3ExprCodeTarget() routine will not attempt to move + ** it into the Parse.pConstExpr list. We should use a new bit for this, + ** for clarity, but we are out of bits in the Expr.flags field so we + ** have to reuse the EP_FromJoin bit. Bummer. */ + exprX.flags |= EP_FromJoin; + sqlite3ExprCodeTarget(pParse, &exprAnd, dest); } sqlite3ReleaseTempReg(pParse, regFree1); /* Ensure adequate test coverage */ - testcase( jumpIfTrue==0 && jumpIfNull==0 && regFree1==0 ); - testcase( jumpIfTrue==0 && jumpIfNull==0 && regFree1!=0 ); - testcase( jumpIfTrue==0 && jumpIfNull!=0 && regFree1==0 ); - testcase( jumpIfTrue==0 && jumpIfNull!=0 && regFree1!=0 ); - testcase( jumpIfTrue!=0 && jumpIfNull==0 && regFree1==0 ); - testcase( jumpIfTrue!=0 && jumpIfNull==0 && regFree1!=0 ); - testcase( jumpIfTrue!=0 && jumpIfNull!=0 && regFree1==0 ); - testcase( jumpIfTrue!=0 && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfTrue && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull==0 && regFree1!=0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1==0 ); + testcase( xJump==sqlite3ExprIfFalse && jumpIfNull!=0 && regFree1!=0 ); + testcase( xJump==0 ); } /* @@ -93075,6 +95085,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int case TK_GE: case TK_NE: case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); @@ -93107,7 +95118,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 1, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfTrue, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -93121,6 +95132,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int } #endif default: { + default_expr: if( exprAlwaysTrue(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( exprAlwaysFalse(pExpr) ){ @@ -93227,6 +95239,7 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int case TK_GE: case TK_NE: case TK_EQ: { + if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; testcase( jumpIfNull==0 ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); @@ -93257,7 +95270,7 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int } case TK_BETWEEN: { testcase( jumpIfNull==0 ); - exprCodeBetween(pParse, pExpr, dest, 0, jumpIfNull); + exprCodeBetween(pParse, pExpr, dest, sqlite3ExprIfFalse, jumpIfNull); break; } #ifndef SQLITE_OMIT_SUBQUERY @@ -93273,6 +95286,7 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int } #endif default: { + default_expr: if( exprAlwaysFalse(pExpr) ){ sqlite3VdbeGoto(v, dest); }else if( exprAlwaysTrue(pExpr) ){ @@ -93400,6 +95414,17 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ return 0; } +/* +** Like sqlite3ExprCompare() except COLLATE operators at the top-level +** are ignored. +*/ +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ + return sqlite3ExprCompare( + sqlite3ExprSkipCollate(pA), + sqlite3ExprSkipCollate(pB), + iTab); +} + /* ** Return true if we can prove the pE2 will always be true if pE1 is ** true. Return false if we cannot complete the proof or if pE2 might @@ -93430,11 +95455,10 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Expr *pE1, Expr *pE2, int iTab){ ){ return 1; } - if( pE2->op==TK_NOTNULL - && sqlite3ExprCompare(pE1->pLeft, pE2->pLeft, iTab)==0 - && (pE1->op!=TK_ISNULL && pE1->op!=TK_IS) - ){ - return 1; + if( pE2->op==TK_NOTNULL && pE1->op!=TK_ISNULL && pE1->op!=TK_IS ){ + Expr *pX = sqlite3ExprSkipCollate(pE1->pLeft); + testcase( pX!=pE1->pLeft ); + if( sqlite3ExprCompare(pX, pE2->pLeft, iTab)==0 ) return 1; } return 0; } @@ -93777,7 +95801,7 @@ SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ if( iReg && pParse->nTempRegaTempReg) ){ int i; struct yColCache *p; - for(i=0, p=pParse->aColCache; iaColCache; inColCache; i++, p++){ if( p->iReg==iReg ){ p->tempReg = 1; return; @@ -93788,10 +95812,11 @@ SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ } /* -** Allocate or deallocate a block of nReg consecutive registers +** Allocate or deallocate a block of nReg consecutive registers. */ SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){ int i, n; + if( nReg==1 ) return sqlite3GetTempReg(pParse); i = pParse->iRangeReg; n = pParse->nRangeReg; if( nReg<=n ){ @@ -93805,6 +95830,10 @@ SQLITE_PRIVATE int sqlite3GetTempRange(Parse *pParse, int nReg){ return i; } SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){ + if( nReg==1 ){ + sqlite3ReleaseTempReg(pParse, iReg); + return; + } sqlite3ExprCacheRemove(pParse, iReg, nReg); if( nReg>pParse->nRangeReg ){ pParse->nRangeReg = nReg; @@ -94260,7 +96289,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_rename_table; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; db->flags |= SQLITE_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ @@ -94351,7 +96380,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( sqlite3NestedParse(pParse, "UPDATE \"%w\".%s SET " "sql = sqlite_rename_parent(sql, %Q, %Q) " - "WHERE %s;", zDb, SCHEMA_TABLE(iDb), zTabName, zName, zWhere); + "WHERE %s;", zDb, MASTER_NAME, zTabName, zName, zWhere); sqlite3DbFree(db, zWhere); } } @@ -94375,7 +96404,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( "ELSE name END " "WHERE tbl_name=%Q COLLATE nocase AND " "(type='table' OR type='index' OR type='trigger');", - zDb, SCHEMA_TABLE(iDb), zName, zName, zName, + zDb, MASTER_NAME, zName, zName, zName, #ifndef SQLITE_OMIT_TRIGGER zName, #endif @@ -94458,7 +96487,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ assert( sqlite3BtreeHoldsAllMutexes(db) ); iDb = sqlite3SchemaToIndex(db, pNew->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; pDflt = pCol->pDflt; @@ -94536,7 +96565,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ "UPDATE \"%w\".%s SET " "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) " "WHERE type = 'table' AND name = %Q", - zDb, SCHEMA_TABLE(iDb), pNew->addColOffset, zCol, pNew->addColOffset+1, + zDb, MASTER_NAME, pNew->addColOffset, zCol, pNew->addColOffset+1, zTab ); sqlite3DbFree(db, zCol); @@ -94620,7 +96649,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ pNew = (Table*)sqlite3DbMallocZero(db, sizeof(Table)); if( !pNew ) goto exit_begin_add_column; pParse->pNewTable = pNew; - pNew->nRef = 1; + pNew->nTabRef = 1; pNew->nCol = pTab->nCol; assert( pNew->nCol>0 ); nAlloc = (((pNew->nCol-1)/8)*8)+8; @@ -94640,7 +96669,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ } pNew->pSchema = db->aDb[iDb].pSchema; pNew->addColOffset = pTab->addColOffset; - pNew->nRef = 1; + pNew->nTabRef = 1; /* Begin a transaction and increment the schema cookie. */ sqlite3BeginWriteOperation(pParse, 0, iDb); @@ -94868,14 +96897,14 @@ static void openStatTable( for(i=0; izName))==0 ){ + if( (pStat = sqlite3FindTable(db, zTab, pDb->zDbSName))==0 ){ if( aTable[i].zCols ){ /* The sqlite_statN table does not exist. Create it. Note that a ** side-effect of the CREATE TABLE statement is to leave the rootpage ** of the new table in register pParse->regRoot. This is important ** because the OpenWrite opcode below will be needing it. */ sqlite3NestedParse(pParse, - "CREATE TABLE %Q.%s(%s)", pDb->zName, zTab, aTable[i].zCols + "CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols ); aRoot[i] = pParse->regRoot; aCreateTbl[i] = OPFLAG_P2ISREG; @@ -94890,7 +96919,7 @@ static void openStatTable( if( zWhere ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE %s=%Q", - pDb->zName, zTab, zWhereType, zWhere + pDb->zDbSName, zTab, zWhereType, zWhere ); }else{ /* The sqlite_stat[134] table already exists. Delete all rows. */ @@ -94948,6 +96977,7 @@ struct Stat4Accum { Stat4Sample *aBest; /* Array of nCol best samples */ int iMin; /* Index in a[] of entry with minimum score */ int nSample; /* Current number of samples */ + int nMaxEqZero; /* Max leading 0 in anEq[] for any a[] entry */ int iGet; /* Index of current sample accessed by stat_get() */ Stat4Sample *a; /* Array of mxSample Stat4Sample objects */ sqlite3 *db; /* Database connection, for malloc() */ @@ -95212,6 +97242,13 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ assert( IsStat4 || nEqZero==0 ); #ifdef SQLITE_ENABLE_STAT4 + /* Stat4Accum.nMaxEqZero is set to the maximum number of leading 0 + ** values in the anEq[] array of any sample in Stat4Accum.a[]. In + ** other words, if nMaxEqZero is n, then it is guaranteed that there + ** are no samples with Stat4Sample.anEq[m]==0 for (m>=n). */ + if( nEqZero>p->nMaxEqZero ){ + p->nMaxEqZero = nEqZero; + } if( pNew->isPSample==0 ){ Stat4Sample *pUpgrade = 0; assert( pNew->anEq[pNew->iCol]>0 ); @@ -95309,12 +97346,22 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){ } } - /* Update the anEq[] fields of any samples already collected. */ + /* Check that no sample contains an anEq[] entry with an index of + ** p->nMaxEqZero or greater set to zero. */ for(i=p->nSample-1; i>=0; i--){ int j; - for(j=iChng; jnCol; j++){ - if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j]; + for(j=p->nMaxEqZero; jnCol; j++) assert( p->a[i].anEq[j]>0 ); + } + + /* Update the anEq[] fields of any samples already collected. */ + if( iChngnMaxEqZero ){ + for(i=p->nSample-1; i>=0; i--){ + int j; + for(j=iChng; jnCol; j++){ + if( p->a[i].anEq[j]==0 ) p->a[i].anEq[j] = p->current.anEq[j]; + } } + p->nMaxEqZero = iChng; } #endif @@ -95455,6 +97502,12 @@ static const FuncDef statPushFuncdef = { ** The content to returned is determined by the parameter J ** which is one of the STAT_GET_xxxx values defined above. ** +** The stat_get(P,J) function is not available to generic SQL. It is +** inserted as part of a manually constructed bytecode program. (See +** the callStatGet() routine below.) It is guaranteed that the P +** parameter will always be a poiner to a Stat4Accum object, never a +** NULL. +** ** If neither STAT3 nor STAT4 are enabled, then J is always ** STAT_GET_STAT1 and is hence omitted and this routine becomes ** a one-parameter function, stat_get(P), that always returns the @@ -95652,7 +97705,7 @@ static void analyzeOneTable( assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_ANALYZE, pTab->zName, 0, - db->aDb[iDb].zName ) ){ + db->aDb[iDb].zDbSName ) ){ return; } #endif @@ -95838,7 +97891,7 @@ static void analyzeOneTable( regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); for(j=0; jnKeyCol; j++){ k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); - assert( k>=0 && knCol ); + assert( k>=0 && knColumn ); sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName)); } @@ -96022,27 +98075,14 @@ SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ if( i==1 ) continue; /* Do not analyze the TEMP database */ analyzeDatabase(pParse, i); } - }else if( pName2->n==0 ){ - /* Form 2: Analyze the database or table named */ - iDb = sqlite3FindDb(db, pName1); - if( iDb>=0 ){ - analyzeDatabase(pParse, iDb); - }else{ - z = sqlite3NameFromToken(db, pName1); - if( z ){ - if( (pIdx = sqlite3FindIndex(db, z, 0))!=0 ){ - analyzeTable(pParse, pIdx->pTable, pIdx); - }else if( (pTab = sqlite3LocateTable(pParse, 0, z, 0))!=0 ){ - analyzeTable(pParse, pTab, 0); - } - sqlite3DbFree(db, z); - } - } + }else if( pName2->n==0 && (iDb = sqlite3FindDb(db, pName1))>=0 ){ + /* Analyze the schema named as the argument */ + analyzeDatabase(pParse, iDb); }else{ - /* Form 3: Analyze the fully qualified table name */ + /* Form 3: Analyze the table or index named as an argument */ iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pTableName); if( iDb>=0 ){ - zDb = db->aDb[iDb].zName; + zDb = pName2->n ? db->aDb[iDb].zDbSName : 0; z = sqlite3NameFromToken(db, pTableName); if( z ){ if( (pIdx = sqlite3FindIndex(db, z, zDb))!=0 ){ @@ -96052,10 +98092,11 @@ SQLITE_PRIVATE void sqlite3Analyze(Parse *pParse, Token *pName1, Token *pName2){ } sqlite3DbFree(db, z); } - } + } + } + if( db->nSqlExec==0 && (v = sqlite3GetVdbe(pParse))!=0 ){ + sqlite3VdbeAddOp0(v, OP_Expire); } - v = sqlite3GetVdbe(pParse); - if( v ) sqlite3VdbeAddOp0(v, OP_Expire); } /* @@ -96184,7 +98225,11 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ #endif pIndex->bUnordered = 0; decodeIntArray((char*)z, nCol, aiRowEst, pIndex->aiRowLogEst, pIndex); - if( pIndex->pPartIdxWhere==0 ) pTable->nRowLogEst = pIndex->aiRowLogEst[0]; + pIndex->hasStat1 = 1; + if( pIndex->pPartIdxWhere==0 ){ + pTable->nRowLogEst = pIndex->aiRowLogEst[0]; + pTable->tabFlags |= TF_HasStat1; + } }else{ Index fakeIdx; fakeIdx.szIdxRow = pTable->szTabRow; @@ -96193,6 +98238,7 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ #endif decodeIntArray((char*)z, 1, 0, &pTable->nRowLogEst, &fakeIdx); pTable->szTabRow = fakeIdx.szIdxRow; + pTable->tabFlags |= TF_HasStat1; } return 0; @@ -96273,7 +98319,7 @@ static void initAvgEq(Index *pIdx){ } } - if( nDist100>nSum100 ){ + if( nDist100>nSum100 && sumEqp, sqlite3_column_blob(pStmt, 4), pSample->n); + if( pSample->n ){ + memcpy(pSample->p, sqlite3_column_blob(pStmt, 4), pSample->n); + } pIdx->nSample++; } rc = sqlite3_finalize(pStmt); @@ -96485,15 +98533,20 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ HashElem *i; char *zSql; int rc = SQLITE_OK; + Schema *pSchema = db->aDb[iDb].pSchema; assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pBt!=0 ); /* Clear any prior statistics */ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ + for(i=sqliteHashFirst(&pSchema->tblHash); i; i=sqliteHashNext(i)){ + Table *pTab = sqliteHashData(i); + pTab->tabFlags &= ~TF_HasStat1; + } + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); - pIdx->aiRowLogEst[0] = 0; + pIdx->hasStat1 = 0; #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 sqlite3DeleteIndexSamples(db, pIdx); pIdx->aSample = 0; @@ -96502,7 +98555,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; - sInfo.zDatabase = db->aDb[iDb].zName; + sInfo.zDatabase = db->aDb[iDb].zDbSName; if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ zSql = sqlite3MPrintf(db, "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); @@ -96516,9 +98569,9 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Set appropriate defaults on all indexes not in the sqlite_stat1 table */ assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); - if( pIdx->aiRowLogEst[0]==0 ) sqlite3DefaultRowEst(pIdx); + if( !pIdx->hasStat1 ) sqlite3DefaultRowEst(pIdx); } /* Load the statistics from the sqlite_stat4 table. */ @@ -96528,7 +98581,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ rc = loadStat4(db, sInfo.zDatabase); db->lookaside.bDisable--; } - for(i=sqliteHashFirst(&db->aDb[iDb].pSchema->idxHash);i;i=sqliteHashNext(i)){ + for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); sqlite3_free(pIdx->aiRowEst); pIdx->aiRowEst = 0; @@ -96645,7 +98698,7 @@ static void attachFunc( goto attach_error; } for(i=0; inDb; i++){ - char *z = db->aDb[i].zName; + char *z = db->aDb[i].zDbSName; assert( z && zName ); if( sqlite3StrICmp(z, zName)==0 ){ zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName); @@ -96685,6 +98738,7 @@ static void attachFunc( rc = sqlite3BtreeOpen(pVfs, zPath, db, &aNew->pBt, 0, flags); sqlite3_free( zPath ); db->nDb++; + db->skipBtreeMutex = 0; if( rc==SQLITE_CONSTRAINT ){ rc = SQLITE_ERROR; zErrDyn = sqlite3MPrintf(db, "database is already attached"); @@ -96710,8 +98764,8 @@ static void attachFunc( sqlite3BtreeLeave(aNew->pBt); } aNew->safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; - aNew->zName = sqlite3DbStrDup(db, zName); - if( rc==SQLITE_OK && aNew->zName==0 ){ + aNew->zDbSName = sqlite3DbStrDup(db, zName); + if( rc==SQLITE_OK && aNew->zDbSName==0 ){ rc = SQLITE_NOMEM_BKPT; } @@ -96740,7 +98794,7 @@ static void attachFunc( case SQLITE_NULL: /* No key specified. Use the key from the main database */ sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); - if( nKey>0 || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ + if( nKey || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); } break; @@ -96823,7 +98877,7 @@ static void detachFunc( for(i=0; inDb; i++){ pDb = &db->aDb[i]; if( pDb->pBt==0 ) continue; - if( sqlite3StrICmp(pDb->zName, zName)==0 ) break; + if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break; } if( i>=db->nDb ){ @@ -96873,6 +98927,7 @@ static void codeAttach( sqlite3* db = pParse->db; int regArgs; + if( pParse->nErr ) goto attach_end; memset(&sName, 0, sizeof(NameContext)); sName.pParse = pParse; @@ -96981,7 +99036,7 @@ SQLITE_PRIVATE void sqlite3FixInit( db = pParse->db; assert( db->nDb>iDb ); pFix->pParse = pParse; - pFix->zDb = db->aDb[iDb].zName; + pFix->zDb = db->aDb[iDb].zDbSName; pFix->pSchema = db->aDb[iDb].pSchema; pFix->zType = zType; pFix->pName = pName; @@ -97078,7 +99133,7 @@ SQLITE_PRIVATE int sqlite3FixExpr( return 1; } } - if( ExprHasProperty(pExpr, EP_TokenOnly) ) break; + if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1; }else{ @@ -97239,9 +99294,9 @@ SQLITE_PRIVATE int sqlite3AuthReadCol( const char *zCol, /* Column name */ int iDb /* Index of containing database. */ ){ - sqlite3 *db = pParse->db; /* Database handle */ - char *zDb = db->aDb[iDb].zName; /* Name of attached database */ - int rc; /* Auth callback return code */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */ + int rc; /* Auth callback return code */ if( db->init.busy ) return SQLITE_OK; rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext @@ -97426,10 +99481,10 @@ SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){ ** codeTableLocks() functions. */ struct TableLock { - int iDb; /* The database containing the table to be locked */ - int iTab; /* The root page of the table to be locked */ - u8 isWriteLock; /* True for write lock. False for a read lock */ - const char *zName; /* Name of the table */ + int iDb; /* The database containing the table to be locked */ + int iTab; /* The root page of the table to be locked */ + u8 isWriteLock; /* True for write lock. False for a read lock */ + const char *zLockName; /* Name of the table */ }; /* @@ -97455,6 +99510,8 @@ SQLITE_PRIVATE void sqlite3TableLock( TableLock *p; assert( iDb>=0 ); + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; for(i=0; inTableLock; i++){ p = &pToplevel->aTableLock[i]; if( p->iDb==iDb && p->iTab==iTab ){ @@ -97471,7 +99528,7 @@ SQLITE_PRIVATE void sqlite3TableLock( p->iDb = iDb; p->iTab = iTab; p->isWriteLock = isWriteLock; - p->zName = zName; + p->zLockName = zName; }else{ pToplevel->nTableLock = 0; sqlite3OomFault(pToplevel->db); @@ -97493,7 +99550,7 @@ static void codeTableLocks(Parse *pParse){ TableLock *p = &pParse->aTableLock[i]; int p1 = p->iDb; sqlite3VdbeAddOp4(pVdbe, OP_TableLock, p1, p->iTab, p->isWriteLock, - p->zName, P4_STATIC); + p->zLockName, P4_STATIC); } } #else @@ -97542,15 +99599,14 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ - while( sqlite3VdbeDeletePriorOpcode(v, OP_Close) ){} sqlite3VdbeAddOp0(v, OP_Halt); #if SQLITE_USER_AUTHENTICATION if( pParse->nTableLock>0 && db->init.busy==0 ){ sqlite3UserAuthInit(db); if( db->auth.authLevelrc = SQLITE_AUTH_USER; sqlite3ErrorMsg(pParse, "user not authenticated"); + pParse->rc = SQLITE_AUTH_USER; return; } } @@ -97569,14 +99625,16 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); sqlite3VdbeJumpHere(v, 0); for(iDb=0; iDbnDb; iDb++){ + Schema *pSchema; if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; sqlite3VdbeUsesBtree(v, iDb); + pSchema = db->aDb[iDb].pSchema; sqlite3VdbeAddOp4Int(v, OP_Transaction, /* Opcode */ iDb, /* P1 */ DbMaskTest(pParse->writeMask,iDb), /* P2 */ - pParse->cookieValue[iDb], /* P3 */ - db->aDb[iDb].pSchema->iGeneration /* P4 */ + pSchema->schema_cookie, /* P3 */ + pSchema->iGeneration /* P4 */ ); if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); VdbeComment((v, @@ -97627,16 +99685,6 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ }else{ pParse->rc = SQLITE_ERROR; } - - /* We are done with this Parse object. There is no need to de-initialize it */ -#if 0 - pParse->colNamesSet = 0; - pParse->nTab = 0; - pParse->nMem = 0; - pParse->nSet = 0; - pParse->nVar = 0; - DbMaskZero(pParse->cookieMask); -#endif } /* @@ -97656,8 +99704,7 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ char *zSql; char *zErrMsg = 0; sqlite3 *db = pParse->db; -# define SAVE_SZ (sizeof(Parse) - offsetof(Parse,nVar)) - char saveBuf[SAVE_SZ]; + char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ @@ -97668,12 +99715,12 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ return; /* A malloc must have failed */ } pParse->nested++; - memcpy(saveBuf, &pParse->nVar, SAVE_SZ); - memset(&pParse->nVar, 0, SAVE_SZ); + memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); sqlite3RunParser(pParse, zSql, &zErrMsg); sqlite3DbFree(db, zErrMsg); sqlite3DbFree(db, zSql); - memcpy(&pParse->nVar, saveBuf, SAVE_SZ); + memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; } @@ -97712,14 +99759,22 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const cha return 0; } #endif - for(i=OMIT_TEMPDB; inDb; i++){ - int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDatabase!=0 && sqlite3StrICmp(zDatabase, db->aDb[j].zName) ) continue; - assert( sqlite3SchemaMutexHeld(db, j, 0) ); - p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); - if( p ) break; + while(1){ + for(i=OMIT_TEMPDB; inDb; i++){ + int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ + if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){ + assert( sqlite3SchemaMutexHeld(db, j, 0) ); + p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); + if( p ) return p; + } + } + /* Not found. If the name we were looking for was temp.sqlite_master + ** then change the name to sqlite_temp_master and try again. */ + if( sqlite3StrICmp(zName, MASTER_NAME)!=0 ) break; + if( sqlite3_stricmp(zDatabase, db->aDb[1].zDbSName)!=0 ) break; + zName = TEMP_MASTER_NAME; } - return p; + return 0; } /* @@ -97755,6 +99810,9 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ Module *pMod = (Module*)sqlite3HashFind(&pParse->db->aModule, zName); + if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ + pMod = sqlite3PragmaVtabRegister(pParse->db, zName); + } if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ return pMod->pEpoTab; } @@ -97791,7 +99849,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTableItem( assert( p->pSchema==0 || p->zDatabase==0 ); if( p->pSchema ){ int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema); - zDb = pParse->db->aDb[iDb].zName; + zDb = pParse->db->aDb[iDb].zDbSName; }else{ zDb = p->zDatabase; } @@ -97819,7 +99877,7 @@ SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const cha int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ Schema *pSchema = db->aDb[j].pSchema; assert( pSchema ); - if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zName) ) continue; + if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); p = sqlite3HashFind(&pSchema->idxHash, zName); if( p ) break; @@ -97888,8 +99946,8 @@ SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3 *db){ for(i=j=2; inDb; i++){ struct Db *pDb = &db->aDb[i]; if( pDb->pBt==0 ){ - sqlite3DbFree(db, pDb->zName); - pDb->zName = 0; + sqlite3DbFree(db, pDb->zDbSName); + pDb->zDbSName = 0; continue; } if( jpnBytesFreed==0) && (--pTable->nRef)>0) ) return; + if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return; deleteTable(db, pTable); } @@ -98091,7 +100149,7 @@ SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ */ SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); - sqlite3TableLock(p, iDb, MASTER_ROOT, 1, SCHEMA_TABLE(iDb)); + sqlite3TableLock(p, iDb, MASTER_ROOT, 1, MASTER_NAME); sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; @@ -98109,7 +100167,10 @@ SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){ if( zName ){ Db *pDb; for(i=(db->nDb-1), pDb=&db->aDb[i]; i>=0; i--, pDb--){ - if( 0==sqlite3StrICmp(pDb->zName, zName) ) break; + if( 0==sqlite3_stricmp(pDb->zDbSName, zName) ) break; + /* "main" is always an acceptable alias for the primary database + ** even if it has been renamed using SQLITE_DBCONFIG_MAINDBNAME. */ + if( i==0 && 0==sqlite3_stricmp("main", zName) ) break; } } return i; @@ -98168,7 +100229,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName( return -1; } }else{ - assert( db->init.iDb==0 || db->init.busy ); + assert( db->init.iDb==0 || db->init.busy || (db->flags & SQLITE_Vacuum)!=0); iDb = db->init.iDb; *pUnqual = pName1; } @@ -98279,7 +100340,7 @@ SQLITE_PRIVATE void sqlite3StartTable( SQLITE_CREATE_VIEW, SQLITE_CREATE_TEMP_VIEW }; - char *zDb = db->aDb[iDb].zName; + char *zDb = db->aDb[iDb].zDbSName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(isTemp), 0, zDb) ){ goto begin_table_error; } @@ -98298,7 +100359,7 @@ SQLITE_PRIVATE void sqlite3StartTable( ** collisions. */ if( !IN_DECLARE_VTAB ){ - char *zDb = db->aDb[iDb].zName; + char *zDb = db->aDb[iDb].zDbSName; if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto begin_table_error; } @@ -98328,7 +100389,7 @@ SQLITE_PRIVATE void sqlite3StartTable( pTable->zName = zName; pTable->iPKey = -1; pTable->pSchema = db->aDb[iDb].pSchema; - pTable->nRef = 1; + pTable->nTabRef = 1; pTable->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; @@ -98505,6 +100566,7 @@ SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){ p = pParse->pNewTable; if( p==0 || NEVER(p->nCol<1) ) return; p->aCol[p->nCol-1].notNull = (u8)onError; + p->tabFlags |= TF_HasNotNull; } /* @@ -98853,6 +100915,9 @@ SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ ** set back to prior value. But schema changes are infrequent ** and the probability of hitting the same cookie value is only ** 1 chance in 2^32. So we're safe enough. +** +** IMPLEMENTATION-OF: R-34230-56049 SQLite automatically increments +** the schema-version whenever the schema changes. */ SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ sqlite3 *db = pParse->db; @@ -99391,7 +101456,7 @@ SQLITE_PRIVATE void sqlite3EndTable( "UPDATE %Q.%s " "SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q " "WHERE rowid=#%d", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, zType, p->zName, p->zName, @@ -99406,13 +101471,13 @@ SQLITE_PRIVATE void sqlite3EndTable( /* Check to see if we need to create an sqlite_sequence table for ** keeping track of autoincrement keys. */ - if( p->tabFlags & TF_Autoincrement ){ + if( (p->tabFlags & TF_Autoincrement)!=0 ){ Db *pDb = &db->aDb[iDb]; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->pSeqTab==0 ){ sqlite3NestedParse(pParse, "CREATE TABLE %Q.sqlite_sequence(name,seq)", - pDb->zName + pDb->zDbSName ); } } @@ -99536,7 +101601,9 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ int nErr = 0; /* Number of errors encountered */ int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ +#ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth; /* Saved xAuth pointer */ +#endif assert( pTable ); @@ -99726,7 +101793,7 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ */ sqlite3NestedParse(pParse, "UPDATE %Q.%s SET rootpage=%d WHERE #%d AND rootpage=#%d", - pParse->db->aDb[iDb].zName, SCHEMA_TABLE(iDb), iTable, r1, r1); + pParse->db->aDb[iDb].zDbSName, MASTER_NAME, iTable, r1, r1); #endif sqlite3ReleaseTempReg(pParse, r1); } @@ -99802,7 +101869,7 @@ static void sqlite3ClearStatTables( const char *zName /* Name of index or table */ ){ int i; - const char *zDbName = pParse->db->aDb[iDb].zName; + const char *zDbName = pParse->db->aDb[iDb].zDbSName; for(i=1; i<=4; i++){ char zTab[24]; sqlite3_snprintf(sizeof(zTab),zTab,"sqlite_stat%d",i); @@ -99855,7 +101922,7 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in if( pTab->tabFlags & TF_Autoincrement ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.sqlite_sequence WHERE name=%Q", - pDb->zName, pTab->zName + pDb->zDbSName, pTab->zName ); } #endif @@ -99869,7 +101936,7 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in */ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", - pDb->zName, SCHEMA_TABLE(iDb), pTab->zName); + pDb->zDbSName, MASTER_NAME, pTab->zName); if( !isView && !IsVirtual(pTab) ){ destroyTable(pParse, pTab); } @@ -99923,7 +101990,7 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, { int code; const char *zTab = SCHEMA_TABLE(iDb); - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zArg2 = 0; if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb)){ goto exit_drop_table; @@ -100164,7 +102231,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ #ifndef SQLITE_OMIT_AUTHORIZATION if( sqlite3AuthCheck(pParse, SQLITE_REINDEX, pIndex->zName, 0, - db->aDb[iDb].zName ) ){ + db->aDb[iDb].zDbSName ) ){ return; } #endif @@ -100216,7 +102283,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ } sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx); sqlite3VdbeAddOp3(v, OP_Last, iIdx, 0, -1); - sqlite3VdbeAddOp3(v, OP_IdxInsert, iIdx, regRecord, 0); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3VdbeAddOp2(v, OP_SorterNext, iSorter, addr2); VdbeCoverage(v); @@ -100416,7 +102483,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( goto exit_create_index; } } - if( sqlite3FindIndex(db, zName, pDb->zName)!=0 ){ + if( sqlite3FindIndex(db, zName, pDb->zDbSName)!=0 ){ if( !ifNotExist ){ sqlite3ErrorMsg(pParse, "index %s already exists", zName); }else{ @@ -100446,7 +102513,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( */ #ifndef SQLITE_OMIT_AUTHORIZATION { - const char *zDb = pDb->zName; + const char *zDb = pDb->zDbSName; if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iDb), 0, zDb) ){ goto exit_create_index; } @@ -100761,7 +102828,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( */ sqlite3NestedParse(pParse, "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName, pTab->zName, iMem, @@ -100839,6 +102906,9 @@ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ int nCopy = MIN(ArraySize(aVal), pIdx->nKeyCol); int i; + /* Indexes with default row estimates should not have stat1 data */ + assert( !pIdx->hasStat1 ); + /* Set the first entry (number of rows in the index) to the estimated ** number of rows in the table, or half the number of rows in the table ** for a partial index. But do not let the estimate drop below 10. */ @@ -100895,7 +102965,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists { int code = SQLITE_DROP_INDEX; Table *pTab = pIndex->pTable; - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zTab = SCHEMA_TABLE(iDb); if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ goto exit_drop_index; @@ -100913,7 +102983,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE name=%Q AND type='index'", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pIndex->zName + db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName ); sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); sqlite3ChangeCookie(pParse, iDb); @@ -101056,7 +103126,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge( /* Allocate additional space if needed */ if( (u32)pSrc->nSrc+nExtra>pSrc->nAlloc ){ SrcList *pNew; - int nAlloc = pSrc->nSrc+nExtra; + int nAlloc = pSrc->nSrc*2+nExtra; int nGot; pNew = sqlite3DbRealloc(db, pSrc, sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) ); @@ -101134,9 +103204,12 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( pList = sqlite3DbMallocRawNN(db, sizeof(SrcList) ); if( pList==0 ) return 0; pList->nAlloc = 1; - pList->nSrc = 0; + pList->nSrc = 1; + memset(&pList->a[0], 0, sizeof(pList->a[0])); + pList->a[0].iCursor = -1; + }else{ + pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc); } - pList = sqlite3SrcListEnlarge(db, pList, 1, pList->nSrc); if( db->mallocFailed ){ sqlite3SrcListDelete(db, pList); return 0; @@ -101434,15 +103507,13 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ */ SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - sqlite3 *db = pToplevel->db; - assert( iDb>=0 && iDbnDb ); - assert( db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDb>=0 && iDbdb->nDb ); + assert( pParse->db->aDb[iDb].pBt!=0 || iDb==1 ); assert( iDbdb, iDb, 0) ); if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ DbMaskSet(pToplevel->cookieMask, iDb); - pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie; if( !OMIT_TEMPDB && iDb==1 ){ sqlite3OpenTempDatabase(pToplevel); } @@ -101458,7 +103529,7 @@ SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb) int i; for(i=0; inDb; i++){ Db *pDb = &db->aDb[i]; - if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zName)) ){ + if( pDb->pBt && (!zDb || 0==sqlite3StrICmp(zDb, pDb->zDbSName)) ){ sqlite3CodeVerifySchema(pParse, i); } } @@ -101705,7 +103776,7 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ if( iDb<0 ) return; z = sqlite3NameFromToken(db, pObjName); if( z==0 ) return; - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; pTab = sqlite3FindTable(db, z, zDb); if( pTab ){ reindexTable(pParse, pTab, 0); @@ -102353,7 +104424,7 @@ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ sqlite3DeleteTable(pParse->db, pItem->pTab); pItem->pTab = pTab; if( pTab ){ - pTab->nRef++; + pTab->nTabRef++; } if( sqlite3IndexedByLookup(pParse, pItem) ){ pTab = 0; @@ -102419,7 +104490,7 @@ SQLITE_PRIVATE void sqlite3MaterializeView( if( pFrom ){ assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); - pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); + pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); assert( pFrom->a[0].pOn==0 ); assert( pFrom->a[0].pUsing==0 ); } @@ -102481,7 +104552,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( ** ); */ - pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); + pSelectRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); if( pSelectRowid == 0 ) goto limit_where_cleanup; pEList = sqlite3ExprListAppend(pParse, 0, pSelectRowid); if( pEList == 0 ) goto limit_where_cleanup; @@ -102500,8 +104571,8 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( if( pSelect == 0 ) return 0; /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ - pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0, 0); - pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0, 0) : 0; + pWhereRowid = sqlite3PExpr(pParse, TK_ROW, 0, 0); + pInClause = pWhereRowid ? sqlite3PExpr(pParse, TK_IN, pWhereRowid, 0) : 0; sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; @@ -102529,7 +104600,6 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ){ Vdbe *v; /* The virtual database engine */ Table *pTab; /* The table from which records will be deleted */ - const char *zDb; /* Name of database holding pTab */ int i; /* Loop counter */ WhereInfo *pWInfo; /* Information about the WHERE clause */ Index *pIdx; /* For looping over indices of the table */ @@ -102606,8 +104676,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - zDb = db->aDb[iDb].zName; - rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, zDb); + rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, + db->aDb[iDb].zDbSName); assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); if( rcauth==SQLITE_DENY ){ goto delete_from_cleanup; @@ -102767,7 +104837,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( nKey = 0; /* Zero tells OP_Found to use a composite key */ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, iKey, sqlite3IndexAffinityStr(pParse->db, pPk), nPk); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iEphCur, iKey); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEphCur, iKey, iPk, nPk); }else{ /* Add the rowid of the row to be deleted to the RowSet */ nKey = 1; /* OP_Seek always uses a single rowid */ @@ -102791,7 +104861,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( !isView ){ int iAddrOnce = 0; if( eOnePass==ONEPASS_MULTI ){ - iAddrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); + iAddrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } testcase( IsVirtual(pTab) ); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, OPFLAG_FORDELETE, @@ -102813,7 +104883,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( } }else if( pPk ){ addrLoop = sqlite3VdbeAddOp1(v, OP_Rewind, iEphCur); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_RowKey, iEphCur, iKey); + sqlite3VdbeAddOp2(v, OP_RowData, iEphCur, iKey); assert( nKey==0 ); /* OP_Found will use a composite key */ }else{ addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); @@ -102837,12 +104907,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( #endif { int count = (pParse->nested==0); /* True to count changes */ - int iIdxNoSeek = -1; - if( bComplex==0 && aiCurOnePass[1]!=iDataCur ){ - iIdxNoSeek = aiCurOnePass[1]; - } sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, - iKey, nKey, count, OE_Default, eOnePass, iIdxNoSeek); + iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]); } /* End of the loop over all rowids/primary-keys. */ @@ -102856,14 +104922,6 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( sqlite3VdbeGoto(v, addrLoop); sqlite3VdbeJumpHere(v, addrLoop); } - - /* Close the cursors open on the table and its indexes. */ - if( !isView && !IsVirtual(pTab) ){ - if( !pPk ) sqlite3VdbeAddOp1(v, OP_Close, iDataCur); - for(i=0, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){ - sqlite3VdbeAddOp1(v, OP_Close, iIdxCur + i); - } - } } /* End non-truncate path */ /* Update the sqlite_sequence table by storing the content of the @@ -102930,15 +104988,17 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ** ** If eMode is ONEPASS_MULTI, then this call is being made as part ** of a ONEPASS delete that affects multiple rows. In this case, if -** iIdxNoSeek is a valid cursor number (>=0), then its position should -** be preserved following the delete operation. Or, if iIdxNoSeek is not -** a valid cursor number, the position of iDataCur should be preserved -** instead. +** iIdxNoSeek is a valid cursor number (>=0) and is not the same as +** iDataCur, then its position should be preserved following the delete +** operation. Or, if iIdxNoSeek is not a valid cursor number, the +** position of iDataCur should be preserved instead. ** ** iIdxNoSeek: -** If iIdxNoSeek is a valid cursor number (>=0), then it identifies an -** index cursor (from within array of cursors starting at iIdxCur) that -** already points to the index entry to be deleted. +** If iIdxNoSeek is a valid cursor number (>=0) not equal to iDataCur, +** then it identifies an index cursor (from within array of cursors +** starting at iIdxCur) that already points to the index entry to be deleted. +** Except, this optimization is disabled if there are BEFORE triggers since +** the trigger body might have moved the cursor. */ SQLITE_PRIVATE void sqlite3GenerateRowDelete( Parse *pParse, /* Parsing context */ @@ -103009,13 +105069,18 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( /* If any BEFORE triggers were coded, then seek the cursor to the ** row to be deleted again. It may be that the BEFORE triggers moved - ** the cursor or of already deleted the row that the cursor was + ** the cursor or already deleted the row that the cursor was ** pointing to. + ** + ** Also disable the iIdxNoSeek optimization since the BEFORE trigger + ** may have moved that cursor. */ if( addrStart=0 ); + iIdxNoSeek = -1; } /* Do FK processing. This call checks that any FK constraints that @@ -103038,11 +105103,13 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); - sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + if( pParse->nested==0 ){ + sqlite3VdbeAppendP4(v, (char*)pTab, P4_TABLE); + } if( eMode!=ONEPASS_OFF ){ sqlite3VdbeChangeP5(v, OPFLAG_AUXDELETE); } - if( iIdxNoSeek>=0 ){ + if( iIdxNoSeek>=0 && iIdxNoSeek!=iDataCur ){ sqlite3VdbeAddOp1(v, OP_Delete, iIdxNoSeek); } if( eMode==ONEPASS_MULTI ) p5 |= OPFLAG_SAVEPOSITION; @@ -103196,6 +105263,10 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey( } if( regOut ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); + if( pIdx->pTable->pSelect ){ + const char *zAff = sqlite3IndexAffinityStr(pParse->db, pIdx); + sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT); + } } sqlite3ReleaseTempRange(pParse, regBase, nCol); return regBase; @@ -103417,23 +105488,26 @@ static void instrFunc( if( typeHaystack==SQLITE_NULL || typeNeedle==SQLITE_NULL ) return; nHaystack = sqlite3_value_bytes(argv[0]); nNeedle = sqlite3_value_bytes(argv[1]); - if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){ - zHaystack = sqlite3_value_blob(argv[0]); - zNeedle = sqlite3_value_blob(argv[1]); - isText = 0; - }else{ - zHaystack = sqlite3_value_text(argv[0]); - zNeedle = sqlite3_value_text(argv[1]); - isText = 1; - } - while( nNeedle<=nHaystack && memcmp(zHaystack, zNeedle, nNeedle)!=0 ){ - N++; - do{ - nHaystack--; - zHaystack++; - }while( isText && (zHaystack[0]&0xc0)==0x80 ); + if( nNeedle>0 ){ + if( typeHaystack==SQLITE_BLOB && typeNeedle==SQLITE_BLOB ){ + zHaystack = sqlite3_value_blob(argv[0]); + zNeedle = sqlite3_value_blob(argv[1]); + isText = 0; + }else{ + zHaystack = sqlite3_value_text(argv[0]); + zNeedle = sqlite3_value_text(argv[1]); + isText = 1; + } + if( zNeedle==0 || (nHaystack && zHaystack==0) ) return; + while( nNeedle<=nHaystack && memcmp(zHaystack, zNeedle, nNeedle)!=0 ){ + N++; + do{ + nHaystack--; + zHaystack++; + }while( isText && (zHaystack[0]&0xc0)==0x80 ); + } + if( nNeedle>nHaystack ) N = 0; } - if( nNeedle>nHaystack ) N = 0; sqlite3_result_int(context, N); } @@ -103813,9 +105887,19 @@ static const struct compareInfo likeInfoNorm = { '%', '_', 0, 1 }; static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; /* -** Compare two UTF-8 strings for equality where the first string can -** potentially be a "glob" or "like" expression. Return true (1) if they -** are the same and false (0) if they are different. +** Possible error returns from patternMatch() +*/ +#define SQLITE_MATCH 0 +#define SQLITE_NOMATCH 1 +#define SQLITE_NOWILDCARDMATCH 2 + +/* +** Compare two UTF-8 strings for equality where the first string is +** a GLOB or LIKE expression. Return values: +** +** SQLITE_MATCH: Match +** SQLITE_NOMATCH: No match +** SQLITE_NOWILDCARDMATCH: No match in spite of having * or % wildcards. ** ** Globbing rules: ** @@ -103866,30 +105950,31 @@ static int patternCompare( ** single character of the input string for each "?" skipped */ while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ - return 0; + return SQLITE_NOWILDCARDMATCH; } } if( c==0 ){ - return 1; /* "*" at the end of the pattern matches */ + return SQLITE_MATCH; /* "*" at the end of the pattern matches */ }else if( c==matchOther ){ if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOWILDCARDMATCH; }else{ /* "[...]" immediately follows the "*". We have to do a slow ** recursive search in this case, but it is an unusual case. */ assert( matchOther<0x80 ); /* '[' is a single-byte character */ - while( *zString - && patternCompare(&zPattern[-1],zString,pInfo,matchOther)==0 ){ + while( *zString ){ + int bMatch = patternCompare(&zPattern[-1],zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; SQLITE_SKIP_UTF8(zString); } - return *zString!=0; + return SQLITE_NOWILDCARDMATCH; } } /* At this point variable c contains the first character of the ** pattern string past the "*". Search in the input string for the - ** first matching character and recursively contine the match from + ** first matching character and recursively continue the match from ** that point. ** ** For a case-insensitive search, set variable cx to be the same as @@ -103898,6 +105983,7 @@ static int patternCompare( */ if( c<=0x80 ){ u32 cx; + int bMatch; if( noCase ){ cx = sqlite3Toupper(c); c = sqlite3Tolower(c); @@ -103906,27 +105992,30 @@ static int patternCompare( } while( (c2 = *(zString++))!=0 ){ if( c2!=c && c2!=cx ) continue; - if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; } }else{ + int bMatch; while( (c2 = Utf8Read(zString))!=0 ){ if( c2!=c ) continue; - if( patternCompare(zPattern,zString,pInfo,matchOther) ) return 1; + bMatch = patternCompare(zPattern,zString,pInfo,matchOther); + if( bMatch!=SQLITE_NOMATCH ) return bMatch; } } - return 0; + return SQLITE_NOWILDCARDMATCH; } if( c==matchOther ){ if( pInfo->matchSet==0 ){ c = sqlite3Utf8Read(&zPattern); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOMATCH; zEscaped = zPattern; }else{ u32 prior_c = 0; int seen = 0; int invert = 0; c = sqlite3Utf8Read(&zString); - if( c==0 ) return 0; + if( c==0 ) return SQLITE_NOMATCH; c2 = sqlite3Utf8Read(&zPattern); if( c2=='^' ){ invert = 1; @@ -103950,7 +106039,7 @@ static int patternCompare( c2 = sqlite3Utf8Read(&zPattern); } if( c2==0 || (seen ^ invert)==0 ){ - return 0; + return SQLITE_NOMATCH; } continue; } @@ -103961,23 +106050,25 @@ static int patternCompare( continue; } if( c==matchOne && zPattern!=zEscaped && c2!=0 ) continue; - return 0; + return SQLITE_NOMATCH; } - return *zString==0; + return *zString==0 ? SQLITE_MATCH : SQLITE_NOMATCH; } /* -** The sqlite3_strglob() interface. +** The sqlite3_strglob() interface. Return 0 on a match (like strcmp()) and +** non-zero if there is no match. */ SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){ - return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '[')==0; + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); } /* -** The sqlite3_strlike() interface. +** The sqlite3_strlike() interface. Return 0 on a match and non-zero for +** a miss - like strcmp(). */ SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ - return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc)==0; + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); } /* @@ -104058,7 +106149,7 @@ static void likeFunc( #ifdef SQLITE_TEST sqlite3_like_count++; #endif - sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape)); + sqlite3_result_int(context, patternCompare(zB, zA, pInfo, escape)==SQLITE_MATCH); } } @@ -104829,7 +106920,7 @@ static void groupConcatStep( zSep = ","; nSep = 1; } - if( nSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep); + if( zSep ) sqlite3StrAccumAppend(pAccum, zSep, nSep); } zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); @@ -104970,6 +107061,9 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ FUNCTION2(unlikely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likelihood, 2, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), FUNCTION2(likely, 1, 0, 0, noopFunc, SQLITE_FUNC_UNLIKELY), +#ifdef SQLITE_DEBUG + FUNCTION2(affinity, 1, 0, 0, noopFunc, SQLITE_FUNC_AFFINITY), +#endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), FUNCTION(rtrim, 1, 2, 0, trimFunc ), @@ -105292,7 +107386,7 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( } for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) ){ + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){ /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number ** of columns. If each indexed column corresponds to a foreign key ** column of pFKey, then this index is a winner. */ @@ -105651,7 +107745,7 @@ static void fkScanChildren( assert( iCol>=0 ); zCol = pFKey->pFrom->aCol[iCol].zName; pRight = sqlite3Expr(db, TK_ID, zCol); - pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pWhere = sqlite3ExprAnd(db, pWhere, pEq); } @@ -105673,7 +107767,7 @@ static void fkScanChildren( if( HasRowid(pTab) ){ pLeft = exprTableRegister(pParse, pTab, regData, -1); pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, -1); - pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight, 0); + pNe = sqlite3PExpr(pParse, TK_NE, pLeft, pRight); }else{ Expr *pEq, *pAll = 0; Index *pPk = sqlite3PrimaryKeyIndex(pTab); @@ -105683,10 +107777,10 @@ static void fkScanChildren( assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); pRight = exprTableColumn(db, pTab, pSrc->a[0].iCursor, iCol); - pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); pAll = sqlite3ExprAnd(db, pAll, pEq); } - pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0, 0); + pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0); } pWhere = sqlite3ExprAnd(db, pWhere, pNe); } @@ -105938,7 +108032,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( if( (db->flags&SQLITE_ForeignKeys)==0 ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - zDb = db->aDb[iDb].zName; + zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ @@ -106074,7 +108168,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( struct SrcList_item *pItem = pSrc->a; pItem->pTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; - pItem->pTab->nRef++; + pItem->pTab->nTabRef++; pItem->iCursor = pParse->nTab++; if( regNew!=0 ){ @@ -106272,10 +108366,9 @@ static Trigger *fkActionTrigger( pEq = sqlite3PExpr(pParse, TK_EQ, sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) - , 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) - , 0); + ); pWhere = sqlite3ExprAnd(db, pWhere, pEq); /* For ON UPDATE, construct the next term of the WHEN clause. @@ -106287,13 +108380,11 @@ static Trigger *fkActionTrigger( pEq = sqlite3PExpr(pParse, TK_IS, sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), - 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), - 0), - 0); + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)) + ); pWhen = sqlite3ExprAnd(db, pWhen, pEq); } @@ -106302,17 +108393,16 @@ static Trigger *fkActionTrigger( if( action==OE_Cascade ){ pNew = sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), - sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) - , 0); + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)); }else if( action==OE_SetDflt ){ Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt; if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); }else{ - pNew = sqlite3PExpr(pParse, TK_NULL, 0, 0, 0); + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); } }else{ - pNew = sqlite3PExpr(pParse, TK_NULL, 0, 0, 0); + pNew = sqlite3ExprAlloc(db, TK_NULL, 0, 0); } pList = sqlite3ExprListAppend(pParse, pList, pNew); sqlite3ExprListSetName(pParse, pList, &tFromCol, 0); @@ -106359,7 +108449,7 @@ static Trigger *fkActionTrigger( pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); if( pWhen ){ - pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0, 0); + pWhen = sqlite3PExpr(pParse, TK_NOT, pWhen, 0); pTrigger->pWhen = sqlite3ExprDup(db, pWhen, EXPRDUP_REDUCE); } } @@ -106678,7 +108768,9 @@ static int readsTable(Parse *p, int iDb, Table *pTab){ /* ** Locate or create an AutoincInfo structure associated with table pTab ** which is in database iDb. Return the register number for the register -** that holds the maximum rowid. +** that holds the maximum rowid. Return zero if pTab is not an AUTOINCREMENT +** table. (Also return zero when doing a VACUUM since we do not want to +** update the AUTOINCREMENT counters during a VACUUM.) ** ** There is at most one AutoincInfo structure per table even if the ** same table is autoincremented multiple times due to inserts within @@ -106701,7 +108793,9 @@ static int autoIncBegin( Table *pTab /* The table we are writing to */ ){ int memId = 0; /* Register holding maximum rowid */ - if( pTab->tabFlags & TF_Autoincrement ){ + if( (pTab->tabFlags & TF_Autoincrement)!=0 + && (pParse->db->flags & SQLITE_Vacuum)==0 + ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); AutoincInfo *pInfo; @@ -106959,8 +109053,7 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3 *db; /* The main database structure */ Table *pTab; /* The table to insert into. aka TABLE */ char *zTab; /* Name of the table into which we are inserting */ - const char *zDb; /* Name of the database holding this table */ - int i, j, idx; /* Loop counters */ + int i, j; /* Loop counters */ Vdbe *v; /* Generate code into this virtual machine */ Index *pIdx; /* For looping over indices of the table */ int nColumn; /* Number of columns in the data */ @@ -106974,7 +109067,6 @@ SQLITE_PRIVATE void sqlite3Insert( int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */ SelectDest dest; /* Destination for SELECT on rhs of INSERT */ int iDb; /* Index of database holding TABLE */ - Db *pDb; /* The database containing table being inserted into */ u8 useTempTable = 0; /* Store SELECT results in intermediate table */ u8 appendFlag = 0; /* True if the insert is likely to be an append */ u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ @@ -107024,9 +109116,8 @@ SQLITE_PRIVATE void sqlite3Insert( } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - pDb = &db->aDb[iDb]; - zDb = pDb->zName; - if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, zDb) ){ + if( sqlite3AuthCheck(pParse, SQLITE_INSERT, pTab->zName, 0, + db->aDb[iDb].zDbSName) ){ goto insert_cleanup; } withoutRowid = !HasRowid(pTab); @@ -107269,8 +109360,10 @@ SQLITE_PRIVATE void sqlite3Insert( if( aRegIdx==0 ){ goto insert_cleanup; } - for(i=0; ipIndex; ipNext, i++){ + assert( pIdx ); aRegIdx[i] = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; } } @@ -107472,12 +109565,26 @@ SQLITE_PRIVATE void sqlite3Insert( #endif { int isReplace; /* Set to true if constraints may cause a replace */ + int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0 ); sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + + /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE + ** constraints or (b) there are no triggers and this table is not a + ** parent table in a foreign key constraint. It is safe to set the + ** flag in the second case as if any REPLACE constraint is hit, an + ** OP_Delete or OP_IdxDelete instruction will be executed on each + ** cursor that is disturbed. And these instructions both clear the + ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT + ** functionality. */ + bUseSeek = (isReplace==0 || (pTrigger==0 && + ((db->flags & SQLITE_ForeignKeys)==0 || sqlite3FkReferences(pTab)==0) + )); sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, - regIns, aRegIdx, 0, appendFlag, isReplace==0); + regIns, aRegIdx, 0, appendFlag, bUseSeek + ); } } @@ -107506,14 +109613,6 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3VdbeJumpHere(v, addrInsTop); } - if( !IsVirtual(pTab) && !isView ){ - /* Close all tables opened */ - if( iDataCurpIndex; pIdx; pIdx=pIdx->pNext, idx++){ - sqlite3VdbeAddOp1(v, OP_Close, idx+iIdxCur); - } - } - insert_end: /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into @@ -107720,7 +109819,6 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int ipkBottom = 0; /* Bottom of the rowid change constraint check */ u8 isUpdate; /* True if this is an UPDATE operation */ u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ - int regRowid = -1; /* Register holding ROWID value */ isUpdate = regOldData!=0; db = pParse->db; @@ -107775,8 +109873,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( case OE_Fail: { char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, pTab->aCol[i].zName); - sqlite3VdbeAddOp4(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, - regNewData+1+i, zMsg, P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, + regNewData+1+i); + sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); VdbeCoverage(v); break; @@ -107840,7 +109939,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } if( isUpdate ){ - /* pkChng!=0 does not mean that the rowid has change, only that + /* pkChng!=0 does not mean that the rowid has changed, only that ** it might have changed. Skip the conflict logic below if the rowid ** is unchanged. */ sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRowidOk, regOldData); @@ -107918,7 +110017,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** OP_Insert replace the existing entry than it is to delete the ** existing entry and then insert a new one. */ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, OPFLAG_ISNOOP); - sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ if( pTab->pIndex ){ @@ -107975,7 +110074,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Create a record for this index entry as it should appear after ** the insert or update. Store that record in the aRegIdx[ix] register */ - regIdx = sqlite3GetTempRange(pParse, pIdx->nColumn); + regIdx = aRegIdx[ix]+1; for(i=0; inColumn; i++){ int iField = pIdx->aiColumn[i]; int x; @@ -107986,9 +110085,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( VdbeComment((v, "%s column %d", pIdx->zName, i)); }else{ if( iField==XN_ROWID || iField==pTab->iPKey ){ - if( regRowid==regIdx+i ) continue; /* ROWID already in regIdx+i */ x = regNewData; - regRowid = pIdx->pPartIdxWhere ? -1 : regIdx+i; }else{ x = iField + regNewData + 1; } @@ -107998,7 +110095,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); VdbeComment((v, "for %s", pIdx->zName)); - sqlite3ExprCacheAffinityChange(pParse, regIdx, pIdx->nColumn); +#ifdef SQLITE_ENABLE_NULL_TRIM + if( pIdx->idxType==2 ) sqlite3SetMakeRecordP5(v, pIdx->pTable); +#endif /* In an UPDATE operation, if this index is the PRIMARY KEY index ** of a WITHOUT ROWID table and there has been no change the @@ -108012,7 +110111,6 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; if( onError==OE_None ){ - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } @@ -108021,7 +110119,26 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( }else if( onError==OE_Default ){ onError = OE_Abort; } - + + /* Collision detection may be omitted if all of the following are true: + ** (1) The conflict resolution algorithm is REPLACE + ** (2) The table is a WITHOUT ROWID table + ** (3) There are no secondary indexes on the table + ** (4) No delete triggers need to be fired if there is a conflict + ** (5) No FK constraint counters need to be updated if a conflict occurs. + */ + if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ + && pPk==pIdx /* Condition 2 */ + && onError==OE_Replace /* Condition 1 */ + && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ + 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) + && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ + (0==pTab->pFKey && 0==sqlite3FkReferences(pTab))) + ){ + sqlite3VdbeResolveLabel(v, addrUniqueOk); + continue; + } + /* Check to see if the new index entry will be unique */ sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, regIdx, pIdx->nKeyCol); VdbeCoverage(v); @@ -108105,13 +110222,12 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, regR, nPkField, 0, OE_Replace, - (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), -1); + (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur); seenReplace = 1; break; } } sqlite3VdbeResolveLabel(v, addrUniqueOk); - sqlite3ReleaseTempRange(pParse, regIdx, pIdx->nColumn); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); } if( ipkTop ){ @@ -108123,6 +110239,28 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace)); } +#ifdef SQLITE_ENABLE_NULL_TRIM +/* +** Change the P5 operand on the last opcode (which should be an OP_MakeRecord) +** to be the number of columns in table pTab that must not be NULL-trimmed. +** +** Or if no columns of pTab may be NULL-trimmed, leave P5 at zero. +*/ +SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ + u16 i; + + /* Records with omitted columns are only allowed for schema format + ** version 2 and later (SQLite version 3.1.4, 2005-02-20). */ + if( pTab->pSchema->file_format<2 ) return; + + for(i=pTab->nCol-1; i>0; i--){ + if( pTab->aCol[i].pDflt!=0 ) break; + if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break; + } + sqlite3VdbeChangeP5(v, i+1); +} +#endif + /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. @@ -108139,7 +110277,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( int iIdxCur, /* First index cursor */ int regNewData, /* Range of content */ int *aRegIdx, /* Register used by each index. 0 for unused indices */ - int isUpdate, /* True for UPDATE, False for INSERT */ + int update_flags, /* True for UPDATE, False for INSERT */ int appendBias, /* True if this is likely to be an append */ int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */ ){ @@ -108151,6 +110289,11 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( int i; /* Loop counter */ u8 bAffinityDone = 0; /* True if OP_Affinity has been run already */ + assert( update_flags==0 + || update_flags==OPFLAG_ISUPDATE + || update_flags==(OPFLAG_ISUPDATE|OPFLAG_SAVEPOSITION) + ); + v = sqlite3GetVdbe(pParse); assert( v!=0 ); assert( pTab->pSelect==0 ); /* This table is not a VIEW */ @@ -108161,26 +110304,39 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); VdbeCoverage(v); } - sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i]); - pik_flags = 0; - if( useSeekResult ) pik_flags = OPFLAG_USESEEKRESULT; + pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; + pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( update_flags==0 ){ + sqlite3VdbeAddOp4(v, OP_InsertInt, + iIdxCur+i, aRegIdx[i], 0, (char*)pTab, P4_TABLE + ); + sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); + } +#endif } + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], + aRegIdx[i]+1, + pIdx->uniqNotNull ? pIdx->nKeyCol: pIdx->nColumn); sqlite3VdbeChangeP5(v, pik_flags); } if( !HasRowid(pTab) ) return; regData = regNewData + 1; regRec = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); - if( !bAffinityDone ) sqlite3TableAffinity(v, pTab, 0); - sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); + sqlite3SetMakeRecordP5(v, pTab); + if( !bAffinityDone ){ + sqlite3TableAffinity(v, pTab, 0); + sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol); + } if( pParse->nested ){ pik_flags = 0; }else{ pik_flags = OPFLAG_NCHANGE; - pik_flags |= (isUpdate?OPFLAG_ISUPDATE:OPFLAG_LASTROWID); + pik_flags |= (update_flags?update_flags:OPFLAG_LASTROWID); } if( appendBias ){ pik_flags |= OPFLAG_APPEND; @@ -108190,7 +110346,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( } sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, (char *)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } sqlite3VdbeChangeP5(v, pik_flags); } @@ -108391,7 +110547,7 @@ static int xferOptimization( return 0; /* tab1 must not have triggers */ } #ifndef SQLITE_OMIT_VIRTUALTABLE - if( pDest->tabFlags & TF_Virtual ){ + if( IsVirtual(pDest) ){ return 0; /* tab1 must not be a virtual table */ } #endif @@ -108453,7 +110609,7 @@ static int xferOptimization( return 0; /* source and destination must both be WITHOUT ROWID or not */ } #ifndef SQLITE_OMIT_VIRTUALTABLE - if( pSrc->tabFlags & TF_Virtual ){ + if( IsVirtual(pSrc) ){ return 0; /* tab2 must not be a virtual table */ } #endif @@ -108573,6 +110729,7 @@ static int xferOptimization( sqlite3VdbeJumpHere(v, addr1); } if( HasRowid(pSrc) ){ + u8 insFlags; sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OP_OpenRead); emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); if( pDest->iPKey>=0 ){ @@ -108588,10 +110745,17 @@ static int xferOptimization( addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } - sqlite3VdbeAddOp2(v, OP_RowData, iSrc, regData); + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( db->flags & SQLITE_Vacuum ){ + sqlite3VdbeAddOp3(v, OP_Last, iDest, 0, -1); + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID| + OPFLAG_APPEND|OPFLAG_USESEEKRESULT; + }else{ + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND; + } sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid, (char*)pDest, P4_TABLE); - sqlite3VdbeChangeP5(v, OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND); + sqlite3VdbeChangeP5(v, insFlags); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -108613,7 +110777,7 @@ static int xferOptimization( sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_RowKey, iSrc, regData); + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); if( db->flags & SQLITE_Vacuum ){ /* This INSERT command is part of a VACUUM operation, which guarantees ** that the destination table is empty. If all indexed columns use @@ -108631,8 +110795,6 @@ static int xferOptimization( ** sorted order. */ for(i=0; inColumn; i++){ const char *zColl = pSrcIdx->azColl[i]; - assert( sqlite3_stricmp(sqlite3StrBINARY, zColl)!=0 - || sqlite3StrBINARY==zColl ); if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break; } if( i==pSrcIdx->nColumn ){ @@ -108643,8 +110805,8 @@ static int xferOptimization( if( !HasRowid(pSrc) && pDestIdx->idxType==2 ){ idxInsFlags |= OPFLAG_NCHANGE; } - sqlite3VdbeAddOp3(v, OP_IdxInsert, iDest, regData, 1); - sqlite3VdbeChangeP5(v, idxInsFlags); + sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData); + sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); @@ -108654,6 +110816,7 @@ static int xferOptimization( sqlite3ReleaseTempReg(pParse, regRowid); sqlite3ReleaseTempReg(pParse, regData); if( emptyDestTest ){ + sqlite3AutoincrementEnd(pParse); sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, 0); sqlite3VdbeJumpHere(v, emptyDestTest); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -108741,7 +110904,7 @@ SQLITE_API int sqlite3_exec( (SQLITE_DONE==rc && !callbackIsInit && db->flags&SQLITE_NullCallback)) ){ if( !callbackIsInit ){ - azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1); + azCols = sqlite3DbMallocRaw(db, (2*nCol+1)*sizeof(const char*)); if( azCols==0 ){ goto exec_out; } @@ -108762,6 +110925,7 @@ SQLITE_API int sqlite3_exec( goto exec_out; } } + azVals[i] = 0; } if( xCallback(pArg, nCol, azVals, azCols) ){ /* EVIDENCE-OF: R-38229-40159 If the callback function to @@ -109117,6 +111281,8 @@ struct sqlite3_api_routines { /* Version 3.14.0 and later */ int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*); char *(*expanded_sql)(sqlite3_stmt*); + /* Version 3.18.0 and later */ + void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64); }; /* @@ -109375,6 +111541,8 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.14.0 and later */ #define sqlite3_trace_v2 sqlite3_api->trace_v2 #define sqlite3_expanded_sql sqlite3_api->expanded_sql +/* Version 3.18.0 and later */ +#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -109397,7 +111565,6 @@ typedef int (*sqlite3_loadext_entry)( /************** End of sqlite3ext.h ******************************************/ /************** Continuing where we left off in loadext.c ********************/ /* #include "sqliteInt.h" */ -/* #include */ #ifndef SQLITE_OMIT_LOAD_EXTENSION /* @@ -109801,7 +111968,9 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_system_errno, /* Version 3.14.0 and later */ sqlite3_trace_v2, - sqlite3_expanded_sql + sqlite3_expanded_sql, + /* Version 3.18.0 and later */ + sqlite3_set_last_insert_rowid }; /* @@ -109999,18 +112168,7 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ return SQLITE_OK; } -#endif /* SQLITE_OMIT_LOAD_EXTENSION */ - -/* -** The auto-extension code added regardless of whether or not extension -** loading is supported. We need a dummy sqlite3Apis pointer for that -** code if regular extension loading is not available. This is that -** dummy pointer. -*/ -#ifdef SQLITE_OMIT_LOAD_EXTENSION -static const sqlite3_api_routines sqlite3Apis = { 0 }; -#endif - +#endif /* !defined(SQLITE_OMIT_LOAD_EXTENSION) */ /* ** The following object holds the list of automatically loaded @@ -110154,6 +112312,11 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ char *zErrmsg; #if SQLITE_THREADSAFE sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + const sqlite3_api_routines *pThunk = 0; +#else + const sqlite3_api_routines *pThunk = &sqlite3Apis; #endif sqlite3_mutex_enter(mutex); if( i>=wsdAutoext.nExt ){ @@ -110164,7 +112327,7 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ } sqlite3_mutex_leave(mutex); zErrmsg = 0; - if( xInit && (rc = xInit(db, &zErrmsg, &sqlite3Apis))!=0 ){ + if( xInit && (rc = xInit(db, &zErrmsg, pThunk))!=0 ){ sqlite3ErrorWithMsg(db, rc, "automatic extension loading failed: %s", zErrmsg); go = 0; @@ -110212,6 +112375,8 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ ** ../tool/mkpragmatab.tcl. To update the set of pragmas, edit ** that script and rerun it. */ + +/* The various pragma types */ #define PragTyp_HEADER_VALUE 0 #define PragTyp_AUTO_VACUUM 1 #define PragTyp_FLAG 2 @@ -110237,11 +112402,11 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_LOCKING_MODE 22 #define PragTyp_PAGE_COUNT 23 #define PragTyp_MMAP_SIZE 24 -#define PragTyp_PAGE_SIZE 25 -#define PragTyp_SECURE_DELETE 26 -#define PragTyp_SHRINK_MEMORY 27 -#define PragTyp_SOFT_HEAP_LIMIT 28 -#define PragTyp_STATS 29 +#define PragTyp_OPTIMIZE 25 +#define PragTyp_PAGE_SIZE 26 +#define PragTyp_SECURE_DELETE 27 +#define PragTyp_SHRINK_MEMORY 28 +#define PragTyp_SOFT_HEAP_LIMIT 29 #define PragTyp_SYNCHRONOUS 30 #define PragTyp_TABLE_INFO 31 #define PragTyp_TEMP_STORE 32 @@ -110255,422 +112420,572 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ #define PragTyp_REKEY 40 #define PragTyp_LOCK_STATUS 41 #define PragTyp_PARSER_TRACE 42 -#define PragFlag_NeedSchema 0x01 -#define PragFlag_ReadOnly 0x02 -static const struct sPragmaNames { - const char *const zName; /* Name of pragma */ - u8 ePragTyp; /* PragTyp_XXX value */ - u8 mPragFlag; /* Zero or more PragFlag_XXX values */ - u32 iArg; /* Extra argument */ -} aPragmaNames[] = { +#define PragTyp_STATS 43 + +/* Property flags associated with various pragma. */ +#define PragFlg_NeedSchema 0x01 /* Force schema load before running */ +#define PragFlg_NoColumns 0x02 /* OP_ResultRow called with zero columns */ +#define PragFlg_NoColumns1 0x04 /* zero columns if RHS argument is present */ +#define PragFlg_ReadOnly 0x08 /* Read-only HEADER_VALUE */ +#define PragFlg_Result0 0x10 /* Acts as query when no argument */ +#define PragFlg_Result1 0x20 /* Acts as query when has one argument */ +#define PragFlg_SchemaOpt 0x40 /* Schema restricts name search if present */ +#define PragFlg_SchemaReq 0x80 /* Schema required - "main" is default */ + +/* Names of columns for pragmas that return multi-column result +** or that return single-column results where the name of the +** result column is different from the name of the pragma +*/ +static const char *const pragCName[] = { + /* 0 */ "cache_size", /* Used by: default_cache_size */ + /* 1 */ "cid", /* Used by: table_info */ + /* 2 */ "name", + /* 3 */ "type", + /* 4 */ "notnull", + /* 5 */ "dflt_value", + /* 6 */ "pk", + /* 7 */ "tbl", /* Used by: stats */ + /* 8 */ "idx", + /* 9 */ "wdth", + /* 10 */ "hght", + /* 11 */ "flgs", + /* 12 */ "seqno", /* Used by: index_info */ + /* 13 */ "cid", + /* 14 */ "name", + /* 15 */ "seqno", /* Used by: index_xinfo */ + /* 16 */ "cid", + /* 17 */ "name", + /* 18 */ "desc", + /* 19 */ "coll", + /* 20 */ "key", + /* 21 */ "seq", /* Used by: index_list */ + /* 22 */ "name", + /* 23 */ "unique", + /* 24 */ "origin", + /* 25 */ "partial", + /* 26 */ "seq", /* Used by: database_list */ + /* 27 */ "name", + /* 28 */ "file", + /* 29 */ "seq", /* Used by: collation_list */ + /* 30 */ "name", + /* 31 */ "id", /* Used by: foreign_key_list */ + /* 32 */ "seq", + /* 33 */ "table", + /* 34 */ "from", + /* 35 */ "to", + /* 36 */ "on_update", + /* 37 */ "on_delete", + /* 38 */ "match", + /* 39 */ "table", /* Used by: foreign_key_check */ + /* 40 */ "rowid", + /* 41 */ "parent", + /* 42 */ "fkid", + /* 43 */ "busy", /* Used by: wal_checkpoint */ + /* 44 */ "log", + /* 45 */ "checkpointed", + /* 46 */ "timeout", /* Used by: busy_timeout */ + /* 47 */ "database", /* Used by: lock_status */ + /* 48 */ "status", +}; + +/* Definitions of all built-in pragmas */ +typedef struct PragmaName { + const char *const zName; /* Name of pragma */ + u8 ePragTyp; /* PragTyp_XXX value */ + u8 mPragFlg; /* Zero or more PragFlg_XXX values */ + u8 iPragCName; /* Start of column names in pragCName[] */ + u8 nPragCName; /* Num of col names. 0 means use pragma name */ + u32 iArg; /* Extra argument */ +} PragmaName; +static const PragmaName aPragmaName[] = { #if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) - { /* zName: */ "activate_extensions", - /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "activate_extensions", + /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "application_id", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_APPLICATION_ID }, + {/* zName: */ "application_id", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_APPLICATION_ID }, #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { /* zName: */ "auto_vacuum", - /* ePragTyp: */ PragTyp_AUTO_VACUUM, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "auto_vacuum", + /* ePragTyp: */ PragTyp_AUTO_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_AUTOMATIC_INDEX) - { /* zName: */ "automatic_index", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_AutoIndex }, -#endif -#endif - { /* zName: */ "busy_timeout", - /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "automatic_index", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_AutoIndex }, +#endif +#endif + {/* zName: */ "busy_timeout", + /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 46, 1, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "cache_size", - /* ePragTyp: */ PragTyp_CACHE_SIZE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "cache_size", + /* ePragTyp: */ PragTyp_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "cache_spill", - /* ePragTyp: */ PragTyp_CACHE_SPILL, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, -#endif - { /* zName: */ "case_sensitive_like", - /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "cell_size_check", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CellSizeCk }, + {/* zName: */ "cache_spill", + /* ePragTyp: */ PragTyp_CACHE_SPILL, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "case_sensitive_like", + /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "cell_size_check", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CellSizeCk }, #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "checkpoint_fullfsync", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CkptFullFSync }, + {/* zName: */ "checkpoint_fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CkptFullFSync }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "collation_list", - /* ePragTyp: */ PragTyp_COLLATION_LIST, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "collation_list", + /* ePragTyp: */ PragTyp_COLLATION_LIST, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 29, 2, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) - { /* zName: */ "compile_options", - /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "compile_options", + /* ePragTyp: */ PragTyp_COMPILE_OPTIONS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "count_changes", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_CountRows }, + {/* zName: */ "count_changes", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_CountRows }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_OS_WIN - { /* zName: */ "data_store_directory", - /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "data_store_directory", + /* ePragTyp: */ PragTyp_DATA_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "data_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ PragFlag_ReadOnly, - /* iArg: */ BTREE_DATA_VERSION }, + {/* zName: */ "data_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_DATA_VERSION }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "database_list", - /* ePragTyp: */ PragTyp_DATABASE_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "database_list", + /* ePragTyp: */ PragTyp_DATABASE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, + /* ColNames: */ 26, 3, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) - { /* zName: */ "default_cache_size", - /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "default_cache_size", + /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 1, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "defer_foreign_keys", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_DeferFKs }, + {/* zName: */ "defer_foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_DeferFKs }, #endif #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "empty_result_callbacks", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_NullCallback }, + {/* zName: */ "empty_result_callbacks", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_NullCallback }, #endif #if !defined(SQLITE_OMIT_UTF16) - { /* zName: */ "encoding", - /* ePragTyp: */ PragTyp_ENCODING, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "encoding", + /* ePragTyp: */ PragTyp_ENCODING, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "foreign_key_check", - /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "foreign_key_check", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 39, 4, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) - { /* zName: */ "foreign_key_list", - /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "foreign_key_list", + /* ePragTyp: */ PragTyp_FOREIGN_KEY_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 31, 8, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) - { /* zName: */ "foreign_keys", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ForeignKeys }, + {/* zName: */ "foreign_keys", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ForeignKeys }, #endif #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "freelist_count", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ PragFlag_ReadOnly, - /* iArg: */ BTREE_FREE_PAGE_COUNT }, + {/* zName: */ "freelist_count", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_ReadOnly|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_FREE_PAGE_COUNT }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "full_column_names", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_FullColNames }, - { /* zName: */ "fullfsync", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_FullFSync }, + {/* zName: */ "full_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullColNames }, + {/* zName: */ "fullfsync", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_FullFSync }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "hexkey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "hexrekey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "hexkey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "hexrekey", + /* ePragTyp: */ PragTyp_HEXKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_CHECK) - { /* zName: */ "ignore_check_constraints", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_IgnoreChecks }, + {/* zName: */ "ignore_check_constraints", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_IgnoreChecks }, #endif #endif #if !defined(SQLITE_OMIT_AUTOVACUUM) - { /* zName: */ "incremental_vacuum", - /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "incremental_vacuum", + /* ePragTyp: */ PragTyp_INCREMENTAL_VACUUM, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "index_info", - /* ePragTyp: */ PragTyp_INDEX_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "index_list", - /* ePragTyp: */ PragTyp_INDEX_LIST, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "index_xinfo", - /* ePragTyp: */ PragTyp_INDEX_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 1 }, + {/* zName: */ "index_info", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 12, 3, + /* iArg: */ 0 }, + {/* zName: */ "index_list", + /* ePragTyp: */ PragTyp_INDEX_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 21, 5, + /* iArg: */ 0 }, + {/* zName: */ "index_xinfo", + /* ePragTyp: */ PragTyp_INDEX_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 15, 6, + /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { /* zName: */ "integrity_check", - /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "integrity_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "journal_mode", - /* ePragTyp: */ PragTyp_JOURNAL_MODE, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "journal_size_limit", - /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "journal_mode", + /* ePragTyp: */ PragTyp_JOURNAL_MODE, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "journal_size_limit", + /* ePragTyp: */ PragTyp_JOURNAL_SIZE_LIMIT, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "key", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "key", + /* ePragTyp: */ PragTyp_KEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "legacy_file_format", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_LegacyFileFmt }, + {/* zName: */ "legacy_file_format", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_LegacyFileFmt }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE - { /* zName: */ "lock_proxy_file", - /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "lock_proxy_file", + /* ePragTyp: */ PragTyp_LOCK_PROXY_FILE, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) - { /* zName: */ "lock_status", - /* ePragTyp: */ PragTyp_LOCK_STATUS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "lock_status", + /* ePragTyp: */ PragTyp_LOCK_STATUS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 47, 2, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "locking_mode", - /* ePragTyp: */ PragTyp_LOCKING_MODE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "max_page_count", - /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "mmap_size", - /* ePragTyp: */ PragTyp_MMAP_SIZE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "page_count", - /* ePragTyp: */ PragTyp_PAGE_COUNT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, - { /* zName: */ "page_size", - /* ePragTyp: */ PragTyp_PAGE_SIZE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "locking_mode", + /* ePragTyp: */ PragTyp_LOCKING_MODE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "max_page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "mmap_size", + /* ePragTyp: */ PragTyp_MMAP_SIZE, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "optimize", + /* ePragTyp: */ PragTyp_OPTIMIZE, + /* ePragFlg: */ PragFlg_Result1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_PAGER_PRAGMAS) + {/* zName: */ "page_count", + /* ePragTyp: */ PragTyp_PAGE_COUNT, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "page_size", + /* ePragTyp: */ PragTyp_PAGE_SIZE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_PARSER_TRACE) - { /* zName: */ "parser_trace", - /* ePragTyp: */ PragTyp_PARSER_TRACE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "parser_trace", + /* ePragTyp: */ PragTyp_PARSER_TRACE, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "query_only", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_QueryOnly }, + {/* zName: */ "query_only", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_QueryOnly }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) - { /* zName: */ "quick_check", - /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "quick_check", + /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "read_uncommitted", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ReadUncommitted }, - { /* zName: */ "recursive_triggers", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_RecTriggers }, + {/* zName: */ "read_uncommitted", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReadUncommitted }, + {/* zName: */ "recursive_triggers", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_RecTriggers }, #endif #if defined(SQLITE_HAS_CODEC) - { /* zName: */ "rekey", - /* ePragTyp: */ PragTyp_REKEY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "rekey", + /* ePragTyp: */ PragTyp_REKEY, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "reverse_unordered_selects", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ReverseOrder }, + {/* zName: */ "reverse_unordered_selects", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ReverseOrder }, #endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "schema_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_SCHEMA_VERSION }, + {/* zName: */ "schema_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_SCHEMA_VERSION }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "secure_delete", - /* ePragTyp: */ PragTyp_SECURE_DELETE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "secure_delete", + /* ePragTyp: */ PragTyp_SECURE_DELETE, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "short_column_names", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_ShortColNames }, -#endif - { /* zName: */ "shrink_memory", - /* ePragTyp: */ PragTyp_SHRINK_MEMORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "soft_heap_limit", - /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "short_column_names", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_ShortColNames }, +#endif + {/* zName: */ "shrink_memory", + /* ePragTyp: */ PragTyp_SHRINK_MEMORY, + /* ePragFlg: */ PragFlg_NoColumns, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "soft_heap_limit", + /* ePragTyp: */ PragTyp_SOFT_HEAP_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) - { /* zName: */ "sql_trace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_SqlTrace }, + {/* zName: */ "sql_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace }, #endif #endif -#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "stats", - /* ePragTyp: */ PragTyp_STATS, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) && defined(SQLITE_DEBUG) + {/* zName: */ "stats", + /* ePragTyp: */ PragTyp_STATS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, + /* ColNames: */ 7, 5, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "synchronous", - /* ePragTyp: */ PragTyp_SYNCHRONOUS, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "synchronous", + /* ePragTyp: */ PragTyp_SYNCHRONOUS, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) - { /* zName: */ "table_info", - /* ePragTyp: */ PragTyp_TABLE_INFO, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "table_info", + /* ePragTyp: */ PragTyp_TABLE_INFO, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 1, 6, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) - { /* zName: */ "temp_store", - /* ePragTyp: */ PragTyp_TEMP_STORE, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "temp_store_directory", - /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, -#endif - { /* zName: */ "threads", - /* ePragTyp: */ PragTyp_THREADS, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, + {/* zName: */ "temp_store", + /* ePragTyp: */ PragTyp_TEMP_STORE, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "temp_store_directory", + /* ePragTyp: */ PragTyp_TEMP_STORE_DIRECTORY, + /* ePragFlg: */ PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, +#endif + {/* zName: */ "threads", + /* ePragTyp: */ PragTyp_THREADS, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) - { /* zName: */ "user_version", - /* ePragTyp: */ PragTyp_HEADER_VALUE, - /* ePragFlag: */ 0, - /* iArg: */ BTREE_USER_VERSION }, + {/* zName: */ "user_version", + /* ePragTyp: */ PragTyp_HEADER_VALUE, + /* ePragFlg: */ PragFlg_NoColumns1|PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ BTREE_USER_VERSION }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if defined(SQLITE_DEBUG) - { /* zName: */ "vdbe_addoptrace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeAddopTrace }, - { /* zName: */ "vdbe_debug", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, - { /* zName: */ "vdbe_eqp", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeEQP }, - { /* zName: */ "vdbe_listing", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeListing }, - { /* zName: */ "vdbe_trace", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_VdbeTrace }, + {/* zName: */ "vdbe_addoptrace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeAddopTrace }, + {/* zName: */ "vdbe_debug", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_SqlTrace|SQLITE_VdbeListing|SQLITE_VdbeTrace }, + {/* zName: */ "vdbe_eqp", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeEQP }, + {/* zName: */ "vdbe_listing", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeListing }, + {/* zName: */ "vdbe_trace", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_VdbeTrace }, #endif #endif #if !defined(SQLITE_OMIT_WAL) - { /* zName: */ "wal_autocheckpoint", - /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, - /* ePragFlag: */ 0, - /* iArg: */ 0 }, - { /* zName: */ "wal_checkpoint", - /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, - /* ePragFlag: */ PragFlag_NeedSchema, - /* iArg: */ 0 }, + {/* zName: */ "wal_autocheckpoint", + /* ePragTyp: */ PragTyp_WAL_AUTOCHECKPOINT, + /* ePragFlg: */ 0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, + {/* zName: */ "wal_checkpoint", + /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, + /* ePragFlg: */ PragFlg_NeedSchema, + /* ColNames: */ 43, 3, + /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) - { /* zName: */ "writable_schema", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlag: */ 0, - /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, + {/* zName: */ "writable_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_WriteSchema|SQLITE_RecoveryMode }, #endif }; -/* Number of pragmas: 60 on by default, 73 total. */ +/* Number of pragmas: 60 on by default, 74 total. */ /************** End of pragma.h **********************************************/ /************** Continuing where we left off in pragma.c *********************/ @@ -110808,29 +113123,29 @@ static int changeTempStorage(Parse *pParse, const char *zStorageType){ #endif /* SQLITE_PAGER_PRAGMAS */ /* -** Set the names of the first N columns to the values in azCol[] +** Set result column names for a pragma. */ -static void setAllColumnNames( - Vdbe *v, /* The query under construction */ - int N, /* Number of columns */ - const char **azCol /* Names of columns */ +static void setPragmaResultColumnNames( + Vdbe *v, /* The query under construction */ + const PragmaName *pPragma /* The pragma */ ){ - int i; - sqlite3VdbeSetNumCols(v, N); - for(i=0; inPragCName; + sqlite3VdbeSetNumCols(v, n==0 ? 1 : n); + if( n==0 ){ + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, pPragma->zName, SQLITE_STATIC); + }else{ + int i, j; + for(i=0, j=pPragma->iPragCName; iupr ? 0 : &aPragmaName[mid]; +} + +/* +** Helper subroutine for PRAGMA integrity_check: +** +** Generate code to output a single-column result row with the result +** held in register regResult. Decrement the result count and halt if +** the maximum number of result rows have been issued. +*/ +static int integrityCheckResultRow(Vdbe *v, int regResult){ + int addr; + sqlite3VdbeAddOp2(v, OP_ResultRow, regResult, 1); + addr = sqlite3VdbeAddOp3(v, OP_IfPos, 1, sqlite3VdbeCurrentAddr(v)+2, 1); + VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); + return addr; +} + /* ** Process a pragma statement. ** @@ -110950,12 +113299,11 @@ SQLITE_PRIVATE void sqlite3Pragma( Token *pId; /* Pointer to token */ char *aFcntl[4]; /* Argument to SQLITE_FCNTL_PRAGMA */ int iDb; /* Database index for */ - int lwr, upr, mid = 0; /* Binary search bounds */ int rc; /* return value form SQLITE_FCNTL_PRAGMA */ sqlite3 *db = pParse->db; /* The database connection */ Db *pDb; /* The specific database being pragmaed */ Vdbe *v = sqlite3GetVdbe(pParse); /* Prepared statement */ - const struct sPragmaNames *pPragma; + const PragmaName *pPragma; /* The pragma */ if( v==0 ) return; sqlite3VdbeRunOnlyOnce(v); @@ -110983,7 +113331,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } assert( pId2 ); - zDb = pId2->n>0 ? pDb->zName : 0; + zDb = pId2->n>0 ? pDb->zDbSName : 0; if( sqlite3AuthCheck(pParse, SQLITE_PRAGMA, zLeft, zRight, zDb) ){ goto pragma_out; } @@ -111010,7 +113358,9 @@ SQLITE_PRIVATE void sqlite3Pragma( db->busyHandler.nBusy = 0; rc = sqlite3_file_control(db, zDb, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); if( rc==SQLITE_OK ){ - returnSingleText(v, "result", aFcntl[0]); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, aFcntl[0], SQLITE_TRANSIENT); + returnSingleText(v, aFcntl[0]); sqlite3_free(aFcntl[0]); goto pragma_out; } @@ -111025,26 +113375,21 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* Locate the pragma in the lookup table */ - lwr = 0; - upr = ArraySize(aPragmaNames)-1; - while( lwr<=upr ){ - mid = (lwr+upr)/2; - rc = sqlite3_stricmp(zLeft, aPragmaNames[mid].zName); - if( rc==0 ) break; - if( rc<0 ){ - upr = mid - 1; - }else{ - lwr = mid + 1; - } - } - if( lwr>upr ) goto pragma_out; - pPragma = &aPragmaNames[mid]; + pPragma = pragmaLocate(zLeft); + if( pPragma==0 ) goto pragma_out; /* Make sure the database schema is loaded if the pragma requires that */ - if( (pPragma->mPragFlag & PragFlag_NeedSchema)!=0 ){ + if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ if( sqlite3ReadSchema(pParse) ) goto pragma_out; } + /* Register the result column names for pragmas that return results */ + if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 + && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) + ){ + setPragmaResultColumnNames(v, pPragma); + } + /* Jump to the appropriate pragma handler */ switch( pPragma->ePragTyp ){ @@ -111081,7 +113426,6 @@ SQLITE_PRIVATE void sqlite3Pragma( VdbeOp *aOp; sqlite3VdbeUsesBtree(v, iDb); if( !zRight ){ - setOneColumnName(v, "cache_size"); pParse->nMem += 2; sqlite3VdbeVerifyNoMallocRequired(v, ArraySize(getCacheSize)); aOp = sqlite3VdbeAddOpList(v, ArraySize(getCacheSize), getCacheSize, iLn); @@ -111116,7 +113460,7 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( pBt!=0 ); if( !zRight ){ int size = ALWAYS(pBt) ? sqlite3BtreeGetPageSize(pBt) : 0; - returnSingleInt(v, "page_size", size); + returnSingleInt(v, size); }else{ /* Malloc may fail when setting the page-size, as there is an internal ** buffer that the pager module resizes using sqlite3_realloc(). @@ -111151,7 +113495,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } } b = sqlite3BtreeSecureDelete(pBt, b); - returnSingleInt(v, "secure_delete", b); + returnSingleInt(v, b); break; } @@ -111183,8 +113527,6 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3AbsInt32(sqlite3Atoi(zRight))); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); break; } @@ -111230,7 +113572,7 @@ SQLITE_PRIVATE void sqlite3Pragma( if( eMode==PAGER_LOCKINGMODE_EXCLUSIVE ){ zRet = "exclusive"; } - returnSingleText(v, "locking_mode", zRet); + returnSingleText(v, zRet); break; } @@ -111243,7 +113585,6 @@ SQLITE_PRIVATE void sqlite3Pragma( int eMode; /* One of the PAGER_JOURNALMODE_XXX symbols */ int ii; /* Loop counter */ - setOneColumnName(v, "journal_mode"); if( zRight==0 ){ /* If there is no "=MODE" part of the pragma, do a query for the ** current mode */ @@ -111289,7 +113630,7 @@ SQLITE_PRIVATE void sqlite3Pragma( if( iLimit<-1 ) iLimit = -1; } iLimit = sqlite3PagerJournalSizeLimit(pPager, iLimit); - returnSingleInt(v, "journal_size_limit", iLimit); + returnSingleInt(v, iLimit); break; } @@ -111307,7 +113648,7 @@ SQLITE_PRIVATE void sqlite3Pragma( Btree *pBt = pDb->pBt; assert( pBt!=0 ); if( !zRight ){ - returnSingleInt(v, "auto_vacuum", sqlite3BtreeGetAutoVacuum(pBt)); + returnSingleInt(v, sqlite3BtreeGetAutoVacuum(pBt)); }else{ int eAuto = getAutoVacuum(zRight); assert( eAuto>=0 && eAuto<=2 ); @@ -111386,7 +113727,7 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_CACHE_SIZE: { assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ - returnSingleInt(v, "cache_size", pDb->pSchema->cache_size); + returnSingleInt(v, pDb->pSchema->cache_size); }else{ int size = sqlite3Atoi(zRight); pDb->pSchema->cache_size = size; @@ -111420,7 +113761,7 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_CACHE_SPILL: { assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ - returnSingleInt(v, "cache_spill", + returnSingleInt(v, (db->flags & SQLITE_CacheSpill)==0 ? 0 : sqlite3BtreeSetSpillSize(pDb->pBt,0)); }else{ @@ -111474,7 +113815,7 @@ SQLITE_PRIVATE void sqlite3Pragma( rc = SQLITE_OK; #endif if( rc==SQLITE_OK ){ - returnSingleInt(v, "mmap_size", sz); + returnSingleInt(v, sz); }else if( rc!=SQLITE_NOTFOUND ){ pParse->nErr++; pParse->rc = rc; @@ -111495,7 +113836,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_TEMP_STORE: { if( !zRight ){ - returnSingleInt(v, "temp_store", db->temp_store); + returnSingleInt(v, db->temp_store); }else{ changeTempStorage(pParse, zRight); } @@ -111514,7 +113855,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_TEMP_STORE_DIRECTORY: { if( !zRight ){ - returnSingleText(v, "temp_store_directory", sqlite3_temp_directory); + returnSingleText(v, sqlite3_temp_directory); }else{ #ifndef SQLITE_OMIT_WSD if( zRight[0] ){ @@ -111558,7 +113899,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_DATA_STORE_DIRECTORY: { if( !zRight ){ - returnSingleText(v, "data_store_directory", sqlite3_data_directory); + returnSingleText(v, sqlite3_data_directory); }else{ #ifndef SQLITE_OMIT_WSD if( zRight[0] ){ @@ -111597,7 +113938,7 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3_file *pFile = sqlite3PagerFile(pPager); sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, &proxy_file_path); - returnSingleText(v, "lock_proxy_file", proxy_file_path); + returnSingleText(v, proxy_file_path); }else{ Pager *pPager = sqlite3BtreePager(pDb->pBt); sqlite3_file *pFile = sqlite3PagerFile(pPager); @@ -111629,12 +113970,12 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_SYNCHRONOUS: { if( !zRight ){ - returnSingleInt(v, "synchronous", pDb->safety_level-1); + returnSingleInt(v, pDb->safety_level-1); }else{ if( !db->autoCommit ){ sqlite3ErrorMsg(pParse, "Safety level may not be changed inside a transaction"); - }else{ + }else if( iDb!=1 ){ int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK; if( iLevel==0 ) iLevel = 1; pDb->safety_level = iLevel; @@ -111649,7 +113990,8 @@ SQLITE_PRIVATE void sqlite3Pragma( #ifndef SQLITE_OMIT_FLAG_PRAGMAS case PragTyp_FLAG: { if( zRight==0 ){ - returnSingleInt(v, pPragma->zName, (db->flags & pPragma->iArg)!=0 ); + setPragmaResultColumnNames(v, pPragma); + returnSingleInt(v, (db->flags & pPragma->iArg)!=0 ); }else{ int mask = pPragma->iArg; /* Mask of bits to set or clear. */ if( db->autoCommit==0 ){ @@ -111699,16 +114041,12 @@ SQLITE_PRIVATE void sqlite3Pragma( Table *pTab; pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); if( pTab ){ - static const char *azCol[] = { - "cid", "name", "type", "notnull", "dflt_value", "pk" - }; int i, k; int nHidden = 0; Column *pCol; Index *pPk = sqlite3PrimaryKeyIndex(pTab); pParse->nMem = 6; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 6, azCol); assert( 6==ArraySize(azCol) ); sqlite3ViewGetColumnNames(pParse, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ if( IsHiddenColumn(pCol) ){ @@ -111736,41 +114074,39 @@ SQLITE_PRIVATE void sqlite3Pragma( } break; +#ifdef SQLITE_DEBUG case PragTyp_STATS: { - static const char *azCol[] = { "table", "index", "width", "height" }; Index *pIdx; HashElem *i; - v = sqlite3GetVdbe(pParse); - pParse->nMem = 4; + pParse->nMem = 5; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) ); for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); - sqlite3VdbeMultiLoad(v, 1, "ssii", + sqlite3VdbeMultiLoad(v, 1, "ssiii", pTab->zName, 0, pTab->szTabRow, - pTab->nRowLogEst); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); + pTab->nRowLogEst, + pTab->tabFlags); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - sqlite3VdbeMultiLoad(v, 2, "sii", + sqlite3VdbeMultiLoad(v, 2, "siii", pIdx->zName, pIdx->szIdxRow, - pIdx->aiRowLogEst[0]); - sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 4); + pIdx->aiRowLogEst[0], + pIdx->hasStat1); + sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 5); } } } break; +#endif case PragTyp_INDEX_INFO: if( zRight ){ Index *pIdx; Table *pTab; pIdx = sqlite3FindIndex(db, zRight, zDb); if( pIdx ){ - static const char *azCol[] = { - "seqno", "cid", "name", "desc", "coll", "key" - }; int i; int mx; if( pPragma->iArg ){ @@ -111784,8 +114120,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } pTab = pIdx->pTable; sqlite3CodeVerifySchema(pParse, iDb); - assert( pParse->nMem<=ArraySize(azCol) ); - setAllColumnNames(v, pParse->nMem, azCol); + assert( pParse->nMem<=pPragma->nPragCName ); for(i=0; iaiColumn[i]; sqlite3VdbeMultiLoad(v, 1, "iis", i, cnum, @@ -111808,13 +114143,8 @@ SQLITE_PRIVATE void sqlite3Pragma( int i; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ - static const char *azCol[] = { - "seq", "name", "unique", "origin", "partial" - }; - v = sqlite3GetVdbe(pParse); pParse->nMem = 5; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 5, azCol); assert( 5==ArraySize(azCol) ); for(pIdx=pTab->pIndex, i=0; pIdx; pIdx=pIdx->pNext, i++){ const char *azOrigin[] = { "c", "u", "pk" }; sqlite3VdbeMultiLoad(v, 1, "isisi", @@ -111830,16 +114160,14 @@ SQLITE_PRIVATE void sqlite3Pragma( break; case PragTyp_DATABASE_LIST: { - static const char *azCol[] = { "seq", "name", "file" }; int i; pParse->nMem = 3; - setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) ); for(i=0; inDb; i++){ if( db->aDb[i].pBt==0 ) continue; - assert( db->aDb[i].zName!=0 ); + assert( db->aDb[i].zDbSName!=0 ); sqlite3VdbeMultiLoad(v, 1, "iss", i, - db->aDb[i].zName, + db->aDb[i].zDbSName, sqlite3BtreeGetFilename(db->aDb[i].pBt)); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); } @@ -111847,11 +114175,9 @@ SQLITE_PRIVATE void sqlite3Pragma( break; case PragTyp_COLLATION_LIST: { - static const char *azCol[] = { "seq", "name" }; int i = 0; HashElem *p; pParse->nMem = 2; - setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) ); for(p=sqliteHashFirst(&db->aCollSeq); p; p=sqliteHashNext(p)){ CollSeq *pColl = (CollSeq *)sqliteHashData(p); sqlite3VdbeMultiLoad(v, 1, "is", i++, pColl->zName); @@ -111867,17 +114193,11 @@ SQLITE_PRIVATE void sqlite3Pragma( Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); if( pTab ){ - v = sqlite3GetVdbe(pParse); pFK = pTab->pFKey; if( pFK ){ - static const char *azCol[] = { - "id", "seq", "table", "from", "to", "on_update", "on_delete", - "match" - }; int i = 0; pParse->nMem = 8; sqlite3CodeVerifySchema(pParse, iDb); - setAllColumnNames(v, 8, azCol); assert( 8==ArraySize(azCol) ); while(pFK){ int j; for(j=0; jnCol; j++){ @@ -111918,14 +114238,11 @@ SQLITE_PRIVATE void sqlite3Pragma( int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ int *aiCols; /* child to parent column mapping */ - static const char *azCol[] = { "table", "rowid", "parent", "fkid" }; regResult = pParse->nMem+1; pParse->nMem += 4; regKey = ++pParse->nMem; regRow = ++pParse->nMem; - v = sqlite3GetVdbe(pParse); - setAllColumnNames(v, 4, azCol); assert( 4==ArraySize(azCol) ); sqlite3CodeVerifySchema(pParse, iDb); k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); while( k ){ @@ -112040,9 +114357,17 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif #ifndef SQLITE_OMIT_INTEGRITY_CHECK - /* Pragma "quick_check" is reduced version of + /* PRAGMA integrity_check + ** PRAGMA integrity_check(N) + ** PRAGMA quick_check + ** PRAGMA quick_check(N) + ** + ** Verify the integrity of the database. + ** + ** The "quick_check" is reduced version of ** integrity_check designed to detect most database corruption - ** without most of the overhead of a full integrity-check. + ** without the overhead of cross-checking indexes. Quick_check + ** is linear time wherease integrity_check is O(NlogN). */ case PragTyp_INTEGRITY_CHECK: { int i, j, addr, mxErr; @@ -112064,7 +114389,6 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Initialize the VDBE program */ pParse->nMem = 6; - setOneColumnName(v, "integrity_check"); /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; @@ -112074,7 +114398,7 @@ SQLITE_PRIVATE void sqlite3Pragma( mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; } } - sqlite3VdbeAddOp2(v, OP_Integer, mxErr, 1); /* reg[1] holds errors left */ + sqlite3VdbeAddOp2(v, OP_Integer, mxErr-1, 1); /* reg[1] holds errors left */ /* Do an integrity check on each database file */ for(i=0; inDb; i++){ @@ -112089,10 +114413,6 @@ SQLITE_PRIVATE void sqlite3Pragma( if( iDb>=0 && i!=iDb ) continue; sqlite3CodeVerifySchema(pParse, i); - addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Halt if out of errors */ - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); - sqlite3VdbeJumpHere(v, addr); /* Do an integrity check of the B-Tree ** @@ -112128,16 +114448,16 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeChangeP5(v, (u8)i); addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, - sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zName), + sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), P4_DYNAMIC); sqlite3VdbeAddOp3(v, OP_Move, 2, 4, 1); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 2); - sqlite3VdbeAddOp2(v, OP_ResultRow, 2, 1); + integrityCheckResultRow(v, 2); sqlite3VdbeJumpHere(v, addr); /* Make sure all the indices are constructed correctly. */ - for(x=sqliteHashFirst(pTbls); x && !isQuick; x=sqliteHashNext(x)){ + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx, *pPk; Index *pPrior = 0; @@ -112145,12 +114465,14 @@ SQLITE_PRIVATE void sqlite3Pragma( int iDataCur, iIdxCur; int r1 = -1; - if( pTab->pIndex==0 ) continue; + if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ + if( pTab->pCheck==0 + && (pTab->tabFlags & TF_HasNotNull)==0 + && (pTab->pIndex==0 || isQuick) + ){ + continue; /* No additional checks needed for this table */ + } pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); - addr = sqlite3VdbeAddOp1(v, OP_IfPos, 1); /* Stop if out of errors */ - VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); - sqlite3VdbeJumpHere(v, addr); sqlite3ExprCacheClear(pParse); sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); @@ -112165,24 +114487,42 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Verify that all NOT NULL columns really are NOT NULL */ for(j=0; jnCol; j++){ char *zErr; - int jmp2, jmp3; + int jmp2; if( j==pTab->iPKey ) continue; if( pTab->aCol[j].notNull==0 ) continue; sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, pTab->aCol[j].zName); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); - sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); - jmp3 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); - sqlite3VdbeAddOp0(v, OP_Halt); + integrityCheckResultRow(v, 3); sqlite3VdbeJumpHere(v, jmp2); - sqlite3VdbeJumpHere(v, jmp3); + } + /* Verify CHECK constraints */ + if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ + int addrCkFault = sqlite3VdbeMakeLabel(v); + int addrCkOk = sqlite3VdbeMakeLabel(v); + ExprList *pCheck = pTab->pCheck; + char *zErr; + int k; + pParse->iSelfTab = iDataCur; + sqlite3ExprCachePush(pParse); + for(k=pCheck->nExpr-1; k>0; k--){ + sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); + } + sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, + SQLITE_JUMPIFNULL); + sqlite3VdbeResolveLabel(v, addrCkFault); + zErr = sqlite3MPrintf(db, "CHECK constraint failed in %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v, 3); + sqlite3VdbeResolveLabel(v, addrCkOk); + sqlite3ExprCachePop(pParse); } /* Validate index entries for the current row */ - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + for(j=0, pIdx=pTab->pIndex; pIdx && !isQuick; pIdx=pIdx->pNext, j++){ int jmp2, jmp3, jmp4, jmp5; int ckUniq = sqlite3VdbeMakeLabel(v); if( pPk==pIdx ) continue; @@ -112193,16 +114533,13 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Verify that an index entry exists for the current table row */ jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, pIdx->nColumn); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ sqlite3VdbeLoadString(v, 3, "row "); sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); sqlite3VdbeLoadString(v, 4, " missing from index "); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); jmp5 = sqlite3VdbeLoadString(v, 4, pIdx->zName); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); - sqlite3VdbeAddOp2(v, OP_ResultRow, 3, 1); - jmp4 = sqlite3VdbeAddOp1(v, OP_IfPos, 1); VdbeCoverage(v); - sqlite3VdbeAddOp0(v, OP_Halt); + jmp4 = integrityCheckResultRow(v, 3); sqlite3VdbeJumpHere(v, jmp2); /* For UNIQUE indexes, verify that only one entry exists with the ** current key. The entry is unique if (1) any column is NULL @@ -112223,7 +114560,6 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeJumpHere(v, jmp6); sqlite3VdbeAddOp4Int(v, OP_IdxGT, iIdxCur+j, uniqOk, r1, pIdx->nKeyCol); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); /* Decrement error limit */ sqlite3VdbeLoadString(v, 3, "non-unique entry in index "); sqlite3VdbeGoto(v, jmp5); sqlite3VdbeResolveLabel(v, uniqOk); @@ -112234,19 +114570,18 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); sqlite3VdbeJumpHere(v, loopTop-1); #ifndef SQLITE_OMIT_BTREECOUNT - sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - if( pPk==pIdx ) continue; - addr = sqlite3VdbeCurrentAddr(v); - sqlite3VdbeAddOp2(v, OP_IfPos, 1, addr+2); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_Halt, 0, 0); - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); - sqlite3VdbeAddOp3(v, OP_Eq, 8+j, addr+8, 3); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); - sqlite3VdbeAddOp2(v, OP_AddImm, 1, -1); - sqlite3VdbeLoadString(v, 3, pIdx->zName); - sqlite3VdbeAddOp3(v, OP_Concat, 3, 2, 7); - sqlite3VdbeAddOp2(v, OP_ResultRow, 7, 1); + if( !isQuick ){ + sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); + for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + if( pPk==pIdx ) continue; + sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); + addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+j, 0, 3); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + sqlite3VdbeLoadString(v, 3, pIdx->zName); + sqlite3VdbeAddOp3(v, OP_Concat, 3, 2, 7); + integrityCheckResultRow(v, 7); + sqlite3VdbeJumpHere(v, addr); + } } #endif /* SQLITE_OMIT_BTREECOUNT */ } @@ -112255,7 +114590,7 @@ SQLITE_PRIVATE void sqlite3Pragma( static const int iLn = VDBE_OFFSET_LINENO(2); static const VdbeOpList endCode[] = { { OP_AddImm, 1, 0, 0}, /* 0 */ - { OP_If, 1, 4, 0}, /* 1 */ + { OP_IfNotZero, 1, 4, 0}, /* 1 */ { OP_String8, 0, 3, 0}, /* 2 */ { OP_ResultRow, 3, 1, 0}, /* 3 */ }; @@ -112263,7 +114598,7 @@ SQLITE_PRIVATE void sqlite3Pragma( aOp = sqlite3VdbeAddOpList(v, ArraySize(endCode), endCode, iLn); if( aOp ){ - aOp[0].p2 = -mxErr; + aOp[0].p2 = 1-mxErr; aOp[2].p4type = P4_STATIC; aOp[2].p4.z = "ok"; } @@ -112316,7 +114651,7 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( encnames[SQLITE_UTF8].enc==SQLITE_UTF8 ); assert( encnames[SQLITE_UTF16LE].enc==SQLITE_UTF16LE ); assert( encnames[SQLITE_UTF16BE].enc==SQLITE_UTF16BE ); - returnSingleText(v, "encoding", encnames[ENC(pParse->db)].zName); + returnSingleText(v, encnames[ENC(pParse->db)].zName); }else{ /* "PRAGMA encoding = XXX" */ /* Only change the value of sqlite.enc if the database handle is not ** initialized. If the main database exists, the new sqlite.enc value @@ -112379,7 +114714,7 @@ SQLITE_PRIVATE void sqlite3Pragma( case PragTyp_HEADER_VALUE: { int iCookie = pPragma->iArg; /* Which cookie to read or write */ sqlite3VdbeUsesBtree(v, iDb); - if( zRight && (pPragma->mPragFlag & PragFlag_ReadOnly)==0 ){ + if( zRight && (pPragma->mPragFlg & PragFlg_ReadOnly)==0 ){ /* Write the specified cookie value */ static const VdbeOpList setCookie[] = { { OP_Transaction, 0, 1, 0}, /* 0 */ @@ -112407,8 +114742,6 @@ SQLITE_PRIVATE void sqlite3Pragma( aOp[0].p1 = iDb; aOp[1].p1 = iDb; aOp[1].p3 = iCookie; - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zLeft, SQLITE_TRANSIENT); sqlite3VdbeReusable(v); } } @@ -112426,7 +114759,6 @@ SQLITE_PRIVATE void sqlite3Pragma( int i = 0; const char *zOpt; pParse->nMem = 1; - setOneColumnName(v, "compile_option"); while( (zOpt = sqlite3_compileoption_get(i++))!=0 ){ sqlite3VdbeLoadString(v, 1, zOpt); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 1); @@ -112443,7 +114775,6 @@ SQLITE_PRIVATE void sqlite3Pragma( ** Checkpoint the database. */ case PragTyp_WAL_CHECKPOINT: { - static const char *azCol[] = { "busy", "log", "checkpointed" }; int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED); int eMode = SQLITE_CHECKPOINT_PASSIVE; if( zRight ){ @@ -112455,7 +114786,6 @@ SQLITE_PRIVATE void sqlite3Pragma( eMode = SQLITE_CHECKPOINT_TRUNCATE; } } - setAllColumnNames(v, 3, azCol); assert( 3==ArraySize(azCol) ); pParse->nMem = 3; sqlite3VdbeAddOp3(v, OP_Checkpoint, iBt, eMode, 1); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 3); @@ -112474,7 +114804,7 @@ SQLITE_PRIVATE void sqlite3Pragma( if( zRight ){ sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, "wal_autocheckpoint", + returnSingleInt(v, db->xWalCallback==sqlite3WalDefaultHook ? SQLITE_PTR_TO_INT(db->pWalArg) : 0); } @@ -112493,6 +114823,118 @@ SQLITE_PRIVATE void sqlite3Pragma( break; } + /* + ** PRAGMA optimize + ** PRAGMA optimize(MASK) + ** PRAGMA schema.optimize + ** PRAGMA schema.optimize(MASK) + ** + ** Attempt to optimize the database. All schemas are optimized in the first + ** two forms, and only the specified schema is optimized in the latter two. + ** + ** The details of optimizations performed by this pragma are expected + ** to change and improve over time. Applications should anticipate that + ** this pragma will perform new optimizations in future releases. + ** + ** The optional argument is a bitmask of optimizations to perform: + ** + ** 0x0001 Debugging mode. Do not actually perform any optimizations + ** but instead return one line of text for each optimization + ** that would have been done. Off by default. + ** + ** 0x0002 Run ANALYZE on tables that might benefit. On by default. + ** See below for additional information. + ** + ** 0x0004 (Not yet implemented) Record usage and performance + ** information from the current session in the + ** database file so that it will be available to "optimize" + ** pragmas run by future database connections. + ** + ** 0x0008 (Not yet implemented) Create indexes that might have + ** been helpful to recent queries + ** + ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all ** of the optimizations listed above except Debug Mode, including new + ** optimizations that have not yet been invented. If new optimizations are + ** ever added that should be off by default, those off-by-default + ** optimizations will have bitmasks of 0x10000 or larger. + ** + ** DETERMINATION OF WHEN TO RUN ANALYZE + ** + ** In the current implementation, a table is analyzed if only if all of + ** the following are true: + ** + ** (1) MASK bit 0x02 is set. + ** + ** (2) The query planner used sqlite_stat1-style statistics for one or + ** more indexes of the table at some point during the lifetime of + ** the current connection. + ** + ** (3) One or more indexes of the table are currently unanalyzed OR + ** the number of rows in the table has increased by 25 times or more + ** since the last time ANALYZE was run. + ** + ** The rules for when tables are analyzed are likely to change in + ** future releases. + */ + case PragTyp_OPTIMIZE: { + int iDbLast; /* Loop termination point for the schema loop */ + int iTabCur; /* Cursor for a table whose size needs checking */ + HashElem *k; /* Loop over tables of a schema */ + Schema *pSchema; /* The current schema */ + Table *pTab; /* A table in the schema */ + Index *pIdx; /* An index of the table */ + LogEst szThreshold; /* Size threshold above which reanalysis is needd */ + char *zSubSql; /* SQL statement for the OP_SqlExec opcode */ + u32 opMask; /* Mask of operations to perform */ + + if( zRight ){ + opMask = (u32)sqlite3Atoi(zRight); + if( (opMask & 0x02)==0 ) break; + }else{ + opMask = 0xfffe; + } + iTabCur = pParse->nTab++; + for(iDbLast = zDb?iDb:db->nDb-1; iDb<=iDbLast; iDb++){ + if( iDb==1 ) continue; + sqlite3CodeVerifySchema(pParse, iDb); + pSchema = db->aDb[iDb].pSchema; + for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ + pTab = (Table*)sqliteHashData(k); + + /* If table pTab has not been used in a way that would benefit from + ** having analysis statistics during the current session, then skip it. + ** This also has the effect of skipping virtual tables and views */ + if( (pTab->tabFlags & TF_StatsUsed)==0 ) continue; + + /* Reanalyze if the table is 25 times larger than the last analysis */ + szThreshold = pTab->nRowLogEst + 46; assert( sqlite3LogEst(25)==46 ); + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( !pIdx->hasStat1 ){ + szThreshold = 0; /* Always analyze if any index lacks statistics */ + break; + } + } + if( szThreshold ){ + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold); + VdbeCoverage(v); + } + zSubSql = sqlite3MPrintf(db, "ANALYZE \"%w\".\"%w\"", + db->aDb[iDb].zDbSName, pTab->zName); + if( opMask & 0x01 ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4(v, OP_String8, 0, r1, 0, zSubSql, P4_DYNAMIC); + sqlite3VdbeAddOp2(v, OP_ResultRow, r1, 1); + }else{ + sqlite3VdbeAddOp4(v, OP_SqlExec, 0, 0, 0, zSubSql, P4_DYNAMIC); + } + } + } + sqlite3VdbeAddOp0(v, OP_Expire); + break; + } + /* ** PRAGMA busy_timeout ** PRAGMA busy_timeout = N @@ -112507,7 +114949,7 @@ SQLITE_PRIVATE void sqlite3Pragma( if( zRight ){ sqlite3_busy_timeout(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, "timeout", db->busyTimeout); + returnSingleInt(v, db->busyTimeout); break; } @@ -112527,7 +114969,7 @@ SQLITE_PRIVATE void sqlite3Pragma( if( zRight && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK ){ sqlite3_soft_heap_limit64(N); } - returnSingleInt(v, "soft_heap_limit", sqlite3_soft_heap_limit64(-1)); + returnSingleInt(v, sqlite3_soft_heap_limit64(-1)); break; } @@ -112546,8 +114988,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ){ sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, (int)(N&0x7fffffff)); } - returnSingleInt(v, "threads", - sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); + returnSingleInt(v, sqlite3_limit(db, SQLITE_LIMIT_WORKER_THREADS, -1)); break; } @@ -112559,23 +115000,21 @@ SQLITE_PRIVATE void sqlite3Pragma( static const char *const azLockName[] = { "unlocked", "shared", "reserved", "pending", "exclusive" }; - static const char *azCol[] = { "database", "status" }; int i; - setAllColumnNames(v, 2, azCol); assert( 2==ArraySize(azCol) ); pParse->nMem = 2; for(i=0; inDb; i++){ Btree *pBt; const char *zState = "unknown"; int j; - if( db->aDb[i].zName==0 ) continue; + if( db->aDb[i].zDbSName==0 ) continue; pBt = db->aDb[i].pBt; if( pBt==0 || sqlite3BtreePager(pBt)==0 ){ zState = "closed"; - }else if( sqlite3_file_control(db, i ? db->aDb[i].zName : 0, + }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){ zState = azLockName[j]; } - sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zName, zState); + sqlite3VdbeMultiLoad(v, 1, "ss", db->aDb[i].zDbSName, zState); sqlite3VdbeAddOp2(v, OP_ResultRow, 1, 2); } break; @@ -112627,10 +115066,325 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* End of the PRAGMA switch */ + /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only + ** purpose is to execute assert() statements to verify that if the + ** PragFlg_NoColumns1 flag is set and the caller specified an argument + ** to the PRAGMA, the implementation has not added any OP_ResultRow + ** instructions to the VM. */ + if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){ + sqlite3VdbeVerifyNoResultRow(v); + } + pragma_out: sqlite3DbFree(db, zLeft); sqlite3DbFree(db, zRight); } +#ifndef SQLITE_OMIT_VIRTUALTABLE +/***************************************************************************** +** Implementation of an eponymous virtual table that runs a pragma. +** +*/ +typedef struct PragmaVtab PragmaVtab; +typedef struct PragmaVtabCursor PragmaVtabCursor; +struct PragmaVtab { + sqlite3_vtab base; /* Base class. Must be first */ + sqlite3 *db; /* The database connection to which it belongs */ + const PragmaName *pName; /* Name of the pragma */ + u8 nHidden; /* Number of hidden columns */ + u8 iHidden; /* Index of the first hidden column */ +}; +struct PragmaVtabCursor { + sqlite3_vtab_cursor base; /* Base class. Must be first */ + sqlite3_stmt *pPragma; /* The pragma statement to run */ + sqlite_int64 iRowid; /* Current rowid */ + char *azArg[2]; /* Value of the argument and schema */ +}; + +/* +** Pragma virtual table module xConnect method. +*/ +static int pragmaVtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + const PragmaName *pPragma = (const PragmaName*)pAux; + PragmaVtab *pTab = 0; + int rc; + int i, j; + char cSep = '('; + StrAccum acc; + char zBuf[200]; + + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3StrAccumAppendAll(&acc, "CREATE TABLE x"); + for(i=0, j=pPragma->iPragCName; inPragCName; i++, j++){ + sqlite3XPrintf(&acc, "%c\"%s\"", cSep, pragCName[j]); + cSep = ','; + } + if( i==0 ){ + sqlite3XPrintf(&acc, "(\"%s\"", pPragma->zName); + cSep = ','; + i++; + } + j = 0; + if( pPragma->mPragFlg & PragFlg_Result1 ){ + sqlite3StrAccumAppendAll(&acc, ",arg HIDDEN"); + j++; + } + if( pPragma->mPragFlg & (PragFlg_SchemaOpt|PragFlg_SchemaReq) ){ + sqlite3StrAccumAppendAll(&acc, ",schema HIDDEN"); + j++; + } + sqlite3StrAccumAppend(&acc, ")", 1); + sqlite3StrAccumFinish(&acc); + assert( strlen(zBuf) < sizeof(zBuf)-1 ); + rc = sqlite3_declare_vtab(db, zBuf); + if( rc==SQLITE_OK ){ + pTab = (PragmaVtab*)sqlite3_malloc(sizeof(PragmaVtab)); + if( pTab==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(pTab, 0, sizeof(PragmaVtab)); + pTab->pName = pPragma; + pTab->db = db; + pTab->iHidden = i; + pTab->nHidden = j; + } + }else{ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + + *ppVtab = (sqlite3_vtab*)pTab; + return rc; +} + +/* +** Pragma virtual table module xDisconnect method. +*/ +static int pragmaVtabDisconnect(sqlite3_vtab *pVtab){ + PragmaVtab *pTab = (PragmaVtab*)pVtab; + sqlite3_free(pTab); + return SQLITE_OK; +} + +/* Figure out the best index to use to search a pragma virtual table. +** +** There are not really any index choices. But we want to encourage the +** query planner to give == constraints on as many hidden parameters as +** possible, and especially on the first hidden parameter. So return a +** high cost if hidden parameters are unconstrained. +*/ +static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ + PragmaVtab *pTab = (PragmaVtab*)tab; + const struct sqlite3_index_constraint *pConstraint; + int i, j; + int seen[2]; + + pIdxInfo->estimatedCost = (double)1; + if( pTab->nHidden==0 ){ return SQLITE_OK; } + pConstraint = pIdxInfo->aConstraint; + seen[0] = 0; + seen[1] = 0; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn < pTab->iHidden ) continue; + j = pConstraint->iColumn - pTab->iHidden; + assert( j < 2 ); + seen[j] = i+1; + } + if( seen[0]==0 ){ + pIdxInfo->estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + return SQLITE_OK; + } + j = seen[0]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 1; + pIdxInfo->aConstraintUsage[j].omit = 1; + if( seen[1]==0 ) return SQLITE_OK; + pIdxInfo->estimatedCost = (double)20; + pIdxInfo->estimatedRows = 20; + j = seen[1]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 2; + pIdxInfo->aConstraintUsage[j].omit = 1; + return SQLITE_OK; +} + +/* Create a new cursor for the pragma virtual table */ +static int pragmaVtabOpen(sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppCursor){ + PragmaVtabCursor *pCsr; + pCsr = (PragmaVtabCursor*)sqlite3_malloc(sizeof(*pCsr)); + if( pCsr==0 ) return SQLITE_NOMEM; + memset(pCsr, 0, sizeof(PragmaVtabCursor)); + pCsr->base.pVtab = pVtab; + *ppCursor = &pCsr->base; + return SQLITE_OK; +} + +/* Clear all content from pragma virtual table cursor. */ +static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ + int i; + sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + for(i=0; iazArg); i++){ + sqlite3_free(pCsr->azArg[i]); + pCsr->azArg[i] = 0; + } +} + +/* Close a pragma virtual table cursor */ +static int pragmaVtabClose(sqlite3_vtab_cursor *cur){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)cur; + pragmaVtabCursorClear(pCsr); + sqlite3_free(pCsr); + return SQLITE_OK; +} + +/* Advance the pragma virtual table cursor to the next row */ +static int pragmaVtabNext(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + int rc = SQLITE_OK; + + /* Increment the xRowid value */ + pCsr->iRowid++; + assert( pCsr->pPragma ); + if( SQLITE_ROW!=sqlite3_step(pCsr->pPragma) ){ + rc = sqlite3_finalize(pCsr->pPragma); + pCsr->pPragma = 0; + pragmaVtabCursorClear(pCsr); + } + return rc; +} + +/* +** Pragma virtual table module xFilter method. +*/ +static int pragmaVtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + int rc; + int i, j; + StrAccum acc; + char *zSql; + + UNUSED_PARAMETER(idxNum); + UNUSED_PARAMETER(idxStr); + pragmaVtabCursorClear(pCsr); + j = (pTab->pName->mPragFlg & PragFlg_Result1)!=0 ? 0 : 1; + for(i=0; iazArg) ); + pCsr->azArg[j] = sqlite3_mprintf("%s", sqlite3_value_text(argv[i])); + if( pCsr->azArg[j]==0 ){ + return SQLITE_NOMEM; + } + } + sqlite3StrAccumInit(&acc, 0, 0, 0, pTab->db->aLimit[SQLITE_LIMIT_SQL_LENGTH]); + sqlite3StrAccumAppendAll(&acc, "PRAGMA "); + if( pCsr->azArg[1] ){ + sqlite3XPrintf(&acc, "%Q.", pCsr->azArg[1]); + } + sqlite3StrAccumAppendAll(&acc, pTab->pName->zName); + if( pCsr->azArg[0] ){ + sqlite3XPrintf(&acc, "=%Q", pCsr->azArg[0]); + } + zSql = sqlite3StrAccumFinish(&acc); + if( zSql==0 ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(pTab->db, zSql, -1, &pCsr->pPragma, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK ){ + pTab->base.zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pTab->db)); + return rc; + } + return pragmaVtabNext(pVtabCursor); +} + +/* +** Pragma virtual table module xEof method. +*/ +static int pragmaVtabEof(sqlite3_vtab_cursor *pVtabCursor){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + return (pCsr->pPragma==0); +} + +/* The xColumn method simply returns the corresponding column from +** the PRAGMA. +*/ +static int pragmaVtabColumn( + sqlite3_vtab_cursor *pVtabCursor, + sqlite3_context *ctx, + int i +){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + PragmaVtab *pTab = (PragmaVtab*)(pVtabCursor->pVtab); + if( iiHidden ){ + sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pPragma, i)); + }else{ + sqlite3_result_text(ctx, pCsr->azArg[i-pTab->iHidden],-1,SQLITE_TRANSIENT); + } + return SQLITE_OK; +} + +/* +** Pragma virtual table module xRowid method. +*/ +static int pragmaVtabRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){ + PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; + *p = pCsr->iRowid; + return SQLITE_OK; +} + +/* The pragma virtual table object */ +static const sqlite3_module pragmaVtabModule = { + 0, /* iVersion */ + 0, /* xCreate - create a table */ + pragmaVtabConnect, /* xConnect - connect to an existing table */ + pragmaVtabBestIndex, /* xBestIndex - Determine search strategy */ + pragmaVtabDisconnect, /* xDisconnect - Disconnect from a table */ + 0, /* xDestroy - Drop a table */ + pragmaVtabOpen, /* xOpen - open a cursor */ + pragmaVtabClose, /* xClose - close a cursor */ + pragmaVtabFilter, /* xFilter - configure scan constraints */ + pragmaVtabNext, /* xNext - advance a cursor */ + pragmaVtabEof, /* xEof */ + pragmaVtabColumn, /* xColumn - read data */ + pragmaVtabRowid, /* xRowid - read data */ + 0, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0, /* xRename - rename the table */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0 /* xRollbackTo */ +}; + +/* +** Check to see if zTabName is really the name of a pragma. If it is, +** then register an eponymous virtual table for that pragma and return +** a pointer to the Module object for the new virtual table. +*/ +SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3 *db, const char *zName){ + const PragmaName *pName; + assert( sqlite3_strnicmp(zName, "pragma_", 7)==0 ); + pName = pragmaLocate(zName+7); + if( pName==0 ) return 0; + if( (pName->mPragFlg & (PragFlg_Result0|PragFlg_Result1))==0 ) return 0; + assert( sqlite3HashFind(&db->aModule, zName)==0 ); + return sqlite3VtabCreateModule(db, zName, &pragmaVtabModule, (void*)pName, 0); +} + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ #endif /* SQLITE_OMIT_PRAGMA */ @@ -112711,6 +115465,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char ** structures that describe the table, index, or view. */ int rc; + u8 saved_iDb = db->init.iDb; sqlite3_stmt *pStmt; TESTONLY(int rcp); /* Return code from sqlite3_prepare() */ @@ -112721,7 +115476,8 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0); rc = db->errCode; assert( (rc&0xFF)==(rcp&0xFF) ); - db->init.iDb = 0; + db->init.iDb = saved_iDb; + assert( saved_iDb==0 || (db->flags & SQLITE_Vacuum)!=0 ); if( SQLITE_OK!=rc ){ if( db->init.orphanTrigger ){ assert( iDb==1 ); @@ -112745,7 +115501,7 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char ** to do here is record the root page number for that index. */ Index *pIndex; - pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zName); + pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zDbSName); if( pIndex==0 ){ /* This can occur if there exists an index on a TEMP table which ** has the same name as another index on a permanent index. Since @@ -112924,7 +115680,7 @@ static int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg){ char *zSql; zSql = sqlite3MPrintf(db, "SELECT name, rootpage, sql FROM \"%w\".%s ORDER BY rowid", - db->aDb[iDb].zName, zMasterName); + db->aDb[iDb].zDbSName, zMasterName); #ifndef SQLITE_OMIT_AUTHORIZATION { sqlite3_xauth xAuth; @@ -113154,18 +115910,14 @@ static int sqlite3Prepare( sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ - Parse *pParse; /* Parsing context */ char *zErrMsg = 0; /* Error message */ int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ + Parse sParse; /* Parsing context */ - /* Allocate the parsing context */ - pParse = sqlite3StackAllocZero(db, sizeof(*pParse)); - if( pParse==0 ){ - rc = SQLITE_NOMEM_BKPT; - goto end_prepare; - } - pParse->pReprepare = pReprepare; + memset(&sParse, 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); + sParse.pReprepare = pReprepare; assert( ppStmt && *ppStmt==0 ); /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ assert( sqlite3_mutex_held(db->mutex) ); @@ -113199,7 +115951,7 @@ static int sqlite3Prepare( assert( sqlite3BtreeHoldsMutex(pBt) ); rc = sqlite3BtreeSchemaLocked(pBt); if( rc ){ - const char *zDb = db->aDb[i].zName; + const char *zDb = db->aDb[i].zDbSName; sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); testcase( db->flags & SQLITE_ReadUncommitted ); goto end_prepare; @@ -113209,8 +115961,7 @@ static int sqlite3Prepare( sqlite3VtabUnlockList(db); - pParse->db = db; - pParse->nQueryLoop = 0; /* Logarithmic, so 0 really means 1 */ + sParse.db = db; if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ char *zSqlCopy; int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; @@ -113223,61 +115974,61 @@ static int sqlite3Prepare( } zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ - sqlite3RunParser(pParse, zSqlCopy, &zErrMsg); - pParse->zTail = &zSql[pParse->zTail-zSqlCopy]; + sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg); + sParse.zTail = &zSql[sParse.zTail-zSqlCopy]; sqlite3DbFree(db, zSqlCopy); }else{ - pParse->zTail = &zSql[nBytes]; + sParse.zTail = &zSql[nBytes]; } }else{ - sqlite3RunParser(pParse, zSql, &zErrMsg); + sqlite3RunParser(&sParse, zSql, &zErrMsg); } - assert( 0==pParse->nQueryLoop ); + assert( 0==sParse.nQueryLoop ); - if( pParse->rc==SQLITE_DONE ) pParse->rc = SQLITE_OK; - if( pParse->checkSchema ){ - schemaIsValid(pParse); + if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK; + if( sParse.checkSchema ){ + schemaIsValid(&sParse); } if( db->mallocFailed ){ - pParse->rc = SQLITE_NOMEM_BKPT; + sParse.rc = SQLITE_NOMEM_BKPT; } if( pzTail ){ - *pzTail = pParse->zTail; + *pzTail = sParse.zTail; } - rc = pParse->rc; + rc = sParse.rc; #ifndef SQLITE_OMIT_EXPLAIN - if( rc==SQLITE_OK && pParse->pVdbe && pParse->explain ){ + if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){ static const char * const azColName[] = { "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", "selectid", "order", "from", "detail" }; int iFirst, mx; - if( pParse->explain==2 ){ - sqlite3VdbeSetNumCols(pParse->pVdbe, 4); + if( sParse.explain==2 ){ + sqlite3VdbeSetNumCols(sParse.pVdbe, 4); iFirst = 8; mx = 12; }else{ - sqlite3VdbeSetNumCols(pParse->pVdbe, 8); + sqlite3VdbeSetNumCols(sParse.pVdbe, 8); iFirst = 0; mx = 8; } for(i=iFirst; ipVdbe, i-iFirst, COLNAME_NAME, + sqlite3VdbeSetColName(sParse.pVdbe, i-iFirst, COLNAME_NAME, azColName[i], SQLITE_STATIC); } } #endif if( db->init.busy==0 ){ - Vdbe *pVdbe = pParse->pVdbe; - sqlite3VdbeSetSql(pVdbe, zSql, (int)(pParse->zTail-zSql), saveSqlFlag); + Vdbe *pVdbe = sParse.pVdbe; + sqlite3VdbeSetSql(pVdbe, zSql, (int)(sParse.zTail-zSql), saveSqlFlag); } - if( pParse->pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ - sqlite3VdbeFinalize(pParse->pVdbe); + if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ + sqlite3VdbeFinalize(sParse.pVdbe); assert(!(*ppStmt)); }else{ - *ppStmt = (sqlite3_stmt*)pParse->pVdbe; + *ppStmt = (sqlite3_stmt*)sParse.pVdbe; } if( zErrMsg ){ @@ -113288,16 +116039,15 @@ static int sqlite3Prepare( } /* Delete any TriggerPrg structures allocated while parsing this statement. */ - while( pParse->pTriggerPrg ){ - TriggerPrg *pT = pParse->pTriggerPrg; - pParse->pTriggerPrg = pT->pNext; + while( sParse.pTriggerPrg ){ + TriggerPrg *pT = sParse.pTriggerPrg; + sParse.pTriggerPrg = pT->pNext; sqlite3DbFree(db, pT); } end_prepare: - sqlite3ParserReset(pParse); - sqlite3StackFree(db, pParse); + sqlite3ParserReset(&sParse); rc = sqlite3ApiExit(db, rc); assert( (rc&db->errMask)==rc ); return rc; @@ -113585,7 +116335,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){ pDest->eDest = (u8)eDest; pDest->iSDParm = iParm; - pDest->affSdst = 0; + pDest->zAffSdst = 0; pDest->iSdst = 0; pDest->nSdst = 0; } @@ -113831,7 +116581,7 @@ static void addWhereTerm( pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft); pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); - pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2, 0); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); if( pEq && isOuterJoin ){ ExprSetProperty(pEq, EP_FromJoin); assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); @@ -114018,7 +116768,7 @@ static void pushOntoSorter( int iLimit; /* LIMIT counter */ assert( bSeq==0 || bSeq==1 ); - assert( nData==1 || regData==regOrigData ); + assert( nData==1 || regData==regOrigData || regOrigData==0 ); if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nExpr - bSeq; @@ -114030,11 +116780,11 @@ static void pushOntoSorter( iLimit = pSelect->iOffset ? pSelect->iOffset+1 : pSelect->iLimit; pSort->labelDone = sqlite3VdbeMakeLabel(v); sqlite3ExprCodeExprList(pParse, pSort->pOrderBy, regBase, regOrigData, - SQLITE_ECEL_DUP|SQLITE_ECEL_REF); + SQLITE_ECEL_DUP | (regOrigData? SQLITE_ECEL_REF : 0)); if( bSeq ){ sqlite3VdbeAddOp2(v, OP_Sequence, pSort->iECursor, regBase+nExpr); } - if( nPrefixReg==0 ){ + if( nPrefixReg==0 && nData>0 ){ sqlite3ExprCodeMove(pParse, regData, regBase+nExpr+bSeq, nData); } sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase+nOBSat, nBase-nOBSat, regRecord); @@ -114084,7 +116834,8 @@ static void pushOntoSorter( }else{ op = OP_IdxInsert; } - sqlite3VdbeAddOp2(v, op, pSort->iECursor, regRecord); + sqlite3VdbeAddOp4Int(v, op, pSort->iECursor, regRecord, + regBase+nOBSat, nBase-nOBSat); if( iLimit ){ int addr; int r1 = 0; @@ -114092,7 +116843,7 @@ static void pushOntoSorter( ** register is initialized with value of LIMIT+OFFSET.) After the sorter ** fills up, delete the least entry in the sorter after each insert. ** Thus we never hold more than the LIMIT+OFFSET rows in memory at once */ - addr = sqlite3VdbeAddOp3(v, OP_IfNotZero, iLimit, 0, 1); VdbeCoverage(v); + addr = sqlite3VdbeAddOp1(v, OP_IfNotZero, iLimit); VdbeCoverage(v); sqlite3VdbeAddOp1(v, OP_Last, pSort->iECursor); if( pSort->bOrderedInnerLoop ){ r1 = ++pParse->nMem; @@ -114152,34 +116903,11 @@ static void codeDistinct( r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iTab, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3ReleaseTempReg(pParse, r1); } -#ifndef SQLITE_OMIT_SUBQUERY -/* -** Generate an error message when a SELECT is used within a subexpression -** (example: "a IN (SELECT * FROM table)") but it has more than 1 result -** column. We do this in a subroutine because the error used to occur -** in multiple places. (The error only occurs in one place now, but we -** retain the subroutine to minimize code disruption.) -*/ -static int checkForMultiColumnSelectError( - Parse *pParse, /* Parse context. */ - SelectDest *pDest, /* Destination of SELECT results */ - int nExpr /* Number of result columns returned by SELECT */ -){ - int eDest = pDest->eDest; - if( nExpr>1 && (eDest==SRT_Mem || eDest==SRT_Set) ){ - sqlite3ErrorMsg(pParse, "only a single result allowed for " - "a SELECT that is part of an expression"); - return 1; - }else{ - return 0; - } -} -#endif - /* ** This routine generates the code for the inside of the inner loop ** of a SELECT. @@ -114187,7 +116915,7 @@ static int checkForMultiColumnSelectError( ** If srcTab is negative, then the pEList expressions ** are evaluated in order to get the data for this row. If srcTab is ** zero or more, then data is pulled from srcTab and pEList is used only -** to get number columns and the datatype for each column. +** to get the number of columns and the collation sequence for each column. */ static void selectInnerLoop( Parse *pParse, /* The parser context */ @@ -114202,13 +116930,20 @@ static void selectInnerLoop( ){ Vdbe *v = pParse->pVdbe; int i; - int hasDistinct; /* True if the DISTINCT keyword is present */ - int regResult; /* Start of memory holding result set */ + int hasDistinct; /* True if the DISTINCT keyword is present */ int eDest = pDest->eDest; /* How to dispose of results */ int iParm = pDest->iSDParm; /* First argument to disposal method */ int nResultCol; /* Number of result columns */ int nPrefixReg = 0; /* Number of extra registers before regResult */ + /* Usually, regResult is the first cell in an array of memory cells + ** containing the current result row. In this case regOrig is set to the + ** same value. However, if the results are being sent to the sorter, the + ** values for any expressions that are also part of the sort-key are omitted + ** from this array. In this case regOrig is set to zero. */ + int regResult; /* Start of memory holding current results */ + int regOrig; /* Start of memory holding full result (or 0) */ + assert( v ); assert( pEList!=0 ); hasDistinct = pDistinct ? pDistinct->eTnctType : WHERE_DISTINCT_NOOP; @@ -114239,7 +116974,7 @@ static void selectInnerLoop( pParse->nMem += nResultCol; } pDest->nSdst = nResultCol; - regResult = pDest->iSdst; + regOrig = regResult = pDest->iSdst; if( srcTab>=0 ){ for(i=0; ipOrderBy), set the associated + ** iOrderByCol value to one more than the index of the ORDER BY + ** expression within the sort-key that pushOntoSorter() will generate. + ** This allows the pEList field to be omitted from the sorted record, + ** saving space and CPU cycles. */ + ecelFlags |= (SQLITE_ECEL_OMITREF|SQLITE_ECEL_REF); + for(i=pSort->nOBSat; ipOrderBy->nExpr; i++){ + int j; + if( (j = pSort->pOrderBy->a[i].u.x.iOrderByCol)>0 ){ + pEList->a[j-1].u.x.iOrderByCol = i+1-pSort->nOBSat; + } + } + regOrig = 0; + assert( eDest==SRT_Set || eDest==SRT_Mem + || eDest==SRT_Coroutine || eDest==SRT_Output ); + } + nResultCol = sqlite3ExprCodeExprList(pParse,pEList,regResult,0,ecelFlags); } /* If the DISTINCT keyword was present on the SELECT statement @@ -114329,7 +117082,7 @@ static void selectInnerLoop( int r1; r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); sqlite3ReleaseTempReg(pParse, r1); break; } @@ -114366,7 +117119,7 @@ static void selectInnerLoop( int addr = sqlite3VdbeCurrentAddr(v) + 4; sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, addr, r1, 0); VdbeCoverage(v); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1,regResult,nResultCol); assert( pSort==0 ); } #endif @@ -114389,20 +117142,20 @@ static void selectInnerLoop( ** item into the set table with bogus data. */ case SRT_Set: { - assert( nResultCol==1 ); - pDest->affSdst = - sqlite3CompareAffinity(pEList->a[0].pExpr, pDest->affSdst); if( pSort ){ /* At first glance you would think we could optimize out the ** ORDER BY in this case since the order of entries in the set ** does not matter. But there might be a LIMIT clause, in which ** case the order does matter */ - pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else{ int r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult,1,r1, &pDest->affSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regResult, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + r1, pDest->zAffSdst, nResultCol); + sqlite3ExprCacheAffinityChange(pParse, regResult, nResultCol); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); sqlite3ReleaseTempReg(pParse, r1); } break; @@ -114417,14 +117170,16 @@ static void selectInnerLoop( } /* If this is a scalar select that is part of an expression, then - ** store the results in the appropriate memory cell and break out - ** of the scan loop. + ** store the results in the appropriate memory cell or array of + ** memory cells and break out of the scan loop. */ case SRT_Mem: { - assert( nResultCol==1 ); if( pSort ){ - pushOntoSorter(pParse, pSort, p, regResult, regResult, 1, nPrefixReg); + assert( nResultCol<=pDest->nSdst ); + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else{ + assert( nResultCol==pDest->nSdst ); assert( regResult==iParm ); /* The LIMIT clause will jump out of the loop for us */ } @@ -114437,7 +117192,7 @@ static void selectInnerLoop( testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); if( pSort ){ - pushOntoSorter(pParse, pSort, p, regResult, regResult, nResultCol, + pushOntoSorter(pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else if( eDest==SRT_Coroutine ){ sqlite3VdbeAddOp1(v, OP_Yield, pDest->iSDParm); @@ -114487,7 +117242,7 @@ static void selectInnerLoop( } sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); if( addrTest ) sqlite3VdbeJumpHere(v, addrTest); sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempRange(pParse, r2, nKey+2); @@ -114525,7 +117280,7 @@ static void selectInnerLoop( */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ int nExtra = (N+X)*(sizeof(CollSeq*)+1); - KeyInfo *p = sqlite3DbMallocRaw(db, sizeof(KeyInfo) + nExtra); + KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra); if( p ){ p->aSortOrder = (u8*)&p->aColl[N+X]; p->nField = (u16)N; @@ -114722,14 +117477,13 @@ static void generateSortTail( int iParm = pDest->iSDParm; int regRow; int regRowid; + int iCol; int nKey; int iSortTab; /* Sorter cursor to read from */ int nSortData; /* Trailing values to read from sorter */ int i; int bSeq; /* True if sorter record includes seq. no. */ -#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS struct ExprList_item *aOutEx = p->pEList->a; -#endif assert( addrBreak<0 ); if( pSort->labelBkOut ){ @@ -114738,21 +117492,21 @@ static void generateSortTail( sqlite3VdbeResolveLabel(v, pSort->labelBkOut); } iTab = pSort->iECursor; - if( eDest==SRT_Output || eDest==SRT_Coroutine ){ + if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ regRowid = 0; regRow = pDest->iSdst; nSortData = nColumn; }else{ regRowid = sqlite3GetTempReg(pParse); - regRow = sqlite3GetTempReg(pParse); - nSortData = 1; + regRow = sqlite3GetTempRange(pParse, nColumn); + nSortData = nColumn; } nKey = pOrderBy->nExpr - pSort->nOBSat; if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; iSortTab = pParse->nTab++; if( pSort->labelBkOut ){ - addrOnce = sqlite3CodeOnce(pParse); VdbeCoverage(v); + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nSortData); if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); @@ -114767,11 +117521,18 @@ static void generateSortTail( iSortTab = iTab; bSeq = 1; } - for(i=0; iaffSdst, 1); - sqlite3ExprCacheAffinityChange(pParse, regRow, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm, regRowid); + assert( nColumn==sqlite3Strlen30(pDest->zAffSdst) ); + sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, nColumn, regRowid, + pDest->zAffSdst, nColumn); + sqlite3ExprCacheAffinityChange(pParse, regRow, nColumn); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, regRowid, regRow, nColumn); break; } case SRT_Mem: { - assert( nColumn==1 ); - sqlite3ExprCodeMove(pParse, regRow, iParm, 1); /* The LIMIT clause will terminate the loop for us */ break; } @@ -114808,7 +117567,11 @@ static void generateSortTail( } } if( regRowid ){ - sqlite3ReleaseTempReg(pParse, regRow); + if( eDest==SRT_Set ){ + sqlite3ReleaseTempRange(pParse, regRow, nColumn); + }else{ + sqlite3ReleaseTempReg(pParse, regRow); + } sqlite3ReleaseTempReg(pParse, regRowid); } /* The bottom of the loop @@ -114955,7 +117718,7 @@ static const char *columnTypeImpl( zOrigTab = pTab->zName; if( pNC->pParse ){ int iDb = sqlite3SchemaToIndex(pNC->pParse->db, pTab->pSchema); - zOrigDb = pNC->pParse->db->aDb[iDb].zName; + zOrigDb = pNC->pParse->db->aDb[iDb].zDbSName; } #else if( iCol<0 ){ @@ -115291,7 +118054,7 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside ** is disabled */ assert( db->lookaside.bDisable ); - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); @@ -115310,7 +118073,7 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ */ static SQLITE_NOINLINE Vdbe *allocVdbe(Parse *pParse){ Vdbe *v = pParse->pVdbe = sqlite3VdbeCreate(pParse); - if( v ) sqlite3VdbeAddOp0(v, OP_Init); + if( v ) sqlite3VdbeAddOp2(v, OP_Init, 0, 1); if( pParse->pToplevel==0 && OptimizationEnabled(pParse->db,SQLITE_FactorOutConst) ){ @@ -115522,6 +118285,7 @@ static void generateWithRecursiveQuery( /* Process the LIMIT and OFFSET clauses, if they exist */ addrBreak = sqlite3VdbeMakeLabel(v); + p->nSelectRow = 320; /* 4 billion rows */ computeLimitRegisters(pParse, p, addrBreak); pLimit = p->pLimit; pOffset = p->pOffset; @@ -115991,7 +118755,7 @@ static int multiSelect( computeLimitRegisters(pParse, p, iBreak); sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); r1 = sqlite3GetTempReg(pParse); - iStart = sqlite3VdbeAddOp2(v, OP_RowKey, tab1, r1); + iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r1); selectInnerLoop(pParse, p, p->pEList, tab1, @@ -116149,19 +118913,17 @@ static int generateOutputSubroutine( } #ifndef SQLITE_OMIT_SUBQUERY - /* If we are creating a set for an "expr IN (SELECT ...)" construct, - ** then there should be a single item on the stack. Write this - ** item into the set table with bogus data. + /* If we are creating a set for an "expr IN (SELECT ...)". */ case SRT_Set: { int r1; - assert( pIn->nSdst==1 || pParse->nErr>0 ); - pDest->affSdst = - sqlite3CompareAffinity(p->pEList->a[0].pExpr, pDest->affSdst); + testcase( pIn->nSdst>1 ); r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, 1, r1, &pDest->affSdst,1); - sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, 1); - sqlite3VdbeAddOp2(v, OP_IdxInsert, pDest->iSDParm, r1); + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + r1, pDest->zAffSdst, pIn->nSdst); + sqlite3ExprCacheAffinityChange(pParse, pIn->iSdst, pIn->nSdst); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1, + pIn->iSdst, pIn->nSdst); sqlite3ReleaseTempReg(pParse, r1); break; } @@ -116620,8 +119382,8 @@ static int multiSelectOrderBy( #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* Forward Declarations */ -static void substExprList(sqlite3*, ExprList*, int, ExprList*); -static void substSelect(sqlite3*, Select *, int, ExprList*, int); +static void substExprList(Parse*, ExprList*, int, ExprList*); +static void substSelect(Parse*, Select *, int, ExprList*, int); /* ** Scan through the expression pExpr. Replace every reference to @@ -116637,36 +119399,46 @@ static void substSelect(sqlite3*, Select *, int, ExprList*, int); ** of the subquery rather the result set of the subquery. */ static Expr *substExpr( - sqlite3 *db, /* Report malloc errors to this connection */ + Parse *pParse, /* Report errors here */ Expr *pExpr, /* Expr in which substitution occurs */ int iTable, /* Table to be substituted */ ExprList *pEList /* Substitute expressions */ ){ + sqlite3 *db = pParse->db; if( pExpr==0 ) return 0; if( pExpr->op==TK_COLUMN && pExpr->iTable==iTable ){ if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; }else{ Expr *pNew; + Expr *pCopy = pEList->a[pExpr->iColumn].pExpr; assert( pEList!=0 && pExpr->iColumnnExpr ); assert( pExpr->pLeft==0 && pExpr->pRight==0 ); - pNew = sqlite3ExprDup(db, pEList->a[pExpr->iColumn].pExpr, 0); - sqlite3ExprDelete(db, pExpr); - pExpr = pNew; + if( sqlite3ExprIsVector(pCopy) ){ + sqlite3VectorErrorMsg(pParse, pCopy); + }else{ + pNew = sqlite3ExprDup(db, pCopy, 0); + if( pNew && (pExpr->flags & EP_FromJoin) ){ + pNew->iRightJoinTable = pExpr->iRightJoinTable; + pNew->flags |= EP_FromJoin; + } + sqlite3ExprDelete(db, pExpr); + pExpr = pNew; + } } }else{ - pExpr->pLeft = substExpr(db, pExpr->pLeft, iTable, pEList); - pExpr->pRight = substExpr(db, pExpr->pRight, iTable, pEList); + pExpr->pLeft = substExpr(pParse, pExpr->pLeft, iTable, pEList); + pExpr->pRight = substExpr(pParse, pExpr->pRight, iTable, pEList); if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - substSelect(db, pExpr->x.pSelect, iTable, pEList, 1); + substSelect(pParse, pExpr->x.pSelect, iTable, pEList, 1); }else{ - substExprList(db, pExpr->x.pList, iTable, pEList); + substExprList(pParse, pExpr->x.pList, iTable, pEList); } } return pExpr; } static void substExprList( - sqlite3 *db, /* Report malloc errors here */ + Parse *pParse, /* Report errors here */ ExprList *pList, /* List to scan and in which to make substitutes */ int iTable, /* Table to be substituted */ ExprList *pEList /* Substitute values */ @@ -116674,11 +119446,11 @@ static void substExprList( int i; if( pList==0 ) return; for(i=0; inExpr; i++){ - pList->a[i].pExpr = substExpr(db, pList->a[i].pExpr, iTable, pEList); + pList->a[i].pExpr = substExpr(pParse, pList->a[i].pExpr, iTable, pEList); } } static void substSelect( - sqlite3 *db, /* Report malloc errors here */ + Parse *pParse, /* Report errors here */ Select *p, /* SELECT statement in which to make substitutions */ int iTable, /* Table to be replaced */ ExprList *pEList, /* Substitute values */ @@ -116689,17 +119461,17 @@ static void substSelect( int i; if( !p ) return; do{ - substExprList(db, p->pEList, iTable, pEList); - substExprList(db, p->pGroupBy, iTable, pEList); - substExprList(db, p->pOrderBy, iTable, pEList); - p->pHaving = substExpr(db, p->pHaving, iTable, pEList); - p->pWhere = substExpr(db, p->pWhere, iTable, pEList); + substExprList(pParse, p->pEList, iTable, pEList); + substExprList(pParse, p->pGroupBy, iTable, pEList); + substExprList(pParse, p->pOrderBy, iTable, pEList); + p->pHaving = substExpr(pParse, p->pHaving, iTable, pEList); + p->pWhere = substExpr(pParse, p->pWhere, iTable, pEList); pSrc = p->pSrc; assert( pSrc!=0 ); for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - substSelect(db, pItem->pSelect, iTable, pEList, 1); + substSelect(pParse, pItem->pSelect, iTable, pEList, 1); if( pItem->fg.isTabFunc ){ - substExprList(db, pItem->u1.pFuncArg, iTable, pEList); + substExprList(pParse, pItem->u1.pFuncArg, iTable, pEList); } } }while( doPrior && (p = p->pPrior)!=0 ); @@ -117096,12 +119868,12 @@ static int flattenSubquery( */ if( ALWAYS(pSubitem->pTab!=0) ){ Table *pTabToDel = pSubitem->pTab; - if( pTabToDel->nRef==1 ){ + if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); pTabToDel->pNextZombie = pToplevel->pZombieTab; pToplevel->pZombieTab = pTabToDel; }else{ - pTabToDel->nRef--; + pTabToDel->nTabRef--; } pSubitem->pTab = 0; } @@ -117216,14 +119988,17 @@ static int flattenSubquery( assert( pParent->pHaving==0 ); pParent->pHaving = pParent->pWhere; pParent->pWhere = pWhere; - pParent->pHaving = sqlite3ExprAnd(db, pParent->pHaving, - sqlite3ExprDup(db, pSub->pHaving, 0)); + pParent->pHaving = sqlite3ExprAnd(db, + sqlite3ExprDup(db, pSub->pHaving, 0), pParent->pHaving + ); assert( pParent->pGroupBy==0 ); pParent->pGroupBy = sqlite3ExprListDup(db, pSub->pGroupBy, 0); }else{ - pParent->pWhere = sqlite3ExprAnd(db, pParent->pWhere, pWhere); + pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere); + } + if( db->mallocFailed==0 ){ + substSelect(pParse, pParent, iParent, pSub->pEList, 0); } - substSelect(db, pParent, iParent, pSub->pEList, 0); /* The flattened query is distinct if either the inner or the ** outer query is distinct. @@ -117297,7 +120072,7 @@ static int flattenSubquery( ** terms are duplicated into the subquery. */ static int pushDownWhereTerms( - sqlite3 *db, /* The database connection (for malloc()) */ + Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ int iCursor /* Cursor number of the subquery */ @@ -117318,16 +120093,16 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(db, pSubq, pWhere->pRight, iCursor); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, iCursor); pWhere = pWhere->pLeft; } if( ExprHasProperty(pWhere,EP_FromJoin) ) return 0; /* restriction 5 */ if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ nChng++; while( pSubq ){ - pNew = sqlite3ExprDup(db, pWhere, 0); - pNew = substExpr(db, pNew, iCursor, pSubq->pEList); - pSubq->pWhere = sqlite3ExprAnd(db, pSubq->pWhere, pNew); + pNew = sqlite3ExprDup(pParse->db, pWhere, 0); + pNew = substExpr(pParse, pNew, iCursor, pSubq->pEList); + pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew); pSubq = pSubq->pPrior; } } @@ -117619,7 +120394,7 @@ static int withExpand( assert( pFrom->pTab==0 ); pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); if( pTab==0 ) return WRC_Abort; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = sqlite3DbStrDup(db, pCte->zName); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); @@ -117642,25 +120417,33 @@ static int withExpand( ){ pItem->pTab = pTab; pItem->fg.isRecursive = 1; - pTab->nRef++; + pTab->nTabRef++; pSel->selFlags |= SF_Recursive; } } } /* Only one recursive reference is permitted. */ - if( pTab->nRef>2 ){ + if( pTab->nTabRef>2 ){ sqlite3ErrorMsg( pParse, "multiple references to recursive table: %s", pCte->zName ); return SQLITE_ERROR; } - assert( pTab->nRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nRef==2 )); + assert( pTab->nTabRef==1 || ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 )); pCte->zCteErr = "circular reference: %s"; pSavedWith = pParse->pWith; pParse->pWith = pWith; - sqlite3WalkSelect(pWalker, bMayRecursive ? pSel->pPrior : pSel); + if( bMayRecursive ){ + Select *pPrior = pSel->pPrior; + assert( pPrior->pWith==0 ); + pPrior->pWith = pSel->pWith; + sqlite3WalkSelect(pWalker, pPrior); + pPrior->pWith = 0; + }else{ + sqlite3WalkSelect(pWalker, pSel); + } pParse->pWith = pWith; for(pLeft=pSel; pLeft->pPrior; pLeft=pLeft->pPrior); @@ -117704,10 +120487,12 @@ static int withExpand( */ static void selectPopWith(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; - With *pWith = findRightmost(p)->pWith; - if( pWith!=0 ){ - assert( pParse->pWith==pWith ); - pParse->pWith = pWith->pOuter; + if( pParse->pWith && p->pPrior==0 ){ + With *pWith = findRightmost(p)->pWith; + if( pWith!=0 ){ + assert( pParse->pWith==pWith ); + pParse->pWith = pWith->pOuter; + } } } #else @@ -117757,8 +120542,8 @@ static int selectExpander(Walker *pWalker, Select *p){ } pTabList = p->pSrc; pEList = p->pEList; - if( pWalker->xSelectCallback2==selectPopWith ){ - sqlite3WithPush(pParse, findRightmost(p)->pWith, 0); + if( p->pWith ){ + sqlite3WithPush(pParse, p->pWith, 0); } /* Make sure cursor numbers have been assigned to all entries in @@ -117788,7 +120573,7 @@ static int selectExpander(Walker *pWalker, Select *p){ if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); if( pTab==0 ) return WRC_Abort; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->zName = sqlite3MPrintf(db, "sqlite_sq_%p", (void*)pTab); while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); @@ -117801,13 +120586,13 @@ static int selectExpander(Walker *pWalker, Select *p){ assert( pFrom->pTab==0 ); pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom); if( pTab==0 ) return WRC_Abort; - if( pTab->nRef==0xffff ){ + if( pTab->nTabRef>=0xffff ){ sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535", pTab->zName); pFrom->pTab = 0; return WRC_Abort; } - pTab->nRef++; + pTab->nTabRef++; if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ return WRC_Abort; } @@ -117911,7 +120696,7 @@ static int selectExpander(Walker *pWalker, Select *p){ continue; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - zSchemaName = iDb>=0 ? db->aDb[iDb].zName : "*"; + zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } for(j=0; jnCol; j++){ char *zName = pTab->aCol[j].zName; @@ -117957,10 +120742,10 @@ static int selectExpander(Walker *pWalker, Select *p){ if( longNames || pTabList->nSrc>1 ){ Expr *pLeft; pLeft = sqlite3Expr(db, TK_ID, zTabName); - pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); if( zSchemaName ){ pLeft = sqlite3Expr(db, TK_ID, zSchemaName); - pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr, 0); + pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); } if( longNames ){ zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName); @@ -118045,9 +120830,7 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; - if( (pSelect->selFlags & SF_MultiValue)==0 ){ - w.xSelectCallback2 = selectPopWith; - } + w.xSelectCallback2 = selectPopWith; sqlite3WalkSelect(&w, pSelect); } @@ -118197,8 +120980,8 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ ExprList *pList = pF->pExpr->x.pList; assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) ); - sqlite3VdbeAddOp4(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0, 0, - (void*)pF->pFunc, P4_FUNCDEF); + sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } @@ -118249,8 +121032,8 @@ static void updateAccumulator(Parse *pParse, AggInfo *pAggInfo){ if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp4(v, OP_AggStep0, 0, regAgg, pF->iMem, - (void*)pF->pFunc, P4_FUNCDEF); + sqlite3VdbeAddOp3(v, OP_AggStep0, 0, regAgg, pF->iMem); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); sqlite3VdbeChangeP5(v, (u8)nArg); sqlite3ExprCacheAffinityChange(pParse, regAgg, nArg); sqlite3ReleaseTempRange(pParse, regAgg, nArg); @@ -118394,16 +121177,6 @@ SQLITE_PRIVATE int sqlite3Select( } #endif - - /* If writing to memory or generating a set - ** only a single column may be output. - */ -#ifndef SQLITE_OMIT_SUBQUERY - if( checkForMultiColumnSelectError(pParse, pDest, p->pEList->nExpr) ){ - goto select_end; - } -#endif - /* Try to flatten subqueries in the FROM clause up into the main query */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) @@ -118494,7 +121267,7 @@ SQLITE_PRIVATE int sqlite3Select( ** inside the subquery. This can help the subquery to run more efficiently. */ if( (pItem->fg.jointype & JT_OUTER)==0 - && pushDownWhereTerms(db, pSub, p->pWhere, pItem->iCursor) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor) ){ #if SELECTTRACE_ENABLED if( sqlite3SelectTrace & 0x100 ){ @@ -118558,7 +121331,7 @@ SQLITE_PRIVATE int sqlite3Select( /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ - onceAddr = sqlite3CodeOnce(pParse); VdbeCoverage(v); + onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); }else{ VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); @@ -118656,7 +121429,9 @@ SQLITE_PRIVATE int sqlite3Select( /* Set the limiter. */ iEnd = sqlite3VdbeMakeLabel(v); - p->nSelectRow = 320; /* 4 billion rows */ + if( (p->selFlags & SF_FixedLimit)==0 ){ + p->nSelectRow = 320; /* 4 billion rows */ + } computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); @@ -119134,7 +121909,7 @@ SQLITE_PRIVATE int sqlite3Select( ** of output. */ resetAccumulator(pParse, &sAggInfo); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMax,0,flag,0); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMax, 0,flag,0); if( pWInfo==0 ){ sqlite3ExprListDelete(db, pDel); goto select_end; @@ -119223,8 +121998,6 @@ SQLITE_PRIVATE int sqlite3Select( ** if they are not used. */ /* #include "sqliteInt.h" */ -/* #include */ -/* #include */ #ifndef SQLITE_OMIT_GET_TABLE @@ -119505,7 +122278,6 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( int iDb; /* The database to store the trigger in */ Token *pName; /* The unqualified db name */ DbFixer sFix; /* State vector for the DB fixer */ - int iTabDb; /* Index of the database holding pTab */ assert( pName1!=0 ); /* pName1->z might be NULL, but not pName1 itself */ assert( pName2!=0 ); @@ -119618,13 +122390,13 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( " trigger on table: %S", pTableName, 0); goto trigger_cleanup; } - iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); #ifndef SQLITE_OMIT_AUTHORIZATION { + int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int code = SQLITE_CREATE_TRIGGER; - const char *zDb = db->aDb[iTabDb].zName; - const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb; + const char *zDb = db->aDb[iTabDb].zDbSName; + const char *zDbTrig = isTemp ? db->aDb[1].zDbSName : zDb; if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER; if( sqlite3AuthCheck(pParse, code, zName, pTab->zName, zDbTrig) ){ goto trigger_cleanup; @@ -119718,7 +122490,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); sqlite3NestedParse(pParse, "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), zName, + db->aDb[iDb].zDbSName, MASTER_NAME, zName, pTrig->table, z); sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); @@ -119907,7 +122679,7 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr) assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); for(i=OMIT_TEMPDB; inDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDb && sqlite3StrICmp(db->aDb[j].zName, zDb) ) continue; + if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); if( pTrigger ) break; @@ -119953,7 +122725,7 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_TRIGGER; - const char *zDb = db->aDb[iDb].zName; + const char *zDb = db->aDb[iDb].zDbSName; const char *zTab = SCHEMA_TABLE(iDb); if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER; if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) || @@ -119969,7 +122741,7 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ if( (v = sqlite3GetVdbe(pParse))!=0 ){ sqlite3NestedParse(pParse, "DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), pTrigger->zName + db->aDb[iDb].zDbSName, MASTER_NAME, pTrigger->zName ); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); @@ -120072,8 +122844,10 @@ static SrcList *targetSrcList( pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget); iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema); if( iDb==0 || iDb>=2 ){ + const char *zDb; assert( iDbnDb ); - pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); + zDb = db->aDb[iDb].zDbSName; + pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb); } } return pSrc; @@ -120287,7 +123061,6 @@ static TriggerPrg *codeRowTrigger( } pProgram->nMem = pSubParse->nMem; pProgram->nCsr = pSubParse->nTab; - pProgram->nOnce = pSubParse->nOnce; pProgram->token = (void *)pTrigger; pPrg->aColmask[0] = pSubParse->oldmask; pPrg->aColmask[1] = pSubParse->newmask; @@ -120580,14 +123353,14 @@ SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc, pCol->affinity, &pValue); if( pValue ){ - sqlite3VdbeChangeP4(v, -1, (const char *)pValue, P4_MEM); + sqlite3VdbeAppendP4(v, pValue, P4_MEM); } + } #ifndef SQLITE_OMIT_FLOATING_POINT - if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ - sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); - } -#endif + if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); } +#endif } /* @@ -120616,7 +123389,7 @@ SQLITE_PRIVATE void sqlite3Update( int iDataCur; /* Cursor for the canonical data btree */ int iIdxCur; /* Cursor for the first index */ sqlite3 *db; /* The database structure */ - int *aRegIdx = 0; /* One register assigned to each index to be updated */ + int *aRegIdx = 0; /* First register in array assigned to each index */ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the ** an expression for the i-th column of the table. ** aXRef[i]==-1 if the i-th column is not changed. */ @@ -120628,10 +123401,11 @@ SQLITE_PRIVATE void sqlite3Update( AuthContext sContext; /* The authorization context */ NameContext sNC; /* The name-context to resolve expressions in */ int iDb; /* Database containing the table being updated */ - int okOnePass; /* True for one-pass algorithm without the FIFO */ + int eOnePass; /* ONEPASS_XXX value from where.c */ int hasFK; /* True if foreign key processing is required */ int labelBreak; /* Jump here to break out of UPDATE loop */ int labelContinue; /* Jump here to continue next step of UPDATE loop */ + int flags; /* Flags for sqlite3WhereBegin() */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True when updating a view (INSTEAD OF trigger) */ @@ -120642,6 +123416,10 @@ SQLITE_PRIVATE void sqlite3Update( int iEph = 0; /* Ephemeral table holding all primary key values */ int nKey = 0; /* Number of elements in regKey for WITHOUT ROWID */ int aiCurOnePass[2]; /* The write cursors opened by WHERE_ONEPASS */ + int addrOpen = 0; /* Address of OP_OpenEphemeral */ + int iPk = 0; /* First of nPk cells holding PRIMARY KEY value */ + i16 nPk = 0; /* Number of components of the PRIMARY KEY */ + int bReplace = 0; /* True if REPLACE conflict resolution might happen */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ @@ -120760,7 +123538,7 @@ SQLITE_PRIVATE void sqlite3Update( int rc; rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, j<0 ? "ROWID" : pTab->aCol[j].zName, - db->aDb[iDb].zName); + db->aDb[iDb].zDbSName); if( rc==SQLITE_DENY ){ goto update_cleanup; }else if( rc==SQLITE_IGNORE ){ @@ -120793,12 +123571,19 @@ SQLITE_PRIVATE void sqlite3Update( int reg; if( chngKey || hasFK || pIdx->pPartIdxWhere || pIdx==pPk ){ reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; }else{ reg = 0; for(i=0; inKeyCol; i++){ i16 iIdxCol = pIdx->aiColumn[i]; if( iIdxCol<0 || aXRef[iIdxCol]>=0 ){ reg = ++pParse->nMem; + pParse->nMem += pIdx->nColumn; + if( (onError==OE_Replace) + || (onError==OE_Default && pIdx->onError==OE_Replace) + ){ + bReplace = 1; + } break; } } @@ -120806,6 +123591,11 @@ SQLITE_PRIVATE void sqlite3Update( if( reg==0 ) aToOpen[j+1] = 0; aRegIdx[j] = reg; } + if( bReplace ){ + /* If REPLACE conflict resolution might be invoked, open cursors on all + ** indexes in case they are needed to delete records. */ + memset(aToOpen, 1, nIdx+1); + } /* Begin generating code. */ v = sqlite3GetVdbe(pParse); @@ -120858,110 +123648,130 @@ SQLITE_PRIVATE void sqlite3Update( } #endif - /* Begin the database scan - */ + /* Initialize the count of updated rows */ + if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){ + regRowCount = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); + } + if( HasRowid(pTab) ){ sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); - pWInfo = sqlite3WhereBegin( - pParse, pTabList, pWhere, 0, 0, - WHERE_ONEPASS_DESIRED | WHERE_SEEK_TABLE, iIdxCur - ); - if( pWInfo==0 ) goto update_cleanup; - okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); - - /* Remember the rowid of every item to be updated. - */ - sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); - if( !okOnePass ){ - sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); - } - - /* End the database scan loop. - */ - sqlite3WhereEnd(pWInfo); }else{ - int iPk; /* First of nPk memory cells holding PRIMARY KEY value */ - i16 nPk; /* Number of components of the PRIMARY KEY */ - int addrOpen; /* Address of the OpenEphemeral instruction */ - assert( pPk!=0 ); nPk = pPk->nKeyCol; iPk = pParse->nMem+1; pParse->nMem += nPk; regKey = ++pParse->nMem; iEph = pParse->nTab++; + sqlite3VdbeAddOp2(v, OP_Null, 0, iPk); addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); sqlite3VdbeSetP4KeyInfo(pParse, pPk); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, - WHERE_ONEPASS_DESIRED, iIdxCur); - if( pWInfo==0 ) goto update_cleanup; - okOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + } + + /* Begin the database scan. + ** + ** Do not consider a single-pass strategy for a multi-row update if + ** there are any triggers or foreign keys to process, or rows may + ** be deleted as a result of REPLACE conflict handling. Any of these + ** things might disturb a cursor being used to scan through the table + ** or index, causing a single-pass approach to malfunction. */ + flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE; + if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ + flags |= WHERE_ONEPASS_MULTIROW; + } + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur); + if( pWInfo==0 ) goto update_cleanup; + + /* A one-pass strategy that might update more than one row may not + ** be used if any column of the index used for the scan is being + ** updated. Otherwise, if there is an index on "b", statements like + ** the following could create an infinite loop: + ** + ** UPDATE t1 SET b=b+1 WHERE b>? + ** + ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI + ** strategy that uses an index for which one or more columns are being + ** updated. */ + eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + if( eOnePass==ONEPASS_MULTI ){ + int iCur = aiCurOnePass[1]; + if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){ + eOnePass = ONEPASS_OFF; + } + assert( iCur!=iDataCur || !HasRowid(pTab) ); + } + + if( HasRowid(pTab) ){ + /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF + ** mode, write the rowid into the FIFO. In either of the one-pass modes, + ** leave it in register regOldRowid. */ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); + if( eOnePass==ONEPASS_OFF ){ + sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); + } + }else{ + /* Read the PK of the current row into an array of registers. In + ** ONEPASS_OFF mode, serialize the array into a record and store it in + ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change + ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table + ** is not required) and leave the PK fields in the array of registers. */ for(i=0; iaiColumn[i]>=0 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, pPk->aiColumn[i], - iPk+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,pPk->aiColumn[i],iPk+i); } - if( okOnePass ){ + if( eOnePass ){ sqlite3VdbeChangeToNoop(v, addrOpen); nKey = nPk; regKey = iPk; }else{ sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, sqlite3IndexAffinityStr(db, pPk), nPk); - sqlite3VdbeAddOp2(v, OP_IdxInsert, iEph, regKey); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk); } - sqlite3WhereEnd(pWInfo); } - /* Initialize the count of updated rows - */ - if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){ - regRowCount = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); + if( eOnePass!=ONEPASS_MULTI ){ + sqlite3WhereEnd(pWInfo); } labelBreak = sqlite3VdbeMakeLabel(v); if( !isView ){ - /* - ** Open every index that needs updating. Note that if any - ** index could potentially invoke a REPLACE conflict resolution - ** action, then we need to open all indices because we might need - ** to be deleting some records. - */ - if( onError==OE_Replace ){ - memset(aToOpen, 1, nIdx+1); - }else{ - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->onError==OE_Replace ){ - memset(aToOpen, 1, nIdx+1); - break; - } - } - } - if( okOnePass ){ + int addrOnce = 0; + + /* Open every index that needs updating. */ + if( eOnePass!=ONEPASS_OFF ){ if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; } + + if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, aToOpen, 0, 0); + if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); } /* Top of the update loop */ - if( okOnePass ){ - if( aToOpen[iDataCur-iBaseCur] && !isView ){ + if( eOnePass!=ONEPASS_OFF ){ + if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){ assert( pPk ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey, nKey); VdbeCoverageNeverTaken(v); } - labelContinue = labelBreak; + if( eOnePass==ONEPASS_SINGLE ){ + labelContinue = labelBreak; + }else{ + labelContinue = sqlite3VdbeMakeLabel(v); + } sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak); VdbeCoverageIf(v, pPk==0); VdbeCoverageIf(v, pPk!=0); }else if( pPk ){ labelContinue = sqlite3VdbeMakeLabel(v); sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); - addrTop = sqlite3VdbeAddOp2(v, OP_RowKey, iEph, regKey); + addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); VdbeCoverage(v); }else{ @@ -121079,7 +123889,6 @@ SQLITE_PRIVATE void sqlite3Update( if( !isView ){ int addr1 = 0; /* Address of jump instruction */ - int bReplace = 0; /* True if REPLACE conflict resolution might happen */ /* Do constraint checks. */ assert( regOldRowid>0 ); @@ -121115,14 +123924,18 @@ SQLITE_PRIVATE void sqlite3Update( assert( regNew==regNewRowid+1 ); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK sqlite3VdbeAddOp3(v, OP_Delete, iDataCur, - OPFLAG_ISUPDATE | ((hasFK || chngKey || pPk!=0) ? 0 : OPFLAG_ISNOOP), + OPFLAG_ISUPDATE | ((hasFK || chngKey) ? 0 : OPFLAG_ISNOOP), regNewRowid ); + if( eOnePass==ONEPASS_MULTI ){ + assert( hasFK==0 && chngKey==0 ); + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + } if( !pParse->nested ){ - sqlite3VdbeChangeP4(v, -1, (char*)pTab, P4_TABLE); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } #else - if( hasFK || chngKey || pPk!=0 ){ + if( hasFK || chngKey ){ sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } #endif @@ -121135,8 +123948,11 @@ SQLITE_PRIVATE void sqlite3Update( } /* Insert the new index entries and the new record. */ - sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, - regNewRowid, aRegIdx, 1, 0, 0); + sqlite3CompleteInsertion( + pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, + OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), + 0, 0 + ); /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key @@ -121158,8 +123974,11 @@ SQLITE_PRIVATE void sqlite3Update( /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. */ - if( okOnePass ){ + if( eOnePass==ONEPASS_SINGLE ){ /* Nothing to do at end-of-loop for a single-pass */ + }else if( eOnePass==ONEPASS_MULTI ){ + sqlite3VdbeResolveLabel(v, labelContinue); + sqlite3WhereEnd(pWInfo); }else if( pPk ){ sqlite3VdbeResolveLabel(v, labelContinue); sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v); @@ -121168,15 +123987,6 @@ SQLITE_PRIVATE void sqlite3Update( } sqlite3VdbeResolveLabel(v, labelBreak); - /* Close all tables */ - for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ - assert( aRegIdx ); - if( aToOpen[i+1] ){ - sqlite3VdbeAddOp2(v, OP_Close, iIdxCur+i, 0); - } - } - if( iDataCurflags&SQLITE_CountRows) ); - return vacuumFinalize(db, pStmt, pzErrMsg); -} - -/* -** Execute zSql on database db. The statement returns exactly -** one column. Execute this as SQL on the same database. -*/ -static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ sqlite3_stmt *pStmt; int rc; - rc = sqlite3_prepare(db, zSql, -1, &pStmt, 0); + /* printf("SQL: [%s]\n", zSql); fflush(stdout); */ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; - - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - rc = execSql(db, pzErrMsg, (char*)sqlite3_column_text(pStmt, 0)); - if( rc!=SQLITE_OK ){ - vacuumFinalize(db, pStmt, pzErrMsg); - return rc; + while( SQLITE_ROW==(rc = sqlite3_step(pStmt)) ){ + const char *zSubSql = (const char*)sqlite3_column_text(pStmt,0); + assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 ); + if( zSubSql ){ + assert( zSubSql[0]!='S' ); + rc = execSql(db, pzErrMsg, zSubSql); + if( rc!=SQLITE_OK ) break; } } - - return vacuumFinalize(db, pStmt, pzErrMsg); + assert( rc!=SQLITE_ROW ); + if( rc==SQLITE_DONE ) rc = SQLITE_OK; + if( rc ){ + sqlite3SetString(pzErrMsg, db, sqlite3_errmsg(db)); + } + (void)sqlite3_finalize(pStmt); + return rc; +} +static int execSqlF(sqlite3 *db, char **pzErrMsg, const char *zSql, ...){ + char *z; + va_list ap; + int rc; + va_start(ap, zSql); + z = sqlite3VMPrintf(db, zSql, ap); + va_end(ap); + if( z==0 ) return SQLITE_NOMEM; + rc = execSql(db, pzErrMsg, z); + sqlite3DbFree(db, z); + return rc; } /* @@ -121445,11 +124250,29 @@ static int execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ ** transient would cause the database file to appear to be deleted ** following reboot. */ -SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse){ +SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse, Token *pNm){ Vdbe *v = sqlite3GetVdbe(pParse); - if( v ){ - sqlite3VdbeAddOp2(v, OP_Vacuum, 0, 0); - sqlite3VdbeUsesBtree(v, 0); + int iDb = 0; + if( v==0 ) return; + if( pNm ){ +#ifndef SQLITE_BUG_COMPATIBLE_20160819 + /* Default behavior: Report an error if the argument to VACUUM is + ** not recognized */ + iDb = sqlite3TwoPartName(pParse, pNm, pNm, &pNm); + if( iDb<0 ) return; +#else + /* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments + ** to VACUUM are silently ignored. This is a back-out of a bug fix that + ** occurred on 2016-08-19 (https://www.sqlite.org/src/info/083f9e6270). + ** The buggy behavior is required for binary compatibility with some + ** legacy applications. */ + iDb = sqlite3FindDb(pParse->db, pNm); + if( iDb<0 ) iDb = 0; +#endif + } + if( iDb!=1 ){ + sqlite3VdbeAddOp1(v, OP_Vacuum, iDb); + sqlite3VdbeUsesBtree(v, iDb); } return; } @@ -121457,11 +124280,10 @@ SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse){ /* ** This routine implements the OP_Vacuum opcode of the VDBE. */ -SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ +SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db, int iDb){ int rc = SQLITE_OK; /* Return code from service routines */ Btree *pMain; /* The database being vacuumed */ Btree *pTemp; /* The temporary database we vacuum into */ - char *zSql = 0; /* SQL statements */ int saved_flags; /* Saved value of the db->flags */ int saved_nChange; /* Saved value of db->nChange */ int saved_nTotalChange; /* Saved value of db->nTotalChange */ @@ -121470,6 +124292,7 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ int isMemDb; /* True if vacuuming a :memory: database */ int nRes; /* Bytes of reserved space at the end of each page */ int nDb; /* Number of attached databases */ + const char *zDbMain; /* Schema name of database to vacuum */ if( !db->autoCommit ){ sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); @@ -121487,11 +124310,13 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ saved_nChange = db->nChange; saved_nTotalChange = db->nTotalChange; saved_mTrace = db->mTrace; - db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin; - db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder); + db->flags |= (SQLITE_WriteSchema | SQLITE_IgnoreChecks + | SQLITE_PreferBuiltin | SQLITE_Vacuum); + db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_CountRows); db->mTrace = 0; - pMain = db->aDb[0].pBt; + zDbMain = db->aDb[iDb].zDbSName; + pMain = db->aDb[iDb].pBt; isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain)); /* Attach the temporary database as 'vacuum_db'. The synchronous pragma @@ -121509,18 +124334,12 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ ** to write the journal header file. */ nDb = db->nDb; - if( sqlite3TempInMemory(db) ){ - zSql = "ATTACH ':memory:' AS vacuum_db;"; - }else{ - zSql = "ATTACH '' AS vacuum_db;"; - } - rc = execSql(db, pzErrMsg, zSql); - if( db->nDb>nDb ){ - pDb = &db->aDb[db->nDb-1]; - assert( strcmp(pDb->zName,"vacuum_db")==0 ); - } + rc = execSql(db, pzErrMsg, "ATTACH''AS vacuum_db"); if( rc!=SQLITE_OK ) goto end_of_vacuum; - pTemp = db->aDb[db->nDb-1].pBt; + assert( (db->nDb-1)==nDb ); + pDb = &db->aDb[nDb]; + assert( strcmp(pDb->zDbSName,"vacuum_db")==0 ); + pTemp = pDb->pBt; /* The call to execSql() to attach the temp database has left the file ** locked (as there was more than one active statement when the transaction @@ -121541,16 +124360,15 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ } #endif - sqlite3BtreeSetCacheSize(pTemp, db->aDb[0].pSchema->cache_size); + sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); - rc = execSql(db, pzErrMsg, "PRAGMA vacuum_db.synchronous=OFF"); - if( rc!=SQLITE_OK ) goto end_of_vacuum; + sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL); /* Begin a transaction and take an exclusive lock on the main database ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below, ** to ensure that we do not try to change the page-size on a WAL database. */ - rc = execSql(db, pzErrMsg, "BEGIN;"); + rc = execSql(db, pzErrMsg, "BEGIN"); if( rc!=SQLITE_OK ) goto end_of_vacuum; rc = sqlite3BtreeBeginTrans(pMain, 2); if( rc!=SQLITE_OK ) goto end_of_vacuum; @@ -121577,64 +124395,48 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ /* Query the schema of the main database. Create a mirror schema ** in the temporary database. */ - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE TABLE vacuum_db.' || substr(sql,14) " - " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" - " AND coalesce(rootpage,1)>0" + db->init.iDb = nDb; /* force new CREATE statements into vacuum_db */ + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='table'AND name<>'sqlite_sequence'" + " AND coalesce(rootpage,1)>0", + zDbMain ); if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE INDEX vacuum_db.' || substr(sql,14)" - " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %' "); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'CREATE UNIQUE INDEX vacuum_db.' || substr(sql,21) " - " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %'"); + rc = execSqlF(db, pzErrMsg, + "SELECT sql FROM \"%w\".sqlite_master" + " WHERE type='index' AND length(sql)>10", + zDbMain + ); if( rc!=SQLITE_OK ) goto end_of_vacuum; + db->init.iDb = 0; /* Loop through the tables in the main database. For each, do ** an "INSERT INTO vacuum_db.xxx SELECT * FROM main.xxx;" to copy ** the contents to the temporary database. */ - assert( (db->flags & SQLITE_Vacuum)==0 ); - db->flags |= SQLITE_Vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'INSERT INTO vacuum_db.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';'" - "FROM main.sqlite_master " - "WHERE type = 'table' AND name!='sqlite_sequence' " - " AND coalesce(rootpage,1)>0" + rc = execSqlF(db, pzErrMsg, + "SELECT'INSERT INTO vacuum_db.'||quote(name)" + "||' SELECT*FROM\"%w\".'||quote(name)" + "FROM vacuum_db.sqlite_master " + "WHERE type='table'AND coalesce(rootpage,1)>0", + zDbMain ); assert( (db->flags & SQLITE_Vacuum)!=0 ); db->flags &= ~SQLITE_Vacuum; if( rc!=SQLITE_OK ) goto end_of_vacuum; - /* Copy over the sequence table - */ - rc = execExecSql(db, pzErrMsg, - "SELECT 'DELETE FROM vacuum_db.' || quote(name) || ';' " - "FROM vacuum_db.sqlite_master WHERE name='sqlite_sequence' " - ); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - rc = execExecSql(db, pzErrMsg, - "SELECT 'INSERT INTO vacuum_db.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';' " - "FROM vacuum_db.sqlite_master WHERE name=='sqlite_sequence';" - ); - if( rc!=SQLITE_OK ) goto end_of_vacuum; - - /* Copy the triggers, views, and virtual tables from the main database ** over to the temporary database. None of these objects has any ** associated storage, so all we have to do is copy their entries ** from the SQLITE_MASTER table. */ - rc = execSql(db, pzErrMsg, - "INSERT INTO vacuum_db.sqlite_master " - " SELECT type, name, tbl_name, rootpage, sql" - " FROM main.sqlite_master" - " WHERE type='view' OR type='trigger'" - " OR (type='table' AND rootpage=0)" + rc = execSqlF(db, pzErrMsg, + "INSERT INTO vacuum_db.sqlite_master" + " SELECT*FROM \"%w\".sqlite_master" + " WHERE type IN('view','trigger')" + " OR(type='table'AND rootpage=0)", + zDbMain ); if( rc ) goto end_of_vacuum; @@ -121688,6 +124490,7 @@ SQLITE_PRIVATE int sqlite3RunVacuum(char **pzErrMsg, sqlite3 *db){ end_of_vacuum: /* Restore the original value of db->flags */ + db->init.iDb = 0; db->flags = saved_flags; db->nChange = saved_nChange; db->nTotalChange = saved_nTotalChange; @@ -121750,6 +124553,41 @@ struct VtabCtx { int bDeclared; /* True after sqlite3_declare_vtab() is called */ }; +/* +** Construct and install a Module object for a virtual table. When this +** routine is called, it is guaranteed that all appropriate locks are held +** and the module is not already part of the connection. +*/ +SQLITE_PRIVATE Module *sqlite3VtabCreateModule( + sqlite3 *db, /* Database in which module is registered */ + const char *zName, /* Name assigned to this module */ + const sqlite3_module *pModule, /* The definition of the module */ + void *pAux, /* Context pointer for xCreate/xConnect */ + void (*xDestroy)(void *) /* Module destructor function */ +){ + Module *pMod; + int nName = sqlite3Strlen30(zName); + pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1); + if( pMod ){ + Module *pDel; + char *zCopy = (char *)(&pMod[1]); + memcpy(zCopy, zName, nName+1); + pMod->zName = zCopy; + pMod->pModule = pModule; + pMod->pAux = pAux; + pMod->xDestroy = xDestroy; + pMod->pEpoTab = 0; + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); + assert( pDel==0 || pDel==pMod ); + if( pDel ){ + sqlite3OomFault(db); + sqlite3DbFree(db, pDel); + pMod = 0; + } + } + return pMod; +} + /* ** The actual function that does the work of creating a new module. ** This function implements the sqlite3_create_module() and @@ -121763,35 +124601,15 @@ static int createModule( void (*xDestroy)(void *) /* Module destructor function */ ){ int rc = SQLITE_OK; - int nName; sqlite3_mutex_enter(db->mutex); - nName = sqlite3Strlen30(zName); if( sqlite3HashFind(&db->aModule, zName) ){ rc = SQLITE_MISUSE_BKPT; }else{ - Module *pMod; - pMod = (Module *)sqlite3DbMallocRawNN(db, sizeof(Module) + nName + 1); - if( pMod ){ - Module *pDel; - char *zCopy = (char *)(&pMod[1]); - memcpy(zCopy, zName, nName+1); - pMod->zName = zCopy; - pMod->pModule = pModule; - pMod->pAux = pAux; - pMod->xDestroy = xDestroy; - pMod->pEpoTab = 0; - pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); - assert( pDel==0 || pDel==pMod ); - if( pDel ){ - sqlite3OomFault(db); - sqlite3DbFree(db, pDel); - } - } + (void)sqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy); } rc = sqlite3ApiExit(db, rc); if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux); - sqlite3_mutex_leave(db->mutex); return rc; } @@ -122046,8 +124864,7 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( iDb = sqlite3SchemaToIndex(db, pTable->pSchema); assert( iDb>=0 ); - pTable->tabFlags |= TF_Virtual; - pTable->nModuleArg = 0; + assert( pTable->nModuleArg==0 ); addModuleArgument(db, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(db, pTable, 0); addModuleArgument(db, pTable, sqlite3DbStrDup(db, pTable->zName)); @@ -122066,7 +124883,7 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( */ if( pTable->azModuleArg ){ sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, - pTable->azModuleArg[0], pParse->db->aDb[iDb].zName); + pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); } #endif } @@ -122130,7 +124947,7 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ "UPDATE %Q.%s " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", - db->aDb[iDb].zName, SCHEMA_TABLE(iDb), + db->aDb[iDb].zDbSName, MASTER_NAME, pTab->zName, pTab->zName, zStmt, @@ -122240,7 +125057,7 @@ static int vtabCallConstructor( pVTable->pMod = pMod; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - pTab->azModuleArg[1] = db->aDb[iDb].zName; + pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); @@ -122335,7 +125152,7 @@ SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ int rc; assert( pTab ); - if( (pTab->tabFlags & TF_Virtual)==0 || sqlite3GetVTable(db, pTab) ){ + if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){ return SQLITE_OK; } @@ -122394,7 +125211,7 @@ static void addToVTrans(sqlite3 *db, VTable *pVTab){ ** This function is invoked by the vdbe to call the xCreate method ** of the virtual table named zTab in database iDb. ** -** If an error occurs, *pzErr is set to point an an English language +** If an error occurs, *pzErr is set to point to an English language ** description of the error and an SQLITE_XXX error code is returned. ** In this case the caller must call sqlite3DbFree(db, ) on *pzErr. */ @@ -122404,8 +125221,8 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, Module *pMod; const char *zMod; - pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); - assert( pTab && (pTab->tabFlags & TF_Virtual)!=0 && !pTab->pVTable ); + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); + assert( pTab && IsVirtual(pTab) && !pTab->pVTable ); /* Locate the required virtual table module */ zMod = pTab->azModuleArg[0]; @@ -122459,7 +125276,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ return SQLITE_MISUSE_BKPT; } pTab = pCtx->pTab; - assert( (pTab->tabFlags & TF_Virtual)!=0 ); + assert( IsVirtual(pTab) ); pParse = sqlite3StackAllocZero(db, sizeof(*pParse)); if( pParse==0 ){ @@ -122473,7 +125290,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ && pParse->pNewTable && !db->mallocFailed && !pParse->pNewTable->pSelect - && (pParse->pNewTable->tabFlags & TF_Virtual)==0 + && !IsVirtual(pParse->pNewTable) ){ if( !pTab->aCol ){ Table *pNew = pParse->pNewTable; @@ -122528,7 +125345,7 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab int rc = SQLITE_OK; Table *pTab; - pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zName); + pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){ VTable *p; int (*xDestroy)(sqlite3_vtab *); @@ -122762,7 +125579,7 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( if( pExpr->op!=TK_COLUMN ) return pDef; pTab = pExpr->pTab; if( NEVER(pTab==0) ) return pDef; - if( (pTab->tabFlags & TF_Virtual)==0 ) return pDef; + if( !IsVirtual(pTab) ) return pDef; pVtab = sqlite3GetVTable(db, pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); @@ -122855,10 +125672,9 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ return 0; } pMod->pEpoTab = pTab; - pTab->nRef = 1; + pTab->nTabRef = 1; pTab->pSchema = db->aDb[0].pSchema; - pTab->tabFlags |= TF_Virtual; - pTab->nModuleArg = 0; + assert( pTab->nModuleArg==0 ); pTab->iPKey = -1; addModuleArgument(db, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(db, pTab, 0); @@ -122929,7 +125745,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ if( !p ){ rc = SQLITE_MISUSE_BKPT; }else{ - assert( p->pTab==0 || (p->pTab->tabFlags & TF_Virtual)!=0 ); + assert( p->pTab==0 || IsVirtual(p->pTab) ); p->pVTable->bConstraint = (u8)va_arg(ap, int); } break; @@ -123095,6 +125911,8 @@ struct WhereLoop { union { struct { /* Information for internal btree tables */ u16 nEq; /* Number of equality constraints */ + u16 nBtm; /* Size of BTM vector */ + u16 nTop; /* Size of TOP vector */ Index *pIndex; /* Index used, or NULL */ } btree; struct { /* Information for virtual tables */ @@ -123217,19 +126035,20 @@ struct WherePath { */ struct WhereTerm { Expr *pExpr; /* Pointer to the subexpression that is this term */ + WhereClause *pWC; /* The clause this term is part of */ + LogEst truthProb; /* Probability of truth for this expression */ + u16 wtFlags; /* TERM_xxx bit flags. See below */ + u16 eOperator; /* A WO_xx value describing */ + u8 nChild; /* Number of children that must disable us */ + u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ int iParent; /* Disable pWC->a[iParent] when this term disabled */ int leftCursor; /* Cursor number of X in "X " */ + int iField; /* Field in (?,?,?) IN (SELECT...) vector */ union { int leftColumn; /* Column number of X in "X " */ WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */ WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */ } u; - LogEst truthProb; /* Probability of truth for this expression */ - u16 eOperator; /* A WO_xx value describing */ - u16 wtFlags; /* TERM_xxx bit flags. See below */ - u8 nChild; /* Number of children that must disable us */ - u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ - WhereClause *pWC; /* The clause this term is part of */ Bitmask prereqRight; /* Bitmask of tables used by pExpr->pRight */ Bitmask prereqAll; /* Bitmask of tables referenced by pExpr */ }; @@ -123365,8 +126184,13 @@ struct WhereLoopBuilder { UnpackedRecord *pRec; /* Probe for stat4 (if required) */ int nRecValid; /* Number of valid fields currently in pRec */ #endif + unsigned int bldFlags; /* SQLITE_BLDF_* flags */ }; +/* Allowed values for WhereLoopBuider.bldFlags */ +#define SQLITE_BLDF_INDEXED 0x0001 /* An index is used */ +#define SQLITE_BLDF_UNIQUE 0x0002 /* All keys of a UNIQUE index used */ + /* ** The WHERE clause processing routine has two halves. The ** first part does the start of the WHERE loop and the second @@ -123381,26 +126205,26 @@ struct WhereInfo { Parse *pParse; /* Parsing and code generating context */ SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ - ExprList *pDistinctSet; /* DISTINCT over all these values */ - WhereLoop *pLoops; /* List of all WhereLoop objects */ - Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ - LogEst nRowOut; /* Estimated number of output rows */ + ExprList *pResultSet; /* Result set of the query */ LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ + int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ + int iContinue; /* Jump here to continue with next record */ + int iBreak; /* Jump here to break out of the loop */ + int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ + u8 nLevel; /* Number of nested loop */ i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ u8 sorted; /* True if really sorted (not just grouped) */ u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */ u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */ u8 eDistinct; /* One of the WHERE_DISTINCT_* values */ - u8 nLevel; /* Number of nested loop */ u8 bOrderedInnerLoop; /* True if only the inner-most loop is ordered */ int iTop; /* The very beginning of the WHERE loop */ - int iContinue; /* Jump here to continue with next record */ - int iBreak; /* Jump here to break out of the loop */ - int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ - int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ - WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ + WhereLoop *pLoops; /* List of all WhereLoop objects */ + Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ + LogEst nRowOut; /* Estimated number of output rows */ WhereClause sWC; /* Decomposition of the WHERE clause */ + WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ WhereLevel a[1]; /* Information about each nest loop in WHERE */ }; @@ -123524,6 +126348,17 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC /************** Continuing where we left off in wherecode.c ******************/ #ifndef SQLITE_OMIT_EXPLAIN + +/* +** Return the name of the i-th column of the pIdx index. +*/ +static const char *explainIndexColumnName(Index *pIdx, int i){ + i = pIdx->aiColumn[i]; + if( i==XN_EXPR ) return ""; + if( i==XN_ROWID ) return "rowid"; + return pIdx->pTable->aCol[i].zName; +} + /* ** This routine is a helper for explainIndexRange() below ** @@ -123534,24 +126369,32 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC */ static void explainAppendTerm( StrAccum *pStr, /* The text expression being built */ - int iTerm, /* Index of this term. First is zero */ - const char *zColumn, /* Name of the column */ + Index *pIdx, /* Index to read column names from */ + int nTerm, /* Number of terms */ + int iTerm, /* Zero-based index of first term. */ + int bAnd, /* Non-zero to append " AND " */ const char *zOp /* Name of the operator */ ){ - if( iTerm ) sqlite3StrAccumAppend(pStr, " AND ", 5); - sqlite3StrAccumAppendAll(pStr, zColumn); + int i; + + assert( nTerm>=1 ); + if( bAnd ) sqlite3StrAccumAppend(pStr, " AND ", 5); + + if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1); + for(i=0; i1 ) sqlite3StrAccumAppend(pStr, ")", 1); + sqlite3StrAccumAppend(pStr, zOp, 1); - sqlite3StrAccumAppend(pStr, "?", 1); -} -/* -** Return the name of the i-th column of the pIdx index. -*/ -static const char *explainIndexColumnName(Index *pIdx, int i){ - i = pIdx->aiColumn[i]; - if( i==XN_EXPR ) return ""; - if( i==XN_ROWID ) return "rowid"; - return pIdx->pTable->aCol[i].zName; + if( nTerm>1 ) sqlite3StrAccumAppend(pStr, "(", 1); + for(i=0; i1 ) sqlite3StrAccumAppend(pStr, ")", 1); } /* @@ -123584,12 +126427,11 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ j = i; if( pLoop->wsFlags&WHERE_BTM_LIMIT ){ - const char *z = explainIndexColumnName(pIndex, i); - explainAppendTerm(pStr, i++, z, ">"); + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nBtm, j, i, ">"); + i = 1; } if( pLoop->wsFlags&WHERE_TOP_LIMIT ){ - const char *z = explainIndexColumnName(pIndex, j); - explainAppendTerm(pStr, i, z, "<"); + explainAppendTerm(pStr, pIndex, pLoop->u.btree.nTop, j, i, "<"); } sqlite3StrAccumAppend(pStr, ")", 1); } @@ -123779,7 +126621,7 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus( */ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ int nLoop = 0; - while( pTerm + while( ALWAYS(pTerm!=0) && (pTerm->wtFlags & TERM_CODED)==0 && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) && (pLevel->notReady & pTerm->prereqAll)==0 @@ -123835,16 +126677,45 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ } } +/* +** Expression pRight, which is the RHS of a comparison operation, is +** either a vector of n elements or, if n==1, a scalar expression. +** Before the comparison operation, affinity zAff is to be applied +** to the pRight values. This function modifies characters within the +** affinity string to SQLITE_AFF_BLOB if either: +** +** * the comparison will be performed with no affinity, or +** * the affinity change in zAff is guaranteed not to change the value. +*/ +static void updateRangeAffinityStr( + Expr *pRight, /* RHS of comparison */ + int n, /* Number of vector elements in comparison */ + char *zAff /* Affinity string to modify */ +){ + int i; + for(i=0; ipVdbe; int iReg; /* Register holding results */ + assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); assert( iTarget>0 ); if( pX->op==TK_EQ || pX->op==TK_IS ){ iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget); @@ -123867,10 +126739,13 @@ static int codeEqualityTerm( sqlite3VdbeAddOp2(v, OP_Null, 0, iReg); #ifndef SQLITE_OMIT_SUBQUERY }else{ - int eType; + int eType = IN_INDEX_NOOP; int iTab; struct InLoop *pIn; WhereLoop *pLoop = pLevel->pWLoop; + int i; + int nEq = 0; + int *aiMap = 0; if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 @@ -123882,7 +126757,78 @@ static int codeEqualityTerm( } assert( pX->op==TK_IN ); iReg = iTarget; - eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0); + + for(i=0; iaLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){ + disableTerm(pLevel, pTerm); + return iTarget; + } + } + for(i=iEq;inLTerm; i++){ + if( ALWAYS(pLoop->aLTerm[i]) && pLoop->aLTerm[i]->pExpr==pX ) nEq++; + } + + if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0); + }else{ + Select *pSelect = pX->x.pSelect; + sqlite3 *db = pParse->db; + u16 savedDbOptFlags = db->dbOptFlags; + ExprList *pOrigRhs = pSelect->pEList; + ExprList *pOrigLhs = pX->pLeft->x.pList; + ExprList *pRhs = 0; /* New Select.pEList for RHS */ + ExprList *pLhs = 0; /* New pX->pLeft vector */ + + for(i=iEq;inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField = pLoop->aLTerm[i]->iField - 1; + Expr *pNewRhs = sqlite3ExprDup(db, pOrigRhs->a[iField].pExpr, 0); + Expr *pNewLhs = sqlite3ExprDup(db, pOrigLhs->a[iField].pExpr, 0); + + pRhs = sqlite3ExprListAppend(pParse, pRhs, pNewRhs); + pLhs = sqlite3ExprListAppend(pParse, pLhs, pNewLhs); + } + } + if( !db->mallocFailed ){ + Expr *pLeft = pX->pLeft; + + if( pSelect->pOrderBy ){ + /* If the SELECT statement has an ORDER BY clause, zero the + ** iOrderByCol variables. These are set to non-zero when an + ** ORDER BY term exactly matches one of the terms of the + ** result-set. Since the result-set of the SELECT statement may + ** have been modified or reordered, these variables are no longer + ** set correctly. Since setting them is just an optimization, + ** it's easiest just to zero them here. */ + ExprList *pOrderBy = pSelect->pOrderBy; + for(i=0; inExpr; i++){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + } + + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + if( pLhs->nExpr==1 ){ + pX->pLeft = pLhs->a[0].pExpr; + }else{ + pLeft->x.pList = pLhs; + aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int) * nEq); + testcase( aiMap==0 ); + } + pSelect->pEList = pRhs; + db->dbOptFlags |= SQLITE_QueryFlattener; + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap); + db->dbOptFlags = savedDbOptFlags; + testcase( aiMap!=0 && aiMap[0]!=0 ); + pSelect->pEList = pOrigRhs; + pLeft->x.pList = pOrigLhs; + pX->pLeft = pLeft; + } + sqlite3ExprListDelete(pParse->db, pLhs); + sqlite3ExprListDelete(pParse->db, pRhs); + } + if( eType==IN_INDEX_INDEX_DESC ){ testcase( bRev ); bRev = !bRev; @@ -123892,28 +126838,45 @@ static int codeEqualityTerm( VdbeCoverageIf(v, bRev); VdbeCoverageIf(v, !bRev); assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + pLoop->wsFlags |= WHERE_IN_ABLE; if( pLevel->u.in.nIn==0 ){ pLevel->addrNxt = sqlite3VdbeMakeLabel(v); } - pLevel->u.in.nIn++; + + i = pLevel->u.in.nIn; + pLevel->u.in.nIn += nEq; pLevel->u.in.aInLoop = sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop, sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); pIn = pLevel->u.in.aInLoop; if( pIn ){ - pIn += pLevel->u.in.nIn - 1; - pIn->iCur = iTab; - if( eType==IN_INDEX_ROWID ){ - pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iReg); - }else{ - pIn->addrInTop = sqlite3VdbeAddOp3(v, OP_Column, iTab, 0, iReg); + int iMap = 0; /* Index in aiMap[] */ + pIn += i; + for(i=iEq;inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iOut = iReg + i - iEq; + if( eType==IN_INDEX_ROWID ){ + testcase( nEq>1 ); /* Happens with a UNIQUE index on ROWID */ + pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut); + }else{ + int iCol = aiMap ? aiMap[iMap++] : 0; + pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut); + } + sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v); + if( i==iEq ){ + pIn->iCur = iTab; + pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen; + }else{ + pIn->eEndLoopOp = OP_Noop; + } + pIn++; + } } - pIn->eEndLoopOp = bRev ? OP_PrevIfOpen : OP_NextIfOpen; - sqlite3VdbeAddOp1(v, OP_IsNull, iReg); VdbeCoverage(v); }else{ pLevel->u.in.nIn = 0; } + sqlite3DbFree(pParse->db, aiMap); #endif } disableTerm(pLevel, pTerm); @@ -124039,7 +127002,7 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j); } } - if( (pTerm->eOperator & WO_IN)!=0 ){ + if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value ** from the RHS of an "? IN (SELECT ...)" expression. The @@ -124370,6 +127333,39 @@ static void codeDeferredSeek( } } +/* +** If the expression passed as the second argument is a vector, generate +** code to write the first nReg elements of the vector into an array +** of registers starting with iReg. +** +** If the expression is not a vector, then nReg must be passed 1. In +** this case, generate code to evaluate the expression and leave the +** result in register iReg. +*/ +static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ + assert( nReg>0 ); + if( sqlite3ExprIsVector(p) ){ +#ifndef SQLITE_OMIT_SUBQUERY + if( (p->flags & EP_xIsSelect) ){ + Vdbe *v = pParse->pVdbe; + int iSelect = sqlite3CodeSubselect(pParse, p, 0, 0); + sqlite3VdbeAddOp3(v, OP_Copy, iSelect, iReg, nReg-1); + }else +#endif + { + int i; + ExprList *pList = p->x.pList; + assert( nReg<=pList->nExpr ); + for(i=0; ia[i].pExpr, iReg+i); + } + } + }else{ + assert( nReg==1 ); + sqlite3ExprCode(pParse, p, iReg); + } +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -124393,6 +127389,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( Vdbe *v; /* The prepared stmt under constructions */ struct SrcList_item *pTabItem; /* FROM clause term being coded */ int addrBrk; /* Jump here to break out of the loop */ + int addrHalt; /* addrBrk for the outermost loop */ int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ @@ -124434,6 +127431,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeComment((v, "init LEFT JOIN no-match flag")); } + /* Compute a safe address to jump to if we discover that the table for + ** this loop is empty and can never contribute content. */ + for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){} + addrHalt = pWInfo->a[j].addrBrk; + /* Special case of a FROM clause subquery implemented as a co-routine */ if( pTabItem->fg.viaCoroutine ){ int regYield = pTabItem->regReturn; @@ -124465,14 +127467,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); addrNotFound = pLevel->addrNxt; }else{ - sqlite3ExprCode(pParse, pTerm->pExpr->pRight, iTarget); + Expr *pRight = pTerm->pExpr->pRight; + codeExprOrVector(pParse, pRight, iTarget, 1); } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, pLoop->u.vtab.idxStr, - pLoop->u.vtab.needFree ? P4_MPRINTF : P4_STATIC); + pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC); VdbeCoverage(v); pLoop->u.vtab.needFree = 0; pLevel->p1 = iCur; @@ -124505,7 +127508,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( /* Generate code that will continue to the next row if ** the IN constraint is not satisfied */ - pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0, 0); + pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); assert( pCompare!=0 || db->mallocFailed ); if( pCompare ){ pCompare->pLeft = pTerm->pExpr->pLeft; @@ -124579,6 +127582,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( pStart ){ Expr *pX; /* The expression that defines the start bound */ int r1, rTemp; /* Registers for holding the start boundary */ + int op; /* Cursor seek operation */ /* The following constant maps TK_xx codes into corresponding ** seek opcodes. It depends on a particular ordering of TK_xx @@ -124598,8 +127602,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pX = pStart->pExpr; assert( pX!=0 ); testcase( pStart->leftCursor!=iCur ); /* transitive constraints */ - r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); - sqlite3VdbeAddOp3(v, aMoveOp[pX->op-TK_GT], iCur, addrBrk, r1); + if( sqlite3ExprIsVector(pX->pRight) ){ + r1 = rTemp = sqlite3GetTempReg(pParse); + codeExprOrVector(pParse, pX->pRight, r1, 1); + op = aMoveOp[(pX->op - TK_GT) | 0x0001]; + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pX->pRight, &rTemp); + disableTerm(pLevel, pStart); + op = aMoveOp[(pX->op - TK_GT)]; + } + sqlite3VdbeAddOp3(v, op, iCur, addrBrk, r1); VdbeComment((v, "pk")); VdbeCoverageIf(v, pX->op==TK_GT); VdbeCoverageIf(v, pX->op==TK_LE); @@ -124607,9 +127619,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ExprCacheAffinityChange(pParse, r1, 1); sqlite3ReleaseTempReg(pParse, rTemp); - disableTerm(pLevel, pStart); }else{ - sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrBrk); + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); } @@ -124621,13 +127632,17 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( testcase( pEnd->leftCursor!=iCur ); /* Transitive constraints */ testcase( pEnd->wtFlags & TERM_VIRTUAL ); memEndValue = ++pParse->nMem; - sqlite3ExprCode(pParse, pX->pRight, memEndValue); - if( pX->op==TK_LT || pX->op==TK_GT ){ + codeExprOrVector(pParse, pX->pRight, memEndValue, 1); + if( 0==sqlite3ExprIsVector(pX->pRight) + && (pX->op==TK_LT || pX->op==TK_GT) + ){ testOp = bRev ? OP_Le : OP_Ge; }else{ testOp = bRev ? OP_Lt : OP_Gt; } - disableTerm(pLevel, pEnd); + if( 0==sqlite3ExprIsVector(pX->pRight) ){ + disableTerm(pLevel, pEnd); + } } start = sqlite3VdbeCurrentAddr(v); pLevel->op = bRev ? OP_Prev : OP_Next; @@ -124694,6 +127709,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( OP_IdxLT, /* 3: (end_constraints && bRev && endEq) */ }; u16 nEq = pLoop->u.btree.nEq; /* Number of == or IN terms */ + u16 nBtm = pLoop->u.btree.nBtm; /* Length of BTM vector */ + u16 nTop = pLoop->u.btree.nTop; /* Length of TOP vector */ int regBase; /* Base register holding constraint values */ WhereTerm *pRangeStart = 0; /* Inequality constraint at range start */ WhereTerm *pRangeEnd = 0; /* Inequality constraint at range end */ @@ -124706,7 +127723,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int nExtraReg = 0; /* Number of extra registers needed */ int op; /* Instruction opcode */ char *zStartAff; /* Affinity for start of range constraint */ - char cEndAff = 0; /* Affinity for end of range constraint */ + char *zEndAff = 0; /* Affinity for end of range constraint */ u8 bSeekPastNull = 0; /* True to seek past initial nulls */ u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */ @@ -124740,14 +127757,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); /* Like optimization range constraints always occur in pairs */ assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ pRangeEnd = pLoop->aLTerm[j++]; - nExtraReg = 1; + nExtraReg = MAX(nExtraReg, pLoop->u.btree.nTop); #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( (pRangeEnd->wtFlags & TERM_LIKEOPT)!=0 ){ assert( pRangeStart!=0 ); /* LIKE opt constraints */ @@ -124765,11 +127782,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pLevel->iLikeRepCntr |= bRev ^ (pIdx->aSortOrder[nEq]==SQLITE_SO_DESC); } #endif - if( pRangeStart==0 - && (j = pIdx->aiColumn[nEq])>=0 - && pIdx->pTable->aCol[j].notNull==0 - ){ - bSeekPastNull = 1; + if( pRangeStart==0 ){ + j = pIdx->aiColumn[nEq]; + if( (j>=0 && pIdx->pTable->aCol[j].notNull==0) || j==XN_EXPR ){ + bSeekPastNull = 1; + } } } assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); @@ -124783,6 +127800,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); + SWAP(u8, nBtm, nTop); } /* Generate code to evaluate all constraint terms using == or IN @@ -124792,7 +127810,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, pRangeEnd); regBase = codeAllEqualityTerms(pParse,pLevel,bRev,nExtraReg,&zStartAff); assert( zStartAff==0 || sqlite3Strlen30(zStartAff)>=nEq ); - if( zStartAff ) cEndAff = zStartAff[nEq]; + if( zStartAff && nTop ){ + zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]); + } addrNxt = pLevel->addrNxt; testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); @@ -124807,7 +127827,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( nConstraint = nEq; if( pRangeStart ){ Expr *pRight = pRangeStart->pExpr->pRight; - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nBtm); whereLikeOptimizationStringFixup(v, pLevel, pRangeStart); if( (pRangeStart->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -124816,18 +127836,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverage(v); } if( zStartAff ){ - if( sqlite3CompareAffinity(pRight, zStartAff[nEq])==SQLITE_AFF_BLOB){ - /* Since the comparison is to be performed with no conversions - ** applied to the operands, set the affinity to apply to pRight to - ** SQLITE_AFF_BLOB. */ - zStartAff[nEq] = SQLITE_AFF_BLOB; - } - if( sqlite3ExprNeedsNoAffinityChange(pRight, zStartAff[nEq]) ){ - zStartAff[nEq] = SQLITE_AFF_BLOB; - } + updateRangeAffinityStr(pRight, nBtm, &zStartAff[nEq]); } - nConstraint++; + nConstraint += nBtm; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeStart); + }else{ + startEq = 1; + } bSeekPastNull = 0; }else if( bSeekPastNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); @@ -124860,7 +127877,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; sqlite3ExprCacheRemove(pParse, regBase+nEq, 1); - sqlite3ExprCode(pParse, pRight, regBase+nEq); + codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 && sqlite3ExprCanBeNull(pRight) @@ -124868,19 +127885,27 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+nEq, addrNxt); VdbeCoverage(v); } - if( sqlite3CompareAffinity(pRight, cEndAff)!=SQLITE_AFF_BLOB - && !sqlite3ExprNeedsNoAffinityChange(pRight, cEndAff) - ){ - codeApplyAffinity(pParse, regBase+nEq, 1, &cEndAff); + if( zEndAff ){ + updateRangeAffinityStr(pRight, nTop, zEndAff); + codeApplyAffinity(pParse, regBase+nEq, nTop, zEndAff); + }else{ + assert( pParse->db->mallocFailed ); } - nConstraint++; + nConstraint += nTop; testcase( pRangeEnd->wtFlags & TERM_VIRTUAL ); + + if( sqlite3ExprIsVector(pRight)==0 ){ + disableTerm(pLevel, pRangeEnd); + }else{ + endEq = 1; + } }else if( bStopAtNull ){ sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); endEq = 0; nConstraint++; } sqlite3DbFree(db, zStartAff); + sqlite3DbFree(db, zEndAff); /* Top of the loop body */ pLevel->p2 = sqlite3VdbeCurrentAddr(v); @@ -124896,12 +127921,13 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } /* Seek the table cursor, if required */ - disableTerm(pLevel, pRangeStart); - disableTerm(pLevel, pRangeEnd); if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ - if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE)!=0 ){ + if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE) || ( + (pWInfo->wctrlFlags & WHERE_SEEK_UNIQ_TABLE) + && (pWInfo->eOnePass==ONEPASS_SINGLE) + )){ iRowidReg = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); sqlite3ExprCacheStore(pParse, iCur, -1, iRowidReg); @@ -124921,9 +127947,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } - /* Record the instruction used to terminate the loop. Disable - ** WHERE clause terms made redundant by the index range scan. - */ + /* Record the instruction used to terminate the loop. */ if( pLoop->wsFlags & WHERE_ONEROW ){ pLevel->op = OP_Noop; }else if( bRev ){ @@ -125000,7 +128024,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( u16 wctrlFlags; /* Flags for sub-WHERE clause */ Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ Table *pTab = pTabItem->pTab; - + pTerm = pLoop->aLTerm[0]; assert( pTerm!=0 ); assert( pTerm->eOperator & WO_OR ); @@ -125086,7 +128110,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr); } if( pAndExpr ){ - pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr, 0); + pAndExpr = sqlite3PExpr(pParse, TK_AND|TKFLG_DONTFOLD, 0, pAndExpr); } } @@ -125159,7 +128183,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } if( iSet>=0 ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, r, nPk, regRowid); - sqlite3VdbeAddOp3(v, OP_IdxInsert, regRowset, regRowid, 0); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, regRowset, regRowid, + r, nPk); if( iSet ) sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); } @@ -125241,7 +128266,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, 0); pLevel->op = aStep[bRev]; pLevel->p1 = iCur; - pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrBrk); + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; @@ -125301,7 +128326,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** the implied "t1.a=123" constraint. */ for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ - Expr *pE, *pEAlt; + Expr *pE, sEAlt; WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; @@ -125319,13 +128344,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( testcase( pAlt->eOperator & WO_IS ); testcase( pAlt->eOperator & WO_IN ); VdbeModuleComment((v, "begin transitive constraint")); - pEAlt = sqlite3StackAllocRaw(db, sizeof(*pEAlt)); - if( pEAlt ){ - *pEAlt = *pAlt->pExpr; - pEAlt->pLeft = pE->pLeft; - sqlite3ExprIfFalse(pParse, pEAlt, addrCont, SQLITE_JUMPIFNULL); - sqlite3StackFree(db, pEAlt); - } + sEAlt = *pAlt->pExpr; + sEAlt.pLeft = pE->pLeft; + sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); } /* For a LEFT OUTER JOIN, generate code that will record the fact that @@ -125434,7 +128455,6 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ sqlite3DbFree(db, pOld); } pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); - memset(&pWC->a[pWC->nTerm], 0, sizeof(pWC->a[0])*(pWC->nSlot-pWC->nTerm)); } pTerm = &pWC->a[idx = pWC->nTerm++]; if( p && ExprHasProperty(p, EP_Unlikely) ){ @@ -125446,13 +128466,15 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ pTerm->wtFlags = wtFlags; pTerm->pWC = pWC; pTerm->iParent = -1; + memset(&pTerm->eOperator, 0, + sizeof(WhereTerm) - offsetof(WhereTerm,eOperator)); return idx; } /* ** Return TRUE if the given operator is one of the operators that is ** allowed for an indexable WHERE clause term. The allowed operators are -** "=", "<", ">", "<=", ">=", "IN", and "IS NULL" +** "=", "<", ">", "<=", ">=", "IN", "IS", and "IS NULL" */ static int allowedOp(int op){ assert( TK_GT>TK_EQ && TK_GTx.pList; pLeft = pList->a[1].pExpr; - if( pLeft->op!=TK_COLUMN - || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT - || IsVirtual(pLeft->pTab) /* Value might be numeric */ - ){ - /* IMP: R-02065-49465 The left-hand side of the LIKE or GLOB operator must - ** be the name of an indexed column with TEXT affinity. */ - return 0; - } - assert( pLeft->iColumn!=(-1) ); /* Because IPK never has AFF_TEXT */ pRight = sqlite3ExprSkipCollate(pList->a[0].pExpr); op = pRight->op; @@ -125594,6 +128607,23 @@ static int isLikeOrGlob( z = pRight->u.zToken; } if( z ){ + + /* If the RHS begins with a digit or a minus sign, then the LHS must + ** be an ordinary column (not a virtual table column) with TEXT affinity. + ** Otherwise the LHS might be numeric and "lhs >= rhs" would be false + ** even though "lhs LIKE rhs" is true. But if the RHS does not start + ** with a digit or '-', then "lhs LIKE rhs" will always be false if + ** the LHS is numeric and so the optimization still works. + */ + if( sqlite3Isdigit(z[0]) || z[0]=='-' ){ + if( pLeft->op!=TK_COLUMN + || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT + || IsVirtual(pLeft->pTab) /* Value might be numeric */ + ){ + sqlite3ValueFree(pVal); + return 0; + } + } cnt = 0; while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){ cnt++; @@ -125647,7 +128677,7 @@ static int isMatchOfColumn( Expr *pExpr, /* Test this expression */ unsigned char *peOp2 /* OUT: 0 for MATCH, or else an op2 value */ ){ - struct Op2 { + static const struct Op2 { const char *zOp; unsigned char eOp2; } aOp[] = { @@ -126090,7 +129120,7 @@ static void exprAnalyzeOrTerm( } assert( pLeft!=0 ); pDup = sqlite3ExprDup(db, pLeft, 0); - pNew = sqlite3PExpr(pParse, TK_IN, pDup, 0, 0); + pNew = sqlite3PExpr(pParse, TK_IN, pDup, 0); if( pNew ){ int idxNew; transferJoinMarkings(pNew, pExpr); @@ -126180,7 +129210,8 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ ** in any index. Return TRUE (1) if pExpr is an indexed term and return ** FALSE (0) if not. If TRUE is returned, also set *piCur to the cursor ** number of the table that is indexed and *piColumn to the column number -** of the column that is indexed, or -2 if an expression is being indexed. +** of the column that is indexed, or XN_EXPR (-2) if an expression is being +** indexed. ** ** If pExpr is a TK_COLUMN column reference, then this routine always returns ** true even if that particular column is not indexed, because the column @@ -126188,6 +129219,7 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ */ static int exprMightBeIndexed( SrcList *pFrom, /* The FROM clause */ + int op, /* The specific comparison operator */ Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ Expr *pExpr, /* An operand of a comparison operator */ int *piCur, /* Write the referenced table cursor number here */ @@ -126196,6 +129228,17 @@ static int exprMightBeIndexed( Index *pIdx; int i; int iCur; + + /* If this expression is a vector to the left or right of a + ** inequality constraint (>, <, >= or <=), perform the processing + ** on the first element of the vector. */ + assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); + assert( TK_ISop==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + pExpr = pExpr->x.pList->a[0].pExpr; + } + if( pExpr->op==TK_COLUMN ){ *piCur = pExpr->iTable; *piColumn = pExpr->iColumn; @@ -126208,10 +129251,10 @@ static int exprMightBeIndexed( for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( pIdx->aColExpr==0 ) continue; for(i=0; inKeyCol; i++){ - if( pIdx->aiColumn[i]!=(-2) ) continue; - if( sqlite3ExprCompare(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ *piCur = iCur; - *piColumn = -2; + *piColumn = XN_EXPR; return 1; } } @@ -126256,6 +129299,7 @@ static void exprAnalyze( Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection */ unsigned char eOp2; /* op2 value for LIKE/REGEXP/GLOB */ + int nLeft; /* Number of elements on left side vector */ if( db->mallocFailed ){ return; @@ -126268,6 +129312,7 @@ static void exprAnalyze( op = pExpr->op; if( op==TK_IN ){ assert( pExpr->pRight==0 ); + if( sqlite3ExprCheckIN(pParse, pExpr) ) return; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ @@ -126284,6 +129329,10 @@ static void exprAnalyze( prereqAll |= x; extraRight = x-1; /* ON clause terms may not be used with an index ** on left table of a LEFT JOIN. Ticket #3015 */ + if( (prereqAll>>1)>=x ){ + sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); + return; + } } pTerm->prereqAll = prereqAll; pTerm->leftCursor = -1; @@ -126294,18 +129343,26 @@ static void exprAnalyze( Expr *pLeft = sqlite3ExprSkipCollate(pExpr->pLeft); Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; - if( exprMightBeIndexed(pSrc, prereqLeft, pLeft, &iCur, &iColumn) ){ + + if( pTerm->iField>0 ){ + assert( op==TK_IN ); + assert( pLeft->op==TK_VECTOR ); + pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr; + } + + if( exprMightBeIndexed(pSrc, op, prereqLeft, pLeft, &iCur, &iColumn) ){ pTerm->leftCursor = iCur; pTerm->u.leftColumn = iColumn; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; if( pRight - && exprMightBeIndexed(pSrc, pTerm->prereqRight, pRight, &iCur, &iColumn) + && exprMightBeIndexed(pSrc, op, pTerm->prereqRight, pRight, &iCur,&iColumn) ){ WhereTerm *pNew; Expr *pDup; u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */ + assert( pTerm->iField==0 ); if( pTerm->leftCursor>=0 ){ int idxNew; pDup = sqlite3ExprDup(db, pExpr, 0); @@ -126366,7 +129423,7 @@ static void exprAnalyze( int idxNew; pNewExpr = sqlite3PExpr(pParse, ops[i], sqlite3ExprDup(db, pExpr->pLeft, 0), - sqlite3ExprDup(db, pList->a[i].pExpr, 0), 0); + sqlite3ExprDup(db, pList->a[i].pExpr, 0)); transferJoinMarkings(pNewExpr, pExpr); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); @@ -126451,7 +129508,7 @@ static void exprAnalyze( pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); pNewExpr1 = sqlite3PExpr(pParse, TK_GE, sqlite3ExprAddCollateString(pParse,pNewExpr1,zCollSeqName), - pStr1, 0); + pStr1); transferJoinMarkings(pNewExpr1, pExpr); idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); testcase( idxNew1==0 ); @@ -126459,7 +129516,7 @@ static void exprAnalyze( pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); pNewExpr2 = sqlite3PExpr(pParse, TK_LT, sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), - pStr2, 0); + pStr2); transferJoinMarkings(pNewExpr2, pExpr); idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); testcase( idxNew2==0 ); @@ -126492,7 +129549,7 @@ static void exprAnalyze( if( (prereqExpr & prereqColumn)==0 ){ Expr *pNewExpr; pNewExpr = sqlite3PExpr(pParse, TK_MATCH, - 0, sqlite3ExprDup(db, pRight, 0), 0); + 0, sqlite3ExprDup(db, pRight, 0)); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); pNewTerm = &pWC->a[idxNew]; @@ -126509,6 +129566,59 @@ static void exprAnalyze( } #endif /* SQLITE_OMIT_VIRTUALTABLE */ + /* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create + ** new terms for each component comparison - "a = ?" and "b = ?". The + ** new terms completely replace the original vector comparison, which is + ** no longer used. + ** + ** This is only required if at least one side of the comparison operation + ** is not a sub-select. */ + if( pWC->op==TK_AND + && (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 + && sqlite3ExprVectorSize(pExpr->pRight)==nLeft + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0) + ){ + int i; + for(i=0; ipLeft, i); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i); + + pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); + transferJoinMarkings(pNew, pExpr); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); + exprAnalyze(pSrc, pWC, idxNew); + } + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags = TERM_CODED|TERM_VIRTUAL; /* Disable the original */ + pTerm->eOperator = 0; + } + + /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create + ** a virtual term for each vector component. The expression object + ** used by each such virtual term is pExpr (the full vector IN(...) + ** expression). The WhereTerm.iField variable identifies the index within + ** the vector on the LHS that the virtual term represents. + ** + ** This only works if the RHS is a simple SELECT, not a compound + */ + if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0 + && pExpr->pLeft->op==TK_VECTOR + && pExpr->x.pSelect->pPrior==0 + ){ + int i; + for(i=0; ipLeft); i++){ + int idxNew; + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL); + pWC->a[idxNew].iField = i+1; + exprAnalyze(pSrc, pWC, idxNew); + markTermAsChild(pWC, idxNew, idxTerm); + } + } + #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 /* When sqlite_stat3 histogram data is available an operator of the ** form "x IS NOT NULL" can sometimes be evaluated more efficiently @@ -126529,7 +129639,7 @@ static void exprAnalyze( pNewExpr = sqlite3PExpr(pParse, TK_GT, sqlite3ExprDup(db, pLeft, 0), - sqlite3PExpr(pParse, TK_NULL, 0, 0, 0), 0); + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); @@ -126550,6 +129660,8 @@ static void exprAnalyze( /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. */ + testcase( pTerm!=&pWC->a[idxTerm] ); + pTerm = &pWC->a[idxTerm]; pTerm->prereqRight |= extraRight; } @@ -126632,13 +129744,14 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ ** tree. */ SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ - Bitmask mask = 0; + Bitmask mask; if( p==0 ) return 0; if( p->op==TK_COLUMN ){ mask = sqlite3WhereGetMask(pMaskSet, p->iTable); return mask; } - mask = sqlite3WhereExprUsage(pMaskSet, p->pRight); + assert( !ExprHasProperty(p, EP_TokenOnly) ); + mask = p->pRight ? sqlite3WhereExprUsage(pMaskSet, p->pRight) : 0; if( p->pLeft ) mask |= sqlite3WhereExprUsage(pMaskSet, p->pLeft); if( ExprHasProperty(p, EP_xIsSelect) ){ mask |= exprSelectUsage(pMaskSet, p->x.pSelect); @@ -126706,13 +129819,13 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( pTab->zName, j); return; } - pColRef = sqlite3PExpr(pParse, TK_COLUMN, 0, 0, 0); + pColRef = sqlite3ExprAlloc(pParse->db, TK_COLUMN, 0, 0); if( pColRef==0 ) return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; pColRef->pTab = pTab; pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, - sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); + sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0)); whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); } } @@ -126919,16 +130032,19 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ WhereTerm *pTerm; /* The term being tested */ int k = pScan->k; /* Where to start scanning */ - while( pScan->iEquiv<=pScan->nEquiv ){ - iCur = pScan->aiCur[pScan->iEquiv-1]; + assert( pScan->iEquiv<=pScan->nEquiv ); + pWC = pScan->pWC; + while(1){ iColumn = pScan->aiColumn[pScan->iEquiv-1]; - if( iColumn==XN_EXPR && pScan->pIdxExpr==0 ) return 0; - while( (pWC = pScan->pWC)!=0 ){ + iCur = pScan->aiCur[pScan->iEquiv-1]; + assert( pWC!=0 ); + do{ for(pTerm=pWC->a+k; knTerm; k++, pTerm++){ if( pTerm->leftCursor==iCur && pTerm->u.leftColumn==iColumn && (iColumn!=XN_EXPR - || sqlite3ExprCompare(pTerm->pExpr->pLeft,pScan->pIdxExpr,iCur)==0) + || sqlite3ExprCompareSkip(pTerm->pExpr->pLeft, + pScan->pIdxExpr,iCur)==0) && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) ){ if( (pTerm->eOperator & WO_EQUIV)!=0 @@ -126973,15 +130089,17 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ testcase( pTerm->eOperator & WO_IS ); continue; } + pScan->pWC = pWC; pScan->k = k+1; return pTerm; } } } - pScan->pWC = pScan->pWC->pOuter; + pWC = pWC->pOuter; k = 0; - } - pScan->pWC = pScan->pOrigWC; + }while( pWC!=0 ); + if( pScan->iEquiv>=pScan->nEquiv ) break; + pWC = pScan->pOrigWC; k = 0; pScan->iEquiv++; } @@ -127015,24 +130133,25 @@ static WhereTerm *whereScanInit( u32 opMask, /* Operator(s) to scan for */ Index *pIdx /* Must be compatible with this index */ ){ - int j = 0; - - /* memset(pScan, 0, sizeof(*pScan)); */ pScan->pOrigWC = pWC; pScan->pWC = pWC; pScan->pIdxExpr = 0; + pScan->idxaff = 0; + pScan->zCollName = 0; if( pIdx ){ - j = iColumn; + int j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==XN_EXPR ) pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; - if( iColumn==pIdx->pTable->iPKey ) iColumn = XN_ROWID; - } - if( pIdx && iColumn>=0 ){ - pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; - pScan->zCollName = pIdx->azColl[j]; - }else{ - pScan->idxaff = 0; - pScan->zCollName = 0; + if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + pScan->zCollName = pIdx->azColl[j]; + }else if( iColumn==pIdx->pTable->iPKey ){ + iColumn = XN_ROWID; + }else if( iColumn>=0 ){ + pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; + pScan->zCollName = pIdx->azColl[j]; + } + }else if( iColumn==XN_EXPR ){ + return 0; } pScan->opMask = opMask; pScan->k = 0; @@ -127232,14 +130351,16 @@ static LogEst estLog(LogEst N){ ** value stored in its output register. */ static void translateColumnToCopy( - Vdbe *v, /* The VDBE containing code to translate */ + Parse *pParse, /* Parsing context */ int iStart, /* Translate from this opcode to the end */ int iTabCur, /* OP_Column/OP_Rowid references to this table */ int iRegister, /* The first column is in this register */ int bIncrRowid /* If non-zero, transform OP_rowid to OP_AddImm(1) */ ){ + Vdbe *v = pParse->pVdbe; VdbeOp *pOp = sqlite3VdbeGetOp(v, iStart); int iEnd = sqlite3VdbeCurrentAddr(v); + if( pParse->db->mallocFailed ) return; for(; iStartp1!=iTabCur ) continue; if( pOp->opcode==OP_Column ){ @@ -127372,7 +130493,7 @@ static void constructAutomaticIndex( ** transient index on 2nd and subsequent iterations of the loop. */ v = pParse->pVdbe; assert( v!=0 ); - addrInit = sqlite3CodeOnce(pParse); VdbeCoverage(v); + addrInit = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ @@ -127517,7 +130638,9 @@ static void constructAutomaticIndex( if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); if( pTabItem->fg.viaCoroutine ){ sqlite3VdbeChangeP2(v, addrCounter, regBase+n); - translateColumnToCopy(v, addrTop, pLevel->iTabCur, pTabItem->regResult, 1); + testcase( pParse->db->mallocFailed ); + translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, + pTabItem->regResult, 1); sqlite3VdbeGoto(v, addrTop); pTabItem->fg.viaCoroutine = 0; }else{ @@ -127547,7 +130670,8 @@ static sqlite3_index_info *allocateIndexInfo( WhereClause *pWC, Bitmask mUnusable, /* Ignore terms with these prereqs */ struct SrcList_item *pSrc, - ExprList *pOrderBy + ExprList *pOrderBy, + u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; int nTerm; @@ -127557,6 +130681,7 @@ static sqlite3_index_info *allocateIndexInfo( WhereTerm *pTerm; int nOrderBy; sqlite3_index_info *pIdxInfo; + u16 mNoOmit = 0; /* Count the number of possible WHERE clause constraints referring ** to this virtual table */ @@ -127645,6 +130770,15 @@ static sqlite3_index_info *allocateIndexInfo( assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); assert( WO_MATCH==SQLITE_INDEX_CONSTRAINT_MATCH ); assert( pTerm->eOperator & (WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_MATCH) ); + + if( op & (WO_LT|WO_LE|WO_GT|WO_GE) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) + ){ + if( i<16 ) mNoOmit |= (1 << i); + if( op==WO_LT ) pIdxCons[j].op = WO_LE; + if( op==WO_GT ) pIdxCons[j].op = WO_GE; + } + j++; } for(i=0; ia[i].sortOrder; } + *pmNoOmit = mNoOmit; return pIdxInfo; } @@ -127928,7 +131063,7 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ /* ** Return the affinity for a single column of an index. */ -static char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ +SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCol){ assert( iCol>=0 && iColnColumn ); if( !pIdx->zColAff ){ if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB; @@ -128105,7 +131240,8 @@ static int whereRangeScanEst( if( nEq==pBuilder->nRecValid ){ UnpackedRecord *pRec = pBuilder->pRec; tRowcnt a[2]; - u8 aff; + int nBtm = pLoop->u.btree.nBtm; + int nTop = pLoop->u.btree.nTop; /* Variable iLower will be set to the estimate of the number of rows in ** the index that are less than the lower bound of the range query. The @@ -128135,8 +131271,6 @@ static int whereRangeScanEst( testcase( pRec->nField!=pBuilder->nRecValid ); pRec->nField = pBuilder->nRecValid; } - aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq); - assert( nEq!=p->nKeyCol || aff==SQLITE_AFF_INTEGER ); /* Determine iLower and iUpper using ($P) only. */ if( nEq==0 ){ iLower = 0; @@ -128155,17 +131289,20 @@ static int whereRangeScanEst( if( p->aSortOrder[nEq] ){ /* The roles of pLower and pUpper are swapped for a DESC index */ SWAP(WhereTerm*, pLower, pUpper); + SWAP(int, nBtm, nTop); } /* If possible, improve on the iLower estimate using ($P:$L). */ if( pLower ){ - int bOk; /* True if value is extracted from pExpr */ + int n; /* Values extracted from pExpr */ Expr *pExpr = pLower->pExpr->pRight; - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nBtm, nEq, &n); + if( rc==SQLITE_OK && n ){ tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); iLwrIdx = whereKeyStats(pParse, p, pRec, 0, a); - iNew = a[0] + ((pLower->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + iNew = a[0] + ((pLower->eOperator & mask) ? a[1] : 0); if( iNew>iLower ) iLower = iNew; nOut--; pLower = 0; @@ -128174,13 +131311,15 @@ static int whereRangeScanEst( /* If possible, improve on the iUpper estimate using ($P:$U). */ if( pUpper ){ - int bOk; /* True if value is extracted from pExpr */ + int n; /* Values extracted from pExpr */ Expr *pExpr = pUpper->pExpr->pRight; - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq, &bOk); - if( rc==SQLITE_OK && bOk ){ + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, nTop, nEq, &n); + if( rc==SQLITE_OK && n ){ tRowcnt iNew; + u16 mask = WO_GT|WO_LE; + if( sqlite3ExprVectorSize(pExpr)>n ) mask = (WO_LE|WO_LT); iUprIdx = whereKeyStats(pParse, p, pRec, 1, a); - iNew = a[0] + ((pUpper->eOperator & (WO_GT|WO_LE)) ? a[1] : 0); + iNew = a[0] + ((pUpper->eOperator & mask) ? a[1] : 0); if( iNewpNew->u.btree.pIndex; int nEq = pBuilder->pNew->u.btree.nEq; UnpackedRecord *pRec = pBuilder->pRec; - u8 aff; /* Column affinity */ int rc; /* Subfunction return code */ tRowcnt a[2]; /* Statistics */ int bOk; @@ -128294,8 +131432,7 @@ static int whereEqualScanEst( return SQLITE_OK; } - aff = sqlite3IndexColumnAffinity(pParse->db, p, nEq-1); - rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, aff, nEq-1, &bOk); + rc = sqlite3Stat4ProbeSetValue(pParse, p, &pRec, pExpr, 1, nEq-1, &bOk); pBuilder->pRec = pRec; if( rc!=SQLITE_OK ) return rc; if( bOk==0 ) return SQLITE_NOTFOUND; @@ -128384,9 +131521,14 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){ sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); } sqlite3DebugPrintf( - "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x\n", + "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x", iTerm, pTerm, zType, zLeft, pTerm->truthProb, pTerm->eOperator, pTerm->wtFlags); + if( pTerm->iField ){ + sqlite3DebugPrintf(" iField=%d\n", pTerm->iField); + }else{ + sqlite3DebugPrintf("\n"); + } sqlite3TreeViewExpr(0, pTerm->pExpr, 0); } } @@ -128908,6 +132050,72 @@ static void whereLoopOutputAdjust( if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; } +/* +** Term pTerm is a vector range comparison operation. The first comparison +** in the vector can be optimized using column nEq of the index. This +** function returns the total number of vector elements that can be used +** as part of the range comparison. +** +** For example, if the query is: +** +** WHERE a = ? AND (b, c, d) > (?, ?, ?) +** +** and the index: +** +** CREATE INDEX ... ON (a, b, c, d, e) +** +** then this function would be invoked with nEq=1. The value returned in +** this case is 3. +*/ +static int whereRangeVectorLen( + Parse *pParse, /* Parsing context */ + int iCur, /* Cursor open on pIdx */ + Index *pIdx, /* The index to be used for a inequality constraint */ + int nEq, /* Number of prior equality constraints on same index */ + WhereTerm *pTerm /* The vector inequality constraint */ +){ + int nCmp = sqlite3ExprVectorSize(pTerm->pExpr->pLeft); + int i; + + nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); + for(i=1; ipExpr->pLeft->x.pList->a[i].pExpr; + Expr *pRhs = pTerm->pExpr->pRight; + if( pRhs->flags & EP_xIsSelect ){ + pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; + }else{ + pRhs = pRhs->x.pList->a[i].pExpr; + } + + /* Check that the LHS of the comparison is a column reference to + ** the right column of the right source table. And that the sort + ** order of the index column is the same as the sort order of the + ** leftmost index column. */ + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + || pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq] + ){ + break; + } + + testcase( pLhs->iColumn==XN_ROWID ); + aff = sqlite3CompareAffinity(pRhs, sqlite3ExprAffinity(pLhs)); + idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); + if( aff!=idxaff ) break; + + pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); + if( pColl==0 ) break; + if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; + } + return i; +} + /* ** Adjust the cost C by the costMult facter T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT @@ -128946,6 +132154,8 @@ static int whereLoopAddBtreeIndex( Bitmask saved_prereq; /* Original value of pNew->prereq */ u16 saved_nLTerm; /* Original value of pNew->nLTerm */ u16 saved_nEq; /* Original value of pNew->u.btree.nEq */ + u16 saved_nBtm; /* Original value of pNew->u.btree.nBtm */ + u16 saved_nTop; /* Original value of pNew->u.btree.nTop */ u16 saved_nSkip; /* Original value of pNew->nSkip */ u32 saved_wsFlags; /* Original value of pNew->wsFlags */ LogEst saved_nOut; /* Original value of pNew->nOut */ @@ -128956,12 +132166,15 @@ static int whereLoopAddBtreeIndex( pNew = pBuilder->pNew; if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; + WHERETRACE(0x800, ("BEGIN addBtreeIdx(%s), nEq=%d\n", + pProbe->zName, pNew->u.btree.nEq)); assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 ); assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 ); if( pNew->wsFlags & WHERE_BTM_LIMIT ){ opMask = WO_LT|WO_LE; }else{ + assert( pNew->u.btree.nBtm==0 ); opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); @@ -128969,6 +132182,8 @@ static int whereLoopAddBtreeIndex( assert( pNew->u.btree.nEqnColumn ); saved_nEq = pNew->u.btree.nEq; + saved_nBtm = pNew->u.btree.nBtm; + saved_nTop = pNew->u.btree.nTop; saved_nSkip = pNew->nSkip; saved_nLTerm = pNew->nLTerm; saved_wsFlags = pNew->wsFlags; @@ -129010,8 +132225,15 @@ static int whereLoopAddBtreeIndex( continue; } + if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ + pBuilder->bldFlags |= SQLITE_BLDF_UNIQUE; + }else{ + pBuilder->bldFlags |= SQLITE_BLDF_INDEXED; + } pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nLTerm = saved_nLTerm; if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTerm; @@ -129028,14 +132250,23 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags |= WHERE_COLUMN_IN; if( ExprHasProperty(pExpr, EP_xIsSelect) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ + int i; nIn = 46; assert( 46==sqlite3LogEst(25) ); + + /* The expression may actually be of the form (x, y) IN (SELECT...). + ** In this case there is a separate term for each of (x) and (y). + ** However, the nIn multiplier should only be applied once, not once + ** for each such term. The following loop checks that pTerm is the + ** first such term in use, and sets nIn back to 0 if it is not. */ + for(i=0; inLTerm-1; i++){ + if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0; + } }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ /* "x IN (value, value, ...)" */ nIn = sqlite3LogEst(pExpr->x.pList->nExpr); + assert( nIn>0 ); /* RHS always has 2 or more terms... The parser + ** changes "x IN (?)" into "x=?". */ } - assert( nIn>0 ); /* RHS always has 2 or more terms... The parser - ** changes "x IN (?)" into "x=?". */ - }else if( eOp & (WO_EQ|WO_IS) ){ int iCol = pProbe->aiColumn[saved_nEq]; pNew->wsFlags |= WHERE_COLUMN_EQ; @@ -129055,6 +132286,9 @@ static int whereLoopAddBtreeIndex( testcase( eOp & WO_GT ); testcase( eOp & WO_GE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pBtm = pTerm; pTop = 0; if( pTerm->wtFlags & TERM_LIKEOPT ){ @@ -129067,12 +132301,16 @@ static int whereLoopAddBtreeIndex( if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ pNew->aLTerm[pNew->nLTerm++] = pTop; pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; } }else{ assert( eOp & (WO_LT|WO_LE) ); testcase( eOp & WO_LT ); testcase( eOp & WO_LE ); pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = whereRangeVectorLen( + pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm + ); pTop = pTerm; pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? pNew->aLTerm[pNew->nLTerm-2] : 0; @@ -129172,6 +132410,8 @@ static int whereLoopAddBtreeIndex( } pNew->prereq = saved_prereq; pNew->u.btree.nEq = saved_nEq; + pNew->u.btree.nBtm = saved_nBtm; + pNew->u.btree.nTop = saved_nTop; pNew->nSkip = saved_nSkip; pNew->wsFlags = saved_wsFlags; pNew->nOut = saved_nOut; @@ -129211,6 +132451,8 @@ static int whereLoopAddBtreeIndex( pNew->wsFlags = saved_wsFlags; } + WHERETRACE(0x800, ("END addBtreeIdx(%s), nEq=%d, rc=%d\n", + pProbe->zName, saved_nEq, rc)); return rc; } @@ -129293,7 +132535,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ /* ** Add all WhereLoop objects for a single table of the join where the table -** is idenfied by pBuilder->pNew->iTab. That table is guaranteed to be +** is identified by pBuilder->pNew->iTab. That table is guaranteed to be ** a b-tree table, not a virtual table. ** ** The costs (WhereLoop.rRun) of the b-tree loops added by this function @@ -129447,6 +132689,8 @@ static int whereLoopAddBtree( } rSize = pProbe->aiRowLogEst[0]; pNew->u.btree.nEq = 0; + pNew->u.btree.nBtm = 0; + pNew->u.btree.nTop = 0; pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; @@ -129533,7 +132777,15 @@ static int whereLoopAddBtree( } } + pBuilder->bldFlags = 0; rc = whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, 0); + if( pBuilder->bldFlags==SQLITE_BLDF_INDEXED ){ + /* If a non-unique index is used, or if a prefix of the key for + ** unique index is used (making the index functionally non-unique) + ** then the sqlite_stat1 data becomes important for scoring the + ** plan */ + pTab->tabFlags |= TF_StatsUsed; + } #ifdef SQLITE_ENABLE_STAT3_OR_STAT4 sqlite3Stat4ProbeFree(pBuilder->pRec); pBuilder->nRecValid = 0; @@ -129575,6 +132827,7 @@ static int whereLoopAddVirtualOne( Bitmask mUsable, /* Mask of usable tables */ u16 mExclude, /* Exclude terms using these operators */ sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ + u16 mNoOmit, /* Do not omit these constraints */ int *pbIn /* OUT: True if plan uses an IN(...) op */ ){ WhereClause *pWC = pBuilder->pWC; @@ -129663,6 +132916,7 @@ static int whereLoopAddVirtualOne( } } } + pNew->u.vtab.omitMask &= ~mNoOmit; pNew->nLTerm = mxTerm+1; assert( pNew->nLTerm<=pNew->nLSlot ); @@ -129736,6 +132990,7 @@ static int whereLoopAddVirtual( int bIn; /* True if plan uses IN(...) operator */ WhereLoop *pNew; Bitmask mBest; /* Tables used by best possible plan */ + u16 mNoOmit; assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; @@ -129744,7 +132999,8 @@ static int whereLoopAddVirtual( pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; assert( IsVirtual(pSrc->pTab) ); - p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy); + p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy, + &mNoOmit); if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; @@ -129758,7 +133014,7 @@ static int whereLoopAddVirtual( /* First call xBestIndex() with all constraints usable. */ WHERETRACE(0x40, (" VirtualOne: all usable\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, &bIn); + rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn); /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0), @@ -129775,7 +133031,8 @@ static int whereLoopAddVirtual( ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, WO_IN, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ @@ -129801,7 +133058,8 @@ static int whereLoopAddVirtual( if( mNext==mBest || mNext==mBestNoIn ) continue; WHERETRACE(0x40, (" VirtualOne: mPrev=%04llx mNext=%04llx\n", (sqlite3_uint64)mPrev, (sqlite3_uint64)mNext)); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mNext|mPrereq, 0, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mNext|mPrereq, 0, p, mNoOmit, &bIn); if( pNew->prereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; @@ -129813,7 +133071,8 @@ static int whereLoopAddVirtual( ** usable), make a call here with all source tables disabled */ if( rc==SQLITE_OK && seenZero==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, 0, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn); if( bIn==0 ) seenZeroNoIN = 1; } @@ -129822,7 +133081,8 @@ static int whereLoopAddVirtual( ** operator, make a final call to obtain one here. */ if( rc==SQLITE_OK && seenZeroNoIN==0 ){ WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, mPrereq, WO_IN, p, &bIn); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn); } } @@ -130166,20 +133426,42 @@ static i8 wherePathSatisfiesOrderBy( rev = revSet = 0; distinctColumns = 0; for(j=0; ju.btree.nEq - && pLoop->nSkip==0 - && ((i = pLoop->aLTerm[j]->eOperator) & eqOpMask)!=0 - ){ - if( i & WO_ISNULL ){ - testcase( isOrderDistinct ); - isOrderDistinct = 0; + assert( j>=pLoop->u.btree.nEq + || (pLoop->aLTerm[j]==0)==(jnSkip) + ); + if( ju.btree.nEq && j>=pLoop->nSkip ){ + u16 eOp = pLoop->aLTerm[j]->eOperator; + + /* Skip over == and IS and ISNULL terms. (Also skip IN terms when + ** doing WHERE_ORDERBY_LIMIT processing). + ** + ** If the current term is a column of an ((?,?) IN (SELECT...)) + ** expression for which the SELECT returns more than one column, + ** check that it is the only column used by this loop. Otherwise, + ** if it is one of two or more, none of the columns can be + ** considered to match an ORDER BY term. */ + if( (eOp & eqOpMask)!=0 ){ + if( eOp & WO_ISNULL ){ + testcase( isOrderDistinct ); + isOrderDistinct = 0; + } + continue; + }else if( ALWAYS(eOp & WO_IN) ){ + /* ALWAYS() justification: eOp is an equality operator due to the + ** ju.btree.nEq constraint above. Any equality other + ** than WO_IN is captured by the previous "if". So this one + ** always has to be WO_IN. */ + Expr *pX = pLoop->aLTerm[j]->pExpr; + for(i=j+1; iu.btree.nEq; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + assert( (pLoop->aLTerm[i]->eOperator & WO_IN) ); + bOnce = 0; + break; + } + } } - continue; } /* Get the column number in the table (iColumn) and sort order @@ -130208,7 +133490,6 @@ static i8 wherePathSatisfiesOrderBy( /* Find the ORDER BY term that corresponds to the j-th column ** of the index and mark that ORDER BY term off */ - bOnce = 1; isMatch = 0; for(i=0; bOnce && ipDistinctSet, pFrom, + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pResultSet, pFrom, WHERE_DISTINCTBY, nLoop-1, pFrom->aLoop[nLoop-1], ¬Used); - if( rc==pWInfo->pDistinctSet->nExpr ){ + if( rc==pWInfo->pResultSet->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } } @@ -130700,13 +133981,20 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ pWInfo->nOBSat = 0; - if( nLoop>0 && (pFrom->aLoop[nLoop-1]->wsFlags & WHERE_ONEROW)==0 ){ - Bitmask m = 0; - int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, + if( nLoop>0 ){ + u32 wsFlags = pFrom->aLoop[nLoop-1]->wsFlags; + if( (wsFlags & WHERE_ONEROW)==0 + && (wsFlags&(WHERE_IPK|WHERE_COLUMN_IN))!=(WHERE_IPK|WHERE_COLUMN_IN) + ){ + Bitmask m = 0; + int rc = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, WHERE_ORDERBY_LIMIT, nLoop-1, pFrom->aLoop[nLoop-1], &m); - if( rc==pWInfo->pOrderBy->nExpr ){ - pWInfo->bOrderedInnerLoop = 1; - pWInfo->revMask = m; + testcase( wsFlags & WHERE_IPK ); + testcase( wsFlags & WHERE_COLUMN_IN ); + if( rc==pWInfo->pOrderBy->nExpr ){ + pWInfo->bOrderedInnerLoop = 1; + pWInfo->revMask = m; + } } } } @@ -130916,7 +134204,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( SrcList *pTabList, /* FROM clause: A list of all tables to be scanned */ Expr *pWhere, /* The WHERE clause */ ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ - ExprList *pDistinctSet, /* Try not to output two rows that duplicate these */ + ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number ** If WHERE_USE_LIMIT, then the limit amount */ @@ -130983,22 +134271,25 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ** some architectures. Hence the ROUND8() below. */ nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); - pWInfo = sqlite3DbMallocZero(db, nByteWInfo + sizeof(WhereLoop)); + pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); pWInfo = 0; goto whereBeginError; } - pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; - pWInfo->nLevel = nTabList; pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; - pWInfo->pDistinctSet = pDistinctSet; + pWInfo->pResultSet = pResultSet; + pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; + pWInfo->nLevel = nTabList; pWInfo->iBreak = pWInfo->iContinue = sqlite3VdbeMakeLabel(v); pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; + memset(&pWInfo->nOBSat, 0, + offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); + memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; sWLB.pWInfo = pWInfo; @@ -131067,13 +134358,13 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( db->mallocFailed ) goto whereBeginError; if( wctrlFlags & WHERE_WANT_DISTINCT ){ - if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pDistinctSet) ){ + if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; }else if( pOrderBy==0 ){ /* Try to ORDER BY the result set to make distinct processing easier */ pWInfo->wctrlFlags |= WHERE_DISTINCTBY; - pWInfo->pOrderBy = pDistinctSet; + pWInfo->pOrderBy = pResultSet; } } @@ -131149,10 +134440,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( #endif /* Attempt to omit tables from the join that do not effect the result */ if( pWInfo->nLevel>=2 - && pDistinctSet!=0 + && pResultSet!=0 && OptimizationEnabled(db, SQLITE_OmitNoopJoin) ){ - Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pDistinctSet); + Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); if( sWLB.pOrderBy ){ tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); } @@ -131402,10 +134693,12 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ sqlite3VdbeJumpHere(v, pIn->addrInTop+1); - sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); - VdbeCoverage(v); - VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen); - VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen); + if( pIn->eEndLoopOp!=OP_Noop ){ + sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); + VdbeCoverage(v); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_PrevIfOpen); + VdbeCoverageIf(v, pIn->eEndLoopOp==OP_NextIfOpen); + } sqlite3VdbeJumpHere(v, pIn->addrInTop-1); } } @@ -131424,13 +134717,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } #endif if( pLevel->iLeftJoin ){ + int ws = pLoop->wsFlags; addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - || (pLoop->wsFlags & WHERE_INDEXED)!=0 ); - if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 ){ + assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); + if( (ws & WHERE_IDX_ONLY)==0 ){ sqlite3VdbeAddOp1(v, OP_NullRow, pTabList->a[i].iCursor); } - if( pLoop->wsFlags & WHERE_INDEXED ){ + if( (ws & WHERE_INDEXED) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) + ){ sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); } if( pLevel->op==OP_Return ){ @@ -131463,33 +134758,13 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** the co-routine into OP_Copy of result contained in a register. ** OP_Rowid becomes OP_Null. */ - if( pTabItem->fg.viaCoroutine && !db->mallocFailed ){ - translateColumnToCopy(v, pLevel->addrBody, pLevel->iTabCur, + if( pTabItem->fg.viaCoroutine ){ + testcase( pParse->db->mallocFailed ); + translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur, pTabItem->regResult, 0); continue; } - /* Close all of the cursors that were opened by sqlite3WhereBegin. - ** Except, do not close cursors that will be reused by the OR optimization - ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors - ** created for the ONEPASS optimization. - */ - if( (pTab->tabFlags & TF_Ephemeral)==0 - && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 - ){ - int ws = pLoop->wsFlags; - if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); - } - if( (ws & WHERE_INDEXED)!=0 - && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 - && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] - ){ - sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); - } - } - /* If this scan uses an index, make VDBE code substitutions to read data ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can @@ -131528,7 +134803,8 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ pOp->p2 = x; pOp->p1 = pLevel->iIdxCur; } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 ); + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 + || pWInfo->eOnePass ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; @@ -131592,6 +134868,19 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ */ #define YYPARSEFREENEVERNULL 1 +/* +** In the amalgamation, the parse.c file generated by lemon and the +** tokenize.c file are concatenated. In that case, sqlite3RunParser() +** has access to the the size of the yyParser object and so the parser +** engine can be allocated from stack. In that case, only the +** sqlite3ParserInit() and sqlite3ParserFinalize() routines are invoked +** and the sqlite3ParserAlloc() and sqlite3ParserFree() routines can be +** omitted. +*/ +#ifdef SQLITE_AMALGAMATION +# define sqlite3Parser_ENGINEALWAYSONSTACK 1 +#endif + /* ** Alternative datatype for the argument to the malloc() routine passed ** into sqlite3ParserAlloc(). The default is size_t. @@ -131607,15 +134896,6 @@ struct LimitVal { Expr *pOffset; /* The OFFSET expression. NULL if there is none */ }; -/* -** An instance of this structure is used to store the LIKE, -** GLOB, NOT LIKE, and NOT GLOB operators. -*/ -struct LikeOp { - Token eOperator; /* "like" or "glob" or "regexp" */ - int bNot; /* True if the NOT keyword is present */ -}; - /* ** An instance of the following structure describes the event of a ** TRIGGER. "a" is the event type, one of TK_UPDATE, TK_INSERT, @@ -131627,11 +134907,6 @@ struct LikeOp { */ struct TrigEvent { int a; IdList * b; }; -/* -** An instance of this structure holds the ATTACH key and the key type. -*/ -struct AttachKey { int type; Token key; }; - /* ** Disable lookaside memory allocation for objects that might be ** shared across database connections. @@ -131678,7 +134953,24 @@ static void disableLookaside(Parse *pParse){ ** that created the expression. */ static void spanExpr(ExprSpan *pOut, Parse *pParse, int op, Token t){ - pOut->pExpr = sqlite3PExpr(pParse, op, 0, 0, &t); + Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = (u8)op; + p->flags = EP_Leaf; + p->iAgg = -1; + p->u.zToken = (char*)&p[1]; + memcpy(p->u.zToken, t.z, t.n); + p->u.zToken[t.n] = 0; + if( sqlite3Isquote(p->u.zToken[0]) ){ + if( p->u.zToken[0]=='"' ) p->flags |= EP_DblQuoted; + sqlite3Dequote(p->u.zToken); + } +#if SQLITE_MAX_EXPR_DEPTH>0 + p->nHeight = 1; +#endif + } + pOut->pExpr = p; pOut->zStart = t.z; pOut->zEnd = &t.z[t.n]; } @@ -131692,7 +134984,7 @@ static void disableLookaside(Parse *pParse){ ExprSpan *pLeft, /* The left operand, and output */ ExprSpan *pRight /* The right operand */ ){ - pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr, 0); + pLeft->pExpr = sqlite3PExpr(pParse, op, pLeft->pExpr, pRight->pExpr); pLeft->zEnd = pRight->zEnd; } @@ -131701,7 +134993,7 @@ static void disableLookaside(Parse *pParse){ */ static void exprNot(Parse *pParse, int doNot, ExprSpan *pSpan){ if( doNot ){ - pSpan->pExpr = sqlite3PExpr(pParse, TK_NOT, pSpan->pExpr, 0, 0); + pSpan->pExpr = sqlite3PExpr(pParse, TK_NOT, pSpan->pExpr, 0); } } @@ -131713,7 +135005,7 @@ static void disableLookaside(Parse *pParse){ ExprSpan *pOperand, /* The operand, and output */ Token *pPostOp /* The operand token for setting the span */ ){ - pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); + pOperand->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); pOperand->zEnd = &pPostOp->z[pPostOp->n]; } @@ -131738,7 +135030,7 @@ static void disableLookaside(Parse *pParse){ Token *pPreOp /* The operand token for setting the span */ ){ pOut->zStart = pPreOp->z; - pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0, 0); + pOut->pExpr = sqlite3PExpr(pParse, op, pOperand->pExpr, 0); pOut->zEnd = pOperand->zEnd; } @@ -131841,7 +135133,6 @@ typedef union { With* yy285; struct TrigEvent yy332; struct LimitVal yy354; - struct LikeOp yy392; struct {int value; int mask;} yy497; } YYMINORTYPE; #ifndef YYSTACKDEPTH @@ -131852,16 +135143,16 @@ typedef union { #define sqlite3ParserARG_FETCH Parse *pParse = yypParser->pParse #define sqlite3ParserARG_STORE yypParser->pParse = pParse #define YYFALLBACK 1 -#define YYNSTATE 443 -#define YYNRULE 328 -#define YY_MAX_SHIFT 442 -#define YY_MIN_SHIFTREDUCE 653 -#define YY_MAX_SHIFTREDUCE 980 -#define YY_MIN_REDUCE 981 -#define YY_MAX_REDUCE 1308 -#define YY_ERROR_ACTION 1309 -#define YY_ACCEPT_ACTION 1310 -#define YY_NO_ACTION 1311 +#define YYNSTATE 456 +#define YYNRULE 332 +#define YY_MAX_SHIFT 455 +#define YY_MIN_SHIFTREDUCE 668 +#define YY_MAX_SHIFTREDUCE 999 +#define YY_MIN_REDUCE 1000 +#define YY_MAX_REDUCE 1331 +#define YY_ERROR_ACTION 1332 +#define YY_ACCEPT_ACTION 1333 +#define YY_NO_ACTION 1334 /************* End control #defines *******************************************/ /* Define the yytestcase() macro to be a no-op if is not already defined @@ -131893,7 +135184,7 @@ typedef union { ** ** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE ** and YY_MAX_REDUCE - +** ** N == YY_ERROR_ACTION A syntax error has occurred. ** ** N == YY_ACCEPT_ACTION The parser accepts its input. @@ -131902,16 +135193,20 @@ typedef union { ** slots in the yy_action[] table. ** ** The action table is constructed as a single large table named yy_action[]. -** Given state S and lookahead X, the action is computed as +** Given state S and lookahead X, the action is computed as either: ** -** yy_action[ yy_shift_ofst[S] + X ] +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] ** -** If the index value yy_shift_ofst[S]+X is out of range or if the value -** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S] -** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table -** and that yy_default[S] should be used instead. +** The (A) formula is preferred. The B formula is used instead if: +** (1) The yy_shift_ofst[S]+X value is out of range, or +** (2) yy_lookahead[yy_shift_ofst[S]+X] is not equal to X, or +** (3) yy_shift_ofst[S] equal YY_SHIFT_USE_DFLT. +** (Implementation note: YY_SHIFT_USE_DFLT is chosen so that +** YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X. +** Hence only tests (1) and (2) need to be evaluated.) ** -** The formula above is for computing the action when the lookahead is +** The formulas above are for computing the action when the lookahead is ** a terminal symbol. If the lookahead is a non-terminal (as occurs after ** a reduce action) then the yy_reduce_ofst[] array is used in place of ** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of @@ -131929,159 +135224,165 @@ typedef union { ** yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define YY_ACTTAB_COUNT (1507) +#define YY_ACTTAB_COUNT (1567) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 317, 814, 341, 808, 5, 195, 195, 802, 93, 94, - /* 10 */ 84, 823, 823, 835, 838, 827, 827, 91, 91, 92, - /* 20 */ 92, 92, 92, 293, 90, 90, 90, 90, 89, 89, - /* 30 */ 88, 88, 88, 87, 341, 317, 958, 958, 807, 807, - /* 40 */ 807, 928, 344, 93, 94, 84, 823, 823, 835, 838, - /* 50 */ 827, 827, 91, 91, 92, 92, 92, 92, 328, 90, - /* 60 */ 90, 90, 90, 89, 89, 88, 88, 88, 87, 341, - /* 70 */ 89, 89, 88, 88, 88, 87, 341, 776, 958, 958, - /* 80 */ 317, 88, 88, 88, 87, 341, 777, 69, 93, 94, - /* 90 */ 84, 823, 823, 835, 838, 827, 827, 91, 91, 92, - /* 100 */ 92, 92, 92, 437, 90, 90, 90, 90, 89, 89, - /* 110 */ 88, 88, 88, 87, 341, 1310, 147, 147, 2, 317, - /* 120 */ 76, 25, 74, 49, 49, 87, 341, 93, 94, 84, - /* 130 */ 823, 823, 835, 838, 827, 827, 91, 91, 92, 92, - /* 140 */ 92, 92, 95, 90, 90, 90, 90, 89, 89, 88, - /* 150 */ 88, 88, 87, 341, 939, 939, 317, 260, 415, 400, - /* 160 */ 398, 58, 737, 737, 93, 94, 84, 823, 823, 835, - /* 170 */ 838, 827, 827, 91, 91, 92, 92, 92, 92, 57, - /* 180 */ 90, 90, 90, 90, 89, 89, 88, 88, 88, 87, - /* 190 */ 341, 317, 1253, 928, 344, 269, 940, 941, 242, 93, - /* 200 */ 94, 84, 823, 823, 835, 838, 827, 827, 91, 91, - /* 210 */ 92, 92, 92, 92, 293, 90, 90, 90, 90, 89, - /* 220 */ 89, 88, 88, 88, 87, 341, 317, 919, 1303, 793, - /* 230 */ 691, 1303, 724, 724, 93, 94, 84, 823, 823, 835, - /* 240 */ 838, 827, 827, 91, 91, 92, 92, 92, 92, 337, - /* 250 */ 90, 90, 90, 90, 89, 89, 88, 88, 88, 87, - /* 260 */ 341, 317, 114, 919, 1304, 684, 395, 1304, 124, 93, - /* 270 */ 94, 84, 823, 823, 835, 838, 827, 827, 91, 91, - /* 280 */ 92, 92, 92, 92, 683, 90, 90, 90, 90, 89, - /* 290 */ 89, 88, 88, 88, 87, 341, 317, 86, 83, 169, - /* 300 */ 801, 917, 234, 399, 93, 94, 84, 823, 823, 835, - /* 310 */ 838, 827, 827, 91, 91, 92, 92, 92, 92, 686, - /* 320 */ 90, 90, 90, 90, 89, 89, 88, 88, 88, 87, - /* 330 */ 341, 317, 436, 742, 86, 83, 169, 917, 741, 93, - /* 340 */ 94, 84, 823, 823, 835, 838, 827, 827, 91, 91, - /* 350 */ 92, 92, 92, 92, 902, 90, 90, 90, 90, 89, - /* 360 */ 89, 88, 88, 88, 87, 341, 317, 321, 434, 434, - /* 370 */ 434, 1, 722, 722, 93, 94, 84, 823, 823, 835, - /* 380 */ 838, 827, 827, 91, 91, 92, 92, 92, 92, 190, - /* 390 */ 90, 90, 90, 90, 89, 89, 88, 88, 88, 87, - /* 400 */ 341, 317, 685, 292, 939, 939, 150, 977, 310, 93, - /* 410 */ 94, 84, 823, 823, 835, 838, 827, 827, 91, 91, - /* 420 */ 92, 92, 92, 92, 437, 90, 90, 90, 90, 89, - /* 430 */ 89, 88, 88, 88, 87, 341, 926, 2, 372, 719, - /* 440 */ 698, 369, 950, 317, 49, 49, 940, 941, 719, 177, - /* 450 */ 72, 93, 94, 84, 823, 823, 835, 838, 827, 827, - /* 460 */ 91, 91, 92, 92, 92, 92, 322, 90, 90, 90, - /* 470 */ 90, 89, 89, 88, 88, 88, 87, 341, 317, 415, - /* 480 */ 405, 824, 824, 836, 839, 75, 93, 82, 84, 823, - /* 490 */ 823, 835, 838, 827, 827, 91, 91, 92, 92, 92, - /* 500 */ 92, 430, 90, 90, 90, 90, 89, 89, 88, 88, - /* 510 */ 88, 87, 341, 317, 340, 340, 340, 658, 659, 660, - /* 520 */ 333, 288, 94, 84, 823, 823, 835, 838, 827, 827, - /* 530 */ 91, 91, 92, 92, 92, 92, 437, 90, 90, 90, - /* 540 */ 90, 89, 89, 88, 88, 88, 87, 341, 317, 882, - /* 550 */ 882, 375, 828, 66, 330, 409, 49, 49, 84, 823, - /* 560 */ 823, 835, 838, 827, 827, 91, 91, 92, 92, 92, - /* 570 */ 92, 351, 90, 90, 90, 90, 89, 89, 88, 88, - /* 580 */ 88, 87, 341, 80, 432, 742, 3, 1180, 351, 350, - /* 590 */ 741, 334, 796, 939, 939, 761, 80, 432, 278, 3, - /* 600 */ 204, 161, 279, 393, 274, 392, 191, 362, 437, 277, - /* 610 */ 745, 77, 78, 272, 800, 254, 355, 243, 79, 342, - /* 620 */ 342, 86, 83, 169, 77, 78, 234, 399, 49, 49, - /* 630 */ 435, 79, 342, 342, 437, 940, 941, 186, 442, 655, - /* 640 */ 390, 387, 386, 435, 235, 213, 108, 421, 761, 351, - /* 650 */ 437, 385, 167, 732, 10, 10, 124, 124, 671, 814, - /* 660 */ 421, 439, 438, 415, 414, 802, 362, 168, 327, 124, - /* 670 */ 49, 49, 814, 219, 439, 438, 800, 186, 802, 326, - /* 680 */ 390, 387, 386, 437, 1248, 1248, 23, 939, 939, 80, - /* 690 */ 432, 385, 3, 761, 416, 876, 807, 807, 807, 809, - /* 700 */ 19, 290, 149, 49, 49, 415, 396, 260, 910, 807, - /* 710 */ 807, 807, 809, 19, 312, 237, 145, 77, 78, 746, - /* 720 */ 168, 702, 437, 149, 79, 342, 342, 114, 358, 940, - /* 730 */ 941, 302, 223, 397, 345, 313, 435, 260, 415, 417, - /* 740 */ 858, 374, 31, 31, 80, 432, 761, 3, 348, 92, - /* 750 */ 92, 92, 92, 421, 90, 90, 90, 90, 89, 89, - /* 760 */ 88, 88, 88, 87, 341, 814, 114, 439, 438, 796, - /* 770 */ 367, 802, 77, 78, 701, 796, 124, 1187, 220, 79, - /* 780 */ 342, 342, 124, 747, 734, 939, 939, 775, 404, 939, - /* 790 */ 939, 435, 254, 360, 253, 402, 895, 346, 254, 360, - /* 800 */ 253, 774, 807, 807, 807, 809, 19, 800, 421, 90, - /* 810 */ 90, 90, 90, 89, 89, 88, 88, 88, 87, 341, - /* 820 */ 814, 114, 439, 438, 939, 939, 802, 940, 941, 114, - /* 830 */ 437, 940, 941, 86, 83, 169, 192, 166, 309, 979, - /* 840 */ 70, 432, 700, 3, 382, 870, 238, 86, 83, 169, - /* 850 */ 10, 10, 361, 406, 763, 190, 222, 807, 807, 807, - /* 860 */ 809, 19, 870, 872, 329, 24, 940, 941, 77, 78, - /* 870 */ 359, 437, 335, 260, 218, 79, 342, 342, 437, 307, - /* 880 */ 306, 305, 207, 303, 339, 338, 668, 435, 339, 338, - /* 890 */ 407, 10, 10, 762, 216, 216, 939, 939, 49, 49, - /* 900 */ 437, 260, 97, 241, 421, 225, 402, 189, 188, 187, - /* 910 */ 309, 918, 980, 149, 221, 898, 814, 868, 439, 438, - /* 920 */ 10, 10, 802, 870, 915, 316, 898, 163, 162, 171, - /* 930 */ 249, 240, 322, 410, 412, 687, 687, 272, 940, 941, - /* 940 */ 239, 965, 901, 437, 226, 403, 226, 437, 963, 367, - /* 950 */ 964, 173, 248, 807, 807, 807, 809, 19, 174, 367, - /* 960 */ 899, 124, 172, 48, 48, 9, 9, 35, 35, 966, - /* 970 */ 966, 899, 363, 966, 966, 814, 900, 808, 725, 939, - /* 980 */ 939, 802, 895, 318, 980, 324, 125, 900, 726, 420, - /* 990 */ 92, 92, 92, 92, 85, 90, 90, 90, 90, 89, - /* 1000 */ 89, 88, 88, 88, 87, 341, 216, 216, 437, 946, - /* 1010 */ 349, 292, 807, 807, 807, 114, 291, 693, 402, 705, - /* 1020 */ 890, 940, 941, 437, 245, 889, 247, 437, 36, 36, - /* 1030 */ 437, 353, 391, 437, 260, 252, 260, 437, 361, 437, - /* 1040 */ 706, 437, 370, 12, 12, 224, 437, 27, 27, 437, - /* 1050 */ 37, 37, 437, 38, 38, 752, 368, 39, 39, 28, - /* 1060 */ 28, 29, 29, 215, 166, 331, 40, 40, 437, 41, - /* 1070 */ 41, 437, 42, 42, 437, 866, 246, 731, 437, 879, - /* 1080 */ 437, 256, 437, 878, 437, 267, 437, 261, 11, 11, - /* 1090 */ 437, 43, 43, 437, 99, 99, 437, 373, 44, 44, - /* 1100 */ 45, 45, 32, 32, 46, 46, 47, 47, 437, 426, - /* 1110 */ 33, 33, 776, 116, 116, 437, 117, 117, 437, 124, - /* 1120 */ 437, 777, 437, 260, 437, 957, 437, 352, 118, 118, - /* 1130 */ 437, 195, 437, 111, 437, 53, 53, 264, 34, 34, - /* 1140 */ 100, 100, 50, 50, 101, 101, 102, 102, 437, 260, - /* 1150 */ 98, 98, 115, 115, 113, 113, 437, 262, 437, 265, - /* 1160 */ 437, 943, 958, 437, 727, 437, 681, 437, 106, 106, - /* 1170 */ 68, 437, 893, 730, 437, 365, 105, 105, 103, 103, - /* 1180 */ 104, 104, 217, 52, 52, 54, 54, 51, 51, 694, - /* 1190 */ 259, 26, 26, 266, 30, 30, 677, 323, 433, 323, - /* 1200 */ 674, 423, 427, 943, 958, 114, 114, 431, 681, 865, - /* 1210 */ 1277, 233, 366, 714, 112, 20, 154, 704, 703, 810, - /* 1220 */ 914, 55, 159, 311, 798, 255, 383, 194, 68, 200, - /* 1230 */ 21, 694, 268, 114, 114, 114, 270, 711, 712, 68, - /* 1240 */ 114, 739, 770, 715, 71, 194, 861, 875, 875, 200, - /* 1250 */ 696, 865, 874, 874, 679, 699, 273, 110, 229, 419, - /* 1260 */ 768, 810, 799, 378, 748, 759, 418, 210, 294, 281, - /* 1270 */ 295, 806, 283, 682, 676, 665, 664, 666, 933, 151, - /* 1280 */ 285, 7, 1267, 308, 251, 790, 354, 244, 892, 364, - /* 1290 */ 287, 422, 300, 164, 160, 936, 974, 127, 197, 137, - /* 1300 */ 909, 907, 971, 388, 276, 863, 862, 56, 698, 325, - /* 1310 */ 148, 59, 122, 66, 356, 381, 357, 176, 152, 62, - /* 1320 */ 371, 130, 877, 181, 377, 760, 211, 182, 132, 133, - /* 1330 */ 134, 135, 258, 146, 140, 795, 787, 263, 183, 379, - /* 1340 */ 667, 394, 184, 332, 894, 314, 718, 717, 857, 716, - /* 1350 */ 696, 315, 709, 690, 65, 196, 6, 408, 289, 708, - /* 1360 */ 275, 689, 688, 948, 756, 757, 280, 282, 425, 755, - /* 1370 */ 284, 336, 73, 67, 754, 429, 411, 96, 286, 413, - /* 1380 */ 205, 934, 673, 22, 209, 440, 119, 120, 109, 206, - /* 1390 */ 208, 441, 662, 661, 656, 843, 654, 343, 158, 236, - /* 1400 */ 170, 347, 107, 227, 121, 738, 873, 298, 296, 297, - /* 1410 */ 299, 871, 794, 128, 129, 728, 230, 131, 175, 250, - /* 1420 */ 888, 136, 138, 231, 232, 139, 60, 61, 891, 178, - /* 1430 */ 179, 887, 8, 13, 180, 257, 880, 968, 194, 141, - /* 1440 */ 142, 376, 153, 670, 380, 185, 143, 277, 63, 384, - /* 1450 */ 14, 707, 271, 15, 389, 64, 319, 320, 126, 228, - /* 1460 */ 813, 812, 841, 736, 123, 16, 401, 740, 4, 769, - /* 1470 */ 165, 212, 214, 193, 144, 764, 71, 68, 17, 18, - /* 1480 */ 856, 842, 840, 897, 845, 896, 199, 198, 923, 155, - /* 1490 */ 424, 929, 924, 156, 201, 202, 428, 844, 157, 203, - /* 1500 */ 811, 680, 81, 1269, 1268, 301, 304, + /* 0 */ 325, 832, 351, 825, 5, 203, 203, 819, 99, 100, + /* 10 */ 90, 978, 978, 853, 856, 845, 845, 97, 97, 98, + /* 20 */ 98, 98, 98, 301, 96, 96, 96, 96, 95, 95, + /* 30 */ 94, 94, 94, 93, 351, 325, 976, 976, 824, 824, + /* 40 */ 826, 946, 354, 99, 100, 90, 978, 978, 853, 856, + /* 50 */ 845, 845, 97, 97, 98, 98, 98, 98, 338, 96, + /* 60 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351, + /* 70 */ 95, 95, 94, 94, 94, 93, 351, 791, 976, 976, + /* 80 */ 325, 94, 94, 94, 93, 351, 792, 75, 99, 100, + /* 90 */ 90, 978, 978, 853, 856, 845, 845, 97, 97, 98, + /* 100 */ 98, 98, 98, 450, 96, 96, 96, 96, 95, 95, + /* 110 */ 94, 94, 94, 93, 351, 1333, 155, 155, 2, 325, + /* 120 */ 275, 146, 132, 52, 52, 93, 351, 99, 100, 90, + /* 130 */ 978, 978, 853, 856, 845, 845, 97, 97, 98, 98, + /* 140 */ 98, 98, 101, 96, 96, 96, 96, 95, 95, 94, + /* 150 */ 94, 94, 93, 351, 957, 957, 325, 268, 428, 413, + /* 160 */ 411, 61, 752, 752, 99, 100, 90, 978, 978, 853, + /* 170 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 60, + /* 180 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 190 */ 351, 325, 270, 329, 273, 277, 958, 959, 250, 99, + /* 200 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, + /* 210 */ 98, 98, 98, 98, 301, 96, 96, 96, 96, 95, + /* 220 */ 95, 94, 94, 94, 93, 351, 325, 937, 1326, 698, + /* 230 */ 706, 1326, 242, 412, 99, 100, 90, 978, 978, 853, + /* 240 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 347, + /* 250 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 260 */ 351, 325, 937, 1327, 384, 699, 1327, 381, 379, 99, + /* 270 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, + /* 280 */ 98, 98, 98, 98, 701, 96, 96, 96, 96, 95, + /* 290 */ 95, 94, 94, 94, 93, 351, 325, 92, 89, 178, + /* 300 */ 833, 935, 373, 700, 99, 100, 90, 978, 978, 853, + /* 310 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 375, + /* 320 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 330 */ 351, 325, 1275, 946, 354, 818, 935, 739, 739, 99, + /* 340 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, + /* 350 */ 98, 98, 98, 98, 230, 96, 96, 96, 96, 95, + /* 360 */ 95, 94, 94, 94, 93, 351, 325, 968, 227, 92, + /* 370 */ 89, 178, 373, 300, 99, 100, 90, 978, 978, 853, + /* 380 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 920, + /* 390 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 400 */ 351, 325, 449, 447, 447, 447, 147, 737, 737, 99, + /* 410 */ 100, 90, 978, 978, 853, 856, 845, 845, 97, 97, + /* 420 */ 98, 98, 98, 98, 296, 96, 96, 96, 96, 95, + /* 430 */ 95, 94, 94, 94, 93, 351, 325, 419, 231, 957, + /* 440 */ 957, 158, 25, 422, 99, 100, 90, 978, 978, 853, + /* 450 */ 856, 845, 845, 97, 97, 98, 98, 98, 98, 450, + /* 460 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 470 */ 351, 443, 224, 224, 420, 957, 957, 961, 325, 52, + /* 480 */ 52, 958, 959, 176, 415, 78, 99, 100, 90, 978, + /* 490 */ 978, 853, 856, 845, 845, 97, 97, 98, 98, 98, + /* 500 */ 98, 379, 96, 96, 96, 96, 95, 95, 94, 94, + /* 510 */ 94, 93, 351, 325, 428, 418, 298, 958, 959, 961, + /* 520 */ 81, 99, 88, 90, 978, 978, 853, 856, 845, 845, + /* 530 */ 97, 97, 98, 98, 98, 98, 717, 96, 96, 96, + /* 540 */ 96, 95, 95, 94, 94, 94, 93, 351, 325, 842, + /* 550 */ 842, 854, 857, 996, 318, 343, 379, 100, 90, 978, + /* 560 */ 978, 853, 856, 845, 845, 97, 97, 98, 98, 98, + /* 570 */ 98, 450, 96, 96, 96, 96, 95, 95, 94, 94, + /* 580 */ 94, 93, 351, 325, 350, 350, 350, 260, 377, 340, + /* 590 */ 928, 52, 52, 90, 978, 978, 853, 856, 845, 845, + /* 600 */ 97, 97, 98, 98, 98, 98, 361, 96, 96, 96, + /* 610 */ 96, 95, 95, 94, 94, 94, 93, 351, 86, 445, + /* 620 */ 846, 3, 1202, 361, 360, 378, 344, 813, 957, 957, + /* 630 */ 1299, 86, 445, 729, 3, 212, 169, 287, 405, 282, + /* 640 */ 404, 199, 232, 450, 300, 760, 83, 84, 280, 245, + /* 650 */ 262, 365, 251, 85, 352, 352, 92, 89, 178, 83, + /* 660 */ 84, 242, 412, 52, 52, 448, 85, 352, 352, 246, + /* 670 */ 958, 959, 194, 455, 670, 402, 399, 398, 448, 243, + /* 680 */ 221, 114, 434, 776, 361, 450, 397, 268, 747, 224, + /* 690 */ 224, 132, 132, 198, 832, 434, 452, 451, 428, 427, + /* 700 */ 819, 415, 734, 713, 132, 52, 52, 832, 268, 452, + /* 710 */ 451, 734, 194, 819, 363, 402, 399, 398, 450, 1270, + /* 720 */ 1270, 23, 957, 957, 86, 445, 397, 3, 228, 429, + /* 730 */ 894, 824, 824, 826, 827, 19, 203, 720, 52, 52, + /* 740 */ 428, 408, 439, 249, 824, 824, 826, 827, 19, 229, + /* 750 */ 403, 153, 83, 84, 761, 177, 241, 450, 721, 85, + /* 760 */ 352, 352, 120, 157, 958, 959, 58, 976, 409, 355, + /* 770 */ 330, 448, 268, 428, 430, 320, 790, 32, 32, 86, + /* 780 */ 445, 776, 3, 341, 98, 98, 98, 98, 434, 96, + /* 790 */ 96, 96, 96, 95, 95, 94, 94, 94, 93, 351, + /* 800 */ 832, 120, 452, 451, 813, 886, 819, 83, 84, 976, + /* 810 */ 813, 132, 410, 919, 85, 352, 352, 132, 407, 789, + /* 820 */ 957, 957, 92, 89, 178, 916, 448, 262, 370, 261, + /* 830 */ 82, 913, 80, 262, 370, 261, 776, 824, 824, 826, + /* 840 */ 827, 19, 933, 434, 96, 96, 96, 96, 95, 95, + /* 850 */ 94, 94, 94, 93, 351, 832, 74, 452, 451, 957, + /* 860 */ 957, 819, 958, 959, 120, 92, 89, 178, 944, 2, + /* 870 */ 917, 964, 268, 1, 975, 76, 445, 762, 3, 708, + /* 880 */ 900, 900, 387, 957, 957, 757, 918, 371, 740, 778, + /* 890 */ 756, 257, 824, 824, 826, 827, 19, 417, 741, 450, + /* 900 */ 24, 958, 959, 83, 84, 369, 957, 957, 177, 226, + /* 910 */ 85, 352, 352, 884, 315, 314, 313, 215, 311, 10, + /* 920 */ 10, 683, 448, 349, 348, 958, 959, 908, 777, 157, + /* 930 */ 120, 957, 957, 337, 776, 416, 711, 310, 450, 434, + /* 940 */ 450, 321, 450, 791, 103, 200, 175, 450, 958, 959, + /* 950 */ 907, 832, 792, 452, 451, 9, 9, 819, 10, 10, + /* 960 */ 52, 52, 51, 51, 180, 716, 248, 10, 10, 171, + /* 970 */ 170, 167, 339, 958, 959, 247, 984, 702, 702, 450, + /* 980 */ 715, 233, 686, 982, 888, 983, 182, 913, 824, 824, + /* 990 */ 826, 827, 19, 183, 256, 423, 132, 181, 394, 10, + /* 1000 */ 10, 888, 890, 749, 957, 957, 916, 268, 985, 198, + /* 1010 */ 985, 349, 348, 425, 415, 299, 817, 832, 326, 825, + /* 1020 */ 120, 332, 133, 819, 268, 98, 98, 98, 98, 91, + /* 1030 */ 96, 96, 96, 96, 95, 95, 94, 94, 94, 93, + /* 1040 */ 351, 157, 810, 371, 382, 359, 958, 959, 358, 268, + /* 1050 */ 450, 917, 368, 324, 824, 824, 826, 450, 709, 450, + /* 1060 */ 264, 380, 888, 450, 876, 746, 253, 918, 255, 433, + /* 1070 */ 36, 36, 234, 450, 234, 120, 269, 37, 37, 12, + /* 1080 */ 12, 334, 272, 27, 27, 450, 330, 118, 450, 162, + /* 1090 */ 742, 280, 450, 38, 38, 450, 985, 356, 985, 450, + /* 1100 */ 709, 1209, 450, 132, 450, 39, 39, 450, 40, 40, + /* 1110 */ 450, 362, 41, 41, 450, 42, 42, 450, 254, 28, + /* 1120 */ 28, 450, 29, 29, 31, 31, 450, 43, 43, 450, + /* 1130 */ 44, 44, 450, 714, 45, 45, 450, 11, 11, 767, + /* 1140 */ 450, 46, 46, 450, 268, 450, 105, 105, 450, 47, + /* 1150 */ 47, 450, 48, 48, 450, 237, 33, 33, 450, 172, + /* 1160 */ 49, 49, 450, 50, 50, 34, 34, 274, 122, 122, + /* 1170 */ 450, 123, 123, 450, 124, 124, 450, 897, 56, 56, + /* 1180 */ 450, 896, 35, 35, 450, 267, 450, 817, 450, 817, + /* 1190 */ 106, 106, 450, 53, 53, 385, 107, 107, 450, 817, + /* 1200 */ 108, 108, 817, 450, 104, 104, 121, 121, 119, 119, + /* 1210 */ 450, 117, 112, 112, 450, 276, 450, 225, 111, 111, + /* 1220 */ 450, 730, 450, 109, 109, 450, 673, 674, 675, 911, + /* 1230 */ 110, 110, 317, 998, 55, 55, 57, 57, 692, 331, + /* 1240 */ 54, 54, 26, 26, 696, 30, 30, 317, 936, 197, + /* 1250 */ 196, 195, 335, 281, 336, 446, 331, 745, 689, 436, + /* 1260 */ 440, 444, 120, 72, 386, 223, 175, 345, 757, 932, + /* 1270 */ 20, 286, 319, 756, 815, 372, 374, 202, 202, 202, + /* 1280 */ 263, 395, 285, 74, 208, 21, 696, 719, 718, 883, + /* 1290 */ 120, 120, 120, 120, 120, 754, 278, 828, 77, 74, + /* 1300 */ 726, 727, 785, 783, 879, 202, 999, 208, 893, 892, + /* 1310 */ 893, 892, 694, 816, 763, 116, 774, 1289, 431, 432, + /* 1320 */ 302, 999, 390, 303, 823, 697, 691, 680, 159, 289, + /* 1330 */ 679, 883, 681, 951, 291, 218, 293, 7, 316, 828, + /* 1340 */ 173, 805, 259, 364, 252, 910, 376, 713, 295, 435, + /* 1350 */ 308, 168, 954, 993, 135, 400, 990, 284, 881, 880, + /* 1360 */ 205, 927, 925, 59, 333, 62, 144, 156, 130, 72, + /* 1370 */ 802, 366, 367, 393, 137, 185, 189, 160, 139, 383, + /* 1380 */ 67, 895, 140, 141, 142, 148, 389, 812, 775, 266, + /* 1390 */ 219, 190, 154, 391, 912, 875, 271, 406, 191, 322, + /* 1400 */ 682, 733, 192, 342, 732, 724, 731, 711, 723, 421, + /* 1410 */ 705, 71, 323, 6, 204, 771, 288, 79, 297, 346, + /* 1420 */ 772, 704, 290, 283, 703, 770, 292, 294, 966, 239, + /* 1430 */ 769, 102, 861, 438, 426, 240, 424, 442, 73, 213, + /* 1440 */ 688, 238, 22, 453, 952, 214, 217, 216, 454, 677, + /* 1450 */ 676, 671, 753, 125, 115, 235, 126, 669, 353, 166, + /* 1460 */ 127, 244, 179, 357, 306, 304, 305, 307, 113, 891, + /* 1470 */ 327, 889, 811, 328, 134, 128, 136, 138, 743, 258, + /* 1480 */ 906, 184, 143, 129, 909, 186, 63, 64, 145, 187, + /* 1490 */ 905, 65, 8, 66, 13, 188, 202, 898, 265, 149, + /* 1500 */ 987, 388, 150, 685, 161, 392, 285, 193, 279, 396, + /* 1510 */ 151, 401, 68, 14, 15, 722, 69, 236, 831, 131, + /* 1520 */ 830, 859, 70, 751, 16, 414, 755, 4, 174, 220, + /* 1530 */ 222, 784, 201, 152, 779, 77, 74, 17, 18, 874, + /* 1540 */ 860, 858, 915, 863, 914, 207, 206, 941, 163, 437, + /* 1550 */ 947, 942, 164, 209, 1002, 441, 862, 165, 210, 829, + /* 1560 */ 695, 87, 312, 211, 1291, 1290, 309, }; static const YYCODETYPE yy_lookahead[] = { /* 0 */ 19, 95, 53, 97, 22, 24, 24, 101, 27, 28, @@ -132096,281 +135397,290 @@ static const YYCODETYPE yy_lookahead[] = { /* 90 */ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, /* 100 */ 39, 40, 41, 152, 43, 44, 45, 46, 47, 48, /* 110 */ 49, 50, 51, 52, 53, 144, 145, 146, 147, 19, - /* 120 */ 137, 22, 139, 172, 173, 52, 53, 27, 28, 29, + /* 120 */ 16, 22, 92, 172, 173, 52, 53, 27, 28, 29, /* 130 */ 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, /* 140 */ 40, 41, 81, 43, 44, 45, 46, 47, 48, 49, /* 150 */ 50, 51, 52, 53, 55, 56, 19, 152, 207, 208, /* 160 */ 115, 24, 117, 118, 27, 28, 29, 30, 31, 32, /* 170 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 79, /* 180 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 190 */ 53, 19, 0, 1, 2, 23, 97, 98, 193, 27, + /* 190 */ 53, 19, 88, 157, 90, 23, 97, 98, 193, 27, /* 200 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, /* 210 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47, - /* 220 */ 48, 49, 50, 51, 52, 53, 19, 22, 23, 163, - /* 230 */ 23, 26, 190, 191, 27, 28, 29, 30, 31, 32, + /* 220 */ 48, 49, 50, 51, 52, 53, 19, 22, 23, 172, + /* 230 */ 23, 26, 119, 120, 27, 28, 29, 30, 31, 32, /* 240 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 187, /* 250 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 260 */ 53, 19, 196, 22, 23, 23, 49, 26, 92, 27, + /* 260 */ 53, 19, 22, 23, 228, 23, 26, 231, 152, 27, /* 270 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, /* 280 */ 38, 39, 40, 41, 172, 43, 44, 45, 46, 47, /* 290 */ 48, 49, 50, 51, 52, 53, 19, 221, 222, 223, - /* 300 */ 23, 96, 119, 120, 27, 28, 29, 30, 31, 32, - /* 310 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 172, + /* 300 */ 23, 96, 152, 172, 27, 28, 29, 30, 31, 32, + /* 310 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152, /* 320 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 330 */ 53, 19, 152, 116, 221, 222, 223, 96, 121, 27, + /* 330 */ 53, 19, 0, 1, 2, 23, 96, 190, 191, 27, /* 340 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, - /* 350 */ 38, 39, 40, 41, 241, 43, 44, 45, 46, 47, - /* 360 */ 48, 49, 50, 51, 52, 53, 19, 157, 168, 169, - /* 370 */ 170, 22, 190, 191, 27, 28, 29, 30, 31, 32, - /* 380 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 30, + /* 350 */ 38, 39, 40, 41, 238, 43, 44, 45, 46, 47, + /* 360 */ 48, 49, 50, 51, 52, 53, 19, 185, 218, 221, + /* 370 */ 222, 223, 152, 152, 27, 28, 29, 30, 31, 32, + /* 380 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 241, /* 390 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 400 */ 53, 19, 172, 152, 55, 56, 24, 247, 248, 27, + /* 400 */ 53, 19, 152, 168, 169, 170, 22, 190, 191, 27, /* 410 */ 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, /* 420 */ 38, 39, 40, 41, 152, 43, 44, 45, 46, 47, - /* 430 */ 48, 49, 50, 51, 52, 53, 146, 147, 228, 179, - /* 440 */ 180, 231, 185, 19, 172, 173, 97, 98, 188, 26, - /* 450 */ 138, 27, 28, 29, 30, 31, 32, 33, 34, 35, - /* 460 */ 36, 37, 38, 39, 40, 41, 107, 43, 44, 45, - /* 470 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 207, - /* 480 */ 208, 30, 31, 32, 33, 138, 27, 28, 29, 30, + /* 430 */ 48, 49, 50, 51, 52, 53, 19, 19, 218, 55, + /* 440 */ 56, 24, 22, 152, 27, 28, 29, 30, 31, 32, + /* 450 */ 33, 34, 35, 36, 37, 38, 39, 40, 41, 152, + /* 460 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 470 */ 53, 250, 194, 195, 56, 55, 56, 55, 19, 172, + /* 480 */ 173, 97, 98, 152, 206, 138, 27, 28, 29, 30, /* 490 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - /* 500 */ 41, 250, 43, 44, 45, 46, 47, 48, 49, 50, - /* 510 */ 51, 52, 53, 19, 168, 169, 170, 7, 8, 9, - /* 520 */ 19, 152, 28, 29, 30, 31, 32, 33, 34, 35, - /* 530 */ 36, 37, 38, 39, 40, 41, 152, 43, 44, 45, - /* 540 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 108, - /* 550 */ 109, 110, 101, 130, 53, 152, 172, 173, 29, 30, + /* 500 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50, + /* 510 */ 51, 52, 53, 19, 207, 208, 152, 97, 98, 97, + /* 520 */ 138, 27, 28, 29, 30, 31, 32, 33, 34, 35, + /* 530 */ 36, 37, 38, 39, 40, 41, 181, 43, 44, 45, + /* 540 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 30, + /* 550 */ 31, 32, 33, 247, 248, 19, 152, 28, 29, 30, /* 560 */ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, /* 570 */ 41, 152, 43, 44, 45, 46, 47, 48, 49, 50, - /* 580 */ 51, 52, 53, 19, 20, 116, 22, 23, 169, 170, - /* 590 */ 121, 207, 85, 55, 56, 26, 19, 20, 101, 22, - /* 600 */ 99, 100, 101, 102, 103, 104, 105, 152, 152, 112, - /* 610 */ 210, 47, 48, 112, 152, 108, 109, 110, 54, 55, - /* 620 */ 56, 221, 222, 223, 47, 48, 119, 120, 172, 173, - /* 630 */ 66, 54, 55, 56, 152, 97, 98, 99, 148, 149, - /* 640 */ 102, 103, 104, 66, 154, 23, 156, 83, 26, 230, - /* 650 */ 152, 113, 152, 163, 172, 173, 92, 92, 21, 95, - /* 660 */ 83, 97, 98, 207, 208, 101, 152, 98, 186, 92, - /* 670 */ 172, 173, 95, 218, 97, 98, 152, 99, 101, 217, - /* 680 */ 102, 103, 104, 152, 119, 120, 196, 55, 56, 19, - /* 690 */ 20, 113, 22, 124, 163, 11, 132, 133, 134, 135, - /* 700 */ 136, 152, 152, 172, 173, 207, 208, 152, 152, 132, - /* 710 */ 133, 134, 135, 136, 164, 152, 84, 47, 48, 49, - /* 720 */ 98, 181, 152, 152, 54, 55, 56, 196, 91, 97, - /* 730 */ 98, 160, 218, 163, 244, 164, 66, 152, 207, 208, - /* 740 */ 103, 217, 172, 173, 19, 20, 124, 22, 193, 38, - /* 750 */ 39, 40, 41, 83, 43, 44, 45, 46, 47, 48, - /* 760 */ 49, 50, 51, 52, 53, 95, 196, 97, 98, 85, - /* 770 */ 152, 101, 47, 48, 181, 85, 92, 140, 193, 54, - /* 780 */ 55, 56, 92, 49, 195, 55, 56, 175, 163, 55, - /* 790 */ 56, 66, 108, 109, 110, 206, 163, 242, 108, 109, - /* 800 */ 110, 175, 132, 133, 134, 135, 136, 152, 83, 43, - /* 810 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 820 */ 95, 196, 97, 98, 55, 56, 101, 97, 98, 196, - /* 830 */ 152, 97, 98, 221, 222, 223, 211, 212, 22, 23, - /* 840 */ 19, 20, 181, 22, 19, 152, 152, 221, 222, 223, - /* 850 */ 172, 173, 219, 19, 124, 30, 238, 132, 133, 134, - /* 860 */ 135, 136, 169, 170, 186, 232, 97, 98, 47, 48, - /* 870 */ 237, 152, 217, 152, 5, 54, 55, 56, 152, 10, - /* 880 */ 11, 12, 13, 14, 47, 48, 17, 66, 47, 48, - /* 890 */ 56, 172, 173, 124, 194, 195, 55, 56, 172, 173, - /* 900 */ 152, 152, 22, 152, 83, 186, 206, 108, 109, 110, - /* 910 */ 22, 23, 96, 152, 193, 12, 95, 152, 97, 98, - /* 920 */ 172, 173, 101, 230, 152, 164, 12, 47, 48, 60, - /* 930 */ 152, 62, 107, 207, 186, 55, 56, 112, 97, 98, - /* 940 */ 71, 100, 193, 152, 183, 152, 185, 152, 107, 152, - /* 950 */ 109, 82, 16, 132, 133, 134, 135, 136, 89, 152, - /* 960 */ 57, 92, 93, 172, 173, 172, 173, 172, 173, 132, - /* 970 */ 133, 57, 152, 132, 133, 95, 73, 97, 75, 55, - /* 980 */ 56, 101, 163, 114, 96, 245, 246, 73, 85, 75, - /* 990 */ 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, - /* 1000 */ 48, 49, 50, 51, 52, 53, 194, 195, 152, 171, - /* 1010 */ 141, 152, 132, 133, 134, 196, 225, 179, 206, 65, - /* 1020 */ 152, 97, 98, 152, 88, 152, 90, 152, 172, 173, - /* 1030 */ 152, 219, 78, 152, 152, 238, 152, 152, 219, 152, - /* 1040 */ 86, 152, 152, 172, 173, 238, 152, 172, 173, 152, - /* 1050 */ 172, 173, 152, 172, 173, 213, 237, 172, 173, 172, - /* 1060 */ 173, 172, 173, 211, 212, 111, 172, 173, 152, 172, - /* 1070 */ 173, 152, 172, 173, 152, 193, 140, 193, 152, 59, - /* 1080 */ 152, 152, 152, 63, 152, 16, 152, 152, 172, 173, - /* 1090 */ 152, 172, 173, 152, 172, 173, 152, 77, 172, 173, - /* 1100 */ 172, 173, 172, 173, 172, 173, 172, 173, 152, 250, - /* 1110 */ 172, 173, 61, 172, 173, 152, 172, 173, 152, 92, - /* 1120 */ 152, 70, 152, 152, 152, 26, 152, 100, 172, 173, - /* 1130 */ 152, 24, 152, 22, 152, 172, 173, 152, 172, 173, - /* 1140 */ 172, 173, 172, 173, 172, 173, 172, 173, 152, 152, - /* 1150 */ 172, 173, 172, 173, 172, 173, 152, 88, 152, 90, - /* 1160 */ 152, 55, 55, 152, 193, 152, 55, 152, 172, 173, - /* 1170 */ 26, 152, 163, 163, 152, 19, 172, 173, 172, 173, - /* 1180 */ 172, 173, 22, 172, 173, 172, 173, 172, 173, 55, - /* 1190 */ 193, 172, 173, 152, 172, 173, 166, 167, 166, 167, - /* 1200 */ 163, 163, 163, 97, 97, 196, 196, 163, 97, 55, - /* 1210 */ 23, 199, 56, 26, 22, 22, 24, 100, 101, 55, - /* 1220 */ 23, 209, 123, 26, 23, 23, 23, 26, 26, 26, - /* 1230 */ 37, 97, 152, 196, 196, 196, 23, 7, 8, 26, - /* 1240 */ 196, 23, 23, 152, 26, 26, 23, 132, 133, 26, - /* 1250 */ 106, 97, 132, 133, 23, 152, 152, 26, 210, 191, - /* 1260 */ 152, 97, 152, 234, 152, 152, 152, 233, 152, 210, - /* 1270 */ 152, 152, 210, 152, 152, 152, 152, 152, 152, 197, - /* 1280 */ 210, 198, 122, 150, 239, 201, 214, 214, 201, 239, - /* 1290 */ 214, 227, 200, 184, 198, 155, 67, 243, 122, 22, - /* 1300 */ 159, 159, 69, 176, 175, 175, 175, 240, 180, 159, - /* 1310 */ 220, 240, 27, 130, 18, 18, 159, 158, 220, 137, - /* 1320 */ 159, 189, 236, 158, 74, 159, 159, 158, 192, 192, - /* 1330 */ 192, 192, 235, 22, 189, 189, 201, 159, 158, 177, - /* 1340 */ 159, 107, 158, 76, 201, 177, 174, 174, 201, 174, - /* 1350 */ 106, 177, 182, 174, 107, 159, 22, 125, 159, 182, - /* 1360 */ 174, 176, 174, 174, 216, 216, 215, 215, 177, 216, - /* 1370 */ 215, 53, 137, 128, 216, 177, 127, 129, 215, 126, - /* 1380 */ 25, 13, 162, 26, 6, 161, 165, 165, 178, 153, - /* 1390 */ 153, 151, 151, 151, 151, 224, 4, 3, 22, 142, - /* 1400 */ 15, 94, 16, 178, 165, 205, 23, 202, 204, 203, - /* 1410 */ 201, 23, 120, 131, 111, 20, 226, 123, 125, 16, - /* 1420 */ 1, 123, 131, 229, 229, 111, 37, 37, 56, 64, - /* 1430 */ 122, 1, 5, 22, 107, 140, 80, 87, 26, 80, - /* 1440 */ 107, 72, 24, 20, 19, 105, 22, 112, 22, 79, - /* 1450 */ 22, 58, 23, 22, 79, 22, 249, 249, 246, 79, - /* 1460 */ 23, 23, 23, 116, 68, 22, 26, 23, 22, 56, - /* 1470 */ 122, 23, 23, 64, 22, 124, 26, 26, 64, 64, - /* 1480 */ 23, 23, 23, 23, 11, 23, 22, 26, 23, 22, - /* 1490 */ 24, 1, 23, 22, 26, 122, 24, 23, 22, 122, - /* 1500 */ 23, 23, 22, 122, 122, 23, 15, + /* 580 */ 51, 52, 53, 19, 168, 169, 170, 238, 19, 53, + /* 590 */ 152, 172, 173, 29, 30, 31, 32, 33, 34, 35, + /* 600 */ 36, 37, 38, 39, 40, 41, 152, 43, 44, 45, + /* 610 */ 46, 47, 48, 49, 50, 51, 52, 53, 19, 20, + /* 620 */ 101, 22, 23, 169, 170, 56, 207, 85, 55, 56, + /* 630 */ 23, 19, 20, 26, 22, 99, 100, 101, 102, 103, + /* 640 */ 104, 105, 238, 152, 152, 210, 47, 48, 112, 152, + /* 650 */ 108, 109, 110, 54, 55, 56, 221, 222, 223, 47, + /* 660 */ 48, 119, 120, 172, 173, 66, 54, 55, 56, 152, + /* 670 */ 97, 98, 99, 148, 149, 102, 103, 104, 66, 154, + /* 680 */ 23, 156, 83, 26, 230, 152, 113, 152, 163, 194, + /* 690 */ 195, 92, 92, 30, 95, 83, 97, 98, 207, 208, + /* 700 */ 101, 206, 179, 180, 92, 172, 173, 95, 152, 97, + /* 710 */ 98, 188, 99, 101, 219, 102, 103, 104, 152, 119, + /* 720 */ 120, 196, 55, 56, 19, 20, 113, 22, 193, 163, + /* 730 */ 11, 132, 133, 134, 135, 136, 24, 65, 172, 173, + /* 740 */ 207, 208, 250, 152, 132, 133, 134, 135, 136, 193, + /* 750 */ 78, 84, 47, 48, 49, 98, 199, 152, 86, 54, + /* 760 */ 55, 56, 196, 152, 97, 98, 209, 55, 163, 244, + /* 770 */ 107, 66, 152, 207, 208, 164, 175, 172, 173, 19, + /* 780 */ 20, 124, 22, 111, 38, 39, 40, 41, 83, 43, + /* 790 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + /* 800 */ 95, 196, 97, 98, 85, 152, 101, 47, 48, 97, + /* 810 */ 85, 92, 207, 193, 54, 55, 56, 92, 49, 175, + /* 820 */ 55, 56, 221, 222, 223, 12, 66, 108, 109, 110, + /* 830 */ 137, 163, 139, 108, 109, 110, 26, 132, 133, 134, + /* 840 */ 135, 136, 152, 83, 43, 44, 45, 46, 47, 48, + /* 850 */ 49, 50, 51, 52, 53, 95, 26, 97, 98, 55, + /* 860 */ 56, 101, 97, 98, 196, 221, 222, 223, 146, 147, + /* 870 */ 57, 171, 152, 22, 26, 19, 20, 49, 22, 179, + /* 880 */ 108, 109, 110, 55, 56, 116, 73, 219, 75, 124, + /* 890 */ 121, 152, 132, 133, 134, 135, 136, 163, 85, 152, + /* 900 */ 232, 97, 98, 47, 48, 237, 55, 56, 98, 5, + /* 910 */ 54, 55, 56, 193, 10, 11, 12, 13, 14, 172, + /* 920 */ 173, 17, 66, 47, 48, 97, 98, 152, 124, 152, + /* 930 */ 196, 55, 56, 186, 124, 152, 106, 160, 152, 83, + /* 940 */ 152, 164, 152, 61, 22, 211, 212, 152, 97, 98, + /* 950 */ 152, 95, 70, 97, 98, 172, 173, 101, 172, 173, + /* 960 */ 172, 173, 172, 173, 60, 181, 62, 172, 173, 47, + /* 970 */ 48, 123, 186, 97, 98, 71, 100, 55, 56, 152, + /* 980 */ 181, 186, 21, 107, 152, 109, 82, 163, 132, 133, + /* 990 */ 134, 135, 136, 89, 16, 207, 92, 93, 19, 172, + /* 1000 */ 173, 169, 170, 195, 55, 56, 12, 152, 132, 30, + /* 1010 */ 134, 47, 48, 186, 206, 225, 152, 95, 114, 97, + /* 1020 */ 196, 245, 246, 101, 152, 38, 39, 40, 41, 42, + /* 1030 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + /* 1040 */ 53, 152, 163, 219, 152, 141, 97, 98, 193, 152, + /* 1050 */ 152, 57, 91, 164, 132, 133, 134, 152, 55, 152, + /* 1060 */ 152, 237, 230, 152, 103, 193, 88, 73, 90, 75, + /* 1070 */ 172, 173, 183, 152, 185, 196, 152, 172, 173, 172, + /* 1080 */ 173, 217, 152, 172, 173, 152, 107, 22, 152, 24, + /* 1090 */ 193, 112, 152, 172, 173, 152, 132, 242, 134, 152, + /* 1100 */ 97, 140, 152, 92, 152, 172, 173, 152, 172, 173, + /* 1110 */ 152, 100, 172, 173, 152, 172, 173, 152, 140, 172, + /* 1120 */ 173, 152, 172, 173, 172, 173, 152, 172, 173, 152, + /* 1130 */ 172, 173, 152, 152, 172, 173, 152, 172, 173, 213, + /* 1140 */ 152, 172, 173, 152, 152, 152, 172, 173, 152, 172, + /* 1150 */ 173, 152, 172, 173, 152, 210, 172, 173, 152, 26, + /* 1160 */ 172, 173, 152, 172, 173, 172, 173, 152, 172, 173, + /* 1170 */ 152, 172, 173, 152, 172, 173, 152, 59, 172, 173, + /* 1180 */ 152, 63, 172, 173, 152, 193, 152, 152, 152, 152, + /* 1190 */ 172, 173, 152, 172, 173, 77, 172, 173, 152, 152, + /* 1200 */ 172, 173, 152, 152, 172, 173, 172, 173, 172, 173, + /* 1210 */ 152, 22, 172, 173, 152, 152, 152, 22, 172, 173, + /* 1220 */ 152, 152, 152, 172, 173, 152, 7, 8, 9, 163, + /* 1230 */ 172, 173, 22, 23, 172, 173, 172, 173, 166, 167, + /* 1240 */ 172, 173, 172, 173, 55, 172, 173, 22, 23, 108, + /* 1250 */ 109, 110, 217, 152, 217, 166, 167, 163, 163, 163, + /* 1260 */ 163, 163, 196, 130, 217, 211, 212, 217, 116, 23, + /* 1270 */ 22, 101, 26, 121, 23, 23, 23, 26, 26, 26, + /* 1280 */ 23, 23, 112, 26, 26, 37, 97, 100, 101, 55, + /* 1290 */ 196, 196, 196, 196, 196, 23, 23, 55, 26, 26, + /* 1300 */ 7, 8, 23, 152, 23, 26, 96, 26, 132, 132, + /* 1310 */ 134, 134, 23, 152, 152, 26, 152, 122, 152, 191, + /* 1320 */ 152, 96, 234, 152, 152, 152, 152, 152, 197, 210, + /* 1330 */ 152, 97, 152, 152, 210, 233, 210, 198, 150, 97, + /* 1340 */ 184, 201, 239, 214, 214, 201, 239, 180, 214, 227, + /* 1350 */ 200, 198, 155, 67, 243, 176, 69, 175, 175, 175, + /* 1360 */ 122, 159, 159, 240, 159, 240, 22, 220, 27, 130, + /* 1370 */ 201, 18, 159, 18, 189, 158, 158, 220, 192, 159, + /* 1380 */ 137, 236, 192, 192, 192, 189, 74, 189, 159, 235, + /* 1390 */ 159, 158, 22, 177, 201, 201, 159, 107, 158, 177, + /* 1400 */ 159, 174, 158, 76, 174, 182, 174, 106, 182, 125, + /* 1410 */ 174, 107, 177, 22, 159, 216, 215, 137, 159, 53, + /* 1420 */ 216, 176, 215, 174, 174, 216, 215, 215, 174, 229, + /* 1430 */ 216, 129, 224, 177, 126, 229, 127, 177, 128, 25, + /* 1440 */ 162, 226, 26, 161, 13, 153, 6, 153, 151, 151, + /* 1450 */ 151, 151, 205, 165, 178, 178, 165, 4, 3, 22, + /* 1460 */ 165, 142, 15, 94, 202, 204, 203, 201, 16, 23, + /* 1470 */ 249, 23, 120, 249, 246, 111, 131, 123, 20, 16, + /* 1480 */ 1, 125, 123, 111, 56, 64, 37, 37, 131, 122, + /* 1490 */ 1, 37, 5, 37, 22, 107, 26, 80, 140, 80, + /* 1500 */ 87, 72, 107, 20, 24, 19, 112, 105, 23, 79, + /* 1510 */ 22, 79, 22, 22, 22, 58, 22, 79, 23, 68, + /* 1520 */ 23, 23, 26, 116, 22, 26, 23, 22, 122, 23, + /* 1530 */ 23, 56, 64, 22, 124, 26, 26, 64, 64, 23, + /* 1540 */ 23, 23, 23, 11, 23, 22, 26, 23, 22, 24, + /* 1550 */ 1, 23, 22, 26, 251, 24, 23, 22, 122, 23, + /* 1560 */ 23, 22, 15, 122, 122, 122, 23, }; -#define YY_SHIFT_USE_DFLT (-95) -#define YY_SHIFT_COUNT (442) -#define YY_SHIFT_MIN (-94) -#define YY_SHIFT_MAX (1491) +#define YY_SHIFT_USE_DFLT (1567) +#define YY_SHIFT_COUNT (455) +#define YY_SHIFT_MIN (-94) +#define YY_SHIFT_MAX (1549) static const short yy_shift_ofst[] = { - /* 0 */ 40, 564, 869, 577, 725, 725, 725, 725, 690, -19, - /* 10 */ 16, 16, 100, 725, 725, 725, 725, 725, 725, 725, - /* 20 */ 841, 841, 538, 507, 684, 565, 61, 137, 172, 207, - /* 30 */ 242, 277, 312, 347, 382, 424, 424, 424, 424, 424, - /* 40 */ 424, 424, 424, 424, 424, 424, 424, 424, 424, 424, - /* 50 */ 459, 424, 494, 529, 529, 670, 725, 725, 725, 725, - /* 60 */ 725, 725, 725, 725, 725, 725, 725, 725, 725, 725, - /* 70 */ 725, 725, 725, 725, 725, 725, 725, 725, 725, 725, - /* 80 */ 725, 725, 725, 725, 821, 725, 725, 725, 725, 725, - /* 90 */ 725, 725, 725, 725, 725, 725, 725, 725, 952, 711, - /* 100 */ 711, 711, 711, 711, 766, 23, 32, 924, 637, 825, - /* 110 */ 837, 837, 924, 73, 183, -51, -95, -95, -95, 501, - /* 120 */ 501, 501, 903, 903, 632, 205, 241, 924, 924, 924, - /* 130 */ 924, 924, 924, 924, 924, 924, 924, 924, 924, 924, - /* 140 */ 924, 924, 924, 924, 924, 924, 924, 192, 1027, 1106, - /* 150 */ 1106, 183, 176, 176, 176, 176, 176, 176, -95, -95, - /* 160 */ -95, 880, -94, -94, 578, 734, 99, 730, 769, 349, - /* 170 */ 924, 924, 924, 924, 924, 924, 924, 924, 924, 924, - /* 180 */ 924, 924, 924, 924, 924, 924, 924, 954, 954, 954, - /* 190 */ 924, 924, 622, 924, 924, 924, -18, 924, 924, 914, - /* 200 */ 924, 924, 924, 924, 924, 924, 924, 924, 924, 924, - /* 210 */ 441, 1020, 1107, 1107, 1107, 569, 45, 217, 510, 423, - /* 220 */ 834, 834, 1156, 423, 1156, 1144, 1187, 359, 1051, 834, - /* 230 */ -17, 1051, 1051, 1099, 469, 1192, 1229, 1176, 1176, 1233, - /* 240 */ 1233, 1176, 1277, 1285, 1183, 1296, 1296, 1296, 1296, 1176, - /* 250 */ 1297, 1183, 1277, 1285, 1285, 1183, 1176, 1297, 1182, 1250, - /* 260 */ 1176, 1176, 1297, 1311, 1176, 1297, 1176, 1297, 1311, 1234, - /* 270 */ 1234, 1234, 1267, 1311, 1234, 1244, 1234, 1267, 1234, 1234, - /* 280 */ 1232, 1247, 1232, 1247, 1232, 1247, 1232, 1247, 1176, 1334, - /* 290 */ 1176, 1235, 1311, 1318, 1318, 1311, 1248, 1253, 1245, 1249, - /* 300 */ 1183, 1355, 1357, 1368, 1368, 1378, 1378, 1378, 1378, -95, - /* 310 */ -95, -95, -95, -95, -95, -95, -95, 451, 936, 816, - /* 320 */ 888, 1069, 799, 1111, 1197, 1193, 1201, 1202, 1203, 1213, - /* 330 */ 1134, 1117, 1230, 497, 1218, 1219, 1154, 1223, 1115, 1120, - /* 340 */ 1231, 1164, 1160, 1392, 1394, 1376, 1257, 1385, 1307, 1386, - /* 350 */ 1383, 1388, 1292, 1282, 1303, 1294, 1395, 1293, 1403, 1419, - /* 360 */ 1298, 1291, 1389, 1390, 1314, 1372, 1365, 1308, 1430, 1427, - /* 370 */ 1411, 1327, 1295, 1356, 1412, 1359, 1350, 1369, 1333, 1418, - /* 380 */ 1423, 1425, 1335, 1340, 1424, 1370, 1426, 1428, 1429, 1431, - /* 390 */ 1375, 1393, 1433, 1380, 1396, 1437, 1438, 1439, 1347, 1443, - /* 400 */ 1444, 1446, 1440, 1348, 1448, 1449, 1413, 1409, 1452, 1351, - /* 410 */ 1450, 1414, 1451, 1415, 1457, 1450, 1458, 1459, 1460, 1461, - /* 420 */ 1462, 1464, 1473, 1465, 1467, 1466, 1468, 1469, 1471, 1472, - /* 430 */ 1468, 1474, 1476, 1477, 1478, 1480, 1373, 1377, 1381, 1382, - /* 440 */ 1482, 1491, 1490, + /* 0 */ 40, 599, 904, 612, 760, 760, 760, 760, 725, -19, + /* 10 */ 16, 16, 100, 760, 760, 760, 760, 760, 760, 760, + /* 20 */ 876, 876, 573, 542, 719, 600, 61, 137, 172, 207, + /* 30 */ 242, 277, 312, 347, 382, 417, 459, 459, 459, 459, + /* 40 */ 459, 459, 459, 459, 459, 459, 459, 459, 459, 459, + /* 50 */ 459, 459, 459, 494, 459, 529, 564, 564, 705, 760, + /* 60 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, + /* 70 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, + /* 80 */ 760, 760, 760, 760, 760, 760, 760, 760, 760, 760, + /* 90 */ 856, 760, 760, 760, 760, 760, 760, 760, 760, 760, + /* 100 */ 760, 760, 760, 760, 987, 746, 746, 746, 746, 746, + /* 110 */ 801, 23, 32, 949, 961, 979, 964, 964, 949, 73, + /* 120 */ 113, -51, 1567, 1567, 1567, 536, 536, 536, 99, 99, + /* 130 */ 813, 813, 667, 205, 240, 949, 949, 949, 949, 949, + /* 140 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949, + /* 150 */ 949, 949, 949, 949, 949, 332, 1011, 422, 422, 113, + /* 160 */ 30, 30, 30, 30, 30, 30, 1567, 1567, 1567, 922, + /* 170 */ -94, -94, 384, 613, 828, 420, 765, 804, 851, 949, + /* 180 */ 949, 949, 949, 949, 949, 949, 949, 949, 949, 949, + /* 190 */ 949, 949, 949, 949, 949, 672, 672, 672, 949, 949, + /* 200 */ 657, 949, 949, 949, -18, 949, 949, 994, 949, 949, + /* 210 */ 949, 949, 949, 949, 949, 949, 949, 949, 772, 1118, + /* 220 */ 712, 712, 712, 810, 45, 769, 1219, 1133, 418, 418, + /* 230 */ 569, 1133, 569, 830, 607, 663, 882, 418, 693, 882, + /* 240 */ 882, 848, 1152, 1065, 1286, 1238, 1238, 1287, 1287, 1238, + /* 250 */ 1344, 1341, 1239, 1353, 1353, 1353, 1353, 1238, 1355, 1239, + /* 260 */ 1344, 1341, 1341, 1239, 1238, 1355, 1243, 1312, 1238, 1238, + /* 270 */ 1355, 1370, 1238, 1355, 1238, 1355, 1370, 1290, 1290, 1290, + /* 280 */ 1327, 1370, 1290, 1301, 1290, 1327, 1290, 1290, 1284, 1304, + /* 290 */ 1284, 1304, 1284, 1304, 1284, 1304, 1238, 1391, 1238, 1280, + /* 300 */ 1370, 1366, 1366, 1370, 1302, 1308, 1310, 1309, 1239, 1414, + /* 310 */ 1416, 1431, 1431, 1440, 1440, 1440, 1440, 1567, 1567, 1567, + /* 320 */ 1567, 1567, 1567, 1567, 1567, 519, 978, 1210, 1225, 104, + /* 330 */ 1141, 1189, 1246, 1248, 1251, 1252, 1253, 1257, 1258, 1273, + /* 340 */ 1003, 1187, 1293, 1170, 1272, 1279, 1234, 1281, 1176, 1177, + /* 350 */ 1289, 1242, 1195, 1453, 1455, 1437, 1319, 1447, 1369, 1452, + /* 360 */ 1446, 1448, 1352, 1345, 1364, 1354, 1458, 1356, 1463, 1479, + /* 370 */ 1359, 1357, 1449, 1450, 1454, 1456, 1372, 1428, 1421, 1367, + /* 380 */ 1489, 1487, 1472, 1388, 1358, 1417, 1470, 1419, 1413, 1429, + /* 390 */ 1395, 1480, 1483, 1486, 1394, 1402, 1488, 1430, 1490, 1491, + /* 400 */ 1485, 1492, 1432, 1457, 1494, 1438, 1451, 1495, 1497, 1498, + /* 410 */ 1496, 1407, 1502, 1503, 1505, 1499, 1406, 1506, 1507, 1475, + /* 420 */ 1468, 1511, 1410, 1509, 1473, 1510, 1474, 1516, 1509, 1517, + /* 430 */ 1518, 1519, 1520, 1521, 1523, 1532, 1524, 1526, 1525, 1527, + /* 440 */ 1528, 1530, 1531, 1527, 1533, 1535, 1536, 1537, 1539, 1436, + /* 450 */ 1441, 1442, 1443, 1543, 1547, 1549, }; #define YY_REDUCE_USE_DFLT (-130) -#define YY_REDUCE_COUNT (316) +#define YY_REDUCE_COUNT (324) #define YY_REDUCE_MIN (-129) -#define YY_REDUCE_MAX (1243) +#define YY_REDUCE_MAX (1300) static const short yy_reduce_ofst[] = { - /* 0 */ -29, 531, 490, 570, -49, 272, 456, 498, 633, 400, - /* 10 */ 612, 626, 113, 482, 678, 719, 384, 726, 748, 791, - /* 20 */ 419, 693, 761, 812, 819, 625, 76, 76, 76, 76, + /* 0 */ -29, 566, 525, 605, -49, 307, 491, 533, 668, 435, + /* 10 */ 601, 644, 148, 747, 786, 795, 419, 788, 827, 790, + /* 20 */ 454, 832, 889, 495, 824, 734, 76, 76, 76, 76, /* 30 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, /* 40 */ 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, - /* 50 */ 76, 76, 76, 76, 76, 793, 795, 856, 871, 875, - /* 60 */ 878, 881, 885, 887, 889, 894, 897, 900, 916, 919, - /* 70 */ 922, 926, 928, 930, 932, 934, 938, 941, 944, 956, - /* 80 */ 963, 966, 968, 970, 972, 974, 978, 980, 982, 996, - /* 90 */ 1004, 1006, 1008, 1011, 1013, 1015, 1019, 1022, 76, 76, - /* 100 */ 76, 76, 76, 76, 76, 76, 76, 555, 210, 260, - /* 110 */ 200, 346, 571, 76, 700, 76, 76, 76, 76, 838, - /* 120 */ 838, 838, 42, 182, 251, 160, 160, 550, 5, 455, - /* 130 */ 585, 721, 749, 882, 884, 971, 618, 462, 797, 514, - /* 140 */ 807, 524, 997, -129, 655, 859, 62, 290, 66, 1030, - /* 150 */ 1032, 589, 1009, 1010, 1037, 1038, 1039, 1044, 740, 852, - /* 160 */ 1012, 112, 147, 230, 257, 180, 369, 403, 500, 549, - /* 170 */ 556, 563, 694, 751, 765, 772, 778, 820, 868, 873, - /* 180 */ 890, 929, 935, 985, 1041, 1080, 1091, 540, 593, 661, - /* 190 */ 1103, 1104, 842, 1108, 1110, 1112, 1048, 1113, 1114, 1068, - /* 200 */ 1116, 1118, 1119, 180, 1121, 1122, 1123, 1124, 1125, 1126, - /* 210 */ 1029, 1034, 1059, 1062, 1070, 842, 1082, 1083, 1133, 1084, - /* 220 */ 1072, 1073, 1045, 1087, 1050, 1127, 1109, 1128, 1129, 1076, - /* 230 */ 1064, 1130, 1131, 1092, 1096, 1140, 1054, 1141, 1142, 1067, - /* 240 */ 1071, 1150, 1090, 1132, 1135, 1136, 1137, 1138, 1139, 1157, - /* 250 */ 1159, 1143, 1098, 1145, 1146, 1147, 1161, 1165, 1086, 1097, - /* 260 */ 1166, 1167, 1169, 1162, 1178, 1180, 1181, 1184, 1168, 1172, - /* 270 */ 1173, 1175, 1170, 1174, 1179, 1185, 1186, 1177, 1188, 1189, - /* 280 */ 1148, 1151, 1149, 1152, 1153, 1155, 1158, 1163, 1196, 1171, - /* 290 */ 1199, 1190, 1191, 1194, 1195, 1198, 1200, 1204, 1206, 1205, - /* 300 */ 1209, 1220, 1224, 1236, 1237, 1240, 1241, 1242, 1243, 1207, - /* 310 */ 1208, 1212, 1221, 1222, 1210, 1225, 1239, + /* 50 */ 76, 76, 76, 76, 76, 76, 76, 76, 783, 898, + /* 60 */ 905, 907, 911, 921, 933, 936, 940, 943, 947, 950, + /* 70 */ 952, 955, 958, 962, 965, 969, 974, 977, 980, 984, + /* 80 */ 988, 991, 993, 996, 999, 1002, 1006, 1010, 1018, 1021, + /* 90 */ 1024, 1028, 1032, 1034, 1036, 1040, 1046, 1051, 1058, 1062, + /* 100 */ 1064, 1068, 1070, 1073, 76, 76, 76, 76, 76, 76, + /* 110 */ 76, 76, 76, 855, 36, 523, 235, 416, 777, 76, + /* 120 */ 278, 76, 76, 76, 76, 700, 700, 700, 150, 220, + /* 130 */ 147, 217, 221, 306, 306, 611, 5, 535, 556, 620, + /* 140 */ 720, 872, 897, 116, 864, 349, 1035, 1037, 404, 1047, + /* 150 */ 992, -129, 1050, 492, 62, 722, 879, 1072, 1089, 808, + /* 160 */ 1066, 1094, 1095, 1096, 1097, 1098, 776, 1054, 557, 57, + /* 170 */ 112, 131, 167, 182, 250, 272, 291, 331, 364, 438, + /* 180 */ 497, 517, 591, 653, 690, 739, 775, 798, 892, 908, + /* 190 */ 924, 930, 1015, 1063, 1069, 355, 784, 799, 981, 1101, + /* 200 */ 926, 1151, 1161, 1162, 945, 1164, 1166, 1128, 1168, 1171, + /* 210 */ 1172, 250, 1173, 1174, 1175, 1178, 1180, 1181, 1088, 1102, + /* 220 */ 1119, 1124, 1126, 926, 1131, 1139, 1188, 1140, 1129, 1130, + /* 230 */ 1103, 1144, 1107, 1179, 1156, 1167, 1182, 1134, 1122, 1183, + /* 240 */ 1184, 1150, 1153, 1197, 1111, 1202, 1203, 1123, 1125, 1205, + /* 250 */ 1147, 1185, 1169, 1186, 1190, 1191, 1192, 1213, 1217, 1193, + /* 260 */ 1157, 1196, 1198, 1194, 1220, 1218, 1145, 1154, 1229, 1231, + /* 270 */ 1233, 1216, 1237, 1240, 1241, 1244, 1222, 1227, 1230, 1232, + /* 280 */ 1223, 1235, 1236, 1245, 1249, 1226, 1250, 1254, 1199, 1201, + /* 290 */ 1204, 1207, 1209, 1211, 1214, 1212, 1255, 1208, 1259, 1215, + /* 300 */ 1256, 1200, 1206, 1260, 1247, 1261, 1263, 1262, 1266, 1278, + /* 310 */ 1282, 1292, 1294, 1297, 1298, 1299, 1300, 1221, 1224, 1228, + /* 320 */ 1288, 1291, 1276, 1277, 1295, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1258, 1248, 1248, 1248, 1180, 1180, 1180, 1180, 1248, 1077, - /* 10 */ 1106, 1106, 1232, 1309, 1309, 1309, 1309, 1309, 1309, 1179, - /* 20 */ 1309, 1309, 1309, 1309, 1248, 1081, 1112, 1309, 1309, 1309, - /* 30 */ 1309, 1309, 1309, 1309, 1309, 1231, 1233, 1120, 1119, 1214, - /* 40 */ 1093, 1117, 1110, 1114, 1181, 1175, 1176, 1174, 1178, 1182, - /* 50 */ 1309, 1113, 1144, 1159, 1143, 1309, 1309, 1309, 1309, 1309, - /* 60 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 70 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 80 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 90 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1153, 1158, - /* 100 */ 1165, 1157, 1154, 1146, 1145, 1147, 1148, 1309, 1000, 1048, - /* 110 */ 1309, 1309, 1309, 1149, 1309, 1150, 1162, 1161, 1160, 1239, - /* 120 */ 1266, 1265, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 130 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 140 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1258, 1248, 1006, - /* 150 */ 1006, 1309, 1248, 1248, 1248, 1248, 1248, 1248, 1244, 1081, - /* 160 */ 1072, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 170 */ 1309, 1236, 1234, 1309, 1195, 1309, 1309, 1309, 1309, 1309, - /* 180 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 190 */ 1309, 1309, 1309, 1309, 1309, 1309, 1077, 1309, 1309, 1309, - /* 200 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1260, - /* 210 */ 1309, 1209, 1077, 1077, 1077, 1079, 1061, 1071, 985, 1116, - /* 220 */ 1095, 1095, 1298, 1116, 1298, 1023, 1280, 1020, 1106, 1095, - /* 230 */ 1177, 1106, 1106, 1078, 1071, 1309, 1301, 1086, 1086, 1300, - /* 240 */ 1300, 1086, 1125, 1051, 1116, 1057, 1057, 1057, 1057, 1086, - /* 250 */ 997, 1116, 1125, 1051, 1051, 1116, 1086, 997, 1213, 1295, - /* 260 */ 1086, 1086, 997, 1188, 1086, 997, 1086, 997, 1188, 1049, - /* 270 */ 1049, 1049, 1038, 1188, 1049, 1023, 1049, 1038, 1049, 1049, - /* 280 */ 1099, 1094, 1099, 1094, 1099, 1094, 1099, 1094, 1086, 1183, - /* 290 */ 1086, 1309, 1188, 1192, 1192, 1188, 1111, 1100, 1109, 1107, - /* 300 */ 1116, 1003, 1041, 1263, 1263, 1259, 1259, 1259, 1259, 1306, - /* 310 */ 1306, 1244, 1275, 1275, 1025, 1025, 1275, 1309, 1309, 1309, - /* 320 */ 1309, 1309, 1309, 1270, 1309, 1197, 1309, 1309, 1309, 1309, - /* 330 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 340 */ 1309, 1309, 1131, 1309, 981, 1241, 1309, 1309, 1240, 1309, - /* 350 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 360 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1297, 1309, 1309, - /* 370 */ 1309, 1309, 1309, 1309, 1212, 1211, 1309, 1309, 1309, 1309, - /* 380 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 390 */ 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1309, 1063, 1309, - /* 400 */ 1309, 1309, 1284, 1309, 1309, 1309, 1309, 1309, 1309, 1309, - /* 410 */ 1108, 1309, 1101, 1309, 1309, 1288, 1309, 1309, 1309, 1309, - /* 420 */ 1309, 1309, 1309, 1309, 1309, 1309, 1250, 1309, 1309, 1309, - /* 430 */ 1249, 1309, 1309, 1309, 1309, 1309, 1133, 1309, 1132, 1136, - /* 440 */ 1309, 991, 1309, + /* 0 */ 1280, 1270, 1270, 1270, 1202, 1202, 1202, 1202, 1270, 1096, + /* 10 */ 1125, 1125, 1254, 1332, 1332, 1332, 1332, 1332, 1332, 1201, + /* 20 */ 1332, 1332, 1332, 1332, 1270, 1100, 1131, 1332, 1332, 1332, + /* 30 */ 1332, 1203, 1204, 1332, 1332, 1332, 1253, 1255, 1141, 1140, + /* 40 */ 1139, 1138, 1236, 1112, 1136, 1129, 1133, 1203, 1197, 1198, + /* 50 */ 1196, 1200, 1204, 1332, 1132, 1167, 1181, 1166, 1332, 1332, + /* 60 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 70 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 80 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 90 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 100 */ 1332, 1332, 1332, 1332, 1175, 1180, 1187, 1179, 1176, 1169, + /* 110 */ 1168, 1170, 1171, 1332, 1019, 1067, 1332, 1332, 1332, 1172, + /* 120 */ 1332, 1173, 1184, 1183, 1182, 1261, 1288, 1287, 1332, 1332, + /* 130 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 140 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 150 */ 1332, 1332, 1332, 1332, 1332, 1280, 1270, 1025, 1025, 1332, + /* 160 */ 1270, 1270, 1270, 1270, 1270, 1270, 1266, 1100, 1091, 1332, + /* 170 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 180 */ 1258, 1256, 1332, 1217, 1332, 1332, 1332, 1332, 1332, 1332, + /* 190 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 200 */ 1332, 1332, 1332, 1332, 1096, 1332, 1332, 1332, 1332, 1332, + /* 210 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1282, 1332, 1231, + /* 220 */ 1096, 1096, 1096, 1098, 1080, 1090, 1004, 1135, 1114, 1114, + /* 230 */ 1321, 1135, 1321, 1042, 1302, 1039, 1125, 1114, 1199, 1125, + /* 240 */ 1125, 1097, 1090, 1332, 1324, 1105, 1105, 1323, 1323, 1105, + /* 250 */ 1146, 1070, 1135, 1076, 1076, 1076, 1076, 1105, 1016, 1135, + /* 260 */ 1146, 1070, 1070, 1135, 1105, 1016, 1235, 1318, 1105, 1105, + /* 270 */ 1016, 1210, 1105, 1016, 1105, 1016, 1210, 1068, 1068, 1068, + /* 280 */ 1057, 1210, 1068, 1042, 1068, 1057, 1068, 1068, 1118, 1113, + /* 290 */ 1118, 1113, 1118, 1113, 1118, 1113, 1105, 1205, 1105, 1332, + /* 300 */ 1210, 1214, 1214, 1210, 1130, 1119, 1128, 1126, 1135, 1022, + /* 310 */ 1060, 1285, 1285, 1281, 1281, 1281, 1281, 1329, 1329, 1266, + /* 320 */ 1297, 1297, 1044, 1044, 1297, 1332, 1332, 1332, 1332, 1332, + /* 330 */ 1332, 1292, 1332, 1219, 1332, 1332, 1332, 1332, 1332, 1332, + /* 340 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 350 */ 1332, 1332, 1152, 1332, 1000, 1263, 1332, 1332, 1262, 1332, + /* 360 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 370 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1320, + /* 380 */ 1332, 1332, 1332, 1332, 1332, 1332, 1234, 1233, 1332, 1332, + /* 390 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 400 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, + /* 410 */ 1332, 1082, 1332, 1332, 1332, 1306, 1332, 1332, 1332, 1332, + /* 420 */ 1332, 1332, 1332, 1127, 1332, 1120, 1332, 1332, 1311, 1332, + /* 430 */ 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1332, 1272, + /* 440 */ 1332, 1332, 1332, 1271, 1332, 1332, 1332, 1332, 1332, 1154, + /* 450 */ 1332, 1153, 1157, 1332, 1010, 1332, }; /********** End of lemon-generated parsing tables *****************************/ @@ -132604,7 +135914,7 @@ static const char *const yyTokenName[] = { "VALUES", "DISTINCT", "DOT", "FROM", "JOIN", "USING", "ORDER", "GROUP", "HAVING", "LIMIT", "WHERE", "INTO", - "INTEGER", "FLOAT", "BLOB", "VARIABLE", + "FLOAT", "BLOB", "INTEGER", "VARIABLE", "CASE", "WHEN", "THEN", "ELSE", "INDEX", "ALTER", "ADD", "error", "input", "cmdlist", "ecmd", "explain", @@ -132780,195 +136090,199 @@ static const char *const yyRuleName[] = { /* 136 */ "where_opt ::= WHERE expr", /* 137 */ "cmd ::= with UPDATE orconf fullname indexed_opt SET setlist where_opt", /* 138 */ "setlist ::= setlist COMMA nm EQ expr", - /* 139 */ "setlist ::= nm EQ expr", - /* 140 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", - /* 141 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", - /* 142 */ "insert_cmd ::= INSERT orconf", - /* 143 */ "insert_cmd ::= REPLACE", - /* 144 */ "idlist_opt ::=", - /* 145 */ "idlist_opt ::= LP idlist RP", - /* 146 */ "idlist ::= idlist COMMA nm", - /* 147 */ "idlist ::= nm", - /* 148 */ "expr ::= LP expr RP", - /* 149 */ "term ::= NULL", - /* 150 */ "expr ::= ID|INDEXED", - /* 151 */ "expr ::= JOIN_KW", - /* 152 */ "expr ::= nm DOT nm", - /* 153 */ "expr ::= nm DOT nm DOT nm", - /* 154 */ "term ::= INTEGER|FLOAT|BLOB", - /* 155 */ "term ::= STRING", - /* 156 */ "expr ::= VARIABLE", - /* 157 */ "expr ::= expr COLLATE ID|STRING", - /* 158 */ "expr ::= CAST LP expr AS typetoken RP", - /* 159 */ "expr ::= ID|INDEXED LP distinct exprlist RP", - /* 160 */ "expr ::= ID|INDEXED LP STAR RP", - /* 161 */ "term ::= CTIME_KW", - /* 162 */ "expr ::= expr AND expr", - /* 163 */ "expr ::= expr OR expr", - /* 164 */ "expr ::= expr LT|GT|GE|LE expr", - /* 165 */ "expr ::= expr EQ|NE expr", - /* 166 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 167 */ "expr ::= expr PLUS|MINUS expr", - /* 168 */ "expr ::= expr STAR|SLASH|REM expr", - /* 169 */ "expr ::= expr CONCAT expr", - /* 170 */ "likeop ::= LIKE_KW|MATCH", - /* 171 */ "likeop ::= NOT LIKE_KW|MATCH", - /* 172 */ "expr ::= expr likeop expr", - /* 173 */ "expr ::= expr likeop expr ESCAPE expr", - /* 174 */ "expr ::= expr ISNULL|NOTNULL", - /* 175 */ "expr ::= expr NOT NULL", - /* 176 */ "expr ::= expr IS expr", - /* 177 */ "expr ::= expr IS NOT expr", - /* 178 */ "expr ::= NOT expr", - /* 179 */ "expr ::= BITNOT expr", - /* 180 */ "expr ::= MINUS expr", - /* 181 */ "expr ::= PLUS expr", - /* 182 */ "between_op ::= BETWEEN", - /* 183 */ "between_op ::= NOT BETWEEN", - /* 184 */ "expr ::= expr between_op expr AND expr", - /* 185 */ "in_op ::= IN", - /* 186 */ "in_op ::= NOT IN", - /* 187 */ "expr ::= expr in_op LP exprlist RP", - /* 188 */ "expr ::= LP select RP", - /* 189 */ "expr ::= expr in_op LP select RP", - /* 190 */ "expr ::= expr in_op nm dbnm paren_exprlist", - /* 191 */ "expr ::= EXISTS LP select RP", - /* 192 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 193 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 194 */ "case_exprlist ::= WHEN expr THEN expr", - /* 195 */ "case_else ::= ELSE expr", - /* 196 */ "case_else ::=", - /* 197 */ "case_operand ::= expr", - /* 198 */ "case_operand ::=", - /* 199 */ "exprlist ::=", - /* 200 */ "nexprlist ::= nexprlist COMMA expr", - /* 201 */ "nexprlist ::= expr", - /* 202 */ "paren_exprlist ::=", - /* 203 */ "paren_exprlist ::= LP exprlist RP", - /* 204 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", - /* 205 */ "uniqueflag ::= UNIQUE", - /* 206 */ "uniqueflag ::=", - /* 207 */ "eidlist_opt ::=", - /* 208 */ "eidlist_opt ::= LP eidlist RP", - /* 209 */ "eidlist ::= eidlist COMMA nm collate sortorder", - /* 210 */ "eidlist ::= nm collate sortorder", - /* 211 */ "collate ::=", - /* 212 */ "collate ::= COLLATE ID|STRING", - /* 213 */ "cmd ::= DROP INDEX ifexists fullname", - /* 214 */ "cmd ::= VACUUM", - /* 215 */ "cmd ::= VACUUM nm", - /* 216 */ "cmd ::= PRAGMA nm dbnm", - /* 217 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 218 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 219 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 220 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 221 */ "plus_num ::= PLUS INTEGER|FLOAT", - /* 222 */ "minus_num ::= MINUS INTEGER|FLOAT", - /* 223 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 224 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 225 */ "trigger_time ::= BEFORE", - /* 226 */ "trigger_time ::= AFTER", - /* 227 */ "trigger_time ::= INSTEAD OF", - /* 228 */ "trigger_time ::=", - /* 229 */ "trigger_event ::= DELETE|INSERT", - /* 230 */ "trigger_event ::= UPDATE", - /* 231 */ "trigger_event ::= UPDATE OF idlist", - /* 232 */ "when_clause ::=", - /* 233 */ "when_clause ::= WHEN expr", - /* 234 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 235 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 236 */ "trnm ::= nm DOT nm", - /* 237 */ "tridxby ::= INDEXED BY nm", - /* 238 */ "tridxby ::= NOT INDEXED", - /* 239 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", - /* 240 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", - /* 241 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", - /* 242 */ "trigger_cmd ::= select", - /* 243 */ "expr ::= RAISE LP IGNORE RP", - /* 244 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 245 */ "raisetype ::= ROLLBACK", - /* 246 */ "raisetype ::= ABORT", - /* 247 */ "raisetype ::= FAIL", - /* 248 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 249 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 250 */ "cmd ::= DETACH database_kw_opt expr", - /* 251 */ "key_opt ::=", - /* 252 */ "key_opt ::= KEY expr", - /* 253 */ "cmd ::= REINDEX", - /* 254 */ "cmd ::= REINDEX nm dbnm", - /* 255 */ "cmd ::= ANALYZE", - /* 256 */ "cmd ::= ANALYZE nm dbnm", - /* 257 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 258 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", - /* 259 */ "add_column_fullname ::= fullname", - /* 260 */ "cmd ::= create_vtab", - /* 261 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 262 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 263 */ "vtabarg ::=", - /* 264 */ "vtabargtoken ::= ANY", - /* 265 */ "vtabargtoken ::= lp anylist RP", - /* 266 */ "lp ::= LP", - /* 267 */ "with ::=", - /* 268 */ "with ::= WITH wqlist", - /* 269 */ "with ::= WITH RECURSIVE wqlist", - /* 270 */ "wqlist ::= nm eidlist_opt AS LP select RP", - /* 271 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", - /* 272 */ "input ::= cmdlist", - /* 273 */ "cmdlist ::= cmdlist ecmd", - /* 274 */ "cmdlist ::= ecmd", - /* 275 */ "ecmd ::= SEMI", - /* 276 */ "ecmd ::= explain cmdx SEMI", - /* 277 */ "explain ::=", - /* 278 */ "trans_opt ::=", - /* 279 */ "trans_opt ::= TRANSACTION", - /* 280 */ "trans_opt ::= TRANSACTION nm", - /* 281 */ "savepoint_opt ::= SAVEPOINT", - /* 282 */ "savepoint_opt ::=", - /* 283 */ "cmd ::= create_table create_table_args", - /* 284 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 285 */ "columnlist ::= columnname carglist", - /* 286 */ "nm ::= ID|INDEXED", - /* 287 */ "nm ::= STRING", - /* 288 */ "nm ::= JOIN_KW", - /* 289 */ "typetoken ::= typename", - /* 290 */ "typename ::= ID|STRING", - /* 291 */ "signed ::= plus_num", - /* 292 */ "signed ::= minus_num", - /* 293 */ "carglist ::= carglist ccons", - /* 294 */ "carglist ::=", - /* 295 */ "ccons ::= NULL onconf", - /* 296 */ "conslist_opt ::= COMMA conslist", - /* 297 */ "conslist ::= conslist tconscomma tcons", - /* 298 */ "conslist ::= tcons", - /* 299 */ "tconscomma ::=", - /* 300 */ "defer_subclause_opt ::= defer_subclause", - /* 301 */ "resolvetype ::= raisetype", - /* 302 */ "selectnowith ::= oneselect", - /* 303 */ "oneselect ::= values", - /* 304 */ "sclp ::= selcollist COMMA", - /* 305 */ "as ::= ID|STRING", - /* 306 */ "expr ::= term", - /* 307 */ "exprlist ::= nexprlist", - /* 308 */ "nmnum ::= plus_num", - /* 309 */ "nmnum ::= nm", - /* 310 */ "nmnum ::= ON", - /* 311 */ "nmnum ::= DELETE", - /* 312 */ "nmnum ::= DEFAULT", - /* 313 */ "plus_num ::= INTEGER|FLOAT", - /* 314 */ "foreach_clause ::=", - /* 315 */ "foreach_clause ::= FOR EACH ROW", - /* 316 */ "trnm ::= nm", - /* 317 */ "tridxby ::=", - /* 318 */ "database_kw_opt ::= DATABASE", - /* 319 */ "database_kw_opt ::=", - /* 320 */ "kwcolumn_opt ::=", - /* 321 */ "kwcolumn_opt ::= COLUMNKW", - /* 322 */ "vtabarglist ::= vtabarg", - /* 323 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 324 */ "vtabarg ::= vtabarg vtabargtoken", - /* 325 */ "anylist ::=", - /* 326 */ "anylist ::= anylist LP anylist RP", - /* 327 */ "anylist ::= anylist ANY", + /* 139 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", + /* 140 */ "setlist ::= nm EQ expr", + /* 141 */ "setlist ::= LP idlist RP EQ expr", + /* 142 */ "cmd ::= with insert_cmd INTO fullname idlist_opt select", + /* 143 */ "cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES", + /* 144 */ "insert_cmd ::= INSERT orconf", + /* 145 */ "insert_cmd ::= REPLACE", + /* 146 */ "idlist_opt ::=", + /* 147 */ "idlist_opt ::= LP idlist RP", + /* 148 */ "idlist ::= idlist COMMA nm", + /* 149 */ "idlist ::= nm", + /* 150 */ "expr ::= LP expr RP", + /* 151 */ "term ::= NULL", + /* 152 */ "expr ::= ID|INDEXED", + /* 153 */ "expr ::= JOIN_KW", + /* 154 */ "expr ::= nm DOT nm", + /* 155 */ "expr ::= nm DOT nm DOT nm", + /* 156 */ "term ::= FLOAT|BLOB", + /* 157 */ "term ::= STRING", + /* 158 */ "term ::= INTEGER", + /* 159 */ "expr ::= VARIABLE", + /* 160 */ "expr ::= expr COLLATE ID|STRING", + /* 161 */ "expr ::= CAST LP expr AS typetoken RP", + /* 162 */ "expr ::= ID|INDEXED LP distinct exprlist RP", + /* 163 */ "expr ::= ID|INDEXED LP STAR RP", + /* 164 */ "term ::= CTIME_KW", + /* 165 */ "expr ::= LP nexprlist COMMA expr RP", + /* 166 */ "expr ::= expr AND expr", + /* 167 */ "expr ::= expr OR expr", + /* 168 */ "expr ::= expr LT|GT|GE|LE expr", + /* 169 */ "expr ::= expr EQ|NE expr", + /* 170 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 171 */ "expr ::= expr PLUS|MINUS expr", + /* 172 */ "expr ::= expr STAR|SLASH|REM expr", + /* 173 */ "expr ::= expr CONCAT expr", + /* 174 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 175 */ "expr ::= expr likeop expr", + /* 176 */ "expr ::= expr likeop expr ESCAPE expr", + /* 177 */ "expr ::= expr ISNULL|NOTNULL", + /* 178 */ "expr ::= expr NOT NULL", + /* 179 */ "expr ::= expr IS expr", + /* 180 */ "expr ::= expr IS NOT expr", + /* 181 */ "expr ::= NOT expr", + /* 182 */ "expr ::= BITNOT expr", + /* 183 */ "expr ::= MINUS expr", + /* 184 */ "expr ::= PLUS expr", + /* 185 */ "between_op ::= BETWEEN", + /* 186 */ "between_op ::= NOT BETWEEN", + /* 187 */ "expr ::= expr between_op expr AND expr", + /* 188 */ "in_op ::= IN", + /* 189 */ "in_op ::= NOT IN", + /* 190 */ "expr ::= expr in_op LP exprlist RP", + /* 191 */ "expr ::= LP select RP", + /* 192 */ "expr ::= expr in_op LP select RP", + /* 193 */ "expr ::= expr in_op nm dbnm paren_exprlist", + /* 194 */ "expr ::= EXISTS LP select RP", + /* 195 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 196 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 197 */ "case_exprlist ::= WHEN expr THEN expr", + /* 198 */ "case_else ::= ELSE expr", + /* 199 */ "case_else ::=", + /* 200 */ "case_operand ::= expr", + /* 201 */ "case_operand ::=", + /* 202 */ "exprlist ::=", + /* 203 */ "nexprlist ::= nexprlist COMMA expr", + /* 204 */ "nexprlist ::= expr", + /* 205 */ "paren_exprlist ::=", + /* 206 */ "paren_exprlist ::= LP exprlist RP", + /* 207 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 208 */ "uniqueflag ::= UNIQUE", + /* 209 */ "uniqueflag ::=", + /* 210 */ "eidlist_opt ::=", + /* 211 */ "eidlist_opt ::= LP eidlist RP", + /* 212 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 213 */ "eidlist ::= nm collate sortorder", + /* 214 */ "collate ::=", + /* 215 */ "collate ::= COLLATE ID|STRING", + /* 216 */ "cmd ::= DROP INDEX ifexists fullname", + /* 217 */ "cmd ::= VACUUM", + /* 218 */ "cmd ::= VACUUM nm", + /* 219 */ "cmd ::= PRAGMA nm dbnm", + /* 220 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 221 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 222 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 223 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 224 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 225 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 226 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 227 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 228 */ "trigger_time ::= BEFORE", + /* 229 */ "trigger_time ::= AFTER", + /* 230 */ "trigger_time ::= INSTEAD OF", + /* 231 */ "trigger_time ::=", + /* 232 */ "trigger_event ::= DELETE|INSERT", + /* 233 */ "trigger_event ::= UPDATE", + /* 234 */ "trigger_event ::= UPDATE OF idlist", + /* 235 */ "when_clause ::=", + /* 236 */ "when_clause ::= WHEN expr", + /* 237 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 238 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 239 */ "trnm ::= nm DOT nm", + /* 240 */ "tridxby ::= INDEXED BY nm", + /* 241 */ "tridxby ::= NOT INDEXED", + /* 242 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt", + /* 243 */ "trigger_cmd ::= insert_cmd INTO trnm idlist_opt select", + /* 244 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt", + /* 245 */ "trigger_cmd ::= select", + /* 246 */ "expr ::= RAISE LP IGNORE RP", + /* 247 */ "expr ::= RAISE LP raisetype COMMA nm RP", + /* 248 */ "raisetype ::= ROLLBACK", + /* 249 */ "raisetype ::= ABORT", + /* 250 */ "raisetype ::= FAIL", + /* 251 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 252 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 253 */ "cmd ::= DETACH database_kw_opt expr", + /* 254 */ "key_opt ::=", + /* 255 */ "key_opt ::= KEY expr", + /* 256 */ "cmd ::= REINDEX", + /* 257 */ "cmd ::= REINDEX nm dbnm", + /* 258 */ "cmd ::= ANALYZE", + /* 259 */ "cmd ::= ANALYZE nm dbnm", + /* 260 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 261 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", + /* 262 */ "add_column_fullname ::= fullname", + /* 263 */ "cmd ::= create_vtab", + /* 264 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 265 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 266 */ "vtabarg ::=", + /* 267 */ "vtabargtoken ::= ANY", + /* 268 */ "vtabargtoken ::= lp anylist RP", + /* 269 */ "lp ::= LP", + /* 270 */ "with ::=", + /* 271 */ "with ::= WITH wqlist", + /* 272 */ "with ::= WITH RECURSIVE wqlist", + /* 273 */ "wqlist ::= nm eidlist_opt AS LP select RP", + /* 274 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", + /* 275 */ "input ::= cmdlist", + /* 276 */ "cmdlist ::= cmdlist ecmd", + /* 277 */ "cmdlist ::= ecmd", + /* 278 */ "ecmd ::= SEMI", + /* 279 */ "ecmd ::= explain cmdx SEMI", + /* 280 */ "explain ::=", + /* 281 */ "trans_opt ::=", + /* 282 */ "trans_opt ::= TRANSACTION", + /* 283 */ "trans_opt ::= TRANSACTION nm", + /* 284 */ "savepoint_opt ::= SAVEPOINT", + /* 285 */ "savepoint_opt ::=", + /* 286 */ "cmd ::= create_table create_table_args", + /* 287 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 288 */ "columnlist ::= columnname carglist", + /* 289 */ "nm ::= ID|INDEXED", + /* 290 */ "nm ::= STRING", + /* 291 */ "nm ::= JOIN_KW", + /* 292 */ "typetoken ::= typename", + /* 293 */ "typename ::= ID|STRING", + /* 294 */ "signed ::= plus_num", + /* 295 */ "signed ::= minus_num", + /* 296 */ "carglist ::= carglist ccons", + /* 297 */ "carglist ::=", + /* 298 */ "ccons ::= NULL onconf", + /* 299 */ "conslist_opt ::= COMMA conslist", + /* 300 */ "conslist ::= conslist tconscomma tcons", + /* 301 */ "conslist ::= tcons", + /* 302 */ "tconscomma ::=", + /* 303 */ "defer_subclause_opt ::= defer_subclause", + /* 304 */ "resolvetype ::= raisetype", + /* 305 */ "selectnowith ::= oneselect", + /* 306 */ "oneselect ::= values", + /* 307 */ "sclp ::= selcollist COMMA", + /* 308 */ "as ::= ID|STRING", + /* 309 */ "expr ::= term", + /* 310 */ "likeop ::= LIKE_KW|MATCH", + /* 311 */ "exprlist ::= nexprlist", + /* 312 */ "nmnum ::= plus_num", + /* 313 */ "nmnum ::= nm", + /* 314 */ "nmnum ::= ON", + /* 315 */ "nmnum ::= DELETE", + /* 316 */ "nmnum ::= DEFAULT", + /* 317 */ "plus_num ::= INTEGER|FLOAT", + /* 318 */ "foreach_clause ::=", + /* 319 */ "foreach_clause ::= FOR EACH ROW", + /* 320 */ "trnm ::= nm", + /* 321 */ "tridxby ::=", + /* 322 */ "database_kw_opt ::= DATABASE", + /* 323 */ "database_kw_opt ::=", + /* 324 */ "kwcolumn_opt ::=", + /* 325 */ "kwcolumn_opt ::= COLUMNKW", + /* 326 */ "vtabarglist ::= vtabarg", + /* 327 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 328 */ "vtabarg ::= vtabarg vtabargtoken", + /* 329 */ "anylist ::=", + /* 330 */ "anylist ::= anylist LP anylist RP", + /* 331 */ "anylist ::= anylist ANY", }; #endif /* NDEBUG */ @@ -133015,6 +136329,31 @@ static int yyGrowStack(yyParser *p){ # define YYMALLOCARGTYPE size_t #endif +/* Initialize a new parser that has already been allocated. +*/ +SQLITE_PRIVATE void sqlite3ParserInit(void *yypParser){ + yyParser *pParser = (yyParser*)yypParser; +#ifdef YYTRACKMAXSTACKDEPTH + pParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + pParser->yytos = NULL; + pParser->yystack = NULL; + pParser->yystksz = 0; + if( yyGrowStack(pParser) ){ + pParser->yystack = &pParser->yystk0; + pParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + pParser->yyerrcnt = -1; +#endif + pParser->yytos = pParser->yystack; + pParser->yystack[0].stateno = 0; + pParser->yystack[0].major = 0; +} + +#ifndef sqlite3Parser_ENGINEALWAYSONSTACK /* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like @@ -133030,28 +136369,11 @@ static int yyGrowStack(yyParser *p){ SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE)){ yyParser *pParser; pParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); - if( pParser ){ -#ifdef YYTRACKMAXSTACKDEPTH - pParser->yyhwm = 0; -#endif -#if YYSTACKDEPTH<=0 - pParser->yytos = NULL; - pParser->yystack = NULL; - pParser->yystksz = 0; - if( yyGrowStack(pParser) ){ - pParser->yystack = &pParser->yystk0; - pParser->yystksz = 1; - } -#endif -#ifndef YYNOERRORRECOVERY - pParser->yyerrcnt = -1; -#endif - pParser->yytos = pParser->yystack; - pParser->yystack[0].stateno = 0; - pParser->yystack[0].major = 0; - } + if( pParser ) sqlite3ParserInit(pParser); return pParser; } +#endif /* sqlite3Parser_ENGINEALWAYSONSTACK */ + /* The following function deletes the "minor type" or semantic value ** associated with a symbol. The symbol can be either a terminal @@ -133177,6 +136499,18 @@ static void yy_pop_parser_stack(yyParser *pParser){ yy_destructor(pParser, yytos->major, &yytos->minor); } +/* +** Clear all secondary memory allocations from the parser +*/ +SQLITE_PRIVATE void sqlite3ParserFinalize(void *p){ + yyParser *pParser = (yyParser*)p; + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif +} + +#ifndef sqlite3Parser_ENGINEALWAYSONSTACK /* ** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. @@ -133189,16 +136523,13 @@ SQLITE_PRIVATE void sqlite3ParserFree( void *p, /* The parser to be deleted */ void (*freeProc)(void*) /* Function used to reclaim memory */ ){ - yyParser *pParser = (yyParser*)p; #ifndef YYPARSEFREENEVERNULL - if( pParser==0 ) return; -#endif - while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); -#if YYSTACKDEPTH<=0 - if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); + if( p==0 ) return; #endif - (*freeProc)((void*)pParser); + sqlite3ParserFinalize(p); + (*freeProc)(p); } +#endif /* sqlite3Parser_ENGINEALWAYSONSTACK */ /* ** Return the peak depth of the stack for a parser. @@ -133225,50 +136556,47 @@ static unsigned int yy_find_shift_action( assert( stateno <= YY_SHIFT_COUNT ); do{ i = yy_shift_ofst[stateno]; - if( i==YY_SHIFT_USE_DFLT ) return yy_default[stateno]; assert( iLookAhead!=YYNOCODE ); i += iLookAhead; if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ - if( iLookAhead>0 ){ #ifdef YYFALLBACK - YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", - yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); - } -#endif - assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ - iLookAhead = iFallback; - continue; + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } #endif #ifdef YYWILDCARD - { - int j = i - iLookAhead + YYWILDCARD; - if( + { + int j = i - iLookAhead + YYWILDCARD; + if( #if YY_SHIFT_MIN+YYWILDCARD<0 - j>=0 && + j>=0 && #endif #if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT - j0 + ){ #ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", - yyTracePrompt, yyTokenName[iLookAhead], - yyTokenName[YYWILDCARD]); - } -#endif /* NDEBUG */ - return yy_action[j]; + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); } +#endif /* NDEBUG */ + return yy_action[j]; } -#endif /* YYWILDCARD */ } +#endif /* YYWILDCARD */ return yy_default[stateno]; }else{ return yy_action[i]; @@ -133312,7 +136640,6 @@ static int yy_find_reduce_action( */ static void yyStackOverflow(yyParser *yypParser){ sqlite3ParserARG_FETCH; - yypParser->yytos--; #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt); @@ -133367,12 +136694,14 @@ static void yy_shift( #endif #if YYSTACKDEPTH>0 if( yypParser->yytos>=&yypParser->yystack[YYSTACKDEPTH] ){ + yypParser->yytos--; yyStackOverflow(yypParser); return; } #else if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ if( yyGrowStack(yypParser) ){ + yypParser->yytos--; yyStackOverflow(yypParser); return; } @@ -133534,7 +136863,9 @@ static const struct { { 201, 2 }, { 149, 8 }, { 218, 5 }, + { 218, 7 }, { 218, 3 }, + { 218, 5 }, { 149, 6 }, { 149, 7 }, { 219, 2 }, @@ -133551,12 +136882,14 @@ static const struct { { 173, 5 }, { 172, 1 }, { 172, 1 }, + { 172, 1 }, { 173, 1 }, { 173, 3 }, { 173, 6 }, { 173, 5 }, { 173, 4 }, { 172, 1 }, + { 173, 5 }, { 173, 3 }, { 173, 3 }, { 173, 3 }, @@ -133565,7 +136898,6 @@ static const struct { { 173, 3 }, { 173, 3 }, { 173, 3 }, - { 221, 1 }, { 221, 2 }, { 173, 3 }, { 173, 5 }, @@ -133702,6 +137034,7 @@ static const struct { { 209, 2 }, { 210, 1 }, { 173, 1 }, + { 221, 1 }, { 208, 1 }, { 230, 1 }, { 230, 1 }, @@ -133844,7 +137177,7 @@ static void yy_reduce( case 67: /* defer_subclause_opt ::= */ yytestcase(yyruleno==67); case 76: /* ifexists ::= */ yytestcase(yyruleno==76); case 90: /* distinct ::= */ yytestcase(yyruleno==90); - case 211: /* collate ::= */ yytestcase(yyruleno==211); + case 214: /* collate ::= */ yytestcase(yyruleno==214); {yymsp[1].minor.yy194 = 0;} break; case 17: /* ifnotexists ::= IF NOT EXISTS */ @@ -133910,7 +137243,7 @@ static void yy_reduce( case 33: /* ccons ::= DEFAULT MINUS term */ { ExprSpan v; - v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy190.pExpr, 0, 0); + v.pExpr = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy190.pExpr, 0); v.zStart = yymsp[-1].minor.yy0.z; v.zEnd = yymsp[0].minor.yy190.zEnd; sqlite3AddDefaultValue(pParse,&v); @@ -133983,14 +137316,14 @@ static void yy_reduce( break; case 56: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ case 71: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==71); - case 142: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==142); + case 144: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==144); {yymsp[-1].minor.yy194 = yymsp[0].minor.yy194;} break; case 58: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ case 75: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==75); - case 183: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==183); - case 186: /* in_op ::= NOT IN */ yytestcase(yyruleno==186); - case 212: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==212); + case 186: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==186); + case 189: /* in_op ::= NOT IN */ yytestcase(yyruleno==189); + case 215: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==215); {yymsp[-1].minor.yy194 = 1;} break; case 59: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ @@ -134026,7 +137359,7 @@ static void yy_reduce( {yymsp[0].minor.yy194 = OE_Ignore;} break; case 73: /* resolvetype ::= REPLACE */ - case 143: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==143); + case 145: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==145); {yymsp[0].minor.yy194 = OE_Replace;} break; case 74: /* cmd ::= DROP TABLE ifexists fullname */ @@ -134154,9 +137487,9 @@ static void yy_reduce( case 91: /* sclp ::= */ case 119: /* orderby_opt ::= */ yytestcase(yyruleno==119); case 126: /* groupby_opt ::= */ yytestcase(yyruleno==126); - case 199: /* exprlist ::= */ yytestcase(yyruleno==199); - case 202: /* paren_exprlist ::= */ yytestcase(yyruleno==202); - case 207: /* eidlist_opt ::= */ yytestcase(yyruleno==207); + case 202: /* exprlist ::= */ yytestcase(yyruleno==202); + case 205: /* paren_exprlist ::= */ yytestcase(yyruleno==205); + case 210: /* eidlist_opt ::= */ yytestcase(yyruleno==210); {yymsp[1].minor.yy148 = 0;} break; case 92: /* selcollist ::= sclp expr as */ @@ -134174,16 +137507,16 @@ static void yy_reduce( break; case 94: /* selcollist ::= sclp nm DOT STAR */ { - Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0, &yymsp[0].minor.yy0); - Expr *pLeft = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); - Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight, 0); + Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); + Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, pDot); } break; case 95: /* as ::= AS nm */ case 106: /* dbnm ::= DOT nm */ yytestcase(yyruleno==106); - case 221: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==221); - case 222: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==222); + case 224: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==224); + case 225: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==225); {yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} break; case 97: /* from ::= */ @@ -134266,14 +137599,14 @@ static void yy_reduce( case 112: /* on_opt ::= ON expr */ case 129: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==129); case 136: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==136); - case 195: /* case_else ::= ELSE expr */ yytestcase(yyruleno==195); + case 198: /* case_else ::= ELSE expr */ yytestcase(yyruleno==198); {yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr;} break; case 113: /* on_opt ::= */ case 128: /* having_opt ::= */ yytestcase(yyruleno==128); case 135: /* where_opt ::= */ yytestcase(yyruleno==135); - case 196: /* case_else ::= */ yytestcase(yyruleno==196); - case 198: /* case_operand ::= */ yytestcase(yyruleno==198); + case 199: /* case_else ::= */ yytestcase(yyruleno==199); + case 201: /* case_operand ::= */ yytestcase(yyruleno==201); {yymsp[1].minor.yy72 = 0;} break; case 115: /* indexed_opt ::= INDEXED BY nm */ @@ -134286,7 +137619,7 @@ static void yy_reduce( {yymsp[-3].minor.yy254 = yymsp[-1].minor.yy254;} break; case 118: /* using_opt ::= */ - case 144: /* idlist_opt ::= */ yytestcase(yyruleno==144); + case 146: /* idlist_opt ::= */ yytestcase(yyruleno==146); {yymsp[1].minor.yy254 = 0;} break; case 120: /* orderby_opt ::= ORDER BY sortlist */ @@ -134347,69 +137680,89 @@ static void yy_reduce( sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, 1); } break; - case 139: /* setlist ::= nm EQ expr */ + case 139: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ +{ + yymsp[-6].minor.yy148 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy148, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr); +} + break; + case 140: /* setlist ::= nm EQ expr */ { yylhsminor.yy148 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy190.pExpr); sqlite3ExprListSetName(pParse, yylhsminor.yy148, &yymsp[-2].minor.yy0, 1); } yymsp[-2].minor.yy148 = yylhsminor.yy148; break; - case 140: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ + case 141: /* setlist ::= LP idlist RP EQ expr */ +{ + yymsp[-4].minor.yy148 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy254, yymsp[0].minor.yy190.pExpr); +} + break; + case 142: /* cmd ::= with insert_cmd INTO fullname idlist_opt select */ { sqlite3WithPush(pParse, yymsp[-5].minor.yy285, 1); sqlite3Insert(pParse, yymsp[-2].minor.yy185, yymsp[0].minor.yy243, yymsp[-1].minor.yy254, yymsp[-4].minor.yy194); } break; - case 141: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ + case 143: /* cmd ::= with insert_cmd INTO fullname idlist_opt DEFAULT VALUES */ { sqlite3WithPush(pParse, yymsp[-6].minor.yy285, 1); sqlite3Insert(pParse, yymsp[-3].minor.yy185, 0, yymsp[-2].minor.yy254, yymsp[-5].minor.yy194); } break; - case 145: /* idlist_opt ::= LP idlist RP */ + case 147: /* idlist_opt ::= LP idlist RP */ {yymsp[-2].minor.yy254 = yymsp[-1].minor.yy254;} break; - case 146: /* idlist ::= idlist COMMA nm */ + case 148: /* idlist ::= idlist COMMA nm */ {yymsp[-2].minor.yy254 = sqlite3IdListAppend(pParse->db,yymsp[-2].minor.yy254,&yymsp[0].minor.yy0);} break; - case 147: /* idlist ::= nm */ + case 149: /* idlist ::= nm */ {yymsp[0].minor.yy254 = sqlite3IdListAppend(pParse->db,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} break; - case 148: /* expr ::= LP expr RP */ + case 150: /* expr ::= LP expr RP */ {spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ yymsp[-2].minor.yy190.pExpr = yymsp[-1].minor.yy190.pExpr;} break; - case 149: /* term ::= NULL */ - case 154: /* term ::= INTEGER|FLOAT|BLOB */ yytestcase(yyruleno==154); - case 155: /* term ::= STRING */ yytestcase(yyruleno==155); + case 151: /* term ::= NULL */ + case 156: /* term ::= FLOAT|BLOB */ yytestcase(yyruleno==156); + case 157: /* term ::= STRING */ yytestcase(yyruleno==157); {spanExpr(&yymsp[0].minor.yy190,pParse,yymsp[0].major,yymsp[0].minor.yy0);/*A-overwrites-X*/} break; - case 150: /* expr ::= ID|INDEXED */ - case 151: /* expr ::= JOIN_KW */ yytestcase(yyruleno==151); + case 152: /* expr ::= ID|INDEXED */ + case 153: /* expr ::= JOIN_KW */ yytestcase(yyruleno==153); {spanExpr(&yymsp[0].minor.yy190,pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 152: /* expr ::= nm DOT nm */ + case 154: /* expr ::= nm DOT nm */ { - Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); - Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); + Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); + Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ - yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2, 0); + yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } break; - case 153: /* expr ::= nm DOT nm DOT nm */ + case 155: /* expr ::= nm DOT nm DOT nm */ { - Expr *temp1 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-4].minor.yy0); - Expr *temp2 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[-2].minor.yy0); - Expr *temp3 = sqlite3PExpr(pParse, TK_ID, 0, 0, &yymsp[0].minor.yy0); - Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3, 0); + Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1); + Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); + Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); + Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3); spanSet(&yymsp[-4].minor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); +} + break; + case 158: /* term ::= INTEGER */ +{ + yylhsminor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + yylhsminor.yy190.zStart = yymsp[0].minor.yy0.z; + yylhsminor.yy190.zEnd = yymsp[0].minor.yy0.z + yymsp[0].minor.yy0.n; + if( yylhsminor.yy190.pExpr ) yylhsminor.yy190.pExpr->flags |= EP_Leaf|EP_Resolved; } + yymsp[0].minor.yy190 = yylhsminor.yy190; break; - case 156: /* expr ::= VARIABLE */ + case 159: /* expr ::= VARIABLE */ { if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ + u32 n = yymsp[0].minor.yy0.n; spanExpr(&yymsp[0].minor.yy190, pParse, TK_VARIABLE, yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy190.pExpr); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy190.pExpr, n); }else{ /* When doing a nested parse, one can include terms in an expression ** that look like this: #1 #2 ... These terms refer to registers @@ -134421,25 +137774,26 @@ static void yy_reduce( sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); yymsp[0].minor.yy190.pExpr = 0; }else{ - yymsp[0].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0, &t); + yymsp[0].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); if( yymsp[0].minor.yy190.pExpr ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy190.pExpr->iTable); } } } break; - case 157: /* expr ::= expr COLLATE ID|STRING */ + case 160: /* expr ::= expr COLLATE ID|STRING */ { yymsp[-2].minor.yy190.pExpr = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy190.pExpr, &yymsp[0].minor.yy0, 1); yymsp[-2].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 158: /* expr ::= CAST LP expr AS typetoken RP */ + case 161: /* expr ::= CAST LP expr AS typetoken RP */ { spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ - yymsp[-5].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CAST, yymsp[-3].minor.yy190.pExpr, 0, &yymsp[-1].minor.yy0); + yymsp[-5].minor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); + sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy190.pExpr, yymsp[-3].minor.yy190.pExpr, 0); } break; - case 159: /* expr ::= ID|INDEXED LP distinct exprlist RP */ + case 162: /* expr ::= ID|INDEXED LP distinct exprlist RP */ { if( yymsp[-1].minor.yy148 && yymsp[-1].minor.yy148->nExpr>pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", &yymsp[-4].minor.yy0); @@ -134452,96 +137806,110 @@ static void yy_reduce( } yymsp[-4].minor.yy190 = yylhsminor.yy190; break; - case 160: /* expr ::= ID|INDEXED LP STAR RP */ + case 163: /* expr ::= ID|INDEXED LP STAR RP */ { yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0); spanSet(&yylhsminor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); } yymsp[-3].minor.yy190 = yylhsminor.yy190; break; - case 161: /* term ::= CTIME_KW */ + case 164: /* term ::= CTIME_KW */ { yylhsminor.yy190.pExpr = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0); spanSet(&yylhsminor.yy190, &yymsp[0].minor.yy0, &yymsp[0].minor.yy0); } yymsp[0].minor.yy190 = yylhsminor.yy190; break; - case 162: /* expr ::= expr AND expr */ - case 163: /* expr ::= expr OR expr */ yytestcase(yyruleno==163); - case 164: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==164); - case 165: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==165); - case 166: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==166); - case 167: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==167); - case 168: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==168); - case 169: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==169); -{spanBinaryExpr(pParse,yymsp[-1].major,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190);} + case 165: /* expr ::= LP nexprlist COMMA expr RP */ +{ + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy148, yymsp[-1].minor.yy190.pExpr); + yylhsminor.yy190.pExpr = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yylhsminor.yy190.pExpr ){ + yylhsminor.yy190.pExpr->x.pList = pList; + spanSet(&yylhsminor.yy190, &yymsp[-4].minor.yy0, &yymsp[0].minor.yy0); + }else{ + sqlite3ExprListDelete(pParse->db, pList); + } +} + yymsp[-4].minor.yy190 = yylhsminor.yy190; break; - case 170: /* likeop ::= LIKE_KW|MATCH */ -{yymsp[0].minor.yy392.eOperator = yymsp[0].minor.yy0; yymsp[0].minor.yy392.bNot = 0;/*A-overwrites-X*/} + case 166: /* expr ::= expr AND expr */ + case 167: /* expr ::= expr OR expr */ yytestcase(yyruleno==167); + case 168: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==168); + case 169: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==169); + case 170: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==170); + case 171: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==171); + case 172: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==172); + case 173: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==173); +{spanBinaryExpr(pParse,yymsp[-1].major,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190);} break; - case 171: /* likeop ::= NOT LIKE_KW|MATCH */ -{yymsp[-1].minor.yy392.eOperator = yymsp[0].minor.yy0; yymsp[-1].minor.yy392.bNot = 1;} + case 174: /* likeop ::= NOT LIKE_KW|MATCH */ +{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} break; - case 172: /* expr ::= expr likeop expr */ + case 175: /* expr ::= expr likeop expr */ { ExprList *pList; + int bNot = yymsp[-1].minor.yy0.n & 0x80000000; + yymsp[-1].minor.yy0.n &= 0x7fffffff; pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy190.pExpr); pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy190.pExpr); - yymsp[-2].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy392.eOperator); - exprNot(pParse, yymsp[-1].minor.yy392.bNot, &yymsp[-2].minor.yy190); + yymsp[-2].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0); + exprNot(pParse, bNot, &yymsp[-2].minor.yy190); yymsp[-2].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd; if( yymsp[-2].minor.yy190.pExpr ) yymsp[-2].minor.yy190.pExpr->flags |= EP_InfixFunc; } break; - case 173: /* expr ::= expr likeop expr ESCAPE expr */ + case 176: /* expr ::= expr likeop expr ESCAPE expr */ { ExprList *pList; + int bNot = yymsp[-3].minor.yy0.n & 0x80000000; + yymsp[-3].minor.yy0.n &= 0x7fffffff; pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr); pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy190.pExpr); pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy190.pExpr); - yymsp[-4].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy392.eOperator); - exprNot(pParse, yymsp[-3].minor.yy392.bNot, &yymsp[-4].minor.yy190); + yymsp[-4].minor.yy190.pExpr = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0); + exprNot(pParse, bNot, &yymsp[-4].minor.yy190); yymsp[-4].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd; if( yymsp[-4].minor.yy190.pExpr ) yymsp[-4].minor.yy190.pExpr->flags |= EP_InfixFunc; } break; - case 174: /* expr ::= expr ISNULL|NOTNULL */ + case 177: /* expr ::= expr ISNULL|NOTNULL */ {spanUnaryPostfix(pParse,yymsp[0].major,&yymsp[-1].minor.yy190,&yymsp[0].minor.yy0);} break; - case 175: /* expr ::= expr NOT NULL */ + case 178: /* expr ::= expr NOT NULL */ {spanUnaryPostfix(pParse,TK_NOTNULL,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy0);} break; - case 176: /* expr ::= expr IS expr */ + case 179: /* expr ::= expr IS expr */ { spanBinaryExpr(pParse,TK_IS,&yymsp[-2].minor.yy190,&yymsp[0].minor.yy190); binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-2].minor.yy190.pExpr, TK_ISNULL); } break; - case 177: /* expr ::= expr IS NOT expr */ + case 180: /* expr ::= expr IS NOT expr */ { spanBinaryExpr(pParse,TK_ISNOT,&yymsp[-3].minor.yy190,&yymsp[0].minor.yy190); binaryToUnaryIfNull(pParse, yymsp[0].minor.yy190.pExpr, yymsp[-3].minor.yy190.pExpr, TK_NOTNULL); } break; - case 178: /* expr ::= NOT expr */ - case 179: /* expr ::= BITNOT expr */ yytestcase(yyruleno==179); + case 181: /* expr ::= NOT expr */ + case 182: /* expr ::= BITNOT expr */ yytestcase(yyruleno==182); {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,yymsp[-1].major,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 180: /* expr ::= MINUS expr */ + case 183: /* expr ::= MINUS expr */ {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UMINUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 181: /* expr ::= PLUS expr */ + case 184: /* expr ::= PLUS expr */ {spanUnaryPrefix(&yymsp[-1].minor.yy190,pParse,TK_UPLUS,&yymsp[0].minor.yy190,&yymsp[-1].minor.yy0);/*A-overwrites-B*/} break; - case 182: /* between_op ::= BETWEEN */ - case 185: /* in_op ::= IN */ yytestcase(yyruleno==185); + case 185: /* between_op ::= BETWEEN */ + case 188: /* in_op ::= IN */ yytestcase(yyruleno==188); {yymsp[0].minor.yy194 = 0;} break; - case 184: /* expr ::= expr between_op expr AND expr */ + case 187: /* expr ::= expr between_op expr AND expr */ { ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr); pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy190.pExpr); - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy190.pExpr, 0, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy190.pExpr, 0); if( yymsp[-4].minor.yy190.pExpr ){ yymsp[-4].minor.yy190.pExpr->x.pList = pList; }else{ @@ -134551,7 +137919,7 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = yymsp[0].minor.yy190.zEnd; } break; - case 187: /* expr ::= expr in_op LP exprlist RP */ + case 190: /* expr ::= expr in_op LP exprlist RP */ { if( yymsp[-1].minor.yy148==0 ){ /* Expressions of the form @@ -134563,7 +137931,7 @@ static void yy_reduce( ** regardless of the value of expr1. */ sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy190.pExpr); - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_INTEGER, 0, 0, &sqlite3IntTokens[yymsp[-3].minor.yy194]); + yymsp[-4].minor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[yymsp[-3].minor.yy194],1); }else if( yymsp[-1].minor.yy148->nExpr==1 ){ /* Expressions of the form: ** @@ -134590,9 +137958,9 @@ static void yy_reduce( pRHS->flags &= ~EP_Collate; pRHS->flags |= EP_Generic; } - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy194 ? TK_NE : TK_EQ, yymsp[-4].minor.yy190.pExpr, pRHS, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, yymsp[-3].minor.yy194 ? TK_NE : TK_EQ, yymsp[-4].minor.yy190.pExpr, pRHS); }else{ - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0); if( yymsp[-4].minor.yy190.pExpr ){ yymsp[-4].minor.yy190.pExpr->x.pList = yymsp[-1].minor.yy148; sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy190.pExpr); @@ -134604,44 +137972,44 @@ static void yy_reduce( yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 188: /* expr ::= LP select RP */ + case 191: /* expr ::= LP select RP */ { spanSet(&yymsp[-2].minor.yy190,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ - yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0, 0); + yymsp[-2].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_SELECT, 0, 0); sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy190.pExpr, yymsp[-1].minor.yy243); } break; - case 189: /* expr ::= expr in_op LP select RP */ + case 192: /* expr ::= expr in_op LP select RP */ { - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0); sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy190.pExpr, yymsp[-1].minor.yy243); exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190); yymsp[-4].minor.yy190.zEnd = &yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n]; } break; - case 190: /* expr ::= expr in_op nm dbnm paren_exprlist */ + case 193: /* expr ::= expr in_op nm dbnm paren_exprlist */ { SrcList *pSrc = sqlite3SrcListAppend(pParse->db, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0,0); if( yymsp[0].minor.yy148 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy148); - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy190.pExpr, 0); sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy190.pExpr, pSelect); exprNot(pParse, yymsp[-3].minor.yy194, &yymsp[-4].minor.yy190); yymsp[-4].minor.yy190.zEnd = yymsp[-1].minor.yy0.z ? &yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n] : &yymsp[-2].minor.yy0.z[yymsp[-2].minor.yy0.n]; } break; - case 191: /* expr ::= EXISTS LP select RP */ + case 194: /* expr ::= EXISTS LP select RP */ { Expr *p; spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-B*/ - p = yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0, 0); + p = yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy243); } break; - case 192: /* expr ::= CASE case_operand case_exprlist case_else END */ + case 195: /* expr ::= CASE case_operand case_exprlist case_else END */ { spanSet(&yymsp[-4].minor.yy190,&yymsp[-4].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-C*/ - yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy72, 0, 0); + yymsp[-4].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy72, 0); if( yymsp[-4].minor.yy190.pExpr ){ yymsp[-4].minor.yy190.pExpr->x.pList = yymsp[-1].minor.yy72 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy148,yymsp[-1].minor.yy72) : yymsp[-2].minor.yy148; sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy190.pExpr); @@ -134651,78 +138019,80 @@ static void yy_reduce( } } break; - case 193: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ + case 196: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[-2].minor.yy190.pExpr); yymsp[-4].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy148, yymsp[0].minor.yy190.pExpr); } break; - case 194: /* case_exprlist ::= WHEN expr THEN expr */ + case 197: /* case_exprlist ::= WHEN expr THEN expr */ { yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy190.pExpr); yymsp[-3].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy148, yymsp[0].minor.yy190.pExpr); } break; - case 197: /* case_operand ::= expr */ + case 200: /* case_operand ::= expr */ {yymsp[0].minor.yy72 = yymsp[0].minor.yy190.pExpr; /*A-overwrites-X*/} break; - case 200: /* nexprlist ::= nexprlist COMMA expr */ + case 203: /* nexprlist ::= nexprlist COMMA expr */ {yymsp[-2].minor.yy148 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy148,yymsp[0].minor.yy190.pExpr);} break; - case 201: /* nexprlist ::= expr */ + case 204: /* nexprlist ::= expr */ {yymsp[0].minor.yy148 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy190.pExpr); /*A-overwrites-Y*/} break; - case 203: /* paren_exprlist ::= LP exprlist RP */ - case 208: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==208); + case 206: /* paren_exprlist ::= LP exprlist RP */ + case 211: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==211); {yymsp[-2].minor.yy148 = yymsp[-1].minor.yy148;} break; - case 204: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + case 207: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ { sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, sqlite3SrcListAppend(pParse->db,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy148, yymsp[-10].minor.yy194, &yymsp[-11].minor.yy0, yymsp[0].minor.yy72, SQLITE_SO_ASC, yymsp[-8].minor.yy194, SQLITE_IDXTYPE_APPDEF); } break; - case 205: /* uniqueflag ::= UNIQUE */ - case 246: /* raisetype ::= ABORT */ yytestcase(yyruleno==246); + case 208: /* uniqueflag ::= UNIQUE */ + case 249: /* raisetype ::= ABORT */ yytestcase(yyruleno==249); {yymsp[0].minor.yy194 = OE_Abort;} break; - case 206: /* uniqueflag ::= */ + case 209: /* uniqueflag ::= */ {yymsp[1].minor.yy194 = OE_None;} break; - case 209: /* eidlist ::= eidlist COMMA nm collate sortorder */ + case 212: /* eidlist ::= eidlist COMMA nm collate sortorder */ { yymsp[-4].minor.yy148 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy148, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194); } break; - case 210: /* eidlist ::= nm collate sortorder */ + case 213: /* eidlist ::= nm collate sortorder */ { yymsp[-2].minor.yy148 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy194, yymsp[0].minor.yy194); /*A-overwrites-Y*/ } break; - case 213: /* cmd ::= DROP INDEX ifexists fullname */ + case 216: /* cmd ::= DROP INDEX ifexists fullname */ {sqlite3DropIndex(pParse, yymsp[0].minor.yy185, yymsp[-1].minor.yy194);} break; - case 214: /* cmd ::= VACUUM */ - case 215: /* cmd ::= VACUUM nm */ yytestcase(yyruleno==215); -{sqlite3Vacuum(pParse);} + case 217: /* cmd ::= VACUUM */ +{sqlite3Vacuum(pParse,0);} + break; + case 218: /* cmd ::= VACUUM nm */ +{sqlite3Vacuum(pParse,&yymsp[0].minor.yy0);} break; - case 216: /* cmd ::= PRAGMA nm dbnm */ + case 219: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 217: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 220: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 218: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 221: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 219: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 222: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 220: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 223: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 223: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 226: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ { Token all; all.z = yymsp[-3].minor.yy0.z; @@ -134730,53 +138100,53 @@ static void yy_reduce( sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy145, &all); } break; - case 224: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 227: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy194, yymsp[-4].minor.yy332.a, yymsp[-4].minor.yy332.b, yymsp[-2].minor.yy185, yymsp[0].minor.yy72, yymsp[-10].minor.yy194, yymsp[-8].minor.yy194); yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ } break; - case 225: /* trigger_time ::= BEFORE */ + case 228: /* trigger_time ::= BEFORE */ { yymsp[0].minor.yy194 = TK_BEFORE; } break; - case 226: /* trigger_time ::= AFTER */ + case 229: /* trigger_time ::= AFTER */ { yymsp[0].minor.yy194 = TK_AFTER; } break; - case 227: /* trigger_time ::= INSTEAD OF */ + case 230: /* trigger_time ::= INSTEAD OF */ { yymsp[-1].minor.yy194 = TK_INSTEAD;} break; - case 228: /* trigger_time ::= */ + case 231: /* trigger_time ::= */ { yymsp[1].minor.yy194 = TK_BEFORE; } break; - case 229: /* trigger_event ::= DELETE|INSERT */ - case 230: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==230); + case 232: /* trigger_event ::= DELETE|INSERT */ + case 233: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==233); {yymsp[0].minor.yy332.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy332.b = 0;} break; - case 231: /* trigger_event ::= UPDATE OF idlist */ + case 234: /* trigger_event ::= UPDATE OF idlist */ {yymsp[-2].minor.yy332.a = TK_UPDATE; yymsp[-2].minor.yy332.b = yymsp[0].minor.yy254;} break; - case 232: /* when_clause ::= */ - case 251: /* key_opt ::= */ yytestcase(yyruleno==251); + case 235: /* when_clause ::= */ + case 254: /* key_opt ::= */ yytestcase(yyruleno==254); { yymsp[1].minor.yy72 = 0; } break; - case 233: /* when_clause ::= WHEN expr */ - case 252: /* key_opt ::= KEY expr */ yytestcase(yyruleno==252); + case 236: /* when_clause ::= WHEN expr */ + case 255: /* key_opt ::= KEY expr */ yytestcase(yyruleno==255); { yymsp[-1].minor.yy72 = yymsp[0].minor.yy190.pExpr; } break; - case 234: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + case 237: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { assert( yymsp[-2].minor.yy145!=0 ); yymsp[-2].minor.yy145->pLast->pNext = yymsp[-1].minor.yy145; yymsp[-2].minor.yy145->pLast = yymsp[-1].minor.yy145; } break; - case 235: /* trigger_cmd_list ::= trigger_cmd SEMI */ + case 238: /* trigger_cmd_list ::= trigger_cmd SEMI */ { assert( yymsp[-1].minor.yy145!=0 ); yymsp[-1].minor.yy145->pLast = yymsp[-1].minor.yy145; } break; - case 236: /* trnm ::= nm DOT nm */ + case 239: /* trnm ::= nm DOT nm */ { yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; sqlite3ErrorMsg(pParse, @@ -134784,195 +138154,196 @@ static void yy_reduce( "statements within triggers"); } break; - case 237: /* tridxby ::= INDEXED BY nm */ + case 240: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 238: /* tridxby ::= NOT INDEXED */ + case 241: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 239: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ + case 242: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt */ {yymsp[-6].minor.yy145 = sqlite3TriggerUpdateStep(pParse->db, &yymsp[-4].minor.yy0, yymsp[-1].minor.yy148, yymsp[0].minor.yy72, yymsp[-5].minor.yy194);} break; - case 240: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ + case 243: /* trigger_cmd ::= insert_cmd INTO trnm idlist_opt select */ {yymsp[-4].minor.yy145 = sqlite3TriggerInsertStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy254, yymsp[0].minor.yy243, yymsp[-4].minor.yy194);/*A-overwrites-R*/} break; - case 241: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ + case 244: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt */ {yymsp[-4].minor.yy145 = sqlite3TriggerDeleteStep(pParse->db, &yymsp[-2].minor.yy0, yymsp[0].minor.yy72);} break; - case 242: /* trigger_cmd ::= select */ + case 245: /* trigger_cmd ::= select */ {yymsp[0].minor.yy145 = sqlite3TriggerSelectStep(pParse->db, yymsp[0].minor.yy243); /*A-overwrites-X*/} break; - case 243: /* expr ::= RAISE LP IGNORE RP */ + case 246: /* expr ::= RAISE LP IGNORE RP */ { spanSet(&yymsp[-3].minor.yy190,&yymsp[-3].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ - yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, 0); + yymsp[-3].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0); if( yymsp[-3].minor.yy190.pExpr ){ yymsp[-3].minor.yy190.pExpr->affinity = OE_Ignore; } } break; - case 244: /* expr ::= RAISE LP raisetype COMMA nm RP */ + case 247: /* expr ::= RAISE LP raisetype COMMA nm RP */ { spanSet(&yymsp[-5].minor.yy190,&yymsp[-5].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/ - yymsp[-5].minor.yy190.pExpr = sqlite3PExpr(pParse, TK_RAISE, 0, 0, &yymsp[-1].minor.yy0); + yymsp[-5].minor.yy190.pExpr = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); if( yymsp[-5].minor.yy190.pExpr ) { yymsp[-5].minor.yy190.pExpr->affinity = (char)yymsp[-3].minor.yy194; } } break; - case 245: /* raisetype ::= ROLLBACK */ + case 248: /* raisetype ::= ROLLBACK */ {yymsp[0].minor.yy194 = OE_Rollback;} break; - case 247: /* raisetype ::= FAIL */ + case 250: /* raisetype ::= FAIL */ {yymsp[0].minor.yy194 = OE_Fail;} break; - case 248: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 251: /* cmd ::= DROP TRIGGER ifexists fullname */ { sqlite3DropTrigger(pParse,yymsp[0].minor.yy185,yymsp[-1].minor.yy194); } break; - case 249: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 252: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { sqlite3Attach(pParse, yymsp[-3].minor.yy190.pExpr, yymsp[-1].minor.yy190.pExpr, yymsp[0].minor.yy72); } break; - case 250: /* cmd ::= DETACH database_kw_opt expr */ + case 253: /* cmd ::= DETACH database_kw_opt expr */ { sqlite3Detach(pParse, yymsp[0].minor.yy190.pExpr); } break; - case 253: /* cmd ::= REINDEX */ + case 256: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 254: /* cmd ::= REINDEX nm dbnm */ + case 257: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 255: /* cmd ::= ANALYZE */ + case 258: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 256: /* cmd ::= ANALYZE nm dbnm */ + case 259: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 257: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 260: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy185,&yymsp[0].minor.yy0); } break; - case 258: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + case 261: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ { yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); } break; - case 259: /* add_column_fullname ::= fullname */ + case 262: /* add_column_fullname ::= fullname */ { disableLookaside(pParse); sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy185); } break; - case 260: /* cmd ::= create_vtab */ + case 263: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 261: /* cmd ::= create_vtab LP vtabarglist RP */ + case 264: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 262: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 265: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy194); } break; - case 263: /* vtabarg ::= */ + case 266: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 264: /* vtabargtoken ::= ANY */ - case 265: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==265); - case 266: /* lp ::= LP */ yytestcase(yyruleno==266); + case 267: /* vtabargtoken ::= ANY */ + case 268: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==268); + case 269: /* lp ::= LP */ yytestcase(yyruleno==269); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 267: /* with ::= */ + case 270: /* with ::= */ {yymsp[1].minor.yy285 = 0;} break; - case 268: /* with ::= WITH wqlist */ + case 271: /* with ::= WITH wqlist */ { yymsp[-1].minor.yy285 = yymsp[0].minor.yy285; } break; - case 269: /* with ::= WITH RECURSIVE wqlist */ + case 272: /* with ::= WITH RECURSIVE wqlist */ { yymsp[-2].minor.yy285 = yymsp[0].minor.yy285; } break; - case 270: /* wqlist ::= nm eidlist_opt AS LP select RP */ + case 273: /* wqlist ::= nm eidlist_opt AS LP select RP */ { yymsp[-5].minor.yy285 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243); /*A-overwrites-X*/ } break; - case 271: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ + case 274: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ { yymsp[-7].minor.yy285 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy285, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy148, yymsp[-1].minor.yy243); } break; default: - /* (272) input ::= cmdlist */ yytestcase(yyruleno==272); - /* (273) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==273); - /* (274) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=274); - /* (275) ecmd ::= SEMI */ yytestcase(yyruleno==275); - /* (276) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==276); - /* (277) explain ::= */ yytestcase(yyruleno==277); - /* (278) trans_opt ::= */ yytestcase(yyruleno==278); - /* (279) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==279); - /* (280) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==280); - /* (281) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==281); - /* (282) savepoint_opt ::= */ yytestcase(yyruleno==282); - /* (283) cmd ::= create_table create_table_args */ yytestcase(yyruleno==283); - /* (284) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==284); - /* (285) columnlist ::= columnname carglist */ yytestcase(yyruleno==285); - /* (286) nm ::= ID|INDEXED */ yytestcase(yyruleno==286); - /* (287) nm ::= STRING */ yytestcase(yyruleno==287); - /* (288) nm ::= JOIN_KW */ yytestcase(yyruleno==288); - /* (289) typetoken ::= typename */ yytestcase(yyruleno==289); - /* (290) typename ::= ID|STRING */ yytestcase(yyruleno==290); - /* (291) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=291); - /* (292) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=292); - /* (293) carglist ::= carglist ccons */ yytestcase(yyruleno==293); - /* (294) carglist ::= */ yytestcase(yyruleno==294); - /* (295) ccons ::= NULL onconf */ yytestcase(yyruleno==295); - /* (296) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==296); - /* (297) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==297); - /* (298) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=298); - /* (299) tconscomma ::= */ yytestcase(yyruleno==299); - /* (300) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=300); - /* (301) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=301); - /* (302) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=302); - /* (303) oneselect ::= values */ yytestcase(yyruleno==303); - /* (304) sclp ::= selcollist COMMA */ yytestcase(yyruleno==304); - /* (305) as ::= ID|STRING */ yytestcase(yyruleno==305); - /* (306) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=306); - /* (307) exprlist ::= nexprlist */ yytestcase(yyruleno==307); - /* (308) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=308); - /* (309) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=309); - /* (310) nmnum ::= ON */ yytestcase(yyruleno==310); - /* (311) nmnum ::= DELETE */ yytestcase(yyruleno==311); - /* (312) nmnum ::= DEFAULT */ yytestcase(yyruleno==312); - /* (313) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==313); - /* (314) foreach_clause ::= */ yytestcase(yyruleno==314); - /* (315) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==315); - /* (316) trnm ::= nm */ yytestcase(yyruleno==316); - /* (317) tridxby ::= */ yytestcase(yyruleno==317); - /* (318) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==318); - /* (319) database_kw_opt ::= */ yytestcase(yyruleno==319); - /* (320) kwcolumn_opt ::= */ yytestcase(yyruleno==320); - /* (321) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==321); - /* (322) vtabarglist ::= vtabarg */ yytestcase(yyruleno==322); - /* (323) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==323); - /* (324) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==324); - /* (325) anylist ::= */ yytestcase(yyruleno==325); - /* (326) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==326); - /* (327) anylist ::= anylist ANY */ yytestcase(yyruleno==327); + /* (275) input ::= cmdlist */ yytestcase(yyruleno==275); + /* (276) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==276); + /* (277) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=277); + /* (278) ecmd ::= SEMI */ yytestcase(yyruleno==278); + /* (279) ecmd ::= explain cmdx SEMI */ yytestcase(yyruleno==279); + /* (280) explain ::= */ yytestcase(yyruleno==280); + /* (281) trans_opt ::= */ yytestcase(yyruleno==281); + /* (282) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==282); + /* (283) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==283); + /* (284) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==284); + /* (285) savepoint_opt ::= */ yytestcase(yyruleno==285); + /* (286) cmd ::= create_table create_table_args */ yytestcase(yyruleno==286); + /* (287) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==287); + /* (288) columnlist ::= columnname carglist */ yytestcase(yyruleno==288); + /* (289) nm ::= ID|INDEXED */ yytestcase(yyruleno==289); + /* (290) nm ::= STRING */ yytestcase(yyruleno==290); + /* (291) nm ::= JOIN_KW */ yytestcase(yyruleno==291); + /* (292) typetoken ::= typename */ yytestcase(yyruleno==292); + /* (293) typename ::= ID|STRING */ yytestcase(yyruleno==293); + /* (294) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=294); + /* (295) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=295); + /* (296) carglist ::= carglist ccons */ yytestcase(yyruleno==296); + /* (297) carglist ::= */ yytestcase(yyruleno==297); + /* (298) ccons ::= NULL onconf */ yytestcase(yyruleno==298); + /* (299) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==299); + /* (300) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==300); + /* (301) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=301); + /* (302) tconscomma ::= */ yytestcase(yyruleno==302); + /* (303) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=303); + /* (304) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=304); + /* (305) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=305); + /* (306) oneselect ::= values */ yytestcase(yyruleno==306); + /* (307) sclp ::= selcollist COMMA */ yytestcase(yyruleno==307); + /* (308) as ::= ID|STRING */ yytestcase(yyruleno==308); + /* (309) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=309); + /* (310) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==310); + /* (311) exprlist ::= nexprlist */ yytestcase(yyruleno==311); + /* (312) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=312); + /* (313) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=313); + /* (314) nmnum ::= ON */ yytestcase(yyruleno==314); + /* (315) nmnum ::= DELETE */ yytestcase(yyruleno==315); + /* (316) nmnum ::= DEFAULT */ yytestcase(yyruleno==316); + /* (317) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==317); + /* (318) foreach_clause ::= */ yytestcase(yyruleno==318); + /* (319) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==319); + /* (320) trnm ::= nm */ yytestcase(yyruleno==320); + /* (321) tridxby ::= */ yytestcase(yyruleno==321); + /* (322) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==322); + /* (323) database_kw_opt ::= */ yytestcase(yyruleno==323); + /* (324) kwcolumn_opt ::= */ yytestcase(yyruleno==324); + /* (325) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==325); + /* (326) vtabarglist ::= vtabarg */ yytestcase(yyruleno==326); + /* (327) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==327); + /* (328) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==328); + /* (329) anylist ::= */ yytestcase(yyruleno==329); + /* (330) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==330); + /* (331) anylist ::= anylist ANY */ yytestcase(yyruleno==331); break; /********** End reduce actions ************************************************/ }; @@ -135163,7 +138534,7 @@ SQLITE_PRIVATE void sqlite3Parser( yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( yypParser->yytos >= &yypParser->yystack + while( yypParser->yytos >= yypParser->yystack && yymx != YYERRORSYMBOL && (yyact = yy_find_reduce_action( yypParser->yytos->stateno, @@ -135321,13 +138692,13 @@ static const unsigned char aiClass[] = { /* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, /* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, /* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 12, 17, 20, 10, +/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10, /* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27, -/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 7, +/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6, /* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8, /* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, /* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* 9x */ 25, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, /* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27, /* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, /* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, @@ -136004,12 +139375,15 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ */ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ int nErr = 0; /* Number of errors encountered */ - int i; /* Loop counter */ void *pEngine; /* The LEMON-generated LALR(1) parser */ + int n = 0; /* Length of the next token token */ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ int mxSqlLen; /* Max length of an SQL string */ +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + unsigned char zSpace[sizeof(yyParser)]; /* Space for parser engine object */ +#endif assert( zSql!=0 ); mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; @@ -136018,27 +139392,41 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr } pParse->rc = SQLITE_OK; pParse->zTail = zSql; - i = 0; assert( pzErrMsg!=0 ); /* sqlite3ParserTrace(stdout, "parser: "); */ +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + pEngine = zSpace; + sqlite3ParserInit(pEngine); +#else pEngine = sqlite3ParserAlloc(sqlite3Malloc); if( pEngine==0 ){ sqlite3OomFault(db); return SQLITE_NOMEM_BKPT; } +#endif assert( pParse->pNewTable==0 ); assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); - assert( pParse->nzVar==0 ); - assert( pParse->azVar==0 ); - while( zSql[i]!=0 ){ - assert( i>=0 ); - pParse->sLastToken.z = &zSql[i]; - pParse->sLastToken.n = sqlite3GetToken((unsigned char*)&zSql[i],&tokenType); - i += pParse->sLastToken.n; - if( i>mxSqlLen ){ - pParse->rc = SQLITE_TOOBIG; - break; + assert( pParse->pVList==0 ); + while( 1 ){ + if( zSql[0]!=0 ){ + n = sqlite3GetToken((u8*)zSql, &tokenType); + mxSqlLen -= n; + if( mxSqlLen<0 ){ + pParse->rc = SQLITE_TOOBIG; + break; + } + }else{ + /* Upon reaching the end of input, call the parser two more times + ** with tokens TK_SEMI and 0, in that order. */ + if( lastTokenParsed==TK_SEMI ){ + tokenType = 0; + }else if( lastTokenParsed==0 ){ + break; + }else{ + tokenType = TK_SEMI; + } + zSql -= n; } if( tokenType>=TK_SPACE ){ assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); @@ -136047,27 +139435,21 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr break; } if( tokenType==TK_ILLEGAL ){ - sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", - &pParse->sLastToken); + sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); break; } + zSql += n; }else{ + pParse->sLastToken.z = zSql; + pParse->sLastToken.n = n; sqlite3Parser(pEngine, tokenType, pParse->sLastToken, pParse); lastTokenParsed = tokenType; + zSql += n; if( pParse->rc!=SQLITE_OK || db->mallocFailed ) break; } } assert( nErr==0 ); - pParse->zTail = &zSql[i]; - if( pParse->rc==SQLITE_OK && db->mallocFailed==0 ){ - assert( zSql[i]==0 ); - if( lastTokenParsed!=TK_SEMI ){ - sqlite3Parser(pEngine, TK_SEMI, pParse->sLastToken, pParse); - } - if( pParse->rc==SQLITE_OK && db->mallocFailed==0 ){ - sqlite3Parser(pEngine, 0, pParse->sLastToken, pParse); - } - } + pParse->zTail = zSql; #ifdef YYTRACKMAXSTACKDEPTH sqlite3_mutex_enter(sqlite3MallocMutex()); sqlite3StatusHighwater(SQLITE_STATUS_PARSER_STACK, @@ -136075,7 +139457,11 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr ); sqlite3_mutex_leave(sqlite3MallocMutex()); #endif /* YYDEBUG */ +#ifdef sqlite3Parser_ENGINEALWAYSONSTACK + sqlite3ParserFinalize(pEngine); +#else sqlite3ParserFree(pEngine, sqlite3_free); +#endif if( db->mallocFailed ){ pParse->rc = SQLITE_NOMEM_BKPT; } @@ -136114,8 +139500,7 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree); sqlite3DeleteTrigger(db, pParse->pNewTrigger); - for(i=pParse->nzVar-1; i>=0; i--) sqlite3DbFree(db, pParse->azVar[i]); - sqlite3DbFree(db, pParse->azVar); + sqlite3DbFree(db, pParse->pVList); while( pParse->pAinc ){ AutoincInfo *p = pParse->pAinc; pParse->pAinc = p->pNext; @@ -137307,6 +140692,11 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ int rc; va_start(ap, op); switch( op ){ + case SQLITE_DBCONFIG_MAINDBNAME: { + db->aDb[0].zDbSName = va_arg(ap,char*); + rc = SQLITE_OK; + break; + } case SQLITE_DBCONFIG_LOOKASIDE: { void *pBuf = va_arg(ap, void*); /* IMP: R-26835-10964 */ int sz = va_arg(ap, int); /* IMP: R-47871-25994 */ @@ -137323,6 +140713,7 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, + { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -137433,6 +140824,21 @@ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ return db->lastRowid; } +/* +** Set the value returned by the sqlite3_last_insert_rowid() API function. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return; + } +#endif + sqlite3_mutex_enter(db->mutex); + db->lastRowid = iRowid; + sqlite3_mutex_leave(db->mutex); +} + /* ** Return the number of changes in the most recent call to sqlite3_exec(). */ @@ -138080,7 +141486,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ SQLITE_API void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) ){ + if( !sqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){ (void)SQLITE_MISUSE_BKPT; return; } @@ -138619,6 +142025,13 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( sqlite3Error(db, rc); } rc = sqlite3ApiExit(db, rc); + + /* If there are no active statements, clear the interrupt flag at this + ** point. */ + if( db->nVdbeActive==0 ){ + db->u1.isInterrupted = 0; + } + sqlite3_mutex_leave(db->mutex); return rc; #endif @@ -139121,6 +142534,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( assert( octet>=0 && octet<256 ); if( octet==0 ){ +#ifndef SQLITE_ENABLE_URI_00_ERROR /* This branch is taken when "%00" appears within the URI. In this ** case we ignore all text in the remainder of the path, name or ** value currently being parsed. So ignore the current character @@ -139133,6 +142547,12 @@ SQLITE_PRIVATE int sqlite3ParseUri( iIn++; } continue; +#else + /* If ENABLE_URI_00_ERROR is defined, "%00" in a URI is an error. */ + *pzErrMsg = sqlite3_mprintf("unexpected %%00 in uri"); + rc = SQLITE_ERROR; + goto parse_uri_out; +#endif } c = octet; }else if( eState==1 && (c=='&' || c=='=') ){ @@ -139237,7 +142657,9 @@ SQLITE_PRIVATE int sqlite3ParseUri( }else{ zFile = sqlite3_malloc64(nUri+2); if( !zFile ) return SQLITE_NOMEM_BKPT; - memcpy(zFile, zUri, nUri); + if( nUri ){ + memcpy(zFile, zUri, nUri); + } zFile[nUri] = '\0'; zFile[nUri+1] = '\0'; flags &= ~SQLITE_OPEN_URI; @@ -139452,9 +142874,9 @@ static int openDatabase( /* The default safety_level for the main database is FULL; for the temp ** database it is OFF. This matches the pager layer defaults. */ - db->aDb[0].zName = "main"; + db->aDb[0].zDbSName = "main"; db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; - db->aDb[1].zName = "temp"; + db->aDb[1].zDbSName = "temp"; db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; db->magic = SQLITE_MAGIC_OPEN; @@ -139468,11 +142890,20 @@ static int openDatabase( */ sqlite3Error(db, SQLITE_OK); sqlite3RegisterPerConnectionBuiltinFunctions(db); + rc = sqlite3_errcode(db); + +#ifdef SQLITE_ENABLE_FTS5 + /* Register any built-in FTS5 module before loading the automatic + ** extensions. This allows automatic extensions to register FTS5 + ** tokenizers and auxiliary functions. */ + if( !db->mallocFailed && rc==SQLITE_OK ){ + rc = sqlite3Fts5Init(db); + } +#endif /* Load automatic extensions - extensions that have been registered ** using the sqlite3_automatic_extension() API. */ - rc = sqlite3_errcode(db); if( rc==SQLITE_OK ){ sqlite3AutoLoadExtensions(db); rc = sqlite3_errcode(db); @@ -139501,12 +142932,6 @@ static int openDatabase( } #endif -#ifdef SQLITE_ENABLE_FTS5 - if( !db->mallocFailed && rc==SQLITE_OK ){ - rc = sqlite3Fts5Init(db); - } -#endif - #ifdef SQLITE_ENABLE_ICU if( !db->mallocFailed && rc==SQLITE_OK ){ rc = sqlite3IcuInit(db); @@ -140028,7 +143453,7 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo */ SQLITE_API int sqlite3_test_control(int op, ...){ int rc = 0; -#ifdef SQLITE_OMIT_BUILTIN_TEST +#ifdef SQLITE_UNTESTABLE UNUSED_PARAMETER(op); #else va_list ap; @@ -140294,6 +143719,15 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } + /* Set the threshold at which OP_Once counters reset back to zero. + ** By default this is 0x7ffffffe (over 2 billion), but that value is + ** too big to test in a reasonable amount of time, so this control is + ** provided to set a small and easily reachable reset value. + */ + case SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD: { + sqlite3GlobalConfig.iOnceResetThreshold = va_arg(ap, int); + break; + } /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); ** @@ -140356,7 +143790,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ } } va_end(ap); -#endif /* SQLITE_OMIT_BUILTIN_TEST */ +#endif /* SQLITE_UNTESTABLE */ return rc; } @@ -140412,15 +143846,8 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64( ** Return the Btree pointer identified by zDbName. Return NULL if not found. */ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ - int i; - for(i=0; inDb; i++){ - if( db->aDb[i].pBt - && (zDbName==0 || sqlite3StrICmp(zDbName, db->aDb[i].zName)==0) - ){ - return db->aDb[i].pBt; - } - } - return 0; + int iDb = zDbName ? sqlite3FindDbName(db, zDbName) : 0; + return iDb<0 ? 0 : db->aDb[iDb].pBt; } /* @@ -140467,7 +143894,6 @@ SQLITE_API int sqlite3_snapshot_get( ){ int rc = SQLITE_ERROR; #ifndef SQLITE_OMIT_WAL - int iDb; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ @@ -140476,13 +143902,15 @@ SQLITE_API int sqlite3_snapshot_get( #endif sqlite3_mutex_enter(db->mutex); - iDb = sqlite3FindDbName(db, zDb); - if( iDb==0 || iDb>1 ){ - Btree *pBt = db->aDb[iDb].pBt; - if( 0==sqlite3BtreeIsInTrans(pBt) ){ - rc = sqlite3BtreeBeginTrans(pBt, 0); - if( rc==SQLITE_OK ){ - rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + if( db->autoCommit==0 ){ + int iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( 0==sqlite3BtreeIsInTrans(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); + } } } } @@ -140529,6 +143957,38 @@ SQLITE_API int sqlite3_snapshot_open( return rc; } +/* +** Recover as many snapshots as possible from the wal file associated with +** schema zDb of database db. +*/ +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ + int rc = SQLITE_ERROR; + int iDb; +#ifndef SQLITE_OMIT_WAL + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } +#endif + + sqlite3_mutex_enter(db->mutex); + iDb = sqlite3FindDbName(db, zDb); + if( iDb==0 || iDb>1 ){ + Btree *pBt = db->aDb[iDb].pBt; + if( 0==sqlite3BtreeIsInReadTrans(pBt) ){ + rc = sqlite3BtreeBeginTrans(pBt, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt)); + sqlite3BtreeCommit(pBt); + } + } + } + sqlite3_mutex_leave(db->mutex); +#endif /* SQLITE_OMIT_WAL */ + return rc; +} + /* ** Free a snapshot handle obtained from sqlite3_snapshot_get(). */ @@ -141679,6 +145139,7 @@ struct Fts3Table { ** statements is run and reset within a single virtual table API call. */ sqlite3_stmt *aStmt[40]; + sqlite3_stmt *pSeekStmt; /* Cache for fts3CursorSeekStmt() */ char *zReadExprlist; char *zWriteExprlist; @@ -141748,6 +145209,7 @@ struct Fts3Cursor { i16 eSearch; /* Search strategy (see below) */ u8 isEof; /* True if at End Of Results */ u8 isRequireSeek; /* True if must seek pStmt to %_content row */ + u8 bSeekStmt; /* True if pStmt is a seek */ sqlite3_stmt *pStmt; /* Prepared statement in use by the cursor */ Fts3Expr *pExpr; /* Parsed MATCH query string */ int iLangid; /* Language being queried for */ @@ -142127,8 +145589,9 @@ SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){ ** Return the number of bytes read, or 0 on error. ** The value is stored in *v. */ -SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *p, sqlite_int64 *v){ - const char *pStart = p; +SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){ + const unsigned char *p = (const unsigned char*)pBuf; + const unsigned char *pStart = p; u32 a; u64 b; int shift; @@ -142270,6 +145733,7 @@ static int fts3DisconnectMethod(sqlite3_vtab *pVtab){ assert( p->pSegments==0 ); /* Free any prepared statements held */ + sqlite3_finalize(p->pSeekStmt); for(i=0; iaStmt); i++){ sqlite3_finalize(p->aStmt[i]); } @@ -143141,9 +146605,9 @@ static int fts3InitVtab( p->pTokenizer = pTokenizer; p->nMaxPendingData = FTS3_MAX_PENDING_DATA; p->bHasDocsize = (isFts4 && bNoDocsize==0); - p->bHasStat = isFts4; - p->bFts4 = isFts4; - p->bDescIdx = bDescIdx; + p->bHasStat = (u8)isFts4; + p->bFts4 = (u8)isFts4; + p->bDescIdx = (u8)bDescIdx; p->nAutoincrmerge = 0xff; /* 0xff means setting unknown */ p->zContentTbl = zContent; p->zLanguageid = zLanguageid; @@ -143174,7 +146638,9 @@ static int fts3InitVtab( char *z; int n = 0; z = (char *)sqlite3Fts3NextToken(aCol[iCol], &n); - memcpy(zCsr, z, n); + if( n>0 ){ + memcpy(zCsr, z, n); + } zCsr[n] = '\0'; sqlite3Fts3Dequote(zCsr); p->azColumn[iCol] = zCsr; @@ -143458,6 +146924,26 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ return SQLITE_OK; } +/* +** Finalize the statement handle at pCsr->pStmt. +** +** Or, if that statement handle is one created by fts3CursorSeekStmt(), +** and the Fts3Table.pSeekStmt slot is currently NULL, save the statement +** pointer there instead of finalizing it. +*/ +static void fts3CursorFinalizeStmt(Fts3Cursor *pCsr){ + if( pCsr->bSeekStmt ){ + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; + if( p->pSeekStmt==0 ){ + p->pSeekStmt = pCsr->pStmt; + sqlite3_reset(pCsr->pStmt); + pCsr->pStmt = 0; + } + pCsr->bSeekStmt = 0; + } + sqlite3_finalize(pCsr->pStmt); +} + /* ** Close the cursor. For additional information see the documentation ** on the xClose method of the virtual table interface. @@ -143465,7 +146951,7 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; assert( ((Fts3Table *)pCsr->base.pVtab)->pSegments==0 ); - sqlite3_finalize(pCsr->pStmt); + fts3CursorFinalizeStmt(pCsr); sqlite3Fts3ExprFree(pCsr->pExpr); sqlite3Fts3FreeDeferredTokens(pCsr); sqlite3_free(pCsr->aDoclist); @@ -143483,20 +146969,23 @@ static int fts3CloseMethod(sqlite3_vtab_cursor *pCursor){ ** ** (or the equivalent for a content=xxx table) and set pCsr->pStmt to ** it. If an error occurs, return an SQLite error code. -** -** Otherwise, set *ppStmt to point to pCsr->pStmt and return SQLITE_OK. */ -static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){ +static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ int rc = SQLITE_OK; if( pCsr->pStmt==0 ){ Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; char *zSql; - zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); - if( !zSql ) return SQLITE_NOMEM; - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); - sqlite3_free(zSql); + if( p->pSeekStmt ){ + pCsr->pStmt = p->pSeekStmt; + p->pSeekStmt = 0; + }else{ + zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); + if( !zSql ) return SQLITE_NOMEM; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pCsr->pStmt, 0); + sqlite3_free(zSql); + } + if( rc==SQLITE_OK ) pCsr->bSeekStmt = 1; } - *ppStmt = pCsr->pStmt; return rc; } @@ -143508,9 +146997,7 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr, sqlite3_stmt **ppStmt){ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ int rc = SQLITE_OK; if( pCsr->isRequireSeek ){ - sqlite3_stmt *pStmt = 0; - - rc = fts3CursorSeekStmt(pCsr, &pStmt); + rc = fts3CursorSeekStmt(pCsr); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); pCsr->isRequireSeek = 0; @@ -144968,7 +148455,7 @@ static int fts3FilterMethod( assert( iIdx==nVal ); /* In case the cursor has been used before, clear it now. */ - sqlite3_finalize(pCsr->pStmt); + fts3CursorFinalizeStmt(pCsr); sqlite3_free(pCsr->aDoclist); sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); sqlite3Fts3ExprFree(pCsr->pExpr); @@ -145036,7 +148523,7 @@ static int fts3FilterMethod( rc = SQLITE_NOMEM; } }else if( eSearch==FTS3_DOCID_SEARCH ){ - rc = fts3CursorSeekStmt(pCsr, &pCsr->pStmt); + rc = fts3CursorSeekStmt(pCsr); if( rc==SQLITE_OK ){ rc = sqlite3_bind_value(pCsr->pStmt, 1, pCons); } @@ -145164,8 +148651,10 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){ const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */ Fts3Table *p = (Fts3Table*)pVtab; - int rc = sqlite3Fts3PendingTermsFlush(p); + int rc; + i64 iLastRowid = sqlite3_last_insert_rowid(p->db); + rc = sqlite3Fts3PendingTermsFlush(p); if( rc==SQLITE_OK && p->nLeafAdd>(nMinMerge/16) && p->nAutoincrmerge && p->nAutoincrmerge!=0xff @@ -145180,6 +148669,7 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){ if( A>(int)nMinMerge ) rc = sqlite3Fts3Incrmerge(p, A, p->nAutoincrmerge); } sqlite3Fts3SegmentsClose(p); + sqlite3_set_last_insert_rowid(p->db, iLastRowid); return rc; } @@ -145200,7 +148690,7 @@ static int fts3SetHasStat(Fts3Table *p){ if( rc==SQLITE_OK ){ int bHasStat = (sqlite3_step(pStmt)==SQLITE_ROW); rc = sqlite3_finalize(pStmt); - if( rc==SQLITE_OK ) p->bHasStat = bHasStat; + if( rc==SQLITE_OK ) p->bHasStat = (u8)bHasStat; } sqlite3_free(zSql); }else{ @@ -156790,11 +160280,14 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ ** Convert the text beginning at *pz into an integer and return ** its value. Advance *pz to point to the first character past ** the integer. +** +** This function used for parameters to merge= and incrmerge= +** commands. */ static int fts3Getint(const char **pz){ const char *z = *pz; int i = 0; - while( (*z)>='0' && (*z)<='9' ) i = 10*i + *(z++) - '0'; + while( (*z)>='0' && (*z)<='9' && i<214748363 ) i = 10*i + *(z++) - '0'; *pz = z; return i; } @@ -159360,16 +162853,16 @@ static int unicodeAddExceptions( ){ const unsigned char *z = (const unsigned char *)zIn; const unsigned char *zTerm = &z[nIn]; - int iCode; + unsigned int iCode; int nEntry = 0; assert( bAlnum==0 || bAlnum==1 ); while( zi; j--) aNew[j] = aNew[j-1]; - aNew[i] = iCode; + aNew[i] = (int)iCode; nNew++; } } @@ -159542,7 +163035,7 @@ static int unicodeNext( ){ unicode_cursor *pCsr = (unicode_cursor *)pC; unicode_tokenizer *p = ((unicode_tokenizer *)pCsr->base.pTokenizer); - int iCode = 0; + unsigned int iCode = 0; char *zOut; const unsigned char *z = &pCsr->aInput[pCsr->iOff]; const unsigned char *zStart = z; @@ -159554,7 +163047,7 @@ static int unicodeNext( ** the input. */ while( z=zTerm ) return SQLITE_DONE; @@ -159574,7 +163067,7 @@ static int unicodeNext( /* Write the folded case of the last character read to the output */ zEnd = z; - iOut = sqlite3FtsUnicodeFold(iCode, p->bRemoveDiacritic); + iOut = sqlite3FtsUnicodeFold((int)iCode, p->bRemoveDiacritic); if( iOut ){ WRITE_UTF8(zOut, iOut); } @@ -159582,8 +163075,8 @@ static int unicodeNext( /* If the cursor is not at EOF, read the next character */ if( z>=zTerm ) break; READ_UTF8(z, zTerm, iCode); - }while( unicodeIsAlnum(p, iCode) - || sqlite3FtsUnicodeIsdiacritic(iCode) + }while( unicodeIsAlnum(p, (int)iCode) + || sqlite3FtsUnicodeIsdiacritic((int)iCode) ); /* Set the output variables and return. */ @@ -159747,9 +163240,9 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){ 0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001, }; - if( c<128 ){ - return ( (aAscii[c >> 5] & (1 << (c & 0x001F)))==0 ); - }else if( c<(1<<22) ){ + if( (unsigned int)c<128 ){ + return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 ); + }else if( (unsigned int)c<(1<<22) ){ unsigned int key = (((unsigned int)c)<<10) | 0x000003FF; int iRes = 0; int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; @@ -159942,16 +163435,17 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){ int ret = c; - assert( c>=0 ); assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 ); if( c<128 ){ if( c>='A' && c<='Z' ) ret = c + ('a' - 'A'); }else if( c<65536 ){ + const struct TableEntry *p; int iHi = sizeof(aEntry)/sizeof(aEntry[0]) - 1; int iLo = 0; int iRes = -1; + assert( c>aEntry[0].iCode ); while( iHi>=iLo ){ int iTest = (iHi + iLo) / 2; int cmp = (c - aEntry[iTest].iCode); @@ -159962,14 +163456,12 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){ iHi = iTest-1; } } - assert( iRes<0 || c>=aEntry[iRes].iCode ); - if( iRes>=0 ){ - const struct TableEntry *p = &aEntry[iRes]; - if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){ - ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF; - assert( ret>0 ); - } + assert( iRes>=0 && c>=aEntry[iRes].iCode ); + p = &aEntry[iRes]; + if( c<(p->iCode + p->nRange) && 0==(0x01 & p->flags & (p->iCode ^ c)) ){ + ret = (c + (aiOff[p->flags>>1])) & 0x0000FFFF; + assert( ret>0 ); } if( bRemoveDiacritic ) ret = remove_diacritic(ret); @@ -160056,6 +163548,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int bRemoveDiacritic){ #ifndef SQLITE_AMALGAMATION #include "sqlite3rtree.h" typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; @@ -160104,13 +163597,16 @@ struct Rtree { sqlite3 *db; /* Host database connection */ int iNodeSize; /* Size in bytes of each node in the node table */ u8 nDim; /* Number of dimensions */ + u8 nDim2; /* Twice the number of dimensions */ u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */ u8 nBytesPerCell; /* Bytes consumed per cell */ + u8 inWrTrans; /* True if inside write transaction */ int iDepth; /* Current depth of the r-tree structure */ char *zDb; /* Name of database containing r-tree table */ char *zName; /* Name of r-tree table */ - int nBusy; /* Current number of users of this structure */ + u32 nBusy; /* Current number of users of this structure */ i64 nRowEst; /* Estimated number of rows in this table */ + u32 nCursor; /* Number of open cursors */ /* List of nodes removed during a CondenseTree operation. List is ** linked together via the pointer normally used for hash chains - @@ -160120,8 +163616,10 @@ struct Rtree { RtreeNode *pDeleted; int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */ + /* Blob I/O on xxx_node */ + sqlite3_blob *pNodeBlob; + /* Statements to read/write/delete a record from xxx_node */ - sqlite3_stmt *pReadNode; sqlite3_stmt *pWriteNode; sqlite3_stmt *pDeleteNode; @@ -160350,6 +163848,58 @@ struct RtreeMatchArg { # define MIN(x,y) ((x) > (y) ? (y) : (x)) #endif +/* What version of GCC is being used. 0 means GCC is not being used . +** Note that the GCC_VERSION macro will also be set correctly when using +** clang, since clang works hard to be gcc compatible. So the gcc +** optimizations will also work when compiling with clang. +*/ +#ifndef GCC_VERSION +#if defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) +# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__) +#else +# define GCC_VERSION 0 +#endif +#endif + +/* The testcase() macro should already be defined in the amalgamation. If +** it is not, make it a no-op. +*/ +#ifndef SQLITE_AMALGAMATION +# define testcase(X) +#endif + +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined +** at run-time. +*/ +#ifndef SQLITE_BYTEORDER +#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__arm__) +# define SQLITE_BYTEORDER 1234 +#elif defined(sparc) || defined(__ppc__) +# define SQLITE_BYTEORDER 4321 +#else +# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ +#endif +#endif + + +/* What version of MSVC is being used. 0 means MSVC is not being used */ +#ifndef MSVC_VERSION +#if defined(_MSC_VER) && !defined(SQLITE_DISABLE_INTRINSIC) +# define MSVC_VERSION _MSC_VER +#else +# define MSVC_VERSION 0 +#endif +#endif + /* ** Functions to deserialize a 16 bit integer, 32 bit real number and ** 64 bit integer. The deserialized value is returned. @@ -160358,24 +163908,47 @@ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ + assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */ +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + pCoord->u = _byteswap_ulong(*(u32*)p); +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + pCoord->u = __builtin_bswap32(*(u32*)p); +#elif SQLITE_BYTEORDER==4321 + pCoord->u = *(u32*)p; +#else pCoord->u = ( (((u32)p[0]) << 24) + (((u32)p[1]) << 16) + (((u32)p[2]) << 8) + (((u32)p[3]) << 0) ); +#endif } static i64 readInt64(u8 *p){ - return ( - (((i64)p[0]) << 56) + - (((i64)p[1]) << 48) + - (((i64)p[2]) << 40) + - (((i64)p[3]) << 32) + - (((i64)p[4]) << 24) + - (((i64)p[5]) << 16) + - (((i64)p[6]) << 8) + - (((i64)p[7]) << 0) +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + u64 x; + memcpy(&x, p, 8); + return (i64)_byteswap_uint64(x); +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + u64 x; + memcpy(&x, p, 8); + return (i64)__builtin_bswap64(x); +#elif SQLITE_BYTEORDER==4321 + i64 x; + memcpy(&x, p, 8); + return x; +#else + return (i64)( + (((u64)p[0]) << 56) + + (((u64)p[1]) << 48) + + (((u64)p[2]) << 40) + + (((u64)p[3]) << 32) + + (((u64)p[4]) << 24) + + (((u64)p[5]) << 16) + + (((u64)p[6]) << 8) + + (((u64)p[7]) << 0) ); +#endif } /* @@ -160383,23 +163956,43 @@ static i64 readInt64(u8 *p){ ** 64 bit integer. The value returned is the number of bytes written ** to the argument buffer (always 2, 4 and 8 respectively). */ -static int writeInt16(u8 *p, int i){ +static void writeInt16(u8 *p, int i){ p[0] = (i>> 8)&0xFF; p[1] = (i>> 0)&0xFF; - return 2; } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; + assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */ assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); +#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + i = __builtin_bswap32(pCoord->u); + memcpy(p, &i, 4); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + i = _byteswap_ulong(pCoord->u); + memcpy(p, &i, 4); +#elif SQLITE_BYTEORDER==4321 + i = pCoord->u; + memcpy(p, &i, 4); +#else i = pCoord->u; p[0] = (i>>24)&0xFF; p[1] = (i>>16)&0xFF; p[2] = (i>> 8)&0xFF; p[3] = (i>> 0)&0xFF; +#endif return 4; } static int writeInt64(u8 *p, i64 i){ +#if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 + i = (i64)__builtin_bswap64((u64)i); + memcpy(p, &i, 8); +#elif SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 + i = (i64)_byteswap_uint64((u64)i); + memcpy(p, &i, 8); +#elif SQLITE_BYTEORDER==4321 + memcpy(p, &i, 8); +#else p[0] = (i>>56)&0xFF; p[1] = (i>>48)&0xFF; p[2] = (i>>40)&0xFF; @@ -160408,6 +164001,7 @@ static int writeInt64(u8 *p, i64 i){ p[5] = (i>>16)&0xFF; p[6] = (i>> 8)&0xFF; p[7] = (i>> 0)&0xFF; +#endif return 8; } @@ -160490,6 +164084,17 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ return pNode; } +/* +** Clear the Rtree.pNodeBlob object +*/ +static void nodeBlobReset(Rtree *pRtree){ + if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); + } +} + /* ** Obtain a reference to an r-tree node. */ @@ -160499,9 +164104,8 @@ static int nodeAcquire( RtreeNode *pParent, /* Either the parent node or NULL */ RtreeNode **ppNode /* OUT: Acquired node */ ){ - int rc; - int rc2 = SQLITE_OK; - RtreeNode *pNode; + int rc = SQLITE_OK; + RtreeNode *pNode = 0; /* Check if the requested node is already in the hash table. If so, ** increase its reference count and return it. @@ -160517,28 +164121,45 @@ static int nodeAcquire( return SQLITE_OK; } - sqlite3_bind_int64(pRtree->pReadNode, 1, iNode); - rc = sqlite3_step(pRtree->pReadNode); - if( rc==SQLITE_ROW ){ - const u8 *zBlob = sqlite3_column_blob(pRtree->pReadNode, 0); - if( pRtree->iNodeSize==sqlite3_column_bytes(pRtree->pReadNode, 0) ){ - pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode)+pRtree->iNodeSize); - if( !pNode ){ - rc2 = SQLITE_NOMEM; - }else{ - pNode->pParent = pParent; - pNode->zData = (u8 *)&pNode[1]; - pNode->nRef = 1; - pNode->iNode = iNode; - pNode->isDirty = 0; - pNode->pNext = 0; - memcpy(pNode->zData, zBlob, pRtree->iNodeSize); - nodeReference(pParent); - } + if( pRtree->pNodeBlob ){ + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + rc = sqlite3_blob_reopen(pBlob, iNode); + pRtree->pNodeBlob = pBlob; + if( rc ){ + nodeBlobReset(pRtree); + if( rc==SQLITE_NOMEM ) return SQLITE_NOMEM; + } + } + if( pRtree->pNodeBlob==0 ){ + char *zTab = sqlite3_mprintf("%s_node", pRtree->zName); + if( zTab==0 ) return SQLITE_NOMEM; + rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0, + &pRtree->pNodeBlob); + sqlite3_free(zTab); + } + if( rc ){ + nodeBlobReset(pRtree); + *ppNode = 0; + /* If unable to open an sqlite3_blob on the desired row, that can only + ** be because the shadow tables hold erroneous data. */ + if( rc==SQLITE_ERROR ) rc = SQLITE_CORRUPT_VTAB; + }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){ + pNode = (RtreeNode *)sqlite3_malloc(sizeof(RtreeNode)+pRtree->iNodeSize); + if( !pNode ){ + rc = SQLITE_NOMEM; + }else{ + pNode->pParent = pParent; + pNode->zData = (u8 *)&pNode[1]; + pNode->nRef = 1; + pNode->iNode = iNode; + pNode->isDirty = 0; + pNode->pNext = 0; + rc = sqlite3_blob_read(pRtree->pNodeBlob, pNode->zData, + pRtree->iNodeSize, 0); + nodeReference(pParent); } } - rc = sqlite3_reset(pRtree->pReadNode); - if( rc==SQLITE_OK ) rc = rc2; /* If the root node was just loaded, set pRtree->iDepth to the height ** of the r-tree structure. A height of zero means all data is stored on @@ -160590,7 +164211,7 @@ static void nodeOverwriteCell( int ii; u8 *p = &pNode->zData[4 + pRtree->nBytesPerCell*iCell]; p += writeInt64(p, pCell->iRowid); - for(ii=0; ii<(pRtree->nDim*2); ii++){ + for(ii=0; iinDim2; ii++){ p += writeCoord(p, &pCell->aCoord[ii]); } pNode->isDirty = 1; @@ -160724,13 +164345,16 @@ static void nodeGetCell( ){ u8 *pData; RtreeCoord *pCoord; - int ii; + int ii = 0; pCell->iRowid = nodeGetRowid(pRtree, pNode, iCell); pData = pNode->zData + (12 + pRtree->nBytesPerCell*iCell); pCoord = pCell->aCoord; - for(ii=0; iinDim*2; ii++){ - readCoord(&pData[ii*4], &pCoord[ii]); - } + do{ + readCoord(pData, &pCoord[ii]); + readCoord(pData+4, &pCoord[ii+1]); + pData += 8; + ii += 2; + }while( iinDim2 ); } @@ -160781,7 +164405,9 @@ static void rtreeReference(Rtree *pRtree){ static void rtreeRelease(Rtree *pRtree){ pRtree->nBusy--; if( pRtree->nBusy==0 ){ - sqlite3_finalize(pRtree->pReadNode); + pRtree->inWrTrans = 0; + pRtree->nCursor = 0; + nodeBlobReset(pRtree); sqlite3_finalize(pRtree->pWriteNode); sqlite3_finalize(pRtree->pDeleteNode); sqlite3_finalize(pRtree->pReadRowid); @@ -160819,6 +164445,7 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){ if( !zCreate ){ rc = SQLITE_NOMEM; }else{ + nodeBlobReset(pRtree); rc = sqlite3_exec(pRtree->db, zCreate, 0, 0, 0); sqlite3_free(zCreate); } @@ -160834,6 +164461,7 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){ */ static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ int rc = SQLITE_NOMEM; + Rtree *pRtree = (Rtree *)pVTab; RtreeCursor *pCsr; pCsr = (RtreeCursor *)sqlite3_malloc(sizeof(RtreeCursor)); @@ -160841,6 +164469,7 @@ static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ memset(pCsr, 0, sizeof(RtreeCursor)); pCsr->base.pVtab = pVTab; rc = SQLITE_OK; + pRtree->nCursor++; } *ppCursor = (sqlite3_vtab_cursor *)pCsr; @@ -160873,10 +164502,13 @@ static int rtreeClose(sqlite3_vtab_cursor *cur){ Rtree *pRtree = (Rtree *)(cur->pVtab); int ii; RtreeCursor *pCsr = (RtreeCursor *)cur; + assert( pRtree->nCursor>0 ); freeCursorConstraints(pCsr); sqlite3_free(pCsr->aPoint); for(ii=0; iiaNode[ii]); sqlite3_free(pCsr); + pRtree->nCursor--; + nodeBlobReset(pRtree); return SQLITE_OK; } @@ -160899,15 +164531,22 @@ static int rtreeEof(sqlite3_vtab_cursor *cur){ ** false. a[] is the four bytes of the on-disk record to be decoded. ** Store the results in "r". ** -** There are three versions of this macro, one each for little-endian and -** big-endian processors and a third generic implementation. The endian- -** specific implementations are much faster and are preferred if the -** processor endianness is known at compile-time. The SQLITE_BYTEORDER -** macro is part of sqliteInt.h and hence the endian-specific -** implementation will only be used if this module is compiled as part -** of the amalgamation. +** There are five versions of this macro. The last one is generic. The +** other four are various architectures-specific optimizations. */ -#if defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==1234 +#if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = _byteswap_ulong(*(u32*)a); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 +#define RTREE_DECODE_COORD(eInt, a, r) { \ + RtreeCoord c; /* Coordinate decoded */ \ + c.u = __builtin_bswap32(*(u32*)a); \ + r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ +} +#elif SQLITE_BYTEORDER==1234 #define RTREE_DECODE_COORD(eInt, a, r) { \ RtreeCoord c; /* Coordinate decoded */ \ memcpy(&c.u,a,4); \ @@ -160915,7 +164554,7 @@ static int rtreeEof(sqlite3_vtab_cursor *cur){ ((c.u&0xff)<<24)|((c.u&0xff00)<<8); \ r = eInt ? (sqlite3_rtree_dbl)c.i : (sqlite3_rtree_dbl)c.f; \ } -#elif defined(SQLITE_BYTEORDER) && SQLITE_BYTEORDER==4321 +#elif SQLITE_BYTEORDER==4321 #define RTREE_DECODE_COORD(eInt, a, r) { \ RtreeCoord c; /* Coordinate decoded */ \ memcpy(&c.u,a,4); \ @@ -160942,10 +164581,10 @@ static int rtreeCallbackConstraint( sqlite3_rtree_dbl *prScore, /* OUT: score for the cell */ int *peWithin /* OUT: visibility of the cell */ ){ - int i; /* Loop counter */ sqlite3_rtree_query_info *pInfo = pConstraint->pInfo; /* Callback info */ int nCoord = pInfo->nCoord; /* No. of coordinates */ int rc; /* Callback return code */ + RtreeCoord c; /* Translator union */ sqlite3_rtree_dbl aCoord[RTREE_MAX_DIMENSIONS*2]; /* Decoded coordinates */ assert( pConstraint->op==RTREE_MATCH || pConstraint->op==RTREE_QUERY ); @@ -160955,13 +164594,41 @@ static int rtreeCallbackConstraint( pInfo->iRowid = readInt64(pCellData); } pCellData += 8; - for(i=0; iop==RTREE_MATCH ){ + int eWithin = 0; rc = pConstraint->u.xGeom((sqlite3_rtree_geometry*)pInfo, - nCoord, aCoord, &i); - if( i==0 ) *peWithin = NOT_WITHIN; + nCoord, aCoord, &eWithin); + if( eWithin==0 ) *peWithin = NOT_WITHIN; *prScore = RTREE_ZERO; }else{ pInfo->aCoord = aCoord; @@ -160997,6 +164664,7 @@ static void rtreeNonleafConstraint( assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ ); + assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ switch( p->op ){ case RTREE_LE: case RTREE_LT: @@ -161037,6 +164705,7 @@ static void rtreeLeafConstraint( assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE || p->op==RTREE_GT || p->op==RTREE_EQ ); pCellData += 8 + p->iCoord*4; + assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ case RTREE_LE: if( xN <= p->u.rValue ) return; break; @@ -161105,7 +164774,7 @@ static int rtreeSearchPointCompare( } /* -** Interchange to search points in a cursor. +** Interchange two search points in a cursor. */ static void rtreeSearchPointSwap(RtreeCursor *p, int i, int j){ RtreeSearchPoint t = p->aPoint[i]; @@ -161353,7 +165022,7 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){ if( rScoreeWithin = eWithin; + p->eWithin = (u8)eWithin; p->id = x.id; p->iCell = x.iCell; RTREE_QUEUE_TRACE(pCur, "PUSH-S:"); @@ -161412,7 +165081,6 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else{ - if( rc ) return rc; nodeGetCoord(pRtree, pNode, p->iCell, i-1, &c); #ifndef SQLITE_RTREE_INT_ONLY if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ @@ -161530,7 +165198,7 @@ static int rtreeFilter( if( idxNum==1 ){ /* Special case - lookup by rowid. */ RtreeNode *pLeaf; /* Leaf on which the required cell resides */ - RtreeSearchPoint *p; /* Search point for the the leaf */ + RtreeSearchPoint *p; /* Search point for the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); @@ -161541,7 +165209,7 @@ static int rtreeFilter( p->id = iNode; p->eWithin = PARTLY_WITHIN; rc = nodeRowidIndex(pRtree, pLeaf, iRowid, &iCell); - p->iCell = iCell; + p->iCell = (u8)iCell; RTREE_QUEUE_TRACE(pCsr, "PUSH-F1:"); }else{ pCsr->atEOF = 1; @@ -161574,7 +165242,7 @@ static int rtreeFilter( if( rc!=SQLITE_OK ){ break; } - p->pInfo->nCoord = pRtree->nDim*2; + p->pInfo->nCoord = pRtree->nDim2; p->pInfo->anQueue = pCsr->anQueue; p->pInfo->mxLevel = pRtree->iDepth + 1; }else{ @@ -161589,7 +165257,7 @@ static int rtreeFilter( } if( rc==SQLITE_OK ){ RtreeSearchPoint *pNew; - pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, pRtree->iDepth+1); + pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); if( pNew==0 ) return SQLITE_NOMEM; pNew->id = 1; pNew->iCell = 0; @@ -161607,19 +165275,6 @@ static int rtreeFilter( return rc; } -/* -** Set the pIdxInfo->estimatedRows variable to nRow. Unless this -** extension is currently being used by a version of SQLite too old to -** support estimatedRows. In that case this function is a no-op. -*/ -static void setEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ -#if SQLITE_VERSION_NUMBER>=3008002 - if( sqlite3_libversion_number()>=3008002 ){ - pIdxInfo->estimatedRows = nRow; - } -#endif -} - /* ** Rtree virtual table module xBestIndex method. There are three ** table scan strategies to choose from (in order from most to @@ -161699,7 +165354,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ** a single row. */ pIdxInfo->estimatedCost = 30.0; - setEstimatedRows(pIdxInfo, 1); + pIdxInfo->estimatedRows = 1; return SQLITE_OK; } @@ -161717,7 +165372,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ break; } zIdxStr[iIdx++] = op; - zIdxStr[iIdx++] = p->iColumn - 1 + '0'; + zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0'); pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); pIdxInfo->aConstraintUsage[ii].omit = 1; } @@ -161731,7 +165386,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ nRow = pRtree->nRowEst >> (iIdx/2); pIdxInfo->estimatedCost = (double)6.0 * (double)nRow; - setEstimatedRows(pIdxInfo, nRow); + pIdxInfo->estimatedRows = nRow; return rc; } @@ -161741,9 +165396,26 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ */ static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){ RtreeDValue area = (RtreeDValue)1; - int ii; - for(ii=0; ii<(pRtree->nDim*2); ii+=2){ - area = (area * (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii]))); + assert( pRtree->nDim>=1 && pRtree->nDim<=5 ); +#ifndef SQLITE_RTREE_INT_ONLY + if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ + switch( pRtree->nDim ){ + case 5: area = p->aCoord[9].f - p->aCoord[8].f; + case 4: area *= p->aCoord[7].f - p->aCoord[6].f; + case 3: area *= p->aCoord[5].f - p->aCoord[4].f; + case 2: area *= p->aCoord[3].f - p->aCoord[2].f; + default: area *= p->aCoord[1].f - p->aCoord[0].f; + } + }else +#endif + { + switch( pRtree->nDim ){ + case 5: area = p->aCoord[9].i - p->aCoord[8].i; + case 4: area *= p->aCoord[7].i - p->aCoord[6].i; + case 3: area *= p->aCoord[5].i - p->aCoord[4].i; + case 2: area *= p->aCoord[3].i - p->aCoord[2].i; + default: area *= p->aCoord[1].i - p->aCoord[0].i; + } } return area; } @@ -161753,11 +165425,12 @@ static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){ ** of the objects size in each dimension. */ static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){ - RtreeDValue margin = (RtreeDValue)0; - int ii; - for(ii=0; ii<(pRtree->nDim*2); ii+=2){ + RtreeDValue margin = 0; + int ii = pRtree->nDim2 - 2; + do{ margin += (DCOORD(p->aCoord[ii+1]) - DCOORD(p->aCoord[ii])); - } + ii -= 2; + }while( ii>=0 ); return margin; } @@ -161765,17 +165438,19 @@ static RtreeDValue cellMargin(Rtree *pRtree, RtreeCell *p){ ** Store the union of cells p1 and p2 in p1. */ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ - int ii; + int ii = 0; if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ - for(ii=0; ii<(pRtree->nDim*2); ii+=2){ + do{ p1->aCoord[ii].f = MIN(p1->aCoord[ii].f, p2->aCoord[ii].f); p1->aCoord[ii+1].f = MAX(p1->aCoord[ii+1].f, p2->aCoord[ii+1].f); - } + ii += 2; + }while( iinDim2 ); }else{ - for(ii=0; ii<(pRtree->nDim*2); ii+=2){ + do{ p1->aCoord[ii].i = MIN(p1->aCoord[ii].i, p2->aCoord[ii].i); p1->aCoord[ii+1].i = MAX(p1->aCoord[ii+1].i, p2->aCoord[ii+1].i); - } + ii += 2; + }while( iinDim2 ); } } @@ -161786,7 +165461,7 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ int ii; int isInt = (pRtree->eCoordType==RTREE_COORD_INT32); - for(ii=0; ii<(pRtree->nDim*2); ii+=2){ + for(ii=0; iinDim2; ii+=2){ RtreeCoord *a1 = &p1->aCoord[ii]; RtreeCoord *a2 = &p2->aCoord[ii]; if( (!isInt && (a2[0].fa1[1].f)) @@ -161821,7 +165496,7 @@ static RtreeDValue cellOverlap( for(ii=0; iinDim*2); jj+=2){ + for(jj=0; jjnDim2; jj+=2){ RtreeDValue x1, x2; x1 = MAX(DCOORD(p->aCoord[jj]), DCOORD(aCell[ii].aCoord[jj])); x2 = MIN(DCOORD(p->aCoord[jj+1]), DCOORD(aCell[ii].aCoord[jj+1])); @@ -162877,7 +166552,7 @@ static int rtreeUpdate( ** This problem was discovered after years of use, so we silently ignore ** these kinds of misdeclared tables to avoid breaking any legacy. */ - assert( nData<=(pRtree->nDim*2 + 3) ); + assert( nData<=(pRtree->nDim2 + 3) ); #ifndef SQLITE_RTREE_INT_ONLY if( pRtree->eCoordType==RTREE_COORD_REAL32 ){ @@ -162967,6 +166642,27 @@ static int rtreeUpdate( return rc; } +/* +** Called when a transaction starts. +*/ +static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ + Rtree *pRtree = (Rtree *)pVtab; + assert( pRtree->inWrTrans==0 ); + pRtree->inWrTrans++; + return SQLITE_OK; +} + +/* +** Called when a transaction completes (either by COMMIT or ROLLBACK). +** The sqlite3_blob object should be released at this point. +*/ +static int rtreeEndTransaction(sqlite3_vtab *pVtab){ + Rtree *pRtree = (Rtree *)pVtab; + pRtree->inWrTrans = 0; + nodeBlobReset(pRtree); + return SQLITE_OK; +} + /* ** The xRename method for rtree module virtual tables. */ @@ -162988,6 +166684,7 @@ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ return rc; } + /* ** This function populates the pRtree->nRowEst variable with an estimate ** of the number of rows in the virtual table. If possible, this is based @@ -163000,10 +166697,12 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ int rc; i64 nRow = 0; - if( sqlite3_table_column_metadata(db,pRtree->zDb,"sqlite_stat1", - 0,0,0,0,0,0)==SQLITE_ERROR ){ + rc = sqlite3_table_column_metadata( + db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 + ); + if( rc!=SQLITE_OK ){ pRtree->nRowEst = RTREE_DEFAULT_ROWEST; - return SQLITE_OK; + return rc==SQLITE_ERROR ? SQLITE_OK : rc; } zSql = sqlite3_mprintf(zFmt, pRtree->zDb, pRtree->zName); if( zSql==0 ){ @@ -163045,15 +166744,15 @@ static sqlite3_module rtreeModule = { rtreeColumn, /* xColumn - read data */ rtreeRowid, /* xRowid - read data */ rtreeUpdate, /* xUpdate - write data */ - 0, /* xBegin - begin transaction */ - 0, /* xSync - sync transaction */ - 0, /* xCommit - commit transaction */ - 0, /* xRollback - rollback transaction */ + rtreeBeginTransaction, /* xBegin - begin transaction */ + rtreeEndTransaction, /* xSync - sync transaction */ + rtreeEndTransaction, /* xCommit - commit transaction */ + rtreeEndTransaction, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ 0, /* xSavepoint */ 0, /* xRelease */ - 0 /* xRollbackTo */ + 0, /* xRollbackTo */ }; static int rtreeSqlInit( @@ -163065,10 +166764,9 @@ static int rtreeSqlInit( ){ int rc = SQLITE_OK; - #define N_STATEMENT 9 + #define N_STATEMENT 8 static const char *azSql[N_STATEMENT] = { - /* Read and write the xxx_node table */ - "SELECT data FROM '%q'.'%q_node' WHERE nodeno = :1", + /* Write the xxx_node table */ "INSERT OR REPLACE INTO '%q'.'%q_node' VALUES(:1, :2)", "DELETE FROM '%q'.'%q_node' WHERE nodeno = :1", @@ -163106,15 +166804,14 @@ static int rtreeSqlInit( } } - appStmt[0] = &pRtree->pReadNode; - appStmt[1] = &pRtree->pWriteNode; - appStmt[2] = &pRtree->pDeleteNode; - appStmt[3] = &pRtree->pReadRowid; - appStmt[4] = &pRtree->pWriteRowid; - appStmt[5] = &pRtree->pDeleteRowid; - appStmt[6] = &pRtree->pReadParent; - appStmt[7] = &pRtree->pWriteParent; - appStmt[8] = &pRtree->pDeleteParent; + appStmt[0] = &pRtree->pWriteNode; + appStmt[1] = &pRtree->pDeleteNode; + appStmt[2] = &pRtree->pReadRowid; + appStmt[3] = &pRtree->pWriteRowid; + appStmt[4] = &pRtree->pDeleteRowid; + appStmt[5] = &pRtree->pReadParent; + appStmt[6] = &pRtree->pWriteParent; + appStmt[7] = &pRtree->pDeleteParent; rc = rtreeQueryStat1(db, pRtree); for(i=0; ibase.pModule = &rtreeModule; pRtree->zDb = (char *)&pRtree[1]; pRtree->zName = &pRtree->zDb[nDb+1]; - pRtree->nDim = (argc-4)/2; - pRtree->nBytesPerCell = 8 + pRtree->nDim*4*2; - pRtree->eCoordType = eCoordType; + pRtree->nDim = (u8)((argc-4)/2); + pRtree->nDim2 = pRtree->nDim*2; + pRtree->nBytesPerCell = 8 + pRtree->nDim2*4; + pRtree->eCoordType = (u8)eCoordType; memcpy(pRtree->zDb, argv[1], nDb); memcpy(pRtree->zName, argv[2], nName); @@ -163327,7 +167025,8 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ UNUSED_PARAMETER(nArg); memset(&node, 0, sizeof(RtreeNode)); memset(&tree, 0, sizeof(Rtree)); - tree.nDim = sqlite3_value_int(apArg[0]); + tree.nDim = (u8)sqlite3_value_int(apArg[0]); + tree.nDim2 = tree.nDim*2; tree.nBytesPerCell = 8 + 8 * tree.nDim; node.zData = (u8 *)sqlite3_value_blob(apArg[1]); @@ -163340,7 +167039,7 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ nodeGetCell(&tree, &node, ii, &cell); sqlite3_snprintf(512-nCell,&zCell[nCell],"%lld", cell.iRowid); nCell = (int)strlen(zCell); - for(jj=0; jj 'i' -** lower('I', 'tr_tr') -> 'ı' (small dotless i) +** lower('I', 'tr_tr') -> '\u131' (small dotless i) ** ** http://www.icu-project.org/userguide/posix.html#case_mappings */ @@ -164048,38 +167747,36 @@ static void icuLoadCollation( ** Register the ICU extension functions with database db. */ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db){ - struct IcuScalar { + static const struct IcuScalar { const char *zName; /* Function name */ - int nArg; /* Number of arguments */ - int enc; /* Optimal text encoding */ - void *pContext; /* sqlite3_user_data() context */ + unsigned char nArg; /* Number of arguments */ + unsigned short enc; /* Optimal text encoding */ + unsigned char iContext; /* sqlite3_user_data() context */ void (*xFunc)(sqlite3_context*,int,sqlite3_value**); } scalars[] = { - {"regexp", 2, SQLITE_ANY, 0, icuRegexpFunc}, - - {"lower", 1, SQLITE_UTF16, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF16, 0, icuCaseFunc16}, - {"upper", 1, SQLITE_UTF16, (void*)1, icuCaseFunc16}, - {"upper", 2, SQLITE_UTF16, (void*)1, icuCaseFunc16}, - - {"lower", 1, SQLITE_UTF8, 0, icuCaseFunc16}, - {"lower", 2, SQLITE_UTF8, 0, icuCaseFunc16}, - {"upper", 1, SQLITE_UTF8, (void*)1, icuCaseFunc16}, - {"upper", 2, SQLITE_UTF8, (void*)1, icuCaseFunc16}, - - {"like", 2, SQLITE_UTF8, 0, icuLikeFunc}, - {"like", 3, SQLITE_UTF8, 0, icuLikeFunc}, - - {"icu_load_collation", 2, SQLITE_UTF8, (void*)db, icuLoadCollation}, + {"icu_load_collation", 2, SQLITE_UTF8, 1, icuLoadCollation}, + {"regexp", 2, SQLITE_ANY|SQLITE_DETERMINISTIC, 0, icuRegexpFunc}, + {"lower", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF16|SQLITE_DETERMINISTIC, 1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF16|SQLITE_DETERMINISTIC, 1, icuCaseFunc16}, + {"lower", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"lower", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuCaseFunc16}, + {"upper", 1, SQLITE_UTF8|SQLITE_DETERMINISTIC, 1, icuCaseFunc16}, + {"upper", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 1, icuCaseFunc16}, + {"like", 2, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, + {"like", 3, SQLITE_UTF8|SQLITE_DETERMINISTIC, 0, icuLikeFunc}, }; - int rc = SQLITE_OK; int i; + for(i=0; rc==SQLITE_OK && i<(int)(sizeof(scalars)/sizeof(scalars[0])); i++){ - struct IcuScalar *p = &scalars[i]; + const struct IcuScalar *p = &scalars[i]; rc = sqlite3_create_function( - db, p->zName, p->nArg, p->enc, p->pContext, p->xFunc, 0, 0 + db, p->zName, p->nArg, p->enc, + p->iContext ? (void*)db : (void*)0, + p->xFunc, 0, 0 ); } @@ -164566,7 +168263,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** may also be named data_, where is any sequence ** of zero or more numeric characters (0-9). This can be significant because ** tables within the RBU database are always processed in order sorted by -** name. By judicious selection of the the portion of the names +** name. By judicious selection of the portion of the names ** of the RBU tables the user can therefore control the order in which they ** are processed. This can be useful, for example, to ensure that "external ** content" FTS4 tables are updated before their underlying content tables. @@ -164781,16 +168478,22 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( ** An RBU vacuum is similar to SQLite's built-in VACUUM command, except ** that it can be suspended and resumed like an RBU update. ** -** The second argument to this function, which may not be NULL, identifies -** a database in which to store the state of the RBU vacuum operation if -** it is suspended. The first time sqlite3rbu_vacuum() is called, to start -** an RBU vacuum operation, the state database should either not exist or -** be empty (contain no tables). If an RBU vacuum is suspended by calling +** The second argument to this function identifies a database in which +** to store the state of the RBU vacuum operation if it is suspended. The +** first time sqlite3rbu_vacuum() is called, to start an RBU vacuum +** operation, the state database should either not exist or be empty +** (contain no tables). If an RBU vacuum is suspended by calling ** sqlite3rbu_close() on the RBU handle before sqlite3rbu_step() has ** returned SQLITE_DONE, the vacuum state is stored in the state database. ** The vacuum can be resumed by calling this function to open a new RBU ** handle specifying the same target and state databases. ** +** If the second argument passed to this function is NULL, then the +** name of the state database is "-vacuum", where +** is the name of the target database file. In this case, on UNIX, if the +** state database is not already present in the file-system, it is created +** with the same permissions as the target db is made. +** ** This function does not delete the state database after an RBU vacuum ** is completed, even if it created it. However, if the call to ** sqlite3rbu_close() returns any value other than SQLITE_OK, the contents @@ -165304,6 +169007,7 @@ struct sqlite3rbu { RbuObjIter objiter; /* Iterator for skipping through tbl/idx */ const char *zVfsName; /* Name of automatically created rbu vfs */ rbu_file *pTargetFd; /* File handle open on target db */ + int nPagePerSector; /* Pages per sector for pTargetFd */ i64 iOalSz; i64 nPhaseOneStep; @@ -167281,16 +170985,19 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ ** Open the database handle and attach the RBU database as "rbu". If an ** error occurs, leave an error code and message in the RBU handle. */ -static void rbuOpenDatabase(sqlite3rbu *p){ - assert( p->rc==SQLITE_OK ); - assert( p->dbMain==0 && p->dbRbu==0 ); - assert( rbuIsVacuum(p) || p->zTarget!=0 ); +static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ + assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); + assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); /* Open the RBU database */ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); + if( p->zState==0 ){ + const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); + p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile); + } } /* If using separate RBU and state databases, attach the state database to @@ -167353,7 +171060,7 @@ static void rbuOpenDatabase(sqlite3rbu *p){ }else{ RbuState *pState = rbuLoadState(p); if( pState ){ - bOpen = (pState->eStage>RBU_STAGE_MOVE); + bOpen = (pState->eStage>=RBU_STAGE_MOVE); rbuFreeState(pState); } } @@ -167365,6 +171072,15 @@ static void rbuOpenDatabase(sqlite3rbu *p){ if( !rbuIsVacuum(p) ){ p->dbMain = rbuOpenDbhandle(p, p->zTarget, 1); }else if( p->pRbuFd->pWalFd ){ + if( pbRetry ){ + p->pRbuFd->bNolock = 0; + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; + *pbRetry = 1; + return; + } p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("cannot vacuum wal mode database"); }else{ @@ -167545,16 +171261,35 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; } - if( p->rc==SQLITE_OK ){ + if( p->rc==SQLITE_OK && p->nFrame>0 ){ p->eStage = RBU_STAGE_CKPT; p->nStep = (pState ? pState->nRow : 0); p->aBuf = rbuMalloc(p, p->pgsz); p->iWalCksum = rbuShmChecksum(p); } - if( p->rc==SQLITE_OK && pState && pState->iWalCksum!=p->iWalCksum ){ - p->rc = SQLITE_DONE; - p->eStage = RBU_STAGE_DONE; + if( p->rc==SQLITE_OK ){ + if( p->nFrame==0 || (pState && pState->iWalCksum!=p->iWalCksum) ){ + p->rc = SQLITE_DONE; + p->eStage = RBU_STAGE_DONE; + }else{ + int nSectorSize; + sqlite3_file *pDb = p->pTargetFd->pReal; + sqlite3_file *pWal = p->pTargetFd->pWalFd->pReal; + assert( p->nPagePerSector==0 ); + nSectorSize = pDb->pMethods->xSectorSize(pDb); + if( nSectorSize>p->pgsz ){ + p->nPagePerSector = nSectorSize / p->pgsz; + }else{ + p->nPagePerSector = 1; + } + + /* Call xSync() on the wal file. This causes SQLite to sync the + ** directory in which the target database and the wal file reside, in + ** case it has not been synced since the rename() call in + ** rbuMoveOalFile(). */ + p->rc = pWal->pMethods->xSync(pWal, SQLITE_SYNC_NORMAL); + } } } @@ -167727,7 +171462,7 @@ static void rbuMoveOalFile(sqlite3rbu *p){ #endif if( p->rc==SQLITE_OK ){ - rbuOpenDatabase(p); + rbuOpenDatabase(p, 0); rbuSetupCheckpoint(p, 0); } } @@ -168209,9 +171944,26 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ p->rc = SQLITE_DONE; } }else{ - RbuFrame *pFrame = &p->aFrame[p->nStep]; - rbuCheckpointFrame(p, pFrame); - p->nStep++; + /* At one point the following block copied a single frame from the + ** wal file to the database file. So that one call to sqlite3rbu_step() + ** checkpointed a single frame. + ** + ** However, if the sector-size is larger than the page-size, and the + ** application calls sqlite3rbu_savestate() or close() immediately + ** after this step, then rbu_step() again, then a power failure occurs, + ** then the database page written here may be damaged. Work around + ** this by checkpointing frames until the next page in the aFrame[] + ** lies on a different disk sector to the current one. */ + u32 iSector; + do{ + RbuFrame *pFrame = &p->aFrame[p->nStep]; + iSector = (pFrame->iDbPage-1) / p->nPagePerSector; + rbuCheckpointFrame(p, pFrame); + p->nStep++; + }while( p->nStepnFrame + && iSector==((p->aFrame[p->nStep].iDbPage-1) / p->nPagePerSector) + && p->rc==SQLITE_OK + ); } p->nProgress++; } @@ -168425,8 +172177,7 @@ static sqlite3rbu *openRbuHandle( sqlite3rbu *p; size_t nTarget = zTarget ? strlen(zTarget) : 0; size_t nRbu = strlen(zRbu); - size_t nState = zState ? strlen(zState) : 0; - size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1+ nState+1; + size_t nByte = sizeof(sqlite3rbu) + nTarget+1 + nRbu+1; p = (sqlite3rbu*)sqlite3_malloc64(nByte); if( p ){ @@ -168439,6 +172190,7 @@ static sqlite3rbu *openRbuHandle( /* Open the target, RBU and state databases */ if( p->rc==SQLITE_OK ){ char *pCsr = (char*)&p[1]; + int bRetry = 0; if( zTarget ){ p->zTarget = pCsr; memcpy(p->zTarget, zTarget, nTarget+1); @@ -168448,10 +172200,20 @@ static sqlite3rbu *openRbuHandle( memcpy(p->zRbu, zRbu, nRbu+1); pCsr += nRbu+1; if( zState ){ - p->zState = pCsr; - memcpy(p->zState, zState, nState+1); + p->zState = rbuMPrintf(p, "%s", zState); + } + + /* If the first attempt to open the database file fails and the bRetry + ** flag it set, this means that the db was not opened because it seemed + ** to be a wal-mode db. But, this may have happened due to an earlier + ** RBU vacuum operation leaving an old wal file in the directory. + ** If this is the case, it will have been checkpointed and deleted + ** when the handle was closed and a second attempt to open the + ** database may succeed. */ + rbuOpenDatabase(p, &bRetry); + if( bRetry ){ + rbuOpenDatabase(p, 0); } - rbuOpenDatabase(p); } if( p->rc==SQLITE_OK ){ @@ -168559,6 +172321,20 @@ static sqlite3rbu *openRbuHandle( return p; } +/* +** Allocate and return an RBU handle with all fields zeroed except for the +** error code, which is set to SQLITE_MISUSE. +*/ +static sqlite3rbu *rbuMisuseError(void){ + sqlite3rbu *pRet; + pRet = sqlite3_malloc64(sizeof(sqlite3rbu)); + if( pRet ){ + memset(pRet, 0, sizeof(sqlite3rbu)); + pRet->rc = SQLITE_MISUSE; + } + return pRet; +} + /* ** Open and return a new RBU handle. */ @@ -168567,6 +172343,7 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( const char *zRbu, const char *zState ){ + if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } /* TODO: Check that zTarget and zRbu are non-NULL */ return openRbuHandle(zTarget, zRbu, zState); } @@ -168578,6 +172355,7 @@ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( const char *zTarget, const char *zState ){ + if( zTarget==0 ){ return rbuMisuseError(); } /* TODO: Check that both arguments are non-NULL */ return openRbuHandle(0, zTarget, zState); } @@ -168626,6 +172404,12 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ p->rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, &p->zErrmsg); } + /* Sync the db file if currently doing an incremental checkpoint */ + if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); + } + rbuSaveState(p, p->eStage); if( p->rc==SQLITE_OK && p->eStage==RBU_STAGE_OAL ){ @@ -168655,6 +172439,7 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ rbuEditErrmsg(p); rc = p->rc; *pzErrmsg = p->zErrmsg; + sqlite3_free(p->zState); sqlite3_free(p); }else{ rc = SQLITE_NOMEM; @@ -168749,6 +172534,12 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbMain, "COMMIT", 0, 0, 0); } + /* Sync the db file */ + if( rc==SQLITE_OK && p->eStage==RBU_STAGE_CKPT ){ + sqlite3_file *pDb = p->pTargetFd->pReal; + rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); + } + p->rc = rc; rbuSaveState(p, p->eStage); rc = p->rc; @@ -170266,7 +174057,7 @@ static int statFilter( " UNION ALL " "SELECT name, rootpage, type" " FROM \"%w\".%s WHERE rootpage!=0" - " ORDER BY name", pTab->db->aDb[pCsr->iDb].zName, zMaster); + " ORDER BY name", pTab->db->aDb[pCsr->iDb].zDbSName, zMaster); if( zSql==0 ){ return SQLITE_NOMEM_BKPT; }else{ @@ -170320,7 +174111,7 @@ static int statColumn( default: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); int iDb = pCsr->iDb; - sqlite3_result_text(ctx, db->aDb[iDb].zName, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC); break; } } @@ -170743,9 +174534,7 @@ static int sessionSerializeValue( if( aBuf ){ sessionVarintPut(&aBuf[1], n); - memcpy(&aBuf[nVarint + 1], eType==SQLITE_TEXT ? - sqlite3_value_text(pValue) : sqlite3_value_blob(pValue), n - ); + if( n ) memcpy(&aBuf[nVarint + 1], z, n); } nByte = 1 + nVarint + n; @@ -172161,7 +175950,7 @@ static void sessionAppendBlob( int nBlob, int *pRc ){ - if( 0==sessionBufferGrow(p, nBlob, pRc) ){ + if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){ memcpy(&p->aBuf[p->nBuf], aBlob, nBlob); p->nBuf += nBlob; } @@ -172347,13 +176136,13 @@ static int sessionAppendUpdate( } default: { - int nByte; - int nHdr = 1 + sessionVarintGet(&pCsr[1], &nByte); + int n; + int nHdr = 1 + sessionVarintGet(&pCsr[1], &n); assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); - nAdvance = nHdr + nByte; + nAdvance = nHdr + n; if( eType==sqlite3_column_type(pStmt, i) - && nByte==sqlite3_column_bytes(pStmt, i) - && 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), nByte) + && n==sqlite3_column_bytes(pStmt, i) + && (n==0 || 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), n)) ){ break; } @@ -173399,7 +177188,7 @@ SQLITE_API int sqlite3changeset_conflict( if( !pIter->pConflict ){ return SQLITE_MISUSE; } - if( iVal<0 || iVal>=sqlite3_column_count(pIter->pConflict) ){ + if( iVal<0 || iVal>=pIter->nCol ){ return SQLITE_RANGE; } *ppValue = sqlite3_column_value(pIter->pConflict, iVal); @@ -173866,7 +177655,13 @@ static int sessionInsertRow( sessionAppendStr(&buf, "INSERT INTO main.", &rc); sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " VALUES(?", &rc); + sessionAppendStr(&buf, "(", &rc); + for(i=0; inCol; i++){ + if( i!=0 ) sessionAppendStr(&buf, ", ", &rc); + sessionAppendIdent(&buf, p->azCol[i], &rc); + } + + sessionAppendStr(&buf, ") VALUES(?", &rc); for(i=1; inCol; i++){ sessionAppendStr(&buf, ", ?", &rc); } @@ -174412,11 +178207,17 @@ static int sessionChangesetApply( nTab = (int)strlen(zTab); sApply.azCol = (const char **)zTab; }else{ + int nMinCol = 0; + int i; + sqlite3changeset_pk(pIter, &abPK, 0); rc = sessionTableInfo( db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK ); if( rc!=SQLITE_OK ) break; + for(i=0; i /* amalgamator: keep */ -# define safe_isdigit(x) isdigit((unsigned char)(x)) -# define safe_isalnum(x) isalnum((unsigned char)(x)) +# define safe_isdigit(x) isdigit((unsigned char)(x)) +# define safe_isalnum(x) isalnum((unsigned char)(x)) +# define safe_isxdigit(x) isxdigit((unsigned char)(x)) #endif /* @@ -175149,9 +178955,10 @@ static const char * const jsonType[] = { #define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ #define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ #define JNODE_REMOVE 0x04 /* Do not output */ -#define JNODE_REPLACE 0x08 /* Replace with JsonNode.iVal */ -#define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ -#define JNODE_LABEL 0x20 /* Is a label of an object */ +#define JNODE_REPLACE 0x08 /* Replace with JsonNode.u.iReplace */ +#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */ +#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */ +#define JNODE_LABEL 0x40 /* Is a label of an object */ /* A single node of parsed JSON @@ -175159,12 +178966,13 @@ static const char * const jsonType[] = { struct JsonNode { u8 eType; /* One of the JSON_ type values */ u8 jnFlags; /* JNODE flags */ - u8 iVal; /* Replacement value when JNODE_REPLACE */ u32 n; /* Bytes of content, or number of sub-nodes */ union { const char *zJContent; /* Content for INT, REAL, and STRING */ u32 iAppend; /* More terms for ARRAY and OBJECT */ u32 iKey; /* Key for ARRAY objects in json_tree() */ + u32 iReplace; /* Replacement content for JNODE_REPLACE */ + JsonNode *pPatch; /* Node chain of patch for JNODE_PATCH */ } u; }; @@ -175421,6 +179229,13 @@ static void jsonRenderNode( JsonString *pOut, /* Write JSON here */ sqlite3_value **aReplace /* Replacement values */ ){ + if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ + if( pNode->jnFlags & JNODE_REPLACE ){ + jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); + return; + } + pNode = pNode->u.pPatch; + } switch( pNode->eType ){ default: { assert( pNode->eType==JSON_NULL ); @@ -175452,12 +179267,7 @@ static void jsonRenderNode( jsonAppendChar(pOut, '['); for(;;){ while( j<=pNode->n ){ - if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){ - if( pNode[j].jnFlags & JNODE_REPLACE ){ - jsonAppendSeparator(pOut); - jsonAppendValue(pOut, aReplace[pNode[j].iVal]); - } - }else{ + if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){ jsonAppendSeparator(pOut); jsonRenderNode(&pNode[j], pOut, aReplace); } @@ -175479,11 +179289,7 @@ static void jsonRenderNode( jsonAppendSeparator(pOut); jsonRenderNode(&pNode[j], pOut, aReplace); jsonAppendChar(pOut, ':'); - if( pNode[j+1].jnFlags & JNODE_REPLACE ){ - jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]); - }else{ - jsonRenderNode(&pNode[j+1], pOut, aReplace); - } + jsonRenderNode(&pNode[j+1], pOut, aReplace); } j += 1 + jsonNodeSize(&pNode[j+1]); } @@ -175606,12 +179412,13 @@ static void jsonReturn( c = z[++i]; if( c=='u' ){ u32 v = 0, k; - for(k=0; k<4 && i='0' && c<='9' ) v = v*16 + c - '0'; - else if( c>='A' && c<='F' ) v = v*16 + c - 'A' + 10; - else if( c>='a' && c<='f' ) v = v*16 + c - 'a' + 10; - else break; + assert( safe_isxdigit(c) ); + if( c<='9' ) v = v*16 + c - '0'; + else if( c<='F' ) v = v*16 + c - 'A' + 10; + else v = v*16 + c - 'a' + 10; } if( v==0 ) break; if( v<=0x7f ){ @@ -175709,12 +179516,20 @@ static int jsonParseAddNode( p = &pParse->aNode[pParse->nNode]; p->eType = (u8)eType; p->jnFlags = 0; - p->iVal = 0; p->n = n; p->u.zJContent = zContent; return pParse->nNode++; } +/* +** Return true if z[] begins with 4 (or more) hexadecimal digits +*/ +static int jsonIs4Hex(const char *z){ + int i; + for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0; + return 1; +} + /* ** Parse a single JSON value which begins at pParse->zJson[i]. Return the ** index of the first character past the end of the value parsed. @@ -175789,8 +179604,13 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( c==0 ) return -1; if( c=='\\' ){ c = pParse->zJson[++j]; - if( c==0 ) return -1; - jnFlags = JNODE_ESCAPE; + if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' + || c=='n' || c=='r' || c=='t' + || (c=='u' && jsonIs4Hex(pParse->zJson+j+1)) ){ + jnFlags = JNODE_ESCAPE; + }else{ + return -1; + } }else if( c=='"' ){ break; } @@ -176161,6 +179981,25 @@ static void jsonWrongNumArgs( sqlite3_free(zMsg); } +/* +** Mark all NULL entries in the Object passed in as JNODE_REMOVE. +*/ +static void jsonRemoveAllNulls(JsonNode *pNode){ + int i, n; + assert( pNode->eType==JSON_OBJECT ); + n = pNode->n; + for(i=2; i<=n; i += jsonNodeSize(&pNode[i])+1){ + switch( pNode[i].eType ){ + case JSON_NULL: + pNode[i].jnFlags |= JNODE_REMOVE; + break; + case JSON_OBJECT: + jsonRemoveAllNulls(&pNode[i]); + break; + } + } +} + /**************************************************************************** ** SQL functions used for testing and debugging @@ -176353,6 +180192,105 @@ static void jsonExtractFunc( jsonParseReset(&x); } +/* This is the RFC 7396 MergePatch algorithm. +*/ +static JsonNode *jsonMergePatch( + JsonParse *pParse, /* The JSON parser that contains the TARGET */ + int iTarget, /* Node of the TARGET in pParse */ + JsonNode *pPatch /* The PATCH */ +){ + u32 i, j; + u32 iRoot; + JsonNode *pTarget; + if( pPatch->eType!=JSON_OBJECT ){ + return pPatch; + } + assert( iTarget>=0 && iTargetnNode ); + pTarget = &pParse->aNode[iTarget]; + assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); + if( pTarget->eType!=JSON_OBJECT ){ + jsonRemoveAllNulls(pPatch); + return pPatch; + } + iRoot = iTarget; + for(i=1; in; i += jsonNodeSize(&pPatch[i+1])+1){ + u32 nKey; + const char *zKey; + assert( pPatch[i].eType==JSON_STRING ); + assert( pPatch[i].jnFlags & JNODE_LABEL ); + nKey = pPatch[i].n; + zKey = pPatch[i].u.zJContent; + assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); + for(j=1; jn; j += jsonNodeSize(&pTarget[j+1])+1 ){ + assert( pTarget[j].eType==JSON_STRING ); + assert( pTarget[j].jnFlags & JNODE_LABEL ); + assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); + if( pTarget[j].n==nKey && strncmp(pTarget[j].u.zJContent,zKey,nKey)==0 ){ + if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break; + if( pPatch[i+1].eType==JSON_NULL ){ + pTarget[j+1].jnFlags |= JNODE_REMOVE; + }else{ + JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); + if( pNew==0 ) return 0; + pTarget = &pParse->aNode[iTarget]; + if( pNew!=&pTarget[j+1] ){ + pTarget[j+1].u.pPatch = pNew; + pTarget[j+1].jnFlags |= JNODE_PATCH; + } + } + break; + } + } + if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ + int iStart, iPatch; + iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); + jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); + iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0); + if( pParse->oom ) return 0; + jsonRemoveAllNulls(pPatch); + pTarget = &pParse->aNode[iTarget]; + pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; + pParse->aNode[iRoot].u.iAppend = iStart - iRoot; + iRoot = iStart; + pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; + pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; + } + } + return pTarget; +} + +/* +** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON +** object that is the result of running the RFC 7396 MergePatch() algorithm +** on the two arguments. +*/ +static void jsonPatchFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonParse x; /* The JSON that is being patched */ + JsonParse y; /* The patch */ + JsonNode *pResult; /* The result of the merge */ + + UNUSED_PARAM(argc); + if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; + if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){ + jsonParseReset(&x); + return; + } + pResult = jsonMergePatch(&x, 0, y.aNode); + assert( pResult!=0 || x.oom ); + if( pResult ){ + jsonReturnJson(pResult, ctx, 0); + }else{ + sqlite3_result_error_nomem(ctx); + } + jsonParseReset(&x); + jsonParseReset(&y); +} + + /* ** Implementation of the json_object(NAME,VALUE,...) function. Return a JSON ** object that contains all name/value given in arguments. Or if any name @@ -176456,11 +180394,11 @@ static void jsonReplaceFunc( if( x.nErr ) goto replace_err; if( pNode ){ pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->iVal = (u8)(i+1); + pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - sqlite3_result_value(ctx, argv[x.aNode[0].iVal]); + sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); } @@ -176510,11 +180448,11 @@ static void jsonSetFunc( goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->iVal = (u8)(i+1); + pNode->u.iReplace = i + 1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - sqlite3_result_value(ctx, argv[x.aNode[0].iVal]); + sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); }else{ jsonReturnJson(x.aNode, ctx, argv); } @@ -176658,7 +180596,7 @@ static void jsonObjectFinal(sqlite3_context *ctx){ if( pStr ){ jsonAppendChar(pStr, '}'); if( pStr->bErr ){ - if( pStr->bErr==0 ) sqlite3_result_error_nomem(ctx); + if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); assert( pStr->bStatic ); }else{ sqlite3_result_text(ctx, pStr->zBuf, pStr->nUsed, @@ -176936,9 +180874,9 @@ static int jsonEachColumn( /* For json_each() path and root are the same so fall through ** into the root case */ } - case JEACH_ROOT: { + default: { const char *zRoot = p->zRoot; - if( zRoot==0 ) zRoot = "$"; + if( zRoot==0 ) zRoot = "$"; sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC); break; } @@ -177157,6 +181095,7 @@ SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){ { "json_extract", -1, 0, jsonExtractFunc }, { "json_insert", -1, 0, jsonSetFunc }, { "json_object", -1, 0, jsonObjectFunc }, + { "json_patch", 2, 0, jsonPatchFunc }, { "json_quote", 1, 0, jsonQuoteFunc }, { "json_remove", -1, 0, jsonRemoveFunc }, { "json_replace", -1, 0, jsonReplaceFunc }, @@ -177850,7 +181789,9 @@ typedef short i16; typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; -#define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0]))) +#ifndef ArraySize +# define ArraySize(x) ((int)(sizeof(x) / sizeof(x[0]))) +#endif #define testcase(x) #define ALWAYS(x) 1 @@ -178557,6 +182498,7 @@ static void sqlite3Fts5ParseNodeFree(Fts5ExprNode*); static void sqlite3Fts5ParseSetDistance(Fts5Parse*, Fts5ExprNearset*, Fts5Token*); static void sqlite3Fts5ParseSetColset(Fts5Parse*, Fts5ExprNearset*, Fts5Colset*); +static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse*, Fts5Colset*); static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p); static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); @@ -178614,12 +182556,13 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); #define FTS5_COLON 5 #define FTS5_LP 6 #define FTS5_RP 7 -#define FTS5_LCP 8 -#define FTS5_RCP 9 -#define FTS5_STRING 10 -#define FTS5_COMMA 11 -#define FTS5_PLUS 12 -#define FTS5_STAR 13 +#define FTS5_MINUS 8 +#define FTS5_LCP 9 +#define FTS5_RCP 10 +#define FTS5_STRING 11 +#define FTS5_COMMA 12 +#define FTS5_PLUS 13 +#define FTS5_STAR 14 /* ** 2000-05-29 @@ -178733,17 +182676,17 @@ static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); #endif /************* Begin control #defines *****************************************/ #define fts5YYCODETYPE unsigned char -#define fts5YYNOCODE 27 +#define fts5YYNOCODE 28 #define fts5YYACTIONTYPE unsigned char #define sqlite3Fts5ParserFTS5TOKENTYPE Fts5Token typedef union { int fts5yyinit; sqlite3Fts5ParserFTS5TOKENTYPE fts5yy0; - Fts5Colset* fts5yy3; - Fts5ExprPhrase* fts5yy11; - Fts5ExprNode* fts5yy18; - int fts5yy20; - Fts5ExprNearset* fts5yy26; + int fts5yy4; + Fts5Colset* fts5yy11; + Fts5ExprNode* fts5yy24; + Fts5ExprNearset* fts5yy46; + Fts5ExprPhrase* fts5yy53; } fts5YYMINORTYPE; #ifndef fts5YYSTACKDEPTH #define fts5YYSTACKDEPTH 100 @@ -178752,16 +182695,16 @@ typedef union { #define sqlite3Fts5ParserARG_PDECL ,Fts5Parse *pParse #define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse = fts5yypParser->pParse #define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse = pParse -#define fts5YYNSTATE 26 -#define fts5YYNRULE 24 -#define fts5YY_MAX_SHIFT 25 -#define fts5YY_MIN_SHIFTREDUCE 40 -#define fts5YY_MAX_SHIFTREDUCE 63 -#define fts5YY_MIN_REDUCE 64 -#define fts5YY_MAX_REDUCE 87 -#define fts5YY_ERROR_ACTION 88 -#define fts5YY_ACCEPT_ACTION 89 -#define fts5YY_NO_ACTION 90 +#define fts5YYNSTATE 29 +#define fts5YYNRULE 26 +#define fts5YY_MAX_SHIFT 28 +#define fts5YY_MIN_SHIFTREDUCE 45 +#define fts5YY_MAX_SHIFTREDUCE 70 +#define fts5YY_MIN_REDUCE 71 +#define fts5YY_MAX_REDUCE 96 +#define fts5YY_ERROR_ACTION 97 +#define fts5YY_ACCEPT_ACTION 98 +#define fts5YY_NO_ACTION 99 /************* End control #defines *******************************************/ /* Define the fts5yytestcase() macro to be a no-op if is not already defined @@ -178793,7 +182736,7 @@ typedef union { ** ** N between fts5YY_MIN_REDUCE Reduce by rule N-fts5YY_MIN_REDUCE ** and fts5YY_MAX_REDUCE - +** ** N == fts5YY_ERROR_ACTION A syntax error has occurred. ** ** N == fts5YY_ACCEPT_ACTION The parser accepts its input. @@ -178802,16 +182745,20 @@ typedef union { ** slots in the fts5yy_action[] table. ** ** The action table is constructed as a single large table named fts5yy_action[]. -** Given state S and lookahead X, the action is computed as +** Given state S and lookahead X, the action is computed as either: ** -** fts5yy_action[ fts5yy_shift_ofst[S] + X ] +** (A) N = fts5yy_action[ fts5yy_shift_ofst[S] + X ] +** (B) N = fts5yy_default[S] ** -** If the index value fts5yy_shift_ofst[S]+X is out of range or if the value -** fts5yy_lookahead[fts5yy_shift_ofst[S]+X] is not equal to X or if fts5yy_shift_ofst[S] -** is equal to fts5YY_SHIFT_USE_DFLT, it means that the action is not in the table -** and that fts5yy_default[S] should be used instead. +** The (A) formula is preferred. The B formula is used instead if: +** (1) The fts5yy_shift_ofst[S]+X value is out of range, or +** (2) fts5yy_lookahead[fts5yy_shift_ofst[S]+X] is not equal to X, or +** (3) fts5yy_shift_ofst[S] equal fts5YY_SHIFT_USE_DFLT. +** (Implementation note: fts5YY_SHIFT_USE_DFLT is chosen so that +** fts5YY_SHIFT_USE_DFLT+X will be out of range for all possible lookaheads X. +** Hence only tests (1) and (2) need to be evaluated.) ** -** The formula above is for computing the action when the lookahead is +** The formulas above are for computing the action when the lookahead is ** a terminal symbol. If the lookahead is a non-terminal (as occurs after ** a reduce action) then the fts5yy_reduce_ofst[] array is used in place of ** the fts5yy_shift_ofst[] array and fts5YY_REDUCE_USE_DFLT is used in place of @@ -178829,48 +182776,50 @@ typedef union { ** fts5yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define fts5YY_ACTTAB_COUNT (78) +#define fts5YY_ACTTAB_COUNT (85) static const fts5YYACTIONTYPE fts5yy_action[] = { - /* 0 */ 89, 15, 46, 5, 48, 24, 12, 19, 23, 14, - /* 10 */ 46, 5, 48, 24, 20, 21, 23, 43, 46, 5, - /* 20 */ 48, 24, 6, 18, 23, 17, 46, 5, 48, 24, - /* 30 */ 75, 7, 23, 25, 46, 5, 48, 24, 62, 47, - /* 40 */ 23, 48, 24, 7, 11, 23, 9, 3, 4, 2, - /* 50 */ 62, 50, 52, 44, 64, 3, 4, 2, 49, 4, - /* 60 */ 2, 1, 23, 11, 16, 9, 12, 2, 10, 61, - /* 70 */ 53, 59, 62, 60, 22, 13, 55, 8, + /* 0 */ 98, 16, 51, 5, 53, 27, 83, 7, 26, 15, + /* 10 */ 51, 5, 53, 27, 13, 69, 26, 48, 51, 5, + /* 20 */ 53, 27, 19, 11, 26, 9, 20, 51, 5, 53, + /* 30 */ 27, 13, 22, 26, 28, 51, 5, 53, 27, 68, + /* 40 */ 1, 26, 19, 11, 17, 9, 52, 10, 53, 27, + /* 50 */ 23, 24, 26, 54, 3, 4, 2, 26, 6, 21, + /* 60 */ 49, 71, 3, 4, 2, 7, 56, 59, 55, 59, + /* 70 */ 4, 2, 12, 69, 58, 60, 18, 67, 62, 69, + /* 80 */ 25, 66, 8, 14, 2, }; static const fts5YYCODETYPE fts5yy_lookahead[] = { - /* 0 */ 15, 16, 17, 18, 19, 20, 10, 11, 23, 16, - /* 10 */ 17, 18, 19, 20, 23, 24, 23, 16, 17, 18, - /* 20 */ 19, 20, 22, 23, 23, 16, 17, 18, 19, 20, - /* 30 */ 5, 6, 23, 16, 17, 18, 19, 20, 13, 17, - /* 40 */ 23, 19, 20, 6, 8, 23, 10, 1, 2, 3, - /* 50 */ 13, 9, 10, 7, 0, 1, 2, 3, 19, 2, - /* 60 */ 3, 6, 23, 8, 21, 10, 10, 3, 10, 25, - /* 70 */ 10, 10, 13, 25, 12, 10, 7, 5, + /* 0 */ 16, 17, 18, 19, 20, 21, 5, 6, 24, 17, + /* 10 */ 18, 19, 20, 21, 11, 14, 24, 17, 18, 19, + /* 20 */ 20, 21, 8, 9, 24, 11, 17, 18, 19, 20, + /* 30 */ 21, 11, 12, 24, 17, 18, 19, 20, 21, 26, + /* 40 */ 6, 24, 8, 9, 22, 11, 18, 11, 20, 21, + /* 50 */ 24, 25, 24, 20, 1, 2, 3, 24, 23, 24, + /* 60 */ 7, 0, 1, 2, 3, 6, 10, 11, 10, 11, + /* 70 */ 2, 3, 9, 14, 11, 11, 22, 26, 7, 14, + /* 80 */ 13, 11, 5, 11, 3, }; -#define fts5YY_SHIFT_USE_DFLT (-5) -#define fts5YY_SHIFT_COUNT (25) -#define fts5YY_SHIFT_MIN (-4) -#define fts5YY_SHIFT_MAX (72) -static const signed char fts5yy_shift_ofst[] = { - /* 0 */ 55, 55, 55, 55, 55, 36, -4, 56, 58, 25, - /* 10 */ 37, 60, 59, 59, 46, 54, 42, 57, 62, 61, - /* 20 */ 62, 69, 65, 62, 72, 64, +#define fts5YY_SHIFT_USE_DFLT (85) +#define fts5YY_SHIFT_COUNT (28) +#define fts5YY_SHIFT_MIN (0) +#define fts5YY_SHIFT_MAX (81) +static const unsigned char fts5yy_shift_ofst[] = { + /* 0 */ 34, 34, 34, 34, 34, 14, 20, 3, 36, 1, + /* 10 */ 59, 64, 64, 65, 65, 53, 61, 56, 58, 63, + /* 20 */ 68, 67, 70, 67, 71, 72, 67, 77, 81, }; -#define fts5YY_REDUCE_USE_DFLT (-16) -#define fts5YY_REDUCE_COUNT (13) -#define fts5YY_REDUCE_MIN (-15) -#define fts5YY_REDUCE_MAX (48) +#define fts5YY_REDUCE_USE_DFLT (-17) +#define fts5YY_REDUCE_COUNT (14) +#define fts5YY_REDUCE_MIN (-16) +#define fts5YY_REDUCE_MAX (54) static const signed char fts5yy_reduce_ofst[] = { - /* 0 */ -15, -7, 1, 9, 17, 22, -9, 0, 39, 44, - /* 10 */ 44, 43, 44, 48, + /* 0 */ -16, -8, 0, 9, 17, 28, 26, 35, 33, 13, + /* 10 */ 13, 22, 54, 13, 51, }; static const fts5YYACTIONTYPE fts5yy_default[] = { - /* 0 */ 88, 88, 88, 88, 88, 69, 82, 88, 88, 87, - /* 10 */ 87, 88, 87, 87, 88, 88, 88, 66, 80, 88, - /* 20 */ 81, 88, 88, 78, 88, 65, + /* 0 */ 97, 97, 97, 97, 97, 76, 91, 97, 97, 96, + /* 10 */ 96, 97, 97, 96, 96, 97, 97, 97, 97, 97, + /* 20 */ 73, 89, 97, 90, 97, 97, 87, 97, 72, }; /********** End of lemon-generated parsing tables *****************************/ @@ -178977,11 +182926,11 @@ static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){ static const char *const fts5yyTokenName[] = { "$", "OR", "AND", "NOT", "TERM", "COLON", "LP", "RP", - "LCP", "RCP", "STRING", "COMMA", - "PLUS", "STAR", "error", "input", - "expr", "cnearset", "exprlist", "nearset", - "colset", "colsetlist", "nearphrases", "phrase", - "neardist_opt", "star_opt", + "MINUS", "LCP", "RCP", "STRING", + "COMMA", "PLUS", "STAR", "error", + "input", "expr", "cnearset", "exprlist", + "nearset", "colset", "colsetlist", "nearphrases", + "phrase", "neardist_opt", "star_opt", }; #endif /* NDEBUG */ @@ -178999,20 +182948,22 @@ static const char *const fts5yyRuleName[] = { /* 7 */ "exprlist ::= exprlist cnearset", /* 8 */ "cnearset ::= nearset", /* 9 */ "cnearset ::= colset COLON nearset", - /* 10 */ "colset ::= LCP colsetlist RCP", - /* 11 */ "colset ::= STRING", - /* 12 */ "colsetlist ::= colsetlist STRING", - /* 13 */ "colsetlist ::= STRING", - /* 14 */ "nearset ::= phrase", - /* 15 */ "nearset ::= STRING LP nearphrases neardist_opt RP", - /* 16 */ "nearphrases ::= phrase", - /* 17 */ "nearphrases ::= nearphrases phrase", - /* 18 */ "neardist_opt ::=", - /* 19 */ "neardist_opt ::= COMMA STRING", - /* 20 */ "phrase ::= phrase PLUS STRING star_opt", - /* 21 */ "phrase ::= STRING star_opt", - /* 22 */ "star_opt ::= STAR", - /* 23 */ "star_opt ::=", + /* 10 */ "colset ::= MINUS LCP colsetlist RCP", + /* 11 */ "colset ::= LCP colsetlist RCP", + /* 12 */ "colset ::= STRING", + /* 13 */ "colset ::= MINUS STRING", + /* 14 */ "colsetlist ::= colsetlist STRING", + /* 15 */ "colsetlist ::= STRING", + /* 16 */ "nearset ::= phrase", + /* 17 */ "nearset ::= STRING LP nearphrases neardist_opt RP", + /* 18 */ "nearphrases ::= phrase", + /* 19 */ "nearphrases ::= nearphrases phrase", + /* 20 */ "neardist_opt ::=", + /* 21 */ "neardist_opt ::= COMMA STRING", + /* 22 */ "phrase ::= phrase PLUS STRING star_opt", + /* 23 */ "phrase ::= STRING star_opt", + /* 24 */ "star_opt ::= STAR", + /* 25 */ "star_opt ::=", }; #endif /* NDEBUG */ @@ -179059,6 +183010,31 @@ static int fts5yyGrowStack(fts5yyParser *p){ # define fts5YYMALLOCARGTYPE size_t #endif +/* Initialize a new parser that has already been allocated. +*/ +static void sqlite3Fts5ParserInit(void *fts5yypParser){ + fts5yyParser *pParser = (fts5yyParser*)fts5yypParser; +#ifdef fts5YYTRACKMAXSTACKDEPTH + pParser->fts5yyhwm = 0; +#endif +#if fts5YYSTACKDEPTH<=0 + pParser->fts5yytos = NULL; + pParser->fts5yystack = NULL; + pParser->fts5yystksz = 0; + if( fts5yyGrowStack(pParser) ){ + pParser->fts5yystack = &pParser->fts5yystk0; + pParser->fts5yystksz = 1; + } +#endif +#ifndef fts5YYNOERRORRECOVERY + pParser->fts5yyerrcnt = -1; +#endif + pParser->fts5yytos = pParser->fts5yystack; + pParser->fts5yystack[0].stateno = 0; + pParser->fts5yystack[0].major = 0; +} + +#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK /* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like @@ -179074,28 +183050,11 @@ static int fts5yyGrowStack(fts5yyParser *p){ static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE)){ fts5yyParser *pParser; pParser = (fts5yyParser*)(*mallocProc)( (fts5YYMALLOCARGTYPE)sizeof(fts5yyParser) ); - if( pParser ){ -#ifdef fts5YYTRACKMAXSTACKDEPTH - pParser->fts5yyhwm = 0; -#endif -#if fts5YYSTACKDEPTH<=0 - pParser->fts5yytos = NULL; - pParser->fts5yystack = NULL; - pParser->fts5yystksz = 0; - if( fts5yyGrowStack(pParser) ){ - pParser->fts5yystack = &pParser->fts5yystk0; - pParser->fts5yystksz = 1; - } -#endif -#ifndef fts5YYNOERRORRECOVERY - pParser->fts5yyerrcnt = -1; -#endif - pParser->fts5yytos = pParser->fts5yystack; - pParser->fts5yystack[0].stateno = 0; - pParser->fts5yystack[0].major = 0; - } + if( pParser ) sqlite3Fts5ParserInit(pParser); return pParser; } +#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */ + /* The following function deletes the "minor type" or semantic value ** associated with a symbol. The symbol can be either a terminal @@ -179122,33 +183081,33 @@ static void fts5yy_destructor( ** inside the C code. */ /********* Begin destructor definitions ***************************************/ - case 15: /* input */ + case 16: /* input */ { (void)pParse; } break; - case 16: /* expr */ - case 17: /* cnearset */ - case 18: /* exprlist */ + case 17: /* expr */ + case 18: /* cnearset */ + case 19: /* exprlist */ { - sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy18)); + sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24)); } break; - case 19: /* nearset */ - case 22: /* nearphrases */ + case 20: /* nearset */ + case 23: /* nearphrases */ { - sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy26)); + sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46)); } break; - case 20: /* colset */ - case 21: /* colsetlist */ + case 21: /* colset */ + case 22: /* colsetlist */ { - sqlite3_free((fts5yypminor->fts5yy3)); + sqlite3_free((fts5yypminor->fts5yy11)); } break; - case 23: /* phrase */ + case 24: /* phrase */ { - sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy11)); + sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53)); } break; /********* End destructor definitions *****************************************/ @@ -179177,6 +183136,18 @@ static void fts5yy_pop_parser_stack(fts5yyParser *pParser){ fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor); } +/* +** Clear all secondary memory allocations from the parser +*/ +static void sqlite3Fts5ParserFinalize(void *p){ + fts5yyParser *pParser = (fts5yyParser*)p; + while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser); +#if fts5YYSTACKDEPTH<=0 + if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack); +#endif +} + +#ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK /* ** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. @@ -179189,16 +183160,13 @@ static void sqlite3Fts5ParserFree( void *p, /* The parser to be deleted */ void (*freeProc)(void*) /* Function used to reclaim memory */ ){ - fts5yyParser *pParser = (fts5yyParser*)p; #ifndef fts5YYPARSEFREENEVERNULL - if( pParser==0 ) return; -#endif - while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser); -#if fts5YYSTACKDEPTH<=0 - if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack); + if( p==0 ) return; #endif - (*freeProc)((void*)pParser); + sqlite3Fts5ParserFinalize(p); + (*freeProc)(p); } +#endif /* sqlite3Fts5Parser_ENGINEALWAYSONSTACK */ /* ** Return the peak depth of the stack for a parser. @@ -179225,50 +183193,47 @@ static unsigned int fts5yy_find_shift_action( assert( stateno <= fts5YY_SHIFT_COUNT ); do{ i = fts5yy_shift_ofst[stateno]; - if( i==fts5YY_SHIFT_USE_DFLT ) return fts5yy_default[stateno]; assert( iLookAhead!=fts5YYNOCODE ); i += iLookAhead; if( i<0 || i>=fts5YY_ACTTAB_COUNT || fts5yy_lookahead[i]!=iLookAhead ){ - if( iLookAhead>0 ){ #ifdef fts5YYFALLBACK - fts5YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", - fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]); - } -#endif - assert( fts5yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ - iLookAhead = iFallback; - continue; + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE, "%sFALLBACK %s => %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], fts5yyTokenName[iFallback]); } +#endif + assert( fts5yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } #endif #ifdef fts5YYWILDCARD - { - int j = i - iLookAhead + fts5YYWILDCARD; - if( + { + int j = i - iLookAhead + fts5YYWILDCARD; + if( #if fts5YY_SHIFT_MIN+fts5YYWILDCARD<0 - j>=0 && + j>=0 && #endif #if fts5YY_SHIFT_MAX+fts5YYWILDCARD>=fts5YY_ACTTAB_COUNT - j0 + ){ #ifndef NDEBUG - if( fts5yyTraceFILE ){ - fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n", - fts5yyTracePrompt, fts5yyTokenName[iLookAhead], - fts5yyTokenName[fts5YYWILDCARD]); - } -#endif /* NDEBUG */ - return fts5yy_action[j]; + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n", + fts5yyTracePrompt, fts5yyTokenName[iLookAhead], + fts5yyTokenName[fts5YYWILDCARD]); } +#endif /* NDEBUG */ + return fts5yy_action[j]; } -#endif /* fts5YYWILDCARD */ } +#endif /* fts5YYWILDCARD */ return fts5yy_default[stateno]; }else{ return fts5yy_action[i]; @@ -179312,7 +183277,6 @@ static int fts5yy_find_reduce_action( */ static void fts5yyStackOverflow(fts5yyParser *fts5yypParser){ sqlite3Fts5ParserARG_FETCH; - fts5yypParser->fts5yytos--; #ifndef NDEBUG if( fts5yyTraceFILE ){ fprintf(fts5yyTraceFILE,"%sStack Overflow!\n",fts5yyTracePrompt); @@ -179367,12 +183331,14 @@ static void fts5yy_shift( #endif #if fts5YYSTACKDEPTH>0 if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5YYSTACKDEPTH] ){ + fts5yypParser->fts5yytos--; fts5yyStackOverflow(fts5yypParser); return; } #else if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz] ){ if( fts5yyGrowStack(fts5yypParser) ){ + fts5yypParser->fts5yytos--; fts5yyStackOverflow(fts5yypParser); return; } @@ -179395,30 +183361,32 @@ static const struct { fts5YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */ unsigned char nrhs; /* Number of right-hand side symbols in the rule */ } fts5yyRuleInfo[] = { - { 15, 1 }, - { 16, 3 }, - { 16, 3 }, - { 16, 3 }, - { 16, 3 }, { 16, 1 }, - { 18, 1 }, - { 18, 2 }, - { 17, 1 }, { 17, 3 }, - { 20, 3 }, - { 20, 1 }, - { 21, 2 }, - { 21, 1 }, + { 17, 3 }, + { 17, 3 }, + { 17, 3 }, + { 17, 1 }, { 19, 1 }, - { 19, 5 }, - { 22, 1 }, + { 19, 2 }, + { 18, 1 }, + { 18, 3 }, + { 21, 4 }, + { 21, 3 }, + { 21, 1 }, + { 21, 2 }, { 22, 2 }, - { 24, 0 }, - { 24, 2 }, - { 23, 4 }, + { 22, 1 }, + { 20, 1 }, + { 20, 5 }, + { 23, 1 }, { 23, 2 }, - { 25, 1 }, { 25, 0 }, + { 25, 2 }, + { 24, 4 }, + { 24, 2 }, + { 26, 1 }, + { 26, 0 }, }; static void fts5yy_accept(fts5yyParser*); /* Forward Declaration */ @@ -179483,120 +183451,131 @@ static void fts5yy_reduce( /********** Begin reduce actions **********************************************/ fts5YYMINORTYPE fts5yylhsminor; case 0: /* input ::= expr */ -{ sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy18); } +{ sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); } break; case 1: /* expr ::= expr AND expr */ { - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_AND, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); } - fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 2: /* expr ::= expr OR expr */ { - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_OR, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); } - fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 3: /* expr ::= expr NOT expr */ { - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18, 0); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_NOT, fts5yymsp[-2].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24, 0); } - fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 4: /* expr ::= LP expr RP */ -{fts5yymsp[-2].minor.fts5yy18 = fts5yymsp[-1].minor.fts5yy18;} +{fts5yymsp[-2].minor.fts5yy24 = fts5yymsp[-1].minor.fts5yy24;} break; case 5: /* expr ::= exprlist */ case 6: /* exprlist ::= cnearset */ fts5yytestcase(fts5yyruleno==6); -{fts5yylhsminor.fts5yy18 = fts5yymsp[0].minor.fts5yy18;} - fts5yymsp[0].minor.fts5yy18 = fts5yylhsminor.fts5yy18; +{fts5yylhsminor.fts5yy24 = fts5yymsp[0].minor.fts5yy24;} + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 7: /* exprlist ::= exprlist cnearset */ { - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy18, fts5yymsp[0].minor.fts5yy18); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseImplicitAnd(pParse, fts5yymsp[-1].minor.fts5yy24, fts5yymsp[0].minor.fts5yy24); } - fts5yymsp[-1].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 8: /* cnearset ::= nearset */ { - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); } - fts5yymsp[0].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 9: /* cnearset ::= colset COLON nearset */ { - sqlite3Fts5ParseSetColset(pParse, fts5yymsp[0].minor.fts5yy26, fts5yymsp[-2].minor.fts5yy3); - fts5yylhsminor.fts5yy18 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy26); + sqlite3Fts5ParseSetColset(pParse, fts5yymsp[0].minor.fts5yy46, fts5yymsp[-2].minor.fts5yy11); + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); } - fts5yymsp[-2].minor.fts5yy18 = fts5yylhsminor.fts5yy18; + fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; - case 10: /* colset ::= LCP colsetlist RCP */ -{ fts5yymsp[-2].minor.fts5yy3 = fts5yymsp[-1].minor.fts5yy3; } + case 10: /* colset ::= MINUS LCP colsetlist RCP */ +{ + fts5yymsp[-3].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); +} + break; + case 11: /* colset ::= LCP colsetlist RCP */ +{ fts5yymsp[-2].minor.fts5yy11 = fts5yymsp[-1].minor.fts5yy11; } + break; + case 12: /* colset ::= STRING */ +{ + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); +} + fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 11: /* colset ::= STRING */ + case 13: /* colset ::= MINUS STRING */ { - fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yymsp[-1].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); } - fts5yymsp[0].minor.fts5yy3 = fts5yylhsminor.fts5yy3; break; - case 12: /* colsetlist ::= colsetlist STRING */ + case 14: /* colsetlist ::= colsetlist STRING */ { - fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy3, &fts5yymsp[0].minor.fts5yy0); } - fts5yymsp[-1].minor.fts5yy3 = fts5yylhsminor.fts5yy3; + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); } + fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 13: /* colsetlist ::= STRING */ + case 15: /* colsetlist ::= STRING */ { - fts5yylhsminor.fts5yy3 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } - fts5yymsp[0].minor.fts5yy3 = fts5yylhsminor.fts5yy3; + fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; - case 14: /* nearset ::= phrase */ -{ fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); } - fts5yymsp[0].minor.fts5yy26 = fts5yylhsminor.fts5yy26; + case 16: /* nearset ::= phrase */ +{ fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } + fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 15: /* nearset ::= STRING LP nearphrases neardist_opt RP */ + case 17: /* nearset ::= STRING LP nearphrases neardist_opt RP */ { sqlite3Fts5ParseNear(pParse, &fts5yymsp[-4].minor.fts5yy0); - sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy26, &fts5yymsp[-1].minor.fts5yy0); - fts5yylhsminor.fts5yy26 = fts5yymsp[-2].minor.fts5yy26; + sqlite3Fts5ParseSetDistance(pParse, fts5yymsp[-2].minor.fts5yy46, &fts5yymsp[-1].minor.fts5yy0); + fts5yylhsminor.fts5yy46 = fts5yymsp[-2].minor.fts5yy46; } - fts5yymsp[-4].minor.fts5yy26 = fts5yylhsminor.fts5yy26; + fts5yymsp[-4].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 16: /* nearphrases ::= phrase */ + case 18: /* nearphrases ::= phrase */ { - fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy11); + fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } - fts5yymsp[0].minor.fts5yy26 = fts5yylhsminor.fts5yy26; + fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 17: /* nearphrases ::= nearphrases phrase */ + case 19: /* nearphrases ::= nearphrases phrase */ { - fts5yylhsminor.fts5yy26 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy26, fts5yymsp[0].minor.fts5yy11); + fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, fts5yymsp[-1].minor.fts5yy46, fts5yymsp[0].minor.fts5yy53); } - fts5yymsp[-1].minor.fts5yy26 = fts5yylhsminor.fts5yy26; + fts5yymsp[-1].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; - case 18: /* neardist_opt ::= */ + case 20: /* neardist_opt ::= */ { fts5yymsp[1].minor.fts5yy0.p = 0; fts5yymsp[1].minor.fts5yy0.n = 0; } break; - case 19: /* neardist_opt ::= COMMA STRING */ + case 21: /* neardist_opt ::= COMMA STRING */ { fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } break; - case 20: /* phrase ::= phrase PLUS STRING star_opt */ + case 22: /* phrase ::= phrase PLUS STRING star_opt */ { - fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy11, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); + fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } - fts5yymsp[-3].minor.fts5yy11 = fts5yylhsminor.fts5yy11; + fts5yymsp[-3].minor.fts5yy53 = fts5yylhsminor.fts5yy53; break; - case 21: /* phrase ::= STRING star_opt */ + case 23: /* phrase ::= STRING star_opt */ { - fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy20); + fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } - fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; + fts5yymsp[-1].minor.fts5yy53 = fts5yylhsminor.fts5yy53; break; - case 22: /* star_opt ::= STAR */ -{ fts5yymsp[0].minor.fts5yy20 = 1; } + case 24: /* star_opt ::= STAR */ +{ fts5yymsp[0].minor.fts5yy4 = 1; } break; - case 23: /* star_opt ::= */ -{ fts5yymsp[1].minor.fts5yy20 = 0; } + case 25: /* star_opt ::= */ +{ fts5yymsp[1].minor.fts5yy4 = 0; } break; default: break; @@ -179790,7 +183769,7 @@ static void sqlite3Fts5Parser( fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion); fts5yymajor = fts5YYNOCODE; }else{ - while( fts5yypParser->fts5yytos >= &fts5yypParser->fts5yystack + while( fts5yypParser->fts5yytos >= fts5yypParser->fts5yystack && fts5yymx != fts5YYERRORSYMBOL && (fts5yyact = fts5yy_find_reduce_action( fts5yypParser->fts5yytos->stateno, @@ -180054,7 +184033,7 @@ static int fts5HighlightCb( if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); p->iOff = iEndOff; - if( iPositer.iEnd ){ + if( iPos>=p->iter.iStart && iPositer.iEnd ){ fts5HighlightAppend(&rc, p, p->zClose, -1); } } @@ -180111,6 +184090,118 @@ static void fts5HighlightFunction( ** End of highlight() implementation. **************************************************************************/ +/* +** Context object passed to the fts5SentenceFinderCb() function. +*/ +typedef struct Fts5SFinder Fts5SFinder; +struct Fts5SFinder { + int iPos; /* Current token position */ + int nFirstAlloc; /* Allocated size of aFirst[] */ + int nFirst; /* Number of entries in aFirst[] */ + int *aFirst; /* Array of first token in each sentence */ + const char *zDoc; /* Document being tokenized */ +}; + +/* +** Add an entry to the Fts5SFinder.aFirst[] array. Grow the array if +** necessary. Return SQLITE_OK if successful, or SQLITE_NOMEM if an +** error occurs. +*/ +static int fts5SentenceFinderAdd(Fts5SFinder *p, int iAdd){ + if( p->nFirstAlloc==p->nFirst ){ + int nNew = p->nFirstAlloc ? p->nFirstAlloc*2 : 64; + int *aNew; + + aNew = (int*)sqlite3_realloc(p->aFirst, nNew*sizeof(int)); + if( aNew==0 ) return SQLITE_NOMEM; + p->aFirst = aNew; + p->nFirstAlloc = nNew; + } + p->aFirst[p->nFirst++] = iAdd; + return SQLITE_OK; +} + +/* +** This function is an xTokenize() callback used by the auxiliary snippet() +** function. Its job is to identify tokens that are the first in a sentence. +** For each such token, an entry is added to the SFinder.aFirst[] array. +*/ +static int fts5SentenceFinderCb( + void *pContext, /* Pointer to HighlightContext object */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStartOff, /* Start offset of token */ + int iEndOff /* End offset of token */ +){ + int rc = SQLITE_OK; + + UNUSED_PARAM2(pToken, nToken); + UNUSED_PARAM(iEndOff); + + if( (tflags & FTS5_TOKEN_COLOCATED)==0 ){ + Fts5SFinder *p = (Fts5SFinder*)pContext; + if( p->iPos>0 ){ + int i; + char c = 0; + for(i=iStartOff-1; i>=0; i--){ + c = p->zDoc[i]; + if( c!=' ' && c!='\t' && c!='\n' && c!='\r' ) break; + } + if( i!=iStartOff-1 && (c=='.' || c==':') ){ + rc = fts5SentenceFinderAdd(p, p->iPos); + } + }else{ + rc = fts5SentenceFinderAdd(p, 0); + } + p->iPos++; + } + return rc; +} + +static int fts5SnippetScore( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + int nDocsize, /* Size of column in tokens */ + unsigned char *aSeen, /* Array with one element per query phrase */ + int iCol, /* Column to score */ + int iPos, /* Starting offset to score */ + int nToken, /* Max tokens per snippet */ + int *pnScore, /* OUT: Score */ + int *piPos /* OUT: Adjusted offset */ +){ + int rc; + int i; + int ip = 0; + int ic = 0; + int iOff = 0; + int iFirst = -1; + int nInst; + int nScore = 0; + int iLast = 0; + + rc = pApi->xInstCount(pFts, &nInst); + for(i=0; ixInst(pFts, i, &ip, &ic, &iOff); + if( rc==SQLITE_OK && ic==iCol && iOff>=iPos && iOff<(iPos+nToken) ){ + nScore += (aSeen[ip] ? 1 : 1000); + aSeen[ip] = 1; + if( iFirst<0 ) iFirst = iOff; + iLast = iOff + pApi->xPhraseSize(pFts, ip); + } + } + + *pnScore = nScore; + if( piPos ){ + int iAdj = iFirst - (nToken - (iLast-iFirst)) / 2; + if( (iAdj+nToken)>nDocsize ) iAdj = nDocsize - nToken; + if( iAdj<0 ) iAdj = 0; + *piPos = iAdj; + } + + return rc; +} + /* ** Implementation of snippet() function. */ @@ -180132,9 +184223,10 @@ static void fts5SnippetFunction( unsigned char *aSeen; /* Array of "seen instance" flags */ int iBestCol; /* Column containing best snippet */ int iBestStart = 0; /* First token of best snippet */ - int iBestLast; /* Last token of best snippet */ int nBestScore = 0; /* Score of best snippet */ int nColSize = 0; /* Total size of iBestCol in tokens */ + Fts5SFinder sFinder; /* Used to find the beginnings of sentences */ + int nCol; if( nVal!=5 ){ const char *zErr = "wrong number of arguments to function snippet()"; @@ -180142,13 +184234,13 @@ static void fts5SnippetFunction( return; } + nCol = pApi->xColumnCount(pFts); memset(&ctx, 0, sizeof(HighlightContext)); iCol = sqlite3_value_int(apVal[0]); ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]); ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); zEllips = (const char*)sqlite3_value_text(apVal[3]); nToken = sqlite3_value_int(apVal[4]); - iBestLast = nToken-1; iBestCol = (iCol>=0 ? iCol : 0); nPhrase = pApi->xPhraseCount(pFts); @@ -180156,65 +184248,94 @@ static void fts5SnippetFunction( if( aSeen==0 ){ rc = SQLITE_NOMEM; } - if( rc==SQLITE_OK ){ rc = pApi->xInstCount(pFts, &nInst); } - for(i=0; rc==SQLITE_OK && ixInst(pFts, i, &ip, &iSnippetCol, &iStart); - if( rc==SQLITE_OK && (iCol<0 || iSnippetCol==iCol) ){ - int nScore = 1000; - int iLast = iStart - 1 + pApi->xPhraseSize(pFts, ip); - int j; - aSeen[ip] = 1; - for(j=i+1; rc==SQLITE_OK && jxInst(pFts, j, &ip, &ic, &io); - iFinal = io + pApi->xPhraseSize(pFts, ip) - 1; - if( rc==SQLITE_OK && ic==iSnippetCol && iLastiLast ) iLast = iFinal; + memset(&sFinder, 0, sizeof(Fts5SFinder)); + for(i=0; ixColumnText(pFts, i, &sFinder.zDoc, &nDoc); + if( rc!=SQLITE_OK ) break; + rc = pApi->xTokenize(pFts, + sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb + ); + if( rc!=SQLITE_OK ) break; + rc = pApi->xColumnSize(pFts, i, &nDocsize); + if( rc!=SQLITE_OK ) break; + + for(ii=0; rc==SQLITE_OK && iixInst(pFts, ii, &ip, &ic, &io); + if( ic!=i || rc!=SQLITE_OK ) continue; + memset(aSeen, 0, nPhrase); + rc = fts5SnippetScore(pApi, pFts, nDocsize, aSeen, i, + io, nToken, &nScore, &iAdj + ); + if( rc==SQLITE_OK && nScore>nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = iAdj; + nColSize = nDocsize; } - } - if( rc==SQLITE_OK && nScore>nBestScore ){ - iBestCol = iSnippetCol; - iBestStart = iStart; - iBestLast = iLast; - nBestScore = nScore; + if( rc==SQLITE_OK && sFinder.nFirst && nDocsize>nToken ){ + for(jj=0; jj<(sFinder.nFirst-1); jj++){ + if( sFinder.aFirst[jj+1]>io ) break; + } + + if( sFinder.aFirst[jj]nBestScore ){ + nBestScore = nScore; + iBestCol = i; + iBestStart = sFinder.aFirst[jj]; + nColSize = nDocsize; + } + } + } } } } - if( rc==SQLITE_OK ){ - rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); - } if( rc==SQLITE_OK ){ rc = pApi->xColumnText(pFts, iBestCol, &ctx.zIn, &ctx.nIn); } + if( rc==SQLITE_OK && nColSize==0 ){ + rc = pApi->xColumnSize(pFts, iBestCol, &nColSize); + } if( ctx.zIn ){ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter); } - if( (iBestStart+nToken-1)>iBestLast ){ - iBestStart -= (iBestStart+nToken-1-iBestLast) / 2; - } - if( iBestStart+nToken>nColSize ){ - iBestStart = nColSize - nToken; - } - if( iBestStart<0 ) iBestStart = 0; - ctx.iRangeStart = iBestStart; ctx.iRangeEnd = iBestStart + nToken - 1; if( iBestStart>0 ){ fts5HighlightAppend(&rc, &ctx, zEllips, -1); } + + /* Advance iterator ctx.iter so that it points to the first coalesced + ** phrase instance at or following position iBestStart. */ + while( ctx.iter.iStart>=0 && ctx.iter.iStartxTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); } @@ -180223,15 +184344,15 @@ static void fts5SnippetFunction( }else{ fts5HighlightAppend(&rc, &ctx, zEllips, -1); } - - if( rc==SQLITE_OK ){ - sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); - }else{ - sqlite3_result_error_code(pCtx, rc); - } - sqlite3_free(ctx.zOut); } + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, (const char*)ctx.zOut, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_error_code(pCtx, rc); + } + sqlite3_free(ctx.zOut); sqlite3_free(aSeen); + sqlite3_free(sFinder.aFirst); } /************************************************************************/ @@ -181950,6 +186071,7 @@ static int fts5ExprGetToken( case ',': tok = FTS5_COMMA; break; case '+': tok = FTS5_PLUS; break; case '*': tok = FTS5_STAR; break; + case '-': tok = FTS5_MINUS; break; case '\0': tok = FTS5_EOF; break; case '"': { @@ -182528,49 +186650,61 @@ static int fts5ExprNearTest( ** Initialize all term iterators in the pNear object. If any term is found ** to match no documents at all, return immediately without initializing any ** further iterators. +** +** If an error occurs, return an SQLite error code. Otherwise, return +** SQLITE_OK. It is not considered an error if some term matches zero +** documents. */ static int fts5ExprNearInitAll( Fts5Expr *pExpr, Fts5ExprNode *pNode ){ Fts5ExprNearset *pNear = pNode->pNear; - int i, j; - int rc = SQLITE_OK; + int i; assert( pNode->bNomatch==0 ); - for(i=0; rc==SQLITE_OK && inPhrase; i++){ + for(i=0; inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - for(j=0; jnTerm; j++){ - Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; - Fts5ExprTerm *p; - int bEof = 1; - - for(p=pTerm; p && rc==SQLITE_OK; p=p->pSynonym){ - if( p->pIter ){ - sqlite3Fts5IterClose(p->pIter); - p->pIter = 0; - } - rc = sqlite3Fts5IndexQuery( - pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), - (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | - (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), - pNear->pColset, - &p->pIter - ); - assert( rc==SQLITE_OK || p->pIter==0 ); - if( p->pIter && 0==sqlite3Fts5IterEof(p->pIter) ){ - bEof = 0; + if( pPhrase->nTerm==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + }else{ + int j; + for(j=0; jnTerm; j++){ + Fts5ExprTerm *pTerm = &pPhrase->aTerm[j]; + Fts5ExprTerm *p; + int bHit = 0; + + for(p=pTerm; p; p=p->pSynonym){ + int rc; + if( p->pIter ){ + sqlite3Fts5IterClose(p->pIter); + p->pIter = 0; + } + rc = sqlite3Fts5IndexQuery( + pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), + (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | + (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), + pNear->pColset, + &p->pIter + ); + assert( (rc==SQLITE_OK)==(p->pIter!=0) ); + if( rc!=SQLITE_OK ) return rc; + if( 0==sqlite3Fts5IterEof(p->pIter) ){ + bHit = 1; + } } - } - if( bEof ){ - pNode->bEof = 1; - return rc; + if( bHit==0 ){ + pNode->bEof = 1; + return SQLITE_OK; + } } } } - return rc; + pNode->bEof = 0; + return SQLITE_OK; } /* @@ -182703,7 +186837,7 @@ static int fts5ExprNodeTest_STRING( } }else{ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - if( pIter->iRowid==iLast ) continue; + if( pIter->iRowid==iLast || pIter->bEof ) continue; bMatch = 0; if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ return rc; @@ -182880,7 +187014,10 @@ static int fts5ExprNodeNext_OR( || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0) ){ int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + pNode->bNomatch = 0; + return rc; + } } } } @@ -182911,7 +187048,10 @@ static int fts5ExprNodeTest_AND( if( cmp>0 ){ /* Advance pChild until it points to iLast or laster */ rc = fts5ExprNodeNext(pExpr, pChild, 1, iLast); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + pAnd->bNomatch = 0; + return rc; + } } /* If the child node is now at EOF, so is the parent AND node. Otherwise, @@ -182950,6 +187090,8 @@ static int fts5ExprNodeNext_AND( int rc = fts5ExprNodeNext(pExpr, pNode->apChild[0], bFromValid, iFrom); if( rc==SQLITE_OK ){ rc = fts5ExprNodeTest_AND(pExpr, pNode); + }else{ + pNode->bNomatch = 0; } return rc; } @@ -182992,6 +187134,9 @@ static int fts5ExprNodeNext_NOT( if( rc==SQLITE_OK ){ rc = fts5ExprNodeTest_NOT(pExpr, pNode); } + if( rc!=SQLITE_OK ){ + pNode->bNomatch = 0; + } return rc; } @@ -183114,7 +187259,10 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD /* If not at EOF but the current rowid occurs earlier than iFirst in ** the iteration order, move to document iFirst or later. */ - if( pRoot->bEof==0 && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){ + if( rc==SQLITE_OK + && 0==pRoot->bEof + && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 + ){ rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); } @@ -183368,7 +187516,7 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( rc = fts5ParseStringFromToken(pToken, &z); if( rc==SQLITE_OK ){ - int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_QUERY : 0); + int flags = FTS5_TOKENIZE_QUERY | (bPrefix ? FTS5_TOKENIZE_PREFIX : 0); int n; sqlite3Fts5Dequote(z); n = (int)strlen(z); @@ -183420,7 +187568,6 @@ static int sqlite3Fts5ExprClonePhrase( ){ int rc = SQLITE_OK; /* Return code */ Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ - int i; /* Used to iterate through phrase terms */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ @@ -183441,7 +187588,7 @@ static int sqlite3Fts5ExprClonePhrase( if( rc==SQLITE_OK ){ Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; if( pColsetOrig ){ - int nByte = sizeof(Fts5Colset) + pColsetOrig->nCol * sizeof(int); + int nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int); Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte); if( pColset ){ memcpy(pColset, pColsetOrig, nByte); @@ -183450,18 +187597,25 @@ static int sqlite3Fts5ExprClonePhrase( } } - for(i=0; rc==SQLITE_OK && inTerm; i++){ - int tflags = 0; - Fts5ExprTerm *p; - for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ - const char *zTerm = p->zTerm; - rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), - 0, 0); - tflags = FTS5_TOKEN_COLOCATED; - } - if( rc==SQLITE_OK ){ - sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + for(i=0; rc==SQLITE_OK && inTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + const char *zTerm = p->zTerm; + rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), + 0, 0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + } } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } if( rc==SQLITE_OK ){ @@ -183576,6 +187730,34 @@ static Fts5Colset *fts5ParseColset( return pNew; } +/* +** Allocate and return an Fts5Colset object specifying the inverse of +** the colset passed as the second argument. Free the colset passed +** as the second argument before returning. +*/ +static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p){ + Fts5Colset *pRet; + int nCol = pParse->pConfig->nCol; + + pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc, + sizeof(Fts5Colset) + sizeof(int)*nCol + ); + if( pRet ){ + int i; + int iOld = 0; + for(i=0; i=p->nCol || p->aiCol[iOld]!=i ){ + pRet->aiCol[pRet->nCol++] = i; + }else{ + iOld++; + } + } + } + + sqlite3_free(p); + return pRet; +} + static Fts5Colset *sqlite3Fts5ParseColset( Fts5Parse *pParse, /* Store SQLITE_NOMEM here if required */ Fts5Colset *pColset, /* Existing colset object */ @@ -185671,7 +189853,6 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ return pRet; } - /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -185680,6 +189861,18 @@ static void fts5DataRelease(Fts5Data *pData){ sqlite3_free(pData); } +static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ + Fts5Data *pRet = fts5DataRead(p, iRowid); + if( pRet ){ + if( pRet->szLeaf>pRet->nn ){ + p->rc = FTS5_CORRUPT; + fts5DataRelease(pRet); + pRet = 0; + } + } + return pRet; +} + static int fts5IndexPrepareStmt( Fts5Index *p, sqlite3_stmt **ppStmt, @@ -186488,7 +190681,7 @@ static void fts5SegIterNextPage( pIter->pLeaf = pIter->pNextLeaf; pIter->pNextLeaf = 0; }else if( pIter->iLeafPgno<=pSeg->pgnoLast ){ - pIter->pLeaf = fts5DataRead(p, + pIter->pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno) ); }else{ @@ -186991,14 +191184,13 @@ static void fts5SegIterNext( if( pLeaf->nn>pLeaf->szLeaf ){ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( &pLeaf->p[pLeaf->szLeaf], pIter->iEndofDoclist - ); + ); } - } else if( pLeaf->nn>pLeaf->szLeaf ){ pIter->iPgidxOff = pLeaf->szLeaf + fts5GetVarint32( &pLeaf->p[pLeaf->szLeaf], iOff - ); + ); pIter->iLeafOffset = iOff; pIter->iEndofDoclist = iOff; bNewTerm = 1; @@ -187032,6 +191224,7 @@ static void fts5SegIterNext( */ int nSz; assert( p->rc==SQLITE_OK ); + assert( pIter->iLeafOffset<=pIter->pLeaf->nn ); fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); pIter->bDel = (nSz & 0x0001); pIter->nPos = nSz>>1; @@ -187238,6 +191431,11 @@ static void fts5LeafSeek( iTermOff += nKeep; iOff = iTermOff; + if( iOff>=n ){ + p->rc = FTS5_CORRUPT; + return; + } + /* Read the nKeep field of the next term. */ fts5FastGetVarint32(a, iOff, nKeep); } @@ -187794,6 +191992,7 @@ static void fts5MultiIterNext( i64 iFrom /* Advance at least as far as this */ ){ int bUseFrom = bFrom; + assert( pIter->base.bEof==0 ); while( p->rc==SQLITE_OK ){ int iFirst = pIter->aFirst[1].iFirst; int bNewTerm = 0; @@ -188020,7 +192219,7 @@ static void fts5ChunkIterate( break; }else{ pgno++; - pData = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); + pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); if( pData==0 ) break; pChunk = &pData->p[4]; nChunk = MIN(nRem, pData->szLeaf - 4); @@ -188164,6 +192363,15 @@ static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){ } } +/* +** xSetOutputs callback used when the Fts5Colset object has nCol==0 (match +** against no columns at all). +*/ +static void fts5IterSetOutputs_ZeroColset(Fts5Iter *pIter, Fts5SegIter *pSeg){ + UNUSED_PARAM(pSeg); + pIter->base.nData = 0; +} + /* ** xSetOutputs callback used by detail=col when there is a column filter ** and there are 100 or more columns. Also called as a fallback from @@ -188269,6 +192477,10 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ pIter->xSetOutputs = fts5IterSetOutputs_Nocolset; } + else if( pIter->pColset->nCol==0 ){ + pIter->xSetOutputs = fts5IterSetOutputs_ZeroColset; + } + else if( pConfig->eDetail==FTS5_DETAIL_FULL ){ pIter->xSetOutputs = fts5IterSetOutputs_Full; } @@ -190769,7 +194981,7 @@ static void fts5IndexIntegrityCheckSegment( ** ignore this b-tree entry. Otherwise, load it into memory. */ if( iIdxLeafpgnoFirst ) continue; iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf); - pLeaf = fts5DataRead(p, iRow); + pLeaf = fts5LeafRead(p, iRow); if( pLeaf==0 ) break; /* Check that the leaf contains at least one term, and that it is equal @@ -194045,7 +198257,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2016-09-12 18:50:49 29dbef4b8585f753861a36d6dd102ca634197bd6", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37", -1, SQLITE_TRANSIENT); } static int fts5Init(sqlite3 *db){ @@ -194708,11 +198920,6 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap } } - /* Write the averages record */ - if( rc==SQLITE_OK ){ - rc = fts5StorageSaveTotals(p); - } - return rc; } @@ -194916,11 +199123,6 @@ static int sqlite3Fts5StorageIndexInsert( } sqlite3_free(buf.p); - /* Write the averages record */ - if( rc==SQLITE_OK ){ - rc = fts5StorageSaveTotals(p); - } - return rc; } @@ -195255,12 +199457,17 @@ static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ ** Flush any data currently held in-memory to disk. */ static int sqlite3Fts5StorageSync(Fts5Storage *p, int bCommit){ - if( bCommit && p->bTotalsValid ){ - int rc = fts5StorageSaveTotals(p); - p->bTotalsValid = 0; - if( rc!=SQLITE_OK ) return rc; + int rc = SQLITE_OK; + i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); + if( p->bTotalsValid ){ + rc = fts5StorageSaveTotals(p); + if( bCommit ) p->bTotalsValid = 0; + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexSync(p->pIndex, bCommit); } - return sqlite3Fts5IndexSync(p->pIndex, bCommit); + sqlite3_set_last_insert_rowid(p->pConfig->db, iLastRowid); + return rc; } static int sqlite3Fts5StorageRollback(Fts5Storage *p){ @@ -197527,8 +201734,19 @@ static int fts5VocabBestIndexMethod( } } - pInfo->idxNum = idxNum; + /* This virtual table always delivers results in ascending order of + ** the "term" column (column 0). So if the user has requested this + ** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the + ** sqlite3_index_info.orderByConsumed flag to tell the core the results + ** are already in sorted order. */ + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 + && pInfo->aOrderBy[0].desc==0 + ){ + pInfo->orderByConsumed = 1; + } + pInfo->idxNum = idxNum; return SQLITE_OK; } diff --git a/TMessagesProj/jni/sqlite/sqlite3.h b/TMessagesProj/jni/sqlite/sqlite3.h index cfbba628e04..7e6afcbf6fd 100644 --- a/TMessagesProj/jni/sqlite/sqlite3.h +++ b/TMessagesProj/jni/sqlite/sqlite3.h @@ -108,25 +108,26 @@ extern "C" { ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** -** Since version 3.6.18, SQLite source code has been stored in the +** Since [version 3.6.18] ([dateof:3.6.18]), +** SQLite source code has been stored in the ** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID -** string contains the date and time of the check-in (UTC) and an SHA1 -** hash of the entire source tree. +** string contains the date and time of the check-in (UTC) and a SHA1 +** or SHA3-256 hash of the entire source tree. ** ** See also: [sqlite3_libversion()], ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.14.2" -#define SQLITE_VERSION_NUMBER 3014002 -#define SQLITE_SOURCE_ID "2016-09-12 18:50:49 29dbef4b8585f753861a36d6dd102ca634197bd6" +#define SQLITE_VERSION "3.18.0" +#define SQLITE_VERSION_NUMBER 3018000 +#define SQLITE_SOURCE_ID "2017-03-28 18:48:43 424a0d380332858ee55bdebc4af3789f74e70a2b3ba1cf29d84b9b4bcf3e2e37" /* ** CAPI3REF: Run-Time Library Version Numbers -** KEYWORDS: sqlite3_version, sqlite3_sourceid +** KEYWORDS: sqlite3_version sqlite3_sourceid ** ** These interfaces provide the same information as the [SQLITE_VERSION], ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros @@ -258,7 +259,11 @@ typedef struct sqlite3 sqlite3; */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; - typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# ifdef SQLITE_UINT64_TYPE + typedef SQLITE_UINT64_TYPE sqlite_uint64; +# else + typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; +# endif #elif defined(_MSC_VER) || defined(__BORLANDC__) typedef __int64 sqlite_int64; typedef unsigned __int64 sqlite_uint64; @@ -452,7 +457,8 @@ SQLITE_API int sqlite3_exec( ** [result codes]. However, experience has shown that many of ** these result codes are too coarse-grained. They do not provide as ** much information about problems as programmers might like. In an effort to -** address this, newer versions of SQLite (version 3.3.8 and later) include +** address this, newer versions of SQLite (version 3.3.8 [dateof:3.3.8] +** and later) include ** support for additional result codes that provide more detailed information ** about errors. These [extended result codes] are enabled or disabled ** on a per database connection basis using the @@ -570,7 +576,7 @@ SQLITE_API int sqlite3_exec( ** file that were written at the application level might have changed ** and that adjacent bytes, even bytes within the same sector are ** guaranteed to be unchanged. The SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN -** flag indicate that a file cannot be deleted when open. The +** flag indicates that a file cannot be deleted when open. The ** SQLITE_IOCAP_IMMUTABLE flag indicates that the file is on ** read-only media and cannot be changed even by processes with ** elevated privileges. @@ -720,6 +726,9 @@ struct sqlite3_file { **
    • [SQLITE_IOCAP_ATOMIC64K] **
    • [SQLITE_IOCAP_SAFE_APPEND] **
    • [SQLITE_IOCAP_SEQUENTIAL] +**
    • [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN] +**
    • [SQLITE_IOCAP_POWERSAFE_OVERWRITE] +**
    • [SQLITE_IOCAP_IMMUTABLE] ** ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of @@ -976,6 +985,12 @@ struct sqlite3_io_methods { ** on whether or not the file has been renamed, moved, or deleted since it ** was first opened. ** +**
    • [[SQLITE_FCNTL_WIN32_GET_HANDLE]] +** The [SQLITE_FCNTL_WIN32_GET_HANDLE] opcode can be used to obtain the +** underlying native file handle associated with a file handle. This file +** control interprets its argument as a pointer to a native file handle and +** writes the resulting value there. +** **
    • [[SQLITE_FCNTL_WIN32_SET_HANDLE]] ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This ** opcode causes the xFileControl method to swap the file handle with the one @@ -1026,6 +1041,8 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_RBU 26 #define SQLITE_FCNTL_VFS_POINTER 27 #define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1969,13 +1986,36 @@ struct sqlite3_mem_methods { ** be a NULL pointer, in which case the new setting is not reported back. ** ** +**
      SQLITE_DBCONFIG_MAINDBNAME
      +**
      ^This option is used to change the name of the "main" database +** schema. ^The sole argument is a pointer to a constant UTF8 string +** which will become the new schema name in place of "main". ^SQLite +** does not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into this DBCONFIG option is unchanged +** until after the database connection closes. +**
      +** +**
      SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
      +**
      Usually, when a database in wal mode is closed or detached from a +** database handle, SQLite checks if this will mean that there are now no +** connections at all to the database. If so, it performs a checkpoint +** operation before closing the connection. This option may be used to +** override this behaviour. The first parameter passed to this operation +** is an integer - non-zero to disable checkpoints-on-close, or zero (the +** default) to enable them. The second parameter is a pointer to an integer +** into which is written 0 or 1 to indicate whether checkpoints-on-close +** have been disabled - 0 if they are not disabled, 1 if they are. +**
      +** ** */ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ #define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ /* @@ -2000,20 +2040,30 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); ** the table has a column of type [INTEGER PRIMARY KEY] then that column ** is another alias for the rowid. ** -** ^The sqlite3_last_insert_rowid(D) interface returns the [rowid] of the -** most recent successful [INSERT] into a rowid table or [virtual table] -** on database connection D. -** ^Inserts into [WITHOUT ROWID] tables are not recorded. -** ^If no successful [INSERT]s into rowid tables -** have ever occurred on the database connection D, -** then sqlite3_last_insert_rowid(D) returns zero. -** -** ^(If an [INSERT] occurs within a trigger or within a [virtual table] -** method, then this routine will return the [rowid] of the inserted -** row as long as the trigger or virtual table method is running. -** But once the trigger or virtual table method ends, the value returned -** by this routine reverts to what it was before the trigger or virtual -** table method began.)^ +** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of +** the most recent successful [INSERT] into a rowid table or [virtual table] +** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** zero. +** +** As well as being set automatically as rows are inserted into database +** tables, the value returned by this function may be set explicitly by +** [sqlite3_set_last_insert_rowid()] +** +** Some virtual table implementations may INSERT rows into rowid tables as +** part of committing a transaction (e.g. to flush data accumulated in memory +** to disk). In this case subsequent calls to this function return the rowid +** associated with these internal INSERT operations, which leads to +** unintuitive results. Virtual table implementations that do write to rowid +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** control to the user. +** +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned +** by this routine reverts to what it was before the trigger was fired.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a ** successful [INSERT] and does not change the value returned by this @@ -2040,6 +2090,16 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); */ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); +/* +** CAPI3REF: Set the Last Insert Rowid value. +** METHOD: sqlite3 +** +** The sqlite3_set_last_insert_rowid(D, R) method allows the application to +** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** without inserting a row into the database. +*/ +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); + /* ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 @@ -3364,9 +3424,9 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** [[SQLITE_LIMIT_VDBE_OP]] ^(
      SQLITE_LIMIT_VDBE_OP
      **
      The maximum number of instructions in a virtual machine program -** used to implement an SQL statement. This limit is not currently -** enforced, though that might be added in some future release of -** SQLite.
      )^ +** used to implement an SQL statement. If [sqlite3_prepare_v2()] or +** the equivalent tries to allocate space for more than this many opcodes +** in a single prepared statement, an SQLITE_NOMEM error is returned.)^ ** ** [[SQLITE_LIMIT_FUNCTION_ARG]] ^(
      SQLITE_LIMIT_FUNCTION_ARG
      **
      The maximum number of arguments on a function.
      )^ @@ -3404,6 +3464,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 + /* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} @@ -3577,6 +3638,10 @@ SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); ** sqlite3_stmt_readonly() to return true since, while those statements ** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. +** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since +** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and +** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so +** sqlite3_stmt_readonly() returns false for those commands. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -3859,8 +3924,12 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^This routine returns 0 if pStmt is an SQL -** statement that does not return data (for example an [UPDATE]). +** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement] returns no data (for example an [UPDATE]). +** ^However, just because this routine returns a positive number does not +** mean that one or more rows of data will be returned. ^A SELECT statement +** will always have a positive sqlite3_column_count() but depending on the +** WHERE clause constraints and the table content, it might return no rows. ** ** See also: [sqlite3_data_count()] */ @@ -4041,7 +4110,8 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** other than [SQLITE_ROW] before any subsequent invocation of ** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after version 3.6.23.1, sqlite3_step() began +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility ** break because any application that ever receives an SQLITE_MISUSE error @@ -5368,7 +5438,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook -** is not invoked when duplication rows are deleted because of an +** is not invoked when conflicting rows are deleted because of an ** [ON CONFLICT | ON CONFLICT REPLACE] clause. ^Nor is the update hook ** invoked when rows are deleted using the [truncate optimization]. ** The exceptions defined in this paragraph might change in a future @@ -5404,7 +5474,8 @@ SQLITE_API void *sqlite3_update_hook( ** and disabled if the argument is false.)^ ** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite version 3.5.0. In prior versions of SQLite, +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent @@ -5498,7 +5569,8 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** from the heap. ** )^ ** -** Beginning with SQLite version 3.7.3, the soft heap limit is enforced +** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]), +** the soft heap limit is enforced ** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] ** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], ** the soft heap limit is enforced on every memory allocation. Without @@ -5892,13 +5964,15 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite version 3.8.2. If a virtual table extension is +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** If a virtual table extension is ** used with an SQLite version earlier than 3.8.2, the results of attempting ** to read or write the estimatedRows field are undefined (but are likely ** to included crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for version 3.9.0. It may therefore only be used if +** was added for [version 3.9.0] ([dateof:3.9.0]). +** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. */ @@ -6146,6 +6220,12 @@ typedef struct sqlite3_blob sqlite3_blob; ** [database connection] error code and message accessible via ** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** +** A BLOB referenced by sqlite3_blob_open() may be read using the +** [sqlite3_blob_read()] interface and modified by using +** [sqlite3_blob_write()]. The [BLOB handle] can be moved to a +** different row of the same table using the [sqlite3_blob_reopen()] +** interface. However, the column, table, or database of a [BLOB handle] +** cannot be changed after the [BLOB handle] is opened. ** ** ^(If the row that a BLOB handle points to is modified by an ** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects @@ -6169,6 +6249,10 @@ typedef struct sqlite3_blob sqlite3_blob; ** ** To avoid a resource leak, every open [BLOB handle] should eventually ** be released by a call to [sqlite3_blob_close()]. +** +** See also: [sqlite3_blob_close()], +** [sqlite3_blob_reopen()], [sqlite3_blob_read()], +** [sqlite3_blob_bytes()], [sqlite3_blob_write()]. */ SQLITE_API int sqlite3_blob_open( sqlite3*, @@ -6184,11 +6268,11 @@ SQLITE_API int sqlite3_blob_open( ** CAPI3REF: Move a BLOB Handle to a New Row ** METHOD: sqlite3_blob ** -** ^This function is used to move an existing blob handle so that it points +** ^This function is used to move an existing [BLOB handle] so that it points ** to a different row of the same database table. ^The new row is identified ** by the rowid value passed as the second argument. Only the row can be ** changed. ^The database, table and column on which the blob handle is open -** remain the same. Moving an existing blob handle to a new row can be +** remain the same. Moving an existing [BLOB handle] to a new row is ** faster than closing the existing handle and opening a new one. ** ** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] - @@ -6596,7 +6680,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ -#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_random() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ #define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ #define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ #define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ @@ -6700,6 +6784,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 #define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 #define SQLITE_TESTCTRL_NEVER_CORRUPT 20 #define SQLITE_TESTCTRL_VDBE_COVERAGE 21 #define SQLITE_TESTCTRL_BYTEORDER 22 @@ -8116,7 +8201,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The [sqlite3_preupdate_hook()] interface registers a callback function ** that is invoked prior to each [INSERT], [UPDATE], and [DELETE] operation -** on a [rowid table]. +** on a database table. ** ^At most one preupdate hook may be registered at a time on a single ** [database connection]; each call to [sqlite3_preupdate_hook()] overrides ** the previous setting. @@ -8125,9 +8210,9 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ^The third parameter to [sqlite3_preupdate_hook()] is passed through as ** the first parameter to callbacks. ** -** ^The preupdate hook only fires for changes to [rowid tables]; the preupdate -** hook is not invoked for changes to [virtual tables] or [WITHOUT ROWID] -** tables. +** ^The preupdate hook only fires for changes to real database tables; the +** preupdate hook is not invoked for changes to [virtual tables] or to +** system tables like sqlite_master or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. @@ -8141,12 +8226,16 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. -** ^The sixth parameter to the preupdate callback is the initial [rowid] of the -** row being changes for SQLITE_UPDATE and SQLITE_DELETE changes and is -** undefined for SQLITE_INSERT changes. -** ^The seventh parameter to the preupdate callback is the final [rowid] of -** the row being changed for SQLITE_UPDATE and SQLITE_INSERT changes and is -** undefined for SQLITE_DELETE changes. +** +** For an UPDATE or DELETE operation on a [rowid table], the sixth +** parameter passed to the preupdate callback is the initial [rowid] of the +** row being modified or deleted. For an INSERT operation on a rowid table, +** or any operation on a WITHOUT ROWID table, the value of the sixth +** parameter is undefined. For an INSERT or UPDATE on a rowid table the +** seventh parameter is the final rowid value of the row being inserted +** or updated. The value of the seventh parameter passed to the callback +** function is not defined for operations on WITHOUT ROWID tables, or for +** INSERT operations on rowid tables. ** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces @@ -8186,7 +8275,8 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** See also: [sqlite3_update_hook()] */ -SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( +#if defined(SQLITE_ENABLE_PREUPDATE_HOOK) +SQLITE_API void *sqlite3_preupdate_hook( sqlite3 *db, void(*xPreUpdate)( void *pCtx, /* Copy of third arg to preupdate_hook() */ @@ -8199,10 +8289,11 @@ SQLITE_API SQLITE_EXPERIMENTAL void *sqlite3_preupdate_hook( ), void* ); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_count(sqlite3 *); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_depth(sqlite3 *); -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_count(sqlite3 *); +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +#endif /* ** CAPI3REF: Low-level system error code @@ -8218,7 +8309,7 @@ SQLITE_API int sqlite3_system_errno(sqlite3*); /* ** CAPI3REF: Database Snapshot -** KEYWORDS: {snapshot} +** KEYWORDS: {snapshot} {sqlite3_snapshot} ** EXPERIMENTAL ** ** An instance of the snapshot object records the state of a [WAL mode] @@ -8242,7 +8333,9 @@ SQLITE_API int sqlite3_system_errno(sqlite3*); ** to an historical snapshot (if possible). The destructor for ** sqlite3_snapshot objects is [sqlite3_snapshot_free()]. */ -typedef struct sqlite3_snapshot sqlite3_snapshot; +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; /* ** CAPI3REF: Record A Database Snapshot @@ -8253,9 +8346,32 @@ typedef struct sqlite3_snapshot sqlite3_snapshot; ** schema S in database connection D. ^On success, the ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. -** ^If schema S of [database connection] D is not a [WAL mode] database -** that is in a read transaction, then [sqlite3_snapshot_get(D,S,P)] -** leaves the *P value unchanged and returns an appropriate [error code]. +** If there is not already a read-transaction open on schema S when +** this function is called, one is opened automatically. +** +** The following must be true for this function to succeed. If any of +** the following statements are false when sqlite3_snapshot_get() is +** called, SQLITE_ERROR is returned. The final value of *P is undefined +** in this case. +** +**
        +**
      • The database handle must be in [autocommit mode]. +** +**
      • Schema S of [database connection] D must be a [WAL mode] database. +** +**
      • There must not be a write transaction open on schema S of database +** connection D. +** +**
      • One or more transactions must have been written to the current wal +** file since it was created on disk (by any connection). This means +** that a snapshot cannot be taken on a wal mode database with no wal +** file immediately after it is first opened. At least one transaction +** must be written to it first. +**
      +** +** This function may also return SQLITE_NOMEM. If it is called with the +** database handle in autocommit mode but fails for some other reason, +** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to ** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()] @@ -8348,6 +8464,28 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( sqlite3_snapshot *p2 ); +/* +** CAPI3REF: Recover snapshots from a wal file +** EXPERIMENTAL +** +** If all connections disconnect from a database file but do not perform +** a checkpoint, the existing wal file is opened along with the database +** file the next time the database is opened. At this point it is only +** possible to successfully call sqlite3_snapshot_open() to open the most +** recent snapshot of the database (the one at the head of the wal file), +** even though the wal file may contain other valid snapshots for which +** clients have sqlite3_snapshot handles. +** +** This function attempts to scan the wal file associated with database zDb +** of database handle db and make all valid snapshots available to +** sqlite3_snapshot_open(). It is an error if there is already a read +** transaction open on the database, or if the database is not a wal mode +** database. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. @@ -8533,7 +8671,7 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; ** attached database. It is not an error if database zDb is not attached ** to the database when the session object is created. */ -int sqlite3session_create( +SQLITE_API int sqlite3session_create( sqlite3 *db, /* Database handle */ const char *zDb, /* Name of db (e.g. "main") */ sqlite3_session **ppSession /* OUT: New session object */ @@ -8551,7 +8689,7 @@ int sqlite3session_create( ** are attached is closed. Refer to the documentation for ** [sqlite3session_create()] for details. */ -void sqlite3session_delete(sqlite3_session *pSession); +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); /* @@ -8571,7 +8709,7 @@ void sqlite3session_delete(sqlite3_session *pSession); ** The return value indicates the final state of the session object: 0 if ** the session is disabled, or 1 if it is enabled. */ -int sqlite3session_enable(sqlite3_session *pSession, int bEnable); +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); /* ** CAPI3REF: Set Or Clear the Indirect Change Flag @@ -8600,7 +8738,7 @@ int sqlite3session_enable(sqlite3_session *pSession, int bEnable); ** The return value indicates the final state of the indirect flag: 0 if ** it is clear, or 1 if it is set. */ -int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); /* ** CAPI3REF: Attach A Table To A Session Object @@ -8630,7 +8768,7 @@ int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); ** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. */ -int sqlite3session_attach( +SQLITE_API int sqlite3session_attach( sqlite3_session *pSession, /* Session object */ const char *zTab /* Table name */ ); @@ -8639,12 +8777,12 @@ int sqlite3session_attach( ** CAPI3REF: Set a table filter on a Session Object. ** ** The second argument (xFilter) is the "filter callback". For changes to rows -** in tables that are not attached to the Session oject, the filter is called +** in tables that are not attached to the Session object, the filter is called ** to determine whether changes to the table's rows should be tracked or not. ** If xFilter returns 0, changes is not tracked. Note that once a table is ** attached, xFilter will not be called again. */ -void sqlite3session_table_filter( +SQLITE_API void sqlite3session_table_filter( sqlite3_session *pSession, /* Session object */ int(*xFilter)( void *pCtx, /* Copy of third arg to _filter_table() */ @@ -8757,7 +8895,7 @@ void sqlite3session_table_filter( ** another field of the same row is updated while the session is enabled, the ** resulting changeset will contain an UPDATE change that updates both fields. */ -int sqlite3session_changeset( +SQLITE_API int sqlite3session_changeset( sqlite3_session *pSession, /* Session object */ int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ @@ -8801,7 +8939,8 @@ int sqlite3session_changeset( ** the from-table, a DELETE record is added to the session object. ** **
    • For each row (primary key) that exists in both tables, but features -** different in each, an UPDATE record is added to the session. +** different non-PK values in each, an UPDATE record is added to the +** session. ** ** ** To clarify, if this function is called and then a changeset constructed @@ -8818,7 +8957,7 @@ int sqlite3session_changeset( ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ -int sqlite3session_diff( +SQLITE_API int sqlite3session_diff( sqlite3_session *pSession, const char *zFromDb, const char *zTbl, @@ -8854,7 +8993,7 @@ int sqlite3session_diff( ** a single table are grouped together, tables appear in the order in which ** they were attached to the session object). */ -int sqlite3session_patchset( +SQLITE_API int sqlite3session_patchset( sqlite3_session *pSession, /* Session object */ int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ void **ppPatchset /* OUT: Buffer containing changeset */ @@ -8875,7 +9014,7 @@ int sqlite3session_patchset( ** guaranteed that a call to sqlite3session_changeset() will return a ** changeset containing zero changes. */ -int sqlite3session_isempty(sqlite3_session *pSession); +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); /* ** CAPI3REF: Create An Iterator To Traverse A Changeset @@ -8905,12 +9044,12 @@ int sqlite3session_isempty(sqlite3_session *pSession); ** [sqlite3changeset_invert()] functions, all changes within the changeset ** that apply to a single table are grouped together. This means that when ** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visted +** this function, all changes that relate to a single table are visited ** consecutively. There is no chance that the iterator will visit a change ** the applies to table X, then one for table Y, and then later on visit ** another change for table X. */ -int sqlite3changeset_start( +SQLITE_API int sqlite3changeset_start( sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ int nChangeset, /* Size of changeset blob in bytes */ void *pChangeset /* Pointer to blob containing changeset */ @@ -8939,7 +9078,7 @@ int sqlite3changeset_start( ** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or ** SQLITE_NOMEM. */ -int sqlite3changeset_next(sqlite3_changeset_iter *pIter); +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); /* ** CAPI3REF: Obtain The Current Operation From A Changeset Iterator @@ -8967,7 +9106,7 @@ int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** SQLite error code is returned. The values of the output variables may not ** be trusted in this case. */ -int sqlite3changeset_op( +SQLITE_API int sqlite3changeset_op( sqlite3_changeset_iter *pIter, /* Iterator object */ const char **pzTab, /* OUT: Pointer to table name */ int *pnCol, /* OUT: Number of columns in table */ @@ -8992,7 +9131,7 @@ int sqlite3changeset_op( ** 0x01 if the corresponding column is part of the tables primary key, or ** 0x00 if it is not. ** -** If argumet pnCol is not NULL, then *pnCol is set to the number of columns +** If argument pnCol is not NULL, then *pnCol is set to the number of columns ** in the table. ** ** If this function is called when the iterator does not point to a valid @@ -9000,7 +9139,7 @@ int sqlite3changeset_op( ** SQLITE_OK is returned and the output variables populated as described ** above. */ -int sqlite3changeset_pk( +SQLITE_API int sqlite3changeset_pk( sqlite3_changeset_iter *pIter, /* Iterator object */ unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ int *pnCol /* OUT: Number of entries in output array */ @@ -9030,7 +9169,7 @@ int sqlite3changeset_pk( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_old( +SQLITE_API int sqlite3changeset_old( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ @@ -9063,7 +9202,7 @@ int sqlite3changeset_old( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_new( +SQLITE_API int sqlite3changeset_new( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ @@ -9090,7 +9229,7 @@ int sqlite3changeset_new( ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ -int sqlite3changeset_conflict( +SQLITE_API int sqlite3changeset_conflict( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int iVal, /* Column number */ sqlite3_value **ppValue /* OUT: Value from conflicting row */ @@ -9106,7 +9245,7 @@ int sqlite3changeset_conflict( ** ** In all other cases this function returns SQLITE_MISUSE. */ -int sqlite3changeset_fk_conflicts( +SQLITE_API int sqlite3changeset_fk_conflicts( sqlite3_changeset_iter *pIter, /* Changeset iterator */ int *pnOut /* OUT: Number of FK violations */ ); @@ -9139,7 +9278,7 @@ int sqlite3changeset_fk_conflicts( ** // An error has occurred ** } */ -int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); /* ** CAPI3REF: Invert A Changeset @@ -9169,7 +9308,7 @@ int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); ** WARNING/TODO: This function currently assumes that the input is a valid ** changeset. If it is not, the results are undefined. */ -int sqlite3changeset_invert( +SQLITE_API int sqlite3changeset_invert( int nIn, const void *pIn, /* Input changeset */ int *pnOut, void **ppOut /* OUT: Inverse of input */ ); @@ -9198,7 +9337,7 @@ int sqlite3changeset_invert( ** ** Refer to the sqlite3_changegroup documentation below for details. */ -int sqlite3changeset_concat( +SQLITE_API int sqlite3changeset_concat( int nA, /* Number of bytes in buffer pA */ void *pA, /* Pointer to buffer containing changeset A */ int nB, /* Number of bytes in buffer pB */ @@ -9209,12 +9348,12 @@ int sqlite3changeset_concat( /* -** Changegroup handle. +** CAPI3REF: Changegroup Handle */ typedef struct sqlite3_changegroup sqlite3_changegroup; /* -** CAPI3REF: Combine two or more changesets into a single changeset. +** CAPI3REF: Create A New Changegroup Object ** ** An sqlite3_changegroup object is used to combine two or more changesets ** (or patchsets) into a single changeset (or patchset). A single changegroup @@ -9251,6 +9390,8 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; int sqlite3changegroup_new(sqlite3_changegroup **pp); /* +** CAPI3REF: Add A Changeset To A Changegroup +** ** Add all changes within the changeset (or patchset) in buffer pData (size ** nData bytes) to the changegroup. ** @@ -9265,7 +9406,7 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); ** apply to the same row as a change already present in the changegroup if ** the two rows have the same primary key. ** -** Changes to rows that that do not already appear in the changegroup are +** Changes to rows that do not already appear in the changegroup are ** simply copied into it. Or, if both the new changeset and the changegroup ** contain changes that apply to a single row, the final contents of the ** changegroup depends on the type of each change, as follows: @@ -9326,6 +9467,8 @@ int sqlite3changegroup_new(sqlite3_changegroup **pp); int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); /* +** CAPI3REF: Obtain A Composite Changeset From A Changegroup +** ** Obtain a buffer containing a changeset (or patchset) representing the ** current contents of the changegroup. If the inputs to the changegroup ** were themselves changesets, the output is a changeset. Or, if the @@ -9354,7 +9497,7 @@ int sqlite3changegroup_output( ); /* -** Delete a changegroup object. +** CAPI3REF: Delete A Changegroup Object */ void sqlite3changegroup_delete(sqlite3_changegroup*); @@ -9382,7 +9525,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); **
        **
      • The table has the same name as the name recorded in the ** changeset, and -**
      • The table has the same number of columns as recorded in the +**
      • The table has at least as many columns as recorded in the ** changeset, and **
      • The table has primary key columns in the same position as ** recorded in the changeset. @@ -9427,7 +9570,11 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** If a row with matching primary key values is found, but one or more of ** the non-primary key fields contains a value different from the original ** row value stored in the changeset, the conflict-handler function is -** invoked with [SQLITE_CHANGESET_DATA] as the second argument. +** invoked with [SQLITE_CHANGESET_DATA] as the second argument. If the +** database table has more columns than are recorded in the changeset, +** only the values of those non-primary key fields are compared against +** the current database contents - any trailing database table columns +** are ignored. ** ** If no row with matching primary key values is found in the database, ** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] @@ -9442,7 +9589,9 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** **
        INSERT Changes
        ** For each INSERT change, an attempt is made to insert the new row into -** the database. +** the database. If the changeset row contains fewer fields than the +** database table, the trailing fields are populated with their default +** values. ** ** If the attempt to insert the row fails because the database already ** contains a row with the same primary key values, the conflict handler @@ -9460,13 +9609,13 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** For each UPDATE change, this function checks if the target database ** contains a row with the same primary key value (or values) as the ** original row values stored in the changeset. If it does, and the values -** stored in all non-primary key columns also match the values stored in -** the changeset the row is updated within the target database. +** stored in all modified non-primary key columns also match the values +** stored in the changeset the row is updated within the target database. ** ** If a row with matching primary key values is found, but one or more of -** the non-primary key fields contains a value different from an original -** row value stored in the changeset, the conflict-handler function is -** invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since +** the modified non-primary key fields contains a value different from an +** original row value stored in the changeset, the conflict-handler function +** is invoked with [SQLITE_CHANGESET_DATA] as the second argument. Since ** UPDATE changes only contain values for non-primary key fields that are ** to be modified, only those fields need to match the original values to ** avoid the SQLITE_CHANGESET_DATA conflict-handler callback. @@ -9494,7 +9643,7 @@ void sqlite3changegroup_delete(sqlite3_changegroup*); ** rolled back, restoring the target database to its original state, and an ** SQLite error code returned. */ -int sqlite3changeset_apply( +SQLITE_API int sqlite3changeset_apply( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ @@ -9695,7 +9844,7 @@ int sqlite3changeset_apply( ** parameter set to a value less than or equal to zero. Other than this, ** no guarantees are made as to the size of the chunks of data returned. */ -int sqlite3changeset_apply_strm( +SQLITE_API int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ void *pIn, /* First arg for xInput */ @@ -9710,7 +9859,7 @@ int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ); -int sqlite3changeset_concat_strm( +SQLITE_API int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, int (*xInputB)(void *pIn, void *pData, int *pnData), @@ -9718,23 +9867,23 @@ int sqlite3changeset_concat_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changeset_invert_strm( +SQLITE_API int sqlite3changeset_invert_strm( int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3changeset_start_strm( +SQLITE_API int sqlite3changeset_start_strm( sqlite3_changeset_iter **pp, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); -int sqlite3session_changeset_strm( +SQLITE_API int sqlite3session_changeset_strm( sqlite3_session *pSession, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -int sqlite3session_patchset_strm( +SQLITE_API int sqlite3session_patchset_strm( sqlite3_session *pSession, int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut diff --git a/TMessagesProj/jni/tgnet/ApiScheme.cpp b/TMessagesProj/jni/tgnet/ApiScheme.cpp index 7df7db70783..0e2b47c728a 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.cpp +++ b/TMessagesProj/jni/tgnet/ApiScheme.cpp @@ -135,6 +135,12 @@ void TL_config::readParams(NativeByteBuffer *stream, bool &error) { if ((flags & 1) != 0) { tmp_sessions = stream->readInt32(&error); } + pinned_dialogs_count_max = stream->readInt32(&error); + call_receive_timeout_ms = stream->readInt32(&error); + call_ring_timeout_ms = stream->readInt32(&error); + call_connect_timeout_ms = stream->readInt32(&error); + call_packet_timeout_ms = stream->readInt32(&error); + me_url_prefix = stream->readString(&error); magic = stream->readUint32(&error); if (magic != 0x1cb5c415) { error = true; @@ -183,6 +189,12 @@ void TL_config::serializeToStream(NativeByteBuffer *stream) { if ((flags & 1) != 0) { stream->writeInt32(tmp_sessions); } + stream->writeInt32(pinned_dialogs_count_max); + stream->writeInt32(call_receive_timeout_ms); + stream->writeInt32(call_ring_timeout_ms); + stream->writeInt32(call_connect_timeout_ms); + stream->writeInt32(call_packet_timeout_ms); + stream->writeString(me_url_prefix); stream->writeInt32(0x1cb5c415); count = (uint32_t) disabled_features.size(); stream->writeInt32(count); diff --git a/TMessagesProj/jni/tgnet/ApiScheme.h b/TMessagesProj/jni/tgnet/ApiScheme.h index 988bcd66492..66c82baf1fe 100644 --- a/TMessagesProj/jni/tgnet/ApiScheme.h +++ b/TMessagesProj/jni/tgnet/ApiScheme.h @@ -70,7 +70,7 @@ class TL_disabledFeature : public TLObject { class TL_config : public TLObject { public: - static const uint32_t constructor = 0x9a6b2e2a; + static const uint32_t constructor = 0xcb601684; int32_t flags; int32_t date; @@ -95,6 +95,12 @@ class TL_config : public TLObject { int32_t rating_e_decay; int32_t stickers_recent_limit; int32_t tmp_sessions; + int32_t pinned_dialogs_count_max; + int32_t call_receive_timeout_ms; + int32_t call_ring_timeout_ms; + int32_t call_connect_timeout_ms; + int32_t call_packet_timeout_ms; + std::string me_url_prefix; std::vector> disabled_features; static TL_config *TLdeserialize(NativeByteBuffer *stream, uint32_t constructor, bool &error); diff --git a/TMessagesProj/jni/tgnet/Connection.cpp b/TMessagesProj/jni/tgnet/Connection.cpp index 9ea752b8d61..3848d29a3a9 100644 --- a/TMessagesProj/jni/tgnet/Connection.cpp +++ b/TMessagesProj/jni/tgnet/Connection.cpp @@ -259,7 +259,7 @@ void Connection::connect() { lastPacketLength = 0; wasConnected = false; hasSomeDataSinceLastConnect = false; - openConnection(hostAddress, hostPort, ipv6); + openConnection(hostAddress, hostPort, ipv6, ConnectionsManager::getInstance().currentNetworkType); if (connectionType == ConnectionTypePush) { if (isTryingNextPort) { setTimeout(20); diff --git a/TMessagesProj/jni/tgnet/ConnectionSession.cpp b/TMessagesProj/jni/tgnet/ConnectionSession.cpp index 8a215e40f0f..a7689dbab4b 100644 --- a/TMessagesProj/jni/tgnet/ConnectionSession.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionSession.cpp @@ -49,12 +49,14 @@ uint32_t ConnectionSession::generateMessageSeqNo(bool increment) { } bool ConnectionSession::isMessageIdProcessed(int64_t messageId) { - return std::find(processedMessageIds.begin(), processedMessageIds.end(), messageId) != processedMessageIds.end(); + return !(messageId & 1) || minProcessedMessageId != 0 && messageId < minProcessedMessageId || std::find(processedMessageIds.begin(), processedMessageIds.end(), messageId) != processedMessageIds.end(); } void ConnectionSession::addProcessedMessageId(int64_t messageId) { if (processedMessageIds.size() > 300) { + std::sort(processedMessageIds.begin(), processedMessageIds.end()); processedMessageIds.erase(processedMessageIds.begin(), processedMessageIds.begin() + 100); + minProcessedMessageId = *(processedMessageIds.begin()); } processedMessageIds.push_back(messageId); } diff --git a/TMessagesProj/jni/tgnet/ConnectionSession.h b/TMessagesProj/jni/tgnet/ConnectionSession.h index 380304a973d..a7e38653f0a 100644 --- a/TMessagesProj/jni/tgnet/ConnectionSession.h +++ b/TMessagesProj/jni/tgnet/ConnectionSession.h @@ -32,6 +32,7 @@ class ConnectionSession { private: int64_t sessionId; uint32_t nextSeqNo = 0; + int64_t minProcessedMessageId = 0; std::vector processedMessageIds; std::vector messagesIdsForConfirmation; diff --git a/TMessagesProj/jni/tgnet/ConnectionSocket.cpp b/TMessagesProj/jni/tgnet/ConnectionSocket.cpp index 095ecd48458..227ab5e1087 100644 --- a/TMessagesProj/jni/tgnet/ConnectionSocket.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionSocket.cpp @@ -28,7 +28,7 @@ ConnectionSocket::ConnectionSocket() { outgoingByteStream = new ByteStream(); - lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMillis(); + lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis(); eventObject = new EventObject(this, EventObjectTypeConnection); } @@ -43,8 +43,10 @@ ConnectionSocket::~ConnectionSocket() { } } -void ConnectionSocket::openConnection(std::string address, uint16_t port, bool ipv6) { +void ConnectionSocket::openConnection(std::string address, uint16_t port, bool ipv6, int32_t networkType) { + currentNetworkType = networkType; int epolFd = ConnectionsManager::getInstance().epolFd; + ConnectionsManager::getInstance().attachConnection(this); if ((socketFd = socket(ipv6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0)) < 0) { DEBUG_E("connection(%p) can't create socket", this); @@ -108,7 +110,7 @@ bool ConnectionSocket::checkSocketError() { } void ConnectionSocket::closeSocket(int reason) { - lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMillis(); + lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis(); ConnectionsManager::getInstance().detachConnection(this); if (socketFd >= 0) { epoll_ctl(ConnectionsManager::getInstance().epolFd, EPOLL_CTL_DEL, socketFd, NULL); @@ -140,7 +142,10 @@ void ConnectionSocket::onEvent(uint32_t events) { } if (readCount > 0) { buffer->limit((uint32_t) readCount); - lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMillis(); + lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis(); + if (ConnectionsManager::getInstance().delegate != nullptr) { + ConnectionsManager::getInstance().delegate->onBytesReceived(readCount, currentNetworkType); + } onReceivedData(buffer); } if (readCount != READ_BUFFER_SIZE) { @@ -155,8 +160,7 @@ void ConnectionSocket::onEvent(uint32_t events) { return; } else { if (!onConnectedSent) { - ConnectionsManager::getInstance().attachConnection(this); - lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMillis(); + lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis(); onConnected(); onConnectedSent = true; } @@ -173,6 +177,9 @@ void ConnectionSocket::onEvent(uint32_t events) { closeSocket(1); return; } else { + if (ConnectionsManager::getInstance().delegate != nullptr) { + ConnectionsManager::getInstance().delegate->onBytesSent(sentLength, currentNetworkType); + } outgoingByteStream->discard((uint32_t) sentLength); adjustWriteOp(); } @@ -208,7 +215,7 @@ void ConnectionSocket::adjustWriteOp() { void ConnectionSocket::setTimeout(time_t time) { timeout = time; - lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMillis(); + lastEventTime = ConnectionsManager::getInstance().getCurrentTimeMonotonicMillis(); } void ConnectionSocket::checkTimeout(int64_t now) { diff --git a/TMessagesProj/jni/tgnet/ConnectionSocket.h b/TMessagesProj/jni/tgnet/ConnectionSocket.h index ca452faee39..6b44560afbe 100644 --- a/TMessagesProj/jni/tgnet/ConnectionSocket.h +++ b/TMessagesProj/jni/tgnet/ConnectionSocket.h @@ -25,7 +25,7 @@ class ConnectionSocket { virtual ~ConnectionSocket(); void writeBuffer(NativeByteBuffer *buffer); - void openConnection(std::string address, uint16_t port, bool ipv6); + void openConnection(std::string address, uint16_t port, bool ipv6, int32_t networkType); void setTimeout(time_t timeout); bool isDisconnected(); void dropConnection(); @@ -47,6 +47,7 @@ class ConnectionSocket { bool onConnectedSent = false; int64_t lastEventTime = 0; EventObject *eventObject; + int32_t currentNetworkType; bool checkSocketError(); void closeSocket(int reason); diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp index 5e85dc302e4..2f4ebf08825 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.cpp +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.cpp @@ -137,7 +137,7 @@ int ConnectionsManager::callEvents(int64_t now) { eventObject->onEvent(0); } else { int diff = (int) (eventObject->time - now); - return diff > 1000 ? 1000 : diff; + return diff > 1000 || diff < 0 ? 1000 : diff; } } } @@ -169,9 +169,9 @@ void ConnectionsManager::checkPendingTasks() { void ConnectionsManager::select() { checkPendingTasks(); - int eventsCount = epoll_wait(epolFd, epollEvents, 128, callEvents(getCurrentTimeMillis())); + int eventsCount = epoll_wait(epolFd, epollEvents, 128, callEvents(getCurrentTimeMonotonicMillis())); checkPendingTasks(); - int64_t now = getCurrentTimeMillis(); + int64_t now = getCurrentTimeMonotonicMillis(); callEvents(now); for (int32_t a = 0; a < eventsCount; a++) { EventObject *eventObject = (EventObject *) epollEvents[a].data.ptr; @@ -184,7 +184,7 @@ void ConnectionsManager::select() { Datacenter *datacenter = getDatacenterWithId(currentDatacenterId); if (pushConnectionEnabled) { - if ((sendingPushPing && abs(now - lastPushPingTime) >= 30000) || abs(now - lastPushPingTime) >= 60000 * 3 + 10000) { + if ((sendingPushPing && llabs(now - lastPushPingTime) >= 30000) || llabs(now - lastPushPingTime) >= 60000 * 3 + 10000) { lastPushPingTime = 0; sendingPushPing = false; if (datacenter != nullptr) { @@ -195,7 +195,7 @@ void ConnectionsManager::select() { } DEBUG_D("push ping timeout"); } - if (abs(now - lastPushPingTime) >= 60000 * 3) { + if (llabs(now - lastPushPingTime) >= 60000 * 3) { DEBUG_D("time for push ping"); lastPushPingTime = now; if (datacenter != nullptr) { @@ -204,7 +204,7 @@ void ConnectionsManager::select() { } } - if (lastPauseTime != 0 && abs(now - lastPauseTime) >= nextSleepTimeout) { + if (lastPauseTime != 0 && llabs(now - lastPauseTime) >= nextSleepTimeout) { bool dontSleep = !requestingSaltsForDc.empty(); if (!dontSleep) { for (requestsIter iter = runningRequests.begin(); iter != runningRequests.end(); iter++) { @@ -248,7 +248,7 @@ void ConnectionsManager::select() { } if (datacenter != nullptr) { if (datacenter->hasAuthKey()) { - if (abs(now - lastPingTime) >= 19000) { + if (llabs(now - lastPingTime) >= 19000) { lastPingTime = now; sendPing(datacenter, false); } @@ -270,7 +270,7 @@ void ConnectionsManager::scheduleTask(std::function task) { } void ConnectionsManager::scheduleEvent(EventObject *eventObject, uint32_t time) { - eventObject->time = getCurrentTimeMillis() + time; + eventObject->time = getCurrentTimeMonotonicMillis() + time; std::list::iterator iter; for (iter = events.begin(); iter != events.end(); iter++) { if ((*iter)->time > eventObject->time) { @@ -498,6 +498,11 @@ int64_t ConnectionsManager::getCurrentTimeMillis() { return (int64_t) timeSpec.tv_sec * 1000 + (int64_t) timeSpec.tv_nsec / 1000000; } +int64_t ConnectionsManager::getCurrentTimeMonotonicMillis() { + clock_gettime(CLOCK_MONOTONIC, &timeSpecMonotonic); + return (int64_t) timeSpecMonotonic.tv_sec * 1000 + (int64_t) timeSpecMonotonic.tv_nsec / 1000000; +} + int32_t ConnectionsManager::getCurrentTime() { return (int32_t) (getCurrentTimeMillis() / 1000) + timeDifference; } @@ -534,7 +539,7 @@ void ConnectionsManager::cleanUp() { TL_error *error = new TL_error(); error->code = -1000; error->text = ""; - request->onComplete(nullptr, error); + request->onComplete(nullptr, error, 0); delete error; } iter = requestsQueue.erase(iter); @@ -549,7 +554,7 @@ void ConnectionsManager::cleanUp() { TL_error *error = new TL_error(); error->code = -1000; error->text = ""; - request->onComplete(nullptr, error); + request->onComplete(nullptr, error, 0); delete error; } iter = runningRequests.erase(iter); @@ -593,7 +598,7 @@ void ConnectionsManager::onConnectionClosed(Connection *connection) { } else if (connection->getConnectionType() == ConnectionTypePush) { DEBUG_D("connection(%p) push connection closed", connection); sendingPushPing = false; - lastPushPingTime = getCurrentTimeMillis() - 60000 * 3 + 4000; + lastPushPingTime = getCurrentTimeMonotonicMillis() - 60000 * 3 + 4000; } } @@ -609,11 +614,11 @@ void ConnectionsManager::onConnectionConnected(Connection *connection) { if (datacenter->hasAuthKey()) { if (connection->getConnectionType() == ConnectionTypePush) { sendingPushPing = false; - lastPushPingTime = getCurrentTimeMillis(); + lastPushPingTime = getCurrentTimeMonotonicMillis(); sendPing(datacenter, true); } else { if (networkPaused && lastPauseTime != 0) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); } processRequestQueue(connection->getConnectionType(), datacenter->getDatacenterId()); } @@ -701,7 +706,7 @@ void ConnectionsManager::onConnectionDataReceived(Connection *connection, Native delete object; } } else { - if (length < 24 + 32 || !datacenter->decryptServerResponse(keyId, data->bytes() + mark + 8, data->bytes() + mark + 24, length - 24)) { + if (length < 24 + 32 || (length - 24) % 16 != 0 || !datacenter->decryptServerResponse(keyId, data->bytes() + mark + 8, data->bytes() + mark + 24, length - 24)) { DEBUG_E("connection(%p) unable to decrypt server response", connection); datacenter->switchTo443Port(); connection->suspendConnection(); @@ -877,14 +882,13 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag } else { TL_pong *response = (TL_pong *) message; if (response->ping_id == lastPingId) { - int64_t currentTime = getCurrentTimeMillis(); - int32_t diff = (int32_t) (currentTime / 1000) - pingTime; + int32_t diff = (int32_t) (getCurrentTimeMonotonicMillis() / 1000) - pingTime; if (abs(diff) < 10) { currentPingTime = (diff + currentPingTime) / 2; if (messageId != 0) { int64_t timeMessage = (int64_t) (messageId / 4294967296.0 * 1000); - timeDifference = (int32_t) ((timeMessage - currentTime) / 1000 - currentPingTime / 2); + timeDifference = (int32_t) ((timeMessage - getCurrentTimeMillis()) / 1000 - currentPingTime / 2); } } } @@ -895,7 +899,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag for (requestsIter iter = runningRequests.begin(); iter != runningRequests.end(); iter++) { Request *request = iter->get(); if (request->respondsToMessageId(requestMid)) { - request->onComplete(response, nullptr); + request->onComplete(response, nullptr, connection->currentNetworkType); request->completed = true; runningRequests.erase(iter); break; @@ -997,12 +1001,12 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag discardResponse = true; request->failedByFloodWait = waitTime; request->startTime = 0; - request->minStartTime = (int32_t) (getCurrentTimeMillis() / 1000 + waitTime); + request->minStartTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000 + waitTime); } else if (error->error_code == 400) { static std::string waitFailed = "MSG_WAIT_FAILED"; if (error->error_message.find(waitFailed) != std::string::npos) { discardResponse = true; - request->minStartTime = (int32_t) (getCurrentTimeMillis() / 1000 + 1); + request->minStartTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000 + 1); request->startTime = 0; } } @@ -1025,12 +1029,12 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag if (!discardResponse) { if (implicitError != nullptr || error2 != nullptr) { isError = true; - request->onComplete(nullptr, implicitError != nullptr ? implicitError : error2); + request->onComplete(nullptr, implicitError != nullptr ? implicitError : error2, connection->currentNetworkType); if (error2 != nullptr) { delete error2; } } else { - request->onComplete(response->result.get(), nullptr); + request->onComplete(response->result.get(), nullptr, connection->currentNetworkType); } } @@ -1177,7 +1181,7 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag if (request->completed) { break; } - int32_t currentTime = (int32_t) (getCurrentTimeMillis() / 1000); + int32_t currentTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); if (request->lastResendTime == 0 || abs(currentTime - request->lastResendTime) >= 60) { request->lastResendTime = currentTime; requestResend = true; @@ -1227,10 +1231,10 @@ void ConnectionsManager::processServerResponse(TLObject *message, int64_t messag } else if (typeInfo == typeid(TL_updatesTooLong)) { if (connection->connectionType == ConnectionTypePush) { if (networkPaused) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); DEBUG_D("received internal push: wakeup network in background"); } else if (lastPauseTime != 0) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); DEBUG_D("received internal push: reset sleep timeout"); } else { DEBUG_D("received internal push"); @@ -1269,7 +1273,7 @@ void ConnectionsManager::sendPing(Datacenter *datacenter, bool usePushConnection request->disconnect_delay = 60 * 7; } else { request->disconnect_delay = 35; - pingTime = (int32_t) (getCurrentTimeMillis() / 1000); + pingTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); } NetworkMessage *networkMessage = new NetworkMessage(); @@ -1648,7 +1652,7 @@ void ConnectionsManager::requestSaltsForDatacenter(Datacenter *datacenter) { requestingSaltsForDc.push_back(datacenter->getDatacenterId()); TL_get_future_salts *request = new TL_get_future_salts(); request->num = 32; - sendRequest(request, [&, datacenter](TLObject *response, TL_error *error) { + sendRequest(request, [&, datacenter](TLObject *response, TL_error *error, int32_t networkType) { std::vector::iterator iter = std::find(requestingSaltsForDc.begin(), requestingSaltsForDc.end(), datacenter->getDatacenterId()); if (iter != requestingSaltsForDc.end()) { requestingSaltsForDc.erase(iter); @@ -1681,7 +1685,7 @@ void ConnectionsManager::registerForInternalPushUpdates() { request->token_type = 7; request->token = to_string_uint64(pushSessionId); - sendRequest(request, [&](TLObject *response, TL_error *error) { + sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) { if (error == nullptr) { registeredForInternalPush = true; DEBUG_D("registered for internal push"); @@ -1722,7 +1726,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t genericConnection = defaultDatacenter->getGenericConnection(true); } - int32_t currentTime = (int32_t) (getCurrentTimeMillis() / 1000); + int32_t currentTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); uint32_t genericRunningRequestCount = 0; uint32_t uploadRunningRequestCount = 0; uint32_t downloadRunningRequestCount = 0; @@ -1857,7 +1861,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t TL_error *error = new TL_error(); error->code = -123; error->text = "RETRY_LIMIT"; - request->onComplete(nullptr, error); + request->onComplete(nullptr, error, connection->currentNetworkType); delete error; iter = runningRequests.erase(iter); continue; @@ -2044,7 +2048,7 @@ void ConnectionsManager::processRequestQueue(uint32_t connectionTypes, uint32_t request->messageId = generateMessageId(); request->serializedLength = requestLength; request->messageSeqNo = connection->generateMessageSeqNo(true); - request->startTime = (int32_t) (getCurrentTimeMillis() / 1000); + request->startTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); request->connectionToken = connection->getConnectionToken(); NetworkMessage *networkMessage = new NetworkMessage(); @@ -2231,11 +2235,11 @@ void ConnectionsManager::updateDcSettings(uint32_t dcNum) { if (updatingDcSettings) { return; } - updatingDcStartTime = (int32_t) (getCurrentTimeMillis() / 1000); + updatingDcStartTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000); updatingDcSettings = true; TL_help_getConfig *request = new TL_help_getConfig(); - sendRequest(request, [&](TLObject *response, TL_error *error) { + sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) { if (!updatingDcSettings) { return; } @@ -2246,7 +2250,7 @@ void ConnectionsManager::updateDcSettings(uint32_t dcNum) { if (updateIn <= 0) { updateIn = 120; } - lastDcUpdateTime = (int32_t) (getCurrentTimeMillis() / 1000) - DC_UPDATE_TIME + updateIn; + lastDcUpdateTime = (int32_t) (getCurrentTimeMonotonicMillis() / 1000) - DC_UPDATE_TIME + updateIn; struct DatacenterInfo { std::vector addressesIpv4; @@ -2332,7 +2336,7 @@ void ConnectionsManager::moveToDatacenter(uint32_t datacenterId) { if (currentUserId) { TL_auth_exportAuthorization *request = new TL_auth_exportAuthorization(); request->dc_id = datacenterId; - sendRequest(request, [&, datacenterId](TLObject *response, TL_error *error) { + sendRequest(request, [&, datacenterId](TLObject *response, TL_error *error, int32_t networkType) { if (error == nullptr) { movingAuthorization = std::move(((TL_auth_exportedAuthorization *) response)->bytes); authorizeOnMovingDatacenter(); @@ -2363,7 +2367,7 @@ void ConnectionsManager::authorizeOnMovingDatacenter() { TL_auth_importAuthorization *request = new TL_auth_importAuthorization(); request->id = currentUserId; request->bytes = std::move(movingAuthorization); - sendRequest(request, [&](TLObject *response, TL_error *error) { + sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) { if (error == nullptr) { authorizedOnMovingDatacenter(); } else { @@ -2391,8 +2395,10 @@ void ConnectionsManager::applyDatacenterAddress(uint32_t datacenterId, std::stri std::map ports; addresses.push_back(ipAddress); ports[ipAddress] = port; - datacenter->replaceAddressesAndPorts(addresses, ports, 0); datacenter->suspendConnections(); + datacenter->replaceAddressesAndPorts(addresses, ports, 0); + datacenter->resetAddressAndPortNum(); + saveConfig(); updateDcSettings(datacenterId); } }); @@ -2422,7 +2428,7 @@ void ConnectionsManager::setPushConnectionEnabled(bool value) { } } -void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection) { +void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType) { currentVersion = version; currentLayer = layer; currentApiId = apiId; @@ -2434,8 +2440,10 @@ void ConnectionsManager::init(uint32_t version, int32_t layer, int32_t apiId, st currentUserId = userId; currentLogPath = logPath; pushConnectionEnabled = enablePushConnection; + currentNetworkType = networkType; + networkAvailable = hasNetwork; if (isPaused) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); } if (!currentConfigPath.empty() && currentConfigPath.find_last_of('/') != currentConfigPath.size() - 1) { @@ -2455,15 +2463,16 @@ void ConnectionsManager::resumeNetwork(bool partial) { scheduleTask([&, partial] { if (partial) { if (networkPaused) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); networkPaused = false; DEBUG_D("wakeup network in background"); } else if (lastPauseTime != 0) { - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); networkPaused = false; DEBUG_D("reset sleep timeout"); } } else { + DEBUG_D("wakeup network"); lastPauseTime = 0; networkPaused = false; } @@ -2474,12 +2483,13 @@ void ConnectionsManager::pauseNetwork() { if (lastPauseTime != 0) { return; } - lastPauseTime = getCurrentTimeMillis(); + lastPauseTime = getCurrentTimeMonotonicMillis(); } -void ConnectionsManager::setNetworkAvailable(bool value) { - scheduleTask([&, value] { +void ConnectionsManager::setNetworkAvailable(bool value, int32_t type) { + scheduleTask([&, value, type] { networkAvailable = value; + currentNetworkType = type; if (!networkAvailable) { connectionState = ConnectionStateWaitingForNetwork; } else { @@ -2501,6 +2511,14 @@ void ConnectionsManager::setUseIpv6(bool value) { }); } +void ConnectionsManager::setMtProtoVersion(int version) { + mtProtoVersion = version; +} + +int32_t ConnectionsManager::getMtProtoVersion() { + return mtProtoVersion; +} + #ifdef ANDROID void ConnectionsManager::useJavaVM(JavaVM *vm, bool useJavaByteBuffers) { javaVm = vm; diff --git a/TMessagesProj/jni/tgnet/ConnectionsManager.h b/TMessagesProj/jni/tgnet/ConnectionsManager.h index 15a6ac364e5..dacfdb15d97 100644 --- a/TMessagesProj/jni/tgnet/ConnectionsManager.h +++ b/TMessagesProj/jni/tgnet/ConnectionsManager.h @@ -43,6 +43,7 @@ class ConnectionsManager { static ConnectionsManager &getInstance(); int64_t getCurrentTimeMillis(); + int64_t getCurrentTimeMonotonicMillis(); int32_t getCurrentTime(); int32_t getTimeDifference(); int32_t sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate); @@ -58,11 +59,13 @@ class ConnectionsManager { void switchBackend(); void resumeNetwork(bool partial); void pauseNetwork(); - void setNetworkAvailable(bool value); + void setNetworkAvailable(bool value, int32_t type); void setUseIpv6(bool value); - void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection); + void init(uint32_t version, int32_t layer, int32_t apiId, std::string deviceModel, std::string systemVersion, std::string appVersion, std::string langCode, std::string configPath, std::string logPath, int32_t userId, bool isPaused, bool enablePushConnection, bool hasNetwork, int32_t networkType); void updateDcSettings(uint32_t datacenterId); void setPushConnectionEnabled(bool value); + void setMtProtoVersion(int version); + int32_t getMtProtoVersion(); #ifdef ANDROID void sendRequest(TLObject *object, onCompleteFunc onComplete, onQuickAckFunc onQuickAck, uint32_t flags, uint32_t datacenterId, ConnectionType connetionType, bool immediate, int32_t requestToken, jobject ptr1, jobject ptr2); @@ -133,7 +136,7 @@ class ConnectionsManager { bool updatingDcSettings = false; int32_t updatingDcStartTime = 0; int32_t lastDcUpdateTime = 0; - int64_t lastPingTime = getCurrentTimeMillis(); + int64_t lastPingTime = getCurrentTimeMonotonicMillis(); bool networkPaused = false; int32_t nextSleepTimeout = CONNECTION_BACKGROUND_KEEP_TIME; int64_t lastPauseTime = 0; @@ -149,6 +152,7 @@ class ConnectionsManager { std::queue> pendingTasks; struct epoll_event *epollEvents; timespec timeSpec; + timespec timeSpecMonotonic; int32_t timeDifference = 0; int64_t lastOutgoingMessageId = 0; bool networkAvailable = true; @@ -164,6 +168,7 @@ class ConnectionsManager { std::vector requestingSaltsForDc; int32_t lastPingId = 0; + int32_t currentNetworkType = NETWORK_TYPE_WIFI; uint32_t currentVersion = 1; int32_t currentLayer = 34; int32_t currentApiId = 6; @@ -176,6 +181,7 @@ class ConnectionsManager { int32_t currentUserId = 0; bool registeredForInternalPush = false; bool pushConnectionEnabled = true; + int32_t mtProtoVersion = 2; ConnectiosManagerDelegate *delegate; diff --git a/TMessagesProj/jni/tgnet/Datacenter.cpp b/TMessagesProj/jni/tgnet/Datacenter.cpp index 60126945805..0cb457ed288 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.cpp +++ b/TMessagesProj/jni/tgnet/Datacenter.cpp @@ -28,17 +28,24 @@ static std::vector serverPublicKeys; static std::vector serverPublicKeysFingerprints; static BN_CTX *bnContext; +static SHA256_CTX sha256Ctx; Datacenter::Datacenter(uint32_t id) { datacenterId = id; for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) { + uploadConnection[a] = nullptr; + } + for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) { downloadConnections[a] = nullptr; } } Datacenter::Datacenter(NativeByteBuffer *data) { for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) { - downloadConnections[a] = nullptr; + uploadConnection[a] = nullptr; + } + for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) { + downloadConnections[a] = nullptr; } uint32_t currentVersion = data->readUint32(nullptr); if (currentVersion >= 2 && currentVersion <= 5) { @@ -340,6 +347,18 @@ void Datacenter::storeCurrentAddressAndPortNum() { buffer->reuse(); } +void Datacenter::resetAddressAndPortNum() { + currentPortNumIpv4 = 0; + currentAddressNumIpv4 = 0; + currentPortNumIpv6 = 0; + currentAddressNumIpv6 = 0; + currentPortNumIpv4Download = 0; + currentAddressNumIpv4Download = 0; + currentPortNumIpv6Download = 0; + currentAddressNumIpv6Download = 0; + storeCurrentAddressAndPortNum(); +} + void Datacenter::replaceAddressesAndPorts(std::vector &newAddresses, std::map &newPorts, uint32_t flags) { std::vector *addresses; if ((flags & 2) != 0) { @@ -518,8 +537,10 @@ void Datacenter::suspendConnections() { if (genericConnection != nullptr) { genericConnection->suspendConnection(); } - if (uploadConnection != nullptr) { - uploadConnection->suspendConnection(); + for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) { + if (uploadConnection[a] != nullptr) { + uploadConnection[a]->suspendConnection(); + } } for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) { if (downloadConnections[a] != nullptr) { @@ -532,8 +553,10 @@ void Datacenter::getSessions(std::vector &sessions) { if (genericConnection != nullptr) { sessions.push_back(genericConnection->getSissionId()); } - if (uploadConnection != nullptr) { - sessions.push_back(uploadConnection->getSissionId()); + for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) { + if (uploadConnection[a] != nullptr) { + sessions.push_back(uploadConnection[a]->getSissionId()); + } } for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) { if (downloadConnections[a] != nullptr) { @@ -546,8 +569,10 @@ void Datacenter::recreateSessions() { if (genericConnection != nullptr) { genericConnection->recreateSession(); } - if (uploadConnection != nullptr) { - uploadConnection->recreateSession(); + for (uint32_t a = 0; a < UPLOAD_CONNECTIONS_COUNT; a++) { + if (uploadConnection[a] != nullptr) { + uploadConnection[a]->recreateSession(); + } } for (uint32_t a = 0; a < DOWNLOAD_CONNECTIONS_COUNT; a++) { if (downloadConnections[a] != nullptr) { @@ -556,18 +581,18 @@ void Datacenter::recreateSessions() { } } -Connection *Datacenter::createDownloadConnection(uint32_t num) { +Connection *Datacenter::createDownloadConnection(uint8_t num) { if (downloadConnections[num] == nullptr) { downloadConnections[num] = new Connection(this, ConnectionTypeDownload); } return downloadConnections[num]; } -Connection *Datacenter::createUploadConnection() { - if (uploadConnection == nullptr) { - uploadConnection = new Connection(this, ConnectionTypeUpload); +Connection *Datacenter::createUploadConnection(uint8_t num) { + if (uploadConnection[num] == nullptr) { + uploadConnection[num] = new Connection(this, ConnectionTypeUpload); } - return uploadConnection; + return uploadConnection[num]; } Connection *Datacenter::createGenericConnection() { @@ -1330,32 +1355,53 @@ void Datacenter::sendAckRequest(int64_t messageId) { inline void generateMessageKey(uint8_t *authKey, uint8_t *messageKey, uint8_t *result, bool incoming) { uint32_t x = incoming ? 8 : 0; - static uint8_t sha[68]; - - memcpy(sha + 20, messageKey, 16); - memcpy(sha + 20 + 16, authKey + x, 32); - SHA1(sha + 20, 48, sha); - memcpy(result, sha, 8); - memcpy(result + 32, sha + 8, 12); - - memcpy(sha + 20, authKey + 32 + x, 16); - memcpy(sha + 20 + 16, messageKey, 16); - memcpy(sha + 20 + 16 + 16, authKey + 48 + x, 16); - SHA1(sha + 20, 48, sha); - memcpy(result + 8, sha + 8, 12); - memcpy(result + 32 + 12, sha, 8); - - memcpy(sha + 20, authKey + 64 + x, 32); - memcpy(sha + 20 + 32, messageKey, 16); - SHA1(sha + 20, 48, sha); - memcpy(result + 8 + 12, sha + 4, 12); - memcpy(result + 32 + 12 + 8, sha + 16, 4); - - memcpy(sha + 20, messageKey, 16); - memcpy(sha + 20 + 16, authKey + 96 + x, 32); - SHA1(sha + 20, 48, sha); - memcpy(result + 32 + 12 + 8 + 4, sha, 8); + switch (ConnectionsManager::getInstance().getMtProtoVersion()) { + case 2: + SHA256_Init(&sha256Ctx); + SHA256_Update(&sha256Ctx, messageKey, 16); + SHA256_Update(&sha256Ctx, authKey + x, 36); + SHA256_Final(sha, &sha256Ctx); + + SHA256_Init(&sha256Ctx); + SHA256_Update(&sha256Ctx, authKey + 40 + x, 36); + SHA256_Update(&sha256Ctx, messageKey, 16); + SHA256_Final(sha + 32, &sha256Ctx); + + memcpy(result, sha, 8); + memcpy(result + 8, sha + 32 + 8, 16); + memcpy(result + 8 + 16, sha + 24, 8); + + memcpy(result + 32, sha + 32, 8); + memcpy(result + 32 + 8, sha + 8, 16); + memcpy(result + 32 + 8 + 16, sha + 32 + 24, 8); + break; + default: + memcpy(sha + 20, messageKey, 16); + memcpy(sha + 20 + 16, authKey + x, 32); + SHA1(sha + 20, 48, sha); + memcpy(result, sha, 8); + memcpy(result + 32, sha + 8, 12); + + memcpy(sha + 20, authKey + 32 + x, 16); + memcpy(sha + 20 + 16, messageKey, 16); + memcpy(sha + 20 + 16 + 16, authKey + 48 + x, 16); + SHA1(sha + 20, 48, sha); + memcpy(result + 8, sha + 8, 12); + memcpy(result + 32 + 12, sha, 8); + + memcpy(sha + 20, authKey + 64 + x, 32); + memcpy(sha + 20 + 32, messageKey, 16); + SHA1(sha + 20, 48, sha); + memcpy(result + 8 + 12, sha + 4, 12); + memcpy(result + 32 + 12 + 8, sha + 16, 4); + + memcpy(sha + 20, messageKey, 16); + memcpy(sha + 20 + 16, authKey + 96 + x, 32); + SHA1(sha + 20, 48, sha); + memcpy(result + 32 + 12 + 8 + 4, sha, 8); + break; + } } NativeByteBuffer *Datacenter::createRequestsData(std::vector> &requests, int32_t *quickAckId, Connection *connection) { @@ -1414,17 +1460,20 @@ NativeByteBuffer *Datacenter::createRequestsData(std::vectorgenerateMessageSeqNo(false); } + int32_t mtProtoVersion = ConnectionsManager::getInstance().getMtProtoVersion(); uint32_t messageSize = messageBody->getObjectSize(); uint32_t additionalSize = (32 + messageSize) % 16; if (additionalSize != 0) { additionalSize = 16 - additionalSize; } + if (mtProtoVersion == 2 && additionalSize < 12) { + additionalSize += 16; + } NativeByteBuffer *buffer = BuffersStorage::getInstance().getFreeBuffer(24 + 32 + messageSize + additionalSize); buffer->writeInt64(authKeyId); buffer->position(24); - int64_t serverSalt = getServerSalt(); - buffer->writeInt64(serverSalt); + buffer->writeInt64(getServerSalt()); buffer->writeInt64(connection->getSissionId()); buffer->writeInt64(messageId); buffer->writeInt32(messageSeqNo); @@ -1437,44 +1486,74 @@ NativeByteBuffer *Datacenter::createRequestsData(std::vectorbytes() + 24 + 32 + messageSize, additionalSize); } - static uint8_t messageKey[84]; - SHA1(buffer->bytes() + 24, 32 + messageSize, messageKey); - memcpy(buffer->bytes() + 8, messageKey + 4, 16); - - if (quickAckId != nullptr) { - *quickAckId = (((messageKey[0] & 0xff)) | - ((messageKey[1] & 0xff) << 8) | - ((messageKey[2] & 0xff) << 16) | - ((messageKey[3] & 0xff) << 24)) & 0x7fffffff; + static uint8_t messageKey[96]; + switch (mtProtoVersion) { + case 2: { + SHA256_Init(&sha256Ctx); + SHA256_Update(&sha256Ctx, authKey->bytes + 88, 32); + SHA256_Update(&sha256Ctx, buffer->bytes() + 24, 32 + messageSize + additionalSize); + SHA256_Final(messageKey, &sha256Ctx); + if (quickAckId != nullptr) { + *quickAckId = (((messageKey[0] & 0xff)) | + ((messageKey[1] & 0xff) << 8) | + ((messageKey[2] & 0xff) << 16) | + ((messageKey[3] & 0xff) << 24)) & 0x7fffffff; + } + break; + } + default: { + SHA1(buffer->bytes() + 24, 32 + messageSize, messageKey + 4); + if (quickAckId != nullptr) { + *quickAckId = (((messageKey[4] & 0xff)) | + ((messageKey[5] & 0xff) << 8) | + ((messageKey[6] & 0xff) << 16) | + ((messageKey[7] & 0xff) << 24)) & 0x7fffffff; + } + break; + } } + memcpy(buffer->bytes() + 8, messageKey + 8, 16); - generateMessageKey(authKey->bytes, messageKey + 4, messageKey + 20, false); - aesIgeEncryption(buffer->bytes() + 24, messageKey + 20, messageKey + 52, true, false, buffer->limit() - 24); + generateMessageKey(authKey->bytes, messageKey + 8, messageKey + 32, false); + aesIgeEncryption(buffer->bytes() + 24, messageKey + 32, messageKey + 64, true, false, buffer->limit() - 24); return buffer; } bool Datacenter::decryptServerResponse(int64_t keyId, uint8_t *key, uint8_t *data, uint32_t length) { - if (authKeyId != keyId || length % 16 != 0) { - return false; + bool error = false; + if (authKeyId != keyId) { + error = true; } - static uint8_t messageKey[84]; - generateMessageKey(authKey->bytes, key, messageKey + 20, true); - aesIgeEncryption(data, messageKey + 20, messageKey + 52, false, false, length); + static uint8_t messageKey[96]; + generateMessageKey(authKey->bytes, key, messageKey + 32, true); + aesIgeEncryption(data, messageKey + 32, messageKey + 64, false, false, length); uint32_t messageLength; memcpy(&messageLength, data + 28, sizeof(uint32_t)); if (messageLength > length - 32) { - return false; + error = true; } messageLength += 32; if (messageLength > length) { messageLength = length; } - SHA1(data, messageLength, messageKey); + switch (ConnectionsManager::getInstance().getMtProtoVersion()) { + case 2: { + SHA256_Init(&sha256Ctx); + SHA256_Update(&sha256Ctx, authKey->bytes + 88 + 8, 32); + SHA256_Update(&sha256Ctx, data, length); + SHA256_Final(messageKey, &sha256Ctx); + break; + } + default: { + SHA1(data, messageLength, messageKey + 4); + break; + } + } - return memcmp(messageKey + 4, key, 16) == 0; + return memcmp(messageKey + 8, key, 16) == 0 && !error; } bool Datacenter::hasAuthKey() { @@ -1482,7 +1561,7 @@ bool Datacenter::hasAuthKey() { } Connection *Datacenter::createConnectionByType(uint32_t connectionType) { - uint32_t connectionNum = connectionType >> 16; + uint8_t connectionNum = (uint8_t) (connectionType >> 16); connectionType = connectionType & 0x0000ffff; switch (connectionType) { case ConnectionTypeGeneric: @@ -1490,7 +1569,7 @@ Connection *Datacenter::createConnectionByType(uint32_t connectionType) { case ConnectionTypeDownload: return createDownloadConnection(connectionNum); case ConnectionTypeUpload: - return createUploadConnection(); + return createUploadConnection(connectionNum); case ConnectionTypePush: return createPushConnection(); default: @@ -1498,7 +1577,7 @@ Connection *Datacenter::createConnectionByType(uint32_t connectionType) { } } -Connection *Datacenter::getDownloadConnection(uint32_t num, bool create) { +Connection *Datacenter::getDownloadConnection(uint8_t num, bool create) { if (authKey == nullptr) { return nullptr; } @@ -1508,14 +1587,14 @@ Connection *Datacenter::getDownloadConnection(uint32_t num, bool create) { return downloadConnections[num]; } -Connection *Datacenter::getUploadConnection(bool create) { +Connection *Datacenter::getUploadConnection(uint8_t num, bool create) { if (authKey == nullptr) { return nullptr; } if (create) { - createUploadConnection()->connect(); + createUploadConnection(num)->connect(); } - return uploadConnection; + return uploadConnection[num]; } Connection *Datacenter::getGenericConnection(bool create) { @@ -1539,7 +1618,7 @@ Connection *Datacenter::getPushConnection(bool create) { } Connection *Datacenter::getConnectionByType(uint32_t connectionType, bool create) { - uint32_t connectionNum = connectionType >> 16; + uint8_t connectionNum = (uint8_t) (connectionType >> 16); connectionType = connectionType & 0x0000ffff; switch (connectionType) { case ConnectionTypeGeneric: @@ -1547,7 +1626,7 @@ Connection *Datacenter::getConnectionByType(uint32_t connectionType, bool create case ConnectionTypeDownload: return getDownloadConnection(connectionNum, create); case ConnectionTypeUpload: - return getUploadConnection(create); + return getUploadConnection(connectionNum, create); case ConnectionTypePush: return getPushConnection(create); default: @@ -1563,14 +1642,14 @@ void Datacenter::exportAuthorization() { TL_auth_exportAuthorization *request = new TL_auth_exportAuthorization(); request->dc_id = datacenterId; DEBUG_D("dc%u begin export authorization", datacenterId); - ConnectionsManager::getInstance().sendRequest(request, [&](TLObject *response, TL_error *error) { + ConnectionsManager::getInstance().sendRequest(request, [&](TLObject *response, TL_error *error, int32_t networkType) { if (error == nullptr) { TL_auth_exportedAuthorization *res = (TL_auth_exportedAuthorization *) response; TL_auth_importAuthorization *request2 = new TL_auth_importAuthorization(); request2->bytes = std::move(res->bytes); request2->id = res->id; DEBUG_D("dc%u begin import authorization", datacenterId); - ConnectionsManager::getInstance().sendRequest(request2, [&](TLObject *response2, TL_error *error2) { + ConnectionsManager::getInstance().sendRequest(request2, [&](TLObject *response2, TL_error *error2, int32_t networkType) { if (error2 == nullptr) { authorized = true; ConnectionsManager::getInstance().onDatacenterExportAuthorizationComplete(this); diff --git a/TMessagesProj/jni/tgnet/Datacenter.h b/TMessagesProj/jni/tgnet/Datacenter.h index e219b4ae75e..13338ad9358 100644 --- a/TMessagesProj/jni/tgnet/Datacenter.h +++ b/TMessagesProj/jni/tgnet/Datacenter.h @@ -46,16 +46,17 @@ class Datacenter { void suspendConnections(); void getSessions(std::vector &sessions); void recreateSessions(); + void resetAddressAndPortNum(); bool isHandshaking(); bool hasAuthKey(); bool isExportingAuthorization(); - Connection *getDownloadConnection(uint32_t num, bool create); - Connection *getUploadConnection(bool create); + Connection *getDownloadConnection(uint8_t num, bool create); + Connection *getUploadConnection(uint8_t num, bool create); Connection *getGenericConnection(bool create); Connection *getPushConnection(bool create); Connection *getConnectionByType(uint32_t connectionType, bool create); - + static void aesIgeEncryption(uint8_t *buffer, uint8_t *key, uint8_t *iv, bool encrypt, bool changeIv, uint32_t length); private: @@ -72,7 +73,7 @@ class Datacenter { uint32_t datacenterId; Connection *genericConnection = nullptr; Connection *downloadConnections[DOWNLOAD_CONNECTIONS_COUNT]; - Connection *uploadConnection = nullptr; + Connection *uploadConnection[UPLOAD_CONNECTIONS_COUNT]; Connection *pushConnection = nullptr; uint32_t lastInitVersion = 0; @@ -100,8 +101,8 @@ class Datacenter { const uint32_t configVersion = 5; const uint32_t paramsConfigVersion = 1; - Connection *createDownloadConnection(uint32_t num); - Connection *createUploadConnection(); + Connection *createDownloadConnection(uint8_t num); + Connection *createUploadConnection(uint8_t num); Connection *createGenericConnection(); Connection *createPushConnection(); Connection *createConnectionByType(uint32_t connectionType); diff --git a/TMessagesProj/jni/tgnet/Defines.h b/TMessagesProj/jni/tgnet/Defines.h index f1bfad87706..a33177315ef 100644 --- a/TMessagesProj/jni/tgnet/Defines.h +++ b/TMessagesProj/jni/tgnet/Defines.h @@ -21,6 +21,7 @@ #define DEFAULT_DATACENTER_ID INT_MAX #define DC_UPDATE_TIME 60 * 60 #define DOWNLOAD_CONNECTIONS_COUNT 2 +#define UPLOAD_CONNECTIONS_COUNT 2 #define CONNECTION_BACKGROUND_KEEP_TIME 10000 #define DOWNLOAD_CHUNK_SIZE 1024 * 32 @@ -29,6 +30,10 @@ #define DOWNLOAD_MAX_BIG_REQUESTS 4 #define DOWNLOAD_BIG_FILE_MIN_SIZE 1024 * 1024 +#define NETWORK_TYPE_MOBILE 0 +#define NETWORK_TYPE_WIFI 1 +#define NETWORK_TYPE_ROAMING 2 + class TLObject; class TL_error; class Request; @@ -37,7 +42,7 @@ class TL_config; class NativeByteBuffer; class FileLoadOperation; -typedef std::function onCompleteFunc; +typedef std::function onCompleteFunc; typedef std::function onQuickAckFunc; typedef std::list> requestsList; typedef requestsList::iterator requestsIter; @@ -94,6 +99,8 @@ typedef struct ConnectiosManagerDelegate { virtual void onLogout() = 0; virtual void onUpdateConfig(TL_config *config) = 0; virtual void onInternalPushReceived() = 0; + virtual void onBytesSent(int32_t amount, int32_t networkType) = 0; + virtual void onBytesReceived(int32_t amount, int32_t networkType) = 0; } ConnectiosManagerDelegate; #define AllConnectionTypes ConnectionTypeGeneric | ConnectionTypeDownload | ConnectionTypeUpload diff --git a/TMessagesProj/jni/tgnet/FileLoadOperation.cpp b/TMessagesProj/jni/tgnet/FileLoadOperation.cpp index e076986ebfb..b8ec541cdf9 100644 --- a/TMessagesProj/jni/tgnet/FileLoadOperation.cpp +++ b/TMessagesProj/jni/tgnet/FileLoadOperation.cpp @@ -374,7 +374,7 @@ void FileLoadOperation::startDownloadRequest() { request->limit = currentDownloadChunkSize; nextDownloadOffset += currentDownloadChunkSize; - requestInfo->requestToken = ConnectionsManager::getInstance().sendRequest(request, [&, requestInfo](TLObject *response, TL_error *error) { + requestInfo->requestToken = ConnectionsManager::getInstance().sendRequest(request, [&, requestInfo](TLObject *response, TL_error *error, int32_t connectionType) { requestInfo->requestToken = 0; if (response != nullptr) { TL_upload_file *res = (TL_upload_file *) response; diff --git a/TMessagesProj/jni/tgnet/Request.cpp b/TMessagesProj/jni/tgnet/Request.cpp index 583e8ffe12a..29a5fdf1438 100644 --- a/TMessagesProj/jni/tgnet/Request.cpp +++ b/TMessagesProj/jni/tgnet/Request.cpp @@ -19,6 +19,7 @@ Request::Request(int32_t token, ConnectionType type, uint32_t flags, uint32_t da datacenterId = datacenter; onCompleteRequestCallback = completeFunc; onQuickAckCallback = quickAckFunc; + dataType = (uint8_t) (requestFlags >> 24); } Request::~Request() { @@ -52,9 +53,9 @@ void Request::clear(bool time) { } } -void Request::onComplete(TLObject *result, TL_error *error) { +void Request::onComplete(TLObject *result, TL_error *error, int32_t networkType) { if (onCompleteRequestCallback != nullptr && (result != nullptr || error != nullptr)) { - onCompleteRequestCallback(result, error); + onCompleteRequestCallback(result, error, networkType); } } diff --git a/TMessagesProj/jni/tgnet/Request.h b/TMessagesProj/jni/tgnet/Request.h index 5ab9a0930d8..4c66b142d97 100644 --- a/TMessagesProj/jni/tgnet/Request.h +++ b/TMessagesProj/jni/tgnet/Request.h @@ -40,6 +40,7 @@ class Request { bool completed = false; bool cancelled = false; bool isInitRequest = false; + uint8_t dataType = 0; int32_t serializedLength = 0; int32_t startTime = 0; int32_t minStartTime = 0; @@ -53,7 +54,7 @@ class Request { void addRespondMessageId(int64_t id); bool respondsToMessageId(int64_t id); void clear(bool time); - void onComplete(TLObject *result, TL_error *error); + void onComplete(TLObject *result, TL_error *error, int32_t networkType); void onQuickAck(); TLObject *getRpcRequest(); diff --git a/TMessagesProj/proguard-rules.pro b/TMessagesProj/proguard-rules.pro index cc87272f608..0f725e1c340 100644 --- a/TMessagesProj/proguard-rules.pro +++ b/TMessagesProj/proguard-rules.pro @@ -5,4 +5,7 @@ } -dontwarn com.google.android.gms.** -dontwarn com.google.common.cache.** --dontwarn com.google.common.primitives.** \ No newline at end of file +-dontwarn com.google.common.primitives.** +# Use -keep to explicitly keep any other classes shrinking would remove +-dontoptimize +-dontobfuscate \ No newline at end of file diff --git a/TMessagesProj/src/main/AndroidManifest.xml b/TMessagesProj/src/main/AndroidManifest.xml index 95d5f51209e..e4f468ecde7 100644 --- a/TMessagesProj/src/main/AndroidManifest.xml +++ b/TMessagesProj/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + @@ -43,6 +44,22 @@ + + + + + + + + + + + + + + + + + + @@ -157,6 +176,22 @@ android:resizeableActivity="false" android:windowSoftInputMode="adjustResize|stateHidden"> + + + + + + @@ -235,6 +272,11 @@ + + + + + @@ -268,10 +310,12 @@ + + - + diff --git a/TMessagesProj/src/main/assets/bluebubbles.attheme b/TMessagesProj/src/main/assets/bluebubbles.attheme new file mode 100644 index 00000000000..f2a96ad2f67 Binary files /dev/null and b/TMessagesProj/src/main/assets/bluebubbles.attheme differ diff --git a/TMessagesProj/src/main/assets/dark.attheme b/TMessagesProj/src/main/assets/dark.attheme new file mode 100644 index 00000000000..342a2be2a42 --- /dev/null +++ b/TMessagesProj/src/main/assets/dark.attheme @@ -0,0 +1,247 @@ +avatar_subtitleInProfilePink=-6576209 +chat_secretTimerBackground=-1239540194 +chat_emojiPanelTrendingDescription=-9342607 +chat_inFileBackground=-10653824 +chat_emojiPanelIconSelected=-11167525 +actionBarActionModeDefaultSelector=2047809827 +chats_menuItemIcon=-2763307 +chat_inTimeText=-645885536 +windowBackgroundGray=-16119286 +windowBackgroundWhiteGreenText2=-12401818 +chat_emojiPanelBackspace=-9276814 +chat_outPreviewInstantSelectedText=-1 +chat_inBubble=-14339006 +chat_outFileInfoSelectedText=-1 +chat_outLoaderSelected=-1 +chat_emojiPanelIcon=-9342607 +chat_selectedBackground=1276090861 +chats_pinnedIcon=-8882056 +player_actionBarTitle=-1579033 +chat_muteIcon=-8487298 +chat_addContact=-11164709 +chat_outMenu=-9594162 +actionBarActionModeDefault=-14339006 +chat_emojiPanelShadowLine=251658239 +chat_inPreviewInstantText=-11164965 +chat_outVoiceSeekbarSelected=-1313793 +chat_outForwardedNameText=-3019777 +chat_outFileProgressSelected=-1 +player_progressBackground=-1979711488 +avatar_actionBarSelectorRed=-16249072 +player_button=-7960954 +chat_inVoiceSeekbar=-10653824 +switchThumb=-12829636 +chats_tabletSelectedOverlay=268435455 +chats_menuItemText=-986896 +chat_outFileNameText=-2954241 +divider=402653183 +chat_outViews=-8211748 +avatar_actionBarSelectorBlue=-14999000 +chats_actionMessage=-11234874 +chat_messageTextIn=-328966 +chat_outLoaderPhoto=-13077852 +chat_outFileIcon=-13143396 +chat_serviceBackgroundSelected=1615417684 +inappPlayerBackground=-668259541 +chat_topPanelLine=-11108183 +player_actionBar=-14935012 +chat_outFileInfoText=-5582866 +chat_outLoaderPhotoIcon=-9263664 +chat_unreadMessagesStartArrowIcon=-9539986 +chat_outAudioProgress=-13077596 +chat_outBubbleShadow=-16777216 +chat_inMenuSelected=-2102800402 +chat_inContactIcon=-14338750 +chat_messageTextOut=-328966 +chat_outAudioTitleText=-3019777 +chat_inLoaderPhotoSelected=-14925725 +inappPlayerPerformer=-328966 +actionBarActionModeDefaultTop=-1543503872 +avatar_subtitleInProfileCyan=-6445137 +profile_actionBackground=-12171706 +chat_outSentClockSelected=-1 +avatar_nameInMessageGreen=-9652901 +chat_outAudioSeekbarFill=-3874313 +player_placeholder=-13948117 +chat_inReplyNameText=-11164965 +chat_messagePanelIcons=-9868951 +graySection=-14540254 +chats_nameIcon=-2236963 +avatar_backgroundActionBarViolet=-14143949 +chat_emojiPanelIconSelector=-11167525 +chat_replyPanelMessage=-7105645 +chat_outPreviewInstantText=-3019777 +chat_emojiPanelTrendingTitle=-723724 +chat_inPreviewInstantSelectedText=-11099429 +chat_inFileInfoSelectedText=-5648402 +avatar_subtitleInProfileRed=-6445137 +chat_outLocationIcon=-10052929 +chat_inAudioPerfomerText=-8812393 +chats_attachMessage=-11234874 +chat_messageLinkIn=-11099173 +chats_unreadCounter=-14183202 +windowBackgroundWhiteGrayText=-10132123 +windowBackgroundWhiteGrayText3=-9408400 +chat_outSentCheckSelected=-1 +chat_outTimeSelectedText=-1 +chat_outFileSelectedIcon=-13925429 +chats_secretIcon=-9316522 +chat_outAudioPerfomerText=-7028510 +chats_pinnedOverlay=167772159 +chat_outContactIcon=-5452289 +windowBackgroundWhiteBlueHeader=-9851917 +actionBarDefaultSelector=-15395047 +chat_emojiPanelEmptyText=-10658467 +chat_inViews=-8812137 +listSelector=771751936 +chat_messagePanelBackground=-14803426 +chats_secretName=-9316522 +chat_inReplyLine=-11230501 +actionBarDefaultSubtitle=-7368817 +switchThumbChecked=-13600600 +chat_inReplyMessageText=-1 +avatar_actionBarSelectorGreen=-15986928 +chat_inFileIcon=-14470078 +chat_inAudioTitleText=-11099173 +chat_inAudioDurationSelectedText=-5648402 +chat_outSentClock=-8211748 +actionBarDefault=-14407896 +chat_goDownButton=-11711155 +chat_inAudioSelectedProgress=-14925469 +profile_actionPressedBackground=-14671840 +chat_outContactPhoneText=-4792321 +chat_inVenueInfoText=-10653824 +chat_outAudioDurationText=-3019777 +chat_outSiteNameText=-3019777 +chat_inBubbleSelected=-14925725 +chats_date=-10592674 +chat_outFileProgress=-9263664 +chat_outBubbleSelected=-13859893 +progressCircle=-13221820 +chats_unreadCounterMuted=-12303292 +stickers_menu=-11710381 +chat_outAudioSeekbarSelected=-1 +chat_inSiteNameText=-11164965 +chat_inFileProgressSelected=-5845010 +chat_topPanelMessage=-9803158 +chat_outVoiceSeekbar=-9263664 +chat_topPanelBackground=-98821092 +chat_outVenueInfoSelectedText=-1 +chats_menuTopShadow=-15724528 +player_actionBarItems=-1 +files_folderIcon=-5855578 +chat_inReplyMediaMessageSelectedText=-9590561 +chat_inViewsSelected=-5648402 +chat_outAudioDurationSelectedText=-1 +avatar_backgroundActionBarGreen=-14143949 +profile_verifiedCheck=-1 +chat_outViewsSelected=-1 +switchTrackChecked=-15316366 +chat_serviceBackground=1713910333 +windowBackgroundWhiteGrayText2=-8816263 +chat_inFileSelectedIcon=-15056797 +profile_actionIcon=-1 +chat_secretChatStatusText=-9934744 +chat_emojiPanelBackground=-14474461 +chat_inPreviewLine=-11230501 +chat_unreadMessagesStartBackground=-12829636 +avatar_backgroundActionBarBlue=-14145496 +chat_inViaBotNameText=-11164965 +avatar_actionBarSelectorCyan=-16249328 +avatar_nameInMessageOrange=-2324391 +windowBackgroundWhiteGrayText4=-9539986 +files_folderIconBackground=-13619152 +profile_verifiedBackground=-11416584 +chat_outFileBackground=-9263664 +chat_inLoaderPhoto=-14404542 +chat_inForwardedNameText=-11164965 +chat_inSentClock=-10653824 +chat_inAudioSeekbarSelected=-5648402 +chats_name=-1644826 +chats_nameMessage=-11696202 +key_chats_menuTopShadow=789516 +windowBackgroundWhite=-15329770 +chat_outBubble=-13077852 +chats_menuBackground=-14669775 +chat_messagePanelHint=-11776948 +chat_replyPanelLine=-14869219 +chat_inReplyMediaMessageText=-8812393 +chat_outReplyMediaMessageText=-3019777 +avatar_backgroundActionBarPink=-14143949 +chat_outLoader=-7421976 +chat_outReplyNameText=-3019777 +avatar_subtitleInProfileViolet=-6445137 +chat_outAudioSelectedProgress=-14187829 +chat_inSentClockSelected=-5648146 +chat_inBubbleShadow=-16777216 +chat_inFileInfoText=-8812137 +chat_inAudioSeekbar=-11443856 +chat_inContactPhoneText=-8812393 +avatar_backgroundInProfileBlue=-15721692 +chat_outInstantSelected=-1 +chat_outLoaderPhotoIconSelected=-1 +chat_outAudioSeekbar=-1770871344 +chat_inLoaderPhotoIcon=-10915968 +windowBackgroundWhiteRedText5=-1038770 +avatar_actionBarSelectorViolet=-16118256 +chats_menuPhone=1627389951 +chat_outVoiceSeekbarFill=-3874313 +chat_outPreviewLine=-3019777 +chats_sentCheck=-10574624 +chat_inMenu=2036100992 +player_seekBarBackground=1196577362 +chats_sentClock=-10452291 +chat_messageLinkOut=-4792577 +chat_unreadMessagesStartText=-620756993 +inappPlayerClose=-10987432 +chat_inAudioProgress=-14338750 +chat_outFileBackgroundSelected=-1 +chat_outInstant=-4792321 +chat_outReplyMessageText=-1 +chat_outContactBackground=-10910270 +chat_inAudioDurationText=-8746857 +listSelectorSDK21=268435455 +chat_goDownButtonIcon=-1776412 +chats_menuCloudBackgroundCats=-11232035 +chat_inLoaderPhotoIconSelected=-5648402 +chat_inContactNameText=-11099173 +chat_topPanelTitle=-11164709 +chat_outLoaderPhotoSelected=-13208924 +avatar_actionBarSelectorPink=-16118000 +chat_outContactNameText=-3019777 +player_actionBarSubtitle=-10526881 +chat_wallpaper=-15526377 +chat_emojiPanelStickerPackSelector=217775871 +chats_menuPhoneCats=-7434610 +chat_reportSpam=-1481631 +avatar_subtitleInProfileGreen=-6445137 +inappPlayerTitle=-6513508 +chat_outViaBotNameText=-3019777 +avatar_backgroundActionBarRed=-14143949 +windowBackgroundWhiteValueText=-12214815 +avatar_backgroundActionBarOrange=-14209485 +chat_inFileBackgroundSelected=-1 +avatar_actionBarSelectorOrange=-16052464 +chat_inVenueInfoSelectedText=-5648402 +actionBarActionModeDefaultIcon=-1 +chats_message=-9934744 +avatar_subtitleInProfileBlue=-8224126 +chat_outVenueNameText=-3019777 +emptyListPlaceholder=-11447983 +chat_inFileProgress=-10653824 +chat_outLocationBackground=-6234891 +chats_muteIcon=-10790053 +windowBackgroundWhiteBlackText=-855310 +windowBackgroundWhiteBlueText=-12413479 +chat_outReplyMediaMessageSelectedText=-1 +avatar_backgroundActionBarCyan=-14209485 +chat_topPanelClose=-11184811 +chat_outSentCheck=-6831126 +chat_outMenuSelected=-1 +chat_messagePanelText=-1118482 +chat_outReplyLine=-3019777 +chat_outVenueInfoText=-4792321 +chat_outTimeText=-693579794 +chat_inTimeSelectedText=-5582866 +switchTrack=-13948117 +avatar_subtitleInProfileOrange=-6510673 diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_0.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_0.png deleted file mode 100644 index eab1dd0b041..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_0.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_1.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_1.png deleted file mode 100644 index 13e30e4d78c..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_1.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_2.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_2.png deleted file mode 100644 index bab02c9c776..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_2.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_3.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_3.png deleted file mode 100644 index 6568f97ecd4..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_0_3.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_0.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_0.png deleted file mode 100644 index 089b9c65e2b..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_0.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_1.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_1.png deleted file mode 100644 index e8d5581a884..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_1.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_2.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_2.png deleted file mode 100644 index b3a6e690c3c..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_2.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_3.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_3.png deleted file mode 100644 index 49d952e2536..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_1_3.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_0.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_0.png deleted file mode 100644 index 3ae8c7eca27..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_0.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_1.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_1.png deleted file mode 100644 index 85dc9c613ce..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_1.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_2.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_2.png deleted file mode 100644 index b47e6c2ac64..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_2.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_3.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_3.png deleted file mode 100644 index a38b090f6dd..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_2_3.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_0.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_0.png deleted file mode 100644 index 65b02f0f58c..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_0.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_1.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_1.png deleted file mode 100644 index ee579a82be3..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_1.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_2.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_2.png deleted file mode 100644 index eb48658690f..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_2.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_3.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_3.png deleted file mode 100644 index d1aaaca7d9f..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_3_3.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_0.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_0.png deleted file mode 100644 index 57b9fafa4fc..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_0.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_1.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_1.png deleted file mode 100644 index 9c525df8be0..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_1.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_2.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_2.png deleted file mode 100644 index da27a76d2d5..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_2.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_3.png b/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_3.png deleted file mode 100644 index 8108b5de6ba..00000000000 Binary files a/TMessagesProj/src/main/assets/emoji/v10_emoji2.0x_4_3.png and /dev/null differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_0.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_0.png new file mode 100644 index 00000000000..9190f987081 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_0.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_1.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_1.png new file mode 100644 index 00000000000..17ee713dbac Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_1.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_2.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_2.png new file mode 100644 index 00000000000..be7305a246a Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_2.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_3.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_3.png new file mode 100644 index 00000000000..d6812c1a022 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_0_3.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_0.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_0.png new file mode 100644 index 00000000000..7031c11ac44 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_0.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_1.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_1.png new file mode 100644 index 00000000000..3ad10d6c7b6 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_1.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_2.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_2.png new file mode 100644 index 00000000000..24a5ee93fc5 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_2.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_3.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_3.png new file mode 100644 index 00000000000..a76dbf8baa8 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_1_3.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_0.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_0.png new file mode 100644 index 00000000000..69e8944f118 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_0.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_1.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_1.png new file mode 100644 index 00000000000..6274e837073 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_1.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_2.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_2.png new file mode 100644 index 00000000000..0235028f8d3 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_2.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_3.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_3.png new file mode 100644 index 00000000000..49a8e632330 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_2_3.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_0.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_0.png new file mode 100644 index 00000000000..65891958e85 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_0.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_1.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_1.png new file mode 100644 index 00000000000..b302bcc580a Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_1.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_2.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_2.png new file mode 100644 index 00000000000..8bb1330c1ea Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_2.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_3.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_3.png new file mode 100644 index 00000000000..d46997ba210 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_3_3.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_0.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_0.png new file mode 100644 index 00000000000..2f4f17e52a7 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_0.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_1.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_1.png new file mode 100644 index 00000000000..d59465e41e2 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_1.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_2.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_2.png new file mode 100644 index 00000000000..de30f417bc3 Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_2.png differ diff --git a/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_3.png b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_3.png new file mode 100644 index 00000000000..eab4d60895e Binary files /dev/null and b/TMessagesProj/src/main/assets/emoji/v11_emoji2.0x_4_3.png differ diff --git a/TMessagesProj/src/main/assets/fonts/rmedium.ttf b/TMessagesProj/src/main/assets/fonts/rmedium.ttf old mode 100644 new mode 100755 index a3c1a1f1702..39c63d74617 Binary files a/TMessagesProj/src/main/assets/fonts/rmedium.ttf and b/TMessagesProj/src/main/assets/fonts/rmedium.ttf differ diff --git a/TMessagesProj/src/main/assets/fonts/rmediumitalic.ttf b/TMessagesProj/src/main/assets/fonts/rmediumitalic.ttf new file mode 100755 index 00000000000..dc743f0a66c Binary files /dev/null and b/TMessagesProj/src/main/assets/fonts/rmediumitalic.ttf differ diff --git a/TMessagesProj/src/main/assets/fonts/rmono.ttf b/TMessagesProj/src/main/assets/fonts/rmono.ttf new file mode 100755 index 00000000000..b158a334eb3 Binary files /dev/null and b/TMessagesProj/src/main/assets/fonts/rmono.ttf differ diff --git a/TMessagesProj/src/main/java/com/android/internal/telephony/ITelephony.java b/TMessagesProj/src/main/java/com/android/internal/telephony/ITelephony.java new file mode 100644 index 00000000000..97da524940b --- /dev/null +++ b/TMessagesProj/src/main/java/com/android/internal/telephony/ITelephony.java @@ -0,0 +1,17 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package com.android.internal.telephony; + +public interface ITelephony { + boolean endCall(); + + void answerRingingCall(); + + void silenceRinger(); +} diff --git a/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java b/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java index 89066116d5e..d70602acf12 100644 --- a/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java +++ b/TMessagesProj/src/main/java/org/telegram/PhoneFormat/PhoneFormat.java @@ -72,6 +72,9 @@ public static String strip(String str) { } public static String stripExceptNumbers(String str, boolean includePlus) { + if (str == null) { + return null; + } StringBuilder res = new StringBuilder(str); String phoneChars = "0123456789"; if (includePlus) { @@ -120,14 +123,14 @@ public void init(String countryCode) { bos.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (stream != null) { stream.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -219,7 +222,7 @@ public String format(String orig) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return orig; } diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java index 49a74fa0aa6..29ae7237b3f 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteCursor.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.SQLite; @@ -76,14 +76,14 @@ public boolean next() throws SQLiteException { int repeatCount = 6; while (repeatCount-- != 0) { try { - FileLog.e("tmessages", "sqlite busy, waiting..."); + FileLog.e("sqlite busy, waiting..."); Thread.sleep(500); res = preparedStatement.step(); if (res == 0) { break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (res == -1) { diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteDatabase.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteDatabase.java index 6ab2e575d14..0066f1b71bd 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteDatabase.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteDatabase.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.SQLite; @@ -60,7 +60,7 @@ public void close() { commitTransaction(); closedb(sqliteHandle); } catch (SQLiteException e) { - FileLog.e("tmessages", e.getMessage(), e); + FileLog.e(e.getMessage(), e); } isOpen = false; } diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteException.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteException.java index fa0680886e1..976460896bb 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteException.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteException.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.SQLite; diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteNoRowException.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteNoRowException.java index 2db0736c2e7..c9c83fb6469 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteNoRowException.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLiteNoRowException.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.SQLite; diff --git a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java index cae02b41f0e..3bf06c49d5c 100755 --- a/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java +++ b/TMessagesProj/src/main/java/org/telegram/SQLite/SQLitePreparedStatement.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.SQLite; @@ -34,7 +34,7 @@ public SQLitePreparedStatement(SQLiteDatabase db, String sql, boolean finalize) } hashMap.put(this, sql); for (HashMap.Entry entry : hashMap.entrySet()) { - FileLog.d("tmessages", "exist entry = " + entry.getValue()); + FileLog.d("exist entry = " + entry.getValue()); } }*/ } @@ -105,7 +105,7 @@ public void finalizeQuery() { isFinalized = true; finalize(sqliteStatementHandle); } catch (SQLiteException e) { - FileLog.e("tmessages", e.getMessage(), e); + FileLog.e(e.getMessage(), e); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 340cb769d8d..d9df524cfb9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -3,17 +3,17 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; @@ -21,6 +21,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.database.ContentObserver; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -40,11 +41,17 @@ import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Handler; +import android.provider.CallLog; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.support.v4.content.FileProvider; +import android.support.v4.view.ViewPager; +import android.support.v4.widget.EdgeEffectCompat; +import android.telephony.TelephonyManager; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.TextUtils; import android.text.style.ForegroundColorSpan; import android.util.DisplayMetrics; import android.util.StateSet; @@ -54,22 +61,26 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.webkit.MimeTypeMap; -import android.widget.AbsListView; import android.widget.EdgeEffect; import android.widget.EditText; import android.widget.ListView; -import android.widget.ProgressBar; +import android.widget.ScrollView; import android.widget.TextView; +import com.android.internal.telephony.ITelephony; + import net.hockeyapp.android.CrashManager; import net.hockeyapp.android.CrashManagerListener; import net.hockeyapp.android.UpdateManager; +import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.ForegroundDetector; -import org.telegram.ui.Components.NumberPicker; import org.telegram.ui.Components.TypefaceSpan; import java.io.ByteArrayOutputStream; @@ -106,6 +117,7 @@ public class AndroidUtilities { public static int leftBaseline; public static boolean usingHardwareInput; public static boolean isInMultiwindow; + private static Boolean isTablet = null; private static int adjustOwnerClassGuid = 0; @@ -136,7 +148,7 @@ public class AndroidUtilities { + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" + "(?:\\b|$)"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -155,14 +167,16 @@ public static int[] calcDrawableColor(Drawable drawable) { Bitmap b = Bitmaps.createScaledBitmap(bitmap, 1, 1, true); if (b != null) { bitmapColor = b.getPixel(0, 0); - b.recycle(); + if (bitmap != b) { + b.recycle(); + } } } } else if (drawable instanceof ColorDrawable) { bitmapColor = ((ColorDrawable) drawable).getColor(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } double[] hsv = rgbToHsv((bitmapColor >> 16) & 0xff, (bitmapColor >> 8) & 0xff, bitmapColor & 0xff); @@ -267,7 +281,6 @@ public static boolean isGoogleMapsInstalled(final BaseFragment fragment) { } AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); builder.setMessage("Install Google Maps?"); - builder.setCancelable(true); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -275,7 +288,7 @@ public void onClick(DialogInterface dialogInterface, int i) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=com.google.android.apps.maps")); fragment.getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -317,7 +330,7 @@ public static void lockOrientation(Activity activity) { } try { prevOrientation = activity.getRequestedOrientation(); - WindowManager manager = (WindowManager)activity.getSystemService(Activity.WINDOW_SERVICE); + WindowManager manager = (WindowManager) activity.getSystemService(Activity.WINDOW_SERVICE); if (manager != null && manager.getDefaultDisplay() != null) { int rotation = manager.getDefaultDisplay().getRotation(); int orientation = activity.getResources().getConfiguration().orientation; @@ -349,7 +362,7 @@ public static void lockOrientation(Activity activity) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -363,7 +376,7 @@ public static void unlockOrientation(Activity activity) { prevOrientation = -10; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -374,7 +387,7 @@ public static Typeface getTypeface(String assetPath) { Typeface t = Typeface.createFromAsset(ApplicationLoader.applicationContext.getAssets(), assetPath); typefaceCache.put(assetPath, t); } catch (Exception e) { - FileLog.e("Typefaces", "Could not get typeface '" + assetPath + "' because " + e.getMessage()); + FileLog.e("Could not get typeface '" + assetPath + "' because " + e.getMessage()); return null; } } @@ -415,10 +428,10 @@ public static void showKeyboard(View view) { return; } try { - InputMethodManager inputManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); inputManager.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -430,7 +443,7 @@ public static boolean isKeyboardShowed(View view) { InputMethodManager inputManager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); return inputManager.isActive(view); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -446,7 +459,7 @@ public static void hideKeyboard(View view) { } imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -455,7 +468,7 @@ public static File getCacheDir() { try { state = Environment.getExternalStorageState(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (state == null || state.startsWith(Environment.MEDIA_MOUNTED)) { try { @@ -464,7 +477,7 @@ public static File getCacheDir() { return file; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { @@ -473,7 +486,7 @@ public static File getCacheDir() { return file; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return new File(""); } @@ -485,6 +498,13 @@ public static int dp(float value) { return (int) Math.ceil(density * value); } + public static int dp2(float value) { + if (value == 0) { + return 0; + } + return (int) Math.floor(density * value); + } + public static int compare(int lhs, int rhs) { if (lhs == rhs) { return 0; @@ -529,9 +549,9 @@ public static void checkDisplaySize(Context context, Configuration newConfigurat displaySize.y = newSize; } } - FileLog.e("tmessages", "display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi); + FileLog.e("display size = " + displaySize.x + " " + displaySize.y + " " + displayMetrics.xdpi + "x" + displayMetrics.ydpi); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -617,95 +637,6 @@ public static int getPhotoSize() { return photoSize; } - public static String formatTTLString(int ttl) { - if (ttl < 60) { - return LocaleController.formatPluralString("Seconds", ttl); - } else if (ttl < 60 * 60) { - return LocaleController.formatPluralString("Minutes", ttl / 60); - } else if (ttl < 60 * 60 * 24) { - return LocaleController.formatPluralString("Hours", ttl / 60 / 60); - } else if (ttl < 60 * 60 * 24 * 7) { - return LocaleController.formatPluralString("Days", ttl / 60 / 60 / 24); - } else { - int days = ttl / 60 / 60 / 24; - if (ttl % 7 == 0) { - return LocaleController.formatPluralString("Weeks", days / 7); - } else { - return String.format("%s %s", LocaleController.formatPluralString("Weeks", days / 7), LocaleController.formatPluralString("Days", days % 7)); - } - } - } - - public static AlertDialog.Builder buildTTLAlert(final Context context, final TLRPC.EncryptedChat encryptedChat) { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(LocaleController.getString("MessageLifetime", R.string.MessageLifetime)); - final NumberPicker numberPicker = new NumberPicker(context); - numberPicker.setMinValue(0); - numberPicker.setMaxValue(20); - if (encryptedChat.ttl > 0 && encryptedChat.ttl < 16) { - numberPicker.setValue(encryptedChat.ttl); - } else if (encryptedChat.ttl == 30) { - numberPicker.setValue(16); - } else if (encryptedChat.ttl == 60) { - numberPicker.setValue(17); - } else if (encryptedChat.ttl == 60 * 60) { - numberPicker.setValue(18); - } else if (encryptedChat.ttl == 60 * 60 * 24) { - numberPicker.setValue(19); - } else if (encryptedChat.ttl == 60 * 60 * 24 * 7) { - numberPicker.setValue(20); - } else if (encryptedChat.ttl == 0) { - numberPicker.setValue(0); - } - numberPicker.setFormatter(new NumberPicker.Formatter() { - @Override - public String format(int value) { - if (value == 0) { - return LocaleController.getString("ShortMessageLifetimeForever", R.string.ShortMessageLifetimeForever); - } else if (value >= 1 && value < 16) { - return AndroidUtilities.formatTTLString(value); - } else if (value == 16) { - return AndroidUtilities.formatTTLString(30); - } else if (value == 17) { - return AndroidUtilities.formatTTLString(60); - } else if (value == 18) { - return AndroidUtilities.formatTTLString(60 * 60); - } else if (value == 19) { - return AndroidUtilities.formatTTLString(60 * 60 * 24); - } else if (value == 20) { - return AndroidUtilities.formatTTLString(60 * 60 * 24 * 7); - } - return ""; - } - }); - builder.setView(numberPicker); - builder.setNegativeButton(LocaleController.getString("Done", R.string.Done), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int oldValue = encryptedChat.ttl; - which = numberPicker.getValue(); - if (which >= 0 && which < 16) { - encryptedChat.ttl = which; - } else if (which == 16) { - encryptedChat.ttl = 30; - } else if (which == 17) { - encryptedChat.ttl = 60; - } else if (which == 18) { - encryptedChat.ttl = 60 * 60; - } else if (which == 19) { - encryptedChat.ttl = 60 * 60 * 24; - } else if (which == 20) { - encryptedChat.ttl = 60 * 60 * 24 * 7; - } - if (oldValue != encryptedChat.ttl) { - SecretChatHelper.getInstance().sendTTLMessage(encryptedChat, null); - MessagesStorage.getInstance().updateEncryptedChatTTL(encryptedChat); - } - } - }); - return builder; - } - public static void clearCursorDrawable(EditText editText) { if (editText == null) { return; @@ -715,23 +646,167 @@ public static void clearCursorDrawable(EditText editText) { mCursorDrawableRes.setAccessible(true); mCursorDrawableRes.setInt(editText, 0); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - public static void setProgressBarAnimationDuration(ProgressBar progressBar, int duration) { - if (progressBar == null) { + private static ContentObserver callLogContentObserver; + private static Runnable unregisterRunnable; + private static boolean hasCallPermissions = Build.VERSION.SDK_INT >= 23; + + @SuppressWarnings("unchecked") + public static void endIncomingCall() { + if (!hasCallPermissions) { return; } try { - Field mCursorDrawableRes = ProgressBar.class.getDeclaredField("mDuration"); - mCursorDrawableRes.setAccessible(true); - mCursorDrawableRes.setInt(progressBar, duration); + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + Class c = Class.forName(tm.getClass().getName()); + Method m = c.getDeclaredMethod("getITelephony"); + m.setAccessible(true); + ITelephony telephonyService = (ITelephony) m.invoke(tm); + telephonyService = (ITelephony) m.invoke(tm); + telephonyService.silenceRinger(); + telephonyService.endCall(); } catch (Exception e) { FileLog.e("tmessages", e); } } + public static boolean checkPhonePattern(String pattern, String phone) { + if (TextUtils.isEmpty(pattern) || pattern.equals("*")) { + return true; + } + String args[] = pattern.split("\\*"); + phone = PhoneFormat.stripExceptNumbers(phone); + int checkStart = 0; + int index; + for (int a = 0; a < args.length; a++) { + String arg = args[a]; + if (!TextUtils.isEmpty(arg)) { + if ((index = phone.indexOf(arg, checkStart)) == -1) { + return false; + } + checkStart = index + arg.length(); + } + } + return true; + } + + public static String obtainLoginPhoneCall(String pattern) { + if (!hasCallPermissions) { + return null; + } + Cursor cursor = null; + try { + cursor = ApplicationLoader.applicationContext.getContentResolver().query( + CallLog.Calls.CONTENT_URI, + new String[]{CallLog.Calls.NUMBER, CallLog.Calls.DATE}, + CallLog.Calls.TYPE + " IN (" + CallLog.Calls.MISSED_TYPE + "," + CallLog.Calls.INCOMING_TYPE + "," + CallLog.Calls.REJECTED_TYPE + ")", + null, + "date DESC LIMIT 5"); + while (cursor.moveToNext()) { + String number = cursor.getString(0); + long date = cursor.getLong(1); + FileLog.e("number = " + number); + if (Math.abs(System.currentTimeMillis() - date) >= 60 * 60 * 1000) { + continue; + } + if (checkPhonePattern(pattern, number)) { + return number; + } + } + } catch (Exception e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + private static void registerLoginContentObserver(boolean shouldRegister, final String number) { + if (shouldRegister) { + if (callLogContentObserver != null) { + return; + } + ApplicationLoader.applicationContext.getContentResolver().registerContentObserver( + android.provider.CallLog.Calls.CONTENT_URI, + true, + callLogContentObserver = new ContentObserver(new Handler()) { + @Override + public boolean deliverSelfNotifications() { + return true; + } + + @Override + public void onChange(boolean selfChange) { + registerLoginContentObserver(false, number); + removeLoginPhoneCall(number, false); + } + }); + runOnUIThread(unregisterRunnable = new Runnable() { + @Override + public void run() { + unregisterRunnable = null; + registerLoginContentObserver(false, number); + } + }, 10000); + } else { + if (callLogContentObserver == null) { + return; + } + if (unregisterRunnable != null) { + cancelRunOnUIThread(unregisterRunnable); + unregisterRunnable = null; + } + try { + ApplicationLoader.applicationContext.getContentResolver().unregisterContentObserver(callLogContentObserver); + } catch (Exception ignore) { + + } finally { + callLogContentObserver = null; + } + } + } + + public static void removeLoginPhoneCall(String number, boolean first) { + if (!hasCallPermissions) { + return; + } + Cursor cursor = null; + try { + cursor = ApplicationLoader.applicationContext.getContentResolver().query( + CallLog.Calls.CONTENT_URI, + new String[]{CallLog.Calls._ID, CallLog.Calls.NUMBER}, + CallLog.Calls.TYPE + " IN (" + CallLog.Calls.MISSED_TYPE + "," + CallLog.Calls.INCOMING_TYPE + "," + CallLog.Calls.REJECTED_TYPE + ")", + null, + "date DESC LIMIT 5"); + boolean removed = false; + while (cursor.moveToNext()) { + String phone = cursor.getString(1); + if (phone.contains(number) || number.contains(phone)) { + removed = true; + ApplicationLoader.applicationContext.getContentResolver().delete( + CallLog.Calls.CONTENT_URI, + CallLog.Calls._ID + " = ? ", + new String[]{String.valueOf(cursor.getInt(0))}); + break; + } + } + if (!removed && first) { + registerLoginContentObserver(true, number); + } + } catch (Exception e) { + FileLog.e(e); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + private static Intent createShortcutIntent(long did, boolean forDelete) { Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); @@ -819,7 +894,7 @@ private static Intent createShortcutIntent(long did, boolean forDelete) { bitmap = result; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (bitmap != null) { @@ -849,7 +924,7 @@ public static void installShortcut(long did) { addIntent.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); ApplicationLoader.applicationContext.sendBroadcast(addIntent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -859,7 +934,7 @@ public static void uninstallShortcut(long did) { addIntent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT"); ApplicationLoader.applicationContext.sendBroadcast(addIntent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -884,7 +959,7 @@ public static int getViewInset(View view) { return insets.bottom; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return 0; } @@ -902,11 +977,11 @@ public static Point getRealScreenSize() { size.set((Integer) mGetRawW.invoke(windowManager.getDefaultDisplay()), (Integer) mGetRawH.invoke(windowManager.getDefaultDisplay())); } catch (Exception e) { size.set(windowManager.getDefaultDisplay().getWidth(), windowManager.getDefaultDisplay().getHeight()); - FileLog.e("tmessages", e); + FileLog.e(e); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return size; } @@ -924,24 +999,56 @@ public static CharSequence getTrimmedString(CharSequence src) { return src; } - public static void setListViewEdgeEffectColor(AbsListView listView, int color) { + public static void setViewPagerEdgeEffectColor(ViewPager viewPager, int color) { + if (Build.VERSION.SDK_INT >= 21) { + try { + Field field = ViewPager.class.getDeclaredField("mLeftEdge"); + field.setAccessible(true); + EdgeEffectCompat mLeftEdge = (EdgeEffectCompat) field.get(viewPager); + if (mLeftEdge != null) { + field = EdgeEffectCompat.class.getDeclaredField("mEdgeEffect"); + field.setAccessible(true); + EdgeEffect mEdgeEffect = (EdgeEffect) field.get(mLeftEdge); + if (mEdgeEffect != null) { + mEdgeEffect.setColor(color); + } + } + + field = ViewPager.class.getDeclaredField("mRightEdge"); + field.setAccessible(true); + EdgeEffectCompat mRightEdge = (EdgeEffectCompat) field.get(viewPager); + if (mRightEdge != null) { + field = EdgeEffectCompat.class.getDeclaredField("mEdgeEffect"); + field.setAccessible(true); + EdgeEffect mEdgeEffect = (EdgeEffect) field.get(mRightEdge); + if (mEdgeEffect != null) { + mEdgeEffect.setColor(color); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + + public static void setScrollViewEdgeEffectColor(ScrollView scrollView, int color) { if (Build.VERSION.SDK_INT >= 21) { try { - Field field = AbsListView.class.getDeclaredField("mEdgeGlowTop"); + Field field = ScrollView.class.getDeclaredField("mEdgeGlowTop"); field.setAccessible(true); - EdgeEffect mEdgeGlowTop = (EdgeEffect) field.get(listView); + EdgeEffect mEdgeGlowTop = (EdgeEffect) field.get(scrollView); if (mEdgeGlowTop != null) { mEdgeGlowTop.setColor(color); } - field = AbsListView.class.getDeclaredField("mEdgeGlowBottom"); + field = ScrollView.class.getDeclaredField("mEdgeGlowBottom"); field.setAccessible(true); - EdgeEffect mEdgeGlowBottom = (EdgeEffect) field.get(listView); + EdgeEffect mEdgeGlowBottom = (EdgeEffect) field.get(scrollView); if (mEdgeGlowBottom != null) { mEdgeGlowBottom.setColor(color); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -969,7 +1076,7 @@ public static void clearDrawableAnimation(View view) { public static final int FLAG_TAG_BR = 1; public static final int FLAG_TAG_BOLD = 2; public static final int FLAG_TAG_COLOR = 4; - public static final int FLAG_TAG_ALL = FLAG_TAG_BR | FLAG_TAG_BOLD | FLAG_TAG_COLOR; + public static final int FLAG_TAG_ALL = FLAG_TAG_BR | FLAG_TAG_BOLD; public static SpannableStringBuilder replaceTags(String str) { return replaceTags(str, FLAG_TAG_ALL); @@ -1001,30 +1108,13 @@ public static SpannableStringBuilder replaceTags(String str, int flag) { bolds.add(end); } } - ArrayList colors = new ArrayList<>(); - if ((flag & FLAG_TAG_COLOR) != 0) { - while ((start = stringBuilder.indexOf("", start); - int color = Color.parseColor(stringBuilder.substring(start, end)); - stringBuilder.replace(start, end + 1, ""); - end = stringBuilder.indexOf(""); - stringBuilder.replace(end, end + 4, ""); - colors.add(start); - colors.add(end); - colors.add(color); - } - } SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(stringBuilder); for (int a = 0; a < bolds.size() / 2; a++) { spannableStringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), bolds.get(a * 2), bolds.get(a * 2 + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - for (int a = 0; a < colors.size() / 3; a++) { - spannableStringBuilder.setSpan(new ForegroundColorSpan(colors.get(a * 3 + 2)), colors.get(a * 3), colors.get(a * 3 + 1), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } return spannableStringBuilder; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return new SpannableStringBuilder(str); } @@ -1046,7 +1136,7 @@ public static void shakeView(final View view, final float x, final int num) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(view, "translationX", AndroidUtilities.dp(x))); animatorSet.setDuration(50); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { shakeView(view, num == 5 ? 0 : -x, num + 1); @@ -1131,7 +1221,7 @@ public static void addToClipboard(CharSequence str) { android.content.ClipData clip = android.content.ClipData.newPlainText("label", str); clipboard.setPrimaryClip(clip); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1153,7 +1243,7 @@ public static void addMediaToGallery(Uri uri) { mediaScanIntent.setData(uri); ApplicationLoader.applicationContext.sendBroadcast(mediaScanIntent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1166,12 +1256,12 @@ private static File getAlbumDir() { storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "Telegram"); if (!storageDir.mkdirs()) { if (!storageDir.exists()){ - FileLog.d("tmessages", "failed to create directory"); + FileLog.d("failed to create directory"); return null; } } } else { - FileLog.d("tmessages", "External storage is not mounted READ/WRITE."); + FileLog.d("External storage is not mounted READ/WRITE."); } return storageDir; @@ -1224,7 +1314,7 @@ public static String getPath(final Uri uri) { return uri.getPath(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -1248,7 +1338,7 @@ public static String getDataColumn(Context context, Uri uri, String selection, S return value; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.close(); @@ -1275,7 +1365,7 @@ public static File generatePicturePath() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); return new File(storageDir, "IMG_" + timeStamp + ".jpg"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -1306,12 +1396,15 @@ public static CharSequence generateSearchName(String name, String name2, String builder.append(wholeString.substring(0, idx)); } - String query = wholeString.substring(idx, end); + String query = wholeString.substring(idx, Math.min(wholeString.length(), end)); if (query.startsWith(" ")) { builder.append(" "); } query = query.trim(); - builder.append(AndroidUtilities.replaceTags("" + query + "")); + + int start = builder.length(); + builder.append(query); + builder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), start, start + query.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); lastIndex = end; } @@ -1329,7 +1422,7 @@ public static File generateVideoPath() { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); return new File(storageDir, "VID_" + timeStamp + ".mp4"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -1359,7 +1452,7 @@ public static byte[] decodeQuotedPrintable(final byte[] bytes) { final int l = Character.digit((char) bytes[++i], 16); buffer.write((char) ((u << 4) + l)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return null; } } else { @@ -1370,7 +1463,7 @@ public static byte[] decodeQuotedPrintable(final byte[] bytes) { try { buffer.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return array; } @@ -1398,7 +1491,7 @@ public static boolean copyFile(File sourceFile, File destFile) throws IOExceptio destination = new FileOutputStream(destFile); destination.getChannel().transferFrom(source.getChannel(), 0, source.getChannel().size()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return false; } finally { if (source != null) { @@ -1467,6 +1560,52 @@ public static void openForView(MessageObject message, Activity activity) throws } } + public static void openForView(TLObject media, Activity activity) throws Exception { + if (media == null || activity == null) { + return; + } + String fileName = FileLoader.getAttachFileName(media); + File f = FileLoader.getPathToAttach(media, true); + if (f != null && f.exists()) { + String realMimeType = null; + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + MimeTypeMap myMime = MimeTypeMap.getSingleton(); + int idx = fileName.lastIndexOf('.'); + if (idx != -1) { + String ext = fileName.substring(idx + 1); + realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); + if (realMimeType == null) { + if (media instanceof TLRPC.TL_document) { + realMimeType = ((TLRPC.TL_document) media).mime_type; + } + if (realMimeType == null || realMimeType.length() == 0) { + realMimeType = null; + } + } + } + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); + } else { + intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); + } + if (realMimeType != null) { + try { + activity.startActivityForResult(intent, 500); + } catch (Exception e) { + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); + } else { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); + } + activity.startActivityForResult(intent, 500); + } + } else { + activity.startActivityForResult(intent, 500); + } + } + } + public static void setRectToRect(Matrix matrix, RectF src, RectF dst, int rotation, Matrix.ScaleToFit align) { float tx, sx; float ty, sy; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AnimatorListenerAdapterProxy.java b/TMessagesProj/src/main/java/org/telegram/messenger/AnimatorListenerAdapterProxy.java deleted file mode 100644 index 855df9b853d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AnimatorListenerAdapterProxy.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.messenger; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; - -public class AnimatorListenerAdapterProxy extends AnimatorListenerAdapter { - - @Override - public void onAnimationCancel(Animator animator) { - - } - - @Override - public void onAnimationEnd(Animator animator) { - - } - - @Override - public void onAnimationRepeat(Animator animator) { - - } - - @Override - public void onAnimationStart(Animator animator) { - - } - - @Override - public void onAnimationPause(Animator animator) { - - } - - @Override - public void onAnimationResume(Animator animator) { - - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AppStartReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/AppStartReceiver.java index 4a3980854fd..5a98f807f54 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AppStartReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AppStartReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java index 2d93a989ab6..a169bc04946 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ApplicationLoader.java @@ -3,11 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; +import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlarmManager; import android.app.Application; @@ -20,10 +21,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.res.Configuration; -import android.graphics.Shader; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.PowerManager; @@ -42,110 +39,15 @@ public class ApplicationLoader extends Application { - private static Drawable cachedWallpaper; - private static int selectedColor; - private static boolean isCustomTheme; - private static final Object sync = new Object(); - - private static int serviceMessageColor; - private static int serviceSelectedMessageColor; - + @SuppressLint("StaticFieldLeak") public static volatile Context applicationContext; public static volatile Handler applicationHandler; private static volatile boolean applicationInited = false; public static volatile boolean isScreenOn = false; public static volatile boolean mainInterfacePaused = true; - - public static boolean isCustomTheme() { - return isCustomTheme; - } - - public static int getSelectedColor() { - return selectedColor; - } - - public static void reloadWallpaper() { - cachedWallpaper = null; - serviceMessageColor = 0; - ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit().remove("serviceMessageColor").commit(); - loadWallpaper(); - } - - private static void calcBackgroundColor() { - int result[] = AndroidUtilities.calcDrawableColor(cachedWallpaper); - serviceMessageColor = result[0]; - serviceSelectedMessageColor = result[1]; - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - preferences.edit().putInt("serviceMessageColor", serviceMessageColor).putInt("serviceSelectedMessageColor", serviceSelectedMessageColor).commit(); - } - - public static int getServiceMessageColor() { - return serviceMessageColor; - } - - public static int getServiceSelectedMessageColor() { - return serviceSelectedMessageColor; - } - - public static void loadWallpaper() { - if (cachedWallpaper != null) { - return; - } - Utilities.searchQueue.postRunnable(new Runnable() { - @Override - public void run() { - synchronized (sync) { - int selectedColor = 0; - try { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - int selectedBackground = preferences.getInt("selectedBackground", 1000001); - selectedColor = preferences.getInt("selectedColor", 0); - serviceMessageColor = preferences.getInt("serviceMessageColor", 0); - serviceSelectedMessageColor = preferences.getInt("serviceSelectedMessageColor", 0); - if (selectedColor == 0) { - if (selectedBackground == 1000001) { - cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd); - isCustomTheme = false; - } else { - File toFile = new File(getFilesDirFixed(), "wallpaper.jpg"); - if (toFile.exists()) { - cachedWallpaper = Drawable.createFromPath(toFile.getAbsolutePath()); - isCustomTheme = true; - } else { - cachedWallpaper = applicationContext.getResources().getDrawable(R.drawable.background_hd); - isCustomTheme = false; - } - } - } - } catch (Throwable throwable) { - //ignore - } - if (cachedWallpaper == null) { - if (selectedColor == 0) { - selectedColor = -2693905; - } - cachedWallpaper = new ColorDrawable(selectedColor); - } - if (serviceMessageColor == 0) { - calcBackgroundColor(); - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.didSetNewWallpapper); - } - }); - } - } - }); - } - - public static Drawable getCachedWallpaper() { - synchronized (sync) { - return cachedWallpaper; - } - } + public static volatile boolean mainInterfacePausedStageQueue = true; + public static volatile long mainInterfacePausedStageQueueTime; private static void convertConfig() { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("dataconfig", Context.MODE_PRIVATE); @@ -172,7 +74,7 @@ private static void convertConfig() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -183,7 +85,7 @@ private static void convertConfig() { fileOutputStream.write(bytes); fileOutputStream.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } buffer.cleanup(); preferences.edit().clear().commit(); @@ -203,7 +105,7 @@ public static File getFilesDirFixed() { path.mkdirs(); return path; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return new File("/data/data/org.telegram.messenger/files"); } @@ -234,9 +136,9 @@ public static void postInitApplication() { try { PowerManager pm = (PowerManager)ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); isScreenOn = pm.isScreenOn(); - FileLog.e("tmessages", "screen state = " + isScreenOn); + FileLog.e("screen state = " + isScreenOn); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } UserConfig.loadConfig(); @@ -285,7 +187,7 @@ public static void postInitApplication() { ApplicationLoader app = (ApplicationLoader)ApplicationLoader.applicationContext; app.initPlayServices(); - FileLog.e("tmessages", "app initied"); + FileLog.e("app initied"); ContactsController.getInstance().checkAppAccount(); MediaController.getInstance(); @@ -359,19 +261,19 @@ private void initPlayServices() { public void run() { if (checkPlayServices()) { if (UserConfig.pushString != null && UserConfig.pushString.length() != 0) { - FileLog.d("tmessages", "GCM regId = " + UserConfig.pushString); + FileLog.d("GCM regId = " + UserConfig.pushString); } else { - FileLog.d("tmessages", "GCM Registration not found."); + FileLog.d("GCM Registration not found."); } //if (UserConfig.pushString == null || UserConfig.pushString.length() == 0) { Intent intent = new Intent(applicationContext, GcmRegistrationIntentService.class); startService(intent); //} else { - // FileLog.d("tmessages", "GCM regId = " + UserConfig.pushString); + // FileLog.d("GCM regId = " + UserConfig.pushString); //} } else { - FileLog.d("tmessages", "No valid Google Play Services APK found."); + FileLog.d("No valid Google Play Services APK found."); } } }, 1000); @@ -383,9 +285,9 @@ public void run() { public void run() { if (checkPlayServices()) { if (UserConfig.pushString != null && UserConfig.pushString.length() != 0) { - FileLog.d("tmessages", "GCM regId = " + UserConfig.pushString); + FileLog.d("GCM regId = " + UserConfig.pushString); } else { - FileLog.d("tmessages", "GCM Registration not found."); + FileLog.d("GCM Registration not found."); } try { if (!FirebaseApp.getApps(ApplicationLoader.applicationContext).isEmpty()) { @@ -395,10 +297,10 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { - FileLog.d("tmessages", "No valid Google Play Services APK found."); + FileLog.d("No valid Google Play Services APK found."); } } }, 2000); @@ -409,7 +311,7 @@ private boolean checkPlayServices() { int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this); return resultCode == ConnectionResult.SUCCESS; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return true; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AuthenticatorService.java b/TMessagesProj/src/main/java/org/telegram/messenger/AuthenticatorService.java index 82bd4f4bfeb..fdccdaf6da1 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AuthenticatorService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AuthenticatorService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java index a00576b781f..684959d5c5d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageHeardReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageReplyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageReplyReceiver.java index 1f45a72734a..1f3f8c0141d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageReplyReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AutoMessageReplyReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Bitmaps.java b/TMessagesProj/src/main/java/org/telegram/messenger/Bitmaps.java index 77778f8d2aa..7e04d72531a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Bitmaps.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Bitmaps.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BringAppForegroundService.java b/TMessagesProj/src/main/java/org/telegram/messenger/BringAppForegroundService.java new file mode 100644 index 00000000000..6ec74bd5cd8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BringAppForegroundService.java @@ -0,0 +1,28 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.app.IntentService; +import android.content.Intent; + +import org.telegram.ui.LaunchActivity; + +public class BringAppForegroundService extends IntentService { + + public BringAppForegroundService() { + super("BringAppForegroundService"); + } + + @Override + protected void onHandleIntent(Intent intent) { + Intent intent2 = new Intent(this, LaunchActivity.class); + intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent2); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java index 1b5947578d6..deb8135c26e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/BuildVars.java @@ -3,21 +3,20 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; public class BuildVars { public static boolean DEBUG_VERSION = false; - public static int BUILD_VERSION = 851; - public static String BUILD_VERSION_STRING = "3.13"; + public static boolean DEBUG_PRIVATE_VERSION = false; + public static int BUILD_VERSION = 957; + public static String BUILD_VERSION_STRING = "3.18"; public static int APP_ID = 0; //obtain your own APP_ID at https://core.telegram.org/api/obtaining_api_id public static String APP_HASH = ""; //obtain your own APP_HASH at https://core.telegram.org/api/obtaining_api_id public static String HOCKEY_APP_HASH = "your-hockeyapp-api-key-here"; public static String HOCKEY_APP_HASH_DEBUG = "your-hockeyapp-api-key-here"; - public static String GCM_SENDER_ID = "760348033672"; - public static String SEND_LOGS_EMAIL = "email@gmail.com"; public static String BING_SEARCH_KEY = ""; //obtain your own KEY at https://www.bing.com/dev/en-us/dev-center public static String FOURSQUARE_API_KEY = ""; //obtain your own KEY at https://developer.foursquare.com/ public static String FOURSQUARE_API_ID = ""; //obtain your own API_ID at https://developer.foursquare.com/ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java index 58eeacd262f..9eccd6a5d7c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/CallReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -20,15 +20,21 @@ public class CallReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { - TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + if (intent.getAction().equals("android.intent.action.PHONE_STATE")) { + String phoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE); + if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) { + String phoneNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveCall, PhoneFormat.stripExceptNumbers(phoneNumber)); + } + } + /*TelephonyManager telephony = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); telephony.listen(new PhoneStateListener() { @Override public void onCallStateChanged(int state, String incomingNumber) { - super.onCallStateChanged(state, incomingNumber); if (state == 1 && incomingNumber != null && incomingNumber.length() > 0) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.didReceiveCall, PhoneFormat.stripExceptNumbers(incomingNumber)); } } - }, PhoneStateListener.LISTEN_CALL_STATE); + }, PhoneStateListener.LISTEN_CALL_STATE);*/ } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java index b3b58216bb2..73477e18f35 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ChatObject.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ClearCacheService.java b/TMessagesProj/src/main/java/org/telegram/messenger/ClearCacheService.java index fb45992d559..bc6a35e4e14 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ClearCacheService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ClearCacheService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -64,7 +64,7 @@ public void run() { f.delete(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (f.lastModified() + diff < currentTime) { f.delete(); @@ -73,7 +73,7 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java index cab3a4fdef5..4296c0d6e0f 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsController.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -53,12 +53,14 @@ public class ContactsController { private boolean updatingInviteText = false; private HashMap sectionsToReplace = new HashMap<>(); - private int loadingDeleteInfo = 0; + private int loadingDeleteInfo; private int deleteAccountTTL; - private int loadingLastSeenInfo = 0; - private int loadingGroupInfo = 0; + private int loadingLastSeenInfo; + private int loadingCallsInfo; + private int loadingGroupInfo; private ArrayList privacyRules = null; private ArrayList groupPrivacyRules = null; + private ArrayList callPrivacyRules = null; public static class Contact { public int id; @@ -166,6 +168,7 @@ public void cleanup() { deleteAccountTTL = 0; loadingLastSeenInfo = 0; loadingGroupInfo = 0; + loadingCallsInfo = 0; Utilities.globalQueue.postRunnable(new Runnable() { @Override public void run() { @@ -221,7 +224,7 @@ public void checkAppAccount() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } accounts = am.getAccountsByType("org.telegram.messenger"); @@ -249,14 +252,14 @@ public void checkAppAccount() { am.removeAccount(accounts[a], null, null); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (UserConfig.isClientActivated()) { try { currentAccount = new Account("" + UserConfig.getClientUserId(), "org.telegram.messenger"); am.addAccountExplicitly(currentAccount, "", null); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -279,7 +282,7 @@ public void checkContacts() { @Override public void run() { if (checkContactsInternal()) { - FileLog.e("tmessages", "detected contacts change"); + FileLog.e("detected contacts change"); ContactsController.getInstance().performSyncPhoneBook(ContactsController.getInstance().getContactsCopy(ContactsController.getInstance().contactsBook), true, false, true, false); } } @@ -318,14 +321,14 @@ private boolean checkContactsInternal() { lastContactsVersions = newContactsVersion; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (pCur != null) { pCur.close(); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return reload; } @@ -367,7 +370,7 @@ private HashMap readContactsFromPhoneBook() { if (pCur.getCount() > 0) { while (pCur.moveToNext()) { String number = pCur.getString(1); - if (number == null || number.length() == 0) { + if (TextUtils.isEmpty(number)) { continue; } number = PhoneFormat.stripExceptNumbers(number, true); @@ -405,7 +408,8 @@ private HashMap readContactsFromPhoneBook() { contact.phoneDeleted.add(0); if (type == ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM) { - contact.phoneTypes.add(pCur.getString(3)); + String custom = pCur.getString(3); + contact.phoneTypes.add(custom != null ? custom : LocaleController.getString("PhoneMobile", R.string.PhoneMobile)); } else if (type == ContactsContract.CommonDataKinds.Phone.TYPE_HOME) { contact.phoneTypes.add(LocaleController.getString("PhoneHome", R.string.PhoneHome)); } else if (type == ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE) { @@ -425,7 +429,7 @@ private HashMap readContactsFromPhoneBook() { String ids = TextUtils.join(",", idsArr); pCur = cr.query(ContactsContract.Data.CONTENT_URI, projectionNames, ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " IN (" + ids + ") AND " + ContactsContract.Data.MIMETYPE + " = '" + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE + "'", null, null); - if (pCur != null && pCur.getCount() > 0) { + if (pCur != null) { while (pCur.moveToNext()) { int id = pCur.getInt(0); String fname = pCur.getString(1); @@ -433,13 +437,13 @@ private HashMap readContactsFromPhoneBook() { String sname2 = pCur.getString(3); String mname = pCur.getString(4); Contact contact = contactsMap.get(id); - if (contact != null && contact.first_name.length() == 0 && contact.last_name.length() == 0) { + if (contact != null && TextUtils.isEmpty(contact.first_name) && TextUtils.isEmpty(contact.last_name)) { contact.first_name = fname; contact.last_name = sname; if (contact.first_name == null) { contact.first_name = ""; } - if (mname != null && mname.length() != 0) { + if (!TextUtils.isEmpty(mname)) { if (contact.first_name.length() != 0) { contact.first_name += " " + mname; } else { @@ -449,7 +453,7 @@ private HashMap readContactsFromPhoneBook() { if (contact.last_name == null) { contact.last_name = ""; } - if (contact.last_name.length() == 0 && contact.first_name.length() == 0 && sname2 != null && sname2.length() != 0) { + if (TextUtils.isEmpty(contact.last_name) && TextUtils.isEmpty(contact.first_name) && !TextUtils.isEmpty(sname2)) { contact.first_name = sname2; } } @@ -457,7 +461,7 @@ private HashMap readContactsFromPhoneBook() { pCur.close(); } - try { + /*try { pCur = cr.query(ContactsContract.RawContacts.CONTENT_URI, new String[]{"display_name", ContactsContract.RawContacts.SYNC1, ContactsContract.RawContacts.CONTACT_ID}, ContactsContract.RawContacts.ACCOUNT_TYPE + " = " + "'com.whatsapp'", null, null); if (pCur != null) { while ((pCur.moveToNext())) { @@ -499,26 +503,26 @@ private HashMap readContactsFromPhoneBook() { pCur.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); - } + FileLog.e(e); + }*/ } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); contactsMap.clear(); } /*if (BuildVars.DEBUG_VERSION) { for (HashMap.Entry entry : contactsMap.entrySet()) { Contact contact = entry.getValue(); - FileLog.e("tmessages", "contact = " + contact.first_name + " " + contact.last_name); + FileLog.e("contact = " + contact.first_name + " " + contact.last_name); if (contact.first_name.length() == 0 && contact.last_name.length() == 0 && contact.phones.size() > 0) { - FileLog.e("tmessages", "warning, empty name for contact = " + contact.id); + FileLog.e("warning, empty name for contact = " + contact.id); } - FileLog.e("tmessages", "phones:"); + FileLog.e("phones:"); for (String s : contact.phones) { - FileLog.e("tmessages", "phone = " + s); + FileLog.e("phone = " + s); } - FileLog.e("tmessages", "short phones:"); + FileLog.e("short phones:"); for (String s : contact.shortPhones) { - FileLog.e("tmessages", "short phone = " + s); + FileLog.e("short phone = " + s); } } }*/ @@ -558,7 +562,7 @@ public void run() { boolean recreateAccount = false; if (UserConfig.isClientActivated()) { if (accounts.length != 1) { - FileLog.e("tmessages", "detected account deletion!"); + FileLog.e("detected account deletion!"); currentAccount = new Account(UserConfig.getCurrentUser().phone, "org.telegram.account"); am.addAccountExplicitly(currentAccount, "", null); AndroidUtilities.runOnUIThread(new Runnable() { @@ -570,7 +574,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } }*/ @@ -582,7 +586,7 @@ public void run() { } } - FileLog.e("tmessages", "start read contacts from phone"); + FileLog.e("start read contacts from phone"); if (!schedule) { checkContactsInternal(); } @@ -607,7 +611,7 @@ public void run() { } } - boolean nameChanged = existing != null && (TextUtils.isEmpty(value.first_name) && !existing.first_name.equals(value.first_name) || !TextUtils.isEmpty(value.last_name) && !existing.last_name.equals(value.last_name)); + boolean nameChanged = existing != null && (!TextUtils.isEmpty(value.first_name) && !existing.first_name.equals(value.first_name) || !TextUtils.isEmpty(value.last_name) && !existing.last_name.equals(value.last_name)); if (existing == null || nameChanged) { for (int a = 0; a < value.phones.size(); a++) { String sphone = value.shortPhones.get(a); @@ -644,16 +648,29 @@ public void run() { String sphone = value.shortPhones.get(a); contactsBookShort.put(sphone, value); int index = existing.shortPhones.indexOf(sphone); + boolean emptyNameReimport = false; + if (request) { + TLRPC.TL_contact contact = contactsByPhone.get(sphone); + if (contact != null) { + TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); + if (user != null && TextUtils.isEmpty(user.first_name) && TextUtils.isEmpty(user.last_name) && (!TextUtils.isEmpty(value.first_name) || !TextUtils.isEmpty(value.last_name))) { + index = -1; + emptyNameReimport = true; + } + } + } if (index == -1) { if (request) { - TLRPC.TL_contact contact = contactsByPhone.get(sphone); - if (contact != null) { - TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); - if (user != null) { - String firstName = user.first_name != null ? user.first_name : ""; - String lastName = user.last_name != null ? user.last_name : ""; - if (user != null && (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name))) { - continue; + if (!emptyNameReimport) { + TLRPC.TL_contact contact = contactsByPhone.get(sphone); + if (contact != null) { + TLRPC.User user = MessagesController.getInstance().getUser(contact.user_id); + if (user != null) { + String firstName = user.first_name != null ? user.first_name : ""; + String lastName = user.last_name != null ? user.last_name : ""; + if (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) { + continue; + } } } } @@ -680,7 +697,7 @@ public void run() { } } if (!first && contactHashMap.isEmpty() && toImport.isEmpty() && oldCount == contactsMap.size()) { - FileLog.e("tmessages", "contacts not changed!"); + FileLog.e("contacts not changed!"); return; } if (request && !contactHashMap.isEmpty() && !contactsMap.isEmpty()) { @@ -692,12 +709,12 @@ public void run() { @Override public void run() { /*if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "need delete contacts"); + FileLog.e("need delete contacts"); for (HashMap.Entry c : contactHashMap.entrySet()) { Contact contact = c.getValue(); - FileLog.e("tmessages", "delete contact " + contact.first_name + " " + contact.last_name); + FileLog.e("delete contact " + contact.first_name + " " + contact.last_name); for (String phone : contact.phones) { - FileLog.e("tmessages", phone); + FileLog.e(phone); } } }*/ @@ -710,7 +727,7 @@ public void run() { for (int a = 0; a < contacts.size(); a++) { TLRPC.TL_contact value = contacts.get(a); TLRPC.User user = MessagesController.getInstance().getUser(value.user_id); - if (user == null || user.phone == null || user.phone.length() == 0) { + if (user == null || TextUtils.isEmpty(user.phone)) { continue; } contactsPhonesShort.put(user.phone, user); @@ -734,7 +751,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -758,7 +775,7 @@ public void run() { if (user != null) { String firstName = user.first_name != null ? user.first_name : ""; String lastName = user.last_name != null ? user.last_name : ""; - if (user != null && (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name))) { + if (firstName.equals(value.first_name) && lastName.equals(value.last_name) || TextUtils.isEmpty(value.first_name) && TextUtils.isEmpty(value.last_name)) { continue; } } @@ -775,14 +792,14 @@ public void run() { } } - FileLog.e("tmessages", "done processing contacts"); + FileLog.e("done processing contacts"); if (request) { if (!toImport.isEmpty()) { /*if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "start import contacts"); + FileLog.e("start import contacts"); for (TLRPC.TL_inputPhoneContact contact : toImport) { - FileLog.e("tmessages", "add contact " + contact.first_name + " " + contact.last_name + " " + contact.phone); + FileLog.e("add contact " + contact.first_name + " " + contact.last_name + " " + contact.phone); } }*/ @@ -800,7 +817,7 @@ public void run() { public void run(TLObject response, TLRPC.TL_error error) { completedRequestsCount++; if (error == null) { - FileLog.e("tmessages", "contacts imported"); + FileLog.e("contacts imported"); TLRPC.TL_contacts_importedContacts res = (TLRPC.TL_contacts_importedContacts) response; if (!res.retry_contacts.isEmpty()) { for (int a = 0; a < res.retry_contacts.size(); a++) { @@ -815,7 +832,7 @@ public void run(TLObject response, TLRPC.TL_error error) { /*if (BuildVars.DEBUG_VERSION) { for (TLRPC.User user : res.users) { - FileLog.e("tmessages", "received user " + user.first_name + " " + user.last_name + " " + user.phone); + FileLog.e("received user " + user.first_name + " " + user.last_name + " " + user.phone); } }*/ MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true); @@ -827,7 +844,7 @@ public void run(TLObject response, TLRPC.TL_error error) { } processLoadedContacts(cArr, res.users, 2); } else { - FileLog.e("tmessages", "import contacts error " + error.text); + FileLog.e("import contacts error " + error.text); } if (completedRequestsCount == count) { Utilities.stageQueue.postRunnable(new Runnable() { @@ -911,10 +928,10 @@ public void loadContacts(boolean fromCache, boolean cacheEmpty) { loadingContacts = true; } if (fromCache) { - FileLog.e("tmessages", "load contacts from cache"); + FileLog.e("load contacts from cache"); MessagesStorage.getInstance().getContacts(); } else { - FileLog.e("tmessages", "load contacts from server"); + FileLog.e("load contacts from server"); TLRPC.TL_contacts_getContacts req = new TLRPC.TL_contacts_getContacts(); req.hash = cacheEmpty ? "" : UserConfig.contactsHash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -937,7 +954,7 @@ public void run() { NotificationCenter.getInstance().postNotificationName(NotificationCenter.contactsDidLoaded); } }); - FileLog.e("tmessages", "load contacts don't change"); + FileLog.e("load contacts don't change"); return; } processLoadedContacts(res.contacts, res.users, 0); @@ -974,7 +991,7 @@ public void run() { if (user != null) { usersDict.put(user.id, user); //if (BuildVars.DEBUG_VERSION) { - // FileLog.e("tmessages", "loaded user contact " + user.first_name + " " + user.last_name + " " + user.phone); + // FileLog.e("loaded user contact " + user.first_name + " " + user.last_name + " " + user.phone); //} } } @@ -982,7 +999,7 @@ public void run() { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - FileLog.e("tmessages", "done loading contacts"); + FileLog.e("done loading contacts"); if (from == 1 && (contactsArr.isEmpty() || Math.abs(System.currentTimeMillis() / 1000 - UserConfig.lastContactsSyncTime) >= 24 * 60 * 60)) { loadContacts(false, true); return; @@ -995,7 +1012,7 @@ public void run() { for (TLRPC.TL_contact contact : contactsArr) { if (usersDict.get(contact.user_id) == null && contact.user_id != UserConfig.getClientUserId()) { loadContacts(false, true); - FileLog.e("tmessages", "contacts are broken, load from server"); + FileLog.e("contacts are broken, load from server"); return; } } @@ -1056,7 +1073,7 @@ public int compare(TLRPC.TL_contact tl_contact, TLRPC.TL_contact tl_contact2) { continue; } contactsDictionary.put(value.user_id, value); - if (contactsByPhonesDict != null) { + if (contactsByPhonesDict != null && !TextUtils.isEmpty(user.phone)) { contactsByPhonesDict.put(user.phone, value); } @@ -1185,7 +1202,7 @@ private void reloadContactsStatusesMaybe() { reloadContactsStatuses(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1194,16 +1211,17 @@ private void saveContactsLoadTime() { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); preferences.edit().putLong("lastReloadStatusTime", System.currentTimeMillis()).commit(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } private void updateUnregisteredContacts(final ArrayList contactsArr) { final HashMap contactsPhonesShort = new HashMap<>(); - for (TLRPC.TL_contact value : contactsArr) { + for (int a = 0; a < contactsArr.size(); a++) { + TLRPC.TL_contact value = contactsArr.get(a); TLRPC.User user = MessagesController.getInstance().getUser(value.user_id); - if (user == null || user.phone == null || user.phone.length() == 0) { + if (user == null || TextUtils.isEmpty(user.phone)) { continue; } contactsPhonesShort.put(user.phone, value); @@ -1328,14 +1346,14 @@ private boolean hasContactsPermission() { return false; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (cursor != null) { cursor.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return true; @@ -1364,7 +1382,7 @@ private void performWriteContactsToPhoneBookInternal(ArrayList } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1394,7 +1412,7 @@ private void applyContactsUpdates(ArrayList ids, ConcurrentHashMap 0) { + if (!TextUtils.isEmpty(u.phone)) { CharSequence name = formatName(u.first_name, u.last_name); MessagesStorage.getInstance().applyPhoneBookUpdates(u.phone, ""); Contact contact = contactsBookSPhones.get(u.phone); @@ -1763,16 +1781,18 @@ public void run() { } }); - for (TLRPC.User user : users) { - if (user.phone != null && user.phone.length() > 0) { - CharSequence name = UserObject.getUserName(user); - MessagesStorage.getInstance().applyPhoneBookUpdates(user.phone, ""); - Contact contact = contactsBookSPhones.get(user.phone); - if (contact != null) { - int index = contact.shortPhones.indexOf(user.phone); - if (index != -1) { - contact.phoneDeleted.set(index, 1); - } + for (int a = 0; a < users.size(); a++) { + TLRPC.User user = users.get(a); + if (TextUtils.isEmpty(user.phone)) { + continue; + } + CharSequence name = UserObject.getUserName(user); + MessagesStorage.getInstance().applyPhoneBookUpdates(user.phone, ""); + Contact contact = contactsBookSPhones.get(user.phone); + if (contact != null) { + int index = contact.shortPhones.indexOf(user.phone); + if (index != -1) { + contact.phoneDeleted.set(index, 1); } } } @@ -1897,6 +1917,30 @@ public void run() { } }); } + if (loadingCallsInfo == 0) { + loadingCallsInfo = 1; + TLRPC.TL_account_getPrivacy req = new TLRPC.TL_account_getPrivacy(); + req.key = new TLRPC.TL_inputPrivacyKeyPhoneCall(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + TLRPC.TL_account_privacyRules rules = (TLRPC.TL_account_privacyRules) response; + MessagesController.getInstance().putUsers(rules.users, false); + callPrivacyRules = rules.rules; + loadingCallsInfo = 2; + } else { + loadingCallsInfo = 0; + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.privacyRulesUpdated); + } + }); + } + }); + } if (loadingGroupInfo == 0) { loadingGroupInfo = 1; TLRPC.TL_account_getPrivacy req = new TLRPC.TL_account_getPrivacy(); @@ -1940,20 +1984,28 @@ public boolean getLoadingLastSeenInfo() { return loadingLastSeenInfo != 2; } + public boolean getLoadingCallsInfo() { + return loadingCallsInfo != 2; + } + public boolean getLoadingGroupInfo() { return loadingGroupInfo != 2; } - public ArrayList getPrivacyRules(boolean isGroup) { - if (isGroup) { + public ArrayList getPrivacyRules(int type) { + if (type == 2) { + return callPrivacyRules; + } else if (type == 1) { return groupPrivacyRules; } else { return privacyRules; } } - public void setPrivacyRules(ArrayList rules, boolean isGroup) { - if (isGroup) { + public void setPrivacyRules(ArrayList rules, int type) { + if (type == 2) { + callPrivacyRules = rules; + } else if (type == 1) { groupPrivacyRules = rules; } else { privacyRules = rules; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsSyncAdapterService.java b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsSyncAdapterService.java index 6a08d0f87dc..c835226a55a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ContactsSyncAdapterService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ContactsSyncAdapterService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -39,7 +39,7 @@ public void onPerformSync(Account account, Bundle extras, String authority, Cont try { ContactsSyncAdapterService.performSync(mContext, account, extras, authority, provider, syncResult); } catch (OperationCanceledException e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -58,6 +58,6 @@ private SyncAdapterImpl getSyncAdapter() { private static void performSync(Context context, Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) throws OperationCanceledException { - FileLog.d("telegram", "performSync: " + account.toString()); + FileLog.d("performSync: " + account.toString()); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DialogObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/DialogObject.java index a31ed060f39..e9f073c87d2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DialogObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DialogObject.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueue.java b/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueue.java index 6dce277ec9e..ec47e9a2915 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueue.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DispatchQueue.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -33,7 +33,7 @@ private void sendMessage(Message msg, int delay) { handler.sendMessageDelayed(msg, delay); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -42,7 +42,7 @@ public void cancelRunnable(Runnable runnable) { syncLatch.await(); handler.removeCallbacks(runnable); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -59,7 +59,7 @@ public void postRunnable(Runnable runnable, long delay) { handler.postDelayed(runnable, delay); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -68,7 +68,7 @@ public void cleanupQueue() { syncLatch.await(); handler.removeCallbacksAndMessages(null); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadObject.java index 2a48e1c0259..7b58b30ad68 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/DownloadObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/DownloadObject.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java index 282a568f09c..888563acbd7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Emoji.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -42,11 +42,11 @@ public class Emoji { private static boolean loadingEmoji[][] = new boolean[5][splitCount]; private static final int[][] cols = { - {12, 12, 12, 12}, + {15, 15, 15, 15}, {6, 6, 6, 6}, + {8, 8, 8, 8}, {9, 9, 9, 9}, - {9, 9, 9, 9}, - {8, 8, 8, 7} + {10, 10, 10, 10} }; static { @@ -111,7 +111,7 @@ private static void loadEmoji(final int page, final int page2) { } } } - FileLog.e("tmessages", q);*/ + FileLog.e(q);*/ String imageName; File imageFile; @@ -129,7 +129,7 @@ private static void loadEmoji(final int page, final int page2) { imageFile.delete(); } } - for (int a = 8; a < 10; a++) { + for (int a = 8; a < 11; a++) { imageName = String.format(Locale.US, "v%d_emoji%.01fx_%d.png", a, scale, page); imageFile = ApplicationLoader.applicationContext.getFileStreamPath(imageName); if (imageFile.exists()) { @@ -137,18 +137,18 @@ private static void loadEmoji(final int page, final int page2) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Bitmap bitmap = null; try { - InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + String.format(Locale.US, "v10_emoji%.01fx_%d_%d.png", scale, page, page2)); + InputStream is = ApplicationLoader.applicationContext.getAssets().open("emoji/" + String.format(Locale.US, "v11_emoji%.01fx_%d_%d.png", scale, page, page2)); BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = false; opts.inSampleSize = imageResize; bitmap = BitmapFactory.decodeStream(is, null, opts); is.close(); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } final Bitmap finalBitmap = bitmap; @@ -160,7 +160,7 @@ public void run() { } }); } catch (Throwable x) { - FileLog.e("tmessages", "Error loading emoji", x); + FileLog.e("Error loading emoji", x); } } @@ -209,7 +209,7 @@ public static String fixEmoji(String emoji) { public static EmojiDrawable getEmojiDrawable(CharSequence code) { DrawableInfo info = rects.get(code); if (info == null) { - FileLog.e("tmessages", "No drawable for emoji " + code); + FileLog.e("No drawable for emoji " + code); return null; } EmojiDrawable ed = new EmojiDrawable(info); @@ -334,6 +334,7 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo if (MessagesController.getInstance().useSystemEmoji || cs == null || cs.length() == 0) { return cs; } + //String str = "\"\uD83D\uDC68\uD83C\uDFFB\u200D\uD83C\uDFA4\"" //SpannableStringLight.isFieldsAvailable(); //SpannableStringLight s = new SpannableStringLight(cs.toString()); Spannable s; @@ -349,11 +350,14 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo int startLength = 0; int previousGoodIndex = 0; StringBuilder emojiCode = new StringBuilder(16); + StringBuilder addionalCode = new StringBuilder(2); boolean nextIsSkinTone; EmojiDrawable drawable; EmojiSpan span; int length = cs.length(); boolean doneEmoji = false; + int nextValidLength; + boolean nextValid; //s.setSpansCount(emojiCount); try { @@ -367,7 +371,7 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo startLength++; buf <<= 16; buf |= c; - } else if (emojiCode.length() > 0 && (c == 0x2640 || c == 0x2642)) { + } else if (emojiCode.length() > 0 && (c == 0x2640 || c == 0x2642 || c == 0x2695)) { emojiCode.append(c); startLength++; buf = 0; @@ -406,6 +410,14 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo emojiOnly = null; } } + if (doneEmoji && i + 2 < length && cs.charAt(i + 1) == 0xD83C) { + char next = cs.charAt(i + 2); + if (next >= 0xDFFB && next <= 0xDFFF) { + emojiCode.append(cs.subSequence(i + 1, i + 3)); + startLength += 2; + i += 2; + } + } previousGoodIndex = i; for (int a = 0; a < 3; a++) { if (i + 1 < length) { @@ -425,24 +437,18 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo } } } + if (doneEmoji && i + 2 < length && cs.charAt(i + 1) == 0xD83C) { + char next = cs.charAt(i + 2); + if (next >= 0xDFFB && next <= 0xDFFF) { + emojiCode.append(cs.subSequence(i + 1, i + 3)); + startLength += 2; + i += 2; + } + } if (doneEmoji) { if (emojiOnly != null) { emojiOnly[0]++; } - if (i + 2 < length) { - if (cs.charAt(i + 1) == 0xD83C && cs.charAt(i + 2) >= 0xDFFB && cs.charAt(i + 2) <= 0xDFFF) { - emojiCode.append(cs.subSequence(i + 1, i + 3)); - startLength += 2; - i += 2; - } - } - if (i + 2 < length) { - if (cs.charAt(i + 1) == 0x200D && (cs.charAt(i + 2) == 0x2640 || cs.charAt(i + 2) == 0x2642)) { - emojiCode.append(cs.subSequence(i + 1, i + 3)); - startLength += 2; - i += 2; - } - } drawable = Emoji.getEmojiDrawable(emojiCode.subSequence(0, emojiCode.length())); if (drawable != null) { span = new EmojiSpan(drawable, DynamicDrawableSpan.ALIGN_BOTTOM, size, fontMetrics); @@ -459,7 +465,7 @@ public static CharSequence replaceEmoji(CharSequence cs, Paint.FontMetricsInt fo } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return cs; } return s; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java index c1d9b37c60e..da7139d4ff5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/EmojiData.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -46,52 +46,61 @@ public class EmojiData { 0x26C4, 0x2602, 0x2614 }; + public static final String[] emojiSecret = { + "😉","😍","😛","😭","😱","😡","😎","😴","😵","😈","😬","😇","😏","👮","👷","💂","👶","👨","👩","👴","👵","😻","😽","🙀","👺","🙈","🙉","🙊","💀","👽","💩","🔥","💥", + "💤","👂","👀","👃","👅","👄","👍","👎","👌","👊","✌","✋","👐","👆","👇","👉","👈","🙏","👏","💪","🚶","🏃","💃","👫","👪","👬","👭","💅","🎩","👑","👒","👟","👞", + "👠","👕","👗","👖","👙","👜","👓","🎀","💄","💛","💙","💜","💚","💍","💎","🐶","🐺","🐱","🐭","🐹","🐰","🐸","🐯","🐨","🐻","🐷","🐮","🐗","🐴","🐑","🐘","🐼","🐧", + "🐥","🐔","🐍","🐢","🐛","🐝","🐜","🐞","🐌","🐙","🐚","🐟","🐬","🐋","🐐","🐊","🐫","🍀","🌹","🌻","🍁","🌾","🍄","🌵","🌴","🌳","🌞","🌚","🌙","🌎","🌋","⚡","☔","❄", + "⛄","🌀","🌈","🌊","🎓","🎆","🎃","👻","🎅","🎄","🎁","🎈","🔮","🎥","📷","💿","💻","☎","📡","📺","📻","🔉","🔔","⏳","⏰","⌚","🔒","🔑","🔎","💡","🔦","🔌","🔋","🚿", + "🚽","🔧","🔨","🚪","🚬","💣","🔫","🔪","💊","💉","💰","💵","💳","✉","📫","📦","📅","📁","✂","📌","📎","✒","✏","📐","📚","🔬","🔭","🎨","🎬","🎤","🎧","🎵","🎹","🎻","🎺", + "🎸","👾","🎮","🃏","🎲","🎯","🏈","🏀","⚽","⚾","🎾","🎱","🏉","🎳","🏁","🏇","🏆","🏊","🏄","☕","🍼","🍺","🍷","🍴","🍕","🍔","🍟","🍗","🍱","🍚","🍜","🍡","🍳","🍞","🍩", + "🍦","🎂","🍰","🍪","🍫","🍭","🍯","🍎","🍏","🍊","🍋","🍒","🍇","🍉","🍓","🍑","🍌","🍐","🍍","🍆","🍅","🌽","🏡","🏥","🏦","⛪","🏰","⛺","🏭","🗻","🗽","🎠","🎡","⛲","🎢", + "🚢","🚤","⚓","🚀","✈","🚁","🚂","🚋","🚎","🚌","🚙","🚗","🚕","🚛","🚨","🚔","🚒","🚑","🚲","🚠","🚜","🚦","⚠","🚧","⛽","🎰","🗿","🎪","🎭","🇯🇵","🇰🇷","🇩🇪","🇨🇳","🇺🇸","🇫🇷","🇪🇸", + "🇮🇹","🇷🇺","🇬🇧","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","0⃣","🔟","❗","❓","♥","♦","💯","🔗","🔱","🔴","🔵","🔶","🔷" + }; + public static final String[] emojiColored = { - "🙌","👏","👍","👎","👊","✊","👋","👈","👉","👆","👇","👌","☝","✌","✋", - "🖐","👐","💪","🙏","🖖","🤘","🖕","✍","💅","👂","👃","👶","👦","👧","👨", - "👩","👱‍♀","👱","👴","👵","👲","👳‍♀","👳","👮‍♀","👮","👷‍♀","👷","💂‍♀","💂", - "🕵‍♀","🕵","🎅","👸","👰","👼","🙇‍♀","🙇","💁","💁‍♂","🙅","🙅‍♂","🙆","🙆‍♂", - "🙋","🙋‍♂","🙎","🙎‍♂","🙍","🙍‍♂","💇","💇‍♂","💆","💆‍♂","💃","🚶‍♀","🚶","🏃‍♀", - "🏃","🏋‍♀","🏋","⛹‍♀","⛹","🏄‍♀","🏄","🏊‍♀","🏊","🚣‍♀","🚣","🚴‍♀","🚴","🚵‍♀", - "🚵","🛀" + "👐","🙌","👏","🙏","👍","👎","👊","✊","🤛","🤜","🤞","✌","🤘","👌","👈","👉","👆","👇","☝","✋","🤚","🖐","🖖","👋","🤙","💪","🖕","✍","🤳","💅","👂","👃","👶","👦","👧","👨","👩","👱‍♀","👱","👴","👵","👲","👳‍♀","👳","👮‍♀","👮","👷‍♀","👷","💂‍♀","💂","🕵‍♀","🕵","👩‍⚕","👨‍⚕","👩‍🌾","👨‍🌾","👩‍🍳","👨‍🍳","👩‍🎓","👨‍🎓","👩‍🎤","👨‍🎤","👩‍🏫","👨‍🏫","👩‍🏭","👨‍🏭","👩‍💻","👨‍💻","👩‍💼","👨‍💼","👩‍🔧","👨‍🔧","👩‍🔬","👨‍🔬","👩‍🎨","👨‍🎨","👩‍🚒","👨‍🚒","👩‍✈","👨‍✈","👩‍🚀","👨‍🚀","👩‍⚖","👨‍⚖","🤶","🎅","👸","🤴","👰","🤵","👼","🤰","🙇‍♀","🙇","💁","💁‍♂","🙅","🙅‍♂","🙆","🙆‍♂","🙋","🙋‍♂","🤦‍♀","🤦‍♂","🤷‍♀","🤷‍♂","🙎","🙎‍♂","🙍","🙍‍♂","💇","💇‍♂","💆","💆‍♂","🕴","💃","🕺","🚶‍♀","🚶","🏃‍♀","🏃","🏋‍♀","🏋","🤸‍♀","🤸‍♂","⛹‍♀","⛹","🤾‍♀","🤾‍♂","🏌‍♀","🏌","🏄‍♀","🏄","🏊‍♀","🏊","🤽‍♀","🤽‍♂","🏄","🏊‍♀","🏊","🤽‍♀","🤽‍♂","🚣‍♀","🚣","🏇","🚴‍♀","🚴","🚵‍♀","🚵","🤹‍♀","🤹‍♂","🛀" }; public static final String[][] dataColored = { new String[]{ - "😀","😬","😁","😂","😃","😄","😅","😆","😇","😉","😊","🙂","🙃","☺", - "😋","😌","😍","😘","😗","😙","😚","😜","😝","😛","🤑","🤓","😎","🤗", - "😏","😶","😐","😑","😒","🙄","🤔","😳","😞","😟","😠","😡","😔","😕", - "🙁","☹","😣","😖","😫","😩","😤","😮","😱","😨","😰","😯","😦","😧", - "😢","😥","😪","😓","😭","😵","😲","🤐","😷","🤒","🤕","😴","💤","💩", - "😈","👿","👹","👺","👻","💀","☠","👽","👾","🤖","😺","😸","😹","😻","😼","😽","🙀","😿","😾", + "😀","😃","😄","😁","😆","😅","😂","🤣","☺","😊","😇","🙂","🙃","😉","😌","😍","😘","😗","😙","😚","😋","😜","😝","😛","🤑","🤗","🤓","😎","🤡","🤠","😏","😒","😞","😔","😟","😕","🙁","☹","😣","😖","😫","😩","😤","😠","😡","😶","😐","😑","😯","😦","😧","😮","😲","😵","😳","😱","😨","😰","😢","😥","🤤","😭","😓","😪","😴","🙄","🤔","🤥","😬","🤐","🤢","🤧","😷","🤒","🤕","😈","👿","👹","👺","💩","👻","💀","☠","👽","👾","🤖","🎃","😺","😸","😹","😻","😼","😽","🙀","😿","😾", + "👐", "🙌", "👏", + "🙏", + "🤝", "👍", "👎", "👊", "✊", - "👋", + "🤛", + "🤜", + "🤞", + "✌", + "🤘", + "👌", "👈", "👉", "👆", "👇", - "👌", "☝", - "✌", "✋", + "🤚", "🖐", - "👐", - "💪", - "🙏", "🖖", - "🤘", + "👋", + "🤙", + "💪", "🖕", "✍", + "🤳", "💅", - "👄","👅", + "💍","💄","💋","👄","👅", "👂", "👃", - "👁","👀","🗣","👤","👥", + "👣","👁","👀","🗣","👤","👥", "👶", "👦", "👧", @@ -112,10 +121,46 @@ public class EmojiData { "💂", "🕵‍♀", "🕵", + "👩‍⚕", + "👨‍⚕", + "👩‍🌾", + "👨‍🌾", + "👩‍🍳", + "👨‍🍳", + "👩‍🎓", + "👨‍🎓", + "👩‍🎤", + "👨‍🎤", + "👩‍🏫", + "👨‍🏫", + "👩‍🏭", + "👨‍🏭", + "👩‍💻", + "👨‍💻", + "👩‍💼", + "👨‍💼", + "👩‍🔧", + "👨‍🔧", + "👩‍🔬", + "👨‍🔬", + "👩‍🎨", + "👨‍🎨", + "👩‍🚒", + "👨‍🚒", + "👩‍✈", + "👨‍✈", + "👩‍🚀", + "👨‍🚀", + "👩‍⚖", + "👨‍⚖", + "🤶", "🎅", "👸", + "🤴", "👰", + "🤵", "👼", + "🤰", "🙇‍♀", "🙇", "💁", @@ -126,6 +171,10 @@ public class EmojiData { "🙆‍♂", "🙋", "🙋‍♂", + "🤦‍♀", + "🤦‍♂", + "🤷‍♀", + "🤷‍♂", "🙎", "🙎‍♂", "🙍", @@ -134,190 +183,71 @@ public class EmojiData { "💇‍♂", "💆", "💆‍♂", + "🕴", "💃", + "🕺", "👯","👯‍♂", "🚶‍♀", "🚶", "🏃‍♀", "🏃", - "👫","👭","👬","💑","👩‍❤‍👩","👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧", - "👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦","👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦", - "👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧","👩‍👦","👩‍👧", - "👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕", - "👖","👔","👗","👙","👘","💄","💋","👣","👠","👡","👢","👞","👟","👒","🎩","🎓", - "👑","⛑","🎒","👝","👛","👜","💼","👓","🕶","💍","🌂","❤","💛","💚","💙","💜", - "💔","❣","💕","💞","💓","💗","💖","💘","💝" + "👫","👭","👬","💑","👩‍❤‍👩","👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧","👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦","👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦","👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧","👩‍👦","👩‍👧","👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕","👖","👔","👗","👙","👘","👠","👡","👢","👞","👟","👒","🎩","🎓","👑","⛑","🎒","👝","👛","👜","💼","👓","🕶","🌂","☂","❤","💛","💚","💙","💜","🖤","💔","❣","💕","💞","💓","💗","💖","💘","💝" }, null, new String[]{ - "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🍅","🍆", - "🌶","🌽","🍠","🍯","🍞","🧀","🍗","🍖","🍤","🍳","🍔","🍟","🌭","🍕","🍝", - "🌮","🌯","🍜","🍲","🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨", - "🍦","🍰","🎂","🍮","🍬","🍭","🍫","🍿","🍩","🍪","🍺","🍻","🍷","🍸","🍹", - "🍾","🍶","🍵","☕","🍼","🍴","🍽","⚽","🏀","🏈","⚾","🎾","🏐","🏉","🎱", - "🏓","🏸","🏒","🏑","🏏","🏹","⛳","🎣","⛸","🎿","⛷","🏂", + "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🥝","🥑","🍅","🍆","🥒","🥕","🌽","🌶","🥔","🍠","🌰","🥜","🍯","🥐","🍞","🥖","🧀","🥚","🍳","🥓","🥞","🍤","🍗","🍖","🍕","🌭","🍔","🍟","🥙","🌮","🌯","🥗","🥘","🍝","🍜","🍲","🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨","🍦","🍰","🎂","🍮","🍭","🍬","🍫","🍿","🍩","🍪","🥛","🍼","☕","🍵","🍶","🍺","🍻","🥂","🍷","🥃","🍸","🍹","🍾","🥄","🍴","🍽","⚽","🏀","🏈","⚾","🎾","🏐","🏉","🎱","🏓","🏸","🥅","🏒","🏑","🏏","⛳","🏹","🎣","🥊","🥋","⛸","🎿","⛷","🏂", "🏋‍♀", "🏋", + "🤺","🤼‍♀","🤼‍♂", + "🤸‍♀", + "🤸‍♂", "⛹‍♀", "⛹", - "🏌‍♀","🏌", + "🤾‍♀", + "🤾‍♂", + "🏌‍♀", + "🏌", "🏄‍♀", "🏄", "🏊‍♀", "🏊", + "🤽‍♀", + "🤽‍♂", "🚣‍♀", - "🚣","🏇", + "🚣", + "🏇", "🚴‍♀", "🚴", "🚵‍♀", "🚵", - "🛀", - "🕴","🎗","🎽","🏅","🎖","🏆","🏵","🎯","🎫","🎟","🎭","🎨","🎪","🎬","🎤","🎧", - "🎼","🎹","🎷","🎺","🎸","🎻","🎮","🎰","🎲","🎳","⌚","📱","📲","💻","⌨","🖥", - "🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽","🎞", - "📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⏳","⌛","📡", - "🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖", - "🔧","🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱", - "🏺","🔮","📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚿","🛁","🛎","🔑", - "🗝","🚪","🛋","🛌","🛏","🖼","⛱","🗿","🛍","🎁","🎈","🎏","🎀","🎊","🎉","🎐", - "🏮","🎎","✉","📩","📨","📧","💌","📥","📤","📦","🏷","🔖","📪","📫","📬","📭", - "📮","📯","📜","📃","📄","📑","📊","📈","📉","🗒","🗓","📆","📅","📇","🗃","🗳", - "🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒","📕","📗","📘","📙","📚","📖", - "🔗","📎","🖇","📐","📏","✂","📌","📍","🚩","🎌","🏳","🏴","🏁","🏳‍🌈","🖌","🖍", - "🖊","🖋","✒","📝","✏","🔏","🔐","🔒","🔓","🔍","🔎" + "🎽","🏅","🎖","🥇","🥈","🥉","🏆","🏵","🎗","🎫","🎟","🎪", + "🤹‍♀", + "🤹‍♂", + "🎭","🎨","🎬","🎤","🎧","🎼","🎹","🥁","🎷","🎺","🎸","🎻","🎲","🎯","🎳","🎮","🎰" }, null, - null + new String[]{ + "💟","☮","✝","☪","🕉","☸","✡","🔯","🕎","☯","☦","🛐","⛎","♈","♉","♊","♋","♌","♍","♎","♏","♐","♑","♒","♓","🆔","⚛","🉑","☢","☣","📴","📳","🈶","🈚","🈸","🈺","🈷","✴","🆚","💮","🉐","㊙","㊗","🈴","🈵","🈹","🈲","🅰","🅱","🆎","🆑","🅾","🆘","❌","⭕","🛑","⛔","📛","🚫","💯","💢","♨","🚷","🚯","🚳","🚱","🔞","📵","🚭","❗","❕","❓","❔","‼","⁉","🔅","🔆","〽","⚠","🚸","🔱","⚜","🔰","♻","✅","🈯","💹","❇","✳","❎","🌐","💠","Ⓜ","🌀","💤","🏧","🚾","♿","🅿","🈳","🈂","🛂","🛃","🛄","🛅","🚹","🚺","🚼","🚻","🚮","🎦","📶","🈁","🔣","ℹ","🔤","🔡","🔠","🆖","🆗","🆙","🆒","🆕","🆓","0⃣","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","🔟","🔢","#⃣","*⃣","▶","⏸","⏯","⏹","⏺","⏭","⏮","⏩","⏪","⏫","⏬","◀","🔼","🔽","➡","⬅","⬆","⬇","↗","↘","↙","↖","↕","↔","↪","↩","⤴","⤵","🔀","🔁","🔂","🔄","🔃","🎵","🎶","➕","➖","➗","✖","💲","💱","™","©","®","〰","➰","➿","🔚","🔙","🔛","🔝","🔜","✔","☑","🔘","⚪","⚫","🔴","🔵","🔺","🔻","🔸","🔹","🔶","🔷","🔳","🔲","▪","▫","◾","◽","◼","◻","⬛","⬜","🔈","🔇","🔉","🔊","🔔","🔕","📣","📢","👁‍🗨","💬","💭","🗯","♠","♣","♥","♦","🃏","🎴","🀄","🕐","🕑","🕒","🕓","🕔","🕕","🕖","🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧","⌚","📱","📲","💻","⌨","🖥","🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽","🎞","📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⌛","⏳","📡","🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖","🔧","🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱","🏺","🔮","📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚰","🚿","🛁", + "🛀", + "🛎","🔑","🗝","🚪","🛋","🛏","🛌","🖼","🛍","🛒","🎁","🎈","🎏","🎀","🎊","🎉","🎎","🏮","🎐","✉","📩","📨","📧","💌","📥","📤","📦","🏷","📪","📫","📬","📭","📮","📯","📜","📃","📄","📑","📊","📈","📉","🗒","🗓","📆","📅","📇","🗃","🗳","🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒","📕","📗","📘","📙","📚","📖","🔖","🔗","📎","🖇","📐","📏","📌","📍","✂","🖊","🖋","✒","🖌","🖍","📝","✏","🔍","🔎","🔏","🔐","🔒","🔓" + } }; public static final String[][] data = { new String[]{ - "😀","😬","😁","😂","😃","😄","😅","😆","😇","😉","😊","🙂","🙃","☺","😋","😌", - "😍","😘","😗","😙","😚","😜","😝","😛","🤑","🤓","😎","🤗","😏","😶","😐","😑", - "😒","🙄","🤔","😳","😞","😟","😠","😡","😔","😕","🙁","☹","😣","😖","😫","😩", - "😤","😮","😱","😨","😰","😯","😦","😧","😢","😥","😪","😓","😭","😵","😲","🤐", - "😷","🤒","🤕","😴","💤","💩","😈","👿","👹","👺","👻","💀","☠","👽","👾","🤖", - "😺","😸","😹","😻","😼","😽","🙀","😿","😾","🙌","🙌🏻","🙌🏼","🙌🏽","🙌🏾", - "🙌🏿","👏","👏🏻","👏🏼","👏🏽","👏🏾","👏🏿","👍","👍🏻","👍🏼","👍🏽","👍🏾", - "👍🏿","👎","👎🏻","👎🏼","👎🏽","👎🏾","👎🏿","👊","👊🏻","👊🏼","👊🏽","👊🏾", - "👊🏿","✊","✊🏻","✊🏼","✊🏽","✊🏾","✊🏿","👋","👋🏻","👋🏼","👋🏽","👋🏾", - "👋🏿","👈","👈🏻","👈🏼","👈🏽","👈🏾","👈🏿","👉","👉🏻","👉🏼","👉🏽","👉🏾", - "👉🏿","👆","👆🏻","👆🏼","👆🏽","👆🏾","👆🏿","👇","👇🏻","👇🏼","👇🏽","👇🏾", - "👇🏿","👌","👌🏻","👌🏼","👌🏽","👌🏾","👌🏿","☝","☝🏻","☝🏼","☝🏽","☝🏾","☝🏿", - "✌","✌🏻","✌🏼","✌🏽","✌🏾","✌🏿","✋","✋🏻","✋🏼","✋🏽","✋🏾","✋🏿","🖐", - "🖐🏻","🖐🏼","🖐🏽","🖐🏾","🖐🏿","👐","👐🏻","👐🏼","👐🏽","👐🏾","👐🏿","💪", - "💪🏻","💪🏼","💪🏽","💪🏾","💪🏿","🙏","🙏🏻","🙏🏼","🙏🏽","🙏🏾","🙏🏿","🖖", - "🖖🏻","🖖🏼","🖖🏽","🖖🏾","🖖🏿","🤘","🤘🏻","🤘🏼","🤘🏽","🤘🏾","🤘🏿","🖕", - "🖕🏻","🖕🏼","🖕🏽","🖕🏾","🖕🏿","✍","✍🏻","✍🏼","✍🏽","✍🏾","✍🏿","💅","💅🏻", - "💅🏼","💅🏽","💅🏾","💅🏿","👄","👅","👂","👂🏻","👂🏼","👂🏽","👂🏾","👂🏿","👃", - "👃🏻","👃🏼","👃🏽","👃🏾","👃🏿","👁","👀","🗣","👤","👥","👶","👶🏻","👶🏼","👶🏽", - "👶🏾","👶🏿","👦","👦🏻","👦🏼","👦🏽","👦🏾","👦🏿","👧","👧🏻","👧🏼","👧🏽","👧🏾", - "👧🏿","👨","👨🏻","👨🏼","👨🏽","👨🏾","👨🏿","👩","👩🏻","👩🏼","👩🏽","👩🏾","👩🏿", - "👱‍♀","👱🏻‍♀","👱🏼‍♀","👱🏽‍♀","👱🏾‍♀","👱🏿‍♀","👱","👱🏻","👱🏼","👱🏽","👱🏾","👱🏿", - "👴","👴🏻","👴🏼","👴🏽","👴🏾","👴🏿","👵","👵🏻","👵🏼","👵🏽","👵🏾","👵🏿","👲", - "👲🏻","👲🏼","👲🏽","👲🏾","👲🏿","👳‍♀","👳🏻‍♀","👳🏼‍♀","👳🏽‍♀","👳🏾‍♀","👳🏿‍♀", - "👳","👳🏻","👳🏼","👳🏽","👳🏾","👳🏿","👮‍♀","👮🏻‍♀","👮🏼‍♀","👮🏽‍♀","👮🏾‍♀", - "👮🏿‍♀","👮","👮🏻","👮🏼","👮🏽","👮🏾","👮🏿","👷‍♀","👷🏻‍♀","👷🏼‍♀","👷🏽‍♀", - "👷🏾‍♀","👷🏿‍♀","👷","👷🏻","👷🏼","👷🏽","👷🏾","👷🏿","💂‍♀","💂🏻‍♀","💂🏼‍♀", - "💂🏽‍♀","💂🏾‍♀","💂🏿‍♀","💂","💂🏻","💂🏼","💂🏽","💂🏾","💂🏿","🕵‍♀","🕵🏻‍♀", - "🕵🏼‍♀","🕵🏽‍♀","🕵🏾‍♀","🕵🏿‍♀","🕵","🕵🏻","🕵🏼","🕵🏽","🕵🏾","🕵🏿","🎅", - "🎅🏻","🎅🏼","🎅🏽","🎅🏾","🎅🏿","👸","👸🏻","👸🏼","👸🏽","👸🏾","👸🏿","👰", - "👰🏻","👰🏼","👰🏽","👰🏾","👰🏿","👼","👼🏻","👼🏼","👼🏽","👼🏾","👼🏿","🙇‍♀", - "🙇🏻‍♀","🙇🏼‍♀","🙇🏽‍♀","🙇🏾‍♀","🙇🏿‍♀","🙇","🙇🏻","🙇🏼","🙇🏽","🙇🏾","🙇🏿", - "💁","💁🏻","💁🏼","💁🏽","💁🏾","💁🏿","💁‍♂","💁🏻‍♂","💁🏼‍♂","💁🏽‍♂","💁🏾‍♂", - "💁🏿‍♂","🙅","🙅🏻","🙅🏼","🙅🏽","🙅🏾","🙅🏿","🙅‍♂","🙅🏻‍♂","🙅🏼‍♂","🙅🏽‍♂", - "🙅🏾‍♂","🙅🏿‍♂","🙆","🙆🏻","🙆🏼","🙆🏽","🙆🏾","🙆🏿","🙆‍♂","🙆🏻‍♂","🙆🏼‍♂","🙆🏽‍♂", - "🙆🏾‍♂","🙆🏿‍♂","🙋","🙋🏻","🙋🏼","🙋🏽","🙋🏾","🙋🏿","🙋‍♂","🙋🏻‍♂","🙋🏼‍♂","🙋🏽‍♂", - "🙋🏾‍♂","🙋🏿‍♂","🙎","🙎🏻","🙎🏼","🙎🏽","🙎🏾","🙎🏿","🙎‍♂","🙎🏻‍♂","🙎🏼‍♂","🙎🏽‍♂", - "🙎🏾‍♂","🙎🏿‍♂","🙍","🙍🏻","🙍🏼","🙍🏽","🙍🏾","🙍🏿","🙍‍♂","🙍🏻‍♂","🙍🏼‍♂","🙍🏽‍♂", - "🙍🏾‍♂","🙍🏿‍♂","💇","💇🏻","💇🏼","💇🏽","💇🏾","💇🏿","💇‍♂","💇🏻‍♂","💇🏼‍♂","💇🏽‍♂", - "💇🏾‍♂","💇🏿‍♂","💆","💆🏻","💆🏼","💆🏽","💆🏾","💆🏿","💆‍♂","💆🏻‍♂","💆🏼‍♂","💆🏽‍♂", - "💆🏾‍♂","💆🏿‍♂","💃","💃🏻","💃🏼","💃🏽","💃🏾","💃🏿","👯","👯‍♂","🚶‍♀","🚶🏻‍♀","🚶🏼‍♀", - "🚶🏽‍♀","🚶🏾‍♀","🚶🏿‍♀","🚶","🚶🏻","🚶🏼","🚶🏽","🚶🏾","🚶🏿","🏃‍♀","🏃🏻‍♀","🏃🏼‍♀", - "🏃🏽‍♀","🏃🏾‍♀","🏃🏿‍♀","🏃","🏃🏻","🏃🏼","🏃🏽","🏃🏾","🏃🏿","👫","👭","👬","💑","👩‍❤‍👩", - "👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧","👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦", - "👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦","👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧", - "👩‍👦","👩‍👧","👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕", - "👖","👔","👗","👙","👘","💄","💋","👣","👠","👡","👢","👞","👟","👒","🎩","🎓","👑","⛑","🎒", - "👝","👛","👜","💼","👓","🕶","💍","🌂","❤","💛","💚","💙","💜","💔","❣","💕","💞","💓","💗","💖","💘","💝" + "😀","😃","😄","😁","😆","😅","😂","🤣","☺","😊","😇","🙂","🙃","😉","😌","😍","😘","😗","😙","😚","😋","😜","😝","😛","🤑","🤗","🤓","😎","🤡","🤠","😏","😒","😞","😔","😟","😕","🙁","☹","😣","😖","😫","😩","😤","😠","😡","😶","😐","😑","😯","😦","😧","😮","😲","😵","😳","😱","😨","😰","😢","😥","🤤","😭","😓","😪","😴","🙄","🤔","🤥","😬","🤐","🤢","🤧","😷","🤒","🤕","😈","👿","👹","👺","💩","👻","💀","☠","👽","👾","🤖","🎃","😺","😸","😹","😻","😼","😽","🙀","😿","😾","👐","👐🏻","👐🏼","👐🏽","👐🏾","👐🏿","🙌","🙌🏻","🙌🏼","🙌🏽","🙌🏾","🙌🏿","👏","👏🏻","👏🏼","👏🏽","👏🏾","👏🏿","🙏","🙏🏻","🙏🏼","🙏🏽","🙏🏾","🙏🏿","🤝","👍","👍🏻","👍🏼","👍🏽","👍🏾","👍🏿","👎","👎🏻","👎🏼","👎🏽","👎🏾","👎🏿","👊","👊🏻","👊🏼","👊🏽","👊🏾","👊🏿","✊","✊🏻","✊🏼","✊🏽","✊🏾","✊🏿","🤛","🤛🏻","🤛🏼","🤛🏽","🤛🏾","🤛🏿","🤜","🤜🏻","🤜🏼","🤜🏽","🤜🏾","🤜🏿","🤞","🤞🏻","🤞🏼","🤞🏽","🤞🏾","🤞🏿","✌","✌🏻","✌🏼","✌🏽","✌🏾","✌🏿","🤘","🤘🏻","🤘🏼","🤘🏽","🤘🏾","🤘🏿","👌","👌🏻","👌🏼","👌🏽","👌🏾","👌🏿","👈","👈🏻","👈🏼","👈🏽","👈🏾","👈🏿","👉","👉🏻","👉🏼","👉🏽","👉🏾","👉🏿","👆","👆🏻","👆🏼","👆🏽","👆🏾","👆🏿","👇","👇🏻","👇🏼","👇🏽","👇🏾","👇🏿","☝","☝🏻","☝🏼","☝🏽","☝🏾","☝🏿","✋","✋🏻","✋🏼","✋🏽","✋🏾","✋🏿","🤚","🤚🏻","🤚🏼","🤚🏽","🤚🏾","🤚🏿","🖐","🖐🏻","🖐🏼","🖐🏽","🖐🏾","🖐🏿","🖖","🖖🏻","🖖🏼","🖖🏽","🖖🏾","🖖🏿","👋","👋🏻","👋🏼","👋🏽","👋🏾","👋🏿","🤙","🤙🏻","🤙🏼","🤙🏽","🤙🏾","🤙🏿","💪","💪🏻","💪🏼","💪🏽","💪🏾","💪🏿","🖕","🖕🏻","🖕🏼","🖕🏽","🖕🏾","🖕🏿","✍","✍🏻","✍🏼","✍🏽","✍🏾","✍🏿","🤳","🤳🏻","🤳🏼","🤳🏽","🤳🏾","🤳🏿","💅","💅🏻","💅🏼","💅🏽","💅🏾","💅🏿","💍","💄","💋","👄","👅","👂","👂🏻","👂🏼","👂🏽","👂🏾","👂🏿","👃","👃🏻","👃🏼","👃🏽","👃🏾","👃🏿","👣","👁","👀","🗣","👤","👥","👶","👶🏻","👶🏼","👶🏽","👶🏾","👶🏿","👦","👦🏻","👦🏼","👦🏽","👦🏾","👦🏿","👧","👧🏻","👧🏼","👧🏽","👧🏾","👧🏿","👨","👨🏻","👨🏼","👨🏽","👨🏾","👨🏿","👩","👩🏻","👩🏼","👩🏽","👩🏾","👩🏿","👱‍♀","👱🏻‍♀","👱🏼‍♀","👱🏽‍♀","👱🏾‍♀","👱🏿‍♀","👱","👱🏻","👱🏼","👱🏽","👱🏾","👱🏿","👴","👴🏻","👴🏼","👴🏽","👴🏾","👴🏿","👵","👵🏻","👵🏼","👵🏽","👵🏾","👵🏿","👲","👲🏻","👲🏼","👲🏽","👲🏾","👲🏿","👳‍♀","👳🏻‍♀","👳🏼‍♀","👳🏽‍♀","👳🏾‍♀","👳🏿‍♀","👳","👳🏻","👳🏼","👳🏽","👳🏾","👳🏿","👮‍♀","👮🏻‍♀","👮🏼‍♀","👮🏽‍♀","👮🏾‍♀","👮🏿‍♀","👮","👮🏻","👮🏼","👮🏽","👮🏾","👮🏿","👷‍♀","👷🏻‍♀","👷🏼‍♀","👷🏽‍♀","👷🏾‍♀","👷🏿‍♀","👷","👷🏻","👷🏼","👷🏽","👷🏾","👷🏿","💂‍♀","💂🏻‍♀","💂🏼‍♀","💂🏽‍♀","💂🏾‍♀","💂🏿‍♀","💂","💂🏻","💂🏼","💂🏽","💂🏾","💂🏿","🕵‍♀","🕵🏻‍♀","🕵🏼‍♀","🕵🏽‍♀","🕵🏾‍♀","🕵🏿‍♀","🕵","🕵🏻","🕵🏼","🕵🏽","🕵🏾","🕵🏿","👩‍⚕","👩🏻‍⚕","👩🏼‍⚕","👩🏽‍⚕","👩🏾‍⚕","👩🏿‍⚕","👨‍⚕","👨🏻‍⚕","👨🏼‍⚕","👨🏽‍⚕","👨🏾‍⚕","👨🏿‍⚕","👩‍🌾","👩🏻‍🌾","👩🏼‍🌾","👩🏽‍🌾","👩🏾‍🌾","👩🏿‍🌾","👨‍🌾","👨🏻‍🌾","👨🏼‍🌾","👨🏽‍🌾","👨🏾‍🌾","👨🏿‍🌾","👩‍🍳","👩🏻‍🍳","👩🏼‍🍳","👩🏽‍🍳","👩🏾‍🍳","👩🏿‍🍳","👨‍🍳","👨🏻‍🍳","👨🏼‍🍳","👨🏽‍🍳","👨🏾‍🍳","👨🏿‍🍳","👩‍🎓","👩🏻‍🎓","👩🏼‍🎓","👩🏽‍🎓","👩🏾‍🎓","👩🏿‍🎓","👨‍🎓","👨🏻‍🎓","👨🏼‍🎓","👨🏽‍🎓","👨🏾‍🎓","👨🏿‍🎓","👩‍🎤","👩🏻‍🎤","👩🏼‍🎤","👩🏽‍🎤","👩🏾‍🎤","👩🏿‍🎤","👨‍🎤","👨🏻‍🎤","👨🏼‍🎤","👨🏽‍🎤","👨🏾‍🎤","👨🏿‍🎤","👩‍🏫","👩🏻‍🏫","👩🏼‍🏫","👩🏽‍🏫","👩🏾‍🏫","👩🏿‍🏫","👨‍🏫","👨🏻‍🏫","👨🏼‍🏫","👨🏽‍🏫","👨🏾‍🏫","👨🏿‍🏫","👩‍🏭","👩🏻‍🏭","👩🏼‍🏭","👩🏽‍🏭","👩🏾‍🏭","👩🏿‍🏭","👨‍🏭","👨🏻‍🏭","👨🏼‍🏭","👨🏽‍🏭","👨🏾‍🏭","👨🏿‍🏭","👩‍💻","👩🏻‍💻","👩🏼‍💻","👩🏽‍💻","👩🏾‍💻","👩🏿‍💻","👨‍💻","👨🏻‍💻","👨🏼‍💻","👨🏽‍💻","👨🏾‍💻","👨🏿‍💻","👩‍💼","👩🏻‍💼","👩🏼‍💼","👩🏽‍💼","👩🏾‍💼","👩🏿‍💼","👨‍💼","👨🏻‍💼","👨🏼‍💼","👨🏽‍💼","👨🏾‍💼","👨🏿‍💼","👩‍🔧","👩🏻‍🔧","👩🏼‍🔧","👩🏽‍🔧","👩🏾‍🔧","👩🏿‍🔧","👨‍🔧","👨🏻‍🔧","👨🏼‍🔧","👨🏽‍🔧","👨🏾‍🔧","👨🏿‍🔧","👩‍🔬","👩🏻‍🔬","👩🏼‍🔬","👩🏽‍🔬","👩🏾‍🔬","👩🏿‍🔬","👨‍🔬","👨🏻‍🔬","👨🏼‍🔬","👨🏽‍🔬","👨🏾‍🔬","👨🏿‍🔬","👩‍🎨","👩🏻‍🎨","👩🏼‍🎨","👩🏽‍🎨","👩🏾‍🎨","👩🏿‍🎨","👨‍🎨","👨🏻‍🎨","👨🏼‍🎨","👨🏽‍🎨","👨🏾‍🎨","👨🏿‍🎨","👩‍🚒","👩🏻‍🚒","👩🏼‍🚒","👩🏽‍🚒","👩🏾‍🚒","👩🏿‍🚒","👨‍🚒","👨🏻‍🚒","👨🏼‍🚒","👨🏽‍🚒","👨🏾‍🚒","👨🏿‍🚒","👩‍✈","👩🏻‍✈","👩🏼‍✈","👩🏽‍✈","👩🏾‍✈","👩🏿‍✈","👨‍✈","👨🏻‍✈","👨🏼‍✈","👨🏽‍✈","👨🏾‍✈","👨🏿‍✈","👩‍🚀","👩🏻‍🚀","👩🏼‍🚀","👩🏽‍🚀","👩🏾‍🚀","👩🏿‍🚀","👨‍🚀","👨🏻‍🚀","👨🏼‍🚀","👨🏽‍🚀","👨🏾‍🚀","👨🏿‍🚀","👩‍⚖","👩🏻‍⚖","👩🏼‍⚖","👩🏽‍⚖","👩🏾‍⚖","👩🏿‍⚖","👨‍⚖","👨🏻‍⚖","👨🏼‍⚖","👨🏽‍⚖","👨🏾‍⚖","👨🏿‍⚖","🤶","🤶🏻","🤶🏼","🤶🏽","🤶🏾","🤶🏿","🎅","🎅🏻","🎅🏼","🎅🏽","🎅🏾","🎅🏿","👸","👸🏻","👸🏼","👸🏽","👸🏾","👸🏿","🤴","🤴🏻","🤴🏼","🤴🏽","🤴🏾","🤴🏿","👰","👰🏻","👰🏼","👰🏽","👰🏾","👰🏿","🤵","🤵🏻","🤵🏼","🤵🏽","🤵🏾","🤵🏿","👼","👼🏻","👼🏼","👼🏽","👼🏾","👼🏿","🤰","🤰🏻","🤰🏼","🤰🏽","🤰🏾","🤰🏿","🙇‍♀","🙇🏻‍♀","🙇🏼‍♀","🙇🏽‍♀","🙇🏾‍♀","🙇🏿‍♀","🙇","🙇🏻","🙇🏼","🙇🏽","🙇🏾","🙇🏿","💁","💁🏻","💁🏼","💁🏽","💁🏾","💁🏿","💁‍♂","💁🏻‍♂","💁🏼‍♂","💁🏽‍♂","💁🏾‍♂","💁🏿‍♂","🙅","🙅🏻","🙅🏼","🙅🏽","🙅🏾","🙅🏿","🙅‍♂","🙅🏻‍♂","🙅🏼‍♂","🙅🏽‍♂","🙅🏾‍♂","🙅🏿‍♂","🙆","🙆🏻","🙆🏼","🙆🏽","🙆🏾","🙆🏿","🙆‍♂","🙆🏻‍♂","🙆🏼‍♂","🙆🏽‍♂","🙆🏾‍♂","🙆🏿‍♂","🙋","🙋🏻","🙋🏼","🙋🏽","🙋🏾","🙋🏿","🙋‍♂","🙋🏻‍♂","🙋🏼‍♂","🙋🏽‍♂","🙋🏾‍♂","🙋🏿‍♂","🤦‍♀","🤦🏻‍♀","🤦🏼‍♀","🤦🏽‍♀","🤦🏾‍♀","🤦🏿‍♀","🤦‍♂","🤦🏻‍♂","🤦🏼‍♂","🤦🏽‍♂","🤦🏾‍♂","🤦🏿‍♂","🤷‍♀","🤷🏻‍♀","🤷🏼‍♀","🤷🏽‍♀","🤷🏾‍♀","🤷🏿‍♀","🤷‍♂","🤷🏻‍♂","🤷🏼‍♂","🤷🏽‍♂","🤷🏾‍♂","🤷🏿‍♂","🙎","🙎🏻","🙎🏼","🙎🏽","🙎🏾","🙎🏿","🙎‍♂","🙎🏻‍♂","🙎🏼‍♂","🙎🏽‍♂","🙎🏾‍♂","🙎🏿‍♂","🙍","🙍🏻","🙍🏼","🙍🏽","🙍🏾","🙍🏿","🙍‍♂","🙍🏻‍♂","🙍🏼‍♂","🙍🏽‍♂","🙍🏾‍♂","🙍🏿‍♂","💇","💇🏻","💇🏼","💇🏽","💇🏾","💇🏿","💇‍♂","💇🏻‍♂","💇🏼‍♂","💇🏽‍♂","💇🏾‍♂","💇🏿‍♂","💆","💆🏻","💆🏼","💆🏽","💆🏾","💆🏿","💆‍♂","💆🏻‍♂","💆🏼‍♂","💆🏽‍♂","💆🏾‍♂","💆🏿‍♂","🕴","🕴🏻","🕴🏼","🕴🏽","🕴🏾","🕴🏿","💃","💃🏻","💃🏼","💃🏽","💃🏾","💃🏿","🕺","🕺🏻","🕺🏼","🕺🏽","🕺🏾","🕺🏿","👯","👯‍♂","🚶‍♀","🚶🏻‍♀","🚶🏼‍♀","🚶🏽‍♀","🚶🏾‍♀","🚶🏿‍♀","🚶","🚶🏻","🚶🏼","🚶🏽","🚶🏾","🚶🏿","🏃‍♀","🏃🏻‍♀","🏃🏼‍♀","🏃🏽‍♀","🏃🏾‍♀","🏃🏿‍♀","🏃","🏃🏻","🏃🏼","🏃🏽","🏃🏾","🏃🏿","👫","👭","👬","💑","👩‍❤‍👩","👨‍❤‍👨","💏","👩‍❤‍💋‍👩","👨‍❤‍💋‍👨","👪","👨‍👩‍👧","👨‍👩‍👧‍👦","👨‍👩‍👦‍👦","👨‍👩‍👧‍👧","👩‍👩‍👦","👩‍👩‍👧","👩‍👩‍👧‍👦","👩‍👩‍👦‍👦","👩‍👩‍👧‍👧","👨‍👨‍👦","👨‍👨‍👧","👨‍👨‍👧‍👦","👨‍👨‍👦‍👦","👨‍👨‍👧‍👧","👩‍👦","👩‍👧","👩‍👧‍👦","👩‍👦‍👦","👩‍👧‍👧","👨‍👦","👨‍👧","👨‍👧‍👦","👨‍👦‍👦","👨‍👧‍👧","👚","👕","👖","👔","👗","👙","👘","👠","👡","👢","👞","👟","👒","🎩","🎓","👑","⛑","🎒","👝","👛","👜","💼","👓","🕶","🌂","☂","❤","💛","💚","💙","💜","🖤","💔","❣","💕","💞","💓","💗","💖","💘","💝" }, new String[]{ - "🐶","🐱","🐭","🐹","🐰","🐻","🐼","🐨","🐯","🦁","🐮","🐷","🐽","🐸","🐙","🐵","🙈","🙉", - "🙊","🐒","🐔","🐧","🐦","🐤","🐣","🐥","🐺","🐗","🐴","🦄","🐝","🐛","🐌","🐞","🐜","🕷", - "🦂","🦀","🐍","🐢","🐠","🐟","🐡","🐬","🐳","🐋","🐊","🐆","🐅","🐃","🐂","🐄","🐪","🐫", - "🐘","🐐","🐏","🐑","🐎","🐖","🐀","🐁","🐓","🦃","🕊","🐕","🐩","🐈","🐇","🐿","🐾","🐉", - "🐲","🌵","🎄","🌲","🌳","🌴","🌱","🌿","☘","🍀","🎍","🎋","🍃","🍂","🍁","🌾","🌺","🌻", - "🌹","🌷","🌼","🌸","💐","🍄","🌰","🎃","🐚","🕸","🌎","🌍","🌏","🌕","🌖","🌗","🌘","🌑", - "🌒","🌓","🌔","🌚","🌝","🌛","🌜","🌞","🌙","⭐","🌟","💫","✨","☄","☀","🌤","⛅","🌥", - "🌦","☁","🌧","⛈","🌩","⚡","🔥","💥","❄","🌨","☃","⛄","🌬","💨","🌪","🌫","☂","☔","💧","💦","🌊" + "🐶","🐱","🐭","🐹","🐰","🦊","🐻","🐼","🐨","🐯","🦁","🐮","🐷","🐽","🐸","🐵","🙈","🙉","🙊","🐒","🐔","🐧","🐦","🐤","🐣","🐥","🦆","🦅","🦉","🦇","🐺","🐗","🐴","🦄","🐝","🐛","🦋","🐌","🐚","🐞","🐜","🕷","🕸","🐢","🐍","🦎","🦂","🦀","🦑","🐙","🦐","🐠","🐟","🐡","🐬","🦈","🐳","🐋","🐊","🐆","🐅","🐃","🐂","🐄","🦌","🐪","🐫","🐘","🦏","🦍","🐎","🐖","🐐","🐏","🐑","🐕","🐩","🐈","🐓","🦃","🕊","🐇","🐁","🐀","🐿","🐾","🐉","🐲","🌵","🎄","🌲","🌳","🌴","🌱","🌿","☘","🍀","🎍","🎋","🍃","🍂","🍁","🍄","🌾","💐","🌷","🌹","🥀","🌻","🌼","🌸","🌺","🌎","🌍","🌏","🌕","🌖","🌗","🌘","🌑","🌒","🌓","🌔","🌚","🌝","🌞","🌛","🌜","🌙","💫","⭐","🌟","✨","⚡","🔥","💥","☄","☀","🌤","⛅","🌥","🌦","🌈","☁","🌧","⛈","🌩","🌨","☃","⛄","❄","🌬","💨","🌪","🌫","🌊","💧","💦","☔" }, new String[]{ - "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🍅","🍆","🌶","🌽", - "🍠","🍯","🍞","🧀","🍗","🍖","🍤","🍳","🍔","🍟","🌭","🍕","🍝","🌮","🌯","🍜","🍲", - "🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨","🍦","🍰","🎂","🍮","🍬","🍭", - "🍫","🍿","🍩","🍪","🍺","🍻","🍷","🍸","🍹","🍾","🍶","🍵","☕","🍼","🍴","🍽","⚽", - "🏀","🏈","⚾","🎾","🏐","🏉","🎱","🏓","🏸","🏒","🏑","🏏","🏹","⛳","🎣","⛸","🎿", - "⛷","🏂","🏋‍♀","🏋🏻‍♀","🏋🏼‍♀","🏋🏽‍♀","🏋🏾‍♀","🏋🏿‍♀","🏋","🏋🏻","🏋🏼","🏋🏽", - "🏋🏾","🏋🏿","⛹‍♀","⛹🏻‍♀","⛹🏼‍♀","⛹🏽‍♀","⛹🏾‍♀","⛹🏿‍♀","⛹","⛹🏻","⛹🏼", - "⛹🏽","⛹🏾","⛹🏿","🏌‍♀","🏌","🏄‍♀","🏄🏻‍♀","🏄🏼‍♀","🏄🏽‍♀","🏄🏾‍♀","🏄🏿‍♀", - "🏄","🏄🏻","🏄🏼","🏄🏽","🏄🏾","🏄🏿","🏊‍♀","🏊🏻‍♀","🏊🏼‍♀","🏊🏽‍♀","🏊🏾‍♀", - "🏊🏿‍♀","🏊","🏊🏻","🏊🏼","🏊🏽","🏊🏾","🏊🏿","🚣‍♀","🚣🏻‍♀","🚣🏼‍♀","🚣🏽‍♀", - "🚣🏾‍♀","🚣🏿‍♀","🚣","🚣🏻","🚣🏼","🚣🏽","🚣🏾","🚣🏿","🏇","🚴‍♀","🚴🏻‍♀","🚴🏼‍♀", - "🚴🏽‍♀","🚴🏾‍♀","🚴🏿‍♀","🚴","🚴🏻","🚴🏼","🚴🏽","🚴🏾","🚴🏿","🚵‍♀","🚵🏻‍♀","🚵🏼‍♀", - "🚵🏽‍♀","🚵🏾‍♀","🚵🏿‍♀","🚵","🚵🏻","🚵🏼","🚵🏽","🚵🏾","🚵🏿","🛀","🛀🏻","🛀🏼", - "🛀🏽","🛀🏾","🛀🏿","🕴","🎗","🎽","🏅","🎖","🏆","🏵","🎯","🎫","🎟","🎭","🎨","🎪", - "🎬","🎤","🎧","🎼","🎹","🎷","🎺","🎸","🎻","🎮","🎰","🎲","🎳","⌚","📱","📲","💻", - "⌨","🖥","🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽", - "🎞","📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⏳","⌛","📡", - "🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖","🔧", - "🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱","🏺","🔮", - "📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚿","🛁","🛎","🔑","🗝","🚪","🛋","🛌", - "🛏","🖼","⛱","🗿","🛍","🎁","🎈","🎏","🎀","🎊","🎉","🎐","🏮","🎎","✉","📩","📨","📧", - "💌","📥","📤","📦","🏷","🔖","📪","📫","📬","📭","📮","📯","📜","📃","📄","📑","📊","📈", - "📉","🗒","🗓","📆","📅","📇","🗃","🗳","🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒", - "📕","📗","📘","📙","📚","📖","🔗","📎","🖇","📐","📏","✂","📌","📍","🚩","🎌","🏳","🏴", - "🏁","🏳‍🌈","🖌","🖍","🖊","🖋","✒","📝","✏","🔏","🔐","🔒","🔓","🔍","🔎" + "🍏","🍎","🍐","🍊","🍋","🍌","🍉","🍇","🍓","🍈","🍒","🍑","🍍","🥝","🥑","🍅","🍆","🥒","🥕","🌽","🌶","🥔","🍠","🌰","🥜","🍯","🥐","🍞","🥖","🧀","🥚","🍳","🥓","🥞","🍤","🍗","🍖","🍕","🌭","🍔","🍟","🥙","🌮","🌯","🥗","🥘","🍝","🍜","🍲","🍥","🍣","🍱","🍛","🍙","🍚","🍘","🍢","🍡","🍧","🍨","🍦","🍰","🎂","🍮","🍭","🍬","🍫","🍿","🍩","🍪","🥛","🍼","☕","🍵","🍶","🍺","🍻","🥂","🍷","🥃","🍸","🍹","🍾","🥄","🍴","🍽","⚽","🏀","🏈","⚾","🎾","🏐","🏉","🎱","🏓","🏸","🥅","🏒","🏑","🏏","⛳","🏹","🎣","🥊","🥋","⛸","🎿","⛷","🏂","🏋‍♀","🏋🏻‍♀","🏋🏼‍♀","🏋🏽‍♀","🏋🏾‍♀","🏋🏿‍♀","🏋","🏋🏻","🏋🏼","🏋🏽","🏋🏾","🏋🏿","🤺","🤼‍♀","🤼‍♂","🤸‍♀","🤸🏻‍♀","🤸🏼‍♀","🤸🏽‍♀","🤸🏾‍♀","🤸🏿‍♀","🤸‍♂","🤸🏻‍♂","🤸🏼‍♂","🤸🏽‍♂","🤸🏾‍♂","🤸🏿‍♂","⛹‍♀","⛹🏻‍♀","⛹🏼‍♀","⛹🏽‍♀","⛹🏾‍♀","⛹🏿‍♀","⛹","⛹🏻","⛹🏼","⛹🏽","⛹🏾","⛹🏿","🤾‍♀","🤾🏻‍♀","🤾🏼‍♀","🤾🏽‍♀","🤾🏾‍♀","🤾🏿‍♀","🤾‍♂","🤾🏻‍♂","🤾🏼‍♂","🤾🏽‍♂","🤾🏾‍♂","🤾🏿‍♂","🏌‍♀","🏌🏻‍♀","🏌🏼‍♀","🏌🏽‍♀","🏌🏾‍♀","🏌🏿‍♀","🏌","🏌🏻","🏌🏼","🏌🏽","🏌🏾","🏌🏿","🏄‍♀","🏄🏻‍♀","🏄🏼‍♀","🏄🏽‍♀","🏄🏾‍♀","🏄🏿‍♀","🏄","🏄🏻","🏄🏼","🏄🏽","🏄🏾","🏄🏿","🏊‍♀","🏊🏻‍♀","🏊🏼‍♀","🏊🏽‍♀","🏊🏾‍♀","🏊🏿‍♀","🏊","🏊🏻","🏊🏼","🏊🏽","🏊🏾","🏊🏿","🤽‍♀","🤽🏻‍♀","🤽🏼‍♀","🤽🏽‍♀","🤽🏾‍♀","🤽🏿‍♀","🤽‍♂","🤽🏻‍♂","🤽🏼‍♂","🤽🏽‍♂","🤽🏾‍♂","🤽🏿‍♂","🚣‍♀","🚣🏻‍♀","🚣🏼‍♀","🚣🏽‍♀","🚣🏾‍♀","🚣🏿‍♀","🚣","🚣🏻","🚣🏼","🚣🏽","🚣🏾","🚣🏿","🏇","🏇🏻","🏇🏼","🏇🏽","🏇🏾","🏇🏿","🚴‍♀","🚴🏻‍♀","🚴🏼‍♀","🚴🏽‍♀","🚴🏾‍♀","🚴🏿‍♀","🚴","🚴🏻","🚴🏼","🚴🏽","🚴🏾","🚴🏿","🚵‍♀","🚵🏻‍♀","🚵🏼‍♀","🚵🏽‍♀","🚵🏾‍♀","🚵🏿‍♀","🚵","🚵🏻","🚵🏼","🚵🏽","🚵🏾","🚵🏿","🎽","🏅","🎖","🥇","🥈","🥉","🏆","🏵","🎗","🎫","🎟","🎪","🤹‍♀","🤹🏻‍♀","🤹🏼‍♀","🤹🏽‍♀","🤹🏾‍♀","🤹🏿‍♀","🤹‍♂","🤹🏻‍♂","🤹🏼‍♂","🤹🏽‍♂","🤹🏾‍♂","🤹🏿‍♂","🎭","🎨","🎬","🎤","🎧","🎼","🎹","🥁","🎷","🎺","🎸","🎻","🎲","🎯","🎳","🎮","🎰" }, new String[]{ - "🚗","🚕","🚙","🚌","🚎","🏎","🚓","🚑","🚒","🚐","🚚","🚛","🚜","🏍","🚲","🚨","🚔","🚍", - "🚘","🚖","🚡","🚠","🚟","🚃","🚋","🚝","🚄","🚅","🚈","🚞","🚂","🚆","🚇","🚊","🚉","🚁", - "🛩","✈","🛫","🛬","⛵","🛥","🚤","⛴","🛳","🚀","🛰","💺","⚓","🚧","⛽","🚏","🚦","🚥", - "🗺","🚢","🎡","🎢","🎠","🏗","🌁","🗼","🏭","⛲","🎑","⛰","🏔","🗻","🌋","🗾","🏕","⛺", - "🏞","🛣","🛤","🌅","🌄","🏜","🏖","🏝","🌇","🌆","🏙","🌃","🌉","🌌","🌠","🎇","🎆","🌈", - "🏘","🏰","🏯","🏟","🗽","🏠","🏡","🏚","🏢","🏬","🏣","🏤","🏥","🏦","🏨","🏪","🏫","🏩", - "💒","🏛","⛪","🕌","🕍","🕋","⛩","🇦🇺","🇦🇹","🇦🇿","🇦🇽","🇦🇱","🇩🇿","🇦🇸","🇦🇮","🇦🇴", - "🇦🇩","🇦🇶","🇦🇬","🇦🇷","🇦🇲","🇦🇼","🇦🇫","🇧🇸","🇧🇩","🇧🇧","🇧🇭","🇧🇾","🇧🇿","🇧🇪", - "🇧🇯","🇧🇲","🇧🇬","🇧🇴","🇧🇶","🇧🇦","🇧🇼","🇧🇷","🇮🇴","🇧🇳","🇧🇫","🇧🇮","🇧🇹","🇻🇺", - "🇻🇦","🇬🇧","🇭🇺","🇻🇪","🇻🇬","🇻🇮","🇹🇱","🇻🇳","🇬🇦","🇭🇹","🇬🇾","🇬🇲","🇬🇭","🇬🇵", - "🇬🇹","🇬🇳","🇬🇼","🇩🇪","🇬🇬","🇬🇮","🇭🇳","🇭🇰","🇬🇩","🇬🇱","🇬🇷","🇬🇪","🇬🇺","🇩🇰", - "🇯🇪","🇩🇯","🇩🇲","🇩🇴","🇪🇺","🇪🇬","🇿🇲","🇪🇭","🇿🇼","🇮🇱","🇮🇳","🇮🇩","🇯🇴","🇮🇶", - "🇮🇷","🇮🇪","🇮🇸","🇪🇸","🇮🇹","🇾🇪","🇨🇻","🇰🇿","🇰🇾","🇰🇭","🇨🇲","🇨🇦","🇮🇨","🇶🇦", - "🇰🇪","🇨🇾","🇰🇬","🇰🇮","🇨🇳","🇰🇵","🇨🇨","🇨🇴","🇰🇲","🇨🇬","🇨🇩","🇽🇰","🇨🇷","🇨🇮", - "🇨🇺","🇰🇼","🇨🇼","🇱🇦","🇱🇻","🇱🇸","🇱🇷","🇱🇧","🇱🇾","🇱🇹","🇱🇮","🇱🇺","🇲🇺","🇲🇷", - "🇲🇬","🇾🇹","🇲🇴","🇲🇰","🇲🇼","🇲🇾","🇲🇱","🇲🇻","🇲🇹","🇲🇦","🇲🇶","🇲🇭","🇲🇽","🇫🇲", - "🇲🇿","🇲🇩","🇲🇨","🇲🇳","🇲🇸","🇲🇲","🇳🇦","🇳🇷","🇳🇵","🇳🇪","🇳🇬","🇳🇱","🇳🇮","🇳🇺", - "🇳🇿","🇳🇨","🇳🇴","🇮🇲","🇳🇫","🇨🇽","🇸🇭","🇨🇰","🇹🇨","🇦🇪","🇴🇲","🇵🇳","🇵🇰","🇵🇼", - "🇵🇸","🇵🇦","🇵🇬","🇵🇾","🇵🇪","🇵🇱","🇵🇹","🇵🇷","🇰🇷","🇷🇪","🇷🇺","🇷🇼","🇷🇴","🇸🇻", - "🇼🇸","🇸🇲","🇸🇹","🇸🇦","🇸🇿","🇲🇵","🇸🇨","🇧🇱","🇵🇲","🇸🇳","🇻🇨","🇰🇳","🇱🇨","🇷🇸", - "🇸🇬","🇸🇽","🇸🇾","🇸🇰","🇸🇮","🇺🇸","🇸🇧","🇸🇴","🇸🇩","🇸🇷","🇸🇱","🇹🇯","🇹🇭","🇹🇼", - "🇹🇿","🇹🇬","🇹🇰","🇹🇴","🇹🇹","🇹🇻","🇹🇳","🇹🇲","🇹🇷","🇺🇬","🇺🇿","🇺🇦","🇼🇫","🇺🇾", - "🇫🇴","🇫🇯","🇵🇭","🇫🇮","🇫🇰","🇫🇷","🇬🇫","🇵🇫","🇹🇫","🇭🇷","🇨🇫","🇹🇩","🇲🇪","🇨🇿", - "🇨🇱","🇨🇭","🇸🇪","🇱🇰","🇪🇨","🇬🇶","🇪🇷","🇪🇪","🇪🇹","🇿🇦","🇬🇸","🇸🇸","🇯🇲","🇯🇵" + "🚗","🚕","🚙","🚌","🚎","🏎","🚓","🚑","🚒","🚐","🚚","🚛","🚜","🛴","🚲","🛵","🏍","🚨","🚔","🚍","🚘","🚖","🚡","🚠","🚟","🚃","🚋","🚞","🚝","🚄","🚅","🚈","🚂","🚆","🚇","🚊","🚉","🚁","🛩","✈","🛫","🛬","🚀","🛰","💺","🛶","⛵","🛥","🚤","🛳","⛴","🚢","⚓","🚧","⛽","🚏","🚦","🚥","🗺","🗿","🗽","⛲","🗼","🏰","🏯","🏟","🎡","🎢","🎠","⛱","🏖","🏝","⛰","🏔","🗻","🌋","🏜","🏕","⛺","🛤","🛣","🏗","🏭","🏠","🏡","🏘","🏚","🏢","🏬","🏣","🏤","🏥","🏦","🏨","🏪","🏫","🏩","💒","🏛","⛪","🕌","🕍","🕋","⛩","🗾","🎑","🏞","🌅","🌄","🌠","🎇","🎆","🌇","🌆","🏙","🌃","🌌","🌉","🌁","🏳","🏴","🏁","🚩","🏳‍🌈","🇦🇺","🇦🇹","🇦🇿","🇦🇽","🇦🇱","🇩🇿","🇦🇸","🇦🇮","🇦🇴","🇦🇩","🇦🇶","🇦🇬","🇦🇷","🇦🇲","🇦🇼","🇦🇫","🇧🇸","🇧🇩","🇧🇧","🇧🇭","🇧🇾","🇧🇿","🇧🇪","🇧🇯","🇧🇲","🇧🇬","🇧🇴","🇧🇶","🇧🇦","🇧🇼","🇧🇷","🇮🇴","🇧🇳","🇧🇫","🇧🇮","🇧🇹","🇻🇺","🇻🇦","🇬🇧","🇭🇺","🇻🇪","🇻🇬","🇻🇮","🇹🇱","🇻🇳","🇬🇦","🇭🇹","🇬🇾","🇬🇲","🇬🇭","🇬🇵","🇬🇹","🇬🇳","🇬🇼","🇩🇪","🇬🇬","🇬🇮","🇭🇳","🇭🇰","🇬🇩","🇬🇱","🇬🇷","🇬🇪","🇬🇺","🇩🇰","🇯🇪","🇩🇯","🇩🇲","🇩🇴","🇪🇺","🇪🇬","🇿🇲","🇪🇭","🇿🇼","🇮🇱","🇮🇳","🇮🇩","🇯🇴","🇮🇶","🇮🇷","🇮🇪","🇮🇸","🇪🇸","🇮🇹","🇾🇪","🇨🇻","🇰🇿","🇰🇾","🇰🇭","🇨🇲","🇨🇦","🇮🇨","🇶🇦","🇰🇪","🇨🇾","🇰🇬","🇰🇮","🇨🇳","🇰🇵","🇨🇨","🇨🇴","🇰🇲","🇨🇬","🇨🇩","🇽🇰","🇨🇷","🇨🇮","🇨🇺","🇰🇼","🇨🇼","🇱🇦","🇱🇻","🇱🇸","🇱🇷","🇱🇧","🇱🇾","🇱🇹","🇱🇮","🇱🇺","🇲🇺","🇲🇷","🇲🇬","🇾🇹","🇲🇴","🇲🇰","🇲🇼","🇲🇾","🇲🇱","🇲🇻","🇲🇹","🇲🇦","🇲🇶","🇲🇭","🇲🇽","🇫🇲","🇲🇿","🇲🇩","🇲🇨","🇲🇳","🇲🇸","🇲🇲","🇳🇦","🇳🇷","🇳🇵","🇳🇪","🇳🇬","🇳🇱","🇳🇮","🇳🇺","🇳🇿","🇳🇨","🇳🇴","🇮🇲","🇳🇫","🇨🇽","🇸🇭","🇨🇰","🇹🇨","🇦🇪","🇴🇲","🇵🇳","🇵🇰","🇵🇼","🇵🇸","🇵🇦","🇵🇬","🇵🇾","🇵🇪","🇵🇱","🇵🇹","🇵🇷","🇰🇷","🇷🇪","🇷🇺","🇷🇼","🇷🇴","🇸🇻","🇼🇸","🇸🇲","🇸🇹","🇸🇦","🇸🇿","🇲🇵","🇸🇨","🇧🇱","🇵🇲","🇸🇳","🇻🇨","🇰🇳","🇱🇨","🇷🇸","🇸🇬","🇸🇽","🇸🇾","🇸🇰","🇸🇮","🇺🇸","🇸🇧","🇸🇴","🇸🇩","🇸🇷","🇸🇱","🇹🇯","🇹🇭","🇹🇼","🇹🇿","🇹🇬","🇹🇰","🇹🇴","🇹🇹","🇹🇻","🇹🇳","🇹🇲","🇹🇷","🇺🇬","🇺🇿","🇺🇦","🇼🇫","🇺🇾","🇫🇴","🇫🇯","🇵🇭","🇫🇮","🇫🇰","🇫🇷","🇬🇫","🇵🇫","🇹🇫","🇭🇷","🇨🇫","🇹🇩","🇲🇪","🇨🇿","🇨🇱","🇨🇭","🇸🇪","🇱🇰","🇪🇨","🇬🇶","🇪🇷","🇪🇪","🇪🇹","🇿🇦","🇬🇸","🇸🇸","🇯🇲","🇯🇵","🎌" }, new String[]{ - "💟","☮","✝","☪","🕉","☸","✡","🔯","🕎","☯","☦","🛐","⛎","♈","♉","♊","♋","♌","♍","♎","♏", - "♐","♑","♒","♓","🆔","⚛","🈳","🈹","☢","☣","📴","📳","🈶","🈚","🈸","🈺","🈷","✴","🆚","🉑", - "💮","🉐","㊙","㊗","🈴","🈵","🈲","🅰","🅱","🆎","🆑","🅾","🆘","⛔","📛","🚫","❌","⭕","💢", - "♨","🚷","🚯","🚳","🚱","🔞","📵","❗","❕","❓","❔","‼","⁉","💯","🔅","🔆","🔱","⚜","〽","⚠", - "🚸","🔰","♻","🈯","💹","❇","✳","❎","✅","🌐","Ⓜ","💠","🌀","➿","🏧","🈂","🛂","🛃","🛄", - "🛅","♿","🚭","🚾","🅿","🚰","🚹","🚺","🚼","🚻","🚮","🎦","📶","🈁","🔤","🔡","🔠","🔣","ℹ", - "🆖","🆗","🆙","🆒","🆕","🆓","0⃣","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","🔟","🔢","#⃣","*⃣","▶", - "⏸","⏯","⏹","⏺","⏭","⏮","⏩","⏪","⏫","⏬","◀","🔼","🔽","➡","⬅","⬆","⬇","↗","↘","↙", - "↖","↕","↔","↪","↩","⤴","⤵","🔀","🔁","🔂","🔄","🔃","🎵","🎶","〰","➰","✔","➕","➖","➗", - "✖","💲","💱","™","©","®","🔚","🔙","🔛","🔝","🔜","☑","🔘","⚪","⚫","🔴","🔵","🔺","🔻","🔸", - "🔹","🔶","🔷","🔳","🔲","▪","▫","◾","◽","◼","◻","⬛","⬜","🔇","🔈","🔉","🔊","🔕","🔔","📣", - "📢","👁‍🗨","💬","💭","🗯","🃏","🀄","🎴","♠","♣","♥","♦","🕐","🕑","🕒","🕓","🕔","🕕","🕖", - "🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧" + "💟","☮","✝","☪","🕉","☸","✡","🔯","🕎","☯","☦","🛐","⛎","♈","♉","♊","♋","♌","♍","♎","♏","♐","♑","♒","♓","🆔","⚛","🉑","☢","☣","📴","📳","🈶","🈚","🈸","🈺","🈷","✴","🆚","💮","🉐","㊙","㊗","🈴","🈵","🈹","🈲","🅰","🅱","🆎","🆑","🅾","🆘","❌","⭕","🛑","⛔","📛","🚫","💯","💢","♨","🚷","🚯","🚳","🚱","🔞","📵","🚭","❗","❕","❓","❔","‼","⁉","🔅","🔆","〽","⚠","🚸","🔱","⚜","🔰","♻","✅","🈯","💹","❇","✳","❎","🌐","💠","Ⓜ","🌀","💤","🏧","🚾","♿","🅿","🈳","🈂","🛂","🛃","🛄","🛅","🚹","🚺","🚼","🚻","🚮","🎦","📶","🈁","🔣","ℹ","🔤","🔡","🔠","🆖","🆗","🆙","🆒","🆕","🆓","0⃣","1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","🔟","🔢","#⃣","*⃣","▶","⏸","⏯","⏹","⏺","⏭","⏮","⏩","⏪","⏫","⏬","◀","🔼","🔽","➡","⬅","⬆","⬇","↗","↘","↙","↖","↕","↔","↪","↩","⤴","⤵","🔀","🔁","🔂","🔄","🔃","🎵","🎶","➕","➖","➗","✖","💲","💱","™","©","®","〰","➰","➿","🔚","🔙","🔛","🔝","🔜","✔","☑","🔘","⚪","⚫","🔴","🔵","🔺","🔻","🔸","🔹","🔶","🔷","🔳","🔲","▪","▫","◾","◽","◼","◻","⬛","⬜","🔈","🔇","🔉","🔊","🔔","🔕","📣","📢","👁‍🗨","💬","💭","🗯","♠","♣","♥","♦","🃏","🎴","🀄","🕐","🕑","🕒","🕓","🕔","🕕","🕖","🕗","🕘","🕙","🕚","🕛","🕜","🕝","🕞","🕟","🕠","🕡","🕢","🕣","🕤","🕥","🕦","🕧","⌚","📱","📲","💻","⌨","🖥","🖨","🖱","🖲","🕹","🗜","💽","💾","💿","📀","📼","📷","📸","📹","🎥","📽","🎞","📞","☎","📟","📠","📺","📻","🎙","🎚","🎛","⏱","⏲","⏰","🕰","⌛","⏳","📡","🔋","🔌","💡","🔦","🕯","🗑","🛢","💸","💵","💴","💶","💷","💰","💳","💎","⚖","🔧","🔨","⚒","🛠","⛏","🔩","⚙","⛓","🔫","💣","🔪","🗡","⚔","🛡","🚬","⚰","⚱","🏺","🔮","📿","💈","⚗","🔭","🔬","🕳","💊","💉","🌡","🚽","🚰","🚿","🛁","🛀","🛀🏻","🛀🏼","🛀🏽","🛀🏾","🛀🏿","🛎","🔑","🗝","🚪","🛋","🛏","🛌","🖼","🛍","🛒","🎁","🎈","🎏","🎀","🎊","🎉","🎎","🏮","🎐","✉","📩","📨","📧","💌","📥","📤","📦","🏷","📪","📫","📬","📭","📮","📯","📜","📃","📄","📑","📊","📈","📉","🗒","🗓","📆","📅","📇","🗃","🗳","🗄","📋","📁","📂","🗂","🗞","📰","📓","📔","📒","📕","📗","📘","📙","📚","📖","🔖","🔗","📎","🖇","📐","📏","📌","📍","✂","🖊","🖋","✒","🖌","🖍","📝","✏","🔍","🔎","🔏","🔐","🔒","🔓" } }; @@ -337,6 +267,5 @@ public class EmojiData { } dataColored[1] = data[1]; dataColored[3] = data[3]; - dataColored[4] = data[4]; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java index 1017c58f87a..7872b00f3ff 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoadOperation.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -25,6 +26,7 @@ private static class RequestInfo { private int requestToken; private int offset; private TLRPC.TL_upload_file response; + private TLRPC.TL_upload_webFile responseWeb; } private final static int stateIdle = 0; @@ -41,6 +43,7 @@ private static class RequestInfo { private boolean started; private int datacenter_id; private TLRPC.InputFileLocation location; + private TLRPC.TL_inputWebFileLocation webLocation; private volatile int state = stateIdle; private int downloadedBytes; private int totalBytesCount; @@ -68,6 +71,8 @@ private static class RequestInfo { private File tempPath; private boolean isForceRequest; + private int currentType; + public interface FileLoadOperationDelegate { void didFinishLoadingFile(FileLoadOperation operation, File finalFile); void didFailedLoadingFile(FileLoadOperation operation, int state); @@ -92,10 +97,30 @@ public FileLoadOperation(TLRPC.FileLocation photoLocation, String extension, int location.local_id = photoLocation.local_id; datacenter_id = photoLocation.dc_id; } + currentType = ConnectionsManager.FileTypePhoto; totalBytesCount = size; ext = extension != null ? extension : "jpg"; } + public FileLoadOperation(TLRPC.TL_webDocument webDocument) { + webLocation = new TLRPC.TL_inputWebFileLocation(); + webLocation.url = webDocument.url; + webLocation.access_hash = webDocument.access_hash; + totalBytesCount = webDocument.size; + datacenter_id = webDocument.dc_id; + String defaultExt = FileLoader.getExtensionByMime(webDocument.mime_type); + if (webDocument.mime_type.startsWith("image/")) { + currentType = ConnectionsManager.FileTypePhoto; + } else if (webDocument.mime_type.equals("audio/ogg")) { + currentType = ConnectionsManager.FileTypeAudio; + } else if (webDocument.mime_type.startsWith("video/")) { + currentType = ConnectionsManager.FileTypeVideo; + } else { + currentType = ConnectionsManager.FileTypeFile; + } + ext = ImageLoader.getHttpUrlExtension(webDocument.url, defaultExt); + } + public FileLoadOperation(TLRPC.Document documentLocation) { try { if (documentLocation instanceof TLRPC.TL_documentEncrypted) { @@ -127,6 +152,13 @@ public FileLoadOperation(TLRPC.Document documentLocation) { } else { ext = ext.substring(idx); } + if ("audio/ogg".equals(documentLocation.mime_type)) { + currentType = ConnectionsManager.FileTypeAudio; + } else if ("video/mp4".equals(documentLocation.mime_type)) { + currentType = ConnectionsManager.FileTypeVideo; + } else { + currentType = ConnectionsManager.FileTypeFile; + } if (ext.length() <= 1) { if (documentLocation.mime_type != null) { switch (documentLocation.mime_type) { @@ -145,7 +177,7 @@ public FileLoadOperation(TLRPC.Document documentLocation) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); onFail(true, 0); } } @@ -167,15 +199,23 @@ public boolean wasStarted() { return started; } + public int getCurrentType() { + return currentType; + } + public String getFileName() { - return location.volume_id + "_" + location.local_id + "." + ext; + if (location != null) { + return location.volume_id + "_" + location.local_id + "." + ext; + } else { + return Utilities.MD5(webLocation.url) + "." + ext; + } } public boolean start() { if (state != stateIdle) { return false; } - if (location == null) { + if (location == null && webLocation == null) { onFail(true, 0); return false; } @@ -183,27 +223,36 @@ public boolean start() { String fileNameFinal; String fileNameTemp; String fileNameIv = null; - if (location.volume_id != 0 && location.local_id != 0) { - if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) { - onFail(true, 0); - return false; - } - - fileNameTemp = location.volume_id + "_" + location.local_id + ".temp"; - fileNameFinal = location.volume_id + "_" + location.local_id + "." + ext; + if (webLocation != null) { + String md5 = Utilities.MD5(webLocation.url); + fileNameTemp = md5 + ".temp"; + fileNameFinal = md5 + "." + ext; if (key != null) { - fileNameIv = location.volume_id + "_" + location.local_id + ".iv"; + fileNameIv = md5 + ".iv"; } } else { - if (datacenter_id == 0 || location.id == 0) { - onFail(true, 0); - return false; - } + if (location.volume_id != 0 && location.local_id != 0) { + if (datacenter_id == Integer.MIN_VALUE || location.volume_id == Integer.MIN_VALUE || datacenter_id == 0) { + onFail(true, 0); + return false; + } - fileNameTemp = datacenter_id + "_" + location.id + ".temp"; - fileNameFinal = datacenter_id + "_" + location.id + ext; - if (key != null) { - fileNameIv = datacenter_id + "_" + location.id + ".iv"; + fileNameTemp = location.volume_id + "_" + location.local_id + ".temp"; + fileNameFinal = location.volume_id + "_" + location.local_id + "." + ext; + if (key != null) { + fileNameIv = location.volume_id + "_" + location.local_id + ".iv"; + } + } else { + if (datacenter_id == 0 || location.id == 0) { + onFail(true, 0); + return false; + } + + fileNameTemp = datacenter_id + "_" + location.id + ".temp"; + fileNameFinal = datacenter_id + "_" + location.id + ext; + if (key != null) { + fileNameIv = datacenter_id + "_" + location.id + ".iv"; + } } } currentDownloadChunkSize = totalBytesCount >= bigFileSizeFrom ? downloadChunkSizeBig : downloadChunkSize; @@ -226,7 +275,7 @@ public boolean start() { } if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", "start loading file to temp = " + cacheFileTemp + " final = " + cacheFileFinal); + FileLog.d("start loading file to temp = " + cacheFileTemp + " final = " + cacheFileFinal); } if (fileNameIv != null) { @@ -240,7 +289,7 @@ public boolean start() { downloadedBytes = 0; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); downloadedBytes = 0; } } @@ -250,7 +299,7 @@ public boolean start() { fileOutputStream.seek(downloadedBytes); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (fileOutputStream == null) { onFail(true, 0); @@ -262,7 +311,7 @@ public boolean start() { public void run() { if (totalBytesCount != 0 && downloadedBytes == totalBytesCount) { try { - onFinishLoadingFile(); + onFinishLoadingFile(false); } catch (Exception e) { onFail(true, 0); } @@ -274,7 +323,7 @@ public void run() { } else { started = true; try { - onFinishLoadingFile(); + onFinishLoadingFile(false); } catch (Exception e) { onFail(true, 0); } @@ -308,13 +357,13 @@ private void cleanup() { try { fileOutputStream.getChannel().close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fileOutputStream.close(); fileOutputStream = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -323,7 +372,7 @@ private void cleanup() { fiv = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (delayedRequestInfos != null) { for (int a = 0; a < delayedRequestInfos.size(); a++) { @@ -331,13 +380,16 @@ private void cleanup() { if (requestInfo.response != null) { requestInfo.response.disableFree = false; requestInfo.response.freeResources(); + } else if (requestInfo.responseWeb != null) { + requestInfo.responseWeb.disableFree = false; + requestInfo.responseWeb.freeResources(); } } delayedRequestInfos.clear(); } } - private void onFinishLoadingFile() throws Exception { + private void onFinishLoadingFile(final boolean increment) throws Exception { if (state != stateDownloading) { return; } @@ -351,7 +403,7 @@ private void onFinishLoadingFile() throws Exception { boolean renameResult = cacheFileTemp.renameTo(cacheFileFinal); if (!renameResult) { if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "unable to rename temp = " + cacheFileTemp + " to final = " + cacheFileFinal + " retry = " + renameRetryCount); + FileLog.e("unable to rename temp = " + cacheFileTemp + " to final = " + cacheFileFinal + " retry = " + renameRetryCount); } renameRetryCount++; if (renameRetryCount < 3) { @@ -360,7 +412,7 @@ private void onFinishLoadingFile() throws Exception { @Override public void run() { try { - onFinishLoadingFile(); + onFinishLoadingFile(increment); } catch (Exception e) { onFail(false, 0); } @@ -372,9 +424,20 @@ public void run() { } } if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "finished downloading file to " + cacheFileFinal); + FileLog.e("finished downloading file to " + cacheFileFinal); } delegate.didFinishLoadingFile(FileLoadOperation.this, cacheFileFinal); + if (increment) { + if (currentType == ConnectionsManager.FileTypeAudio) { + StatsController.getInstance().incrementReceivedItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_AUDIOS, 1); + } else if (currentType == ConnectionsManager.FileTypeVideo) { + StatsController.getInstance().incrementReceivedItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_VIDEOS, 1); + } else if (currentType == ConnectionsManager.FileTypePhoto) { + StatsController.getInstance().incrementReceivedItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_PHOTOS, 1); + } else if (currentType == ConnectionsManager.FileTypeFile) { + StatsController.getInstance().incrementReceivedItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_FILES, 1); + } + } } private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) { @@ -384,28 +447,38 @@ private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) if (downloadedBytes != requestInfo.offset) { if (state == stateDownloading) { delayedRequestInfos.add(requestInfo); - requestInfo.response.disableFree = true; + if (requestInfo.response != null) { + requestInfo.response.disableFree = true; + } else { + requestInfo.responseWeb.disableFree = true; + } } return; } - if (requestInfo.response.bytes == null || requestInfo.response.bytes.limit() == 0) { - onFinishLoadingFile(); + NativeByteBuffer bytes; + if (requestInfo.response != null) { + bytes = requestInfo.response.bytes; + } else { + bytes = requestInfo.responseWeb.bytes; + } + if (bytes == null || bytes.limit() == 0) { + onFinishLoadingFile(true); return; } - int currentBytesSize = requestInfo.response.bytes.limit(); + int currentBytesSize = bytes.limit(); downloadedBytes += currentBytesSize; boolean finishedDownloading = currentBytesSize != currentDownloadChunkSize || (totalBytesCount == downloadedBytes || downloadedBytes % currentDownloadChunkSize != 0) && (totalBytesCount <= 0 || totalBytesCount <= downloadedBytes); if (key != null) { - Utilities.aesIgeEncryption(requestInfo.response.bytes.buffer, key, iv, false, true, 0, requestInfo.response.bytes.limit()); + Utilities.aesIgeEncryption(bytes.buffer, key, iv, false, true, 0, bytes.limit()); if (finishedDownloading && bytesCountPadding != 0) { - requestInfo.response.bytes.limit(requestInfo.response.bytes.limit() - bytesCountPadding); + bytes.limit(bytes.limit() - bytesCountPadding); } } if (fileOutputStream != null) { FileChannel channel = fileOutputStream.getChannel(); - channel.write(requestInfo.response.bytes.buffer); + channel.write(bytes.buffer); } if (fiv != null) { fiv.seek(0); @@ -420,20 +493,25 @@ private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) if (downloadedBytes == delayedRequestInfo.offset) { delayedRequestInfos.remove(a); processRequestResult(delayedRequestInfo, null); - delayedRequestInfo.response.disableFree = false; - delayedRequestInfo.response.freeResources(); + if (delayedRequestInfo.response != null) { + delayedRequestInfo.response.disableFree = false; + delayedRequestInfo.response.freeResources(); + } else { + delayedRequestInfo.responseWeb.disableFree = false; + delayedRequestInfo.responseWeb.freeResources(); + } break; } } if (finishedDownloading) { - onFinishLoadingFile(); + onFinishLoadingFile(true); } else { startDownloadRequest(); } } catch (Exception e) { onFail(false, 0); - FileLog.e("tmessages", e); + FileLog.e(e); } } else { if (error.text.contains("FILE_MIGRATE_")) { @@ -456,9 +534,9 @@ private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) } else if (error.text.contains("OFFSET_INVALID")) { if (downloadedBytes % currentDownloadChunkSize == 0) { try { - onFinishLoadingFile(); + onFinishLoadingFile(true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); onFail(false, 0); } } else { @@ -468,7 +546,9 @@ private void processRequestResult(RequestInfo requestInfo, TLRPC.TL_error error) onFail(false, 2); } else { if (location != null) { - FileLog.e("tmessages", "" + location + " id = " + location.id + " local_id = " + location.local_id + " access_hash = " + location.access_hash + " volume_id = " + location.volume_id + " secret = " + location.secret); + FileLog.e("" + location + " id = " + location.id + " local_id = " + location.local_id + " access_hash = " + location.access_hash + " volume_id = " + location.volume_id + " secret = " + location.secret); + } else if (webLocation != null) { + FileLog.e("" + webLocation + " id = " + webLocation.url + " access_hash = " + webLocation.access_hash); } onFail(false, 0); } @@ -504,22 +584,51 @@ private void startDownloadRequest() { break; } boolean isLast = totalBytesCount <= 0 || a == count - 1 || totalBytesCount > 0 && nextDownloadOffset + currentDownloadChunkSize >= totalBytesCount; - TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile(); - req.location = location; - req.offset = nextDownloadOffset; - req.limit = currentDownloadChunkSize; + TLObject request; + int offset; + int flags; + if (webLocation != null) { + TLRPC.TL_upload_getWebFile req = new TLRPC.TL_upload_getWebFile(); + req.location = webLocation; + req.offset = offset = nextDownloadOffset; + req.limit = currentDownloadChunkSize; + request = req; + //flags = ConnectionsManager.ConnectionTypeGeneric; + flags = requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2; + } else { + TLRPC.TL_upload_getFile req = new TLRPC.TL_upload_getFile(); + req.location = location; + req.offset = offset = nextDownloadOffset; + req.limit = currentDownloadChunkSize; + request = req; + flags = requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2; + } nextDownloadOffset += currentDownloadChunkSize; - final RequestInfo requestInfo = new RequestInfo(); requestInfos.add(requestInfo); - requestInfo.offset = req.offset; - requestInfo.requestToken = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + requestInfo.offset = offset; + requestInfo.requestToken = ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { - requestInfo.response = (TLRPC.TL_upload_file) response; + if (response instanceof TLRPC.TL_upload_file) { + requestInfo.response = (TLRPC.TL_upload_file) response; + }else { + requestInfo.responseWeb = (TLRPC.TL_upload_webFile) response; + } + if (response != null) { + if (currentType == ConnectionsManager.FileTypeAudio) { + StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_AUDIOS, response.getObjectSize() + 4); + } else if (currentType == ConnectionsManager.FileTypeVideo) { + StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_VIDEOS, response.getObjectSize() + 4); + } else if (currentType == ConnectionsManager.FileTypePhoto) { + StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_PHOTOS, response.getObjectSize() + 4); + } else if (currentType == ConnectionsManager.FileTypeFile) { + StatsController.getInstance().incrementReceivedBytesCount(response.networkType, StatsController.TYPE_FILES, response.getObjectSize() + 4); + } + } processRequestResult(requestInfo, error); } - }, null, (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors, datacenter_id, requestsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeDownload : ConnectionsManager.ConnectionTypeDownload2, isLast); + }, null, (isForceRequest ? ConnectionsManager.RequestFlagForceDownload : 0) | ConnectionsManager.RequestFlagFailOnServerErrors, datacenter_id, flags, isLast); requestsCount++; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java index 3345031487c..f4687991005 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLoader.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -134,11 +134,11 @@ public void run() { }); } - public void uploadFile(final String location, final boolean encrypted, final boolean small) { - uploadFile(location, encrypted, small, 0); + public void uploadFile(final String location, final boolean encrypted, final boolean small, final int type) { + uploadFile(location, encrypted, small, 0, type); } - public void uploadFile(final String location, final boolean encrypted, final boolean small, final int estimatedSize) { + public void uploadFile(final String location, final boolean encrypted, final boolean small, final int estimatedSize, final int type) { if (location == null) { return; } @@ -162,13 +162,13 @@ public void run() { uploadSizes.remove(location); } } - FileUploadOperation operation = new FileUploadOperation(location, encrypted, esimated); + FileUploadOperation operation = new FileUploadOperation(location, encrypted, esimated, type); if (encrypted) { uploadOperationPathsEnc.put(location, operation); } else { uploadOperationPaths.put(location, operation); } - operation.delegate = new FileUploadOperation.FileUploadOperationDelegate() { + operation.setDelegate(new FileUploadOperation.FileUploadOperationDelegate() { @Override public void didFinishUploadingFile(final FileUploadOperation operation, final TLRPC.InputFile inputFile, final TLRPC.InputEncryptedFile inputEncryptedFile, final byte[] key, final byte[] iv) { fileLoaderQueue.postRunnable(new Runnable() { @@ -247,7 +247,7 @@ public void didChangedUploadProgress(FileUploadOperation operation, final float delegate.fileUploadProgressChanged(location, progress, encrypted); } } - }; + }); if (small) { if (currentUploadSmallOperationsCount < 1) { currentUploadSmallOperationsCount++; @@ -268,18 +268,22 @@ public void didChangedUploadProgress(FileUploadOperation operation, final float } public void cancelLoadFile(TLRPC.Document document) { - cancelLoadFile(document, null, null); + cancelLoadFile(document, null, null, null); + } + + public void cancelLoadFile(TLRPC.TL_webDocument document) { + cancelLoadFile(null, document, null, null); } public void cancelLoadFile(TLRPC.PhotoSize photo) { - cancelLoadFile(null, photo.location, null); + cancelLoadFile(null, null, photo.location, null); } public void cancelLoadFile(TLRPC.FileLocation location, String ext) { - cancelLoadFile(null, location, ext); + cancelLoadFile(null, null, location, ext); } - private void cancelLoadFile(final TLRPC.Document document, final TLRPC.FileLocation location, final String locationExt) { + private void cancelLoadFile(final TLRPC.Document document, final TLRPC.TL_webDocument webDocument, final TLRPC.FileLocation location, final String locationExt) { if (location == null && document == null) { return; } @@ -291,18 +295,20 @@ public void run() { fileName = getAttachFileName(location, locationExt); } else if (document != null) { fileName = getAttachFileName(document); + } else if (webDocument != null) { + fileName = getAttachFileName(webDocument); } if (fileName == null) { return; } FileLoadOperation operation = loadOperationPaths.remove(fileName); if (operation != null) { - if (MessageObject.isVoiceDocument(document)) { + if (MessageObject.isVoiceDocument(document) || MessageObject.isVoiceWebDocument(webDocument)) { if (!audioLoadOperationQueue.remove(operation)) { currentAudioLoadOperationsCount--; } } else if (location != null) { - if (!photoLoadOperationQueue.remove(operation)) { + if (!photoLoadOperationQueue.remove(operation) || MessageObject.isImageWebDocument(webDocument)) { currentPhotoLoadOperationsCount--; } } else { @@ -329,24 +335,28 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return result[0]; } public void loadFile(TLRPC.PhotoSize photo, String ext, boolean cacheOnly) { - loadFile(null, photo.location, ext, photo.size, false, cacheOnly || (photo != null && photo.size == 0 || photo.location.key != null)); + loadFile(null, null, photo.location, ext, photo.size, false, cacheOnly || (photo != null && photo.size == 0 || photo.location.key != null)); } public void loadFile(TLRPC.Document document, boolean force, boolean cacheOnly) { - loadFile(document, null, null, 0, force, cacheOnly || document != null && document.key != null); + loadFile(document, null, null, null, 0, force, cacheOnly || document != null && document.key != null); + } + + public void loadFile(TLRPC.TL_webDocument document, boolean force, boolean cacheOnly) { + loadFile(null, document, null, null, 0, force, cacheOnly); } public void loadFile(TLRPC.FileLocation location, String ext, int size, boolean cacheOnly) { - loadFile(null, location, ext, size, true, cacheOnly || size == 0 || (location != null && location.key != null)); + loadFile(null, null, location, ext, size, true, cacheOnly || size == 0 || (location != null && location.key != null)); } - private void loadFile(final TLRPC.Document document, final TLRPC.FileLocation location, final String locationExt, final int locationSize, final boolean force, final boolean cacheOnly) { + private void loadFile(final TLRPC.Document document, final TLRPC.TL_webDocument webDocument, final TLRPC.FileLocation location, final String locationExt, final int locationSize, final boolean force, final boolean cacheOnly) { fileLoaderQueue.postRunnable(new Runnable() { @Override public void run() { @@ -355,6 +365,8 @@ public void run() { fileName = getAttachFileName(location, locationExt); } else if (document != null) { fileName = getAttachFileName(document); + } else if (webDocument != null) { + fileName = getAttachFileName(webDocument); } if (fileName == null || fileName.contains("" + Integer.MIN_VALUE)) { return; @@ -366,9 +378,9 @@ public void run() { if (force) { operation.setForceRequest(true); LinkedList downloadQueue; - if (MessageObject.isVoiceDocument(document)) { + if (MessageObject.isVoiceDocument(document) || MessageObject.isVoiceWebDocument(webDocument)) { downloadQueue = audioLoadOperationQueue; - } else if (location != null) { + } else if (location != null || MessageObject.isImageWebDocument(webDocument)) { downloadQueue = photoLoadOperationQueue; } else { downloadQueue = loadOperationQueue; @@ -400,6 +412,17 @@ public void run() { } else { type = MEDIA_DIR_DOCUMENT; } + } else if (webDocument != null) { + operation = new FileLoadOperation(webDocument); + if (MessageObject.isVoiceWebDocument(webDocument)) { + type = MEDIA_DIR_AUDIO; + } else if (MessageObject.isVideoWebDocument(webDocument)) { + type = MEDIA_DIR_VIDEO; + } else if (MessageObject.isImageWebDocument(webDocument)) { + type = MEDIA_DIR_IMAGE; + } else { + type = MEDIA_DIR_DOCUMENT; + } } if (!cacheOnly) { storeDir = getDirectory(type); @@ -414,12 +437,12 @@ public void didFinishLoadingFile(FileLoadOperation operation, File finalFile) { if (delegate != null) { delegate.fileDidLoaded(finalFileName, finalFile, finalType); } - checkDownloadQueue(document, location, finalFileName); + checkDownloadQueue(document, webDocument, location, finalFileName); } @Override public void didFailedLoadingFile(FileLoadOperation operation, int reason) { - checkDownloadQueue(document, location, finalFileName); + checkDownloadQueue(document, webDocument, location, finalFileName); if (delegate != null) { delegate.fileDidFailedLoad(finalFileName, reason); } @@ -506,12 +529,12 @@ public void didChangedLoadProgress(FileLoadOperation operation, float progress) }); } - private void checkDownloadQueue(final TLRPC.Document document, final TLRPC.FileLocation location, final String arg1) { + private void checkDownloadQueue(final TLRPC.Document document, final TLRPC.TL_webDocument webDocument, final TLRPC.FileLocation location, final String arg1) { fileLoaderQueue.postRunnable(new Runnable() { @Override public void run() { FileLoadOperation operation = loadOperationPaths.remove(arg1); - if (MessageObject.isVoiceDocument(document)) { + if (MessageObject.isVoiceDocument(document) || MessageObject.isVoiceWebDocument(webDocument)) { if (operation != null) { if (operation.wasStarted()) { currentAudioLoadOperationsCount--; @@ -531,7 +554,7 @@ public void run() { break; } } - } else if (location != null) { + } else if (location != null || MessageObject.isImageWebDocument(webDocument)) { if (operation != null) { if (operation.wasStarted()) { currentPhotoLoadOperationsCount--; @@ -616,6 +639,13 @@ public static String getMessageFileName(TLRPC.Message message) { } } else if (message.media.webpage.document != null) { return getAttachFileName(message.media.webpage.document); + } else if (message.media instanceof TLRPC.TL_messageMediaInvoice) { + return getAttachFileName(((TLRPC.TL_messageMediaInvoice) message.media).photo); + } + } else if (message.media instanceof TLRPC.TL_messageMediaInvoice) { + TLRPC.TL_webDocument document = ((TLRPC.TL_messageMediaInvoice) message.media).photo; + if (document != null) { + return Utilities.MD5(document.url) + "." + ImageLoader.getHttpUrlExtension(document.url, getExtensionByMime(document.mime_type)); } } } @@ -659,6 +689,8 @@ public static File getPathToMessage(TLRPC.Message message) { } } } + } else if (message.media instanceof TLRPC.TL_messageMediaInvoice) { + return getPathToAttach(((TLRPC.TL_messageMediaInvoice) message.media).photo, true); } } return new File(""); @@ -704,6 +736,17 @@ public static File getPathToAttach(TLObject attach, String ext, boolean forceCac } else { dir = getInstance().getDirectory(MEDIA_DIR_IMAGE); } + } else if (attach instanceof TLRPC.TL_webDocument) { + TLRPC.TL_webDocument document = (TLRPC.TL_webDocument) attach; + if (document.mime_type.startsWith("image/")) { + dir = getInstance().getDirectory(MEDIA_DIR_IMAGE); + } else if (document.mime_type.startsWith("audio/")) { + dir = getInstance().getDirectory(MEDIA_DIR_AUDIO); + } else if (document.mime_type.startsWith("video/")) { + dir = getInstance().getDirectory(MEDIA_DIR_VIDEO); + } else { + dir = getInstance().getDirectory(MEDIA_DIR_DOCUMENT); + } } } if (dir == null) { @@ -768,6 +811,14 @@ public static String getDocumentFileName(TLRPC.Document document) { return ""; } + public static String getExtensionByMime(String mime) { + int index; + if ((index = mime.indexOf('/')) != -1) { + return mime.substring(index + 1); + } + return ""; + } + public static String getDocumentExtension(TLRPC.Document document) { String fileName = getDocumentFileName(document); int idx = fileName.lastIndexOf('.'); @@ -832,6 +883,9 @@ public static String getAttachFileName(TLObject attach, String ext) { return document.dc_id + "_" + document.id + "_" + document.version; } } + } else if (attach instanceof TLRPC.TL_webDocument) { + TLRPC.TL_webDocument document = (TLRPC.TL_webDocument) attach; + return Utilities.MD5(document.url) + "." + ImageLoader.getHttpUrlExtension(document.url, getExtensionByMime(document.mime_type)); } else if (attach instanceof TLRPC.PhotoSize) { TLRPC.PhotoSize photo = (TLRPC.PhotoSize) attach; if (photo.location == null || photo.location instanceof TLRPC.TL_fileLocationUnavailable) { @@ -863,7 +917,7 @@ public void run() { file.deleteOnExit(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { @@ -874,7 +928,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (type == 2) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java index 6f0b6dc6cdc..7d199cfd072 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileLog.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -85,17 +85,17 @@ public static String getNetworkLogPath() { return ""; } - public static void e(final String tag, final String message, final Throwable exception) { + public static void e(final String message, final Throwable exception) { if (!BuildVars.DEBUG_VERSION) { return; } - Log.e(tag, message, exception); + Log.e("tmessages", message, exception); if (getInstance().streamWriter != null) { getInstance().logQueue.postRunnable(new Runnable() { @Override public void run() { try { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + message + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + message + "\n"); getInstance().streamWriter.write(exception.toString()); getInstance().streamWriter.flush(); } catch (Exception e) { @@ -106,17 +106,17 @@ public void run() { } } - public static void e(final String tag, final String message) { + public static void e(final String message) { if (!BuildVars.DEBUG_VERSION) { return; } - Log.e(tag, message); + Log.e("tmessages", message); if (getInstance().streamWriter != null) { getInstance().logQueue.postRunnable(new Runnable() { @Override public void run() { try { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + message + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + message + "\n"); getInstance().streamWriter.flush(); } catch (Exception e) { e.printStackTrace(); @@ -126,7 +126,7 @@ public void run() { } } - public static void e(final String tag, final Throwable e) { + public static void e(final Throwable e) { if (!BuildVars.DEBUG_VERSION) { return; } @@ -136,10 +136,10 @@ public static void e(final String tag, final Throwable e) { @Override public void run() { try { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + e + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + e + "\n"); StackTraceElement[] stack = e.getStackTrace(); for (int a = 0; a < stack.length; a++) { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/" + tag + "﹕ " + stack[a] + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " E/tmessages: " + stack[a] + "\n"); } getInstance().streamWriter.flush(); } catch (Exception e) { @@ -152,17 +152,17 @@ public void run() { } } - public static void d(final String tag, final String message) { + public static void d(final String message) { if (!BuildVars.DEBUG_VERSION) { return; } - Log.d(tag, message); + Log.d("tmessages", message); if (getInstance().streamWriter != null) { getInstance().logQueue.postRunnable(new Runnable() { @Override public void run() { try { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " D/" + tag + "﹕ " + message + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " D/tmessages: " + message + "\n"); getInstance().streamWriter.flush(); } catch (Exception e) { e.printStackTrace(); @@ -172,17 +172,17 @@ public void run() { } } - public static void w(final String tag, final String message) { + public static void w(final String message) { if (!BuildVars.DEBUG_VERSION) { return; } - Log.w(tag, message); + Log.w("tmessages", message); if (getInstance().streamWriter != null) { getInstance().logQueue.postRunnable(new Runnable() { @Override public void run() { try { - getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/" + tag + ": " + message + "\n"); + getInstance().streamWriter.write(getInstance().dateFormat.format(System.currentTimeMillis()) + " W/tmessages: " + message + "\n"); getInstance().streamWriter.flush(); } catch (Exception e) { e.printStackTrace(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/FileUploadOperation.java b/TMessagesProj/src/main/java/org/telegram/messenger/FileUploadOperation.java index 0cdf5e64f1c..457e58a2baf 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/FileUploadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/FileUploadOperation.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -22,34 +22,51 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; public class FileUploadOperation { - private int uploadChunkSize = 1024 * 32; + + private class UploadCachedResult { + private long bytesOffset; + private byte[] iv; + } + + private boolean isLastPart = false; + private final int maxRequestsCount = 8; + private int uploadChunkSize = 1024 * 128; + private ArrayList freeRequestIvs; + private int requestNum; private String uploadingFilePath; - public int state = 0; + private int state; private byte[] readBuffer; - public FileUploadOperationDelegate delegate; - private int requestToken = 0; - private int currentPartNum = 0; + private FileUploadOperationDelegate delegate; + private HashMap requestTokens = new HashMap<>(); + private int currentPartNum; private long currentFileId; - private boolean isLastPart = false; - private long totalFileSize = 0; - private int totalPartsCount = 0; - private long currentUploaded = 0; - private int saveInfoTimes = 0; + private long totalFileSize; + private int totalPartsCount; + private long readBytesCount; + private long uploadedBytesCount; + private int saveInfoTimes; private byte[] key; private byte[] iv; private byte[] ivChange; - private boolean isEncrypted = false; - private int fingerprint = 0; - private boolean isBigFile = false; + private boolean isEncrypted; + private int fingerprint; + private boolean isBigFile; private String fileKey; - private int estimatedSize = 0; - private int uploadStartTime = 0; + private int estimatedSize; + private int uploadStartTime; private FileInputStream stream; - private MessageDigest mdEnc = null; - private boolean started = false; + private MessageDigest mdEnc; + private boolean started; + private int currentUploadRequetsCount; + private SharedPreferences preferences; + private int currentType; + private int lastSavedPartNum; + private HashMap cachedResults = new HashMap<>(); public interface FileUploadOperationDelegate { void didFinishUploadingFile(FileUploadOperation operation, TLRPC.InputFile inputFile, TLRPC.InputEncryptedFile inputEncryptedFile, byte[] key, byte[] iv); @@ -57,16 +74,21 @@ public interface FileUploadOperationDelegate { void didChangedUploadProgress(FileUploadOperation operation, float progress); } - public FileUploadOperation(String location, boolean encrypted, int estimated) { + public FileUploadOperation(String location, boolean encrypted, int estimated, int type) { uploadingFilePath = location; isEncrypted = encrypted; estimatedSize = estimated; + currentType = type; } public long getTotalFileSize() { return totalFileSize; } + public void setDelegate(FileUploadOperationDelegate fileUploadOperationDelegate) { + delegate = fileUploadOperationDelegate; + } + public void start() { if (state != 0) { return; @@ -75,7 +97,10 @@ public void start() { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - startUploadRequest(); + preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); + for (int a = 0; a < maxRequestsCount; a++) { + startUploadRequest(); + } } }); } @@ -85,15 +110,17 @@ public void cancel() { return; } state = 2; - if (requestToken != 0) { - ConnectionsManager.getInstance().cancelRequest(requestToken, true); + for (Integer num : requestTokens.values()) { + ConnectionsManager.getInstance().cancelRequest(num, true); } delegate.didFailedUploadingFile(this); cleanup(); } private void cleanup() { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); + if (preferences == null) { + preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); + } preferences.edit().remove(fileKey + "_time"). remove(fileKey + "_size"). remove(fileKey + "_uploaded"). @@ -107,7 +134,7 @@ private void cleanup() { stream = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -120,18 +147,17 @@ public void run() { totalFileSize = finalSize; totalPartsCount = (int) (totalFileSize + uploadChunkSize - 1) / uploadChunkSize; if (started) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); - storeFileUploadInfo(preferences); + storeFileUploadInfo(); } } - if (requestToken == 0) { + if (currentUploadRequetsCount < maxRequestsCount) { startUploadRequest(); } } }); } - private void storeFileUploadInfo(SharedPreferences preferences) { + private void storeFileUploadInfo() { SharedPreferences.Editor editor = preferences.edit(); editor.putInt(fileKey + "_time", uploadStartTime); editor.putLong(fileKey + "_size", totalFileSize); @@ -152,9 +178,17 @@ private void startUploadRequest() { TLObject finalRequest; + final int currentRequestBytes; + final byte[] currentRequestIv; try { started = true; if (stream == null) { + if (isEncrypted) { + freeRequestIvs = new ArrayList<>(maxRequestsCount); + for (int a = 0; a < maxRequestsCount; a++) { + freeRequestIvs.add(new byte[32]); + } + } File cacheFile = new File(uploadingFilePath); stream = new FileInputStream(cacheFile); if (estimatedSize != 0) { @@ -168,11 +202,11 @@ private void startUploadRequest() { try { mdEnc = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - uploadChunkSize = (int) Math.max(32, (totalFileSize + 1024 * 3000 - 1) / (1024 * 3000)); + uploadChunkSize = (int) Math.max(128, (totalFileSize + 1024 * 3000 - 1) / (1024 * 3000)); if (1024 % uploadChunkSize != 0) { int chunkSize = 64; while (uploadChunkSize > chunkSize) { @@ -186,7 +220,6 @@ private void startUploadRequest() { readBuffer = new byte[uploadChunkSize]; fileKey = Utilities.MD5(uploadingFilePath + (isEncrypted ? "enc" : "")); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); long fileSize = preferences.getLong(fileKey + "_size", 0); uploadStartTime = (int)(System.currentTimeMillis() / 1000); boolean rewrite = false; @@ -218,25 +251,25 @@ private void startUploadRequest() { } if (date != 0) { if (uploadedSize > 0) { - currentUploaded = uploadedSize; + readBytesCount = uploadedSize; currentPartNum = (int) (uploadedSize / uploadChunkSize); if (!isBigFile) { - for (int b = 0; b < currentUploaded / uploadChunkSize; b++) { - int read = stream.read(readBuffer); + for (int b = 0; b < readBytesCount / uploadChunkSize; b++) { + int bytesRead = stream.read(readBuffer); int toAdd = 0; - if (isEncrypted && read % 16 != 0) { - toAdd += 16 - read % 16; + if (isEncrypted && bytesRead % 16 != 0) { + toAdd += 16 - bytesRead % 16; } - NativeByteBuffer sendBuffer = new NativeByteBuffer(read + toAdd); - if (read != uploadChunkSize || totalPartsCount == currentPartNum + 1) { + NativeByteBuffer sendBuffer = new NativeByteBuffer(bytesRead + toAdd); + if (bytesRead != uploadChunkSize || totalPartsCount == currentPartNum + 1) { isLastPart = true; } - sendBuffer.writeBytes(readBuffer, 0, read); + sendBuffer.writeBytes(readBuffer, 0, bytesRead); if (isEncrypted) { for (int a = 0; a < toAdd; a++) { sendBuffer.writeByte(0); } - Utilities.aesIgeEncryption(sendBuffer.buffer, key, ivChange, true, true, 0, read + toAdd); + Utilities.aesIgeEncryption(sendBuffer.buffer, key, ivChange, true, true, 0, bytesRead + toAdd); } sendBuffer.rewind(); mdEnc.update(sendBuffer.buffer); @@ -250,12 +283,12 @@ private void startUploadRequest() { ivChange = Utilities.hexToBytes(ivcString); if (ivChange == null || ivChange.length != 32) { rewrite = true; - currentUploaded = 0; + readBytesCount = 0; currentPartNum = 0; } } else { rewrite = true; - currentUploaded = 0; + readBytesCount = 0; currentPartNum = 0; } } @@ -281,7 +314,7 @@ private void startUploadRequest() { } currentFileId = Utilities.random.nextLong(); if (estimatedSize == 0) { - storeFileUploadInfo(preferences); + storeFileUploadInfo(); } } @@ -296,47 +329,43 @@ private void startUploadRequest() { fingerprint |= ((digest[a] ^ digest[a + 4]) & 0xFF) << (a * 8); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - } else if (estimatedSize == 0) { - if (saveInfoTimes >= 4) { - saveInfoTimes = 0; - } - if (isBigFile && currentUploaded % (1024 * 1024) == 0 || !isBigFile && saveInfoTimes == 0) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("uploadinfo", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putLong(fileKey + "_uploaded", currentUploaded); - if (isEncrypted) { - editor.putString(fileKey + "_ivc", Utilities.bytesToHex(ivChange)); - } - editor.commit(); - } - saveInfoTimes++; + uploadedBytesCount = readBytesCount; + lastSavedPartNum = currentPartNum; } if (estimatedSize != 0) { long size = stream.getChannel().size(); - if (currentUploaded + uploadChunkSize > size) { + if (readBytesCount + uploadChunkSize > size) { return; } } - int read = stream.read(readBuffer); + currentRequestBytes = stream.read(readBuffer); + if (currentRequestBytes == -1) { + return; + } int toAdd = 0; - if (isEncrypted && read % 16 != 0) { - toAdd += 16 - read % 16; + if (isEncrypted && currentRequestBytes % 16 != 0) { + toAdd += 16 - currentRequestBytes % 16; } - NativeByteBuffer sendBuffer = new NativeByteBuffer(read + toAdd); - if (read != uploadChunkSize || estimatedSize == 0 && totalPartsCount == currentPartNum + 1) { + NativeByteBuffer sendBuffer = new NativeByteBuffer(currentRequestBytes + toAdd); + if (currentRequestBytes != uploadChunkSize || estimatedSize == 0 && totalPartsCount == currentPartNum + 1) { isLastPart = true; } - sendBuffer.writeBytes(readBuffer, 0, read); + sendBuffer.writeBytes(readBuffer, 0, currentRequestBytes); if (isEncrypted) { for (int a = 0; a < toAdd; a++) { sendBuffer.writeByte(0); } - Utilities.aesIgeEncryption(sendBuffer.buffer, key, ivChange, true, true, 0, read + toAdd); + Utilities.aesIgeEncryption(sendBuffer.buffer, key, ivChange, true, true, 0, currentRequestBytes + toAdd); + currentRequestIv = freeRequestIvs.get(0); + System.arraycopy(ivChange, 0, currentRequestIv, 0, 32); + freeRequestIvs.remove(0); + } else { + currentRequestIv = null; } sendBuffer.rewind(); if (!isBigFile) { @@ -360,62 +389,122 @@ private void startUploadRequest() { req.bytes = sendBuffer; finalRequest = req; } - currentUploaded += read; + readBytesCount += currentRequestBytes; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); delegate.didFailedUploadingFile(this); cleanup(); return; } - requestToken = ConnectionsManager.getInstance().sendRequest(finalRequest, new RequestDelegate() { + currentUploadRequetsCount++; + final int requestNumFinal = requestNum++; + final long currentRequestBytesOffset = readBytesCount; + final int currentRequestPartNum = currentPartNum++; + final int requestSize = finalRequest.getObjectSize() + 4; + int requestToken = ConnectionsManager.getInstance().sendRequest(finalRequest, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { - requestToken = 0; - if (error == null) { - if (response instanceof TLRPC.TL_boolTrue) { - currentPartNum++; - delegate.didChangedUploadProgress(FileUploadOperation.this, currentUploaded / (float) totalFileSize); - if (isLastPart) { - state = 3; - if (key == null) { - TLRPC.InputFile result; - if (isBigFile) { - result = new TLRPC.TL_inputFileBig(); - } else { - result = new TLRPC.TL_inputFile(); - result.md5_checksum = String.format(Locale.US, "%32s", new BigInteger(1, mdEnc.digest()).toString(16)).replace(' ', '0'); + int networkType = response != null ? response.networkType : ConnectionsManager.getCurrentNetworkType(); + if (currentType == ConnectionsManager.FileTypeAudio) { + StatsController.getInstance().incrementSentBytesCount(networkType, StatsController.TYPE_AUDIOS, requestSize); + } else if (currentType == ConnectionsManager.FileTypeVideo) { + StatsController.getInstance().incrementSentBytesCount(networkType, StatsController.TYPE_VIDEOS, requestSize); + } else if (currentType == ConnectionsManager.FileTypePhoto) { + StatsController.getInstance().incrementSentBytesCount(networkType, StatsController.TYPE_PHOTOS, requestSize); + } else if (currentType == ConnectionsManager.FileTypeFile) { + StatsController.getInstance().incrementSentBytesCount(networkType, StatsController.TYPE_FILES, requestSize); + } + if (currentRequestIv != null) { + freeRequestIvs.add(currentRequestIv); + } + requestTokens.remove(requestNumFinal); + if (response instanceof TLRPC.TL_boolTrue) { + uploadedBytesCount += currentRequestBytes; + delegate.didChangedUploadProgress(FileUploadOperation.this, uploadedBytesCount / (float) totalFileSize); + currentUploadRequetsCount--; + if (isLastPart && currentUploadRequetsCount == 0 && state == 1) { + state = 3; + if (key == null) { + TLRPC.InputFile result; + if (isBigFile) { + result = new TLRPC.TL_inputFileBig(); + } else { + result = new TLRPC.TL_inputFile(); + result.md5_checksum = String.format(Locale.US, "%32s", new BigInteger(1, mdEnc.digest()).toString(16)).replace(' ', '0'); + } + result.parts = currentPartNum; + result.id = currentFileId; + result.name = uploadingFilePath.substring(uploadingFilePath.lastIndexOf("/") + 1); + delegate.didFinishUploadingFile(FileUploadOperation.this, result, null, null, null); + cleanup(); + } else { + TLRPC.InputEncryptedFile result; + if (isBigFile) { + result = new TLRPC.TL_inputEncryptedFileBigUploaded(); + } else { + result = new TLRPC.TL_inputEncryptedFileUploaded(); + result.md5_checksum = String.format(Locale.US, "%32s", new BigInteger(1, mdEnc.digest()).toString(16)).replace(' ', '0'); + } + result.parts = currentPartNum; + result.id = currentFileId; + result.key_fingerprint = fingerprint; + delegate.didFinishUploadingFile(FileUploadOperation.this, null, result, key, iv); + cleanup(); + } + } else if (currentUploadRequetsCount < maxRequestsCount) { + if (estimatedSize == 0) { + if (saveInfoTimes >= 4) { + saveInfoTimes = 0; + } + if (currentRequestPartNum == lastSavedPartNum) { + lastSavedPartNum++; + long offsetToSave = currentRequestBytesOffset; + byte[] ivToSave = currentRequestIv; + UploadCachedResult result; + while ((result = cachedResults.get(lastSavedPartNum)) != null) { + offsetToSave = result.bytesOffset; + ivToSave = result.iv; + cachedResults.remove(lastSavedPartNum); + lastSavedPartNum++; + } + if (isBigFile && offsetToSave % (1024 * 1024) == 0 || !isBigFile && saveInfoTimes == 0) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putLong(fileKey + "_uploaded", offsetToSave); + if (isEncrypted) { + editor.putString(fileKey + "_ivc", Utilities.bytesToHex(ivToSave)); + } + editor.commit(); } - result.parts = currentPartNum; - result.id = currentFileId; - result.name = uploadingFilePath.substring(uploadingFilePath.lastIndexOf("/") + 1); - delegate.didFinishUploadingFile(FileUploadOperation.this, result, null, null, null); - cleanup(); } else { - TLRPC.InputEncryptedFile result; - if (isBigFile) { - result = new TLRPC.TL_inputEncryptedFileBigUploaded(); - } else { - result = new TLRPC.TL_inputEncryptedFileUploaded(); - result.md5_checksum = String.format(Locale.US, "%32s", new BigInteger(1, mdEnc.digest()).toString(16)).replace(' ', '0'); + UploadCachedResult result = new UploadCachedResult(); + result.bytesOffset = currentRequestBytesOffset; + if (currentRequestIv != null) { + result.iv = new byte[32]; + System.arraycopy(currentRequestIv, 0, result.iv, 0, 32); } - result.parts = currentPartNum; - result.id = currentFileId; - result.key_fingerprint = fingerprint; - delegate.didFinishUploadingFile(FileUploadOperation.this, null, result, key, iv); - cleanup(); + cachedResults.put(currentRequestPartNum, result); } - } else { - startUploadRequest(); + saveInfoTimes++; } - } else { - delegate.didFailedUploadingFile(FileUploadOperation.this); - cleanup(); + + + startUploadRequest(); + } + if (currentType == ConnectionsManager.FileTypeAudio) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_AUDIOS, 1); + } else if (currentType == ConnectionsManager.FileTypeVideo) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_VIDEOS, 1); + } else if (currentType == ConnectionsManager.FileTypePhoto) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_PHOTOS, 1); + } else if (currentType == ConnectionsManager.FileTypeFile) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_FILES, 1); } } else { delegate.didFailedUploadingFile(FileUploadOperation.this); cleanup(); } } - }, 0, ConnectionsManager.ConnectionTypeUpload); + }, 0, currentUploadRequetsCount % 2 == 0 ? ConnectionsManager.ConnectionTypeUpload : ConnectionsManager.ConnectionTypeUpload2); + requestTokens.put(requestNumFinal, requestToken); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/GcmInstanceIDListenerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/GcmInstanceIDListenerService.java index 71f9cff7664..11903a18e9a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/GcmInstanceIDListenerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/GcmInstanceIDListenerService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java index e02a2d1e500..8553a4ac430 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/GcmPushListenerService.java @@ -3,11 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Bundle; import com.google.android.gms.gcm.GcmListenerService; @@ -21,7 +24,7 @@ public class GcmPushListenerService extends GcmListenerService { @Override public void onMessageReceived(String from, final Bundle bundle) { - FileLog.d("tmessages", "GCM received bundle: " + bundle + " from: " + from); + FileLog.d("GCM received bundle: " + bundle + " from: " + from); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -41,9 +44,20 @@ public void run() { String ip = parts[0]; int port = Integer.parseInt(parts[1]); ConnectionsManager.getInstance().applyDatacenterAddress(dc, ip, port); + } else { + if (ApplicationLoader.mainInterfacePaused) { + int value = bundle.getInt("badge", -1); + if (value == -1) { + ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); + if (netInfo == null || !netInfo.isConnected()) { + NotificationsController.getInstance().showSingleBackgroundNotification(); + } + } + } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } ConnectionsManager.onInternalPushReceived(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/GcmRegistrationIntentService.java b/TMessagesProj/src/main/java/org/telegram/messenger/GcmRegistrationIntentService.java index f99e711f5a1..b217161a8bd 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/GcmRegistrationIntentService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/GcmRegistrationIntentService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -25,7 +25,7 @@ protected void onHandleIntent(Intent intent) { try { InstanceID instanceID = InstanceID.getInstance(this); final String token = instanceID.getToken(getString(R.string.gcm_defaultSenderId), GoogleCloudMessaging.INSTANCE_ID_SCOPE, null); - FileLog.d("tmessages", "GCM Registration Token: " + token); + FileLog.d("GCM Registration Token: " + token); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -34,21 +34,23 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); - final int failCount = intent.getIntExtra("failCount", 0); - if (failCount < 60) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - try { - Intent intent = new Intent(ApplicationLoader.applicationContext, GcmRegistrationIntentService.class); - intent.putExtra("failCount", failCount + 1); - startService(intent); - } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + if (intent != null) { + final int failCount = intent.getIntExtra("failCount", 0); + if (failCount < 60) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + Intent intent = new Intent(ApplicationLoader.applicationContext, GcmRegistrationIntentService.class); + intent.putExtra("failCount", failCount + 1); + startService(intent); + } catch (Exception e) { + FileLog.e(e); + } } - } - }, failCount < 20 ? 10000 : 60000 * 30); + }, failCount < 20 ? 10000 : 60000 * 30); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java index 5129e4711b3..c107af8ca2e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageLoader.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -37,7 +37,6 @@ import java.io.FileOutputStream; import java.io.InputStream; import java.io.RandomAccessFile; -import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.SocketException; import java.net.SocketTimeoutException; @@ -173,7 +172,7 @@ protected Boolean doInBackground(Void... voids) { } else if (e instanceof FileNotFoundException) { canRetry = false; } - FileLog.e("tmessages", e); + FileLog.e(e); } if (canRetry) { @@ -185,7 +184,7 @@ protected Boolean doInBackground(Void... voids) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (httpConnection != null) { try { @@ -200,7 +199,7 @@ protected Boolean doInBackground(Void... voids) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -230,12 +229,12 @@ protected Boolean doInBackground(Void... voids) { break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); break; } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -245,7 +244,7 @@ protected Boolean doInBackground(Void... voids) { fileOutputStream = null; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -253,7 +252,7 @@ protected Boolean doInBackground(Void... voids) { httpConnectionStream.close(); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -338,7 +337,7 @@ protected Boolean doInBackground(Void... voids) { } else if (e instanceof FileNotFoundException) { canRetry = false; } - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -351,7 +350,7 @@ protected Boolean doInBackground(Void... voids) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (imageSize == 0 && httpConnection != null) { try { @@ -366,7 +365,7 @@ protected Boolean doInBackground(Void... voids) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -396,12 +395,12 @@ protected Boolean doInBackground(Void... voids) { break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); break; } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -412,7 +411,7 @@ protected Boolean doInBackground(Void... voids) { fileOutputStream = null; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -420,7 +419,7 @@ protected Boolean doInBackground(Void... voids) { httpConnectionStream.close(); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (done) { @@ -564,7 +563,7 @@ public void run() { try { stream.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } final BitmapDrawable bitmapDrawable = new BitmapDrawable(originalBitmap); AndroidUtilities.runOnUIThread(new Runnable() { @@ -591,7 +590,7 @@ public void run() { } }); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); removeTask(); } } @@ -653,13 +652,13 @@ public void run() { } randomAccessFile.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (randomAccessFile != null) { try { randomAccessFile.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -744,16 +743,18 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { try { + String mediaThumbPath = null; if (cacheImage.httpUrl != null) { if (cacheImage.httpUrl.startsWith("thumb://")) { int idx = cacheImage.httpUrl.indexOf(":", 8); if (idx >= 0) { mediaId = Long.parseLong(cacheImage.httpUrl.substring(8, idx)); mediaIsVideo = false; + mediaThumbPath = cacheImage.httpUrl.substring(idx + 1); } canDeleteFile = false; } else if (cacheImage.httpUrl.startsWith("vthumb://")) { @@ -800,7 +801,7 @@ public void run() { if (w_filter != 0 && h_filter != 0) { opts.inJustDecodeBounds = true; - if (mediaId != null) { + if (mediaId != null && mediaThumbPath == null) { if (mediaIsVideo) { MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); } else { @@ -821,6 +822,19 @@ public void run() { opts.inJustDecodeBounds = false; opts.inSampleSize = (int) scaleFactor; } + } else if (mediaThumbPath != null) { + opts.inJustDecodeBounds = true; + FileInputStream is = new FileInputStream(cacheFileFinal); + image = BitmapFactory.decodeStream(is, null, opts); + is.close(); + float photoW = opts.outWidth; + float photoH = opts.outHeight; + float scaleFactor = Math.max(photoW / 512, photoH / 384); + if (scaleFactor < 1) { + scaleFactor = 1; + } + opts.inJustDecodeBounds = false; + opts.inSampleSize = (int) scaleFactor; } synchronized (sync) { if (isCancelled) { @@ -838,7 +852,7 @@ public void run() { } opts.inDither = false; - if (mediaId != null) { + if (mediaId != null && mediaThumbPath == null) { if (mediaIsVideo) { image = MediaStore.Video.Thumbnails.getThumbnail(ApplicationLoader.applicationContext.getContentResolver(), mediaId, MediaStore.Video.Thumbnails.MINI_KIND, opts); } else { @@ -953,53 +967,6 @@ public void cancel() { } } - public class VMRuntimeHack { - private Object runtime = null; - private Method trackAllocation = null; - private Method trackFree = null; - - public boolean trackAlloc(long size) { - if (runtime == null) { - return false; - } - try { - Object res = trackAllocation.invoke(runtime, size); - return (res instanceof Boolean) ? (Boolean) res : true; - } catch (Exception e) { - return false; - } - } - - public boolean trackFree(long size) { - if (runtime == null) { - return false; - } - try { - Object res = trackFree.invoke(runtime, size); - return (res instanceof Boolean) ? (Boolean) res : true; - } catch (Exception e) { - return false; - } - } - - @SuppressWarnings("unchecked") - public VMRuntimeHack() { - try { - Class cl = Class.forName("dalvik.system.VMRuntime"); - Method getRt = cl.getMethod("getRuntime", new Class[0]); - Object[] objects = new Object[0]; - runtime = getRt.invoke(null, objects); - trackAllocation = cl.getMethod("trackExternalAllocation", new Class[]{long.class}); - trackFree = cl.getMethod("trackExternalFree", new Class[]{long.class}); - } catch (Exception e) { - FileLog.e("tmessages", e); - runtime = null; - trackAllocation = null; - trackFree = null; - } - } - } - private class CacheImage { protected String key; protected String url; @@ -1053,6 +1020,8 @@ public void removeImageReceiver(ImageReceiver imageReceiver) { FileLoader.getInstance().cancelLoadFile((TLRPC.FileLocation) location, ext); } else if (location instanceof TLRPC.Document) { FileLoader.getInstance().cancelLoadFile((TLRPC.Document) location); + } else if (location instanceof TLRPC.TL_webDocument) { + FileLoader.getInstance().cancelLoadFile((TLRPC.TL_webDocument) location); } } if (cacheTask != null) { @@ -1261,7 +1230,7 @@ public void run() { BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context arg0, Intent intent) { - FileLog.e("tmessages", "file system changed"); + FileLog.e("file system changed"); Runnable r = new Runnable() { public void run() { checkMediaPaths(); @@ -1294,13 +1263,13 @@ public void run() { try { cachePath.mkdirs(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { new File(cachePath, ".nomedia").createNewFile(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } mediaDirs.put(FileLoader.MEDIA_DIR_CACHE, cachePath); FileLoader.getInstance().setMediaDirs(mediaDirs); @@ -1330,17 +1299,17 @@ public HashMap createMediaPaths() { try { cachePath.mkdirs(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { new File(cachePath, ".nomedia").createNewFile(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } mediaDirs.put(FileLoader.MEDIA_DIR_CACHE, cachePath); - FileLog.e("tmessages", "cache path = " + cachePath); + FileLog.e("cache path = " + cachePath); try { if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { @@ -1353,10 +1322,10 @@ public HashMap createMediaPaths() { imagePath.mkdir(); if (imagePath.isDirectory() && canMoveFiles(cachePath, imagePath, FileLoader.MEDIA_DIR_IMAGE)) { mediaDirs.put(FileLoader.MEDIA_DIR_IMAGE, imagePath); - FileLog.e("tmessages", "image path = " + imagePath); + FileLog.e("image path = " + imagePath); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -1364,10 +1333,10 @@ public HashMap createMediaPaths() { videoPath.mkdir(); if (videoPath.isDirectory() && canMoveFiles(cachePath, videoPath, FileLoader.MEDIA_DIR_VIDEO)) { mediaDirs.put(FileLoader.MEDIA_DIR_VIDEO, videoPath); - FileLog.e("tmessages", "video path = " + videoPath); + FileLog.e("video path = " + videoPath); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -1376,10 +1345,10 @@ public HashMap createMediaPaths() { if (audioPath.isDirectory() && canMoveFiles(cachePath, audioPath, FileLoader.MEDIA_DIR_AUDIO)) { new File(audioPath, ".nomedia").createNewFile(); mediaDirs.put(FileLoader.MEDIA_DIR_AUDIO, audioPath); - FileLog.e("tmessages", "audio path = " + audioPath); + FileLog.e("audio path = " + audioPath); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -1388,18 +1357,18 @@ public HashMap createMediaPaths() { if (documentPath.isDirectory() && canMoveFiles(cachePath, documentPath, FileLoader.MEDIA_DIR_DOCUMENT)) { new File(documentPath, ".nomedia").createNewFile(); mediaDirs.put(FileLoader.MEDIA_DIR_DOCUMENT, documentPath); - FileLog.e("tmessages", "documents path = " + documentPath); + FileLog.e("documents path = " + documentPath); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else { - FileLog.e("tmessages", "this Android can't rename files"); + FileLog.e("this Android can't rename files"); } MediaController.getInstance().checkSaveToGalleryFiles(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return mediaDirs; @@ -1436,14 +1405,14 @@ private boolean canMoveFiles(File from, File to, int type) { return true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (file != null) { file.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return false; @@ -1459,10 +1428,23 @@ public Float getFileProgress(String location) { private void performReplace(String oldKey, String newKey) { BitmapDrawable b = memCache.get(oldKey); if (b != null) { - ignoreRemoval = oldKey; - memCache.remove(oldKey); - memCache.put(newKey, b); - ignoreRemoval = null; + BitmapDrawable oldBitmap = memCache.get(newKey); + boolean dontChange = false; + if (oldBitmap != null && oldBitmap.getBitmap() != null && b.getBitmap() != null) { + Bitmap oldBitmapObject = oldBitmap.getBitmap(); + Bitmap newBitmapObject = b.getBitmap(); + if (oldBitmapObject.getWidth() > newBitmapObject.getWidth() || oldBitmapObject.getHeight() > newBitmapObject.getHeight()) { + dontChange = true; + } + } + if (!dontChange) { + ignoreRemoval = oldKey; + memCache.remove(oldKey); + memCache.put(newKey, b); + ignoreRemoval = null; + } else { + memCache.remove(oldKey); + } } Integer val = bitmapUseCounts.get(oldKey); if (val != null) { @@ -1573,6 +1555,9 @@ public BitmapDrawable getImageFromMemory(TLObject fileLocation, String httpUrl, } else { key = location.dc_id + "_" + location.id + "_" + location.version; } + } else if (fileLocation instanceof TLRPC.TL_webDocument) { + TLRPC.TL_webDocument location = (TLRPC.TL_webDocument) fileLocation; + key = Utilities.MD5(location.url); } } if (filter != null) { @@ -1730,7 +1715,9 @@ public void run() { if (thumb != 2) { CacheImage img = new CacheImage(); - if (httpLocation != null && !httpLocation.startsWith("vthumb") && !httpLocation.startsWith("thumb") && (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) || imageLocation instanceof TLRPC.Document && MessageObject.isGifDocument((TLRPC.Document) imageLocation)) { + if (httpLocation != null && !httpLocation.startsWith("vthumb") && !httpLocation.startsWith("thumb") && (httpLocation.endsWith("mp4") || httpLocation.endsWith("gif")) || + imageLocation instanceof TLRPC.TL_webDocument && ((TLRPC.TL_webDocument) imageLocation).mime_type.equals("image/gif") || + imageLocation instanceof TLRPC.Document && MessageObject.isGifDocument((TLRPC.Document) imageLocation)) { img.animatedFile = true; } @@ -1738,6 +1725,12 @@ public void run() { if (cacheOnly || size == 0 || httpLocation != null) { cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), url); } else if (imageLocation instanceof TLRPC.Document) { + if (MessageObject.isVideoDocument((TLRPC.Document) imageLocation)) { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_VIDEO), url); + } else { + cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), url); + } + } else if (imageLocation instanceof TLRPC.TL_webDocument) { cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_DOCUMENT), url); } else { cacheFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_IMAGE), url); @@ -1769,6 +1762,8 @@ public void run() { FileLoader.getInstance().loadFile(location, ext, size, size == 0 || location.key != null || cacheOnly); } else if (imageLocation instanceof TLRPC.Document) { FileLoader.getInstance().loadFile((TLRPC.Document) imageLocation, true, cacheOnly); + } else if (imageLocation instanceof TLRPC.TL_webDocument) { + FileLoader.getInstance().loadFile((TLRPC.TL_webDocument) imageLocation, true, cacheOnly); } } else { String file = Utilities.MD5(httpLocation); @@ -1838,6 +1833,11 @@ public void loadImageForImageReceiver(ImageReceiver imageReceiver) { if (imageReceiver.getExt() != null || location.key != null || location.volume_id == Integer.MIN_VALUE && location.local_id < 0) { saveImageToCache = true; } + } else if (imageLocation instanceof TLRPC.TL_webDocument) { + TLRPC.TL_webDocument document = (TLRPC.TL_webDocument) imageLocation; + String defaultExt = FileLoader.getExtensionByMime(document.mime_type); + key = Utilities.MD5(document.url); + url = key + "." + getHttpUrlExtension(document.url, defaultExt); } else if (imageLocation instanceof TLRPC.Document) { TLRPC.Document document = (TLRPC.Document) imageLocation; if (document.id == 0 || document.dc_id == 0) { @@ -2072,7 +2072,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH try { path = AndroidUtilities.getPath(uri); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2087,7 +2087,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH inputStream.close(); inputStream = ApplicationLoader.applicationContext.getContentResolver().openInputStream(uri); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); return null; } } @@ -2128,7 +2128,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH break; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2147,7 +2147,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); ImageLoader.getInstance().clearMemory(); try { if (b == null) { @@ -2164,7 +2164,7 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } } } catch (Throwable e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } } else if (uri != null) { @@ -2181,12 +2181,12 @@ public static Bitmap loadBitmap(String path, Uri uri, float maxWidth, float maxH } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { inputStream.close(); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2207,7 +2207,7 @@ public static void fillPhotoSizeWithBytes(TLRPC.PhotoSize photoSize) { f.readFully(photoSize.bytes, 0, photoSize.bytes.length); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2295,13 +2295,13 @@ public static TLRPC.PhotoSize scaleAndSaveImage(Bitmap bitmap, float maxWidth, f try { return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); ImageLoader.getInstance().clearMemory(); System.gc(); try { return scaleAndSaveImageInternal(bitmap, w, h, photoW, photoH, scaleFactor, quality, cache, scaleAnyway); } catch (Throwable e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); return null; } } @@ -2357,7 +2357,7 @@ public static void saveMessageThumbs(TLRPC.Message message) { writeFile.write(photoSize.bytes); writeFile.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } TLRPC.TL_photoSize newPhotoSize = new TLRPC.TL_photoSize(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java index 2dff1c7aa2b..93abf9aa790 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ImageReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -144,6 +144,7 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa || (fileLocation != null && !(fileLocation instanceof TLRPC.TL_fileLocation) && !(fileLocation instanceof TLRPC.TL_fileEncryptedLocation) && !(fileLocation instanceof TLRPC.TL_document) + && !(fileLocation instanceof TLRPC.TL_webDocument) && !(fileLocation instanceof TLRPC.TL_documentEncrypted))) { recycleBitmap(null, false); recycleBitmap(null, true); @@ -185,6 +186,9 @@ public void setImage(TLObject fileLocation, String httpUrl, String filter, Drawa if (fileLocation instanceof TLRPC.FileLocation) { TLRPC.FileLocation location = (TLRPC.FileLocation) fileLocation; key = location.volume_id + "_" + location.local_id; + } else if (fileLocation instanceof TLRPC.TL_webDocument) { + TLRPC.TL_webDocument location = (TLRPC.TL_webDocument) fileLocation; + key = Utilities.MD5(location.url); } else { TLRPC.Document location = (TLRPC.Document) fileLocation; if (location.dc_id != 0) { @@ -286,6 +290,10 @@ public void setInvalidateAll(boolean value) { invalidateAll = value; } + public Drawable getStaticThumb() { + return staticThumb; + } + public int getAnimatedOrientation() { if (currentImage instanceof AnimatedFileDrawable) { return ((AnimatedFileDrawable) currentImage).getOrientation(); @@ -391,7 +399,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha if (hasFilter && !isPressed) { if (shader != null) { roundPaint.setColorFilter(null); - } else { + } else if (staticThumb != drawable) { bitmapDrawable.setColorFilter(null); } } else if (!hasFilter && isPressed) { @@ -478,7 +486,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha currentThumbKey = null; } setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly); - FileLog.e("tmessages", e); + FileLog.e(e); } canvas.restore(); } else { @@ -501,6 +509,9 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha bitmapH /= scaleW; drawRegion.set(imageX, imageY - (bitmapH - imageH) / 2, imageX + imageW, imageY + (bitmapH + imageH) / 2); } + if (bitmapDrawable instanceof AnimatedFileDrawable) { + ((AnimatedFileDrawable) bitmapDrawable).setActualDrawRect(imageX, imageY, imageW, imageH); + } if (orientation % 360 == 90 || orientation % 360 == 270) { int width = (drawRegion.right - drawRegion.left) / 2; int height = (drawRegion.bottom - drawRegion.top) / 2; @@ -523,7 +534,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha currentThumbKey = null; } setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly); - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -538,6 +549,9 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha } } drawRegion.set(imageX, imageY, imageX + imageW, imageY + imageH); + if (bitmapDrawable instanceof AnimatedFileDrawable) { + ((AnimatedFileDrawable) bitmapDrawable).setActualDrawRect(imageX, imageY, imageW, imageH); + } if (orientation % 360 == 90 || orientation % 360 == 270) { int width = (drawRegion.right - drawRegion.left) / 2; int height = (drawRegion.bottom - drawRegion.top) / 2; @@ -560,7 +574,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha currentThumbKey = null; } setImage(currentImageLocation, currentHttpUrl, currentFilter, currentThumb, currentThumbLocation, currentThumbFilter, currentSize, currentExt, currentCacheOnly); - FileLog.e("tmessages", e); + FileLog.e(e); } } canvas.restore(); @@ -575,7 +589,7 @@ private void drawDrawable(Canvas canvas, Drawable drawable, int alpha, BitmapSha drawable.setAlpha(alpha); drawable.draw(canvas); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -657,7 +671,7 @@ public boolean draw(Canvas canvas) { checkAlphaAnimation(animationNotReady); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -743,6 +757,10 @@ public void setParentView(View view) { } } + public void setImageY(int y) { + imageY = y; + } + public void setImageCoords(int x, int y, int width, int height) { imageX = x; imageY = y; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 05ed68d5b75..8cae5aa245b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -25,10 +25,12 @@ import java.io.File; import java.io.FileInputStream; +import java.text.NumberFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; +import java.util.Currency; import java.util.Date; import java.util.HashMap; import java.util.Locale; @@ -52,6 +54,7 @@ public class LocaleController { public FastDateFormat formatterYear; public FastDateFormat formatterMonthYear; public FastDateFormat formatterYearMax; + public FastDateFormat formatterStats; public FastDateFormat chatDate; public FastDateFormat chatFullDate; @@ -66,6 +69,7 @@ public class LocaleController { private String languageOverride; private boolean changingConfiguration = false; + private HashMap currencyValues; private HashMap translitChars; private class TimeZoneChangedReceiver extends BroadcastReceiver { @@ -208,14 +212,6 @@ public LocaleController() { sortedLanguages.add(localeInfo); languagesDict.put(localeInfo.shortName, localeInfo); - localeInfo = new LocaleInfo(); - localeInfo.name = "Português (Portugal)"; - localeInfo.nameEnglish = "Portuguese (Portugal)"; - localeInfo.shortName = "pt_PT"; - localeInfo.pathToFile = null; - sortedLanguages.add(localeInfo); - languagesDict.put(localeInfo.shortName, localeInfo); - localeInfo = new LocaleInfo(); localeInfo.name = "한국어"; localeInfo.nameEnglish = "Korean"; @@ -271,14 +267,14 @@ public int compare(LocaleController.LocaleInfo o, LocaleController.LocaleInfo o2 } applyLanguage(currentInfo, override); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { IntentFilter timezoneFilter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED); ApplicationLoader.applicationContext.registerReceiver(new TimeZoneChangedReceiver(), timezoneFilter); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -413,7 +409,7 @@ public int compare(LocaleController.LocaleInfo o, LocaleController.LocaleInfo o2 return true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -509,14 +505,14 @@ private HashMap getLocaleFileStrings(File file) { } return stringMap; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (stream != null) { stream.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return new HashMap<>(); @@ -590,16 +586,12 @@ public void applyLanguage(LocaleInfo localeInfo, boolean override, boolean fromF changingConfiguration = false; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); changingConfiguration = false; } recreateFormatters(); } - private void loadCurrentLocale() { - localeValues.clear(); - } - public static String getCurrentLanguageName() { return getString("LanguageName", R.string.LanguageName); } @@ -610,7 +602,7 @@ private String getStringInternal(String key, int res) { try { value = ApplicationLoader.applicationContext.getString(res); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (value == null) { @@ -646,11 +638,155 @@ public static String formatString(String key, int res, Object... args) { return String.format(value, args); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return "LOC_ERR: " + key; } } + public static String formatTTLString(int ttl) { + if (ttl < 60) { + return LocaleController.formatPluralString("Seconds", ttl); + } else if (ttl < 60 * 60) { + return LocaleController.formatPluralString("Minutes", ttl / 60); + } else if (ttl < 60 * 60 * 24) { + return LocaleController.formatPluralString("Hours", ttl / 60 / 60); + } else if (ttl < 60 * 60 * 24 * 7) { + return LocaleController.formatPluralString("Days", ttl / 60 / 60 / 24); + } else { + int days = ttl / 60 / 60 / 24; + if (ttl % 7 == 0) { + return LocaleController.formatPluralString("Weeks", days / 7); + } else { + return String.format("%s %s", LocaleController.formatPluralString("Weeks", days / 7), LocaleController.formatPluralString("Days", days % 7)); + } + } + } + + public String formatCurrencyString(long amount, String type) { + type = type.toUpperCase(); + String customFormat; + double doubleAmount; + boolean discount = amount < 0; + amount = Math.abs(amount); + switch (type) { + case "CLF": + customFormat = " %.4f"; + doubleAmount = amount / 10000.0; + break; + + case "BHD": + case "IQD": + case "JOD": + case "KWD": + case "LYD": + case "OMR": + case "TND": + customFormat = " %.3f"; + doubleAmount = amount / 1000.0; + break; + + case "BIF": + case "BYR": + case "CLP": + case "CVE": + case "DJF": + case "GNF": + case "ISK": + case "JPY": + case "KMF": + case "KRW": + case "MGA": + case "PYG": + case "RWF": + case "UGX": + case "UYI": + case "VND": + case "VUV": + case "XAF": + case "XOF": + case "XPF": + customFormat = " %.0f"; + doubleAmount = amount; + break; + + case "MRO": + customFormat = " %.1f"; + doubleAmount = amount / 10.0; + break; + + default: + customFormat = " %.2f"; + doubleAmount = amount / 100.0; + break; + } + Currency сurrency = Currency.getInstance(type); + if (сurrency != null) { + NumberFormat format = NumberFormat.getCurrencyInstance(currentLocale != null ? currentLocale : systemDefaultLocale); + format.setCurrency(сurrency); + return (discount ? "-" : "") + format.format(doubleAmount); + } + return (discount ? "-" : "") + String.format(type + customFormat, doubleAmount); + } + + public String formatCurrencyDecimalString(long amount, String type) { + type = type.toUpperCase(); + String customFormat; + double doubleAmount; + amount = Math.abs(amount); + switch (type) { + case "CLF": + customFormat = " %.4f"; + doubleAmount = amount / 10000.0; + break; + + case "BHD": + case "IQD": + case "JOD": + case "KWD": + case "LYD": + case "OMR": + case "TND": + customFormat = " %.3f"; + doubleAmount = amount / 1000.0; + break; + + case "BIF": + case "BYR": + case "CLP": + case "CVE": + case "DJF": + case "GNF": + case "ISK": + case "JPY": + case "KMF": + case "KRW": + case "MGA": + case "PYG": + case "RWF": + case "UGX": + case "UYI": + case "VND": + case "VUV": + case "XAF": + case "XOF": + case "XPF": + customFormat = " %.0f"; + doubleAmount = amount; + break; + + case "MRO": + customFormat = " %.1f"; + doubleAmount = amount / 10.0; + break; + + default: + customFormat = " %.2f"; + doubleAmount = amount / 100.0; + break; + } + return String.format(type + customFormat, doubleAmount); + } + public static String formatStringSimple(String string, Object... args) { try { if (getInstance().currentLocale != null) { @@ -659,11 +795,26 @@ public static String formatStringSimple(String string, Object... args) { return String.format(string, args); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return "LOC_ERR: " + string; } } + public static String formatCallDuration(int duration) { + if (duration > 3600) { + String result = LocaleController.formatPluralString("Hours", duration / 3600); + int minutes = duration % 3600 / 60; + if (minutes > 0) { + result += ", " + LocaleController.formatPluralString("Minutes", minutes); + } + return result; + } else if (duration > 60) { + return LocaleController.formatPluralString("Minutes", duration / 60); + } else { + return LocaleController.formatPluralString("Seconds", duration); + } + } + public void onDeviceConfigurationChange(Configuration newConfig) { if (changingConfiguration) { return; @@ -694,80 +845,107 @@ public void onDeviceConfigurationChange(Configuration newConfig) { public static String formatDateChat(long date) { try { Calendar rightNow = Calendar.getInstance(); - int year = rightNow.get(Calendar.YEAR); + date *= 1000; - rightNow.setTimeInMillis(date * 1000); - int dateYear = rightNow.get(Calendar.YEAR); + rightNow.setTimeInMillis(date); - if (year == dateYear) { - return getInstance().chatDate.format(date * 1000); + if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + return getInstance().chatDate.format(date); } - return getInstance().chatFullDate.format(date * 1000); + return getInstance().chatFullDate.format(date); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return "LOC_ERR: formatDateChat"; } public static String formatDate(long date) { try { + date *= 1000; Calendar rightNow = Calendar.getInstance(); int day = rightNow.get(Calendar.DAY_OF_YEAR); int year = rightNow.get(Calendar.YEAR); - rightNow.setTimeInMillis(date * 1000); + rightNow.setTimeInMillis(date); int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); int dateYear = rightNow.get(Calendar.YEAR); if (dateDay == day && year == dateYear) { - return getInstance().formatterDay.format(new Date(date * 1000)); + return getInstance().formatterDay.format(new Date(date)); } else if (dateDay + 1 == day && year == dateYear) { return getString("Yesterday", R.string.Yesterday); - } else if (year == dateYear) { - return getInstance().formatterMonth.format(new Date(date * 1000)); + } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + return getInstance().formatterMonth.format(new Date(date)); } else { - return getInstance().formatterYear.format(new Date(date * 1000)); + return getInstance().formatterYear.format(new Date(date)); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return "LOC_ERR: formatDate"; } public static String formatDateAudio(long date) { try { + date *= 1000; + Calendar rightNow = Calendar.getInstance(); + int day = rightNow.get(Calendar.DAY_OF_YEAR); + int year = rightNow.get(Calendar.YEAR); + rightNow.setTimeInMillis(date); + int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); + int dateYear = rightNow.get(Calendar.YEAR); + + if (dateDay == day && year == dateYear) { + return String.format("%s %s", LocaleController.getString("TodayAt", R.string.TodayAt), getInstance().formatterDay.format(new Date(date))); + } else if (dateDay + 1 == day && year == dateYear) { + return String.format("%s %s", LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date))); + } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterMonth.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); + } else { + return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterYear.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); + } + } catch (Exception e) { + FileLog.e(e); + } + return "LOC_ERR"; + } + + public static String formatDateCallLog(long date) { + try { + date *= 1000; Calendar rightNow = Calendar.getInstance(); int day = rightNow.get(Calendar.DAY_OF_YEAR); int year = rightNow.get(Calendar.YEAR); - rightNow.setTimeInMillis(date * 1000); + rightNow.setTimeInMillis(date); int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); int dateYear = rightNow.get(Calendar.YEAR); if (dateDay == day && year == dateYear) { - return String.format("%s %s", LocaleController.getString("TodayAt", R.string.TodayAt), getInstance().formatterDay.format(new Date(date * 1000))); + return getInstance().formatterDay.format(new Date(date)); } else if (dateDay + 1 == day && year == dateYear) { - return String.format("%s %s", LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date * 1000))); - } else if (year == dateYear) { - return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterMonth.format(new Date(date * 1000)), getInstance().formatterDay.format(new Date(date * 1000))); + return String.format("%s %s", LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date))); + } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().chatDate.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); } else { - return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterYear.format(new Date(date * 1000)), getInstance().formatterDay.format(new Date(date * 1000))); + return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().chatFullDate.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return "LOC_ERR"; } public static String formatDateOnline(long date) { try { + date *= 1000; Calendar rightNow = Calendar.getInstance(); int day = rightNow.get(Calendar.DAY_OF_YEAR); int year = rightNow.get(Calendar.YEAR); - rightNow.setTimeInMillis(date * 1000); + rightNow.setTimeInMillis(date); int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); int dateYear = rightNow.get(Calendar.YEAR); if (dateDay == day && year == dateYear) { - return String.format("%s %s %s", LocaleController.getString("LastSeen", R.string.LastSeen), LocaleController.getString("TodayAt", R.string.TodayAt), getInstance().formatterDay.format(new Date(date * 1000))); + return String.format("%s %s %s", LocaleController.getString("LastSeen", R.string.LastSeen), LocaleController.getString("TodayAt", R.string.TodayAt), getInstance().formatterDay.format(new Date(date))); /*int diff = (int) (ConnectionsManager.getInstance().getCurrentTime() - date) / 60; if (diff < 1) { return LocaleController.getString("LastSeenNow", R.string.LastSeenNow); @@ -777,16 +955,16 @@ public static String formatDateOnline(long date) { return LocaleController.formatPluralString("LastSeenHours", (int) Math.ceil(diff / 60.0f)); }*/ } else if (dateDay + 1 == day && year == dateYear) { - return String.format("%s %s %s", LocaleController.getString("LastSeen", R.string.LastSeen), LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date * 1000))); - } else if (year == dateYear) { - String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterMonth.format(new Date(date * 1000)), getInstance().formatterDay.format(new Date(date * 1000))); + return String.format("%s %s %s", LocaleController.getString("LastSeen", R.string.LastSeen), LocaleController.getString("YesterdayAt", R.string.YesterdayAt), getInstance().formatterDay.format(new Date(date))); + } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { + String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterMonth.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); return String.format("%s %s", LocaleController.getString("LastSeenDate", R.string.LastSeenDate), format); } else { - String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterYear.format(new Date(date * 1000)), getInstance().formatterDay.format(new Date(date * 1000))); + String format = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().formatterYear.format(new Date(date)), getInstance().formatterDay.format(new Date(date))); return String.format("%s %s", LocaleController.getString("LastSeenDate", R.string.LastSeenDate), format); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return "LOC_ERR"; } @@ -825,31 +1003,37 @@ public void recreateFormatters() { formatterWeek = createFormatter(locale, getStringInternal("formatterWeek", R.string.formatterWeek), "EEE"); formatterMonthYear = createFormatter(locale, getStringInternal("formatterMonthYear", R.string.formatterMonthYear), "MMMM yyyy"); formatterDay = createFormatter(lang.toLowerCase().equals("ar") || lang.toLowerCase().equals("ko") ? locale : Locale.US, is24HourFormat ? getStringInternal("formatterDay24H", R.string.formatterDay24H) : getStringInternal("formatterDay12H", R.string.formatterDay12H), is24HourFormat ? "HH:mm" : "h:mm a"); + formatterStats = createFormatter(locale, is24HourFormat ? getStringInternal("formatterStats24H", R.string.formatterStats24H) : getStringInternal("formatterStats12H", R.string.formatterStats12H), is24HourFormat ? "MMM dd yyyy, HH:mm" : "MMM dd yyyy, h:mm a"); + } + + public static boolean isRTLCharacter(char ch) { + return Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_EMBEDDING || Character.getDirectionality(ch) == Character.DIRECTIONALITY_RIGHT_TO_LEFT_OVERRIDE; } public static String stringForMessageListDate(long date) { try { + date *= 1000; Calendar rightNow = Calendar.getInstance(); int day = rightNow.get(Calendar.DAY_OF_YEAR); int year = rightNow.get(Calendar.YEAR); - rightNow.setTimeInMillis(date * 1000); + rightNow.setTimeInMillis(date); int dateDay = rightNow.get(Calendar.DAY_OF_YEAR); int dateYear = rightNow.get(Calendar.YEAR); - if (year != dateYear) { - return getInstance().formatterYear.format(new Date(date * 1000)); + if (Math.abs(System.currentTimeMillis() - date) >= 31536000000L) { + return getInstance().formatterYear.format(new Date(date)); } else { int dayDiff = dateDay - day; - if(dayDiff == 0 || dayDiff == -1 && (int)(System.currentTimeMillis() / 1000) - date < 60 * 60 * 8) { - return getInstance().formatterDay.format(new Date(date * 1000)); + if(dayDiff == 0 || dayDiff == -1 && System.currentTimeMillis() - date < 60 * 60 * 8 * 1000) { + return getInstance().formatterDay.format(new Date(date)); } else if(dayDiff > -7 && dayDiff <= -1) { - return getInstance().formatterWeek.format(new Date(date * 1000)); + return getInstance().formatterWeek.format(new Date(date)); } else { - return getInstance().formatterMonth.format(new Date(date * 1000)); + return getInstance().formatterMonth.format(new Date(date)); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return "LOC_ERR"; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java index 0b894cf2af8..3a91beb2ea5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LruCache.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 6e732082522..0613818ef7b 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -13,7 +13,6 @@ import android.annotation.TargetApi; import android.app.Activity; import android.app.DownloadManager; -import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -57,9 +56,12 @@ import org.telegram.messenger.video.MP4Builder; import org.telegram.messenger.video.Mp4Movie; import org.telegram.messenger.video.OutputSurface; +import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.EmbedBottomSheet; import org.telegram.ui.PhotoViewer; import java.io.File; @@ -392,7 +394,7 @@ public void run() { } samplesCount = newSamplesCount; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } buffer.position(0); final double amplitude = Math.sqrt(sum / len / 2); @@ -539,7 +541,7 @@ public void run() { internalObserver = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (externalObserver != null) { @@ -547,7 +549,7 @@ public void run() { externalObserver = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -589,14 +591,14 @@ public MediaController() { freePlayerBuffers.add(new AudioBuffer(playerBufferSize)); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { sensorManager = (SensorManager) ApplicationLoader.applicationContext.getSystemService(Context.SENSOR_SERVICE); linearSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION); gravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); if (linearSensor == null || gravitySensor == null) { - FileLog.e("tmessages", "gravity or linear sensor not found"); + FileLog.e("gravity or linear sensor not found"); accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); linearSensor = null; gravitySensor = null; @@ -605,7 +607,7 @@ public MediaController() { PowerManager powerManager = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); proximityWakeLock = powerManager.newWakeLock(0x00000020, "proximity"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fileBuffer = ByteBuffer.allocateDirect(1920); recordQueue = new DispatchQueue("recordQueue"); @@ -679,30 +681,43 @@ public void onReceive(Context context, Intent intent) { try { ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, new GalleryObserverExternal()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, new GalleryObserverInternal()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { PhoneStateListener phoneStateListener = new PhoneStateListener() { @Override - public void onCallStateChanged(int state, String incomingNumber) { - if (state == TelephonyManager.CALL_STATE_RINGING) { - if (isPlayingAudio(getPlayingMessageObject()) && !isAudioPaused()) { - pauseAudio(getPlayingMessageObject()); - } else if (recordStartRunnable != null || recordingAudio != null) { - stopRecording(2); + public void onCallStateChanged(final int state, String incomingNumber) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (state == TelephonyManager.CALL_STATE_RINGING) { + if (isPlayingAudio(getPlayingMessageObject()) && !isAudioPaused()) { + pauseAudio(getPlayingMessageObject()); + } else if (recordStartRunnable != null || recordingAudio != null) { + stopRecording(2); + } + EmbedBottomSheet embedBottomSheet = EmbedBottomSheet.getInstance(); + if (embedBottomSheet != null) { + embedBottomSheet.pause(); + } + callInProgress = true; + } else if (state == TelephonyManager.CALL_STATE_IDLE) { + callInProgress = false; + } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) { + EmbedBottomSheet embedBottomSheet = EmbedBottomSheet.getInstance(); + if (embedBottomSheet != null) { + embedBottomSheet.pause(); + } + callInProgress = true; + } } - callInProgress = true; - } else if (state == TelephonyManager.CALL_STATE_IDLE) { - callInProgress = false; - } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) { - callInProgress = true; - } + }); } }; TelephonyManager mgr = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); @@ -710,7 +725,7 @@ public void onCallStateChanged(int state, String incomingNumber) { mgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -756,7 +771,7 @@ private void setPlayerVolume() { audioTrackPlayer.setStereoVolume(volume, volume); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -767,7 +782,7 @@ private void startProgressTimer(final MessageObject currentPlayingMessageObject) progressTimer.cancel(); progressTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } progressTimer = new Timer(); @@ -804,7 +819,7 @@ public void run() { currentPlayingMessageObject.audioProgressSec = lastProgress / 1000; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioProgressDidChanged, currentPlayingMessageObject.getId(), value); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -822,7 +837,7 @@ private void stopProgressTimer() { progressTimer.cancel(); progressTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1105,14 +1120,14 @@ public void startMediaObserver() { ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, false, externalObserver = new ExternalObserver()); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (externalObserver == null) { ApplicationLoader.applicationContext.getContentResolver().registerContentObserver(MediaStore.Images.Media.INTERNAL_CONTENT_URI, false, internalObserver = new InternalObserver()); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1176,7 +1191,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1381,7 +1396,7 @@ public void didReceivedNotification(int id, Object... args) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (id == NotificationCenter.messagesDeleted) { int channelId = (Integer) args[1]; @@ -1513,7 +1528,7 @@ public void run() { try { count = audioTrackPlayer.write(buffer.bufferBytes, 0, buffer.size); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } buffersWrited++; @@ -1567,8 +1582,10 @@ public void onSensorChanged(SensorEvent event) { if (!sensorsStarted) { return; } + if(VoIPService.getSharedInstance()!=null) + return; if (event.sensor == proximitySensor) { - FileLog.e("tmessages", "proximity changed to " + event.values[0]); + FileLog.e("proximity changed to " + event.values[0]); if (lastProximityValue == -100) { lastProximityValue = event.values[0]; } else if (lastProximityValue != event.values[0]) { @@ -1633,7 +1650,7 @@ public void onSensorChanged(SensorEvent event) { raisedToTop = 0; countLess = 0; timeSinceRaise = System.currentTimeMillis(); - //FileLog.e("tmessages", "motion detected"); + //FileLog.e("motion detected"); } } } else { @@ -1650,13 +1667,13 @@ public void onSensorChanged(SensorEvent event) { } previousAccValue = val; accelerometerVertical = gravityFast[1] > 2.5f && Math.abs(gravityFast[2]) < 4.0f && /*Math.abs(gravityFast[0]) < 9.0f &&*/ Math.abs(gravityFast[0]) > 1.5f; - //FileLog.e("tmessages", accelerometerVertical + " val = " + val + " acc (" + linearAcceleration[0] + ", " + linearAcceleration[1] + ", " + linearAcceleration[2] + ") grav (" + gravityFast[0] + ", " + gravityFast[1] + ", " + gravityFast[2] + ")"); + //FileLog.e(accelerometerVertical + " val = " + val + " acc (" + linearAcceleration[0] + ", " + linearAcceleration[1] + ", " + linearAcceleration[2] + ") grav (" + gravityFast[0] + ", " + gravityFast[1] + ", " + gravityFast[2] + ")"); } if (raisedToBack == minCount && accelerometerVertical && proximityTouched && !NotificationsController.getInstance().audioManager.isWiredHeadsetOn()) { - FileLog.e("tmessages", "sensor values reached"); + FileLog.e("sensor values reached"); if (playingMessageObject == null && recordStartRunnable == null && recordingAudio == null && !PhotoViewer.getInstance().isVisible() && ApplicationLoader.isScreenOn && !inputFieldHasText && allowStartRecord && raiseChat != null && !callInProgress) { if (!raiseToEarRecord) { - FileLog.e("tmessages", "start record"); + FileLog.e("start record"); useFrontSpeaker = true; if (!raiseChat.playFirstUnreadVoiceMessage()) { raiseToEarRecord = true; @@ -1670,7 +1687,7 @@ public void onSensorChanged(SensorEvent event) { } } else if (playingMessageObject != null && playingMessageObject.isVoice()) { if (!useFrontSpeaker) { - FileLog.e("tmessages", "start listen"); + FileLog.e("start listen"); if (proximityHasDifferentValues && proximityWakeLock != null && !proximityWakeLock.isHeld()) { proximityWakeLock.acquire(); } @@ -1685,7 +1702,7 @@ public void onSensorChanged(SensorEvent event) { } else if (proximityTouched) { if (playingMessageObject != null && playingMessageObject.isVoice()) { if (!useFrontSpeaker) { - FileLog.e("tmessages", "start listen by proximity only"); + FileLog.e("start listen by proximity only"); if (proximityHasDifferentValues && proximityWakeLock != null && !proximityWakeLock.isHeld()) { proximityWakeLock.acquire(); } @@ -1696,7 +1713,7 @@ public void onSensorChanged(SensorEvent event) { } } else if (!proximityTouched) { if (raiseToEarRecord) { - FileLog.e("tmessages", "stop record"); + FileLog.e("stop record"); stopRecording(2); raiseToEarRecord = false; ignoreOnPause = false; @@ -1704,7 +1721,7 @@ public void onSensorChanged(SensorEvent event) { proximityWakeLock.release(); } } else if (useFrontSpeaker) { - FileLog.e("tmessages", "stop listen"); + FileLog.e("stop listen"); useFrontSpeaker = false; startAudioAgain(true); ignoreOnPause = false; @@ -1848,18 +1865,18 @@ public void cleanupPlayer(boolean notify, boolean stopService, boolean byVoiceEn try { audioPlayer.reset(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioPlayer.stop(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioPlayer.release(); audioPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (audioTrackPlayer != null) { synchronized (playerObjectSync) { @@ -1867,13 +1884,13 @@ public void cleanupPlayer(boolean notify, boolean stopService, boolean byVoiceEn audioTrackPlayer.pause(); audioTrackPlayer.flush(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioTrackPlayer.release(); audioTrackPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1977,7 +1994,7 @@ public boolean seekToProgress(MessageObject messageObject, float progress) { seekOpusPlayer(progress); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return false; } return true; @@ -2076,18 +2093,18 @@ private void playNextMessage(boolean byStop) { try { audioPlayer.reset(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioPlayer.stop(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioPlayer.release(); audioPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (audioTrackPlayer != null) { synchronized (playerObjectSync) { @@ -2095,13 +2112,13 @@ private void playNextMessage(boolean byStop) { audioTrackPlayer.pause(); audioTrackPlayer.flush(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { audioTrackPlayer.release(); audioTrackPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2126,6 +2143,14 @@ private void playNextMessage(boolean byStop) { public void playPreviousMessage() { ArrayList currentPlayList = shuffleMusic ? shuffledPlaylist : playlist; + if (currentPlayList.isEmpty()) { + return; + } + MessageObject currentSong = currentPlayList.get(currentPlaylistNum); + if (currentSong.audioProgressSec > 10) { + MediaController.getInstance().seekToProgress(currentSong, 0); + return; + } currentPlaylistNum--; if (currentPlaylistNum < 0) { @@ -2293,7 +2318,7 @@ public void onPeriodicNotification(AudioTrack audioTrack) { }); audioTrackPlayer.play(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); if (audioTrackPlayer != null) { audioTrackPlayer.release(); audioTrackPlayer = null; @@ -2329,11 +2354,11 @@ public void onCompletion(MediaPlayer mediaPlayer) { try { audioInfo = AudioInfo.getAudioInfo(cacheFile); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject != null ? playingMessageObject.getId() : 0); if (audioPlayer != null) { audioPlayer.release(); @@ -2368,7 +2393,7 @@ public void onCompletion(MediaPlayer mediaPlayer) { playingMessageObject.audioProgress = 0; playingMessageObject.audioProgressSec = 0; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioProgressDidChanged, playingMessageObject.getId(), 0); - FileLog.e("tmessages", e2); + FileLog.e(e2); } } else if (audioTrackPlayer != null) { if (playingMessageObject.audioProgress == 1) { @@ -2383,7 +2408,7 @@ public void run() { seekOpusFile(playingMessageObject.audioProgress); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } synchronized (playerSync) { freePlayerBuffers.addAll(usedPlayerBuffers); @@ -2415,7 +2440,7 @@ public void stopAudio() { try { audioPlayer.reset(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } audioPlayer.stop(); } else if (audioTrackPlayer != null) { @@ -2423,7 +2448,7 @@ public void stopAudio() { audioTrackPlayer.flush(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (audioPlayer != null) { @@ -2436,7 +2461,7 @@ public void stopAudio() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } stopProgressTimer(); playingMessageObject = null; @@ -2505,7 +2530,7 @@ public boolean pauseAudio(MessageObject messageObject) { isPaused = true; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); isPaused = false; return false; } @@ -2529,7 +2554,7 @@ public boolean resumeAudio(MessageObject messageObject) { isPaused = false; NotificationCenter.getInstance().postNotificationName(NotificationCenter.audioPlayStateChanged, playingMessageObject.getId()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return false; } return true; @@ -2559,7 +2584,7 @@ public void startRecording(final long dialog_id, final MessageObject reply_to_ms v.vibrate(50); //NotificationsController.getInstance().playRecordSound(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } recordQueue.postRunnable(recordStartRunnable = new Runnable() { @@ -2610,7 +2635,7 @@ public void run() { audioRecorder.startRecording(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); recordingAudio = null; stopRecord(); recordingAudioFile.delete(); @@ -2619,7 +2644,7 @@ public void run() { audioRecorder.release(); audioRecorder = null; } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } AndroidUtilities.runOnUIThread(new Runnable() { @@ -2725,7 +2750,7 @@ public void run() { audioRecorder = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } recordingAudio = null; recordingAudioFile = null; @@ -2746,7 +2771,7 @@ public void run() { sendAfterDone = send; audioRecorder.stop(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); if (recordingAudioFile != null) { recordingAudioFile.delete(); } @@ -2758,7 +2783,7 @@ public void run() { Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); v.vibrate(50); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -2790,15 +2815,13 @@ public static void saveFile(String fullPath, Context context, final int type, fi final File sourceFile = file; final boolean[] cancelled = new boolean[1]; if (sourceFile.exists()) { - ProgressDialog progressDialog = null; + AlertDialog progressDialog = null; if (context != null) { try { - progressDialog = new ProgressDialog(context); + progressDialog = new AlertDialog(context, 2); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(true); - progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); - progressDialog.setMax(100); progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { @@ -2807,31 +2830,46 @@ public void onCancel(DialogInterface dialog) { }); progressDialog.show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - final ProgressDialog finalProgress = progressDialog; + final AlertDialog finalProgress = progressDialog; new Thread(new Runnable() { @Override public void run() { try { - File destFile = null; + File destFile; if (type == 0) { destFile = AndroidUtilities.generatePicturePath(); } else if (type == 1) { destFile = AndroidUtilities.generateVideoPath(); - } else if (type == 2) { - File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); - f.mkdir(); - destFile = new File(f, name); - } else if (type == 3) { - File f = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); - f.mkdirs(); - destFile = new File(f, name); + } else { + File dir; + if (type == 2) { + dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + } else { + dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); + } + dir.mkdir(); + destFile = new File(dir, name); + if (destFile.exists()) { + int idx = name.lastIndexOf('.'); + for (int a = 0; a < 10; a++) { + String newName; + if (idx != -1) { + newName = name.substring(0, idx) + "(" + (a + 1) + ")" + name.substring(idx); + } else { + newName = name + "(" + (a + 1) + ")"; + } + destFile = new File(dir, newName); + if (!destFile.exists()) { + break; + } + } + } } - if (!destFile.exists()) { destFile.createNewFile(); } @@ -2858,7 +2896,7 @@ public void run() { try { finalProgress.setProgress(progress); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2866,7 +2904,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); result = false; } finally { try { @@ -2898,7 +2936,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (finalProgress != null) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -2907,7 +2945,7 @@ public void run() { try { finalProgress.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2932,14 +2970,14 @@ public static boolean isWebp(Uri uri) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } return false; @@ -2957,14 +2995,14 @@ public static boolean isGif(Uri uri) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } return false; @@ -2980,7 +3018,7 @@ public static String getFileName(Uri uri) { result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.close(); @@ -3018,21 +3056,21 @@ public static String copyFileToCache(Uri uri, String ext) { } return f.getAbsolutePath(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (inputStream != null) { inputStream.close(); } } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } try { if (output != null) { output.close(); } } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } return null; @@ -3103,7 +3141,7 @@ public void checkSaveToGalleryFiles() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -3135,7 +3173,12 @@ public void run() { final ArrayList videoAlbumsSorted = new ArrayList<>(); HashMap albums = new HashMap<>(); AlbumEntry allPhotosAlbum = null; - String cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/"; + String cameraFolder = null; + try { + cameraFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + "/" + "Camera/"; + } catch (Exception e) { + FileLog.e(e); + } Integer cameraAlbumId = null; Integer cameraAlbumVideoId = null; @@ -3190,13 +3233,13 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { try { cursor.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -3251,13 +3294,13 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { try { cursor.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -3287,10 +3330,20 @@ public void run() { } public void scheduleVideoConvert(MessageObject messageObject) { + scheduleVideoConvert(messageObject, false); + } + + public boolean scheduleVideoConvert(MessageObject messageObject, boolean isEmpty) { + if (isEmpty && !videoConvertQueue.isEmpty()) { + return false; + } else if (isEmpty) { + new File(messageObject.messageOwner.attachPath).delete(); + } videoConvertQueue.add(messageObject); if (videoConvertQueue.size() == 1) { startVideoConvertFromQueue(); } + return true; } public void cancelVideoConvert(MessageObject messageObject) { @@ -3304,13 +3357,14 @@ public void cancelVideoConvert(MessageObject messageObject) { synchronized (videoConvertSync) { cancelCurrentVideoConversion = true; } + } else { + videoConvertQueue.remove(messageObject); } - videoConvertQueue.remove(messageObject); } } } - private void startVideoConvertFromQueue() { + private boolean startVideoConvertFromQueue() { if (!videoConvertQueue.isEmpty()) { synchronized (videoConvertSync) { cancelCurrentVideoConversion = false; @@ -3327,9 +3381,13 @@ private void startVideoConvertFromQueue() { } } } - ApplicationLoader.applicationContext.startService(intent); + if (messageObject.getId() != 0) { + ApplicationLoader.applicationContext.startService(intent); + } VideoConvertRunnable.runConversion(messageObject); + return true; } + return false; } @SuppressLint("NewApi") @@ -3412,6 +3470,13 @@ private void didWriteData(final MessageObject messageObject, final File file, fi AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { + if (error || last) { + synchronized (videoConvertSync) { + cancelCurrentVideoConversion = false; + } + videoConvertQueue.remove(messageObject); + startVideoConvertFromQueue(); + } if (error) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.FilePreparingFailed, messageObject, file.toString()); } else { @@ -3420,13 +3485,6 @@ public void run() { } NotificationCenter.getInstance().postNotificationName(NotificationCenter.FileNewChunkAvailable, messageObject, file.toString(), last ? file.length() : 0); } - if (error || last) { - synchronized (videoConvertSync) { - cancelCurrentVideoConversion = false; - } - videoConvertQueue.remove(messageObject); - startVideoConvertFromQueue(); - } } }); } @@ -3449,7 +3507,6 @@ private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor long startTime = -1; checkConversionCanceled(); - long lastTimestamp = -100; while (!inputDone) { checkConversionCanceled(); @@ -3458,6 +3515,32 @@ private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor int index = extractor.getSampleTrackIndex(); if (index == trackIndex) { info.size = extractor.readSampleData(buffer, 0); + if (Build.VERSION.SDK_INT < 21) { + buffer.position(0); + buffer.limit(info.size); + } + if (!isAudio) { + byte[] array = buffer.array(); + if (array != null) { + int offset = buffer.arrayOffset(); + int len = offset + buffer.limit(); + int writeStart = -1; + for (int a = offset; a <= len - 4; a++) { + if (array[a] == 0 && array[a + 1] == 0 && array[a + 2] == 0 && array[a + 3] == 1 || a == len - 4) { + if (writeStart != -1) { + int l = a - writeStart - (a != len - 4 ? 4 : 0); + array[writeStart] = (byte) (l >> 24); + array[writeStart + 1] = (byte) (l >> 16); + array[writeStart + 2] = (byte) (l >> 8); + array[writeStart + 3] = (byte) l; + writeStart = a; + } else { + writeStart = a; + } + } + } + } + } if (info.size >= 0) { info.presentationTimeUs = extractor.getSampleTime(); } else { @@ -3470,14 +3553,11 @@ private long readAndWriteTrack(final MessageObject messageObject, MediaExtractor startTime = info.presentationTimeUs; } if (end < 0 || info.presentationTimeUs < end) { - if (info.presentationTimeUs > lastTimestamp) { - info.offset = 0; - info.flags = extractor.getSampleFlags(); - if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, isAudio)) { - didWriteData(messageObject, file, false, false); - } + info.offset = 0; + info.flags = extractor.getSampleFlags(); + if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, false)) { + didWriteData(messageObject, file, false, false); } - lastTimestamp = info.presentationTimeUs; } else { eof = true; } @@ -3524,7 +3604,7 @@ public void run() { th.start(); th.join(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }).start(); @@ -3581,14 +3661,15 @@ private boolean convertVideo(final MessageObject messageObject) { } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("videoconvert", Activity.MODE_PRIVATE); - boolean isPreviousOk = preferences.getBoolean("isPreviousOk", true); - preferences.edit().putBoolean("isPreviousOk", false).commit(); - File inputFile = new File(videoPath); - if (!inputFile.canRead() || !isPreviousOk) { - didWriteData(messageObject, cacheFile, true, true); - preferences.edit().putBoolean("isPreviousOk", true).commit(); - return false; + if (messageObject.getId() != 0) { + boolean isPreviousOk = preferences.getBoolean("isPreviousOk", true); + preferences.edit().putBoolean("isPreviousOk", false).commit(); + if (!inputFile.canRead() || !isPreviousOk) { + didWriteData(messageObject, cacheFile, true, true); + preferences.edit().putBoolean("isPreviousOk", true).commit(); + return false; + } } videoConvertFirstWrite = true; @@ -3657,11 +3738,11 @@ private boolean convertVideo(final MessageObject messageObject) { } else if (codecName.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { processorType = PROCESSOR_TYPE_TI; } - FileLog.e("tmessages", "codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL); + FileLog.e("codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL); } else { colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; } - FileLog.e("tmessages", "colorFormat = " + colorFormat); + FileLog.e("colorFormat = " + colorFormat); int resultHeightAligned = resultHeight; int padding = 0; @@ -3806,7 +3887,7 @@ private boolean convertVideo(final MessageObject messageObject) { } if (info.size > 1) { if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { - if (mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, false)) { + if (mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, true)) { didWriteData(messageObject, cacheFile, false, false); } } else if (videoTrackIndex == -5) { @@ -3853,7 +3934,7 @@ private boolean convertVideo(final MessageObject messageObject) { } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat newFormat = decoder.getOutputFormat(); - FileLog.e("tmessages", "newFormat = " + newFormat); + FileLog.e("newFormat = " + newFormat); } else if (decoderStatus < 0) { throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus); } else { @@ -3872,7 +3953,7 @@ private boolean convertVideo(final MessageObject messageObject) { if (startTime > 0 && videoTime == -1) { if (info.presentationTimeUs < startTime) { doRender = false; - FileLog.e("tmessages", "drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs); + FileLog.e("drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs); } else { videoTime = info.presentationTimeUs; } @@ -3884,7 +3965,7 @@ private boolean convertVideo(final MessageObject messageObject) { outputSurface.awaitNewImage(); } catch (Exception e) { errorWait = true; - FileLog.e("tmessages", e); + FileLog.e(e); } if (!errorWait) { if (Build.VERSION.SDK_INT >= 18) { @@ -3901,14 +3982,14 @@ private boolean convertVideo(final MessageObject messageObject) { Utilities.convertVideoFrame(rgbBuf, yuvBuf, colorFormat, resultWidth, resultHeight, padding, swapUV); encoder.queueInputBuffer(inputBufIndex, 0, bufferSize, info.presentationTimeUs, 0); } else { - FileLog.e("tmessages", "input buffer not available"); + FileLog.e("input buffer not available"); } } } } if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { decoderOutputAvailable = false; - FileLog.e("tmessages", "decoder stream end"); + FileLog.e("decoder stream end"); if (Build.VERSION.SDK_INT >= 18) { encoder.signalEndOfInputStream(); } else { @@ -3926,7 +4007,7 @@ private boolean convertVideo(final MessageObject messageObject) { videoStartTime = videoTime; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); error = true; } @@ -3960,7 +4041,7 @@ private boolean convertVideo(final MessageObject messageObject) { } } catch (Exception e) { error = true; - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (extractor != null) { extractor.release(); @@ -3969,10 +4050,10 @@ private boolean convertVideo(final MessageObject messageObject) { try { mediaMuxer.finishMovie(false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - FileLog.e("tmessages", "time = " + (System.currentTimeMillis() - time)); + FileLog.e("time = " + (System.currentTimeMillis() - time)); } } else { preferences.edit().putBoolean("isPreviousOk", true).commit(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java index 594a63209d1..f6ce592460c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageKeyData.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index b9e954d36b6..9e514bdf574 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -3,15 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; -import android.graphics.Paint; import android.graphics.Typeface; import android.text.Layout; import android.text.Spannable; +import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; @@ -26,6 +26,7 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; +import org.telegram.ui.Components.URLSpanMono; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.URLSpanNoUnderlineBold; import org.telegram.ui.Components.URLSpanReplacement; @@ -57,6 +58,7 @@ public class MessageObject { public boolean deleted; public float audioProgress; public int audioProgressSec; + public boolean isDateObject; public ArrayList photoThumbs; public ArrayList photoThumbs2; public VideoEditedInfo videoEditedInfo; @@ -65,18 +67,17 @@ public class MessageObject { public boolean attachPathExists; public boolean mediaExists; public boolean resendAsIs; + public String customReplyName; + public boolean useCustomPhoto; + public StringBuilder botButtonsLayout; public boolean forceUpdate; - private static TextPaint textPaint; - private static TextPaint botButtonPaint; - private static TextPaint gameTextPaint; - private static TextPaint textPaintOneEmoji; - private static TextPaint textPaintTwoEmoji; - private static TextPaint textPaintThreeEmoji; public int lastLineWidth; public int textWidth; public int textHeight; + public boolean hasRtl; + public float textXOffset; private boolean layoutCreated; private int generatedWithMinSize; @@ -85,10 +86,15 @@ public class MessageObject { public static class TextLayoutBlock { public StaticLayout textLayout; - public float textXOffset; public float textYOffset; public int charactersOffset; + public int charactersEnd; public int height; + public byte directionFlags; + + public boolean isRtl() { + return (directionFlags & 1) != 0 && (directionFlags & 2) == 0; + } } private static final int LINES_PER_BLOCK = 10; @@ -100,32 +106,7 @@ public MessageObject(TLRPC.Message message, AbstractMap use } public MessageObject(TLRPC.Message message, AbstractMap users, AbstractMap chats, boolean generateLayout) { - if (textPaint == null) { - textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(Theme.MSG_TEXT_COLOR); - textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - } - if (gameTextPaint == null) { - gameTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - gameTextPaint.setColor(Theme.MSG_TEXT_COLOR); - gameTextPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - } - if (textPaintOneEmoji == null) { - textPaintOneEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintOneEmoji.setTextSize(AndroidUtilities.dp(28)); - } - if (textPaintTwoEmoji == null) { - textPaintTwoEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintTwoEmoji.setTextSize(AndroidUtilities.dp(24)); - } - if (textPaintThreeEmoji == null) { - textPaintThreeEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintThreeEmoji.setTextSize(AndroidUtilities.dp(20)); - } - - - textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize)); - gameTextPaint.setTextSize(AndroidUtilities.dp(14)); + Theme.createChatResources(null, true); messageOwner = message; @@ -269,9 +250,9 @@ public MessageObject(TLRPC.Message message, AbstractMap use } else if (message.action instanceof TLRPC.TL_messageActionTTLChange) { if (message.action.ttl != 0) { if (isOut()) { - messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, AndroidUtilities.formatTTLString(message.action.ttl)); + messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(message.action.ttl)); } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(message.action.ttl)); + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(message.action.ttl)); } } else { if (isOut()) { @@ -314,9 +295,9 @@ public MessageObject(TLRPC.Message message, AbstractMap use TLRPC.TL_decryptedMessageActionSetMessageTTL action = (TLRPC.TL_decryptedMessageActionSetMessageTTL) message.action.encryptedAction; if (action.ttl_seconds != 0) { if (isOut()) { - messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, AndroidUtilities.formatTTLString(action.ttl_seconds)); + messageText = LocaleController.formatString("MessageLifetimeChangedOutgoing", R.string.MessageLifetimeChangedOutgoing, LocaleController.formatTTLString(action.ttl_seconds)); } else { - messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), AndroidUtilities.formatTTLString(action.ttl_seconds)); + messageText = LocaleController.formatString("MessageLifetimeChanged", R.string.MessageLifetimeChanged, UserObject.getFirstName(fromUser), LocaleController.formatTTLString(action.ttl_seconds)); } } else { if (isOut()) { @@ -339,11 +320,57 @@ public MessageObject(TLRPC.Message message, AbstractMap use } else if (message.action instanceof TLRPC.TL_messageActionChannelMigrateFrom) { messageText = LocaleController.getString("ActionMigrateFromGroup", R.string.ActionMigrateFromGroup); } else if (message.action instanceof TLRPC.TL_messageActionPinMessage) { - generatePinMessageText(fromUser, fromUser == null ? chats.get(message.to_id.channel_id) : null); + generatePinMessageText(fromUser, fromUser == null && chats != null ? chats.get(message.to_id.channel_id) : null); } else if (message.action instanceof TLRPC.TL_messageActionHistoryClear) { messageText = LocaleController.getString("HistoryCleared", R.string.HistoryCleared); } else if (message.action instanceof TLRPC.TL_messageActionGameScore) { generateGameMessageText(fromUser); + } else if (message.action instanceof TLRPC.TL_messageActionPhoneCall) { + TLRPC.TL_messageActionPhoneCall call = (TLRPC.TL_messageActionPhoneCall) messageOwner.action; + boolean isMissed = call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed; + if (messageOwner.from_id == UserConfig.getClientUserId()) { + if (isMissed) { + messageText = LocaleController.getString("CallMessageOutgoingMissed", R.string.CallMessageOutgoingMissed); + }else { + messageText = LocaleController.getString("CallMessageOutgoing", R.string.CallMessageOutgoing); + } + } else { + if (isMissed) { + messageText = LocaleController.getString("CallMessageIncomingMissed", R.string.CallMessageIncomingMissed); + } else if(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { + messageText = LocaleController.getString("CallMessageIncomingDeclined", R.string.CallMessageIncomingDeclined); + } else { + messageText = LocaleController.getString("CallMessageIncoming", R.string.CallMessageIncoming); + } + } + if (call.duration > 0) { + String duration = LocaleController.formatCallDuration(call.duration); + messageText = LocaleController.formatString("CallMessageWithDuration", R.string.CallMessageWithDuration, messageText, duration); + String _messageText = messageText.toString(); + int start = _messageText.indexOf(duration); + if (start != -1) { + SpannableString sp = new SpannableString(messageText); + int end = start + duration.length(); + if (start > 0 && _messageText.charAt(start - 1) == '(') { + start--; + } + if (end < _messageText.length() && _messageText.charAt(end) == ')') { + end++; + } + sp.setSpan(new TypefaceSpan(Typeface.DEFAULT), start, end, 0); + messageText = sp; + } + } + } else if (message.action instanceof TLRPC.TL_messageActionPaymentSent) { + TLRPC.User user = null; + int uid = (int) getDialogId(); + if (users != null) { + fromUser = users.get(uid); + } + if (fromUser == null) { + fromUser = MessagesController.getInstance().getUser(uid); + } + generatePaymentSentMessageText(user); } } } else if (!isMediaEmpty()) { @@ -359,6 +386,8 @@ public MessageObject(TLRPC.Message message, AbstractMap use messageText = LocaleController.getString("AttachContact", R.string.AttachContact); } else if (message.media instanceof TLRPC.TL_messageMediaGame) { messageText = message.message; + } else if (message.media instanceof TLRPC.TL_messageMediaInvoice) { + messageText = message.media.description; } else if (message.media instanceof TLRPC.TL_messageMediaUnsupported) { messageText = LocaleController.getString("UnsupportedMedia", R.string.UnsupportedMedia); } else if (message.media instanceof TLRPC.TL_messageMediaDocument) { @@ -411,9 +440,9 @@ public MessageObject(TLRPC.Message message, AbstractMap use if (generateLayout) { TextPaint paint; if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - paint = gameTextPaint; + paint = Theme.chat_msgGameTextPaint; } else { - paint = textPaint; + paint = Theme.chat_msgTextPaint; } int[] emojiOnly = MessagesController.getInstance().allowBigEmoji ? new int[1] : null; messageText = Emoji.replaceEmoji(messageText, paint.getFontMetricsInt(), AndroidUtilities.dp(20), false, emojiOnly); @@ -422,16 +451,16 @@ public MessageObject(TLRPC.Message message, AbstractMap use int size; switch (emojiOnly[0]) { case 1: - emojiPaint = textPaintOneEmoji; + emojiPaint = Theme.chat_msgTextPaintOneEmoji; size = AndroidUtilities.dp(32); break; case 2: - emojiPaint = textPaintTwoEmoji; + emojiPaint = Theme.chat_msgTextPaintTwoEmoji; size = AndroidUtilities.dp(28); break; case 3: default: - emojiPaint = textPaintThreeEmoji; + emojiPaint = Theme.chat_msgTextPaintThreeEmoji; size = AndroidUtilities.dp(24); break; } @@ -460,42 +489,14 @@ public void applyNewText() { messageText = messageOwner.message; TextPaint paint; if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - paint = gameTextPaint; + paint = Theme.chat_msgGameTextPaint; } else { - paint = textPaint; + paint = Theme.chat_msgTextPaint; } messageText = Emoji.replaceEmoji(messageText, paint.getFontMetricsInt(), AndroidUtilities.dp(20), false); generateLayout(fromUser); } - public static TextPaint getTextPaint() { - if (textPaint == null) { - textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(Theme.MSG_TEXT_COLOR); - textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - } - if (gameTextPaint == null) { - gameTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - gameTextPaint.setColor(Theme.MSG_TEXT_COLOR); - gameTextPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - } - if (textPaintOneEmoji == null) { - textPaintOneEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintOneEmoji.setTextSize(AndroidUtilities.dp(28)); - } - if (textPaintTwoEmoji == null) { - textPaintTwoEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintTwoEmoji.setTextSize(AndroidUtilities.dp(24)); - } - if (textPaintThreeEmoji == null) { - textPaintThreeEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaintThreeEmoji.setTextSize(AndroidUtilities.dp(20)); - } - textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize)); - gameTextPaint.setTextSize(AndroidUtilities.dp(14)); - return textPaint; - } - public void generateGameMessageText(TLRPC.User fromUser) { if (fromUser == null) { if (messageOwner.from_id > 0) { @@ -522,6 +523,23 @@ public void generateGameMessageText(TLRPC.User fromUser) { } } + public void generatePaymentSentMessageText(TLRPC.User fromUser) { + if (fromUser == null) { + fromUser = MessagesController.getInstance().getUser((int) getDialogId()); + } + String name; + if (fromUser != null) { + name = UserObject.getFirstName(fromUser); + } else { + name = ""; + } + if (replyMessageObject != null && replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + messageText = LocaleController.formatString("PaymentSuccessfullyPaid", R.string.PaymentSuccessfullyPaid, LocaleController.getInstance().formatCurrencyString(messageOwner.action.total_amount, messageOwner.action.currency), name, replyMessageObject.messageOwner.media.title); + } else { + messageText = LocaleController.formatString("PaymentSuccessfullyPaidNoItem", R.string.PaymentSuccessfullyPaidNoItem, LocaleController.getInstance().formatCurrencyString(messageOwner.action.total_amount, messageOwner.action.currency), name); + } + } + public void generatePinMessageText(TLRPC.User fromUser, TLRPC.Chat chat) { if (fromUser == null && chat == null) { if (messageOwner.from_id > 0) { @@ -554,13 +572,13 @@ public void generatePinMessageText(TLRPC.User fromUser, TLRPC.Chat chat) { messageText = replaceWithLink(LocaleController.getString("ActionPinnedPhoto", R.string.ActionPinnedPhoto), "un1", fromUser != null ? fromUser : chat); } else if (replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { messageText = replaceWithLink(LocaleController.formatString("ActionPinnedGame", R.string.ActionPinnedGame, "\uD83C\uDFAE " + replyMessageObject.messageOwner.media.game.title), "un1", fromUser != null ? fromUser : chat); - messageText = Emoji.replaceEmoji(messageText, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + messageText = Emoji.replaceEmoji(messageText, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } else if (replyMessageObject.messageText != null && replyMessageObject.messageText.length() > 0) { CharSequence mess = replyMessageObject.messageText; if (mess.length() > 20) { mess = mess.subSequence(0, 20) + "..."; } - mess = Emoji.replaceEmoji(mess, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + mess = Emoji.replaceEmoji(mess, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); messageText = replaceWithLink(LocaleController.formatString("ActionPinnedText", R.string.ActionPinnedText, mess), "un1", fromUser != null ? fromUser : chat); } else { messageText = replaceWithLink(LocaleController.getString("ActionPinnedNoText", R.string.ActionPinnedNoText), "un1", fromUser != null ? fromUser : chat); @@ -573,18 +591,26 @@ public void measureInlineBotButtons() { if (!(messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup)) { return; } - if (botButtonPaint == null) { - botButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - botButtonPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + Theme.createChatResources(null, true); + if (botButtonsLayout == null) { + botButtonsLayout = new StringBuilder(); + } else { + botButtonsLayout.setLength(0); } - botButtonPaint.setTextSize(AndroidUtilities.dp(15)); for (int a = 0; a < messageOwner.reply_markup.rows.size(); a++) { TLRPC.TL_keyboardButtonRow row = messageOwner.reply_markup.rows.get(a); int maxButtonSize = 0; int size = row.buttons.size(); for (int b = 0; b < size; b++) { - CharSequence text = Emoji.replaceEmoji(row.buttons.get(b).text, botButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false); - StaticLayout staticLayout = new StaticLayout(text, botButtonPaint, AndroidUtilities.dp(2000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + TLRPC.KeyboardButton button = row.buttons.get(b); + botButtonsLayout.append(a).append(b); + CharSequence text; + if (button instanceof TLRPC.TL_keyboardButtonBuy && (messageOwner.media.flags & 4) != 0) { + text = LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt); + } else { + text = Emoji.replaceEmoji(button.text, Theme.chat_msgBotButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false); + } + StaticLayout staticLayout = new StaticLayout(text, Theme.chat_msgBotButtonPaint, AndroidUtilities.dp(2000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (staticLayout.getLineCount() > 0) { maxButtonSize = Math.max(maxButtonSize, (int) Math.ceil(staticLayout.getLineWidth(0) - staticLayout.getLineLeft(0)) + AndroidUtilities.dp(4)); } @@ -629,6 +655,8 @@ public void setType() { } } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { type = 0; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + type = 0; } } else if (messageOwner instanceof TLRPC.TL_messageService) { if (messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { @@ -647,6 +675,8 @@ public void setType() { } else if (messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear) { contentType = -1; type = -1; + } else if (messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { + type = 16; } else { contentType = 1; type = 10; @@ -675,9 +705,9 @@ public boolean checkLayout() { } TextPaint paint; if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - paint = gameTextPaint; + paint = Theme.chat_msgGameTextPaint; } else { - paint = textPaint; + paint = Theme.chat_msgTextPaint; } messageText = Emoji.replaceEmoji(messageText, paint.getFontMetricsInt(), AndroidUtilities.dp(20), false); generateLayout(fromUser); @@ -686,17 +716,62 @@ public boolean checkLayout() { return false; } + public String getMimeType() { + if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + return messageOwner.media.document.mime_type; + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + TLRPC.TL_webDocument photo = ((TLRPC.TL_messageMediaInvoice) messageOwner.media).photo; + if (photo != null) { + return photo.mime_type; + } + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + return "image/jpeg"; + } + return ""; + } + public static boolean isGifDocument(TLRPC.Document document) { return document != null && document.thumb != null && document.mime_type != null && (document.mime_type.equals("image/gif") || isNewGifDocument(document)); } + public static boolean isVoiceVideoDocument(TLRPC.Document document) { + if (document != null && document.mime_type != null && document.mime_type.equals("video/mp4")) { + int width = 0; + int height = 0; + boolean animated = false; + for (int a = 0; a < document.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeAnimated) { + animated = true; + } else if (attribute instanceof TLRPC.TL_documentAttributeVideo) { + width = attribute.w; + height = attribute.w; + } + } + if (animated && width <= 1280 && height <= 1280) { + return true; + } + } + return false; + } + public static boolean isNewGifDocument(TLRPC.Document document) { if (document != null && document.mime_type != null && document.mime_type.equals("video/mp4")) { + int width = 0; + int height = 0; + boolean animated = false; for (int a = 0; a < document.attributes.size(); a++) { - if (document.attributes.get(a) instanceof TLRPC.TL_documentAttributeAnimated) { - return true; + TLRPC.DocumentAttribute attribute = document.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeAnimated) { + animated = true; + } else if (attribute instanceof TLRPC.TL_documentAttributeVideo) { + width = attribute.w; + height = attribute.w; } } + if (animated && width <= 1280 && height <= 1280) { + return true; + } } return false; } @@ -990,16 +1065,18 @@ public void generateLinkDescription() { linkDescription = Spannable.Factory.getInstance().newSpannable(messageOwner.media.webpage.description); } else if (messageOwner.media instanceof TLRPC.TL_messageMediaGame && messageOwner.media.game.description != null) { linkDescription = Spannable.Factory.getInstance().newSpannable(messageOwner.media.game.description); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaInvoice && messageOwner.media.description != null) { + linkDescription = Spannable.Factory.getInstance().newSpannable(messageOwner.media.description); } if (linkDescription != null) { if (containsUrls(linkDescription)) { try { Linkify.addLinks((Spannable) linkDescription, Linkify.WEB_URLS); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - linkDescription = Emoji.replaceEmoji(linkDescription, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + linkDescription = Emoji.replaceEmoji(linkDescription, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } } @@ -1008,19 +1085,19 @@ public void generateCaption() { return; } if (messageOwner.media != null && messageOwner.media.caption != null && messageOwner.media.caption.length() > 0) { - caption = Emoji.replaceEmoji(messageOwner.media.caption, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + caption = Emoji.replaceEmoji(messageOwner.media.caption, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); if (containsUrls(caption)) { try { Linkify.addLinks((Spannable) caption, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - addUsernamesAndHashtags(caption, true); + addUsernamesAndHashtags(isOutOwner(), caption, true); } } } - private static void addUsernamesAndHashtags(CharSequence charSequence, boolean botCommands) { + private static void addUsernamesAndHashtags(boolean isOut, CharSequence charSequence, boolean botCommands) { try { if (urlPattern == null) { urlPattern = Pattern.compile("(^|\\s)/[a-zA-Z@\\d_]{1,255}|(^|\\s)@[a-zA-Z\\d_]{1,32}|(^|\\s)#[\\w\\.]+"); @@ -1035,7 +1112,7 @@ private static void addUsernamesAndHashtags(CharSequence charSequence, boolean b URLSpanNoUnderline url = null; if (charSequence.charAt(start) == '/') { if (botCommands) { - url = new URLSpanBotCommand(charSequence.subSequence(start, end).toString()); + url = new URLSpanBotCommand(charSequence.subSequence(start, end).toString(), isOut); } } else { url = new URLSpanNoUnderline(charSequence.subSequence(start, end).toString()); @@ -1045,31 +1122,31 @@ private static void addUsernamesAndHashtags(CharSequence charSequence, boolean b } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - public static void addLinks(CharSequence messageText) { - addLinks(messageText, true); + public static void addLinks(boolean isOut, CharSequence messageText) { + addLinks(isOut, messageText, true); } - public static void addLinks(CharSequence messageText, boolean botCommands) { + public static void addLinks(boolean isOut, CharSequence messageText, boolean botCommands) { if (messageText instanceof Spannable && containsUrls(messageText)) { if (messageText.length() < 200) { try { Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS | Linkify.PHONE_NUMBERS); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { try { Linkify.addLinks((Spannable) messageText, Linkify.WEB_URLS); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - addUsernamesAndHashtags(messageText, botCommands); + addUsernamesAndHashtags(isOut, messageText, botCommands); } } @@ -1103,17 +1180,18 @@ public void generateLayout(TLRPC.User fromUser) { messageOwner instanceof TLRPC.TL_messageForwarded_old || messageOwner instanceof TLRPC.TL_messageForwarded_old2 || messageOwner instanceof TLRPC.TL_message_secret || + messageOwner.media instanceof TLRPC.TL_messageMediaInvoice || isOut() && messageOwner.send_state != MESSAGE_SEND_STATE_SENT || messageOwner.id < 0 || messageOwner.media instanceof TLRPC.TL_messageMediaUnsupported); if (useManualParse) { - addLinks(messageText); + addLinks(isOutOwner(), messageText); } else { if (messageText instanceof Spannable && messageText.length() < 200) { try { Linkify.addLinks((Spannable) messageText, Linkify.PHONE_NUMBERS); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1147,15 +1225,15 @@ public void generateLayout(TLRPC.User fromUser) { } else if (entity instanceof TLRPC.TL_messageEntityItalic) { spannable.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityCode || entity instanceof TLRPC.TL_messageEntityPre) { - spannable.setSpan(new TypefaceSpan(Typeface.MONOSPACE, AndroidUtilities.dp(MessagesController.getInstance().fontSize - 1)), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new URLSpanMono(spannable, entity.offset, entity.offset + entity.length, isOutOwner()), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityMentionName) { - spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_messageEntityMentionName) entity).user_id, isOutOwner()), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { - spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id, isOutOwner()), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (!useManualParse) { String url = messageOwner.message.substring(entity.offset, entity.offset + entity.length); if (entity instanceof TLRPC.TL_messageEntityBotCommand) { - spannable.setSpan(new URLSpanBotCommand(url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new URLSpanBotCommand(url, isOutOwner()), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityHashtag || entity instanceof TLRPC.TL_messageEntityMention) { spannable.setSpan(new URLSpanNoUnderline(url), entity.offset, entity.offset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityEmail) { @@ -1174,7 +1252,7 @@ public void generateLayout(TLRPC.User fromUser) { } int maxWidth; - boolean needShare = messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame) && !isOut(); + boolean needShare = messageOwner.from_id > 0 && (messageOwner.to_id.channel_id != 0 || messageOwner.to_id.chat_id != 0 || messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !isOut(); generatedWithMinSize = AndroidUtilities.isTablet() ? AndroidUtilities.getMinTabletSide() : AndroidUtilities.displaySize.x; maxWidth = generatedWithMinSize - AndroidUtilities.dp(needShare ? 122 : 80); if (fromUser != null && fromUser.bot || (isMegagroup() || messageOwner.fwd_from != null && messageOwner.fwd_from.channel_id != 0) && !isOut()) { @@ -1188,14 +1266,15 @@ public void generateLayout(TLRPC.User fromUser) { TextPaint paint; if (messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - paint = gameTextPaint; + paint = Theme.chat_msgGameTextPaint; } else { - paint = textPaint; + paint = Theme.chat_msgTextPaint; } + try { textLayout = new StaticLayout(messageText, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return; } @@ -1222,9 +1301,9 @@ public void generateLayout(TLRPC.User fromUser) { continue; } block.charactersOffset = startCharacter; + block.charactersEnd = endCharacter; try { - CharSequence str = messageText.subSequence(startCharacter, endCharacter); - block.textLayout = new StaticLayout(str, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + block.textLayout = new StaticLayout(messageText, startCharacter, endCharacter, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); block.textYOffset = textLayout.getLineTop(linesOffset); if (a != 0) { block.height = (int) (block.textYOffset - prevOffset); @@ -1232,7 +1311,7 @@ public void generateLayout(TLRPC.User fromUser) { block.height = Math.max(block.height, block.textLayout.getLineBottom(block.textLayout.getLineCount() - 1)); prevOffset = block.textYOffset; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); continue; } if (a == blocksCount - 1) { @@ -1240,48 +1319,53 @@ public void generateLayout(TLRPC.User fromUser) { try { textHeight = Math.max(textHeight, (int) (block.textYOffset + block.textLayout.getHeight())); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } textLayoutBlocks.add(block); - float lastLeft = block.textXOffset = 0; + float lastLeft; try { - lastLeft = block.textXOffset = block.textLayout.getLineLeft(currentBlockLinesCount - 1); + lastLeft = block.textLayout.getLineLeft(currentBlockLinesCount - 1); + if (a == 0) { + textXOffset = lastLeft; + } } catch (Exception e) { - FileLog.e("tmessages", e); + lastLeft = 0; + if (a == 0) { + textXOffset = 0; + } + FileLog.e(e); } - float lastLine = 0; + float lastLine; try { lastLine = block.textLayout.getLineWidth(currentBlockLinesCount - 1); } catch (Exception e) { - FileLog.e("tmessages", e); + lastLine = 0; + FileLog.e(e); } int linesMaxWidth = (int) Math.ceil(lastLine); int lastLineWidthWithLeft; int linesMaxWidthWithLeft; - boolean hasNonRTL = false; if (a == blocksCount - 1) { lastLineWidth = linesMaxWidth; } linesMaxWidthWithLeft = lastLineWidthWithLeft = (int) Math.ceil(lastLine + lastLeft); - if (lastLeft == 0) { - hasNonRTL = true; - } if (currentBlockLinesCount > 1) { + boolean hasNonRTL = false; float textRealMaxWidth = 0, textRealMaxWidthWithLeft = 0, lineWidth, lineLeft; for (int n = 0; n < currentBlockLinesCount; n++) { try { lineWidth = block.textLayout.getLineWidth(n); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); lineWidth = 0; } @@ -1292,17 +1376,26 @@ public void generateLayout(TLRPC.User fromUser) { try { lineLeft = block.textLayout.getLineLeft(n); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); lineLeft = 0; } - if (lineLeft >= 0) { - block.textXOffset = Math.min(block.textXOffset, lineLeft); + if (lineLeft > 0) { + textXOffset = Math.min(textXOffset, lineLeft); + block.directionFlags |= 1; + hasRtl = true; + } else { + block.directionFlags |= 2; } - if (lineLeft == 0) { + try { + if (!hasNonRTL && lineLeft == 0 && block.textLayout.getParagraphDirection(n) == Layout.DIR_LEFT_TO_RIGHT) { + hasNonRTL = true; + } + } catch (Exception ignore) { hasNonRTL = true; } + textRealMaxWidth = Math.max(textRealMaxWidth, lineWidth); textRealMaxWidthWithLeft = Math.max(textRealMaxWidthWithLeft, lineWidth + lineLeft); linesMaxWidth = Math.max(linesMaxWidth, (int) Math.ceil(lineWidth)); @@ -1318,11 +1411,15 @@ public void generateLayout(TLRPC.User fromUser) { } textWidth = Math.max(textWidth, (int) Math.ceil(textRealMaxWidth)); } else { - textWidth = Math.max(textWidth, Math.min(maxWidth, linesMaxWidth)); - } + if (lastLeft > 0) { + textXOffset = Math.min(textXOffset, lastLeft); + hasRtl = blocksCount != 1; + block.directionFlags |= 1; + } else { + block.directionFlags |= 2; + } - if (hasNonRTL) { - block.textXOffset = 0; + textWidth = Math.max(textWidth, Math.min(maxWidth, linesMaxWidth)); } linesOffset += currentBlockLinesCount; @@ -1463,8 +1560,11 @@ public String getSecretTimeString() { } public String getDocumentName() { - if (messageOwner.media != null && messageOwner.media.document != null) { + TLRPC.Document document; + if (messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { return FileLoader.getDocumentFileName(messageOwner.media.document); + } else if (messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) { + return FileLoader.getDocumentFileName(messageOwner.media.webpage.document); } return ""; } @@ -1505,6 +1605,18 @@ public static boolean isVoiceDocument(TLRPC.Document document) { return false; } + public static boolean isVoiceWebDocument(TLRPC.TL_webDocument webDocument) { + return webDocument != null && webDocument.mime_type.equals("audio/ogg"); + } + + public static boolean isImageWebDocument(TLRPC.TL_webDocument webDocument) { + return webDocument != null && webDocument.mime_type.startsWith("image/"); + } + + public static boolean isVideoWebDocument(TLRPC.TL_webDocument webDocument) { + return webDocument != null && webDocument.mime_type.startsWith("video/"); + } + public static boolean isMusicDocument(TLRPC.Document document) { if (document != null) { for (int a = 0; a < document.attributes.size(); a++) { @@ -1521,14 +1633,22 @@ public static boolean isVideoDocument(TLRPC.Document document) { if (document != null) { boolean isAnimated = false; boolean isVideo = false; + int width = 0; + int height = 0; + for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeVideo) { isVideo = true; + width = attribute.w; + height = attribute.h; } else if (attribute instanceof TLRPC.TL_documentAttributeAnimated) { isAnimated = true; } } + if (isAnimated && (width > 1280 || height > 1280)) { + isAnimated = false; + } return isVideo && !isAnimated; } return false; @@ -1556,6 +1676,10 @@ public static boolean isMusicMessage(TLRPC.Message message) { return message.media != null && message.media.document != null && isMusicDocument(message.media.document); } + public static boolean isVideoVoiceMessage(TLRPC.Message message) { + return message.media != null && message.media.document != null && isVoiceVideoDocument(message.media.document); + } + public static boolean isVoiceMessage(TLRPC.Message message) { if (message.media instanceof TLRPC.TL_messageMediaWebPage) { return isVoiceDocument(message.media.webpage.document); @@ -1581,6 +1705,10 @@ public static boolean isGameMessage(TLRPC.Message message) { return message.media instanceof TLRPC.TL_messageMediaGame; } + public static boolean isInvoiceMessage(TLRPC.Message message) { + return message.media instanceof TLRPC.TL_messageMediaInvoice; + } + public static TLRPC.InputStickerSet getInputStickerSet(TLRPC.Message message) { if (message.media != null && message.media.document != null) { for (TLRPC.DocumentAttribute attribute : message.media.document.attributes) { @@ -1735,6 +1863,14 @@ public boolean isGame() { return isGameMessage(messageOwner); } + public boolean isInvoice() { + return isInvoiceMessage(messageOwner); + } + + public boolean isVideoVoice() { + return isVideoVoiceMessage(messageOwner) && BuildVars.DEBUG_PRIVATE_VERSION; + } + public boolean hasPhotoStickers() { return messageOwner.media != null && messageOwner.media.photo != null && messageOwner.media.photo.has_stickers; } @@ -1922,8 +2058,8 @@ public static boolean canDeleteMessage(TLRPC.Message message, TLRPC.Chat chat) { if (message.from_id > 0 && !message.post) { return true; } - } else if (isOut(message) && message.from_id > 0) { - return true; + } else { + return chat.megagroup && isOut(message) && message.from_id > 0; } } return isOut(message) || !ChatObject.isChannel(chat); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index fafdeedd0b0..70dcc893eb2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -3,18 +3,19 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Base64; import android.util.SparseArray; import android.util.SparseIntArray; @@ -26,12 +27,14 @@ import org.telegram.messenger.query.MessagesQuery; import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.query.StickersQuery; +import org.telegram.messenger.voip.VoIPService; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ChatActivity; import org.telegram.ui.Components.AlertsCreator; @@ -71,7 +74,7 @@ public class MessagesController implements NotificationCenter.NotificationCenter public HashMap printingStringsTypes = new HashMap<>(); public HashMap> sendingTypings = new HashMap<>(); public ConcurrentHashMap onlinePrivacy = new ConcurrentHashMap<>(20, 1.0f, 2); - private int lastPrintingStringCount = 0; + private int lastPrintingStringCount; private HashMap loadingPeerSettings = new HashMap<>(); @@ -98,10 +101,10 @@ public class MessagesController implements NotificationCenter.NotificationCenter private ArrayList updatesQueueSeq = new ArrayList<>(); private ArrayList updatesQueuePts = new ArrayList<>(); private ArrayList updatesQueueQts = new ArrayList<>(); - private long updatesStartWaitTimeSeq = 0; - private long updatesStartWaitTimePts = 0; - private long updatesStartWaitTimeQts = 0; - private HashMap fullUsersAbout = new HashMap<>(); + private long updatesStartWaitTimeSeq; + private long updatesStartWaitTimePts; + private long updatesStartWaitTimeQts; + private HashMap fullUsers = new HashMap<>(); private ArrayList loadingFullUsers = new ArrayList<>(); private ArrayList loadedFullUsers = new ArrayList<>(); private ArrayList loadingFullChats = new ArrayList<>(); @@ -114,30 +117,33 @@ public class MessagesController implements NotificationCenter.NotificationCenter private HashMap> reloadingMessages = new HashMap<>(); - private boolean gettingNewDeleteTask = false; - private int currentDeletingTaskTime = 0; - private ArrayList currentDeletingTaskMids = null; - private Runnable currentDeleteTaskRunnable = null; + private boolean gettingNewDeleteTask; + private int currentDeletingTaskTime; + private ArrayList currentDeletingTaskMids; + private Runnable currentDeleteTaskRunnable; - public boolean loadingDialogs = false; - private boolean migratingDialogs = false; - public boolean dialogsEndReached = false; - public boolean gettingDifference = false; - public boolean updatingState = false; - public boolean firstGettingTask = false; - public boolean registeringForPush = false; + public boolean loadingDialogs; + private boolean migratingDialogs; + public boolean dialogsEndReached; + public boolean serverDialogsEndReached; + public boolean gettingDifference; + public boolean updatingState; + public boolean firstGettingTask; + public boolean registeringForPush; public int secretWebpagePreview = 2; - private long lastStatusUpdateTime = 0; - private int statusRequest = 0; - private int statusSettingState = 0; - private boolean offlineSent = false; - private String uploadingAvatar = null; + private long lastStatusUpdateTime; + private int statusRequest; + private int statusSettingState; + private boolean offlineSent; + private String uploadingAvatar; public boolean enableJoined = true; - public boolean allowBigEmoji = false; - public boolean useSystemEmoji = false; + public boolean allowBigEmoji; + public boolean useSystemEmoji; + public boolean callsEnabled; + public String linkPrefix = "t.me"; public int fontSize = AndroidUtilities.dp(16); public int maxGroupCount = 200; public int maxBroadcastCount = 100; @@ -148,6 +154,12 @@ public class MessagesController implements NotificationCenter.NotificationCenter public int ratingDecay; public int maxRecentStickersCount = 30; public int maxRecentGifsCount = 200; + public int callReceiveTimeout = 20000; + public int callRingTimeout = 90000; + public int callConnectTimeout = 30000; + public int callPacketTimeout = 10000; + public int maxPinnedDialogsCount = 5; + private ArrayList disabledFeatures = new ArrayList<>(); private class UserActionUpdatesSeq extends TLRPC.Updates { @@ -186,6 +198,19 @@ public static class PrintingUser { private final Comparator dialogComparator = new Comparator() { @Override public int compare(TLRPC.TL_dialog dialog1, TLRPC.TL_dialog dialog2) { + if (!dialog1.pinned && dialog2.pinned) { + return 1; + } else if (dialog1.pinned && !dialog2.pinned) { + return -1; + } else if (dialog1.pinned && dialog2.pinned) { + if (dialog1.pinnedNum < dialog2.pinnedNum) { + return 1; + } else if (dialog1.pinnedNum > dialog2.pinnedNum) { + return -1; + } else { + return 0; + } + } TLRPC.DraftMessage draftMessage = DraftQuery.getDraft(dialog1.id); int date1 = draftMessage != null && draftMessage.date >= dialog1.last_message_date ? draftMessage.date : dialog1.last_message_date; draftMessage = DraftQuery.getDraft(dialog2.id); @@ -260,6 +285,14 @@ public MessagesController() { fontSize = preferences.getInt("fons_size", AndroidUtilities.isTablet() ? 18 : 16); allowBigEmoji = preferences.getBoolean("allowBigEmoji", false); useSystemEmoji = preferences.getBoolean("useSystemEmoji", false); + callsEnabled = preferences.getBoolean("callsEnabled", false); + linkPrefix = preferences.getString("linkPrefix", "t.me"); + callReceiveTimeout = preferences.getInt("callReceiveTimeout", 20000); + callRingTimeout = preferences.getInt("callRingTimeout", 90000); + callConnectTimeout = preferences.getInt("callConnectTimeout", 30000); + callPacketTimeout = preferences.getInt("callPacketTimeout", 10000); + maxPinnedDialogsCount = preferences.getInt("maxPinnedDialogsCount", 5); + String disabledFeaturesString = preferences.getString("disabledFeatures", null); if (disabledFeaturesString != null && disabledFeaturesString.length() != 0) { try { @@ -275,7 +308,7 @@ public MessagesController() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -293,6 +326,22 @@ public void run() { ratingDecay = config.rating_e_decay; maxRecentGifsCount = config.saved_gifs_limit; maxRecentStickersCount = config.stickers_recent_limit; + boolean callsOld = callsEnabled; + callsEnabled = config.phonecalls_enabled; + linkPrefix = config.me_url_prefix; + if (linkPrefix.endsWith("/")) { + linkPrefix = linkPrefix.substring(0, linkPrefix.length() - 1); + } + if (linkPrefix.startsWith("https://")) { + linkPrefix = linkPrefix.substring(8); + } else if (linkPrefix.startsWith("http://")) { + linkPrefix = linkPrefix.substring(7); + } + callReceiveTimeout = config.call_receive_timeout_ms; + callRingTimeout = config.call_ring_timeout_ms; + callConnectTimeout = config.call_connect_timeout_ms; + callPacketTimeout = config.call_packet_timeout_ms; + maxPinnedDialogsCount = config.pinned_dialogs_count_max; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); @@ -304,6 +353,13 @@ public void run() { editor.putInt("ratingDecay", ratingDecay); editor.putInt("maxRecentGifsCount", maxRecentGifsCount); editor.putInt("maxRecentStickersCount", maxRecentStickersCount); + editor.putInt("callReceiveTimeout", callReceiveTimeout); + editor.putInt("callRingTimeout", callRingTimeout); + editor.putInt("callConnectTimeout", callConnectTimeout); + editor.putInt("callPacketTimeout", callPacketTimeout); + editor.putBoolean("callsEnabled", callsEnabled); + editor.putString("linkPrefix", linkPrefix); + editor.putInt("maxPinnedDialogsCount", maxPinnedDialogsCount); try { SerializedData data = new SerializedData(); data.writeInt32(disabledFeatures.size()); @@ -316,9 +372,13 @@ public void run() { } } catch (Exception e) { editor.remove("disabledFeatures"); - FileLog.e("tmessages", e); + FileLog.e(e); } editor.commit(); + + if (callsEnabled != callsOld) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.mainUserInfoChanged); + } } }); } @@ -535,7 +595,7 @@ public void cleanup() { dialogs_read_inbox_max.clear(); dialogs_read_outbox_max.clear(); exportedChats.clear(); - fullUsersAbout.clear(); + fullUsers.clear(); dialogs.clear(); joiningToChannels.clear(); channelViewsToSend.clear(); @@ -584,6 +644,7 @@ public void run() { gettingNewDeleteTask = false; loadingDialogs = false; dialogsEndReached = false; + serverDialogsEndReached = false; loadingBlockedUsers = false; firstGettingTask = false; updatingState = false; @@ -639,16 +700,16 @@ public TLRPC.EncryptedChat getEncryptedChat(Integer id) { return encryptedChats.get(id); } - public TLRPC.EncryptedChat getEncryptedChatDB(int chat_id) { + public TLRPC.EncryptedChat getEncryptedChatDB(int chat_id, boolean created) { TLRPC.EncryptedChat chat = encryptedChats.get(chat_id); - if (chat == null || chat.auth_key == null) { + if (chat == null || created && (chat instanceof TLRPC.TL_encryptedChatWaiting || chat instanceof TLRPC.TL_encryptedChatRequested)) { Semaphore semaphore = new Semaphore(0); ArrayList result = new ArrayList<>(); MessagesStorage.getInstance().getEncryptedChat(chat_id, semaphore, result); try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (result.size() == 2) { chat = (TLRPC.EncryptedChat) result.get(0); @@ -683,10 +744,10 @@ public boolean putUser(TLRPC.User user, boolean fromCache) { } fromCache = fromCache && user.id / 1000 != 333 && user.id != 777000; TLRPC.User oldUser = users.get(user.id); - if (oldUser != null && oldUser.username != null && oldUser.username.length() > 0) { - usersByUsernames.remove(oldUser.username); + if (oldUser != null && !TextUtils.isEmpty(oldUser.username)) { + usersByUsernames.remove(oldUser.username.toLowerCase()); } - if (user.username != null && user.username.length() > 0) { + if (!TextUtils.isEmpty(user.username)) { usersByUsernames.put(user.username.toLowerCase(), user); } if (user.min) { @@ -853,8 +914,8 @@ public void putEncryptedChats(ArrayList encryptedChats, boo } } - public String getUserAbout(int uid) { - return fullUsersAbout.get(uid); + public TLRPC.TL_userFull getUserFull(int uid) { + return fullUsers.get(uid); } public void cancelLoadFullUser(int uid) { @@ -871,16 +932,27 @@ protected void clearFullUsers() { } private void reloadDialogsReadValue(ArrayList dialogs, long did) { - if (dialogs.isEmpty()) { + if (did == 0 && (dialogs == null || dialogs.isEmpty())) { return; } TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs(); if (dialogs != null) { for (int a = 0; a < dialogs.size(); a++) { - req.peers.add(getInputPeer((int) dialogs.get(a).id)); + TLRPC.InputPeer inputPeer = getInputPeer((int) dialogs.get(a).id); + if (inputPeer instanceof TLRPC.TL_inputPeerChannel && inputPeer.access_hash == 0) { + continue; + } + req.peers.add(inputPeer); } } else { - req.peers.add(getInputPeer((int) did)); + TLRPC.InputPeer inputPeer = getInputPeer((int) did); + if (inputPeer instanceof TLRPC.TL_inputPeerChannel && inputPeer.access_hash == 0) { + return; + } + req.peers.add(inputPeer); + } + if (req.peers.isEmpty()) { + return; } ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -958,6 +1030,7 @@ public void loadFullChat(final int chat_id, final int classGuid, boolean force) } loadingFullChats.add(chat_id); TLObject request; + final long dialog_id = -chat_id; final TLRPC.Chat chat = getChat(chat_id); if (ChatObject.isChannel(chat_id)) { TLRPC.TL_channels_getFullChannel req = new TLRPC.TL_channels_getFullChannel(); @@ -967,6 +1040,9 @@ public void loadFullChat(final int chat_id, final int classGuid, boolean force) TLRPC.TL_messages_getFullChat req = new TLRPC.TL_messages_getFullChat(); req.chat_id = chat_id; request = req; + if (dialogs_read_inbox_max.get(dialog_id) == null || dialogs_read_outbox_max.get(dialog_id) == null) { + reloadDialogsReadValue(null, dialog_id); + } } int reqId = ConnectionsManager.getInstance().sendRequest(request, new RequestDelegate() { @Override @@ -977,10 +1053,9 @@ public void run(TLObject response, final TLRPC.TL_error error) { MessagesStorage.getInstance().updateChatInfo(res.full_chat, false); if (ChatObject.isChannel(chat)) { - long dialog_id = -chat_id; Integer value = dialogs_read_inbox_max.get(dialog_id); if (value == null) { - value = MessagesStorage.getInstance().getDialogReadMax(true, dialog_id); + value = MessagesStorage.getInstance().getDialogReadMax(false, dialog_id); } dialogs_read_inbox_max.put(dialog_id, Math.max(res.full_chat.read_inbox_max_id, value)); @@ -1052,6 +1127,10 @@ public void loadFullUser(final TLRPC.User user, final int classGuid, boolean for loadingFullUsers.add(user.id); TLRPC.TL_users_getFullUser req = new TLRPC.TL_users_getFullUser(); req.id = getInputUser(user); + long dialog_id = user.id; + if (dialogs_read_inbox_max.get(dialog_id) == null || dialogs_read_outbox_max.get(dialog_id) == null) { + reloadDialogsReadValue(null, dialog_id); + } int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, TLRPC.TL_error error) { @@ -1064,11 +1143,7 @@ public void run() { if (userFull.bot_info instanceof TLRPC.TL_botInfo) { BotQuery.putBotInfo(userFull.bot_info); } - if (userFull.about != null && userFull.about.length() > 0) { - fullUsersAbout.put(user.id, userFull.about); - } else { - fullUsersAbout.remove(user.id); - } + fullUsers.put(user.id, userFull); loadingFullUsers.remove((Integer) user.id); loadedFullUsers.add(user.id); String names = user.first_name + user.last_name + user.username; @@ -1082,7 +1157,7 @@ public void run() { if (userFull.bot_info instanceof TLRPC.TL_botInfo) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.botInfoDidLoaded, userFull.bot_info, classGuid); } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.userInfoDidLoaded, user.id); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.userInfoDidLoaded, user.id, userFull); } }); } else { @@ -1150,7 +1225,7 @@ public void run(TLObject response, TLRPC.TL_error error) { Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance().getDialogReadMax(true, dialog_id); + inboxValue = MessagesStorage.getInstance().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } @@ -1217,44 +1292,71 @@ public void hideReportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Ch SharedPreferences.Editor editor = preferences.edit(); editor.putInt("spam3_" + dialogId, 1); editor.commit(); - TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); - if (currentUser != null) { - req.peer = MessagesController.getInputPeer(currentUser.id); - } else if (currentChat != null) { - req.peer = MessagesController.getInputPeer(-currentChat.id); - } - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - + if ((int) dialogId != 0) { + TLRPC.TL_messages_hideReportSpam req = new TLRPC.TL_messages_hideReportSpam(); + if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); + } else if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); } - }); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } } - public void reportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { - if (currentUser == null && currentChat == null) { + public void reportSpam(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat, TLRPC.EncryptedChat currentEncryptedChat) { + if (currentUser == null && currentChat == null && currentEncryptedChat == null) { return; } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); editor.putInt("spam3_" + dialogId, 1); editor.commit(); - TLRPC.TL_messages_reportSpam req = new TLRPC.TL_messages_reportSpam(); - if (currentChat != null) { - req.peer = MessagesController.getInputPeer(-currentChat.id); - } else if (currentUser != null) { - req.peer = MessagesController.getInputPeer(currentUser.id); - } - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { + if ((int) dialogId == 0) { + if (currentEncryptedChat == null || currentEncryptedChat.access_hash == 0) { + return; + } + TLRPC.TL_messages_reportEncryptedSpam req = new TLRPC.TL_messages_reportEncryptedSpam(); + req.peer = new TLRPC.TL_inputEncryptedChat(); + req.peer.chat_id = currentEncryptedChat.id; + req.peer.access_hash = currentEncryptedChat.access_hash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } else { + TLRPC.TL_messages_reportSpam req = new TLRPC.TL_messages_reportSpam(); + if (currentChat != null) { + req.peer = MessagesController.getInputPeer(-currentChat.id); + } else if (currentUser != null) { + req.peer = MessagesController.getInputPeer(currentUser.id); } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } } - public void loadPeerSettings(final long dialogId, TLRPC.User currentUser, TLRPC.Chat currentChat) { - if (loadingPeerSettings.containsKey(dialogId) || currentUser == null && currentChat == null) { + public void loadPeerSettings(TLRPC.User currentUser, TLRPC.Chat currentChat) { + if (currentUser == null && currentChat == null) { + return; + } + final long dialogId; + if (currentUser != null) { + dialogId = currentUser.id; + } else { + dialogId = -currentChat.id; + } + if (loadingPeerSettings.containsKey(dialogId)) { return; } loadingPeerSettings.put(dialogId, true); @@ -1320,7 +1422,7 @@ public void run() { } protected void processNewChannelDifferenceParams(int pts, int pts_count, int channelId) { - FileLog.e("tmessages", "processNewChannelDifferenceParams pts = " + pts + " pts_count = " + pts_count + " channeldId = " + channelId); + FileLog.e("processNewChannelDifferenceParams pts = " + pts + " pts_count = " + pts_count + " channeldId = " + channelId); TLRPC.TL_dialog dialog = dialogs_dict.get((long) -channelId); if (!DialogObject.isChannel(dialog)) { return; @@ -1334,7 +1436,7 @@ protected void processNewChannelDifferenceParams(int pts, int pts_count, int cha channelsPts.put(channelId, channelPts); } if (channelPts + pts_count == pts) { - FileLog.e("tmessages", "APPLY CHANNEL PTS"); + FileLog.e("APPLY CHANNEL PTS"); channelsPts.put(channelId, pts); MessagesStorage.getInstance().saveChannelPts(channelId, pts); } else if (channelPts != pts) { @@ -1344,7 +1446,7 @@ protected void processNewChannelDifferenceParams(int pts, int pts_count, int cha gettingDifferenceChannel = false; } if (gettingDifferenceChannel || updatesStartWaitTime == null || Math.abs(System.currentTimeMillis() - updatesStartWaitTime) <= 1500) { - FileLog.e("tmessages", "ADD CHANNEL UPDATE TO QUEUE pts = " + pts + " pts_count = " + pts_count); + FileLog.e("ADD CHANNEL UPDATE TO QUEUE pts = " + pts + " pts_count = " + pts_count); if (updatesStartWaitTime == null) { updatesStartWaitTimeChannels.put(channelId, System.currentTimeMillis()); } @@ -1365,15 +1467,15 @@ protected void processNewChannelDifferenceParams(int pts, int pts_count, int cha } protected void processNewDifferenceParams(int seq, int pts, int date, int pts_count) { - FileLog.e("tmessages", "processNewDifferenceParams seq = " + seq + " pts = " + pts + " date = " + date + " pts_count = " + pts_count); + FileLog.e("processNewDifferenceParams seq = " + seq + " pts = " + pts + " date = " + date + " pts_count = " + pts_count); if (pts != -1) { if (MessagesStorage.lastPtsValue + pts_count == pts) { - FileLog.e("tmessages", "APPLY PTS"); + FileLog.e("APPLY PTS"); MessagesStorage.lastPtsValue = pts; MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); } else if (MessagesStorage.lastPtsValue != pts) { if (gettingDifference || updatesStartWaitTimePts == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { - FileLog.e("tmessages", "ADD UPDATE TO QUEUE pts = " + pts + " pts_count = " + pts_count); + FileLog.e("ADD UPDATE TO QUEUE pts = " + pts + " pts_count = " + pts_count); if (updatesStartWaitTimePts == 0) { updatesStartWaitTimePts = System.currentTimeMillis(); } @@ -1388,7 +1490,7 @@ protected void processNewDifferenceParams(int seq, int pts, int date, int pts_co } if (seq != -1) { if (MessagesStorage.lastSeqValue + 1 == seq) { - FileLog.e("tmessages", "APPLY SEQ"); + FileLog.e("APPLY SEQ"); MessagesStorage.lastSeqValue = seq; if (date != -1) { MessagesStorage.lastDateValue = date; @@ -1396,7 +1498,7 @@ protected void processNewDifferenceParams(int seq, int pts, int date, int pts_co MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); } else if (MessagesStorage.lastSeqValue != seq) { if (gettingDifference || updatesStartWaitTimeSeq == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimeSeq) <= 1500) { - FileLog.e("tmessages", "ADD UPDATE TO QUEUE seq = " + seq); + FileLog.e("ADD UPDATE TO QUEUE seq = " + seq); if (updatesStartWaitTimeSeq == 0) { updatesStartWaitTimeSeq = System.currentTimeMillis(); } @@ -1449,7 +1551,7 @@ private boolean checkDeletingTask(boolean runnable) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - deleteMessages(currentDeletingTaskMids, null, null, 0); + deleteMessages(currentDeletingTaskMids, null, null, 0, false); Utilities.stageQueue.postRunnable(new Runnable() { @Override @@ -1726,7 +1828,7 @@ public void run() { public void uploadAndApplyUserAvatar(TLRPC.PhotoSize bigPhoto) { if (bigPhoto != null) { uploadingAvatar = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + bigPhoto.location.volume_id + "_" + bigPhoto.location.local_id + ".jpg"; - FileLoader.getInstance().uploadFile(uploadingAvatar, false, true); + FileLoader.getInstance().uploadFile(uploadingAvatar, false, true, ConnectionsManager.FileTypePhoto); } } @@ -1743,35 +1845,62 @@ public void markChannelDialogMessageAsDeleted(ArrayList messages, final } } - public void deleteMessages(ArrayList messages, ArrayList randoms, TLRPC.EncryptedChat encryptedChat, final int channelId) { - if (messages == null || messages.isEmpty()) { + public void deleteMessages(ArrayList messages, ArrayList randoms, TLRPC.EncryptedChat encryptedChat, final int channelId, boolean forAll) { + deleteMessages(messages, randoms, encryptedChat, channelId, forAll, 0, null); + } + + public void deleteMessages(ArrayList messages, ArrayList randoms, TLRPC.EncryptedChat encryptedChat, final int channelId, boolean forAll, long taskId, TLObject taskRequest) { + if ((messages == null || messages.isEmpty()) && taskRequest == null) { return; } - if (channelId == 0) { - for (int a = 0; a < messages.size(); a++) { - Integer id = messages.get(a); - MessageObject obj = dialogMessagesByIds.get(id); - if (obj != null) { - obj.deleted = true; + ArrayList toSend = null; + if (taskId == 0) { + if (channelId == 0) { + for (int a = 0; a < messages.size(); a++) { + Integer id = messages.get(a); + MessageObject obj = dialogMessagesByIds.get(id); + if (obj != null) { + obj.deleted = true; + } } + } else { + markChannelDialogMessageAsDeleted(messages, channelId); } - } else { - markChannelDialogMessageAsDeleted(messages, channelId); - } - ArrayList toSend = new ArrayList<>(); - for (int a = 0; a < messages.size(); a++) { - Integer mid = messages.get(a); - if (mid > 0) { - toSend.add(mid); + toSend = new ArrayList<>(); + for (int a = 0; a < messages.size(); a++) { + Integer mid = messages.get(a); + if (mid > 0) { + toSend.add(mid); + } } + MessagesStorage.getInstance().markMessagesAsDeleted(messages, true, channelId); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(messages, null, true, channelId); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, messages, channelId); } - MessagesStorage.getInstance().markMessagesAsDeleted(messages, true, channelId); - MessagesStorage.getInstance().updateDialogsWithDeletedMessages(messages, true, channelId); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDeleted, messages, channelId); + + final long newTaskId; if (channelId != 0) { - TLRPC.TL_channels_deleteMessages req = new TLRPC.TL_channels_deleteMessages(); - req.id = toSend; - req.channel = getInputChannel(channelId); + TLRPC.TL_channels_deleteMessages req; + if (taskRequest != null) { + req = (TLRPC.TL_channels_deleteMessages) taskRequest; + newTaskId = taskId; + } else { + req = new TLRPC.TL_channels_deleteMessages(); + req.id = toSend; + req.channel = getInputChannel(channelId); + + NativeByteBuffer data = null; + try { + data = new NativeByteBuffer(8 + req.getObjectSize()); + data.writeInt32(7); + data.writeInt32(channelId); + req.serializeToStream(data); + } catch (Exception e) { + FileLog.e(e); + } + newTaskId = MessagesStorage.getInstance().createPendingTask(data); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -1779,14 +1908,36 @@ public void run(TLObject response, TLRPC.TL_error error) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewChannelDifferenceParams(res.pts, res.pts_count, channelId); } + if (newTaskId != 0) { + MessagesStorage.getInstance().removePendingTask(newTaskId); + } } }); } else { if (randoms != null && encryptedChat != null && !randoms.isEmpty()) { SecretChatHelper.getInstance().sendMessagesDeleteMessage(encryptedChat, randoms, null); } - TLRPC.TL_messages_deleteMessages req = new TLRPC.TL_messages_deleteMessages(); - req.id = toSend; + TLRPC.TL_messages_deleteMessages req; + if (taskRequest != null) { + req = (TLRPC.TL_messages_deleteMessages) taskRequest; + newTaskId = taskId; + } else { + req = new TLRPC.TL_messages_deleteMessages(); + req.id = toSend; + req.revoke = forAll; + + NativeByteBuffer data = null; + try { + data = new NativeByteBuffer(8 + req.getObjectSize()); + data.writeInt32(7); + data.writeInt32(channelId); + req.serializeToStream(data); + } catch (Exception e) { + FileLog.e(e); + } + newTaskId = MessagesStorage.getInstance().createPendingTask(data); + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -1794,6 +1945,9 @@ public void run(TLObject response, TLRPC.TL_error error) { TLRPC.TL_messages_affectedMessages res = (TLRPC.TL_messages_affectedMessages) response; processNewDifferenceParams(-1, res.pts, -1, res.pts_count); } + if (newTaskId != 0) { + MessagesStorage.getInstance().removePendingTask(newTaskId); + } } }); } @@ -2059,31 +2213,33 @@ public void updateTimerProc() { checkDeletingTask(false); if (UserConfig.isClientActivated()) { - if (ConnectionsManager.getInstance().getPauseTime() == 0 && ApplicationLoader.isScreenOn && !ApplicationLoader.mainInterfacePaused) { - if (statusSettingState != 1 && (lastStatusUpdateTime == 0 || Math.abs(System.currentTimeMillis() - lastStatusUpdateTime) >= 55000 || offlineSent)) { - statusSettingState = 1; + if (ConnectionsManager.getInstance().getPauseTime() == 0 && ApplicationLoader.isScreenOn && !ApplicationLoader.mainInterfacePausedStageQueue) { + if (ApplicationLoader.mainInterfacePausedStageQueueTime != 0 && Math.abs(ApplicationLoader.mainInterfacePausedStageQueueTime - System.currentTimeMillis()) > 1000) { + if (statusSettingState != 1 && (lastStatusUpdateTime == 0 || Math.abs(System.currentTimeMillis() - lastStatusUpdateTime) >= 55000 || offlineSent)) { + statusSettingState = 1; - if (statusRequest != 0) { - ConnectionsManager.getInstance().cancelRequest(statusRequest, true); - } + if (statusRequest != 0) { + ConnectionsManager.getInstance().cancelRequest(statusRequest, true); + } - TLRPC.TL_account_updateStatus req = new TLRPC.TL_account_updateStatus(); - req.offline = false; - statusRequest = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, TLRPC.TL_error error) { - if (error == null) { - lastStatusUpdateTime = System.currentTimeMillis(); - offlineSent = false; - statusSettingState = 0; - } else { - if (lastStatusUpdateTime != 0) { - lastStatusUpdateTime += 5000; + TLRPC.TL_account_updateStatus req = new TLRPC.TL_account_updateStatus(); + req.offline = false; + statusRequest = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + lastStatusUpdateTime = System.currentTimeMillis(); + offlineSent = false; + statusSettingState = 0; + } else { + if (lastStatusUpdateTime != 0) { + lastStatusUpdateTime += 5000; + } } + statusRequest = 0; } - statusRequest = 0; - } - }); + }); + } } } else if (statusSettingState != 2 && !offlineSent && Math.abs(System.currentTimeMillis() - ConnectionsManager.getInstance().getPauseTime()) >= 2000) { statusSettingState = 2; @@ -2113,7 +2269,7 @@ public void run(TLObject response, TLRPC.TL_error error) { int key = keys.get(a); Long updatesStartWaitTime = updatesStartWaitTimeChannels.get(key); if (updatesStartWaitTime != null && updatesStartWaitTime + 1500 < currentTime) { - FileLog.e("tmessages", "QUEUE CHANNEL " + key + " UPDATES WAIT TIMEOUT - CHECK QUEUE"); + FileLog.e("QUEUE CHANNEL " + key + " UPDATES WAIT TIMEOUT - CHECK QUEUE"); processChannelsUpdatesQueue(key, 0); } } @@ -2121,7 +2277,7 @@ public void run(TLObject response, TLRPC.TL_error error) { for (int a = 0; a < 3; a++) { if (getUpdatesStartTime(a) != 0 && getUpdatesStartTime(a) + 1500 < currentTime) { - FileLog.e("tmessages", a + " QUEUE UPDATES WAIT TIMEOUT - CHECK QUEUE"); + FileLog.e(a + " QUEUE UPDATES WAIT TIMEOUT - CHECK QUEUE"); processUpdatesQueue(a, 0); } } @@ -2213,7 +2369,13 @@ public void run() { ArrayList arr = printingUsers.get(key); for (int a = 0; a < arr.size(); a++) { PrintingUser user = arr.get(a); - if (user.lastTime + 5900 < currentTime) { + int timeToRemove; + if (user.action instanceof TLRPC.TL_sendMessageGamePlayAction) { + timeToRemove = 30000; + } else { + timeToRemove = 5900; + } + if (user.lastTime + timeToRemove < currentTime) { updated = true; arr.remove(user); a--; @@ -2304,12 +2466,12 @@ private void updatePrintingStrings() { } newPrintingStringsTypes.put(key, 2); } else if (pu.action instanceof TLRPC.TL_sendMessageGamePlayAction) { - /*if (lower_id < 0) { //TODO + if (lower_id < 0) { newPrintingStrings.put(key, LocaleController.formatString("IsSendingGame", R.string.IsSendingGame, getUserNameForTyping(user))); } else { newPrintingStrings.put(key, LocaleController.getString("SendingGame", R.string.SendingGame)); } - newPrintingStringsTypes.put(key, 2); //TODO*/ + newPrintingStringsTypes.put(key, 3); } else { if (lower_id < 0) { newPrintingStrings.put(key, String.format("%s %s", getUserNameForTyping(user), LocaleController.getString("IsTyping", R.string.IsTyping))); @@ -2409,6 +2571,8 @@ public void sendTyping(final long dialog_id, final int action, int classGuid) { req.action = new TLRPC.TL_sendMessageUploadPhotoAction(); } else if (action == 5) { req.action = new TLRPC.TL_sendMessageUploadVideoAction(); + } else if (action == 6) { + req.action = new TLRPC.TL_sendMessageGamePlayAction(); } typings.put(dialog_id, true); int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -2461,19 +2625,21 @@ public void run() { } } - public void loadMessages(final long dialog_id, final int count, final int max_id, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex) { - loadMessages(dialog_id, count, max_id, fromCache, midDate, classGuid, load_type, last_message_id, isChannel, loadIndex, 0, 0, 0, false); + public void loadMessages(final long dialog_id, final int count, final int max_id, final int offset_date, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex) { + loadMessages(dialog_id, count, max_id, offset_date, fromCache, midDate, classGuid, load_type, last_message_id, isChannel, loadIndex, 0, 0, 0, false); } - public void loadMessages(final long dialog_id, final int count, final int max_id, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex, final int first_unread, final int unread_count, final int last_date, final boolean queryFromServer) { - FileLog.e("tmessages", "load messages in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + midDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + public void loadMessages(final long dialog_id, final int count, final int max_id, final int offset_date, boolean fromCache, int midDate, final int classGuid, final int load_type, final int last_message_id, final boolean isChannel, final int loadIndex, final int first_unread, final int unread_count, final int last_date, final boolean queryFromServer) { + FileLog.e("load messages in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + fromCache + " mindate = " + midDate + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); int lower_part = (int) dialog_id; if (fromCache || lower_part == 0) { - MessagesStorage.getInstance().getMessages(dialog_id, count, max_id, midDate, classGuid, load_type, isChannel, loadIndex); + MessagesStorage.getInstance().getMessages(dialog_id, count, max_id, offset_date, midDate, classGuid, load_type, isChannel, loadIndex); } else { TLRPC.TL_messages_getHistory req = new TLRPC.TL_messages_getHistory(); req.peer = getInputPeer(lower_part); - if (load_type == 3) { + if (load_type == 4) { + req.add_offset = -count + 5; + } else if (load_type == 3) { req.add_offset = -count / 2; } else if (load_type == 1) { req.add_offset = -count - 1; @@ -2490,6 +2656,7 @@ public void loadMessages(final long dialog_id, final int count, final int max_id } req.limit = count; req.offset_id = max_id; + req.offset_date = offset_date; int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -2498,7 +2665,18 @@ public void run(TLObject response, TLRPC.TL_error error) { if (res.messages.size() > count) { res.messages.remove(0); } - processLoadedMessages(res, dialog_id, count, max_id, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, isChannel, false, loadIndex, queryFromServer); + int mid = max_id; + if (offset_date != 0 && !res.messages.isEmpty()) { + mid = res.messages.get(res.messages.size() - 1).id; + for (int a = res.messages.size() - 1; a >= 0; a--) { + TLRPC.Message message = res.messages.get(a); + if (message.date > offset_date) { + mid = message.id; + break; + } + } + } + processLoadedMessages(res, dialog_id, count, mid, offset_date, false, classGuid, first_unread, last_message_id, unread_count, last_date, load_type, isChannel, false, loadIndex, queryFromServer); } } }); @@ -2559,9 +2737,9 @@ public void run() { } } - public void processLoadedMessages(final TLRPC.messages_Messages messagesRes, final long dialog_id, final int count, final int max_id, final boolean isCache, final int classGuid, + public void processLoadedMessages(final TLRPC.messages_Messages messagesRes, final long dialog_id, final int count, final int max_id, final int offset_date, final boolean isCache, final int classGuid, final int first_unread, final int last_message_id, final int unread_count, final int last_date, final int load_type, final boolean isChannel, final boolean isEnd, final int loadIndex, final boolean queryFromServer) { - FileLog.e("tmessages", "processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " isChannel " + isChannel + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); + FileLog.e("processLoadedMessages size " + messagesRes.messages.size() + " in chat " + dialog_id + " count " + count + " max_id " + max_id + " cache " + isCache + " guid " + classGuid + " load_type " + load_type + " last_message_id " + last_message_id + " isChannel " + isChannel + " index " + loadIndex + " firstUnread " + first_unread + " underad count " + unread_count + " last_date " + last_date + " queryFromServer " + queryFromServer); Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -2576,7 +2754,7 @@ public void run() { channelsPts.put(channelId, messagesRes.pts); createDialog = true; if (needShortPollChannels.indexOfKey(channelId) >= 0 && shortPollChannels.indexOfKey(channelId) < 0) { - getChannelDifference(channelId, 2, 0); + getChannelDifference(channelId, 2, 0, null); } else { getChannelDifference(channelId); } @@ -2599,7 +2777,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - loadMessages(dialog_id, count, load_type == 2 && queryFromServer ? first_unread : max_id, false, 0, classGuid, load_type, last_message_id, isChannel, loadIndex, first_unread, unread_count, last_date, queryFromServer); + loadMessages(dialog_id, count, load_type == 2 && queryFromServer ? first_unread : max_id, offset_date, false, 0, classGuid, load_type, last_message_id, isChannel, loadIndex, first_unread, unread_count, last_date, queryFromServer); } }); return; @@ -2618,7 +2796,7 @@ public void run() { if (!isCache) { Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance().getDialogReadMax(true, dialog_id); + inboxValue = MessagesStorage.getInstance().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } @@ -2697,7 +2875,7 @@ public void run() { if (first_unread_final == Integer.MAX_VALUE) { first_unread_final = first_unread; } - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDidLoaded, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messagesDidLoaded, dialog_id, count, objects, isCache, first_unread_final, last_message_id, unread_count, last_date, load_type, isEnd, classGuid, loadIndex, max_id); if (!messagesToReload.isEmpty()) { reloadMessages(messagesToReload, dialog_id); } @@ -2716,15 +2894,19 @@ public void loadDialogs(final int offset, final int count, boolean fromCache) { } loadingDialogs = true; NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); - FileLog.e("tmessages", "load cacheOffset = " + offset + " count = " + count + " cache = " + fromCache); + FileLog.e("load cacheOffset = " + offset + " count = " + count + " cache = " + fromCache); if (fromCache) { MessagesStorage.getInstance().getDialogs(offset == 0 ? 0 : nextDialogsCacheOffset, count); } else { TLRPC.TL_messages_getDialogs req = new TLRPC.TL_messages_getDialogs(); req.limit = count; + req.exclude_pinned = true; boolean found = false; for (int a = dialogs.size() - 1; a >= 0; a--) { TLRPC.TL_dialog dialog = dialogs.get(a); + if (dialog.pinned) { + continue; + } int lower_id = (int) dialog.id; int high_id = (int) (dialog.id >> 32); if (lower_id != 0 && high_id != 1 && dialog.top_message > 0) { @@ -2754,7 +2936,7 @@ public void loadDialogs(final int offset, final int count, boolean fromCache) { public void run(TLObject response, TLRPC.TL_error error) { if (error == null) { final TLRPC.messages_Dialogs dialogsRes = (TLRPC.messages_Dialogs) response; - processLoadedDialogs(dialogsRes, null, 0, count, 0, false, false); + processLoadedDialogs(dialogsRes, null, 0, count, 0, false, false, false); } } }); @@ -2902,9 +3084,9 @@ public void run() { } cursor.dispose(); - processLoadedDialogs(dialogsRes, null, offsetId, 0, 0, false, true); + processLoadedDialogs(dialogsRes, null, offsetId, 0, 0, false, true, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -2926,11 +3108,16 @@ public void run() { }); } - public void processLoadedDialogs(final TLRPC.messages_Dialogs dialogsRes, final ArrayList encChats, final int offset, final int count, final int loadType, final boolean resetEnd, final boolean migrate) { + public void processLoadedDialogs(final TLRPC.messages_Dialogs dialogsRes, final ArrayList encChats, final int offset, final int count, final int loadType, final boolean resetEnd, final boolean migrate, final boolean fromCache) { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - FileLog.e("tmessages", "loaded loadType " + loadType + " count " + dialogsRes.dialogs.size()); + if (!firstGettingTask) { + getNewDeleteTask(null); + firstGettingTask = true; + } + + FileLog.e("loaded loadType " + loadType + " count " + dialogsRes.dialogs.size()); if (loadType == 1 && dialogsRes.dialogs.size() == 0) { AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -2939,6 +3126,7 @@ public void run() { loadingDialogs = false; if (resetEnd) { dialogsEndReached = false; + serverDialogsEndReached = false; } NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); loadDialogs(0, count, false); @@ -3070,7 +3258,7 @@ public void run() { message.unread = value < message.id; } } - MessagesStorage.getInstance().putDialogs(dialogsRes); + MessagesStorage.getInstance().putDialogs(dialogsRes, false); } if (loadType == 2) { TLRPC.Chat chat = dialogsRes.chats.get(0); @@ -3129,6 +3317,8 @@ public void run() { if (loadType != 1) { currentDialog.notify_settings = value.notify_settings; } + currentDialog.pinned = value.pinned; + currentDialog.pinnedNum = value.pinnedNum; MessageObject oldMsg = dialogMessage.get(key); if (oldMsg != null && oldMsg.deleted || oldMsg == null || currentDialog.top_message > 0) { if (value.top_message >= currentDialog.top_message) { @@ -3175,6 +3365,9 @@ public void run() { if (loadType != 2) { if (!migrate) { dialogsEndReached = (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && loadType == 0; + if (!fromCache) { + serverDialogsEndReached = (dialogsRes.dialogs.size() == 0 || dialogsRes.dialogs.size() != count) && loadType == 0; + } } } NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); @@ -3224,13 +3417,13 @@ private void applyDialogNotificationsSettings(long dialog_id, TLRPC.PeerNotifySe } else { if (currentValue != 3 || currentValue2 != notify_settings.mute_until) { updated = true; - until = notify_settings.mute_until; editor.putInt("notify2_" + dialog_id, 3); editor.putInt("notifyuntil_" + dialog_id, notify_settings.mute_until); if (dialog != null) { dialog.notify_settings.mute_until = until; } } + until = notify_settings.mute_until; } MessagesStorage.getInstance().setDialogFlags(dialog_id, ((long) until << 32) | 1); NotificationsController.getInstance().removeNotificationsForDialog(dialog_id); @@ -3319,8 +3512,8 @@ protected void checkLastDialogMessage(final TLRPC.TL_dialog dialog, final TLRPC. if (taskId == 0) { NativeByteBuffer data = null; try { - data = new NativeByteBuffer(40 + peer.getObjectSize()); - data.writeInt32(2); + data = new NativeByteBuffer(48 + peer.getObjectSize()); + data.writeInt32(5); data.writeInt64(dialog.id); data.writeInt32(dialog.top_message); data.writeInt32(dialog.read_inbox_max_id); @@ -3329,9 +3522,11 @@ protected void checkLastDialogMessage(final TLRPC.TL_dialog dialog, final TLRPC. data.writeInt32(dialog.last_message_date); data.writeInt32(dialog.pts); data.writeInt32(dialog.flags); + data.writeBool(dialog.pinned); + data.writeInt32(dialog.pinnedNum); peer.serializeToStream(data); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } newTaskId = MessagesStorage.getInstance().createPendingTask(data); } else { @@ -3355,6 +3550,8 @@ public void run(TLObject response, TLRPC.TL_error error) { newDialog.unread_count = dialog.unread_count; newDialog.read_inbox_max_id = dialog.read_inbox_max_id; newDialog.read_outbox_max_id = dialog.read_outbox_max_id; + newDialog.pinned = dialog.pinned; + newDialog.pinnedNum = dialog.pinnedNum; newMessage.dialog_id = newDialog.id = dialog.id; dialogs.users.addAll(res.users); dialogs.chats.addAll(res.chats); @@ -3799,7 +3996,7 @@ public int createChat(String title, ArrayList selectedContacts, final S return 0; } else if (type == ChatObject.CHAT_TYPE_CHAT) { - TLRPC.TL_messages_createChat req = new TLRPC.TL_messages_createChat(); + final TLRPC.TL_messages_createChat req = new TLRPC.TL_messages_createChat(); req.title = title; for (int a = 0; a < selectedContacts.size(); a++) { TLRPC.User user = getUser(selectedContacts.get(a)); @@ -3815,11 +4012,7 @@ public void run(TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (error.text.startsWith("FLOOD_WAIT")) { - AlertsCreator.showFloodWaitAlert(error.text, fragment); - } else { - AlertsCreator.showAddUserAlert(error.text, fragment, false); - } + AlertsCreator.processError(error, fragment, req); NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidFailCreate); } }); @@ -3842,7 +4035,7 @@ public void run() { } }, ConnectionsManager.RequestFlagFailOnServerErrors); } else if (type == ChatObject.CHAT_TYPE_CHANNEL || type == ChatObject.CHAT_TYPE_MEGAGROUP) { - TLRPC.TL_channels_createChannel req = new TLRPC.TL_channels_createChannel(); + final TLRPC.TL_channels_createChannel req = new TLRPC.TL_channels_createChannel(); req.title = title; req.about = about; if (type == ChatObject.CHAT_TYPE_MEGAGROUP) { @@ -3857,9 +4050,7 @@ public void run(TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (error.text.startsWith("FLOOD_WAIT")) { - AlertsCreator.showFloodWaitAlert(error.text, fragment); - } + AlertsCreator.processError(error, fragment, req); NotificationCenter.getInstance().postNotificationName(NotificationCenter.chatDidFailCreate); } }); @@ -3888,7 +4079,7 @@ public void run() { public void convertToMegaGroup(final Context context, int chat_id) { TLRPC.TL_messages_migrateChat req = new TLRPC.TL_messages_migrateChat(); req.chat_id = chat_id; - final ProgressDialog progressDialog = new ProgressDialog(context); + final AlertDialog progressDialog = new AlertDialog(context, 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -3903,7 +4094,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -3918,7 +4109,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -3938,7 +4129,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3953,7 +4144,7 @@ public void addUsersToChannel(int chat_id, ArrayList users, fin if (users == null || users.isEmpty()) { return; } - TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel(); + final TLRPC.TL_channels_inviteToChannel req = new TLRPC.TL_channels_inviteToChannel(); req.channel = getInputChannel(chat_id); req.users = users; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -3963,11 +4154,7 @@ public void run(TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (fragment != null) { - AlertsCreator.showAddUserAlert(error.text, fragment, true); - } else if (error.text.equals("PEER_FLOOD")) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 1); - } + AlertsCreator.processError(error, fragment, req, true); } }); return; @@ -4118,7 +4305,7 @@ public void addUserToChat(final int chat_id, final TLRPC.User user, final TLRPC. } if (chat_id > 0) { - TLObject request; + final TLObject request; final boolean isChannel = ChatObject.isChannel(chat_id); final boolean isMegagroup = isChannel && getChat(chat_id).megagroup; @@ -4183,13 +4370,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - if (fragment != null) { - AlertsCreator.showAddUserAlert(error.text, fragment, isChannel && !isMegagroup); - } else { - if (error.text.equals("PEER_FLOOD")) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 1); - } - } + AlertsCreator.processError(error, fragment, request, isChannel && !isMegagroup); } }); return; @@ -4218,7 +4399,7 @@ public void run() { }, 1000); } if (isChannel && inputUser instanceof TLRPC.TL_inputUserSelf) { - MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), true, chat_id); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), null, true, chat_id); } } }); @@ -4442,11 +4623,26 @@ public void run(TLObject response, TLRPC.TL_error error) { ContactsController.getInstance().deleteAllAppAccounts(); } +// public void generateJoinedMessage(final int uid) { +// Utilities.stageQueue.postRunnable(new Runnable() { +// @Override +// public void run() { +// TLRPC.TL_updateContactRegistered update = new TLRPC.TL_updateContactRegistered(); +// update.user_id = uid; +// update.date = (int) (System.currentTimeMillis() / 1000); +// ArrayList updates = new ArrayList<>(); +// updates.add(update); +// processUpdateArray(updates, null, null, false); +// } +// }); +// } + public void generateUpdateMessage() { if (BuildVars.DEBUG_VERSION || UserConfig.lastUpdateVersion == null || UserConfig.lastUpdateVersion.equals(BuildVars.BUILD_VERSION_STRING)) { return; } TLRPC.TL_help_getAppChangelog req = new TLRPC.TL_help_getAppChangelog(); + req.prev_app_version = UserConfig.lastUpdateVersion; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, TLRPC.TL_error error) { @@ -4454,15 +4650,8 @@ public void run(TLObject response, TLRPC.TL_error error) { UserConfig.lastUpdateVersion = BuildVars.BUILD_VERSION_STRING; UserConfig.saveConfig(false); } - if (response instanceof TLRPC.TL_help_appChangelog) { - TLRPC.TL_updateServiceNotification update = new TLRPC.TL_updateServiceNotification(); - update.message = ((TLRPC.TL_help_appChangelog) response).text; - update.media = new TLRPC.TL_messageMediaEmpty(); - update.type = "update"; - update.popup = false; - ArrayList updates = new ArrayList<>(); - updates.add(update); - processUpdateArray(updates, null, null, false); + if (response instanceof TLRPC.Updates) { + processUpdates((TLRPC.Updates) response, false); } } }); @@ -4483,7 +4672,7 @@ public void registerForPush(final String regid) { @Override public void run(TLObject response, TLRPC.TL_error error) { if (response instanceof TLRPC.TL_boolTrue) { - FileLog.e("tmessages", "registered for push"); + FileLog.e("registered for push"); UserConfig.registeredForPush = true; UserConfig.pushString = regid; UserConfig.saveConfig(false); @@ -4624,13 +4813,13 @@ public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) { } else if (updateState == 1) { Long updatesStartWaitTime = updatesStartWaitTimeChannels.get(channelId); if (updatesStartWaitTime != null && (anyProceed || Math.abs(System.currentTimeMillis() - updatesStartWaitTime) <= 1500)) { - FileLog.e("tmessages", "HOLE IN CHANNEL " + channelId + " UPDATES QUEUE - will wait more time"); + FileLog.e("HOLE IN CHANNEL " + channelId + " UPDATES QUEUE - will wait more time"); if (anyProceed) { updatesStartWaitTimeChannels.put(channelId, System.currentTimeMillis()); } return; } else { - FileLog.e("tmessages", "HOLE IN CHANNEL " + channelId + " UPDATES QUEUE - getChannelDifference "); + FileLog.e("HOLE IN CHANNEL " + channelId + " UPDATES QUEUE - getChannelDifference "); updatesStartWaitTimeChannels.remove(channelId); updatesQueueChannels.remove(channelId); getChannelDifference(channelId); @@ -4643,7 +4832,7 @@ public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) { } updatesQueueChannels.remove(channelId); updatesStartWaitTimeChannels.remove(channelId); - FileLog.e("tmessages", "UPDATES CHANNEL " + channelId + " QUEUE PROCEED - OK"); + FileLog.e("UPDATES CHANNEL " + channelId + " QUEUE PROCEED - OK"); } private void processUpdatesQueue(int type, int state) { @@ -4695,13 +4884,13 @@ public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) { a--; } else if (updateState == 1) { if (getUpdatesStartTime(type) != 0 && (anyProceed || Math.abs(System.currentTimeMillis() - getUpdatesStartTime(type)) <= 1500)) { - FileLog.e("tmessages", "HOLE IN UPDATES QUEUE - will wait more time"); + FileLog.e("HOLE IN UPDATES QUEUE - will wait more time"); if (anyProceed) { setUpdatesStartTime(type, System.currentTimeMillis()); } return; } else { - FileLog.e("tmessages", "HOLE IN UPDATES QUEUE - getDifference"); + FileLog.e("HOLE IN UPDATES QUEUE - getDifference"); setUpdatesStartTime(type, 0); updatesQueue.clear(); getDifference(); @@ -4713,7 +4902,7 @@ public int compare(TLRPC.Updates updates, TLRPC.Updates updates2) { } } updatesQueue.clear(); - FileLog.e("tmessages", "UPDATES QUEUE PROCEED - OK"); + FileLog.e("UPDATES QUEUE PROCEED - OK"); } setUpdatesStartTime(type, 0); } @@ -4722,10 +4911,18 @@ protected void loadUnknownChannel(final TLRPC.Chat channel, final long taskId) { if (!(channel instanceof TLRPC.TL_channel) || gettingUnknownChannels.containsKey(channel.id)) { return; } - gettingUnknownChannels.put(channel.id, true); + if (channel.access_hash == 0) { + if (taskId != 0) { + MessagesStorage.getInstance().removePendingTask(taskId); + } + return; + } TLRPC.TL_inputPeerChannel inputPeer = new TLRPC.TL_inputPeerChannel(); inputPeer.channel_id = channel.id; inputPeer.access_hash = channel.access_hash; + + gettingUnknownChannels.put(channel.id, true); + TLRPC.TL_messages_getPeerDialogs req = new TLRPC.TL_messages_getPeerDialogs(); req.peers.add(inputPeer); final long newTaskId; @@ -4735,9 +4932,8 @@ protected void loadUnknownChannel(final TLRPC.Chat channel, final long taskId) { data = new NativeByteBuffer(4 + channel.getObjectSize()); data.writeInt32(0); channel.serializeToStream(data); - } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } newTaskId = MessagesStorage.getInstance().createPendingTask(data); } else { @@ -4754,7 +4950,7 @@ public void run(TLObject response, TLRPC.TL_error error) { dialogs.messages.addAll(res.messages); dialogs.users.addAll(res.users); dialogs.chats.addAll(res.chats); - processLoadedDialogs(dialogs, null, 0, 1, 2, false, false); + processLoadedDialogs(dialogs, null, 0, 1, 2, false, false, false); } } if (newTaskId != 0) { @@ -4774,7 +4970,7 @@ public void run() { } else { needShortPollChannels.put(channelId, 0); if (shortPollChannels.indexOfKey(channelId) < 0) { - getChannelDifference(channelId, 2, 0); + getChannelDifference(channelId, 3, 0, null); } } } @@ -4782,10 +4978,10 @@ public void run() { } private void getChannelDifference(final int channelId) { - getChannelDifference(channelId, 0, 0); + getChannelDifference(channelId, 0, 0, null); } - protected void getChannelDifference(final int channelId, final int newDialogType, final long taskId) { + protected void getChannelDifference(final int channelId, final int newDialogType, final long taskId, TLRPC.InputChannel inputChannel) { Boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); if (gettingDifferenceChannel == null) { gettingDifferenceChannel = false; @@ -4809,7 +5005,7 @@ protected void getChannelDifference(final int channelId, final int newDialogType if (channelPts != 0) { channelsPts.put(channelId, channelPts); } - if (channelPts == 0 && newDialogType == 2) { + if (channelPts == 0 && (newDialogType == 2 || newDialogType == 3)) { return; } } @@ -4818,16 +5014,26 @@ protected void getChannelDifference(final int channelId, final int newDialogType } } + if (inputChannel == null) { + inputChannel = getInputChannel(channelId); + } + if (inputChannel == null || inputChannel.access_hash == 0) { + if (taskId != 0) { + MessagesStorage.getInstance().removePendingTask(taskId); + } + return; + } final long newTaskId; if (taskId == 0) { NativeByteBuffer data = null; try { - data = new NativeByteBuffer(12); - data.writeInt32(1); + data = new NativeByteBuffer(12 + inputChannel.getObjectSize()); + data.writeInt32(6); data.writeInt32(channelId); data.writeInt32(newDialogType); + inputChannel.serializeToStream(data); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } newTaskId = MessagesStorage.getInstance().createPendingTask(data); } else { @@ -4836,11 +5042,12 @@ protected void getChannelDifference(final int channelId, final int newDialogType gettingDifferenceChannels.put(channelId, true); TLRPC.TL_updates_getChannelDifference req = new TLRPC.TL_updates_getChannelDifference(); - req.channel = getInputChannel(channelId); + req.channel = inputChannel; req.filter = new TLRPC.TL_channelMessagesFilterEmpty(); req.pts = channelPts; req.limit = limit; - FileLog.e("tmessages", "start getChannelDifference with pts = " + channelPts + " channelId = " + channelId); + req.force = newDialogType != 3; + FileLog.e("start getChannelDifference with pts = " + channelPts + " channelId = " + channelId); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(TLObject response, final TLRPC.TL_error error) { @@ -4923,7 +5130,7 @@ public void run() { long dialog_id = -channelId; Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance().getDialogReadMax(true, dialog_id); + inboxValue = MessagesStorage.getInstance().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } @@ -4991,7 +5198,7 @@ public void run() { Integer inboxValue = dialogs_read_inbox_max.get(dialog_id); if (inboxValue == null) { - inboxValue = MessagesStorage.getInstance().getDialogReadMax(true, dialog_id); + inboxValue = MessagesStorage.getInstance().getDialogReadMax(false, dialog_id); dialogs_read_inbox_max.put(dialog_id, inboxValue); } @@ -5020,8 +5227,8 @@ public void run() { if (!res.isFinal) { getChannelDifference(channelId); } - FileLog.e("tmessages", "received channel difference with pts = " + res.pts + " channelId = " + channelId); - FileLog.e("tmessages", "new_messages = " + res.new_messages.size() + " messages = " + res.messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); + FileLog.e("received channel difference with pts = " + res.pts + " channelId = " + channelId); + FileLog.e("new_messages = " + res.new_messages.size() + " messages = " + res.messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); if (newTaskId != 0) { MessagesStorage.getInstance().removePendingTask(newTaskId); @@ -5073,10 +5280,6 @@ public void getDifference(int pts, int date, int qts, boolean slice) { if (!slice && gettingDifference) { return; } - if (!firstGettingTask) { - getNewDeleteTask(null); - firstGettingTask = true; - } gettingDifference = true; TLRPC.TL_updates_getDifference req = new TLRPC.TL_updates_getDifference(); req.pts = pts; @@ -5085,7 +5288,7 @@ public void getDifference(int pts, int date, int qts, boolean slice) { if (req.date == 0) { req.date = ConnectionsManager.getInstance().getCurrentTime(); } - FileLog.e("tmessages", "start getDifference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue); + FileLog.e("start getDifference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue); ConnectionsManager.getInstance().setIsUpdating(true); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -5288,7 +5491,7 @@ public void run() { } } MessagesStorage.getInstance().saveDiffParams(MessagesStorage.lastSeqValue, MessagesStorage.lastPtsValue, MessagesStorage.lastDateValue, MessagesStorage.lastQtsValue); - FileLog.e("tmessages", "received difference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); + FileLog.e("received difference with date = " + MessagesStorage.lastDateValue + " pts = " + MessagesStorage.lastPtsValue + " seq = " + MessagesStorage.lastSeqValue + " messages = " + res.new_messages.size() + " users = " + res.users.size() + " chats = " + res.chats.size() + " other updates = " + res.other_updates.size()); } }); } @@ -5301,6 +5504,289 @@ public void run() { }); } + public boolean canPinDialog(boolean secret) { + int count = 0; + for (int a = 0; a < dialogs.size(); a++) { + TLRPC.TL_dialog dialog = dialogs.get(a); + int lower_id = (int) dialog.id; + if (secret && lower_id != 0 || !secret && lower_id == 0) { + continue; + } + if (dialog.pinned) { + count++; + } + } + return count < maxPinnedDialogsCount; + } + + public boolean pinDialog(long did, boolean pin, TLRPC.InputPeer peer, long taskId) { + int lower_id = (int) did; + TLRPC.TL_dialog dialog = dialogs_dict.get(did); + if (dialog == null || dialog.pinned == pin) { + return dialog != null; + } + dialog.pinned = pin; + if (pin) { + int maxPinnedNum = 0; + for (int a = 0; a < dialogs.size(); a++) { + TLRPC.TL_dialog d = dialogs.get(a); + if (!d.pinned) { + break; + } + maxPinnedNum = Math.max(d.pinnedNum, maxPinnedNum); + } + dialog.pinnedNum = maxPinnedNum + 1; + } else { + dialog.pinnedNum = 0; + } + sortDialogs(null); + if (!pin && dialogs.get(dialogs.size() - 1) == dialog) { + dialogs.remove(dialogs.size() - 1); + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + if (lower_id != 0) { + if (taskId != -1) { + TLRPC.TL_messages_toggleDialogPin req = new TLRPC.TL_messages_toggleDialogPin(); + req.pinned = pin; + if (peer == null) { + peer = getInputPeer(lower_id); + } + if (peer instanceof TLRPC.TL_inputPeerEmpty) { + return false; + } + req.peer = peer; + + final long newTaskId; + if (taskId == 0) { + NativeByteBuffer data = null; + try { + data = new NativeByteBuffer(16 + peer.getObjectSize()); + data.writeInt32(1); + data.writeInt64(did); + data.writeBool(pin); + peer.serializeToStream(data); + } catch (Exception e) { + FileLog.e(e); + } + newTaskId = MessagesStorage.getInstance().createPendingTask(data); + } else { + newTaskId = taskId; + } + + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (newTaskId != 0) { + MessagesStorage.getInstance().removePendingTask(newTaskId); + } + } + }); + } + } + MessagesStorage.getInstance().setDialogPinned(did, dialog.pinnedNum); + return true; + } + + public void loadPinnedDialogs(final long newDialogId, final ArrayList order) { + if (UserConfig.pinnedDialogsLoaded) { + return; + } + TLRPC.TL_messages_getPinnedDialogs req = new TLRPC.TL_messages_getPinnedDialogs(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + if (response != null) { + final TLRPC.TL_messages_peerDialogs res = (TLRPC.TL_messages_peerDialogs) response; + final TLRPC.TL_messages_dialogs toCache = new TLRPC.TL_messages_dialogs(); + toCache.users.addAll(res.users); + toCache.chats.addAll(res.chats); + toCache.dialogs.addAll(res.dialogs); + toCache.messages.addAll(res.messages); + + final HashMap new_dialogMessage = new HashMap<>(); + final HashMap usersDict = new HashMap<>(); + final HashMap chatsDict = new HashMap<>(); + final ArrayList newPinnedOrder = new ArrayList<>(); + + for (int a = 0; a < res.users.size(); a++) { + TLRPC.User u = res.users.get(a); + usersDict.put(u.id, u); + } + for (int a = 0; a < res.chats.size(); a++) { + TLRPC.Chat c = res.chats.get(a); + chatsDict.put(c.id, c); + } + + for (int a = 0; a < res.messages.size(); a++) { + TLRPC.Message message = res.messages.get(a); + if (message.to_id.channel_id != 0) { + TLRPC.Chat chat = chatsDict.get(message.to_id.channel_id); + if (chat != null && chat.left) { + continue; + } + } else if (message.to_id.chat_id != 0) { + TLRPC.Chat chat = chatsDict.get(message.to_id.chat_id); + if (chat != null && chat.migrated_to != null) { + continue; + } + } + MessageObject messageObject = new MessageObject(message, usersDict, chatsDict, false); + new_dialogMessage.put(messageObject.getDialogId(), messageObject); + } + for (int a = 0; a < res.dialogs.size(); a++) { + TLRPC.TL_dialog d = res.dialogs.get(a); + if (d.id == 0) { + if (d.peer.user_id != 0) { + d.id = d.peer.user_id; + } else if (d.peer.chat_id != 0) { + d.id = -d.peer.chat_id; + } else if (d.peer.channel_id != 0) { + d.id = -d.peer.channel_id; + } + } + newPinnedOrder.add(d.id); + if (DialogObject.isChannel(d)) { + TLRPC.Chat chat = chatsDict.get(-(int) d.id); + if (chat != null && chat.left) { + continue; + } + } else if ((int) d.id < 0) { + TLRPC.Chat chat = chatsDict.get(-(int) d.id); + if (chat != null && chat.migrated_to != null) { + continue; + } + } + if (d.last_message_date == 0) { + MessageObject mess = new_dialogMessage.get(d.id); + if (mess != null) { + d.last_message_date = mess.messageOwner.date; + } + } + + Integer value = dialogs_read_inbox_max.get(d.id); + if (value == null) { + value = 0; + } + dialogs_read_inbox_max.put(d.id, Math.max(value, d.read_inbox_max_id)); + + value = dialogs_read_outbox_max.get(d.id); + if (value == null) { + value = 0; + } + dialogs_read_outbox_max.put(d.id, Math.max(value, d.read_outbox_max_id)); + } + + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + applyDialogsNotificationsSettings(res.dialogs); + boolean changed = false; + boolean added = false; + int maxPinnedNum = 0; + HashMap oldPinnedDialogNums = new HashMap<>(); + ArrayList oldPinnedOrder = new ArrayList<>(); + for (int a = 0; a < dialogs.size(); a++) { + TLRPC.TL_dialog dialog = dialogs.get(a); + if ((int) dialog.id == 0) { + continue; + } + if (!dialog.pinned) { + break; + } + maxPinnedNum = Math.max(dialog.pinnedNum, maxPinnedNum); + oldPinnedDialogNums.put(dialog.id, dialog.pinnedNum); + oldPinnedOrder.add(dialog.id); + dialog.pinned = false; + dialog.pinnedNum = 0; + changed = true; + } + + ArrayList pinnedDialogs = new ArrayList<>(); + ArrayList orderArrayList = order != null ? order : newPinnedOrder; + if (orderArrayList.size() < oldPinnedOrder.size()) { + orderArrayList.add(0L); + } + while (oldPinnedOrder.size() < orderArrayList.size()) { + oldPinnedOrder.add(0, 0L); + } + if (!res.dialogs.isEmpty()) { + putUsers(res.users, false); + putChats(res.chats, false); + for (int a = 0; a < res.dialogs.size(); a++) { + TLRPC.TL_dialog dialog = res.dialogs.get(a); + if (newDialogId != 0) { + Integer oldNum = oldPinnedDialogNums.get(dialog.id); + if (oldNum != null) { + dialog.pinnedNum = oldNum; + } + } else { + int oldIdx = oldPinnedOrder.indexOf(dialog.id); + int newIdx = orderArrayList.indexOf(dialog.id); + if (oldIdx != -1 && newIdx != -1) { + if (oldIdx == newIdx) { + Integer oldNum = oldPinnedDialogNums.get(dialog.id); + if (oldNum != null) { + dialog.pinnedNum = oldNum; + } + } else { + long oldDid = oldPinnedOrder.get(newIdx); + Integer oldNum = oldPinnedDialogNums.get(oldDid); + if (oldNum != null) { + dialog.pinnedNum = oldNum; + } + } + } + } + if (dialog.pinnedNum == 0) { + dialog.pinnedNum = (res.dialogs.size() - a) + maxPinnedNum; + } + pinnedDialogs.add(dialog.id); + TLRPC.TL_dialog d = dialogs_dict.get(dialog.id); + + if (d != null) { + d.pinned = true; + d.pinnedNum = dialog.pinnedNum; + MessagesStorage.getInstance().setDialogPinned(dialog.id, dialog.pinnedNum); + } else { + added = true; + dialogs_dict.put(dialog.id, dialog); + MessageObject messageObject = new_dialogMessage.get(dialog.id); + dialogMessage.put(dialog.id, messageObject); + if (messageObject != null && messageObject.messageOwner.to_id.channel_id == 0) { + dialogMessagesByIds.put(messageObject.getId(), messageObject); + if (messageObject.messageOwner.random_id != 0) { + dialogMessagesByRandomIds.put(messageObject.messageOwner.random_id, messageObject); + } + } + } + + changed = true; + } + } + if (changed) { + if (added) { + dialogs.clear(); + dialogs.addAll(dialogs_dict.values()); + } + sortDialogs(null); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + } + MessagesStorage.getInstance().unpinAllDialogsExceptNew(pinnedDialogs); + MessagesStorage.getInstance().putDialogs(toCache, true); + UserConfig.pinnedDialogsLoaded = true; + UserConfig.saveConfig(false); + } + }); + } + }); + } + } + }); + } + public void generateJoinMessage(final int chat_id, boolean ignoreLeft) { TLRPC.Chat chat = getChat(chat_id); if (chat == null || !ChatObject.isChannel(chat_id) || (chat.left || chat.kicked) && !ignoreLeft) { @@ -5448,7 +5934,8 @@ private int getUpdateType(TLRPC.Update update) { return 0; } else if (update instanceof TLRPC.TL_updateNewEncryptedMessage) { return 1; - } else if (update instanceof TLRPC.TL_updateNewChannelMessage || update instanceof TLRPC.TL_updateDeleteChannelMessages || update instanceof TLRPC.TL_updateEditChannelMessage) { + } else if (update instanceof TLRPC.TL_updateNewChannelMessage || update instanceof TLRPC.TL_updateDeleteChannelMessages || update instanceof TLRPC.TL_updateEditChannelMessage || + update instanceof TLRPC.TL_updateChannelWebPage) { return 2; } else { return 3; @@ -5658,12 +6145,12 @@ public void run() { } MessagesStorage.getInstance().putMessages(arr, false, true, false, 0); } else if (MessagesStorage.lastPtsValue != updates.pts) { - FileLog.e("tmessages", "need get diff short message, pts: " + MessagesStorage.lastPtsValue + " " + updates.pts + " count = " + updates.pts_count); + FileLog.e("need get diff short message, pts: " + MessagesStorage.lastPtsValue + " " + updates.pts + " count = " + updates.pts_count); if (gettingDifference || updatesStartWaitTimePts == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { if (updatesStartWaitTimePts == 0) { updatesStartWaitTimePts = System.currentTimeMillis(); } - FileLog.e("tmessages", "add to queue"); + FileLog.e("add to queue"); updatesQueuePts.add(updates); } else { needGetDiff = true; @@ -5699,7 +6186,7 @@ public void run() { if (update instanceof TLRPC.TL_updateNewChannelMessage) { int channelId = ((TLRPC.TL_updateNewChannelMessage) update).message.to_id.channel_id; if (minChannels.containsKey(channelId)) { - FileLog.e("tmessages", "need get diff because of min channel " + channelId); + FileLog.e("need get diff because of min channel " + channelId); needGetDiff = true; break; } @@ -5730,18 +6217,18 @@ public void run() { } if (MessagesStorage.lastPtsValue + updatesNew.pts_count == updatesNew.pts) { if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false)) { - FileLog.e("tmessages", "need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); + FileLog.e("need get diff inner TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); needGetDiff = true; } else { MessagesStorage.lastPtsValue = updatesNew.pts; } } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { - FileLog.e("tmessages", update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + updatesNew.pts + " count = " + updatesNew.pts_count); + FileLog.e(update + " need get diff, pts: " + MessagesStorage.lastPtsValue + " " + updatesNew.pts + " count = " + updatesNew.pts_count); if (gettingDifference || updatesStartWaitTimePts == 0 || updatesStartWaitTimePts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimePts) <= 1500) { if (updatesStartWaitTimePts == 0) { updatesStartWaitTimePts = System.currentTimeMillis(); } - FileLog.e("tmessages", "add to queue"); + FileLog.e("add to queue"); updatesQueuePts.add(updatesNew); } else { needGetDiff = true; @@ -5767,12 +6254,12 @@ public void run() { MessagesStorage.lastQtsValue = updatesNew.pts; needReceivedQueue = true; } else if (MessagesStorage.lastPtsValue != updatesNew.pts) { - FileLog.e("tmessages", update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.pts); + FileLog.e(update + " need get diff, qts: " + MessagesStorage.lastQtsValue + " " + updatesNew.pts); if (gettingDifference || updatesStartWaitTimeQts == 0 || updatesStartWaitTimeQts != 0 && Math.abs(System.currentTimeMillis() - updatesStartWaitTimeQts) <= 1500) { if (updatesStartWaitTimeQts == 0) { updatesStartWaitTimeQts = System.currentTimeMillis(); } - FileLog.e("tmessages", "add to queue"); + FileLog.e("add to queue"); updatesQueueQts.add(updatesNew); } else { needGetDiff = true; @@ -5816,7 +6303,7 @@ public void run() { if (!skipUpdate) { if (channelPts + updatesNew.pts_count == updatesNew.pts) { if (!processUpdateArray(updatesNew.updates, updates.users, updates.chats, false)) { - FileLog.e("tmessages", "need get channel diff inner TL_updates, channel_id = " + channelId); + FileLog.e("need get channel diff inner TL_updates, channel_id = " + channelId); if (needGetChannelsDiff == null) { needGetChannelsDiff = new ArrayList<>(); } else if (!needGetChannelsDiff.contains(channelId)) { @@ -5827,7 +6314,7 @@ public void run() { MessagesStorage.getInstance().saveChannelPts(channelId, updatesNew.pts); } } else if (channelPts != updatesNew.pts) { - FileLog.e("tmessages", update + " need get channel diff, pts: " + channelPts + " " + updatesNew.pts + " count = " + updatesNew.pts_count + " channelId = " + channelId); + FileLog.e(update + " need get channel diff, pts: " + channelPts + " " + updatesNew.pts + " count = " + updatesNew.pts_count + " channelId = " + channelId); Long updatesStartWaitTime = updatesStartWaitTimeChannels.get(channelId); Boolean gettingDifferenceChannel = gettingDifferenceChannels.get(channelId); if (gettingDifferenceChannel == null) { @@ -5837,7 +6324,7 @@ public void run() { if (updatesStartWaitTime == null) { updatesStartWaitTimeChannels.put(channelId, System.currentTimeMillis()); } - FileLog.e("tmessages", "add to queue"); + FileLog.e("add to queue"); ArrayList arrayList = updatesQueueChannels.get(channelId); if (arrayList == null) { arrayList = new ArrayList<>(); @@ -5853,7 +6340,7 @@ public void run() { } } } else { - FileLog.e("tmessages", "need load unknown channel = " + channelId); + FileLog.e("need load unknown channel = " + channelId); } } else { break; @@ -5878,16 +6365,16 @@ public void run() { } } else { if (updates instanceof TLRPC.TL_updatesCombined) { - FileLog.e("tmessages", "need get diff TL_updatesCombined, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq_start); + FileLog.e("need get diff TL_updatesCombined, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq_start); } else { - FileLog.e("tmessages", "need get diff TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); + FileLog.e("need get diff TL_updates, seq: " + MessagesStorage.lastSeqValue + " " + updates.seq); } if (gettingDifference || updatesStartWaitTimeSeq == 0 || Math.abs(System.currentTimeMillis() - updatesStartWaitTimeSeq) <= 1500) { if (updatesStartWaitTimeSeq == 0) { updatesStartWaitTimeSeq = System.currentTimeMillis(); } - FileLog.e("tmessages", "add TL_updates/Combined to queue"); + FileLog.e("add TL_updates/Combined to queue"); updatesQueueSeq.add(updates); } else { needGetDiff = true; @@ -5895,7 +6382,7 @@ public void run() { } } } else if (updates instanceof TLRPC.TL_updatesTooLong) { - FileLog.e("tmessages", "need get diff TL_updatesTooLong"); + FileLog.e("need get diff TL_updatesTooLong"); needGetDiff = true; } else if (updates instanceof UserActionUpdatesSeq) { MessagesStorage.lastSeqValue = updates.seq; @@ -6020,7 +6507,7 @@ public void run() { for (int c = 0; c < updates.size(); c++) { TLRPC.Update update = updates.get(c); - FileLog.d("tmessages", "process update " + update); + FileLog.d("process update " + update); if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateNewChannelMessage) { TLRPC.Message message; if (update instanceof TLRPC.TL_updateNewMessage) { @@ -6028,7 +6515,7 @@ public void run() { } else { message = ((TLRPC.TL_updateNewChannelMessage) update).message; if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + message.to_id.channel_id); + FileLog.d(update + " channelId = " + message.to_id.channel_id); } if (!message.out && message.from_id == UserConfig.getClientUserId()) { message.out = true; @@ -6057,7 +6544,7 @@ public void run() { if (checkForUsers) { if (chat_id != 0) { if (chat == null) { - FileLog.d("tmessages", "not found chat " + chat_id); + FileLog.d("not found chat " + chat_id); return false; } } @@ -6091,7 +6578,7 @@ public void run() { putUser(user, true); } if (user == null) { - FileLog.d("tmessages", "not found user " + user_id); + FileLog.d("not found user " + user_id); return false; } if (a == 1 && user.status != null && user.status.expires <= 0) { @@ -6303,38 +6790,6 @@ public void run() { contactsIds.add(-update.user_id); } } - } else if (update instanceof TLRPC.TL_updateNewAuthorization) { - if (!MessagesStorage.getInstance().hasAuthMessage(update.date)) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.newSessionReceived); - } - }); - TLRPC.TL_messageService newMessage = new TLRPC.TL_messageService(); - newMessage.action = new TLRPC.TL_messageActionLoginUnknownLocation(); - newMessage.action.title = update.device; - newMessage.action.address = update.location; - newMessage.local_id = newMessage.id = UserConfig.getNewMessageId(); - UserConfig.saveConfig(false); - newMessage.unread = true; - newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - newMessage.date = update.date; - newMessage.from_id = 777000; - newMessage.to_id = new TLRPC.TL_peerUser(); - newMessage.to_id.user_id = UserConfig.getClientUserId(); - newMessage.dialog_id = 777000; - - messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); - ArrayList arr = messages.get(newMessage.dialog_id); - if (arr == null) { - arr = new ArrayList<>(); - messages.put(newMessage.dialog_id, arr); - } - arr.add(obj); - pushMessages.add(obj); - } } else if (update instanceof TLRPC.TL_updateNewGeoChatMessage) { //DEPRECATED } else if (update instanceof TLRPC.TL_updateNewEncryptedMessage) { @@ -6357,7 +6812,7 @@ public void run() { } } } else if (update instanceof TLRPC.TL_updateEncryptedChatTyping) { - TLRPC.EncryptedChat encryptedChat = getEncryptedChatDB(update.chat_id); + TLRPC.EncryptedChat encryptedChat = getEncryptedChatDB(update.chat_id, true); if (encryptedChat != null) { update.user_id = encryptedChat.user_id; long uid = ((long) update.chat_id) << 32; @@ -6392,7 +6847,7 @@ public void run() { MessagesStorage.getInstance().updateChatInfo(update.chat_id, update.user_id, 0, update.inviter_id, update.version); } else if (update instanceof TLRPC.TL_updateChatParticipantDelete) { MessagesStorage.getInstance().updateChatInfo(update.chat_id, update.user_id, 1, 0, update.version); - } else if (update instanceof TLRPC.TL_updateDcOptions) { + } else if (update instanceof TLRPC.TL_updateDcOptions || update instanceof TLRPC.TL_updateConfig) { ConnectionsManager.getInstance().updateDcSettings(); } else if (update instanceof TLRPC.TL_updateEncryption) { SecretChatHelper.getInstance().processUpdateEncryption((TLRPC.TL_updateEncryption) update, usersDict); @@ -6426,40 +6881,58 @@ public void run() { } else if (update instanceof TLRPC.TL_updateNotifySettings) { updatesOnMainThread.add(update); } else if (update instanceof TLRPC.TL_updateServiceNotification) { - TLRPC.TL_updateServiceNotification notification = (TLRPC.TL_updateServiceNotification) update; + final TLRPC.TL_updateServiceNotification notification = (TLRPC.TL_updateServiceNotification) update; if (notification.popup && notification.message != null && notification.message.length() > 0) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 2, notification.message); - } - TLRPC.TL_message newMessage = new TLRPC.TL_message(); - newMessage.local_id = newMessage.id = UserConfig.getNewMessageId(); - UserConfig.saveConfig(false); - newMessage.unread = true; - newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - newMessage.date = ConnectionsManager.getInstance().getCurrentTime(); - newMessage.from_id = 777000; - newMessage.to_id = new TLRPC.TL_peerUser(); - newMessage.to_id.user_id = UserConfig.getClientUserId(); - newMessage.dialog_id = 777000; - newMessage.media = update.media; - newMessage.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; - newMessage.message = notification.message; - - messagesArr.add(newMessage); - MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); - ArrayList arr = messages.get(newMessage.dialog_id); - if (arr == null) { - arr = new ArrayList<>(); - messages.put(newMessage.dialog_id, arr); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 2, notification.message); + } + }); } - arr.add(obj); - pushMessages.add(obj); + if ((notification.flags & 2) != 0) { + TLRPC.TL_message newMessage = new TLRPC.TL_message(); + newMessage.local_id = newMessage.id = UserConfig.getNewMessageId(); + UserConfig.saveConfig(false); + newMessage.unread = true; + newMessage.flags = TLRPC.MESSAGE_FLAG_HAS_FROM_ID; + newMessage.date = notification.inbox_date; + newMessage.from_id = 777000; + newMessage.to_id = new TLRPC.TL_peerUser(); + newMessage.to_id.user_id = UserConfig.getClientUserId(); + newMessage.dialog_id = 777000; + if (update.media != null) { + newMessage.media = update.media; + newMessage.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; + } + newMessage.message = notification.message; + if (notification.entities != null) { + newMessage.entities = notification.entities; + } + + messagesArr.add(newMessage); + MessageObject obj = new MessageObject(newMessage, usersDict, chatsDict, createdDialogIds.contains(newMessage.dialog_id)); + ArrayList arr = messages.get(newMessage.dialog_id); + if (arr == null) { + arr = new ArrayList<>(); + messages.put(newMessage.dialog_id, arr); + } + arr.add(obj); + pushMessages.add(obj); + } + } else if (update instanceof TLRPC.TL_updateDialogPinned) { + updatesOnMainThread.add(update); + } else if (update instanceof TLRPC.TL_updatePinnedDialogs) { + updatesOnMainThread.add(update); } else if (update instanceof TLRPC.TL_updatePrivacy) { updatesOnMainThread.add(update); } else if (update instanceof TLRPC.TL_updateWebPage) { webPages.put(update.webpage.id, update.webpage); + } else if (update instanceof TLRPC.TL_updateChannelWebPage) { + webPages.put(update.webpage.id, update.webpage); } else if (update instanceof TLRPC.TL_updateChannelTooLong) { if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + update.channel_id); + FileLog.d(update + " channelId = " + update.channel_id); } Integer channelPts = channelsPts.get(update.channel_id); if (channelPts == null) { @@ -6510,7 +6983,7 @@ public void run() { read_max.put(dialog_id, Math.max(value, update.max_id)); } else if (update instanceof TLRPC.TL_updateDeleteChannelMessages) { if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + update.channel_id); + FileLog.d(update + " channelId = " + update.channel_id); } ArrayList arrayList = deletedMessages.get(update.channel_id); if (arrayList == null) { @@ -6520,12 +6993,12 @@ public void run() { arrayList.addAll(update.messages); } else if (update instanceof TLRPC.TL_updateChannel) { if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + update.channel_id); + FileLog.d(update + " channelId = " + update.channel_id); } updatesOnMainThread.add(update); } else if (update instanceof TLRPC.TL_updateChannelMessageViews) { if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + update.channel_id); + FileLog.d(update + " channelId = " + update.channel_id); } TLRPC.TL_updateChannelMessageViews updateChannelMessageViews = (TLRPC.TL_updateChannelMessageViews) update; SparseIntArray array = channelViews.get(update.channel_id); @@ -6639,12 +7112,14 @@ public void run() { arr.add(obj); } else if (update instanceof TLRPC.TL_updateChannelPinnedMessage) { if (BuildVars.DEBUG_VERSION) { - FileLog.d("tmessages", update + " channelId = " + update.channel_id); + FileLog.d(update + " channelId = " + update.channel_id); } TLRPC.TL_updateChannelPinnedMessage updateChannelPinnedMessage = (TLRPC.TL_updateChannelPinnedMessage) update; MessagesStorage.getInstance().updateChannelPinnedMessage(update.channel_id, updateChannelPinnedMessage.id); } else if (update instanceof TLRPC.TL_updateReadFeaturedStickers) { updatesOnMainThread.add(update); + } else if (update instanceof TLRPC.TL_updatePhoneCall) { + updatesOnMainThread.add(update); } } if (!messages.isEmpty()) { @@ -6683,6 +7158,7 @@ public void run() { } if (!messagesArr.isEmpty()) { + StatsController.getInstance().incrementReceivedItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, messagesArr.size()); MessagesStorage.getInstance().putMessages(messagesArr, true, true, false, MediaController.getInstance().getAutodownloadMask()); } if (!editingMessages.isEmpty()) { @@ -6717,9 +7193,11 @@ public void run() { final TLRPC.User currentUser = getUser(update.user_id); if (update instanceof TLRPC.TL_updatePrivacy) { if (update.key instanceof TLRPC.TL_privacyKeyStatusTimestamp) { - ContactsController.getInstance().setPrivacyRules(update.rules, false); + ContactsController.getInstance().setPrivacyRules(update.rules, 0); } else if (update.key instanceof TLRPC.TL_privacyKeyChatInvite) { - ContactsController.getInstance().setPrivacyRules(update.rules, true); + ContactsController.getInstance().setPrivacyRules(update.rules, 1); + } else if (update.key instanceof TLRPC.TL_privacyKeyPhoneCall) { + ContactsController.getInstance().setPrivacyRules(update.rules, 2); } } else if (update instanceof TLRPC.TL_updateUserStatus) { if (update.status instanceof TLRPC.TL_userStatusRecently) { @@ -6756,6 +7234,44 @@ public void run() { toDbUser.last_name = update.last_name; toDbUser.username = update.username; dbUsers.add(toDbUser); + } else if (update instanceof TLRPC.TL_updateDialogPinned) { + TLRPC.TL_updateDialogPinned updateDialogPinned = (TLRPC.TL_updateDialogPinned) update; + long did; + if (updateDialogPinned.peer instanceof TLRPC.TL_peerUser) { + did = updateDialogPinned.peer.user_id; + } else if (updateDialogPinned.peer instanceof TLRPC.TL_peerChat) { + did = -updateDialogPinned.peer.chat_id; + } else { + did = -updateDialogPinned.peer.channel_id; + } + if (!pinDialog(did, updateDialogPinned.pinned, null, -1)) { + UserConfig.pinnedDialogsLoaded = false; + UserConfig.saveConfig(false); + loadPinnedDialogs(did, null); + } + } else if (update instanceof TLRPC.TL_updatePinnedDialogs) { + UserConfig.pinnedDialogsLoaded = false; + UserConfig.saveConfig(false); + ArrayList order; + if ((update.flags & 1) != 0) { + order = new ArrayList<>(); + ArrayList peers = ((TLRPC.TL_updatePinnedDialogs) update).order; + for (int b = 0; b < peers.size(); b++) { + long did; + TLRPC.Peer peer = peers.get(b); + if (peer.user_id != 0) { + did = peer.user_id; + } else if (peer.chat_id != 0) { + did = -peer.chat_id; + } else { + did = -peer.channel_id; + } + order.add(did); + } + } else { + order = null; + } + loadPinnedDialogs(0, order); } else if (update instanceof TLRPC.TL_updateUserPhoto) { if (currentUser != null) { currentUser.photo = update.photo; @@ -6795,9 +7311,10 @@ public void run() { dialog.notify_settings = update.notify_settings; } editor.putBoolean("silent_" + dialog_id, update.notify_settings.silent); - if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime()) { + int currentTime = ConnectionsManager.getInstance().getCurrentTime(); + if (update.notify_settings.mute_until > currentTime) { int until = 0; - if (update.notify_settings.mute_until > ConnectionsManager.getInstance().getCurrentTime() + 60 * 60 * 24 * 365) { + if (update.notify_settings.mute_until > currentTime + 60 * 60 * 24 * 365) { editor.putInt("notify2_" + dialog_id, 2); if (dialog != null) { dialog.notify_settings.mute_until = Integer.MAX_VALUE; @@ -6828,7 +7345,7 @@ public void run() { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - getChannelDifference(update.channel_id, 1, 0); + getChannelDifference(update.channel_id, 1, 0, null); } }); } else if (chat.left && dialog != null) { @@ -6842,7 +7359,7 @@ public void run() { } else if (update instanceof TLRPC.TL_updateStickerSets) { StickersQuery.loadStickers(update.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, false, true); } else if (update instanceof TLRPC.TL_updateStickerSetsOrder) { - StickersQuery.reorderStickers(update.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, update.order); + StickersQuery.reorderStickers(update.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, ((TLRPC.TL_updateStickerSetsOrder) update).order); } else if (update instanceof TLRPC.TL_updateNewStickerSet) { StickersQuery.addNewStickerSet(update.stickerset); } else if (update instanceof TLRPC.TL_updateSavedGifs) { @@ -6865,6 +7382,59 @@ public void run() { DraftQuery.saveDraft(did, update.draft, null, true); } else if (update instanceof TLRPC.TL_updateReadFeaturedStickers) { StickersQuery.markFaturedStickersAsRead(false); + } else if (update instanceof TLRPC.TL_updatePhoneCall) { + TLRPC.TL_updatePhoneCall upd = (TLRPC.TL_updatePhoneCall) update; + TLRPC.PhoneCall call = upd.phone_call; + VoIPService svc = VoIPService.getSharedInstance(); + if (BuildVars.DEBUG_VERSION) { + FileLog.d("Received call in update: "+call); + FileLog.d("call id "+call.id); + } + if (call instanceof TLRPC.TL_phoneCallRequested) { + if (call.date + callRingTimeout / 1000 < ConnectionsManager.getInstance().getCurrentTime()) { + if (BuildVars.DEBUG_VERSION) + FileLog.d("ignoring too old call"); + continue; + } + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + if (svc != null || VoIPService.callIShouldHavePutIntoIntent!=null || tm.getCallState() != TelephonyManager.CALL_STATE_IDLE) { + if (BuildVars.DEBUG_VERSION) { + FileLog.d("Auto-declining call "+call.id+" because there's already active one"); + } + TLRPC.TL_phone_discardCall req = new TLRPC.TL_phone_discardCall(); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = call.access_hash; + req.peer.id = call.id; + req.reason = new TLRPC.TL_phoneCallDiscardReasonBusy(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response != null) { + TLRPC.Updates updates = (TLRPC.Updates) response; + processUpdates(updates, false); + } + } + }); + continue; + } + if (BuildVars.DEBUG_VERSION) { + FileLog.d("Starting service for call "+call.id); + } + VoIPService.callIShouldHavePutIntoIntent = call; + Intent intent = new Intent(ApplicationLoader.applicationContext, VoIPService.class); + intent.putExtra("is_outgoing", false); + intent.putExtra("user_id", call.participant_id == UserConfig.getClientUserId() ? call.admin_id : call.participant_id); + ApplicationLoader.applicationContext.startService(intent); + } else { + if (svc != null && call!=null) { + svc.onCallUpdated(call); + }else if(VoIPService.callIShouldHavePutIntoIntent!=null){ + FileLog.d("Updated the call while the service is starting"); + if(call.id==VoIPService.callIShouldHavePutIntoIntent.id){ + VoIPService.callIShouldHavePutIntoIntent=call; + } + } + } } } if (editor != null) { @@ -7067,10 +7637,15 @@ public void run() { } if (deletedMessages.size() != 0) { for (int a = 0; a < deletedMessages.size(); a++) { - int key = deletedMessages.keyAt(a); - ArrayList arrayList = deletedMessages.get(key); - MessagesStorage.getInstance().markMessagesAsDeleted(arrayList, true, key); - MessagesStorage.getInstance().updateDialogsWithDeletedMessages(arrayList, true, key); + final int key = deletedMessages.keyAt(a); + final ArrayList arrayList = deletedMessages.get(key); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + ArrayList dialogIds = MessagesStorage.getInstance().markMessagesAsDeleted(arrayList, false, key); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(arrayList, dialogIds, false, key); + } + }); } } if (!tasks.isEmpty()) { @@ -7301,6 +7876,9 @@ private static String getRestrictionReason(String reason) { } private static void showCantOpenAlert(BaseFragment fragment, String reason) { + if (fragment == null || fragment.getParentActivity() == null) { + return; + } AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); @@ -7379,7 +7957,7 @@ public static void openByUserName(String username, final BaseFragment fragment, if (fragment.getParentActivity() == null) { return; } - final ProgressDialog progressDialog = new ProgressDialog(fragment.getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(fragment.getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -7395,7 +7973,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fragment.setVisibleDialog(null); if (error == null) { @@ -7413,7 +7991,7 @@ public void run() { try { Toast.makeText(fragment.getParentActivity(), LocaleController.getString("NoUsernameFound", R.string.NoUsernameFound), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -7428,7 +8006,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (fragment != null) { fragment.setVisibleDialog(null); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java index b48bd13a7e2..e6d0537ecc8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesStorage.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -23,6 +23,7 @@ import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.NativeByteBuffer; +import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; @@ -72,7 +73,7 @@ public static MessagesStorage getInstance() { public MessagesStorage() { storageQueue.setPriority(Thread.MAX_PRIORITY); - openDatabase(); + openDatabase(true); } public SQLiteDatabase getDatabase() { @@ -83,7 +84,7 @@ public DispatchQueue getStorageQueue() { return storageQueue; } - public void openDatabase() { + public void openDatabase(boolean first) { cacheFile = new File(ApplicationLoader.getFilesDirFixed(), "cache4.db"); boolean createTable = false; @@ -96,6 +97,7 @@ public void openDatabase() { database.executeFast("PRAGMA secure_delete = ON").stepThis().dispose(); database.executeFast("PRAGMA temp_store = 1").stepThis().dispose(); if (createTable) { + FileLog.e("create new database"); database.executeFast("CREATE TABLE messages_holes(uid INTEGER, start INTEGER, end INTEGER, PRIMARY KEY(uid, start));").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS uid_end_messages_holes ON messages_holes(uid, end);").stepThis().dispose(); @@ -115,7 +117,7 @@ public void openDatabase() { database.executeFast("CREATE TABLE user_phones_v6(uid INTEGER, phone TEXT, sphone TEXT, deleted INTEGER, PRIMARY KEY (uid, phone))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS sphone_deleted_idx_user_phones ON user_phones_v6(sphone, deleted);").stepThis().dispose(); - database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE dialogs(did INTEGER PRIMARY KEY, date INTEGER, unread_count INTEGER, last_mid INTEGER, inbox_max INTEGER, outbox_max INTEGER, last_mid_i INTEGER, unread_count_i INTEGER, pts INTEGER, date_i INTEGER, pinned INTEGER)").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS date_idx_dialogs ON dialogs(date);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS last_mid_idx_dialogs ON dialogs(last_mid);").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS unread_count_idx_dialogs ON dialogs(unread_count);").stepThis().dispose(); @@ -149,10 +151,13 @@ public void openDatabase() { database.executeFast("CREATE TABLE chat_hints(did INTEGER, type INTEGER, rating REAL, date INTEGER, PRIMARY KEY(did, type))").stepThis().dispose(); database.executeFast("CREATE INDEX IF NOT EXISTS chat_hints_rating_idx ON chat_hints(rating);").stepThis().dispose(); + database.executeFast("CREATE TABLE botcache(id TEXT PRIMARY KEY, date INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS botcache_date_idx ON botcache(date);").stepThis().dispose(); + database.executeFast("CREATE TABLE users_data(uid INTEGER PRIMARY KEY, about TEXT)").stepThis().dispose(); database.executeFast("CREATE TABLE users(uid INTEGER PRIMARY KEY, name TEXT, status INTEGER, data BLOB)").stepThis().dispose(); database.executeFast("CREATE TABLE chats(uid INTEGER PRIMARY KEY, name TEXT, data BLOB)").stepThis().dispose(); - database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB, in_seq_no INTEGER)").stepThis().dispose(); + database.executeFast("CREATE TABLE enc_chats(uid INTEGER PRIMARY KEY, user INTEGER, name TEXT, data BLOB, g BLOB, authkey BLOB, ttl INTEGER, layer INTEGER, seq_in INTEGER, seq_out INTEGER, use_count INTEGER, exchange_id INTEGER, key_date INTEGER, fprint INTEGER, fauthkey BLOB, khash BLOB, in_seq_no INTEGER, admin_id INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE channel_users_v2(did INTEGER, uid INTEGER, date INTEGER, data BLOB, PRIMARY KEY(did, uid))").stepThis().dispose(); database.executeFast("CREATE TABLE contacts(uid INTEGER PRIMARY KEY, mutual INTEGER)").stepThis().dispose(); database.executeFast("CREATE TABLE wallpapers(uid INTEGER PRIMARY KEY, data BLOB)").stepThis().dispose(); @@ -174,11 +179,16 @@ public void openDatabase() { database.executeFast("CREATE TABLE requested_holes(uid INTEGER, seq_out_start INTEGER, seq_out_end INTEGER, PRIMARY KEY (uid, seq_out_start, seq_out_end));").stepThis().dispose(); //version - database.executeFast("PRAGMA user_version = 37").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 41").stepThis().dispose(); //database.executeFast("CREATE TABLE secret_holes(uid INTEGER, seq_in INTEGER, seq_out INTEGER, data BLOB, PRIMARY KEY (uid, seq_in, seq_out));").stepThis().dispose(); //database.executeFast("CREATE TABLE attach_data(uid INTEGER, id INTEGER, data BLOB, PRIMARY KEY (uid, id))").stepThis().dispose(); } else { + int version = database.executeInt("PRAGMA user_version"); + FileLog.e("current db version = " + version); + if (version == 0) { + throw new Exception("malformed"); + } try { SQLiteCursor cursor = database.queryFinalized("SELECT seq, pts, date, qts, lsv, sg, pbytes FROM params WHERE id = 1"); if (cursor.next()) { @@ -199,27 +209,30 @@ public void openDatabase() { } cursor.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); try { database.executeFast("CREATE TABLE IF NOT EXISTS params(id INTEGER PRIMARY KEY, seq INTEGER, pts INTEGER, date INTEGER, qts INTEGER, lsv INTEGER, sg INTEGER, pbytes BLOB)").stepThis().dispose(); database.executeFast("INSERT INTO params VALUES(1, 0, 0, 0, 0, 0, 0, NULL)").stepThis().dispose(); } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } - int version = database.executeInt("PRAGMA user_version"); - if (version < 37) { + if (version < 41) { updateDbToLastVersion(version); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + if (first && e.getMessage().contains("malformed")) { + cleanupInternal(); + openDatabase(false); + } } loadUnreadMessages(); loadPendingTasks(); } - public void updateDbToLastVersion(final int currentVersion) { + private void updateDbToLastVersion(final int currentVersion) { storageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -257,42 +270,7 @@ public void run() { database.executeFast("UPDATE messages SET send_state = 2 WHERE mid < 0 AND send_state = 1").stepThis().dispose(); - storageQueue.postRunnable(new Runnable() { - @Override - public void run() { - ArrayList ids = new ArrayList<>(); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); - Map values = preferences.getAll(); - for (Map.Entry entry : values.entrySet()) { - String key = entry.getKey(); - if (key.startsWith("notify2_")) { - Integer value = (Integer) entry.getValue(); - if (value == 2) { - key = key.replace("notify2_", ""); - try { - ids.add(Integer.parseInt(key)); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } - try { - database.beginTransaction(); - SQLitePreparedStatement state = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); - for (Integer id : ids) { - state.requery(); - state.bindLong(1, id); - state.bindInteger(2, 1); - state.step(); - } - state.dispose(); - database.commitTransaction(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); + fixNotificationSettings(); database.executeFast("PRAGMA user_version = 4").stepThis().dispose(); version = 4; } @@ -356,10 +334,7 @@ public void run() { database.executeFast("PRAGMA user_version = 11").stepThis().dispose(); version = 11; } - if (version == 11) { - version = 12; - } - if (version == 12) { + if (version == 11 || version == 12) { database.executeFast("DROP INDEX IF EXISTS uid_mid_idx_media;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS mid_idx_media;").stepThis().dispose(); database.executeFast("DROP INDEX IF EXISTS uid_date_mid_idx_media;").stepThis().dispose(); @@ -468,7 +443,7 @@ public void run() { database.executeFast("PRAGMA user_version = 23").stepThis().dispose(); version = 23; } - if (version == 24) { + if (version == 23 || version == 24) { database.executeFast("DELETE FROM media_holes_v2 WHERE uid != 0 AND type >= 0 AND start IN (0, 1)").stepThis().dispose(); database.executeFast("PRAGMA user_version = 25").stepThis().dispose(); version = 25; @@ -483,11 +458,7 @@ public void run() { database.executeFast("PRAGMA user_version = 28").stepThis().dispose(); version = 28; } - if (version == 28) { - database.executeFast("PRAGMA user_version = 29").stepThis().dispose(); - version = 29; - } - if (version == 29) { + if (version == 28 || version == 29) { database.executeFast("DELETE FROM sent_files_v2 WHERE 1").stepThis().dispose(); database.executeFast("DELETE FROM download_queue WHERE 1").stepThis().dispose(); database.executeFast("PRAGMA user_version = 30").stepThis().dispose(); @@ -533,42 +504,67 @@ public void run() { if (version == 36) { database.executeFast("ALTER TABLE enc_chats ADD COLUMN in_seq_no INTEGER default 0").stepThis().dispose(); database.executeFast("PRAGMA user_version = 37").stepThis().dispose(); - //version = 37; + version = 37; + } + if (version == 37) { + database.executeFast("CREATE TABLE IF NOT EXISTS botcache(id TEXT PRIMARY KEY, date INTEGER, data BLOB)").stepThis().dispose(); + database.executeFast("CREATE INDEX IF NOT EXISTS botcache_date_idx ON botcache(date);").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 38").stepThis().dispose(); + version = 38; + } + if (version == 38) { + database.executeFast("ALTER TABLE dialogs ADD COLUMN pinned INTEGER default 0").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 39").stepThis().dispose(); + version = 39; + } + if (version == 39) { + database.executeFast("ALTER TABLE enc_chats ADD COLUMN admin_id INTEGER default 0").stepThis().dispose(); + database.executeFast("PRAGMA user_version = 40").stepThis().dispose(); + version = 40; + } + if (version == 40) { + fixNotificationSettings(); + database.executeFast("PRAGMA user_version = 41").stepThis().dispose(); + //version = 41; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } + private void cleanupInternal() { + lastDateValue = 0; + lastSeqValue = 0; + lastPtsValue = 0; + lastQtsValue = 0; + lastSecretVersion = 0; + + lastSavedSeq = 0; + lastSavedPts = 0; + lastSavedDate = 0; + lastSavedQts = 0; + + secretPBytes = null; + secretG = 0; + if (database != null) { + database.close(); + database = null; + } + if (cacheFile != null) { + cacheFile.delete(); + cacheFile = null; + } + } + public void cleanup(final boolean isLogin) { storageQueue.cleanupQueue(); storageQueue.postRunnable(new Runnable() { @Override public void run() { - lastDateValue = 0; - lastSeqValue = 0; - lastPtsValue = 0; - lastQtsValue = 0; - lastSecretVersion = 0; - - lastSavedSeq = 0; - lastSavedPts = 0; - lastSavedDate = 0; - lastSavedQts = 0; - - secretPBytes = null; - secretG = 0; - if (database != null) { - database.close(); - database = null; - } - if (cacheFile != null) { - cacheFile.delete(); - cacheFile = null; - } - openDatabase(); + cleanupInternal(); + openDatabase(false); if (isLogin) { Utilities.stageQueue.postRunnable(new Runnable() { @Override @@ -598,7 +594,63 @@ public void run() { state.dispose(); data.reuse(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } + } + }); + } + + private void fixNotificationSettings() { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + HashMap ids = new HashMap<>(); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); + Map values = preferences.getAll(); + for (Map.Entry entry : values.entrySet()) { + String key = entry.getKey(); + if (key.startsWith("notify2_")) { + Integer value = (Integer) entry.getValue(); + if (value == 2 || value == 3) { + key = key.replace("notify2_", ""); + long flags; + if (value == 2) { + flags = 1; + } else { + Integer time = (Integer) values.get("notifyuntil_" + key); + if (time != null) { + flags = ((long) time << 32) | 1; + } else { + flags = 1; + } + } + try { + ids.put(Long.parseLong(key), flags); + } catch (Exception e) { + e.printStackTrace(); + } + } else if (value == 3) { + + } + } + } + try { + database.beginTransaction(); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); + for (HashMap.Entry entry : ids.entrySet()) { + state.requery(); + state.bindLong(1, entry.getKey()); + state.bindLong(2, entry.getValue()); + state.step(); + } + state.dispose(); + database.commitTransaction(); + } catch (Exception e) { + FileLog.e(e); + } + } catch (Throwable e) { + FileLog.e(e); } } }); @@ -619,7 +671,7 @@ public void run() { state.step(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { data.reuse(); } @@ -635,7 +687,7 @@ public void run() { try { database.executeFast("DELETE FROM pending_tasks WHERE id = " + id).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -671,12 +723,13 @@ public void run() { Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - MessagesController.getInstance().getChannelDifference(channelId, newDialogType, taskId); + MessagesController.getInstance().getChannelDifference(channelId, newDialogType, taskId, null); } }); break; } - case 2: { + case 2: + case 5: { final TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = data.readInt64(false); dialog.top_message = data.readInt32(false); @@ -686,6 +739,10 @@ public void run() { dialog.last_message_date = data.readInt32(false); dialog.pts = data.readInt32(false); dialog.flags = data.readInt32(false); + if (type == 5) { + dialog.pinned = data.readBool(false); + dialog.pinnedNum = data.readInt32(false); + } final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -702,13 +759,57 @@ public void run() { SendMessagesHelper.getInstance().sendGame(peer, game, random_id, taskId); break; } + case 4: { + final long did = data.readInt64(false); + final boolean pin = data.readBool(false); + final TLRPC.InputPeer peer = TLRPC.InputPeer.TLdeserialize(data, data.readInt32(false), false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().pinDialog(did, pin, peer, taskId); + } + }); + break; + } + case 6: { + final int channelId = data.readInt32(false); + final int newDialogType = data.readInt32(false); + final TLRPC.InputChannel inputChannel = TLRPC.InputChannel.TLdeserialize(data, data.readInt32(false), false); + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().getChannelDifference(channelId, newDialogType, taskId, inputChannel); + } + }); + break; + } + case 7: { + final int channelId = data.readInt32(false); + int constructor = data.readInt32(false); + TLObject request = TLRPC.TL_messages_deleteMessages.TLdeserialize(data, constructor, false); + if (request == null) { + request = TLRPC.TL_channels_deleteMessages.TLdeserialize(data, constructor, false); + } + if (request == null) { + removePendingTask(taskId); + } else { + final TLObject finalRequest = request; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().deleteMessages(null, null, null, channelId, true, taskId, finalRequest); + } + }); + } + break; + } } data.reuse(); } } cursor.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -725,7 +826,7 @@ public void run() { state.step(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -751,7 +852,7 @@ public void run() { lastSavedDate = date; lastSavedQts = qts; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -764,7 +865,7 @@ public void run() { try { database.executeFast(String.format(Locale.US, "REPLACE INTO dialog_settings VALUES(%d, %d)", did, flags)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -782,8 +883,12 @@ public void run() { final HashMap pushDialogs = new HashMap<>(); SQLiteCursor cursor = database.queryFinalized("SELECT d.did, d.unread_count, s.flags FROM dialogs as d LEFT JOIN dialog_settings as s ON d.did = s.did WHERE d.unread_count != 0"); StringBuilder ids = new StringBuilder(); + int currentTime = ConnectionsManager.getInstance().getCurrentTime(); while (cursor.next()) { - if (cursor.isNull(2) || cursor.intValue(2) != 1) { + long flags = cursor.longValue(2); + boolean muted = (flags & 1) != 0; + int mutedUntil = (int) (flags >> 32); + if (cursor.isNull(2) || !muted || mutedUntil != 0 && mutedUntil < currentTime) { long did = cursor.longValue(0); int count = cursor.intValue(1); pushDialogs.put(did, count); @@ -842,7 +947,10 @@ public void run() { } try { - if (message.reply_to_msg_id != 0 && (message.action instanceof TLRPC.TL_messageActionPinMessage || message.action instanceof TLRPC.TL_messageActionGameScore)) { + if (message.reply_to_msg_id != 0 && ( + message.action instanceof TLRPC.TL_messageActionPinMessage || + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(6)) { data = cursor.byteBufferValue(6); if (data != null) { @@ -870,7 +978,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -938,7 +1046,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -966,7 +1074,7 @@ public void run() { state.dispose(); database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1007,7 +1115,7 @@ public void run() { } }); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1040,7 +1148,7 @@ public void run() { state.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1053,7 +1161,7 @@ public void run() { try { database.executeFast("DELETE FROM web_recent_v3 WHERE type = " + type).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1104,7 +1212,7 @@ public void run() { database.commitTransaction(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1133,7 +1241,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1164,7 +1272,7 @@ public void run() { MessagesController.getInstance().processLoadedBlockedUsers(ids, users, true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1177,7 +1285,7 @@ public void run() { try { database.executeFast("DELETE FROM blocked_users WHERE uid = " + id).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1204,7 +1312,7 @@ public void run() { state.dispose(); database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1248,7 +1356,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cursor.dispose(); AndroidUtilities.runOnUIThread(new Runnable() { @@ -1258,7 +1366,7 @@ public void run() { } }); markMessagesAsDeletedInternal(mids, channelId); - updateDialogsWithDeletedMessagesInternal(mids, channelId); + updateDialogsWithDeletedMessagesInternal(mids, null, channelId); FileLoader.getInstance().deleteFiles(filesToDelete, 0); if (!mids.isEmpty()) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -1269,7 +1377,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1322,7 +1430,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cursor.dispose(); FileLoader.getInstance().deleteFiles(filesToDelete, messagesOnly); @@ -1365,7 +1473,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cursor2.dispose(); @@ -1404,7 +1512,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1442,7 +1550,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1455,7 +1563,7 @@ public void run() { try { database.executeFast("DELETE FROM user_photos WHERE uid = " + uid).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1468,7 +1576,7 @@ public void run() { try { database.executeFast("DELETE FROM user_photos WHERE uid = " + uid + " AND id = " + pid).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1498,7 +1606,7 @@ public void run() { } state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1527,7 +1635,7 @@ public void run() { cursor.dispose(); MessagesController.getInstance().processLoadedDeleteTask(date, arr); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1602,7 +1710,7 @@ public void run() { MessagesController.getInstance().didAddedNewTask(minDate, messages); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1686,7 +1794,7 @@ private void updateDialogsWithReadMessagesInternal(final ArrayList mess MessagesController.getInstance().processDialogsUpdateRead(dialogsToUpdate); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1747,7 +1855,7 @@ public void run() { data.reuse(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1781,10 +1889,79 @@ public void run() { database.commitTransaction(); loadChatInfo(channel_id, null, false, true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } + } + }); + } + + public void saveBotCache(final String key, final TLObject result) { + if (result == null || TextUtils.isEmpty(key)) { + return; + } + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + if (result instanceof TLRPC.TL_messages_botCallbackAnswer) { + currentDate += ((TLRPC.TL_messages_botCallbackAnswer) result).cache_time; + } else if (result instanceof TLRPC.TL_messages_botResults) { + currentDate += ((TLRPC.TL_messages_botResults) result).cache_time; + } + SQLitePreparedStatement state = database.executeFast("REPLACE INTO botcache VALUES(?, ?, ?)"); + NativeByteBuffer data = new NativeByteBuffer(result.getObjectSize()); + result.serializeToStream(data); + state.bindString(1, key); + state.bindInteger(2, currentDate); + state.bindByteBuffer(3, data); + state.step(); + state.dispose(); + data.reuse(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void getBotCache(final String key, final RequestDelegate requestDelegate) { + if (key == null || requestDelegate == null) { + return; + } + final int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + TLObject result = null; + try { + database.executeFast("DELETE FROM botcache WHERE date < " + currentDate).stepThis().dispose(); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT data FROM botcache WHERE id = '%s'", key)); + if (cursor.next()) { + try { + NativeByteBuffer data = cursor.byteBufferValue(0); + if (data != null) { + int constructor = data.readInt32(false); + if (constructor == TLRPC.TL_messages_botCallbackAnswer.constructor) { + result = TLRPC.TL_messages_botCallbackAnswer.TLdeserialize(data, constructor, false); + } else { + result = TLRPC.TL_messages_botResults.TLdeserialize(data, constructor, false); + } + data.reuse(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + cursor.dispose(); + } catch (Exception e) { + FileLog.e(e); + } finally { + requestDelegate.run(result, null); } } }); + } public void updateChatInfo(final TLRPC.ChatFull info, final boolean ifExist) { @@ -1811,7 +1988,7 @@ public void run() { data.reuse(); if (info instanceof TLRPC.TL_channelFull) { - SQLiteCursor cursor = database.queryFinalized("SELECT date, pts, last_mid, inbox_max, outbox_max FROM dialogs WHERE did = " + (-info.id)); + SQLiteCursor cursor = database.queryFinalized("SELECT date, pts, last_mid, inbox_max, outbox_max, pinned FROM dialogs WHERE did = " + (-info.id)); if (cursor.next()) { int inbox_max = cursor.intValue(3); if (inbox_max <= info.read_inbox_max_id) { @@ -1823,8 +2000,9 @@ public void run() { int pts = cursor.intValue(1); long last_mid = cursor.longValue(2); int outbox_max = cursor.intValue(4); + int pinned = cursor.intValue(5); - state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.bindLong(1, -info.id); state.bindInteger(2, dialog_date); state.bindInteger(3, info.unread_count); @@ -1835,6 +2013,7 @@ public void run() { state.bindInteger(8, 0); state.bindInteger(9, pts); state.bindInteger(10, 0); + state.bindInteger(11, pinned); state.step(); state.dispose(); } @@ -1842,7 +2021,7 @@ public void run() { cursor.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1888,7 +2067,7 @@ public void run() { data.reuse(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1973,7 +2152,7 @@ public void run() { data.reuse(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2002,7 +2181,7 @@ public void run() { semaphore.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (semaphore != null) { semaphore.release(); @@ -2013,7 +2192,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return result[0]; } @@ -2080,7 +2259,7 @@ public void run() { info.participants.participants.add(chatChannelParticipant); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } cursor.dispose(); @@ -2103,7 +2282,7 @@ public void run() { pinnedMessageObject = MessagesQuery.loadPinnedMessage(chat_id, info.pinned_msg_id, false); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { MessagesController.getInstance().processChatInfo(chat_id, info, loadedUsers, true, force, byChannelUsers, pinnedMessageObject); if (semaphore != null) { @@ -2157,7 +2336,7 @@ public void run() { database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2187,7 +2366,7 @@ public void run() { state.dispose(); database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2204,7 +2383,7 @@ public void run() { String ids = TextUtils.join(",", uids); database.executeFast("DELETE FROM contacts WHERE uid IN(" + ids + ")").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2225,7 +2404,7 @@ public void run() { database.executeFast(String.format(Locale.US, "UPDATE user_phones_v6 SET deleted = 1 WHERE sphone IN(%s)", deletes)).stepThis().dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2262,7 +2441,7 @@ public void run() { state2.dispose(); database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2282,6 +2461,12 @@ public void run() { contact = new ContactsController.Contact(); contact.first_name = cursor.stringValue(1); contact.last_name = cursor.stringValue(2); + if (contact.first_name == null) { + contact.first_name = ""; + } + if (contact.last_name == null) { + contact.last_name = ""; + } contact.id = uid; contactHashMap.put(uid, contact); } @@ -2304,7 +2489,7 @@ public void run() { cursor.dispose(); } catch (Exception e) { contactHashMap.clear(); - FileLog.e("tmessages", e); + FileLog.e(e); } ContactsController.getInstance().performSyncPhoneBook(contactHashMap, true, true, false, false); } @@ -2339,7 +2524,7 @@ public void run() { } catch (Exception e) { contacts.clear(); users.clear(); - FileLog.e("tmessages", e); + FileLog.e(e); } ContactsController.getInstance().processLoadedContacts(contacts, users, 1); } @@ -2450,7 +2635,7 @@ public void run() { SendMessagesHelper.getInstance().processUnsentMessages(messages, users, chats, encryptedChats); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2469,7 +2654,7 @@ public void run() { result[0] = true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -2481,12 +2666,12 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return result[0]; } - public void getMessages(final long dialog_id, final int count, final int max_id, final int minDate, final int classGuid, final int load_type, final boolean isChannel, final int loadIndex) { + public void getMessages(final long dialog_id, final int count, final int max_id, final int offset_date, final int minDate, final int classGuid, final int load_type, final boolean isChannel, final int loadIndex) { storageQueue.postRunnable(new Runnable() { @Override public void run() { @@ -2500,6 +2685,7 @@ public void run() { int max_unread_date = 0; long messageMaxId = max_id; int max_id_query = max_id; + int max_id_override = max_id; int channelId = 0; if (isChannel) { channelId = -(int) dialog_id; @@ -2508,7 +2694,7 @@ public void run() { messageMaxId |= ((long) channelId) << 32; } boolean isEnd = false; - int num = dialog_id == 777000 ? 4 : 1; + int num = dialog_id == 777000 ? 10 : 1; try { ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); @@ -2519,7 +2705,15 @@ public void run() { SQLiteCursor cursor; int lower_id = (int) dialog_id; if (lower_id != 0) { - if (load_type != 1 && load_type != 3 && minDate == 0) { + if (load_type == 3 && minDate == 0) { + cursor = database.queryFinalized("SELECT inbox_max, unread_count, date FROM dialogs WHERE did = " + dialog_id); + if (cursor.next()) { + min_unread_id = cursor.intValue(0) + 1; + count_unread = cursor.intValue(1); + max_unread_date = cursor.intValue(2); + } + cursor.dispose(); + } else if (load_type != 1 && load_type != 3 && load_type != 4 && minDate == 0) { if (load_type == 2) { cursor = database.queryFinalized("SELECT inbox_max, unread_count, date FROM dialogs WHERE did = " + dialog_id); if (cursor.next()) { @@ -2618,13 +2812,58 @@ public void run() { cursor.dispose(); } - if (load_type == 3 || queryFromServer && load_type == 2) { + if (load_type == 3 || load_type == 4 || queryFromServer && load_type == 2) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND mid > 0", dialog_id)); if (cursor.next()) { last_message_id = cursor.intValue(0); } cursor.dispose(); + if (load_type == 4 && offset_date != 0) { + int startMid; + int endMid; + + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid) FROM messages WHERE uid = %d AND date <= %d AND mid > 0", dialog_id, offset_date)); + if (cursor.next()) { + startMid = cursor.intValue(0); + } else { + startMid = -1; + } + cursor.dispose(); + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND date >= %d AND mid > 0", dialog_id, offset_date)); + if (cursor.next()) { + endMid = cursor.intValue(0); + } else { + endMid = -1; + } + cursor.dispose(); + if (startMid != -1 && endMid != -1) { + if (startMid == endMid) { + max_id_query = startMid; + } else { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialog_id, startMid, startMid)); + if (cursor.next()) { + startMid = -1; + } + cursor.dispose(); + if (startMid != -1) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start <= %d AND end > %d", dialog_id, endMid, endMid)); + if (cursor.next()) { + endMid = -1; + } + cursor.dispose(); + if (endMid != -1) { + max_id_override = endMid; + messageMaxId = max_id_query = endMid; + if (messageMaxId != 0 && channelId != 0) { + messageMaxId |= ((long) channelId) << 32; + } + } + } + } + } + } + boolean containMessage = max_id_query != 0; if (containMessage) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT start FROM messages_holes WHERE uid = %d AND start < %d AND end > %d", dialog_id, max_id_query, max_id_query)); @@ -2731,7 +2970,41 @@ public void run() { } } else { isEnd = true; - if (load_type == 1) { + + if (load_type == 3 && minDate == 0) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid < 0", dialog_id)); + if (cursor.next()) { + min_unread_id = cursor.intValue(0); + } + cursor.dispose(); + + int min_unread_id2 = 0; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT max(mid), max(date) FROM messages WHERE uid = %d AND out = 0 AND read_state IN(0,2) AND mid < 0", dialog_id)); + if (cursor.next()) { + min_unread_id2 = cursor.intValue(0); + max_unread_date = cursor.intValue(1); + } + cursor.dispose(); + if (min_unread_id2 != 0) { + min_unread_id = min_unread_id2; + cursor = database.queryFinalized(String.format(Locale.US, "SELECT COUNT(*) FROM messages WHERE uid = %d AND mid <= %d AND out = 0 AND read_state IN(0,2)", dialog_id, min_unread_id2)); + if (cursor.next()) { + count_unread = cursor.intValue(0); + } + cursor.dispose(); + } + } + + if (load_type == 3 || load_type == 4) { + cursor = database.queryFinalized(String.format(Locale.US, "SELECT min(mid) FROM messages WHERE uid = %d AND mid < 0", dialog_id)); + if (cursor.next()) { + last_message_id = cursor.intValue(0); + } + cursor.dispose(); + + cursor = database.queryFinalized(String.format(Locale.US, "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid <= %d ORDER BY m.mid DESC LIMIT %d) UNION " + + "SELECT * FROM (SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid > %d ORDER BY m.mid ASC LIMIT %d)", dialog_id, messageMaxId, count_query / 2, dialog_id, messageMaxId, count_query / 2)); + } else if (load_type == 1) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT m.read_state, m.data, m.send_state, m.mid, m.date, r.random_id, m.replydata, m.media, m.ttl FROM messages as m LEFT JOIN randoms as r ON r.mid = m.mid WHERE m.uid = %d AND m.mid < %d ORDER BY m.mid DESC LIMIT %d", dialog_id, max_id, count_query)); } else if (minDate != 0) { if (max_id != 0) { @@ -2850,7 +3123,7 @@ public void run() { } cursor2.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2884,20 +3157,21 @@ public int compare(TLRPC.Message lhs, TLRPC.Message rhs) { } }); - if ((load_type == 3 || load_type == 2 && queryFromServer) && !res.messages.isEmpty()) { - int minId = res.messages.get(res.messages.size() - 1).id; - int maxId = res.messages.get(0).id; - if (!(minId <= max_id_query && maxId >= max_id_query)) { - replyMessages.clear(); - usersToLoad.clear(); - chatsToLoad.clear(); + if (lower_id != 0) { + if ((load_type == 3 || load_type == 4 || load_type == 2 && queryFromServer) && !res.messages.isEmpty()) { + int minId = res.messages.get(res.messages.size() - 1).id; + int maxId = res.messages.get(0).id; + if (!(minId <= max_id_query && maxId >= max_id_query)) { + replyMessages.clear(); + usersToLoad.clear(); + chatsToLoad.clear(); + res.messages.clear(); + } + } + if ((load_type == 4 || load_type == 3) && res.messages.size() == 1) { res.messages.clear(); } } - if (load_type == 3 && res.messages.size() == 1) { - res.messages.clear(); - } - if (!replyMessages.isEmpty()) { if (!replyMessageOwners.isEmpty()) { cursor = database.queryFinalized(String.format(Locale.US, "SELECT data, mid, date FROM messages WHERE mid IN(%s)", TextUtils.join(",", replyMessages))); @@ -2955,9 +3229,9 @@ public int compare(TLRPC.Message lhs, TLRPC.Message rhs) { res.messages.clear(); res.chats.clear(); res.users.clear(); - FileLog.e("tmessages", e); + FileLog.e(e); } finally { - MessagesController.getInstance().processLoadedMessages(res, dialog_id, count_query, max_id, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer); + MessagesController.getInstance().processLoadedMessages(res, dialog_id, count_query, max_id_override, offset_date, true, classGuid, min_unread_id, last_message_id, count_unread, max_unread_date, load_type, isChannel, isEnd, loadIndex, queryFromServer); } } }); @@ -2971,7 +3245,7 @@ public void run() { try { database.beginTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2979,7 +3253,7 @@ public void run() { try { database.beginTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2992,7 +3266,7 @@ public void run() { try { database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3000,13 +3274,13 @@ public void run() { try { database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } public TLObject getSentFile(final String path, final int type) { - if (path == null) { + if (path == null || path.endsWith("attheme")) { return null; } final Semaphore semaphore = new Semaphore(0); @@ -3033,7 +3307,7 @@ public void run() { cursor.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { semaphore.release(); } @@ -3042,7 +3316,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return !result.isEmpty() ? result.get(0) : null; } @@ -3082,7 +3356,7 @@ public void run() { data.reuse(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -3109,7 +3383,7 @@ public void run() { state.bindInteger(5, chat.id); state.step(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -3133,7 +3407,7 @@ public void run() { state.bindInteger(2, chat.id); state.step(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -3157,7 +3431,7 @@ public void run() { state.bindInteger(2, chat.id); state.step(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -3180,7 +3454,7 @@ public void run() { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } - state = database.executeFast("UPDATE enc_chats SET data = ?, g = ?, authkey = ?, ttl = ?, layer = ?, seq_in = ?, seq_out = ?, use_count = ?, exchange_id = ?, key_date = ?, fprint = ?, fauthkey = ?, khash = ?, in_seq_no = ? WHERE uid = ?"); + state = database.executeFast("UPDATE enc_chats SET data = ?, g = ?, authkey = ?, ttl = ?, layer = ?, seq_in = ?, seq_out = ?, use_count = ?, exchange_id = ?, key_date = ?, fprint = ?, fauthkey = ?, khash = ?, in_seq_no = ?, admin_id = ? WHERE uid = ?"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); @@ -3213,7 +3487,8 @@ public void run() { state.bindByteBuffer(12, data4); state.bindByteBuffer(13, data5); state.bindInteger(14, chat.in_seq_no); - state.bindInteger(15, chat.id); + state.bindInteger(15, chat.admin_id); + state.bindInteger(16, chat.id); state.step(); data.reuse(); @@ -3222,7 +3497,7 @@ public void run() { data4.reuse(); data5.reuse(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -3243,7 +3518,7 @@ public void run() { result[0] = cursor.next(); cursor.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { semaphore.release(); } @@ -3252,7 +3527,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return result[0]; } @@ -3268,7 +3543,7 @@ public void run() { result[0] = cursor.next(); cursor.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { semaphore.release(); } @@ -3277,7 +3552,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return result[0]; } @@ -3302,7 +3577,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { semaphore.release(); } @@ -3321,7 +3596,7 @@ public void run() { if ((chat.key_hash == null || chat.key_hash.length < 16) && chat.auth_key != null) { chat.key_hash = AndroidUtilities.calcAuthKeyHash(chat.auth_key); } - SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_chats VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + SQLitePreparedStatement state = database.executeFast("REPLACE INTO enc_chats VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); NativeByteBuffer data = new NativeByteBuffer(chat.getObjectSize()); NativeByteBuffer data2 = new NativeByteBuffer(chat.a_or_b != null ? chat.a_or_b.length : 1); NativeByteBuffer data3 = new NativeByteBuffer(chat.auth_key != null ? chat.auth_key.length : 1); @@ -3358,6 +3633,7 @@ public void run() { state.bindByteBuffer(15, data4); state.bindByteBuffer(16, data5); state.bindInteger(17, chat.in_seq_no); + state.bindInteger(18, chat.admin_id); state.step(); state.dispose(); @@ -3367,7 +3643,7 @@ public void run() { data4.reuse(); data5.reuse(); if (dialog != null) { - state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state.bindLong(1, dialog.id); state.bindInteger(2, dialog.last_message_date); state.bindInteger(3, dialog.unread_count); @@ -3378,11 +3654,12 @@ public void run() { state.bindInteger(8, 0); state.bindInteger(9, dialog.pts); state.bindInteger(10, 0); + state.bindInteger(11, dialog.pinnedNum); state.step(); state.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3440,7 +3717,7 @@ private void putUsersInternal(ArrayList users) throws Exception { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } cursor.dispose(); @@ -3502,7 +3779,7 @@ private void putChatsInternal(ArrayList chats) throws Exception { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } cursor.dispose(); @@ -3543,7 +3820,7 @@ public void getUsersInternal(String usersToLoad, ArrayList result) t } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } cursor.dispose(); @@ -3565,7 +3842,7 @@ public void getChatsInternal(String chatsToLoad, ArrayList result) t } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } cursor.dispose(); @@ -3575,7 +3852,7 @@ public void getEncryptedChatsInternal(String chatsToLoad, ArrayList users, final A database.commitTransaction(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -3662,7 +3943,7 @@ public void run() { database.executeFast(String.format(Locale.US, "DELETE FROM download_queue WHERE uid = %d AND type = %d", id, type)).stepThis().dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3679,7 +3960,7 @@ public void run() { database.executeFast(String.format(Locale.US, "DELETE FROM download_queue WHERE type = %d", type)).stepThis().dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3717,7 +3998,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3743,35 +4024,36 @@ public void putWebPages(final HashMap webPages) { @Override public void run() { try { - String ids = TextUtils.join(",", webPages.keySet()); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid FROM webpage_pending WHERE id IN (%s)", ids)); - ArrayList mids = new ArrayList<>(); - while (cursor.next()) { - mids.add(cursor.longValue(0)); - } - cursor.dispose(); - - if (mids.isEmpty()) { - return; - } final ArrayList messages = new ArrayList<>(); - cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages WHERE mid IN (%s)", TextUtils.join(",", mids))); - while (cursor.next()) { - int mid = cursor.intValue(0); - NativeByteBuffer data = cursor.byteBufferValue(1); - if (data != null) { - TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); - data.reuse(); - if (message.media instanceof TLRPC.TL_messageMediaWebPage) { - message.id = mid; - message.media.webpage = webPages.get(message.media.webpage.id); - messages.add(message); + for (HashMap.Entry entry : webPages.entrySet()) { + SQLiteCursor cursor = database.queryFinalized("SELECT mid FROM webpage_pending WHERE id = " + entry.getKey()); + ArrayList mids = new ArrayList<>(); + while (cursor.next()) { + mids.add(cursor.longValue(0)); + } + cursor.dispose(); + + if (mids.isEmpty()) { + continue; + } + cursor = database.queryFinalized(String.format(Locale.US, "SELECT mid, data FROM messages WHERE mid IN (%s)", TextUtils.join(",", mids))); + while (cursor.next()) { + int mid = cursor.intValue(0); + NativeByteBuffer data = cursor.byteBufferValue(1); + if (data != null) { + TLRPC.Message message = TLRPC.Message.TLdeserialize(data, data.readInt32(false), false); + data.reuse(); + if (message.media instanceof TLRPC.TL_messageMediaWebPage) { + message.id = mid; + message.media.webpage = entry.getValue(); + messages.add(message); + } } } + cursor.dispose(); } - cursor.dispose(); - database.executeFast(String.format(Locale.US, "DELETE FROM webpage_pending WHERE id IN (%s)", ids)).stepThis().dispose(); + //database.executeFast(String.format(Locale.US, "DELETE FROM webpage_pending WHERE id IN (%s)", ids)).stepThis().dispose(); if (messages.isEmpty()) { return; @@ -3815,7 +4097,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3828,13 +4110,18 @@ public void run() { try { boolean checkInvite = false; final long did = -channel_id; - if (newDialogType != 0) { - SQLiteCursor cursor = database.queryFinalized("SELECT pts FROM dialogs WHERE did = " + did); - if (!cursor.next()) { + int pinned = 0; + + SQLiteCursor cursor = database.queryFinalized("SELECT pts, pinned FROM dialogs WHERE did = " + did); + if (!cursor.next()) { + if (newDialogType != 0) { checkInvite = true; } - cursor.dispose(); + } else { + pinned = cursor.intValue(1); } + cursor.dispose(); + database.executeFast("DELETE FROM messages WHERE uid = " + did).stepThis().dispose(); database.executeFast("DELETE FROM bot_keyboard WHERE uid = " + did).stepThis().dispose(); @@ -3858,11 +4145,13 @@ public void run() { dialog.read_outbox_max_id = difference.read_outbox_max_id; dialog.unread_count = difference.unread_count; dialog.notify_settings = null; + dialog.pinned = pinned != 0; + dialog.pinnedNum = pinned; dialog.pts = difference.pts; dialogs.dialogs.add(dialog); - putDialogsInternal(dialogs); + putDialogsInternal(dialogs, false); - MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), false, channel_id); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), null, false, channel_id); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -3877,7 +4166,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3909,7 +4198,7 @@ public void run() { state.dispose(); database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4133,7 +4422,7 @@ private void putMessagesInternal(final ArrayList messages, final state2.step(); } - if (message.media instanceof TLRPC.TL_messageMediaWebPage && message.media.webpage instanceof TLRPC.TL_webPagePending) { + if (message.media instanceof TLRPC.TL_messageMediaWebPage) { state5.requery(); state5.bindLong(1, message.media.webpage.id); state5.bindLong(2, messageId); @@ -4206,7 +4495,7 @@ private void putMessagesInternal(final ArrayList messages, final state4.dispose(); state5.dispose(); - state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + state = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); HashMap dids = new HashMap<>(); dids.putAll(messagesMap); @@ -4222,13 +4511,14 @@ private void putMessagesInternal(final ArrayList messages, final channelId = message.to_id.channel_id; } - SQLiteCursor cursor = database.queryFinalized("SELECT date, unread_count, pts, last_mid, inbox_max, outbox_max FROM dialogs WHERE did = " + key); + SQLiteCursor cursor = database.queryFinalized("SELECT date, unread_count, pts, last_mid, inbox_max, outbox_max, pinned FROM dialogs WHERE did = " + key); int dialog_date = 0; int last_mid = 0; int old_unread_count = 0; int pts = channelId != 0 ? 1 : 0; int inbox_max = 0; int outbox_max = 0; + int pinned = 0; if (cursor.next()) { dialog_date = cursor.intValue(0); old_unread_count = cursor.intValue(1); @@ -4236,6 +4526,7 @@ private void putMessagesInternal(final ArrayList messages, final last_mid = cursor.intValue(3); inbox_max = cursor.intValue(4); outbox_max = cursor.intValue(5); + pinned = cursor.intValue(6); } else if (channelId != 0) { MessagesController.getInstance().checkChannelInviter(channelId); } @@ -4273,6 +4564,7 @@ private void putMessagesInternal(final ArrayList messages, final state.bindInteger(8, 0); state.bindInteger(9, pts); state.bindInteger(10, 0); + state.bindInteger(11, pinned); state.step(); } state.dispose(); @@ -4317,7 +4609,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -4352,7 +4644,7 @@ public void run() { } database.executeFast("UPDATE messages SET send_state = 2 WHERE mid = " + messageId).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4365,7 +4657,7 @@ public void run() { try { } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4378,7 +4670,7 @@ public void run() { try { database.executeFast("DELETE FROM secret_holes WHERE uid = " + enc_id).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4406,7 +4698,7 @@ public void run() { state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4425,7 +4717,7 @@ public void run() { state.step(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4443,7 +4735,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i _oldId = cursor.intValue(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -4466,7 +4758,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i did = cursor.longValue(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -4484,7 +4776,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i state.bindLong(2, newMessageId); state.step(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -4504,7 +4796,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i database.executeFast(String.format(Locale.US, "DELETE FROM messages WHERE mid = %d", oldMessageId)).stepThis().dispose(); database.executeFast(String.format(Locale.US, "DELETE FROM messages_seq WHERE mid = %d", oldMessageId)).stepThis().dispose(); } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } finally { if (state != null) { @@ -4522,7 +4814,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i try { database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid = %d", oldMessageId)).stepThis().dispose(); } catch (Exception e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } finally { if (state != null) { @@ -4537,7 +4829,7 @@ private long[] updateMessageStateAndIdInternal(long random_id, Integer _oldId, i state.bindLong(2, oldMessageId); state.step(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (state != null) { state.dispose(); @@ -4626,7 +4918,7 @@ private void updateUsersInternal(final ArrayList users, final boolea } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -4675,7 +4967,7 @@ private void markMessagesAsReadInternal(SparseArray inbox, SparseArray messages, int channelId) { + private ArrayList markMessagesAsDeletedInternal(final ArrayList messages, int channelId) { try { String ids; - int unread_count = 0; + ArrayList dialogsIds = new ArrayList<>(); + HashMap dialogsToUpdate = new HashMap<>(); if (channelId != 0) { StringBuilder builder = new StringBuilder(messages.size()); for (int a = 0; a < messages.size(); a++) { @@ -4759,13 +5052,23 @@ private void markMessagesAsDeletedInternal(final ArrayList messages, in } else { ids = TextUtils.join(",", messages); } - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state FROM messages WHERE mid IN(%s)", ids)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT uid, data, read_state, out FROM messages WHERE mid IN(%s)", ids)); ArrayList filesToDelete = new ArrayList<>(); + int currentUser = UserConfig.getClientUserId(); try { while (cursor.next()) { long did = cursor.longValue(0); - if (channelId != 0 && cursor.intValue(2) == 0) { + if (did == currentUser) { + continue; + } + int read_state = cursor.intValue(2); + if ((read_state == 0 || read_state == 2) && cursor.intValue(3) == 0) { + Integer unread_count = dialogsToUpdate.get(did); + if (unread_count == null) { + unread_count = 0; + } unread_count++; + dialogsToUpdate.put(did, unread_count); } if ((int) did != 0) { continue; @@ -4796,17 +5099,18 @@ private void markMessagesAsDeletedInternal(final ArrayList messages, in } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cursor.dispose(); FileLoader.getInstance().deleteFiles(filesToDelete, 0); - if (channelId != 0 && unread_count != 0) { - long did = -channelId; - SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = ((SELECT unread_count FROM dialogs WHERE did = ?) - ?) WHERE did = ?"); + for (HashMap.Entry entry : dialogsToUpdate.entrySet()) { + Long did = entry.getKey(); + dialogsIds.add(did); + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET unread_count = max(0, ((SELECT unread_count FROM dialogs WHERE did = ?) - ?)) WHERE did = ?"); state.requery(); state.bindLong(1, did); - state.bindInteger(2, unread_count); + state.bindInteger(2, entry.getValue()); state.bindLong(3, did); state.step(); state.dispose(); @@ -4818,23 +5122,25 @@ private void markMessagesAsDeletedInternal(final ArrayList messages, in database.executeFast(String.format(Locale.US, "DELETE FROM media_v2 WHERE mid IN(%s)", ids)).stepThis().dispose(); database.executeFast("DELETE FROM media_counts_v2 WHERE 1").stepThis().dispose(); BotQuery.clearBotKeyboard(0, messages); + return dialogsIds; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } + return null; } - private void updateDialogsWithDeletedMessagesInternal(final ArrayList messages, int channelId) { + private void updateDialogsWithDeletedMessagesInternal(final ArrayList messages, ArrayList additionalDialogsToUpdate, int channelId) { if (Thread.currentThread().getId() != storageQueue.getId()) { throw new RuntimeException("wrong db thread"); } try { String ids; + ArrayList dialogsToUpdate = new ArrayList<>(); if (!messages.isEmpty()) { SQLitePreparedStatement state; - ArrayList dialogsToUpdate = new ArrayList<>(); if (channelId != 0) { dialogsToUpdate.add((long) -channelId); - state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ? )) WHERE did = ?"); + state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ?)) WHERE did = ?"); } else { ids = TextUtils.join(",", messages); SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did FROM dialogs WHERE last_mid IN(%s)", ids)); @@ -4842,7 +5148,7 @@ private void updateDialogsWithDeletedMessagesInternal(final ArrayList m dialogsToUpdate.add(cursor.longValue(0)); } cursor.dispose(); - state = database.executeFast("UPDATE dialogs SET unread_count = 0, unread_count_i = 0, last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ? AND date != 0)) WHERE did = ?"); + state = database.executeFast("UPDATE dialogs SET last_mid = (SELECT mid FROM messages WHERE uid = ? AND date = (SELECT MAX(date) FROM messages WHERE uid = ? AND date != 0)) WHERE did = ?"); } database.beginTransaction(); for (int a = 0; a < dialogsToUpdate.size(); a++) { @@ -4855,18 +5161,25 @@ private void updateDialogsWithDeletedMessagesInternal(final ArrayList m } state.dispose(); database.commitTransaction(); - - ids = TextUtils.join(",", dialogsToUpdate); } else { - ids = "" + (-channelId); + dialogsToUpdate.add((long)-channelId); + } + if (additionalDialogsToUpdate != null) { + for (int a = 0; a < additionalDialogsToUpdate.size(); a++) { + Long did = additionalDialogsToUpdate.get(a); + if (!dialogsToUpdate.contains(did)) { + dialogsToUpdate.add(did); + } + } } + ids = TextUtils.join(",", dialogsToUpdate); TLRPC.messages_Dialogs dialogs = new TLRPC.messages_Dialogs(); ArrayList encryptedChats = new ArrayList<>(); ArrayList usersToLoad = new ArrayList<>(); ArrayList chatsToLoad = new ArrayList<>(); ArrayList encryptedToLoad = new ArrayList<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", ids)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, m.date, d.pts, d.inbox_max, d.outbox_max, d.pinned FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid WHERE d.did IN(%s)", ids)); while (cursor.next()) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = cursor.longValue(0); @@ -4877,6 +5190,8 @@ private void updateDialogsWithDeletedMessagesInternal(final ArrayList m dialog.last_message_date = cursor.intValue(3); dialog.pts = cursor.intValue(9); dialog.flags = channelId == 0 ? 0 : 1; + dialog.pinnedNum = cursor.intValue(12); + dialog.pinned = dialog.pinnedNum != 0; dialogs.dialogs.add(dialog); @@ -4939,11 +5254,11 @@ private void updateDialogsWithDeletedMessagesInternal(final ArrayList m MessagesController.getInstance().processDialogsUpdate(dialogs, encryptedChats); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - public void updateDialogsWithDeletedMessages(final ArrayList messages, boolean useQueue, final int channelId) { + public void updateDialogsWithDeletedMessages(final ArrayList messages, final ArrayList additionalDialogsToUpdate, boolean useQueue, final int channelId) { if (messages.isEmpty() && channelId == 0) { return; } @@ -4951,17 +5266,17 @@ public void updateDialogsWithDeletedMessages(final ArrayList messages, storageQueue.postRunnable(new Runnable() { @Override public void run() { - updateDialogsWithDeletedMessagesInternal(messages, channelId); + updateDialogsWithDeletedMessagesInternal(messages, additionalDialogsToUpdate, channelId); } }); } else { - updateDialogsWithDeletedMessagesInternal(messages, channelId); + updateDialogsWithDeletedMessagesInternal(messages, additionalDialogsToUpdate, channelId); } } - public void markMessagesAsDeleted(final ArrayList messages, boolean useQueue, final int channelId) { + public ArrayList markMessagesAsDeleted(final ArrayList messages, boolean useQueue, final int channelId) { if (messages.isEmpty()) { - return; + return null; } if (useQueue) { storageQueue.postRunnable(new Runnable() { @@ -4971,8 +5286,9 @@ public void run() { } }); } else { - markMessagesAsDeletedInternal(messages, channelId); + return markMessagesAsDeletedInternal(messages, channelId); } + return null; } private void fixUnsupportedMedia(TLRPC.Message message) { @@ -5093,7 +5409,7 @@ public void closeHolesInMedia(long did, int minId, int maxId, int type) throws E try { database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET end = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", minId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else if (minId <= hole.start + 1) { @@ -5101,7 +5417,7 @@ public void closeHolesInMedia(long did, int minId, int maxId, int type) throws E try { database.executeFast(String.format(Locale.US, "UPDATE media_holes_v2 SET start = %d WHERE uid = %d AND type = %d AND start = %d AND end = %d", maxId, did, hole.type, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else { @@ -5124,7 +5440,7 @@ public void closeHolesInMedia(long did, int minId, int maxId, int type) throws E } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -5155,7 +5471,7 @@ private void closeHolesInTable(String table, long did, int minId, int maxId) thr try { database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET end = %d WHERE uid = %d AND start = %d AND end = %d", minId, did, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else if (minId <= hole.start + 1) { @@ -5163,7 +5479,7 @@ private void closeHolesInTable(String table, long did, int minId, int maxId) thr try { database.executeFast(String.format(Locale.US, "UPDATE " + table + " SET start = %d WHERE uid = %d AND start = %d AND end = %d", maxId, did, hole.start, hole.end)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else { @@ -5184,7 +5500,7 @@ private void closeHolesInTable(String table, long did, int minId, int maxId) thr } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -5210,8 +5526,8 @@ public void run() { int maxId = messages.messages.get(0).id; closeHolesInTable("messages_holes", dialog_id, max_id, maxId); closeHolesInMedia(dialog_id, max_id, maxId, -1); - } else if (load_type == 3 || load_type == 2) { - int maxId = max_id == 0 ? Integer.MAX_VALUE : messages.messages.get(0).id; + } else if (load_type == 3 || load_type == 2 || load_type == 4) { + int maxId = max_id == 0 && load_type != 4 ? Integer.MAX_VALUE : messages.messages.get(0).id; int minId = messages.messages.get(messages.messages.size() - 1).id; closeHolesInTable("messages_holes", dialog_id, minId, maxId); closeHolesInMedia(dialog_id, minId, maxId, -1); @@ -5222,6 +5538,7 @@ public void run() { //load_type == 1 ? forward loading //load_type == 2 ? load from first unread //load_type == 3 ? load around message + //load_type == 4 ? load around date SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); @@ -5262,7 +5579,14 @@ public void run() { } if (a == 0 && createDialog) { - SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + int pinned = 0; + SQLiteCursor cursor = database.queryFinalized("SELECT pinned FROM dialogs WHERE did = " + dialog_id); + if (cursor.next()) { + pinned = cursor.intValue(0); + } + cursor.dispose(); + + SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); state3.bindLong(1, dialog_id); state3.bindInteger(2, message.date); state3.bindInteger(3, 0); @@ -5273,6 +5597,7 @@ public void run() { state3.bindInteger(8, message.ttl); state3.bindInteger(9, messages.pts); state3.bindInteger(10, message.date); + state3.bindInteger(11, pinned); state3.step(); state3.dispose(); } @@ -5307,7 +5632,7 @@ public void run() { state2.step(); } data.reuse(); - if (message.media instanceof TLRPC.TL_messageMediaWebPage && message.media.webpage instanceof TLRPC.TL_webPagePending) { + if (message.media instanceof TLRPC.TL_messageMediaWebPage) { if (state5 == null) { state5 = database.executeFast("REPLACE INTO webpage_pending VALUES(?, ?)"); } @@ -5338,10 +5663,10 @@ public void run() { database.commitTransaction(); if (createDialog) { - MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), false, channelId); + MessagesStorage.getInstance().updateDialogsWithDeletedMessages(new ArrayList(), null, false, channelId); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -5428,7 +5753,7 @@ public void run() { ArrayList encryptedToLoad = new ArrayList<>(); ArrayList replyMessages = new ArrayList<>(); HashMap replyMessageOwners = new HashMap<>(); - SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.date DESC LIMIT %d,%d", offset, count)); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT d.did, d.last_mid, d.unread_count, d.date, m.data, m.read_state, m.mid, m.send_state, s.flags, m.date, d.pts, d.inbox_max, d.outbox_max, m.replydata, d.pinned FROM dialogs as d LEFT JOIN messages as m ON d.last_mid = m.mid LEFT JOIN dialog_settings as s ON d.did = s.did ORDER BY d.pinned DESC, d.date DESC LIMIT %d,%d", offset, count)); while (cursor.next()) { TLRPC.TL_dialog dialog = new TLRPC.TL_dialog(); dialog.id = cursor.longValue(0); @@ -5439,6 +5764,8 @@ public void run() { dialog.flags = dialog.pts == 0 || (int) dialog.id > 0 ? 0 : 1; dialog.read_inbox_max_id = cursor.intValue(11); dialog.read_outbox_max_id = cursor.intValue(12); + dialog.pinnedNum = cursor.intValue(14); + dialog.pinned = dialog.pinnedNum != 0; long flags = cursor.longValue(8); int low_flags = (int) flags; dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); @@ -5468,7 +5795,10 @@ public void run() { addUsersAndChatsFromMessage(message, usersToLoad, chatsToLoad); try { - if (message.reply_to_msg_id != 0 && (message.action instanceof TLRPC.TL_messageActionPinMessage || message.action instanceof TLRPC.TL_messageActionGameScore)) { + if (message.reply_to_msg_id != 0 && ( + message.action instanceof TLRPC.TL_messageActionPinMessage || + message.action instanceof TLRPC.TL_messageActionPaymentSent || + message.action instanceof TLRPC.TL_messageActionGameScore)) { if (!cursor.isNull(13)) { data = cursor.byteBufferValue(13); if (data != null) { @@ -5491,7 +5821,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -5555,14 +5885,14 @@ public void run() { if (!usersToLoad.isEmpty()) { getUsersInternal(TextUtils.join(",", usersToLoad), dialogs.users); } - MessagesController.getInstance().processLoadedDialogs(dialogs, encryptedChats, offset, count, 1, false, false); + MessagesController.getInstance().processLoadedDialogs(dialogs, encryptedChats, offset, count, 1, false, false, true); } catch (Exception e) { dialogs.dialogs.clear(); dialogs.users.clear(); dialogs.chats.clear(); encryptedChats.clear(); - FileLog.e("tmessages", e); - MessagesController.getInstance().processLoadedDialogs(dialogs, encryptedChats, 0, 100, 1, true, false); + FileLog.e(e); + MessagesController.getInstance().processLoadedDialogs(dialogs, encryptedChats, 0, 100, 1, true, false, true); } } }); @@ -5585,7 +5915,7 @@ public static void createFirstHoles(long did, SQLitePreparedStatement state5, SQ } } - private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs) { + private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs, boolean check) { try { database.beginTransaction(); final HashMap new_dialogMessage = new HashMap<>(); @@ -5596,7 +5926,7 @@ private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs) { if (!dialogs.dialogs.isEmpty()) { SQLitePreparedStatement state = database.executeFast("REPLACE INTO messages VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)"); - SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + SQLitePreparedStatement state2 = database.executeFast("REPLACE INTO dialogs VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); SQLitePreparedStatement state3 = database.executeFast("REPLACE INTO media_v2 VALUES(?, ?, ?, ?, ?)"); SQLitePreparedStatement state4 = database.executeFast("REPLACE INTO dialog_settings VALUES(?, ?)"); SQLitePreparedStatement state5 = database.executeFast("REPLACE INTO messages_holes VALUES(?, ?, ?)"); @@ -5614,6 +5944,14 @@ private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs) { dialog.id = -dialog.peer.channel_id; } } + if (check) { + SQLiteCursor cursor = database.queryFinalized("SELECT did FROM dialogs WHERE did = " + dialog.id); + boolean exists = cursor.next(); + cursor.dispose(); + if (exists) { + continue; + } + } int messageDate = 0; TLRPC.Message message = new_dialogMessage.get(dialog.id); @@ -5676,6 +6014,7 @@ private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs) { state2.bindInteger(8, 0); state2.bindInteger(9, dialog.pts); state2.bindInteger(10, 0); + state2.bindInteger(11, dialog.pinnedNum); state2.step(); if (dialog.notify_settings != null) { @@ -5698,18 +6037,112 @@ private void putDialogsInternal(final TLRPC.messages_Dialogs dialogs) { database.commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - public void putDialogs(final TLRPC.messages_Dialogs dialogs) { + public void unpinAllDialogsExceptNew(final ArrayList dids) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + ArrayList unpinnedDialogs = new ArrayList<>(); + SQLiteCursor cursor = database.queryFinalized(String.format(Locale.US, "SELECT did FROM dialogs WHERE pinned != 0 AND did NOT IN (%s)", TextUtils.join(",", dids))); + while (cursor.next()) { + long did = cursor.longValue(0); + if ((int) did != 0) { + unpinnedDialogs.add(cursor.longValue(0)); + } + } + cursor.dispose(); + if (!unpinnedDialogs.isEmpty()) { + int minDate = 0; + cursor = database.queryFinalized("SELECT min(date), min(date_i) FROM dialogs WHERE (date != 0 OR date_i != 0) AND pinned = 0"); + if (cursor.next()) { + int date = cursor.intValue(0); + int date_i = cursor.intValue(1); + if (date != 0 && date_i != 0) { + minDate = Math.min(date, date_i); + } else if (date == 0) { + minDate = date_i; + } else { + minDate = date; + } + } + cursor.dispose(); + + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?"); + for (int a = 0; a < unpinnedDialogs.size(); a++) { + long did = unpinnedDialogs.get(a); + + int dialogDate = 0; + cursor = database.queryFinalized("SELECT date FROM dialogs WHERE did = " + did); + if (cursor.next()) { + dialogDate = cursor.intValue(0); + } + cursor.dispose(); + + if (dialogDate <= minDate) { + database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose(); + continue; + } + state.requery(); + state.bindInteger(1, 0); + state.bindLong(2, did); + state.step(); + } + state.dispose(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void setDialogPinned(final long did, final int pinned) { + storageQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + if (pinned == 0 && (int) did != 0) { + int dialogDate = 0; + int minDate = 0; + SQLiteCursor cursor = database.queryFinalized("SELECT date FROM dialogs WHERE did = " + did); + if (cursor.next()) { + dialogDate = cursor.intValue(0); + } + cursor.dispose(); + cursor = database.queryFinalized("SELECT min(date) FROM dialogs WHERE date != 0 AND pinned = 0"); + if (cursor.next()) { + minDate = cursor.intValue(0); + } + cursor.dispose(); + if (dialogDate <= minDate) { + database.executeFast("DELETE FROM dialogs WHERE did = " + did).stepThis().dispose(); + return; + } + } + SQLitePreparedStatement state = database.executeFast("UPDATE dialogs SET pinned = ? WHERE did = ?"); + state.bindInteger(1, pinned); + state.bindLong(2, did); + state.step(); + state.dispose(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void putDialogs(final TLRPC.messages_Dialogs dialogs, final boolean check) { if (dialogs.dialogs.isEmpty()) { return; } storageQueue.postRunnable(new Runnable() { @Override public void run() { - putDialogsInternal(dialogs); + putDialogsInternal(dialogs, check); loadUnreadMessages(); } }); @@ -5732,7 +6165,7 @@ public void run() { max[0] = cursor.intValue(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -5744,7 +6177,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return max[0]; } @@ -5762,7 +6195,7 @@ public void run() { pts[0] = cursor.intValue(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -5773,14 +6206,14 @@ public void run() { semaphore.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return pts[0]; } @@ -5798,7 +6231,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return user[0]; } @@ -5816,7 +6249,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return chat[0]; } @@ -5830,7 +6263,7 @@ public TLRPC.User getUser(final int user_id) { user = users.get(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return user; } @@ -5841,7 +6274,7 @@ public ArrayList getUsers(final ArrayList uids) { getUsersInternal(TextUtils.join(",", uids), users); } catch (Exception e) { users.clear(); - FileLog.e("tmessages", e); + FileLog.e(e); } return users; } @@ -5855,7 +6288,7 @@ public TLRPC.Chat getChat(final int chat_id) { chat = chats.get(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return chat; } @@ -5869,7 +6302,7 @@ public TLRPC.EncryptedChat getEncryptedChat(final int chat_id) { chat = encryptedChats.get(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return chat; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java index 0c2163bee67..b5f6039f185 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicBrowserService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -53,7 +53,6 @@ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class MusicBrowserService extends MediaBrowserService implements NotificationCenter.NotificationCenterDelegate { - private static final String AUTO_APP_PACKAGE_NAME = "com.google.android.projection.gearhead"; private static final String SLOT_RESERVATION_SKIP_TO_NEXT = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; private static final String SLOT_RESERVATION_SKIP_TO_PREV = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; private static final String SLOT_RESERVATION_QUEUE = "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE"; @@ -221,7 +220,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -316,7 +315,7 @@ private void loadChildrenImpl(final String parentMediaId, final Result arrayList = musicObjects.get(did); if (arrayList != null) { @@ -352,7 +351,7 @@ private Bitmap createRoundBitmap(File path) { return result; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -416,7 +415,7 @@ public void onPlayFromMediaId(String mediaId, Bundle extras) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } handlePlayRequest(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerReceiver.java index 5af30d3e18f..e6738cad31e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java index e161b016c9a..39176b3642c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MusicPlayerService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -82,7 +82,7 @@ public void run() { remoteControlClient.setTransportControlFlags(RemoteControlClient.FLAG_KEY_MEDIA_PLAY | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE | RemoteControlClient.FLAG_KEY_MEDIA_STOP | RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS | RemoteControlClient.FLAG_KEY_MEDIA_NEXT); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } createNotification(messageObject); @@ -193,7 +193,7 @@ private void createNotification(MessageObject messageObject) { try { metadataEditor.putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, audioInfo.getCover()); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } metadataEditor.apply(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java index 26319569393..bb54cc90d37 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NativeCrashManager.java @@ -51,7 +51,7 @@ public static String createLogFile() { write.close(); return filename + ".faketrace"; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; @@ -88,7 +88,7 @@ public void run() { urlConnection.connect(); - FileLog.e("tmessages", "response code = " + urlConnection.getResponseCode() + " message = " + urlConnection.getResponseMessage()); + FileLog.e("response code = " + urlConnection.getResponseCode() + " message = " + urlConnection.getResponseMessage()); } catch (IOException e) { e.printStackTrace(); } finally { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java index d27139468ef..59ae60747a4 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NativeLoader.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -23,7 +23,7 @@ public class NativeLoader { - private final static int LIB_VERSION = 24; + private final static int LIB_VERSION = 26; private final static String LIB_NAME = "tmessages." + LIB_VERSION; private final static String LIB_SO_NAME = "lib" + LIB_NAME + ".so"; private final static String LOCALE_LIB_SO_NAME = "lib" + LIB_NAME + "loc.so"; @@ -55,7 +55,7 @@ private static boolean loadFromZip(Context context, File destDir, File destLocal file.delete(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } ZipFile zipFile = null; @@ -86,24 +86,24 @@ private static boolean loadFromZip(Context context, File destDir, File destLocal init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION); nativeLoaded = true; } catch (Error e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return true; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (stream != null) { try { stream.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (zipFile != null) { try { zipFile.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -130,10 +130,10 @@ public static synchronized void initNativeLibs(Context context) { folder = "mips"; } else { folder = "armeabi"; - FileLog.e("tmessages", "Unsupported arch: " + Build.CPU_ABI); + FileLog.e("Unsupported arch: " + Build.CPU_ABI); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); folder = "armeabi"; } @@ -147,14 +147,14 @@ public static synchronized void initNativeLibs(Context context) { if (destFile != null) { destFile = new File(destFile, LIB_SO_NAME); if (destFile.exists()) { - FileLog.d("tmessages", "load normal lib"); + FileLog.d("load normal lib"); try { System.loadLibrary(LIB_NAME); init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION); nativeLoaded = true; return; } catch (Error e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -165,18 +165,18 @@ public static synchronized void initNativeLibs(Context context) { File destLocalFile = new File(destDir, LOCALE_LIB_SO_NAME); if (destLocalFile.exists()) { try { - FileLog.d("tmessages", "Load local lib"); + FileLog.d("Load local lib"); System.load(destLocalFile.getAbsolutePath()); init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION); nativeLoaded = true; return; } catch (Error e) { - FileLog.e("tmessages", e); + FileLog.e(e); } destLocalFile.delete(); } - FileLog.e("tmessages", "Library not found, arch = " + folder); + FileLog.e("Library not found, arch = " + folder); if (loadFromZip(context, destDir, destLocalFile, folder)) { return; @@ -190,7 +190,7 @@ public static synchronized void initNativeLibs(Context context) { init(Constants.FILES_PATH, BuildVars.DEBUG_VERSION); nativeLoaded = true; } catch (Error e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationBadge.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationBadge.java new file mode 100644 index 00000000000..87ebe405815 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationBadge.java @@ -0,0 +1,743 @@ +/* + https://github.com/leolin310148/ShortcutBadger + */ + +package org.telegram.messenger; + +import android.annotation.TargetApi; +import android.content.AsyncQueryHandler; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class NotificationBadge { + + private static final List> BADGERS = new LinkedList<>(); + private static boolean initied; + private static Badger badger; + private static ComponentName componentName; + + public interface Badger { + void executeBadge(int badgeCount); + + List getSupportLaunchers(); + } + + public static class AdwHomeBadger implements Badger { + + public static final String INTENT_UPDATE_COUNTER = "org.adw.launcher.counter.SEND"; + public static final String PACKAGENAME = "PNAME"; + public static final String CLASSNAME = "CNAME"; + public static final String COUNT = "COUNT"; + + @Override + public void executeBadge(int badgeCount) { + + final Intent intent = new Intent(INTENT_UPDATE_COUNTER); + intent.putExtra(PACKAGENAME, componentName.getPackageName()); + intent.putExtra(CLASSNAME, componentName.getClassName()); + intent.putExtra(COUNT, badgeCount); + if (canResolveBroadcast(intent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList( + "org.adw.launcher", + "org.adwfreak.launcher" + ); + } + } + + public static class ApexHomeBadger implements Badger { + + private static final String INTENT_UPDATE_COUNTER = "com.anddoes.launcher.COUNTER_CHANGED"; + private static final String PACKAGENAME = "package"; + private static final String COUNT = "count"; + private static final String CLASS = "class"; + + @Override + public void executeBadge(int badgeCount) { + + final Intent intent = new Intent(INTENT_UPDATE_COUNTER); + intent.putExtra(PACKAGENAME, componentName.getPackageName()); + intent.putExtra(COUNT, badgeCount); + intent.putExtra(CLASS, componentName.getClassName()); + if (canResolveBroadcast(intent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.anddoes.launcher"); + } + } + + public static class AsusHomeBadger implements Badger { + + private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE"; + private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count"; + private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name"; + + @Override + public void executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName()); + intent.putExtra("badge_vip_count", 0); + if (canResolveBroadcast(intent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.asus.launcher"); + } + } + + public static class DefaultBadger implements Badger { + private static final String INTENT_ACTION = "android.intent.action.BADGE_COUNT_UPDATE"; + private static final String INTENT_EXTRA_BADGE_COUNT = "badge_count"; + private static final String INTENT_EXTRA_PACKAGENAME = "badge_count_package_name"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "badge_count_class_name"; + + @Override + public void executeBadge(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName()); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } catch (Exception ignore) { + + } + } + }); + } + + @Override + public List getSupportLaunchers() { + return new ArrayList<>(0); + } + } + + public static class HuaweiHomeBadger implements Badger { + + @Override + public void executeBadge(int badgeCount) { + final Bundle localBundle = new Bundle(); + localBundle.putString("package", ApplicationLoader.applicationContext.getPackageName()); + localBundle.putString("class", componentName.getClassName()); + localBundle.putInt("badgenumber", badgeCount); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + ApplicationLoader.applicationContext.getContentResolver().call(Uri.parse("content://com.huawei.android.launcher.settings/badge/"), "change_badge", null, localBundle); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList( + "com.huawei.android.launcher" + ); + } + } + + public static class NewHtcHomeBadger implements Badger { + + public static final String INTENT_UPDATE_SHORTCUT = "com.htc.launcher.action.UPDATE_SHORTCUT"; + public static final String INTENT_SET_NOTIFICATION = "com.htc.launcher.action.SET_NOTIFICATION"; + public static final String PACKAGENAME = "packagename"; + public static final String COUNT = "count"; + public static final String EXTRA_COMPONENT = "com.htc.launcher.extra.COMPONENT"; + public static final String EXTRA_COUNT = "com.htc.launcher.extra.COUNT"; + + @Override + public void executeBadge(int badgeCount) { + + final Intent intent1 = new Intent(INTENT_SET_NOTIFICATION); + intent1.putExtra(EXTRA_COMPONENT, componentName.flattenToShortString()); + intent1.putExtra(EXTRA_COUNT, badgeCount); + + final Intent intent = new Intent(INTENT_UPDATE_SHORTCUT); + intent.putExtra(PACKAGENAME, componentName.getPackageName()); + intent.putExtra(COUNT, badgeCount); + + if (canResolveBroadcast(intent1) || canResolveBroadcast(intent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent1); + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.htc.launcher"); + } + } + + public static class NovaHomeBadger implements Badger { + + private static final String CONTENT_URI = "content://com.teslacoilsw.notifier/unread_count"; + private static final String COUNT = "count"; + private static final String TAG = "tag"; + + @Override + public void executeBadge(int badgeCount) { + ContentValues contentValues = new ContentValues(); + contentValues.put(TAG, componentName.getPackageName() + "/" + componentName.getClassName()); + contentValues.put(COUNT, badgeCount); + ApplicationLoader.applicationContext.getContentResolver().insert(Uri.parse(CONTENT_URI), contentValues); + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.teslacoilsw.launcher"); + } + } + + public static class OPPOHomeBader implements Badger { + + private static final String PROVIDER_CONTENT_URI = "content://com.android.badge/badge"; + private static final String INTENT_ACTION = "com.oppo.unsettledevent"; + private static final String INTENT_EXTRA_PACKAGENAME = "pakeageName"; + private static final String INTENT_EXTRA_BADGE_COUNT = "number"; + private static final String INTENT_EXTRA_BADGE_UPGRADENUMBER = "upgradeNumber"; + private static final String INTENT_EXTRA_BADGEUPGRADE_COUNT = "app_badge_count"; + private static int ROMVERSION = -1; + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public void executeBadge(int badgeCount) { + if (badgeCount == 0) { + badgeCount = -1; + } + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_PACKAGENAME, componentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_BADGE_COUNT, badgeCount); + intent.putExtra(INTENT_EXTRA_BADGE_UPGRADENUMBER, badgeCount); + if (canResolveBroadcast(intent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } else { + int version = getSupportVersion(); + if (version == 6) { + try { + final Bundle extras = new Bundle(); + extras.putInt(INTENT_EXTRA_BADGEUPGRADE_COUNT, badgeCount); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + ApplicationLoader.applicationContext.getContentResolver().call(Uri.parse(PROVIDER_CONTENT_URI), "setAppBadgeCount", null, extras); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } catch (Throwable ignore) { + + } + } + } + } + + @Override + public List getSupportLaunchers() { + return Collections.singletonList("com.oppo.launcher"); + } + + private int getSupportVersion() { + int i = ROMVERSION; + if (i >= 0) { + return ROMVERSION; + } + try { + i = ((Integer) executeClassLoad(getClass("com.color.os.ColorBuild"), "getColorOSVERSION", null, null)).intValue(); + } catch (Exception e) { + i = 0; + } + if (i == 0) { + try { + String str = getSystemProperty("ro.build.version.opporom"); + if (str.startsWith("V1.4")) { + return 3; + } + if (str.startsWith("V2.0")) { + return 4; + } + if (str.startsWith("V2.1")) { + return 5; + } + } catch (Exception ignored) { + + } + } + ROMVERSION = i; + return ROMVERSION; + } + + + private Object executeClassLoad(Class cls, String str, Class[] clsArr, Object[] objArr) { + Object obj = null; + if (!(cls == null || checkObjExists(str))) { + Method method = getMethod(cls, str, clsArr); + if (method != null) { + method.setAccessible(true); + try { + obj = method.invoke(null, objArr); + } catch (Throwable ignore) { + + } + } + } + return obj; + } + + @SuppressWarnings("unchecked") + private Method getMethod(Class cls, String str, Class[] clsArr) { + Method method = null; + if (cls == null || checkObjExists(str)) { + return method; + } + try { + cls.getMethods(); + cls.getDeclaredMethods(); + return cls.getDeclaredMethod(str, clsArr); + } catch (Exception e) { + try { + return cls.getMethod(str, clsArr); + } catch (Exception e2) { + return cls.getSuperclass() != null ? getMethod(cls.getSuperclass(), str, clsArr) : method; + } + } + } + + private Class getClass(String str) { + Class cls = null; + try { + cls = Class.forName(str); + } catch (ClassNotFoundException ignored) { + } + return cls; + } + + + private boolean checkObjExists(Object obj) { + return obj == null || obj.toString().equals("") || obj.toString().trim().equals("null"); + } + + + private String getSystemProperty(String propName) { + String line; + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + propName); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + line = input.readLine(); + input.close(); + } catch (Throwable ex) { + return null; + } finally { + closeQuietly(input); + } + return line; + } + } + + public static class SamsungHomeBadger implements Badger { + private static final String CONTENT_URI = "content://com.sec.badge/apps?notify=true"; + private static final String[] CONTENT_PROJECTION = new String[]{"_id","class"}; + + private static DefaultBadger defaultBadger; + + @Override + public void executeBadge(int badgeCount) { + try { + if (defaultBadger == null) { + defaultBadger = new DefaultBadger(); + } + defaultBadger.executeBadge(badgeCount); + } catch (Exception ignore) { + + } + + Uri mUri = Uri.parse(CONTENT_URI); + ContentResolver contentResolver = ApplicationLoader.applicationContext.getContentResolver(); + Cursor cursor = null; + try { + cursor = contentResolver.query(mUri, CONTENT_PROJECTION, "package=?", new String[]{componentName.getPackageName()}, null); + if (cursor != null) { + String entryActivityName = componentName.getClassName(); + boolean entryActivityExist = false; + while (cursor.moveToNext()) { + int id = cursor.getInt(0); + ContentValues contentValues = getContentValues(componentName, badgeCount, false); + contentResolver.update(mUri, contentValues, "_id=?", new String[]{String.valueOf(id)}); + if (entryActivityName.equals(cursor.getString(cursor.getColumnIndex("class")))) { + entryActivityExist = true; + } + } + + if (!entryActivityExist) { + ContentValues contentValues = getContentValues(componentName, badgeCount, true); + contentResolver.insert(mUri, contentValues); + } + } + } finally { + close(cursor); + } + } + + private ContentValues getContentValues(ComponentName componentName, int badgeCount, boolean isInsert) { + ContentValues contentValues = new ContentValues(); + if (isInsert) { + contentValues.put("package", componentName.getPackageName()); + contentValues.put("class", componentName.getClassName()); + } + + contentValues.put("badgecount", badgeCount); + + return contentValues; + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList( + "com.sec.android.app.launcher", + "com.sec.android.app.twlauncher" + ); + } + } + + public static class SonyHomeBadger implements Badger { + + private static final String INTENT_ACTION = "com.sonyericsson.home.action.UPDATE_BADGE"; + private static final String INTENT_EXTRA_PACKAGE_NAME = "com.sonyericsson.home.intent.extra.badge.PACKAGE_NAME"; + private static final String INTENT_EXTRA_ACTIVITY_NAME = "com.sonyericsson.home.intent.extra.badge.ACTIVITY_NAME"; + private static final String INTENT_EXTRA_MESSAGE = "com.sonyericsson.home.intent.extra.badge.MESSAGE"; + private static final String INTENT_EXTRA_SHOW_MESSAGE = "com.sonyericsson.home.intent.extra.badge.SHOW_MESSAGE"; + + private static final String PROVIDER_CONTENT_URI = "content://com.sonymobile.home.resourceprovider/badge"; + private static final String PROVIDER_COLUMNS_BADGE_COUNT = "badge_count"; + private static final String PROVIDER_COLUMNS_PACKAGE_NAME = "package_name"; + private static final String PROVIDER_COLUMNS_ACTIVITY_NAME = "activity_name"; + private static final String SONY_HOME_PROVIDER_NAME = "com.sonymobile.home.resourceprovider"; + private final Uri BADGE_CONTENT_URI = Uri.parse(PROVIDER_CONTENT_URI); + + private static AsyncQueryHandler mQueryHandler; + + @Override + public void executeBadge(int badgeCount) { + if (sonyBadgeContentProviderExists()) { + executeBadgeByContentProvider(badgeCount); + } else { + executeBadgeByBroadcast(badgeCount); + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.sonyericsson.home", "com.sonymobile.home"); + } + + private static void executeBadgeByBroadcast(int badgeCount) { + final Intent intent = new Intent(INTENT_ACTION); + intent.putExtra(INTENT_EXTRA_PACKAGE_NAME, componentName.getPackageName()); + intent.putExtra(INTENT_EXTRA_ACTIVITY_NAME, componentName.getClassName()); + intent.putExtra(INTENT_EXTRA_MESSAGE, String.valueOf(badgeCount)); + intent.putExtra(INTENT_EXTRA_SHOW_MESSAGE, badgeCount > 0); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + }); + } + + private void executeBadgeByContentProvider(int badgeCount) { + if (badgeCount < 0) { + return; + } + + if (mQueryHandler == null) { + mQueryHandler = new AsyncQueryHandler(ApplicationLoader.applicationContext.getApplicationContext().getContentResolver()) { + + @Override + public void handleMessage(Message msg) { + try { + super.handleMessage(msg); + } catch (Throwable ignore) { + + } + } + }; + } + insertBadgeAsync(badgeCount, componentName.getPackageName(), componentName.getClassName()); + } + + private void insertBadgeAsync(int badgeCount, String packageName, String activityName) { + final ContentValues contentValues = new ContentValues(); + contentValues.put(PROVIDER_COLUMNS_BADGE_COUNT, badgeCount); + contentValues.put(PROVIDER_COLUMNS_PACKAGE_NAME, packageName); + contentValues.put(PROVIDER_COLUMNS_ACTIVITY_NAME, activityName); + mQueryHandler.startInsert(0, null, BADGE_CONTENT_URI, contentValues); + } + + private static boolean sonyBadgeContentProviderExists() { + boolean exists = false; + ProviderInfo info = ApplicationLoader.applicationContext.getPackageManager().resolveContentProvider(SONY_HOME_PROVIDER_NAME, 0); + if (info != null) { + exists = true; + } + return exists; + } + } + + public static class XiaomiHomeBadger implements Badger { + + public static final String INTENT_ACTION = "android.intent.action.APPLICATION_MESSAGE_UPDATE"; + public static final String EXTRA_UPDATE_APP_COMPONENT_NAME = "android.intent.extra.update_application_component_name"; + public static final String EXTRA_UPDATE_APP_MSG_TEXT = "android.intent.extra.update_application_message_text"; + + @Override + public void executeBadge(int badgeCount) { + try { + Class miuiNotificationClass = Class.forName("android.app.MiuiNotification"); + Object miuiNotification = miuiNotificationClass.newInstance(); + Field field = miuiNotification.getClass().getDeclaredField("messageCount"); + field.setAccessible(true); + field.set(miuiNotification, String.valueOf(badgeCount == 0 ? "" : badgeCount)); + } catch (Throwable e) { + final Intent localIntent = new Intent(INTENT_ACTION); + localIntent.putExtra(EXTRA_UPDATE_APP_COMPONENT_NAME, componentName.getPackageName() + "/" + componentName.getClassName()); + localIntent.putExtra(EXTRA_UPDATE_APP_MSG_TEXT, String.valueOf(badgeCount == 0 ? "" : badgeCount)); + if (canResolveBroadcast(localIntent)) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + ApplicationLoader.applicationContext.sendBroadcast(localIntent); + } + }); + } + } + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList( + "com.miui.miuilite", + "com.miui.home", + "com.miui.miuihome", + "com.miui.miuihome2", + "com.miui.mihome", + "com.miui.mihome2" + ); + } + } + + public static class ZukHomeBadger implements Badger { + + private final Uri CONTENT_URI = Uri.parse("content://com.android.badge/badge"); + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + @Override + public void executeBadge(int badgeCount) { + final Bundle extra = new Bundle(); + extra.putInt("app_badge_count", badgeCount); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + try { + ApplicationLoader.applicationContext.getContentResolver().call(CONTENT_URI, "setAppBadgeCount", null, extra); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + @Override + public List getSupportLaunchers() { + return Collections.singletonList("com.zui.launcher"); + } + } + + public static class VivoHomeBadger implements Badger { + + @Override + public void executeBadge(int badgeCount) { + Intent intent = new Intent("launcher.action.CHANGE_APPLICATION_NOTIFICATION_NUM"); + intent.putExtra("packageName", ApplicationLoader.applicationContext.getPackageName()); + intent.putExtra("className", componentName.getClassName()); + intent.putExtra("notificationNum", badgeCount); + ApplicationLoader.applicationContext.sendBroadcast(intent); + } + + @Override + public List getSupportLaunchers() { + return Arrays.asList("com.vivo.launcher"); + } + } + + static { + BADGERS.add(AdwHomeBadger.class); + BADGERS.add(ApexHomeBadger.class); + BADGERS.add(NewHtcHomeBadger.class); + BADGERS.add(NovaHomeBadger.class); + BADGERS.add(SonyHomeBadger.class); + BADGERS.add(XiaomiHomeBadger.class); + BADGERS.add(AsusHomeBadger.class); + BADGERS.add(HuaweiHomeBadger.class); + BADGERS.add(OPPOHomeBader.class); + BADGERS.add(SamsungHomeBadger.class); + BADGERS.add(ZukHomeBadger.class); + BADGERS.add(VivoHomeBadger.class); + } + + public static boolean applyCount(int badgeCount) { + try { + if (badger == null && !initied) { + initBadger(); + initied = true; + } + if (badger == null) { + return false; + } + badger.executeBadge(badgeCount); + return true; + } catch (Throwable e) { + return false; + } + } + + private static boolean initBadger() { + Context context = ApplicationLoader.applicationContext; + Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); + if (launchIntent == null) { + return false; + } + + componentName = launchIntent.getComponent(); + + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); + + if (resolveInfo == null || resolveInfo.activityInfo.name.toLowerCase().contains("resolver")) { + return false; + } + + String currentHomePackage = resolveInfo.activityInfo.packageName; + + for (Class b : BADGERS) { + Badger shortcutBadger = null; + try { + shortcutBadger = b.newInstance(); + } catch (Exception ignored) { + } + if (shortcutBadger != null && shortcutBadger.getSupportLaunchers().contains(currentHomePackage)) { + badger = shortcutBadger; + break; + } + } + + if (badger == null) { + if (Build.MANUFACTURER.equalsIgnoreCase("Xiaomi")) { + badger = new XiaomiHomeBadger(); + } else if (Build.MANUFACTURER.equalsIgnoreCase("ZUK")) { + badger = new ZukHomeBadger(); + } else if (Build.MANUFACTURER.equalsIgnoreCase("OPPO")) { + badger = new OPPOHomeBader(); + } else if (Build.MANUFACTURER.equalsIgnoreCase("VIVO")) { + badger = new VivoHomeBadger(); + } else { + badger = new DefaultBadger(); + } + } + + return true; + } + + private static boolean canResolveBroadcast(Intent intent) { + PackageManager packageManager = ApplicationLoader.applicationContext.getPackageManager(); + List receivers = packageManager.queryBroadcastReceivers(intent, 0); + return receivers != null && receivers.size() > 0; + } + + public static void close(Cursor cursor) { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (Throwable ignore) { + + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index d442d571e08..c3fb039352d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -51,6 +51,7 @@ public class NotificationCenter { public static final int replaceMessagesObjects = totalEvents++; public static final int didSetPasscode = totalEvents++; public static final int didSetTwoStepPassword = totalEvents++; + public static final int didRemovedTwoStepPassword = totalEvents++; public static final int screenStateChanged = totalEvents++; public static final int didLoadedReplyMessages = totalEvents++; public static final int didLoadedPinnedMessage = totalEvents++; @@ -79,6 +80,8 @@ public class NotificationCenter { public static final int cameraInitied = totalEvents++; public static final int needReloadArchivedStickers = totalEvents++; public static final int didSetNewWallpapper = totalEvents++; + public static final int archivedStickersCountDidLoaded = totalEvents++; + public static final int paymentFinished = totalEvents++; public static final int httpFileDidLoaded = totalEvents++; public static final int httpFileDidFailedLoad = totalEvents++; @@ -116,6 +119,10 @@ public class NotificationCenter { public static final int audioDidStarted = totalEvents++; public static final int audioRouteChanged = totalEvents++; + public static final int didStartedCall = totalEvents++; + public static final int didEndedCall = totalEvents++; + public static final int closeInCallActivity = totalEvents++; + private SparseArray> observers = new SparseArray<>(); private SparseArray> removeAfterBroadcast = new SparseArray<>(); private SparseArray> addAfterBroadcast = new SparseArray<>(); @@ -197,7 +204,7 @@ public void postNotificationNameInternal(int id, boolean allowDuringAnimation, O DelayedPost delayedPost = new DelayedPost(id, args); delayedPosts.add(delayedPost); if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "delay post notification " + id + " with args count = " + args.length); + FileLog.e("delay post notification " + id + " with args count = " + args.length); } return; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationDismissReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationDismissReceiver.java index a32a872f7c7..b81e78ded61 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationDismissReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationDismissReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationRepeat.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationRepeat.java index c1e87f8b4ed..0948e9de8df 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationRepeat.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationRepeat.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index d5ce70041f8..e2e6f168269 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -12,12 +12,9 @@ import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; @@ -45,7 +42,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; -import java.util.List; public class NotificationsController { @@ -68,7 +64,7 @@ public class NotificationsController { private boolean notifyCheck = false; private int lastOnlineFromOtherDevice = 0; private boolean inChatSoundEnabled = true; - private int lastBadgeCount; + private int lastBadgeCount = -1; private String launcherClassName; private Runnable notificationDelayRunnable; @@ -90,7 +86,7 @@ public class NotificationsController { public static NotificationsController getInstance() { NotificationsController localInstance = Instance; if (localInstance == null) { - synchronized (MessagesController.class) { + synchronized (NotificationsController.class) { localInstance = Instance; if (localInstance == null) { Instance = localInstance = new NotificationsController(); @@ -108,12 +104,12 @@ public NotificationsController() { try { audioManager = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { alarmManager = (AlarmManager) ApplicationLoader.applicationContext.getSystemService(Context.ALARM_SERVICE); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -121,13 +117,13 @@ public NotificationsController() { notificationDelayWakelock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock"); notificationDelayWakelock.setReferenceCounted(false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } notificationDelayRunnable = new Runnable() { @Override public void run() { - FileLog.e("tmessages", "delay reached"); + FileLog.e("delay reached"); if (!delayedPushMessages.isEmpty()) { showOrUpdateNotification(true); delayedPushMessages.clear(); @@ -137,7 +133,7 @@ public void run() { notificationDelayWakelock.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }; @@ -164,7 +160,7 @@ public void run() { notificationDelayWakelock.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } setBadge(0); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); @@ -192,7 +188,7 @@ public void setLastOnlineFromOtherDevice(final int time) { notificationsQueue.postRunnable(new Runnable() { @Override public void run() { - FileLog.e("tmessages", "set last online from other device = " + time); + FileLog.e("set last online from other device = " + time); lastOnlineFromOtherDevice = time; } }); @@ -218,6 +214,128 @@ public boolean hasMessagesToReply() { return false; } + protected void showSingleBackgroundNotification() { + notificationsQueue.postRunnable(new Runnable() { + @Override + public void run() { + try { + if (!ApplicationLoader.mainInterfacePaused) { + return; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); + + boolean notifyDisabled = false; + int needVibrate = 0; + String choosenSoundPath = null; + int ledColor = 0xff0000ff; + int priority = 0; + int vibrateOverride; + + if (!preferences.getBoolean("EnableAll", true)) { + notifyDisabled = true; + } + + String defaultPath = Settings.System.DEFAULT_NOTIFICATION_URI.getPath(); + if (!notifyDisabled) { + vibrateOverride = 0; + choosenSoundPath = null; + boolean vibrateOnlyIfSilent = false; + + if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) { + choosenSoundPath = null; + } else if (choosenSoundPath == null) { + choosenSoundPath = preferences.getString("GlobalSoundPath", defaultPath); + } + needVibrate = preferences.getInt("vibrate_messages", 0); + priority = preferences.getInt("priority_group", 1); + ledColor = preferences.getInt("MessagesLed", 0xff0000ff); + + if (needVibrate == 4) { + vibrateOnlyIfSilent = true; + needVibrate = 0; + } + if (needVibrate == 2 && (vibrateOverride == 1 || vibrateOverride == 3) || needVibrate != 2 && vibrateOverride == 2 || vibrateOverride != 0 && vibrateOverride != 4) { + needVibrate = vibrateOverride; + } + if (vibrateOnlyIfSilent && needVibrate != 2) { + try { + int mode = audioManager.getRingerMode(); + if (mode != AudioManager.RINGER_MODE_SILENT && mode != AudioManager.RINGER_MODE_VIBRATE) { + needVibrate = 2; + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + + Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + intent.setAction("com.tmessages.openchat" + Math.random() + Integer.MAX_VALUE); + intent.setFlags(32768); + + PendingIntent contentIntent = PendingIntent.getActivity(ApplicationLoader.applicationContext, 0, intent, PendingIntent.FLAG_ONE_SHOT); + + String name = LocaleController.getString("AppName", R.string.AppName); + + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(ApplicationLoader.applicationContext) + .setContentTitle(name) + .setSmallIcon(R.drawable.notification) + .setAutoCancel(true) + .setNumber(total_unread_count) + .setContentIntent(contentIntent) + .setGroup("messages") + .setGroupSummary(true) + .setColor(0xff2ca5e0); + + mBuilder.setCategory(NotificationCompat.CATEGORY_MESSAGE); + + String lastMessage = LocaleController.getString("YouHaveNewMessage", R.string.YouHaveNewMessage); + mBuilder.setContentText(lastMessage); + mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(lastMessage)); + + if (priority == 0) { + mBuilder.setPriority(NotificationCompat.PRIORITY_DEFAULT); + } else if (priority == 1) { + mBuilder.setPriority(NotificationCompat.PRIORITY_HIGH); + } else if (priority == 2) { + mBuilder.setPriority(NotificationCompat.PRIORITY_MAX); + } + + if (!notifyDisabled) { + if (lastMessage.length() > 100) { + lastMessage = lastMessage.substring(0, 100).replace('\n', ' ').trim() + "..."; + } + mBuilder.setTicker(lastMessage); + if (choosenSoundPath != null && !choosenSoundPath.equals("NoSound")) { + if (choosenSoundPath.equals(defaultPath)) { + mBuilder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, AudioManager.STREAM_NOTIFICATION); + } else { + mBuilder.setSound(Uri.parse(choosenSoundPath), AudioManager.STREAM_NOTIFICATION); + } + } + if (ledColor != 0) { + mBuilder.setLights(ledColor, 1000, 1000); + } + if (needVibrate == 2 || MediaController.getInstance().isRecordingAudio()) { + mBuilder.setVibrate(new long[]{0, 0}); + } else if (needVibrate == 1) { + mBuilder.setVibrate(new long[]{0, 100, 0, 100}); + } else if (needVibrate == 0 || needVibrate == 4) { + mBuilder.setDefaults(NotificationCompat.DEFAULT_VIBRATE); + } else if (needVibrate == 3) { + mBuilder.setVibrate(new long[]{0, 1000}); + } + } else { + mBuilder.setVibrate(new long[]{0, 0}); + } + notificationManager.notify(1, mBuilder.build()); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + protected void forceShowPopupForReply() { notificationsQueue.postRunnable(new Runnable() { @Override @@ -456,8 +574,22 @@ public void run() { added = true; Boolean value = settingsCache.get(dialog_id); - boolean isChat = (int) dialog_id < 0; - popup = (int) dialog_id == 0 ? 0 : preferences.getInt(isChat ? "popupGroup" : "popupAll", 0); + int lower_id = (int) dialog_id; + boolean isChat = lower_id < 0; + if (lower_id != 0) { + if (preferences.getBoolean("custom_" + dialog_id, false)) { + popup = preferences.getInt("popup_" + dialog_id, 0); + } else { + popup = 0; + } + if (popup == 0) { + popup = preferences.getInt((int) dialog_id < 0 ? "popupGroup" : "popupAll", 0); + } else if (popup == 1) { + popup = 3; + } else if (popup == 2) { + popup = 0; + } + } if (value == null) { int notifyOverride = getNotifyOverride(preferences, dialog_id); value = !(notifyOverride == 2 || (!preferences.getBoolean("EnableAll", true) || isChat && !preferences.getBoolean("EnableGroup", true)) && notifyOverride == 0); @@ -611,7 +743,8 @@ public void run() { HashMap settingsCache = new HashMap<>(); if (messages != null) { - for (TLRPC.Message message : messages) { + for (int a = 0; a < messages.size(); a++) { + TLRPC.Message message = messages.get(a); long mid = message.id; if (message.to_id.channel_id != 0) { mid |= ((long) message.to_id.channel_id) << 32; @@ -694,38 +827,7 @@ public void run() { return; } lastBadgeCount = count; - try { - ContentValues cv = new ContentValues(); - cv.put("tag", "org.telegram.messenger/org.telegram.ui.LaunchActivity"); - cv.put("count", count); - ApplicationLoader.applicationContext.getContentResolver().insert(Uri.parse("content://com.teslacoilsw.notifier/unread_count"), cv); - } catch (Throwable e) { - //ignore - } - try { - if (launcherClassName == null) { - launcherClassName = getLauncherClassName(ApplicationLoader.applicationContext); - } - if (launcherClassName == null) { - return; - } - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - try { - Intent intent = new Intent("android.intent.action.BADGE_COUNT_UPDATE"); - intent.putExtra("badge_count", count); - intent.putExtra("badge_count_package_name", ApplicationLoader.applicationContext.getPackageName()); - intent.putExtra("badge_count_class_name", launcherClassName); - ApplicationLoader.applicationContext.sendBroadcast(intent); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } catch (Throwable e) { - FileLog.e("tmessages", e); - } + NotificationBadge.applyCount(count); } }); } @@ -791,8 +893,13 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { String date = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(((long) messageObject.messageOwner.date) * 1000), LocaleController.getInstance().formatterDay.format(((long) messageObject.messageOwner.date) * 1000)); msg = LocaleController.formatString("NotificationUnrecognizedDevice", R.string.NotificationUnrecognizedDevice, UserConfig.getCurrentUser().first_name, date, messageObject.messageOwner.action.title, messageObject.messageOwner.action.address); - } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { msg = messageObject.messageText.toString(); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { + TLRPC.PhoneCallDiscardReason reason = messageObject.messageOwner.action.reason; + if (!messageObject.isOut() && (reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed)) { + msg = LocaleController.getString("CallMessageIncomingMissed", R.string.CallMessageIncomingMissed); + } } } else { if (messageObject.isMediaEmpty()) { @@ -1063,104 +1170,53 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { msg = messageObject.messageText.toString(); } - } else { - if (ChatObject.isChannel(chat) && !chat.megagroup) { - if (messageObject.messageOwner.post) { - if (messageObject.isMediaEmpty()) { - if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); - } else { - msg = LocaleController.formatString("ChannelMessageNoText", R.string.ChannelMessageNoText, name, chat.title); - } - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessagePhoto", R.string.ChannelMessagePhoto, name, chat.title); - } - } else if (messageObject.isVideo()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, name, chat.title); - } - } else if (messageObject.isVoice()) { - msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name, chat.title); - } else if (messageObject.isMusic()) { - msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - msg = LocaleController.formatString("ChannelMessageContact", R.string.ChannelMessageContact, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { - msg = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { - String emoji = messageObject.getStickerEmoji(); - if (emoji != null) { - msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, chat.title, emoji); - } else { - msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name, chat.title); - } - } else if (messageObject.isGif()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, name, chat.title); - } - } else { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageDocument", R.string.ChannelMessageDocument, name, chat.title); - } - } + } else if (ChatObject.isChannel(chat) && !chat.megagroup) { + if (messageObject.messageOwner.post) { + if (messageObject.isMediaEmpty()) { + if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, messageObject.messageOwner.message); + } else { + msg = LocaleController.formatString("ChannelMessageNoText", R.string.ChannelMessageNoText, name); } - } else { - if (messageObject.isMediaEmpty()) { - if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("ChannelMessagePhoto", R.string.ChannelMessagePhoto, name); + } + } else if (messageObject.isVideo()) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("ChannelMessageVideo", R.string.ChannelMessageVideo, name); + } + } else if (messageObject.isVoice()) { + msg = LocaleController.formatString("ChannelMessageAudio", R.string.ChannelMessageAudio, name); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("ChannelMessageMusic", R.string.ChannelMessageMusic, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + msg = LocaleController.formatString("ChannelMessageContact", R.string.ChannelMessageContact, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { + msg = LocaleController.formatString("ChannelMessageMap", R.string.ChannelMessageMap, name); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (messageObject.isSticker()) { + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("ChannelMessageStickerEmoji", R.string.ChannelMessageStickerEmoji, name, emoji); } else { - msg = LocaleController.formatString("ChannelMessageGroupNoText", R.string.ChannelMessageGroupNoText, name, chat.title); + msg = LocaleController.formatString("ChannelMessageSticker", R.string.ChannelMessageSticker, name); } - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + } else if (messageObject.isGif()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("ChannelMessageGroupPhoto", R.string.ChannelMessageGroupPhoto, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGIF", R.string.ChannelMessageGIF, name); } - } else if (messageObject.isVideo()) { + } else { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + msg = LocaleController.formatString("NotificationMessageText", R.string.NotificationMessageText, name, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("ChannelMessageGroupVideo", R.string.ChannelMessageGroupVideo, name, chat.title); - } - } else if (messageObject.isVoice()) { - msg = LocaleController.formatString("ChannelMessageGroupAudio", R.string.ChannelMessageGroupAudio, name, chat.title); - } else if (messageObject.isMusic()) { - msg = LocaleController.formatString("ChannelMessageGroupMusic", R.string.ChannelMessageGroupMusic, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - msg = LocaleController.formatString("ChannelMessageGroupContact", R.string.ChannelMessageGroupContact, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { - msg = LocaleController.formatString("ChannelMessageGroupMap", R.string.ChannelMessageGroupMap, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { - if (messageObject.isSticker()) { - String emoji = messageObject.getStickerEmoji(); - if (emoji != null) { - msg = LocaleController.formatString("ChannelMessageGroupStickerEmoji", R.string.ChannelMessageGroupStickerEmoji, name, chat.title, emoji); - } else { - msg = LocaleController.formatString("ChannelMessageGroupSticker", R.string.ChannelMessageGroupSticker, name, chat.title); - } - } else if (messageObject.isGif()) { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGroupGif", R.string.ChannelMessageGroupGif, name, chat.title); - } - } else { - if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { - msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); - } else { - msg = LocaleController.formatString("ChannelMessageGroupDocument", R.string.ChannelMessageGroupDocument, name, chat.title); - } + msg = LocaleController.formatString("ChannelMessageDocument", R.string.ChannelMessageDocument, name); } } } @@ -1169,53 +1225,102 @@ private String getStringForMessage(MessageObject messageObject, boolean shortMes if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); } else { - msg = LocaleController.formatString("NotificationMessageGroupNoText", R.string.NotificationMessageGroupNoText, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupNoText", R.string.ChannelMessageGroupNoText, name, chat.title); } } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("NotificationMessageGroupPhoto", R.string.NotificationMessageGroupPhoto, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupPhoto", R.string.ChannelMessageGroupPhoto, name, chat.title); } } else if (messageObject.isVideo()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("NotificationMessageGroupVideo", R.string.NotificationMessageGroupVideo, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupVideo", R.string.ChannelMessageGroupVideo, name, chat.title); } } else if (messageObject.isVoice()) { - msg = LocaleController.formatString("NotificationMessageGroupAudio", R.string.NotificationMessageGroupAudio, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupAudio", R.string.ChannelMessageGroupAudio, name, chat.title); } else if (messageObject.isMusic()) { - msg = LocaleController.formatString("NotificationMessageGroupMusic", R.string.NotificationMessageGroupMusic, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupMusic", R.string.ChannelMessageGroupMusic, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { - msg = LocaleController.formatString("NotificationMessageGroupContact", R.string.NotificationMessageGroupContact, name, chat.title); - } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - msg = LocaleController.formatString("NotificationMessageGroupGame", R.string.NotificationMessageGroupGame, name, chat.title, messageObject.messageOwner.media.game.title); + msg = LocaleController.formatString("ChannelMessageGroupContact", R.string.ChannelMessageGroupContact, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { - msg = LocaleController.formatString("NotificationMessageGroupMap", R.string.NotificationMessageGroupMap, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupMap", R.string.ChannelMessageGroupMap, name, chat.title); } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { if (messageObject.isSticker()) { String emoji = messageObject.getStickerEmoji(); if (emoji != null) { - msg = LocaleController.formatString("NotificationMessageGroupStickerEmoji", R.string.NotificationMessageGroupStickerEmoji, name, chat.title, emoji); + msg = LocaleController.formatString("ChannelMessageGroupStickerEmoji", R.string.ChannelMessageGroupStickerEmoji, name, chat.title, emoji); } else { - msg = LocaleController.formatString("NotificationMessageGroupSticker", R.string.NotificationMessageGroupSticker, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupSticker", R.string.ChannelMessageGroupSticker, name, chat.title); } } else if (messageObject.isGif()) { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("NotificationMessageGroupGif", R.string.NotificationMessageGroupGif, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupGif", R.string.ChannelMessageGroupGif, name, chat.title); } } else { if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); } else { - msg = LocaleController.formatString("NotificationMessageGroupDocument", R.string.NotificationMessageGroupDocument, name, chat.title); + msg = LocaleController.formatString("ChannelMessageGroupDocument", R.string.ChannelMessageGroupDocument, name, chat.title); } } } } + } else { + if (messageObject.isMediaEmpty()) { + if (!shortMessage && messageObject.messageOwner.message != null && messageObject.messageOwner.message.length() != 0) { + msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, messageObject.messageOwner.message); + } else { + msg = LocaleController.formatString("NotificationMessageGroupNoText", R.string.NotificationMessageGroupNoText, name, chat.title); + } + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaPhoto) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDDBC " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("NotificationMessageGroupPhoto", R.string.NotificationMessageGroupPhoto, name, chat.title); + } + } else if (messageObject.isVideo()) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCF9 " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("NotificationMessageGroupVideo", R.string.NotificationMessageGroupVideo, name, chat.title); + } + } else if (messageObject.isVoice()) { + msg = LocaleController.formatString("NotificationMessageGroupAudio", R.string.NotificationMessageGroupAudio, name, chat.title); + } else if (messageObject.isMusic()) { + msg = LocaleController.formatString("NotificationMessageGroupMusic", R.string.NotificationMessageGroupMusic, name, chat.title); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaContact) { + msg = LocaleController.formatString("NotificationMessageGroupContact", R.string.NotificationMessageGroupContact, name, chat.title); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + msg = LocaleController.formatString("NotificationMessageGroupGame", R.string.NotificationMessageGroupGame, name, chat.title, messageObject.messageOwner.media.game.title); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGeo || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaVenue) { + msg = LocaleController.formatString("NotificationMessageGroupMap", R.string.NotificationMessageGroupMap, name, chat.title); + } else if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaDocument) { + if (messageObject.isSticker()) { + String emoji = messageObject.getStickerEmoji(); + if (emoji != null) { + msg = LocaleController.formatString("NotificationMessageGroupStickerEmoji", R.string.NotificationMessageGroupStickerEmoji, name, chat.title, emoji); + } else { + msg = LocaleController.formatString("NotificationMessageGroupSticker", R.string.NotificationMessageGroupSticker, name, chat.title); + } + } else if (messageObject.isGif()) { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83C\uDFAC " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("NotificationMessageGroupGif", R.string.NotificationMessageGroupGif, name, chat.title); + } + } else { + if (!shortMessage && Build.VERSION.SDK_INT >= 19 && !TextUtils.isEmpty(messageObject.messageOwner.media.caption)) { + msg = LocaleController.formatString("NotificationMessageGroupText", R.string.NotificationMessageGroupText, name, chat.title, "\uD83D\uDCCE " + messageObject.messageOwner.media.caption); + } else { + msg = LocaleController.formatString("NotificationMessageGroupDocument", R.string.NotificationMessageGroupDocument, name, chat.title); + } + } + } } } else { if (ChatObject.isChannel(chat) && !chat.megagroup) { @@ -1240,30 +1345,10 @@ private void scheduleNotificationRepeat() { alarmManager.cancel(pintent); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - private static String getLauncherClassName(Context context) { - try { - PackageManager pm = context.getPackageManager(); - - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - - List resolveInfos = pm.queryIntentActivities(intent, 0); - for (ResolveInfo resolveInfo : resolveInfos) { - String pkgName = resolveInfo.activityInfo.applicationInfo.packageName; - if (pkgName.equalsIgnoreCase(context.getPackageName())) { - return resolveInfo.activityInfo.name; - } - } - } catch (Throwable e) { - FileLog.e("tmessages", e); - } - return null; - } - private boolean isPersonalMessage(MessageObject messageObject) { return messageObject.messageOwner.to_id != null && messageObject.messageOwner.to_id.chat_id == 0 && messageObject.messageOwner.to_id.channel_id == 0 && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty); @@ -1296,7 +1381,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1328,12 +1413,12 @@ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { soundPool.play(soundRecord, 1.0f, 1.0f, 1, 0, 1.0f); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } }*/ @@ -1346,7 +1431,7 @@ private void playInChatSound() { return; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -1368,7 +1453,11 @@ public void run() { @Override public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { if (status == 0) { - soundPool.play(sampleId, 1.0f, 1.0f, 1, 0, 1.0f); + try { + soundPool.play(sampleId, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + FileLog.e(e); + } } } }); @@ -1378,26 +1467,30 @@ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { soundIn = soundPool.load(ApplicationLoader.applicationContext, R.raw.sound_in, 1); } if (soundIn != 0) { - soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); + try { + soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + FileLog.e(e); + } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } private void scheduleNotificationDelay(boolean onlineReason) { try { - FileLog.e("tmessages", "delay notification start, onlineReason = " + onlineReason); + FileLog.e("delay notification start, onlineReason = " + onlineReason); notificationDelayWakelock.acquire(10000); AndroidUtilities.cancelRunOnUIThread(notificationDelayRunnable); AndroidUtilities.runOnUIThread(notificationDelayRunnable, (onlineReason ? 3 * 1000 : 1000)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); showOrUpdateNotification(notifyCheck); } } @@ -1457,7 +1550,7 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { boolean notifyDisabled = false; int needVibrate = 0; String choosenSoundPath = null; - int ledColor = 0xff00ff00; + int ledColor = 0xff0000ff; boolean inAppSounds; boolean inAppVibrate; boolean inAppPreview = false; @@ -1472,8 +1565,15 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { } if (!notifyDisabled && dialog_id == override_dialog_id && chat != null) { - int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); - int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); + int notifyMaxCount; + int notifyDelay; + if (preferences.getBoolean("custom_" + dialog_id, false)) { + notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); + notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); + } else { + notifyMaxCount = 2; + notifyDelay = 3 * 60; + } if (notifyMaxCount != 0) { Point dialogInfo = smartNotificationsDialogs.get(dialog_id); if (dialogInfo == null) { @@ -1501,11 +1601,19 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { inAppVibrate = preferences.getBoolean("EnableInAppVibrate", true); inAppPreview = preferences.getBoolean("EnableInAppPreview", true); inAppPriority = preferences.getBoolean("EnableInAppPriority", false); - vibrateOverride = preferences.getInt("vibrate_" + dialog_id, 0); - priorityOverride = preferences.getInt("priority_" + dialog_id, 3); + boolean custom; + if (custom = preferences.getBoolean("custom_" + dialog_id, false)) { + vibrateOverride = preferences.getInt("vibrate_" + dialog_id, 0); + priorityOverride = preferences.getInt("priority_" + dialog_id, 3); + choosenSoundPath = preferences.getString("sound_path_" + dialog_id, null); + } else { + vibrateOverride = 0; + priorityOverride = 3; + choosenSoundPath = null; + } boolean vibrateOnlyIfSilent = false; - choosenSoundPath = preferences.getString("sound_path_" + dialog_id, null); + if (chat_id != 0) { if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) { choosenSoundPath = null; @@ -1514,7 +1622,7 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { } needVibrate = preferences.getInt("vibrate_group", 0); priority = preferences.getInt("priority_group", 1); - ledColor = preferences.getInt("GroupLed", 0xff00ff00); + ledColor = preferences.getInt("GroupLed", 0xff0000ff); } else if (user_id != 0) { if (choosenSoundPath != null && choosenSoundPath.equals(defaultPath)) { choosenSoundPath = null; @@ -1523,10 +1631,12 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { } needVibrate = preferences.getInt("vibrate_messages", 0); priority = preferences.getInt("priority_group", 1); - ledColor = preferences.getInt("MessagesLed", 0xff00ff00); + ledColor = preferences.getInt("MessagesLed", 0xff0000ff); } - if (preferences.contains("color_" + dialog_id)) { - ledColor = preferences.getInt("color_" + dialog_id, 0); + if (custom) { + if (preferences.contains("color_" + dialog_id)) { + ledColor = preferences.getInt("color_" + dialog_id, 0); + } } if (priorityOverride != 3) { @@ -1537,7 +1647,7 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { vibrateOnlyIfSilent = true; needVibrate = 0; } - if (needVibrate == 2 && (vibrateOverride == 1 || vibrateOverride == 3 || vibrateOverride == 5) || needVibrate != 2 && vibrateOverride == 2 || vibrateOverride != 0) { + if (needVibrate == 2 && (vibrateOverride == 1 || vibrateOverride == 3) || needVibrate != 2 && vibrateOverride == 2 || vibrateOverride != 0 && vibrateOverride != 4) { needVibrate = vibrateOverride; } if (!ApplicationLoader.mainInterfacePaused) { @@ -1560,7 +1670,7 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { needVibrate = 2; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1762,7 +1872,7 @@ private void showOrUpdateNotification(boolean notifyAboutLast) { scheduleNotificationRepeat(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1852,7 +1962,7 @@ private void showExtraNotifications(NotificationCompat.Builder notificationBuild NotificationCompat.Action wearReplyAction = null; - if (!ChatObject.isChannel(chat) && !AndroidUtilities.needShowPasscode(false) && !UserConfig.isWaitingForPasscodeEnter) { + if ((!ChatObject.isChannel(chat) || chat != null && chat.megagroup) && !AndroidUtilities.needShowPasscode(false) && !UserConfig.isWaitingForPasscodeEnter) { Intent msgReplyIntent = new Intent(); msgReplyIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); msgReplyIntent.setAction("org.telegram.messenger.ACTION_MESSAGE_REPLY"); @@ -1873,7 +1983,7 @@ private void showExtraNotifications(NotificationCompat.Builder notificationBuild } else { replyToString = LocaleController.formatString("ReplyToUser", R.string.ReplyToUser, name); } - wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon, replyToString, replyPendingIntent).addRemoteInput(remoteInputWear).build(); + wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_reply_icon, replyToString, replyPendingIntent).setAllowGeneratedReplies(true).addRemoteInput(remoteInputWear).build(); } Integer count = pushDialogs.get(dialog_id); @@ -1918,6 +2028,18 @@ private void showExtraNotifications(NotificationCompat.Builder notificationBuild wearableExtender.addAction(wearReplyAction); } + String dismissalID=null; + if(chat!=null) + dismissalID="tgchat"+chat.id+"_"+max_id; + else if(user!=null) + dismissalID="tguser"+user.id+"_"+max_id; + + wearableExtender.setDismissalId(dismissalID); + + NotificationCompat.WearableExtender summaryExtender=new NotificationCompat.WearableExtender(); + summaryExtender.setDismissalId("summary_"+dismissalID); + notificationBuilder.extend(summaryExtender); + NotificationCompat.Builder builder = new NotificationCompat.Builder(ApplicationLoader.applicationContext) .setContentTitle(name) .setSmallIcon(R.drawable.notification) @@ -1974,7 +2096,7 @@ public void playOutChatSound() { return; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } notificationsQueue.postRunnable(new Runnable() { @Override @@ -1990,7 +2112,11 @@ public void run() { @Override public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { if (status == 0) { - soundPool.play(sampleId, 1.0f, 1.0f, 1, 0, 1.0f); + try { + soundPool.play(sampleId, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + FileLog.e(e); + } } } }); @@ -2000,10 +2126,14 @@ public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { soundOut = soundPool.load(ApplicationLoader.applicationContext, R.raw.sound_out, 1); } if (soundOut != 0) { - soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); + try { + soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + FileLog.e(e); + } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsService.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsService.java index 065dc025bf3..40c66321863 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -17,7 +17,7 @@ public class NotificationsService extends Service { @Override public void onCreate() { - FileLog.e("tmessages", "service started"); + FileLog.e("service started"); ApplicationLoader.postInitApplication(); } @@ -32,7 +32,7 @@ public IBinder onBind(Intent intent) { } public void onDestroy() { - FileLog.e("tmessages", "service destroyed"); + FileLog.e("service destroyed"); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", MODE_PRIVATE); if (preferences.getBoolean("pushService", true)) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java index 2f50ed96396..8bcfc7b0eba 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/OpenChatReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/PopupReplyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/PopupReplyReceiver.java index 74409d0e5cc..fedfa3142dc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/PopupReplyReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/PopupReplyReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ScreenReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ScreenReceiver.java index 77f678de461..c5e584b4540 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ScreenReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ScreenReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -19,11 +19,11 @@ public class ScreenReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { - FileLog.e("tmessages", "screen off"); + FileLog.e("screen off"); ConnectionsManager.getInstance().setAppPaused(true, true); ApplicationLoader.isScreenOn = false; } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { - FileLog.e("tmessages", "screen on"); + FileLog.e("screen on"); ConnectionsManager.getInstance().setAppPaused(false, true); ApplicationLoader.isScreenOn = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java index a9417f550b1..6f8373ebfc6 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SecretChatHelper.java @@ -3,14 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; @@ -22,6 +20,7 @@ import org.telegram.tgnet.TLClassStore; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import java.io.File; import java.math.BigInteger; @@ -157,15 +156,7 @@ public void sendMessagesReadMessage(TLRPC.EncryptedChat encryptedChat, ArrayList if (!(encryptedChat instanceof TLRPC.TL_encryptedChat)) { return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -184,7 +175,7 @@ public void sendMessagesReadMessage(TLRPC.EncryptedChat encryptedChat, ArrayList protected void processUpdateEncryption(TLRPC.TL_updateEncryption update, ConcurrentHashMap usersDict) { final TLRPC.EncryptedChat newChat = update.chat; long dialog_id = ((long) newChat.id) << 32; - TLRPC.EncryptedChat existingChat = MessagesController.getInstance().getEncryptedChatDB(newChat.id); + TLRPC.EncryptedChat existingChat = MessagesController.getInstance().getEncryptedChatDB(newChat.id, false); if (newChat instanceof TLRPC.TL_encryptedChatRequested && existingChat == null) { int user_id = newChat.participant_id; @@ -233,6 +224,7 @@ public void run() { newChat.ttl = exist.ttl; newChat.seq_in = exist.seq_in; newChat.seq_out = exist.seq_out; + newChat.admin_id = exist.admin_id; } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -251,15 +243,7 @@ public void sendMessagesDeleteMessage(TLRPC.EncryptedChat encryptedChat, ArrayLi if (!(encryptedChat instanceof TLRPC.TL_encryptedChat)) { return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -279,15 +263,7 @@ public void sendClearHistoryMessage(TLRPC.EncryptedChat encryptedChat, TLRPC.Mes if (!(encryptedChat instanceof TLRPC.TL_encryptedChat)) { return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -310,15 +286,7 @@ public void sendNotifyLayerMessage(final TLRPC.EncryptedChat encryptedChat, TLRP return; } sendingNotifyLayer.add(encryptedChat.id); - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -339,15 +307,7 @@ public void sendRequestKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -370,15 +330,7 @@ public void sendAcceptKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC. return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -402,15 +354,7 @@ public void sendCommitKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC. return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -433,15 +377,7 @@ public void sendAbortKeyMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.M return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -463,15 +399,7 @@ public void sendNoopMessage(final TLRPC.EncryptedChat encryptedChat, TLRPC.Messa return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -491,15 +419,7 @@ public void sendTTLMessage(TLRPC.EncryptedChat encryptedChat, TLRPC.Message rese return; } - TLRPC.TL_decryptedMessageService reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } - + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; if (resendMessage != null) { @@ -527,14 +447,7 @@ public void sendScreenshotMessage(TLRPC.EncryptedChat encryptedChat, ArrayList= 17) { - reqSend = new TLRPC.TL_decryptedMessageService(); - } else { - reqSend = new TLRPC.TL_decryptedMessageService_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); - } + TLRPC.TL_decryptedMessageService reqSend = new TLRPC.TL_decryptedMessageService(); TLRPC.Message message; @@ -644,51 +557,47 @@ protected void performSendEncryptedRequest(final TLRPC.DecryptedMessage req, fin public void run() { try { TLObject toEncryptObject; - if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 17) { - TLRPC.TL_decryptedMessageLayer layer = new TLRPC.TL_decryptedMessageLayer(); - int myLayer = Math.max(17, AndroidUtilities.getMyLayerVersion(chat.layer)); - layer.layer = Math.min(myLayer, AndroidUtilities.getPeerLayerVersion(chat.layer)); - layer.message = req; - layer.random_bytes = new byte[15]; - Utilities.random.nextBytes(layer.random_bytes); - toEncryptObject = layer; - - if (chat.seq_in == 0 && chat.seq_out == 0) { - if (chat.admin_id == UserConfig.getClientUserId()) { - chat.seq_out = 1; - } else { - chat.seq_in = 1; - } + + TLRPC.TL_decryptedMessageLayer layer = new TLRPC.TL_decryptedMessageLayer(); + int myLayer = Math.max(46, AndroidUtilities.getMyLayerVersion(chat.layer)); + layer.layer = Math.min(myLayer, Math.max(46, AndroidUtilities.getPeerLayerVersion(chat.layer))); + layer.message = req; + layer.random_bytes = new byte[15]; + Utilities.random.nextBytes(layer.random_bytes); + toEncryptObject = layer; + + if (chat.seq_in == 0 && chat.seq_out == 0) { + if (chat.admin_id == UserConfig.getClientUserId()) { + chat.seq_out = 1; + } else { + chat.seq_in = 1; } + } - if (newMsgObj.seq_in == 0 && newMsgObj.seq_out == 0) { - layer.in_seq_no = chat.seq_in; - layer.out_seq_no = chat.seq_out; - chat.seq_out += 2; - if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) { - if (chat.key_create_date == 0) { - chat.key_create_date = ConnectionsManager.getInstance().getCurrentTime(); - } - chat.key_use_count_out++; - if ((chat.key_use_count_out >= 100 || chat.key_create_date < ConnectionsManager.getInstance().getCurrentTime() - 60 * 60 * 24 * 7) && chat.exchange_id == 0 && chat.future_key_fingerprint == 0) { - requestNewSecretChatKey(chat); - } + if (newMsgObj.seq_in == 0 && newMsgObj.seq_out == 0) { + layer.in_seq_no = chat.seq_in; + layer.out_seq_no = chat.seq_out; + chat.seq_out += 2; + if (AndroidUtilities.getPeerLayerVersion(chat.layer) >= 20) { + if (chat.key_create_date == 0) { + chat.key_create_date = ConnectionsManager.getInstance().getCurrentTime(); } - MessagesStorage.getInstance().updateEncryptedChatSeq(chat); - if (newMsgObj != null) { - newMsgObj.seq_in = layer.in_seq_no; - newMsgObj.seq_out = layer.out_seq_no; - MessagesStorage.getInstance().setMessageSeq(newMsgObj.id, newMsgObj.seq_in, newMsgObj.seq_out); + chat.key_use_count_out++; + if ((chat.key_use_count_out >= 100 || chat.key_create_date < ConnectionsManager.getInstance().getCurrentTime() - 60 * 60 * 24 * 7) && chat.exchange_id == 0 && chat.future_key_fingerprint == 0) { + requestNewSecretChatKey(chat); } - } else { - layer.in_seq_no = newMsgObj.seq_in; - layer.out_seq_no = newMsgObj.seq_out; } - FileLog.e("tmessages", req + " send message with in_seq = " + layer.in_seq_no + " out_seq = " + layer.out_seq_no); + MessagesStorage.getInstance().updateEncryptedChatSeq(chat); + if (newMsgObj != null) { + newMsgObj.seq_in = layer.in_seq_no; + newMsgObj.seq_out = layer.out_seq_no; + MessagesStorage.getInstance().setMessageSeq(newMsgObj.id, newMsgObj.seq_in, newMsgObj.seq_out); + } } else { - toEncryptObject = req; + layer.in_seq_no = newMsgObj.seq_in; + layer.out_seq_no = newMsgObj.seq_out; } - + FileLog.e(req + " send message with in_seq = " + layer.in_seq_no + " out_seq = " + layer.out_seq_no); int len = toEncryptObject.getObjectSize(); NativeByteBuffer toEncrypt = new NativeByteBuffer(4 + len); @@ -778,7 +687,7 @@ public void run(TLObject response, TLRPC.TL_error error) { currentChat.key_hash = key_hash; MessagesStorage.getInstance().updateEncryptedChat(currentChat); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -837,7 +746,7 @@ public void run() { } }, ConnectionsManager.RequestFlagInvokeAfter); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -857,7 +766,7 @@ private void applyPeerLayer(final TLRPC.EncryptedChat chat, int newPeerLayer) { chat.key_hash = key_hash; MessagesStorage.getInstance().updateEncryptedChat(chat); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } chat.layer = AndroidUtilities.setPeerLayerVersion(chat.layer, newPeerLayer); @@ -1184,7 +1093,7 @@ public void run() { } else if (serviceMessage.action instanceof TLRPC.TL_decryptedMessageActionRequestKey) { if (chat.exchange_id != 0) { if (chat.exchange_id > serviceMessage.action.exchange_id) { - FileLog.e("tmessages", "we already have request key with higher exchange_id"); + FileLog.e("we already have request key with higher exchange_id"); return null; } else { sendAbortKeyMessage(chat, null, chat.exchange_id); //TODO don't send? @@ -1335,10 +1244,10 @@ public void run() { return null; } } else { - FileLog.e("tmessages", "unknown message " + object); + FileLog.e("unknown message " + object); } } else { - FileLog.e("tmessages", "unknown TLObject"); + FileLog.e("unknown TLObject"); } return null; } @@ -1451,7 +1360,7 @@ public void run() { SendMessagesHelper.getInstance().processUnsentMessages(messages, new ArrayList(), new ArrayList(), encryptedChats); MessagesStorage.getInstance().getDatabase().executeFast(String.format(Locale.US, "REPLACE INTO requested_holes VALUES(%d, %d, %d)", encryptedChat.id, sSeq, endSeq)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1502,7 +1411,7 @@ public int compare(TL_decryptedMessageHolder lhs, TL_decryptedMessageHolder rhs) } protected ArrayList decryptMessage(TLRPC.EncryptedMessage message) { - final TLRPC.EncryptedChat chat = MessagesController.getInstance().getEncryptedChatDB(message.chat_id); + final TLRPC.EncryptedChat chat = MessagesController.getInstance().getEncryptedChatDB(message.chat_id, true); if (chat == null || chat instanceof TLRPC.TL_encryptedChatDiscarded) { return null; } @@ -1552,16 +1461,16 @@ protected ArrayList decryptMessage(TLRPC.EncryptedMessage message } } if (layer.random_bytes.length < 15) { - FileLog.e("tmessages", "got random bytes less than needed"); + FileLog.e("got random bytes less than needed"); return null; } - FileLog.e("tmessages", "current chat in_seq = " + chat.seq_in + " out_seq = " + chat.seq_out); - FileLog.e("tmessages", "got message with in_seq = " + layer.in_seq_no + " out_seq = " + layer.out_seq_no); + FileLog.e("current chat in_seq = " + chat.seq_in + " out_seq = " + chat.seq_out); + FileLog.e("got message with in_seq = " + layer.in_seq_no + " out_seq = " + layer.out_seq_no); if (layer.out_seq_no < chat.seq_in) { return null; } if (chat.seq_in != layer.out_seq_no && chat.seq_in != layer.out_seq_no - 2) { - FileLog.e("tmessages", "got hole"); + FileLog.e("got hole"); ArrayList arr = secretHolesQueue.get(chat.id); if (arr == null) { arr = new ArrayList<>(); @@ -1604,6 +1513,8 @@ public void run() { chat.in_seq_no = layer.in_seq_no; MessagesStorage.getInstance().updateEncryptedChatSeq(chat); object = layer.message; + } else if (!(object instanceof TLRPC.TL_decryptedMessageService && ((TLRPC.TL_decryptedMessageService) object).action instanceof TLRPC.TL_decryptedMessageActionNotifyLayer)) { + return null; } ArrayList messages = new ArrayList<>(); TLRPC.Message decryptedMessage = processDecryptedObject(chat, message.file, message.date, message.random_id, object, new_key_used); @@ -1614,10 +1525,10 @@ public void run() { return messages; } else { is.reuse(); - FileLog.e("tmessages", String.format("fingerprint mismatch %x", fingerprint)); + FileLog.e(String.format("fingerprint mismatch %x", fingerprint)); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; @@ -1700,6 +1611,7 @@ public void run() { newChat.key_use_count_out = encryptedChat.key_use_count_out; newChat.seq_in = encryptedChat.seq_in; newChat.seq_out = encryptedChat.seq_out; + newChat.admin_id = encryptedChat.admin_id; MessagesStorage.getInstance().updateEncryptedChat(newChat); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -1837,7 +1749,7 @@ public void startSecretChat(final Context context, final TLRPC.User user) { return; } startingSecretChat = true; - final ProgressDialog progressDialog = new ProgressDialog(context); + final AlertDialog progressDialog = new AlertDialog(context, 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -1859,7 +1771,7 @@ public void run() { progressDialog.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1900,7 +1812,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } TLRPC.EncryptedChat chat = (TLRPC.EncryptedChat) response; @@ -1941,7 +1853,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -1964,7 +1876,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1979,7 +1891,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java index e9f0c53d207..65c27c25021 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SendMessagesHelper.java @@ -3,13 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -23,6 +22,7 @@ import android.os.Build; import android.os.Bundle; import android.provider.MediaStore; +import android.support.v13.view.inputmethod.InputContentInfoCompat; import android.text.TextUtils; import android.webkit.MimeTypeMap; import android.widget.Toast; @@ -38,8 +38,11 @@ import org.telegram.tgnet.SerializedData; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.PaymentFormActivity; import java.io.File; import java.io.RandomAccessFile; @@ -93,7 +96,7 @@ public void onLocationChanged(Location location) { if (location == null || locationQueryCancelRunnable == null) { return; } - FileLog.e("tmessages", "found location " + location); + FileLog.e("found location " + location); lastKnownLocation = location; if (location.getAccuracy() < 100) { if (delegate != null) { @@ -148,12 +151,12 @@ public void start() { try { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1, 0, gpsLocationListener); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 1, 0, networkLocationListener); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); @@ -161,7 +164,7 @@ public void start() { lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (locationQueryCancelRunnable != null) { AndroidUtilities.cancelRunOnUIThread(locationQueryCancelRunnable); @@ -342,6 +345,9 @@ public void didReceivedNotification(int id, final Object... args) { } } else if (id == NotificationCenter.FilePreparingStarted) { MessageObject messageObject = (MessageObject) args[0]; + if (messageObject.getId() == 0) { + return; + } String finalPath = (String) args[1]; ArrayList arr = delayedMessages.get(messageObject.messageOwner.attachPath); @@ -361,6 +367,9 @@ public void didReceivedNotification(int id, final Object... args) { } } else if (id == NotificationCenter.FileNewChunkAvailable) { MessageObject messageObject = (MessageObject) args[0]; + if (messageObject.getId() == 0) { + return; + } String finalPath = (String) args[1]; long finalSize = (Long) args[2]; boolean isEncrypted = ((int) messageObject.getDialogId()) == 0; @@ -388,6 +397,9 @@ public void didReceivedNotification(int id, final Object... args) { } } else if (id == NotificationCenter.FilePreparingFailed) { MessageObject messageObject = (MessageObject) args[0]; + if (messageObject.getId() == 0) { + return; + } String finalPath = (String) args[1]; stopVideoService(messageObject.messageOwner.attachPath); @@ -435,7 +447,7 @@ public void run() { performSendDelayedMessage(message); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateMessageMedia, message.obj); } else { - FileLog.e("tmessages", "can't load image " + message.httpLocation + " to file " + cacheFile.toString()); + FileLog.e("can't load image " + message.httpLocation + " to file " + cacheFile.toString()); MessagesStorage.getInstance().markMessageAsSendError(message.obj.messageOwner); message.obj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, message.obj.getId()); @@ -460,7 +472,7 @@ public void run() { } } catch (Exception e) { message.documentLocation.thumb = null; - FileLog.e("tmessages", e); + FileLog.e(e); } if (message.documentLocation.thumb == null) { message.documentLocation.thumb = new TLRPC.TL_photoSizeEmpty(); @@ -541,7 +553,7 @@ public void cancelSendingMessage(MessageObject object) { } ArrayList messages = new ArrayList<>(); messages.add(object.getId()); - MessagesController.getInstance().deleteMessages(messages, null, null, object.messageOwner.to_id.channel_id); + MessagesController.getInstance().deleteMessages(messages, null, null, object.messageOwner.to_id.channel_id, false); } public boolean retrySendMessage(MessageObject messageObject, boolean unsent) { @@ -609,7 +621,7 @@ public void processForwardFromMyName(MessageObject messageObject, long did) { if (messageObject == null) { return; } - if (messageObject.messageOwner.media != null && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage)) { + if (messageObject.messageOwner.media != null && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaEmpty) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { if (messageObject.messageOwner.media.photo instanceof TLRPC.TL_photo) { sendMessage((TLRPC.TL_photo) messageObject.messageOwner.media.photo, null, did, messageObject.replyMessageObject, null, null); } else if (messageObject.messageOwner.media.document instanceof TLRPC.TL_document) { @@ -623,7 +635,7 @@ public void processForwardFromMyName(MessageObject messageObject, long did) { user.last_name = messageObject.messageOwner.media.last_name; user.id = messageObject.messageOwner.media.user_id; sendMessage(user, did, messageObject.replyMessageObject, null, null); - } else { + } else if ((int) did != 0) { ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); sendMessage(arrayList, did); @@ -634,7 +646,7 @@ public void processForwardFromMyName(MessageObject messageObject, long did) { webPage = messageObject.messageOwner.media.webpage; } sendMessage(messageObject.messageOwner.message, did, messageObject.replyMessageObject, webPage, true, messageObject.messageOwner.entities, null, null); - } else { + } else if ((int) did != 0) { ArrayList arrayList = new ArrayList<>(); arrayList.add(messageObject); sendMessage(arrayList, did); @@ -651,6 +663,17 @@ public void sendSticker(TLRPC.Document document, long peer, MessageObject replyi if (encryptedChat == null) { return; } + TLRPC.TL_document newDocument = new TLRPC.TL_document(); + newDocument.id = document.id; + newDocument.access_hash = document.access_hash; + newDocument.date = document.date; + newDocument.mime_type = document.mime_type; + newDocument.size = document.size; + newDocument.dc_id = document.dc_id; + newDocument.attributes = new ArrayList<>(document.attributes); + if (newDocument.mime_type == null) { + newDocument.mime_type = ""; + } if (document.thumb instanceof TLRPC.TL_photoSize) { File file = FileLoader.getPathToAttach(document.thumb, true); if (file.exists()) { @@ -659,7 +682,7 @@ public void sendSticker(TLRPC.Document document, long peer, MessageObject replyi byte[] arr = new byte[(int) file.length()]; RandomAccessFile reader = new RandomAccessFile(file, "r"); reader.readFully(arr); - TLRPC.TL_document newDocument = new TLRPC.TL_document(); + newDocument.thumb = new TLRPC.TL_photoCachedSize(); newDocument.thumb.location = document.thumb.location; newDocument.thumb.size = document.thumb.size; @@ -667,299 +690,304 @@ public void sendSticker(TLRPC.Document document, long peer, MessageObject replyi newDocument.thumb.h = document.thumb.h; newDocument.thumb.type = document.thumb.type; newDocument.thumb.bytes = arr; - - newDocument.id = document.id; - newDocument.access_hash = document.access_hash; - newDocument.date = document.date; - newDocument.mime_type = document.mime_type; - newDocument.size = document.size; - newDocument.dc_id = document.dc_id; - newDocument.attributes = document.attributes; - if (newDocument.mime_type == null) { - newDocument.mime_type = ""; - } - document = newDocument; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } + if (newDocument.thumb == null) { + newDocument.thumb = new TLRPC.TL_photoSizeEmpty(); + newDocument.thumb.type = "s"; + } + document = newDocument; } SendMessagesHelper.getInstance().sendMessage((TLRPC.TL_document) document, null, null, peer, replyingMessageObject, null, null); } public void sendMessage(ArrayList messages, final long peer) { - if ((int) peer == 0 || messages == null || messages.isEmpty()) { + if (messages == null || messages.isEmpty()) { return; } int lower_id = (int) peer; - final TLRPC.Peer to_id = MessagesController.getPeer((int) peer); - boolean isMegagroup = false; - boolean isSignature = false; - if (lower_id > 0) { - TLRPC.User sendToUser = MessagesController.getInstance().getUser(lower_id); - if (sendToUser == null) { - return; - } - } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); - if (ChatObject.isChannel(chat)) { - isMegagroup = chat.megagroup; - isSignature = chat.signatures; + if (lower_id != 0) { + final TLRPC.Peer to_id = MessagesController.getPeer((int) peer); + boolean isMegagroup = false; + boolean isSignature = false; + if (lower_id > 0) { + TLRPC.User sendToUser = MessagesController.getInstance().getUser(lower_id); + if (sendToUser == null) { + return; + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + if (ChatObject.isChannel(chat)) { + isMegagroup = chat.megagroup; + isSignature = chat.signatures; + } } - } - ArrayList objArr = new ArrayList<>(); - ArrayList arr = new ArrayList<>(); - ArrayList randomIds = new ArrayList<>(); - ArrayList ids = new ArrayList<>(); - HashMap messagesByRandomIds = new HashMap<>(); - TLRPC.InputPeer inputPeer = MessagesController.getInputPeer(lower_id); - long lastDialogId = 0; - for (int a = 0; a < messages.size(); a++) { - MessageObject msgObj = messages.get(a); - if (msgObj.getId() <= 0) { - continue; - } + ArrayList objArr = new ArrayList<>(); + ArrayList arr = new ArrayList<>(); + ArrayList randomIds = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); + HashMap messagesByRandomIds = new HashMap<>(); + TLRPC.InputPeer inputPeer = MessagesController.getInputPeer(lower_id); + long lastDialogId = 0; + final boolean toMyself = peer == UserConfig.getClientUserId(); + for (int a = 0; a < messages.size(); a++) { + MessageObject msgObj = messages.get(a); + if (msgObj.getId() <= 0) { + continue; + } - final TLRPC.Message newMsg = new TLRPC.TL_message(); - if (msgObj.isForwarded()) { - newMsg.fwd_from = msgObj.messageOwner.fwd_from; - } else { - newMsg.fwd_from = new TLRPC.TL_messageFwdHeader(); - if (msgObj.isFromUser()) { - newMsg.fwd_from.from_id = msgObj.messageOwner.from_id; - newMsg.fwd_from.flags |= 1; + final TLRPC.Message newMsg = new TLRPC.TL_message(); + if (msgObj.isForwarded()) { + newMsg.fwd_from = msgObj.messageOwner.fwd_from; } else { - newMsg.fwd_from.channel_id = msgObj.messageOwner.to_id.channel_id; - newMsg.fwd_from.flags |= 2; - if (msgObj.messageOwner.post) { - newMsg.fwd_from.channel_post = msgObj.getId(); - newMsg.fwd_from.flags |= 4; - if (msgObj.messageOwner.from_id > 0) { - newMsg.fwd_from.from_id = msgObj.messageOwner.from_id; - newMsg.fwd_from.flags |= 1; + newMsg.fwd_from = new TLRPC.TL_messageFwdHeader(); + if (msgObj.isFromUser()) { + newMsg.fwd_from.from_id = msgObj.messageOwner.from_id; + newMsg.fwd_from.flags |= 1; + } else { + newMsg.fwd_from.channel_id = msgObj.messageOwner.to_id.channel_id; + newMsg.fwd_from.flags |= 2; + if (msgObj.messageOwner.post) { + newMsg.fwd_from.channel_post = msgObj.getId(); + newMsg.fwd_from.flags |= 4; + if (msgObj.messageOwner.from_id > 0) { + newMsg.fwd_from.from_id = msgObj.messageOwner.from_id; + newMsg.fwd_from.flags |= 1; + } } } + newMsg.date = msgObj.messageOwner.date; } - newMsg.date = msgObj.messageOwner.date; - } - newMsg.media = msgObj.messageOwner.media; - newMsg.flags = TLRPC.MESSAGE_FLAG_FWD; - if (newMsg.media != null) { - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; - } - if (isMegagroup) { - newMsg.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; - } - if (msgObj.messageOwner.via_bot_id != 0) { - newMsg.via_bot_id = msgObj.messageOwner.via_bot_id; - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_BOT_ID; - } - newMsg.message = msgObj.messageOwner.message; - newMsg.fwd_msg_id = msgObj.getId(); - newMsg.attachPath = msgObj.messageOwner.attachPath; - newMsg.entities = msgObj.messageOwner.entities; - if (!newMsg.entities.isEmpty()) { - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; - } - if (newMsg.attachPath == null) { - newMsg.attachPath = ""; - } - newMsg.local_id = newMsg.id = UserConfig.getNewMessageId(); - newMsg.out = true; - if (to_id.channel_id != 0 && !isMegagroup) { - newMsg.from_id = isSignature ? UserConfig.getClientUserId() : -to_id.channel_id; - newMsg.post = true; - } else { - newMsg.from_id = UserConfig.getClientUserId(); - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_FROM_ID; - } - if (newMsg.random_id == 0) { - newMsg.random_id = getNextRandomId(); - } - randomIds.add(newMsg.random_id); - messagesByRandomIds.put(newMsg.random_id, newMsg); - ids.add(newMsg.fwd_msg_id); - newMsg.date = ConnectionsManager.getInstance().getCurrentTime(); - if (inputPeer instanceof TLRPC.TL_inputPeerChannel) { - if (!isMegagroup) { - newMsg.views = 1; - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; + newMsg.media = msgObj.messageOwner.media; + newMsg.flags = TLRPC.MESSAGE_FLAG_FWD; + if (newMsg.media != null) { + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; + } + if (isMegagroup) { + newMsg.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } + if (msgObj.messageOwner.via_bot_id != 0) { + newMsg.via_bot_id = msgObj.messageOwner.via_bot_id; + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_BOT_ID; + } + newMsg.message = msgObj.messageOwner.message; + newMsg.fwd_msg_id = msgObj.getId(); + newMsg.attachPath = msgObj.messageOwner.attachPath; + newMsg.entities = msgObj.messageOwner.entities; + if (!newMsg.entities.isEmpty()) { + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; + } + if (newMsg.attachPath == null) { + newMsg.attachPath = ""; + } + newMsg.local_id = newMsg.id = UserConfig.getNewMessageId(); + newMsg.out = true; + if (to_id.channel_id != 0 && !isMegagroup) { + newMsg.from_id = isSignature ? UserConfig.getClientUserId() : -to_id.channel_id; + newMsg.post = true; } else { + newMsg.from_id = UserConfig.getClientUserId(); + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_FROM_ID; + } + if (newMsg.random_id == 0) { + newMsg.random_id = getNextRandomId(); + } + randomIds.add(newMsg.random_id); + messagesByRandomIds.put(newMsg.random_id, newMsg); + ids.add(newMsg.fwd_msg_id); + newMsg.date = ConnectionsManager.getInstance().getCurrentTime(); + if (inputPeer instanceof TLRPC.TL_inputPeerChannel) { + if (!isMegagroup) { + newMsg.views = 1; + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; + } else { + newMsg.unread = true; + } + } else { + if ((msgObj.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { + newMsg.views = msgObj.messageOwner.views; + newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; + } newMsg.unread = true; } - } else { - if ((msgObj.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - newMsg.views = msgObj.messageOwner.views; - newMsg.flags |= TLRPC.MESSAGE_FLAG_HAS_VIEWS; + newMsg.dialog_id = peer; + newMsg.to_id = to_id; + if (MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) { + newMsg.media_unread = true; } - newMsg.unread = true; - } - newMsg.dialog_id = peer; - newMsg.to_id = to_id; - if (MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) { - newMsg.media_unread = true; - } - if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { - newMsg.ttl = -msgObj.messageOwner.to_id.channel_id; - } - MessageObject newMsgObj = new MessageObject(newMsg, null, true); - newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; - objArr.add(newMsgObj); - arr.add(newMsg); + if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { + newMsg.ttl = -msgObj.messageOwner.to_id.channel_id; + } + MessageObject newMsgObj = new MessageObject(newMsg, null, true); + newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SENDING; + objArr.add(newMsgObj); + arr.add(newMsg); - putToSendingMessages(newMsg); - boolean differentDialog = false; + putToSendingMessages(newMsg); + boolean differentDialog = false; - if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "forward message user_id = " + inputPeer.user_id + " chat_id = " + inputPeer.chat_id + " channel_id = " + inputPeer.channel_id + " access_hash = " + inputPeer.access_hash); - } + if (BuildVars.DEBUG_VERSION) { + FileLog.e("forward message user_id = " + inputPeer.user_id + " chat_id = " + inputPeer.chat_id + " channel_id = " + inputPeer.channel_id + " access_hash = " + inputPeer.access_hash); + } - if (arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { - MessagesStorage.getInstance().putMessages(new ArrayList<>(arr), false, true, false, 0); - MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); - UserConfig.saveConfig(false); + if (arr.size() == 100 || a == messages.size() - 1 || a != messages.size() - 1 && messages.get(a + 1).getDialogId() != msgObj.getDialogId()) { + MessagesStorage.getInstance().putMessages(new ArrayList<>(arr), false, true, false, 0); + MessagesController.getInstance().updateInterfaceWithMessages(peer, objArr); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.dialogsNeedReload); + UserConfig.saveConfig(false); - TLRPC.TL_messages_forwardMessages req = new TLRPC.TL_messages_forwardMessages(); - req.to_peer = inputPeer; - if (req.to_peer instanceof TLRPC.TL_inputPeerChannel) { - req.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false); - } - if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(msgObj.messageOwner.to_id.channel_id); - req.from_peer = new TLRPC.TL_inputPeerChannel(); - req.from_peer.channel_id = msgObj.messageOwner.to_id.channel_id; - if (chat != null) { - req.from_peer.access_hash = chat.access_hash; + final TLRPC.TL_messages_forwardMessages req = new TLRPC.TL_messages_forwardMessages(); + req.to_peer = inputPeer; + if (req.to_peer instanceof TLRPC.TL_inputPeerChannel) { + req.silent = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("silent_" + peer, false); } - } else { - req.from_peer = new TLRPC.TL_inputPeerEmpty(); - } - req.random_id = randomIds; - req.id = ids; - req.with_my_score = messages.size() == 1 && messages.get(0).messageOwner.with_my_score; - - final ArrayList newMsgObjArr = arr; - final ArrayList newMsgArr = objArr; - final HashMap messagesByRandomIdsFinal = messagesByRandomIds; - final boolean isMegagroupFinal = isMegagroup; - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(TLObject response, final TLRPC.TL_error error) { - if (error == null) { - HashMap newMessagesByIds = new HashMap<>(); - TLRPC.Updates updates = (TLRPC.Updates) response; - for (int a = 0; a < updates.updates.size(); a++) { - TLRPC.Update update = updates.updates.get(a); - if (update instanceof TLRPC.TL_updateMessageID) { - TLRPC.TL_updateMessageID updateMessageID = (TLRPC.TL_updateMessageID) update; - newMessagesByIds.put(updateMessageID.id, updateMessageID.random_id); - updates.updates.remove(a); - a--; - } - } - Integer value = MessagesController.getInstance().dialogs_read_outbox_max.get(peer); - if (value == null) { - value = MessagesStorage.getInstance().getDialogReadMax(true, peer); - MessagesController.getInstance().dialogs_read_outbox_max.put(peer, value); - } + if (msgObj.messageOwner.to_id instanceof TLRPC.TL_peerChannel) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(msgObj.messageOwner.to_id.channel_id); + req.from_peer = new TLRPC.TL_inputPeerChannel(); + req.from_peer.channel_id = msgObj.messageOwner.to_id.channel_id; + if (chat != null) { + req.from_peer.access_hash = chat.access_hash; + } + } else { + req.from_peer = new TLRPC.TL_inputPeerEmpty(); + } + req.random_id = randomIds; + req.id = ids; + req.with_my_score = messages.size() == 1 && messages.get(0).messageOwner.with_my_score; - for (int a = 0; a < updates.updates.size(); a++) { - TLRPC.Update update = updates.updates.get(a); - if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateNewChannelMessage) { - final TLRPC.Message message; - if (update instanceof TLRPC.TL_updateNewMessage) { - message = ((TLRPC.TL_updateNewMessage) update).message; - MessagesController.getInstance().processNewDifferenceParams(-1, update.pts, -1, update.pts_count); - } else { - message = ((TLRPC.TL_updateNewChannelMessage) update).message; - MessagesController.getInstance().processNewChannelDifferenceParams(update.pts, update.pts_count, message.to_id.channel_id); - if (isMegagroupFinal) { - message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; - } + final ArrayList newMsgObjArr = arr; + final ArrayList newMsgArr = objArr; + final HashMap messagesByRandomIdsFinal = messagesByRandomIds; + final boolean isMegagroupFinal = isMegagroup; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, final TLRPC.TL_error error) { + if (error == null) { + HashMap newMessagesByIds = new HashMap<>(); + TLRPC.Updates updates = (TLRPC.Updates) response; + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (update instanceof TLRPC.TL_updateMessageID) { + TLRPC.TL_updateMessageID updateMessageID = (TLRPC.TL_updateMessageID) update; + newMessagesByIds.put(updateMessageID.id, updateMessageID.random_id); + updates.updates.remove(a); + a--; } - message.unread = value < message.id; + } + Integer value = MessagesController.getInstance().dialogs_read_outbox_max.get(peer); + if (value == null) { + value = MessagesStorage.getInstance().getDialogReadMax(true, peer); + MessagesController.getInstance().dialogs_read_outbox_max.put(peer, value); + } - Long random_id = newMessagesByIds.get(message.id); - if (random_id != null) { - final TLRPC.Message newMsgObj = messagesByRandomIdsFinal.get(random_id); - if (newMsgObj == null) { - continue; + int sentCount = 0; + for (int a = 0; a < updates.updates.size(); a++) { + TLRPC.Update update = updates.updates.get(a); + if (update instanceof TLRPC.TL_updateNewMessage || update instanceof TLRPC.TL_updateNewChannelMessage) { + final TLRPC.Message message; + if (update instanceof TLRPC.TL_updateNewMessage) { + message = ((TLRPC.TL_updateNewMessage) update).message; + MessagesController.getInstance().processNewDifferenceParams(-1, update.pts, -1, update.pts_count); + } else { + message = ((TLRPC.TL_updateNewChannelMessage) update).message; + MessagesController.getInstance().processNewChannelDifferenceParams(update.pts, update.pts_count, message.to_id.channel_id); + if (isMegagroupFinal) { + message.flags |= TLRPC.MESSAGE_FLAG_MEGAGROUP; + } } - int index = newMsgObjArr.indexOf(newMsgObj); - if (index == -1) { - continue; + message.unread = value < message.id; + if (toMyself) { + message.out = true; + message.unread = false; } - MessageObject msgObj = newMsgArr.get(index); - newMsgObjArr.remove(index); - newMsgArr.remove(index); - final int oldId = newMsgObj.id; - final ArrayList sentMessages = new ArrayList<>(); - sentMessages.add(message); - newMsgObj.id = message.id; - updateMediaPaths(msgObj, message, null, true); - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - MessagesStorage.getInstance().updateMessageStateAndId(newMsgObj.random_id, oldId, newMsgObj.id, 0, false, to_id.channel_id); - MessagesStorage.getInstance().putMessages(sentMessages, true, false, false, 0); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; - SearchQuery.increasePeerRaiting(peer); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, message.id, message, peer); - processSentMessage(oldId); - removeFromSendingMessages(oldId); + + Long random_id = newMessagesByIds.get(message.id); + if (random_id != null) { + final TLRPC.Message newMsgObj = messagesByRandomIdsFinal.get(random_id); + if (newMsgObj == null) { + continue; + } + int index = newMsgObjArr.indexOf(newMsgObj); + if (index == -1) { + continue; + } + MessageObject msgObj = newMsgArr.get(index); + newMsgObjArr.remove(index); + newMsgArr.remove(index); + final int oldId = newMsgObj.id; + final ArrayList sentMessages = new ArrayList<>(); + sentMessages.add(message); + newMsgObj.id = message.id; + sentCount++; + updateMediaPaths(msgObj, message, null, true); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + MessagesStorage.getInstance().updateMessageStateAndId(newMsgObj.random_id, oldId, newMsgObj.id, 0, false, to_id.channel_id); + MessagesStorage.getInstance().putMessages(sentMessages, true, false, false, 0); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; + SearchQuery.increasePeerRaiting(peer); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, message.id, message, peer); + processSentMessage(oldId); + removeFromSendingMessages(oldId); + } + }); + if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { + stopVideoService(newMsgObj.attachPath); } - }); - if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { - stopVideoService(newMsgObj.attachPath); } - } - }); + }); + } } } - } - } else { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (error.text.equals("PEER_FLOOD")) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 0); + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, sentCount); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + AlertsCreator.processError(error, null, req); } - } - }); - } - for (int a = 0; a < newMsgObjArr.size(); a++) { - final TLRPC.Message newMsgObj = newMsgObjArr.get(a); - MessagesStorage.getInstance().markMessageAsSendError(newMsgObj); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; - NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); - processSentMessage(newMsgObj.id); - if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { - stopVideoService(newMsgObj.attachPath); + }); + } + for (int a = 0; a < newMsgObjArr.size(); a++) { + final TLRPC.Message newMsgObj = newMsgObjArr.get(a); + MessagesStorage.getInstance().markMessageAsSendError(newMsgObj); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageSendError, newMsgObj.id); + processSentMessage(newMsgObj.id); + if (MessageObject.isVideoMessage(newMsgObj) || MessageObject.isNewGifMessage(newMsgObj)) { + stopVideoService(newMsgObj.attachPath); + } + removeFromSendingMessages(newMsgObj.id); } - removeFromSendingMessages(newMsgObj.id); - } - }); + }); + } } - } - }, ConnectionsManager.RequestFlagCanCompress | ConnectionsManager.RequestFlagInvokeAfter); + }, ConnectionsManager.RequestFlagCanCompress | ConnectionsManager.RequestFlagInvokeAfter); - if (a != messages.size() - 1) { - objArr = new ArrayList<>(); - arr = new ArrayList<>(); - randomIds = new ArrayList<>(); - ids = new ArrayList<>(); - messagesByRandomIds = new HashMap<>(); + if (a != messages.size() - 1) { + objArr = new ArrayList<>(); + arr = new ArrayList<>(); + randomIds = new ArrayList<>(); + ids = new ArrayList<>(); + messagesByRandomIds = new HashMap<>(); + } } } + } else { + for (int a = 0; a < messages.size(); a++) { + processForwardFromMyName(messages.get(a), peer); + } } } @@ -968,7 +996,7 @@ public int editMessage(MessageObject messageObject, String message, boolean sear return 0; } - TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); + final TLRPC.TL_messages_editMessage req = new TLRPC.TL_messages_editMessage(); req.peer = MessagesController.getInputPeer((int) messageObject.getDialogId()); req.message = message; req.flags |= 2048; @@ -980,7 +1008,7 @@ public int editMessage(MessageObject messageObject, String message, boolean sear } return ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override - public void run(TLObject response, TLRPC.TL_error error) { + public void run(TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { @@ -990,18 +1018,12 @@ public void run() { if (error == null) { MessagesController.getInstance().processUpdates((TLRPC.Updates) response, false); } else { - if (!error.text.equals("MESSAGE_NOT_MODIFIED")) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("EditMessageError", R.string.EditMessageError)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - fragment.showDialog(builder.create()); - } - }); - } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + AlertsCreator.processError(error, fragment, req); + } + }); } } }); @@ -1019,99 +1041,164 @@ private void sendLocation(Location location) { } public void sendCurrentLocation(final MessageObject messageObject, final TLRPC.KeyboardButton button) { - final String key = messageObject.getId() + "_" + Utilities.bytesToHex(button.data); + if (messageObject == null || button == null) { + return; + } + final String key = messageObject.getDialogId() + "_" + messageObject.getId() + "_" + Utilities.bytesToHex(button.data) + "_" + (button instanceof TLRPC.TL_keyboardButtonGame ? "1" : "0"); waitingForLocation.put(key, messageObject); locationProvider.start(); } public boolean isSendingCurrentLocation(MessageObject messageObject, TLRPC.KeyboardButton button) { - return !(messageObject == null || button == null) && waitingForLocation.containsKey(messageObject.getId() + "_" + Utilities.bytesToHex(button.data)); + if (messageObject == null || button == null) { + return false; + } + final String key = messageObject.getDialogId() + "_" + messageObject.getId() + "_" + Utilities.bytesToHex(button.data) + "_" + (button instanceof TLRPC.TL_keyboardButtonGame ? "1" : "0"); + return waitingForLocation.containsKey(key); } - public void sendCallback(final MessageObject messageObject, final TLRPC.KeyboardButton button, final ChatActivity parentFragment) { + public void sendCallback(final boolean cache, final MessageObject messageObject, final TLRPC.KeyboardButton button, final ChatActivity parentFragment) { if (messageObject == null || button == null || parentFragment == null) { return; } - final String key = messageObject.getId() + "_" + Utilities.bytesToHex(button.data); - waitingForCallback.put(key, messageObject); - TLRPC.TL_messages_getBotCallbackAnswer req = new TLRPC.TL_messages_getBotCallbackAnswer(); - req.peer = MessagesController.getInputPeer((int) messageObject.getDialogId()); - req.msg_id = messageObject.getId(); - req.game = button instanceof TLRPC.TL_keyboardButtonGame; - if (button.data != null) { - req.flags |= 1; - req.data = button.data; + final boolean cacheFinal; + int type; + if (button instanceof TLRPC.TL_keyboardButtonGame) { + cacheFinal = false; + type = 1; + } else { + cacheFinal = cache; + if (button instanceof TLRPC.TL_keyboardButtonBuy) { + type = 2; + } else { + type = 0; + } } - ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + final String key = messageObject.getDialogId() + "_" + messageObject.getId() + "_" + Utilities.bytesToHex(button.data) + "_" + type; + waitingForCallback.put(key, messageObject); + + RequestDelegate requestDelegate = new RequestDelegate() { @Override public void run(final TLObject response, TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { waitingForCallback.remove(key); - if (response != null) { - TLRPC.TL_messages_botCallbackAnswer res = (TLRPC.TL_messages_botCallbackAnswer) response; - if (res.message != null) { - if (res.alert) { + if (cacheFinal && response == null) { + sendCallback(false, messageObject, button, parentFragment); + } else if (response != null) { + if (button instanceof TLRPC.TL_keyboardButtonBuy) { + if (response instanceof TLRPC.TL_payments_paymentForm) { + final TLRPC.TL_payments_paymentForm form = (TLRPC.TL_payments_paymentForm) response; + MessagesController.getInstance().putUsers(form.users, false); + parentFragment.presentFragment(new PaymentFormActivity(form, messageObject)); + } else if (response instanceof TLRPC.TL_payments_paymentReceipt) { + parentFragment.presentFragment(new PaymentFormActivity(messageObject, (TLRPC.TL_payments_paymentReceipt) response)); + } + } else { + TLRPC.TL_messages_botCallbackAnswer res = (TLRPC.TL_messages_botCallbackAnswer) response; + if (!cacheFinal && res.cache_time != 0) { + MessagesStorage.getInstance().saveBotCache(key, res); + } + if (res.message != null) { + if (res.alert) { + if (parentFragment.getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentFragment.getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(res.message); + parentFragment.showDialog(builder.create()); + } else { + int uid = messageObject.messageOwner.from_id; + if (messageObject.messageOwner.via_bot_id != 0) { + uid = messageObject.messageOwner.via_bot_id; + } + String name = null; + if (uid > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(uid); + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); + if (chat != null) { + name = chat.title; + } + } + if (name == null) { + name = "bot"; + } + parentFragment.showAlert(name, res.message); + } + } else if (res.url != null) { if (parentFragment.getParentActivity() == null) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(parentFragment.getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - builder.setMessage(res.message); - parentFragment.showDialog(builder.create()); - } else { int uid = messageObject.messageOwner.from_id; if (messageObject.messageOwner.via_bot_id != 0) { uid = messageObject.messageOwner.via_bot_id; } - String name = null; - if (uid > 0) { - TLRPC.User user = MessagesController.getInstance().getUser(uid); - if (user != null) { - name = ContactsController.formatName(user.first_name, user.last_name); + TLRPC.User user = MessagesController.getInstance().getUser(uid); + boolean verified = user != null && user.verified; + if (button instanceof TLRPC.TL_keyboardButtonGame) { + TLRPC.TL_game game = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame ? messageObject.messageOwner.media.game : null; + if (game == null) { + return; } + parentFragment.showOpenGameAlert(game, messageObject, res.url, !verified && ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("askgame_" + uid, true), uid); } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); - if (chat != null) { - name = chat.title; - } - } - if (name == null) { - name = "bot"; - } - parentFragment.showAlert(name, res.message); - } - } else if (res.url != null) { - if (parentFragment.getParentActivity() == null) { - return; - } - int uid = messageObject.messageOwner.from_id; - if (messageObject.messageOwner.via_bot_id != 0) { - uid = messageObject.messageOwner.via_bot_id; - } - TLRPC.User user = MessagesController.getInstance().getUser(uid); - boolean verified = user != null && user.verified; - if (button instanceof TLRPC.TL_keyboardButtonGame) { - TLRPC.TL_game game = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame ? messageObject.messageOwner.media.game : null; - if (game == null) { - return; + parentFragment.showOpenUrlAlert(res.url, false); } - parentFragment.showOpenGameAlert(game, messageObject, res.url, !verified && ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).getBoolean("askgame_" + uid, true), uid); - } else { - parentFragment.showOpenUrlAlert(res.url, false); } } } } }); } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + }; + if (cacheFinal) { + MessagesStorage.getInstance().getBotCache(key, requestDelegate); + } else { + if (button instanceof TLRPC.TL_keyboardButtonBuy) { + if ((messageObject.messageOwner.media.flags & 4) == 0) { + TLRPC.TL_payments_getPaymentForm req = new TLRPC.TL_payments_getPaymentForm(); + req.msg_id = messageObject.getId(); + ConnectionsManager.getInstance().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + } else { + TLRPC.TL_payments_getPaymentReceipt req = new TLRPC.TL_payments_getPaymentReceipt(); + req.msg_id = messageObject.messageOwner.media.receipt_msg_id; + ConnectionsManager.getInstance().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + } + } else { + TLRPC.TL_messages_getBotCallbackAnswer req = new TLRPC.TL_messages_getBotCallbackAnswer(); + req.peer = MessagesController.getInputPeer((int) messageObject.getDialogId()); + req.msg_id = messageObject.getId(); + req.game = button instanceof TLRPC.TL_keyboardButtonGame; + if (button.data != null) { + req.flags |= 1; + req.data = button.data; + } + ConnectionsManager.getInstance().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + } + } } public boolean isSendingCallback(MessageObject messageObject, TLRPC.KeyboardButton button) { - return !(messageObject == null || button == null) && waitingForCallback.containsKey(messageObject.getId() + "_" + Utilities.bytesToHex(button.data)); + if (messageObject == null || button == null) { + return false; + } + int type; + if (button instanceof TLRPC.TL_keyboardButtonGame) { + type = 1; + } else if (button instanceof TLRPC.TL_keyboardButtonBuy) { + type = 2; + } else { + type = 0; + } + final String key = messageObject.getDialogId() + "_" + messageObject.getId() + "_" + Utilities.bytesToHex(button.data) + "_" + type; + return waitingForCallback.containsKey(key); } public void sendGame(TLRPC.InputPeer peer, TLRPC.TL_inputMediaGame game, long random_id, final long taskId) { @@ -1135,7 +1222,7 @@ public void sendGame(TLRPC.InputPeer peer, TLRPC.TL_inputMediaGame game, long ra peer.serializeToStream(data); game.serializeToStream(data); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } newTaskId = MessagesStorage.getInstance().createPendingTask(data); } else { @@ -1259,7 +1346,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } } else { if (message != null) { - if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { + if (encryptedChat != null) { newMsg = new TLRPC.TL_message_secret(); } else { newMsg = new TLRPC.TL_message(); @@ -1289,7 +1376,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } newMsg.message = message; } else if (location != null) { - if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { + if (encryptedChat != null) { newMsg = new TLRPC.TL_message_secret(); } else { newMsg = new TLRPC.TL_message(); @@ -1302,7 +1389,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p type = 1; } } else if (photo != null) { - if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { + if (encryptedChat != null) { newMsg = new TLRPC.TL_message_secret(); } else { newMsg = new TLRPC.TL_message(); @@ -1332,7 +1419,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p type = 9; } } else if (user != null) { - if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { + if (encryptedChat != null) { newMsg = new TLRPC.TL_message_secret(); } else { newMsg = new TLRPC.TL_message(); @@ -1355,7 +1442,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p type = 6; } } else if (document != null) { - if (encryptedChat != null && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { + if (encryptedChat != null) { newMsg = new TLRPC.TL_message_secret(); } else { newMsg = new TLRPC.TL_message(); @@ -1386,25 +1473,25 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p for (int a = 0; a < document.attributes.size(); a++) { TLRPC.DocumentAttribute attribute = document.attributes.get(a); if (attribute instanceof TLRPC.TL_documentAttributeSticker) { - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) < 46) { - document.attributes.remove(a); - document.attributes.add(new TLRPC.TL_documentAttributeSticker_old()); - } else { - document.attributes.remove(a); - TLRPC.TL_documentAttributeSticker_layer55 attributeSticker = new TLRPC.TL_documentAttributeSticker_layer55(); - document.attributes.add(attributeSticker); - attributeSticker.alt = attribute.alt; - if (attribute.stickerset != null) { - String name = StickersQuery.getStickerSetName(attribute.stickerset.id); - if (name != null && name.length() > 0) { - attributeSticker.stickerset = new TLRPC.TL_inputStickerSetShortName(); - attributeSticker.stickerset.short_name = name; - } else { - attributeSticker.stickerset = new TLRPC.TL_inputStickerSetEmpty(); - } + document.attributes.remove(a); + TLRPC.TL_documentAttributeSticker_layer55 attributeSticker = new TLRPC.TL_documentAttributeSticker_layer55(); + document.attributes.add(attributeSticker); + attributeSticker.alt = attribute.alt; + if (attribute.stickerset != null) { + String name; + if (attribute.stickerset instanceof TLRPC.TL_inputStickerSetShortName) { + name = attribute.stickerset.short_name; + } else { + name = StickersQuery.getStickerSetName(attribute.stickerset.id); + } + if (!TextUtils.isEmpty(name)) { + attributeSticker.stickerset = new TLRPC.TL_inputStickerSetShortName(); + attributeSticker.stickerset.short_name = name; } else { attributeSticker.stickerset = new TLRPC.TL_inputStickerSetEmpty(); } + } else { + attributeSticker.stickerset = new TLRPC.TL_inputStickerSetEmpty(); } break; } @@ -1516,7 +1603,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p newMsg.to_id.user_id = encryptedChat.participant_id; } newMsg.ttl = encryptedChat.ttl; - if (newMsg.ttl != 0) { + if (newMsg.ttl != 0 && newMsg.media.document != null) { if (MessageObject.isVoiceMessage(newMsg)) { int duration = 0; for (int a = 0; a < newMsg.media.document.attributes.size(); a++) { @@ -1540,7 +1627,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } } } - if ((encryptedChat == null || AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) && high_id != 1 && MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) { + if (high_id != 1 && MessageObject.isVoiceMessage(newMsg) && newMsg.to_id.channel_id == 0) { newMsg.media_unread = true; } @@ -1561,7 +1648,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p if (BuildVars.DEBUG_VERSION) { if (sendToPeer != null) { - FileLog.e("tmessages", "send message user_id = " + sendToPeer.user_id + " chat_id = " + sendToPeer.chat_id + " channel_id = " + sendToPeer.channel_id + " access_hash = " + sendToPeer.access_hash); + FileLog.e("send message user_id = " + sendToPeer.user_id + " chat_id = " + sendToPeer.chat_id + " channel_id = " + sendToPeer.channel_id + " access_hash = " + sendToPeer.access_hash); } } @@ -1605,24 +1692,15 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } } else { TLRPC.TL_decryptedMessage reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - reqSend = new TLRPC.TL_decryptedMessage(); - reqSend.ttl = newMsg.ttl; - if (entities != null && !entities.isEmpty()) { - reqSend.entities = entities; - reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; - } - if (reply_to_msg != null && reply_to_msg.messageOwner.random_id != 0) { - reqSend.reply_to_random_id = reply_to_msg.messageOwner.random_id; - reqSend.flags |= TLRPC.MESSAGE_FLAG_REPLY; - } - } else if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessage_layer17(); - reqSend.ttl = newMsg.ttl; - } else { - reqSend = new TLRPC.TL_decryptedMessage_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); + reqSend = new TLRPC.TL_decryptedMessage(); + reqSend.ttl = newMsg.ttl; + if (entities != null && !entities.isEmpty()) { + reqSend.entities = entities; + reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; + } + if (reply_to_msg != null && reply_to_msg.messageOwner.random_id != 0) { + reqSend.reply_to_random_id = reply_to_msg.messageOwner.random_id; + reqSend.flags |= TLRPC.MESSAGE_FLAG_REPLY; } if (params != null && params.get("bot_name") != null) { reqSend.via_bot_name = params.get("bot_name"); @@ -1841,26 +1919,17 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } } else { TLRPC.TL_decryptedMessage reqSend; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - reqSend = new TLRPC.TL_decryptedMessage(); - reqSend.ttl = newMsg.ttl; - if (entities != null && !entities.isEmpty()) { - reqSend.entities = entities; - reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; - } - if (reply_to_msg != null && reply_to_msg.messageOwner.random_id != 0) { - reqSend.reply_to_random_id = reply_to_msg.messageOwner.random_id; - reqSend.flags |= TLRPC.MESSAGE_FLAG_REPLY; - } - reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; - } else if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend = new TLRPC.TL_decryptedMessage_layer17(); - reqSend.ttl = newMsg.ttl; - } else { - reqSend = new TLRPC.TL_decryptedMessage_layer8(); - reqSend.random_bytes = new byte[15]; - Utilities.random.nextBytes(reqSend.random_bytes); + reqSend = new TLRPC.TL_decryptedMessage(); + reqSend.ttl = newMsg.ttl; + if (entities != null && !entities.isEmpty()) { + reqSend.entities = entities; + reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_ENTITIES; + } + if (reply_to_msg != null && reply_to_msg.messageOwner.random_id != 0) { + reqSend.reply_to_random_id = reply_to_msg.messageOwner.random_id; + reqSend.flags |= TLRPC.MESSAGE_FLAG_REPLY; } + reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_MEDIA; if (params != null && params.get("bot_name") != null) { reqSend.via_bot_name = params.get("bot_name"); reqSend.flags |= TLRPC.MESSAGE_FLAG_HAS_BOT_ID; @@ -1868,7 +1937,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p reqSend.random_id = newMsg.random_id; reqSend.message = ""; if (type == 1) { - if (location instanceof TLRPC.TL_messageMediaVenue && AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { + if (location instanceof TLRPC.TL_messageMediaVenue) { reqSend.media = new TLRPC.TL_decryptedMessageMediaVenue(); reqSend.media.address = location.address; reqSend.media.title = location.title; @@ -1884,21 +1953,12 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p TLRPC.PhotoSize small = photo.sizes.get(0); TLRPC.PhotoSize big = photo.sizes.get(photo.sizes.size() - 1); ImageLoader.fillPhotoSizeWithBytes(small); - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaPhoto(); - reqSend.media.caption = photo.caption != null ? photo.caption : ""; - if (small.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaPhoto) reqSend.media).thumb = small.bytes; - } else { - ((TLRPC.TL_decryptedMessageMediaPhoto) reqSend.media).thumb = new byte[0]; - } + reqSend.media = new TLRPC.TL_decryptedMessageMediaPhoto(); + reqSend.media.caption = photo.caption != null ? photo.caption : ""; + if (small.bytes != null) { + ((TLRPC.TL_decryptedMessageMediaPhoto) reqSend.media).thumb = small.bytes; } else { - reqSend.media = new TLRPC.TL_decryptedMessageMediaPhoto_layer8(); - if (small.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaPhoto_layer8) reqSend.media).thumb = small.bytes; - } else { - ((TLRPC.TL_decryptedMessageMediaPhoto_layer8) reqSend.media).thumb = new byte[0]; - } + ((TLRPC.TL_decryptedMessageMediaPhoto) reqSend.media).thumb = new byte[0]; } reqSend.media.thumb_h = small.h; reqSend.media.thumb_w = small.w; @@ -1928,36 +1988,20 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } } else if (type == 3) { ImageLoader.fillPhotoSizeWithBytes(document.thumb); - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - if (MessageObject.isNewGifDocument(document)) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); - reqSend.media.attributes = document.attributes; - if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; - } else { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; - } - } else { - reqSend.media = new TLRPC.TL_decryptedMessageMediaVideo(); - if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaVideo) reqSend.media).thumb = document.thumb.bytes; - } else { - ((TLRPC.TL_decryptedMessageMediaVideo) reqSend.media).thumb = new byte[0]; - } - } - } else if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaVideo_layer17(); + if (MessageObject.isNewGifDocument(document)) { + reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); + reqSend.media.attributes = document.attributes; if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaVideo_layer17) reqSend.media).thumb = document.thumb.bytes; + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; } else { - ((TLRPC.TL_decryptedMessageMediaVideo_layer17) reqSend.media).thumb = new byte[0]; + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; } } else { - reqSend.media = new TLRPC.TL_decryptedMessageMediaVideo_layer8(); + reqSend.media = new TLRPC.TL_decryptedMessageMediaVideo(); if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaVideo_layer8) reqSend.media).thumb = document.thumb.bytes; + ((TLRPC.TL_decryptedMessageMediaVideo) reqSend.media).thumb = document.thumb.bytes; } else { - ((TLRPC.TL_decryptedMessageMediaVideo_layer8) reqSend.media).thumb = new byte[0]; + ((TLRPC.TL_decryptedMessageMediaVideo) reqSend.media).thumb = new byte[0]; } } reqSend.media.caption = document.caption != null ? document.caption : ""; @@ -1974,7 +2018,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p } reqSend.media.thumb_h = document.thumb.h; reqSend.media.thumb_w = document.thumb.w; - if (document.access_hash == 0) { + if (document.key == null) { DelayedMessage delayedMessage = new DelayedMessage(); delayedMessage.originalPath = originalPath; delayedMessage.sendEncryptedRequest = reqSend; @@ -2018,31 +2062,17 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p SecretChatHelper.getInstance().performSendEncryptedRequest(reqSend, newMsgObj.messageOwner, encryptedChat, null, null, newMsgObj); } else { ImageLoader.fillPhotoSizeWithBytes(document.thumb); - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); - reqSend.media.attributes = document.attributes; - reqSend.media.caption = document.caption != null ? document.caption : ""; - if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; - reqSend.media.thumb_h = document.thumb.h; - reqSend.media.thumb_w = document.thumb.w; - } else { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; - reqSend.media.thumb_h = 0; - reqSend.media.thumb_w = 0; - } + reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); + reqSend.media.attributes = document.attributes; + reqSend.media.caption = document.caption != null ? document.caption : ""; + if (document.thumb != null && document.thumb.bytes != null) { + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; + reqSend.media.thumb_h = document.thumb.h; + reqSend.media.thumb_w = document.thumb.w; } else { - reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument_layer8(); - reqSend.media.file_name = FileLoader.getDocumentFileName(document); - if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaDocument_layer8) reqSend.media).thumb = document.thumb.bytes; - reqSend.media.thumb_h = document.thumb.h; - reqSend.media.thumb_w = document.thumb.w; - } else { - ((TLRPC.TL_decryptedMessageMediaDocument_layer8) reqSend.media).thumb = new byte[0]; - reqSend.media.thumb_h = 0; - reqSend.media.thumb_w = 0; - } + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; + reqSend.media.thumb_h = 0; + reqSend.media.thumb_w = 0; } reqSend.media.size = document.size; reqSend.media.mime_type = document.mime_type; @@ -2076,39 +2106,21 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p delayedMessage.documentLocation = document; delayedMessage.type = 3; - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); - reqSend.media.attributes = document.attributes; - reqSend.media.caption = document.caption != null ? document.caption : ""; - if (document.thumb != null && document.thumb.bytes != null) { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; - reqSend.media.thumb_h = document.thumb.h; - reqSend.media.thumb_w = document.thumb.w; - } else { - ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; - reqSend.media.thumb_h = 0; - reqSend.media.thumb_w = 0; - } - reqSend.media.mime_type = document.mime_type; - reqSend.media.size = document.size; - delayedMessage.originalPath = originalPath; + reqSend.media = new TLRPC.TL_decryptedMessageMediaDocument(); + reqSend.media.attributes = document.attributes; + reqSend.media.caption = document.caption != null ? document.caption : ""; + if (document.thumb != null && document.thumb.bytes != null) { + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = document.thumb.bytes; + reqSend.media.thumb_h = document.thumb.h; + reqSend.media.thumb_w = document.thumb.w; } else { - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 17) { - reqSend.media = new TLRPC.TL_decryptedMessageMediaAudio(); - } else { - reqSend.media = new TLRPC.TL_decryptedMessageMediaAudio_layer8(); - } - for (int a = 0; a < document.attributes.size(); a++) { - TLRPC.DocumentAttribute attribute = document.attributes.get(a); - if (attribute instanceof TLRPC.TL_documentAttributeAudio) { - reqSend.media.duration = attribute.duration; - break; - } - } - reqSend.media.mime_type = "audio/ogg"; - reqSend.media.size = document.size; - delayedMessage.type = 3; + ((TLRPC.TL_decryptedMessageMediaDocument) reqSend.media).thumb = new byte[0]; + reqSend.media.thumb_h = 0; + reqSend.media.thumb_w = 0; } + reqSend.media.mime_type = document.mime_type; + reqSend.media.size = document.size; + delayedMessage.originalPath = originalPath; performSendDelayedMessage(delayedMessage); } if (retryMessageObject == null) { @@ -2159,7 +2171,7 @@ private void sendMessage(String message, TLRPC.MessageMedia location, TLRPC.TL_p performSendMessageRequest(reqSend, newMsgObj, null); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); MessagesStorage.getInstance().markMessageAsSendError(newMsg); if (newMsgObj != null) { newMsgObj.messageOwner.send_state = MessageObject.MESSAGE_SEND_STATE_SEND_ERROR; @@ -2178,11 +2190,15 @@ private void performSendDelayedMessage(final DelayedMessage message) { if (message.sendRequest != null) { String location = FileLoader.getPathToAttach(message.location).toString(); putToDelayedMessages(location, message); - FileLoader.getInstance().uploadFile(location, false, true); + FileLoader.getInstance().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } else { String location = FileLoader.getPathToAttach(message.location).toString(); if (message.sendEncryptedRequest != null && message.location.dc_id != 0) { File file = new File(location); + if (!file.exists()) { + location = FileLoader.getPathToAttach(message.location, true).toString(); + file = new File(location); + } if (!file.exists()) { putToDelayedMessages(FileLoader.getAttachFileName(message.location), message); FileLoader.getInstance().loadFile(message.location, "jpg", 0, false); @@ -2190,7 +2206,7 @@ private void performSendDelayedMessage(final DelayedMessage message) { } } putToDelayedMessages(location, message); - FileLoader.getInstance().uploadFile(location, true, true); + FileLoader.getInstance().uploadFile(location, true, true, ConnectionsManager.FileTypePhoto); } } } else if (message.type == 1) { @@ -2216,25 +2232,33 @@ private void performSendDelayedMessage(final DelayedMessage message) { } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null) { - FileLoader.getInstance().uploadFile(location, false, false, message.documentLocation.size); + FileLoader.getInstance().uploadFile(location, false, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance().uploadFile(location, false, false); + FileLoader.getInstance().uploadFile(location, false, false, ConnectionsManager.FileTypeVideo); } } else { String location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.location.volume_id + "_" + message.location.local_id + ".jpg"; putToDelayedMessages(location, message); - FileLoader.getInstance().uploadFile(location, false, true); + FileLoader.getInstance().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } } else { String location = message.obj.messageOwner.attachPath; if (location == null) { location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.documentLocation.id + ".mp4"; } + if (message.sendEncryptedRequest != null && message.documentLocation.dc_id != 0) { + File file = new File(location); + if (!file.exists()) { + putToDelayedMessages(FileLoader.getAttachFileName(message.documentLocation), message); + FileLoader.getInstance().loadFile(message.documentLocation, true, false); + return; + } + } putToDelayedMessages(location, message); if (message.obj.videoEditedInfo != null) { - FileLoader.getInstance().uploadFile(location, true, false, message.documentLocation.size); + FileLoader.getInstance().uploadFile(location, true, false, message.documentLocation.size, ConnectionsManager.FileTypeVideo); } else { - FileLoader.getInstance().uploadFile(location, true, false); + FileLoader.getInstance().uploadFile(location, true, false, ConnectionsManager.FileTypeVideo); } } } @@ -2253,15 +2277,11 @@ private void performSendDelayedMessage(final DelayedMessage message) { if (media.file == null) { String location = message.obj.messageOwner.attachPath; putToDelayedMessages(location, message); - if (message.sendRequest != null) { - FileLoader.getInstance().uploadFile(location, false, false); - } else { - FileLoader.getInstance().uploadFile(location, true, false); - } + FileLoader.getInstance().uploadFile(location, message.sendRequest == null, false, ConnectionsManager.FileTypeFile); } else if (media.thumb == null && message.location != null) { String location = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + message.location.volume_id + "_" + message.location.local_id + ".jpg"; putToDelayedMessages(location, message); - FileLoader.getInstance().uploadFile(location, false, true); + FileLoader.getInstance().uploadFile(location, false, true, ConnectionsManager.FileTypePhoto); } } else { String location = message.obj.messageOwner.attachPath; @@ -2274,17 +2294,13 @@ private void performSendDelayedMessage(final DelayedMessage message) { } } putToDelayedMessages(location, message); - FileLoader.getInstance().uploadFile(location, true, false); + FileLoader.getInstance().uploadFile(location, true, false, ConnectionsManager.FileTypeFile); } } } else if (message.type == 3) { String location = message.obj.messageOwner.attachPath; putToDelayedMessages(location, message); - if (message.sendRequest != null) { - FileLoader.getInstance().uploadFile(location, false, true); - } else { - FileLoader.getInstance().uploadFile(location, true, true); - } + FileLoader.getInstance().uploadFile(location, message.sendRequest == null, true, ConnectionsManager.FileTypeAudio); } } @@ -2353,10 +2369,11 @@ public void run() { }); sentMessages.add(newMsgObj); } else if (response instanceof TLRPC.Updates) { - ArrayList updates = ((TLRPC.Updates) response).updates; + final TLRPC.Updates updates = (TLRPC.Updates) response; + ArrayList updatesArr = ((TLRPC.Updates) response).updates; TLRPC.Message message = null; - for (int a = 0; a < updates.size(); a++) { - TLRPC.Update update = updates.get(a); + for (int a = 0; a < updatesArr.size(); a++) { + TLRPC.Update update = updatesArr.get(a); if (update instanceof TLRPC.TL_updateNewMessage) { final TLRPC.TL_updateNewMessage newMessage = (TLRPC.TL_updateNewMessage) update; sentMessages.add(message = newMessage.message); @@ -2367,6 +2384,7 @@ public void run() { MessagesController.getInstance().processNewDifferenceParams(-1, newMessage.pts, -1, newMessage.pts_count); } }); + updatesArr.remove(a); break; } else if (update instanceof TLRPC.TL_updateNewChannelMessage) { final TLRPC.TL_updateNewChannelMessage newMessage = (TLRPC.TL_updateNewChannelMessage) update; @@ -2380,6 +2398,7 @@ public void run() { MessagesController.getInstance().processNewChannelDifferenceParams(newMessage.pts, newMessage.pts_count, newMessage.message.to_id.channel_id); } }); + updatesArr.remove(a); break; } } @@ -2396,9 +2415,16 @@ public void run() { } else { isSentError = true; } + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + MessagesController.getInstance().processUpdates(updates, false); + } + }); } if (!isSentError) { + StatsController.getInstance().incrementSentItemsCount(ConnectionsManager.getCurrentNetworkType(), StatsController.TYPE_MESSAGES, 1); newMsgObj.send_state = MessageObject.MESSAGE_SEND_STATE_SENT; NotificationCenter.getInstance().postNotificationName(NotificationCenter.messageReceivedByServer, oldId, (isBroadcast ? oldId : newMsgObj.id), newMsgObj, newMsgObj.dialog_id); //TODO remove later? MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @@ -2437,9 +2463,7 @@ public void run() { }); } } else { - if (error.text.equals("PEER_FLOOD")) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 0); - } + AlertsCreator.processError(error, null, req); isSentError = true; } if (isSentError) { @@ -2692,8 +2716,8 @@ private static boolean prepareSendingDocumentInternal(String path, String origin } MimeTypeMap myMime = MimeTypeMap.getSingleton(); TLRPC.TL_documentAttributeAudio attributeAudio = null; + String extension = null; if (uri != null) { - String extension = null; if (mime != null) { extension = myMime.getExtensionFromMimeType(mime); } @@ -2715,9 +2739,13 @@ private static boolean prepareSendingDocumentInternal(String path, String origin String name = f.getName(); String ext = ""; - int idx = path.lastIndexOf('.'); - if (idx != -1) { - ext = path.substring(idx + 1); + if (extension != null) { + ext = extension; + } else { + int idx = path.lastIndexOf('.'); + if (idx != -1) { + ext = path.substring(idx + 1); + } } if (ext.toLowerCase().equals("mp3") || ext.toLowerCase().equals("m4a")) { AudioInfo audioInfo = AudioInfo.getAudioInfo(f); @@ -2728,11 +2756,7 @@ private static boolean prepareSendingDocumentInternal(String path, String origin if (encryptedChat == null) { return false; } - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) >= 46) { - attributeAudio = new TLRPC.TL_documentAttributeAudio(); - } else { - attributeAudio = new TLRPC.TL_documentAttributeAudio_old(); - } + attributeAudio = new TLRPC.TL_documentAttributeAudio(); } else { attributeAudio = new TLRPC.TL_documentAttributeAudio(); } @@ -2741,16 +2765,19 @@ private static boolean prepareSendingDocumentInternal(String path, String origin attributeAudio.performer = audioInfo.getArtist(); if (attributeAudio.title == null) { attributeAudio.title = ""; - attributeAudio.flags |= 1; } + attributeAudio.flags |= 1; if (attributeAudio.performer == null) { attributeAudio.performer = ""; - attributeAudio.flags |= 2; } + attributeAudio.flags |= 2; } } + boolean sendNew = false; if (originalPath != null) { - if (attributeAudio != null) { + if (originalPath.endsWith("attheme")) { + sendNew = true; + } else if (attributeAudio != null) { originalPath += "audio" + f.length(); } else { originalPath += "" + f.length(); @@ -2758,7 +2785,7 @@ private static boolean prepareSendingDocumentInternal(String path, String origin } TLRPC.TL_document document = null; - if (!isEncrypted) { + if (!sendNew && !isEncrypted) { document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(originalPath, !isEncrypted ? 1 : 4); if (document == null && !path.equals(originalPath) && !isEncrypted) { document = (TLRPC.TL_document) MessagesStorage.getInstance().getSentFile(path + f.length(), !isEncrypted ? 1 : 4); @@ -2799,7 +2826,7 @@ private static boolean prepareSendingDocumentInternal(String path, String origin bitmap.recycle(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (document.mime_type.equals("image/webp") && allowSticker) { @@ -2811,7 +2838,7 @@ private static boolean prepareSendingDocumentInternal(String path, String origin Utilities.loadWebpImage(null, buffer, buffer.limit(), bmOptions, true); file.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (bmOptions.outWidth != 0 && bmOptions.outHeight != 0 && bmOptions.outWidth <= 800 && bmOptions.outHeight <= 800) { TLRPC.TL_documentAttributeSticker attributeSticker = new TLRPC.TL_documentAttributeSticker(); @@ -2846,7 +2873,7 @@ public void run() { return true; } - public static void prepareSendingDocument(String path, String originalPath, Uri uri, String mine, long dialog_id, MessageObject reply_to_msg) { + public static void prepareSendingDocument(String path, String originalPath, Uri uri, String mine, long dialog_id, MessageObject reply_to_msg, InputContentInfoCompat inputContent) { if ((path == null || originalPath == null) && uri == null) { return; } @@ -2855,10 +2882,13 @@ public static void prepareSendingDocument(String path, String originalPath, Uri ArrayList uris = null; if (uri != null) { uris = new ArrayList<>(); + uris.add(uri); + } + if (path != null) { + paths.add(path); + originalPaths.add(originalPath); } - paths.add(path); - originalPaths.add(originalPath); - prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg); + prepareSendingDocuments(paths, originalPaths, uris, mine, dialog_id, reply_to_msg, inputContent); } public static void prepareSendingAudioDocuments(final ArrayList messageObjects, final long dialog_id, final MessageObject reply_to_msg) { @@ -2892,17 +2922,6 @@ public void run() { if (encryptedChat == null) { return; } - if (AndroidUtilities.getPeerLayerVersion(encryptedChat.layer) < 46) { - for (int b = 0; b < document.attributes.size(); b++) { - if (document.attributes.get(b) instanceof TLRPC.TL_documentAttributeAudio) { - TLRPC.TL_documentAttributeAudio_old old = new TLRPC.TL_documentAttributeAudio_old(); - old.duration = document.attributes.get(b).duration; - document.attributes.remove(b); - document.attributes.add(old); - break; - } - } - } } final HashMap params = new HashMap<>(); @@ -2921,7 +2940,7 @@ public void run() { }).start(); } - public static void prepareSendingDocuments(final ArrayList paths, final ArrayList originalPaths, final ArrayList uris, final String mime, final long dialog_id, final MessageObject reply_to_msg) { + public static void prepareSendingDocuments(final ArrayList paths, final ArrayList originalPaths, final ArrayList uris, final String mime, final long dialog_id, final MessageObject reply_to_msg, final InputContentInfoCompat inputContent) { if (paths == null && originalPaths == null && uris == null || paths != null && originalPaths != null && paths.size() != originalPaths.size()) { return; } @@ -2943,6 +2962,9 @@ public void run() { } } } + if (inputContent != null) { + inputContent.releasePermission(); + } if (error) { AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -2951,7 +2973,7 @@ public void run() { Toast toast = Toast.makeText(ApplicationLoader.applicationContext, LocaleController.getString("UnsupportedAttachment", R.string.UnsupportedAttachment), Toast.LENGTH_SHORT); toast.show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -2960,7 +2982,7 @@ public void run() { }).start(); } - public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, ArrayList stickers) { + public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long dialog_id, MessageObject reply_to_msg, CharSequence caption, ArrayList stickers, InputContentInfoCompat inputContent) { ArrayList paths = null; ArrayList uris = null; ArrayList captions = null; @@ -2981,7 +3003,7 @@ public static void prepareSendingPhoto(String imageFilePath, Uri imageUri, long masks = new ArrayList<>(); masks.add(new ArrayList<>(stickers)); } - prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, masks); + prepareSendingPhotos(paths, uris, dialog_id, reply_to_msg, captions, masks, inputContent); } public static void prepareSendingBotContextResult(final TLRPC.BotInlineResult result, final HashMap params, final long dialog_id, final MessageObject reply_to_msg) { @@ -3064,7 +3086,7 @@ public void run() { bitmap.recycle(); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } break; } @@ -3121,7 +3143,7 @@ public void run() { bitmap.recycle(); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } break; } @@ -3143,7 +3165,7 @@ public void run() { bitmap.recycle(); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } break; } @@ -3208,7 +3230,18 @@ public void run() { } }).run(); } else if (result.send_message instanceof TLRPC.TL_botInlineMessageText) { - SendMessagesHelper.getInstance().sendMessage(result.send_message.message, dialog_id, reply_to_msg, null, !result.send_message.no_webpage, result.send_message.entities, result.send_message.reply_markup, params); + TLRPC.WebPage webPage = null; + if ((int) dialog_id == 0) { + for (int a = 0; a < result.send_message.entities.size(); a++) { + TLRPC.MessageEntity entity = result.send_message.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityUrl) { + webPage = new TLRPC.TL_webPagePending(); + webPage.url = result.send_message.message.substring(entity.offset, entity.offset + entity.length); + break; + } + } + } + SendMessagesHelper.getInstance().sendMessage(result.send_message.message, dialog_id, reply_to_msg, webPage, !result.send_message.no_webpage, result.send_message.entities, result.send_message.reply_markup, params); } else if (result.send_message instanceof TLRPC.TL_botInlineMessageMediaVenue) { TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); venue.geo = result.send_message.geo; @@ -3301,7 +3334,7 @@ public void run() { bitmap.recycle(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (document.thumb == null) { @@ -3426,7 +3459,7 @@ public void run() { }); } - public static void prepareSendingPhotos(ArrayList paths, ArrayList uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList captions, final ArrayList> masks) { + public static void prepareSendingPhotos(ArrayList paths, ArrayList uris, final long dialog_id, final MessageObject reply_to_msg, final ArrayList captions, final ArrayList> masks, final InputContentInfoCompat inputContent) { if (paths == null && uris == null || paths != null && paths.isEmpty() || uris != null && uris.isEmpty()) { return; } @@ -3550,6 +3583,9 @@ public void run() { } } } + if (inputContent != null) { + inputContent.releasePermission(); + } if (sendAsDocuments != null && !sendAsDocuments.isEmpty()) { for (int a = 0; a < sendAsDocuments.size(); a++) { prepareSendingDocumentInternal(sendAsDocuments.get(a), sendAsDocumentsOriginal.get(a), null, extension, dialog_id, reply_to_msg, sendAsDocumentsCaptions.get(a)); @@ -3593,14 +3629,14 @@ private static void fillVideoAttribute(String videoPath, TLRPC.TL_documentAttrib } infoObtained = true; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { try { if (mediaMetadataRetriever != null) { mediaMetadataRetriever.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (!infoObtained) { @@ -3613,7 +3649,7 @@ private static void fillVideoAttribute(String videoPath, TLRPC.TL_documentAttrib mp.release(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/ShareBroadcastReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/ShareBroadcastReceiver.java index ab6b116385a..87447deb85e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/ShareBroadcastReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/ShareBroadcastReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java index 816939e2ad3..4a54faa9e73 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/SmsListener.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -55,11 +55,11 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } catch(Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java new file mode 100644 index 00000000000..f247f290cae --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/StatsController.java @@ -0,0 +1,176 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.messenger; + +import android.content.Context; +import android.content.SharedPreferences; + +public class StatsController { + + public static final int TYPE_MOBILE = 0; + public static final int TYPE_WIFI = 1; + public static final int TYPE_ROAMING = 2; + + public static final int TYPE_CALLS = 0; + public static final int TYPE_MESSAGES = 1; + public static final int TYPE_VIDEOS = 2; + public static final int TYPE_AUDIOS = 3; + public static final int TYPE_PHOTOS = 4; + public static final int TYPE_FILES = 5; + public static final int TYPE_TOTAL = 6; + private static final int TYPES_COUNT = 7; + + private long sentBytes[][] = new long[3][TYPES_COUNT]; + private long receivedBytes[][] = new long[3][TYPES_COUNT]; + private int sentItems[][] = new int[3][TYPES_COUNT]; + private int receivedItems[][] = new int[3][TYPES_COUNT]; + private long resetStatsDate[] = new long[3]; + private int callsTotalTime[] = new int[3]; + private SharedPreferences.Editor editor; + private DispatchQueue statsSaveQueue = new DispatchQueue("statsSaveQueue"); + + private static final ThreadLocal lastStatsSaveTime = new ThreadLocal() { + @Override + protected Long initialValue() { + return System.currentTimeMillis() - 1000; + } + }; + + private static volatile StatsController Instance = null; + + public static StatsController getInstance() { + StatsController localInstance = Instance; + if (localInstance == null) { + synchronized (StatsController.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new StatsController(); + } + } + } + return localInstance; + } + + private StatsController() { + SharedPreferences sharedPreferences = ApplicationLoader.applicationContext.getSharedPreferences("stats", Context.MODE_PRIVATE); + boolean save = false; + editor = sharedPreferences.edit(); + for (int a = 0; a < 3; a++) { + callsTotalTime[a] = sharedPreferences.getInt("callsTotalTime" + a, 0); + resetStatsDate[a] = sharedPreferences.getLong("resetStatsDate" + a, 0); + for (int b = 0; b < TYPES_COUNT; b++) { + sentBytes[a][b] = sharedPreferences.getLong("sentBytes" + a + "_" + b, 0); + receivedBytes[a][b] = sharedPreferences.getLong("receivedBytes" + a + "_" + b, 0); + sentItems[a][b] = sharedPreferences.getInt("sentItems" + a + "_" + b, 0); + receivedItems[a][b] = sharedPreferences.getInt("receivedItems" + a + "_" + b, 0); + } + if (resetStatsDate[a] == 0) { + save = true; + resetStatsDate[a] = System.currentTimeMillis(); + } + } + if (save) { + saveStats(); + } + } + + public void incrementReceivedItemsCount(int networkType, int dataType, int value) { + receivedItems[networkType][dataType] += value; + saveStats(); + } + + public void incrementSentItemsCount(int networkType, int dataType, int value) { + sentItems[networkType][dataType] += value; + saveStats(); + } + + public void incrementReceivedBytesCount(int networkType, int dataType, long value) { + receivedBytes[networkType][dataType] += value; + saveStats(); + } + + public void incrementSentBytesCount(int networkType, int dataType, long value) { + sentBytes[networkType][dataType] += value; + saveStats(); + } + + public void incrementTotalCallsTime(int networkType, int value) { + callsTotalTime[networkType] += value; + saveStats(); + } + + public int getRecivedItemsCount(int networkType, int dataType) { + return receivedItems[networkType][dataType]; + } + + public int getSentItemsCount(int networkType, int dataType) { + return sentItems[networkType][dataType]; + } + + public long getSentBytesCount(int networkType, int dataType) { + if (dataType == TYPE_MESSAGES) { + return sentBytes[networkType][TYPE_TOTAL] - sentBytes[networkType][TYPE_FILES] - sentBytes[networkType][TYPE_AUDIOS] - sentBytes[networkType][TYPE_VIDEOS] - sentBytes[networkType][TYPE_PHOTOS]; + } + return sentBytes[networkType][dataType]; + } + + public long getReceivedBytesCount(int networkType, int dataType) { + if (dataType == TYPE_MESSAGES) { + return receivedBytes[networkType][TYPE_TOTAL] - receivedBytes[networkType][TYPE_FILES] - receivedBytes[networkType][TYPE_AUDIOS] - receivedBytes[networkType][TYPE_VIDEOS] - receivedBytes[networkType][TYPE_PHOTOS]; + } + return receivedBytes[networkType][dataType]; + } + + public int getCallsTotalTime(int networkType) { + return callsTotalTime[networkType]; + } + + public long getResetStatsDate(int networkType) { + return resetStatsDate[networkType]; + } + + public void resetStats(int networkType) { + resetStatsDate[networkType] = System.currentTimeMillis(); + for (int a = 0; a < TYPES_COUNT; a++) { + sentBytes[networkType][a] = 0; + receivedBytes[networkType][a] = 0; + sentItems[networkType][a] = 0; + receivedItems[networkType][a] = 0; + } + callsTotalTime[networkType] = 0; + saveStats(); + } + + private void saveStats() { + long newTime = System.currentTimeMillis(); + if (Math.abs(newTime - lastStatsSaveTime.get()) >= 1000) { + lastStatsSaveTime.set(newTime); + statsSaveQueue.postRunnable(new Runnable() { + @Override + public void run() { + for (int networkType = 0; networkType < 3; networkType++) { + for (int a = 0; a < TYPES_COUNT; a++) { + editor.putInt("receivedItems" + networkType + "_" + a, receivedItems[networkType][a]); + editor.putInt("sentItems" + networkType + "_" + a, sentItems[networkType][a]); + editor.putLong("receivedBytes" + networkType + "_" + a, receivedBytes[networkType][a]); + editor.putLong("sentBytes" + networkType + "_" + a, sentBytes[networkType][a]); + } + editor.putInt("callsTotalTime" + networkType, callsTotalTime[networkType]); + editor.putLong("resetStatsDate" + networkType, resetStatsDate[networkType]); + } + try { + editor.commit(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java index 4e53ecf2bab..d2de3b31eaa 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/TgChooserTargetService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -104,7 +104,7 @@ public void run() { MessagesStorage.getInstance().getUsersInternal(TextUtils.join(",", usersToLoad), users); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } for (int a = 0; a < dialogs.size(); a++) { Bundle extras = new Bundle(); @@ -153,7 +153,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return targets; } @@ -176,7 +176,7 @@ private Icon createRoundBitmap(File path) { return Icon.createWithBitmap(result); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java index a698626eeab..0b124f2445c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserConfig.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -16,6 +16,8 @@ import org.telegram.tgnet.TLRPC; import java.io.File; +import java.util.ArrayList; +import java.util.Map; public class UserConfig { @@ -34,6 +36,7 @@ public class UserConfig { public static boolean appLocked; public static int passcodeType; public static int autoLockIn = 60 * 60; + public static boolean allowScreenCapture; public static int lastPauseTime; public static boolean isWaitingForPasscodeEnter; public static boolean useFingerprint = true; @@ -41,6 +44,9 @@ public class UserConfig { public static int lastContactsSyncTime; public static int lastHintsSyncTime; public static boolean draftsLoaded; + public static boolean notificationsConverted = true; + public static boolean pinnedDialogsLoaded = true; + public static TLRPC.TL_account_tmpPassword tmpPassword; public static int migrateOffsetId = -1; public static int migrateOffsetDate = -1; @@ -86,6 +92,9 @@ public static void saveConfig(boolean withFile, File oldFile) { editor.putBoolean("useFingerprint", useFingerprint); editor.putInt("lastHintsSyncTime", lastHintsSyncTime); editor.putBoolean("draftsLoaded", draftsLoaded); + editor.putBoolean("notificationsConverted", notificationsConverted); + editor.putBoolean("allowScreenCapture", allowScreenCapture); + editor.putBoolean("pinnedDialogsLoaded", pinnedDialogsLoaded); editor.putInt("migrateOffsetId", migrateOffsetId); if (migrateOffsetId != -1) { @@ -95,13 +104,22 @@ public static void saveConfig(boolean withFile, File oldFile) { editor.putInt("migrateOffsetChannelId", migrateOffsetChannelId); editor.putLong("migrateOffsetAccess", migrateOffsetAccess); } + if (tmpPassword != null) { + SerializedData data = new SerializedData(); + tmpPassword.serializeToStream(data); + String string = Base64.encodeToString(data.toByteArray(), Base64.DEFAULT); + editor.putString("tmpPassword", string); + data.cleanup(); + } else { + editor.remove("tmpPassword"); + } if (currentUser != null) { if (withFile) { SerializedData data = new SerializedData(); currentUser.serializeToStream(data); - String userString = Base64.encodeToString(data.toByteArray(), Base64.DEFAULT); - editor.putString("user", userString); + String string = Base64.encodeToString(data.toByteArray(), Base64.DEFAULT); + editor.putString("user", string); data.cleanup(); } } else { @@ -113,7 +131,7 @@ public static void saveConfig(boolean withFile, File oldFile) { oldFile.delete(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -201,7 +219,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("userconfing", Context.MODE_PRIVATE); @@ -223,6 +241,13 @@ public void run() { lastContactsSyncTime = preferences.getInt("lastContactsSyncTime", (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60); lastHintsSyncTime = preferences.getInt("lastHintsSyncTime", (int) (System.currentTimeMillis() / 1000) - 25 * 60 * 60); draftsLoaded = preferences.getBoolean("draftsLoaded", false); + notificationsConverted = preferences.getBoolean("notificationsConverted", false); + allowScreenCapture = preferences.getBoolean("allowScreenCapture", false); + pinnedDialogsLoaded = preferences.getBoolean("pinnedDialogsLoaded", false); + + if (UserConfig.passcodeHash.length() > 0 && lastPauseTime == 0) { + lastPauseTime = (int) (System.currentTimeMillis() / 1000 - 60 * 10); + } migrateOffsetId = preferences.getInt("migrateOffsetId", 0); if (migrateOffsetId != -1) { @@ -233,11 +258,21 @@ public void run() { migrateOffsetAccess = preferences.getLong("migrateOffsetAccess", 0); } - String user = preferences.getString("user", null); - if (user != null) { - byte[] userBytes = Base64.decode(user, Base64.DEFAULT); - if (userBytes != null) { - SerializedData data = new SerializedData(userBytes); + String string = preferences.getString("tmpPassword", null); + if (string != null) { + byte[] bytes = Base64.decode(string, Base64.DEFAULT); + if (bytes != null) { + SerializedData data = new SerializedData(bytes); + tmpPassword = TLRPC.TL_account_tmpPassword.TLdeserialize(data, data.readInt32(false), false); + data.cleanup(); + } + } + + string = preferences.getString("user", null); + if (string != null) { + byte[] bytes = Base64.decode(string, Base64.DEFAULT); + if (bytes != null) { + SerializedData data = new SerializedData(bytes); currentUser = TLRPC.User.TLdeserialize(data, data.readInt32(false), false); data.cleanup(); } @@ -248,6 +283,83 @@ public void run() { } else { passcodeSalt = new byte[0]; } + + if (!notificationsConverted) { + try { + ArrayList customDialogs = new ArrayList<>(); + preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Context.MODE_PRIVATE); + Map all = preferences.getAll(); + String defaultSound = LocaleController.getString("SoundDefault", R.string.SoundDefault); + int defaultVibrate = 0; + int defaultPriority = 0; + int defaultColor = 0; + int defaultMaxCount = 2; + int defaultMaxDelay = 3 * 60; + for (Map.Entry entry : all.entrySet()) { + String key = entry.getKey(); + if (key.startsWith("sound_")) { + String value = (String) entry.getValue(); + if (!value.equals(defaultSound)) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } else if (key.startsWith("vibrate_")) { + Integer value = (Integer) entry.getValue(); + if (value != defaultVibrate) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } else if (key.startsWith("priority_")) { + Integer value = (Integer) entry.getValue(); + if (value != defaultPriority) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } else if (key.startsWith("color_")) { + Integer value = (Integer) entry.getValue(); + if (value != defaultColor) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } else if (key.startsWith("smart_max_count_")) { + Integer value = (Integer) entry.getValue(); + if (value != defaultMaxCount) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } else if (key.startsWith("smart_delay_")) { + Integer value = (Integer) entry.getValue(); + if (value != defaultMaxDelay) { + long dialogId = Utilities.parseLong(key); + if (!customDialogs.contains(dialogId)) { + customDialogs.add(dialogId); + } + } + } + } + if (!customDialogs.isEmpty()) { + SharedPreferences.Editor editor = preferences.edit(); + for (int a = 0; a < customDialogs.size(); a++) { + editor.putBoolean("custom_" + customDialogs.get(a), true); + } + editor.commit(); + } + } catch (Exception e) { + FileLog.e(e); + } + notificationsConverted = true; + saveConfig(false); + } } } } @@ -267,7 +379,7 @@ public static boolean checkPasscode(String passcode) { passcodeHash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); saveConfig(false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return result; @@ -281,7 +393,7 @@ public static boolean checkPasscode(String passcode) { String hash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); return passcodeHash.equals(hash); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return false; @@ -309,7 +421,10 @@ public static void clearConfig() { lastPauseTime = 0; useFingerprint = true; draftsLoaded = true; + notificationsConverted = true; isWaitingForPasscodeEnter = false; + allowScreenCapture = false; + pinnedDialogsLoaded = false; lastUpdateVersion = BuildVars.BUILD_VERSION_STRING; lastContactsSyncTime = (int) (System.currentTimeMillis() / 1000) - 23 * 60 * 60; lastHintsSyncTime = (int) (System.currentTimeMillis() / 1000) - 25 * 60 * 60; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java index c0aa1443854..5f5afda25ae 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserObject.java @@ -3,11 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; +import android.text.TextUtils; + import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.tgnet.TLRPC; @@ -41,6 +43,6 @@ public static String getFirstName(TLRPC.User user) { if (name == null || name.length() == 0) { name = user.last_name; } - return name != null && name.length() > 0 ? name : LocaleController.getString("HiddenName", R.string.HiddenName); + return !TextUtils.isEmpty(name) ? name : LocaleController.getString("HiddenName", R.string.HiddenName); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java index 66463f4bd5c..0f52c7cff17 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/Utilities.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -41,7 +41,7 @@ public class Utilities { sUrandomIn.close(); random.setSeed(buffer); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -70,7 +70,7 @@ public static Integer parseInt(String value) { val = Integer.parseInt(num); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return val; } @@ -87,7 +87,7 @@ public static Long parseLong(String value) { val = Long.parseLong(num); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return val; } @@ -199,7 +199,7 @@ public static byte[] computeSHA1(byte[] convertme, int offset, int len) { md.update(convertme, offset, len); return md.digest(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return new byte[20]; } @@ -214,7 +214,7 @@ public static byte[] computeSHA1(ByteBuffer convertme, int offset, int len) { md.update(convertme); return md.digest(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { convertme.limit(oldl); convertme.position(oldp); @@ -236,7 +236,7 @@ public static byte[] computeSHA256(byte[] convertme, int offset, int len) { md.update(convertme, offset, len); return md.digest(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -259,7 +259,7 @@ public static String MD5(String md5) { } return sb.toString(); } catch (java.security.NoSuchAlgorithmException e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java index e3629ec7d7c..df50bc923c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -20,6 +20,8 @@ public class VideoEditedInfo { public int resultHeight; public int bitrate; public String originalPath; + public long estimatedSize; + public long estimatedDuration; public String getString() { return String.format(Locale.US, "-1_%d_%d_%d_%d_%d_%d_%d_%d_%s", startTime, endTime, rotationValue, originalWidth, originalHeight, bitrate, resultWidth, resultHeight, originalPath); @@ -50,7 +52,7 @@ public boolean parseString(String string) { } return true; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEncodingService.java b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEncodingService.java index 8ed05cce4b1..d61bdb27e6d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEncodingService.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEncodingService.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; @@ -34,7 +34,7 @@ public void onDestroy() { stopForeground(true); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileUploadProgressChanged); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.stopEncodingService); - FileLog.e("tmessages", "destroy video service"); + FileLog.e("destroy video service"); } @Override @@ -46,7 +46,11 @@ public void didReceivedNotification(int id, Object... args) { Boolean enc = (Boolean) args[2]; currentProgress = (int)(progress * 100); builder.setProgress(100, currentProgress, currentProgress == 0); - NotificationManagerCompat.from(ApplicationLoader.applicationContext).notify(4, builder.build()); + try { + NotificationManagerCompat.from(ApplicationLoader.applicationContext).notify(4, builder.build()); + } catch (Throwable e) { + FileLog.e(e); + } } } else if (id == NotificationCenter.stopEncodingService) { String filepath = (String)args[0]; @@ -63,7 +67,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { stopSelf(); return Service.START_NOT_STICKY; } - FileLog.e("tmessages", "start video service"); + FileLog.e("start video service"); if (builder == null) { builder = new NotificationCompat.Builder(ApplicationLoader.applicationContext); builder.setSmallIcon(android.R.drawable.stat_sys_upload); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java index 6a490d6f1a3..2f90aaa17c2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/WearReplyReceiver.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java index 0248a9ae8dc..ef3469b19d3 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/browser/Browser.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.browser; @@ -93,7 +93,7 @@ public void onServiceConnected(CustomTabsClient client) { try { customTabsClient.warmup(0); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -108,7 +108,7 @@ public void onServiceDisconnected() { customTabsServiceConnection = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -123,7 +123,7 @@ public static void unbindCustomTabsService(Activity activity) { try { activity.unbindService(customTabsServiceConnection); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } customTabsClient = null; customTabsSession = null; @@ -132,7 +132,7 @@ public static void unbindCustomTabsService(Activity activity) { private static class NavigationCallback extends CustomTabsCallback { @Override public void onNavigationEvent(int navigationEvent, Bundle extras) { - FileLog.e("tmessages", "code = " + navigationEvent + " extras " + extras); + } } @@ -158,30 +158,34 @@ public static void openUrl(Context context, Uri uri, boolean allowCustom) { if (context == null || uri == null) { return; } + boolean internalUri = isInternalUri(uri); try { String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : ""; - boolean internalUri = isInternalUri(uri); if (Build.VERSION.SDK_INT >= 15 && allowCustom && MediaController.getInstance().canCustomTabs() && !internalUri && !scheme.equals("tel")) { Intent share = new Intent(ApplicationLoader.applicationContext, ShareBroadcastReceiver.class); share.setAction(Intent.ACTION_SEND); CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(getSession()); - builder.setToolbarColor(Theme.ACTION_BAR_COLOR); + builder.setToolbarColor(Theme.getColor(Theme.key_actionBarDefault)); builder.setShowTitle(true); builder.setActionButton(BitmapFactory.decodeResource(context.getResources(), R.drawable.abc_ic_menu_share_mtrl_alpha), LocaleController.getString("ShareFile", R.string.ShareFile), PendingIntent.getBroadcast(ApplicationLoader.applicationContext, 0, share, 0), false); CustomTabsIntent intent = builder.build(); intent.launchUrl((Activity) context, uri); - } else { - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - if (internalUri) { - ComponentName componentName = new ComponentName(context.getPackageName(), LaunchActivity.class.getName()); - intent.setComponent(componentName); - } - intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - context.startActivity(intent); + return; + } + } catch (Exception e) { + FileLog.e(e); + } + try { + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + if (internalUri) { + ComponentName componentName = new ComponentName(context.getPackageName(), LaunchActivity.class.getName()); + intent.setComponent(componentName); } + intent.putExtra(android.provider.Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + context.startActivity(intent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -192,6 +196,6 @@ public static boolean isInternalUrl(String url) { public static boolean isInternalUri(Uri uri) { String host = uri.getHost(); host = host != null ? host.toLowerCase() : ""; - return "tg".equals(uri.getScheme()) || "telegram.me".equals(host) || "telegram.dog".equals(host); + return "tg".equals(uri.getScheme()) || "telegram.me".equals(host) || "t.me".equals(host) || "telegram.dog".equals(host); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraController.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraController.java index 977f6abac11..0cb970d8afd 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraController.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.camera; @@ -42,7 +42,7 @@ public class CameraController implements MediaRecorder.OnInfoListener { private static final int CORE_POOL_SIZE = 1; - private static final int MAX_POOL_SIZE = Runtime.getRuntime().availableProcessors(); + private static final int MAX_POOL_SIZE = 1; private static final int KEEP_ALIVE_SECONDS = 60; private ThreadPoolExecutor threadPool; @@ -51,6 +51,7 @@ public class CameraController implements MediaRecorder.OnInfoListener { private String recordedFile; protected ArrayList cameraInfos = null; private VideoTakeCallback onVideoTakeCallback; + private boolean recordingSmallVideo; private boolean cameraInitied; private static volatile CameraController Instance = null; @@ -125,7 +126,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -146,6 +147,7 @@ public void run() { CameraInfo info = cameraInfos.get(a); if (info.camera != null) { info.camera.stopPreview(); + info.camera.setPreviewCallbackWithBuffer(null); info.camera.release(); info.camera = null; } @@ -155,32 +157,39 @@ public void run() { }); } - public void close(final CameraSession session, final Semaphore semaphore) { + public void close(final CameraSession session, final Semaphore semaphore, final Runnable beforeDestroyRunnable) { session.destroy(); - final Camera camera = session.cameraInfo.camera; - session.cameraInfo.camera = null; threadPool.execute(new Runnable() { @Override public void run() { + if (beforeDestroyRunnable != null) { + beforeDestroyRunnable.run(); + } + if (session.cameraInfo.camera == null) { + return; + } + try { + session.cameraInfo.camera.stopPreview(); + session.cameraInfo.camera.setPreviewCallbackWithBuffer(null); + } catch (Exception e) { + FileLog.e(e); + } try { - if (camera != null) { - camera.stopPreview(); - camera.release(); - } + session.cameraInfo.camera.release(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } + session.cameraInfo.camera = null; if (semaphore != null) { semaphore.release(); } - } }); if (semaphore != null) { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -298,10 +307,18 @@ public void onPictureTaken(byte[] data, Camera camera) { String key = String.format(Locale.US, "%s@%d_%d", Utilities.MD5(path.getAbsolutePath()), size, size); try { BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(data, 0, data.length, options); + float scaleFactor = Math.max((float) options.outWidth / AndroidUtilities.getPhotoSize(), (float) options.outHeight / AndroidUtilities.getPhotoSize()); + if (scaleFactor < 1) { + scaleFactor = 1; + } + options.inJustDecodeBounds = false; + options.inSampleSize = (int) scaleFactor; options.inPurgeable = true; bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (info.frontCamera != 0) { @@ -324,7 +341,7 @@ public void onPictureTaken(byte[] data, Camera camera) { } return; } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } FileOutputStream outputStream = new FileOutputStream(path); @@ -336,7 +353,7 @@ public void onPictureTaken(byte[] data, Camera camera) { ImageLoader.getInstance().putImageToCache(new BitmapDrawable(bitmap), key); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (callback != null) { callback.run(); @@ -345,7 +362,7 @@ public void onPictureTaken(byte[] data, Camera camera) { }); return true; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -369,13 +386,13 @@ public void run() { if (camera != null) { camera.release(); } - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } - public void open(final CameraSession session, final SurfaceTexture texture, final Runnable callback) { + public void open(final CameraSession session, final SurfaceTexture texture, final Runnable callback, final Runnable prestartCallback) { if (session == null || texture == null) { return; } @@ -402,6 +419,10 @@ public void run() { session.checkFlashMode(availableFlashModes.get(0)); } + if (prestartCallback != null) { + prestartCallback.run(); + } + session.configurePhotoCamera(); camera.setPreviewTexture(texture); camera.startPreview(); @@ -413,51 +434,76 @@ public void run() { if (camera != null) { camera.release(); } - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } - public void recordVideo(CameraSession session, final File path, final VideoTakeCallback callback) { + public void recordVideo(final CameraSession session, final File path, final VideoTakeCallback callback, final Runnable onVideoStartRecord, final boolean smallVideo) { if (session == null) { return; } - try { - CameraInfo info = session.cameraInfo; - Camera camera = info.camera; - if (camera != null) { - camera.stopPreview(); - camera.unlock(); + + final CameraInfo info = session.cameraInfo; + final Camera camera = info.camera; + threadPool.execute(new Runnable() { + @Override + public void run() { try { - recorder = new MediaRecorder(); - recorder.setCamera(camera); - recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); - session.configureRecorder(1, recorder); - recorder.setOutputFile(path.getAbsolutePath()); - recorder.setMaxFileSize(1024 * 1024 * 1024); - recorder.setVideoFrameRate(30); - recorder.setMaxDuration(0); - org.telegram.messenger.camera.Size pictureSize = new Size(16, 9); - pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 720, 480, pictureSize); - recorder.setVideoSize(pictureSize.getWidth(), pictureSize.getHeight()); - recorder.setVideoEncodingBitRate(900000 * 2); - recorder.setOnInfoListener(this); - - recorder.prepare(); - recorder.start(); - onVideoTakeCallback = callback; - recordedFile = path.getAbsolutePath(); + if (camera != null) { + try { + Camera.Parameters params = camera.getParameters(); + params.setFlashMode(session.getCurrentFlashMode().equals(Camera.Parameters.FLASH_MODE_ON) ? Camera.Parameters.FLASH_MODE_TORCH : Camera.Parameters.FLASH_MODE_OFF); + camera.setParameters(params); + } catch (Exception e) { + FileLog.e(e); + } + camera.unlock(); + //camera.stopPreview(); + try { + recordingSmallVideo = smallVideo; + + recorder = new MediaRecorder(); + recorder.setCamera(camera); + recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); + recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); + session.configureRecorder(1, recorder); + recorder.setOutputFile(path.getAbsolutePath()); + recorder.setMaxFileSize(1024 * 1024 * 1024); + recorder.setVideoFrameRate(30); + recorder.setMaxDuration(0); + org.telegram.messenger.camera.Size pictureSize; + if (recordingSmallVideo) { + pictureSize = new Size(4, 3); + pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 640, 480, pictureSize); + recorder.setVideoEncodingBitRate(900000); + } else { + pictureSize = new Size(16, 9); + pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 720, 480, pictureSize); + recorder.setVideoEncodingBitRate(900000 * 2); + } + recorder.setVideoSize(pictureSize.getWidth(), pictureSize.getHeight()); + recorder.setOnInfoListener(CameraController.this); + recorder.prepare(); + recorder.start(); + + onVideoTakeCallback = callback; + recordedFile = path.getAbsolutePath(); + if (onVideoStartRecord != null) { + AndroidUtilities.runOnUIThread(onVideoStartRecord); + } + } catch (Exception e) { + recorder.release(); + recorder = null; + FileLog.e(e); + } + } } catch (Exception e) { - recorder.release(); - recorder = null; - FileLog.e("tmessages", e); + FileLog.e(e); } } - } catch (Exception e) { - FileLog.e("tmessages", e); - } + }); } @Override @@ -469,43 +515,93 @@ public void onInfo(MediaRecorder mediaRecorder, int what, int extra) { tempRecorder.stop(); tempRecorder.release(); } - final Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(recordedFile, MediaStore.Video.Thumbnails.MINI_KIND); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - onVideoTakeCallback.onFinishVideoRecording(bitmap); - } - }); - } - } - - public void stopVideoRecording(CameraSession session, boolean abandon) { - try { - CameraInfo info = session.cameraInfo; - Camera camera = info.camera; - if (camera != null && recorder != null) { - MediaRecorder tempRecorder = recorder; - recorder = null; - tempRecorder.stop(); - tempRecorder.release(); - camera.reconnect(); - camera.startPreview(); - session.stopVideoRecording(); - } - if (!abandon) { + if (onVideoTakeCallback != null) { final Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(recordedFile, MediaStore.Video.Thumbnails.MINI_KIND); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - onVideoTakeCallback.onFinishVideoRecording(bitmap); + if (onVideoTakeCallback != null) { + onVideoTakeCallback.onFinishVideoRecording(bitmap); + onVideoTakeCallback = null; + } } }); } - } catch (Exception e) { - FileLog.e("tmessages", e); } } + public void stopVideoRecording(final CameraSession session, final boolean abandon) { + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + CameraInfo info = session.cameraInfo; + final Camera camera = info.camera; + if (camera != null && recorder != null) { + MediaRecorder tempRecorder = recorder; + recorder = null; + try { + tempRecorder.stop(); + } catch (Exception e) { + FileLog.e(e); + } + try { + tempRecorder.release(); + } catch (Exception e) { + FileLog.e(e); + } + try { + camera.reconnect(); + camera.startPreview(); + } catch (Exception e) { + FileLog.e(e); + } + try { + session.stopVideoRecording(); + } catch (Exception e) { + FileLog.e(e); + } + } + try { + Camera.Parameters params = camera.getParameters(); + params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); + camera.setParameters(params); + } catch (Exception e) { + FileLog.e(e); + } + threadPool.execute(new Runnable() { + @Override + public void run() { + try { + Camera.Parameters params = camera.getParameters(); + params.setFlashMode(session.getCurrentFlashMode()); + camera.setParameters(params); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + if (!abandon && onVideoTakeCallback != null) { + final Bitmap bitmap = !recordingSmallVideo ? ThumbnailUtils.createVideoThumbnail(recordedFile, MediaStore.Video.Thumbnails.MINI_KIND) : null; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (onVideoTakeCallback != null) { + onVideoTakeCallback.onFinishVideoRecording(bitmap); + onVideoTakeCallback = null; + } + } + }); + } else { + onVideoTakeCallback = null; + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + public static Size chooseOptimalSize(List choices, int width, int height, Size aspectRatio) { List bigEnough = new ArrayList<>(); int w = aspectRatio.getWidth(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraInfo.java index c585c64ae64..7395b5e8150 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraInfo.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.camera; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraSession.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraSession.java index 1b520f51f67..91b3910a680 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraSession.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraSession.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.camera; @@ -11,6 +11,7 @@ import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Rect; import android.hardware.Camera; import android.media.CamcorderProfile; import android.media.MediaRecorder; @@ -30,11 +31,29 @@ public class CameraSession { private String currentFlashMode = Camera.Parameters.FLASH_MODE_OFF; private OrientationEventListener orientationEventListener; private int lastOrientation = -1; + private int lastDisplayOrientation = -1; private boolean isVideo; private final Size pictureSize; private final Size previewSize; private final int pictureFormat; private boolean initied; + private boolean meteringAreaSupported; + private int currentOrientation; + private int jpegOrientation; + private boolean sameTakePictureOrientation; + + public static final int ORIENTATION_HYSTERESIS = 5; + + private Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() { + @Override + public void onAutoFocus(boolean success, Camera camera) { + if (success) { + + } else { + + } + } + }; public CameraSession(CameraInfo info, Size preview, Size picture, int format) { previewSize = preview; @@ -48,16 +67,18 @@ public CameraSession(CameraInfo info, Size preview, Size picture, int format) { orientationEventListener = new OrientationEventListener(ApplicationLoader.applicationContext) { @Override public void onOrientationChanged(int orientation) { - if (orientationEventListener == null || !initied) { + if (orientationEventListener == null || !initied || orientation == ORIENTATION_UNKNOWN) { return; } + jpegOrientation = roundOrientation(orientation, jpegOrientation); WindowManager mgr = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE); int rotation = mgr.getDefaultDisplay().getRotation(); - if (lastOrientation != rotation) { + if (lastOrientation != jpegOrientation || rotation != lastDisplayOrientation) { if (!isVideo) { configurePhotoCamera(); } - lastOrientation = rotation; + lastDisplayOrientation = rotation; + lastOrientation = jpegOrientation; } } }; @@ -70,6 +91,20 @@ public void onOrientationChanged(int orientation) { } } + private int roundOrientation(int orientation, int orientationHistory) { + boolean changeOrientation; + if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) { + changeOrientation = true; + } else { + int dist = Math.abs(orientation - orientationHistory); + dist = Math.min( dist, 360 - dist ); + changeOrientation = ( dist >= 45 + ORIENTATION_HYSTERESIS ); + } + if (changeOrientation) { + return ((orientation + 45) / 90 * 90) % 360; + } + return orientationHistory; + } public void checkFlashMode(String mode) { ArrayList modes = CameraController.getInstance().availableFlashModes; @@ -116,6 +151,14 @@ protected boolean isInitied() { return initied; } + public int getCurrentOrientation() { + return currentOrientation; + } + + public boolean isSameTakePictureOrientation() { + return sameTakePictureOrientation; + } + protected void configurePhotoCamera() { try { Camera camera = cameraInfo.camera; @@ -125,7 +168,7 @@ protected void configurePhotoCamera() { try { params = camera.getParameters(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Camera.getCameraInfo(cameraInfo.getCameraId(), info); @@ -163,36 +206,33 @@ protected void configurePhotoCamera() { } cameraDisplayOrientation = temp; } - camera.setDisplayOrientation(cameraDisplayOrientation); + camera.setDisplayOrientation(currentOrientation = cameraDisplayOrientation); if (params != null) { params.setPreviewSize(previewSize.getWidth(), previewSize.getHeight()); params.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight()); params.setPictureFormat(pictureFormat); - String desiredMode; - /*if (focusMode == FocusMode.OFF) { - desiredMode = Camera.Parameters.FOCUS_MODE_FIXED; - } else if (focusMode == FocusMode.EDOF) { - desiredMode = Camera.Parameters.FOCUS_MODE_EDOF; - } else if (isVideo) { - desiredMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO; - } else { - desiredMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; - }*/ - desiredMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; + String desiredMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; if (params.getSupportedFocusModes().contains(desiredMode)) { params.setFocusMode(desiredMode); } - int outputOrientation; - if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { - outputOrientation = (360 - displayOrientation) % 360; - } else { - outputOrientation = displayOrientation; + int outputOrientation = 0; + if (jpegOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) { + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + outputOrientation = (info.orientation - jpegOrientation + 360) % 360; + } else { + outputOrientation = (info.orientation + jpegOrientation) % 360; + } } try { params.setRotation(outputOrientation); + if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { + sameTakePictureOrientation = (360 - displayOrientation) % 360 == outputOrientation; + } else { + sameTakePictureOrientation = displayOrientation == outputOrientation; + } } catch (Exception e) { // } @@ -202,10 +242,52 @@ protected void configurePhotoCamera() { } catch (Exception e) { // } + + if (params.getMaxNumMeteringAreas() > 0) { + meteringAreaSupported = true; + } } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } + } + + protected void focusToRect(Rect focusRect, Rect meteringRect) { + try { + Camera camera = cameraInfo.camera; + if (camera != null) { + + camera.cancelAutoFocus(); + Camera.Parameters parameters = null; + try { + parameters = camera.getParameters(); + } catch (Exception e) { + FileLog.e(e); + } + + if (parameters != null) { + parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); + ArrayList meteringAreas = new ArrayList<>(); + meteringAreas.add(new Camera.Area(focusRect, 1000)); + parameters.setFocusAreas(meteringAreas); + + if (meteringAreaSupported) { + meteringAreas = new ArrayList<>(); + meteringAreas.add(new Camera.Area(meteringRect, 1000)); + parameters.setMeteringAreas(meteringAreas); + } + + try { + camera.setParameters(parameters); + camera.autoFocus(autoFocusCallback); + } catch (Exception e) { + FileLog.e(e); + } + } + } + } catch (Exception e) { + FileLog.e(e); } } @@ -279,6 +361,17 @@ private int getDisplayOrientation(Camera.CameraInfo info, boolean isStillCapture return displayOrientation; } + public int getDisplayOrientation() { + try { + Camera.CameraInfo info = new Camera.CameraInfo(); + Camera.getCameraInfo(cameraInfo.getCameraId(), info); + return getDisplayOrientation(info, true); + } catch (Exception e) { + FileLog.e(e); + } + return 0; + } + public void destroy() { initied = false; if (orientationEventListener != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java index 77c503d1c2c..d302b2f0b24 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.camera; @@ -11,11 +11,18 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; +import android.graphics.Canvas; import android.graphics.ImageFormat; import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.SurfaceTexture; +import android.hardware.Camera; import android.view.Surface; import android.view.TextureView; +import android.view.View; +import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; @@ -26,7 +33,7 @@ @SuppressLint("NewApi") public class CameraView extends FrameLayout implements TextureView.SurfaceTextureListener { - private org.telegram.messenger.camera.Size previewSize; + private Size previewSize; private boolean mirror; private TextureView textureView; private CameraSession cameraSession; @@ -36,16 +43,37 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur private int clipLeft; private boolean isFrontface; private Matrix txform = new Matrix(); + private Matrix matrix = new Matrix(); + private int focusAreaSize; + private boolean circleShape = false; + + private long lastDrawTime; + private float focusProgress = 1.0f; + private float innerAlpha; + private float outerAlpha; + private int cx; + private int cy; + private Paint outerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint innerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private DecelerateInterpolator interpolator = new DecelerateInterpolator(); public interface CameraViewDelegate { + void onCameraCreated(Camera camera); void onCameraInit(); } - public CameraView(Context context) { + public CameraView(Context context, boolean frontface) { super(context, null); + isFrontface = frontface; textureView = new TextureView(context); textureView.setSurfaceTextureListener(this); addView(textureView); + focusAreaSize = AndroidUtilities.dp(96); + outerPaint.setColor(0xffffffff); + outerPaint.setStyle(Paint.Style.STROKE); + outerPaint.setStrokeWidth(AndroidUtilities.dp(2)); + innerPaint.setColor(0x7fffffff); } @Override @@ -74,7 +102,7 @@ public boolean hasFrontFaceCamera() { public void switchCamera() { if (cameraSession != null) { - CameraController.getInstance().close(cameraSession, null); + CameraController.getInstance().close(cameraSession, null, null); cameraSession = null; } initied = false; @@ -85,6 +113,9 @@ public void switchCamera() { private void initCamera(boolean front) { CameraInfo info = null; ArrayList cameraInfos = CameraController.getInstance().getCameras(); + if (cameraInfos == null) { + return; + } for (int a = 0; a < cameraInfos.size(); a++) { CameraInfo cameraInfo = cameraInfos.get(a); if (isFrontface && cameraInfo.frontCamera != 0 || !isFrontface && cameraInfo.frontCamera == 0) { @@ -98,22 +129,40 @@ private void initCamera(boolean front) { float size4to3 = 4.0f / 3.0f; float size16to9 = 16.0f / 9.0f; float screenSize = (float) Math.max(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); - org.telegram.messenger.camera.Size pictureSize; + org.telegram.messenger.camera.Size aspectRatio; + int wantedWidth; + int wantedHeight; if (Math.abs(screenSize - size4to3) < 0.1f) { - pictureSize = new Size(4, 3); + aspectRatio = new Size(4, 3); + wantedWidth = 1280; + wantedHeight = 960; } else { - pictureSize = new Size(16, 9); + aspectRatio = new Size(16, 9); + wantedWidth = 1280; + wantedHeight = 720; } if (textureView.getWidth() > 0 && textureView.getHeight() > 0) { int width = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); - int height = width * pictureSize.getHeight() / pictureSize.getWidth(); - previewSize = CameraController.chooseOptimalSize(info.getPreviewSizes(), width, height, pictureSize); + int height = width * aspectRatio.getHeight() / aspectRatio.getWidth(); + previewSize = CameraController.chooseOptimalSize(info.getPreviewSizes(), width, height, aspectRatio); + } + org.telegram.messenger.camera.Size pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), wantedWidth, wantedHeight, aspectRatio); + if (pictureSize.getWidth() >= 1280 && pictureSize.getHeight() >= 1280) { + if (Math.abs(screenSize - size4to3) < 0.1f) { + aspectRatio = new Size(3, 4); + } else { + aspectRatio = new Size(9, 16); + } + org.telegram.messenger.camera.Size pictureSize2 = CameraController.chooseOptimalSize(info.getPictureSizes(), wantedHeight, wantedWidth, aspectRatio); + if (pictureSize2.getWidth() < 1280 || pictureSize2.getHeight() < 1280) { + pictureSize = pictureSize2; + } } - pictureSize = CameraController.chooseOptimalSize(info.getPictureSizes(), 1280, 1280, pictureSize); - if (previewSize != null && textureView.getSurfaceTexture() != null) { - textureView.getSurfaceTexture().setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); + if (previewSize != null && surfaceTexture != null) { + surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); cameraSession = new CameraSession(info, previewSize, pictureSize, ImageFormat.JPEG); - CameraController.getInstance().open(cameraSession, textureView.getSurfaceTexture(), new Runnable() { + CameraController.getInstance().open(cameraSession, surfaceTexture, new Runnable() { @Override public void run() { if (cameraSession != null) { @@ -121,24 +170,35 @@ public void run() { } checkPreviewMatrix(); } + }, new Runnable() { + @Override + public void run() { + if (delegate != null) { + delegate.onCameraCreated(cameraSession.cameraInfo.camera); + } + } }); } } + public Size getPreviewSize() { + return previewSize; + } + @Override - public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) { + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { initCamera(isFrontface); } @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i1) { + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { checkPreviewMatrix(); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { if (cameraSession != null) { - CameraController.getInstance().close(cameraSession, null); + CameraController.getInstance().close(cameraSession, null, null); } return false; } @@ -207,6 +267,51 @@ private void adjustAspectRatio(int previewWidth, int previewHeight, int rotation } textureView.setTransform(txform); + + Matrix matrix = new Matrix(); + matrix.postRotate(cameraSession.getDisplayOrientation()); + matrix.postScale(viewWidth / 2000f, viewHeight / 2000f); + matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); + matrix.invert(this.matrix); + } + + private Rect calculateTapArea(float x, float y, float coefficient) { + int areaSize = Float.valueOf(focusAreaSize * coefficient).intValue(); + + int left = clamp((int) x - areaSize / 2, 0, getWidth() - areaSize); + int top = clamp((int) y - areaSize / 2, 0, getHeight() - areaSize); + + RectF rectF = new RectF(left, top, left + areaSize, top + areaSize); + matrix.mapRect(rectF); + + return new Rect(Math.round(rectF.left), Math.round(rectF.top), Math.round(rectF.right), Math.round(rectF.bottom)); + } + + private int clamp(int x, int min, int max) { + if (x > max) { + return max; + } + if (x < min) { + return min; + } + return x; + } + + public void focusToPoint(int x, int y) { + Rect focusRect = calculateTapArea(x, y, 1f); + Rect meteringRect = calculateTapArea(x, y, 1.5f); + + if (cameraSession != null) { + cameraSession.focusToRect(focusRect, meteringRect); + } + + focusProgress = 0.0f; + innerAlpha = 1.0f; + outerAlpha = 1.0f; + cx = x; + cy = y; + lastDrawTime = System.currentTimeMillis(); + invalidate(); } public void setDelegate(CameraViewDelegate cameraViewDelegate) { @@ -221,10 +326,50 @@ public CameraSession getCameraSession() { return cameraSession; } - public void destroy(boolean async) { + public void destroy(boolean async, final Runnable beforeDestroyRunnable) { if (cameraSession != null) { cameraSession.destroy(); - CameraController.getInstance().close(cameraSession, !async ? new Semaphore(0) : null); + CameraController.getInstance().close(cameraSession, !async ? new Semaphore(0) : null, beforeDestroyRunnable); + } + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (focusProgress != 1.0f || innerAlpha != 0.0f || outerAlpha != 0.0f) { + int baseRad = AndroidUtilities.dp(30); + long newTime = System.currentTimeMillis(); + long dt = newTime - lastDrawTime; + if (dt < 0 || dt > 17) { + dt = 17; + } + lastDrawTime = newTime; + outerPaint.setAlpha((int) (interpolator.getInterpolation(outerAlpha) * 255)); + innerPaint.setAlpha((int) (interpolator.getInterpolation(innerAlpha) * 127)); + float interpolated = interpolator.getInterpolation(focusProgress); + canvas.drawCircle(cx, cy, baseRad + baseRad * (1.0f - interpolated), outerPaint); + canvas.drawCircle(cx, cy, baseRad * interpolated, innerPaint); + + if (focusProgress < 1) { + focusProgress += dt / 200.0f; + if (focusProgress > 1) { + focusProgress = 1; + } + invalidate(); + } else if (innerAlpha != 0) { + innerAlpha -= dt / 150.0f; + if (innerAlpha < 0) { + innerAlpha = 0; + } + invalidate(); + } else if (outerAlpha != 0) { + outerAlpha -= dt / 150.0f; + if (outerAlpha < 0) { + outerAlpha = 0; + } + invalidate(); + } } + return result; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/Size.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/Size.java index da1b86dd3ba..9805500c142 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/Size.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/Size.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.camera; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/AspectRatioFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/AspectRatioFrameLayout.java deleted file mode 100755 index a8d5bc5ce7a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/AspectRatioFrameLayout.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.content.Context; -import android.graphics.Matrix; -import android.util.AttributeSet; -import android.view.TextureView; -import android.view.View; -import android.widget.FrameLayout; - -/** - * A {@link FrameLayout} that resizes itself to match a specified aspect ratio. - */ -public final class AspectRatioFrameLayout extends FrameLayout { - - /** - * The {@link FrameLayout} will not resize itself if the fractional difference between its natural - * aspect ratio and the requested aspect ratio falls below this threshold. - *

        - * This tolerance allows the view to occupy the whole of the screen when the requested aspect - * ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce - * the number of view layers that need to be composited by the underlying system, which can help - * to reduce power consumption. - */ - private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; - - private float videoAspectRatio; - private int rotation; - private Matrix matrix = new Matrix(); - - public AspectRatioFrameLayout(Context context) { - super(context); - } - - public AspectRatioFrameLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Set the aspect ratio that this view should satisfy. - * - * @param widthHeightRatio The width to height ratio. - */ - public void setAspectRatio(float widthHeightRatio, int rotation) { - if (this.videoAspectRatio != widthHeightRatio || this.rotation != rotation) { - this.videoAspectRatio = widthHeightRatio; - this.rotation = rotation; - requestLayout(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (videoAspectRatio == 0) { - // Aspect ratio not set. - return; - } - - int width = getMeasuredWidth(); - int height = getMeasuredHeight(); - float viewAspectRatio = (float) width / height; - float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; - if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) { - // We're within the allowed tolerance. - return; - } - - if (aspectDeformation > 0) { - height = (int) (width / videoAspectRatio); - } else { - width = (int) (height * videoAspectRatio); - } - super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - int count = getChildCount(); - for (int a = 0; a < count; a++) { - View child = getChildAt(a); - if (child instanceof TextureView) { - matrix.reset(); - int px = getWidth() / 2; - int py = getHeight() / 2; - matrix.postRotate(rotation, px, py); - if (rotation == 90 || rotation == 270) { - float ratio = (float) getHeight() / getWidth(); - matrix.postScale(1 / ratio, ratio, px, py); - } - ((TextureView) child).setTransform(matrix); - break; - } - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/C.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/C.java deleted file mode 100755 index 43d4ee7ba2c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/C.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.media.AudioFormat; -import android.media.MediaCodec; -import android.media.MediaExtractor; -import org.telegram.messenger.exoplayer.util.Util; - -/** - * Defines constants that are generally useful throughout the library. - */ -public final class C { - - /** - * Represents an unknown microsecond time or duration. - */ - public static final long UNKNOWN_TIME_US = -1L; - - /** - * Represents a microsecond duration whose exact value is unknown, but which should match the - * longest of some other known durations. - */ - public static final long MATCH_LONGEST_US = -2L; - - /** - * The number of microseconds in one second. - */ - public static final long MICROS_PER_SECOND = 1000000L; - - /** - * Represents an unbounded length of data. - */ - public static final int LENGTH_UNBOUNDED = -1; - - /** - * The name of the UTF-8 charset. - */ - public static final String UTF8_NAME = "UTF-8"; - - /** - * @see MediaCodec#CRYPTO_MODE_AES_CTR - */ - @SuppressWarnings("InlinedApi") - public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; - - /** - * @see AudioFormat#ENCODING_INVALID - */ - public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; - - /** - * @see AudioFormat#ENCODING_PCM_8BIT - */ - public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; - - /** - * @see AudioFormat#ENCODING_PCM_16BIT - */ - public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; - - /** - * PCM encoding with 24 bits per sample. - */ - public static final int ENCODING_PCM_24BIT = 0x80000000; - - /** - * PCM encoding with 32 bits per sample. - */ - public static final int ENCODING_PCM_32BIT = 0x40000000; - - /** - * @see AudioFormat#ENCODING_AC3 - */ - @SuppressWarnings("InlinedApi") - public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; - - /** - * @see AudioFormat#ENCODING_E_AC3 - */ - @SuppressWarnings("InlinedApi") - public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; - - /** - * @see AudioFormat#ENCODING_DTS - */ - @SuppressWarnings("InlinedApi") - public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; - - /** - * @see AudioFormat#ENCODING_DTS_HD - */ - @SuppressWarnings("InlinedApi") - public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; - - /** - * @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND - */ - @SuppressWarnings({"InlinedApi", "deprecation"}) - public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23 - ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; - - /** - * @see MediaExtractor#SAMPLE_FLAG_SYNC - */ - @SuppressWarnings("InlinedApi") - public static final int SAMPLE_FLAG_SYNC = MediaExtractor.SAMPLE_FLAG_SYNC; - - /** - * @see MediaExtractor#SAMPLE_FLAG_ENCRYPTED - */ - @SuppressWarnings("InlinedApi") - public static final int SAMPLE_FLAG_ENCRYPTED = MediaExtractor.SAMPLE_FLAG_ENCRYPTED; - - /** - * Indicates that a sample should be decoded but not rendered. - */ - public static final int SAMPLE_FLAG_DECODE_ONLY = 0x8000000; - - /** - * A return value for methods where the end of an input was encountered. - */ - public static final int RESULT_END_OF_INPUT = -1; - - /** - * A return value for methods where the length of parsed data exceeds the maximum length allowed. - */ - public static final int RESULT_MAX_LENGTH_EXCEEDED = -2; - - private C() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CodecCounters.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CodecCounters.java deleted file mode 100755 index 5d138946cdc..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CodecCounters.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -/** - * Maintains codec event counts, for debugging purposes only. - *

        - * Counters should be written from the playback thread only. Counters may be read from any thread. - * To ensure that the counter values are correctly reflected between threads, users of this class - * should invoke {@link #ensureUpdated()} prior to reading and after writing. - */ -public final class CodecCounters { - - public int codecInitCount; - public int codecReleaseCount; - public int inputBufferCount; - public int outputFormatChangedCount; - public int outputBuffersChangedCount; - public int renderedOutputBufferCount; - public int skippedOutputBufferCount; - public int droppedOutputBufferCount; - public int maxConsecutiveDroppedOutputBufferCount; - - /** - * Should be invoked from the playback thread after the counters have been updated. Should also - * be invoked from any other thread that wishes to read the counters, before reading. These calls - * ensure that counter updates are made visible to the reading threads. - */ - public synchronized void ensureUpdated() { - // Do nothing. The use of synchronized ensures a memory barrier should another thread also - // call this method. - } - - public String getDebugString() { - ensureUpdated(); - StringBuilder builder = new StringBuilder(); - builder.append("cic:").append(codecInitCount); - builder.append(" crc:").append(codecReleaseCount); - builder.append(" ibc:").append(inputBufferCount); - builder.append(" ofc:").append(outputFormatChangedCount); - builder.append(" obc:").append(outputBuffersChangedCount); - builder.append(" ren:").append(renderedOutputBufferCount); - builder.append(" sob:").append(skippedOutputBufferCount); - builder.append(" dob:").append(droppedOutputBufferCount); - builder.append(" mcdob:").append(maxConsecutiveDroppedOutputBufferCount); - return builder.toString(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CryptoInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CryptoInfo.java deleted file mode 100755 index 7fedf1e1d81..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/CryptoInfo.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.TargetApi; -import android.media.MediaExtractor; -import org.telegram.messenger.exoplayer.util.Util; - -/** - * Compatibility wrapper around {@link android.media.MediaCodec.CryptoInfo}. - */ -public final class CryptoInfo { - - /** - * @see android.media.MediaCodec.CryptoInfo#iv - */ - public byte[] iv; - /** - * @see android.media.MediaCodec.CryptoInfo#key - */ - public byte[] key; - /** - * @see android.media.MediaCodec.CryptoInfo#mode - */ - public int mode; - /** - * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData - */ - public int[] numBytesOfClearData; - /** - * @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData - */ - public int[] numBytesOfEncryptedData; - /** - * @see android.media.MediaCodec.CryptoInfo#numSubSamples - */ - public int numSubSamples; - - private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; - - public CryptoInfo() { - frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null; - } - - /** - * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) - */ - public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, - byte[] key, byte[] iv, int mode) { - this.numSubSamples = numSubSamples; - this.numBytesOfClearData = numBytesOfClearData; - this.numBytesOfEncryptedData = numBytesOfEncryptedData; - this.key = key; - this.iv = iv; - this.mode = mode; - if (Util.SDK_INT >= 16) { - updateFrameworkCryptoInfoV16(); - } - } - - /** - * Equivalent to {@link MediaExtractor#getSampleCryptoInfo(android.media.MediaCodec.CryptoInfo)}. - * - * @param extractor The extractor from which to retrieve the crypto information. - */ - @TargetApi(16) - public void setFromExtractorV16(MediaExtractor extractor) { - extractor.getSampleCryptoInfo(frameworkCryptoInfo); - numSubSamples = frameworkCryptoInfo.numSubSamples; - numBytesOfClearData = frameworkCryptoInfo.numBytesOfClearData; - numBytesOfEncryptedData = frameworkCryptoInfo.numBytesOfEncryptedData; - key = frameworkCryptoInfo.key; - iv = frameworkCryptoInfo.iv; - mode = frameworkCryptoInfo.mode; - } - - /** - * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. - *

        - * Successive calls to this method on a single {@link CryptoInfo} will return the same instance. - * Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object - * should not be modified directly. - * - * @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance. - */ - @TargetApi(16) - public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() { - return frameworkCryptoInfo; - } - - @TargetApi(16) - private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() { - return new android.media.MediaCodec.CryptoInfo(); - } - - @TargetApi(16) - private void updateFrameworkCryptoInfoV16() { - frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv, - mode); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DecoderInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DecoderInfo.java deleted file mode 100755 index 5042dbfaa39..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DecoderInfo.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.TargetApi; -import android.media.MediaCodecInfo.CodecCapabilities; -import org.telegram.messenger.exoplayer.util.Util; - -/** - * Contains information about a media decoder. - */ -@TargetApi(16) -public final class DecoderInfo { - - /** - * The name of the decoder. - *

        - * May be passed to {@link android.media.MediaCodec#createByCodecName(String)} to create an - * instance of the decoder. - */ - public final String name; - - /** - * {@link CodecCapabilities} for this decoder. - */ - public final CodecCapabilities capabilities; - - /** - * Whether the decoder supports seamless resolution switches. - * - * @see android.media.MediaCodecInfo.CodecCapabilities#isFeatureSupported(String) - * @see android.media.MediaCodecInfo.CodecCapabilities#FEATURE_AdaptivePlayback - */ - public final boolean adaptive; - - /** - * @param name The name of the decoder. - * @param capabilities {@link CodecCapabilities} of the decoder. - */ - /* package */ DecoderInfo(String name, CodecCapabilities capabilities) { - this.name = name; - this.capabilities = capabilities; - this.adaptive = isAdaptive(capabilities); - } - - private static boolean isAdaptive(CodecCapabilities capabilities) { - return capabilities != null && Util.SDK_INT >= 19 && isAdaptiveV19(capabilities); - } - - @TargetApi(19) - private static boolean isAdaptiveV19(CodecCapabilities capabilities) { - return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DefaultLoadControl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DefaultLoadControl.java deleted file mode 100755 index 56553d6936c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DefaultLoadControl.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.os.Handler; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.upstream.NetworkLock; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -/** - * A {@link LoadControl} implementation that allows loads to continue in a sequence that prevents - * any loader from getting too far ahead or behind any of the other loaders. - *

        - * Loads are scheduled so as to fill the available buffer space as rapidly as possible. Once the - * duration of buffered media and the buffer utilization both exceed respective thresholds, the - * control switches to a draining state during which no loads are permitted to start. During - * draining periods, resources such as the device radio have an opportunity to switch into low - * power modes. The control reverts back to the loading state when either the duration of buffered - * media or the buffer utilization fall below respective thresholds. - *

        - * This implementation of {@link LoadControl} integrates with {@link NetworkLock}, by registering - * itself as a task with priority {@link NetworkLock#STREAMING_PRIORITY} during loading periods, - * and unregistering itself during draining periods. - */ -public final class DefaultLoadControl implements LoadControl { - - /** - * Interface definition for a callback to be notified of {@link DefaultLoadControl} events. - */ - public interface EventListener { - - /** - * Invoked when the control transitions from a loading to a draining state, or vice versa. - * - * @param loading Whether the control is now in a loading state. - */ - void onLoadingChanged(boolean loading); - - } - - public static final int DEFAULT_LOW_WATERMARK_MS = 15000; - public static final int DEFAULT_HIGH_WATERMARK_MS = 30000; - public static final float DEFAULT_LOW_BUFFER_LOAD = 0.2f; - public static final float DEFAULT_HIGH_BUFFER_LOAD = 0.8f; - - private static final int ABOVE_HIGH_WATERMARK = 0; - private static final int BETWEEN_WATERMARKS = 1; - private static final int BELOW_LOW_WATERMARK = 2; - - private final Allocator allocator; - private final List loaders; - private final HashMap loaderStates; - private final Handler eventHandler; - private final EventListener eventListener; - - private final long lowWatermarkUs; - private final long highWatermarkUs; - private final float lowBufferLoad; - private final float highBufferLoad; - - private int targetBufferSize; - private long maxLoadStartPositionUs; - private int bufferState; - private boolean fillingBuffers; - private boolean streamingPrioritySet; - - /** - * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. - * - * @param allocator The {@link Allocator} used by the loader. - */ - public DefaultLoadControl(Allocator allocator) { - this(allocator, null, null); - } - - /** - * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. - * - * @param allocator The {@link Allocator} used by the loader. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public DefaultLoadControl(Allocator allocator, Handler eventHandler, - EventListener eventListener) { - this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS, - DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_BUFFER_LOAD, DEFAULT_HIGH_BUFFER_LOAD); - } - - /** - * Constructs a new instance. - * - * @param allocator The {@link Allocator} used by the loader. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param lowWatermarkMs The minimum duration of media that can be buffered for the control to - * be in the draining state. If less media is buffered, then the control will transition to - * the filling state. - * @param highWatermarkMs The minimum duration of media that can be buffered for the control to - * transition from filling to draining. - * @param lowBufferLoad The minimum fraction of the buffer that must be utilized for the control - * to be in the draining state. If the utilization is lower, then the control will transition - * to the filling state. - * @param highBufferLoad The minimum fraction of the buffer that must be utilized for the control - * to transition from the loading state to the draining state. - */ - public DefaultLoadControl(Allocator allocator, Handler eventHandler, EventListener eventListener, - int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, float highBufferLoad) { - this.allocator = allocator; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.loaders = new ArrayList<>(); - this.loaderStates = new HashMap<>(); - this.lowWatermarkUs = lowWatermarkMs * 1000L; - this.highWatermarkUs = highWatermarkMs * 1000L; - this.lowBufferLoad = lowBufferLoad; - this.highBufferLoad = highBufferLoad; - } - - @Override - public void register(Object loader, int bufferSizeContribution) { - loaders.add(loader); - loaderStates.put(loader, new LoaderState(bufferSizeContribution)); - targetBufferSize += bufferSizeContribution; - } - - @Override - public void unregister(Object loader) { - loaders.remove(loader); - LoaderState state = loaderStates.remove(loader); - targetBufferSize -= state.bufferSizeContribution; - updateControlState(); - } - - @Override - public void trimAllocator() { - allocator.trim(targetBufferSize); - } - - @Override - public Allocator getAllocator() { - return allocator; - } - - @Override - public boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs, - boolean loading) { - // Update the loader state. - int loaderBufferState = getLoaderBufferState(playbackPositionUs, nextLoadPositionUs); - LoaderState loaderState = loaderStates.get(loader); - boolean loaderStateChanged = loaderState.bufferState != loaderBufferState - || loaderState.nextLoadPositionUs != nextLoadPositionUs || loaderState.loading != loading; - if (loaderStateChanged) { - loaderState.bufferState = loaderBufferState; - loaderState.nextLoadPositionUs = nextLoadPositionUs; - loaderState.loading = loading; - } - - // Update the buffer state. - int currentBufferSize = allocator.getTotalBytesAllocated(); - int bufferState = getBufferState(currentBufferSize); - boolean bufferStateChanged = this.bufferState != bufferState; - if (bufferStateChanged) { - this.bufferState = bufferState; - } - - // If either of the individual states have changed, update the shared control state. - if (loaderStateChanged || bufferStateChanged) { - updateControlState(); - } - - return currentBufferSize < targetBufferSize && nextLoadPositionUs != -1 - && nextLoadPositionUs <= maxLoadStartPositionUs; - } - - private int getLoaderBufferState(long playbackPositionUs, long nextLoadPositionUs) { - if (nextLoadPositionUs == -1) { - return ABOVE_HIGH_WATERMARK; - } else { - long timeUntilNextLoadPosition = nextLoadPositionUs - playbackPositionUs; - return timeUntilNextLoadPosition > highWatermarkUs ? ABOVE_HIGH_WATERMARK : - timeUntilNextLoadPosition < lowWatermarkUs ? BELOW_LOW_WATERMARK : - BETWEEN_WATERMARKS; - } - } - - private int getBufferState(int currentBufferSize) { - float bufferLoad = (float) currentBufferSize / targetBufferSize; - return bufferLoad > highBufferLoad ? ABOVE_HIGH_WATERMARK - : bufferLoad < lowBufferLoad ? BELOW_LOW_WATERMARK - : BETWEEN_WATERMARKS; - } - - private void updateControlState() { - boolean loading = false; - boolean haveNextLoadPosition = false; - int highestState = bufferState; - for (int i = 0; i < loaders.size(); i++) { - LoaderState loaderState = loaderStates.get(loaders.get(i)); - loading |= loaderState.loading; - haveNextLoadPosition |= loaderState.nextLoadPositionUs != -1; - highestState = Math.max(highestState, loaderState.bufferState); - } - - fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition) - && (highestState == BELOW_LOW_WATERMARK - || (highestState == BETWEEN_WATERMARKS && fillingBuffers)); - if (fillingBuffers && !streamingPrioritySet) { - NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY); - streamingPrioritySet = true; - notifyLoadingChanged(true); - } else if (!fillingBuffers && streamingPrioritySet && !loading) { - NetworkLock.instance.remove(NetworkLock.STREAMING_PRIORITY); - streamingPrioritySet = false; - notifyLoadingChanged(false); - } - - maxLoadStartPositionUs = -1; - if (fillingBuffers) { - for (int i = 0; i < loaders.size(); i++) { - Object loader = loaders.get(i); - LoaderState loaderState = loaderStates.get(loader); - long loaderTime = loaderState.nextLoadPositionUs; - if (loaderTime != -1 - && (maxLoadStartPositionUs == -1 || loaderTime < maxLoadStartPositionUs)) { - maxLoadStartPositionUs = loaderTime; - } - } - } - } - - private void notifyLoadingChanged(final boolean loading) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadingChanged(loading); - } - }); - } - } - - private static class LoaderState { - - public final int bufferSizeContribution; - - public int bufferState; - public boolean loading; - public long nextLoadPositionUs; - - public LoaderState(int bufferSizeContribution) { - this.bufferSizeContribution = bufferSizeContribution; - bufferState = ABOVE_HIGH_WATERMARK; - loading = false; - nextLoadPositionUs = -1; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DummyTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DummyTrackRenderer.java deleted file mode 100755 index 9afe11f0ed7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/DummyTrackRenderer.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -/** - * A {@link TrackRenderer} that does nothing. - *

        - * This renderer returns 0 from {@link #getTrackCount()} in order to request that it should be - * ignored. {@link IllegalStateException} is thrown from all other methods documented to indicate - * that they should not be invoked unless the renderer is prepared. - */ -public final class DummyTrackRenderer extends TrackRenderer { - - @Override - protected boolean doPrepare(long positionUs) throws ExoPlaybackException { - return true; - } - - @Override - protected int getTrackCount() { - return 0; - } - - @Override - protected MediaFormat getFormat(int track) { - throw new IllegalStateException(); - } - - @Override - protected boolean isEnded() { - throw new IllegalStateException(); - } - - @Override - protected boolean isReady() { - throw new IllegalStateException(); - } - - @Override - protected void seekTo(long positionUs) { - throw new IllegalStateException(); - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) { - throw new IllegalStateException(); - } - - @Override - protected void maybeThrowError() { - throw new IllegalStateException(); - } - - @Override - protected long getDurationUs() { - throw new IllegalStateException(); - } - - @Override - protected long getBufferedPositionUs() { - throw new IllegalStateException(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlaybackException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlaybackException.java deleted file mode 100755 index 9a1bd5cf7e9..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlaybackException.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -/** - * Thrown when a non-recoverable playback failure occurs. - *

        - * Where possible, the cause returned by {@link #getCause()} will indicate the reason for failure. - */ -public final class ExoPlaybackException extends Exception { - - /** - * True if the cause (i.e. the {@link Throwable} returned by {@link #getCause()}) was only caught - * by a fail-safe at the top level of the player. False otherwise. - */ - public final boolean caughtAtTopLevel; - - public ExoPlaybackException(String message) { - super(message); - caughtAtTopLevel = false; - } - - public ExoPlaybackException(Throwable cause) { - super(cause); - caughtAtTopLevel = false; - } - - public ExoPlaybackException(String message, Throwable cause) { - super(message, cause); - caughtAtTopLevel = false; - } - - /* package */ ExoPlaybackException(Throwable cause, boolean caughtAtTopLevel) { - super(cause); - this.caughtAtTopLevel = caughtAtTopLevel; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayer.java deleted file mode 100755 index a09f427d1f9..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayer.java +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.os.Looper; - -/** - * An extensible media player exposing traditional high-level media player functionality, such as - * the ability to prepare, play, pause and seek. - * - *

        Topics covered here are: - *

          - *
        1. Assumptions and player composition - *
        2. Threading model - *
        3. Player state - *
        - * - * - *

        Assumptions and player construction

        - * - *

        The implementation is designed to make no assumptions about (and hence impose no restrictions - * on) the type of the media being played, how and where it is stored, or how it is rendered. - * Rather than implementing the loading and rendering of media directly, {@link ExoPlayer} instead - * delegates this work to one or more {@link TrackRenderer}s, which are injected when the player - * is prepared. Hence {@link ExoPlayer} is capable of loading and playing any media for which a - * {@link TrackRenderer} implementation can be provided. - * - *

        {@link MediaCodecAudioTrackRenderer} and {@link MediaCodecVideoTrackRenderer} can be used for - * the common cases of rendering audio and video. These components in turn require an - * upstream {@link SampleSource} to be injected through their constructors, where upstream - * is defined to denote a component that is closer to the source of the media. This pattern of - * upstream dependency injection is actively encouraged, since it means that the functionality of - * the player is built up through the composition of components that can easily be exchanged for - * alternate implementations. For example a {@link SampleSource} implementation may require a - * further upstream data loading component to be injected through its constructor, with different - * implementations enabling the loading of data from various sources. - * - * - *

        Threading model

        - * - *

        The figure below shows the {@link ExoPlayer} threading model.

        - *

        MediaPlayer state diagram

        - * - *
          - *
        • It is recommended that instances are created and accessed from a single application thread. - * An application's main thread is ideal. Accessing an instance from multiple threads is - * discouraged, however if an application does wish to do this then it may do so provided that it - * ensures accesses are synchronized. - *
        • - *
        • Registered {@link Listener}s are invoked on the thread that created the {@link ExoPlayer} - * instance.
        • - *
        • An internal playback thread is responsible for managing playback and invoking the - * {@link TrackRenderer}s in order to load and play the media.
        • - *
        • {@link TrackRenderer} implementations (or any upstream components that they depend on) may - * use additional background threads (e.g. to load data). These are implementation specific.
        • - *
        - * - * - *

        Player state

        - * - *

        The components of an {@link ExoPlayer}'s state can be divided into two distinct groups. State - * accessed by {@link #getSelectedTrack(int)} and {@link #getPlayWhenReady()} is only ever - * changed by invoking the player's methods, and are never changed as a result of operations that - * have been performed asynchronously by the playback thread. In contrast, the playback state - * accessed by {@link #getPlaybackState()} is only ever changed as a result of operations - * completing on the playback thread, as illustrated below.

        - *

        ExoPlayer state

        - * - *

        The possible playback state transitions are shown below. Transitions can be triggered either - * by changes in the state of the {@link TrackRenderer}s being used, or as a result of - * {@link #prepare(TrackRenderer[])}, {@link #stop()} or {@link #release()} being invoked.

        - *

        ExoPlayer playback state transitions

        - */ -public interface ExoPlayer { - - /** - * A factory for instantiating ExoPlayer instances. - */ - public static final class Factory { - - /** - * The default minimum duration of data that must be buffered for playback to start or resume - * following a user action such as a seek. - */ - public static final int DEFAULT_MIN_BUFFER_MS = 2500; - - /** - * The default minimum duration of data that must be buffered for playback to resume - * after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). - */ - public static final int DEFAULT_MIN_REBUFFER_MS = 5000; - - private Factory() {} - - /** - * Obtains an {@link ExoPlayer} instance. - *

        - * Must be invoked from a thread that has an associated {@link Looper}. - * - * @param rendererCount The number of {@link TrackRenderer}s that will be passed to - * {@link #prepare(TrackRenderer[])}. - * @param minBufferMs A minimum duration of data that must be buffered for playback to start - * or resume following a user action such as a seek. - * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume - * after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). - */ - public static ExoPlayer newInstance(int rendererCount, int minBufferMs, int minRebufferMs) { - return new ExoPlayerImpl(rendererCount, minBufferMs, minRebufferMs); - } - - /** - * Obtains an {@link ExoPlayer} instance. - *

        - * Must be invoked from a thread that has an associated {@link Looper}. - * - * @param rendererCount The number of {@link TrackRenderer}s that will be passed to - * {@link #prepare(TrackRenderer[])}. - */ - public static ExoPlayer newInstance(int rendererCount) { - return new ExoPlayerImpl(rendererCount, DEFAULT_MIN_BUFFER_MS, DEFAULT_MIN_REBUFFER_MS); - } - - } - - /** - * Interface definition for a callback to be notified of changes in player state. - */ - public interface Listener { - /** - * Invoked when the value returned from either {@link ExoPlayer#getPlayWhenReady()} or - * {@link ExoPlayer#getPlaybackState()} changes. - * - * @param playWhenReady Whether playback will proceed when ready. - * @param playbackState One of the {@code STATE} constants defined in the {@link ExoPlayer} - * interface. - */ - void onPlayerStateChanged(boolean playWhenReady, int playbackState); - /** - * Invoked when the current value of {@link ExoPlayer#getPlayWhenReady()} has been reflected - * by the internal playback thread. - *

        - * An invocation of this method will shortly follow any call to - * {@link ExoPlayer#setPlayWhenReady(boolean)} that changes the state. If multiple calls are - * made in rapid succession, then this method will be invoked only once, after the final state - * has been reflected. - */ - void onPlayWhenReadyCommitted(); - /** - * Invoked when an error occurs. The playback state will transition to - * {@link ExoPlayer#STATE_IDLE} immediately after this method is invoked. The player instance - * can still be used, and {@link ExoPlayer#release()} must still be called on the player should - * it no longer be required. - * - * @param error The error. - */ - void onPlayerError(ExoPlaybackException error); - } - - /** - * A component of an {@link ExoPlayer} that can receive messages on the playback thread. - *

        - * Messages can be delivered to a component via {@link ExoPlayer#sendMessage} and - * {@link ExoPlayer#blockingSendMessage}. - */ - public interface ExoPlayerComponent { - - /** - * Handles a message delivered to the component. Invoked on the playback thread. - * - * @param messageType An integer identifying the type of message. - * @param message The message object. - * @throws ExoPlaybackException If an error occurred whilst handling the message. - */ - void handleMessage(int messageType, Object message) throws ExoPlaybackException; - - } - - /** - * The player is neither prepared or being prepared. - */ - public static final int STATE_IDLE = 1; - /** - * The player is being prepared. - */ - public static final int STATE_PREPARING = 2; - /** - * The player is prepared but not able to immediately play from the current position. The cause - * is {@link TrackRenderer} specific, but this state typically occurs when more data needs - * to be buffered for playback to start. - */ - public static final int STATE_BUFFERING = 3; - /** - * The player is prepared and able to immediately play from the current position. The player will - * be playing if {@link #getPlayWhenReady()} returns true, and paused otherwise. - */ - public static final int STATE_READY = 4; - /** - * The player has finished playing the media. - */ - public static final int STATE_ENDED = 5; - - /** - * A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to - * disable the renderer. - */ - public static final int TRACK_DISABLED = -1; - /** - * A value that can be passed as the second argument to {@link #setSelectedTrack(int, int)} to - * select the default track. - */ - public static final int TRACK_DEFAULT = 0; - - /** - * Represents an unknown time or duration. - */ - public static final long UNKNOWN_TIME = -1; - - /** - * Gets the {@link Looper} associated with the playback thread. - * - * @return The {@link Looper} associated with the playback thread. - */ - public Looper getPlaybackLooper(); - - /** - * Register a listener to receive events from the player. The listener's methods will be invoked - * on the thread that was used to construct the player. - * - * @param listener The listener to register. - */ - public void addListener(Listener listener); - - /** - * Unregister a listener. The listener will no longer receive events from the player. - * - * @param listener The listener to unregister. - */ - public void removeListener(Listener listener); - - /** - * Returns the current state of the player. - * - * @return One of the {@code STATE} constants defined in this interface. - */ - public int getPlaybackState(); - - /** - * Prepares the player for playback. - * - * @param renderers The {@link TrackRenderer}s to use. The number of renderers must match the - * value that was passed to the {@link ExoPlayer.Factory#newInstance} method. - */ - public void prepare(TrackRenderer... renderers); - - /** - * Returns the number of tracks exposed by the specified renderer. - * - * @param rendererIndex The index of the renderer. - * @return The number of tracks. - */ - public int getTrackCount(int rendererIndex); - - /** - * Returns the format of a track. - * - * @param rendererIndex The index of the renderer. - * @param trackIndex The index of the track. - * @return The format of the track. - */ - public MediaFormat getTrackFormat(int rendererIndex, int trackIndex); - - /** - * Selects a track for the specified renderer. - * - * @param rendererIndex The index of the renderer. - * @param trackIndex The index of the track. A negative value or a value greater than or equal to - * the renderer's track count will disable the renderer. - */ - public void setSelectedTrack(int rendererIndex, int trackIndex); - - /** - * Returns the index of the currently selected track for the specified renderer. - * - * @param rendererIndex The index of the renderer. - * @return The selected track. A negative value or a value greater than or equal to the renderer's - * track count indicates that the renderer is disabled. - */ - public int getSelectedTrack(int rendererIndex); - - /** - * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. - * If the player is already in this state, then this method can be used to pause and resume - * playback. - * - * @param playWhenReady Whether playback should proceed when ready. - */ - public void setPlayWhenReady(boolean playWhenReady); - - /** - * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. - * - * @return Whether playback will proceed when ready. - */ - public boolean getPlayWhenReady(); - - /** - * Whether the current value of {@link ExoPlayer#getPlayWhenReady()} has been reflected by the - * internal playback thread. - * - * @return True if the current value has been reflected. False otherwise. - */ - public boolean isPlayWhenReadyCommitted(); - - /** - * Seeks to a position specified in milliseconds. - * - * @param positionMs The seek position. - */ - public void seekTo(long positionMs); - - /** - * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention - * is to pause playback. - *

        - * Calling this method will cause the playback state to transition to - * {@link ExoPlayer#STATE_IDLE}. The player instance can still be used, and - * {@link ExoPlayer#release()} must still be called on the player if it's no longer required. - *

        - * Calling this method does not reset the playback position. If this player instance will be used - * to play another video from its start, then {@code seekTo(0)} should be called after stopping - * the player and before preparing it for the next video. - */ - public void stop(); - - /** - * Releases the player. This method must be called when the player is no longer required. - *

        - * The player must not be used after calling this method. - */ - public void release(); - - /** - * Sends a message to a specified component. The message is delivered to the component on the - * playback thread. If the component throws a {@link ExoPlaybackException}, then it is - * propagated out of the player as an error. - * - * @param target The target to which the message should be delivered. - * @param messageType An integer that can be used to identify the type of the message. - * @param message The message object. - */ - public void sendMessage(ExoPlayerComponent target, int messageType, Object message); - - /** - * Blocking variant of {@link #sendMessage(ExoPlayerComponent, int, Object)} that does not return - * until after the message has been delivered. - * - * @param target The target to which the message should be delivered. - * @param messageType An integer that can be used to identify the type of the message. - * @param message The message object. - */ - public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message); - - /** - * Gets the duration of the track in milliseconds. - * - * @return The duration of the track in milliseconds, or {@link ExoPlayer#UNKNOWN_TIME} if the - * duration is not known. - */ - public long getDuration(); - - /** - * Gets the current playback position in milliseconds. - * - * @return The current playback position in milliseconds. - */ - public long getCurrentPosition(); - - /** - * Gets an estimate of the absolute position in milliseconds up to which data is buffered. - * - * @return An estimate of the absolute position in milliseconds up to which data is buffered, - * or {@link ExoPlayer#UNKNOWN_TIME} if no estimate is available. - */ - public long getBufferedPosition(); - - /** - * Gets an estimate of the percentage into the media up to which data is buffered. - * - * @return An estimate of the percentage into the media up to which data is buffered. 0 if the - * duration of the media is not known or if no estimate is available. - */ - public int getBufferedPercentage(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImpl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImpl.java deleted file mode 100755 index 80a742b546e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImpl.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import java.util.Arrays; -import java.util.concurrent.CopyOnWriteArraySet; - -/** - * Concrete implementation of {@link ExoPlayer}. - */ -/* package */ final class ExoPlayerImpl implements ExoPlayer { - - private static final String TAG = "ExoPlayerImpl"; - - private final Handler eventHandler; - private final ExoPlayerImplInternal internalPlayer; - private final CopyOnWriteArraySet listeners; - private final MediaFormat[][] trackFormats; - private final int[] selectedTrackIndices; - - private boolean playWhenReady; - private int playbackState; - private int pendingPlayWhenReadyAcks; - - /** - * Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}. - * - * @param rendererCount The number of {@link TrackRenderer}s that will be passed to - * {@link #prepare(TrackRenderer[])}. - * @param minBufferMs A minimum duration of data that must be buffered for playback to start - * or resume following a user action such as a seek. - * @param minRebufferMs A minimum duration of data that must be buffered for playback to resume - * after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and - * not due to a user action such as starting playback or seeking). - */ - @SuppressLint("HandlerLeak") - public ExoPlayerImpl(int rendererCount, int minBufferMs, int minRebufferMs) { - Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION); - this.playWhenReady = false; - this.playbackState = STATE_IDLE; - this.listeners = new CopyOnWriteArraySet<>(); - this.trackFormats = new MediaFormat[rendererCount][]; - this.selectedTrackIndices = new int[rendererCount]; - eventHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - ExoPlayerImpl.this.handleEvent(msg); - } - }; - internalPlayer = new ExoPlayerImplInternal(eventHandler, playWhenReady, selectedTrackIndices, - minBufferMs, minRebufferMs); - } - - @Override - public Looper getPlaybackLooper() { - return internalPlayer.getPlaybackLooper(); - } - - @Override - public void addListener(Listener listener) { - listeners.add(listener); - } - - @Override - public void removeListener(Listener listener) { - listeners.remove(listener); - } - - @Override - public int getPlaybackState() { - return playbackState; - } - - @Override - public void prepare(TrackRenderer... renderers) { - Arrays.fill(trackFormats, null); - internalPlayer.prepare(renderers); - } - - @Override - public int getTrackCount(int rendererIndex) { - return trackFormats[rendererIndex] != null ? trackFormats[rendererIndex].length : 0; - } - - @Override - public MediaFormat getTrackFormat(int rendererIndex, int trackIndex) { - return trackFormats[rendererIndex][trackIndex]; - } - - @Override - public void setSelectedTrack(int rendererIndex, int trackIndex) { - if (selectedTrackIndices[rendererIndex] != trackIndex) { - selectedTrackIndices[rendererIndex] = trackIndex; - internalPlayer.setRendererSelectedTrack(rendererIndex, trackIndex); - } - } - - @Override - public int getSelectedTrack(int rendererIndex) { - return selectedTrackIndices[rendererIndex]; - } - - @Override - public void setPlayWhenReady(boolean playWhenReady) { - if (this.playWhenReady != playWhenReady) { - this.playWhenReady = playWhenReady; - pendingPlayWhenReadyAcks++; - internalPlayer.setPlayWhenReady(playWhenReady); - for (Listener listener : listeners) { - listener.onPlayerStateChanged(playWhenReady, playbackState); - } - } - } - - @Override - public boolean getPlayWhenReady() { - return playWhenReady; - } - - @Override - public boolean isPlayWhenReadyCommitted() { - return pendingPlayWhenReadyAcks == 0; - } - - @Override - public void seekTo(long positionMs) { - internalPlayer.seekTo(positionMs); - } - - @Override - public void stop() { - internalPlayer.stop(); - } - - @Override - public void release() { - internalPlayer.release(); - eventHandler.removeCallbacksAndMessages(null); - } - - @Override - public void sendMessage(ExoPlayerComponent target, int messageType, Object message) { - internalPlayer.sendMessage(target, messageType, message); - } - - @Override - public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) { - internalPlayer.blockingSendMessage(target, messageType, message); - } - - @Override - public long getDuration() { - return internalPlayer.getDuration(); - } - - @Override - public long getCurrentPosition() { - return internalPlayer.getCurrentPosition(); - } - - @Override - public long getBufferedPosition() { - return internalPlayer.getBufferedPosition(); - } - - @Override - public int getBufferedPercentage() { - long bufferedPosition = getBufferedPosition(); - long duration = getDuration(); - return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0 - : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); - } - - // Not private so it can be called from an inner class without going through a thunk method. - /* package */ void handleEvent(Message msg) { - switch (msg.what) { - case ExoPlayerImplInternal.MSG_PREPARED: { - System.arraycopy(msg.obj, 0, trackFormats, 0, trackFormats.length); - playbackState = msg.arg1; - for (Listener listener : listeners) { - listener.onPlayerStateChanged(playWhenReady, playbackState); - } - break; - } - case ExoPlayerImplInternal.MSG_STATE_CHANGED: { - playbackState = msg.arg1; - for (Listener listener : listeners) { - listener.onPlayerStateChanged(playWhenReady, playbackState); - } - break; - } - case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: { - pendingPlayWhenReadyAcks--; - if (pendingPlayWhenReadyAcks == 0) { - for (Listener listener : listeners) { - listener.onPlayWhenReadyCommitted(); - } - } - break; - } - case ExoPlayerImplInternal.MSG_ERROR: { - ExoPlaybackException exception = (ExoPlaybackException) msg.obj; - for (Listener listener : listeners) { - listener.onPlayerError(exception); - } - break; - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImplInternal.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImplInternal.java deleted file mode 100755 index 70aad06a8fe..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerImplInternal.java +++ /dev/null @@ -1,666 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.os.SystemClock; -import android.util.Log; -import android.util.Pair; -import org.telegram.messenger.exoplayer.ExoPlayer.ExoPlayerComponent; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.PriorityHandlerThread; -import org.telegram.messenger.exoplayer.util.TraceUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Implements the internal behavior of {@link ExoPlayerImpl}. - */ -/* package */ final class ExoPlayerImplInternal implements Handler.Callback { - - private static final String TAG = "ExoPlayerImplInternal"; - - // External messages - public static final int MSG_PREPARED = 1; - public static final int MSG_STATE_CHANGED = 2; - public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3; - public static final int MSG_ERROR = 4; - - // Internal messages - private static final int MSG_PREPARE = 1; - private static final int MSG_INCREMENTAL_PREPARE = 2; - private static final int MSG_SET_PLAY_WHEN_READY = 3; - private static final int MSG_STOP = 4; - private static final int MSG_RELEASE = 5; - private static final int MSG_SEEK_TO = 6; - private static final int MSG_DO_SOME_WORK = 7; - private static final int MSG_SET_RENDERER_SELECTED_TRACK = 8; - private static final int MSG_CUSTOM = 9; - - private static final int PREPARE_INTERVAL_MS = 10; - private static final int RENDERING_INTERVAL_MS = 10; - private static final int IDLE_INTERVAL_MS = 1000; - - private final Handler handler; - private final HandlerThread internalPlaybackThread; - private final Handler eventHandler; - private final StandaloneMediaClock standaloneMediaClock; - private final AtomicInteger pendingSeekCount; - private final List enabledRenderers; - private final MediaFormat[][] trackFormats; - private final int[] selectedTrackIndices; - private final long minBufferUs; - private final long minRebufferUs; - - private TrackRenderer[] renderers; - private TrackRenderer rendererMediaClockSource; - private MediaClock rendererMediaClock; - - private boolean released; - private boolean playWhenReady; - private boolean rebuffering; - private int state; - private int customMessagesSent = 0; - private int customMessagesProcessed = 0; - private long lastSeekPositionMs; - private long elapsedRealtimeUs; - - private volatile long durationUs; - private volatile long positionUs; - private volatile long bufferedPositionUs; - - public ExoPlayerImplInternal(Handler eventHandler, boolean playWhenReady, - int[] selectedTrackIndices, int minBufferMs, int minRebufferMs) { - this.eventHandler = eventHandler; - this.playWhenReady = playWhenReady; - this.minBufferUs = minBufferMs * 1000L; - this.minRebufferUs = minRebufferMs * 1000L; - this.selectedTrackIndices = Arrays.copyOf(selectedTrackIndices, selectedTrackIndices.length); - this.state = ExoPlayer.STATE_IDLE; - this.durationUs = TrackRenderer.UNKNOWN_TIME_US; - this.bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US; - - standaloneMediaClock = new StandaloneMediaClock(); - pendingSeekCount = new AtomicInteger(); - enabledRenderers = new ArrayList<>(selectedTrackIndices.length); - trackFormats = new MediaFormat[selectedTrackIndices.length][]; - // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can - // not normally change to this priority" is incorrect. - internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler", - Process.THREAD_PRIORITY_AUDIO); - internalPlaybackThread.start(); - handler = new Handler(internalPlaybackThread.getLooper(), this); - } - - public Looper getPlaybackLooper() { - return internalPlaybackThread.getLooper(); - } - - public long getCurrentPosition() { - return pendingSeekCount.get() > 0 ? lastSeekPositionMs : (positionUs / 1000); - } - - public long getBufferedPosition() { - return bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US ? ExoPlayer.UNKNOWN_TIME - : bufferedPositionUs / 1000; - } - - public long getDuration() { - return durationUs == TrackRenderer.UNKNOWN_TIME_US ? ExoPlayer.UNKNOWN_TIME - : durationUs / 1000; - } - - public void prepare(TrackRenderer... renderers) { - handler.obtainMessage(MSG_PREPARE, renderers).sendToTarget(); - } - - public void setPlayWhenReady(boolean playWhenReady) { - handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); - } - - public void seekTo(long positionMs) { - lastSeekPositionMs = positionMs; - pendingSeekCount.incrementAndGet(); - handler.obtainMessage(MSG_SEEK_TO, Util.getTopInt(positionMs), - Util.getBottomInt(positionMs)).sendToTarget(); - } - - public void stop() { - handler.sendEmptyMessage(MSG_STOP); - } - - public void setRendererSelectedTrack(int rendererIndex, int trackIndex) { - handler.obtainMessage(MSG_SET_RENDERER_SELECTED_TRACK, rendererIndex, trackIndex) - .sendToTarget(); - } - - public void sendMessage(ExoPlayerComponent target, int messageType, Object message) { - customMessagesSent++; - handler.obtainMessage(MSG_CUSTOM, messageType, 0, Pair.create(target, message)).sendToTarget(); - } - - public synchronized void blockingSendMessage(ExoPlayerComponent target, int messageType, - Object message) { - if (released) { - Log.w(TAG, "Sent message(" + messageType + ") after release. Message ignored."); - return; - } - int messageNumber = customMessagesSent++; - handler.obtainMessage(MSG_CUSTOM, messageType, 0, Pair.create(target, message)).sendToTarget(); - while (customMessagesProcessed <= messageNumber) { - try { - wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - } - - public synchronized void release() { - if (released) { - return; - } - handler.sendEmptyMessage(MSG_RELEASE); - while (!released) { - try { - wait(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - internalPlaybackThread.quit(); - } - - @Override - public boolean handleMessage(Message msg) { - try { - switch (msg.what) { - case MSG_PREPARE: { - prepareInternal((TrackRenderer[]) msg.obj); - return true; - } - case MSG_INCREMENTAL_PREPARE: { - incrementalPrepareInternal(); - return true; - } - case MSG_SET_PLAY_WHEN_READY: { - setPlayWhenReadyInternal(msg.arg1 != 0); - return true; - } - case MSG_DO_SOME_WORK: { - doSomeWork(); - return true; - } - case MSG_SEEK_TO: { - seekToInternal(Util.getLong(msg.arg1, msg.arg2)); - return true; - } - case MSG_STOP: { - stopInternal(); - return true; - } - case MSG_RELEASE: { - releaseInternal(); - return true; - } - case MSG_CUSTOM: { - sendMessageInternal(msg.arg1, msg.obj); - return true; - } - case MSG_SET_RENDERER_SELECTED_TRACK: { - setRendererSelectedTrackInternal(msg.arg1, msg.arg2); - return true; - } - default: - return false; - } - } catch (ExoPlaybackException e) { - Log.e(TAG, "Internal track renderer error.", e); - eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); - stopInternal(); - return true; - } catch (RuntimeException e) { - Log.e(TAG, "Internal runtime error.", e); - eventHandler.obtainMessage(MSG_ERROR, new ExoPlaybackException(e, true)).sendToTarget(); - stopInternal(); - return true; - } - } - - private void setState(int state) { - if (this.state != state) { - this.state = state; - eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget(); - } - } - - private void prepareInternal(TrackRenderer[] renderers) throws ExoPlaybackException { - resetInternal(); - this.renderers = renderers; - Arrays.fill(trackFormats, null); - setState(ExoPlayer.STATE_PREPARING); - incrementalPrepareInternal(); - } - - private void incrementalPrepareInternal() throws ExoPlaybackException { - long operationStartTimeMs = SystemClock.elapsedRealtime(); - boolean prepared = true; - for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) { - TrackRenderer renderer = renderers[rendererIndex]; - if (renderer.getState() == TrackRenderer.STATE_UNPREPARED) { - int state = renderer.prepare(positionUs); - if (state == TrackRenderer.STATE_UNPREPARED) { - renderer.maybeThrowError(); - prepared = false; - } - } - } - - if (!prepared) { - // We're still waiting for some sources to be prepared. - scheduleNextOperation(MSG_INCREMENTAL_PREPARE, operationStartTimeMs, PREPARE_INTERVAL_MS); - return; - } - - long durationUs = 0; - boolean allRenderersEnded = true; - boolean allRenderersReadyOrEnded = true; - for (int rendererIndex = 0; rendererIndex < renderers.length; rendererIndex++) { - TrackRenderer renderer = renderers[rendererIndex]; - int rendererTrackCount = renderer.getTrackCount(); - MediaFormat[] rendererTrackFormats = new MediaFormat[rendererTrackCount]; - for (int trackIndex = 0; trackIndex < rendererTrackCount; trackIndex++) { - rendererTrackFormats[trackIndex] = renderer.getFormat(trackIndex); - } - trackFormats[rendererIndex] = rendererTrackFormats; - if (rendererTrackCount > 0) { - if (durationUs == TrackRenderer.UNKNOWN_TIME_US) { - // We've already encountered a track for which the duration is unknown, so the media - // duration is unknown regardless of the duration of this track. - } else { - long trackDurationUs = renderer.getDurationUs(); - if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) { - durationUs = TrackRenderer.UNKNOWN_TIME_US; - } else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) { - // Do nothing. - } else { - durationUs = Math.max(durationUs, trackDurationUs); - } - } - int trackIndex = selectedTrackIndices[rendererIndex]; - if (0 <= trackIndex && trackIndex < rendererTrackFormats.length) { - enableRenderer(renderer, trackIndex, false); - allRenderersEnded = allRenderersEnded && renderer.isEnded(); - allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded(renderer); - } - } - } - this.durationUs = durationUs; - - if (allRenderersEnded - && (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) { - // We don't expect this case, but handle it anyway. - state = ExoPlayer.STATE_ENDED; - } else { - state = allRenderersReadyOrEnded ? ExoPlayer.STATE_READY : ExoPlayer.STATE_BUFFERING; - } - - // Fire an event indicating that the player has been prepared, passing the initial state and - // renderer track information. - eventHandler.obtainMessage(MSG_PREPARED, state, 0, trackFormats).sendToTarget(); - - // Start the renderers if required, and schedule the first piece of work. - if (playWhenReady && state == ExoPlayer.STATE_READY) { - startRenderers(); - } - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } - - private void enableRenderer(TrackRenderer renderer, int trackIndex, boolean joining) - throws ExoPlaybackException { - renderer.enable(trackIndex, positionUs, joining); - enabledRenderers.add(renderer); - MediaClock mediaClock = renderer.getMediaClock(); - if (mediaClock != null) { - Assertions.checkState(rendererMediaClock == null); - rendererMediaClock = mediaClock; - rendererMediaClockSource = renderer; - } - } - - private boolean rendererReadyOrEnded(TrackRenderer renderer) { - if (renderer.isEnded()) { - return true; - } - if (!renderer.isReady()) { - return false; - } - if (state == ExoPlayer.STATE_READY) { - return true; - } - long rendererDurationUs = renderer.getDurationUs(); - long rendererBufferedPositionUs = renderer.getBufferedPositionUs(); - long minBufferDurationUs = rebuffering ? minRebufferUs : minBufferUs; - return minBufferDurationUs <= 0 - || rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US - || rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US - || rendererBufferedPositionUs >= positionUs + minBufferDurationUs - || (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US - && rendererDurationUs != TrackRenderer.MATCH_LONGEST_US - && rendererBufferedPositionUs >= rendererDurationUs); - } - - private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException { - try { - rebuffering = false; - this.playWhenReady = playWhenReady; - if (!playWhenReady) { - stopRenderers(); - updatePositionUs(); - } else { - if (state == ExoPlayer.STATE_READY) { - startRenderers(); - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } else if (state == ExoPlayer.STATE_BUFFERING) { - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } - } - } finally { - eventHandler.obtainMessage(MSG_SET_PLAY_WHEN_READY_ACK).sendToTarget(); - } - } - - private void startRenderers() throws ExoPlaybackException { - rebuffering = false; - standaloneMediaClock.start(); - for (int i = 0; i < enabledRenderers.size(); i++) { - enabledRenderers.get(i).start(); - } - } - - private void stopRenderers() throws ExoPlaybackException { - standaloneMediaClock.stop(); - for (int i = 0; i < enabledRenderers.size(); i++) { - ensureStopped(enabledRenderers.get(i)); - } - } - - private void updatePositionUs() { - if (rendererMediaClock != null && enabledRenderers.contains(rendererMediaClockSource) - && !rendererMediaClockSource.isEnded()) { - positionUs = rendererMediaClock.getPositionUs(); - standaloneMediaClock.setPositionUs(positionUs); - } else { - positionUs = standaloneMediaClock.getPositionUs(); - } - elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; - } - - private void doSomeWork() throws ExoPlaybackException { - TraceUtil.beginSection("doSomeWork"); - long operationStartTimeMs = SystemClock.elapsedRealtime(); - long bufferedPositionUs = durationUs != TrackRenderer.UNKNOWN_TIME_US ? durationUs - : Long.MAX_VALUE; - boolean allRenderersEnded = true; - boolean allRenderersReadyOrEnded = true; - updatePositionUs(); - for (int i = 0; i < enabledRenderers.size(); i++) { - TrackRenderer renderer = enabledRenderers.get(i); - // TODO: Each renderer should return the maximum delay before which it wishes to be - // invoked again. The minimum of these values should then be used as the delay before the next - // invocation of this method. - renderer.doSomeWork(positionUs, elapsedRealtimeUs); - allRenderersEnded = allRenderersEnded && renderer.isEnded(); - - // Determine whether the renderer is ready (or ended). If it's not, throw an error that's - // preventing the renderer from making progress, if such an error exists. - boolean rendererReadyOrEnded = rendererReadyOrEnded(renderer); - if (!rendererReadyOrEnded) { - renderer.maybeThrowError(); - } - allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded; - - if (bufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) { - // We've already encountered a track for which the buffered position is unknown. Hence the - // media buffer position unknown regardless of the buffered position of this track. - } else { - long rendererDurationUs = renderer.getDurationUs(); - long rendererBufferedPositionUs = renderer.getBufferedPositionUs(); - if (rendererBufferedPositionUs == TrackRenderer.UNKNOWN_TIME_US) { - bufferedPositionUs = TrackRenderer.UNKNOWN_TIME_US; - } else if (rendererBufferedPositionUs == TrackRenderer.END_OF_TRACK_US - || (rendererDurationUs != TrackRenderer.UNKNOWN_TIME_US - && rendererDurationUs != TrackRenderer.MATCH_LONGEST_US - && rendererBufferedPositionUs >= rendererDurationUs)) { - // This track is fully buffered. - } else { - bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); - } - } - } - this.bufferedPositionUs = bufferedPositionUs; - - if (allRenderersEnded - && (durationUs == TrackRenderer.UNKNOWN_TIME_US || durationUs <= positionUs)) { - setState(ExoPlayer.STATE_ENDED); - stopRenderers(); - } else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded) { - setState(ExoPlayer.STATE_READY); - if (playWhenReady) { - startRenderers(); - } - } else if (state == ExoPlayer.STATE_READY && !allRenderersReadyOrEnded) { - rebuffering = playWhenReady; - setState(ExoPlayer.STATE_BUFFERING); - stopRenderers(); - } - - handler.removeMessages(MSG_DO_SOME_WORK); - if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) { - scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, RENDERING_INTERVAL_MS); - } else if (!enabledRenderers.isEmpty()) { - scheduleNextOperation(MSG_DO_SOME_WORK, operationStartTimeMs, IDLE_INTERVAL_MS); - } - - TraceUtil.endSection(); - } - - private void scheduleNextOperation(int operationType, long thisOperationStartTimeMs, - long intervalMs) { - long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs; - long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime(); - if (nextOperationDelayMs <= 0) { - handler.sendEmptyMessage(operationType); - } else { - handler.sendEmptyMessageDelayed(operationType, nextOperationDelayMs); - } - } - - private void seekToInternal(long positionMs) throws ExoPlaybackException { - try { - if (positionMs == (positionUs / 1000)) { - // Seek is to the current position. Do nothing. - return; - } - - rebuffering = false; - positionUs = positionMs * 1000; - standaloneMediaClock.stop(); - standaloneMediaClock.setPositionUs(positionUs); - if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) { - return; - } - for (int i = 0; i < enabledRenderers.size(); i++) { - TrackRenderer renderer = enabledRenderers.get(i); - ensureStopped(renderer); - renderer.seekTo(positionUs); - } - setState(ExoPlayer.STATE_BUFFERING); - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } finally { - pendingSeekCount.decrementAndGet(); - } - } - - private void stopInternal() { - resetInternal(); - setState(ExoPlayer.STATE_IDLE); - } - - private void releaseInternal() { - resetInternal(); - setState(ExoPlayer.STATE_IDLE); - synchronized (this) { - released = true; - notifyAll(); - } - } - - private void resetInternal() { - handler.removeMessages(MSG_DO_SOME_WORK); - handler.removeMessages(MSG_INCREMENTAL_PREPARE); - rebuffering = false; - standaloneMediaClock.stop(); - if (renderers == null) { - return; - } - for (int i = 0; i < renderers.length; i++) { - TrackRenderer renderer = renderers[i]; - stopAndDisable(renderer); - release(renderer); - } - renderers = null; - rendererMediaClock = null; - rendererMediaClockSource = null; - enabledRenderers.clear(); - } - - private void stopAndDisable(TrackRenderer renderer) { - try { - ensureDisabled(renderer); - } catch (ExoPlaybackException e) { - // There's nothing we can do. - Log.e(TAG, "Stop failed.", e); - } catch (RuntimeException e) { - // Ditto. - Log.e(TAG, "Stop failed.", e); - } - } - - private void release(TrackRenderer renderer) { - try { - renderer.release(); - } catch (ExoPlaybackException e) { - // There's nothing we can do. - Log.e(TAG, "Release failed.", e); - } catch (RuntimeException e) { - // Ditto. - Log.e(TAG, "Release failed.", e); - } - } - - private void sendMessageInternal(int what, Object obj) - throws ExoPlaybackException { - try { - @SuppressWarnings("unchecked") - Pair targetAndMessage = (Pair) obj; - targetAndMessage.first.handleMessage(what, targetAndMessage.second); - if (state != ExoPlayer.STATE_IDLE && state != ExoPlayer.STATE_PREPARING) { - // The message may have caused something to change that now requires us to do work. - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } - } finally { - synchronized (this) { - customMessagesProcessed++; - notifyAll(); - } - } - } - - private void setRendererSelectedTrackInternal(int rendererIndex, int trackIndex) - throws ExoPlaybackException { - if (selectedTrackIndices[rendererIndex] == trackIndex) { - return; - } - - selectedTrackIndices[rendererIndex] = trackIndex; - if (state == ExoPlayer.STATE_IDLE || state == ExoPlayer.STATE_PREPARING) { - return; - } - - TrackRenderer renderer = renderers[rendererIndex]; - int rendererState = renderer.getState(); - if (rendererState == TrackRenderer.STATE_UNPREPARED - || rendererState == TrackRenderer.STATE_RELEASED - || renderer.getTrackCount() == 0) { - return; - } - - boolean isEnabled = rendererState == TrackRenderer.STATE_ENABLED - || rendererState == TrackRenderer.STATE_STARTED; - boolean shouldEnable = 0 <= trackIndex && trackIndex < trackFormats[rendererIndex].length; - - if (isEnabled) { - // The renderer is currently enabled. We need to disable it, so that we can either re-enable - // it with the newly selected track (if shouldEnable is true) or because we want to leave it - // disabled (if shouldEnable is false). - if (!shouldEnable && renderer == rendererMediaClockSource) { - // We've been using rendererMediaClockSource to advance the current position, but it's being - // disabled and won't be re-enabled. Sync standaloneMediaClock so that it can take over - // timing responsibilities. - standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); - } - ensureDisabled(renderer); - enabledRenderers.remove(renderer); - } - - if (shouldEnable) { - // Re-enable the renderer with the newly selected track. - boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; - // Consider as joining if the renderer was previously disabled, but not when switching tracks. - boolean joining = !isEnabled && playing; - enableRenderer(renderer, trackIndex, joining); - if (playing) { - renderer.start(); - } - handler.sendEmptyMessage(MSG_DO_SOME_WORK); - } - } - - private void ensureStopped(TrackRenderer renderer) throws ExoPlaybackException { - if (renderer.getState() == TrackRenderer.STATE_STARTED) { - renderer.stop(); - } - } - - private void ensureDisabled(TrackRenderer renderer) throws ExoPlaybackException { - ensureStopped(renderer); - if (renderer.getState() == TrackRenderer.STATE_ENABLED) { - renderer.disable(); - if (renderer == rendererMediaClockSource) { - rendererMediaClock = null; - rendererMediaClockSource = null; - } - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerLibraryInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerLibraryInfo.java deleted file mode 100755 index be410291454..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ExoPlayerLibraryInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -/** - * Information about the ExoPlayer library. - */ -public final class ExoPlayerLibraryInfo { - - /** - * The version of the library, expressed as a string. - */ - public static final String VERSION = "1.5.10"; - - /** - * The version of the library, expressed as an integer. - *

        - * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the - * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding - * integer version 123045006 (123-045-006). - */ - public static final int VERSION_INT = 1005010; - - /** - * Whether the library was compiled with {@link org.telegram.messenger.exoplayer.util.Assertions} - * checks enabled. - */ - public static final boolean ASSERTIONS_ENABLED = true; - - /** - * Whether the library was compiled with {@link org.telegram.messenger.exoplayer.util.TraceUtil} - * trace enabled. - */ - public static final boolean TRACE_ENABLED = true; - - private ExoPlayerLibraryInfo() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/FrameworkSampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/FrameworkSampleSource.java deleted file mode 100755 index 7a5ffc8e199..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/FrameworkSampleSource.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.media.MediaExtractor; -import android.net.Uri; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.extractor.ExtractorSampleSource; -import org.telegram.messenger.exoplayer.extractor.mp4.PsshAtomUtil; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.FileDescriptor; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Map; -import java.util.UUID; - -/** - * Extracts samples from a stream using Android's {@link MediaExtractor}. - *

        - * Warning - This class is marked as deprecated because there are known device specific issues - * associated with its use, including playbacks not starting, playbacks stuttering and other - * miscellaneous failures. For mp4, m4a, mp3, webm, mkv, mpeg-ts, ogg, wav and aac playbacks it is - * strongly recommended to use {@link ExtractorSampleSource} instead. Where this is not possible - * this class can still be used, but please be aware of the associated risks. Playing container - * formats for which an ExoPlayer extractor does not yet exist (e.g. avi) is a valid use case of - * this class. - *

        - * Over time we hope to enhance {@link ExtractorSampleSource} to support more formats, and hence - * make use of this class unnecessary. - */ -// TODO: This implementation needs to be fixed so that its methods are non-blocking (either -// through use of a background thread, or through changes to the framework's MediaExtractor API). -@Deprecated -@TargetApi(16) -public final class FrameworkSampleSource implements SampleSource, SampleSourceReader { - - private static final int ALLOWED_FLAGS_MASK = C.SAMPLE_FLAG_SYNC | C.SAMPLE_FLAG_ENCRYPTED; - - private static final int TRACK_STATE_DISABLED = 0; - private static final int TRACK_STATE_ENABLED = 1; - private static final int TRACK_STATE_FORMAT_SENT = 2; - - // Parameters for a Uri data source. - private final Context context; - private final Uri uri; - private final Map headers; - - // Parameters for a FileDescriptor data source. - private final FileDescriptor fileDescriptor; - private final long fileDescriptorOffset; - private final long fileDescriptorLength; - - private IOException preparationError; - private MediaExtractor extractor; - private MediaFormat[] trackFormats; - private boolean prepared; - private int remainingReleaseCount; - private int[] trackStates; - private boolean[] pendingDiscontinuities; - - private long lastSeekPositionUs; - private long pendingSeekPositionUs; - - /** - * Instantiates a new sample extractor reading from the specified {@code uri}. - * - * @param context Context for resolving {@code uri}. - * @param uri The content URI from which to extract data. - * @param headers Headers to send with requests for data. - */ - public FrameworkSampleSource(Context context, Uri uri, Map headers) { - Assertions.checkState(Util.SDK_INT >= 16); - this.context = Assertions.checkNotNull(context); - this.uri = Assertions.checkNotNull(uri); - this.headers = headers; - fileDescriptor = null; - fileDescriptorOffset = 0; - fileDescriptorLength = 0; - } - - /** - * Instantiates a new sample extractor reading from the specified seekable {@code fileDescriptor}. - * The caller is responsible for releasing the file descriptor. - * - * @param fileDescriptor File descriptor from which to read. - * @param fileDescriptorOffset The offset in bytes where the data to be extracted starts. - * @param fileDescriptorLength The length in bytes of the data to be extracted. - */ - public FrameworkSampleSource(FileDescriptor fileDescriptor, long fileDescriptorOffset, - long fileDescriptorLength) { - Assertions.checkState(Util.SDK_INT >= 16); - this.fileDescriptor = Assertions.checkNotNull(fileDescriptor); - this.fileDescriptorOffset = fileDescriptorOffset; - this.fileDescriptorLength = fileDescriptorLength; - context = null; - uri = null; - headers = null; - } - - @Override - public SampleSourceReader register() { - remainingReleaseCount++; - return this; - } - - @Override - public boolean prepare(long positionUs) { - if (!prepared) { - if (preparationError != null) { - return false; - } - - extractor = new MediaExtractor(); - try { - if (context != null) { - extractor.setDataSource(context, uri, headers); - } else { - extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength); - } - } catch (IOException e) { - preparationError = e; - return false; - } - - trackStates = new int[extractor.getTrackCount()]; - pendingDiscontinuities = new boolean[trackStates.length]; - trackFormats = new MediaFormat[trackStates.length]; - for (int i = 0; i < trackStates.length; i++) { - trackFormats[i] = createMediaFormat(extractor.getTrackFormat(i)); - } - prepared = true; - } - return true; - } - - @Override - public int getTrackCount() { - Assertions.checkState(prepared); - return trackStates.length; - } - - @Override - public MediaFormat getFormat(int track) { - Assertions.checkState(prepared); - return trackFormats[track]; - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] == TRACK_STATE_DISABLED); - trackStates[track] = TRACK_STATE_ENABLED; - extractor.selectTrack(track); - seekToUsInternal(positionUs, positionUs != 0); - } - - @Override - public boolean continueBuffering(int track, long positionUs) { - // MediaExtractor takes care of buffering and blocks until it has samples, so we can always - // return true here. Although note that the blocking behavior is itself as bug, as per the - // TODO further up this file. This method will need to return something else as part of fixing - // the TODO. - return true; - } - - @Override - public long readDiscontinuity(int track) { - if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return lastSeekPositionUs; - } - return NO_DISCONTINUITY; - } - - @Override - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); - if (pendingDiscontinuities[track]) { - return NOTHING_READ; - } - if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { - formatHolder.format = trackFormats[track]; - formatHolder.drmInitData = Util.SDK_INT >= 18 ? getDrmInitDataV18() : null; - trackStates[track] = TRACK_STATE_FORMAT_SENT; - return FORMAT_READ; - } - int extractorTrackIndex = extractor.getSampleTrackIndex(); - if (extractorTrackIndex == track) { - if (sampleHolder.data != null) { - int offset = sampleHolder.data.position(); - sampleHolder.size = extractor.readSampleData(sampleHolder.data, offset); - sampleHolder.data.position(offset + sampleHolder.size); - } else { - sampleHolder.size = 0; - } - sampleHolder.timeUs = extractor.getSampleTime(); - sampleHolder.flags = extractor.getSampleFlags() & ALLOWED_FLAGS_MASK; - if (sampleHolder.isEncrypted()) { - sampleHolder.cryptoInfo.setFromExtractorV16(extractor); - } - pendingSeekPositionUs = C.UNKNOWN_TIME_US; - extractor.advance(); - return SAMPLE_READ; - } else { - return extractorTrackIndex < 0 ? END_OF_STREAM : NOTHING_READ; - } - } - - @Override - public void disable(int track) { - Assertions.checkState(prepared); - Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); - extractor.unselectTrack(track); - pendingDiscontinuities[track] = false; - trackStates[track] = TRACK_STATE_DISABLED; - } - - @Override - public void maybeThrowError() throws IOException { - if (preparationError != null) { - throw preparationError; - } - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(prepared); - seekToUsInternal(positionUs, false); - } - - @Override - public long getBufferedPositionUs() { - Assertions.checkState(prepared); - long bufferedDurationUs = extractor.getCachedDuration(); - if (bufferedDurationUs == -1) { - return TrackRenderer.UNKNOWN_TIME_US; - } else { - long sampleTime = extractor.getSampleTime(); - return sampleTime == -1 ? TrackRenderer.END_OF_TRACK_US : sampleTime + bufferedDurationUs; - } - } - - @Override - public void release() { - Assertions.checkState(remainingReleaseCount > 0); - if (--remainingReleaseCount == 0 && extractor != null) { - extractor.release(); - extractor = null; - } - } - - @TargetApi(18) - private DrmInitData getDrmInitDataV18() { - // MediaExtractor only supports psshInfo for MP4, so it's ok to hard code the mimeType here. - Map psshInfo = extractor.getPsshInfo(); - if (psshInfo == null || psshInfo.isEmpty()) { - return null; - } - DrmInitData.Mapped drmInitData = new DrmInitData.Mapped(); - for (UUID uuid : psshInfo.keySet()) { - byte[] psshAtom = PsshAtomUtil.buildPsshAtom(uuid, psshInfo.get(uuid)); - drmInitData.put(uuid, new SchemeInitData(MimeTypes.VIDEO_MP4, psshAtom)); - } - return drmInitData; - } - - private void seekToUsInternal(long positionUs, boolean force) { - // Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case - // that there have been no interleaving calls to readSample. - if (force || pendingSeekPositionUs != positionUs) { - lastSeekPositionUs = positionUs; - pendingSeekPositionUs = positionUs; - extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); - for (int i = 0; i < trackStates.length; ++i) { - if (trackStates[i] != TRACK_STATE_DISABLED) { - pendingDiscontinuities[i] = true; - } - } - } - } - - @SuppressLint("InlinedApi") - private static MediaFormat createMediaFormat(android.media.MediaFormat format) { - String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); - String language = getOptionalStringV16(format, android.media.MediaFormat.KEY_LANGUAGE); - int maxInputSize = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE); - int width = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_WIDTH); - int height = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT); - int rotationDegrees = getOptionalIntegerV16(format, "rotation-degrees"); - int channelCount = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT); - int sampleRate = getOptionalIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE); - int encoderDelay = getOptionalIntegerV16(format, "encoder-delay"); - int encoderPadding = getOptionalIntegerV16(format, "encoder-padding"); - ArrayList initializationData = new ArrayList<>(); - for (int i = 0; format.containsKey("csd-" + i); i++) { - ByteBuffer buffer = format.getByteBuffer("csd-" + i); - byte[] data = new byte[buffer.limit()]; - buffer.get(data); - initializationData.add(data); - buffer.flip(); - } - long durationUs = format.containsKey(android.media.MediaFormat.KEY_DURATION) - ? format.getLong(android.media.MediaFormat.KEY_DURATION) : C.UNKNOWN_TIME_US; - int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT - : MediaFormat.NO_VALUE; - MediaFormat mediaFormat = new MediaFormat(null, mimeType, MediaFormat.NO_VALUE, maxInputSize, - durationUs, width, height, rotationDegrees, MediaFormat.NO_VALUE, channelCount, sampleRate, - language, MediaFormat.OFFSET_SAMPLE_RELATIVE, initializationData, false, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, pcmEncoding, encoderDelay, encoderPadding); - mediaFormat.setFrameworkFormatV16(format); - return mediaFormat; - } - - @TargetApi(16) - private static final String getOptionalStringV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getString(key) : null; - } - - @TargetApi(16) - private static final int getOptionalIntegerV16(android.media.MediaFormat format, String key) { - return format.containsKey(key) ? format.getInteger(key) : MediaFormat.NO_VALUE; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/LoadControl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/LoadControl.java deleted file mode 100755 index 292aa75390a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/LoadControl.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import org.telegram.messenger.exoplayer.upstream.Allocator; - -/** - * Coordinates multiple loaders of time series data. - */ -public interface LoadControl { - - /** - * Registers a loader. - * - * @param loader The loader being registered. - * @param bufferSizeContribution For instances whose {@link Allocator} maintains a pool of memory - * for the purpose of satisfying allocation requests, this is a hint indicating the loader's - * desired contribution to the size of the pool, in bytes. - */ - void register(Object loader, int bufferSizeContribution); - - /** - * Unregisters a loader. - * - * @param loader The loader being unregistered. - */ - void unregister(Object loader); - - /** - * Gets the {@link Allocator} that loaders should use to obtain memory allocations into which - * data can be loaded. - * - * @return The {@link Allocator} to use. - */ - Allocator getAllocator(); - - /** - * Hints to the control that it should consider trimming any unused memory being held in order - * to satisfy allocation requests. - *

        - * This method is typically invoked by a recently unregistered loader, once it has released all - * of its allocations back to the {@link Allocator}. - */ - void trimAllocator(); - - /** - * Invoked by a loader to update the control with its current state. - *

        - * This method must be called by a registered loader whenever its state changes. This is true - * even if the registered loader does not itself wish to start its next load (since the state of - * the loader will still affect whether other registered loaders are allowed to proceed). - * - * @param loader The loader invoking the update. - * @param playbackPositionUs The loader's playback position. - * @param nextLoadPositionUs The loader's next load position. -1 if finished, failed, or if the - * next load position is not yet known. - * @param loading Whether the loader is currently loading data. - * @return True if the loader is allowed to start its next load. False otherwise. - */ - boolean update(Object loader, long playbackPositionUs, long nextLoadPositionUs, boolean loading); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecAudioTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecAudioTrackRenderer.java deleted file mode 100755 index ec21fd37ab5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecAudioTrackRenderer.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.TargetApi; -import android.media.AudioManager; -import android.media.MediaCodec; -import android.media.PlaybackParams; -import android.media.audiofx.Virtualizer; -import android.os.Handler; -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; -import org.telegram.messenger.exoplayer.audio.AudioCapabilities; -import org.telegram.messenger.exoplayer.audio.AudioTrack; -import org.telegram.messenger.exoplayer.drm.DrmSessionManager; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.nio.ByteBuffer; - -/** - * Decodes and renders audio using {@link MediaCodec} and {@link android.media.AudioTrack}. - */ -@TargetApi(16) -public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implements MediaClock { - - /** - * Interface definition for a callback to be notified of {@link MediaCodecAudioTrackRenderer} - * events. - */ - public interface EventListener extends MediaCodecTrackRenderer.EventListener { - - /** - * Invoked when an {@link AudioTrack} fails to initialize. - * - * @param e The corresponding exception. - */ - void onAudioTrackInitializationError(AudioTrack.InitializationException e); - - /** - * Invoked when an {@link AudioTrack} write fails. - * - * @param e The corresponding exception. - */ - void onAudioTrackWriteError(AudioTrack.WriteException e); - - /** - * Invoked when an {@link AudioTrack} underrun occurs. - * - * @param bufferSize The size of the {@link AudioTrack}'s buffer, in bytes. - * @param bufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds, if it is - * configured for PCM output. -1 if it is configured for passthrough output, as the buffered - * media can have a variable bitrate so the duration may be unknown. - * @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data. - */ - void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); - - } - - /** - * The type of a message that can be passed to an instance of this class via - * {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object - * should be a {@link Float} with 0 being silence and 1 being unity gain. - */ - public static final int MSG_SET_VOLUME = 1; - - /** - * The type of a message that can be passed to an instance of this class via - * {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object - * should be a {@link android.media.PlaybackParams}, which will be used to configure the - * underlying {@link android.media.AudioTrack}. The message object should not be modified by the - * caller after it has been passed - */ - public static final int MSG_SET_PLAYBACK_PARAMS = 2; - - private final EventListener eventListener; - private final AudioTrack audioTrack; - - private boolean passthroughEnabled; - private android.media.MediaFormat passthroughMediaFormat; - private int pcmEncoding; - private int audioSessionId; - private long currentPositionUs; - private boolean allowPositionDiscontinuity; - - private boolean audioTrackHasData; - private long lastFeedElapsedRealtimeMs; - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector) { - this(source, mediaCodecSelector, null, true); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) { - this(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - Handler eventHandler, EventListener eventListener) { - this(source, mediaCodecSelector, null, true, eventHandler, eventListener); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, - Handler eventHandler, EventListener eventListener) { - this(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, - eventListener, null, AudioManager.STREAM_MUSIC); - } - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param audioCapabilities The audio capabilities for playback on this device. May be null if the - * default capabilities (no encoded audio passthrough support) should be assumed. - * @param streamType The type of audio stream for the {@link AudioTrack}. - */ - public MediaCodecAudioTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, - Handler eventHandler, EventListener eventListener, AudioCapabilities audioCapabilities, - int streamType) { - this (new SampleSource[] {source}, mediaCodecSelector, drmSessionManager, - playClearSamplesWithoutKeys, eventHandler, eventListener, audioCapabilities, streamType); - } - - /** - * @param sources The upstream sources from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param audioCapabilities The audio capabilities for playback on this device. May be null if the - * default capabilities (no encoded audio passthrough support) should be assumed. - * @param streamType The type of audio stream for the {@link AudioTrack}. - */ - public MediaCodecAudioTrackRenderer(SampleSource[] sources, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys, - Handler eventHandler, EventListener eventListener, AudioCapabilities audioCapabilities, - int streamType) { - super(sources, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, - eventListener); - this.eventListener = eventListener; - this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET; - this.audioTrack = new AudioTrack(audioCapabilities, streamType); - } - - @Override - protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat) - throws DecoderQueryException { - String mimeType = mediaFormat.mimeType; - return MimeTypes.isAudio(mimeType) && (MimeTypes.AUDIO_UNKNOWN.equals(mimeType) - || (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) - || mediaCodecSelector.getDecoderInfo(mimeType, false) != null); - } - - @Override - protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, String mimeType, - boolean requiresSecureDecoder) throws DecoderQueryException { - if (allowPassthrough(mimeType)) { - DecoderInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); - if (passthroughDecoderInfo != null) { - passthroughEnabled = true; - return passthroughDecoderInfo; - } - } - passthroughEnabled = false; - return super.getDecoderInfo(mediaCodecSelector, mimeType, requiresSecureDecoder); - } - - /** - * Returns whether encoded audio passthrough should be used for playing back the input format. - * This implementation returns true if the {@link AudioTrack}'s audio capabilities indicate that - * passthrough is supported. - * - * @param mimeType The type of input media. - * @return True if passthrough playback should be used. False otherwise. - */ - protected boolean allowPassthrough(String mimeType) { - return audioTrack.isPassthroughSupported(mimeType); - } - - @Override - protected void configureCodec(MediaCodec codec, boolean codecIsAdaptive, - android.media.MediaFormat format, android.media.MediaCrypto crypto) { - String mimeType = format.getString(android.media.MediaFormat.KEY_MIME); - if (passthroughEnabled) { - // Override the MIME type used to configure the codec if we are using a passthrough decoder. - format.setString(android.media.MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); - codec.configure(format, null, crypto, 0); - format.setString(android.media.MediaFormat.KEY_MIME, mimeType); - passthroughMediaFormat = format; - } else { - codec.configure(format, null, crypto, 0); - passthroughMediaFormat = null; - } - } - - @Override - protected MediaClock getMediaClock() { - return this; - } - - @Override - protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { - super.onInputFormatChanged(holder); - // If the input format is anything other than PCM then we assume that the audio decoder will - // output 16-bit PCM. - pcmEncoding = MimeTypes.AUDIO_RAW.equals(holder.format.mimeType) ? holder.format.pcmEncoding - : C.ENCODING_PCM_16BIT; - } - - @Override - protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { - boolean passthrough = passthroughMediaFormat != null; - String mimeType = passthrough - ? passthroughMediaFormat.getString(android.media.MediaFormat.KEY_MIME) - : MimeTypes.AUDIO_RAW; - android.media.MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; - int channelCount = format.getInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT); - int sampleRate = format.getInteger(android.media.MediaFormat.KEY_SAMPLE_RATE); - audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding); - } - - /** - * Invoked when the audio session id becomes known. Once the id is known it will not change - * (and hence this method will not be invoked again) unless the renderer is disabled and then - * subsequently re-enabled. - *

        - * The default implementation is a no-op. One reason for overriding this method would be to - * instantiate and enable a {@link Virtualizer} in order to spatialize the audio channels. For - * this use case, any {@link Virtualizer} instances should be released in {@link #onDisabled()} - * (if not before). - * - * @param audioSessionId The audio session id. - */ - protected void onAudioSessionId(int audioSessionId) { - // Do nothing. - } - - @Override - protected void onStarted() { - super.onStarted(); - audioTrack.play(); - } - - @Override - protected void onStopped() { - audioTrack.pause(); - super.onStopped(); - } - - @Override - protected boolean isEnded() { - return super.isEnded() && !audioTrack.hasPendingData(); - } - - @Override - protected boolean isReady() { - return audioTrack.hasPendingData() || super.isReady(); - } - - @Override - public long getPositionUs() { - long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded()); - if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) { - currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs - : Math.max(currentPositionUs, newCurrentPositionUs); - allowPositionDiscontinuity = false; - } - return currentPositionUs; - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - audioSessionId = AudioTrack.SESSION_ID_NOT_SET; - try { - audioTrack.release(); - } finally { - super.onDisabled(); - } - } - - @Override - protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { - super.onDiscontinuity(positionUs); - audioTrack.reset(); - currentPositionUs = positionUs; - allowPositionDiscontinuity = true; - } - - @Override - protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, - ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) - throws ExoPlaybackException { - if (passthroughEnabled && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { - // Discard output buffers from the passthrough (raw) decoder containing codec specific data. - codec.releaseOutputBuffer(bufferIndex, false); - return true; - } - - if (shouldSkip) { - codec.releaseOutputBuffer(bufferIndex, false); - codecCounters.skippedOutputBufferCount++; - audioTrack.handleDiscontinuity(); - return true; - } - - if (!audioTrack.isInitialized()) { - // Initialize the AudioTrack now. - try { - if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) { - audioTrack.initialize(audioSessionId); - } else { - audioSessionId = audioTrack.initialize(); - onAudioSessionId(audioSessionId); - } - audioTrackHasData = false; - } catch (AudioTrack.InitializationException e) { - notifyAudioTrackInitializationError(e); - throw new ExoPlaybackException(e); - } - if (getState() == TrackRenderer.STATE_STARTED) { - audioTrack.play(); - } - } else { - // Check for AudioTrack underrun. - boolean audioTrackHadData = audioTrackHasData; - audioTrackHasData = audioTrack.hasPendingData(); - if (audioTrackHadData && !audioTrackHasData && getState() == TrackRenderer.STATE_STARTED) { - long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; - long bufferSizeUs = audioTrack.getBufferSizeUs(); - long bufferSizeMs = bufferSizeUs == C.UNKNOWN_TIME_US ? -1 : bufferSizeUs / 1000; - notifyAudioTrackUnderrun(audioTrack.getBufferSize(), bufferSizeMs, elapsedSinceLastFeedMs); - } - } - - int handleBufferResult; - try { - handleBufferResult = audioTrack.handleBuffer( - buffer, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs); - lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime(); - } catch (AudioTrack.WriteException e) { - notifyAudioTrackWriteError(e); - throw new ExoPlaybackException(e); - } - - // If we are out of sync, allow currentPositionUs to jump backwards. - if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { - handleAudioTrackDiscontinuity(); - allowPositionDiscontinuity = true; - } - - // Release the buffer if it was consumed. - if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { - codec.releaseOutputBuffer(bufferIndex, false); - codecCounters.renderedOutputBufferCount++; - return true; - } - - return false; - } - - @Override - protected void onOutputStreamEnded() { - audioTrack.handleEndOfStream(); - } - - protected void handleAudioTrackDiscontinuity() { - // Do nothing - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - switch (messageType) { - case MSG_SET_VOLUME: - audioTrack.setVolume((Float) message); - break; - case MSG_SET_PLAYBACK_PARAMS: - audioTrack.setPlaybackParams((PlaybackParams) message); - break; - default: - super.handleMessage(messageType, message); - break; - } - } - - private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onAudioTrackInitializationError(e); - } - }); - } - } - - private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onAudioTrackWriteError(e); - } - }); - } - } - - private void notifyAudioTrackUnderrun(final int bufferSize, final long bufferSizeMs, - final long elapsedSinceLastFeedMs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); - } - }); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecSelector.java deleted file mode 100755 index 9cc78a55878..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecSelector.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.media.MediaCodec; -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; - -/** - * Selector of {@link MediaCodec} instances. - */ -public interface MediaCodecSelector { - - /** - * Default implementation of {@link MediaCodecSelector}. - */ - MediaCodecSelector DEFAULT = new MediaCodecSelector() { - - @Override - public DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) - throws DecoderQueryException { - return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder); - } - - @Override - public DecoderInfo getPassthroughDecoderInfo() throws DecoderQueryException { - return MediaCodecUtil.getPassthroughDecoderInfo(); - } - - }; - - /** - * Selects a decoder to instantiate for a given mime type. - * - * @param mimeType The mime type for which a decoder is required. - * @param requiresSecureDecoder Whether a secure decoder is required. - * @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - DecoderInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) - throws DecoderQueryException; - - /** - * Selects a decoder to instantiate for audio passthrough. - * - * @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - DecoderInfo getPassthroughDecoderInfo() throws DecoderQueryException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecTrackRenderer.java deleted file mode 100755 index 94aa163f1f9..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecTrackRenderer.java +++ /dev/null @@ -1,1178 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.TargetApi; -import android.media.MediaCodec; -import android.media.MediaCodec.CodecException; -import android.media.MediaCodec.CryptoException; -import android.media.MediaCrypto; -import android.os.Handler; -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.drm.DrmSessionManager; -import org.telegram.messenger.exoplayer.drm.FrameworkMediaCrypto; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.TraceUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * An abstract {@link TrackRenderer} that uses {@link MediaCodec} to decode samples for rendering. - */ -@TargetApi(16) -public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer { - - /** - * Interface definition for a callback to be notified of {@link MediaCodecTrackRenderer} events. - */ - public interface EventListener { - - /** - * Invoked when a decoder fails to initialize. - * - * @param e The corresponding exception. - */ - void onDecoderInitializationError(DecoderInitializationException e); - - /** - * Invoked when a decoder operation raises a {@link CryptoException}. - * - * @param e The corresponding exception. - */ - void onCryptoError(CryptoException e); - - /** - * Invoked when a decoder is successfully created. - * - * @param decoderName The decoder that was configured and created. - * @param elapsedRealtimeMs {@code elapsedRealtime} timestamp of when the initialization - * finished. - * @param initializationDurationMs Amount of time taken to initialize the decoder. - */ - void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, - long initializationDurationMs); - - } - - /** - * Thrown when a failure occurs instantiating a decoder. - */ - public static class DecoderInitializationException extends Exception { - - private static final int CUSTOM_ERROR_CODE_BASE = -50000; - private static final int NO_SUITABLE_DECODER_ERROR = CUSTOM_ERROR_CODE_BASE + 1; - private static final int DECODER_QUERY_ERROR = CUSTOM_ERROR_CODE_BASE + 2; - - /** - * The mime type for which a decoder was being initialized. - */ - public final String mimeType; - - /** - * Whether it was required that the decoder support a secure output path. - */ - public final boolean secureDecoderRequired; - - /** - * The name of the decoder that failed to initialize. Null if no suitable decoder was found. - */ - public final String decoderName; - - /** - * An optional developer-readable diagnostic information string. May be null. - */ - public final String diagnosticInfo; - - public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, - boolean secureDecoderRequired, int errorCode) { - super("Decoder init failed: [" + errorCode + "], " + mediaFormat, cause); - this.mimeType = mediaFormat.mimeType; - this.secureDecoderRequired = secureDecoderRequired; - this.decoderName = null; - this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode); - } - - public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, - boolean secureDecoderRequired, String decoderName) { - super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause); - this.mimeType = mediaFormat.mimeType; - this.secureDecoderRequired = secureDecoderRequired; - this.decoderName = decoderName; - this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; - } - - @TargetApi(21) - private static String getDiagnosticInfoV21(Throwable cause) { - if (cause instanceof CodecException) { - return ((CodecException) cause).getDiagnosticInfo(); - } - return null; - } - - private static String buildCustomDiagnosticInfo(int errorCode) { - String sign = errorCode < 0 ? "neg_" : ""; - return "org.telegram.messenger.exoplayer.MediaCodecTrackRenderer_" + sign + Math.abs(errorCode); - } - - } - - /** - * Value returned by {@link #getSourceState()} when the source is not ready. - */ - protected static final int SOURCE_STATE_NOT_READY = 0; - /** - * Value returned by {@link #getSourceState()} when the source is ready and we're able to read - * from it. - */ - protected static final int SOURCE_STATE_READY = 1; - /** - * Value returned by {@link #getSourceState()} when the source is ready but we might not be able - * to read from it. We transition to this state when an attempt to read a sample fails despite the - * source reporting that samples are available. This can occur when the next sample to be provided - * by the source is for another renderer. - */ - protected static final int SOURCE_STATE_READY_READ_MAY_FAIL = 2; - - /** - * If the {@link MediaCodec} is hotswapped (i.e. replaced during playback), this is the period of - * time during which {@link #isReady()} will report true regardless of whether the new codec has - * output frames that are ready to be rendered. - *

        - * This allows codec hotswapping to be performed seamlessly, without interrupting the playback of - * other renderers, provided the new codec is able to decode some frames within this time period. - */ - private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000; - - /** - * There is no pending adaptive reconfiguration work. - */ - private static final int RECONFIGURATION_STATE_NONE = 0; - /** - * Codec configuration data needs to be written into the next buffer. - */ - private static final int RECONFIGURATION_STATE_WRITE_PENDING = 1; - /** - * Codec configuration data has been written into the next buffer, but that buffer still needs to - * be returned to the codec. - */ - private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; - - /** - * The codec does not need to be re-initialized. - */ - private static final int REINITIALIZATION_STATE_NONE = 0; - /** - * The input format has changed in a way that requires the codec to be re-initialized, but we - * haven't yet signaled an end of stream to the existing codec. We need to do so in order to - * ensure that it outputs any remaining buffers before we release it. - */ - private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1; - /** - * The input format has changed in a way that requires the codec to be re-initialized, and we've - * signaled an end of stream to the existing codec. We're waiting for the codec to output an end - * of stream signal to indicate that it has output any remaining buffers before we release it. - */ - private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; - - /** - * H.264/AVC buffer to queue when using the adaptation workaround (see - * {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes: - * Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be - * queued to force a resolution change when adapting to a new format. - */ - private static final byte[] ADAPTATION_WORKAROUND_BUFFER = Util.getBytesFromHexString( - "0000016742C00BDA259000000168CE0F13200000016588840DCE7118A0002FBF1C31C3275D78"); - private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32; - - public final CodecCounters codecCounters; - - private final MediaCodecSelector mediaCodecSelector; - private final DrmSessionManager drmSessionManager; - private final boolean playClearSamplesWithoutKeys; - private final SampleHolder sampleHolder; - private final MediaFormatHolder formatHolder; - private final List decodeOnlyPresentationTimestamps; - private final MediaCodec.BufferInfo outputBufferInfo; - private final EventListener eventListener; - private final boolean deviceNeedsAutoFrcWorkaround; - protected final Handler eventHandler; - - private MediaFormat format; - private DrmInitData drmInitData; - private MediaCodec codec; - private boolean codecIsAdaptive; - private boolean codecNeedsDiscardToSpsWorkaround; - private boolean codecNeedsFlushWorkaround; - private boolean codecNeedsAdaptationWorkaround; - private boolean codecNeedsEosPropagationWorkaround; - private boolean codecNeedsEosFlushWorkaround; - private boolean codecNeedsMonoChannelCountWorkaround; - private boolean codecNeedsAdaptationWorkaroundBuffer; - private boolean shouldSkipAdaptationWorkaroundOutputBuffer; - private ByteBuffer[] inputBuffers; - private ByteBuffer[] outputBuffers; - private long codecHotswapTimeMs; - private int inputIndex; - private int outputIndex; - private boolean openedDrmSession; - private boolean codecReconfigured; - private int codecReconfigurationState; - private int codecReinitializationState; - private boolean codecReceivedBuffers; - private boolean codecReceivedEos; - - private int sourceState; - private boolean inputStreamEnded; - private boolean outputStreamEnded; - private boolean waitingForKeys; - private boolean waitingForFirstSyncFrame; - - /** - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted media. May be null if support for encrypted - * media is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecTrackRenderer(SampleSource source, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) { - this (new SampleSource[] {source}, mediaCodecSelector, drmSessionManager, - playClearSamplesWithoutKeys, eventHandler, eventListener); - } - - /** - * @param sources The upstream sources from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param drmSessionManager For use with encrypted media. May be null if support for encrypted - * media is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisition. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public MediaCodecTrackRenderer(SampleSource[] sources, MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener) { - super(sources); - Assertions.checkState(Util.SDK_INT >= 16); - this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector); - this.drmSessionManager = drmSessionManager; - this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); - codecCounters = new CodecCounters(); - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); - formatHolder = new MediaFormatHolder(); - decodeOnlyPresentationTimestamps = new ArrayList<>(); - outputBufferInfo = new MediaCodec.BufferInfo(); - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; - } - - @Override - protected final boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException { - return handlesTrack(mediaCodecSelector, mediaFormat); - } - - /** - * Returns whether this renderer is capable of handling the provided track. - * - * @param mediaCodecSelector The decoder selector. - * @param mediaFormat The format of the track. - * @return True if the renderer can handle the track, false otherwise. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - protected abstract boolean handlesTrack(MediaCodecSelector mediaCodecSelector, - MediaFormat mediaFormat) throws DecoderQueryException; - - /** - * Returns a {@link DecoderInfo} for a given format. - * - * @param mediaCodecSelector The decoder selector. - * @param mimeType The mime type for which a decoder is required. - * @param requiresSecureDecoder Whether a secure decoder is required. - * @return A {@link DecoderInfo} describing the decoder to instantiate, or null if no suitable - * decoder exists. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - protected DecoderInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, String mimeType, - boolean requiresSecureDecoder) throws DecoderQueryException { - return mediaCodecSelector.getDecoderInfo(mimeType, requiresSecureDecoder); - } - - /** - * Configures a newly created {@link MediaCodec}. - * - * @param codec The {@link MediaCodec} to configure. - * @param codecIsAdaptive Whether the codec is adaptive. - * @param format The format for which the codec is being configured. - * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. - */ - protected abstract void configureCodec(MediaCodec codec, boolean codecIsAdaptive, - android.media.MediaFormat format, MediaCrypto crypto); - - @SuppressWarnings("deprecation") - protected final void maybeInitCodec() throws ExoPlaybackException { - if (!shouldInitCodec()) { - return; - } - - String mimeType = format.mimeType; - MediaCrypto mediaCrypto = null; - boolean requiresSecureDecoder = false; - if (drmInitData != null) { - if (drmSessionManager == null) { - throw new ExoPlaybackException("Media requires a DrmSessionManager"); - } - if (!openedDrmSession) { - drmSessionManager.open(drmInitData); - openedDrmSession = true; - } - int drmSessionState = drmSessionManager.getState(); - if (drmSessionState == DrmSessionManager.STATE_ERROR) { - throw new ExoPlaybackException(drmSessionManager.getError()); - } else if (drmSessionState == DrmSessionManager.STATE_OPENED - || drmSessionState == DrmSessionManager.STATE_OPENED_WITH_KEYS) { - mediaCrypto = drmSessionManager.getMediaCrypto().getWrappedMediaCrypto(); - requiresSecureDecoder = drmSessionManager.requiresSecureDecoderComponent(mimeType); - } else { - // The drm session isn't open yet. - return; - } - } - - DecoderInfo decoderInfo = null; - try { - decoderInfo = getDecoderInfo(mediaCodecSelector, mimeType, requiresSecureDecoder); - } catch (DecoderQueryException e) { - notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, - requiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); - } - - if (decoderInfo == null) { - notifyAndThrowDecoderInitError(new DecoderInitializationException(format, null, - requiresSecureDecoder, DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); - } - - String codecName = decoderInfo.name; - codecIsAdaptive = decoderInfo.adaptive; - codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); - codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); - codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); - codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); - codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); - codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); - try { - long codecInitializingTimestamp = SystemClock.elapsedRealtime(); - TraceUtil.beginSection("createByCodecName(" + codecName + ")"); - codec = MediaCodec.createByCodecName(codecName); - TraceUtil.endSection(); - TraceUtil.beginSection("configureCodec"); - configureCodec(codec, decoderInfo.adaptive, getFrameworkMediaFormat(format), mediaCrypto); - TraceUtil.endSection(); - TraceUtil.beginSection("codec.start()"); - codec.start(); - TraceUtil.endSection(); - long codecInitializedTimestamp = SystemClock.elapsedRealtime(); - notifyDecoderInitialized(codecName, codecInitializedTimestamp, - codecInitializedTimestamp - codecInitializingTimestamp); - inputBuffers = codec.getInputBuffers(); - outputBuffers = codec.getOutputBuffers(); - } catch (Exception e) { - notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, - requiresSecureDecoder, codecName)); - } - codecHotswapTimeMs = getState() == TrackRenderer.STATE_STARTED ? - SystemClock.elapsedRealtime() : -1; - inputIndex = -1; - outputIndex = -1; - waitingForFirstSyncFrame = true; - codecCounters.codecInitCount++; - } - - private void notifyAndThrowDecoderInitError(DecoderInitializationException e) - throws ExoPlaybackException { - notifyDecoderInitializationError(e); - throw new ExoPlaybackException(e); - } - - protected boolean shouldInitCodec() { - return codec == null && format != null; - } - - protected final boolean codecInitialized() { - return codec != null; - } - - protected final boolean haveFormat() { - return format != null; - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - format = null; - drmInitData = null; - try { - releaseCodec(); - } finally { - try { - if (openedDrmSession) { - drmSessionManager.close(); - openedDrmSession = false; - } - } finally { - super.onDisabled(); - } - } - } - - protected void releaseCodec() { - if (codec != null) { - codecHotswapTimeMs = -1; - inputIndex = -1; - outputIndex = -1; - waitingForKeys = false; - decodeOnlyPresentationTimestamps.clear(); - inputBuffers = null; - outputBuffers = null; - codecReconfigured = false; - codecReceivedBuffers = false; - codecIsAdaptive = false; - codecNeedsDiscardToSpsWorkaround = false; - codecNeedsFlushWorkaround = false; - codecNeedsAdaptationWorkaround = false; - codecNeedsEosPropagationWorkaround = false; - codecNeedsEosFlushWorkaround = false; - codecNeedsMonoChannelCountWorkaround = false; - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codecReceivedEos = false; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecReinitializationState = REINITIALIZATION_STATE_NONE; - codecCounters.codecReleaseCount++; - try { - codec.stop(); - } finally { - try { - codec.release(); - } finally { - codec = null; - } - } - } - } - - @Override - protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { - sourceState = SOURCE_STATE_NOT_READY; - inputStreamEnded = false; - outputStreamEnded = false; - if (codec != null) { - flushCodec(); - } - } - - @Override - protected void onStarted() { - // Do nothing. Overridden to remove throws clause. - } - - @Override - protected void onStopped() { - // Do nothing. Overridden to remove throws clause. - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) - throws ExoPlaybackException { - sourceState = sourceIsReady - ? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState) - : SOURCE_STATE_NOT_READY; - if (format == null) { - readFormat(positionUs); - } - maybeInitCodec(); - if (codec != null) { - TraceUtil.beginSection("drainAndFeed"); - while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} - if (feedInputBuffer(positionUs, true)) { - while (feedInputBuffer(positionUs, false)) {} - } - TraceUtil.endSection(); - } - codecCounters.ensureUpdated(); - } - - private void readFormat(long positionUs) throws ExoPlaybackException { - int result = readSource(positionUs, formatHolder, null); - if (result == SampleSource.FORMAT_READ) { - onInputFormatChanged(formatHolder); - } - } - - protected void flushCodec() throws ExoPlaybackException { - codecHotswapTimeMs = -1; - inputIndex = -1; - outputIndex = -1; - waitingForFirstSyncFrame = true; - waitingForKeys = false; - decodeOnlyPresentationTimestamps.clear(); - codecNeedsAdaptationWorkaroundBuffer = false; - shouldSkipAdaptationWorkaroundOutputBuffer = false; - if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { - // Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053]. - releaseCodec(); - maybeInitCodec(); - } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { - // We're already waiting to release and re-initialize the codec. Since we're now flushing, - // there's no need to wait any longer. - releaseCodec(); - maybeInitCodec(); - } else { - // We can flush and re-use the existing decoder. - codec.flush(); - codecReceivedBuffers = false; - } - if (codecReconfigured && format != null) { - // Any reconfiguration data that we send shortly before the flush may be discarded. We - // avoid this issue by sending reconfiguration data following every flush. - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - } - } - - /** - * @param positionUs The current media time in microseconds, measured at the start of the - * current iteration of the rendering loop. - * @param firstFeed True if this is the first call to this method from the current invocation of - * {@link #doSomeWork(long, long)}. False otherwise. - * @return True if it may be possible to feed more input data. False otherwise. - * @throws ExoPlaybackException If an error occurs feeding the input buffer. - */ - private boolean feedInputBuffer(long positionUs, boolean firstFeed) throws ExoPlaybackException { - if (inputStreamEnded - || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { - // The input stream has ended, or we need to re-initialize the codec but are still waiting - // for the existing codec to output any final output buffers. - return false; - } - - if (inputIndex < 0) { - inputIndex = codec.dequeueInputBuffer(0); - if (inputIndex < 0) { - return false; - } - sampleHolder.data = inputBuffers[inputIndex]; - sampleHolder.clearData(); - } - - if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { - // We need to re-initialize the codec. Send an end of stream signal to the existing codec so - // that it outputs any remaining buffers before we release it. - if (codecNeedsEosPropagationWorkaround) { - // Do nothing. - } else { - codecReceivedEos = true; - codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); - inputIndex = -1; - } - codecReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM; - return false; - } - - if (codecNeedsAdaptationWorkaroundBuffer) { - codecNeedsAdaptationWorkaroundBuffer = false; - sampleHolder.data.put(ADAPTATION_WORKAROUND_BUFFER); - codec.queueInputBuffer(inputIndex, 0, ADAPTATION_WORKAROUND_BUFFER.length, 0, 0); - inputIndex = -1; - codecReceivedBuffers = true; - return true; - } - - int result; - if (waitingForKeys) { - // We've already read an encrypted sample into sampleHolder, and are waiting for keys. - result = SampleSource.SAMPLE_READ; - } else { - // For adaptive reconfiguration OMX decoders expect all reconfiguration data to be supplied - // at the start of the buffer that also contains the first frame in the new format. - if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) { - for (int i = 0; i < format.initializationData.size(); i++) { - byte[] data = format.initializationData.get(i); - sampleHolder.data.put(data); - } - codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; - } - result = readSource(positionUs, formatHolder, sampleHolder); - if (firstFeed && sourceState == SOURCE_STATE_READY && result == SampleSource.NOTHING_READ) { - sourceState = SOURCE_STATE_READY_READ_MAY_FAIL; - } - } - - if (result == SampleSource.NOTHING_READ) { - return false; - } - if (result == SampleSource.FORMAT_READ) { - if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { - // We received two formats in a row. Clear the current buffer of any reconfiguration data - // associated with the first format. - sampleHolder.clearData(); - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - } - onInputFormatChanged(formatHolder); - return true; - } - if (result == SampleSource.END_OF_STREAM) { - if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { - // We received a new format immediately before the end of the stream. We need to clear - // the corresponding reconfiguration data from the current buffer, but re-write it into - // a subsequent buffer if there are any (e.g. if the user seeks backwards). - sampleHolder.clearData(); - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - } - inputStreamEnded = true; - if (!codecReceivedBuffers) { - processEndOfStream(); - return false; - } - try { - if (codecNeedsEosPropagationWorkaround) { - // Do nothing. - } else { - codecReceivedEos = true; - codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); - inputIndex = -1; - } - } catch (CryptoException e) { - notifyCryptoError(e); - throw new ExoPlaybackException(e); - } - return false; - } - if (waitingForFirstSyncFrame) { - // TODO: Find out if it's possible to supply samples prior to the first sync - // frame for HE-AAC. - if (!sampleHolder.isSyncFrame()) { - sampleHolder.clearData(); - if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { - // The buffer we just cleared contained reconfiguration data. We need to re-write this - // data into a subsequent buffer (if there is one). - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - } - return true; - } - waitingForFirstSyncFrame = false; - } - boolean sampleEncrypted = sampleHolder.isEncrypted(); - waitingForKeys = shouldWaitForKeys(sampleEncrypted); - if (waitingForKeys) { - return false; - } - if (codecNeedsDiscardToSpsWorkaround && !sampleEncrypted) { - NalUnitUtil.discardToSps(sampleHolder.data); - if (sampleHolder.data.position() == 0) { - return true; - } - codecNeedsDiscardToSpsWorkaround = false; - } - try { - int bufferSize = sampleHolder.data.position(); - int adaptiveReconfigurationBytes = bufferSize - sampleHolder.size; - long presentationTimeUs = sampleHolder.timeUs; - if (sampleHolder.isDecodeOnly()) { - decodeOnlyPresentationTimestamps.add(presentationTimeUs); - } - - onQueuedInputBuffer(presentationTimeUs, sampleHolder.data, bufferSize, sampleEncrypted); - - if (sampleEncrypted) { - MediaCodec.CryptoInfo cryptoInfo = getFrameworkCryptoInfo(sampleHolder, - adaptiveReconfigurationBytes); - codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0); - } else { - codec.queueInputBuffer(inputIndex, 0, bufferSize, presentationTimeUs, 0); - } - inputIndex = -1; - codecReceivedBuffers = true; - codecReconfigurationState = RECONFIGURATION_STATE_NONE; - codecCounters.inputBufferCount++; - } catch (CryptoException e) { - notifyCryptoError(e); - throw new ExoPlaybackException(e); - } - return true; - } - - private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(SampleHolder sampleHolder, - int adaptiveReconfigurationBytes) { - MediaCodec.CryptoInfo cryptoInfo = sampleHolder.cryptoInfo.getFrameworkCryptoInfoV16(); - if (adaptiveReconfigurationBytes == 0) { - return cryptoInfo; - } - // There must be at least one sub-sample, although numBytesOfClearData is permitted to be - // null if it contains no clear data. Instantiate it if needed, and add the reconfiguration - // bytes to the clear byte count of the first sub-sample. - if (cryptoInfo.numBytesOfClearData == null) { - cryptoInfo.numBytesOfClearData = new int[1]; - } - cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes; - return cryptoInfo; - } - - private android.media.MediaFormat getFrameworkMediaFormat(MediaFormat format) { - android.media.MediaFormat mediaFormat = format.getFrameworkMediaFormatV16(); - if (deviceNeedsAutoFrcWorkaround) { - mediaFormat.setInteger("auto-frc", 0); - } - return mediaFormat; - } - - private boolean shouldWaitForKeys(boolean sampleEncrypted) throws ExoPlaybackException { - if (!openedDrmSession) { - return false; - } - int drmManagerState = drmSessionManager.getState(); - if (drmManagerState == DrmSessionManager.STATE_ERROR) { - throw new ExoPlaybackException(drmSessionManager.getError()); - } - if (drmManagerState != DrmSessionManager.STATE_OPENED_WITH_KEYS && - (sampleEncrypted || !playClearSamplesWithoutKeys)) { - return true; - } - return false; - } - - /** - * Invoked when a new format is read from the upstream {@link SampleSource}. - * - * @param formatHolder Holds the new format. - * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. - */ - protected void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException { - MediaFormat oldFormat = format; - format = formatHolder.format; - drmInitData = formatHolder.drmInitData; - if (codec != null && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { - codecReconfigured = true; - codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; - codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround - && format.width == oldFormat.width && format.height == oldFormat.height; - } else { - if (codecReceivedBuffers) { - // Signal end of stream and wait for any final output buffers before re-initialization. - codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; - } else { - // There aren't any final output buffers, so perform re-initialization immediately. - releaseCodec(); - maybeInitCodec(); - } - } - } - - /** - * Invoked when the output format of the {@link MediaCodec} changes. - *

        - * The default implementation is a no-op. - * - * @param codec The {@link MediaCodec} instance. - * @param outputFormat The new output format. - * @throws ExoPlaybackException If an error occurs on output format change. - */ - protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) - throws ExoPlaybackException { - // Do nothing. - } - - /** - * Invoked when the output stream ends, meaning that the last output buffer has been processed - * and the {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag has been propagated through the - * decoder. - *

        - * The default implementation is a no-op. - */ - protected void onOutputStreamEnded() { - // Do nothing. - } - - /** - * Invoked immediately before an input buffer is queued into the codec. - *

        - * The default implementation is a no-op. - * - * @param presentationTimeUs The timestamp associated with the input buffer. - * @param buffer The buffer to be queued. - * @param bufferSize the size of the sample data stored in the buffer. - * @param sampleEncrypted Whether the sample data is encrypted. - */ - protected void onQueuedInputBuffer( - long presentationTimeUs, ByteBuffer buffer, int bufferSize, boolean sampleEncrypted) { - // Do nothing. - } - - /** - * Invoked when an output buffer is successfully processed. - *

        - * The default implementation is a no-op. - * - * @param presentationTimeUs The timestamp associated with the output buffer. - */ - protected void onProcessedOutputBuffer(long presentationTimeUs) { - // Do nothing. - } - - /** - * Determines whether the existing {@link MediaCodec} should be reconfigured for a new format by - * sending codec specific initialization data at the start of the next input buffer. If true is - * returned then the {@link MediaCodec} instance will be reconfigured in this way. If false is - * returned then the instance will be released, and a new instance will be created for the new - * format. - *

        - * The default implementation returns false. - * - * @param codec The existing {@link MediaCodec} instance. - * @param codecIsAdaptive Whether the codec is adaptive. - * @param oldFormat The format for which the existing instance is configured. - * @param newFormat The new format. - * @return True if the existing instance can be reconfigured. False otherwise. - */ - protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, - MediaFormat oldFormat, MediaFormat newFormat) { - return false; - } - - @Override - protected boolean isEnded() { - return outputStreamEnded; - } - - @Override - protected boolean isReady() { - return format != null && !waitingForKeys - && (sourceState != SOURCE_STATE_NOT_READY || outputIndex >= 0 || isWithinHotswapPeriod()); - } - - /** - * Gets the source state. - * - * @return One of {@link #SOURCE_STATE_NOT_READY}, {@link #SOURCE_STATE_READY} and - * {@link #SOURCE_STATE_READY_READ_MAY_FAIL}. - */ - protected final int getSourceState() { - return sourceState; - } - - private boolean isWithinHotswapPeriod() { - return SystemClock.elapsedRealtime() < codecHotswapTimeMs + MAX_CODEC_HOTSWAP_TIME_MS; - } - - /** - * Returns the maximum time to block whilst waiting for a decoded output buffer. - * - * @return The maximum time to block, in microseconds. - */ - protected long getDequeueOutputBufferTimeoutUs() { - return 0; - } - - /** - * @return True if it may be possible to drain more output data. False otherwise. - * @throws ExoPlaybackException If an error occurs draining the output buffer. - */ - @SuppressWarnings("deprecation") - private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) - throws ExoPlaybackException { - if (outputStreamEnded) { - return false; - } - - if (outputIndex < 0) { - outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); - } - - if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { - processOutputFormat(); - return true; - } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { - outputBuffers = codec.getOutputBuffers(); - codecCounters.outputBuffersChangedCount++; - return true; - } else if (outputIndex < 0) { - if (codecNeedsEosPropagationWorkaround && (inputStreamEnded - || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { - processEndOfStream(); - return true; - } - return false; - } - - if (shouldSkipAdaptationWorkaroundOutputBuffer) { - shouldSkipAdaptationWorkaroundOutputBuffer = false; - codec.releaseOutputBuffer(outputIndex, false); - outputIndex = -1; - return true; - } - - if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { - processEndOfStream(); - return false; - } - - int decodeOnlyIndex = getDecodeOnlyIndex(outputBufferInfo.presentationTimeUs); - if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex], - outputBufferInfo, outputIndex, decodeOnlyIndex != -1)) { - onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs); - if (decodeOnlyIndex != -1) { - decodeOnlyPresentationTimestamps.remove(decodeOnlyIndex); - } - outputIndex = -1; - return true; - } - - return false; - } - - /** - * Processes a new output format. - * - * @throws ExoPlaybackException If an error occurs processing the output format. - */ - private void processOutputFormat() throws ExoPlaybackException { - android.media.MediaFormat format = codec.getOutputFormat(); - if (codecNeedsAdaptationWorkaround - && format.getInteger(android.media.MediaFormat.KEY_WIDTH) - == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT - && format.getInteger(android.media.MediaFormat.KEY_HEIGHT) - == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) { - // We assume this format changed event was caused by the adaptation workaround. - shouldSkipAdaptationWorkaroundOutputBuffer = true; - return; - } - if (codecNeedsMonoChannelCountWorkaround) { - format.setInteger(android.media.MediaFormat.KEY_CHANNEL_COUNT, 1); - } - onOutputFormatChanged(codec, format); - codecCounters.outputFormatChangedCount++; - } - - /** - * Processes the provided output buffer. - * - * @return True if the output buffer was processed (e.g. rendered or discarded) and hence is no - * longer required. False otherwise. - * @throws ExoPlaybackException If an error occurs processing the output buffer. - */ - protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, - MediaCodec codec, ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, - boolean shouldSkip) throws ExoPlaybackException; - - /** - * Processes an end of stream signal. - * - * @throws ExoPlaybackException If an error occurs processing the signal. - */ - private void processEndOfStream() throws ExoPlaybackException { - if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { - // We're waiting to re-initialize the codec, and have now processed all final buffers. - releaseCodec(); - maybeInitCodec(); - } else { - outputStreamEnded = true; - onOutputStreamEnded(); - } - } - - private void notifyDecoderInitializationError(final DecoderInitializationException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDecoderInitializationError(e); - } - }); - } - } - - private void notifyCryptoError(final CryptoException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onCryptoError(e); - } - }); - } - } - - private void notifyDecoderInitialized(final String decoderName, - final long initializedTimestamp, final long initializationDuration) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDecoderInitialized(decoderName, initializedTimestamp, - initializationDuration); - } - }); - } - } - - private int getDecodeOnlyIndex(long presentationTimeUs) { - final int size = decodeOnlyPresentationTimestamps.size(); - for (int i = 0; i < size; i++) { - if (decodeOnlyPresentationTimestamps.get(i).longValue() == presentationTimeUs) { - return i; - } - } - return -1; - } - - /** - * Returns whether the decoder is known to fail when flushed. - *

        - * If true is returned, the renderer will work around the issue by releasing the decoder and - * instantiating a new one rather than flushing the current instance. - * - * @param name The name of the decoder. - * @return True if the decoder is known to fail when flushed. - */ - private static boolean codecNeedsFlushWorkaround(String name) { - return Util.SDK_INT < 18 - || (Util.SDK_INT == 18 - && ("OMX.SEC.avc.dec".equals(name) || "OMX.SEC.avc.dec.secure".equals(name))) - || (Util.SDK_INT == 19 && Util.MODEL.startsWith("SM-G800") - && ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.secure".equals(name))); - } - - /** - * Returns whether the decoder is known to get stuck during some adaptations where the resolution - * does not change. - *

        - * If true is returned, the renderer will work around the issue by queueing and discarding a blank - * frame at a different resolution, which resets the codec's internal state. - *

        - * See [Internal: b/27807182]. - * - * @param name The name of the decoder. - * @return True if the decoder is known to get stuck during some adaptations. - */ - private static boolean codecNeedsAdaptationWorkaround(String name) { - return Util.SDK_INT < 24 - && ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name)) - && (Util.DEVICE.equals("flounder") || Util.DEVICE.equals("flounder_lte") - || Util.DEVICE.equals("grouper") || Util.DEVICE.equals("tilapia")); - } - - /** - * Returns whether the decoder is an H.264/AVC decoder known to fail if NAL units are queued - * before the codec specific data. - *

        - * If true is returned, the renderer will work around the issue by discarding data up to the SPS. - * - * @param name The name of the decoder. - * @param format The format used to configure the decoder. - * @return True if the decoder is known to fail if NAL units are queued before CSD. - */ - private static boolean codecNeedsDiscardToSpsWorkaround(String name, MediaFormat format) { - return Util.SDK_INT < 21 && format.initializationData.isEmpty() - && "OMX.MTK.VIDEO.DECODER.AVC".equals(name); - } - - /** - * Returns whether the decoder is known to handle the propagation of the - * {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device. - *

        - * If true is returned, the renderer will work around the issue by approximating end of stream - * behavior without relying on the flag being propagated through to an output buffer by the - * underlying decoder. - * - * @param name The name of the decoder. - * @return True if the decoder is known to handle {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} - * propagation incorrectly on the host device. False otherwise. - */ - private static boolean codecNeedsEosPropagationWorkaround(String name) { - return Util.SDK_INT <= 17 && ("OMX.rk.video_decoder.avc".equals(name) - || "OMX.allwinner.video.decoder.avc".equals(name)); - } - - /** - * Returns whether the decoder is known to behave incorrectly if flushed after receiving an input - * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. - *

        - * If true is returned, the renderer will work around the issue by instantiating a new decoder - * when this case occurs. - * - * @param name The name of the decoder. - * @return True if the decoder is known to behave incorrectly if flushed after receiving an input - * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. False otherwise. - */ - private static boolean codecNeedsEosFlushWorkaround(String name) { - return Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name); - } - - /** - * Returns whether the decoder is known to set the number of audio channels in the output format - * to 2 for the given input format, whilst only actually outputting a single channel. - *

        - * If true is returned then we explicitly override the number of channels in the output format, - * setting it to 1. - * - * @param name The decoder name. - * @param format The input format. - * @return True if the device is known to set the number of audio channels in the output format - * to 2 for the given input format, whilst only actually outputting a single channel. False - * otherwise. - */ - private static boolean codecNeedsMonoChannelCountWorkaround(String name, MediaFormat format) { - return Util.SDK_INT <= 18 && format.channelCount == 1 - && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); - } - - /** - * Returns whether the device is known to enable frame-rate conversion logic that negatively - * impacts ExoPlayer. - *

        - * If true is returned then we explicitly disable the feature. - * - * @return True if the device is known to enable frame-rate conversion logic that negatively - * impacts ExoPlayer. False otherwise. - */ - private static boolean deviceNeedsAutoFrcWorkaround() { - // nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of - // content to the refresh rate of the display. For example playback of 23.976fps content is - // adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the - // implementation causes ExoPlayer's reported playback position to drift out of sync. Captions - // also lose sync [Internal: b/26453592]. - return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecUtil.java deleted file mode 100755 index cb8d635b02b..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecUtil.java +++ /dev/null @@ -1,512 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.TargetApi; -import android.media.MediaCodecInfo; -import android.media.MediaCodecInfo.CodecCapabilities; -import android.media.MediaCodecInfo.CodecProfileLevel; -import android.media.MediaCodecList; -import android.text.TextUtils; -import android.util.Log; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * A utility class for querying the available codecs. - */ -@TargetApi(16) -public final class MediaCodecUtil { - - /** - * Thrown when an error occurs querying the device for its underlying media capabilities. - *

        - * Such failures are not expected in normal operation and are normally temporary (e.g. if the - * mediaserver process has crashed and is yet to restart). - */ - public static class DecoderQueryException extends IOException { - - private DecoderQueryException(Throwable cause) { - super("Failed to query underlying media codecs", cause); - } - - } - - private static final String TAG = "MediaCodecUtil"; - private static final DecoderInfo PASSTHROUGH_DECODER_INFO = - new DecoderInfo("OMX.google.raw.decoder", null); - - private static final Map> decoderInfosCache = new HashMap<>(); - - // Lazily initialized. - private static int maxH264DecodableFrameSize = -1; - - private MediaCodecUtil() {} - - /** - * Optional call to warm the codec cache for a given mime type. - *

        - * Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}. - * - * @param mimeType The mime type. - * @param secure Whether the decoder is required to support secure decryption. Always pass false - * unless secure decryption really is required. - */ - public static void warmCodec(String mimeType, boolean secure) { - try { - getDecoderInfos(mimeType, secure); - } catch (DecoderQueryException e) { - // Codec warming is best effort, so we can swallow the exception. - Log.e(TAG, "Codec warming failed", e); - } - } - - /** - * Gets information about a decoder suitable for audio passthrough. - ** - * @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists. - */ - public static DecoderInfo getPassthroughDecoderInfo() { - // TODO: Return null if the raw decoder doesn't exist. - return PASSTHROUGH_DECODER_INFO; - } - - /** - * Get information about the preferred decoder for a given mime type. - * - * @param mimeType The mime type. - * @param secure Whether the decoder is required to support secure decryption. Always pass false - * unless secure decryption really is required. - * @return A {@link DecoderInfo} describing the decoder, or null if no suitable decoder exists. - */ - public static DecoderInfo getDecoderInfo(String mimeType, boolean secure) - throws DecoderQueryException { - List decoderInfos = getDecoderInfos(mimeType, secure); - return decoderInfos.isEmpty() ? null : decoderInfos.get(0); - } - - /** - * Returns all @{link DecoderInfo}s for a given mime type, in the order given by - * {@link MediaCodecList}. - * - * @param mimeType The mime type. - * @param secure Whether the decoders are required to support secure decryption. Always pass false - * unless secure decryption really is required. - * @return A list of all @{link DecoderInfo}s for the given mime type. May be empty if no suitable - * decoders exist. - */ - public static synchronized List getDecoderInfos(String mimeType, boolean secure) - throws DecoderQueryException { - CodecKey key = new CodecKey(mimeType, secure); - List decoderInfos = decoderInfosCache.get(key); - if (decoderInfos != null) { - return decoderInfos; - } - MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 - ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); - decoderInfos = getDecoderInfosInternal(key, mediaCodecList); - if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) { - // Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the - // legacy path. We also try this path on API levels 22 and 23 as a defensive measure. - mediaCodecList = new MediaCodecListCompatV16(); - decoderInfos = getDecoderInfosInternal(key, mediaCodecList); - if (!decoderInfos.isEmpty()) { - Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType - + ". Assuming: " + decoderInfos.get(0).name); - } - } - decoderInfos = Collections.unmodifiableList(decoderInfos); - decoderInfosCache.put(key, decoderInfos); - return decoderInfos; - } - - private static List getDecoderInfosInternal( - CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { - try { - List decoderInfos = new ArrayList<>(); - String mimeType = key.mimeType; - int numberOfCodecs = mediaCodecList.getCodecCount(); - boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); - // Note: MediaCodecList is sorted by the framework such that the best decoders come first. - for (int i = 0; i < numberOfCodecs; i++) { - MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i); - String codecName = codecInfo.getName(); - if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit)) { - for (String supportedType : codecInfo.getSupportedTypes()) { - if (supportedType.equalsIgnoreCase(mimeType)) { - try { - CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType); - boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); - if ((secureDecodersExplicit && key.secure == secure) - || (!secureDecodersExplicit && !key.secure)) { - decoderInfos.add(new DecoderInfo(codecName, capabilities)); - } else if (!secureDecodersExplicit && secure) { - decoderInfos.add(new DecoderInfo(codecName + ".secure", capabilities)); - // It only makes sense to have one synthesized secure decoder, return immediately. - return decoderInfos; - } - } catch (Exception e) { - if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) { - // Suppress error querying secondary codec capabilities up to API level 23. - Log.e(TAG, "Skipping codec " + codecName + " (failed to query capabilities)"); - } else { - // Rethrow error querying primary codec capabilities, or secondary codec - // capabilities if API level is greater than 23. - Log.e(TAG, "Failed to query codec " + codecName + " (" + supportedType + ")"); - throw e; - } - } - } - } - } - } - return decoderInfos; - } catch (Exception e) { - // If the underlying mediaserver is in a bad state, we may catch an IllegalStateException - // or an IllegalArgumentException here. - throw new DecoderQueryException(e); - } - } - - /** - * Returns whether the specified codec is usable for decoding on the current device. - */ - private static boolean isCodecUsableDecoder(MediaCodecInfo info, String name, - boolean secureDecodersExplicit) { - if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) { - return false; - } - - // Work around broken audio decoders. - if (Util.SDK_INT < 21 - && ("CIPAACDecoder".equals(name) - || "CIPMP3Decoder".equals(name) - || "CIPVorbisDecoder".equals(name) - || "AACDecoder".equals(name) - || "MP3Decoder".equals(name))) { - return false; - } - // Work around https://github.com/google/ExoPlayer/issues/398 - if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) { - return false; - } - // Work around https://github.com/google/ExoPlayer/issues/1528 - if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) - && "a70".equals(Util.DEVICE)) { - return false; - } - - // Work around an issue where querying/creating a particular MP3 decoder on some devices on - // platform API version 16 fails. - if (Util.SDK_INT == 16 && Util.DEVICE != null - && "OMX.qcom.audio.decoder.mp3".equals(name) - && ("dlxu".equals(Util.DEVICE) // HTC Butterfly - || "protou".equals(Util.DEVICE) // HTC Desire X - || "ville".equals(Util.DEVICE) // HTC One S - || "villeplus".equals(Util.DEVICE) - || "villec2".equals(Util.DEVICE) - || Util.DEVICE.startsWith("gee") // LGE Optimus G - || "C6602".equals(Util.DEVICE) // Sony Xperia Z - || "C6603".equals(Util.DEVICE) - || "C6606".equals(Util.DEVICE) - || "C6616".equals(Util.DEVICE) - || "L36h".equals(Util.DEVICE) - || "SO-02E".equals(Util.DEVICE))) { - return false; - } - - // Work around an issue where large timestamps are not propagated correctly. - if (Util.SDK_INT == 16 - && "OMX.qcom.audio.decoder.aac".equals(name) - && ("C1504".equals(Util.DEVICE) // Sony Xperia E - || "C1505".equals(Util.DEVICE) - || "C1604".equals(Util.DEVICE) // Sony Xperia E dual - || "C1605".equals(Util.DEVICE))) { - return false; - } - - // Work around https://github.com/google/ExoPlayer/issues/548 - // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3 does not render video. - if (Util.SDK_INT <= 19 && Util.DEVICE != null - && (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano") - || Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos")) - && "samsung".equals(Util.MANUFACTURER) && name.equals("OMX.SEC.vp8.dec")) { - return false; - } - // VP8 decoder on Samsung Galaxy S4 cannot be queried. - if (Util.SDK_INT <= 19 && Util.DEVICE != null && Util.DEVICE.startsWith("jflte") - && "OMX.qcom.video.decoder.vp8".equals(name)) { - return false; - } - - return true; - } - - /** - * Tests whether the device advertises it can decode video of a given type at a specified width - * and height. - *

        - * Must not be called if the device SDK version is less than 21. - * - * @param mimeType The mime type. - * @param secure Whether the decoder is required to support secure decryption. Always pass false - * unless secure decryption really is required. - * @param width Width in pixels. - * @param height Height in pixels. - * @return Whether the decoder advertises support of the given size. - */ - @TargetApi(21) - public static boolean isSizeSupportedV21(String mimeType, boolean secure, int width, - int height) throws DecoderQueryException { - Assertions.checkState(Util.SDK_INT >= 21); - MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure); - return videoCapabilities != null && videoCapabilities.isSizeSupported(width, height); - } - - /** - * Tests whether the device advertises it can decode video of a given type at a specified - * width, height, and frame rate. - *

        - * Must not be called if the device SDK version is less than 21. - * - * @param mimeType The mime type. - * @param secure Whether the decoder is required to support secure decryption. Always pass false - * unless secure decryption really is required. - * @param width Width in pixels. - * @param height Height in pixels. - * @param frameRate Frame rate in frames per second. - * @return Whether the decoder advertises support of the given size and frame rate. - */ - @TargetApi(21) - public static boolean isSizeAndRateSupportedV21(String mimeType, boolean secure, - int width, int height, double frameRate) throws DecoderQueryException { - Assertions.checkState(Util.SDK_INT >= 21); - MediaCodecInfo.VideoCapabilities videoCapabilities = getVideoCapabilitiesV21(mimeType, secure); - return videoCapabilities != null - && videoCapabilities.areSizeAndRateSupported(width, height, frameRate); - } - - /** - * @param profile An AVC profile constant from {@link CodecProfileLevel}. - * @param level An AVC profile level from {@link CodecProfileLevel}. - * @return Whether the specified profile is supported at the specified level. - * @deprecated Prefer {@link #getDecoderInfos(String, boolean)} for new code. - */ - @Deprecated - public static boolean isH264ProfileSupported(int profile, int level) - throws DecoderQueryException { - DecoderInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); - if (decoderInfo == null) { - return false; - } - for (CodecProfileLevel profileLevel : decoderInfo.capabilities.profileLevels) { - if (profileLevel.profile == profile && profileLevel.level >= level) { - return true; - } - } - return false; - } - - /** - * @return the maximum frame size for an H264 stream that can be decoded on the device. - */ - public static int maxH264DecodableFrameSize() throws DecoderQueryException { - if (maxH264DecodableFrameSize == -1) { - int result = 0; - DecoderInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); - if (decoderInfo != null) { - for (CodecProfileLevel profileLevel : decoderInfo.capabilities.profileLevels) { - result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); - } - // We assume support for at least 360p. - result = Math.max(result, 480 * 360); - } - maxH264DecodableFrameSize = result; - } - return maxH264DecodableFrameSize; - } - - @TargetApi(21) - private static MediaCodecInfo.VideoCapabilities getVideoCapabilitiesV21(String mimeType, - boolean secure) throws DecoderQueryException { - DecoderInfo decoderInfo = getDecoderInfo(mimeType, secure); - return decoderInfo == null ? null : decoderInfo.capabilities.getVideoCapabilities(); - } - - /** - * Conversion values taken from ISO 14496-10 Table A-1. - * - * @param avcLevel one of CodecProfileLevel.AVCLevel* constants. - * @return maximum frame size that can be decoded by a decoder with the specified avc level - * (or {@code -1} if the level is not recognized) - */ - private static int avcLevelToMaxFrameSize(int avcLevel) { - switch (avcLevel) { - case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16; - case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16; - case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16; - case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16; - case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16; - case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16; - case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16; - case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16; - case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16; - case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16; - case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; - case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; - case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; - default: return -1; - } - } - - private interface MediaCodecListCompat { - - /** - * The number of codecs in the list. - */ - int getCodecCount(); - - /** - * The info at the specified index in the list. - * - * @param index The index. - */ - MediaCodecInfo getCodecInfoAt(int index); - - /** - * @return Returns whether secure decoders are explicitly listed, if present. - */ - boolean secureDecodersExplicit(); - - /** - * Whether secure playback is supported for the given {@link CodecCapabilities}, which should - * have been obtained from a {@link MediaCodecInfo} obtained from this list. - */ - boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities); - - } - - @TargetApi(21) - private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { - - private final int codecKind; - - private MediaCodecInfo[] mediaCodecInfos; - - public MediaCodecListCompatV21(boolean includeSecure) { - codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; - } - - @Override - public int getCodecCount() { - ensureMediaCodecInfosInitialized(); - return mediaCodecInfos.length; - } - - @Override - public MediaCodecInfo getCodecInfoAt(int index) { - ensureMediaCodecInfosInitialized(); - return mediaCodecInfos[index]; - } - - @Override - public boolean secureDecodersExplicit() { - return true; - } - - @Override - public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { - return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); - } - - private void ensureMediaCodecInfosInitialized() { - if (mediaCodecInfos == null) { - mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); - } - } - - } - - @SuppressWarnings("deprecation") - private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { - - @Override - public int getCodecCount() { - return MediaCodecList.getCodecCount(); - } - - @Override - public MediaCodecInfo getCodecInfoAt(int index) { - return MediaCodecList.getCodecInfoAt(index); - } - - @Override - public boolean secureDecodersExplicit() { - return false; - } - - @Override - public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { - // Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure - // H264 decoder exists. - return MimeTypes.VIDEO_H264.equals(mimeType); - } - - } - - private static final class CodecKey { - - public final String mimeType; - public final boolean secure; - - public CodecKey(String mimeType, boolean secure) { - this.mimeType = mimeType; - this.secure = secure; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); - result = prime * result + (secure ? 1231 : 1237); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || obj.getClass() != CodecKey.class) { - return false; - } - CodecKey other = (CodecKey) obj; - return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecVideoTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecVideoTrackRenderer.java deleted file mode 100755 index 3af816ef993..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaCodecVideoTrackRenderer.java +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.media.MediaCodec; -import android.media.MediaCrypto; -import android.os.Handler; -import android.os.SystemClock; -import android.view.Surface; -import android.view.TextureView; -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; -import org.telegram.messenger.exoplayer.drm.DrmSessionManager; -import org.telegram.messenger.exoplayer.drm.FrameworkMediaCrypto; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.TraceUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.nio.ByteBuffer; - -/** - * Decodes and renders video using {@link MediaCodec}. - */ -@TargetApi(16) -public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { - - /** - * Interface definition for a callback to be notified of {@link MediaCodecVideoTrackRenderer} - * events. - */ - public interface EventListener extends MediaCodecTrackRenderer.EventListener { - - /** - * Invoked to report the number of frames dropped by the renderer. Dropped frames are reported - * whenever the renderer is stopped having dropped frames, and optionally, whenever the count - * reaches a specified threshold whilst the renderer is started. - * - * @param count The number of dropped frames. - * @param elapsed The duration in milliseconds over which the frames were dropped. This - * duration is timed from when the renderer was started or from when dropped frames were - * last reported (whichever was more recent), and not from when the first of the reported - * drops occurred. - */ - void onDroppedFrames(int count, long elapsed); - - /** - * Invoked each time there's a change in the size of the video being rendered. - * - * @param width The video width in pixels. - * @param height The video height in pixels. - * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise - * rotation in degrees that the application should apply for the video for it to be rendered - * in the correct orientation. This value will always be zero on API levels 21 and above, - * since the renderer will apply all necessary rotations internally. On earlier API levels - * this is not possible. Applications that use {@link TextureView} can apply the rotation by - * calling {@link TextureView#setTransform}. Applications that do not expect to encounter - * rotated videos can safely ignore this parameter. - * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case - * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic - * content. - */ - void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, - float pixelWidthHeightRatio); - - /** - * Invoked when a frame is rendered to a surface for the first time following that surface - * having been set as the target for the renderer. - * - * @param surface The surface to which a first frame has been rendered. - */ - void onDrawnToSurface(Surface surface); - - } - - // TODO: Use MediaFormat constants if these get exposed through the API. See - // [Internal: b/14127601]. - private static final String KEY_CROP_LEFT = "crop-left"; - private static final String KEY_CROP_RIGHT = "crop-right"; - private static final String KEY_CROP_BOTTOM = "crop-bottom"; - private static final String KEY_CROP_TOP = "crop-top"; - - /** - * The type of a message that can be passed to an instance of this class via - * {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object - * should be the target {@link Surface}, or null. - */ - public static final int MSG_SET_SURFACE = 1; - - private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper; - private final EventListener eventListener; - private final long allowedJoiningTimeUs; - private final int videoScalingMode; - private final int maxDroppedFrameCountToNotify; - - private Surface surface; - private boolean reportedDrawnToSurface; - private boolean renderedFirstFrame; - private long joiningDeadlineUs; - private long droppedFrameAccumulationStartTimeMs; - private int droppedFrameCount; - private int consecutiveDroppedFrameCount; - - private int pendingRotationDegrees; - private float pendingPixelWidthHeightRatio; - private int currentWidth; - private int currentHeight; - private int currentUnappliedRotationDegrees; - private float currentPixelWidthHeightRatio; - private int lastReportedWidth; - private int lastReportedHeight; - private int lastReportedUnappliedRotationDegrees; - private float lastReportedPixelWidthHeightRatio; - - /** - * @param context A context. - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param videoScalingMode The scaling mode to pass to - * {@link MediaCodec#setVideoScalingMode(int)}. - */ - public MediaCodecVideoTrackRenderer(Context context, SampleSource source, - MediaCodecSelector mediaCodecSelector, int videoScalingMode) { - this(context, source, mediaCodecSelector, videoScalingMode, 0); - } - - /** - * @param context A context. - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param videoScalingMode The scaling mode to pass to - * {@link MediaCodec#setVideoScalingMode(int)}. - * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer - * can attempt to seamlessly join an ongoing playback. - */ - public MediaCodecVideoTrackRenderer(Context context, SampleSource source, - MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs) { - this(context, source, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, null, - -1); - } - - /** - * @param context A context. - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param videoScalingMode The scaling mode to pass to - * {@link MediaCodec#setVideoScalingMode(int)}. - * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer - * can attempt to seamlessly join an ongoing playback. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between - * invocations of {@link EventListener#onDroppedFrames(int, long)}. - */ - public MediaCodecVideoTrackRenderer(Context context, SampleSource source, - MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs, - Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) { - this(context, source, mediaCodecSelector, videoScalingMode, allowedJoiningTimeMs, null, false, - eventHandler, eventListener, maxDroppedFrameCountToNotify); - } - - /** - * @param context A context. - * @param source The upstream source from which the renderer obtains samples. - * @param mediaCodecSelector A decoder selector. - * @param videoScalingMode The scaling mode to pass to - * {@link MediaCodec#setVideoScalingMode(int)}. - * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer - * can attempt to seamlessly join an ongoing playback. - * @param drmSessionManager For use with encrypted content. May be null if support for encrypted - * content is not required. - * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. - * For example a media file may start with a short clear region so as to allow playback to - * begin in parallel with key acquisision. This parameter specifies whether the renderer is - * permitted to play clear regions of encrypted media files before {@code drmSessionManager} - * has obtained the keys necessary to decrypt encrypted regions of the media. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between - * invocations of {@link EventListener#onDroppedFrames(int, long)}. - */ - public MediaCodecVideoTrackRenderer(Context context, SampleSource source, - MediaCodecSelector mediaCodecSelector, int videoScalingMode, long allowedJoiningTimeMs, - DrmSessionManager drmSessionManager, - boolean playClearSamplesWithoutKeys, Handler eventHandler, EventListener eventListener, - int maxDroppedFrameCountToNotify) { - super(source, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, - eventListener); - this.frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); - this.videoScalingMode = videoScalingMode; - this.allowedJoiningTimeUs = allowedJoiningTimeMs * 1000; - this.eventListener = eventListener; - this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify; - joiningDeadlineUs = -1; - currentWidth = -1; - currentHeight = -1; - currentPixelWidthHeightRatio = -1; - pendingPixelWidthHeightRatio = -1; - lastReportedWidth = -1; - lastReportedHeight = -1; - lastReportedPixelWidthHeightRatio = -1; - } - - @Override - protected boolean handlesTrack(MediaCodecSelector mediaCodecSelector, MediaFormat mediaFormat) - throws DecoderQueryException { - String mimeType = mediaFormat.mimeType; - return MimeTypes.isVideo(mimeType) && (MimeTypes.VIDEO_UNKNOWN.equals(mimeType) - || mediaCodecSelector.getDecoderInfo(mimeType, false) != null); - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - if (joining && allowedJoiningTimeUs > 0) { - joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs; - } - frameReleaseTimeHelper.enable(); - } - - @Override - protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { - super.onDiscontinuity(positionUs); - renderedFirstFrame = false; - consecutiveDroppedFrameCount = 0; - joiningDeadlineUs = -1; - } - - @Override - protected boolean isReady() { - if (super.isReady() && (renderedFirstFrame || !codecInitialized() - || getSourceState() == SOURCE_STATE_READY_READ_MAY_FAIL)) { - // Ready. If we were joining then we've now joined, so clear the joining deadline. - joiningDeadlineUs = -1; - return true; - } else if (joiningDeadlineUs == -1) { - // Not joining. - return false; - } else if (SystemClock.elapsedRealtime() * 1000 < joiningDeadlineUs) { - // Joining and still within the joining deadline. - return true; - } else { - // The joining deadline has been exceeded. Give up and clear the deadline. - joiningDeadlineUs = -1; - return false; - } - } - - @Override - protected void onStarted() { - super.onStarted(); - droppedFrameCount = 0; - droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); - } - - @Override - protected void onStopped() { - joiningDeadlineUs = -1; - maybeNotifyDroppedFrameCount(); - super.onStopped(); - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - currentWidth = -1; - currentHeight = -1; - currentPixelWidthHeightRatio = -1; - pendingPixelWidthHeightRatio = -1; - lastReportedWidth = -1; - lastReportedHeight = -1; - lastReportedPixelWidthHeightRatio = -1; - frameReleaseTimeHelper.disable(); - super.onDisabled(); - } - - @Override - public void handleMessage(int messageType, Object message) throws ExoPlaybackException { - if (messageType == MSG_SET_SURFACE) { - setSurface((Surface) message); - } else { - super.handleMessage(messageType, message); - } - } - - /** - * @param surface The surface to set. - * @throws ExoPlaybackException - */ - private void setSurface(Surface surface) throws ExoPlaybackException { - if (this.surface == surface) { - return; - } - this.surface = surface; - this.reportedDrawnToSurface = false; - int state = getState(); - if (state == TrackRenderer.STATE_ENABLED || state == TrackRenderer.STATE_STARTED) { - releaseCodec(); - maybeInitCodec(); - } - } - - @Override - protected boolean shouldInitCodec() { - return super.shouldInitCodec() && surface != null && surface.isValid(); - } - - // Override configureCodec to provide the surface. - @Override - protected void configureCodec(MediaCodec codec, boolean codecIsAdaptive, - android.media.MediaFormat format, MediaCrypto crypto) { - maybeSetMaxInputSize(format, codecIsAdaptive); - codec.configure(format, surface, crypto, 0); - } - - @Override - protected void onInputFormatChanged(MediaFormatHolder holder) throws ExoPlaybackException { - super.onInputFormatChanged(holder); - pendingPixelWidthHeightRatio = holder.format.pixelWidthHeightRatio == MediaFormat.NO_VALUE ? 1 - : holder.format.pixelWidthHeightRatio; - pendingRotationDegrees = holder.format.rotationDegrees == MediaFormat.NO_VALUE ? 0 - : holder.format.rotationDegrees; - } - - /** - * @return True if the first frame has been rendered (playback has not necessarily begun). - */ - protected final boolean haveRenderedFirstFrame() { - return renderedFirstFrame; - } - - @Override - protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { - boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) - && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) - && outputFormat.containsKey(KEY_CROP_TOP); - currentWidth = hasCrop - ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1 - : outputFormat.getInteger(android.media.MediaFormat.KEY_WIDTH); - currentHeight = hasCrop - ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1 - : outputFormat.getInteger(android.media.MediaFormat.KEY_HEIGHT); - currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio; - if (Util.SDK_INT >= 21) { - // On API level 21 and above the decoder applies the rotation when rendering to the surface. - // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need - // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. - if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) { - int rotatedHeight = currentWidth; - currentWidth = currentHeight; - currentHeight = rotatedHeight; - currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio; - } - } else { - // On API level 20 and below the decoder does not apply the rotation. - currentUnappliedRotationDegrees = pendingRotationDegrees; - } - // Must be applied each time the output format changes. - codec.setVideoScalingMode(videoScalingMode); - } - - @Override - protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, - MediaFormat oldFormat, MediaFormat newFormat) { - return newFormat.mimeType.equals(oldFormat.mimeType) - && (codecIsAdaptive - || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)); - } - - @Override - protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, - ByteBuffer buffer, MediaCodec.BufferInfo bufferInfo, int bufferIndex, boolean shouldSkip) { - if (shouldSkip) { - skipOutputBuffer(codec, bufferIndex); - consecutiveDroppedFrameCount = 0; - return true; - } - - if (!renderedFirstFrame) { - if (Util.SDK_INT >= 21) { - renderOutputBufferV21(codec, bufferIndex, System.nanoTime()); - } else { - renderOutputBuffer(codec, bufferIndex); - } - consecutiveDroppedFrameCount = 0; - return true; - } - - if (getState() != TrackRenderer.STATE_STARTED) { - return false; - } - - // Compute how many microseconds it is until the buffer's presentation time. - long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; - long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; - - // Compute the buffer's desired release time in nanoseconds. - long systemTimeNs = System.nanoTime(); - long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000); - - // Apply a timestamp adjustment, if there is one. - long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime( - bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs); - earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; - - if (earlyUs < -30000) { - // We're more than 30ms late rendering the frame. - dropOutputBuffer(codec, bufferIndex); - return true; - } - - if (Util.SDK_INT >= 21) { - // Let the underlying framework time the release. - if (earlyUs < 50000) { - renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs); - consecutiveDroppedFrameCount = 0; - return true; - } - } else { - // We need to time the release ourselves. - if (earlyUs < 30000) { - if (earlyUs > 11000) { - // We're a little too early to render the frame. Sleep until the frame can be rendered. - // Note: The 11ms threshold was chosen fairly arbitrarily. - try { - // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms. - Thread.sleep((earlyUs - 10000) / 1000); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - renderOutputBuffer(codec, bufferIndex); - consecutiveDroppedFrameCount = 0; - return true; - } - } - - // We're either not playing, or it's not time to render the frame yet. - return false; - } - - protected void skipOutputBuffer(MediaCodec codec, int bufferIndex) { - TraceUtil.beginSection("skipVideoBuffer"); - codec.releaseOutputBuffer(bufferIndex, false); - TraceUtil.endSection(); - codecCounters.skippedOutputBufferCount++; - } - - protected void dropOutputBuffer(MediaCodec codec, int bufferIndex) { - TraceUtil.beginSection("dropVideoBuffer"); - codec.releaseOutputBuffer(bufferIndex, false); - TraceUtil.endSection(); - codecCounters.droppedOutputBufferCount++; - droppedFrameCount++; - consecutiveDroppedFrameCount++; - codecCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount, - codecCounters.maxConsecutiveDroppedOutputBufferCount); - if (droppedFrameCount == maxDroppedFrameCountToNotify) { - maybeNotifyDroppedFrameCount(); - } - } - - protected void renderOutputBuffer(MediaCodec codec, int bufferIndex) { - maybeNotifyVideoSizeChanged(); - TraceUtil.beginSection("releaseOutputBuffer"); - codec.releaseOutputBuffer(bufferIndex, true); - TraceUtil.endSection(); - codecCounters.renderedOutputBufferCount++; - renderedFirstFrame = true; - maybeNotifyDrawnToSurface(); - } - - @TargetApi(21) - protected void renderOutputBufferV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) { - maybeNotifyVideoSizeChanged(); - TraceUtil.beginSection("releaseOutputBuffer"); - codec.releaseOutputBuffer(bufferIndex, releaseTimeNs); - TraceUtil.endSection(); - codecCounters.renderedOutputBufferCount++; - renderedFirstFrame = true; - maybeNotifyDrawnToSurface(); - } - - @SuppressLint("InlinedApi") - private void maybeSetMaxInputSize(android.media.MediaFormat format, boolean codecIsAdaptive) { - if (format.containsKey(android.media.MediaFormat.KEY_MAX_INPUT_SIZE)) { - // Already set. The source of the format may know better, so do nothing. - return; - } - int maxHeight = format.getInteger(android.media.MediaFormat.KEY_HEIGHT); - if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_HEIGHT)) { - maxHeight = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_HEIGHT)); - } - int maxWidth = format.getInteger(android.media.MediaFormat.KEY_WIDTH); - if (codecIsAdaptive && format.containsKey(android.media.MediaFormat.KEY_MAX_WIDTH)) { - maxWidth = Math.max(maxHeight, format.getInteger(android.media.MediaFormat.KEY_MAX_WIDTH)); - } - int maxPixels; - int minCompressionRatio; - switch (format.getString(android.media.MediaFormat.KEY_MIME)) { - case MimeTypes.VIDEO_H263: - case MimeTypes.VIDEO_MP4V: - maxPixels = maxWidth * maxHeight; - minCompressionRatio = 2; - break; - case MimeTypes.VIDEO_H264: - if ("BRAVIA 4K 2015".equals(Util.MODEL)) { - // The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video - // maximum input size, so use the default value. - return; - } - // Round up width/height to an integer number of macroblocks. - maxPixels = ((maxWidth + 15) / 16) * ((maxHeight + 15) / 16) * 16 * 16; - minCompressionRatio = 2; - break; - case MimeTypes.VIDEO_VP8: - // VPX does not specify a ratio so use the values from the platform's SoftVPX.cpp. - maxPixels = maxWidth * maxHeight; - minCompressionRatio = 2; - break; - case MimeTypes.VIDEO_H265: - case MimeTypes.VIDEO_VP9: - maxPixels = maxWidth * maxHeight; - minCompressionRatio = 4; - break; - default: - // Leave the default max input size. - return; - } - // Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames. - int maxInputSize = (maxPixels * 3) / (2 * minCompressionRatio); - format.setInteger(android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); - } - - private void maybeNotifyVideoSizeChanged() { - if (eventHandler == null || eventListener == null - || (lastReportedWidth == currentWidth && lastReportedHeight == currentHeight - && lastReportedUnappliedRotationDegrees == currentUnappliedRotationDegrees - && lastReportedPixelWidthHeightRatio == currentPixelWidthHeightRatio)) { - return; - } - // Make final copies to ensure the runnable reports the correct values. - final int currentWidth = this.currentWidth; - final int currentHeight = this.currentHeight; - final int currentUnappliedRotationDegrees = this.currentUnappliedRotationDegrees; - final float currentPixelWidthHeightRatio = this.currentPixelWidthHeightRatio; - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onVideoSizeChanged(currentWidth, currentHeight, - currentUnappliedRotationDegrees, currentPixelWidthHeightRatio); - } - }); - // Update the last reported values. - lastReportedWidth = currentWidth; - lastReportedHeight = currentHeight; - lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; - lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; - } - - private void maybeNotifyDrawnToSurface() { - if (eventHandler == null || eventListener == null || reportedDrawnToSurface) { - return; - } - // Make a final copy to ensure the runnable reports the correct surface. - final Surface surface = this.surface; - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDrawnToSurface(surface); - } - }); - // Record that we have reported that the surface has been drawn to. - reportedDrawnToSurface = true; - } - - private void maybeNotifyDroppedFrameCount() { - if (eventHandler == null || eventListener == null || droppedFrameCount == 0) { - return; - } - long now = SystemClock.elapsedRealtime(); - // Make final copies to ensure the runnable reports the correct values. - final int countToNotify = droppedFrameCount; - final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs; - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDroppedFrames(countToNotify, elapsedToNotify); - } - }); - // Reset the dropped frame tracking. - droppedFrameCount = 0; - droppedFrameAccumulationStartTimeMs = now; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormat.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormat.java deleted file mode 100755 index e791469b181..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormat.java +++ /dev/null @@ -1,500 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.os.Parcel; -import android.os.Parcelable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * Defines the format of an elementary media stream. - */ -public final class MediaFormat implements Parcelable { - - public static final int NO_VALUE = -1; - - /** - * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to - * the timestamps of their parent samples. - */ - public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; - - /** - * The identifier for the track represented by the format, or null if unknown or not applicable. - */ - public final String trackId; - /** - * The mime type of the format. - */ - public final String mimeType; - /** - * The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable. - */ - public final int bitrate; - /** - * The maximum size of a buffer of data (typically one sample) in the format, or {@link #NO_VALUE} - * if unknown or not applicable. - */ - public final int maxInputSize; - /** - * The duration in microseconds, or {@link C#UNKNOWN_TIME_US} if the duration is unknown, or - * {@link C#MATCH_LONGEST_US} if the duration should match the duration of the longest track whose - * duration is known. - */ - public final long durationUs; - /** - * Initialization data that must be provided to the decoder. Will not be null, but may be empty - * if initialization data is not required. - */ - public final List initializationData; - /** - * Whether the format represents an adaptive track, meaning that the format of the actual media - * data may change (e.g. to adapt to network conditions). - */ - public final boolean adaptive; - - // Video specific. - - /** - * The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. - */ - public final int width; - - /** - * The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. - */ - public final int height; - /** - * For formats that belong to an adaptive video track (either describing the track, or describing - * a specific format within it), this is the maximum width of the video in pixels that will be - * encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable. - */ - public final int maxWidth; - /** - * For formats that belong to an adaptive video track (either describing the track, or describing - * a specific format within it), this is the maximum height of the video in pixels that will be - * encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable. - */ - public final int maxHeight; - /** - * The clockwise rotation that should be applied to the video for it to be rendered in the correct - * orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are - * supported. - */ - public final int rotationDegrees; - /** - * The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not - * applicable. - */ - public final float pixelWidthHeightRatio; - - // Audio specific. - - /** - * The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable. - */ - public final int channelCount; - /** - * The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. - */ - public final int sampleRate; - /** - * The encoding for PCM audio streams. If {@link #mimeType} is {@link MimeTypes#AUDIO_RAW} then - * one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} - * and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for other media types. - */ - public final int pcmEncoding; - /** - * The number of samples to trim from the start of the decoded audio stream. - */ - public final int encoderDelay; - /** - * The number of samples to trim from the end of the decoded audio stream. - */ - public final int encoderPadding; - - // Text specific. - - /** - * The language of the track, or null if unknown or not applicable. - */ - public final String language; - - /** - * For samples that contain subsamples, this is an offset that should be added to subsample - * timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are - * relative to the timestamps of their parent samples. - */ - public final long subsampleOffsetUs; - - // Lazy-initialized hashcode and framework media format. - - private int hashCode; - private android.media.MediaFormat frameworkMediaFormat; - - public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate, - int maxInputSize, long durationUs, int width, int height, List initializationData) { - return createVideoFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - initializationData, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createVideoFormat(String trackId, String mimeType, int bitrate, - int maxInputSize, long durationUs, int width, int height, List initializationData, - int rotationDegrees, float pixelWidthHeightRatio) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, - initializationData, false, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate, - int maxInputSize, long durationUs, int channelCount, int sampleRate, - List initializationData, String language) { - return createAudioFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, channelCount, - sampleRate, initializationData, language, NO_VALUE); - } - - public static MediaFormat createAudioFormat(String trackId, String mimeType, int bitrate, - int maxInputSize, long durationUs, int channelCount, int sampleRate, - List initializationData, String language, int pcmEncoding) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, channelCount, sampleRate, language, OFFSET_SAMPLE_RELATIVE, - initializationData, false, NO_VALUE, NO_VALUE, pcmEncoding, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate, - long durationUs, String language) { - return createTextFormat(trackId, mimeType, bitrate, durationUs, language, - OFFSET_SAMPLE_RELATIVE); - } - - public static MediaFormat createTextFormat(String trackId, String mimeType, int bitrate, - long durationUs, String language, long subsampleOffsetUs) { - return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createImageFormat(String trackId, String mimeType, int bitrate, - long durationUs, List initializationData, String language) { - return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, OFFSET_SAMPLE_RELATIVE, - initializationData, false, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createFormatForMimeType(String trackId, String mimeType, int bitrate, - long durationUs) { - return new MediaFormat(trackId, mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE); - } - - public static MediaFormat createId3Format() { - return createFormatForMimeType(null, MimeTypes.APPLICATION_ID3, MediaFormat.NO_VALUE, - C.UNKNOWN_TIME_US); - } - - /* package */ MediaFormat(Parcel in) { - trackId = in.readString(); - mimeType = in.readString(); - bitrate = in.readInt(); - maxInputSize = in.readInt(); - durationUs = in.readLong(); - width = in.readInt(); - height = in.readInt(); - rotationDegrees = in.readInt(); - pixelWidthHeightRatio = in.readFloat(); - channelCount = in.readInt(); - sampleRate = in.readInt(); - language = in.readString(); - subsampleOffsetUs = in.readLong(); - initializationData = new ArrayList<>(); - in.readList(initializationData, null); - adaptive = in.readInt() == 1; - maxWidth = in.readInt(); - maxHeight = in.readInt(); - pcmEncoding = in.readInt(); - encoderDelay = in.readInt(); - encoderPadding = in.readInt(); - } - - /* package */ MediaFormat(String trackId, String mimeType, int bitrate, int maxInputSize, - long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio, - int channelCount, int sampleRate, String language, long subsampleOffsetUs, - List initializationData, boolean adaptive, int maxWidth, int maxHeight, - int pcmEncoding, int encoderDelay, int encoderPadding) { - this.trackId = trackId; - this.mimeType = Assertions.checkNotEmpty(mimeType); - this.bitrate = bitrate; - this.maxInputSize = maxInputSize; - this.durationUs = durationUs; - this.width = width; - this.height = height; - this.rotationDegrees = rotationDegrees; - this.pixelWidthHeightRatio = pixelWidthHeightRatio; - this.channelCount = channelCount; - this.sampleRate = sampleRate; - this.language = language; - this.subsampleOffsetUs = subsampleOffsetUs; - this.initializationData = initializationData == null ? Collections.emptyList() - : initializationData; - this.adaptive = adaptive; - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.pcmEncoding = pcmEncoding; - this.encoderDelay = encoderDelay; - this.encoderPadding = encoderPadding; - } - - public MediaFormat copyWithMaxInputSize(int maxInputSize) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyWithSubsampleOffsetUs(long subsampleOffsetUs) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyWithDurationUs(long durationUs) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyWithLanguage(String language) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyWithFixedTrackInfo(String trackId, int bitrate, int width, int height, - String language) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, NO_VALUE, NO_VALUE, pcmEncoding, - encoderDelay, encoderPadding); - } - - public MediaFormat copyAsAdaptive(String trackId) { - return new MediaFormat(trackId, mimeType, NO_VALUE, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth, - maxHeight, NO_VALUE, NO_VALUE, NO_VALUE); - } - - public MediaFormat copyWithGaplessInfo(int encoderDelay, int encoderPadding) { - return new MediaFormat(trackId, mimeType, bitrate, maxInputSize, durationUs, width, height, - rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, - subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight, pcmEncoding, - encoderDelay, encoderPadding); - } - - /** - * @return A {@link MediaFormat} representation of this format. - */ - @SuppressLint("InlinedApi") - @TargetApi(16) - public final android.media.MediaFormat getFrameworkMediaFormatV16() { - if (frameworkMediaFormat == null) { - android.media.MediaFormat format = new android.media.MediaFormat(); - format.setString(android.media.MediaFormat.KEY_MIME, mimeType); - maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); - maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); - maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); - maybeSetIntegerV16(format, "encoder-delay", encoderDelay); - maybeSetIntegerV16(format, "encoder-padding", encoderPadding); - for (int i = 0; i < initializationData.size(); i++) { - format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); - } - if (durationUs != C.UNKNOWN_TIME_US) { - format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs); - } - frameworkMediaFormat = format; - } - return frameworkMediaFormat; - } - - /** - * Sets the framework format returned by {@link #getFrameworkMediaFormatV16()}. - * - * @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated. - * @param format The framework format. - */ - @Deprecated - @TargetApi(16) - /* package */ final void setFrameworkFormatV16(android.media.MediaFormat format) { - frameworkMediaFormat = format; - } - - @Override - public String toString() { - return "MediaFormat(" + trackId + ", " + mimeType + ", " + bitrate + ", " + maxInputSize - + ", " + width + ", " + height + ", " + rotationDegrees + ", " + pixelWidthHeightRatio - + ", " + channelCount + ", " + sampleRate + ", " + language + ", " + durationUs + ", " - + adaptive + ", " + maxWidth + ", " + maxHeight + ", " + pcmEncoding + ", " + encoderDelay - + ", " + encoderPadding + ")"; - } - - @Override - public int hashCode() { - if (hashCode == 0) { - int result = 17; - result = 31 * result + (trackId == null ? 0 : trackId.hashCode()); - result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode()); - result = 31 * result + bitrate; - result = 31 * result + maxInputSize; - result = 31 * result + width; - result = 31 * result + height; - result = 31 * result + rotationDegrees; - result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio); - result = 31 * result + (int) durationUs; - result = 31 * result + (adaptive ? 1231 : 1237); - result = 31 * result + maxWidth; - result = 31 * result + maxHeight; - result = 31 * result + channelCount; - result = 31 * result + sampleRate; - result = 31 * result + pcmEncoding; - result = 31 * result + encoderDelay; - result = 31 * result + encoderPadding; - result = 31 * result + (language == null ? 0 : language.hashCode()); - result = 31 * result + (int) subsampleOffsetUs; - for (int i = 0; i < initializationData.size(); i++) { - result = 31 * result + Arrays.hashCode(initializationData.get(i)); - } - hashCode = result; - } - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - MediaFormat other = (MediaFormat) obj; - if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize - || durationUs != other.durationUs || width != other.width || height != other.height - || rotationDegrees != other.rotationDegrees - || pixelWidthHeightRatio != other.pixelWidthHeightRatio - || maxWidth != other.maxWidth || maxHeight != other.maxHeight - || channelCount != other.channelCount || sampleRate != other.sampleRate - || pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay - || encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs - || !Util.areEqual(trackId, other.trackId) || !Util.areEqual(language, other.language) - || !Util.areEqual(mimeType, other.mimeType) - || initializationData.size() != other.initializationData.size()) { - return false; - } - for (int i = 0; i < initializationData.size(); i++) { - if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) { - return false; - } - } - return true; - } - - @TargetApi(16) - private static final void maybeSetStringV16(android.media.MediaFormat format, String key, - String value) { - if (value != null) { - format.setString(key, value); - } - } - - @TargetApi(16) - private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key, - int value) { - if (value != NO_VALUE) { - format.setInteger(key, value); - } - } - - // Parcelable implementation. - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(trackId); - dest.writeString(mimeType); - dest.writeInt(bitrate); - dest.writeInt(maxInputSize); - dest.writeLong(durationUs); - dest.writeInt(width); - dest.writeInt(height); - dest.writeInt(rotationDegrees); - dest.writeFloat(pixelWidthHeightRatio); - dest.writeInt(channelCount); - dest.writeInt(sampleRate); - dest.writeString(language); - dest.writeLong(subsampleOffsetUs); - dest.writeList(initializationData); - dest.writeInt(adaptive ? 1 : 0); - dest.writeInt(maxWidth); - dest.writeInt(maxHeight); - dest.writeInt(pcmEncoding); - dest.writeInt(encoderDelay); - dest.writeInt(encoderPadding); - } - - public static final Creator CREATOR = new Creator() { - - @Override - public MediaFormat createFromParcel(Parcel in) { - return new MediaFormat(in); - } - - @Override - public MediaFormat[] newArray(int size) { - return new MediaFormat[size]; - } - - }; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormatHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormatHolder.java deleted file mode 100755 index 2092c851186..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaFormatHolder.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import org.telegram.messenger.exoplayer.drm.DrmInitData; - -/** - * Holds a {@link MediaFormat} and corresponding drm scheme initialization data. - */ -public final class MediaFormatHolder { - - /** - * The format of the media. - */ - public MediaFormat format; - /** - * Initialization data for drm schemes supported by the media. Null if the media is not encrypted. - */ - public DrmInitData drmInitData; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ParserException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ParserException.java deleted file mode 100755 index aa2bfe7f260..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/ParserException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import java.io.IOException; - -/** - * Thrown when an error occurs parsing media data. - */ -public class ParserException extends IOException { - - public ParserException() { - super(); - } - - public ParserException(String message) { - super(message); - } - - public ParserException(Throwable cause) { - super(cause); - } - - public ParserException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleHolder.java deleted file mode 100755 index e8ae0e36a36..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleHolder.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import java.nio.ByteBuffer; - -/** - * Holds sample data and corresponding metadata. - */ -public final class SampleHolder { - - /** - * Disallows buffer replacement. - */ - public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; - - /** - * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. - */ - public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; - - /** - * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. - */ - public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2; - - public final CryptoInfo cryptoInfo; - - /** - * A buffer holding the sample data. - */ - public ByteBuffer data; - - /** - * The size of the sample in bytes. - */ - public int size; - - /** - * Flags that accompany the sample. A combination of {@link C#SAMPLE_FLAG_SYNC}, - * {@link C#SAMPLE_FLAG_ENCRYPTED} and {@link C#SAMPLE_FLAG_DECODE_ONLY}. - */ - public int flags; - - /** - * The time at which the sample should be presented. - */ - public long timeUs; - - private final int bufferReplacementMode; - - /** - * @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One - * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and - * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. - */ - public SampleHolder(int bufferReplacementMode) { - this.cryptoInfo = new CryptoInfo(); - this.bufferReplacementMode = bufferReplacementMode; - } - - /** - * Ensures that {@link #data} is large enough to accommodate a write of a given length at its - * current position. - *

        - * If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is - * insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer} - * whose capacity is sufficient. Data up to the current position is copied to the new buffer. - * - * @param length The length of the write that must be accommodated, in bytes. - * @throws IllegalStateException If there is insufficient capacity to accommodate the write and - * the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. - */ - public void ensureSpaceForWrite(int length) throws IllegalStateException { - if (data == null) { - data = createReplacementBuffer(length); - return; - } - // Check whether the current buffer is sufficient. - int capacity = data.capacity(); - int position = data.position(); - int requiredCapacity = position + length; - if (capacity >= requiredCapacity) { - return; - } - // Instantiate a new buffer if possible. - ByteBuffer newData = createReplacementBuffer(requiredCapacity); - // Copy data up to the current position from the old buffer to the new one. - if (position > 0) { - data.position(0); - data.limit(position); - newData.put(data); - } - // Set the new buffer. - data = newData; - } - - /** - * Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_ENCRYPTED} set. - */ - public boolean isEncrypted() { - return (flags & C.SAMPLE_FLAG_ENCRYPTED) != 0; - } - - /** - * Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_DECODE_ONLY} set. - */ - public boolean isDecodeOnly() { - return (flags & C.SAMPLE_FLAG_DECODE_ONLY) != 0; - } - - /** - * Returns whether {@link #flags} has {@link C#SAMPLE_FLAG_SYNC} set. - */ - public boolean isSyncFrame() { - return (flags & C.SAMPLE_FLAG_SYNC) != 0; - } - - /** - * Clears {@link #data}. Does nothing if {@link #data} is null. - */ - public void clearData() { - if (data != null) { - data.clear(); - } - } - - private ByteBuffer createReplacementBuffer(int requiredCapacity) { - if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) { - return ByteBuffer.allocate(requiredCapacity); - } else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) { - return ByteBuffer.allocateDirect(requiredCapacity); - } else { - int currentCapacity = data == null ? 0 : data.capacity(); - throw new IllegalStateException("Buffer too small (" + currentCapacity + " < " - + requiredCapacity + ")"); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSource.java deleted file mode 100755 index 255820c4932..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSource.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import java.io.IOException; - -/** - * A source of media samples. - *

        - * A {@link SampleSource} may expose one or multiple tracks. The number of tracks and each track's - * media format can be queried using {@link SampleSourceReader#getTrackCount()} and - * {@link SampleSourceReader#getFormat(int)} respectively. - */ -public interface SampleSource { - - /** - * The end of stream has been reached. - */ - public static final int END_OF_STREAM = -1; - /** - * Neither a sample nor a format was read in full. This may be because insufficient data is - * buffered upstream. If multiple tracks are enabled, this return value may indicate that the - * next piece of data to be returned from the {@link SampleSource} corresponds to a different - * track than the one for which data was requested. - */ - public static final int NOTHING_READ = -2; - /** - * A sample was read. - */ - public static final int SAMPLE_READ = -3; - /** - * A format was read. - */ - public static final int FORMAT_READ = -4; - /** - * Returned from {@link SampleSourceReader#readDiscontinuity(int)} to indicate no discontinuity. - */ - public static final long NO_DISCONTINUITY = Long.MIN_VALUE; - - /** - * A consumer of samples should call this method to register themselves and gain access to the - * source through the returned {@link SampleSourceReader}. - *

        - * {@link SampleSourceReader#release()} should be called on the returned object when access is no - * longer required. - * - * @return A {@link SampleSourceReader} that provides access to the source. - */ - public SampleSourceReader register(); - - /** - * An interface providing read access to a {@link SampleSource}. - */ - public interface SampleSourceReader { - - /** - * If the source is currently having difficulty preparing or loading samples, then this method - * throws the underlying error. Otherwise does nothing. - * - * @throws IOException The underlying error. - */ - public void maybeThrowError() throws IOException; - - /** - * Prepares the source. - *

        - * Preparation may require reading from the data source (e.g. to determine the available tracks - * and formats). If insufficient data is available then the call will return {@code false} - * rather than block. The method can be called repeatedly until the return value indicates - * success. - * - * @param positionUs The player's current playback position. - * @return True if the source was prepared, false otherwise. - */ - public boolean prepare(long positionUs); - - /** - * Returns the number of tracks exposed by the source. - *

        - * This method should only be called after the source has been prepared. - * - * @return The number of tracks. - */ - public int getTrackCount(); - - /** - * Returns the format of the specified track. - *

        - * Note that whilst the format of a track will remain constant, the format of the actual media - * stream may change dynamically. An example of this is where the track is adaptive - * (i.e. @link {@link MediaFormat#adaptive} is true). Hence the track formats returned through - * this method should not be used to configure decoders. Decoder configuration should be - * performed using the formats obtained when reading the media stream through calls to - * {@link #readData(int, long, MediaFormatHolder, SampleHolder)}. - *

        - * This method should only be called after the source has been prepared. - * - * @param track The track index. - * @return The format of the specified track. - */ - public MediaFormat getFormat(int track); - - /** - * Enable the specified track. This allows the track's format and samples to be read from - * {@link #readData(int, long, MediaFormatHolder, SampleHolder)}. - *

        - * This method should only be called after the source has been prepared, and when the specified - * track is disabled. - * - * @param track The track to enable. - * @param positionUs The player's current playback position. - */ - public void enable(int track, long positionUs); - - /** - * Indicates to the source that it should still be buffering data for the specified track. - *

        - * This method should only be called when the specified track is enabled. - * - * @param track The track to continue buffering. - * @param positionUs The current playback position. - * @return True if the track has available samples, or if the end of the stream has been - * reached. False if more data needs to be buffered for samples to become available. - */ - public boolean continueBuffering(int track, long positionUs); - - /** - * Attempts to read a pending discontinuity from the source. - *

        - * This method should only be called when the specified track is enabled. - * - * @param track The track from which to read. - * @return If a discontinuity was read then the playback position after the discontinuity. Else - * {@link #NO_DISCONTINUITY}. - */ - public long readDiscontinuity(int track); - - /** - * Attempts to read a sample or a new format from the source. - *

        - * This method should only be called when the specified track is enabled. - *

        - * Note that where multiple tracks are enabled, {@link #NOTHING_READ} may be returned if the - * next piece of data to be read from the {@link SampleSource} corresponds to a different track - * than the one for which data was requested. - *

        - * This method will always return {@link #NOTHING_READ} in the case that there's a pending - * discontinuity to be read from {@link #readDiscontinuity(int)} for the specified track. - * - * @param track The track from which to read. - * @param positionUs The current playback position. - * @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new - * format. - * @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. - * If the caller requires the sample data then it must ensure that {@link SampleHolder#data} - * references a valid output buffer. - * @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ}, - * {@link #NOTHING_READ} or {@link #END_OF_STREAM}. - */ - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder); - - /** - * Seeks to the specified time in microseconds. - *

        - * This method should only be called when at least one track is enabled. - * - * @param positionUs The seek position in microseconds. - */ - public void seekToUs(long positionUs); - - /** - * Returns an estimate of the position up to which data is buffered. - *

        - * This method should only be called when at least one track is enabled. - * - * @return An estimate of the absolute position in microseconds up to which data is buffered, - * or {@link TrackRenderer#END_OF_TRACK_US} if data is buffered to the end of the stream, - * or {@link TrackRenderer#UNKNOWN_TIME_US} if no estimate is available. - */ - public long getBufferedPositionUs(); - - /** - * Disable the specified track. - *

        - * This method should only be called when the specified track is enabled. - * - * @param track The track to disable. - */ - public void disable(int track); - - /** - * Releases the {@link SampleSourceReader}. - *

        - * This method should be called when access to the {@link SampleSource} is no longer required. - */ - public void release(); - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSourceTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSourceTrackRenderer.java deleted file mode 100755 index b10e3727d1c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SampleSourceTrackRenderer.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import java.io.IOException; -import java.util.Arrays; - -/** - * Base class for {@link TrackRenderer} implementations that render samples obtained from a - * {@link SampleSource}. - */ -public abstract class SampleSourceTrackRenderer extends TrackRenderer { - - private final SampleSourceReader[] sources; - - private int[] handledSourceIndices; - private int[] handledSourceTrackIndices; - - private SampleSourceReader enabledSource; - private int enabledSourceTrackIndex; - - private long durationUs; - - /** - * @param sources One or more upstream sources from which the renderer can obtain samples. - */ - public SampleSourceTrackRenderer(SampleSource... sources) { - this.sources = new SampleSourceReader[sources.length]; - for (int i = 0; i < sources.length; i++) { - this.sources[i] = sources[i].register(); - } - } - - @Override - protected final boolean doPrepare(long positionUs) throws ExoPlaybackException { - boolean allSourcesPrepared = true; - for (int i = 0; i < sources.length; i++) { - allSourcesPrepared &= sources[i].prepare(positionUs); - } - if (!allSourcesPrepared) { - return false; - } - // The sources are all prepared. - int totalSourceTrackCount = 0; - for (int i = 0; i < sources.length; i++) { - totalSourceTrackCount += sources[i].getTrackCount(); - } - long durationUs = 0; - int handledTrackCount = 0; - int[] handledSourceIndices = new int[totalSourceTrackCount]; - int[] handledTrackIndices = new int[totalSourceTrackCount]; - int sourceCount = sources.length; - for (int sourceIndex = 0; sourceIndex < sourceCount; sourceIndex++) { - SampleSourceReader source = sources[sourceIndex]; - int sourceTrackCount = source.getTrackCount(); - for (int trackIndex = 0; trackIndex < sourceTrackCount; trackIndex++) { - MediaFormat format = source.getFormat(trackIndex); - boolean handlesTrack; - try { - handlesTrack = handlesTrack(format); - } catch (DecoderQueryException e) { - throw new ExoPlaybackException(e); - } - if (handlesTrack) { - handledSourceIndices[handledTrackCount] = sourceIndex; - handledTrackIndices[handledTrackCount] = trackIndex; - handledTrackCount++; - if (durationUs == TrackRenderer.UNKNOWN_TIME_US) { - // We've already encountered a track for which the duration is unknown, so the media - // duration is unknown regardless of the duration of this track. - } else { - long trackDurationUs = format.durationUs; - if (trackDurationUs == TrackRenderer.UNKNOWN_TIME_US) { - durationUs = TrackRenderer.UNKNOWN_TIME_US; - } else if (trackDurationUs == TrackRenderer.MATCH_LONGEST_US) { - // Do nothing. - } else { - durationUs = Math.max(durationUs, trackDurationUs); - } - } - } - } - } - this.durationUs = durationUs; - this.handledSourceIndices = Arrays.copyOf(handledSourceIndices, handledTrackCount); - this.handledSourceTrackIndices = Arrays.copyOf(handledTrackIndices, handledTrackCount); - return true; - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - positionUs = shiftInputPosition(positionUs); - enabledSource = sources[handledSourceIndices[track]]; - enabledSourceTrackIndex = handledSourceTrackIndices[track]; - enabledSource.enable(enabledSourceTrackIndex, positionUs); - onDiscontinuity(positionUs); - } - - @Override - protected final void seekTo(long positionUs) throws ExoPlaybackException { - positionUs = shiftInputPosition(positionUs); - enabledSource.seekToUs(positionUs); - checkForDiscontinuity(positionUs); - } - - @Override - protected final void doSomeWork(long positionUs, long elapsedRealtimeUs) - throws ExoPlaybackException { - positionUs = shiftInputPosition(positionUs); - boolean sourceIsReady = enabledSource.continueBuffering(enabledSourceTrackIndex, positionUs); - positionUs = checkForDiscontinuity(positionUs); - doSomeWork(positionUs, elapsedRealtimeUs, sourceIsReady); - } - - @Override - protected long getBufferedPositionUs() { - return enabledSource.getBufferedPositionUs(); - } - - @Override - protected long getDurationUs() { - return durationUs; - } - - @Override - protected void maybeThrowError() throws ExoPlaybackException { - if (enabledSource != null) { - maybeThrowError(enabledSource); - } else { - int sourceCount = sources.length; - for (int i = 0; i < sourceCount; i++) { - maybeThrowError(sources[i]); - } - } - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - enabledSource.disable(enabledSourceTrackIndex); - enabledSource = null; - } - - @Override - protected void onReleased() throws ExoPlaybackException { - int sourceCount = sources.length; - for (int i = 0; i < sourceCount; i++) { - sources[i].release(); - } - } - - @Override - protected final int getTrackCount() { - return handledSourceTrackIndices.length; - } - - @Override - protected final MediaFormat getFormat(int track) { - SampleSourceReader source = sources[handledSourceIndices[track]]; - return source.getFormat(handledSourceTrackIndices[track]); - } - - /** - * Shifts positions passed to {@link #onEnabled(int, long, boolean)}, {@link #seekTo(long)} and - * {@link #doSomeWork(long, long)}. - *

        - * The default implementation does not modify the position. Except in very specific cases, - * subclasses should not override this method. - * - * @param positionUs The position in microseconds. - * @return The adjusted position in microseconds. - */ - protected long shiftInputPosition(long positionUs) { - return positionUs; - } - - // Methods to be called by subclasses. - - /** - * Reads from the enabled upstream source. - * - * @param positionUs The current playback position. - * @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new format. - * @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. - * If the caller requires the sample data then it must ensure that {@link SampleHolder#data} - * references a valid output buffer. - * @return The result, which can be {@link SampleSource#SAMPLE_READ}, - * {@link SampleSource#FORMAT_READ}, {@link SampleSource#NOTHING_READ} or - * {@link SampleSource#END_OF_STREAM}. - */ - protected final int readSource(long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - return enabledSource.readData(enabledSourceTrackIndex, positionUs, formatHolder, sampleHolder); - } - - // Abstract methods. - - /** - * Returns whether this renderer is capable of handling the provided track. - * - * @param mediaFormat The format of the track. - * @return True if the renderer can handle the track, false otherwise. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - protected abstract boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException; - - /** - * Invoked when a discontinuity is encountered. Also invoked when the renderer is enabled, for - * convenience. - * - * @param positionUs The playback position after the discontinuity, or the position at which - * the renderer is being enabled. - * @throws ExoPlaybackException If an error occurs handling the discontinuity. - */ - protected abstract void onDiscontinuity(long positionUs) throws ExoPlaybackException; - - /** - * Called by {@link #doSomeWork(long, long)}. - * - * @param positionUs The current media time in microseconds, measured at the start of the - * current iteration of the rendering loop. - * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, - * measured at the start of the current iteration of the rendering loop. - * @param sourceIsReady The result of the most recent call to - * {@link SampleSourceReader#continueBuffering(int, long)}. - * @throws ExoPlaybackException If an error occurs. - * @throws ExoPlaybackException - */ - protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) - throws ExoPlaybackException; - - // Private methods. - - private long checkForDiscontinuity(long positionUs) throws ExoPlaybackException { - long discontinuityPositionUs = enabledSource.readDiscontinuity(enabledSourceTrackIndex); - if (discontinuityPositionUs != SampleSource.NO_DISCONTINUITY) { - onDiscontinuity(discontinuityPositionUs); - return discontinuityPositionUs; - } - return positionUs; - } - - private void maybeThrowError(SampleSourceReader source) throws ExoPlaybackException { - try { - source.maybeThrowError(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SingleSampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SingleSampleSource.java deleted file mode 100755 index f2b7eba30cb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/SingleSampleSource.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.net.Uri; -import android.os.Handler; -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; -import java.util.Arrays; - -/** - * A {@link SampleSource} that loads the data at a given {@link Uri} as a single sample. - */ -public final class SingleSampleSource implements SampleSource, SampleSourceReader, Loader.Callback, - Loadable { - - /** - * Interface definition for a callback to be notified of {@link SingleSampleSource} events. - */ - public interface EventListener { - - /** - * Invoked when an error occurs loading media data. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param e The cause of the failure. - */ - void onLoadError(int sourceId, IOException e); - - } - - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - - /** - * The initial size of the allocation used to hold the sample data. - */ - private static final int INITIAL_SAMPLE_SIZE = 1; - - private static final int STATE_SEND_FORMAT = 0; - private static final int STATE_SEND_SAMPLE = 1; - private static final int STATE_END_OF_STREAM = 2; - - private final Uri uri; - private final DataSource dataSource; - private final MediaFormat format; - private final int minLoadableRetryCount; - private final Handler eventHandler; - private final EventListener eventListener; - private final int eventSourceId; - - private int state; - private byte[] sampleData; - private int sampleSize; - - private long pendingDiscontinuityPositionUs; - private boolean loadingFinished; - private Loader loader; - private IOException currentLoadableException; - private int currentLoadableExceptionCount; - private long currentLoadableExceptionTimestamp; - - public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format) { - this(uri, dataSource, format, DEFAULT_MIN_LOADABLE_RETRY_COUNT); - } - - public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, - int minLoadableRetryCount) { - this(uri, dataSource, format, minLoadableRetryCount, null, null, 0); - } - - public SingleSampleSource(Uri uri, DataSource dataSource, MediaFormat format, - int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this.uri = uri; - this.dataSource = dataSource; - this.format = format; - this.minLoadableRetryCount = minLoadableRetryCount; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.eventSourceId = eventSourceId; - sampleData = new byte[INITIAL_SAMPLE_SIZE]; - } - - @Override - public SampleSourceReader register() { - return this; - } - - @Override - public boolean prepare(long positionUs) { - if (loader == null) { - loader = new Loader("Loader:" + format.mimeType); - } - return true; - } - - @Override - public int getTrackCount() { - return 1; - } - - @Override - public MediaFormat getFormat(int track) { - return format; - } - - @Override - public void enable(int track, long positionUs) { - state = STATE_SEND_FORMAT; - pendingDiscontinuityPositionUs = NO_DISCONTINUITY; - clearCurrentLoadableException(); - maybeStartLoading(); - } - - @Override - public boolean continueBuffering(int track, long positionUs) { - maybeStartLoading(); - return loadingFinished; - } - - @Override - public void maybeThrowError() throws IOException { - if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) { - throw currentLoadableException; - } - } - - @Override - public long readDiscontinuity(int track) { - long discontinuityPositionUs = pendingDiscontinuityPositionUs; - pendingDiscontinuityPositionUs = NO_DISCONTINUITY; - return discontinuityPositionUs; - } - - @Override - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - if (state == STATE_END_OF_STREAM) { - return END_OF_STREAM; - } else if (state == STATE_SEND_FORMAT) { - formatHolder.format = format; - state = STATE_SEND_SAMPLE; - return FORMAT_READ; - } - - Assertions.checkState(state == STATE_SEND_SAMPLE); - if (!loadingFinished) { - return NOTHING_READ; - } else { - sampleHolder.timeUs = 0; - sampleHolder.size = sampleSize; - sampleHolder.flags = C.SAMPLE_FLAG_SYNC; - sampleHolder.ensureSpaceForWrite(sampleHolder.size); - sampleHolder.data.put(sampleData, 0, sampleSize); - state = STATE_END_OF_STREAM; - return SAMPLE_READ; - } - } - - @Override - public void seekToUs(long positionUs) { - if (state == STATE_END_OF_STREAM) { - pendingDiscontinuityPositionUs = positionUs; - state = STATE_SEND_SAMPLE; - } - } - - @Override - public long getBufferedPositionUs() { - return loadingFinished ? TrackRenderer.END_OF_TRACK_US : 0; - } - - @Override - public void disable(int track) { - state = STATE_END_OF_STREAM; - } - - @Override - public void release() { - if (loader != null) { - loader.release(); - loader = null; - } - } - - // Private methods. - - private void maybeStartLoading() { - if (loadingFinished || state == STATE_END_OF_STREAM || loader.isLoading()) { - return; - } - if (currentLoadableException != null) { - long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp; - if (elapsedMillis < getRetryDelayMillis(currentLoadableExceptionCount)) { - return; - } - currentLoadableException = null; - } - loader.startLoading(this, this); - } - - private void clearCurrentLoadableException() { - currentLoadableException = null; - currentLoadableExceptionCount = 0; - } - - private long getRetryDelayMillis(long errorCount) { - return Math.min((errorCount - 1) * 1000, 5000); - } - - // Loader.Callback implementation. - - @Override - public void onLoadCompleted(Loadable loadable) { - loadingFinished = true; - clearCurrentLoadableException(); - } - - @Override - public void onLoadCanceled(Loadable loadable) { - // Never happens. - } - - @Override - public void onLoadError(Loadable loadable, IOException e) { - currentLoadableException = e; - currentLoadableExceptionCount++; - currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); - notifyLoadError(e); - maybeStartLoading(); - } - - // Loadable implementation. - - @Override - public void cancelLoad() { - // Never happens. - } - - @Override - public boolean isLoadCanceled() { - return false; - } - - @Override - public void load() throws IOException, InterruptedException { - // We always load from the beginning, so reset the sampleSize to 0. - sampleSize = 0; - try { - // Create and open the input. - dataSource.open(new DataSpec(uri)); - // Load the sample data. - int result = 0; - while (result != C.RESULT_END_OF_INPUT) { - sampleSize += result; - if (sampleSize == sampleData.length) { - sampleData = Arrays.copyOf(sampleData, sampleData.length * 2); - } - result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize); - } - } finally { - dataSource.close(); - } - } - - private void notifyLoadError(final IOException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadError(eventSourceId, e); - } - }); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TimeRange.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TimeRange.java deleted file mode 100755 index 2cd18977cc3..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TimeRange.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.util.Clock; - -/** - * A container to store a start and end time in microseconds. - */ -public interface TimeRange { - - /** - * Whether the range is static, meaning repeated calls to {@link #getCurrentBoundsMs(long[])} - * or {@link #getCurrentBoundsUs(long[])} will return identical results. - * - * @return Whether the range is static. - */ - public boolean isStatic(); - - /** - * Returns the start and end times (in milliseconds) of the TimeRange in the provided array, - * or creates a new one. - * - * @param out An array to store the start and end times; can be null. - * @return An array containing the start time (index 0) and end time (index 1) in milliseconds. - */ - public long[] getCurrentBoundsMs(long[] out); - - /** - * Returns the start and end times (in microseconds) of the TimeRange in the provided array, - * or creates a new one. - * - * @param out An array to store the start and end times; can be null. - * @return An array containing the start time (index 0) and end time (index 1) in microseconds. - */ - public long[] getCurrentBoundsUs(long[] out); - - /** - * A static {@link TimeRange}. - */ - public static final class StaticTimeRange implements TimeRange { - - private final long startTimeUs; - private final long endTimeUs; - - /** - * @param startTimeUs The beginning of the range. - * @param endTimeUs The end of the range. - */ - public StaticTimeRange(long startTimeUs, long endTimeUs) { - this.startTimeUs = startTimeUs; - this.endTimeUs = endTimeUs; - } - - @Override - public boolean isStatic() { - return true; - } - - @Override - public long[] getCurrentBoundsMs(long[] out) { - out = getCurrentBoundsUs(out); - out[0] /= 1000; - out[1] /= 1000; - return out; - } - - @Override - public long[] getCurrentBoundsUs(long[] out) { - if (out == null || out.length < 2) { - out = new long[2]; - } - out[0] = startTimeUs; - out[1] = endTimeUs; - return out; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (int) startTimeUs; - result = 31 * result + (int) endTimeUs; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - StaticTimeRange other = (StaticTimeRange) obj; - return other.startTimeUs == startTimeUs - && other.endTimeUs == endTimeUs; - } - - } - - /** - * A dynamic {@link TimeRange}. - */ - public static final class DynamicTimeRange implements TimeRange { - - private final long minStartTimeUs; - private final long maxEndTimeUs; - private final long elapsedRealtimeAtStartUs; - private final long bufferDepthUs; - private final Clock systemClock; - - /** - * @param minStartTimeUs A lower bound on the beginning of the range. - * @param maxEndTimeUs An upper bound on the end of the range. - * @param elapsedRealtimeAtStartUs The value of {@link SystemClock#elapsedRealtime()}, - * multiplied by 1000, corresponding to a media time of zero. - * @param bufferDepthUs The buffer depth of the media, or -1. - * @param systemClock A system clock. - */ - public DynamicTimeRange(long minStartTimeUs, long maxEndTimeUs, long elapsedRealtimeAtStartUs, - long bufferDepthUs, Clock systemClock) { - this.minStartTimeUs = minStartTimeUs; - this.maxEndTimeUs = maxEndTimeUs; - this.elapsedRealtimeAtStartUs = elapsedRealtimeAtStartUs; - this.bufferDepthUs = bufferDepthUs; - this.systemClock = systemClock; - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public long[] getCurrentBoundsMs(long[] out) { - out = getCurrentBoundsUs(out); - out[0] /= 1000; - out[1] /= 1000; - return out; - } - - @Override - public long[] getCurrentBoundsUs(long[] out) { - if (out == null || out.length < 2) { - out = new long[2]; - } - // Don't allow the end time to be greater than the total elapsed time. - long currentEndTimeUs = Math.min(maxEndTimeUs, - (systemClock.elapsedRealtime() * 1000) - elapsedRealtimeAtStartUs); - long currentStartTimeUs = minStartTimeUs; - if (bufferDepthUs != -1) { - // Don't allow the start time to be less than the current end time minus the buffer depth. - currentStartTimeUs = Math.max(currentStartTimeUs, - currentEndTimeUs - bufferDepthUs); - } - out[0] = currentStartTimeUs; - out[1] = currentEndTimeUs; - return out; - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (int) minStartTimeUs; - result = 31 * result + (int) maxEndTimeUs; - result = 31 * result + (int) elapsedRealtimeAtStartUs; - result = 31 * result + (int) bufferDepthUs; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - DynamicTimeRange other = (DynamicTimeRange) obj; - return other.minStartTimeUs == minStartTimeUs - && other.maxEndTimeUs == maxEndTimeUs - && other.elapsedRealtimeAtStartUs == elapsedRealtimeAtStartUs - && other.bufferDepthUs == bufferDepthUs; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TrackRenderer.java deleted file mode 100755 index fbc0cd0a2c0..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/TrackRenderer.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer; - -import org.telegram.messenger.exoplayer.ExoPlayer.ExoPlayerComponent; -import org.telegram.messenger.exoplayer.util.Assertions; - -/** - * Renders a single component of media. - * - *

        Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The player - * will transition its renderers through various states as the overall playback state changes. The - * valid state transitions are shown below, annotated with the methods that are invoked during each - * transition. - *

        TrackRenderer state transitions

        - */ -public abstract class TrackRenderer implements ExoPlayerComponent { - - /** - * Represents an unknown time or duration. Equal to {@link C#UNKNOWN_TIME_US}. - */ - public static final long UNKNOWN_TIME_US = C.UNKNOWN_TIME_US; // -1 - /** - * Represents a time or duration that should match the duration of the longest track whose - * duration is known. Equal to {@link C#MATCH_LONGEST_US}. - */ - public static final long MATCH_LONGEST_US = C.MATCH_LONGEST_US; // -2 - /** - * Represents the time of the end of the track. - */ - public static final long END_OF_TRACK_US = -3; - - /** - * The renderer has been released and should not be used. - */ - protected static final int STATE_RELEASED = -1; - /** - * The renderer has not yet been prepared. - */ - protected static final int STATE_UNPREPARED = 0; - /** - * The renderer has completed necessary preparation. Preparation may include, for example, - * reading the header of a media file to determine the track format and duration. - *

        - * The renderer should not hold scarce or expensive system resources (e.g. media decoders) and - * should not be actively buffering media data when in this state. - */ - protected static final int STATE_PREPARED = 1; - /** - * The renderer is enabled. It should either be ready to be started, or be actively working - * towards this state (e.g. a renderer in this state will typically hold any resources that it - * requires, such as media decoders, and will have buffered or be buffering any media data that - * is required to start playback). - */ - protected static final int STATE_ENABLED = 2; - /** - * The renderer is started. Calls to {@link #doSomeWork(long, long)} should cause the media to be - * rendered. - */ - protected static final int STATE_STARTED = 3; - - private int state; - - /** - * If the renderer advances its own playback position then this method returns a corresponding - * {@link MediaClock}. If provided, the player will use the returned {@link MediaClock} as its - * source of time during playback. A player may have at most one renderer that returns a - * {@link MediaClock} from this method. - * - * @return The {@link MediaClock} tracking the playback position of the renderer, or null. - */ - protected MediaClock getMediaClock() { - return null; - } - - /** - * Returns the current state of the renderer. - * - * @return The current state (one of the STATE_* constants). - */ - protected final int getState() { - return state; - } - - /** - * Prepares the renderer. This method is non-blocking, and hence it may be necessary to call it - * more than once in order to transition the renderer into the prepared state. - * - * @param positionUs The player's current playback position. - * @return The current state (one of the STATE_* constants), for convenience. - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final int prepare(long positionUs) throws ExoPlaybackException { - Assertions.checkState(state == STATE_UNPREPARED); - state = doPrepare(positionUs) ? STATE_PREPARED : STATE_UNPREPARED; - return state; - } - - /** - * Invoked to make progress when the renderer is in the {@link #STATE_UNPREPARED} state. This - * method will be called repeatedly until {@code true} is returned. - *

        - * This method should return quickly, and should not block if the renderer is currently unable to - * make any useful progress. - * - * @param positionUs The player's current playback position. - * @return True if the renderer is now prepared. False otherwise. - * @throws ExoPlaybackException If an error occurs. - */ - protected abstract boolean doPrepare(long positionUs) throws ExoPlaybackException; - - /** - * Returns the number of tracks exposed by the renderer. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @return The number of tracks. - */ - protected abstract int getTrackCount(); - - /** - * Returns the format of the specified track. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @param track The track index. - * @return The format of the specified track. - */ - protected abstract MediaFormat getFormat(int track); - - /** - * Enable the renderer for a specified track. - * - * @param track The track for which the renderer is being enabled. - * @param positionUs The player's current position. - * @param joining Whether this renderer is being enabled to join an ongoing playback. - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void enable(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - Assertions.checkState(state == STATE_PREPARED); - state = STATE_ENABLED; - onEnabled(track, positionUs, joining); - } - - /** - * Called when the renderer is enabled. - *

        - * The default implementation is a no-op. - * - * @param track The track for which the renderer is being enabled. - * @param positionUs The player's current position. - * @param joining Whether this renderer is being enabled to join an ongoing playback. - * @throws ExoPlaybackException If an error occurs. - */ - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - // Do nothing. - } - - /** - * Starts the renderer, meaning that calls to {@link #doSomeWork(long, long)} will cause the - * track to be rendered. - * - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void start() throws ExoPlaybackException { - Assertions.checkState(state == STATE_ENABLED); - state = STATE_STARTED; - onStarted(); - } - - /** - * Called when the renderer is started. - *

        - * The default implementation is a no-op. - * - * @throws ExoPlaybackException If an error occurs. - */ - protected void onStarted() throws ExoPlaybackException { - // Do nothing. - } - - /** - * Stops the renderer. - * - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void stop() throws ExoPlaybackException { - Assertions.checkState(state == STATE_STARTED); - state = STATE_ENABLED; - onStopped(); - } - - /** - * Called when the renderer is stopped. - *

        - * The default implementation is a no-op. - * - * @throws ExoPlaybackException If an error occurs. - */ - protected void onStopped() throws ExoPlaybackException { - // Do nothing. - } - - /** - * Disable the renderer. - * - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void disable() throws ExoPlaybackException { - Assertions.checkState(state == STATE_ENABLED); - state = STATE_PREPARED; - onDisabled(); - } - - /** - * Called when the renderer is disabled. - *

        - * The default implementation is a no-op. - * - * @throws ExoPlaybackException If an error occurs. - */ - protected void onDisabled() throws ExoPlaybackException { - // Do nothing. - } - - /** - * Releases the renderer. - * - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void release() throws ExoPlaybackException { - Assertions.checkState(state != STATE_ENABLED - && state != STATE_STARTED - && state != STATE_RELEASED); - state = STATE_RELEASED; - onReleased(); - } - - /** - * Called when the renderer is released. - *

        - * The default implementation is a no-op. - * - * @throws ExoPlaybackException If an error occurs. - */ - protected void onReleased() throws ExoPlaybackException { - // Do nothing. - } - - /** - * Whether the renderer is ready for the {@link ExoPlayer} instance to transition to - * {@link ExoPlayer#STATE_ENDED}. The player will make this transition as soon as {@code true} is - * returned by all of its {@link TrackRenderer}s. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @return Whether the renderer is ready for the player to transition to the ended state. - */ - protected abstract boolean isEnded(); - - /** - * Whether the renderer is able to immediately render media from the current position. - *

        - * If the renderer is in the {@link #STATE_STARTED} state then returning true indicates that the - * renderer has everything that it needs to continue playback. Returning false indicates that - * the player should pause until the renderer is ready. - *

        - * If the renderer is in the {@link #STATE_ENABLED} state then returning true indicates that the - * renderer is ready for playback to be started. Returning false indicates that it is not. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @return True if the renderer is ready to render media. False otherwise. - */ - protected abstract boolean isReady(); - - /** - * Invoked to make progress when the renderer is in the {@link #STATE_ENABLED} or - * {@link #STATE_STARTED} states. - *

        - * If the renderer's state is {@link #STATE_STARTED}, then repeated calls to this method should - * cause the media track to be rendered. If the state is {@link #STATE_ENABLED}, then repeated - * calls should make progress towards getting the renderer into a position where it is ready to - * render the track. - *

        - * This method should return quickly, and should not block if the renderer is currently unable to - * make any useful progress. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @param positionUs The current media time in microseconds, measured at the start of the - * current iteration of the rendering loop. - * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, - * measured at the start of the current iteration of the rendering loop. - * @throws ExoPlaybackException If an error occurs. - */ - protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs) - throws ExoPlaybackException; - - /** - * Throws an error that's preventing the renderer from making progress or buffering more data at - * this point in time. - * - * @throws ExoPlaybackException An error that's preventing the renderer from making progress or - * buffering more data. - */ - protected abstract void maybeThrowError() throws ExoPlaybackException; - - /** - * Returns the duration of the media being rendered. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_PREPARED}, {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @return The duration of the track in microseconds, or {@link #MATCH_LONGEST_US} if - * the track's duration should match that of the longest track whose duration is known, or - * or {@link #UNKNOWN_TIME_US} if the duration is not known. - */ - protected abstract long getDurationUs(); - - /** - * Returns an estimate of the absolute position in microseconds up to which data is buffered. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED}, {@link #STATE_STARTED} - * - * @return An estimate of the absolute position in microseconds up to which data is buffered, - * or {@link #END_OF_TRACK_US} if the track is fully buffered, or {@link #UNKNOWN_TIME_US} if - * no estimate is available. - */ - protected abstract long getBufferedPositionUs(); - - /** - * Seeks to a specified time in the track. - *

        - * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED} - * - * @param positionUs The desired playback position in microseconds. - * @throws ExoPlaybackException If an error occurs. - */ - protected abstract void seekTo(long positionUs) throws ExoPlaybackException; - - @Override - public void handleMessage(int what, Object object) throws ExoPlaybackException { - // Do nothing. - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilitiesReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilitiesReceiver.java deleted file mode 100755 index ec004967af8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilitiesReceiver.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.audio; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; - -/** - * Notifies a listener when the audio playback capabilities change. Call {@link #register} to start - * (or resume) receiving notifications, and {@link #unregister} to stop. - */ -public final class AudioCapabilitiesReceiver { - - /** - * Listener notified when audio capabilities change. - */ - public interface Listener { - - /** - * Called when the audio capabilities change. - * - * @param audioCapabilities Current audio capabilities for the device. - */ - void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities); - - } - - private final Context context; - private final Listener listener; - private final BroadcastReceiver receiver; - - /* package */ AudioCapabilities audioCapabilities; - - /** - * Constructs a new audio capabilities receiver. - * - * @param context Context for registering to receive broadcasts. - * @param listener Listener to notify when audio capabilities change. - */ - public AudioCapabilitiesReceiver(Context context, Listener listener) { - this.context = Assertions.checkNotNull(context); - this.listener = Assertions.checkNotNull(listener); - this.receiver = Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null; - } - - /** - * Registers to notify the listener when audio capabilities change. The current capabilities will - * be returned. It is important to call {@link #unregister} so that the listener can be garbage - * collected. - * - * @return Current audio capabilities for the device. - */ - @SuppressWarnings("InlinedApi") - public AudioCapabilities register() { - Intent stickyIntent = receiver == null ? null - : context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); - audioCapabilities = AudioCapabilities.getCapabilities(stickyIntent); - return audioCapabilities; - } - - /** - * Unregisters to stop notifying the listener when audio capabilities change. - */ - public void unregister() { - if (receiver != null) { - context.unregisterReceiver(receiver); - } - } - - private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (!isInitialStickyBroadcast()) { - AudioCapabilities newAudioCapabilities = AudioCapabilities.getCapabilities(intent); - if (!newAudioCapabilities.equals(audioCapabilities)) { - audioCapabilities = newAudioCapabilities; - listener.onAudioCapabilitiesChanged(newAudioCapabilities); - } - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseChunkSampleSourceEventListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseChunkSampleSourceEventListener.java deleted file mode 100755 index ecaebffec35..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseChunkSampleSourceEventListener.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import java.io.IOException; - -/** - * Interface for callbacks to be notified of chunk based {@link SampleSource} events. - */ -public interface BaseChunkSampleSourceEventListener { - - /** - * Invoked when an upstream load is started. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param length The length of the data being loaded in bytes, or {@link C#LENGTH_UNBOUNDED} if - * the length of the data is not known in advance. - * @param type The type of the data being loaded. - * @param trigger The reason for the data being loaded. - * @param format The particular format to which this data corresponds, or null if the data being - * loaded does not correspond to a format. - * @param mediaStartTimeMs The media time of the start of the data being loaded, or -1 if this - * load is for initialization data. - * @param mediaEndTimeMs The media time of the end of the data being loaded, or -1 if this - * load is for initialization data. - */ - void onLoadStarted(int sourceId, long length, int type, int trigger, Format format, - long mediaStartTimeMs, long mediaEndTimeMs); - - /** - * Invoked when the current load operation completes. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param bytesLoaded The number of bytes that were loaded. - * @param type The type of the loaded data. - * @param trigger The reason for the data being loaded. - * @param format The particular format to which this data corresponds, or null if the loaded data - * does not correspond to a format. - * @param mediaStartTimeMs The media time of the start of the loaded data, or -1 if this load was - * for initialization data. - * @param mediaEndTimeMs The media time of the end of the loaded data, or -1 if this load was for - * initialization data. - * @param elapsedRealtimeMs {@code elapsedRealtime} timestamp of when the load finished. - * @param loadDurationMs Amount of time taken to load the data. - */ - void onLoadCompleted(int sourceId, long bytesLoaded, int type, int trigger, Format format, - long mediaStartTimeMs, long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs); - - /** - * Invoked when the current upstream load operation is canceled. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param bytesLoaded The number of bytes that were loaded prior to the cancellation. - */ - void onLoadCanceled(int sourceId, long bytesLoaded); - - /** - * Invoked when an error occurs loading media data. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param e The cause of the failure. - */ - void onLoadError(int sourceId, IOException e); - - /** - * Invoked when data is removed from the back of the buffer, typically so that it can be - * re-buffered using a different representation. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param mediaStartTimeMs The media time of the start of the discarded data. - * @param mediaEndTimeMs The media time of the end of the discarded data. - */ - void onUpstreamDiscarded(int sourceId, long mediaStartTimeMs, long mediaEndTimeMs); - - /** - * Invoked when the downstream format changes (i.e. when the format being supplied to the - * caller of {@link SampleSourceReader#readData} changes). - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param format The format. - * @param trigger The trigger specified in the corresponding upstream load, as specified by the - * {@link ChunkSource}. - * @param mediaTimeMs The media time at which the change occurred. - */ - void onDownstreamFormatChanged(int sourceId, Format format, int trigger, long mediaTimeMs); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseMediaChunk.java deleted file mode 100755 index 05b85e123bd..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/BaseMediaChunk.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.DefaultTrackOutput; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; - -/** - * A base implementation of {@link MediaChunk}, for chunks that contain a single track. - *

        - * Loaded samples are output to a {@link DefaultTrackOutput}. - */ -public abstract class BaseMediaChunk extends MediaChunk { - - /** - * Whether {@link #getMediaFormat()} and {@link #getDrmInitData()} can be called at any time to - * obtain the chunk's media format and drm initialization data. If false, these methods are only - * guaranteed to return correct data after the first sample data has been output from the chunk. - */ - public final boolean isMediaFormatFinal; - - private DefaultTrackOutput output; - private int firstSampleIndex; - - /** - * @param dataSource A {@link DataSource} for loading the data. - * @param dataSpec Defines the data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param startTimeUs The start time of the media contained by the chunk, in microseconds. - * @param endTimeUs The end time of the media contained by the chunk, in microseconds. - * @param chunkIndex The index of the chunk. - * @param isMediaFormatFinal True if {@link #getMediaFormat()} and {@link #getDrmInitData()} can - * be called at any time to obtain the media format and drm initialization data. False if - * these methods are only guaranteed to return correct data after the first sample data has - * been output from the chunk. - * @param parentId Identifier for a parent from which this chunk originates. - */ - public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - long startTimeUs, long endTimeUs, int chunkIndex, boolean isMediaFormatFinal, int parentId) { - super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, parentId); - this.isMediaFormatFinal = isMediaFormatFinal; - } - - /** - * Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive - * samples as they are loaded. - * - * @param output The output that will receive the loaded samples. - */ - public void init(DefaultTrackOutput output) { - this.output = output; - this.firstSampleIndex = output.getWriteIndex(); - } - - /** - * Returns the index of the first sample in the output that was passed to - * {@link #init(DefaultTrackOutput)} that will originate from this chunk. - */ - public final int getFirstSampleIndex() { - return firstSampleIndex; - } - - /** - * Gets the {@link MediaFormat} corresponding to the chunk. - *

        - * See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return - * correct data. - * - * @return The {@link MediaFormat} corresponding to this chunk. - */ - public abstract MediaFormat getMediaFormat(); - - /** - * Gets the {@link DrmInitData} corresponding to the chunk. - *

        - * See {@link #isMediaFormatFinal} for information about when this method is guaranteed to return - * correct data. - * - * @return The {@link DrmInitData} corresponding to this chunk. - */ - public abstract DrmInitData getDrmInitData(); - - /** - * Returns the output most recently passed to {@link #init(DefaultTrackOutput)}. - */ - protected final DefaultTrackOutput getOutput() { - return output; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Chunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Chunk.java deleted file mode 100755 index e1c74410fdf..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Chunk.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.util.Assertions; - -/** - * An abstract base class for {@link Loadable} implementations that load chunks of data required - * for the playback of streams. - */ -public abstract class Chunk implements Loadable { - - /** - * Value of {@link #type} for chunks containing unspecified data. - */ - public static final int TYPE_UNSPECIFIED = 0; - /** - * Value of {@link #type} for chunks containing media data. - */ - public static final int TYPE_MEDIA = 1; - /** - * Value of {@link #type} for chunks containing media initialization data. - */ - public static final int TYPE_MEDIA_INITIALIZATION = 2; - /** - * Value of {@link #type} for chunks containing drm related data. - */ - public static final int TYPE_DRM = 3; - /** - * Value of {@link #type} for chunks containing manifest or playlist data. - */ - public static final int TYPE_MANIFEST = 4; - /** - * Implementations may define custom {@link #type} codes greater than or equal to this value. - */ - public static final int TYPE_CUSTOM_BASE = 10000; - - /** - * Value of {@link #trigger} for a load whose reason is unspecified. - */ - public static final int TRIGGER_UNSPECIFIED = 0; - /** - * Value of {@link #trigger} for a load triggered by an initial format selection. - */ - public static final int TRIGGER_INITIAL = 1; - /** - * Value of {@link #trigger} for a load triggered by a user initiated format selection. - */ - public static final int TRIGGER_MANUAL = 2; - /** - * Value of {@link #trigger} for a load triggered by an adaptive format selection. - */ - public static final int TRIGGER_ADAPTIVE = 3; - /** - * Value of {@link #trigger} for a load triggered whilst in a trick play mode. - */ - public static final int TRIGGER_TRICK_PLAY = 4; - /** - * Implementations may define custom {@link #trigger} codes greater than or equal to this value. - */ - public static final int TRIGGER_CUSTOM_BASE = 10000; - /** - * Value of {@link #parentId} if no parent id need be specified. - */ - public static final int NO_PARENT_ID = -1; - - /** - * The type of the chunk. For reporting only. - */ - public final int type; - /** - * The reason why the chunk was generated. For reporting only. - */ - public final int trigger; - /** - * The format associated with the data being loaded, or null if the data being loaded is not - * associated with a specific format. - */ - public final Format format; - /** - * The {@link DataSpec} that defines the data to be loaded. - */ - public final DataSpec dataSpec; - /** - * Optional identifier for a parent from which this chunk originates. - */ - public final int parentId; - - protected final DataSource dataSource; - - /** - * @param dataSource The source from which the data should be loaded. - * @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed - * {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then - * the length resolved by {@code dataSource.open(dataSpec)} must not exceed - * {@link Integer#MAX_VALUE}. - * @param type See {@link #type}. - * @param trigger See {@link #trigger}. - * @param format See {@link #format}. - * @param parentId See {@link #parentId}. - */ - public Chunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format, - int parentId) { - this.dataSource = Assertions.checkNotNull(dataSource); - this.dataSpec = Assertions.checkNotNull(dataSpec); - this.type = type; - this.trigger = trigger; - this.format = format; - this.parentId = parentId; - } - - /** - * Gets the number of bytes that have been loaded. - * - * @return The number of bytes that have been loaded. - */ - public abstract long bytesLoaded(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkExtractorWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkExtractorWrapper.java deleted file mode 100755 index b2faa3481de..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkExtractorWrapper.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * An {@link Extractor} wrapper for loading chunks containing a single track. - *

        - * The wrapper allows switching of the {@link SingleTrackOutput} that receives parsed data. - */ -public class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput { - - /** - * Receives stream level data extracted by the wrapped {@link Extractor}. - */ - public interface SingleTrackOutput extends TrackOutput { - - /** - * @see ExtractorOutput#seekMap(SeekMap) - */ - void seekMap(SeekMap seekMap); - - /** - * @see ExtractorOutput#drmInitData(DrmInitData) - */ - void drmInitData(DrmInitData drmInitData); - - } - - private final Extractor extractor; - private boolean extractorInitialized; - private SingleTrackOutput output; - - // Accessed only on the loader thread. - private boolean seenTrack; - - /** - * @param extractor The extractor to wrap. - */ - public ChunkExtractorWrapper(Extractor extractor) { - this.extractor = extractor; - } - - /** - * Initializes the extractor to output to the provided {@link SingleTrackOutput}, and configures - * it to receive data from a new chunk. - * - * @param output The {@link SingleTrackOutput} that will receive the parsed data. - */ - public void init(SingleTrackOutput output) { - this.output = output; - if (!extractorInitialized) { - extractor.init(this); - extractorInitialized = true; - } else { - extractor.seek(); - } - } - - /** - * Reads from the provided {@link ExtractorInput}. - * - * @param input The {@link ExtractorInput} from which to read. - * @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}. - * @throws IOException If an error occurred reading from the source. - * @throws InterruptedException If the thread was interrupted. - */ - public int read(ExtractorInput input) throws IOException, InterruptedException { - int result = extractor.read(input, null); - Assertions.checkState(result != Extractor.RESULT_SEEK); - return result; - } - - // ExtractorOutput implementation. - - @Override - public TrackOutput track(int id) { - Assertions.checkState(!seenTrack); - seenTrack = true; - return this; - } - - @Override - public void endTracks() { - Assertions.checkState(seenTrack); - } - - @Override - public void seekMap(SeekMap seekMap) { - output.seekMap(seekMap); - } - - @Override - public void drmInitData(DrmInitData drmInitData) { - output.drmInitData(drmInitData); - } - - // TrackOutput implementation. - - @Override - public void format(MediaFormat format) { - output.format(format); - } - - @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return output.sampleData(input, length, allowEndOfInput); - } - - @Override - public void sampleData(ParsableByteArray data, int length) { - output.sampleData(data, length); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - output.sampleMetadata(timeUs, flags, size, offset, encryptionKey); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkOperationHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkOperationHolder.java deleted file mode 100755 index fc5639f6457..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkOperationHolder.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -/** - * Holds a chunk operation, which consists of a either: - *

          - *
        • The number of {@link MediaChunk}s that should be retained on the queue ({@link #queueSize}) - * together with the next {@link Chunk} to load ({@link #chunk}). {@link #chunk} may be null if the - * next chunk cannot be provided yet.
        • - *
        • A flag indicating that the end of the stream has been reached ({@link #endOfStream}).
        • - *
        - */ -public final class ChunkOperationHolder { - - /** - * The number of {@link MediaChunk}s to retain in a queue. - */ - public int queueSize; - - /** - * The chunk. - */ - public Chunk chunk; - - /** - * Indicates that the end of the stream has been reached. - */ - public boolean endOfStream; - - /** - * Clears the holder. - */ - public void clear() { - queueSize = 0; - chunk = null; - endOfStream = false; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSampleSource.java deleted file mode 100755 index ad9da90eb56..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSampleSource.java +++ /dev/null @@ -1,686 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import android.os.Handler; -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.LoadControl; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.extractor.DefaultTrackOutput; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -/** - * A {@link SampleSource} that loads media in {@link Chunk}s, which are themselves obtained from a - * {@link ChunkSource}. - */ -public class ChunkSampleSource implements SampleSource, SampleSourceReader, Loader.Callback { - - /** - * Interface definition for a callback to be notified of {@link ChunkSampleSource} events. - */ - public interface EventListener extends BaseChunkSampleSourceEventListener {} - - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - - protected final DefaultTrackOutput sampleQueue; - - private static final int STATE_IDLE = 0; - private static final int STATE_INITIALIZED = 1; - private static final int STATE_PREPARED = 2; - private static final int STATE_ENABLED = 3; - - private static final long NO_RESET_PENDING = Long.MIN_VALUE; - - private final int eventSourceId; - private final LoadControl loadControl; - private final ChunkSource chunkSource; - private final ChunkOperationHolder currentLoadableHolder; - private final LinkedList mediaChunks; - private final List readOnlyMediaChunks; - private final int bufferSizeContribution; - private final Handler eventHandler; - private final EventListener eventListener; - private final int minLoadableRetryCount; - - private int state; - private long downstreamPositionUs; - private long lastSeekPositionUs; - private long pendingResetPositionUs; - private long lastPerformedBufferOperation; - private boolean pendingDiscontinuity; - - private Loader loader; - private boolean loadingFinished; - private IOException currentLoadableException; - private int enabledTrackCount; - private int currentLoadableExceptionCount; - private long currentLoadableExceptionTimestamp; - private long currentLoadStartTimeMs; - - private MediaFormat downstreamMediaFormat; - private Format downstreamFormat; - - /** - * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. - * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. - */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution) { - this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); - } - - /** - * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. - * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, - eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); - } - - /** - * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. - * @param loadControl Controls when the source is permitted to load data. - * @param bufferSizeContribution The contribution of this source to the media buffer, in bytes. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - * @param minLoadableRetryCount The minimum number of times that the source should retry a load - * before propagating an error. - */ - public ChunkSampleSource(ChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, EventListener eventListener, - int eventSourceId, int minLoadableRetryCount) { - this.chunkSource = chunkSource; - this.loadControl = loadControl; - this.bufferSizeContribution = bufferSizeContribution; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.eventSourceId = eventSourceId; - this.minLoadableRetryCount = minLoadableRetryCount; - currentLoadableHolder = new ChunkOperationHolder(); - mediaChunks = new LinkedList<>(); - readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); - sampleQueue = new DefaultTrackOutput(loadControl.getAllocator()); - state = STATE_IDLE; - pendingResetPositionUs = NO_RESET_PENDING; - } - - @Override - public SampleSourceReader register() { - Assertions.checkState(state == STATE_IDLE); - state = STATE_INITIALIZED; - return this; - } - - @Override - public boolean prepare(long positionUs) { - Assertions.checkState(state == STATE_INITIALIZED || state == STATE_PREPARED); - if (state == STATE_PREPARED) { - return true; - } else if (!chunkSource.prepare()) { - return false; - } - if (chunkSource.getTrackCount() > 0) { - loader = new Loader("Loader:" + chunkSource.getFormat(0).mimeType); - } - state = STATE_PREPARED; - return true; - } - - @Override - public int getTrackCount() { - Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED); - return chunkSource.getTrackCount(); - } - - @Override - public MediaFormat getFormat(int track) { - Assertions.checkState(state == STATE_PREPARED || state == STATE_ENABLED); - return chunkSource.getFormat(track); - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(state == STATE_PREPARED); - Assertions.checkState(enabledTrackCount++ == 0); - state = STATE_ENABLED; - chunkSource.enable(track); - loadControl.register(this, bufferSizeContribution); - downstreamFormat = null; - downstreamMediaFormat = null; - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - pendingDiscontinuity = false; - restartFrom(positionUs); - } - - @Override - public void disable(int track) { - Assertions.checkState(state == STATE_ENABLED); - Assertions.checkState(--enabledTrackCount == 0); - state = STATE_PREPARED; - try { - chunkSource.disable(mediaChunks); - } finally { - loadControl.unregister(this); - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - sampleQueue.clear(); - mediaChunks.clear(); - clearCurrentLoadable(); - loadControl.trimAllocator(); - } - } - } - - @Override - public boolean continueBuffering(int track, long positionUs) { - Assertions.checkState(state == STATE_ENABLED); - downstreamPositionUs = positionUs; - chunkSource.continueBuffering(positionUs); - updateLoadControl(); - return loadingFinished || !sampleQueue.isEmpty(); - } - - @Override - public long readDiscontinuity(int track) { - if (pendingDiscontinuity) { - pendingDiscontinuity = false; - return lastSeekPositionUs; - } - return NO_DISCONTINUITY; - } - - @Override - public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - Assertions.checkState(state == STATE_ENABLED); - downstreamPositionUs = positionUs; - - if (pendingDiscontinuity || isPendingReset()) { - return NOTHING_READ; - } - - boolean haveSamples = !sampleQueue.isEmpty(); - BaseMediaChunk currentChunk = mediaChunks.getFirst(); - while (haveSamples && mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { - mediaChunks.removeFirst(); - currentChunk = mediaChunks.getFirst(); - } - - Format format = currentChunk.format; - if (!format.equals(downstreamFormat)) { - notifyDownstreamFormatChanged(format, currentChunk.trigger, currentChunk.startTimeUs); - } - downstreamFormat = format; - - if (haveSamples || currentChunk.isMediaFormatFinal) { - MediaFormat mediaFormat = currentChunk.getMediaFormat(); - if (!mediaFormat.equals(downstreamMediaFormat)) { - formatHolder.format = mediaFormat; - formatHolder.drmInitData = currentChunk.getDrmInitData(); - downstreamMediaFormat = mediaFormat; - return FORMAT_READ; - } - // If mediaFormat and downstreamMediaFormat are equal but different objects then the equality - // check above will have been expensive, comparing the fields in each format. We update - // downstreamMediaFormat here so that referential equality can be cheaply established during - // subsequent calls. - downstreamMediaFormat = mediaFormat; - } - - if (!haveSamples) { - if (loadingFinished) { - return END_OF_STREAM; - } - return NOTHING_READ; - } - - if (sampleQueue.getSample(sampleHolder)) { - boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; - sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0; - onSampleRead(currentChunk, sampleHolder); - return SAMPLE_READ; - } - - return NOTHING_READ; - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(state == STATE_ENABLED); - - long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - if (currentPositionUs == positionUs) { - return; - } - - // If we're not pending a reset, see if we can seek within the sample queue. - boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs); - if (seekInsideBuffer) { - // We succeeded. All we need to do is discard any chunks that we've moved past. - boolean haveSamples = !sampleQueue.isEmpty(); - while (haveSamples && mediaChunks.size() > 1 - && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { - mediaChunks.removeFirst(); - } - } else { - // We failed, and need to restart. - restartFrom(positionUs); - } - // Either way, we need to send a discontinuity to the downstream components. - pendingDiscontinuity = true; - } - - @Override - public void maybeThrowError() throws IOException { - if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) { - throw currentLoadableException; - } else if (currentLoadableHolder.chunk == null) { - chunkSource.maybeThrowError(); - } - } - - @Override - public long getBufferedPositionUs() { - Assertions.checkState(state == STATE_ENABLED); - if (isPendingReset()) { - return pendingResetPositionUs; - } else if (loadingFinished) { - return TrackRenderer.END_OF_TRACK_US; - } else { - long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs(); - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; - } - } - - @Override - public void release() { - Assertions.checkState(state != STATE_ENABLED); - if (loader != null) { - loader.release(); - loader = null; - } - state = STATE_IDLE; - } - - @Override - public void onLoadCompleted(Loadable loadable) { - long now = SystemClock.elapsedRealtime(); - long loadDurationMs = now - currentLoadStartTimeMs; - Chunk currentLoadable = currentLoadableHolder.chunk; - chunkSource.onChunkLoadCompleted(currentLoadable); - if (isMediaChunk(currentLoadable)) { - BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable; - notifyLoadCompleted(currentLoadable.bytesLoaded(), mediaChunk.type, mediaChunk.trigger, - mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs, now, loadDurationMs); - } else { - notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type, - currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs); - } - clearCurrentLoadable(); - updateLoadControl(); - } - - @Override - public void onLoadCanceled(Loadable loadable) { - Chunk currentLoadable = currentLoadableHolder.chunk; - notifyLoadCanceled(currentLoadable.bytesLoaded()); - clearCurrentLoadable(); - if (state == STATE_ENABLED) { - restartFrom(pendingResetPositionUs); - } else { - sampleQueue.clear(); - mediaChunks.clear(); - clearCurrentLoadable(); - loadControl.trimAllocator(); - } - } - - @Override - public void onLoadError(Loadable loadable, IOException e) { - currentLoadableException = e; - currentLoadableExceptionCount++; - currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); - notifyLoadError(e); - chunkSource.onChunkLoadError(currentLoadableHolder.chunk, e); - updateLoadControl(); - } - - /** - * Called when a sample has been read. Can be used to perform any modifications necessary before - * the sample is returned. - * - * @param mediaChunk The chunk from which the sample was obtained. - * @param sampleHolder Holds the read sample. - */ - protected void onSampleRead(MediaChunk mediaChunk, SampleHolder sampleHolder) { - // Do nothing. - } - - private void restartFrom(long positionUs) { - pendingResetPositionUs = positionUs; - loadingFinished = false; - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - sampleQueue.clear(); - mediaChunks.clear(); - clearCurrentLoadable(); - updateLoadControl(); - } - } - - private void clearCurrentLoadable() { - currentLoadableHolder.chunk = null; - clearCurrentLoadableException(); - } - - private void clearCurrentLoadableException() { - currentLoadableException = null; - currentLoadableExceptionCount = 0; - } - - private void updateLoadControl() { - long now = SystemClock.elapsedRealtime(); - long nextLoadPositionUs = getNextLoadPositionUs(); - boolean isBackedOff = currentLoadableException != null; - boolean loadingOrBackedOff = loader.isLoading() || isBackedOff; - - // If we're not loading or backed off, evaluate the operation if (a) we don't have the next - // chunk yet and we're not finished, or (b) if the last evaluation was over 2000ms ago. - if (!loadingOrBackedOff && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1) - || (now - lastPerformedBufferOperation > 2000))) { - // Perform the evaluation. - lastPerformedBufferOperation = now; - doChunkOperation(); - boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize); - // Update the next load position as appropriate. - if (currentLoadableHolder.chunk == null) { - // Set loadPosition to -1 to indicate that we don't have anything to load. - nextLoadPositionUs = -1; - } else if (chunksDiscarded) { - // Chunks were discarded, so we need to re-evaluate the load position. - nextLoadPositionUs = getNextLoadPositionUs(); - } - } - - // Update the control with our current state, and determine whether we're the next loader. - boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, - loadingOrBackedOff); - - if (isBackedOff) { - long elapsedMillis = now - currentLoadableExceptionTimestamp; - if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { - resumeFromBackOff(); - } - return; - } - - if (!loader.isLoading() && nextLoader) { - maybeStartLoading(); - } - } - - /** - * Gets the next load time, assuming that the next load starts where the previous chunk ended (or - * from the pending reset time, if there is one). - */ - private long getNextLoadPositionUs() { - if (isPendingReset()) { - return pendingResetPositionUs; - } else { - return loadingFinished ? -1 : mediaChunks.getLast().endTimeUs; - } - } - - /** - * Resumes loading. - *

        - * If the {@link ChunkSource} returns a chunk equivalent to the backed off chunk B, then the - * loading of B will be resumed. In all other cases B will be discarded and the new chunk will - * be loaded. - */ - private void resumeFromBackOff() { - currentLoadableException = null; - - Chunk backedOffChunk = currentLoadableHolder.chunk; - if (!isMediaChunk(backedOffChunk)) { - doChunkOperation(); - discardUpstreamMediaChunks(currentLoadableHolder.queueSize); - if (currentLoadableHolder.chunk == backedOffChunk) { - // Chunk was unchanged. Resume loading. - loader.startLoading(backedOffChunk, this); - } else { - // Chunk was changed. Notify that the existing load was canceled. - notifyLoadCanceled(backedOffChunk.bytesLoaded()); - // Start loading the replacement. - maybeStartLoading(); - } - return; - } - - if (backedOffChunk == mediaChunks.getFirst()) { - // We're not able to clear the first media chunk, so we have no choice but to continue - // loading it. - loader.startLoading(backedOffChunk, this); - return; - } - - // The current loadable is the last media chunk. Remove it before we invoke the chunk source, - // and add it back again afterwards. - BaseMediaChunk removedChunk = mediaChunks.removeLast(); - Assertions.checkState(backedOffChunk == removedChunk); - doChunkOperation(); - mediaChunks.add(removedChunk); - - if (currentLoadableHolder.chunk == backedOffChunk) { - // Chunk was unchanged. Resume loading. - loader.startLoading(backedOffChunk, this); - } else { - // Chunk was changed. Notify that the existing load was canceled. - notifyLoadCanceled(backedOffChunk.bytesLoaded()); - // This call will remove and release at least one chunk from the end of mediaChunks. Since - // the current loadable is the last media chunk, it is guaranteed to be removed. - discardUpstreamMediaChunks(currentLoadableHolder.queueSize); - clearCurrentLoadableException(); - maybeStartLoading(); - } - } - - private void maybeStartLoading() { - Chunk currentLoadable = currentLoadableHolder.chunk; - if (currentLoadable == null) { - // Nothing to load. - return; - } - currentLoadStartTimeMs = SystemClock.elapsedRealtime(); - if (isMediaChunk(currentLoadable)) { - BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable; - mediaChunk.init(sampleQueue); - mediaChunks.add(mediaChunk); - if (isPendingReset()) { - pendingResetPositionUs = NO_RESET_PENDING; - } - notifyLoadStarted(mediaChunk.dataSpec.length, mediaChunk.type, mediaChunk.trigger, - mediaChunk.format, mediaChunk.startTimeUs, mediaChunk.endTimeUs); - } else { - notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type, - currentLoadable.trigger, currentLoadable.format, -1, -1); - } - loader.startLoading(currentLoadable, this); - } - - /** - * Sets up the {@link #currentLoadableHolder}, passes it to the chunk source to cause it to be - * updated with the next operation, and updates {@link #loadingFinished} if the end of the stream - * is reached. - */ - private void doChunkOperation() { - currentLoadableHolder.endOfStream = false; - currentLoadableHolder.queueSize = readOnlyMediaChunks.size(); - chunkSource.getChunkOperation(readOnlyMediaChunks, - pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs, - currentLoadableHolder); - loadingFinished = currentLoadableHolder.endOfStream; - } - - /** - * Discard upstream media chunks until the queue length is equal to the length specified. - * - * @param queueLength The desired length of the queue. - * @return True if chunks were discarded. False otherwise. - */ - private boolean discardUpstreamMediaChunks(int queueLength) { - if (mediaChunks.size() <= queueLength) { - return false; - } - long startTimeUs = 0; - long endTimeUs = mediaChunks.getLast().endTimeUs; - - BaseMediaChunk removed = null; - while (mediaChunks.size() > queueLength) { - removed = mediaChunks.removeLast(); - startTimeUs = removed.startTimeUs; - loadingFinished = false; - } - sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex()); - - notifyUpstreamDiscarded(startTimeUs, endTimeUs); - return true; - } - - private boolean isMediaChunk(Chunk chunk) { - return chunk instanceof BaseMediaChunk; - } - - private boolean isPendingReset() { - return pendingResetPositionUs != NO_RESET_PENDING; - } - - private long getRetryDelayMillis(long errorCount) { - return Math.min((errorCount - 1) * 1000, 5000); - } - - protected final long usToMs(long timeUs) { - return timeUs / 1000; - } - - private void notifyLoadStarted(final long length, final int type, final int trigger, - final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadStarted(eventSourceId, length, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs)); - } - }); - } - } - - private void notifyLoadCompleted(final long bytesLoaded, final int type, final int trigger, - final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs, - final long elapsedRealtimeMs, final long loadDurationMs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadCompleted(eventSourceId, bytesLoaded, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs); - } - }); - } - } - - private void notifyLoadCanceled(final long bytesLoaded) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadCanceled(eventSourceId, bytesLoaded); - } - }); - } - } - - private void notifyLoadError(final IOException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadError(eventSourceId, e); - } - }); - } - } - - private void notifyUpstreamDiscarded(final long mediaStartTimeUs, final long mediaEndTimeUs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onUpstreamDiscarded(eventSourceId, usToMs(mediaStartTimeUs), - usToMs(mediaEndTimeUs)); - } - }); - } - } - - private void notifyDownstreamFormatChanged(final Format format, final int trigger, - final long positionUs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger, - usToMs(positionUs)); - } - }); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSource.java deleted file mode 100755 index 124c7ae164f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ChunkSource.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.MediaFormat; -import java.io.IOException; -import java.util.List; - -/** - * A provider of {@link Chunk}s for a {@link ChunkSampleSource} to load. - */ -/* - * TODO: Share more state between this interface and {@link ChunkSampleSource}. In particular - * implementations of this class needs to know about errors, and should be more tightly integrated - * into the process of resuming loading of a chunk after an error occurs. - */ -public interface ChunkSource { - - /** - * If the source is currently having difficulty preparing or providing chunks, then this method - * throws the underlying error. Otherwise does nothing. - * - * @throws IOException The underlying error. - */ - void maybeThrowError() throws IOException; - - /** - * Prepares the source. - *

        - * The method can be called repeatedly until the return value indicates success. - * - * @return True if the source was prepared, false otherwise. - */ - boolean prepare(); - - /** - * Returns the number of tracks exposed by the source. - *

        - * This method should only be called after the source has been prepared. - * - * @return The number of tracks. - */ - int getTrackCount(); - - /** - * Gets the format of the specified track. - *

        - * This method should only be called after the source has been prepared. - * - * @param track The track index. - * @return The format of the track. - */ - MediaFormat getFormat(int track); - - /** - * Enable the source for the specified track. - *

        - * This method should only be called after the source has been prepared, and when the source is - * disabled. - * - * @param track The track index. - */ - void enable(int track); - - /** - * Indicates to the source that it should still be checking for updates to the stream. - *

        - * This method should only be called when the source is enabled. - * - * @param playbackPositionUs The current playback position. - */ - void continueBuffering(long playbackPositionUs); - - /** - * Updates the provided {@link ChunkOperationHolder} to contain the next operation that should - * be performed by the calling {@link ChunkSampleSource}. - *

        - * This method should only be called when the source is enabled. - * - * @param queue A representation of the currently buffered {@link MediaChunk}s. - * @param playbackPositionUs The current playback position. If the queue is empty then this - * parameter is the position from which playback is expected to start (or restart) and hence - * should be interpreted as a seek position. - * @param out A holder for the next operation, whose {@link ChunkOperationHolder#endOfStream} is - * initially set to false, whose {@link ChunkOperationHolder#queueSize} is initially equal to - * the length of the queue, and whose {@link ChunkOperationHolder#chunk} is initially equal to - * null or a {@link Chunk} previously supplied by the {@link ChunkSource} that the caller has - * not yet finished loading. In the latter case the chunk can either be replaced or left - * unchanged. Note that leaving the chunk unchanged is both preferred and more efficient than - * replacing it with a new but identical chunk. - */ - void getChunkOperation(List queue, long playbackPositionUs, - ChunkOperationHolder out); - - /** - * Invoked when the {@link ChunkSampleSource} has finished loading a chunk obtained from this - * source. - *

        - * This method should only be called when the source is enabled. - * - * @param chunk The chunk whose load has been completed. - */ - void onChunkLoadCompleted(Chunk chunk); - - /** - * Invoked when the {@link ChunkSampleSource} encounters an error loading a chunk obtained from - * this source. - *

        - * This method should only be called when the source is enabled. - * - * @param chunk The chunk whose load encountered the error. - * @param e The error. - */ - void onChunkLoadError(Chunk chunk, Exception e); - - /** - * Disables the source. - *

        - * This method should only be called when the source is enabled. - * - * @param queue A representation of the currently buffered {@link MediaChunk}s. - */ - void disable(List queue); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ContainerMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ContainerMediaChunk.java deleted file mode 100755 index 87bc8eb90ec..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/ContainerMediaChunk.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.DefaultExtractorInput; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * A {@link BaseMediaChunk} that uses an {@link Extractor} to parse sample data. - */ -public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackOutput { - - private final ChunkExtractorWrapper extractorWrapper; - private final long sampleOffsetUs; - private final int adaptiveMaxWidth; - private final int adaptiveMaxHeight; - - private MediaFormat mediaFormat; - private DrmInitData drmInitData; - - private volatile int bytesLoaded; - private volatile boolean loadCanceled; - - /** - * @param dataSource A {@link DataSource} for loading the data. - * @param dataSpec Defines the data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param startTimeUs The start time of the media contained by the chunk, in microseconds. - * @param endTimeUs The end time of the media contained by the chunk, in microseconds. - * @param chunkIndex The index of the chunk. - * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. - * @param extractorWrapper A wrapped extractor to use for parsing the data. - * @param mediaFormat The {@link MediaFormat} of the chunk, if known. May be null if the data is - * known to define its own format. - * @param adaptiveMaxWidth If this chunk contains video and is part of an adaptive playback, this - * is the maximum width of the video in pixels that will be encountered during the playback. - * {@link MediaFormat#NO_VALUE} otherwise. - * @param adaptiveMaxHeight If this chunk contains video and is part of an adaptive playback, this - * is the maximum height of the video in pixels that will be encountered during the playback. - * {@link MediaFormat#NO_VALUE} otherwise. - * @param drmInitData The {@link DrmInitData} for the chunk. Null if the media is not drm - * protected. May also be null if the data is known to define its own initialization data. - * @param isMediaFormatFinal True if {@code mediaFormat} and {@code drmInitData} are known to be - * correct and final. False if the data may define its own format or initialization data. - * @param parentId Identifier for a parent from which this chunk originates. - */ - public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - long startTimeUs, long endTimeUs, int chunkIndex, long sampleOffsetUs, - ChunkExtractorWrapper extractorWrapper, MediaFormat mediaFormat, int adaptiveMaxWidth, - int adaptiveMaxHeight, DrmInitData drmInitData, boolean isMediaFormatFinal, int parentId) { - super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, - isMediaFormatFinal, parentId); - this.extractorWrapper = extractorWrapper; - this.sampleOffsetUs = sampleOffsetUs; - this.adaptiveMaxWidth = adaptiveMaxWidth; - this.adaptiveMaxHeight = adaptiveMaxHeight; - this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth, - adaptiveMaxHeight); - this.drmInitData = drmInitData; - } - - @Override - public final long bytesLoaded() { - return bytesLoaded; - } - - @Override - public final MediaFormat getMediaFormat() { - return mediaFormat; - } - - @Override - public final DrmInitData getDrmInitData() { - return drmInitData; - } - - // SingleTrackOutput implementation. - - @Override - public final void seekMap(SeekMap seekMap) { - // Do nothing. - } - - @Override - public final void drmInitData(DrmInitData drmInitData) { - this.drmInitData = drmInitData; - } - - @Override - public final void format(MediaFormat mediaFormat) { - this.mediaFormat = getAdjustedMediaFormat(mediaFormat, sampleOffsetUs, adaptiveMaxWidth, - adaptiveMaxHeight); - } - - @Override - public final int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return getOutput().sampleData(input, length, allowEndOfInput); - } - - @Override - public final void sampleData(ParsableByteArray data, int length) { - getOutput().sampleData(data, length); - } - - @Override - public final void sampleMetadata(long timeUs, int flags, int size, int offset, - byte[] encryptionKey) { - getOutput().sampleMetadata(timeUs + sampleOffsetUs, flags, size, offset, encryptionKey); - } - - // Loadable implementation. - - @Override - public final void cancelLoad() { - loadCanceled = true; - } - - @Override - public final boolean isLoadCanceled() { - return loadCanceled; - } - - @SuppressWarnings("NonAtomicVolatileUpdate") - @Override - public final void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); - try { - // Create and open the input. - ExtractorInput input = new DefaultExtractorInput(dataSource, - loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); - if (bytesLoaded == 0) { - // Set the target to ourselves. - extractorWrapper.init(this); - } - // Load and parse the sample data. - try { - int result = Extractor.RESULT_CONTINUE; - while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - result = extractorWrapper.read(input); - } - } finally { - bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); - } - } finally { - dataSource.close(); - } - } - - // Private methods. - - private static MediaFormat getAdjustedMediaFormat(MediaFormat format, long sampleOffsetUs, - int adaptiveMaxWidth, int adaptiveMaxHeight) { - if (format == null) { - return null; - } - if (sampleOffsetUs != 0 && format.subsampleOffsetUs != MediaFormat.OFFSET_SAMPLE_RELATIVE) { - format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs); - } - if (adaptiveMaxWidth != MediaFormat.NO_VALUE || adaptiveMaxHeight != MediaFormat.NO_VALUE) { - format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight); - } - return format; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Format.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Format.java deleted file mode 100755 index 9578743932a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/Format.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.util.Assertions; -import java.util.Comparator; - -/** - * Defines the high level format of a media stream. - */ -public class Format { - - /** - * Sorts {@link Format} objects in order of decreasing bandwidth. - */ - public static final class DecreasingBandwidthComparator implements Comparator { - - @Override - public int compare(Format a, Format b) { - return b.bitrate - a.bitrate; - } - - } - - /** - * An identifier for the format. - */ - public final String id; - - /** - * The mime type of the format. - */ - public final String mimeType; - - /** - * The average bandwidth in bits per second. - */ - public final int bitrate; - - /** - * The width of the video in pixels, or -1 if unknown or not applicable. - */ - public final int width; - - /** - * The height of the video in pixels, or -1 if unknown or not applicable. - */ - public final int height; - - /** - * The video frame rate in frames per second, or -1 if unknown or not applicable. - */ - public final float frameRate; - - /** - * The number of audio channels, or -1 if unknown or not applicable. - */ - public final int audioChannels; - - /** - * The audio sampling rate in Hz, or -1 if unknown or not applicable. - */ - public final int audioSamplingRate; - - /** - * The codecs used to decode the format. Can be {@code null} if unknown. - */ - public final String codecs; - - /** - * The language of the format. Can be null if unknown. - *

        - * The language codes are two-letter lowercase ISO language codes (such as "en") as defined by - * ISO 639-1. - */ - public final String language; - - /** - * @param id The format identifier. - * @param mimeType The format mime type. - * @param width The width of the video in pixels, or -1 if unknown or not applicable. - * @param height The height of the video in pixels, or -1 if unknown or not applicable. - * @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not - * applicable. - * @param numChannels The number of audio channels, or -1 if unknown or not applicable. - * @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable. - * @param bitrate The average bandwidth of the format in bits per second. - */ - public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels, - int audioSamplingRate, int bitrate) { - this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, null); - } - - /** - * @param id The format identifier. - * @param mimeType The format mime type. - * @param width The width of the video in pixels, or -1 if unknown or not applicable. - * @param height The height of the video in pixels, or -1 if unknown or not applicable. - * @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not - * applicable. - * @param numChannels The number of audio channels, or -1 if unknown or not applicable. - * @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable. - * @param bitrate The average bandwidth of the format in bits per second. - * @param language The language of the format. - */ - public Format(String id, String mimeType, int width, int height, float frameRate, int numChannels, - int audioSamplingRate, int bitrate, String language) { - this(id, mimeType, width, height, frameRate, numChannels, audioSamplingRate, bitrate, language, - null); - } - - /** - * @param id The format identifier. - * @param mimeType The format mime type. - * @param width The width of the video in pixels, or -1 if unknown or not applicable. - * @param height The height of the video in pixels, or -1 if unknown or not applicable. - * @param frameRate The frame rate of the video in frames per second, or -1 if unknown or not - * applicable. - * @param audioChannels The number of audio channels, or -1 if unknown or not applicable. - * @param audioSamplingRate The audio sampling rate in Hz, or -1 if unknown or not applicable. - * @param bitrate The average bandwidth of the format in bits per second. - * @param language The language of the format. - * @param codecs The codecs used to decode the format. - */ - public Format(String id, String mimeType, int width, int height, float frameRate, - int audioChannels, int audioSamplingRate, int bitrate, String language, String codecs) { - this.id = Assertions.checkNotNull(id); - this.mimeType = mimeType; - this.width = width; - this.height = height; - this.frameRate = frameRate; - this.audioChannels = audioChannels; - this.audioSamplingRate = audioSamplingRate; - this.bitrate = bitrate; - this.language = language; - this.codecs = codecs; - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - /** - * Implements equality based on {@link #id} only. - */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - Format other = (Format) obj; - return other.id.equals(id); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatEvaluator.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatEvaluator.java deleted file mode 100755 index 0e34e5d0be1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatEvaluator.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.upstream.BandwidthMeter; -import java.util.List; -import java.util.Random; - -/** - * Selects from a number of available formats during playback. - */ -public interface FormatEvaluator { - - /** - * Enables the evaluator. - */ - void enable(); - - /** - * Disables the evaluator. - */ - void disable(); - - /** - * Update the supplied evaluation. - *

        - * When the method is invoked, {@code evaluation} will contain the currently selected - * format (null for the first evaluation), the most recent trigger (TRIGGER_INITIAL for the - * first evaluation) and the current queue size. The implementation should update these - * fields as necessary. - *

        - * The trigger should be considered "sticky" for as long as a given representation is selected, - * and so should only be changed if the representation is also changed. - * - * @param queue A read only representation of the currently buffered {@link MediaChunk}s. - * @param playbackPositionUs The current playback position. - * @param formats The formats from which to select, ordered by decreasing bandwidth. - * @param evaluation The evaluation. - */ - // TODO: Pass more useful information into this method, and finalize the interface. - void evaluate(List queue, long playbackPositionUs, Format[] formats, - Evaluation evaluation); - - /** - * A format evaluation. - */ - public static final class Evaluation { - - /** - * The desired size of the queue. - */ - public int queueSize; - - /** - * The sticky reason for the format selection. - */ - public int trigger; - - /** - * The selected format. - */ - public Format format; - - public Evaluation() { - trigger = Chunk.TRIGGER_INITIAL; - } - - } - - /** - * Always selects the first format. - */ - public static final class FixedEvaluator implements FormatEvaluator { - - @Override - public void enable() { - // Do nothing. - } - - @Override - public void disable() { - // Do nothing. - } - - @Override - public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { - evaluation.format = formats[0]; - } - - } - - /** - * Selects randomly between the available formats. - */ - public static final class RandomEvaluator implements FormatEvaluator { - - private final Random random; - - public RandomEvaluator() { - this.random = new Random(); - } - - /** - * @param seed A seed for the underlying random number generator. - */ - public RandomEvaluator(int seed) { - this.random = new Random(seed); - } - - @Override - public void enable() { - // Do nothing. - } - - @Override - public void disable() { - // Do nothing. - } - - @Override - public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { - Format newFormat = formats[random.nextInt(formats.length)]; - if (evaluation.format != null && !evaluation.format.equals(newFormat)) { - evaluation.trigger = Chunk.TRIGGER_ADAPTIVE; - } - evaluation.format = newFormat; - } - - } - - /** - * An adaptive evaluator for video formats, which attempts to select the best quality possible - * given the current network conditions and state of the buffer. - *

        - * This implementation should be used for video only, and should not be used for audio. It is a - * reference implementation only. It is recommended that application developers implement their - * own adaptive evaluator to more precisely suit their use case. - */ - public static final class AdaptiveEvaluator implements FormatEvaluator { - - public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000; - - public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000; - public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000; - public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000; - public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f; - - private final BandwidthMeter bandwidthMeter; - - private final int maxInitialBitrate; - private final long minDurationForQualityIncreaseUs; - private final long maxDurationForQualityDecreaseUs; - private final long minDurationToRetainAfterDiscardUs; - private final float bandwidthFraction; - - /** - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - */ - public AdaptiveEvaluator(BandwidthMeter bandwidthMeter) { - this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, - DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, - DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, - DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); - } - - /** - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed - * when bandwidthMeter cannot provide an estimate due to playback having only just started. - * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for - * the evaluator to consider switching to a higher quality format. - * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for - * the evaluator to consider switching to a lower quality format. - * @param minDurationToRetainAfterDiscardMs When switching to a significantly higher quality - * format, the evaluator may discard some of the media that it has already buffered at the - * lower quality, so as to switch up to the higher quality faster. This is the minimum - * duration of media that must be retained at the lower quality. - * @param bandwidthFraction The fraction of the available bandwidth that the evaluator should - * consider available for use. Setting to a value less than 1 is recommended to account - * for inaccuracies in the bandwidth estimator. - */ - public AdaptiveEvaluator(BandwidthMeter bandwidthMeter, - int maxInitialBitrate, - int minDurationForQualityIncreaseMs, - int maxDurationForQualityDecreaseMs, - int minDurationToRetainAfterDiscardMs, - float bandwidthFraction) { - this.bandwidthMeter = bandwidthMeter; - this.maxInitialBitrate = maxInitialBitrate; - this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L; - this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; - this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L; - this.bandwidthFraction = bandwidthFraction; - } - - @Override - public void enable() { - // Do nothing. - } - - @Override - public void disable() { - // Do nothing. - } - - @Override - public void evaluate(List queue, long playbackPositionUs, - Format[] formats, Evaluation evaluation) { - long bufferedDurationUs = queue.isEmpty() ? 0 - : queue.get(queue.size() - 1).endTimeUs - playbackPositionUs; - Format current = evaluation.format; - Format ideal = determineIdealFormat(formats, bandwidthMeter.getBitrateEstimate()); - boolean isHigher = ideal != null && current != null && ideal.bitrate > current.bitrate; - boolean isLower = ideal != null && current != null && ideal.bitrate < current.bitrate; - if (isHigher) { - if (bufferedDurationUs < minDurationForQualityIncreaseUs) { - // The ideal format is a higher quality, but we have insufficient buffer to - // safely switch up. Defer switching up for now. - ideal = current; - } else if (bufferedDurationUs >= minDurationToRetainAfterDiscardUs) { - // We're switching from an SD stream to a stream of higher resolution. Consider - // discarding already buffered media chunks. Specifically, discard media chunks starting - // from the first one that is of lower bandwidth, lower resolution and that is not HD. - for (int i = 1; i < queue.size(); i++) { - MediaChunk thisChunk = queue.get(i); - long durationBeforeThisSegmentUs = thisChunk.startTimeUs - playbackPositionUs; - if (durationBeforeThisSegmentUs >= minDurationToRetainAfterDiscardUs - && thisChunk.format.bitrate < ideal.bitrate - && thisChunk.format.height < ideal.height - && thisChunk.format.height < 720 - && thisChunk.format.width < 1280) { - // Discard chunks from this one onwards. - evaluation.queueSize = i; - break; - } - } - } - } else if (isLower && current != null - && bufferedDurationUs >= maxDurationForQualityDecreaseUs) { - // The ideal format is a lower quality, but we have sufficient buffer to defer switching - // down for now. - ideal = current; - } - if (current != null && ideal != current) { - evaluation.trigger = Chunk.TRIGGER_ADAPTIVE; - } - evaluation.format = ideal; - } - - /** - * Compute the ideal format ignoring buffer health. - */ - private Format determineIdealFormat(Format[] formats, long bitrateEstimate) { - long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE - ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction); - for (int i = 0; i < formats.length; i++) { - Format format = formats[i]; - if (format.bitrate <= effectiveBitrate) { - return format; - } - } - // We didn't manage to calculate a suitable format. Return the lowest quality format. - return formats[formats.length - 1]; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatWrapper.java deleted file mode 100755 index 4ee8e4723e3..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/FormatWrapper.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -/** - * Represents an object that wraps a {@link Format}. - */ -public interface FormatWrapper { - - /** - * Returns the wrapped format. - */ - Format getFormat(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/InitializationChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/InitializationChunk.java deleted file mode 100755 index 80174e1900e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/InitializationChunk.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackOutput; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.DefaultExtractorInput; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * A {@link Chunk} that uses an {@link Extractor} to parse initialization data for single track. - */ -public final class InitializationChunk extends Chunk implements SingleTrackOutput { - - private final ChunkExtractorWrapper extractorWrapper; - - // Initialization results. Set by the loader thread and read by any thread that knows loading - // has completed. These variables do not need to be volatile, since a memory barrier must occur - // for the reading thread to know that loading has completed. - private MediaFormat mediaFormat; - private DrmInitData drmInitData; - private SeekMap seekMap; - - private volatile int bytesLoaded; - private volatile boolean loadCanceled; - - public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - ChunkExtractorWrapper extractorWrapper) { - this(dataSource, dataSpec, trigger, format, extractorWrapper, Chunk.NO_PARENT_ID); - } - - /** - * Constructor for a chunk of media samples. - * - * @param dataSource A {@link DataSource} for loading the initialization data. - * @param dataSpec Defines the initialization data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param extractorWrapper A wrapped extractor to use for parsing the initialization data. - * @param parentId Identifier for a parent from which this chunk originates. - */ - public InitializationChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - ChunkExtractorWrapper extractorWrapper, int parentId) { - super(dataSource, dataSpec, Chunk.TYPE_MEDIA_INITIALIZATION, trigger, format, parentId); - this.extractorWrapper = extractorWrapper; - } - - @Override - public long bytesLoaded() { - return bytesLoaded; - } - - /** - * True if a {@link MediaFormat} was parsed from the chunk. False otherwise. - *

        - * Should be called after loading has completed. - */ - public boolean hasFormat() { - return mediaFormat != null; - } - - /** - * Returns a {@link MediaFormat} parsed from the chunk, or null. - *

        - * Should be called after loading has completed. - */ - public MediaFormat getFormat() { - return mediaFormat; - } - - /** - * True if a {@link DrmInitData} was parsed from the chunk. False otherwise. - *

        - * Should be called after loading has completed. - */ - public boolean hasDrmInitData() { - return drmInitData != null; - } - - /** - * Returns a {@link DrmInitData} parsed from the chunk, or null. - *

        - * Should be called after loading has completed. - */ - public DrmInitData getDrmInitData() { - return drmInitData; - } - - /** - * True if a {@link SeekMap} was parsed from the chunk. False otherwise. - *

        - * Should be called after loading has completed. - */ - public boolean hasSeekMap() { - return seekMap != null; - } - - /** - * Returns a {@link SeekMap} parsed from the chunk, or null. - *

        - * Should be called after loading has completed. - */ - public SeekMap getSeekMap() { - return seekMap; - } - - // SingleTrackOutput implementation. - - @Override - public void seekMap(SeekMap seekMap) { - this.seekMap = seekMap; - } - - @Override - public void drmInitData(DrmInitData drmInitData) { - this.drmInitData = drmInitData; - } - - @Override - public void format(MediaFormat mediaFormat) { - this.mediaFormat = mediaFormat; - } - - @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - throw new IllegalStateException("Unexpected sample data in initialization chunk"); - } - - @Override - public void sampleData(ParsableByteArray data, int length) { - throw new IllegalStateException("Unexpected sample data in initialization chunk"); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - throw new IllegalStateException("Unexpected sample data in initialization chunk"); - } - - // Loadable implementation. - - @Override - public void cancelLoad() { - loadCanceled = true; - } - - @Override - public boolean isLoadCanceled() { - return loadCanceled; - } - - @SuppressWarnings("NonAtomicVolatileUpdate") - @Override - public void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); - try { - // Create and open the input. - ExtractorInput input = new DefaultExtractorInput(dataSource, - loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); - if (bytesLoaded == 0) { - // Set the target to ourselves. - extractorWrapper.init(this); - } - // Load and parse the initialization data. - try { - int result = Extractor.RESULT_CONTINUE; - while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - result = extractorWrapper.read(input); - } - } finally { - bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); - } - } finally { - dataSource.close(); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/MediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/MediaChunk.java deleted file mode 100755 index 2b05536edf8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/MediaChunk.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Assertions; - -/** - * An abstract base class for {@link Chunk}s that contain media samples. - */ -public abstract class MediaChunk extends Chunk { - - /** - * The start time of the media contained by the chunk. - */ - public final long startTimeUs; - /** - * The end time of the media contained by the chunk. - */ - public final long endTimeUs; - /** - * The chunk index. - */ - public final int chunkIndex; - - public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - long startTimeUs, long endTimeUs, int chunkIndex) { - this(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, - Chunk.NO_PARENT_ID); - } - - /** - * @param dataSource A {@link DataSource} for loading the data. - * @param dataSpec Defines the data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param startTimeUs The start time of the media contained by the chunk, in microseconds. - * @param endTimeUs The end time of the media contained by the chunk, in microseconds. - * @param chunkIndex The index of the chunk. - * @param parentId Identifier for a parent from which this chunk originates. - */ - public MediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - long startTimeUs, long endTimeUs, int chunkIndex, int parentId) { - super(dataSource, dataSpec, Chunk.TYPE_MEDIA, trigger, format, parentId); - Assertions.checkNotNull(format); - this.startTimeUs = startTimeUs; - this.endTimeUs = endTimeUs; - this.chunkIndex = chunkIndex; - } - - public int getNextChunkIndex() { - return chunkIndex + 1; - } - - public long getDurationUs() { - return endTimeUs - startTimeUs; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/SingleSampleMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/SingleSampleMediaChunk.java deleted file mode 100755 index 681d03d3d17..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/SingleSampleMediaChunk.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * A {@link BaseMediaChunk} for chunks consisting of a single raw sample. - */ -public final class SingleSampleMediaChunk extends BaseMediaChunk { - - private final MediaFormat sampleFormat; - private final DrmInitData sampleDrmInitData; - - private volatile int bytesLoaded; - private volatile boolean loadCanceled; - - /** - * @param dataSource A {@link DataSource} for loading the data. - * @param dataSpec Defines the data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param startTimeUs The start time of the media contained by the chunk, in microseconds. - * @param endTimeUs The end time of the media contained by the chunk, in microseconds. - * @param chunkIndex The index of the chunk. - * @param sampleFormat The format of the sample. - * @param sampleDrmInitData The {@link DrmInitData} for the sample. Null if the sample is not drm - * protected. - * @param parentId Identifier for a parent from which this chunk originates. - */ - public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, int trigger, - Format format, long startTimeUs, long endTimeUs, int chunkIndex, MediaFormat sampleFormat, - DrmInitData sampleDrmInitData, int parentId) { - super(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, chunkIndex, true, - parentId); - this.sampleFormat = sampleFormat; - this.sampleDrmInitData = sampleDrmInitData; - } - - @Override - public long bytesLoaded() { - return bytesLoaded; - } - - @Override - public MediaFormat getMediaFormat() { - return sampleFormat; - } - - @Override - public DrmInitData getDrmInitData() { - return sampleDrmInitData; - } - - // Loadable implementation. - - @Override - public void cancelLoad() { - loadCanceled = true; - } - - @Override - public boolean isLoadCanceled() { - return loadCanceled; - } - - @SuppressWarnings("NonAtomicVolatileUpdate") - @Override - public void load() throws IOException, InterruptedException { - DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); - try { - // Create and open the input. - dataSource.open(loadDataSpec); - // Load the sample data. - int result = 0; - while (result != C.RESULT_END_OF_INPUT) { - bytesLoaded += result; - result = getOutput().sampleData(dataSource, Integer.MAX_VALUE, true); - } - int sampleSize = bytesLoaded; - getOutput().sampleMetadata(startTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); - } finally { - dataSource.close(); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/VideoFormatSelectorUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/VideoFormatSelectorUtil.java deleted file mode 100755 index a317609bd04..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/VideoFormatSelectorUtil.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.chunk; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Point; -import android.text.TextUtils; -import android.util.Log; -import android.view.Display; -import android.view.WindowManager; -import org.telegram.messenger.exoplayer.MediaCodecUtil; -import org.telegram.messenger.exoplayer.MediaCodecUtil.DecoderQueryException; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -/** - * Selects from possible video formats. - */ -public final class VideoFormatSelectorUtil { - - private static final String TAG = "VideoFormatSelectorUtil"; - - /** - * If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the - * corresponding viewport dimension, then the video is considered as filling the viewport (in that - * dimension). - */ - private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; - - /** - * Chooses a suitable subset from a number of video formats, to be rendered on the device's - * default display. - * - * @param context A context. - * @param formatWrappers Wrapped formats from which to select. - * @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all - * mime types. - * @param filterHdFormats True to filter HD formats. False otherwise. - * @return An array holding the indices of the selected formats. - * @throws DecoderQueryException Thrown if there was an error querying decoders. - */ - public static int[] selectVideoFormatsForDefaultDisplay(Context context, - List formatWrappers, String[] allowedContainerMimeTypes, - boolean filterHdFormats) throws DecoderQueryException { - Point viewportSize = getDisplaySize(context); // Assume the viewport is fullscreen. - return selectVideoFormats(formatWrappers, allowedContainerMimeTypes, filterHdFormats, true, - false, viewportSize.x, viewportSize.y); - } - - /** - * Chooses a suitable subset from a number of video formats. - *

        - * A format is filtered (i.e. not selected) if: - *

          - *
        • {@code allowedContainerMimeTypes} is non-null and the format does not have one of the - * permitted mime types. - *
        • {@code filterHdFormats} is true and the format is HD. - *
        • It's determined that the video decoder isn't powerful enough to decode the format. - *
        • There exists another format of lower resolution whose resolution exceeds the maximum size - * in pixels that the video can be rendered within the viewport. - *
        - * - * @param formatWrappers Wrapped formats from which to select. - * @param allowedContainerMimeTypes An array of allowed container mime types. Null allows all - * mime types. - * @param filterHdFormats True to filter HD formats. False otherwise. - * @param orientationMayChange True if the video's orientation may change with respect to the - * viewport during playback. - * @param secureDecoder True if secure decoder is required. - * @param viewportWidth The width in pixels of the viewport within which the video will be - * displayed. If the viewport size may change, this should be set to the maximum possible - * width. -1 if selection should not be constrained by a viewport. - * @param viewportHeight The height in pixels of the viewport within which the video will be - * displayed. If the viewport size may change, this should be set to the maximum possible - * height. -1 if selection should not be constrained by a viewport. - * @return An array holding the indices of the selected formats. - * @throws DecoderQueryException - */ - public static int[] selectVideoFormats(List formatWrappers, - String[] allowedContainerMimeTypes, boolean filterHdFormats, boolean orientationMayChange, - boolean secureDecoder, int viewportWidth, int viewportHeight) throws DecoderQueryException { - int maxVideoPixelsToRetain = Integer.MAX_VALUE; - ArrayList selectedIndexList = new ArrayList<>(); - - // First pass to filter out formats that individually fail to meet the selection criteria. - int formatWrapperCount = formatWrappers.size(); - for (int i = 0; i < formatWrapperCount; i++) { - Format format = formatWrappers.get(i).getFormat(); - if (isFormatPlayable(format, allowedContainerMimeTypes, filterHdFormats, secureDecoder)) { - // Select the format for now. It may still be filtered in the second pass below. - selectedIndexList.add(i); - // Keep track of the number of pixels of the selected format whose resolution is the - // smallest to exceed the maximum size at which it can be displayed within the viewport. - // We'll discard formats of higher resolution in a second pass. - if (format.width > 0 && format.height > 0 && viewportWidth > 0 && viewportHeight > 0) { - Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange, - viewportWidth, viewportHeight, format.width, format.height); - int videoPixels = format.width * format.height; - if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN) - && format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN) - && videoPixels < maxVideoPixelsToRetain) { - maxVideoPixelsToRetain = videoPixels; - } - } - } - } - - // Second pass to filter out formats that exceed maxVideoPixelsToRetain. These formats are have - // unnecessarily high resolution given the size at which the video will be displayed within the - // viewport. - if (maxVideoPixelsToRetain != Integer.MAX_VALUE) { - for (int i = selectedIndexList.size() - 1; i >= 0; i--) { - Format format = formatWrappers.get(selectedIndexList.get(i)).getFormat(); - if (format.width > 0 && format.height > 0 - && format.width * format.height > maxVideoPixelsToRetain) { - selectedIndexList.remove(i); - } - } - } - - return Util.toArray(selectedIndexList); - } - - /** - * Determines whether an individual format is playable, given an array of allowed container types, - * whether HD formats should be filtered and a maximum decodable frame size in pixels. - */ - private static boolean isFormatPlayable(Format format, String[] allowedContainerMimeTypes, - boolean filterHdFormats, boolean secureDecoder) throws DecoderQueryException { - if (allowedContainerMimeTypes != null - && !Util.contains(allowedContainerMimeTypes, format.mimeType)) { - // Filtering format based on its container mime type. - return false; - } - if (filterHdFormats && (format.width >= 1280 || format.height >= 720)) { - // Filtering format because it's HD. - return false; - } - if (format.width > 0 && format.height > 0) { - if (Util.SDK_INT >= 21) { - String videoMediaMimeType = MimeTypes.getVideoMediaMimeType(format.codecs); - if (MimeTypes.VIDEO_UNKNOWN.equals(videoMediaMimeType)) { - // Assume the video is H.264. - videoMediaMimeType = MimeTypes.VIDEO_H264; - } - if (format.frameRate > 0) { - return MediaCodecUtil.isSizeAndRateSupportedV21(videoMediaMimeType, secureDecoder, - format.width, format.height, format.frameRate); - } else { - return MediaCodecUtil.isSizeSupportedV21(videoMediaMimeType, secureDecoder, format.width, - format.height); - } - } - // Assume the video is H.264. - if (format.width * format.height > MediaCodecUtil.maxH264DecodableFrameSize()) { - // Filtering format because it exceeds the maximum decodable frame size. - return false; - } - } - return true; - } - - /** - * Given viewport dimensions and video dimensions, computes the maximum size of the video as it - * will be rendered to fit inside of the viewport. - */ - private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth, - int viewportHeight, int videoWidth, int videoHeight) { - if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) { - // Rotation is allowed, and the video will be larger in the rotated viewport. - int tempViewportWidth = viewportWidth; - viewportWidth = viewportHeight; - viewportHeight = tempViewportWidth; - } - - if (videoWidth * viewportHeight >= videoHeight * viewportWidth) { - // Horizontal letter-boxing along top and bottom. - return new Point(viewportWidth, Util.ceilDivide(viewportWidth * videoHeight, videoWidth)); - } else { - // Vertical letter-boxing along edges. - return new Point(Util.ceilDivide(viewportHeight * videoWidth, videoHeight), viewportHeight); - } - } - - private static Point getDisplaySize(Context context) { - // Before API 25 the platform Display object does not provide a working way to identify Android - // TVs that can show 4k resolution in a SurfaceView, so check for supported devices here. - if (Util.SDK_INT < 25) { - if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL != null && Util.MODEL.startsWith("BRAVIA") - && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { - return new Point(3840, 2160); - } else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL != null - && Util.MODEL.contains("SHIELD")) { - // Attempt to read sys.display-size. - String sysDisplaySize = null; - try { - Class systemProperties = Class.forName("android.os.SystemProperties"); - Method getMethod = systemProperties.getMethod("get", String.class); - sysDisplaySize = (String) getMethod.invoke(systemProperties, "sys.display-size"); - } catch (Exception e) { - Log.e(TAG, "Failed to read sys.display-size", e); - } - // If we managed to read sys.display-size, attempt to parse it. - if (!TextUtils.isEmpty(sysDisplaySize)) { - try { - String[] sysDisplaySizeParts = sysDisplaySize.trim().split("x"); - if (sysDisplaySizeParts.length == 2) { - int width = Integer.parseInt(sysDisplaySizeParts[0]); - int height = Integer.parseInt(sysDisplaySizeParts[1]); - if (width > 0 && height > 0) { - return new Point(width, height); - } - } - } catch (NumberFormatException e) { - // Do nothing. - } - Log.e(TAG, "Invalid sys.display-size: " + sysDisplaySize); - } - } - } - - WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - Display display = windowManager.getDefaultDisplay(); - Point displaySize = new Point(); - if (Util.SDK_INT >= 23) { - getDisplaySizeV23(display, displaySize); - } else if (Util.SDK_INT >= 17) { - getDisplaySizeV17(display, displaySize); - } else if (Util.SDK_INT >= 16) { - getDisplaySizeV16(display, displaySize); - } else { - getDisplaySizeV9(display, displaySize); - } - return displaySize; - } - - @TargetApi(23) - private static void getDisplaySizeV23(Display display, Point outSize) { - Display.Mode mode = display.getMode(); - outSize.x = mode.getPhysicalWidth(); - outSize.y = mode.getPhysicalHeight(); - } - - @TargetApi(17) - private static void getDisplaySizeV17(Display display, Point outSize) { - display.getRealSize(outSize); - } - - @TargetApi(16) - private static void getDisplaySizeV16(Display display, Point outSize) { - display.getSize(outSize); - } - - @SuppressWarnings("deprecation") - private static void getDisplaySizeV9(Display display, Point outSize) { - outSize.x = display.getWidth(); - outSize.y = display.getHeight(); - } - - private VideoFormatSelectorUtil() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashChunkSource.java deleted file mode 100755 index 73ea30b0eb5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashChunkSource.java +++ /dev/null @@ -1,1118 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash; - -import android.os.Handler; -import android.util.Log; -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.BehindLiveWindowException; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.TimeRange; -import org.telegram.messenger.exoplayer.TimeRange.DynamicTimeRange; -import org.telegram.messenger.exoplayer.TimeRange.StaticTimeRange; -import org.telegram.messenger.exoplayer.chunk.Chunk; -import org.telegram.messenger.exoplayer.chunk.ChunkExtractorWrapper; -import org.telegram.messenger.exoplayer.chunk.ChunkOperationHolder; -import org.telegram.messenger.exoplayer.chunk.ChunkSource; -import org.telegram.messenger.exoplayer.chunk.ContainerMediaChunk; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.Format.DecreasingBandwidthComparator; -import org.telegram.messenger.exoplayer.chunk.FormatEvaluator; -import org.telegram.messenger.exoplayer.chunk.FormatEvaluator.Evaluation; -import org.telegram.messenger.exoplayer.chunk.InitializationChunk; -import org.telegram.messenger.exoplayer.chunk.MediaChunk; -import org.telegram.messenger.exoplayer.chunk.SingleSampleMediaChunk; -import org.telegram.messenger.exoplayer.dash.DashTrackSelector.Output; -import org.telegram.messenger.exoplayer.dash.mpd.AdaptationSet; -import org.telegram.messenger.exoplayer.dash.mpd.ContentProtection; -import org.telegram.messenger.exoplayer.dash.mpd.MediaPresentationDescription; -import org.telegram.messenger.exoplayer.dash.mpd.Period; -import org.telegram.messenger.exoplayer.dash.mpd.RangedUri; -import org.telegram.messenger.exoplayer.dash.mpd.Representation; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.ChunkIndex; -import org.telegram.messenger.exoplayer.extractor.mp4.FragmentedMp4Extractor; -import org.telegram.messenger.exoplayer.extractor.webm.WebmExtractor; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Clock; -import org.telegram.messenger.exoplayer.util.ManifestFetcher; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.SystemClock; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; - -/** - * An {@link ChunkSource} for DASH streams. - *

        - * This implementation currently supports fMP4, webm, webvtt and ttml. - *

        - * This implementation makes the following assumptions about multi-period manifests: - *

          - *
        1. that new periods will contain the same representations as previous periods (i.e. no new or - * missing representations) and
        2. - *
        3. that representations are contiguous across multiple periods
        4. - *
        - */ -// TODO: handle cases where the above assumption are false -public class DashChunkSource implements ChunkSource, Output { - - /** - * Interface definition for a callback to be notified of {@link DashChunkSource} events. - */ - public interface EventListener { - - /** - * Invoked when the available seek range of the stream has changed. - * - * @param sourceId The id of the reporting {@link DashChunkSource}. - * @param availableRange The range which specifies available content that can be seeked to. - */ - public void onAvailableRangeChanged(int sourceId, TimeRange availableRange); - - } - - /** - * Thrown when an AdaptationSet is missing from the MPD. - */ - public static class NoAdaptationSetException extends IOException { - - public NoAdaptationSetException(String message) { - super(message); - } - - } - - private static final String TAG = "DashChunkSource"; - - private final Handler eventHandler; - private final EventListener eventListener; - - private final DataSource dataSource; - private final FormatEvaluator adaptiveFormatEvaluator; - private final Evaluation evaluation; - private final ManifestFetcher manifestFetcher; - private final DashTrackSelector trackSelector; - private final ArrayList tracks; - private final SparseArray periodHolders; - private final Clock systemClock; - private final long liveEdgeLatencyUs; - private final long elapsedRealtimeOffsetUs; - private final long[] availableRangeValues; - private final boolean live; - private final int eventSourceId; - - private MediaPresentationDescription currentManifest; - private MediaPresentationDescription processedManifest; - private ExposedTrack enabledTrack; - private int nextPeriodHolderIndex; - private TimeRange availableRange; - private boolean prepareCalled; - private boolean startAtLiveEdge; - private boolean lastChunkWasInitialization; - private IOException fatalError; - - /** - * Lightweight constructor to use for fixed duration content. - * - * @param trackSelector Selects tracks to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param durationMs The duration of the content. - * @param adaptationSetType The type of the adaptation set to which the representations belong. - * One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and - * {@link AdaptationSet#TYPE_TEXT}. - * @param representations The representations to be considered by the source. - */ - public DashChunkSource(DashTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long durationMs, int adaptationSetType, - Representation... representations) { - this(trackSelector, dataSource, adaptiveFormatEvaluator, durationMs, adaptationSetType, - Arrays.asList(representations)); - } - - /** - * Lightweight constructor to use for fixed duration content. - * - * @param trackSelector Selects tracks to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param durationMs The duration of the content. - * @param adaptationSetType The type of the adaptation set to which the representations belong. - * One of {@link AdaptationSet#TYPE_AUDIO}, {@link AdaptationSet#TYPE_VIDEO} and - * {@link AdaptationSet#TYPE_TEXT}. - * @param representations The representations to be considered by the source. - */ - public DashChunkSource(DashTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long durationMs, int adaptationSetType, - List representations) { - this(buildManifest(durationMs, adaptationSetType, representations), trackSelector, dataSource, - adaptiveFormatEvaluator); - } - - /** - * Constructor to use for fixed duration content. - * - * @param manifest The manifest. - * @param trackSelector Selects tracks from manifest periods to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - */ - public DashChunkSource(MediaPresentationDescription manifest, DashTrackSelector trackSelector, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator) { - this(null, manifest, trackSelector, dataSource, adaptiveFormatEvaluator, new SystemClock(), 0, - 0, false, null, null, 0); - } - - /** - * Constructor to use for live streaming. - *

        - * May also be used for fixed duration content, in which case the call is equivalent to calling - * the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument. - * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. - * @param trackSelector Selects tracks from manifest periods to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param liveEdgeLatencyMs For live streams, the number of milliseconds that the playback should - * lag behind the "live edge" (i.e. the end of the most recently defined media in the - * manifest). Choosing a small value will minimize latency introduced by the player, however - * note that the value sets an upper bound on the length of media that the player can buffer. - * Hence a small value may increase the probability of rebuffering and playback failures. - * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between - * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified - * as the server's unix time minus the local elapsed time. It unknown, set to 0. - * @param eventHandler A handler to use when delivering events to {@code EventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - */ - public DashChunkSource(ManifestFetcher manifestFetcher, - DashTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, - Handler eventHandler, EventListener eventListener, int eventSourceId) { - this(manifestFetcher, manifestFetcher.getManifest(), trackSelector, - dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000, - elapsedRealtimeOffsetMs * 1000, true, eventHandler, eventListener, eventSourceId); - } - - /** - * Constructor to use for live DVR streaming. - * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. - * @param trackSelector Selects tracks from manifest periods to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param liveEdgeLatencyMs For live streams, the number of milliseconds that the playback should - * lag behind the "live edge" (i.e. the end of the most recently defined media in the - * manifest). Choosing a small value will minimize latency introduced by the player, however - * note that the value sets an upper bound on the length of media that the player can buffer. - * Hence a small value may increase the probability of rebuffering and playback failures. - * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between - * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified - * as the server's unix time minus the local elapsed time. It unknown, set to 0. - * @param startAtLiveEdge True if the stream should start at the live edge; false if it should - * at the beginning of the live window. - * @param eventHandler A handler to use when delivering events to {@code EventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - */ - public DashChunkSource(ManifestFetcher manifestFetcher, - DashTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs, long elapsedRealtimeOffsetMs, - boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this(manifestFetcher, manifestFetcher.getManifest(), trackSelector, - dataSource, adaptiveFormatEvaluator, new SystemClock(), liveEdgeLatencyMs * 1000, - elapsedRealtimeOffsetMs * 1000, startAtLiveEdge, eventHandler, eventListener, - eventSourceId); - } - - /* package */ DashChunkSource(ManifestFetcher manifestFetcher, - MediaPresentationDescription initialManifest, DashTrackSelector trackSelector, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, - Clock systemClock, long liveEdgeLatencyUs, long elapsedRealtimeOffsetUs, - boolean startAtLiveEdge, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this.manifestFetcher = manifestFetcher; - this.currentManifest = initialManifest; - this.trackSelector = trackSelector; - this.dataSource = dataSource; - this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; - this.systemClock = systemClock; - this.liveEdgeLatencyUs = liveEdgeLatencyUs; - this.elapsedRealtimeOffsetUs = elapsedRealtimeOffsetUs; - this.startAtLiveEdge = startAtLiveEdge; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.eventSourceId = eventSourceId; - this.evaluation = new Evaluation(); - this.availableRangeValues = new long[2]; - periodHolders = new SparseArray<>(); - tracks = new ArrayList<>(); - live = initialManifest.dynamic; - } - - // ChunkSource implementation. - - @Override - public void maybeThrowError() throws IOException { - if (fatalError != null) { - throw fatalError; - } else if (manifestFetcher != null) { - manifestFetcher.maybeThrowError(); - } - } - - @Override - public boolean prepare() { - if (!prepareCalled) { - prepareCalled = true; - try { - trackSelector.selectTracks(currentManifest, 0, this); - } catch (IOException e) { - fatalError = e; - } - } - return fatalError == null; - } - - @Override - public int getTrackCount() { - return tracks.size(); - } - - @Override - public final MediaFormat getFormat(int track) { - return tracks.get(track).trackFormat; - } - - @Override - public void enable(int track) { - enabledTrack = tracks.get(track); - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.enable(); - } - if (manifestFetcher != null) { - manifestFetcher.enable(); - processManifest(manifestFetcher.getManifest()); - } else { - processManifest(currentManifest); - } - } - - @Override - public void continueBuffering(long playbackPositionUs) { - if (manifestFetcher == null || !currentManifest.dynamic || fatalError != null) { - return; - } - - MediaPresentationDescription newManifest = manifestFetcher.getManifest(); - if (newManifest != null && newManifest != processedManifest) { - processManifest(newManifest); - // Manifests may be rejected, so the new manifest may not become the next currentManifest. - // Track a manifest has been processed to avoid processing twice when it was discarded. - processedManifest = newManifest; - } - - // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where - // minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit - // signaling in the stream, according to: - // http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/ - long minUpdatePeriod = currentManifest.minUpdatePeriod; - if (minUpdatePeriod == 0) { - minUpdatePeriod = 5000; - } - - if (android.os.SystemClock.elapsedRealtime() - > manifestFetcher.getManifestLoadStartTimestamp() + minUpdatePeriod) { - manifestFetcher.requestRefresh(); - } - } - - @Override - public final void getChunkOperation(List queue, long playbackPositionUs, - ChunkOperationHolder out) { - if (fatalError != null) { - out.chunk = null; - return; - } - - evaluation.queueSize = queue.size(); - if (evaluation.format == null || !lastChunkWasInitialization) { - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, enabledTrack.adaptiveFormats, - evaluation); - } else { - evaluation.format = enabledTrack.fixedFormat; - evaluation.trigger = Chunk.TRIGGER_MANUAL; - } - } - - Format selectedFormat = evaluation.format; - out.queueSize = evaluation.queueSize; - - if (selectedFormat == null) { - out.chunk = null; - return; - } else if (out.queueSize == queue.size() && out.chunk != null - && out.chunk.format.equals(selectedFormat)) { - // We already have a chunk, and the evaluation hasn't changed either the format or the size - // of the queue. Leave unchanged. - return; - } - - // In all cases where we return before instantiating a new chunk, we want out.chunk to be null. - out.chunk = null; - - boolean startingNewPeriod; - PeriodHolder periodHolder; - - availableRange.getCurrentBoundsUs(availableRangeValues); - if (queue.isEmpty()) { - if (live) { - if (playbackPositionUs != 0) { - // If the position is non-zero then assume the client knows where it's seeking. - startAtLiveEdge = false; - } - if (startAtLiveEdge) { - // We want live streams to start at the live edge instead of the beginning of the - // manifest - playbackPositionUs = Math.max(availableRangeValues[0], - availableRangeValues[1] - liveEdgeLatencyUs); - } else { - // we subtract 1 from the upper bound because it's exclusive for that bound - playbackPositionUs = Math.min(playbackPositionUs, availableRangeValues[1] - 1); - playbackPositionUs = Math.max(playbackPositionUs, availableRangeValues[0]); - } - } - - periodHolder = findPeriodHolder(playbackPositionUs); - startingNewPeriod = true; - } else { - if (startAtLiveEdge) { - // now that we know the player is consuming media chunks (since the queue isn't empty), - // set startAtLiveEdge to false so that the user can perform seek operations - startAtLiveEdge = false; - } - - MediaChunk previous = queue.get(out.queueSize - 1); - long nextSegmentStartTimeUs = previous.endTimeUs; - if (live && nextSegmentStartTimeUs < availableRangeValues[0]) { - // This is before the first chunk in the current manifest. - fatalError = new BehindLiveWindowException(); - return; - } else if (currentManifest.dynamic && nextSegmentStartTimeUs >= availableRangeValues[1]) { - // This chunk is beyond the last chunk in the current manifest. If the index is bounded - // we'll need to wait until it's refreshed. If it's unbounded we just need to wait for a - // while before attempting to load the chunk. - return; - } else { - // A period's duration is the maximum of its various representation's durations, so it's - // possible that due to the minor differences between them our available range values might - // not sync exactly with the actual available content, so double check whether or not we've - // really run out of content to play. - PeriodHolder lastPeriodHolder = periodHolders.valueAt(periodHolders.size() - 1); - if (previous.parentId == lastPeriodHolder.localIndex) { - RepresentationHolder representationHolder = - lastPeriodHolder.representationHolders.get(previous.format.id); - if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) { - if (!currentManifest.dynamic) { - // The current manifest isn't dynamic, so we've reached the end of the stream. - out.endOfStream = true; - } - return; - } - } - } - - startingNewPeriod = false; - periodHolder = periodHolders.get(previous.parentId); - if (periodHolder == null) { - // The previous chunk was from a period that's no longer on the manifest, therefore the - // next chunk must be the first one in the first period that's still on the manifest - // (note that we can't actually update the segmentNum yet because the new period might - // have a different sequence and it's segmentIndex might not have been loaded yet). - periodHolder = periodHolders.valueAt(0); - startingNewPeriod = true; - } else if (!periodHolder.isIndexUnbounded()) { - RepresentationHolder representationHolder = - periodHolder.representationHolders.get(previous.format.id); - if (representationHolder.isBeyondLastSegment(previous.getNextChunkIndex())) { - // We reached the end of a period. Start the next one. - periodHolder = periodHolders.get(previous.parentId + 1); - startingNewPeriod = true; - } - } - } - - RepresentationHolder representationHolder = - periodHolder.representationHolders.get(selectedFormat.id); - Representation selectedRepresentation = representationHolder.representation; - - RangedUri pendingInitializationUri = null; - RangedUri pendingIndexUri = null; - - MediaFormat mediaFormat = representationHolder.mediaFormat; - if (mediaFormat == null) { - pendingInitializationUri = selectedRepresentation.getInitializationUri(); - } - if (representationHolder.segmentIndex == null) { - pendingIndexUri = selectedRepresentation.getIndexUri(); - } - - if (pendingInitializationUri != null || pendingIndexUri != null) { - // We have initialization and/or index requests to make. - Chunk initializationChunk = newInitializationChunk(pendingInitializationUri, pendingIndexUri, - selectedRepresentation, representationHolder.extractorWrapper, dataSource, - periodHolder.localIndex, evaluation.trigger); - lastChunkWasInitialization = true; - out.chunk = initializationChunk; - return; - } - - int segmentNum = queue.isEmpty() ? representationHolder.getSegmentNum(playbackPositionUs) - : startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum() - : queue.get(out.queueSize - 1).getNextChunkIndex(); - Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource, - mediaFormat, enabledTrack, segmentNum, evaluation.trigger); - lastChunkWasInitialization = false; - out.chunk = nextMediaChunk; - } - - @Override - public void onChunkLoadCompleted(Chunk chunk) { - if (chunk instanceof InitializationChunk) { - InitializationChunk initializationChunk = (InitializationChunk) chunk; - String formatId = initializationChunk.format.id; - PeriodHolder periodHolder = periodHolders.get(initializationChunk.parentId); - if (periodHolder == null) { - // period for this initialization chunk may no longer be on the manifest - return; - } - - RepresentationHolder representationHolder = periodHolder.representationHolders.get(formatId); - if (initializationChunk.hasFormat()) { - representationHolder.mediaFormat = initializationChunk.getFormat(); - } - // The null check avoids overwriting an index obtained from the manifest with one obtained - // from the stream. If the manifest defines an index then the stream shouldn't, but in cases - // where it does we should ignore it. - if (representationHolder.segmentIndex == null && initializationChunk.hasSeekMap()) { - representationHolder.segmentIndex = new DashWrappingSegmentIndex( - (ChunkIndex) initializationChunk.getSeekMap(), - initializationChunk.dataSpec.uri.toString()); - } - // The null check avoids overwriting drmInitData obtained from the manifest with drmInitData - // obtained from the stream, as per DASH IF Interoperability Recommendations V3.0, 7.5.3. - if (periodHolder.drmInitData == null && initializationChunk.hasDrmInitData()) { - periodHolder.drmInitData = initializationChunk.getDrmInitData(); - } - } - } - - @Override - public void onChunkLoadError(Chunk chunk, Exception e) { - // Do nothing. - } - - @Override - public void disable(List queue) { - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.disable(); - } - if (manifestFetcher != null) { - manifestFetcher.disable(); - } - periodHolders.clear(); - evaluation.format = null; - availableRange = null; - fatalError = null; - enabledTrack = null; - } - - // DashTrackSelector.Output implementation. - - @Override - public void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex, - int adaptationSetIndex, int[] representationIndices) { - if (adaptiveFormatEvaluator == null) { - Log.w(TAG, "Skipping adaptive track (missing format evaluator)"); - return; - } - AdaptationSet adaptationSet = manifest.getPeriod(periodIndex).adaptationSets.get( - adaptationSetIndex); - int maxWidth = 0; - int maxHeight = 0; - Format maxHeightRepresentationFormat = null; - Format[] representationFormats = new Format[representationIndices.length]; - for (int i = 0; i < representationFormats.length; i++) { - Format format = adaptationSet.representations.get(representationIndices[i]).format; - if (maxHeightRepresentationFormat == null || format.height > maxHeight) { - maxHeightRepresentationFormat = format; - } - maxWidth = Math.max(maxWidth, format.width); - maxHeight = Math.max(maxHeight, format.height); - representationFormats[i] = format; - } - Arrays.sort(representationFormats, new DecreasingBandwidthComparator()); - long trackDurationUs = live ? C.UNKNOWN_TIME_US : manifest.duration * 1000; - String mediaMimeType = getMediaMimeType(maxHeightRepresentationFormat); - if (mediaMimeType == null) { - Log.w(TAG, "Skipped adaptive track (unknown media mime type)"); - return; - } - MediaFormat trackFormat = getTrackFormat(adaptationSet.type, maxHeightRepresentationFormat, - mediaMimeType, trackDurationUs); - if (trackFormat == null) { - Log.w(TAG, "Skipped adaptive track (unknown media format)"); - return; - } - tracks.add(new ExposedTrack(trackFormat.copyAsAdaptive(null), adaptationSetIndex, - representationFormats, maxWidth, maxHeight)); - } - - @Override - public void fixedTrack(MediaPresentationDescription manifest, int periodIndex, - int adaptationSetIndex, int representationIndex) { - List adaptationSets = manifest.getPeriod(periodIndex).adaptationSets; - AdaptationSet adaptationSet = adaptationSets.get(adaptationSetIndex); - Format representationFormat = adaptationSet.representations.get(representationIndex).format; - String mediaMimeType = getMediaMimeType(representationFormat); - if (mediaMimeType == null) { - Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media mime type)"); - return; - } - MediaFormat trackFormat = getTrackFormat(adaptationSet.type, representationFormat, - mediaMimeType, manifest.dynamic ? C.UNKNOWN_TIME_US : manifest.duration * 1000); - if (trackFormat == null) { - Log.w(TAG, "Skipped track " + representationFormat.id + " (unknown media format)"); - return; - } - tracks.add(new ExposedTrack(trackFormat, adaptationSetIndex, representationFormat)); - } - - // Private methods. - - // Visible for testing. - /* package */ TimeRange getAvailableRange() { - return availableRange; - } - - private static MediaPresentationDescription buildManifest(long durationMs, - int adaptationSetType, List representations) { - AdaptationSet adaptationSet = new AdaptationSet(0, adaptationSetType, representations); - Period period = new Period(null, 0, Collections.singletonList(adaptationSet)); - return new MediaPresentationDescription(-1, durationMs, -1, false, -1, -1, null, null, - Collections.singletonList(period)); - } - - private static MediaFormat getTrackFormat(int adaptationSetType, Format format, - String mediaMimeType, long durationUs) { - switch (adaptationSetType) { - case AdaptationSet.TYPE_VIDEO: - return MediaFormat.createVideoFormat(format.id, mediaMimeType, format.bitrate, - MediaFormat.NO_VALUE, durationUs, format.width, format.height, null); - case AdaptationSet.TYPE_AUDIO: - return MediaFormat.createAudioFormat(format.id, mediaMimeType, format.bitrate, - MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, null, - format.language); - case AdaptationSet.TYPE_TEXT: - return MediaFormat.createTextFormat(format.id, mediaMimeType, format.bitrate, - durationUs, format.language); - default: - return null; - } - } - - private static String getMediaMimeType(Format format) { - String formatMimeType = format.mimeType; - if (MimeTypes.isAudio(formatMimeType)) { - return MimeTypes.getAudioMediaMimeType(format.codecs); - } else if (MimeTypes.isVideo(formatMimeType)) { - return MimeTypes.getVideoMediaMimeType(format.codecs); - } else if (mimeTypeIsRawText(formatMimeType)) { - return formatMimeType; - } else if (MimeTypes.APPLICATION_MP4.equals(formatMimeType)) { - if ("stpp".equals(format.codecs)) { - return MimeTypes.APPLICATION_TTML; - } - if ("wvtt".equals(format.codecs)) { - return MimeTypes.APPLICATION_MP4VTT; - } - } - return null; - } - - /* package */ static boolean mimeTypeIsWebm(String mimeType) { - return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM) - || mimeType.startsWith(MimeTypes.APPLICATION_WEBM); - } - - /* package */ static boolean mimeTypeIsRawText(String mimeType) { - return MimeTypes.TEXT_VTT.equals(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType); - } - - private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, - Representation representation, ChunkExtractorWrapper extractor, DataSource dataSource, - int manifestIndex, int trigger) { - RangedUri requestUri; - if (initializationUri != null) { - // It's common for initialization and index data to be stored adjacently. Attempt to merge - // the two requests together to request both at once. - requestUri = initializationUri.attemptMerge(indexUri); - if (requestUri == null) { - requestUri = initializationUri; - } - } else { - requestUri = indexUri; - } - DataSpec dataSpec = new DataSpec(requestUri.getUri(), requestUri.start, requestUri.length, - representation.getCacheKey()); - return new InitializationChunk(dataSource, dataSpec, trigger, representation.format, - extractor, manifestIndex); - } - - protected Chunk newMediaChunk( - PeriodHolder periodHolder, RepresentationHolder representationHolder, DataSource dataSource, - MediaFormat mediaFormat, ExposedTrack enabledTrack, int segmentNum, int trigger) { - Representation representation = representationHolder.representation; - Format format = representation.format; - long startTimeUs = representationHolder.getSegmentStartTimeUs(segmentNum); - long endTimeUs = representationHolder.getSegmentEndTimeUs(segmentNum); - RangedUri segmentUri = representationHolder.getSegmentUrl(segmentNum); - DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, - representation.getCacheKey()); - - long sampleOffsetUs = periodHolder.startTimeUs - representation.presentationTimeOffsetUs; - if (mimeTypeIsRawText(format.mimeType)) { - return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, format, - startTimeUs, endTimeUs, segmentNum, enabledTrack.trackFormat, null, - periodHolder.localIndex); - } else { - boolean isMediaFormatFinal = (mediaFormat != null); - return new ContainerMediaChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, - segmentNum, sampleOffsetUs, representationHolder.extractorWrapper, mediaFormat, - enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight, periodHolder.drmInitData, - isMediaFormatFinal, periodHolder.localIndex); - } - } - - private long getNowUnixTimeUs() { - if (elapsedRealtimeOffsetUs != 0) { - return (systemClock.elapsedRealtime() * 1000) + elapsedRealtimeOffsetUs; - } else { - return System.currentTimeMillis() * 1000; - } - } - - private PeriodHolder findPeriodHolder(long positionUs) { - // if positionUs is before the first period, return the first period - if (positionUs < periodHolders.valueAt(0).getAvailableStartTimeUs()) { - return periodHolders.valueAt(0); - } - - for (int i = 0; i < periodHolders.size() - 1; i++) { - PeriodHolder periodHolder = periodHolders.valueAt(i); - if (positionUs < periodHolder.getAvailableEndTimeUs()) { - return periodHolder; - } - } - - // positionUs is within or after the last period - return periodHolders.valueAt(periodHolders.size() - 1); - } - - private void processManifest(MediaPresentationDescription manifest) { - // Remove old periods. - Period firstPeriod = manifest.getPeriod(0); - while (periodHolders.size() > 0 - && periodHolders.valueAt(0).startTimeUs < firstPeriod.startMs * 1000) { - PeriodHolder periodHolder = periodHolders.valueAt(0); - // TODO: Use periodHolders.removeAt(0) if the minimum API level is ever increased to 11. - periodHolders.remove(periodHolder.localIndex); - } - - // After discarding old periods, we should never have more periods than listed in the new - // manifest. That would mean that a previously announced period is no longer advertised. If - // this condition occurs, assume that we are hitting a manifest server that is out of sync and - // behind, discard this manifest, and try again later. - if (periodHolders.size() > manifest.getPeriodCount()) { - return; - } - - // Update existing periods. Only the first and last periods can change. - try { - int periodHolderCount = periodHolders.size(); - if (periodHolderCount > 0) { - periodHolders.valueAt(0).updatePeriod(manifest, 0, enabledTrack); - if (periodHolderCount > 1) { - int lastIndex = periodHolderCount - 1; - periodHolders.valueAt(lastIndex).updatePeriod(manifest, lastIndex, enabledTrack); - } - } - } catch (BehindLiveWindowException e) { - fatalError = e; - return; - } - - // Add new periods. - for (int i = periodHolders.size(); i < manifest.getPeriodCount(); i++) { - PeriodHolder holder = new PeriodHolder(nextPeriodHolderIndex, manifest, i, enabledTrack); - periodHolders.put(nextPeriodHolderIndex, holder); - nextPeriodHolderIndex++; - } - - // Update the available range. - TimeRange newAvailableRange = getAvailableRange(getNowUnixTimeUs()); - if (availableRange == null || !availableRange.equals(newAvailableRange)) { - availableRange = newAvailableRange; - notifyAvailableRangeChanged(availableRange); - } - - currentManifest = manifest; - } - - private TimeRange getAvailableRange(long nowUnixTimeUs) { - PeriodHolder firstPeriod = periodHolders.valueAt(0); - PeriodHolder lastPeriod = periodHolders.valueAt(periodHolders.size() - 1); - - if (!currentManifest.dynamic || lastPeriod.isIndexExplicit()) { - return new StaticTimeRange(firstPeriod.getAvailableStartTimeUs(), - lastPeriod.getAvailableEndTimeUs()); - } - - long minStartPositionUs = firstPeriod.getAvailableStartTimeUs(); - long maxEndPositionUs = lastPeriod.isIndexUnbounded() ? Long.MAX_VALUE - : lastPeriod.getAvailableEndTimeUs(); - long elapsedRealtimeAtZeroUs = (systemClock.elapsedRealtime() * 1000) - - (nowUnixTimeUs - (currentManifest.availabilityStartTime * 1000)); - long timeShiftBufferDepthUs = currentManifest.timeShiftBufferDepth == -1 ? -1 - : currentManifest.timeShiftBufferDepth * 1000; - return new DynamicTimeRange(minStartPositionUs, maxEndPositionUs, elapsedRealtimeAtZeroUs, - timeShiftBufferDepthUs, systemClock); - } - - private void notifyAvailableRangeChanged(final TimeRange seekRange) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onAvailableRangeChanged(eventSourceId, seekRange); - } - }); - } - } - - // Protected classes. - - protected static final class ExposedTrack { - - public final MediaFormat trackFormat; - public final int adaptiveMaxWidth; - public final int adaptiveMaxHeight; - - private final int adaptationSetIndex; - - // Non-adaptive track variables. - private final Format fixedFormat; - - // Adaptive track variables. - private final Format[] adaptiveFormats; - - public ExposedTrack(MediaFormat trackFormat, int adaptationSetIndex, Format fixedFormat) { - this.trackFormat = trackFormat; - this.adaptationSetIndex = adaptationSetIndex; - this.fixedFormat = fixedFormat; - this.adaptiveFormats = null; - this.adaptiveMaxWidth = -1; - this.adaptiveMaxHeight = -1; - } - - public ExposedTrack(MediaFormat trackFormat, int adaptationSetIndex, Format[] adaptiveFormats, - int maxWidth, int maxHeight) { - this.trackFormat = trackFormat; - this.adaptationSetIndex = adaptationSetIndex; - this.adaptiveFormats = adaptiveFormats; - this.adaptiveMaxWidth = maxWidth; - this.adaptiveMaxHeight = maxHeight; - this.fixedFormat = null; - } - - public boolean isAdaptive() { - return adaptiveFormats != null; - } - - } - - protected static final class RepresentationHolder { - - public final boolean mimeTypeIsRawText; - public final ChunkExtractorWrapper extractorWrapper; - - public Representation representation; - public DashSegmentIndex segmentIndex; - public MediaFormat mediaFormat; - - private final long periodStartTimeUs; - - private long periodDurationUs; - private int segmentNumShift; - - public RepresentationHolder(long periodStartTimeUs, long periodDurationUs, - Representation representation) { - this.periodStartTimeUs = periodStartTimeUs; - this.periodDurationUs = periodDurationUs; - this.representation = representation; - String mimeType = representation.format.mimeType; - mimeTypeIsRawText = mimeTypeIsRawText(mimeType); - extractorWrapper = mimeTypeIsRawText ? null : new ChunkExtractorWrapper( - mimeTypeIsWebm(mimeType) ? new WebmExtractor() : new FragmentedMp4Extractor()); - segmentIndex = representation.getIndex(); - } - - public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) - throws BehindLiveWindowException{ - DashSegmentIndex oldIndex = representation.getIndex(); - DashSegmentIndex newIndex = newRepresentation.getIndex(); - - periodDurationUs = newPeriodDurationUs; - representation = newRepresentation; - if (oldIndex == null) { - // Segment numbers cannot shift if the index isn't defined by the manifest. - return; - } - - segmentIndex = newIndex; - if (!oldIndex.isExplicit()) { - // Segment numbers cannot shift if the index isn't explicit. - return; - } - - int oldIndexLastSegmentNum = oldIndex.getLastSegmentNum(periodDurationUs); - long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum) - + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs); - int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum(); - long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum); - if (oldIndexEndTimeUs == newIndexStartTimeUs) { - // The new index continues where the old one ended, with no overlap. - segmentNumShift += oldIndex.getLastSegmentNum(periodDurationUs) + 1 - - newIndexFirstSegmentNum; - } else if (oldIndexEndTimeUs < newIndexStartTimeUs) { - // There's a gap between the old index and the new one which means we've slipped behind the - // live window and can't proceed. - throw new BehindLiveWindowException(); - } else { - // The new index overlaps with the old one. - segmentNumShift += oldIndex.getSegmentNum(newIndexStartTimeUs, periodDurationUs) - - newIndexFirstSegmentNum; - } - } - - public int getSegmentNum(long positionUs) { - return segmentIndex.getSegmentNum(positionUs - periodStartTimeUs, periodDurationUs) - + segmentNumShift; - } - - public long getSegmentStartTimeUs(int segmentNum) { - return segmentIndex.getTimeUs(segmentNum - segmentNumShift) + periodStartTimeUs; - } - - public long getSegmentEndTimeUs(int segmentNum) { - return getSegmentStartTimeUs(segmentNum) - + segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs); - } - - public int getLastSegmentNum() { - return segmentIndex.getLastSegmentNum(periodDurationUs); - } - - public boolean isBeyondLastSegment(int segmentNum) { - int lastSegmentNum = getLastSegmentNum(); - return lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED ? false - : segmentNum > (lastSegmentNum + segmentNumShift); - } - - public int getFirstAvailableSegmentNum() { - return segmentIndex.getFirstSegmentNum() + segmentNumShift; - } - - public RangedUri getSegmentUrl(int segmentNum) { - return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift); - } - - } - - protected static final class PeriodHolder { - - public final int localIndex; - public final long startTimeUs; - public final HashMap representationHolders; - - private final int[] representationIndices; - - private DrmInitData drmInitData; - - private boolean indexIsUnbounded; - private boolean indexIsExplicit; - private long availableStartTimeUs; - private long availableEndTimeUs; - - public PeriodHolder(int localIndex, MediaPresentationDescription manifest, int manifestIndex, - ExposedTrack selectedTrack) { - this.localIndex = localIndex; - - Period period = manifest.getPeriod(manifestIndex); - long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex); - AdaptationSet adaptationSet = period.adaptationSets.get(selectedTrack.adaptationSetIndex); - List representations = adaptationSet.representations; - - startTimeUs = period.startMs * 1000; - drmInitData = getDrmInitData(adaptationSet); - - if (!selectedTrack.isAdaptive()) { - representationIndices = new int[] { - getRepresentationIndex(representations, selectedTrack.fixedFormat.id)}; - } else { - representationIndices = new int[selectedTrack.adaptiveFormats.length]; - for (int j = 0; j < selectedTrack.adaptiveFormats.length; j++) { - representationIndices[j] = getRepresentationIndex( - representations, selectedTrack.adaptiveFormats[j].id); - } - } - - representationHolders = new HashMap<>(); - for (int i = 0; i < representationIndices.length; i++) { - Representation representation = representations.get(representationIndices[i]); - RepresentationHolder representationHolder = new RepresentationHolder(startTimeUs, - periodDurationUs, representation); - representationHolders.put(representation.format.id, representationHolder); - } - updateRepresentationIndependentProperties(periodDurationUs, - representations.get(representationIndices[0])); - } - - public void updatePeriod(MediaPresentationDescription manifest, int manifestIndex, - ExposedTrack selectedTrack) throws BehindLiveWindowException { - Period period = manifest.getPeriod(manifestIndex); - long periodDurationUs = getPeriodDurationUs(manifest, manifestIndex); - List representations = period.adaptationSets - .get(selectedTrack.adaptationSetIndex).representations; - - for (int j = 0; j < representationIndices.length; j++) { - Representation representation = representations.get(representationIndices[j]); - representationHolders.get(representation.format.id).updateRepresentation(periodDurationUs, - representation); - } - updateRepresentationIndependentProperties(periodDurationUs, - representations.get(representationIndices[0])); - } - - public long getAvailableStartTimeUs() { - return availableStartTimeUs; - } - - public long getAvailableEndTimeUs() { - if (isIndexUnbounded()) { - throw new IllegalStateException("Period has unbounded index"); - } - return availableEndTimeUs; - } - - public boolean isIndexUnbounded() { - return indexIsUnbounded; - } - - public boolean isIndexExplicit() { - return indexIsExplicit; - } - - public DrmInitData getDrmInitData() { - return drmInitData; - } - - // Private methods. - - private void updateRepresentationIndependentProperties(long periodDurationUs, - Representation arbitaryRepresentation) { - DashSegmentIndex segmentIndex = arbitaryRepresentation.getIndex(); - if (segmentIndex != null) { - int firstSegmentNum = segmentIndex.getFirstSegmentNum(); - int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs); - indexIsUnbounded = lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; - indexIsExplicit = segmentIndex.isExplicit(); - availableStartTimeUs = startTimeUs + segmentIndex.getTimeUs(firstSegmentNum); - if (!indexIsUnbounded) { - availableEndTimeUs = startTimeUs + segmentIndex.getTimeUs(lastSegmentNum) - + segmentIndex.getDurationUs(lastSegmentNum, periodDurationUs); - } - } else { - indexIsUnbounded = false; - indexIsExplicit = true; - availableStartTimeUs = startTimeUs; - availableEndTimeUs = startTimeUs + periodDurationUs; - } - } - - private static int getRepresentationIndex(List representations, - String formatId) { - for (int i = 0; i < representations.size(); i++) { - Representation representation = representations.get(i); - if (formatId.equals(representation.format.id)) { - return i; - } - } - throw new IllegalStateException("Missing format id: " + formatId); - } - - private static DrmInitData getDrmInitData(AdaptationSet adaptationSet) { - if (adaptationSet.contentProtections.isEmpty()) { - return null; - } else { - DrmInitData.Mapped drmInitData = null; - for (int i = 0; i < adaptationSet.contentProtections.size(); i++) { - ContentProtection contentProtection = adaptationSet.contentProtections.get(i); - if (contentProtection.uuid != null && contentProtection.data != null) { - if (drmInitData == null) { - drmInitData = new DrmInitData.Mapped(); - } - drmInitData.put(contentProtection.uuid, contentProtection.data); - } - } - return drmInitData; - } - } - - private static long getPeriodDurationUs(MediaPresentationDescription manifest, int index) { - long durationMs = manifest.getPeriodDuration(index); - if (durationMs == -1) { - return C.UNKNOWN_TIME_US; - } else { - return durationMs * 1000; - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashTrackSelector.java deleted file mode 100755 index 889a95c0cfd..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashTrackSelector.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash; - -import org.telegram.messenger.exoplayer.dash.mpd.MediaPresentationDescription; -import org.telegram.messenger.exoplayer.dash.mpd.Period; -import java.io.IOException; - -/** - * Specifies a track selection from a {@link Period} of a media presentation description. - */ -public interface DashTrackSelector { - - /** - * Defines a selector output. - */ - interface Output { - - /** - * Outputs an adaptive track, covering the specified representations in the specified - * adaptation set. - * - * @param manifest The media presentation description being processed. - * @param periodIndex The index of the period being processed. - * @param adaptationSetIndex The index of the adaptation set within which the representations - * are located. - * @param representationIndices The indices of the track within the element. - */ - void adaptiveTrack(MediaPresentationDescription manifest, int periodIndex, - int adaptationSetIndex, int[] representationIndices); - - /** - * Outputs an fixed track corresponding to the specified representation in the specified - * adaptation set. - * - * @param manifest The media presentation description being processed. - * @param periodIndex The index of the period being processed. - * @param adaptationSetIndex The index of the adaptation set within which the track is located. - * @param representationIndex The index of the representation within the adaptation set. - */ - void fixedTrack(MediaPresentationDescription manifest, int periodIndex, int adaptationSetIndex, - int representationIndex); - - } - - /** - * Outputs a track selection for a given period. - * - * @param manifest the media presentation description to process. - * @param periodIndex The index of the period to process. - * @param output The output to receive tracks. - * @throws IOException If an error occurs processing the period. - */ - void selectTracks(MediaPresentationDescription manifest, int periodIndex, Output output) - throws IOException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DefaultDashTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DefaultDashTrackSelector.java deleted file mode 100755 index 333f5964af4..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DefaultDashTrackSelector.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash; - -import android.content.Context; -import org.telegram.messenger.exoplayer.chunk.VideoFormatSelectorUtil; -import org.telegram.messenger.exoplayer.dash.mpd.AdaptationSet; -import org.telegram.messenger.exoplayer.dash.mpd.MediaPresentationDescription; -import org.telegram.messenger.exoplayer.dash.mpd.Period; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * A default {@link DashTrackSelector} implementation. - */ -// TODO: Add more configuration options (e.g. ability to disable adaptive track output). -public final class DefaultDashTrackSelector implements DashTrackSelector { - - private final int adaptationSetType; - - private final Context context; - private final boolean filterVideoRepresentations; - private final boolean filterProtectedHdContent; - - /** - * @param context A context. May be null if {@code filterVideoRepresentations == false}. - * @param filterVideoRepresentations Whether video representations should be filtered according to - * the capabilities of the device. It is strongly recommended to set this to {@code true}, - * unless the application has already verified that all representations are playable. - * @param filterProtectedHdContent Whether video representations that are both drm protected and - * high definition should be filtered when tracks are built. If - * {@code filterVideoRepresentations == false} then this parameter is ignored. - */ - public static DefaultDashTrackSelector newVideoInstance(Context context, - boolean filterVideoRepresentations, boolean filterProtectedHdContent) { - return new DefaultDashTrackSelector(AdaptationSet.TYPE_VIDEO, context, - filterVideoRepresentations, filterProtectedHdContent); - } - - public static DefaultDashTrackSelector newAudioInstance() { - return new DefaultDashTrackSelector(AdaptationSet.TYPE_AUDIO, null, false, false); - } - - public static DefaultDashTrackSelector newTextInstance() { - return new DefaultDashTrackSelector(AdaptationSet.TYPE_TEXT, null, false, false); - } - - private DefaultDashTrackSelector(int adaptationSetType, Context context, - boolean filterVideoRepresentations, boolean filterProtectedHdContent) { - this.adaptationSetType = adaptationSetType; - this.context = context; - this.filterVideoRepresentations = filterVideoRepresentations; - this.filterProtectedHdContent = filterProtectedHdContent; - } - - @Override - public void selectTracks(MediaPresentationDescription manifest, int periodIndex, Output output) - throws IOException { - Period period = manifest.getPeriod(periodIndex); - for (int i = 0; i < period.adaptationSets.size(); i++) { - AdaptationSet adaptationSet = period.adaptationSets.get(i); - if (adaptationSet.type == adaptationSetType) { - if (adaptationSetType == AdaptationSet.TYPE_VIDEO) { - int[] representations; - if (filterVideoRepresentations) { - representations = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( - context, adaptationSet.representations, null, - filterProtectedHdContent && adaptationSet.hasContentProtection()); - } else { - representations = Util.firstIntegersArray(adaptationSet.representations.size()); - } - int representationCount = representations.length; - if (representationCount > 1) { - output.adaptiveTrack(manifest, periodIndex, i, representations); - } - for (int j = 0; j < representationCount; j++) { - output.fixedTrack(manifest, periodIndex, i, representations[j]); - } - } else { - for (int j = 0; j < adaptationSet.representations.size(); j++) { - output.fixedTrack(manifest, periodIndex, i, j); - } - } - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/AdaptationSet.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/AdaptationSet.java deleted file mode 100755 index 41de991225f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/AdaptationSet.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import java.util.Collections; -import java.util.List; - -/** - * Represents a set of interchangeable encoded versions of a media content component. - */ -public class AdaptationSet { - - public static final int TYPE_UNKNOWN = -1; - public static final int TYPE_VIDEO = 0; - public static final int TYPE_AUDIO = 1; - public static final int TYPE_TEXT = 2; - - public final int id; - - public final int type; - - public final List representations; - public final List contentProtections; - - public AdaptationSet(int id, int type, List representations, - List contentProtections) { - this.id = id; - this.type = type; - this.representations = Collections.unmodifiableList(representations); - if (contentProtections == null) { - this.contentProtections = Collections.emptyList(); - } else { - this.contentProtections = Collections.unmodifiableList(contentProtections); - } - } - - public AdaptationSet(int id, int type, List representations) { - this(id, type, representations, null); - } - - public boolean hasContentProtection() { - return !contentProtections.isEmpty(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/ContentProtection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/ContentProtection.java deleted file mode 100755 index f125076f823..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/ContentProtection.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.UUID; - -/** - * Represents a ContentProtection tag in an AdaptationSet. - */ -public class ContentProtection { - - /** - * Identifies the content protection scheme. - */ - public final String schemeUriId; - - /** - * The UUID of the protection scheme. May be null. - */ - public final UUID uuid; - - /** - * Protection scheme specific initialization data. May be null. - */ - public final SchemeInitData data; - - /** - * @param schemeUriId Identifies the content protection scheme. - * @param uuid The UUID of the protection scheme, if known. May be null. - * @param data Protection scheme specific initialization data. May be null. - */ - public ContentProtection(String schemeUriId, UUID uuid, SchemeInitData data) { - this.schemeUriId = Assertions.checkNotNull(schemeUriId); - this.uuid = uuid; - this.data = data; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof ContentProtection)) { - return false; - } - if (obj == this) { - return true; - } - - ContentProtection other = (ContentProtection) obj; - return schemeUriId.equals(other.schemeUriId) - && Util.areEqual(uuid, other.uuid) - && Util.areEqual(data, other.data); - } - - @Override - public int hashCode() { - int hashCode = schemeUriId.hashCode(); - hashCode = (37 * hashCode) + (uuid != null ? uuid.hashCode() : 0); - hashCode = (37 * hashCode) + (data != null ? data.hashCode() : 0); - return hashCode; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/DashSingleSegmentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/DashSingleSegmentIndex.java deleted file mode 100755 index 229bf2bfd4a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/DashSingleSegmentIndex.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import org.telegram.messenger.exoplayer.dash.DashSegmentIndex; - -/** - * A {@link DashSegmentIndex} that defines a single segment. - */ -/* package */ final class DashSingleSegmentIndex implements DashSegmentIndex { - - private final RangedUri uri; - - /** - * @param uri A {@link RangedUri} defining the location of the segment data. - */ - public DashSingleSegmentIndex(RangedUri uri) { - this.uri = uri; - } - - @Override - public int getSegmentNum(long timeUs, long periodDurationUs) { - return 0; - } - - @Override - public long getTimeUs(int segmentNum) { - return 0; - } - - @Override - public long getDurationUs(int segmentNum, long periodDurationUs) { - return periodDurationUs; - } - - @Override - public RangedUri getSegmentUrl(int segmentNum) { - return uri; - } - - @Override - public int getFirstSegmentNum() { - return 0; - } - - @Override - public int getLastSegmentNum(long periodDurationUs) { - return 0; - } - - @Override - public boolean isExplicit() { - return true; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescription.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescription.java deleted file mode 100755 index 3b4af8ae891..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescription.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import org.telegram.messenger.exoplayer.util.ManifestFetcher.RedirectingManifest; -import java.util.Collections; -import java.util.List; - -/** - * Represents a DASH media presentation description (mpd). - */ -public class MediaPresentationDescription implements RedirectingManifest { - - public final long availabilityStartTime; - - public final long duration; - - public final long minBufferTime; - - public final boolean dynamic; - - public final long minUpdatePeriod; - - public final long timeShiftBufferDepth; - - public final UtcTimingElement utcTiming; - - public final String location; - - private final List periods; - - public MediaPresentationDescription(long availabilityStartTime, long duration, long minBufferTime, - boolean dynamic, long minUpdatePeriod, long timeShiftBufferDepth, UtcTimingElement utcTiming, - String location, List periods) { - this.availabilityStartTime = availabilityStartTime; - this.duration = duration; - this.minBufferTime = minBufferTime; - this.dynamic = dynamic; - this.minUpdatePeriod = minUpdatePeriod; - this.timeShiftBufferDepth = timeShiftBufferDepth; - this.utcTiming = utcTiming; - this.location = location; - this.periods = periods == null ? Collections.emptyList() : periods; - } - - @Override - public final String getNextManifestUri() { - return location; - } - - public final int getPeriodCount() { - return periods.size(); - } - - public final Period getPeriod(int index) { - return periods.get(index); - } - - public final long getPeriodDuration(int index) { - return index == periods.size() - 1 - ? (duration == -1 ? -1 : duration - periods.get(index).startMs) - : periods.get(index + 1).startMs - periods.get(index).startMs; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java deleted file mode 100755 index 6c0156b466f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/MediaPresentationDescriptionParser.java +++ /dev/null @@ -1,847 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; -import android.util.Pair; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.SegmentList; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.SegmentTemplate; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.SegmentTimelineElement; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.extractor.mp4.PsshAtomUtil; -import org.telegram.messenger.exoplayer.upstream.UriLoadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParserUtil; -import org.telegram.messenger.exoplayer.util.UriUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.io.InputStream; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.xml.sax.helpers.DefaultHandler; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -/** - * A parser of media presentation description files. - */ -public class MediaPresentationDescriptionParser extends DefaultHandler - implements UriLoadable.Parser { - - private static final String TAG = "MediaPresentationDescriptionParser"; - - private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); - - private final String contentId; - private final XmlPullParserFactory xmlParserFactory; - - /** - * Equivalent to calling {@code new MediaPresentationDescriptionParser(null)}. - */ - public MediaPresentationDescriptionParser() { - this(null); - } - - /** - * @param contentId An optional content identifier to include in the parsed manifest. - */ - // TODO: Remove the need to inject a content identifier here, by not including it in the parsed - // manifest. Instead, it should be injected directly where needed (i.e. DashChunkSource). - public MediaPresentationDescriptionParser(String contentId) { - this.contentId = contentId; - try { - xmlParserFactory = XmlPullParserFactory.newInstance(); - } catch (XmlPullParserException e) { - throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); - } - } - - // MPD parsing. - - @Override - public MediaPresentationDescription parse(String connectionUrl, InputStream inputStream) - throws IOException, ParserException { - try { - XmlPullParser xpp = xmlParserFactory.newPullParser(); - xpp.setInput(inputStream, null); - int eventType = xpp.next(); - if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { - throw new ParserException( - "inputStream does not contain a valid media presentation description"); - } - return parseMediaPresentationDescription(xpp, connectionUrl); - } catch (XmlPullParserException e) { - throw new ParserException(e); - } catch (ParseException e) { - throw new ParserException(e); - } - } - - protected MediaPresentationDescription parseMediaPresentationDescription(XmlPullParser xpp, - String baseUrl) throws XmlPullParserException, IOException, ParseException { - long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", -1); - long durationMs = parseDuration(xpp, "mediaPresentationDuration", -1); - long minBufferTimeMs = parseDuration(xpp, "minBufferTime", -1); - String typeString = xpp.getAttributeValue(null, "type"); - boolean dynamic = (typeString != null) ? typeString.equals("dynamic") : false; - long minUpdateTimeMs = (dynamic) ? parseDuration(xpp, "minimumUpdatePeriod", -1) : -1; - long timeShiftBufferDepthMs = (dynamic) ? parseDuration(xpp, "timeShiftBufferDepth", -1) : -1; - UtcTimingElement utcTiming = null; - String location = null; - - List periods = new ArrayList<>(); - long nextPeriodStartMs = dynamic ? -1 : 0; - boolean seenEarlyAccessPeriod = false; - boolean seenFirstBaseUrl = false; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "BaseURL")) { - if (!seenFirstBaseUrl) { - baseUrl = parseBaseUrl(xpp, baseUrl); - seenFirstBaseUrl = true; - } - } else if (ParserUtil.isStartTag(xpp, "UTCTiming")) { - utcTiming = parseUtcTiming(xpp); - } else if (ParserUtil.isStartTag(xpp, "Location")) { - location = xpp.nextText(); - } else if (ParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { - Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); - Period period = periodWithDurationMs.first; - if (period.startMs == -1) { - if (dynamic) { - // This is an early access period. Ignore it. All subsequent periods must also be - // early access. - seenEarlyAccessPeriod = true; - } else { - throw new ParserException("Unable to determine start of period " + periods.size()); - } - } else { - long periodDurationMs = periodWithDurationMs.second; - nextPeriodStartMs = periodDurationMs == -1 ? -1 : period.startMs + periodDurationMs; - periods.add(period); - } - } - } while (!ParserUtil.isEndTag(xpp, "MPD")); - - if (durationMs == -1) { - if (nextPeriodStartMs != -1) { - // If we know the end time of the final period, we can use it as the duration. - durationMs = nextPeriodStartMs; - } else if (!dynamic) { - throw new ParserException("Unable to determine duration of static manifest."); - } - } - - if (periods.isEmpty()) { - throw new ParserException("No periods found."); - } - - return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, utcTiming, location, periods); - } - - protected MediaPresentationDescription buildMediaPresentationDescription( - long availabilityStartTime, long durationMs, long minBufferTimeMs, boolean dynamic, - long minUpdateTimeMs, long timeShiftBufferDepthMs, UtcTimingElement utcTiming, - String location, List periods) { - return new MediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, - dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, utcTiming, location, periods); - } - - protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { - String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); - String value = xpp.getAttributeValue(null, "value"); - return buildUtcTimingElement(schemeIdUri, value); - } - - protected UtcTimingElement buildUtcTimingElement(String schemeIdUri, String value) { - return new UtcTimingElement(schemeIdUri, value); - } - - protected Pair parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs) - throws XmlPullParserException, IOException { - String id = xpp.getAttributeValue(null, "id"); - long startMs = parseDuration(xpp, "start", defaultStartMs); - long durationMs = parseDuration(xpp, "duration", -1); - SegmentBase segmentBase = null; - List adaptationSets = new ArrayList<>(); - boolean seenFirstBaseUrl = false; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "BaseURL")) { - if (!seenFirstBaseUrl) { - baseUrl = parseBaseUrl(xpp, baseUrl); - seenFirstBaseUrl = true; - } - } else if (ParserUtil.isStartTag(xpp, "AdaptationSet")) { - adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase)); - } else if (ParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, null); - } else if (ParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, null); - } else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, null); - } - } while (!ParserUtil.isEndTag(xpp, "Period")); - - return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs); - } - - protected Period buildPeriod(String id, long startMs, List adaptationSets) { - return new Period(id, startMs, adaptationSets); - } - - // AdaptationSet parsing. - - protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String baseUrl, - SegmentBase segmentBase) throws XmlPullParserException, IOException { - int id = parseInt(xpp, "id", -1); - int contentType = parseContentType(xpp); - - String mimeType = xpp.getAttributeValue(null, "mimeType"); - String codecs = xpp.getAttributeValue(null, "codecs"); - int width = parseInt(xpp, "width", -1); - int height = parseInt(xpp, "height", -1); - float frameRate = parseFrameRate(xpp, -1); - int audioChannels = -1; - int audioSamplingRate = parseInt(xpp, "audioSamplingRate", -1); - String language = xpp.getAttributeValue(null, "lang"); - - ContentProtectionsBuilder contentProtectionsBuilder = new ContentProtectionsBuilder(); - List representations = new ArrayList<>(); - boolean seenFirstBaseUrl = false; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "BaseURL")) { - if (!seenFirstBaseUrl) { - baseUrl = parseBaseUrl(xpp, baseUrl); - seenFirstBaseUrl = true; - } - } else if (ParserUtil.isStartTag(xpp, "ContentProtection")) { - ContentProtection contentProtection = parseContentProtection(xpp); - if (contentProtection != null) { - contentProtectionsBuilder.addAdaptationSetProtection(contentProtection); - } - } else if (ParserUtil.isStartTag(xpp, "ContentComponent")) { - language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang")); - contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); - } else if (ParserUtil.isStartTag(xpp, "Representation")) { - Representation representation = parseRepresentation(xpp, baseUrl, mimeType, codecs, width, - height, frameRate, audioChannels, audioSamplingRate, language, segmentBase, - contentProtectionsBuilder); - contentProtectionsBuilder.endRepresentation(); - contentType = checkContentTypeConsistency(contentType, getContentType(representation)); - representations.add(representation); - } else if (ParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { - audioChannels = parseAudioChannelConfiguration(xpp); - } else if (ParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase); - } else if (ParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase); - } else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase); - } else if (ParserUtil.isStartTag(xpp)) { - parseAdaptationSetChild(xpp); - } - } while (!ParserUtil.isEndTag(xpp, "AdaptationSet")); - - return buildAdaptationSet(id, contentType, representations, contentProtectionsBuilder.build()); - } - - protected AdaptationSet buildAdaptationSet(int id, int contentType, - List representations, List contentProtections) { - return new AdaptationSet(id, contentType, representations, contentProtections); - } - - protected int parseContentType(XmlPullParser xpp) { - String contentType = xpp.getAttributeValue(null, "contentType"); - return TextUtils.isEmpty(contentType) ? AdaptationSet.TYPE_UNKNOWN - : MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? AdaptationSet.TYPE_AUDIO - : MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? AdaptationSet.TYPE_VIDEO - : MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? AdaptationSet.TYPE_TEXT - : AdaptationSet.TYPE_UNKNOWN; - } - - protected int getContentType(Representation representation) { - String mimeType = representation.format.mimeType; - if (TextUtils.isEmpty(mimeType)) { - return AdaptationSet.TYPE_UNKNOWN; - } else if (MimeTypes.isVideo(mimeType)) { - return AdaptationSet.TYPE_VIDEO; - } else if (MimeTypes.isAudio(mimeType)) { - return AdaptationSet.TYPE_AUDIO; - } else if (MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType)) { - return AdaptationSet.TYPE_TEXT; - } else if (MimeTypes.APPLICATION_MP4.equals(mimeType)) { - // The representation uses mp4 but does not contain video or audio. Use codecs to determine - // whether the container holds text. - String codecs = representation.format.codecs; - if ("stpp".equals(codecs) || "wvtt".equals(codecs)) { - return AdaptationSet.TYPE_TEXT; - } - } - return AdaptationSet.TYPE_UNKNOWN; - } - - /** - * Parses a {@link ContentProtection} element. - * - * @throws XmlPullParserException If an error occurs parsing the element. - * @throws IOException If an error occurs reading the element. - * @return The parsed {@link ContentProtection} element, or null if the element is unsupported. - **/ - protected ContentProtection parseContentProtection(XmlPullParser xpp) - throws XmlPullParserException, IOException { - String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); - UUID uuid = null; - SchemeInitData data = null; - boolean seenPsshElement = false; - do { - xpp.next(); - // The cenc:pssh element is defined in 23001-7:2015. - if (ParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { - seenPsshElement = true; - data = new SchemeInitData(MimeTypes.VIDEO_MP4, - Base64.decode(xpp.getText(), Base64.DEFAULT)); - uuid = PsshAtomUtil.parseUuid(data.data); - } - } while (!ParserUtil.isEndTag(xpp, "ContentProtection")); - if (seenPsshElement && uuid == null) { - Log.w(TAG, "Skipped unsupported ContentProtection element"); - return null; - } - return buildContentProtection(schemeIdUri, uuid, data); - } - - protected ContentProtection buildContentProtection(String schemeIdUri, UUID uuid, - SchemeInitData data) { - return new ContentProtection(schemeIdUri, uuid, data); - } - - /** - * Parses children of AdaptationSet elements not specifically parsed elsewhere. - * - * @param xpp The XmpPullParser from which the AdaptationSet child should be parsed. - * @throws XmlPullParserException If an error occurs parsing the element. - * @throws IOException If an error occurs reading the element. - **/ - protected void parseAdaptationSetChild(XmlPullParser xpp) - throws XmlPullParserException, IOException { - // pass - } - - // Representation parsing. - - protected Representation parseRepresentation(XmlPullParser xpp, String baseUrl, - String adaptationSetMimeType, String adaptationSetCodecs, int adaptationSetWidth, - int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, - int adaptationSetAudioSamplingRate, String adaptationSetLanguage, SegmentBase segmentBase, - ContentProtectionsBuilder contentProtectionsBuilder) - throws XmlPullParserException, IOException { - String id = xpp.getAttributeValue(null, "id"); - int bandwidth = parseInt(xpp, "bandwidth"); - - String mimeType = parseString(xpp, "mimeType", adaptationSetMimeType); - String codecs = parseString(xpp, "codecs", adaptationSetCodecs); - int width = parseInt(xpp, "width", adaptationSetWidth); - int height = parseInt(xpp, "height", adaptationSetHeight); - float frameRate = parseFrameRate(xpp, adaptationSetFrameRate); - int audioChannels = adaptationSetAudioChannels; - int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate); - String language = adaptationSetLanguage; - - boolean seenFirstBaseUrl = false; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "BaseURL")) { - if (!seenFirstBaseUrl) { - baseUrl = parseBaseUrl(xpp, baseUrl); - seenFirstBaseUrl = true; - } - } else if (ParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { - audioChannels = parseAudioChannelConfiguration(xpp); - } else if (ParserUtil.isStartTag(xpp, "SegmentBase")) { - segmentBase = parseSegmentBase(xpp, baseUrl, (SingleSegmentBase) segmentBase); - } else if (ParserUtil.isStartTag(xpp, "SegmentList")) { - segmentBase = parseSegmentList(xpp, baseUrl, (SegmentList) segmentBase); - } else if (ParserUtil.isStartTag(xpp, "SegmentTemplate")) { - segmentBase = parseSegmentTemplate(xpp, baseUrl, (SegmentTemplate) segmentBase); - } else if (ParserUtil.isStartTag(xpp, "ContentProtection")) { - ContentProtection contentProtection = parseContentProtection(xpp); - if (contentProtection != null) { - contentProtectionsBuilder.addAdaptationSetProtection(contentProtection); - } - } - } while (!ParserUtil.isEndTag(xpp, "Representation")); - - Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels, - audioSamplingRate, bandwidth, language, codecs); - return buildRepresentation(contentId, -1, format, - segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl)); - } - - protected Format buildFormat(String id, String mimeType, int width, int height, float frameRate, - int audioChannels, int audioSamplingRate, int bandwidth, String language, String codecs) { - return new Format(id, mimeType, width, height, frameRate, audioChannels, audioSamplingRate, - bandwidth, language, codecs); - } - - protected Representation buildRepresentation(String contentId, int revisionId, Format format, - SegmentBase segmentBase) { - return Representation.newInstance(contentId, revisionId, format, segmentBase); - } - - // SegmentBase, SegmentList and SegmentTemplate parsing. - - protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, String baseUrl, - SingleSegmentBase parent) throws XmlPullParserException, IOException { - - long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); - long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", - parent != null ? parent.presentationTimeOffset : 0); - - long indexStart = parent != null ? parent.indexStart : 0; - long indexLength = parent != null ? parent.indexLength : -1; - String indexRangeText = xpp.getAttributeValue(null, "indexRange"); - if (indexRangeText != null) { - String[] indexRange = indexRangeText.split("-"); - indexStart = Long.parseLong(indexRange[0]); - indexLength = Long.parseLong(indexRange[1]) - indexStart + 1; - } - - RangedUri initialization = parent != null ? parent.initialization : null; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); - } - } while (!ParserUtil.isEndTag(xpp, "SegmentBase")); - - return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl, - indexStart, indexLength); - } - - protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, - long presentationTimeOffset, String baseUrl, long indexStart, long indexLength) { - return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, baseUrl, - indexStart, indexLength); - } - - protected SegmentList parseSegmentList(XmlPullParser xpp, String baseUrl, SegmentList parent) - throws XmlPullParserException, IOException { - - long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); - long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", - parent != null ? parent.presentationTimeOffset : 0); - long duration = parseLong(xpp, "duration", parent != null ? parent.duration : -1); - int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); - - RangedUri initialization = null; - List timeline = null; - List segments = null; - - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); - } else if (ParserUtil.isStartTag(xpp, "SegmentTimeline")) { - timeline = parseSegmentTimeline(xpp); - } else if (ParserUtil.isStartTag(xpp, "SegmentURL")) { - if (segments == null) { - segments = new ArrayList<>(); - } - segments.add(parseSegmentUrl(xpp, baseUrl)); - } - } while (!ParserUtil.isEndTag(xpp, "SegmentList")); - - if (parent != null) { - initialization = initialization != null ? initialization : parent.initialization; - timeline = timeline != null ? timeline : parent.segmentTimeline; - segments = segments != null ? segments : parent.mediaSegments; - } - - return buildSegmentList(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, segments); - } - - protected SegmentList buildSegmentList(RangedUri initialization, long timescale, - long presentationTimeOffset, int startNumber, long duration, - List timeline, List segments) { - return new SegmentList(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, segments); - } - - protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, String baseUrl, - SegmentTemplate parent) throws XmlPullParserException, IOException { - - long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); - long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", - parent != null ? parent.presentationTimeOffset : 0); - long duration = parseLong(xpp, "duration", parent != null ? parent.duration : -1); - int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); - UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media", - parent != null ? parent.mediaTemplate : null); - UrlTemplate initializationTemplate = parseUrlTemplate(xpp, "initialization", - parent != null ? parent.initializationTemplate : null); - - RangedUri initialization = null; - List timeline = null; - - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "Initialization")) { - initialization = parseInitialization(xpp, baseUrl); - } else if (ParserUtil.isStartTag(xpp, "SegmentTimeline")) { - timeline = parseSegmentTimeline(xpp); - } - } while (!ParserUtil.isEndTag(xpp, "SegmentTemplate")); - - if (parent != null) { - initialization = initialization != null ? initialization : parent.initialization; - timeline = timeline != null ? timeline : parent.segmentTimeline; - } - - return buildSegmentTemplate(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); - } - - protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, - long presentationTimeOffset, int startNumber, long duration, - List timeline, UrlTemplate initializationTemplate, - UrlTemplate mediaTemplate, String baseUrl) { - return new SegmentTemplate(initialization, timescale, presentationTimeOffset, - startNumber, duration, timeline, initializationTemplate, mediaTemplate, baseUrl); - } - - protected List parseSegmentTimeline(XmlPullParser xpp) - throws XmlPullParserException, IOException { - List segmentTimeline = new ArrayList<>(); - long elapsedTime = 0; - do { - xpp.next(); - if (ParserUtil.isStartTag(xpp, "S")) { - elapsedTime = parseLong(xpp, "t", elapsedTime); - long duration = parseLong(xpp, "d"); - int count = 1 + parseInt(xpp, "r", 0); - for (int i = 0; i < count; i++) { - segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); - elapsedTime += duration; - } - } - } while (!ParserUtil.isEndTag(xpp, "SegmentTimeline")); - return segmentTimeline; - } - - protected SegmentTimelineElement buildSegmentTimelineElement(long elapsedTime, long duration) { - return new SegmentTimelineElement(elapsedTime, duration); - } - - protected UrlTemplate parseUrlTemplate(XmlPullParser xpp, String name, - UrlTemplate defaultValue) { - String valueString = xpp.getAttributeValue(null, name); - if (valueString != null) { - return UrlTemplate.compile(valueString); - } - return defaultValue; - } - - protected RangedUri parseInitialization(XmlPullParser xpp, String baseUrl) { - return parseRangedUrl(xpp, baseUrl, "sourceURL", "range"); - } - - protected RangedUri parseSegmentUrl(XmlPullParser xpp, String baseUrl) { - return parseRangedUrl(xpp, baseUrl, "media", "mediaRange"); - } - - protected RangedUri parseRangedUrl(XmlPullParser xpp, String baseUrl, String urlAttribute, - String rangeAttribute) { - String urlText = xpp.getAttributeValue(null, urlAttribute); - long rangeStart = 0; - long rangeLength = -1; - String rangeText = xpp.getAttributeValue(null, rangeAttribute); - if (rangeText != null) { - String[] rangeTextArray = rangeText.split("-"); - rangeStart = Long.parseLong(rangeTextArray[0]); - if (rangeTextArray.length == 2) { - rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1; - } - } - return buildRangedUri(baseUrl, urlText, rangeStart, rangeLength); - } - - protected RangedUri buildRangedUri(String baseUrl, String urlText, long rangeStart, - long rangeLength) { - return new RangedUri(baseUrl, urlText, rangeStart, rangeLength); - } - - // AudioChannelConfiguration parsing. - - protected int parseAudioChannelConfiguration(XmlPullParser xpp) - throws XmlPullParserException, IOException { - int audioChannels; - String schemeIdUri = parseString(xpp, "schemeIdUri", null); - if ("urn:mpeg:dash:23003:3:audio_channel_configuration:2011".equals(schemeIdUri)) { - audioChannels = parseInt(xpp, "value"); - } else { - audioChannels = -1; - } - do { - xpp.next(); - } while (!ParserUtil.isEndTag(xpp, "AudioChannelConfiguration")); - return audioChannels; - } - - // Utility methods. - - /** - * Checks two languages for consistency, returning the consistent language, or throwing an - * {@link IllegalStateException} if the languages are inconsistent. - *

        - * Two languages are consistent if they are equal, or if one is null. - * - * @param firstLanguage The first language. - * @param secondLanguage The second language. - * @return The consistent language. - */ - private static String checkLanguageConsistency(String firstLanguage, String secondLanguage) { - if (firstLanguage == null) { - return secondLanguage; - } else if (secondLanguage == null) { - return firstLanguage; - } else { - Assertions.checkState(firstLanguage.equals(secondLanguage)); - return firstLanguage; - } - } - - /** - * Checks two adaptation set content types for consistency, returning the consistent type, or - * throwing an {@link IllegalStateException} if the types are inconsistent. - *

        - * Two types are consistent if they are equal, or if one is {@link AdaptationSet#TYPE_UNKNOWN}. - * Where one of the types is {@link AdaptationSet#TYPE_UNKNOWN}, the other is returned. - * - * @param firstType The first type. - * @param secondType The second type. - * @return The consistent type. - */ - private static int checkContentTypeConsistency(int firstType, int secondType) { - if (firstType == AdaptationSet.TYPE_UNKNOWN) { - return secondType; - } else if (secondType == AdaptationSet.TYPE_UNKNOWN) { - return firstType; - } else { - Assertions.checkState(firstType == secondType); - return firstType; - } - } - - protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) { - float frameRate = defaultValue; - String frameRateAttribute = xpp.getAttributeValue(null, "frameRate"); - if (frameRateAttribute != null) { - Matcher frameRateMatcher = FRAME_RATE_PATTERN.matcher(frameRateAttribute); - if (frameRateMatcher.matches()) { - int numerator = Integer.parseInt(frameRateMatcher.group(1)); - String denominatorString = frameRateMatcher.group(2); - if (!TextUtils.isEmpty(denominatorString)) { - frameRate = (float) numerator / Integer.parseInt(denominatorString); - } else { - frameRate = numerator; - } - } - } - return frameRate; - } - - protected static long parseDuration(XmlPullParser xpp, String name, long defaultValue) { - String value = xpp.getAttributeValue(null, name); - if (value == null) { - return defaultValue; - } else { - return Util.parseXsDuration(value); - } - } - - protected static long parseDateTime(XmlPullParser xpp, String name, long defaultValue) - throws ParseException { - String value = xpp.getAttributeValue(null, name); - if (value == null) { - return defaultValue; - } else { - return Util.parseXsDateTime(value); - } - } - - protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) - throws XmlPullParserException, IOException { - xpp.next(); - return UriUtil.resolve(parentBaseUrl, xpp.getText()); - } - - protected static int parseInt(XmlPullParser xpp, String name) { - return parseInt(xpp, name, -1); - } - - protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) { - String value = xpp.getAttributeValue(null, name); - return value == null ? defaultValue : Integer.parseInt(value); - } - - protected static long parseLong(XmlPullParser xpp, String name) { - return parseLong(xpp, name, -1); - } - - protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) { - String value = xpp.getAttributeValue(null, name); - return value == null ? defaultValue : Long.parseLong(value); - } - - protected static String parseString(XmlPullParser xpp, String name, String defaultValue) { - String value = xpp.getAttributeValue(null, name); - return value == null ? defaultValue : value; - } - - /** - * Builds a list of {@link ContentProtection} elements for an {@link AdaptationSet}. - *

        - * If child Representation elements contain ContentProtection elements, then it is required that - * they all define the same ones. If they do, the ContentProtection elements are bubbled up to the - * AdaptationSet. Child Representation elements defining different ContentProtection elements is - * considered an error. - */ - protected static final class ContentProtectionsBuilder implements Comparator { - - private ArrayList adaptationSetProtections; - private ArrayList representationProtections; - private ArrayList currentRepresentationProtections; - - private boolean representationProtectionsSet; - - /** - * Adds a {@link ContentProtection} found in the AdaptationSet element. - * - * @param contentProtection The {@link ContentProtection} to add. - */ - public void addAdaptationSetProtection(ContentProtection contentProtection) { - if (adaptationSetProtections == null) { - adaptationSetProtections = new ArrayList<>(); - } - maybeAddContentProtection(adaptationSetProtections, contentProtection); - } - - /** - * Adds a {@link ContentProtection} found in a child Representation element. - * - * @param contentProtection The {@link ContentProtection} to add. - */ - public void addRepresentationProtection(ContentProtection contentProtection) { - if (currentRepresentationProtections == null) { - currentRepresentationProtections = new ArrayList<>(); - } - maybeAddContentProtection(currentRepresentationProtections, contentProtection); - } - - /** - * Should be invoked after processing each child Representation element, in order to apply - * consistency checks. - */ - public void endRepresentation() { - if (!representationProtectionsSet) { - if (currentRepresentationProtections != null) { - Collections.sort(currentRepresentationProtections, this); - } - representationProtections = currentRepresentationProtections; - representationProtectionsSet = true; - } else { - // Assert that each Representation element defines the same ContentProtection elements. - if (currentRepresentationProtections == null) { - Assertions.checkState(representationProtections == null); - } else { - Collections.sort(currentRepresentationProtections, this); - Assertions.checkState(currentRepresentationProtections.equals(representationProtections)); - } - } - currentRepresentationProtections = null; - } - - /** - * Returns the final list of consistent {@link ContentProtection} elements. - */ - public ArrayList build() { - if (adaptationSetProtections == null) { - return representationProtections; - } else if (representationProtections == null) { - return adaptationSetProtections; - } else { - // Bubble up ContentProtection elements found in the child Representation elements. - for (int i = 0; i < representationProtections.size(); i++) { - maybeAddContentProtection(adaptationSetProtections, representationProtections.get(i)); - } - return adaptationSetProtections; - } - } - - /** - * Checks a ContentProtection for consistency with the given list, adding it if necessary. - *

          - *
        • If the new ContentProtection matches another in the list, it's consistent and is not - * added to the list. - *
        • If the new ContentProtection has the same schemeUriId as another ContentProtection in the - * list, but its other attributes do not match, then it's inconsistent and an - * {@link IllegalStateException} is thrown. - *
        • Else the new ContentProtection has a unique schemeUriId, it's consistent and is added. - *
        - * - * @param contentProtections The list of ContentProtection elements currently known. - * @param contentProtection The ContentProtection to add. - */ - private void maybeAddContentProtection(List contentProtections, - ContentProtection contentProtection) { - if (!contentProtections.contains(contentProtection)) { - for (int i = 0; i < contentProtections.size(); i++) { - // If contains returned false (no complete match), but find a matching schemeUriId, then - // the MPD contains inconsistent ContentProtection data. - Assertions.checkState( - !contentProtections.get(i).schemeUriId.equals(contentProtection.schemeUriId)); - } - contentProtections.add(contentProtection); - } - } - - // Comparator implementation. - - @Override - public int compare(ContentProtection first, ContentProtection second) { - return first.schemeUriId.compareTo(second.schemeUriId); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/RangedUri.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/RangedUri.java deleted file mode 100755 index 7d0d4769b04..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/RangedUri.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import android.net.Uri; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.UriUtil; - -/** - * Defines a range of data located at a {@link Uri}. - */ -public final class RangedUri { - - /** - * The (zero based) index of the first byte of the range. - */ - public final long start; - - /** - * The length of the range, or -1 to indicate that the range is unbounded. - */ - public final long length; - - // The URI is stored internally in two parts: reference URI and a base URI to use when - // resolving it. This helps optimize memory usage in the same way that DASH manifests allow many - // URLs to be expressed concisely in the form of a single BaseURL and many relative paths. Note - // that this optimization relies on the same object being passed as the base URI to many - // instances of this class. - private final String baseUri; - private final String referenceUri; - - private int hashCode; - - /** - * Constructs an ranged uri. - * - * @param baseUri A uri that can form the base of the uri defined by the instance. - * @param referenceUri A reference uri that should be resolved with respect to {@code baseUri}. - * @param start The (zero based) index of the first byte of the range. - * @param length The length of the range, or -1 to indicate that the range is unbounded. - */ - public RangedUri(String baseUri, String referenceUri, long start, long length) { - Assertions.checkArgument(baseUri != null || referenceUri != null); - this.baseUri = baseUri; - this.referenceUri = referenceUri; - this.start = start; - this.length = length; - } - - /** - * Returns the {@link Uri} represented by the instance. - * - * @return The {@link Uri} represented by the instance. - */ - public Uri getUri() { - return UriUtil.resolveToUri(baseUri, referenceUri); - } - - /** - * Returns the uri represented by the instance as a string. - * - * @return The uri represented by the instance. - */ - public String getUriString() { - return UriUtil.resolve(baseUri, referenceUri); - } - - /** - * Attempts to merge this {@link RangedUri} with another. - *

        - * A merge is successful if both instances define the same {@link Uri}, and if one starte the - * byte after the other ends, forming a contiguous region with no overlap. - *

        - * If {@code other} is null then the merge is considered unsuccessful, and null is returned. - * - * @param other The {@link RangedUri} to merge. - * @return The merged {@link RangedUri} if the merge was successful. Null otherwise. - */ - public RangedUri attemptMerge(RangedUri other) { - if (other == null || !getUriString().equals(other.getUriString())) { - return null; - } else if (length != -1 && start + length == other.start) { - return new RangedUri(baseUri, referenceUri, start, - other.length == -1 ? -1 : length + other.length); - } else if (other.length != -1 && other.start + other.length == start) { - return new RangedUri(baseUri, referenceUri, other.start, - length == -1 ? -1 : other.length + length); - } else { - return null; - } - } - - @Override - public int hashCode() { - if (hashCode == 0) { - int result = 17; - result = 31 * result + (int) start; - result = 31 * result + (int) length; - result = 31 * result + getUriString().hashCode(); - hashCode = result; - } - return hashCode; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - RangedUri other = (RangedUri) obj; - return this.start == other.start - && this.length == other.length - && getUriString().equals(other.getUriString()); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Representation.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Representation.java deleted file mode 100755 index 61bc24e0ea3..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Representation.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import android.net.Uri; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.FormatWrapper; -import org.telegram.messenger.exoplayer.dash.DashSegmentIndex; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.MultiSegmentBase; -import org.telegram.messenger.exoplayer.dash.mpd.SegmentBase.SingleSegmentBase; - -/** - * A DASH representation. - */ -public abstract class Representation implements FormatWrapper { - - /** - * Identifies the piece of content to which this {@link Representation} belongs. - *

        - * For example, all {@link Representation}s belonging to a video should have the same - * {@link #contentId}, which should uniquely identify that video. - */ - public final String contentId; - /** - * Identifies the revision of the content. - *

        - * If the media for a given ({@link #contentId} can change over time without a change to the - * {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an - * updated encoder), then this identifier must uniquely identify the revision of the media. The - * timestamp at which the media was encoded is often a suitable. - */ - public final long revisionId; - /** - * The format of the representation. - */ - public final Format format; - /** - * The offset of the presentation timestamps in the media stream relative to media time. - */ - public final long presentationTimeOffsetUs; - - private final String cacheKey; - private final RangedUri initializationUri; - - /** - * Constructs a new instance. - * - * @param contentId Identifies the piece of content to which this representation belongs. - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param segmentBase A segment base element for the representation. - * @return The constructed instance. - */ - public static Representation newInstance(String contentId, long revisionId, Format format, - SegmentBase segmentBase) { - return newInstance(contentId, revisionId, format, segmentBase, null); - } - - /** - * Constructs a new instance. - * - * @param contentId Identifies the piece of content to which this representation belongs. - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param segmentBase A segment base element for the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. - * @return The constructed instance. - */ - public static Representation newInstance(String contentId, long revisionId, Format format, - SegmentBase segmentBase, String customCacheKey) { - if (segmentBase instanceof SingleSegmentBase) { - return new SingleSegmentRepresentation(contentId, revisionId, format, - (SingleSegmentBase) segmentBase, customCacheKey, -1); - } else if (segmentBase instanceof MultiSegmentBase) { - return new MultiSegmentRepresentation(contentId, revisionId, format, - (MultiSegmentBase) segmentBase, customCacheKey); - } else { - throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " - + "MultiSegmentBase"); - } - } - - private Representation(String contentId, long revisionId, Format format, - SegmentBase segmentBase, String customCacheKey) { - this.contentId = contentId; - this.revisionId = revisionId; - this.format = format; - this.cacheKey = customCacheKey != null ? customCacheKey - : contentId + "." + format.id + "." + revisionId; - initializationUri = segmentBase.getInitialization(this); - presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); - } - - @Override - public Format getFormat() { - return format; - } - - /** - * Gets a {@link RangedUri} defining the location of the representation's initialization data. - * May be null if no initialization data exists. - * - * @return A {@link RangedUri} defining the location of the initialization data, or null. - */ - public RangedUri getInitializationUri() { - return initializationUri; - } - - /** - * Gets a {@link RangedUri} defining the location of the representation's segment index. Null if - * the representation provides an index directly. - * - * @return The location of the segment index, or null. - */ - public abstract RangedUri getIndexUri(); - - /** - * Gets a segment index, if the representation is able to provide one directly. Null if the - * segment index is defined externally. - * - * @return The segment index, or null. - */ - public abstract DashSegmentIndex getIndex(); - - /** - * A cache key for the {@link Representation}, in the format - * {@code contentId + "." + format.id + "." + revisionId}. - * - * @return A cache key. - */ - public String getCacheKey() { - return cacheKey; - } - - /** - * A DASH representation consisting of a single segment. - */ - public static class SingleSegmentRepresentation extends Representation { - - /** - * The uri of the single segment. - */ - public final Uri uri; - - /** - * The content length, or -1 if unknown. - */ - public final long contentLength; - - private final RangedUri indexUri; - private final DashSingleSegmentIndex segmentIndex; - - /** - * @param contentId Identifies the piece of content to which this representation belongs. - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param uri The uri of the media. - * @param initializationStart The offset of the first byte of initialization data. - * @param initializationEnd The offset of the last byte of initialization data. - * @param indexStart The offset of the first byte of index data. - * @param indexEnd The offset of the last byte of index data. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. - * @param contentLength The content length, or -1 if unknown. - */ - public static SingleSegmentRepresentation newInstance(String contentId, long revisionId, - Format format, String uri, long initializationStart, long initializationEnd, - long indexStart, long indexEnd, String customCacheKey, long contentLength) { - RangedUri rangedUri = new RangedUri(uri, null, initializationStart, - initializationEnd - initializationStart + 1); - SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, uri, indexStart, - indexEnd - indexStart + 1); - return new SingleSegmentRepresentation(contentId, revisionId, - format, segmentBase, customCacheKey, contentLength); - } - - /** - * @param contentId Identifies the piece of content to which this representation belongs. - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param segmentBase The segment base underlying the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. - * @param contentLength The content length, or -1 if unknown. - */ - public SingleSegmentRepresentation(String contentId, long revisionId, Format format, - SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { - super(contentId, revisionId, format, segmentBase, customCacheKey); - this.uri = Uri.parse(segmentBase.uri); - this.indexUri = segmentBase.getIndex(); - this.contentLength = contentLength; - // If we have an index uri then the index is defined externally, and we shouldn't return one - // directly. If we don't, then we can't do better than an index defining a single segment. - segmentIndex = indexUri != null ? null - : new DashSingleSegmentIndex(new RangedUri(segmentBase.uri, null, 0, contentLength)); - } - - @Override - public RangedUri getIndexUri() { - return indexUri; - } - - @Override - public DashSegmentIndex getIndex() { - return segmentIndex; - } - - } - - /** - * A DASH representation consisting of multiple segments. - */ - public static class MultiSegmentRepresentation extends Representation - implements DashSegmentIndex { - - private final MultiSegmentBase segmentBase; - - /** - * @param contentId Identifies the piece of content to which this representation belongs. - * @param revisionId Identifies the revision of the content. - * @param format The format of the representation. - * @param segmentBase The segment base underlying the representation. - * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. - */ - public MultiSegmentRepresentation(String contentId, long revisionId, Format format, - MultiSegmentBase segmentBase, String customCacheKey) { - super(contentId, revisionId, format, segmentBase, customCacheKey); - this.segmentBase = segmentBase; - } - - @Override - public RangedUri getIndexUri() { - return null; - } - - @Override - public DashSegmentIndex getIndex() { - return this; - } - - // DashSegmentIndex implementation. - - @Override - public RangedUri getSegmentUrl(int segmentIndex) { - return segmentBase.getSegmentUrl(this, segmentIndex); - } - - @Override - public int getSegmentNum(long timeUs, long periodDurationUs) { - return segmentBase.getSegmentNum(timeUs, periodDurationUs); - } - - @Override - public long getTimeUs(int segmentIndex) { - return segmentBase.getSegmentTimeUs(segmentIndex); - } - - @Override - public long getDurationUs(int segmentIndex, long periodDurationUs) { - return segmentBase.getSegmentDurationUs(segmentIndex, periodDurationUs); - } - - @Override - public int getFirstSegmentNum() { - return segmentBase.getFirstSegmentNum(); - } - - @Override - public int getLastSegmentNum(long periodDurationUs) { - return segmentBase.getLastSegmentNum(periodDurationUs); - } - - @Override - public boolean isExplicit() { - return segmentBase.isExplicit(); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElementResolver.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElementResolver.java deleted file mode 100755 index 73169e6615d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElementResolver.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.dash.mpd; - -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.upstream.UriDataSource; -import org.telegram.messenger.exoplayer.upstream.UriLoadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Locale; -import java.util.TimeZone; -import java.util.concurrent.CancellationException; - -/** - * Resolves a {@link UtcTimingElement}. - */ -public final class UtcTimingElementResolver implements Loader.Callback { - - /** - * Callback for timing element resolution. - */ - public interface UtcTimingCallback { - - /** - * Invoked when the element has been resolved. - * - * @param utcTiming The element that was resolved. - * @param elapsedRealtimeOffset The offset between the resolved UTC time and - * {@link SystemClock#elapsedRealtime()} in milliseconds, specified as the UTC time minus - * the local elapsed time. - */ - void onTimestampResolved(UtcTimingElement utcTiming, long elapsedRealtimeOffset); - - /** - * Invoked when the element was not successfully resolved. - * - * @param utcTiming The element that was not resolved. - * @param e The cause of the failure. - */ - void onTimestampError(UtcTimingElement utcTiming, IOException e); - } - - private final UriDataSource uriDataSource; - private final UtcTimingElement timingElement; - private final long timingElementElapsedRealtime; - private final UtcTimingCallback callback; - - private Loader singleUseLoader; - private UriLoadable singleUseLoadable; - - /** - * Resolves a {@link UtcTimingElement}. - * - * @param uriDataSource A source to use should loading from a URI be necessary. - * @param timingElement The element to resolve. - * @param timingElementElapsedRealtime The {@link SystemClock#elapsedRealtime()} timestamp at - * which the element was obtained. Used if the element contains a timestamp directly. - * @param callback The callback to invoke on resolution or failure. - */ - public static void resolveTimingElement(UriDataSource uriDataSource, - UtcTimingElement timingElement, long timingElementElapsedRealtime, - UtcTimingCallback callback) { - UtcTimingElementResolver resolver = new UtcTimingElementResolver(uriDataSource, timingElement, - timingElementElapsedRealtime, callback); - resolver.resolve(); - } - - private UtcTimingElementResolver(UriDataSource uriDataSource, UtcTimingElement timingElement, - long timingElementElapsedRealtime, UtcTimingCallback callback) { - this.uriDataSource = uriDataSource; - this.timingElement = Assertions.checkNotNull(timingElement); - this.timingElementElapsedRealtime = timingElementElapsedRealtime; - this.callback = Assertions.checkNotNull(callback); - } - - private void resolve() { - String scheme = timingElement.schemeIdUri; - if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { - resolveDirect(); - } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) { - resolveHttp(new Iso8601Parser()); - } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012") - || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { - resolveHttp(new XsDateTimeParser()); - } else { - // Unsupported scheme. - callback.onTimestampError(timingElement, new IOException("Unsupported utc timing scheme")); - } - } - - private void resolveDirect() { - try { - long utcTimestamp = Util.parseXsDateTime(timingElement.value); - long elapsedRealtimeOffset = utcTimestamp - timingElementElapsedRealtime; - callback.onTimestampResolved(timingElement, elapsedRealtimeOffset); - } catch (ParseException e) { - callback.onTimestampError(timingElement, new ParserException(e)); - } - } - - private void resolveHttp(UriLoadable.Parser parser) { - singleUseLoader = new Loader("utctiming"); - singleUseLoadable = new UriLoadable<>(timingElement.value, uriDataSource, parser); - singleUseLoader.startLoading(singleUseLoadable, this); - } - - @Override - public void onLoadCanceled(Loadable loadable) { - onLoadError(loadable, new IOException("Load cancelled", new CancellationException())); - } - - @Override - public void onLoadCompleted(Loadable loadable) { - releaseLoader(); - long elapsedRealtimeOffset = singleUseLoadable.getResult() - SystemClock.elapsedRealtime(); - callback.onTimestampResolved(timingElement, elapsedRealtimeOffset); - } - - @Override - public void onLoadError(Loadable loadable, IOException exception) { - releaseLoader(); - callback.onTimestampError(timingElement, exception); - } - - private void releaseLoader() { - singleUseLoader.release(); - } - - private static class XsDateTimeParser implements UriLoadable.Parser { - - @Override - public Long parse(String connectionUrl, InputStream inputStream) throws ParserException, - IOException { - String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); - try { - return Util.parseXsDateTime(firstLine); - } catch (ParseException e) { - throw new ParserException(e); - } - } - - } - - private static class Iso8601Parser implements UriLoadable.Parser { - - @Override - public Long parse(String connectionUrl, InputStream inputStream) throws ParserException, - IOException { - String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); - try { - // TODO: It may be necessary to handle timestamp offsets from UTC. - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); - format.setTimeZone(TimeZone.getTimeZone("UTC")); - return format.parse(firstLine).getTime(); - } catch (ParseException e) { - throw new ParserException(e); - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmInitData.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmInitData.java deleted file mode 100755 index 65b7fc75de4..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmInitData.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.drm; - -import android.media.MediaDrm; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * Encapsulates initialization data required by a {@link MediaDrm} instances. - */ -public interface DrmInitData { - - /** - * Retrieves initialization data for a given DRM scheme, specified by its UUID. - * - * @param schemeUuid The DRM scheme's UUID. - * @return The initialization data for the scheme, or null if the scheme is not supported. - */ - public abstract SchemeInitData get(UUID schemeUuid); - - /** - * A {@link DrmInitData} implementation that maps UUID onto scheme specific data. - */ - public static final class Mapped implements DrmInitData { - - private final Map schemeData; - - public Mapped() { - schemeData = new HashMap<>(); - } - - @Override - public SchemeInitData get(UUID schemeUuid) { - return schemeData.get(schemeUuid); - } - - /** - * Inserts scheme specific initialization data. - * - * @param schemeUuid The scheme UUID. - * @param schemeInitData The corresponding initialization data. - */ - public void put(UUID schemeUuid, SchemeInitData schemeInitData) { - schemeData.put(schemeUuid, schemeInitData); - } - - } - - /** - * A {@link DrmInitData} implementation that returns the same initialization data for all schemes. - */ - public static final class Universal implements DrmInitData { - - private SchemeInitData data; - - public Universal(SchemeInitData data) { - this.data = data; - } - - @Override - public SchemeInitData get(UUID schemeUuid) { - return data; - } - - } - - /** - * Scheme initialization data. - */ - public static final class SchemeInitData { - - /** - * The mimeType of {@link #data}. - */ - public final String mimeType; - /** - * The initialization data. - */ - public final byte[] data; - - /** - * @param mimeType The mimeType of the initialization data. - * @param data The initialization data. - */ - public SchemeInitData(String mimeType, byte[] data) { - this.mimeType = Assertions.checkNotNull(mimeType); - this.data = Assertions.checkNotNull(data); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof SchemeInitData)) { - return false; - } - if (obj == this) { - return true; - } - - SchemeInitData other = (SchemeInitData) obj; - return mimeType.equals(other.mimeType) && Arrays.equals(data, other.data); - } - - @Override - public int hashCode() { - return mimeType.hashCode() + 31 * Arrays.hashCode(data); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmSessionManager.java deleted file mode 100755 index 07ed1d811d8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/DrmSessionManager.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.drm; - -import android.annotation.TargetApi; -import android.media.MediaCrypto; - -/** - * Manages a DRM session. - */ -@TargetApi(16) -public interface DrmSessionManager { - - /** - * The error state. {@link #getError()} can be used to retrieve the cause. - */ - public static final int STATE_ERROR = 0; - /** - * The session is closed. - */ - public static final int STATE_CLOSED = 1; - /** - * The session is being opened (i.e. {@link #open(DrmInitData)} has been called, but the session - * is not yet open). - */ - public static final int STATE_OPENING = 2; - /** - * The session is open, but does not yet have the keys required for decryption. - */ - public static final int STATE_OPENED = 3; - /** - * The session is open and has the keys required for decryption. - */ - public static final int STATE_OPENED_WITH_KEYS = 4; - - /** - * Opens the session, possibly asynchronously. - * - * @param drmInitData DRM initialization data. - */ - void open(DrmInitData drmInitData); - - /** - * Closes the session. - */ - void close(); - - /** - * Gets the current state of the session. - * - * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, - * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. - */ - int getState(); - - /** - * Gets an {@link ExoMediaCrypto} for the open session. - *

        - * This method may be called when the manager is in the following states: - * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} - * - * @return An {@link ExoMediaCrypto} for the open session. - * @throws IllegalStateException If called when a session isn't opened. - */ - T getMediaCrypto(); - - /** - * Whether the session requires a secure decoder for the specified mime type. - *

        - * Normally this method should return {@link MediaCrypto#requiresSecureDecoderComponent(String)}, - * however in some cases implementations may wish to modify the return value (i.e. to force a - * secure decoder even when one is not required). - *

        - * This method may be called when the manager is in the following states: - * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} - * - * @return Whether the open session requires a secure decoder for the specified mime type. - * @throws IllegalStateException If called when a session isn't opened. - */ - boolean requiresSecureDecoderComponent(String mimeType); - - /** - * Gets the cause of the error state. - *

        - * This method may be called when the manager is in any state. - * - * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. - */ - Exception getError(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/StreamingDrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/StreamingDrmSessionManager.java deleted file mode 100755 index 7823429bcf2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/StreamingDrmSessionManager.java +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.drm; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.media.DeniedByServerException; -import android.media.MediaDrm; -import android.media.NotProvisionedException; -import android.media.UnsupportedSchemeException; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.drm.ExoMediaDrm.KeyRequest; -import org.telegram.messenger.exoplayer.drm.ExoMediaDrm.OnEventListener; -import org.telegram.messenger.exoplayer.drm.ExoMediaDrm.ProvisionRequest; -import org.telegram.messenger.exoplayer.extractor.mp4.PsshAtomUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.HashMap; -import java.util.UUID; - -/** - * A base class for {@link DrmSessionManager} implementations that support streaming playbacks - * using {@link ExoMediaDrm}. - */ -@TargetApi(18) -public class StreamingDrmSessionManager implements DrmSessionManager { - - /** - * Interface definition for a callback to be notified of {@link StreamingDrmSessionManager} - * events. - */ - public interface EventListener { - - /** - * Invoked each time keys are loaded. - */ - void onDrmKeysLoaded(); - - /** - * Invoked when a drm error occurs. - * - * @param e The corresponding exception. - */ - void onDrmSessionManagerError(Exception e); - - } - - /** - * UUID for the Widevine DRM scheme. - */ - public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); - - /** - * UUID for the PlayReady DRM scheme. - *

        - * Note that PlayReady is unsupported by most Android devices, with the exception of Android TV - * devices, which do provide support. - */ - public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L); - - /** - * The key to use when passing CustomData to a PlayReady instance in an optional parameter map. - */ - public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; - - private static final int MSG_PROVISION = 0; - private static final int MSG_KEYS = 1; - - private final Handler eventHandler; - private final EventListener eventListener; - private final ExoMediaDrm mediaDrm; - private final HashMap optionalKeyRequestParameters; - - /* package */ final MediaDrmHandler mediaDrmHandler; - /* package */ final MediaDrmCallback callback; - /* package */ final PostResponseHandler postResponseHandler; - /* package */ final UUID uuid; - - private HandlerThread requestHandlerThread; - private Handler postRequestHandler; - - private int openCount; - private boolean provisioningInProgress; - private int state; - private T mediaCrypto; - private Exception lastException; - private SchemeInitData schemeInitData; - private byte[] sessionId; - - private static FrameworkMediaDrm createFrameworkDrm(UUID uuid) throws UnsupportedDrmException { - try { - return new FrameworkMediaDrm(uuid); - } catch (UnsupportedSchemeException e) { - throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME, e); - } catch (Exception e) { - throw new UnsupportedDrmException(UnsupportedDrmException.REASON_INSTANTIATION_ERROR, e); - } - } - - /** - * Instantiates a new instance using the Widevine scheme. - * - * @param playbackLooper The looper associated with the media playback thread. Should usually be - * obtained using {@link org.telegram.messenger.exoplayer.ExoPlayer#getPlaybackLooper()}. - * @param callback Performs key and provisioning requests. - * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument - * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @throws UnsupportedDrmException If the specified DRM scheme is not supported. - */ - public static StreamingDrmSessionManager newWidevineInstance( - Looper playbackLooper, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, - Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { - return StreamingDrmSessionManager.newFrameworkInstance(WIDEVINE_UUID, playbackLooper, callback, - optionalKeyRequestParameters, eventHandler, eventListener); - } - - /** - * Instantiates a new instance using the PlayReady scheme. - *

        - * Note that PlayReady is unsupported by most Android devices, with the exception of Android TV - * devices, which do provide support. - * - * @param playbackLooper The looper associated with the media playback thread. Should usually be - * obtained using {@link org.telegram.messenger.exoplayer.ExoPlayer#getPlaybackLooper()}. - * @param callback Performs key and provisioning requests. - * @param customData Optional custom data to include in requests generated by the instance. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @throws UnsupportedDrmException If the specified DRM scheme is not supported. - */ - public static StreamingDrmSessionManager newPlayReadyInstance( - Looper playbackLooper, MediaDrmCallback callback, String customData, Handler eventHandler, - EventListener eventListener) throws UnsupportedDrmException { - HashMap optionalKeyRequestParameters; - if (!TextUtils.isEmpty(customData)) { - optionalKeyRequestParameters = new HashMap<>(); - optionalKeyRequestParameters.put(PLAYREADY_CUSTOM_DATA_KEY, customData); - } else { - optionalKeyRequestParameters = null; - } - return StreamingDrmSessionManager.newFrameworkInstance(PLAYREADY_UUID, playbackLooper, callback, - optionalKeyRequestParameters, eventHandler, eventListener); - } - - /** - * @param uuid The UUID of the drm scheme. - * @param playbackLooper The looper associated with the media playback thread. Should usually be - * obtained using {@link org.telegram.messenger.exoplayer.ExoPlayer#getPlaybackLooper()}. - * @param callback Performs key and provisioning requests. - * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument - * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @throws UnsupportedDrmException If the specified DRM scheme is not supported. - */ - public static StreamingDrmSessionManager newFrameworkInstance( - UUID uuid, Looper playbackLooper, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, Handler eventHandler, - EventListener eventListener) throws UnsupportedDrmException { - return StreamingDrmSessionManager.newInstance(uuid, playbackLooper, callback, - optionalKeyRequestParameters, eventHandler, eventListener, createFrameworkDrm(uuid)); - } - - /** - * @param uuid The UUID of the drm scheme. - * @param playbackLooper The looper associated with the media playback thread. Should usually be - * obtained using {@link org.telegram.messenger.exoplayer.ExoPlayer#getPlaybackLooper()}. - * @param callback Performs key and provisioning requests. - * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument - * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. - * @throws UnsupportedDrmException If the specified DRM scheme is not supported. - */ - public static StreamingDrmSessionManager newInstance( - UUID uuid, Looper playbackLooper, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, Handler eventHandler, - EventListener eventListener, ExoMediaDrm mediaDrm) throws UnsupportedDrmException { - return new StreamingDrmSessionManager<>(uuid, playbackLooper, callback, - optionalKeyRequestParameters, eventHandler, eventListener, mediaDrm); - } - - /** - * @param uuid The UUID of the drm scheme. - * @param playbackLooper The looper associated with the media playback thread. Should usually be - * obtained using {@link org.telegram.messenger.exoplayer.ExoPlayer#getPlaybackLooper()}. - * @param callback Performs key and provisioning requests. - * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument - * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. - * @throws UnsupportedDrmException If the specified DRM scheme is not supported. - */ - private StreamingDrmSessionManager(UUID uuid, Looper playbackLooper, MediaDrmCallback callback, - HashMap optionalKeyRequestParameters, Handler eventHandler, - EventListener eventListener, ExoMediaDrm mediaDrm) throws UnsupportedDrmException { - this.uuid = uuid; - this.callback = callback; - this.optionalKeyRequestParameters = optionalKeyRequestParameters; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.mediaDrm = mediaDrm; - mediaDrm.setOnEventListener(new MediaDrmEventListener()); - mediaDrmHandler = new MediaDrmHandler(playbackLooper); - postResponseHandler = new PostResponseHandler(playbackLooper); - state = STATE_CLOSED; - } - - @Override - public final int getState() { - return state; - } - - @Override - public final T getMediaCrypto() { - if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - throw new IllegalStateException(); - } - return mediaCrypto; - } - - @Override - public boolean requiresSecureDecoderComponent(String mimeType) { - if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - throw new IllegalStateException(); - } - return mediaCrypto.requiresSecureDecoderComponent(mimeType); - } - - @Override - public final Exception getError() { - return state == STATE_ERROR ? lastException : null; - } - - /** - * Provides access to {@link MediaDrm#getPropertyString(String)}. - *

        - * This method may be called when the manager is in any state. - * - * @param key The key to request. - * @return The retrieved property. - */ - public final String getPropertyString(String key) { - return mediaDrm.getPropertyString(key); - } - - /** - * Provides access to {@link MediaDrm#setPropertyString(String, String)}. - *

        - * This method may be called when the manager is in any state. - * - * @param key The property to write. - * @param value The value to write. - */ - public final void setPropertyString(String key, String value) { - mediaDrm.setPropertyString(key, value); - } - - /** - * Provides access to {@link MediaDrm#getPropertyByteArray(String)}. - *

        - * This method may be called when the manager is in any state. - * - * @param key The key to request. - * @return The retrieved property. - */ - public final byte[] getPropertyByteArray(String key) { - return mediaDrm.getPropertyByteArray(key); - } - - /** - * Provides access to {@link MediaDrm#setPropertyByteArray(String, byte[])}. - *

        - * This method may be called when the manager is in any state. - * - * @param key The property to write. - * @param value The value to write. - */ - public final void setPropertyByteArray(String key, byte[] value) { - mediaDrm.setPropertyByteArray(key, value); - } - - @Override - public void open(DrmInitData drmInitData) { - if (++openCount != 1) { - return; - } - if (postRequestHandler == null) { - requestHandlerThread = new HandlerThread("DrmRequestHandler"); - requestHandlerThread.start(); - postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); - } - if (schemeInitData == null) { - schemeInitData = drmInitData.get(uuid); - if (schemeInitData == null) { - onError(new IllegalStateException("Media does not support uuid: " + uuid)); - return; - } - if (Util.SDK_INT < 21) { - // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. - byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeInitData.data, WIDEVINE_UUID); - if (psshData == null) { - // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. - } else { - schemeInitData = new SchemeInitData(schemeInitData.mimeType, psshData); - } - } - } - state = STATE_OPENING; - openInternal(true); - } - - @Override - public void close() { - if (--openCount != 0) { - return; - } - state = STATE_CLOSED; - provisioningInProgress = false; - mediaDrmHandler.removeCallbacksAndMessages(null); - postResponseHandler.removeCallbacksAndMessages(null); - postRequestHandler.removeCallbacksAndMessages(null); - postRequestHandler = null; - requestHandlerThread.quit(); - requestHandlerThread = null; - schemeInitData = null; - mediaCrypto = null; - lastException = null; - if (sessionId != null) { - mediaDrm.closeSession(sessionId); - sessionId = null; - } - } - - private void openInternal(boolean allowProvisioning) { - try { - sessionId = mediaDrm.openSession(); - mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId); - state = STATE_OPENED; - postKeyRequest(); - } catch (NotProvisionedException e) { - if (allowProvisioning) { - postProvisionRequest(); - } else { - onError(e); - } - } catch (Exception e) { - onError(e); - } - } - - private void postProvisionRequest() { - if (provisioningInProgress) { - return; - } - provisioningInProgress = true; - ProvisionRequest request = mediaDrm.getProvisionRequest(); - postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); - } - - private void onProvisionResponse(Object response) { - provisioningInProgress = false; - if (state != STATE_OPENING && state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - // This event is stale. - return; - } - - if (response instanceof Exception) { - onError((Exception) response); - return; - } - - try { - mediaDrm.provideProvisionResponse((byte[]) response); - if (state == STATE_OPENING) { - openInternal(false); - } else { - postKeyRequest(); - } - } catch (DeniedByServerException e) { - onError(e); - } - } - - private void postKeyRequest() { - KeyRequest keyRequest; - try { - keyRequest = mediaDrm.getKeyRequest(sessionId, schemeInitData.data, schemeInitData.mimeType, - MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters); - postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); - } catch (NotProvisionedException e) { - onKeysError(e); - } - } - - private void onKeyResponse(Object response) { - if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { - // This event is stale. - return; - } - - if (response instanceof Exception) { - onKeysError((Exception) response); - return; - } - - try { - mediaDrm.provideKeyResponse(sessionId, (byte[]) response); - state = STATE_OPENED_WITH_KEYS; - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDrmKeysLoaded(); - } - }); - } - } catch (Exception e) { - onKeysError(e); - } - } - - private void onKeysError(Exception e) { - if (e instanceof NotProvisionedException) { - postProvisionRequest(); - } else { - onError(e); - } - } - - private void onError(final Exception e) { - lastException = e; - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDrmSessionManagerError(e); - } - }); - } - if (state != STATE_OPENED_WITH_KEYS) { - state = STATE_ERROR; - } - } - - @SuppressLint("HandlerLeak") - private class MediaDrmHandler extends Handler { - - public MediaDrmHandler(Looper looper) { - super(looper); - } - - @SuppressWarnings("deprecation") - @Override - public void handleMessage(Message msg) { - if (openCount == 0 || (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS)) { - return; - } - switch (msg.what) { - case MediaDrm.EVENT_KEY_REQUIRED: - postKeyRequest(); - return; - case MediaDrm.EVENT_KEY_EXPIRED: - state = STATE_OPENED; - onError(new KeysExpiredException()); - return; - case MediaDrm.EVENT_PROVISION_REQUIRED: - state = STATE_OPENED; - postProvisionRequest(); - return; - } - } - - } - - private class MediaDrmEventListener implements OnEventListener { - - @Override - public void onEvent(ExoMediaDrm mediaDrm, byte[] sessionId, int event, - int extra, byte[] data) { - mediaDrmHandler.sendEmptyMessage(event); - } - } - - @SuppressLint("HandlerLeak") - private class PostResponseHandler extends Handler { - - public PostResponseHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_PROVISION: - onProvisionResponse(msg.obj); - return; - case MSG_KEYS: - onKeyResponse(msg.obj); - return; - } - } - - } - - @SuppressLint("HandlerLeak") - private class PostRequestHandler extends Handler { - - public PostRequestHandler(Looper backgroundLooper) { - super(backgroundLooper); - } - - @Override - public void handleMessage(Message msg) { - Object response; - try { - switch (msg.what) { - case MSG_PROVISION: - response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj); - break; - case MSG_KEYS: - response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj); - break; - default: - throw new RuntimeException(); - } - } catch (Exception e) { - response = e; - } - postResponseHandler.obtainMessage(msg.what, response).sendToTarget(); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/UnsupportedDrmException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/UnsupportedDrmException.java deleted file mode 100755 index 1f7e533a467..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/UnsupportedDrmException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.drm; - -/** - * Thrown when the requested DRM scheme is not supported. - */ -public final class UnsupportedDrmException extends Exception { - - /** - * The requested DRM scheme is unsupported by the device. - */ - public static final int REASON_UNSUPPORTED_SCHEME = 1; - /** - * There device advertises support for the requested DRM scheme, but there was an error - * instantiating it. The cause can be retrieved using {@link #getCause()}. - */ - public static final int REASON_INSTANTIATION_ERROR = 2; - - public final int reason; - - public UnsupportedDrmException(int reason) { - this.reason = reason; - } - - public UnsupportedDrmException(int reason, Exception cause) { - super(cause); - this.reason = reason; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultTrackOutput.java deleted file mode 100755 index 7da0369b349..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultTrackOutput.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.EOFException; -import java.io.IOException; - -/** - * A {@link TrackOutput} that buffers extracted samples in a queue, and allows for consumption from - * that queue. - */ -public class DefaultTrackOutput implements TrackOutput { - - private final RollingSampleBuffer rollingBuffer; - private final SampleHolder sampleInfoHolder; - - // Accessed only by the consuming thread. - private boolean needKeyframe; - private long lastReadTimeUs; - private long spliceOutTimeUs; - - // Accessed by both the loading and consuming threads. - private volatile long largestParsedTimestampUs; - private volatile MediaFormat format; - - /** - * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. - */ - public DefaultTrackOutput(Allocator allocator) { - rollingBuffer = new RollingSampleBuffer(allocator); - sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); - needKeyframe = true; - lastReadTimeUs = Long.MIN_VALUE; - spliceOutTimeUs = Long.MIN_VALUE; - largestParsedTimestampUs = Long.MIN_VALUE; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Clears the queue, returning all allocations to the allocator. - */ - public void clear() { - rollingBuffer.clear(); - needKeyframe = true; - lastReadTimeUs = Long.MIN_VALUE; - spliceOutTimeUs = Long.MIN_VALUE; - largestParsedTimestampUs = Long.MIN_VALUE; - } - - /** - * Returns the current absolute write index. - */ - public int getWriteIndex() { - return rollingBuffer.getWriteIndex(); - } - - /** - * Discards samples from the write side of the queue. - * - * @param discardFromIndex The absolute index of the first sample to be discarded. - */ - public void discardUpstreamSamples(int discardFromIndex) { - rollingBuffer.discardUpstreamSamples(discardFromIndex); - largestParsedTimestampUs = rollingBuffer.peekSample(sampleInfoHolder) ? sampleInfoHolder.timeUs - : Long.MIN_VALUE; - } - - // Called by the consuming thread. - - /** - * Returns the current absolute read index. - */ - public int getReadIndex() { - return rollingBuffer.getReadIndex(); - } - - /** - * True if the output has received a format. False otherwise. - */ - public boolean hasFormat() { - return format != null; - } - - /** - * The format most recently received by the output, or null if a format has yet to be received. - */ - public MediaFormat getFormat() { - return format; - } - - /** - * The largest timestamp of any sample received by the output, or {@link Long#MIN_VALUE} if a - * sample has yet to be received. - */ - public long getLargestParsedTimestampUs() { - return largestParsedTimestampUs; - } - - /** - * True if at least one sample can be read from the queue. False otherwise. - */ - public boolean isEmpty() { - return !advanceToEligibleSample(); - } - - /** - * Removes the next sample from the head of the queue, writing it into the provided holder. - *

        - * The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples - * queued prior to the first keyframe are discarded. - * - * @param holder A {@link SampleHolder} into which the sample should be read. - * @return True if a sample was read. False otherwise. - */ - public boolean getSample(SampleHolder holder) { - boolean foundEligibleSample = advanceToEligibleSample(); - if (!foundEligibleSample) { - return false; - } - // Write the sample into the holder. - rollingBuffer.readSample(holder); - needKeyframe = false; - lastReadTimeUs = holder.timeUs; - return true; - } - - /** - * Discards samples from the queue up to the specified time. - * - * @param timeUs The time up to which samples should be discarded, in microseconds. - */ - public void discardUntil(long timeUs) { - while (rollingBuffer.peekSample(sampleInfoHolder) && sampleInfoHolder.timeUs < timeUs) { - rollingBuffer.skipSample(); - // We're discarding one or more samples. A subsequent read will need to start at a keyframe. - needKeyframe = true; - } - lastReadTimeUs = Long.MIN_VALUE; - } - - /** - * Attempts to skip to the keyframe before the specified time, if it's present in the buffer. - * - * @param timeUs The seek time. - * @return True if the skip was successful. False otherwise. - */ - public boolean skipToKeyframeBefore(long timeUs) { - return rollingBuffer.skipToKeyframeBefore(timeUs); - } - - /** - * Attempts to configure a splice from this queue to the next. - * - * @param nextQueue The queue being spliced to. - * @return Whether the splice was configured successfully. - */ - public boolean configureSpliceTo(DefaultTrackOutput nextQueue) { - if (spliceOutTimeUs != Long.MIN_VALUE) { - // We've already configured the splice. - return true; - } - long firstPossibleSpliceTime; - if (rollingBuffer.peekSample(sampleInfoHolder)) { - firstPossibleSpliceTime = sampleInfoHolder.timeUs; - } else { - firstPossibleSpliceTime = lastReadTimeUs + 1; - } - RollingSampleBuffer nextRollingBuffer = nextQueue.rollingBuffer; - while (nextRollingBuffer.peekSample(sampleInfoHolder) - && (sampleInfoHolder.timeUs < firstPossibleSpliceTime || !sampleInfoHolder.isSyncFrame())) { - // Discard samples from the next queue for as long as they are before the earliest possible - // splice time, or not keyframes. - nextRollingBuffer.skipSample(); - } - if (nextRollingBuffer.peekSample(sampleInfoHolder)) { - // We've found a keyframe in the next queue that can serve as the splice point. Set the - // splice point now. - spliceOutTimeUs = sampleInfoHolder.timeUs; - return true; - } - return false; - } - - /** - * Advances the underlying buffer to the next sample that is eligible to be returned. - * - * @return True if an eligible sample was found. False otherwise, in which case the underlying - * buffer has been emptied. - */ - private boolean advanceToEligibleSample() { - boolean haveNext = rollingBuffer.peekSample(sampleInfoHolder); - if (needKeyframe) { - while (haveNext && !sampleInfoHolder.isSyncFrame()) { - rollingBuffer.skipSample(); - haveNext = rollingBuffer.peekSample(sampleInfoHolder); - } - } - if (!haveNext) { - return false; - } - if (spliceOutTimeUs != Long.MIN_VALUE && sampleInfoHolder.timeUs >= spliceOutTimeUs) { - return false; - } - return true; - } - - // Called by the loading thread. - - /** - * Invoked to write sample data to the output. - * - * @param dataSource A {@link DataSource} from which to read the sample data. - * @param length The maximum length to read from the input. - * @param allowEndOfInput True if encountering the end of the input having read no data is - * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it - * should be considered an error, causing an {@link EOFException} to be thrown. - * @return The number of bytes appended. - * @throws IOException If an error occurred reading from the input. - */ - public int sampleData(DataSource dataSource, int length, boolean allowEndOfInput) - throws IOException { - return rollingBuffer.appendData(dataSource, length, allowEndOfInput); - } - - // TrackOutput implementation. Called by the loading thread. - - @Override - public void format(MediaFormat format) { - this.format = format; - } - - @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return rollingBuffer.appendData(input, length, allowEndOfInput); - } - - @Override - public void sampleData(ParsableByteArray buffer, int length) { - rollingBuffer.appendData(buffer, length); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs); - rollingBuffer.commitSample(timeUs, flags, rollingBuffer.getWritePosition() - size - offset, - size, encryptionKey); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DummyTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DummyTrackOutput.java deleted file mode 100755 index eb94c8b8c71..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DummyTrackOutput.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * A dummy {@link TrackOutput} implementation. - */ -public class DummyTrackOutput implements TrackOutput { - @Override - public void format(MediaFormat format) { - // Do nothing. - } - - @Override - public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - return input.skip(length); - } - - @Override - public void sampleData(ParsableByteArray data, int length) { - data.skipBytes(length); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - // Do nothing. - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorOutput.java deleted file mode 100755 index 6b2ee095a57..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorOutput.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import org.telegram.messenger.exoplayer.drm.DrmInitData; - -/** - * Receives stream level data extracted by an {@link Extractor}. - */ -public interface ExtractorOutput { - - /** - * Invoked when the {@link Extractor} identifies the existence of a track in the stream. - *

        - * Returns a {@link TrackOutput} that will receive track level data belonging to the track. - * - * @param trackId A track identifier. - * @return The {@link TrackOutput} that should receive track level data belonging to the track. - */ - TrackOutput track(int trackId); - - /** - * Invoked when all tracks have been identified, meaning that {@link #track(int)} will not be - * invoked again. - */ - void endTracks(); - - /** - * Invoked when a {@link SeekMap} has been extracted from the stream. - * - * @param seekMap The extracted {@link SeekMap}. - */ - void seekMap(SeekMap seekMap); - - /** - * Invoked when {@link DrmInitData} has been extracted from the stream. - * - * @param drmInitData The extracted {@link DrmInitData}. - */ - void drmInitData(DrmInitData drmInitData); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorSampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorSampleSource.java deleted file mode 100755 index 630ecab88c2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorSampleSource.java +++ /dev/null @@ -1,913 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import android.net.Uri; -import android.os.Handler; -import android.os.SystemClock; -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.EOFException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link SampleSource} that extracts sample data using an {@link Extractor}. - * - *

        If no {@link Extractor} instances are passed to the constructor, the input stream container - * format will be detected automatically from the following supported formats: - * - *

          - *
        • MP4, including M4A ({@link org.telegram.messenger.exoplayer.extractor.mp4.Mp4Extractor})
        • - *
        • fMP4 ({@link org.telegram.messenger.exoplayer.extractor.mp4.FragmentedMp4Extractor})
        • - *
        • Matroska and WebM ({@link org.telegram.messenger.exoplayer.extractor.webm.WebmExtractor})
        • - *
        • Ogg Vorbis/FLAC ({@link org.telegram.messenger.exoplayer.extractor.ogg.OggExtractor}
        • - *
        • MP3 ({@link org.telegram.messenger.exoplayer.extractor.mp3.Mp3Extractor})
        • - *
        • AAC ({@link org.telegram.messenger.exoplayer.extractor.ts.AdtsExtractor})
        • - *
        • MPEG TS ({@link org.telegram.messenger.exoplayer.extractor.ts.TsExtractor})
        • - *
        • MPEG PS ({@link org.telegram.messenger.exoplayer.extractor.ts.PsExtractor})
        • - *
        • FLV ({@link org.telegram.messenger.exoplayer.extractor.flv.FlvExtractor})
        • - *
        • WAV ({@link org.telegram.messenger.exoplayer.extractor.wav.WavExtractor})
        • - *
        • FLAC (only available if the FLAC extension is built and included)
        • - *
        - * - *

        Seeking in AAC, MPEG TS and FLV streams is not supported. - * - *

        To override the default extractors, pass one or more {@link Extractor} instances to the - * constructor. When reading a new stream, the first {@link Extractor} that returns {@code true} - * from {@link Extractor#sniff(ExtractorInput)} will be used. - */ -public final class ExtractorSampleSource implements SampleSource, SampleSourceReader, - ExtractorOutput, Loader.Callback { - - /** - * Interface definition for a callback to be notified of {@link ExtractorSampleSource} events. - */ - public interface EventListener { - - /** - * Invoked when an error occurs loading media data. - * - * @param sourceId The id of the reporting {@link SampleSource}. - * @param e The cause of the failure. - */ - void onLoadError(int sourceId, IOException e); - - } - - /** - * Thrown if the input format could not recognized. - */ - public static final class UnrecognizedInputFormatException extends ParserException { - - public UnrecognizedInputFormatException(Extractor[] extractors) { - super("None of the available extractors (" - + Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream."); - } - - } - - /** - * The default minimum number of times to retry loading prior to failing for on-demand streams. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND = 3; - - /** - * The default minimum number of times to retry loading prior to failing for live streams. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE = 6; - - private static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1; - private static final long NO_RESET_PENDING = Long.MIN_VALUE; - - /** - * Default extractor classes in priority order. They are referred to indirectly so that it is - * possible to remove unused extractors. - */ - private static final List> DEFAULT_EXTRACTOR_CLASSES; - static { - DEFAULT_EXTRACTOR_CLASSES = new ArrayList<>(); - // Load extractors using reflection so that they can be deleted cleanly. - // Class.forName() appears for each extractor so that automated tools like proguard - // can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.webm.WebmExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.mp4.FragmentedMp4Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.mp4.Mp4Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.mp3.Mp3Extractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.ts.AdtsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.ts.TsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.flv.FlvExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.ogg.OggExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.ts.PsExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.extractor.wav.WavExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - try { - DEFAULT_EXTRACTOR_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.ext.flac.FlacExtractor") - .asSubclass(Extractor.class)); - } catch (ClassNotFoundException e) { - // Extractor not found. - } - } - - private final ExtractorHolder extractorHolder; - private final Allocator allocator; - private final int requestedBufferSize; - private final SparseArray sampleQueues; - private final int minLoadableRetryCount; - private final Uri uri; - private final DataSource dataSource; - private final Handler eventHandler; - private final EventListener eventListener; - private final int eventSourceId; - - private volatile boolean tracksBuilt; - private volatile SeekMap seekMap; - private volatile DrmInitData drmInitData; - - private boolean prepared; - private int enabledTrackCount; - private MediaFormat[] mediaFormats; - private long maxTrackDurationUs; - private boolean[] pendingMediaFormat; - private boolean[] pendingDiscontinuities; - private boolean[] trackEnabledStates; - - private int remainingReleaseCount; - private long downstreamPositionUs; - private long lastSeekPositionUs; - private long pendingResetPositionUs; - - private boolean havePendingNextSampleUs; - private long pendingNextSampleUs; - private long sampleTimeOffsetUs; - - private Loader loader; - private ExtractingLoadable loadable; - private IOException currentLoadableException; - // TODO: Set this back to 0 in the correct place (some place indicative of making progress). - private int currentLoadableExceptionCount; - private long currentLoadableExceptionTimestamp; - private boolean loadingFinished; - - private int extractedSampleCount; - private int extractedSampleCountAtStartOfLoad; - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSource A data source to read the media stream. - * @param allocator An {@link Allocator} from which to obtain memory allocations. - * @param requestedBufferSize The requested total buffer size for storing sample data, in bytes. - * The actual allocated size may exceed the value passed in if the implementation requires it. - * @param extractors {@link Extractor}s to extract the media stream, in order of decreasing - * priority. If omitted, the default extractors will be used. - */ - public ExtractorSampleSource(Uri uri, DataSource dataSource, Allocator allocator, - int requestedBufferSize, Extractor... extractors) { - this(uri, dataSource, allocator, requestedBufferSize, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, - extractors); - } - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSource A data source to read the media stream. - * @param allocator An {@link Allocator} from which to obtain memory allocations. - * @param requestedBufferSize The requested total buffer size for storing sample data, in bytes. - * The actual allocated size may exceed the value passed in if the implementation requires it. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - * @param extractors {@link Extractor}s to extract the media stream, in order of decreasing - * priority. If omitted, the default extractors will be used. - */ - public ExtractorSampleSource(Uri uri, DataSource dataSource, Allocator allocator, - int requestedBufferSize, Handler eventHandler, EventListener eventListener, - int eventSourceId, Extractor... extractors) { - this(uri, dataSource, allocator, requestedBufferSize, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, - eventHandler, eventListener, eventSourceId, extractors); - } - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSource A data source to read the media stream. - * @param allocator An {@link Allocator} from which to obtain memory allocations. - * @param requestedBufferSize The requested total buffer size for storing sample data, in bytes. - * The actual allocated size may exceed the value passed in if the implementation requires it. - * @param minLoadableRetryCount The minimum number of times that the sample source will retry - * if a loading error occurs. - * @param extractors {@link Extractor}s to extract the media stream, in order of decreasing - * priority. If omitted, the default extractors will be used. - */ - public ExtractorSampleSource(Uri uri, DataSource dataSource, Allocator allocator, - int requestedBufferSize, int minLoadableRetryCount, Extractor... extractors) { - this(uri, dataSource, allocator, requestedBufferSize, minLoadableRetryCount, null, null, 0, - extractors); - } - - /** - * @param uri The {@link Uri} of the media stream. - * @param dataSource A data source to read the media stream. - * @param allocator An {@link Allocator} from which to obtain memory allocations. - * @param requestedBufferSize The requested total buffer size for storing sample data, in bytes. - * The actual allocated size may exceed the value passed in if the implementation requires it. - * @param minLoadableRetryCount The minimum number of times that the sample source will retry - * if a loading error occurs. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - * @param eventSourceId An identifier that gets passed to {@code eventListener} methods. - * @param extractors {@link Extractor}s to extract the media stream, in order of decreasing - * priority. If omitted, the default extractors will be used. - */ - public ExtractorSampleSource(Uri uri, DataSource dataSource, Allocator allocator, - int requestedBufferSize, int minLoadableRetryCount, Handler eventHandler, - EventListener eventListener, int eventSourceId, Extractor... extractors) { - this.uri = uri; - this.dataSource = dataSource; - this.eventListener = eventListener; - this.eventHandler = eventHandler; - this.eventSourceId = eventSourceId; - this.allocator = allocator; - this.requestedBufferSize = requestedBufferSize; - this.minLoadableRetryCount = minLoadableRetryCount; - if (extractors == null || extractors.length == 0) { - extractors = new Extractor[DEFAULT_EXTRACTOR_CLASSES.size()]; - for (int i = 0; i < extractors.length; i++) { - try { - extractors[i] = DEFAULT_EXTRACTOR_CLASSES.get(i).newInstance(); - } catch (InstantiationException e) { - throw new IllegalStateException("Unexpected error creating default extractor", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Unexpected error creating default extractor", e); - } - } - } - extractorHolder = new ExtractorHolder(extractors, this); - sampleQueues = new SparseArray<>(); - pendingResetPositionUs = NO_RESET_PENDING; - } - - @Override - public SampleSourceReader register() { - remainingReleaseCount++; - return this; - } - - @Override - public boolean prepare(long positionUs) { - if (prepared) { - return true; - } - if (loader == null) { - loader = new Loader("Loader:ExtractorSampleSource"); - } - - maybeStartLoading(); - - if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) { - int trackCount = sampleQueues.size(); - trackEnabledStates = new boolean[trackCount]; - pendingDiscontinuities = new boolean[trackCount]; - pendingMediaFormat = new boolean[trackCount]; - mediaFormats = new MediaFormat[trackCount]; - maxTrackDurationUs = C.UNKNOWN_TIME_US; - for (int i = 0; i < trackCount; i++) { - MediaFormat format = sampleQueues.valueAt(i).getFormat(); - mediaFormats[i] = format; - if (format.durationUs != C.UNKNOWN_TIME_US && format.durationUs > maxTrackDurationUs) { - maxTrackDurationUs = format.durationUs; - } - } - prepared = true; - return true; - } - - return false; - } - - @Override - public int getTrackCount() { - return sampleQueues.size(); - } - - @Override - public MediaFormat getFormat(int track) { - Assertions.checkState(prepared); - return mediaFormats[track]; - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(prepared); - Assertions.checkState(!trackEnabledStates[track]); - enabledTrackCount++; - trackEnabledStates[track] = true; - pendingMediaFormat[track] = true; - pendingDiscontinuities[track] = false; - if (enabledTrackCount == 1) { - // Treat all enables in non-seekable media as being from t=0. - positionUs = !seekMap.isSeekable() ? 0 : positionUs; - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - restartFrom(positionUs); - } - } - - @Override - public void disable(int track) { - Assertions.checkState(prepared); - Assertions.checkState(trackEnabledStates[track]); - enabledTrackCount--; - trackEnabledStates[track] = false; - if (enabledTrackCount == 0) { - downstreamPositionUs = Long.MIN_VALUE; - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - clearState(); - allocator.trim(0); - } - } - } - - @Override - public boolean continueBuffering(int track, long playbackPositionUs) { - Assertions.checkState(prepared); - Assertions.checkState(trackEnabledStates[track]); - downstreamPositionUs = playbackPositionUs; - discardSamplesForDisabledTracks(downstreamPositionUs); - if (loadingFinished) { - return true; - } - maybeStartLoading(); - if (isPendingReset()) { - return false; - } - return !sampleQueues.valueAt(track).isEmpty(); - } - - @Override - public long readDiscontinuity(int track) { - if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return lastSeekPositionUs; - } - return NO_DISCONTINUITY; - } - - @Override - public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - downstreamPositionUs = playbackPositionUs; - - if (pendingDiscontinuities[track] || isPendingReset()) { - return NOTHING_READ; - } - - InternalTrackOutput sampleQueue = sampleQueues.valueAt(track); - if (pendingMediaFormat[track]) { - formatHolder.format = sampleQueue.getFormat(); - formatHolder.drmInitData = drmInitData; - pendingMediaFormat[track] = false; - return FORMAT_READ; - } - - if (sampleQueue.getSample(sampleHolder)) { - boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; - sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0; - if (havePendingNextSampleUs) { - // Set the offset to make the timestamp of this sample equal to pendingNextSampleUs. - sampleTimeOffsetUs = pendingNextSampleUs - sampleHolder.timeUs; - havePendingNextSampleUs = false; - } - sampleHolder.timeUs += sampleTimeOffsetUs; - return SAMPLE_READ; - } - - if (loadingFinished) { - return END_OF_STREAM; - } - - return NOTHING_READ; - } - - @Override - public void maybeThrowError() throws IOException { - if (currentLoadableException == null) { - return; - } - if (isCurrentLoadableExceptionFatal()) { - throw currentLoadableException; - } - int minLoadableRetryCountForMedia; - if (minLoadableRetryCount != MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA) { - minLoadableRetryCountForMedia = minLoadableRetryCount; - } else { - minLoadableRetryCountForMedia = seekMap != null && !seekMap.isSeekable() - ? DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE - : DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND; - } - if (currentLoadableExceptionCount > minLoadableRetryCountForMedia) { - throw currentLoadableException; - } - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(prepared); - Assertions.checkState(enabledTrackCount > 0); - // Treat all seeks into non-seekable media as being to t=0. - positionUs = !seekMap.isSeekable() ? 0 : positionUs; - - long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - if (currentPositionUs == positionUs) { - return; - } - - // If we're not pending a reset, see if we can seek within the sample queues. - boolean seekInsideBuffer = !isPendingReset(); - for (int i = 0; seekInsideBuffer && i < sampleQueues.size(); i++) { - seekInsideBuffer &= sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); - } - - // If we failed to seek within the sample queues, we need to restart. - if (!seekInsideBuffer) { - restartFrom(positionUs); - } - - // Either way, we need to send discontinuities to the downstream components. - for (int i = 0; i < pendingDiscontinuities.length; i++) { - pendingDiscontinuities[i] = true; - } - } - - @Override - public long getBufferedPositionUs() { - if (loadingFinished) { - return TrackRenderer.END_OF_TRACK_US; - } else if (isPendingReset()) { - return pendingResetPositionUs; - } else { - long largestParsedTimestampUs = Long.MIN_VALUE; - for (int i = 0; i < sampleQueues.size(); i++) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, - sampleQueues.valueAt(i).getLargestParsedTimestampUs()); - } - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; - } - } - - @Override - public void release() { - Assertions.checkState(remainingReleaseCount > 0); - if (--remainingReleaseCount == 0) { - if (loader != null) { - loader.release(new Runnable() { - @Override - public void run() { - extractorHolder.release(); - } - }); - loader = null; - } - } - } - - // Loader.Callback implementation. - - @Override - public void onLoadCompleted(Loadable loadable) { - loadingFinished = true; - } - - @Override - public void onLoadCanceled(Loadable loadable) { - if (enabledTrackCount > 0) { - restartFrom(pendingResetPositionUs); - } else { - clearState(); - allocator.trim(0); - } - } - - @Override - public void onLoadError(Loadable ignored, IOException e) { - currentLoadableException = e; - currentLoadableExceptionCount = extractedSampleCount > extractedSampleCountAtStartOfLoad ? 1 - : currentLoadableExceptionCount + 1; - currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); - notifyLoadError(e); - maybeStartLoading(); - } - - // ExtractorOutput implementation. - - @Override - public TrackOutput track(int id) { - InternalTrackOutput sampleQueue = sampleQueues.get(id); - if (sampleQueue == null) { - sampleQueue = new InternalTrackOutput(allocator); - sampleQueues.put(id, sampleQueue); - } - return sampleQueue; - } - - @Override - public void endTracks() { - tracksBuilt = true; - } - - @Override - public void seekMap(SeekMap seekMap) { - this.seekMap = seekMap; - } - - @Override - public void drmInitData(DrmInitData drmInitData) { - this.drmInitData = drmInitData; - } - - // Internal stuff. - - private void restartFrom(long positionUs) { - pendingResetPositionUs = positionUs; - loadingFinished = false; - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - clearState(); - maybeStartLoading(); - } - } - - private void maybeStartLoading() { - if (loadingFinished || loader.isLoading()) { - return; - } - - if (currentLoadableException != null) { - if (isCurrentLoadableExceptionFatal()) { - return; - } - Assertions.checkState(loadable != null); - long elapsedMillis = SystemClock.elapsedRealtime() - currentLoadableExceptionTimestamp; - if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { - currentLoadableException = null; - if (!prepared) { - // We don't know whether we're playing an on-demand or a live stream. For a live stream - // we need to load from the start, as outlined below. Since we might be playing a live - // stream, play it safe and load from the start. - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).clear(); - } - loadable = createLoadableFromStart(); - } else if (!seekMap.isSeekable() && maxTrackDurationUs == C.UNKNOWN_TIME_US) { - // We're playing a non-seekable stream with unknown duration. Assume it's live, and - // therefore that the data at the uri is a continuously shifting window of the latest - // available media. For this case there's no way to continue loading from where a previous - // load finished, so it's necessary to load from the start whenever commencing a new load. - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).clear(); - } - loadable = createLoadableFromStart(); - // To avoid introducing a discontinuity, we shift the sample timestamps so that they will - // continue from the current downstream position. - pendingNextSampleUs = downstreamPositionUs; - havePendingNextSampleUs = true; - } else { - // We're playing a seekable on-demand stream. Resume the current loadable, which will - // request data starting from the point it left off. - } - extractedSampleCountAtStartOfLoad = extractedSampleCount; - loader.startLoading(loadable, this); - } - return; - } - - // We're not retrying, so we're either starting a playback or responding to an explicit seek. - // In both cases sampleTimeOffsetUs should be reset to zero, and any pending adjustment to - // sample timestamps should be discarded. - sampleTimeOffsetUs = 0; - havePendingNextSampleUs = false; - - if (!prepared) { - loadable = createLoadableFromStart(); - } else { - Assertions.checkState(isPendingReset()); - if (maxTrackDurationUs != C.UNKNOWN_TIME_US && pendingResetPositionUs >= maxTrackDurationUs) { - loadingFinished = true; - pendingResetPositionUs = NO_RESET_PENDING; - return; - } - loadable = createLoadableFromPositionUs(pendingResetPositionUs); - pendingResetPositionUs = NO_RESET_PENDING; - } - extractedSampleCountAtStartOfLoad = extractedSampleCount; - loader.startLoading(loadable, this); - } - - private ExtractingLoadable createLoadableFromStart() { - return new ExtractingLoadable(uri, dataSource, extractorHolder, allocator, requestedBufferSize, - 0); - } - - private ExtractingLoadable createLoadableFromPositionUs(long positionUs) { - return new ExtractingLoadable(uri, dataSource, extractorHolder, allocator, requestedBufferSize, - seekMap.getPosition(positionUs)); - } - - private boolean haveFormatsForAllTracks() { - for (int i = 0; i < sampleQueues.size(); i++) { - if (!sampleQueues.valueAt(i).hasFormat()) { - return false; - } - } - return true; - } - - private void discardSamplesForDisabledTracks(long timeUs) { - for (int i = 0; i < trackEnabledStates.length; i++) { - if (!trackEnabledStates[i]) { - sampleQueues.valueAt(i).discardUntil(timeUs); - } - } - } - - private void clearState() { - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).clear(); - } - loadable = null; - currentLoadableException = null; - currentLoadableExceptionCount = 0; - } - - private boolean isPendingReset() { - return pendingResetPositionUs != NO_RESET_PENDING; - } - - private boolean isCurrentLoadableExceptionFatal() { - return currentLoadableException instanceof UnrecognizedInputFormatException; - } - - private long getRetryDelayMillis(long errorCount) { - return Math.min((errorCount - 1) * 1000, 5000); - } - - private void notifyLoadError(final IOException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadError(eventSourceId, e); - } - }); - } - } - - /** - * Extension of {@link DefaultTrackOutput} that increments a shared counter of the total number - * of extracted samples. - */ - private class InternalTrackOutput extends DefaultTrackOutput { - - public InternalTrackOutput(Allocator allocator) { - super(allocator); - } - - @Override - public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - super.sampleMetadata(timeUs, flags, size, offset, encryptionKey); - extractedSampleCount++; - } - - } - - /** - * Loads the media stream and extracts sample data from it. - */ - private static class ExtractingLoadable implements Loadable { - - private final Uri uri; - private final DataSource dataSource; - private final ExtractorHolder extractorHolder; - private final Allocator allocator; - private final int requestedBufferSize; - private final PositionHolder positionHolder; - - private volatile boolean loadCanceled; - - private boolean pendingExtractorSeek; - - public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, - Allocator allocator, int requestedBufferSize, long position) { - this.uri = Assertions.checkNotNull(uri); - this.dataSource = Assertions.checkNotNull(dataSource); - this.extractorHolder = Assertions.checkNotNull(extractorHolder); - this.allocator = Assertions.checkNotNull(allocator); - this.requestedBufferSize = requestedBufferSize; - positionHolder = new PositionHolder(); - positionHolder.position = position; - pendingExtractorSeek = true; - } - - @Override - public void cancelLoad() { - loadCanceled = true; - } - - @Override - public boolean isLoadCanceled() { - return loadCanceled; - } - - @Override - public void load() throws IOException, InterruptedException { - int result = Extractor.RESULT_CONTINUE; - while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - ExtractorInput input = null; - try { - long position = positionHolder.position; - long length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null)); - if (length != C.LENGTH_UNBOUNDED) { - length += position; - } - input = new DefaultExtractorInput(dataSource, position, length); - Extractor extractor = extractorHolder.selectExtractor(input); - if (pendingExtractorSeek) { - extractor.seek(); - pendingExtractorSeek = false; - } - while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize); - result = extractor.read(input, positionHolder); - // TODO: Implement throttling to stop us from buffering data too often. - } - } finally { - if (result == Extractor.RESULT_SEEK) { - result = Extractor.RESULT_CONTINUE; - } else if (input != null) { - positionHolder.position = input.getPosition(); - } - dataSource.close(); - } - } - } - - } - - /** - * Stores a list of extractors and a selected extractor when the format has been detected. - */ - private static final class ExtractorHolder { - - private final Extractor[] extractors; - private final ExtractorOutput extractorOutput; - private Extractor extractor; - - /** - * Creates a holder that will select an extractor and initialize it using the specified output. - * - * @param extractors One or more extractors to choose from. - * @param extractorOutput The output that will be used to initialize the selected extractor. - */ - public ExtractorHolder(Extractor[] extractors, ExtractorOutput extractorOutput) { - this.extractors = extractors; - this.extractorOutput = extractorOutput; - } - - /** - * Returns an initialized extractor for reading {@code input}, and returns the same extractor on - * later calls. - * - * @param input The {@link ExtractorInput} from which data should be read. - * @throws UnrecognizedInputFormatException Thrown if the input format could not be detected. - * @throws IOException Thrown if the input could not be read. - * @throws InterruptedException Thrown if the thread was interrupted. - */ - public Extractor selectExtractor(ExtractorInput input) - throws UnrecognizedInputFormatException, IOException, InterruptedException { - if (extractor != null) { - return extractor; - } - for (Extractor extractor : extractors) { - try { - if (extractor.sniff(input)) { - this.extractor = extractor; - break; - } - } catch (EOFException e) { - // Do nothing. - } finally { - input.resetPeekPosition(); - } - } - if (extractor == null) { - throw new UnrecognizedInputFormatException(extractors); - } - extractor.init(extractorOutput); - return extractor; - } - - public void release() { - if (extractor != null) { - extractor.release(); - extractor = null; - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/GaplessInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/GaplessInfo.java deleted file mode 100755 index 70025ebd3bc..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/GaplessInfo.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utility for parsing and representing gapless playback information. - */ -public final class GaplessInfo { - - private static final String GAPLESS_COMMENT_ID = "iTunSMPB"; - private static final Pattern GAPLESS_COMMENT_PATTERN = - Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"); - - /** - * Parses a gapless playback comment (stored in an ID3 header or MPEG 4 user data). - * - * @param name The comment's identifier. - * @param data The comment's payload data. - * @return Parsed gapless playback information, if present and non-zero. {@code null} otherwise. - */ - public static GaplessInfo createFromComment(String name, String data) { - if (!GAPLESS_COMMENT_ID.equals(name)) { - return null; - } - Matcher matcher = GAPLESS_COMMENT_PATTERN.matcher(data); - if (matcher.find()) { - try { - int encoderDelay = Integer.parseInt(matcher.group(1), 16); - int encoderPadding = Integer.parseInt(matcher.group(2), 16); - return encoderDelay == 0 && encoderPadding == 0 ? null - : new GaplessInfo(encoderDelay, encoderPadding); - } catch (NumberFormatException e) { - // Ignore incorrectly formatted comments. - } - } - return null; - } - - /** - * Parses gapless playback information associated with an MP3 Xing header. - * - * @param value The 24-bit value to parse. - * @return Parsed gapless playback information, if non-zero. {@code null} otherwise. - */ - public static GaplessInfo createFromXingHeaderValue(int value) { - int encoderDelay = value >> 12; - int encoderPadding = value & 0x0FFF; - return encoderDelay == 0 && encoderPadding == 0 ? null - : new GaplessInfo(encoderDelay, encoderPadding); - } - - /** - * The number of samples to trim from the start of the decoded audio stream. - */ - public final int encoderDelay; - /** - * The number of samples to trim from the end of the decoded audio stream. - */ - public final int encoderPadding; - - /** - * Creates a new {@link GaplessInfo} with the specified encoder delay and padding. - * - * @param encoderDelay The encoder delay. - * @param encoderPadding The encoder padding. - */ - private GaplessInfo(int encoderDelay, int encoderPadding) { - this.encoderDelay = encoderDelay; - this.encoderPadding = encoderPadding; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/RollingSampleBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/RollingSampleBuffer.java deleted file mode 100755 index 62ea323bd66..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/RollingSampleBuffer.java +++ /dev/null @@ -1,673 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.upstream.Allocation; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.EOFException; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.concurrent.LinkedBlockingDeque; - -/** - * A rolling buffer of sample data and corresponding sample information. - */ -/* package */ final class RollingSampleBuffer { - - private static final int INITIAL_SCRATCH_SIZE = 32; - - private final Allocator allocator; - private final int allocationLength; - - private final InfoQueue infoQueue; - private final LinkedBlockingDeque dataQueue; - private final SampleExtrasHolder extrasHolder; - private final ParsableByteArray scratch; - - // Accessed only by the consuming thread. - private long totalBytesDropped; - - // Accessed only by the loading thread. - private long totalBytesWritten; - private Allocation lastAllocation; - private int lastAllocationOffset; - - /** - * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. - */ - public RollingSampleBuffer(Allocator allocator) { - this.allocator = allocator; - allocationLength = allocator.getIndividualAllocationLength(); - infoQueue = new InfoQueue(); - dataQueue = new LinkedBlockingDeque<>(); - extrasHolder = new SampleExtrasHolder(); - scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); - lastAllocationOffset = allocationLength; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Clears the buffer, returning all allocations to the allocator. - */ - public void clear() { - infoQueue.clear(); - - allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()])); - dataQueue.clear(); - - totalBytesDropped = 0; - totalBytesWritten = 0; - lastAllocation = null; - lastAllocationOffset = allocationLength; - } - - /** - * Returns the current absolute write index. - */ - public int getWriteIndex() { - return infoQueue.getWriteIndex(); - } - - /** - * Discards samples from the write side of the buffer. - * - * @param discardFromIndex The absolute index of the first sample to be discarded. - */ - public void discardUpstreamSamples(int discardFromIndex) { - totalBytesWritten = infoQueue.discardUpstreamSamples(discardFromIndex); - dropUpstreamFrom(totalBytesWritten); - } - - /** - * Discards data from the write side of the buffer. Data is discarded from the specified absolute - * position. Any allocations that are fully discarded are returned to the allocator. - * - * @param absolutePosition The absolute position (inclusive) from which to discard data. - */ - private void dropUpstreamFrom(long absolutePosition) { - int relativePosition = (int) (absolutePosition - totalBytesDropped); - // Calculate the index of the allocation containing the position, and the offset within it. - int allocationIndex = relativePosition / allocationLength; - int allocationOffset = relativePosition % allocationLength; - // We want to discard any allocations after the one at allocationIdnex. - int allocationDiscardCount = dataQueue.size() - allocationIndex - 1; - if (allocationOffset == 0) { - // If the allocation at allocationIndex is empty, we should discard that one too. - allocationDiscardCount++; - } - // Discard the allocations. - for (int i = 0; i < allocationDiscardCount; i++) { - allocator.release(dataQueue.removeLast()); - } - // Update lastAllocation and lastAllocationOffset to reflect the new position. - lastAllocation = dataQueue.peekLast(); - lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset; - } - - // Called by the consuming thread. - - /** - * Returns the current absolute read index. - */ - public int getReadIndex() { - return infoQueue.getReadIndex(); - } - - /** - * Fills {@code holder} with information about the current sample, but does not write its data. - *

        - * The fields set are {@link SampleHolder#size}, {@link SampleHolder#timeUs} and - * {@link SampleHolder#flags}. - * - * @param holder The holder into which the current sample information should be written. - * @return True if the holder was filled. False if there is no current sample. - */ - public boolean peekSample(SampleHolder holder) { - return infoQueue.peekSample(holder, extrasHolder); - } - - /** - * Skips the current sample. - */ - public void skipSample() { - long nextOffset = infoQueue.moveToNextSample(); - dropDownstreamTo(nextOffset); - } - - /** - * Attempts to skip to the keyframe before the specified time, if it's present in the buffer. - * - * @param timeUs The seek time. - * @return True if the skip was successful. False otherwise. - */ - public boolean skipToKeyframeBefore(long timeUs) { - long nextOffset = infoQueue.skipToKeyframeBefore(timeUs); - if (nextOffset == -1) { - return false; - } - dropDownstreamTo(nextOffset); - return true; - } - - /** - * Reads the current sample, advancing the read index to the next sample. - * - * @param sampleHolder The holder into which the current sample should be written. - * @return True if a sample was read. False if there is no current sample. - */ - public boolean readSample(SampleHolder sampleHolder) { - // Write the sample information into the holder and extrasHolder. - boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder); - if (!haveSample) { - return false; - } - - // Read encryption data if the sample is encrypted. - if (sampleHolder.isEncrypted()) { - readEncryptionData(sampleHolder, extrasHolder); - } - // Write the sample data into the holder. - sampleHolder.ensureSpaceForWrite(sampleHolder.size); - readData(extrasHolder.offset, sampleHolder.data, sampleHolder.size); - // Advance the read head. - long nextOffset = infoQueue.moveToNextSample(); - dropDownstreamTo(nextOffset); - return true; - } - - /** - * Reads encryption data for the current sample. - *

        - * The encryption data is written into {@code sampleHolder.cryptoInfo}, and - * {@code sampleHolder.size} is adjusted to subtract the number of bytes that were read. The - * same value is added to {@code extrasHolder.offset}. - * - * @param sampleHolder The holder into which the encryption data should be written. - * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. - */ - private void readEncryptionData(SampleHolder sampleHolder, SampleExtrasHolder extrasHolder) { - long offset = extrasHolder.offset; - - // Read the signal byte. - readData(offset, scratch.data, 1); - offset++; - byte signalByte = scratch.data[0]; - boolean subsampleEncryption = (signalByte & 0x80) != 0; - int ivSize = signalByte & 0x7F; - - // Read the initialization vector. - if (sampleHolder.cryptoInfo.iv == null) { - sampleHolder.cryptoInfo.iv = new byte[16]; - } - readData(offset, sampleHolder.cryptoInfo.iv, ivSize); - offset += ivSize; - - // Read the subsample count, if present. - int subsampleCount; - if (subsampleEncryption) { - readData(offset, scratch.data, 2); - offset += 2; - scratch.setPosition(0); - subsampleCount = scratch.readUnsignedShort(); - } else { - subsampleCount = 1; - } - - // Write the clear and encrypted subsample sizes. - int[] clearDataSizes = sampleHolder.cryptoInfo.numBytesOfClearData; - if (clearDataSizes == null || clearDataSizes.length < subsampleCount) { - clearDataSizes = new int[subsampleCount]; - } - int[] encryptedDataSizes = sampleHolder.cryptoInfo.numBytesOfEncryptedData; - if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) { - encryptedDataSizes = new int[subsampleCount]; - } - if (subsampleEncryption) { - int subsampleDataLength = 6 * subsampleCount; - ensureCapacity(scratch, subsampleDataLength); - readData(offset, scratch.data, subsampleDataLength); - offset += subsampleDataLength; - scratch.setPosition(0); - for (int i = 0; i < subsampleCount; i++) { - clearDataSizes[i] = scratch.readUnsignedShort(); - encryptedDataSizes[i] = scratch.readUnsignedIntToInt(); - } - } else { - clearDataSizes[0] = 0; - encryptedDataSizes[0] = sampleHolder.size - (int) (offset - extrasHolder.offset); - } - - // Populate the cryptoInfo. - sampleHolder.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes, - extrasHolder.encryptionKeyId, sampleHolder.cryptoInfo.iv, C.CRYPTO_MODE_AES_CTR); - - // Adjust the offset and size to take into account the bytes read. - int bytesRead = (int) (offset - extrasHolder.offset); - extrasHolder.offset += bytesRead; - sampleHolder.size -= bytesRead; - } - - /** - * Reads data from the front of the rolling buffer. - * - * @param absolutePosition The absolute position from which data should be read. - * @param target The buffer into which data should be written. - * @param length The number of bytes to read. - */ - private void readData(long absolutePosition, ByteBuffer target, int length) { - int remaining = length; - while (remaining > 0) { - dropDownstreamTo(absolutePosition); - int positionInAllocation = (int) (absolutePosition - totalBytesDropped); - int toCopy = Math.min(remaining, allocationLength - positionInAllocation); - Allocation allocation = dataQueue.peek(); - target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy); - absolutePosition += toCopy; - remaining -= toCopy; - } - } - - /** - * Reads data from the front of the rolling buffer. - * - * @param absolutePosition The absolute position from which data should be read. - * @param target The array into which data should be written. - * @param length The number of bytes to read. - */ - // TODO: Consider reducing duplication of this method and the one above. - private void readData(long absolutePosition, byte[] target, int length) { - int bytesRead = 0; - while (bytesRead < length) { - dropDownstreamTo(absolutePosition); - int positionInAllocation = (int) (absolutePosition - totalBytesDropped); - int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation); - Allocation allocation = dataQueue.peek(); - System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target, - bytesRead, toCopy); - absolutePosition += toCopy; - bytesRead += toCopy; - } - } - - /** - * Discard any allocations that hold data prior to the specified absolute position, returning - * them to the allocator. - * - * @param absolutePosition The absolute position up to which allocations can be discarded. - */ - private void dropDownstreamTo(long absolutePosition) { - int relativePosition = (int) (absolutePosition - totalBytesDropped); - int allocationIndex = relativePosition / allocationLength; - for (int i = 0; i < allocationIndex; i++) { - allocator.release(dataQueue.remove()); - totalBytesDropped += allocationLength; - } - } - - /** - * Ensure that the passed {@link ParsableByteArray} is of at least the specified limit. - */ - private static void ensureCapacity(ParsableByteArray byteArray, int limit) { - if (byteArray.limit() < limit) { - byteArray.reset(new byte[limit], limit); - } - } - - // Called by the loading thread. - - /** - * Returns the current write position in the rolling buffer. - * - * @return The current write position. - */ - public long getWritePosition() { - return totalBytesWritten; - } - - /** - * Appends data to the rolling buffer. - * - * @param dataSource The source from which to read. - * @param length The maximum length of the read. - * @param allowEndOfInput True if encountering the end of the input having appended no data is - * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it - * should be considered an error, causing an {@link EOFException} to be thrown. - * @return The number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. - * @throws IOException If an error occurs reading from the source. - */ - public int appendData(DataSource dataSource, int length, boolean allowEndOfInput) - throws IOException { - length = prepareForAppend(length); - int bytesAppended = dataSource.read(lastAllocation.data, - lastAllocation.translateOffset(lastAllocationOffset), length); - if (bytesAppended == C.RESULT_END_OF_INPUT) { - if (allowEndOfInput) { - return C.RESULT_END_OF_INPUT; - } - throw new EOFException(); - } - lastAllocationOffset += bytesAppended; - totalBytesWritten += bytesAppended; - return bytesAppended; - } - - /** - * Appends data to the rolling buffer. - * - * @param input The source from which to read. - * @param length The maximum length of the read. - * @param allowEndOfInput True if encountering the end of the input having appended no data is - * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it - * should be considered an error, causing an {@link EOFException} to be thrown. - * @return The number of bytes appended, or {@link C#RESULT_END_OF_INPUT} if the input has ended. - * @throws IOException If an error occurs reading from the source. - * @throws InterruptedException If the thread has been interrupted. - */ - public int appendData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException { - length = prepareForAppend(length); - int bytesAppended = input.read(lastAllocation.data, - lastAllocation.translateOffset(lastAllocationOffset), length); - if (bytesAppended == C.RESULT_END_OF_INPUT) { - if (allowEndOfInput) { - return C.RESULT_END_OF_INPUT; - } - throw new EOFException(); - } - lastAllocationOffset += bytesAppended; - totalBytesWritten += bytesAppended; - return bytesAppended; - } - - /** - * Appends data to the rolling buffer. - * - * @param buffer A buffer containing the data to append. - * @param length The length of the data to append. - */ - public void appendData(ParsableByteArray buffer, int length) { - while (length > 0) { - int thisAppendLength = prepareForAppend(length); - buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), - thisAppendLength); - lastAllocationOffset += thisAppendLength; - totalBytesWritten += thisAppendLength; - length -= thisAppendLength; - } - } - - /** - * Indicates the end point for the current sample, making it available for consumption. - * - * @param sampleTimeUs The sample timestamp. - * @param flags Flags that accompany the sample. See {@link SampleHolder#flags}. - * @param position The position of the sample data in the rolling buffer. - * @param size The size of the sample, in bytes. - * @param encryptionKey The encryption key associated with the sample, or null. - */ - public void commitSample(long sampleTimeUs, int flags, long position, int size, - byte[] encryptionKey) { - infoQueue.commitSample(sampleTimeUs, flags, position, size, encryptionKey); - } - - /** - * Prepares the rolling sample buffer for an append of up to {@code length} bytes, returning the - * number of bytes that can actually be appended. - */ - private int prepareForAppend(int length) { - if (lastAllocationOffset == allocationLength) { - lastAllocationOffset = 0; - lastAllocation = allocator.allocate(); - dataQueue.add(lastAllocation); - } - return Math.min(length, allocationLength - lastAllocationOffset); - } - - /** - * Holds information about the samples in the rolling buffer. - */ - private static final class InfoQueue { - - private static final int SAMPLE_CAPACITY_INCREMENT = 1000; - - private int capacity; - - private long[] offsets; - private int[] sizes; - private int[] flags; - private long[] timesUs; - private byte[][] encryptionKeys; - - private int queueSize; - private int absoluteReadIndex; - private int relativeReadIndex; - private int relativeWriteIndex; - - public InfoQueue() { - capacity = SAMPLE_CAPACITY_INCREMENT; - offsets = new long[capacity]; - timesUs = new long[capacity]; - flags = new int[capacity]; - sizes = new int[capacity]; - encryptionKeys = new byte[capacity][]; - } - - // Called by the consuming thread, but only when there is no loading thread. - - /** - * Clears the queue. - */ - public void clear() { - absoluteReadIndex = 0; - relativeReadIndex = 0; - relativeWriteIndex = 0; - queueSize = 0; - } - - /** - * Returns the current absolute write index. - */ - public int getWriteIndex() { - return absoluteReadIndex + queueSize; - } - - /** - * Discards samples from the write side of the buffer. - * - * @param discardFromIndex The absolute index of the first sample to be discarded. - * @return The reduced total number of bytes written, after the samples have been discarded. - */ - public long discardUpstreamSamples(int discardFromIndex) { - int discardCount = getWriteIndex() - discardFromIndex; - Assertions.checkArgument(0 <= discardCount && discardCount <= queueSize); - - if (discardCount == 0) { - if (absoluteReadIndex == 0) { - // queueSize == absoluteReadIndex == 0, so nothing has been written to the queue. - return 0; - } - int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; - return offsets[lastWriteIndex] + sizes[lastWriteIndex]; - } - - queueSize -= discardCount; - relativeWriteIndex = (relativeWriteIndex + capacity - discardCount) % capacity; - return offsets[relativeWriteIndex]; - } - - // Called by the consuming thread. - - /** - * Returns the current absolute read index. - */ - public int getReadIndex() { - return absoluteReadIndex; - } - - /** - * Fills {@code holder} with information about the current sample, but does not write its data. - * The first entry in {@code offsetHolder} is filled with the absolute position of the sample's - * data in the rolling buffer. - *

        - * Populates {@link SampleHolder#size}, {@link SampleHolder#timeUs}, {@link SampleHolder#flags} - * and the {@code extrasHolder}. - * - * @param holder The holder into which the current sample information should be written. - * @param extrasHolder The holder into which extra sample information should be written. - * @return True if the holders were filled. False if there is no current sample. - */ - public synchronized boolean peekSample(SampleHolder holder, SampleExtrasHolder extrasHolder) { - if (queueSize == 0) { - return false; - } - holder.timeUs = timesUs[relativeReadIndex]; - holder.size = sizes[relativeReadIndex]; - holder.flags = flags[relativeReadIndex]; - extrasHolder.offset = offsets[relativeReadIndex]; - extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex]; - return true; - } - - /** - * Advances the read index to the next sample. - * - * @return The absolute position of the first byte in the rolling buffer that may still be - * required after advancing the index. Data prior to this position can be dropped. - */ - public synchronized long moveToNextSample() { - queueSize--; - int lastReadIndex = relativeReadIndex++; - absoluteReadIndex++; - if (relativeReadIndex == capacity) { - // Wrap around. - relativeReadIndex = 0; - } - return queueSize > 0 ? offsets[relativeReadIndex] - : (sizes[lastReadIndex] + offsets[lastReadIndex]); - } - - /** - * Attempts to locate the keyframe before the specified time, if it's present in the buffer. - * - * @param timeUs The seek time. - * @return The offset of the keyframe's data if the keyframe was present. -1 otherwise. - */ - public synchronized long skipToKeyframeBefore(long timeUs) { - if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) { - return -1; - } - - int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; - long lastTimeUs = timesUs[lastWriteIndex]; - if (timeUs > lastTimeUs) { - return -1; - } - - // TODO: This can be optimized further using binary search, although the fact that the array - // is cyclic means we'd need to implement the binary search ourselves. - int sampleCount = 0; - int sampleCountToKeyframe = -1; - int searchIndex = relativeReadIndex; - while (searchIndex != relativeWriteIndex) { - if (timesUs[searchIndex] > timeUs) { - // We've gone too far. - break; - } else if ((flags[searchIndex] & C.SAMPLE_FLAG_SYNC) != 0) { - // We've found a keyframe, and we're still before the seek position. - sampleCountToKeyframe = sampleCount; - } - searchIndex = (searchIndex + 1) % capacity; - sampleCount++; - } - - if (sampleCountToKeyframe == -1) { - return -1; - } - - queueSize -= sampleCountToKeyframe; - relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; - absoluteReadIndex += sampleCountToKeyframe; - return offsets[relativeReadIndex]; - } - - // Called by the loading thread. - - public synchronized void commitSample(long timeUs, int sampleFlags, long offset, int size, - byte[] encryptionKey) { - timesUs[relativeWriteIndex] = timeUs; - offsets[relativeWriteIndex] = offset; - sizes[relativeWriteIndex] = size; - flags[relativeWriteIndex] = sampleFlags; - encryptionKeys[relativeWriteIndex] = encryptionKey; - // Increment the write index. - queueSize++; - if (queueSize == capacity) { - // Increase the capacity. - int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT; - long[] newOffsets = new long[newCapacity]; - long[] newTimesUs = new long[newCapacity]; - int[] newFlags = new int[newCapacity]; - int[] newSizes = new int[newCapacity]; - byte[][] newEncryptionKeys = new byte[newCapacity][]; - int beforeWrap = capacity - relativeReadIndex; - System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap); - System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap); - System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap); - System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap); - System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap); - int afterWrap = relativeReadIndex; - System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); - System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); - System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); - System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); - System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap); - offsets = newOffsets; - timesUs = newTimesUs; - flags = newFlags; - sizes = newSizes; - encryptionKeys = newEncryptionKeys; - relativeReadIndex = 0; - relativeWriteIndex = capacity; - queueSize = capacity; - capacity = newCapacity; - } else { - relativeWriteIndex++; - if (relativeWriteIndex == capacity) { - // Wrap around. - relativeWriteIndex = 0; - } - } - } - - } - - /** - * Holds additional sample information not held by {@link SampleHolder}. - */ - private static final class SampleExtrasHolder { - - public long offset; - public byte[] encryptionKeyId; - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/SeekMap.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/SeekMap.java deleted file mode 100755 index 3a0df3cc048..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/SeekMap.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -/** - * Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream. - */ -public interface SeekMap { - - /** - * A {@link SeekMap} that does not support seeking. - */ - public static final SeekMap UNSEEKABLE = new SeekMap() { - - @Override - public boolean isSeekable() { - return false; - } - - @Override - public long getPosition(long timeUs) { - return 0; - } - - }; - - /** - * Whether or not the seeking is supported. - *

        - * If seeking is not supported then the only valid seek position is the start of the file, and so - * {@link #getPosition(long)} will return 0 for all input values. - * - * @return True if seeking is supported. False otherwise. - */ - boolean isSeekable(); - - /** - * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream - * from which data can be provided to the extractor. - * - * @param timeUs A seek position in microseconds. - * @return The corresponding position (byte offset) in the stream from which data can be provided - * to the extractor, or 0 if {@code #isSeekable()} returns false. - */ - long getPosition(long timeUs); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/TrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/TrackOutput.java deleted file mode 100755 index da7d5cbc28b..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/TrackOutput.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.EOFException; -import java.io.IOException; - -/** - * Receives track level data extracted by an {@link Extractor}. - */ -public interface TrackOutput { - - /** - * Invoked when the {@link MediaFormat} of the track has been extracted from the stream. - * - * @param format The extracted {@link MediaFormat}. - */ - void format(MediaFormat format); - - /** - * Invoked to write sample data to the output. - * - * @param input An {@link ExtractorInput} from which to read the sample data. - * @param length The maximum length to read from the input. - * @param allowEndOfInput True if encountering the end of the input having read no data is - * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it - * should be considered an error, causing an {@link EOFException} to be thrown. - * @return The number of bytes appended. - * @throws IOException If an error occurred reading from the input. - * @throws InterruptedException If the thread was interrupted. - */ - int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) - throws IOException, InterruptedException; - - /** - * Invoked to write sample data to the output. - * - * @param data A {@link ParsableByteArray} from which to read the sample data. - * @param length The number of bytes to read. - */ - void sampleData(ParsableByteArray data, int length); - - /** - * Invoked when metadata associated with a sample has been extracted from the stream. - *

        - * The corresponding sample data will have already been passed to the output via calls to - * {@link #sampleData(ExtractorInput, int, boolean)} or - * {@link #sampleData(ParsableByteArray, int)}. - * - * @param timeUs The media timestamp associated with the sample, in microseconds. - * @param flags Flags associated with the sample. See {@link SampleHolder#flags}. - * @param size The size of the sample data, in bytes. - * @param offset The number of bytes that have been passed to - * {@link #sampleData(ExtractorInput, int, boolean)} or - * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample - * whose metadata is being passed. - * @param encryptionKey The encryption key associated with the sample. May be null. - */ - void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/TagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/TagPayloadReader.java deleted file mode 100755 index 302db7527b1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/TagPayloadReader.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.flv; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * Extracts individual samples from FLV tags, preserving original order. - */ -/* package */ abstract class TagPayloadReader { - - /** - * Thrown when the format is not supported. - */ - public static final class UnsupportedFormatException extends ParserException { - - public UnsupportedFormatException(String msg) { - super(msg); - } - - } - - protected final TrackOutput output; - - private long durationUs; - - /** - * @param output A {@link TrackOutput} to which samples should be written. - */ - protected TagPayloadReader(TrackOutput output) { - this.output = output; - this.durationUs = C.UNKNOWN_TIME_US; - } - - /** - * Sets duration in microseconds. - * - * @param durationUs duration in microseconds. - */ - public final void setDurationUs(long durationUs) { - this.durationUs = durationUs; - } - - /** - * Gets the duration in microseconds. - * - * @return The duration in microseconds. - */ - public final long getDurationUs() { - return durationUs; - } - - /** - * Notifies the reader that a seek has occurred. - *

        - * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, long)} will not be a continuation of the data that - * was previously passed. Hence the reader should reset any internal state. - */ - public abstract void seek(); - - /** - * Consumes payload data. - * - * @param data The payload data to consume. - * @param timeUs The timestamp associated with the payload. - * @throws ParserException If an error occurs parsing the data. - */ - public final void consume(ParsableByteArray data, long timeUs) throws ParserException { - if (parseHeader(data)) { - parsePayload(data, timeUs); - } - } - - /** - * Parses tag header. - * - * @param data Buffer where the tag header is stored. - * @return True if the header was parsed successfully and the payload should be read. False - * otherwise. - * @throws ParserException If an error occurs parsing the header. - */ - protected abstract boolean parseHeader(ParsableByteArray data) throws ParserException; - - /** - * Parses tag payload. - * - * @param data Buffer where tag payload is stored - * @param timeUs Time position of the frame - * @throws ParserException If an error occurs parsing the payload. - */ - protected abstract void parsePayload(ParsableByteArray data, long timeUs) throws ParserException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/VideoTagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/VideoTagPayloadReader.java deleted file mode 100755 index 6b44e1e06d9..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/VideoTagPayloadReader.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.flv; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.ArrayList; -import java.util.List; - -/** - * Parses video tags from an FLV stream and extracts H.264 nal units. - */ -/* package */ final class VideoTagPayloadReader extends TagPayloadReader { - - // Video codec. - private static final int VIDEO_CODEC_AVC = 7; - - // Frame types. - private static final int VIDEO_FRAME_KEYFRAME = 1; - private static final int VIDEO_FRAME_VIDEO_INFO = 5; - - // Packet types. - private static final int AVC_PACKET_TYPE_SEQUENCE_HEADER = 0; - private static final int AVC_PACKET_TYPE_AVC_NALU = 1; - - // Temporary arrays. - private final ParsableByteArray nalStartCode; - private final ParsableByteArray nalLength; - private int nalUnitLengthFieldLength; - - // State variables. - private boolean hasOutputFormat; - private int frameType; - - /** - * @param output A {@link TrackOutput} to which samples should be written. - */ - public VideoTagPayloadReader(TrackOutput output) { - super(output); - nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); - nalLength = new ParsableByteArray(4); - } - - @Override - public void seek() { - // Do nothing. - } - - @Override - protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException { - int header = data.readUnsignedByte(); - int frameType = (header >> 4) & 0x0F; - int videoCodec = (header & 0x0F); - // Support just H.264 encoded content. - if (videoCodec != VIDEO_CODEC_AVC) { - throw new UnsupportedFormatException("Video format not supported: " + videoCodec); - } - this.frameType = frameType; - return (frameType != VIDEO_FRAME_VIDEO_INFO); - } - - @Override - protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException { - int packetType = data.readUnsignedByte(); - int compositionTimeMs = data.readUnsignedInt24(); - timeUs += compositionTimeMs * 1000L; - // Parse avc sequence header in case this was not done before. - if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { - ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]); - data.readBytes(videoSequence.data, 0, data.bytesLeft()); - - AvcSequenceHeaderData avcData = parseAvcCodecPrivate(videoSequence); - nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength; - - // Construct and output the format. - MediaFormat mediaFormat = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), avcData.width, - avcData.height, avcData.initializationData, MediaFormat.NO_VALUE, - avcData.pixelWidthAspectRatio); - output.format(mediaFormat); - hasOutputFormat = true; - } else if (packetType == AVC_PACKET_TYPE_AVC_NALU) { - // TODO: Deduplicate with Mp4Extractor. - // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case - // they're only 1 or 2 bytes long. - byte[] nalLengthData = nalLength.data; - nalLengthData[0] = 0; - nalLengthData[1] = 0; - nalLengthData[2] = 0; - int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength; - // NAL units are length delimited, but the decoder requires start code delimited units. - // Loop until we've written the sample to the track output, replacing length delimiters with - // start codes as we encounter them. - int bytesWritten = 0; - int bytesToWrite; - while (data.bytesLeft() > 0) { - // Read the NAL length so that we know where we find the next one. - data.readBytes(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); - nalLength.setPosition(0); - bytesToWrite = nalLength.readUnsignedIntToInt(); - - // Write a start code for the current NAL unit. - nalStartCode.setPosition(0); - output.sampleData(nalStartCode, 4); - bytesWritten += 4; - - // Write the payload of the NAL unit. - output.sampleData(data, bytesToWrite); - bytesWritten += bytesToWrite; - } - output.sampleMetadata(timeUs, frameType == VIDEO_FRAME_KEYFRAME ? C.SAMPLE_FLAG_SYNC : 0, - bytesWritten, 0, null); - } - } - - /** - * Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data. - * - * @return The AvcSequenceHeader data needed to initialize the video codec. - * @throws ParserException If the initialization data could not be built. - */ - private AvcSequenceHeaderData parseAvcCodecPrivate(ParsableByteArray buffer) - throws ParserException { - // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. - buffer.setPosition(4); - int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; - Assertions.checkState(nalUnitLengthFieldLength != 3); - List initializationData = new ArrayList<>(); - int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; - for (int i = 0; i < numSequenceParameterSets; i++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - int numPictureParameterSets = buffer.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - - float pixelWidthAspectRatio = 1; - int width = MediaFormat.NO_VALUE; - int height = MediaFormat.NO_VALUE; - if (numSequenceParameterSets > 0) { - // Parse the first sequence parameter set to obtain pixelWidthAspectRatio. - ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); - // Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). - spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); - NalUnitUtil.SpsData sps = NalUnitUtil.parseSpsNalUnit(spsDataBitArray); - width = sps.width; - height = sps.height; - pixelWidthAspectRatio = sps.pixelWidthAspectRatio; - } - - return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength, - width, height, pixelWidthAspectRatio); - } - - /** - * Holds data parsed from an Sequence Header video tag atom. - */ - private static final class AvcSequenceHeaderData { - - public final List initializationData; - public final int nalUnitLengthFieldLength; - public final float pixelWidthAspectRatio; - public final int width; - public final int height; - - public AvcSequenceHeaderData(List initializationData, int nalUnitLengthFieldLength, - int width, int height, float pixelWidthAspectRatio) { - this.initializationData = initializationData; - this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; - this.pixelWidthAspectRatio = pixelWidthAspectRatio; - this.width = width; - this.height = height; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Id3Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Id3Util.java deleted file mode 100755 index d287086d908..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Id3Util.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp3; - -import android.util.Pair; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.GaplessInfo; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.nio.charset.Charset; - -/** - * Utility for parsing ID3 version 2 metadata in MP3 files. - */ -/* package */ final class Id3Util { - - /** - * The maximum valid length for metadata in bytes. - */ - private static final int MAXIMUM_METADATA_SIZE = 3 * 1024 * 1024; - - private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); - private static final Charset[] CHARSET_BY_ENCODING = new Charset[] {Charset.forName("ISO-8859-1"), - Charset.forName("UTF-16LE"), Charset.forName("UTF-16BE"), Charset.forName("UTF-8")}; - - /** - * Peeks data from the input and parses ID3 metadata. - * - * @param input The {@link ExtractorInput} from which data should be peeked. - * @return The gapless playback information, if present and non-zero. {@code null} otherwise. - * @throws IOException If an error occurred peeking from the input. - * @throws InterruptedException If the thread was interrupted. - */ - public static GaplessInfo parseId3(ExtractorInput input) - throws IOException, InterruptedException { - ParsableByteArray scratch = new ParsableByteArray(10); - int peekedId3Bytes = 0; - GaplessInfo metadata = null; - while (true) { - input.peekFully(scratch.data, 0, 10); - scratch.setPosition(0); - if (scratch.readUnsignedInt24() != ID3_TAG) { - break; - } - - int majorVersion = scratch.readUnsignedByte(); - int minorVersion = scratch.readUnsignedByte(); - int flags = scratch.readUnsignedByte(); - int length = scratch.readSynchSafeInt(); - if (metadata == null && canParseMetadata(majorVersion, minorVersion, flags, length)) { - byte[] frame = new byte[length]; - input.peekFully(frame, 0, length); - metadata = parseGaplessInfo(new ParsableByteArray(frame), majorVersion, flags); - } else { - input.advancePeekPosition(length); - } - - peekedId3Bytes += 10 + length; - } - input.resetPeekPosition(); - input.advancePeekPosition(peekedId3Bytes); - return metadata; - } - - private static boolean canParseMetadata(int majorVersion, int minorVersion, int flags, - int length) { - return minorVersion != 0xFF && majorVersion >= 2 && majorVersion <= 4 - && length <= MAXIMUM_METADATA_SIZE - && !(majorVersion == 2 && ((flags & 0x3F) != 0 || (flags & 0x40) != 0)) - && !(majorVersion == 3 && (flags & 0x1F) != 0) - && !(majorVersion == 4 && (flags & 0x0F) != 0); - } - - private static GaplessInfo parseGaplessInfo(ParsableByteArray frame, int version, int flags) { - unescape(frame, version, flags); - - // Skip any extended header. - frame.setPosition(0); - if (version == 3 && (flags & 0x40) != 0) { - if (frame.bytesLeft() < 4) { - return null; - } - int extendedHeaderSize = frame.readUnsignedIntToInt(); - if (extendedHeaderSize > frame.bytesLeft()) { - return null; - } - int paddingSize = 0; - if (extendedHeaderSize >= 6) { - frame.skipBytes(2); // extended flags - paddingSize = frame.readUnsignedIntToInt(); - frame.setPosition(4); - frame.setLimit(frame.limit() - paddingSize); - if (frame.bytesLeft() < extendedHeaderSize) { - return null; - } - } - frame.skipBytes(extendedHeaderSize); - } else if (version == 4 && (flags & 0x40) != 0) { - if (frame.bytesLeft() < 4) { - return null; - } - int extendedHeaderSize = frame.readSynchSafeInt(); - if (extendedHeaderSize < 6 || extendedHeaderSize > frame.bytesLeft() + 4) { - return null; - } - frame.setPosition(extendedHeaderSize); - } - - // Extract gapless playback metadata stored in comments. - Pair comment; - while ((comment = findNextComment(version, frame)) != null) { - if (comment.first.length() > 3) { - GaplessInfo gaplessInfo = - GaplessInfo.createFromComment(comment.first.substring(3), comment.second); - if (gaplessInfo != null) { - return gaplessInfo; - } - } - } - return null; - } - - private static Pair findNextComment(int majorVersion, ParsableByteArray data) { - int frameSize; - while (true) { - if (majorVersion == 2) { - if (data.bytesLeft() < 6) { - return null; - } - String id = data.readString(3, Charset.forName("US-ASCII")); - if (id.equals("\0\0\0")) { - return null; - } - frameSize = data.readUnsignedInt24(); - if (frameSize == 0 || frameSize > data.bytesLeft()) { - return null; - } - if (id.equals("COM")) { - break; - } - } else /* major == 3 || major == 4 */ { - if (data.bytesLeft() < 10) { - return null; - } - String id = data.readString(4, Charset.forName("US-ASCII")); - if (id.equals("\0\0\0\0")) { - return null; - } - frameSize = majorVersion == 4 ? data.readSynchSafeInt() : data.readUnsignedIntToInt(); - if (frameSize == 0 || frameSize > data.bytesLeft() - 2) { - return null; - } - int flags = data.readUnsignedShort(); - boolean compressedOrEncrypted = (majorVersion == 4 && (flags & 0x0C) != 0) - || (majorVersion == 3 && (flags & 0xC0) != 0); - if (!compressedOrEncrypted && id.equals("COMM")) { - break; - } - } - data.skipBytes(frameSize); - } - - // The comment tag is at the reading position in data. - int encoding = data.readUnsignedByte(); - if (encoding < 0 || encoding >= CHARSET_BY_ENCODING.length) { - return null; - } - Charset charset = CHARSET_BY_ENCODING[encoding]; - String[] commentFields = data.readString(frameSize - 1, charset).split("\0"); - return commentFields.length == 2 ? Pair.create(commentFields[0], commentFields[1]) : null; - } - - private static boolean unescape(ParsableByteArray frame, int version, int flags) { - if (version != 4) { - if ((flags & 0x80) != 0) { - // Remove unsynchronization on ID3 version < 2.4.0. - byte[] bytes = frame.data; - int newLength = bytes.length; - for (int i = 0; i + 1 < newLength; i++) { - if ((bytes[i] & 0xFF) == 0xFF && bytes[i + 1] == 0x00) { - System.arraycopy(bytes, i + 2, bytes, i + 1, newLength - i - 2); - newLength--; - } - } - frame.setLimit(newLength); - } - } else { - // Remove unsynchronization on ID3 version 2.4.0. - if (canUnescapeVersion4(frame, false)) { - unescapeVersion4(frame, false); - } else if (canUnescapeVersion4(frame, true)) { - unescapeVersion4(frame, true); - } else { - return false; - } - } - return true; - } - - private static boolean canUnescapeVersion4(ParsableByteArray frame, - boolean unsignedIntDataSizeHack) { - frame.setPosition(0); - while (frame.bytesLeft() >= 10) { - if (frame.readInt() == 0) { - return true; - } - long dataSize = frame.readUnsignedInt(); - if (!unsignedIntDataSizeHack) { - // Parse the data size as a syncsafe integer. - if ((dataSize & 0x808080L) != 0) { - return false; - } - dataSize = (dataSize & 0x7F) | (((dataSize >> 8) & 0x7F) << 7) - | (((dataSize >> 16) & 0x7F) << 14) | (((dataSize >> 24) & 0x7F) << 21); - } - if (dataSize > frame.bytesLeft() - 2) { - return false; - } - int flags = frame.readUnsignedShort(); - if ((flags & 1) != 0) { - if (frame.bytesLeft() < 4) { - return false; - } - } - frame.skipBytes((int) dataSize); - } - return true; - } - - private static void unescapeVersion4(ParsableByteArray frame, boolean unsignedIntDataSizeHack) { - frame.setPosition(0); - byte[] bytes = frame.data; - while (frame.bytesLeft() >= 10) { - if (frame.readInt() == 0) { - return; - } - int dataSize = - unsignedIntDataSizeHack ? frame.readUnsignedIntToInt() : frame.readSynchSafeInt(); - int flags = frame.readUnsignedShort(); - int previousFlags = flags; - if ((flags & 1) != 0) { - // Strip data length indicator. - int offset = frame.getPosition(); - System.arraycopy(bytes, offset + 4, bytes, offset, frame.bytesLeft() - 4); - dataSize -= 4; - flags &= ~1; - frame.setLimit(frame.limit() - 4); - } - if ((flags & 2) != 0) { - // Unescape 0xFF00 to 0xFF in the next dataSize bytes. - int readOffset = frame.getPosition() + 1; - int writeOffset = readOffset; - for (int i = 0; i + 1 < dataSize; i++) { - if ((bytes[readOffset - 1] & 0xFF) == 0xFF && bytes[readOffset] == 0) { - readOffset++; - dataSize--; - } - bytes[writeOffset++] = bytes[readOffset++]; - } - frame.setLimit(frame.limit() - (readOffset - writeOffset)); - System.arraycopy(bytes, readOffset, bytes, writeOffset, frame.bytesLeft() - readOffset); - flags &= ~2; - } - if (flags != previousFlags || unsignedIntDataSizeHack) { - int dataSizeOffset = frame.getPosition() - 6; - writeSyncSafeInteger(bytes, dataSizeOffset, dataSize); - bytes[dataSizeOffset + 4] = (byte) (flags >> 8); - bytes[dataSizeOffset + 5] = (byte) (flags & 0xFF); - } - frame.skipBytes(dataSize); - } - } - - private static void writeSyncSafeInteger(byte[] bytes, int offset, int value) { - bytes[offset] = (byte) ((value >> 21) & 0x7F); - bytes[offset + 1] = (byte) ((value >> 14) & 0x7F); - bytes[offset + 2] = (byte) ((value >> 7) & 0x7F); - bytes[offset + 3] = (byte) (value & 0x7F); - } - - private Id3Util() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Mp3Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Mp3Extractor.java deleted file mode 100755 index f3e13105d3f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/Mp3Extractor.java +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp3; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.GaplessInfo; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MpegAudioHeader; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.EOFException; -import java.io.IOException; - -/** - * Extracts data from an MP3 file. - */ -public final class Mp3Extractor implements Extractor { - - /** - * The maximum number of bytes to search when synchronizing, before giving up. - */ - private static final int MAX_SYNC_BYTES = 128 * 1024; - /** - * The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up. - */ - private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; - - /** - * Mask that includes the audio header values that must match between frames. - */ - private static final int HEADER_MASK = 0xFFFE0C00; - private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); - private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); - private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); - - private final long forcedFirstSampleTimestampUs; - private final ParsableByteArray scratch; - private final MpegAudioHeader synchronizedHeader; - - // Extractor outputs. - private ExtractorOutput extractorOutput; - private TrackOutput trackOutput; - - private int synchronizedHeaderData; - - private GaplessInfo gaplessInfo; - private Seeker seeker; - private long basisTimeUs; - private long samplesRead; - private int sampleBytesRemaining; - - /** - * Constructs a new {@link Mp3Extractor}. - */ - public Mp3Extractor() { - this(-1); - } - - /** - * Constructs a new {@link Mp3Extractor}. - * - * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or -1 if forcing - * is not required. - */ - public Mp3Extractor(long forcedFirstSampleTimestampUs) { - this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; - scratch = new ParsableByteArray(4); - synchronizedHeader = new MpegAudioHeader(); - basisTimeUs = -1; - } - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - return synchronize(input, true); - } - - @Override - public void init(ExtractorOutput extractorOutput) { - this.extractorOutput = extractorOutput; - trackOutput = extractorOutput.track(0); - extractorOutput.endTracks(); - } - - @Override - public void seek() { - synchronizedHeaderData = 0; - samplesRead = 0; - basisTimeUs = -1; - sampleBytesRemaining = 0; - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - if (synchronizedHeaderData == 0 && !synchronizeCatchingEndOfInput(input)) { - return RESULT_END_OF_INPUT; - } - if (seeker == null) { - setupSeeker(input); - extractorOutput.seekMap(seeker); - MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, synchronizedHeader.mimeType, - MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), - synchronizedHeader.channels, synchronizedHeader.sampleRate, null, null); - if (gaplessInfo != null) { - mediaFormat = - mediaFormat.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding); - } - trackOutput.format(mediaFormat); - } - return readSample(input); - } - - private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { - if (sampleBytesRemaining == 0) { - if (!maybeResynchronize(extractorInput)) { - return RESULT_END_OF_INPUT; - } - if (basisTimeUs == -1) { - basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); - if (forcedFirstSampleTimestampUs != -1) { - long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0); - basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs; - } - } - sampleBytesRemaining = synchronizedHeader.frameSize; - } - int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); - if (bytesAppended == C.RESULT_END_OF_INPUT) { - return RESULT_END_OF_INPUT; - } - sampleBytesRemaining -= bytesAppended; - if (sampleBytesRemaining > 0) { - return RESULT_CONTINUE; - } - long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); - trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, synchronizedHeader.frameSize, 0, null); - samplesRead += synchronizedHeader.samplesPerFrame; - sampleBytesRemaining = 0; - return RESULT_CONTINUE; - } - - /** - * Attempts to read an MPEG audio header at the current offset, resynchronizing if necessary. - */ - private boolean maybeResynchronize(ExtractorInput extractorInput) - throws IOException, InterruptedException { - extractorInput.resetPeekPosition(); - if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { - return false; - } - - scratch.setPosition(0); - int sampleHeaderData = scratch.readInt(); - if ((sampleHeaderData & HEADER_MASK) == (synchronizedHeaderData & HEADER_MASK)) { - int frameSize = MpegAudioHeader.getFrameSize(sampleHeaderData); - if (frameSize != -1) { - MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); - return true; - } - } - - synchronizedHeaderData = 0; - extractorInput.skipFully(1); - return synchronizeCatchingEndOfInput(extractorInput); - } - - private boolean synchronizeCatchingEndOfInput(ExtractorInput input) - throws IOException, InterruptedException { - // An EOFException will be raised if any peek operation was partially satisfied. If a seek - // operation resulted in reading from within the last frame, we may try to peek past the end of - // the file in a partially-satisfied read operation, so we need to catch the exception. - try { - return synchronize(input, false); - } catch (EOFException e) { - return false; - } - } - - private boolean synchronize(ExtractorInput input, boolean sniffing) - throws IOException, InterruptedException { - int searched = 0; - int validFrameCount = 0; - int candidateSynchronizedHeaderData = 0; - int peekedId3Bytes = 0; - input.resetPeekPosition(); - if (input.getPosition() == 0) { - gaplessInfo = Id3Util.parseId3(input); - peekedId3Bytes = (int) input.getPeekPosition(); - if (!sniffing) { - input.skipFully(peekedId3Bytes); - } - } - while (true) { - if (sniffing && searched == MAX_SNIFF_BYTES) { - return false; - } - if (!sniffing && searched == MAX_SYNC_BYTES) { - throw new ParserException("Searched too many bytes."); - } - if (!input.peekFully(scratch.data, 0, 4, true)) { - return false; - } - scratch.setPosition(0); - int headerData = scratch.readInt(); - int frameSize; - if ((candidateSynchronizedHeaderData != 0 - && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) - || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == -1) { - // The header is invalid or doesn't match the candidate header. Try the next byte offset. - validFrameCount = 0; - candidateSynchronizedHeaderData = 0; - searched++; - if (sniffing) { - input.resetPeekPosition(); - input.advancePeekPosition(peekedId3Bytes + searched); - } else { - input.skipFully(1); - } - } else { - // The header is valid and matches the candidate header. - validFrameCount++; - if (validFrameCount == 1) { - MpegAudioHeader.populateHeader(headerData, synchronizedHeader); - candidateSynchronizedHeaderData = headerData; - } else if (validFrameCount == 4) { - break; - } - input.advancePeekPosition(frameSize - 4); - } - } - // Prepare to read the synchronized frame. - if (sniffing) { - input.skipFully(peekedId3Bytes + searched); - } else { - input.resetPeekPosition(); - } - synchronizedHeaderData = candidateSynchronizedHeaderData; - return true; - } - - /** - * Sets {@link #seeker} to seek using metadata read from {@code input}, which should provide data - * from the start of the first frame in the stream. On returning, the input's position will be set - * to the start of the first frame of audio. - * - * @param input The {@link ExtractorInput} from which to read. - * @throws IOException Thrown if there was an error reading from the stream. Not expected if the - * next two frames were already peeked during synchronization. - * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if - * the next two frames were already peeked during synchronization. - */ - private void setupSeeker(ExtractorInput input) throws IOException, InterruptedException { - // Read the first frame which may contain a Xing or VBRI header with seeking metadata. - ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize); - input.peekFully(frame.data, 0, synchronizedHeader.frameSize); - - long position = input.getPosition(); - long length = input.getLength(); - - // Check if there is a Xing header. - int xingBase = (synchronizedHeader.version & 1) != 0 - ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 - : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 - frame.setPosition(xingBase); - int headerData = frame.readInt(); - if (headerData == XING_HEADER || headerData == INFO_HEADER) { - seeker = XingSeeker.create(synchronizedHeader, frame, position, length); - if (seeker != null && gaplessInfo == null) { - // If there is a Xing header, read gapless playback metadata at a fixed offset. - input.resetPeekPosition(); - input.advancePeekPosition(xingBase + 141); - input.peekFully(scratch.data, 0, 3); - scratch.setPosition(0); - gaplessInfo = GaplessInfo.createFromXingHeaderValue(scratch.readUnsignedInt24()); - } - input.skipFully(synchronizedHeader.frameSize); - } else { - // Check if there is a VBRI header. - frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. - headerData = frame.readInt(); - if (headerData == VBRI_HEADER) { - seeker = VbriSeeker.create(synchronizedHeader, frame, position, length); - input.skipFully(synchronizedHeader.frameSize); - } - } - - if (seeker == null) { - // Repopulate the synchronized header in case we had to skip an invalid seeking header, which - // would give an invalid CBR bitrate. - input.resetPeekPosition(); - input.peekFully(scratch.data, 0, 4); - scratch.setPosition(0); - MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); - seeker = new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate, length); - } - } - - /** - * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be - * used to work out the new sample basis timestamp after seeking and resynchronization. - */ - /* package */ interface Seeker extends SeekMap { - - /** - * Maps a position (byte offset) to a corresponding sample timestamp. - * - * @param position A seek position (byte offset) relative to the start of the stream. - * @return The corresponding timestamp of the next sample to be read, in microseconds. - */ - long getTimeUs(long position); - - /** Returns the duration of the source, in microseconds. */ - long getDurationUs(); - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/AtomParsers.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/AtomParsers.java deleted file mode 100755 index 4a4a96188a5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/AtomParsers.java +++ /dev/null @@ -1,1283 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp4; - -import android.util.Pair; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.GaplessInfo; -import org.telegram.messenger.exoplayer.util.Ac3Util; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.CodecSpecificDataUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. - */ -/* package */ final class AtomParsers { - - /** - * Parses a trak atom (defined in 14496-12). - * - * @param trak Atom to parse. - * @param mvhd Movie header atom, used to get the timescale. - * @param duration The duration in units of the timescale declared in the mvhd atom, or -1 if the - * duration should be parsed from the tkhd atom. - * @param isQuickTime True for QuickTime media. False otherwise. - * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. - */ - public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, - boolean isQuickTime) { - Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); - int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); - if (trackType != Track.TYPE_soun && trackType != Track.TYPE_vide && trackType != Track.TYPE_text - && trackType != Track.TYPE_sbtl && trackType != Track.TYPE_subt) { - return null; - } - - TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); - if (duration == -1) { - duration = tkhdData.duration; - } - long movieTimescale = parseMvhd(mvhd.data); - long durationUs; - if (duration == -1) { - durationUs = C.UNKNOWN_TIME_US; - } else { - durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale); - } - Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) - .getContainerAtomOfType(Atom.TYPE_stbl); - - Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); - StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, - durationUs, tkhdData.rotationDegrees, mdhdData.second, isQuickTime); - Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); - return stsdData.mediaFormat == null ? null - : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, - stsdData.mediaFormat, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength, - edtsData.first, edtsData.second); - } - - /** - * Parses an stbl atom (defined in 14496-12). - * - * @param track Track to which this sample table corresponds. - * @param stblAtom stbl (sample table) atom to parse. - * @return Sample table described by the stbl atom. - * @throws ParserException If the resulting sample sequence does not contain a sync sample. - */ - public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom) - throws ParserException { - SampleSizeBox sampleSizeBox; - Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); - if (stszAtom != null) { - sampleSizeBox = new StszSampleSizeBox(stszAtom); - } else { - Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); - if (stz2Atom == null) { - throw new ParserException("Track has no sample table size information"); - } - sampleSizeBox = new Stz2SampleSizeBox(stz2Atom); - } - - int sampleCount = sampleSizeBox.getSampleCount(); - if (sampleCount == 0) { - return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]); - } - - // Entries are byte offsets of chunks. - boolean chunkOffsetsAreLongs = false; - Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); - if (chunkOffsetsAtom == null) { - chunkOffsetsAreLongs = true; - chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); - } - ParsableByteArray chunkOffsets = chunkOffsetsAtom.data; - // Entries are (chunk number, number of samples per chunk, sample description index). - ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data; - // Entries are (number of samples, timestamp delta between those samples). - ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; - // Entries are the indices of samples that are synchronization samples. - Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); - ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; - // Entries are (number of samples, timestamp offset). - Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); - ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; - - // Prepare to read chunk information. - ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); - - // Prepare to read sample timestamps. - stts.setPosition(Atom.FULL_HEADER_SIZE); - int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1; - int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); - int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); - - // Prepare to read sample timestamp offsets, if ctts is present. - int remainingSamplesAtTimestampOffset = 0; - int remainingTimestampOffsetChanges = 0; - int timestampOffset = 0; - if (ctts != null) { - ctts.setPosition(Atom.FULL_HEADER_SIZE); - remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt(); - } - - int nextSynchronizationSampleIndex = -1; - int remainingSynchronizationSamples = 0; - if (stss != null) { - stss.setPosition(Atom.FULL_HEADER_SIZE); - remainingSynchronizationSamples = stss.readUnsignedIntToInt(); - if (remainingSynchronizationSamples > 0) { - nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; - } else { - // Ignore empty stss boxes, which causes all samples to be treated as sync samples. - stss = null; - } - } - - // True if we can rechunk fixed-sample-size data. Note that we only rechunk raw audio. - boolean isRechunkable = sampleSizeBox.isFixedSampleSize() - && MimeTypes.AUDIO_RAW.equals(track.mediaFormat.mimeType) - && remainingTimestampDeltaChanges == 0 - && remainingTimestampOffsetChanges == 0 - && remainingSynchronizationSamples == 0; - - long[] offsets; - int[] sizes; - int maximumSize = 0; - long[] timestamps; - int[] flags; - - if (!isRechunkable) { - offsets = new long[sampleCount]; - sizes = new int[sampleCount]; - timestamps = new long[sampleCount]; - flags = new int[sampleCount]; - long timestampTimeUnits = 0; - long offset = 0; - int remainingSamplesInChunk = 0; - - for (int i = 0; i < sampleCount; i++) { - // Advance to the next chunk if necessary. - while (remainingSamplesInChunk == 0) { - Assertions.checkState(chunkIterator.moveNext()); - offset = chunkIterator.offset; - remainingSamplesInChunk = chunkIterator.numSamples; - } - - // Add on the timestamp offset if ctts is present. - if (ctts != null) { - while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) { - remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt(); - // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers - // in version 0 ctts boxes, however some streams violate the spec and use signed - // integers instead. It's safe to always parse sample offsets as signed integers here, - // because unsigned integers will still be parsed correctly (unless their top bit is - // set, which is never true in practice because sample offsets are always small). - timestampOffset = ctts.readInt(); - remainingTimestampOffsetChanges--; - } - remainingSamplesAtTimestampOffset--; - } - - offsets[i] = offset; - sizes[i] = sampleSizeBox.readNextSampleSize(); - if (sizes[i] > maximumSize) { - maximumSize = sizes[i]; - } - timestamps[i] = timestampTimeUnits + timestampOffset; - - // All samples are synchronization samples if the stss is not present. - flags[i] = stss == null ? C.SAMPLE_FLAG_SYNC : 0; - if (i == nextSynchronizationSampleIndex) { - flags[i] = C.SAMPLE_FLAG_SYNC; - remainingSynchronizationSamples--; - if (remainingSynchronizationSamples > 0) { - nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; - } - } - - // Add on the duration of this sample. - timestampTimeUnits += timestampDeltaInTimeUnits; - remainingSamplesAtTimestampDelta--; - if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) { - remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); - timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); - remainingTimestampDeltaChanges--; - } - - offset += sizes[i]; - remainingSamplesInChunk--; - } - - Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0); - // Remove trailing ctts entries with 0-valued sample counts. - while (remainingTimestampOffsetChanges > 0) { - Assertions.checkArgument(ctts.readUnsignedIntToInt() == 0); - ctts.readInt(); // Ignore offset. - remainingTimestampOffsetChanges--; - } - - // Check all the expected samples have been seen. - Assertions.checkArgument(remainingSynchronizationSamples == 0); - Assertions.checkArgument(remainingSamplesAtTimestampDelta == 0); - Assertions.checkArgument(remainingSamplesInChunk == 0); - Assertions.checkArgument(remainingTimestampDeltaChanges == 0); - } else { - long[] chunkOffsetsBytes = new long[chunkIterator.length]; - int[] chunkSampleCounts = new int[chunkIterator.length]; - while (chunkIterator.moveNext()) { - chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset; - chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples; - } - int fixedSampleSize = sampleSizeBox.readNextSampleSize(); - FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk( - fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits); - offsets = rechunkedResults.offsets; - sizes = rechunkedResults.sizes; - maximumSize = rechunkedResults.maximumSize; - timestamps = rechunkedResults.timestamps; - flags = rechunkedResults.flags; - } - - if (track.editListDurations == null) { - Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); - } - - // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that truncate audio and - // require prerolling from a sync sample after reordering are not supported. This - // implementation handles simple discarding/delaying of samples. The extractor may place - // further restrictions on what edited streams are playable. - - if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) { - // The current version of the spec leaves handling of an edit with zero segment_duration in - // unfragmented files open to interpretation. We handle this as a special case and include all - // samples in the edit. - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0], - C.MICROS_PER_SECOND, track.timescale); - } - return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); - } - - // Count the number of samples after applying edits. - int editedSampleCount = 0; - int nextSampleIndex = 0; - boolean copyMetadata = false; - for (int i = 0; i < track.editListDurations.length; i++) { - long mediaTime = track.editListMediaTimes[i]; - if (mediaTime != -1) { - long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, - track.movieTimescale); - int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); - int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, true, false); - editedSampleCount += endIndex - startIndex; - copyMetadata |= nextSampleIndex != startIndex; - nextSampleIndex = endIndex; - } - } - copyMetadata |= editedSampleCount != sampleCount; - - // Calculate edited sample timestamps and update the corresponding metadata arrays. - long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets; - int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes; - int editedMaximumSize = copyMetadata ? 0 : maximumSize; - int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags; - long[] editedTimestamps = new long[editedSampleCount]; - long pts = 0; - int sampleIndex = 0; - for (int i = 0; i < track.editListDurations.length; i++) { - long mediaTime = track.editListMediaTimes[i]; - long duration = track.editListDurations[i]; - if (mediaTime != -1) { - long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, - track.movieTimescale); - int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); - int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, true, false); - if (copyMetadata) { - int count = endIndex - startIndex; - System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count); - System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count); - System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count); - } - for (int j = startIndex; j < endIndex; j++) { - long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); - long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime, - C.MICROS_PER_SECOND, track.timescale); - editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs; - if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) { - editedMaximumSize = sizes[j]; - } - sampleIndex++; - } - } - pts += duration; - } - - boolean hasSyncSample = false; - for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) { - hasSyncSample |= (editedFlags[i] & C.SAMPLE_FLAG_SYNC) != 0; - } - if (!hasSyncSample) { - throw new ParserException("The edited sample sequence does not contain a sync sample."); - } - - return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, - editedFlags); - } - - /** - * Parses a udta atom. - * - * @param udtaAtom The udta (user data) atom to parse. - * @param isQuickTime True for QuickTime media. False otherwise. - * @return Gapless playback information stored in the user data, or {@code null} if not present. - */ - public static GaplessInfo parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { - if (isQuickTime) { - // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and - // parse one. - return null; - } - ParsableByteArray udtaData = udtaAtom.data; - udtaData.setPosition(Atom.HEADER_SIZE); - while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { - int atomSize = udtaData.readInt(); - int atomType = udtaData.readInt(); - if (atomType == Atom.TYPE_meta) { - udtaData.setPosition(udtaData.getPosition() - Atom.HEADER_SIZE); - udtaData.setLimit(udtaData.getPosition() + atomSize); - return parseMetaAtom(udtaData); - } else { - udtaData.skipBytes(atomSize - Atom.HEADER_SIZE); - } - } - return null; - } - - private static GaplessInfo parseMetaAtom(ParsableByteArray data) { - data.skipBytes(Atom.FULL_HEADER_SIZE); - ParsableByteArray ilst = new ParsableByteArray(); - while (data.bytesLeft() >= Atom.HEADER_SIZE) { - int payloadSize = data.readInt() - Atom.HEADER_SIZE; - int atomType = data.readInt(); - if (atomType == Atom.TYPE_ilst) { - ilst.reset(data.data, data.getPosition() + payloadSize); - ilst.setPosition(data.getPosition()); - GaplessInfo gaplessInfo = parseIlst(ilst); - if (gaplessInfo != null) { - return gaplessInfo; - } - } - data.skipBytes(payloadSize); - } - return null; - } - - private static GaplessInfo parseIlst(ParsableByteArray ilst) { - while (ilst.bytesLeft() > 0) { - int position = ilst.getPosition(); - int endPosition = position + ilst.readInt(); - int type = ilst.readInt(); - if (type == Atom.TYPE_DASHES) { - String lastCommentMean = null; - String lastCommentName = null; - String lastCommentData = null; - while (ilst.getPosition() < endPosition) { - int length = ilst.readInt() - Atom.FULL_HEADER_SIZE; - int key = ilst.readInt(); - ilst.skipBytes(4); - if (key == Atom.TYPE_mean) { - lastCommentMean = ilst.readString(length); - } else if (key == Atom.TYPE_name) { - lastCommentName = ilst.readString(length); - } else if (key == Atom.TYPE_data) { - ilst.skipBytes(4); - lastCommentData = ilst.readString(length - 4); - } else { - ilst.skipBytes(length); - } - } - if (lastCommentName != null && lastCommentData != null - && "com.apple.iTunes".equals(lastCommentMean)) { - return GaplessInfo.createFromComment(lastCommentName, lastCommentData); - } - } else { - ilst.setPosition(endPosition); - } - } - return null; - } - - /** - * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. - * - * @param mvhd Contents of the mvhd atom to be parsed. - * @return Timescale for the movie. - */ - private static long parseMvhd(ParsableByteArray mvhd) { - mvhd.setPosition(Atom.HEADER_SIZE); - - int fullAtom = mvhd.readInt(); - int version = Atom.parseFullAtomVersion(fullAtom); - - mvhd.skipBytes(version == 0 ? 8 : 16); - - return mvhd.readUnsignedInt(); - } - - /** - * Parses a tkhd atom (defined in 14496-12). - * - * @return An object containing the parsed data. - */ - private static TkhdData parseTkhd(ParsableByteArray tkhd) { - tkhd.setPosition(Atom.HEADER_SIZE); - int fullAtom = tkhd.readInt(); - int version = Atom.parseFullAtomVersion(fullAtom); - - tkhd.skipBytes(version == 0 ? 8 : 16); - int trackId = tkhd.readInt(); - - tkhd.skipBytes(4); - boolean durationUnknown = true; - int durationPosition = tkhd.getPosition(); - int durationByteCount = version == 0 ? 4 : 8; - for (int i = 0; i < durationByteCount; i++) { - if (tkhd.data[durationPosition + i] != -1) { - durationUnknown = false; - break; - } - } - long duration; - if (durationUnknown) { - tkhd.skipBytes(durationByteCount); - duration = -1; - } else { - duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); - if (duration == 0) { - // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media - // samples are in fragments). Treat as unknown. - duration = -1; - } - } - - tkhd.skipBytes(16); - int a00 = tkhd.readInt(); - int a01 = tkhd.readInt(); - tkhd.skipBytes(4); - int a10 = tkhd.readInt(); - int a11 = tkhd.readInt(); - - int rotationDegrees; - int fixedOne = 65536; - if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) { - rotationDegrees = 90; - } else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) { - rotationDegrees = 270; - } else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) { - rotationDegrees = 180; - } else { - // Only 0, 90, 180 and 270 are supported. Treat anything else as 0. - rotationDegrees = 0; - } - - return new TkhdData(trackId, duration, rotationDegrees); - } - - /** - * Parses an hdlr atom. - * - * @param hdlr The hdlr atom to parse. - * @return The track type. - */ - private static int parseHdlr(ParsableByteArray hdlr) { - hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4); - return hdlr.readInt(); - } - - /** - * Parses an mdhd atom (defined in 14496-12). - * - * @param mdhd The mdhd atom to parse. - * @return A pair consisting of the media timescale defined as the number of time units that pass - * in one second, and the language code. - */ - private static Pair parseMdhd(ParsableByteArray mdhd) { - mdhd.setPosition(Atom.HEADER_SIZE); - int fullAtom = mdhd.readInt(); - int version = Atom.parseFullAtomVersion(fullAtom); - mdhd.skipBytes(version == 0 ? 8 : 16); - long timescale = mdhd.readUnsignedInt(); - mdhd.skipBytes(version == 0 ? 4 : 8); - int languageCode = mdhd.readUnsignedShort(); - String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) - + (char) (((languageCode >> 5) & 0x1F) + 0x60) - + (char) (((languageCode) & 0x1F) + 0x60); - return Pair.create(timescale, language); - } - - /** - * Parses a stsd atom (defined in 14496-12). - * - * @param stsd The stsd atom to parse. - * @param trackId The track's identifier in its container. - * @param durationUs The duration of the track in microseconds. - * @param rotationDegrees The rotation of the track in degrees. - * @param language The language of the track. - * @param isQuickTime True for QuickTime media. False otherwise. - * @return An object containing the parsed data. - */ - private static StsdData parseStsd(ParsableByteArray stsd, int trackId, long durationUs, - int rotationDegrees, String language, boolean isQuickTime) { - stsd.setPosition(Atom.FULL_HEADER_SIZE); - int numberOfEntries = stsd.readInt(); - StsdData out = new StsdData(numberOfEntries); - for (int i = 0; i < numberOfEntries; i++) { - int childStartPosition = stsd.getPosition(); - int childAtomSize = stsd.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); - int childAtomType = stsd.readInt(); - if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 - || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v - || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 - || childAtomType == Atom.TYPE_s263 || childAtomType == Atom.TYPE_vp08 - || childAtomType == Atom.TYPE_vp09) { - parseVideoSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, - durationUs, rotationDegrees, out, i); - } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca - || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 - || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse - || childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl - || childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb - || childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt) { - parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, - durationUs, language, isQuickTime, out, i); - } else if (childAtomType == Atom.TYPE_TTML) { - out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, durationUs, language); - } else if (childAtomType == Atom.TYPE_tx3g) { - out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TX3G, MediaFormat.NO_VALUE, durationUs, language); - } else if (childAtomType == Atom.TYPE_wvtt) { - out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_MP4VTT, MediaFormat.NO_VALUE, durationUs, language); - } else if (childAtomType == Atom.TYPE_stpp) { - out.mediaFormat = MediaFormat.createTextFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_TTML, MediaFormat.NO_VALUE, durationUs, language, - 0 /* subsample timing is absolute */); - } - stsd.setPosition(childStartPosition + childAtomSize); - } - return out; - } - - private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, - int size, int trackId, long durationUs, int rotationDegrees, StsdData out, int entryIndex) { - parent.setPosition(position + Atom.HEADER_SIZE); - - parent.skipBytes(24); - int width = parent.readUnsignedShort(); - int height = parent.readUnsignedShort(); - boolean pixelWidthHeightRatioFromPasp = false; - float pixelWidthHeightRatio = 1; - parent.skipBytes(50); - - int childPosition = parent.getPosition(); - if (atomType == Atom.TYPE_encv) { - parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); - parent.setPosition(childPosition); - } - - List initializationData = null; - String mimeType = null; - while (childPosition - position < size) { - parent.setPosition(childPosition); - int childStartPosition = parent.getPosition(); - int childAtomSize = parent.readInt(); - if (childAtomSize == 0 && parent.getPosition() - position == size) { - // Handle optional terminating four zero bytes in MOV files. - break; - } - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); - int childAtomType = parent.readInt(); - if (childAtomType == Atom.TYPE_avcC) { - Assertions.checkState(mimeType == null); - mimeType = MimeTypes.VIDEO_H264; - AvcCData avcCData = parseAvcCFromParent(parent, childStartPosition); - initializationData = avcCData.initializationData; - out.nalUnitLengthFieldLength = avcCData.nalUnitLengthFieldLength; - if (!pixelWidthHeightRatioFromPasp) { - pixelWidthHeightRatio = avcCData.pixelWidthAspectRatio; - } - } else if (childAtomType == Atom.TYPE_hvcC) { - Assertions.checkState(mimeType == null); - mimeType = MimeTypes.VIDEO_H265; - Pair, Integer> hvcCData = parseHvcCFromParent(parent, childStartPosition); - initializationData = hvcCData.first; - out.nalUnitLengthFieldLength = hvcCData.second; - } else if (childAtomType == Atom.TYPE_d263) { - Assertions.checkState(mimeType == null); - mimeType = MimeTypes.VIDEO_H263; - } else if (childAtomType == Atom.TYPE_esds) { - Assertions.checkState(mimeType == null); - Pair mimeTypeAndInitializationData = - parseEsdsFromParent(parent, childStartPosition); - mimeType = mimeTypeAndInitializationData.first; - initializationData = Collections.singletonList(mimeTypeAndInitializationData.second); - } else if (childAtomType == Atom.TYPE_pasp) { - pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); - pixelWidthHeightRatioFromPasp = true; - } else if (childAtomType == Atom.TYPE_vpcC) { - Assertions.checkState(mimeType == null); - mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9; - } - childPosition += childAtomSize; - } - - // If the media type was not recognized, ignore the track. - if (mimeType == null) { - return; - } - - out.mediaFormat = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, width, height, initializationData, - rotationDegrees, pixelWidthHeightRatio); - } - - private static AvcCData parseAvcCFromParent(ParsableByteArray parent, int position) { - parent.setPosition(position + Atom.HEADER_SIZE + 4); - // Start of the AVCDecoderConfigurationRecord (defined in 14496-15) - int nalUnitLengthFieldLength = (parent.readUnsignedByte() & 0x3) + 1; - if (nalUnitLengthFieldLength == 3) { - throw new IllegalStateException(); - } - List initializationData = new ArrayList<>(); - float pixelWidthAspectRatio = 1; - int numSequenceParameterSets = parent.readUnsignedByte() & 0x1F; - for (int j = 0; j < numSequenceParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); - } - int numPictureParameterSets = parent.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(parent)); - } - - if (numSequenceParameterSets > 0) { - // Parse the first sequence parameter set to obtain pixelWidthAspectRatio. - ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0)); - // Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte). - spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1)); - pixelWidthAspectRatio = NalUnitUtil.parseSpsNalUnit(spsDataBitArray).pixelWidthAspectRatio; - } - - return new AvcCData(initializationData, nalUnitLengthFieldLength, pixelWidthAspectRatio); - } - - private static Pair, Integer> parseHvcCFromParent(ParsableByteArray parent, - int position) { - // Skip to the NAL unit length size field. - parent.setPosition(position + Atom.HEADER_SIZE + 21); - int lengthSizeMinusOne = parent.readUnsignedByte() & 0x03; - - // Calculate the combined size of all VPS/SPS/PPS bitstreams. - int numberOfArrays = parent.readUnsignedByte(); - int csdLength = 0; - int csdStartPosition = parent.getPosition(); - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - csdLength += 4 + nalUnitLength; // Start code and NAL unit. - parent.skipBytes(nalUnitLength); - } - } - - // Concatenate the codec-specific data into a single buffer. - parent.setPosition(csdStartPosition); - byte[] buffer = new byte[csdLength]; - int bufferPosition = 0; - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, - NalUnitUtil.NAL_START_CODE.length); - bufferPosition += NalUnitUtil.NAL_START_CODE.length; - System.arraycopy(parent.data, parent.getPosition(), buffer, bufferPosition, nalUnitLength); - bufferPosition += nalUnitLength; - parent.skipBytes(nalUnitLength); - } - } - - List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); - return Pair.create(initializationData, lengthSizeMinusOne + 1); - } - - /** - * Parses the edts atom (defined in 14496-12 subsection 8.6.5). - * - * @param edtsAtom edts (edit box) atom to parse. - * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are - * not present. - */ - private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { - Atom.LeafAtom elst; - if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { - return Pair.create(null, null); - } - ParsableByteArray elstData = elst.data; - elstData.setPosition(Atom.HEADER_SIZE); - int fullAtom = elstData.readInt(); - int version = Atom.parseFullAtomVersion(fullAtom); - int entryCount = elstData.readUnsignedIntToInt(); - long[] editListDurations = new long[entryCount]; - long[] editListMediaTimes = new long[entryCount]; - for (int i = 0; i < entryCount; i++) { - editListDurations[i] = - version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt(); - editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt(); - int mediaRateInteger = elstData.readShort(); - if (mediaRateInteger != 1) { - // The extractor does not handle dwell edits (mediaRateInteger == 0). - throw new IllegalArgumentException("Unsupported media rate."); - } - elstData.skipBytes(2); - } - return Pair.create(editListDurations, editListMediaTimes); - } - - private static float parsePaspFromParent(ParsableByteArray parent, int position) { - parent.setPosition(position + Atom.HEADER_SIZE); - int hSpacing = parent.readUnsignedIntToInt(); - int vSpacing = parent.readUnsignedIntToInt(); - return (float) hSpacing / vSpacing; - } - - private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, - int size, int trackId, long durationUs, String language, boolean isQuickTime, StsdData out, - int entryIndex) { - parent.setPosition(position + Atom.HEADER_SIZE); - - int quickTimeSoundDescriptionVersion = 0; - if (isQuickTime) { - parent.skipBytes(8); - quickTimeSoundDescriptionVersion = parent.readUnsignedShort(); - parent.skipBytes(6); - } else { - parent.skipBytes(16); - } - - int channelCount; - int sampleRate; - - if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { - channelCount = parent.readUnsignedShort(); - parent.skipBytes(6); // sampleSize, compressionId, packetSize. - sampleRate = parent.readUnsignedFixedPoint1616(); - - if (quickTimeSoundDescriptionVersion == 1) { - parent.skipBytes(16); - } - } else if (quickTimeSoundDescriptionVersion == 2) { - parent.skipBytes(16); // always[3,16,Minus2,0,65536], sizeOfStructOnly - - sampleRate = (int) Math.round(parent.readDouble()); - channelCount = parent.readUnsignedIntToInt(); - - // Skip always7F000000, sampleSize, formatSpecificFlags, constBytesPerAudioPacket, - // constLPCMFramesPerAudioPacket. - parent.skipBytes(20); - } else { - // Unsupported version. - return; - } - - int childPosition = parent.getPosition(); - if (atomType == Atom.TYPE_enca) { - atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); - parent.setPosition(childPosition); - } - - // If the atom type determines a MIME type, set it immediately. - String mimeType = null; - if (atomType == Atom.TYPE_ac_3) { - mimeType = MimeTypes.AUDIO_AC3; - } else if (atomType == Atom.TYPE_ec_3) { - mimeType = MimeTypes.AUDIO_E_AC3; - } else if (atomType == Atom.TYPE_dtsc) { - mimeType = MimeTypes.AUDIO_DTS; - } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) { - mimeType = MimeTypes.AUDIO_DTS_HD; - } else if (atomType == Atom.TYPE_dtse) { - mimeType = MimeTypes.AUDIO_DTS_EXPRESS; - } else if (atomType == Atom.TYPE_samr) { - mimeType = MimeTypes.AUDIO_AMR_NB; - } else if (atomType == Atom.TYPE_sawb) { - mimeType = MimeTypes.AUDIO_AMR_WB; - } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) { - mimeType = MimeTypes.AUDIO_RAW; - } - - byte[] initializationData = null; - while (childPosition - position < size) { - parent.setPosition(childPosition); - int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); - int childAtomType = parent.readInt(); - if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) { - int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition - : findEsdsPosition(parent, childPosition, childAtomSize); - if (esdsAtomPosition != -1) { - Pair mimeTypeAndInitializationData = - parseEsdsFromParent(parent, esdsAtomPosition); - mimeType = mimeTypeAndInitializationData.first; - initializationData = mimeTypeAndInitializationData.second; - if (MimeTypes.AUDIO_AAC.equals(mimeType)) { - // TODO: Do we really need to do this? See [Internal: b/10903778] - // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. - Pair audioSpecificConfig = - CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); - sampleRate = audioSpecificConfig.first; - channelCount = audioSpecificConfig.second; - } - } - } else if (childAtomType == Atom.TYPE_dac3) { - parent.setPosition(Atom.HEADER_SIZE + childPosition); - out.mediaFormat = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), - durationUs, language); - } else if (childAtomType == Atom.TYPE_dec3) { - parent.setPosition(Atom.HEADER_SIZE + childPosition); - out.mediaFormat = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), - durationUs, language); - } else if (childAtomType == Atom.TYPE_ddts) { - out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, - language); - } - childPosition += childAtomSize; - } - - if (out.mediaFormat == null && mimeType != null) { - // TODO: Determine the correct PCM encoding. - int pcmEncoding = MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT - : MediaFormat.NO_VALUE; - out.mediaFormat = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, - initializationData == null ? null : Collections.singletonList(initializationData), - language, pcmEncoding); - } - } - - /** - * Returns the position of the esds box within a parent, or -1 if no esds box is found - */ - private static int findEsdsPosition(ParsableByteArray parent, int position, int size) { - int childAtomPosition = parent.getPosition(); - while (childAtomPosition - position < size) { - parent.setPosition(childAtomPosition); - int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); - int childType = parent.readInt(); - if (childType == Atom.TYPE_esds) { - return childAtomPosition; - } - childAtomPosition += childAtomSize; - } - return -1; - } - - /** - * Returns codec-specific initialization data contained in an esds box. - */ - private static Pair parseEsdsFromParent(ParsableByteArray parent, int position) { - parent.setPosition(position + Atom.HEADER_SIZE + 4); - // Start of the ES_Descriptor (defined in 14496-1) - parent.skipBytes(1); // ES_Descriptor tag - parseExpandableClassSize(parent); - parent.skipBytes(2); // ES_ID - - int flags = parent.readUnsignedByte(); - if ((flags & 0x80 /* streamDependenceFlag */) != 0) { - parent.skipBytes(2); - } - if ((flags & 0x40 /* URL_Flag */) != 0) { - parent.skipBytes(parent.readUnsignedShort()); - } - if ((flags & 0x20 /* OCRstreamFlag */) != 0) { - parent.skipBytes(2); - } - - // Start of the DecoderConfigDescriptor (defined in 14496-1) - parent.skipBytes(1); // DecoderConfigDescriptor tag - parseExpandableClassSize(parent); - - // Set the MIME type based on the object type indication (14496-1 table 5). - int objectTypeIndication = parent.readUnsignedByte(); - String mimeType; - switch (objectTypeIndication) { - case 0x6B: - mimeType = MimeTypes.AUDIO_MPEG; - return Pair.create(mimeType, null); - case 0x20: - mimeType = MimeTypes.VIDEO_MP4V; - break; - case 0x21: - mimeType = MimeTypes.VIDEO_H264; - break; - case 0x23: - mimeType = MimeTypes.VIDEO_H265; - break; - case 0x40: - case 0x66: - case 0x67: - case 0x68: - mimeType = MimeTypes.AUDIO_AAC; - break; - case 0xA5: - mimeType = MimeTypes.AUDIO_AC3; - break; - case 0xA6: - mimeType = MimeTypes.AUDIO_E_AC3; - break; - case 0xA9: - case 0xAC: - mimeType = MimeTypes.AUDIO_DTS; - return Pair.create(mimeType, null); - case 0xAA: - case 0xAB: - mimeType = MimeTypes.AUDIO_DTS_HD; - return Pair.create(mimeType, null); - default: - mimeType = null; - break; - } - - parent.skipBytes(12); - - // Start of the AudioSpecificConfig. - parent.skipBytes(1); // AudioSpecificConfig tag - int initializationDataSize = parseExpandableClassSize(parent); - byte[] initializationData = new byte[initializationDataSize]; - parent.readBytes(initializationData, 0, initializationDataSize); - return Pair.create(mimeType, initializationData); - } - - /** - * Parses encryption data from an audio/video sample entry, populating {@code out} and returning - * the unencrypted atom type, or 0 if no sinf atom was present. - */ - private static int parseSampleEntryEncryptionData(ParsableByteArray parent, int position, - int size, StsdData out, int entryIndex) { - int childPosition = parent.getPosition(); - while (childPosition - position < size) { - parent.setPosition(childPosition); - int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); - int childAtomType = parent.readInt(); - if (childAtomType == Atom.TYPE_sinf) { - Pair result = parseSinfFromParent(parent, childPosition, - childAtomSize); - Integer dataFormat = result.first; - Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); - out.trackEncryptionBoxes[entryIndex] = result.second; - return dataFormat; - } - childPosition += childAtomSize; - } - // This enca/encv box does not have a data format so return an invalid atom type. - return 0; - } - - private static Pair parseSinfFromParent(ParsableByteArray parent, - int position, int size) { - int childPosition = position + Atom.HEADER_SIZE; - - TrackEncryptionBox trackEncryptionBox = null; - Integer dataFormat = null; - while (childPosition - position < size) { - parent.setPosition(childPosition); - int childAtomSize = parent.readInt(); - int childAtomType = parent.readInt(); - if (childAtomType == Atom.TYPE_frma) { - dataFormat = parent.readInt(); - } else if (childAtomType == Atom.TYPE_schm) { - parent.skipBytes(4); - parent.readInt(); // schemeType. Expect cenc - parent.readInt(); // schemeVersion. Expect 0x00010000 - } else if (childAtomType == Atom.TYPE_schi) { - trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); - } - childPosition += childAtomSize; - } - - return Pair.create(dataFormat, trackEncryptionBox); - } - - private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, - int size) { - int childPosition = position + Atom.HEADER_SIZE; - while (childPosition - position < size) { - parent.setPosition(childPosition); - int childAtomSize = parent.readInt(); - int childAtomType = parent.readInt(); - if (childAtomType == Atom.TYPE_tenc) { - parent.skipBytes(6); - boolean defaultIsEncrypted = parent.readUnsignedByte() == 1; - int defaultInitVectorSize = parent.readUnsignedByte(); - byte[] defaultKeyId = new byte[16]; - parent.readBytes(defaultKeyId, 0, defaultKeyId.length); - return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); - } - childPosition += childAtomSize; - } - return null; - } - - /** - * Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3. - */ - private static int parseExpandableClassSize(ParsableByteArray data) { - int currentByte = data.readUnsignedByte(); - int size = currentByte & 0x7F; - while ((currentByte & 0x80) == 0x80) { - currentByte = data.readUnsignedByte(); - size = (size << 7) | (currentByte & 0x7F); - } - return size; - } - - private AtomParsers() { - // Prevent instantiation. - } - - private static final class ChunkIterator { - - public final int length; - - public int index; - public int numSamples; - public long offset; - - private final boolean chunkOffsetsAreLongs; - private final ParsableByteArray chunkOffsets; - private final ParsableByteArray stsc; - - private int nextSamplesPerChunkChangeIndex; - private int remainingSamplesPerChunkChanges; - - public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, - boolean chunkOffsetsAreLongs) { - this.stsc = stsc; - this.chunkOffsets = chunkOffsets; - this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; - chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); - length = chunkOffsets.readUnsignedIntToInt(); - stsc.setPosition(Atom.FULL_HEADER_SIZE); - remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); - Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); - index = -1; - } - - public boolean moveNext() { - if (++index == length) { - return false; - } - offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong() - : chunkOffsets.readUnsignedInt(); - if (index == nextSamplesPerChunkChangeIndex) { - numSamples = stsc.readUnsignedIntToInt(); - stsc.skipBytes(4); // Skip sample_description_index - nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0 - ? (stsc.readUnsignedIntToInt() - 1) : -1; - } - return true; - } - - } - - /** - * Holds data parsed from a tkhd atom. - */ - private static final class TkhdData { - - private final int id; - private final long duration; - private final int rotationDegrees; - - public TkhdData(int id, long duration, int rotationDegrees) { - this.id = id; - this.duration = duration; - this.rotationDegrees = rotationDegrees; - } - - } - - /** - * Holds data parsed from an stsd atom and its children. - */ - private static final class StsdData { - - public final TrackEncryptionBox[] trackEncryptionBoxes; - - public MediaFormat mediaFormat; - public int nalUnitLengthFieldLength; - - public StsdData(int numberOfEntries) { - trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries]; - nalUnitLengthFieldLength = -1; - } - - } - - /** - * Holds data parsed from an AvcC atom. - */ - private static final class AvcCData { - - public final List initializationData; - public final int nalUnitLengthFieldLength; - public final float pixelWidthAspectRatio; - - public AvcCData(List initializationData, int nalUnitLengthFieldLength, - float pixelWidthAspectRatio) { - this.initializationData = initializationData; - this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; - this.pixelWidthAspectRatio = pixelWidthAspectRatio; - } - - } - - /** - * A box containing sample sizes (e.g. stsz, stz2). - */ - private interface SampleSizeBox { - - /** - * Returns the number of samples. - */ - int getSampleCount(); - - /** - * Returns the size for the next sample. - */ - int readNextSampleSize(); - - /** - * Returns whether samples have a fixed size. - */ - boolean isFixedSampleSize(); - - } - - /** - * An stsz sample size box. - */ - /* package */ static final class StszSampleSizeBox implements SampleSizeBox { - - private final int fixedSampleSize; - private final int sampleCount; - private final ParsableByteArray data; - - public StszSampleSizeBox(Atom.LeafAtom stszAtom) { - data = stszAtom.data; - data.setPosition(Atom.FULL_HEADER_SIZE); - fixedSampleSize = data.readUnsignedIntToInt(); - sampleCount = data.readUnsignedIntToInt(); - } - - @Override - public int getSampleCount() { - return sampleCount; - } - - @Override - public int readNextSampleSize() { - return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize; - } - - @Override - public boolean isFixedSampleSize() { - return fixedSampleSize != 0; - } - - } - - /** - * An stz2 sample size box. - */ - /* package */ static final class Stz2SampleSizeBox implements SampleSizeBox { - - private final ParsableByteArray data; - private final int sampleCount; - private final int fieldSize; // Can be 4, 8, or 16. - - // Used only if fieldSize == 4. - private int sampleIndex; - private int currentByte; - - public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) { - data = stz2Atom.data; - data.setPosition(Atom.FULL_HEADER_SIZE); - fieldSize = data.readUnsignedIntToInt() & 0x000000FF; - sampleCount = data.readUnsignedIntToInt(); - } - - @Override - public int getSampleCount() { - return sampleCount; - } - - @Override - public int readNextSampleSize() { - if (fieldSize == 8) { - return data.readUnsignedByte(); - } else if (fieldSize == 16) { - return data.readUnsignedShort(); - } else { - // fieldSize == 4. - if ((sampleIndex++ % 2) == 0) { - // Read the next byte into our cached byte when we are reading the upper bits. - currentByte = data.readUnsignedByte(); - // Read the upper bits from the byte and shift them to the lower 4 bits. - return (currentByte & 0xF0) >> 4; - } else { - // Mask out the upper 4 bits of the last byte we read. - return currentByte & 0x0F; - } - } - } - - @Override - public boolean isFixedSampleSize() { - return false; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Sniffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Sniffer.java deleted file mode 100755 index 4016d8aa6dc..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Sniffer.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp4; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * Provides methods that peek data from an {@link ExtractorInput} and return whether the input - * appears to be in MP4 format. - */ -/* package */ final class Sniffer { - - /** - * The maximum number of bytes to peek when sniffing. - */ - private static final int SEARCH_LENGTH = 4 * 1024; - - private static final int[] COMPATIBLE_BRANDS = new int[] { - Util.getIntegerCodeForString("isom"), - Util.getIntegerCodeForString("iso2"), - Util.getIntegerCodeForString("iso3"), - Util.getIntegerCodeForString("iso4"), - Util.getIntegerCodeForString("iso5"), - Util.getIntegerCodeForString("iso6"), - Util.getIntegerCodeForString("avc1"), - Util.getIntegerCodeForString("hvc1"), - Util.getIntegerCodeForString("hev1"), - Util.getIntegerCodeForString("mp41"), - Util.getIntegerCodeForString("mp42"), - Util.getIntegerCodeForString("3g2a"), - Util.getIntegerCodeForString("3g2b"), - Util.getIntegerCodeForString("3gr6"), - Util.getIntegerCodeForString("3gs6"), - Util.getIntegerCodeForString("3ge6"), - Util.getIntegerCodeForString("3gg6"), - Util.getIntegerCodeForString("M4V "), - Util.getIntegerCodeForString("M4A "), - Util.getIntegerCodeForString("f4v "), - Util.getIntegerCodeForString("kddi"), - Util.getIntegerCodeForString("M4VP"), - Util.getIntegerCodeForString("qt "), // Apple QuickTime - Util.getIntegerCodeForString("MSNV"), // Sony PSP - }; - - /** - * Returns whether data peeked from the current position in {@code input} is consistent with the - * input being a fragmented MP4 file. - * - * @param input The extractor input from which to peek data. The peek position will be modified. - * @return True if the input appears to be in the fragmented MP4 format. False otherwise. - * @throws IOException If an error occurs reading from the input. - * @throws InterruptedException If the thread has been interrupted. - */ - public static boolean sniffFragmented(ExtractorInput input) - throws IOException, InterruptedException { - return sniffInternal(input, true); - } - - /** - * Returns whether data peeked from the current position in {@code input} is consistent with the - * input being an unfragmented MP4 file. - * - * @param input The extractor input from which to peek data. The peek position will be modified. - * @return True if the input appears to be in the unfragmented MP4 format. False otherwise. - * @throws IOException If an error occurs reading from the input. - * @throws InterruptedException If the thread has been interrupted. - */ - public static boolean sniffUnfragmented(ExtractorInput input) - throws IOException, InterruptedException { - return sniffInternal(input, false); - } - - private static boolean sniffInternal(ExtractorInput input, boolean fragmented) - throws IOException, InterruptedException { - long inputLength = input.getLength(); - int bytesToSearch = (int) (inputLength == C.LENGTH_UNBOUNDED || inputLength > SEARCH_LENGTH - ? SEARCH_LENGTH : inputLength); - - ParsableByteArray buffer = new ParsableByteArray(64); - int bytesSearched = 0; - boolean foundGoodFileType = false; - boolean isFragmented = false; - while (bytesSearched < bytesToSearch) { - // Read an atom header. - int headerSize = Atom.HEADER_SIZE; - input.peekFully(buffer.data, 0, headerSize); - buffer.setPosition(0); - long atomSize = buffer.readUnsignedInt(); - int atomType = buffer.readInt(); - if (atomSize == Atom.LONG_SIZE_PREFIX) { - headerSize = Atom.LONG_HEADER_SIZE; - input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); - atomSize = buffer.readUnsignedLongToLong(); - } - - if (atomSize < headerSize) { - // The file is invalid because the atom size is too small for its header. - return false; - } - bytesSearched += headerSize; - - if (atomType == Atom.TYPE_moov) { - // Check for an mvex atom inside the moov atom to identify whether the file is fragmented. - continue; - } - - if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mvex) { - // The movie is fragmented. Stop searching as we must have read any ftyp atom already. - isFragmented = true; - break; - } - - if (bytesSearched + atomSize - headerSize >= bytesToSearch) { - // Stop searching as peeking this atom would exceed the search limit. - break; - } - - int atomDataSize = (int) (atomSize - headerSize); - bytesSearched += atomDataSize; - if (atomType == Atom.TYPE_ftyp) { - // Parse the atom and check the file type/brand is compatible with the extractors. - if (atomDataSize < 8) { - return false; - } - if (buffer.capacity() < atomDataSize) { - buffer.reset(new byte[atomDataSize], atomDataSize); - } - input.peekFully(buffer.data, 0, atomDataSize); - int brandsCount = atomDataSize / 4; - for (int i = 0; i < brandsCount; i++) { - if (i == 1) { - // This index refers to the minorVersion, not a brand, so skip it. - buffer.skipBytes(4); - } else if (isCompatibleBrand(buffer.readInt())) { - foundGoodFileType = true; - break; - } - } - if (!foundGoodFileType) { - // The types were not compatible and there is only one ftyp atom, so reject the file. - return false; - } - } else if (atomDataSize != 0) { - // Skip the atom. - input.advancePeekPosition(atomDataSize); - } - } - return foundGoodFileType && fragmented == isFragmented; - } - - /** - * Returns whether {@code brand} is an ftyp atom brand that is compatible with the MP4 extractors. - */ - private static boolean isCompatibleBrand(int brand) { - // Accept all brands starting '3gp'. - if (brand >>> 8 == Util.getIntegerCodeForString("3gp")) { - return true; - } - for (int compatibleBrand : COMPATIBLE_BRANDS) { - if (compatibleBrand == brand) { - return true; - } - } - return false; - } - - private Sniffer() { - // Prevent instantiation. - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Track.java deleted file mode 100755 index 2fce11a7a7a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Track.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp4; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.util.Util; - -/** - * Encapsulates information describing an MP4 track. - */ -public final class Track { - - public static final int TYPE_vide = Util.getIntegerCodeForString("vide"); - public static final int TYPE_soun = Util.getIntegerCodeForString("soun"); - public static final int TYPE_text = Util.getIntegerCodeForString("text"); - public static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl"); - public static final int TYPE_subt = Util.getIntegerCodeForString("subt"); - - /** - * The track identifier. - */ - public final int id; - - /** - * One of {@link #TYPE_vide}, {@link #TYPE_soun}, {@link #TYPE_text} and {@link #TYPE_sbtl} and - * {@link #TYPE_subt}. - */ - public final int type; - - /** - * The track timescale, defined as the number of time units that pass in one second. - */ - public final long timescale; - - /** - * The movie timescale. - */ - public final long movieTimescale; - - /** - * The duration of the track in microseconds, or {@link C#UNKNOWN_TIME_US} if unknown. - */ - public final long durationUs; - - /** - * The media format. - */ - public final MediaFormat mediaFormat; - - /** - * Track encryption boxes for the different track sample descriptions. Entries may be null. - */ - public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; - - /** - * Durations of edit list segments in the movie timescale. Null if there is no edit list. - */ - public final long[] editListDurations; - - /** - * Media times for edit list segments in the track timescale. Null if there is no edit list. - */ - public final long[] editListMediaTimes; - - /** - * For H264 video tracks, the length in bytes of the NALUnitLength field in each sample. -1 for - * other track types. - */ - public final int nalUnitLengthFieldLength; - - public Track(int id, int type, long timescale, long movieTimescale, long durationUs, - MediaFormat mediaFormat, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, - int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) { - this.id = id; - this.type = type; - this.timescale = timescale; - this.movieTimescale = movieTimescale; - this.durationUs = durationUs; - this.mediaFormat = mediaFormat; - this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes; - this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; - this.editListDurations = editListDurations; - this.editListMediaTimes = editListMediaTimes; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackFragment.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackFragment.java deleted file mode 100755 index 9e2dadb8b85..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackFragment.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.mp4; - -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * A holder for information corresponding to a single fragment of an mp4 file. - */ -/* package */ final class TrackFragment { - - /** - * The default values for samples from the track fragment header. - */ - public DefaultSampleValues header; - /** - * The position (byte offset) of the start of sample data. - */ - public long dataPosition; - /** - * The position (byte offset) of the start of auxiliary data. - */ - public long auxiliaryDataPosition; - /** - * The number of samples contained by the fragment. - */ - public int length; - /** - * The size of each sample in the run. - */ - public int[] sampleSizeTable; - /** - * The composition time offset of each sample in the run. - */ - public int[] sampleCompositionTimeOffsetTable; - /** - * The decoding time of each sample in the run. - */ - public long[] sampleDecodingTimeTable; - /** - * Indicates which samples are sync frames. - */ - public boolean[] sampleIsSyncFrameTable; - /** - * True if the fragment defines encryption data. False otherwise. - */ - public boolean definesEncryptionData; - /** - * If {@link #definesEncryptionData} is true, indicates which samples use sub-sample encryption. - * Undefined otherwise. - */ - public boolean[] sampleHasSubsampleEncryptionTable; - /** - * If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data. - * Undefined otherwise. - */ - public int sampleEncryptionDataLength; - /** - * If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined - * otherwise. - */ - public ParsableByteArray sampleEncryptionData; - /** - * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. - */ - public boolean sampleEncryptionDataNeedsFill; - /** - * Fragment specific track encryption. May be null. - */ - public TrackEncryptionBox trackEncryptionBox; - - /** - * The absolute decode time of the start of the next fragment. - */ - public long nextFragmentDecodeTime; - - /** - * Resets the fragment. - *

        - * {@link #length} and {@link #nextFragmentDecodeTime} are set to 0, both - * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false, - * and {@link #trackEncryptionBox} is set to null. - */ - public void reset() { - length = 0; - nextFragmentDecodeTime = 0; - definesEncryptionData = false; - sampleEncryptionDataNeedsFill = false; - trackEncryptionBox = null; - } - - /** - * Configures the fragment for the specified number of samples. - *

        - * The {@link #length} of the fragment is set to the specified sample count, and the contained - * tables are resized if necessary such that they are at least this length. - * - * @param sampleCount The number of samples in the new run. - */ - public void initTables(int sampleCount) { - length = sampleCount; - if (sampleSizeTable == null || sampleSizeTable.length < length) { - // Size the tables 25% larger than needed, so as to make future resize operations less - // likely. The choice of 25% is relatively arbitrary. - int tableSize = (sampleCount * 125) / 100; - sampleSizeTable = new int[tableSize]; - sampleCompositionTimeOffsetTable = new int[tableSize]; - sampleDecodingTimeTable = new long[tableSize]; - sampleIsSyncFrameTable = new boolean[tableSize]; - sampleHasSubsampleEncryptionTable = new boolean[tableSize]; - } - } - - /** - * Configures the fragment to be one that defines encryption data of the specified length. - *

        - * {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to - * the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it - * is at least this length. - * - * @param length The length in bytes of the encryption data. - */ - public void initEncryptionData(int length) { - if (sampleEncryptionData == null || sampleEncryptionData.limit() < length) { - sampleEncryptionData = new ParsableByteArray(length); - } - sampleEncryptionDataLength = length; - definesEncryptionData = true; - sampleEncryptionDataNeedsFill = true; - } - - /** - * Fills {@link #sampleEncryptionData} from the provided input. - * - * @param input An {@link ExtractorInput} from which to read the encryption data. - */ - public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException { - input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength); - sampleEncryptionData.setPosition(0); - sampleEncryptionDataNeedsFill = false; - } - - /** - * Fills {@link #sampleEncryptionData} from the provided source. - * - * @param source A source from which to read the encryption data. - */ - public void fillEncryptionData(ParsableByteArray source) { - source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength); - sampleEncryptionData.setPosition(0); - sampleEncryptionDataNeedsFill = false; - } - - public long getSamplePresentationTime(int index) { - return sampleDecodingTimeTable[index] + sampleCompositionTimeOffsetTable[index]; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/FlacReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/FlacReader.java deleted file mode 100755 index aca0e87faf8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/FlacReader.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.util.FlacSeekTable; -import org.telegram.messenger.exoplayer.util.FlacStreamInfo; -import org.telegram.messenger.exoplayer.util.FlacUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * {@link StreamReader} to extract Flac data out of Ogg byte stream. - */ -/* package */ final class FlacReader extends StreamReader { - - private static final byte AUDIO_PACKET_TYPE = (byte) 0xFF; - private static final byte SEEKTABLE_PACKET_TYPE = 0x03; - - private FlacStreamInfo streamInfo; - - private FlacSeekTable seekTable; - - private boolean firstAudioPacketProcessed; - - /* package */ static boolean verifyBitstreamType(ParsableByteArray data) { - return data.readUnsignedByte() == 0x7F && // packet type - data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - long position = input.getPosition(); - - if (!oggParser.readPacket(input, scratch)) { - return Extractor.RESULT_END_OF_INPUT; - } - - byte[] data = scratch.data; - if (streamInfo == null) { - streamInfo = new FlacStreamInfo(data, 17); - - byte[] metadata = Arrays.copyOfRange(data, 9, scratch.limit()); - metadata[4] = (byte) 0x80; // Set the last metadata block flag, ignore the other blocks - List initializationData = Collections.singletonList(metadata); - - MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_FLAC, - streamInfo.bitRate(), MediaFormat.NO_VALUE, streamInfo.durationUs(), - streamInfo.channels, streamInfo.sampleRate, initializationData, null); - trackOutput.format(mediaFormat); - - } else if (data[0] == AUDIO_PACKET_TYPE) { - if (!firstAudioPacketProcessed) { - if (seekTable != null) { - extractorOutput.seekMap(seekTable.createSeekMap(position, streamInfo.sampleRate)); - seekTable = null; - } else { - extractorOutput.seekMap(SeekMap.UNSEEKABLE); - } - firstAudioPacketProcessed = true; - } - - trackOutput.sampleData(scratch, scratch.limit()); - scratch.setPosition(0); - long timeUs = FlacUtil.extractSampleTimestamp(streamInfo, scratch); - trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null); - - } else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE && seekTable == null) { - seekTable = FlacSeekTable.parseSeekTable(scratch); - } - - scratch.reset(); - return Extractor.RESULT_CONTINUE; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggExtractor.java deleted file mode 100755 index 81a69ccf0eb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggExtractor.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * Ogg {@link Extractor}. - */ -public class OggExtractor implements Extractor { - - private StreamReader streamReader; - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - try { - ParsableByteArray scratch = new ParsableByteArray(new byte[OggUtil.PAGE_HEADER_SIZE], 0); - OggUtil.PageHeader header = new OggUtil.PageHeader(); - if (!OggUtil.populatePageHeader(input, header, scratch, true) - || (header.type & 0x02) != 0x02 || header.bodySize < 7) { - return false; - } - scratch.reset(); - input.peekFully(scratch.data, 0, 7); - if (FlacReader.verifyBitstreamType(scratch)) { - streamReader = new FlacReader(); - } else { - scratch.reset(); - if (VorbisReader.verifyBitstreamType(scratch)) { - streamReader = new VorbisReader(); - } else { - return false; - } - } - return true; - } catch (ParserException e) { - // does not happen - } finally { - } - return false; - } - - @Override - public void init(ExtractorOutput output) { - TrackOutput trackOutput = output.track(0); - output.endTracks(); - streamReader.init(output, trackOutput); - } - - @Override - public void seek() { - streamReader.seek(); - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - return streamReader.read(input, seekPosition); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggParser.java deleted file mode 100755 index db3294ecbeb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggParser.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ogg.OggUtil.PacketInfoHolder; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * Reads OGG packets from an {@link ExtractorInput}. - */ -/* package */ final class OggParser { - - public static final int OGG_MAX_SEGMENT_SIZE = 255; - - private final OggUtil.PageHeader pageHeader = new OggUtil.PageHeader(); - private final ParsableByteArray headerArray = new ParsableByteArray(27 + 255); - private final PacketInfoHolder holder = new PacketInfoHolder(); - - private int currentSegmentIndex = -1; - private long elapsedSamples; - - /** - * Resets this reader. - */ - public void reset() { - pageHeader.reset(); - headerArray.reset(); - currentSegmentIndex = -1; - } - - /** - * Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make - * sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader - * can resume properly from an error while reading a continued packet spanned across multiple - * pages. - * - * @param input the {@link ExtractorInput} to read data from. - * @param packetArray the {@link ParsableByteArray} to write the packet data into. - * @return {@code true} if the read was successful. {@code false} if the end of the input was - * encountered having read no data. - * @throws IOException thrown if reading from the input fails. - * @throws InterruptedException thrown if interrupted while reading from input. - */ - public boolean readPacket(ExtractorInput input, ParsableByteArray packetArray) - throws IOException, InterruptedException { - Assertions.checkState(input != null && packetArray != null); - - boolean packetComplete = false; - while (!packetComplete) { - if (currentSegmentIndex < 0) { - // We're at the start of a page. - if (!OggUtil.populatePageHeader(input, pageHeader, headerArray, true)) { - return false; - } - int segmentIndex = 0; - int bytesToSkip = pageHeader.headerSize; - if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) { - // After seeking, the first packet may be the remainder - // part of a continued packet which has to be discarded. - OggUtil.calculatePacketSize(pageHeader, segmentIndex, holder); - segmentIndex += holder.segmentCount; - bytesToSkip += holder.size; - } - input.skipFully(bytesToSkip); - currentSegmentIndex = segmentIndex; - } - - OggUtil.calculatePacketSize(pageHeader, currentSegmentIndex, holder); - int segmentIndex = currentSegmentIndex + holder.segmentCount; - if (holder.size > 0) { - input.readFully(packetArray.data, packetArray.limit(), holder.size); - packetArray.setLimit(packetArray.limit() + holder.size); - packetComplete = pageHeader.laces[segmentIndex - 1] != 255; - } - // advance now since we are sure reading didn't throw an exception - currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? -1 - : segmentIndex; - } - return true; - } - - /** - * Skips to the last Ogg page in the stream and reads the header's granule field which is the - * total number of samples per channel. - * - * @param input The {@link ExtractorInput} to read from. - * @return the total number of samples of this input. - * @throws IOException thrown if reading from the input fails. - * @throws InterruptedException thrown if interrupted while reading from the input. - */ - public long readGranuleOfLastPage(ExtractorInput input) - throws IOException, InterruptedException { - Assertions.checkArgument(input.getLength() != C.LENGTH_UNBOUNDED); // never read forever! - OggUtil.skipToNextPage(input); - pageHeader.reset(); - while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < input.getLength()) { - OggUtil.populatePageHeader(input, pageHeader, headerArray, false); - input.skipFully(pageHeader.headerSize + pageHeader.bodySize); - } - return pageHeader.granulePosition; - } - - /** - * Skips to the position of the start of the page containing the {@code targetGranule} and - * returns the elapsed samples which is the granule of the page previous to the target page. - *

        - * Note that the position of the {@code input} must be before the start of the page previous to - * the page containing the targetGranule to get the correct number of elapsed samples. - * Which is in short like: {@code pos(input) <= pos(targetPage.pageSequence - 1)}. - * - * @param input the {@link ExtractorInput} to read from. - * @param targetGranule the target granule (number of frames per channel). - * @return the number of elapsed samples at the start of the target page. - * @throws ParserException thrown if populating the page header fails. - * @throws IOException thrown if reading from the input fails. - * @throws InterruptedException thrown if interrupted while reading from the input. - */ - public long skipToPageOfGranule(ExtractorInput input, long targetGranule) - throws IOException, InterruptedException { - OggUtil.skipToNextPage(input); - OggUtil.populatePageHeader(input, pageHeader, headerArray, false); - while (pageHeader.granulePosition < targetGranule) { - input.skipFully(pageHeader.headerSize + pageHeader.bodySize); - // Store in a member field to be able to resume after IOExceptions. - elapsedSamples = pageHeader.granulePosition; - // Peek next header. - OggUtil.populatePageHeader(input, pageHeader, headerArray, false); - } - if (elapsedSamples == 0) { - throw new ParserException(); - } - input.resetPeekPosition(); - long returnValue = elapsedSamples; - // Reset member state. - elapsedSamples = 0; - currentSegmentIndex = -1; - return returnValue; - } - - /** - * Returns the {@link OggUtil.PageHeader} of the current page. The header might not have been - * populated if the first packet has yet to be read. - *

        - * Note that there is only a single instance of {@code OggParser.PageHeader} which is mutable. - * The value of the fields might be changed by the reader when reading the stream advances and - * the next page is read (which implies reading and populating the next header). - * - * @return the {@code PageHeader} of the current page or {@code null}. - */ - public OggUtil.PageHeader getPageHeader() { - return pageHeader; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggSeeker.java deleted file mode 100755 index 126b3630741..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggSeeker.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * Used to seek in an Ogg stream. - */ -/* package */ final class OggSeeker { - - private static final int MATCH_RANGE = 72000; - - private final OggUtil.PageHeader pageHeader = new OggUtil.PageHeader(); - private final ParsableByteArray headerArray = new ParsableByteArray(27 + 255); - private long audioDataLength = C.LENGTH_UNBOUNDED; - private long totalSamples; - - /** - * Setup the seeker with the data it needs to to an educated guess of seeking positions. - * - * @param audioDataLength the length of the audio data (total bytes - header bytes). - * @param totalSamples the total number of samples of audio data. - */ - public void setup(long audioDataLength, long totalSamples) { - Assertions.checkArgument(audioDataLength > 0 && totalSamples > 0); - this.audioDataLength = audioDataLength; - this.totalSamples = totalSamples; - } - - /** - * Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput} - * has to seek and then be passed for another call until -1 is return. If -1 is returned the - * input is at a position which is before the start of the page before the target page and at - * which it is sensible to just skip pages to the target granule and pre-roll instead of doing - * another seek request. - * - * @param targetGranule the target granule position to seek to. - * @param input the {@link ExtractorInput} to read from. - * @return the position to seek the {@link ExtractorInput} to for a next call or -1 if it's close - * enough to skip to the target page. - * @throws IOException thrown if reading from the input fails. - * @throws InterruptedException thrown if interrupted while reading from the input. - */ - public long getNextSeekPosition(long targetGranule, ExtractorInput input) - throws IOException, InterruptedException { - Assertions.checkState(audioDataLength != C.LENGTH_UNBOUNDED && totalSamples != 0); - OggUtil.populatePageHeader(input, pageHeader, headerArray, false); - long granuleDistance = targetGranule - pageHeader.granulePosition; - if (granuleDistance <= 0 || granuleDistance > MATCH_RANGE) { - // estimated position too high or too low - long offset = (pageHeader.bodySize + pageHeader.headerSize) - * (granuleDistance <= 0 ? 2 : 1); - return input.getPosition() - offset + (granuleDistance * audioDataLength / totalSamples); - } - // position accepted (below target granule and within MATCH_RANGE) - input.resetPeekPosition(); - return -1; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggUtil.java deleted file mode 100755 index c0dbd60035e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/OggUtil.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.EOFException; -import java.io.IOException; - -/** - * Utility methods for reading ogg streams. - */ -/* package */ final class OggUtil { - - public static final int PAGE_HEADER_SIZE = 27; - - private static final int TYPE_OGGS = Util.getIntegerCodeForString("OggS"); - - /** - * Reads an int of {@code length} bits from {@code src} starting at - * {@code leastSignificantBitIndex}. - * - * @param src the {@code byte} to read from. - * @param length the length in bits of the int to read. - * @param leastSignificantBitIndex the index of the least significant bit of the int to read. - * @return the int value read. - */ - public static int readBits(byte src, int length, int leastSignificantBitIndex) { - return (src >> leastSignificantBitIndex) & (255 >>> (8 - length)); - } - - /** - * Skips to the next page. - * - * @param input The {@code ExtractorInput} to skip to the next page. - * @throws IOException thrown if peeking/reading from the input fails. - * @throws InterruptedException thrown if interrupted while peeking/reading from the input. - */ - public static void skipToNextPage(ExtractorInput input) - throws IOException, InterruptedException { - - byte[] buffer = new byte[2048]; - int peekLength = buffer.length; - while (true) { - if (input.getLength() != C.LENGTH_UNBOUNDED - && input.getPosition() + peekLength > input.getLength()) { - // Make sure to not peek beyond the end of the input. - peekLength = (int) (input.getLength() - input.getPosition()); - if (peekLength < 4) { - // Not found until eof. - throw new EOFException(); - } - } - input.peekFully(buffer, 0, peekLength, false); - for (int i = 0; i < peekLength - 3; i++) { - if (buffer[i] == 'O' && buffer[i + 1] == 'g' && buffer[i + 2] == 'g' - && buffer[i + 3] == 'S') { - // Match! Skip to the start of the pattern. - input.skipFully(i); - return; - } - } - // Overlap by not skipping the entire peekLength. - input.skipFully(peekLength - 3); - } - } - - /** - * Peeks an Ogg page header and stores the data in the {@code header} object passed - * as argument. - * - * @param input the {@link ExtractorInput} to read from. - * @param header the {@link PageHeader} to read from. - * @param scratch a scratch array temporary use. Its size should be at least PAGE_HEADER_SIZE - * @param quite if {@code true} no Exceptions are thrown but {@code false} is return if something - * goes wrong. - * @return {@code true} if the read was successful. {@code false} if the end of the - * input was encountered having read no data. - * @throws IOException thrown if reading data fails or the stream is invalid. - * @throws InterruptedException thrown if thread is interrupted when reading/peeking. - */ - public static boolean populatePageHeader(ExtractorInput input, PageHeader header, - ParsableByteArray scratch, boolean quite) throws IOException, InterruptedException { - - scratch.reset(); - header.reset(); - boolean hasEnoughBytes = input.getLength() == C.LENGTH_UNBOUNDED - || input.getLength() - input.getPeekPosition() >= PAGE_HEADER_SIZE; - if (!hasEnoughBytes || !input.peekFully(scratch.data, 0, PAGE_HEADER_SIZE, true)) { - if (quite) { - return false; - } else { - throw new EOFException(); - } - } - if (scratch.readUnsignedInt() != TYPE_OGGS) { - if (quite) { - return false; - } else { - throw new ParserException("expected OggS capture pattern at begin of page"); - } - } - - header.revision = scratch.readUnsignedByte(); - if (header.revision != 0x00) { - if (quite) { - return false; - } else { - throw new ParserException("unsupported bit stream revision"); - } - } - header.type = scratch.readUnsignedByte(); - - header.granulePosition = scratch.readLittleEndianLong(); - header.streamSerialNumber = scratch.readLittleEndianUnsignedInt(); - header.pageSequenceNumber = scratch.readLittleEndianUnsignedInt(); - header.pageChecksum = scratch.readLittleEndianUnsignedInt(); - header.pageSegmentCount = scratch.readUnsignedByte(); - - scratch.reset(); - // calculate total size of header including laces - header.headerSize = PAGE_HEADER_SIZE + header.pageSegmentCount; - input.peekFully(scratch.data, 0, header.pageSegmentCount); - for (int i = 0; i < header.pageSegmentCount; i++) { - header.laces[i] = scratch.readUnsignedByte(); - header.bodySize += header.laces[i]; - } - return true; - } - - /** - * Calculates the size of the packet starting from {@code startSegmentIndex}. - * - * @param header the {@link PageHeader} with laces. - * @param startSegmentIndex the index of the first segment of the packet. - * @param holder a position holder to store the resulting size value. - */ - public static void calculatePacketSize(PageHeader header, int startSegmentIndex, - PacketInfoHolder holder) { - holder.segmentCount = 0; - holder.size = 0; - while (startSegmentIndex + holder.segmentCount < header.pageSegmentCount) { - int segmentLength = header.laces[startSegmentIndex + holder.segmentCount++]; - holder.size += segmentLength; - if (segmentLength != 255) { - // packets end at first lace < 255 - break; - } - } - } - - /** - * Data object to store header information. Be aware that {@code laces.length} is always 255. - * Instead use {@code pageSegmentCount} to iterate. - */ - public static final class PageHeader { - - public int revision; - public int type; - public long granulePosition; - public long streamSerialNumber; - public long pageSequenceNumber; - public long pageChecksum; - public int pageSegmentCount; - public int headerSize; - public int bodySize; - public final int[] laces = new int[255]; - - /** - * Resets all primitive member fields to zero. - */ - public void reset() { - revision = 0; - type = 0; - granulePosition = 0; - streamSerialNumber = 0; - pageSequenceNumber = 0; - pageChecksum = 0; - pageSegmentCount = 0; - headerSize = 0; - bodySize = 0; - } - - } - - /** - * Holds size and number of segments of a packet. - */ - public static class PacketInfoHolder { - public int size; - public int segmentCount; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/StreamReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/StreamReader.java deleted file mode 100755 index 91dbac44a26..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/StreamReader.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * StreamReader abstract class. - */ -/* package */ abstract class StreamReader { - - protected final ParsableByteArray scratch = new ParsableByteArray( - new byte[OggParser.OGG_MAX_SEGMENT_SIZE * 255], 0); - - protected final OggParser oggParser = new OggParser(); - - protected TrackOutput trackOutput; - - protected ExtractorOutput extractorOutput; - - void init(ExtractorOutput output, TrackOutput trackOutput) { - this.extractorOutput = output; - this.trackOutput = trackOutput; - } - - /** - * @see Extractor#seek() - */ - void seek() { - oggParser.reset(); - scratch.reset(); - } - - /** - * @see Extractor#read(ExtractorInput, PositionHolder) - */ - abstract int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException; -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisReader.java deleted file mode 100755 index 2717023e7f0..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisReader.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ogg; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.ogg.VorbisUtil.Mode; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; -import java.util.ArrayList; - -/** - * {@link StreamReader} to extract Vorbis data out of Ogg byte stream. - */ -/* package */ final class VorbisReader extends StreamReader implements SeekMap { - - private static final long LARGEST_EXPECTED_PAGE_SIZE = 8000; - - private VorbisSetup vorbisSetup; - private int previousPacketBlockSize; - private long elapsedSamples; - private boolean seenFirstAudioPacket; - - private final OggSeeker oggSeeker = new OggSeeker(); - private long targetGranule = -1; - - private VorbisUtil.VorbisIdHeader vorbisIdHeader; - private VorbisUtil.CommentHeader commentHeader; - private long inputLength; - private long audioStartPosition; - private long totalSamples; - private long duration; - - /* package */ static boolean verifyBitstreamType(ParsableByteArray data) { - try { - return VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, data, true); - } catch (ParserException e) { - return false; - } - } - - @Override - public void seek() { - super.seek(); - previousPacketBlockSize = 0; - elapsedSamples = 0; - seenFirstAudioPacket = false; - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - - // setup - if (totalSamples == 0) { - if (vorbisSetup == null) { - inputLength = input.getLength(); - vorbisSetup = readSetupHeaders(input, scratch); - audioStartPosition = input.getPosition(); - extractorOutput.seekMap(this); - if (inputLength != C.LENGTH_UNBOUNDED) { - // seek to the end just before the last page of stream to get the duration - seekPosition.position = Math.max(0, input.getLength() - LARGEST_EXPECTED_PAGE_SIZE); - return Extractor.RESULT_SEEK; - } - } - totalSamples = inputLength == C.LENGTH_UNBOUNDED ? -1 - : oggParser.readGranuleOfLastPage(input); - - ArrayList codecInitialisationData = new ArrayList<>(); - codecInitialisationData.add(vorbisSetup.idHeader.data); - codecInitialisationData.add(vorbisSetup.setupHeaderData); - - duration = inputLength == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US - : totalSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate; - trackOutput.format(MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_VORBIS, - this.vorbisSetup.idHeader.bitrateNominal, OggParser.OGG_MAX_SEGMENT_SIZE * 255, duration, - this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, - codecInitialisationData, null)); - - if (inputLength != C.LENGTH_UNBOUNDED) { - oggSeeker.setup(inputLength - audioStartPosition, totalSamples); - // seek back to resume from where we finished reading vorbis headers - seekPosition.position = audioStartPosition; - return Extractor.RESULT_SEEK; - } - } - - // seeking requested - if (!seenFirstAudioPacket && targetGranule > -1) { - OggUtil.skipToNextPage(input); - long position = oggSeeker.getNextSeekPosition(targetGranule, input); - if (position != -1) { - seekPosition.position = position; - return Extractor.RESULT_SEEK; - } else { - elapsedSamples = oggParser.skipToPageOfGranule(input, targetGranule); - previousPacketBlockSize = vorbisIdHeader.blockSize0; - // we're never at the first packet after seeking - seenFirstAudioPacket = true; - } - } - - // playback - if (oggParser.readPacket(input, scratch)) { - // if this is an audio packet... - if ((scratch.data[0] & 0x01) != 1) { - // ... we need to decode the block size - int packetBlockSize = decodeBlockSize(scratch.data[0], vorbisSetup); - // a packet contains samples produced from overlapping the previous and current frame data - // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) - int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 - : 0; - if (elapsedSamples + samplesInPacket >= targetGranule) { - // codec expects the number of samples appended to audio data - appendNumberOfSamples(scratch, samplesInPacket); - // calculate time and send audio data to codec - long timeUs = elapsedSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate; - trackOutput.sampleData(scratch, scratch.limit()); - trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null); - targetGranule = -1; - } - // update state in members for next iteration - seenFirstAudioPacket = true; - elapsedSamples += samplesInPacket; - previousPacketBlockSize = packetBlockSize; - } - scratch.reset(); - return Extractor.RESULT_CONTINUE; - } - return Extractor.RESULT_END_OF_INPUT; - } - - //@VisibleForTesting - /* package */ VorbisSetup readSetupHeaders(ExtractorInput input, ParsableByteArray scratch) - throws IOException, InterruptedException { - - if (vorbisIdHeader == null) { - oggParser.readPacket(input, scratch); - vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); - scratch.reset(); - } - - if (commentHeader == null) { - oggParser.readPacket(input, scratch); - commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); - scratch.reset(); - } - - oggParser.readPacket(input, scratch); - // the third packet contains the setup header - byte[] setupHeaderData = new byte[scratch.limit()]; - // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 - System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); - // partially decode setup header to get the modes - Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); - // we need the ilog of modes all the time when extracting, so we compute it once - int iLogModes = VorbisUtil.iLog(modes.length - 1); - scratch.reset(); - - return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); - } - - //@VisibleForTesting - /* package */ static void appendNumberOfSamples(ParsableByteArray buffer, - long packetSampleCount) { - - buffer.setLimit(buffer.limit() + 4); - // The vorbis decoder expects the number of samples in the packet - // to be appended to the audio data as an int32 - buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); - buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); - buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); - buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); - } - - private static int decodeBlockSize(byte firstByteOfAudioPacket, VorbisSetup vorbisSetup) { - // read modeNumber (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-730004.3.1) - int modeNumber = OggUtil.readBits(firstByteOfAudioPacket, vorbisSetup.iLogModes, 1); - int currentBlockSize; - if (!vorbisSetup.modes[modeNumber].blockFlag) { - currentBlockSize = vorbisSetup.idHeader.blockSize0; - } else { - currentBlockSize = vorbisSetup.idHeader.blockSize1; - } - return currentBlockSize; - } - - @Override - public boolean isSeekable() { - return vorbisSetup != null && inputLength != C.LENGTH_UNBOUNDED; - } - - @Override - public long getPosition(long timeUs) { - if (timeUs == 0) { - targetGranule = -1; - return audioStartPosition; - } - targetGranule = vorbisSetup.idHeader.sampleRate * timeUs / C.MICROS_PER_SECOND; - return Math.max(audioStartPosition, ((inputLength - audioStartPosition) * timeUs - / duration) - 4000); - } - - /** - * Class to hold all data read from Vorbis setup headers. - */ - /* package */ static final class VorbisSetup { - - public final VorbisUtil.VorbisIdHeader idHeader; - public final VorbisUtil.CommentHeader commentHeader; - public final byte[] setupHeaderData; - public final Mode[] modes; - public final int iLogModes; - - public VorbisSetup(VorbisUtil.VorbisIdHeader idHeader, VorbisUtil.CommentHeader - commentHeader, byte[] setupHeaderData, Mode[] modes, int iLogModes) { - this.idHeader = idHeader; - this.commentHeader = commentHeader; - this.setupHeaderData = setupHeaderData; - this.modes = modes; - this.iLogModes = iLogModes; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Ac3Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Ac3Reader.java deleted file mode 100755 index 7aee19ccd04..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Ac3Reader.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.Ac3Util; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. - */ -/* package */ final class Ac3Reader extends ElementaryStreamReader { - - private static final int STATE_FINDING_SYNC = 0; - private static final int STATE_READING_HEADER = 1; - private static final int STATE_READING_SAMPLE = 2; - - private static final int HEADER_SIZE = 8; - - private final boolean isEac3; - private final ParsableBitArray headerScratchBits; - private final ParsableByteArray headerScratchBytes; - - private int state; - private int bytesRead; - - // Used to find the header. - private boolean lastByteWas0B; - - // Used when parsing the header. - private long sampleDurationUs; - private MediaFormat mediaFormat; - private int sampleSize; - - // Used when reading the samples. - private long timeUs; - - /** - * Constructs a new reader for (E-)AC-3 elementary streams. - * - * @param output Track output for extracted samples. - * @param isEac3 Whether the stream is E-AC-3 (ETSI TS 102 366 Annex E). Specify {@code false} to - * parse sample headers as AC-3. - */ - public Ac3Reader(TrackOutput output, boolean isEac3) { - super(output); - this.isEac3 = isEac3; - headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); - headerScratchBytes = new ParsableByteArray(headerScratchBits.data); - state = STATE_FINDING_SYNC; - } - - @Override - public void seek() { - state = STATE_FINDING_SYNC; - bytesRead = 0; - lastByteWas0B = false; - } - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - timeUs = pesTimeUs; - } - - @Override - public void consume(ParsableByteArray data) { - while (data.bytesLeft() > 0) { - switch (state) { - case STATE_FINDING_SYNC: - if (skipToNextSync(data)) { - state = STATE_READING_HEADER; - headerScratchBytes.data[0] = 0x0B; - headerScratchBytes.data[1] = 0x77; - bytesRead = 2; - } - break; - case STATE_READING_HEADER: - if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { - parseHeader(); - headerScratchBytes.setPosition(0); - output.sampleData(headerScratchBytes, HEADER_SIZE); - state = STATE_READING_SAMPLE; - } - break; - case STATE_READING_SAMPLE: - int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); - output.sampleData(data, bytesToRead); - bytesRead += bytesToRead; - if (bytesRead == sampleSize) { - output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); - timeUs += sampleDurationUs; - state = STATE_FINDING_SYNC; - } - break; - } - } - } - - @Override - public void packetFinished() { - // Do nothing. - } - - /** - * Continues a read from the provided {@code source} into a given {@code target}. It's assumed - * that the data should be written into {@code target} starting from an offset of zero. - * - * @param source The source from which to read. - * @param target The target into which data is to be read. - * @param targetLength The target length of the read. - * @return Whether the target length was reached. - */ - private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { - int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); - source.readBytes(target, bytesRead, bytesToRead); - bytesRead += bytesToRead; - return bytesRead == targetLength; - } - - /** - * Locates the next syncword, advancing the position to the byte that immediately follows it. If a - * syncword was not located, the position is advanced to the limit. - * - * @param pesBuffer The buffer whose position should be advanced. - * @return True if a syncword position was found. False otherwise. - */ - private boolean skipToNextSync(ParsableByteArray pesBuffer) { - while (pesBuffer.bytesLeft() > 0) { - if (!lastByteWas0B) { - lastByteWas0B = pesBuffer.readUnsignedByte() == 0x0B; - continue; - } - int secondByte = pesBuffer.readUnsignedByte(); - if (secondByte == 0x77) { - lastByteWas0B = false; - return true; - } else { - lastByteWas0B = secondByte == 0x0B; - } - } - return false; - } - - /** - * Parses the sample header. - */ - private void parseHeader() { - if (mediaFormat == null) { - mediaFormat = isEac3 - ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null) - : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, C.UNKNOWN_TIME_US, null); - output.format(mediaFormat); - } - sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) - : Ac3Util.parseAc3SyncframeSize(headerScratchBits.data); - int audioSamplesPerSyncframe = isEac3 - ? Ac3Util.parseEAc3SyncframeAudioSampleCount(headerScratchBits.data) - : Ac3Util.getAc3SyncframeAudioSampleCount(); - // In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate - // specifies the number of PCM audio samples per second. - sampleDurationUs = - (int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / mediaFormat.sampleRate); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsExtractor.java deleted file mode 100755 index 0a4ba2033dd..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsExtractor.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS - * headers. - */ -public final class AdtsExtractor implements Extractor { - - private static final int MAX_PACKET_SIZE = 200; - private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); - /** - * The maximum number of bytes to search when sniffing, excluding the header, before giving up. - * Frame sizes are represented by 13-bit fields, so expect a valid frame in the first 8192 bytes. - */ - private static final int MAX_SNIFF_BYTES = 8 * 1024; - - private final long firstSampleTimestampUs; - private final ParsableByteArray packetBuffer; - - // Accessed only by the loading thread. - private AdtsReader adtsReader; - private boolean startedPacket; - - public AdtsExtractor() { - this(0); - } - - public AdtsExtractor(long firstSampleTimestampUs) { - this.firstSampleTimestampUs = firstSampleTimestampUs; - packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); - } - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - // Skip any ID3 headers. - ParsableByteArray scratch = new ParsableByteArray(10); - ParsableBitArray scratchBits = new ParsableBitArray(scratch.data); - int startPosition = 0; - while (true) { - input.peekFully(scratch.data, 0, 10); - scratch.setPosition(0); - if (scratch.readUnsignedInt24() != ID3_TAG) { - break; - } - int length = (scratch.data[6] & 0x7F) << 21 | ((scratch.data[7] & 0x7F) << 14) - | ((scratch.data[8] & 0x7F) << 7) | (scratch.data[9] & 0x7F); - startPosition += 10 + length; - input.advancePeekPosition(length); - } - input.resetPeekPosition(); - input.advancePeekPosition(startPosition); - - // Try to find four or more consecutive AAC audio frames, exceeding the MPEG TS packet size. - int headerPosition = startPosition; - int validFramesSize = 0; - int validFramesCount = 0; - while (true) { - input.peekFully(scratch.data, 0, 2); - scratch.setPosition(0); - int syncBytes = scratch.readUnsignedShort(); - if ((syncBytes & 0xFFF6) != 0xFFF0) { - validFramesCount = 0; - validFramesSize = 0; - input.resetPeekPosition(); - if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) { - return false; - } - input.advancePeekPosition(headerPosition); - } else { - if (++validFramesCount >= 4 && validFramesSize > 188) { - return true; - } - - // Skip the frame. - input.peekFully(scratch.data, 0, 4); - scratchBits.setPosition(14); - int frameSize = scratchBits.readBits(13); - // Either the stream is malformed OR we're not parsing an ADTS stream. - if (frameSize <= 6) { - return false; - } - input.advancePeekPosition(frameSize - 6); - validFramesSize += frameSize; - } - } - } - - @Override - public void init(ExtractorOutput output) { - adtsReader = new AdtsReader(output.track(0), output.track(1)); - output.endTracks(); - output.seekMap(SeekMap.UNSEEKABLE); - } - - @Override - public void seek() { - startedPacket = false; - adtsReader.seek(); - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); - if (bytesRead == -1) { - return RESULT_END_OF_INPUT; - } - - // Feed whatever data we have to the reader, regardless of whether the read finished or not. - packetBuffer.setPosition(0); - packetBuffer.setLimit(bytesRead); - - // TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes - // unnecessary to copy the data through packetBuffer. - if (!startedPacket) { - // Pass data to the reader as though it's contained within a single infinitely long packet. - adtsReader.packetStarted(firstSampleTimestampUs, true); - startedPacket = true; - } - adtsReader.consume(packetBuffer); - return RESULT_CONTINUE; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/ElementaryStreamReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/ElementaryStreamReader.java deleted file mode 100755 index 77576e5d961..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/ElementaryStreamReader.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * Extracts individual samples from an elementary media stream, preserving original order. - */ -/* package */ abstract class ElementaryStreamReader { - - protected final TrackOutput output; - - /** - * @param output A {@link TrackOutput} to which samples should be written. - */ - protected ElementaryStreamReader(TrackOutput output) { - this.output = output; - } - - /** - * Notifies the reader that a seek has occurred. - */ - public abstract void seek(); - - /** - * Invoked when a packet starts. - * - * @param pesTimeUs The timestamp associated with the packet. - * @param dataAlignmentIndicator The data alignment indicator associated with the packet. - */ - public abstract void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); - - /** - * Consumes (possibly partial) data from the current packet. - * - * @param data The data to consume. - */ - public abstract void consume(ParsableByteArray data); - - /** - * Invoked when a packet ends. - */ - public abstract void packetFinished(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H262Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H262Reader.java deleted file mode 100755 index 032d09b65fb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H262Reader.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import android.util.Pair; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.Arrays; -import java.util.Collections; - -/** - * Parses a continuous H262 byte stream and extracts individual frames. - */ -/* package */ final class H262Reader extends ElementaryStreamReader { - - private static final int START_PICTURE = 0x00; - private static final int START_SEQUENCE_HEADER = 0xB3; - private static final int START_EXTENSION = 0xB5; - private static final int START_GROUP = 0xB8; - - // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. - private static final double[] FRAME_RATE_VALUES = new double[] { - 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60}; - - // State that should not be reset on seek. - private boolean hasOutputFormat; - private long frameDurationUs; - - // State that should be reset on seek. - private final boolean[] prefixFlags; - private final CsdBuffer csdBuffer; - private boolean foundFirstFrameInGroup; - private long totalBytesWritten; - - // Per packet state that gets reset at the start of each packet. - private long pesTimeUs; - private boolean pesPtsUsAvailable; - - // Per sample state that gets reset at the start of each frame. - private boolean isKeyframe; - private long framePosition; - private long frameTimeUs; - - public H262Reader(TrackOutput output) { - super(output); - prefixFlags = new boolean[4]; - csdBuffer = new CsdBuffer(128); - } - - @Override - public void seek() { - NalUnitUtil.clearPrefixFlags(prefixFlags); - csdBuffer.reset(); - pesPtsUsAvailable = false; - foundFirstFrameInGroup = false; - totalBytesWritten = 0; - } - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - pesPtsUsAvailable = pesTimeUs != C.UNKNOWN_TIME_US; - if (pesPtsUsAvailable) { - this.pesTimeUs = pesTimeUs; - } - } - - @Override - public void consume(ParsableByteArray data) { - while (data.bytesLeft() > 0) { - int offset = data.getPosition(); - int limit = data.limit(); - byte[] dataArray = data.data; - - // Append the data to the buffer. - totalBytesWritten += data.bytesLeft(); - output.sampleData(data, data.bytesLeft()); - - int searchOffset = offset; - while (true) { - int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, searchOffset, limit, prefixFlags); - - if (startCodeOffset == limit) { - // We've scanned to the end of the data without finding another start code. - if (!hasOutputFormat) { - csdBuffer.onData(dataArray, offset, limit); - } - return; - } - - // We've found a start code with the following value. - int startCodeValue = data.data[startCodeOffset + 3] & 0xFF; - - if (!hasOutputFormat) { - // This is the number of bytes from the current offset to the start of the next start - // code. It may be negative if the start code started in the previously consumed data. - int lengthToStartCode = startCodeOffset - offset; - if (lengthToStartCode > 0) { - csdBuffer.onData(dataArray, offset, startCodeOffset); - } - // This is the number of bytes belonging to the next start code that have already been - // passed to csdDataTargetBuffer. - int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; - if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { - // The csd data is complete, so we can parse and output the media format. - Pair result = parseCsdBuffer(csdBuffer); - output.format(result.first); - frameDurationUs = result.second; - hasOutputFormat = true; - } - } - - if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { - int bytesWrittenPastStartCode = limit - startCodeOffset; - if (foundFirstFrameInGroup) { - int flags = isKeyframe ? C.SAMPLE_FLAG_SYNC : 0; - int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; - output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); - isKeyframe = false; - } - if (startCodeValue == START_GROUP) { - foundFirstFrameInGroup = false; - isKeyframe = true; - } else /* startCode == START_PICTURE */ { - frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); - framePosition = totalBytesWritten - bytesWrittenPastStartCode; - pesPtsUsAvailable = false; - foundFirstFrameInGroup = true; - } - } - - offset = startCodeOffset; - searchOffset = offset + 3; - } - } - } - - @Override - public void packetFinished() { - // Do nothing. - } - - /** - * Parses the {@link MediaFormat} and frame duration from a csd buffer. - * - * @param csdBuffer The csd buffer. - * @return A pair consisting of the {@link MediaFormat} and the frame duration in microseconds, or - * 0 if the duration could not be determined. - */ - private static Pair parseCsdBuffer(CsdBuffer csdBuffer) { - byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length); - - int firstByte = csdData[4] & 0xFF; - int secondByte = csdData[5] & 0xFF; - int thirdByte = csdData[6] & 0xFF; - int width = (firstByte << 4) | (secondByte >> 4); - int height = (secondByte & 0x0F) << 8 | thirdByte; - - float pixelWidthHeightRatio = 1f; - int aspectRatioCode = (csdData[7] & 0xF0) >> 4; - switch(aspectRatioCode) { - case 2: - pixelWidthHeightRatio = (4 * height) / (float) (3 * width); - break; - case 3: - pixelWidthHeightRatio = (16 * height) / (float) (9 * width); - break; - case 4: - pixelWidthHeightRatio = (121 * height) / (float) (100 * width); - break; - default: - // Do nothing. - break; - } - - MediaFormat format = MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_MPEG2, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, width, height, - Collections.singletonList(csdData), MediaFormat.NO_VALUE, pixelWidthHeightRatio); - - long frameDurationUs = 0; - int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1; - if (0 <= frameRateCodeMinusOne && frameRateCodeMinusOne < FRAME_RATE_VALUES.length) { - double frameRate = FRAME_RATE_VALUES[frameRateCodeMinusOne]; - int sequenceExtensionPosition = csdBuffer.sequenceExtensionPosition; - int frameRateExtensionN = (csdData[sequenceExtensionPosition + 9] & 0x60) >> 5; - int frameRateExtensionD = (csdData[sequenceExtensionPosition + 9] & 0x1F); - if (frameRateExtensionN != frameRateExtensionD) { - frameRate *= (frameRateExtensionN + 1d) / (frameRateExtensionD + 1); - } - frameDurationUs = (long) (C.MICROS_PER_SECOND / frameRate); - } - - return Pair.create(format, frameDurationUs); - } - - private static final class CsdBuffer { - - private boolean isFilling; - - public int length; - public int sequenceExtensionPosition; - public byte[] data; - - public CsdBuffer(int initialCapacity) { - data = new byte[initialCapacity]; - } - - /** - * Resets the buffer, clearing any data that it holds. - */ - public void reset() { - isFilling = false; - length = 0; - sequenceExtensionPosition = 0; - } - - /** - * Invoked when a start code is encountered in the stream. - * - * @param startCodeValue The start code value. - * @param bytesAlreadyPassed The number of bytes of the start code that have already been - * passed to {@link #onData(byte[], int, int)}, or 0. - * @return True if the csd data is now complete. False otherwise. If true is returned, neither - * this method or {@link #onData(byte[], int, int)} should be called again without an - * interleaving call to {@link #reset()}. - */ - public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) { - if (isFilling) { - if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) { - sequenceExtensionPosition = length; - } else { - length -= bytesAlreadyPassed; - isFilling = false; - return true; - } - } else if (startCodeValue == START_SEQUENCE_HEADER) { - isFilling = true; - } - return false; - } - - /** - * Invoked to pass stream data. - * - * @param newData Holds the data being passed. - * @param offset The offset of the data in {@code data}. - * @param limit The limit (exclusive) of the data in {@code data}. - */ - public void onData(byte[] newData, int offset, int limit) { - if (!isFilling) { - return; - } - int readLength = limit - offset; - if (data.length < length + readLength) { - data = Arrays.copyOf(data, (length + readLength) * 2); - } - System.arraycopy(newData, offset, data, length, readLength); - length += readLength; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H264Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H264Reader.java deleted file mode 100755 index d73ebe84853..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H264Reader.java +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.NalUnitUtil.SpsData; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Parses a continuous H264 byte stream and extracts individual frames. - */ -/* package */ final class H264Reader extends ElementaryStreamReader { - - private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information - private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set - private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set - - // State that should not be reset on seek. - private boolean hasOutputFormat; - - // State that should be reset on seek. - private final SeiReader seiReader; - private final boolean[] prefixFlags; - private final SampleReader sampleReader; - private final NalUnitTargetBuffer sps; - private final NalUnitTargetBuffer pps; - private final NalUnitTargetBuffer sei; - private long totalBytesWritten; - - // Per packet state that gets reset at the start of each packet. - private long pesTimeUs; - - // Scratch variables to avoid allocations. - private final ParsableByteArray seiWrapper; - - /** - * @param output A {@link TrackOutput} to which H.264 samples should be written. - * @param seiReader A reader for EIA-608 samples in SEI NAL units. - * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as - * synchronization samples (key-frames). - * @param detectAccessUnits Whether to split the input stream into access units (samples) based on - * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs). - */ - public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes, - boolean detectAccessUnits) { - super(output); - this.seiReader = seiReader; - prefixFlags = new boolean[3]; - sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); - sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); - pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); - sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); - seiWrapper = new ParsableByteArray(); - } - - @Override - public void seek() { - NalUnitUtil.clearPrefixFlags(prefixFlags); - sps.reset(); - pps.reset(); - sei.reset(); - sampleReader.reset(); - totalBytesWritten = 0; - } - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - this.pesTimeUs = pesTimeUs; - } - - @Override - public void consume(ParsableByteArray data) { - while (data.bytesLeft() > 0) { - int offset = data.getPosition(); - int limit = data.limit(); - byte[] dataArray = data.data; - - // Append the data to the buffer. - totalBytesWritten += data.bytesLeft(); - output.sampleData(data, data.bytesLeft()); - - // Scan the appended data, processing NAL units as they are encountered - while (true) { - int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); - - if (nalUnitOffset == limit) { - // We've scanned to the end of the data without finding the start of another NAL unit. - nalUnitData(dataArray, offset, limit); - return; - } - - // We've seen the start of a NAL unit of the following type. - int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset); - - // This is the number of bytes from the current offset to the start of the next NAL unit. - // It may be negative if the NAL unit started in the previously consumed data. - int lengthToNalUnit = nalUnitOffset - offset; - if (lengthToNalUnit > 0) { - nalUnitData(dataArray, offset, nalUnitOffset); - } - int bytesWrittenPastPosition = limit - nalUnitOffset; - long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; - // Indicate the end of the previous NAL unit. If the length to the start of the next unit - // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes - // when notifying that the unit has ended. - endNalUnit(absolutePosition, bytesWrittenPastPosition, - lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); - // Indicate the start of the next NAL unit. - startNalUnit(absolutePosition, nalUnitType, pesTimeUs); - // Continue scanning the data. - offset = nalUnitOffset + 3; - } - } - } - - @Override - public void packetFinished() { - // Do nothing. - } - - private void startNalUnit(long position, int nalUnitType, long pesTimeUs) { - if (!hasOutputFormat || sampleReader.needsSpsPps()) { - sps.startNalUnit(nalUnitType); - pps.startNalUnit(nalUnitType); - } - sei.startNalUnit(nalUnitType); - sampleReader.startNalUnit(position, nalUnitType, pesTimeUs); - } - - private void nalUnitData(byte[] dataArray, int offset, int limit) { - if (!hasOutputFormat || sampleReader.needsSpsPps()) { - sps.appendToNalUnit(dataArray, offset, limit); - pps.appendToNalUnit(dataArray, offset, limit); - } - sei.appendToNalUnit(dataArray, offset, limit); - sampleReader.appendToNalUnit(dataArray, offset, limit); - } - - private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { - if (!hasOutputFormat || sampleReader.needsSpsPps()) { - sps.endNalUnit(discardPadding); - pps.endNalUnit(discardPadding); - if (!hasOutputFormat) { - if (sps.isCompleted() && pps.isCompleted()) { - List initializationData = new ArrayList<>(); - initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); - initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); - NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps)); - NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps)); - output.format(MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H264, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, spsData.width, - spsData.height, initializationData, MediaFormat.NO_VALUE, - spsData.pixelWidthAspectRatio)); - hasOutputFormat = true; - sampleReader.putSps(spsData); - sampleReader.putPps(ppsData); - sps.reset(); - pps.reset(); - } - } else if (sps.isCompleted()) { - NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(unescape(sps)); - sampleReader.putSps(spsData); - sps.reset(); - } else if (pps.isCompleted()) { - NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(unescape(pps)); - sampleReader.putPps(ppsData); - pps.reset(); - } - } - if (sei.endNalUnit(discardPadding)) { - int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength); - seiWrapper.reset(sei.nalData, unescapedLength); - seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. - seiReader.consume(pesTimeUs, seiWrapper); - } - sampleReader.endNalUnit(position, offset); - } - - private static ParsableBitArray unescape(NalUnitTargetBuffer buffer) { - int length = NalUnitUtil.unescapeStream(buffer.nalData, buffer.nalLength); - ParsableBitArray bitArray = new ParsableBitArray(buffer.nalData, length); - bitArray.skipBits(32); // NAL header - return bitArray; - } - - /** - * Consumes a stream of NAL units and outputs samples. - */ - private static final class SampleReader { - - private static final int DEFAULT_BUFFER_SIZE = 128; - - private static final int NAL_UNIT_TYPE_NON_IDR = 1; // Coded slice of a non-IDR picture - private static final int NAL_UNIT_TYPE_PARTITION_A = 2; // Coded slice data partition A - private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture - private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter - - private final TrackOutput output; - private final boolean allowNonIdrKeyframes; - private final boolean detectAccessUnits; - private final ParsableBitArray scratch; - private final SparseArray sps; - private final SparseArray pps; - - private byte[] buffer; - private int bufferLength; - - // Per NAL unit state. A sample consists of one or more NAL units. - private int nalUnitType; - private long nalUnitStartPosition; - private boolean isFilling; - private long nalUnitTimeUs; - private SliceHeaderData previousSliceHeader; - private SliceHeaderData sliceHeader; - - // Per sample state that gets reset at the start of each sample. - private boolean readingSample; - private long samplePosition; - private long sampleTimeUs; - private boolean sampleIsKeyframe; - - public SampleReader(TrackOutput output, boolean allowNonIdrKeyframes, - boolean detectAccessUnits) { - this.output = output; - this.allowNonIdrKeyframes = allowNonIdrKeyframes; - this.detectAccessUnits = detectAccessUnits; - sps = new SparseArray<>(); - pps = new SparseArray<>(); - previousSliceHeader = new SliceHeaderData(); - sliceHeader = new SliceHeaderData(); - scratch = new ParsableBitArray(); - buffer = new byte[DEFAULT_BUFFER_SIZE]; - reset(); - } - - public boolean needsSpsPps() { - return detectAccessUnits; - } - - public void putSps(NalUnitUtil.SpsData spsData) { - sps.append(spsData.seqParameterSetId, spsData); - } - - public void putPps(NalUnitUtil.PpsData ppsData) { - pps.append(ppsData.picParameterSetId, ppsData); - } - - public void reset() { - isFilling = false; - readingSample = false; - sliceHeader.clear(); - } - - public void startNalUnit(long position, int type, long pesTimeUs) { - nalUnitType = type; - nalUnitTimeUs = pesTimeUs; - nalUnitStartPosition = position; - if ((allowNonIdrKeyframes && nalUnitType == NAL_UNIT_TYPE_NON_IDR) - || (detectAccessUnits && (nalUnitType == NAL_UNIT_TYPE_IDR - || nalUnitType == NAL_UNIT_TYPE_NON_IDR - || nalUnitType == NAL_UNIT_TYPE_PARTITION_A))) { - // Store the previous header and prepare to populate the new one. - SliceHeaderData newSliceHeader = previousSliceHeader; - previousSliceHeader = sliceHeader; - sliceHeader = newSliceHeader; - sliceHeader.clear(); - bufferLength = 0; - isFilling = true; - } - } - - /** - * Invoked to pass stream data. The data passed should not include the 3 byte start code. - * - * @param data Holds the data being passed. - * @param offset The offset of the data in {@code data}. - * @param limit The limit (exclusive) of the data in {@code data}. - */ - public void appendToNalUnit(byte[] data, int offset, int limit) { - if (!isFilling) { - return; - } - int readLength = limit - offset; - if (buffer.length < bufferLength + readLength) { - buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2); - } - System.arraycopy(data, offset, buffer, bufferLength, readLength); - bufferLength += readLength; - - scratch.reset(buffer, bufferLength); - if (scratch.bitsLeft() < 8) { - return; - } - scratch.skipBits(1); // forbidden_zero_bit - int nalRefIdc = scratch.readBits(2); - scratch.skipBits(5); // nal_unit_type - - // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013) - // subsection 7.3.3. - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - scratch.readUnsignedExpGolombCodedInt(); // first_mb_in_slice - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - int sliceType = scratch.readUnsignedExpGolombCodedInt(); - if (!detectAccessUnits) { - // There are AUDs in the stream so the rest of the header can be ignored. - isFilling = false; - sliceHeader.setSliceType(sliceType); - return; - } - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - int picParameterSetId = scratch.readUnsignedExpGolombCodedInt(); - if (pps.indexOfKey(picParameterSetId) < 0) { - // We have not seen the PPS yet, so don't try to parse the slice header. - isFilling = false; - return; - } - NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId); - NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId); - if (spsData.separateColorPlaneFlag) { - if (scratch.bitsLeft() < 2) { - return; - } - scratch.skipBits(2); // colour_plane_id - } - if (scratch.bitsLeft() < spsData.frameNumLength) { - return; - } - boolean fieldPicFlag = false; - boolean bottomFieldFlagPresent = false; - boolean bottomFieldFlag = false; - int frameNum = scratch.readBits(spsData.frameNumLength); - if (!spsData.frameMbsOnlyFlag) { - if (scratch.bitsLeft() < 1) { - return; - } - fieldPicFlag = scratch.readBit(); - if (fieldPicFlag) { - if (scratch.bitsLeft() < 1) { - return; - } - bottomFieldFlag = scratch.readBit(); - bottomFieldFlagPresent = true; - } - } - boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR; - int idrPicId = 0; - if (idrPicFlag) { - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - idrPicId = scratch.readUnsignedExpGolombCodedInt(); - } - int picOrderCntLsb = 0; - int deltaPicOrderCntBottom = 0; - int deltaPicOrderCnt0 = 0; - int deltaPicOrderCnt1 = 0; - if (spsData.picOrderCountType == 0) { - if (scratch.bitsLeft() < spsData.picOrderCntLsbLength) { - return; - } - picOrderCntLsb = scratch.readBits(spsData.picOrderCntLsbLength); - if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - deltaPicOrderCntBottom = scratch.readSignedExpGolombCodedInt(); - } - } else if (spsData.picOrderCountType == 1 - && !spsData.deltaPicOrderAlwaysZeroFlag) { - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - deltaPicOrderCnt0 = scratch.readSignedExpGolombCodedInt(); - if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { - if (!scratch.canReadExpGolombCodedNum()) { - return; - } - deltaPicOrderCnt1 = scratch.readSignedExpGolombCodedInt(); - } - } - sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag, - bottomFieldFlagPresent, bottomFieldFlag, idrPicFlag, idrPicId, picOrderCntLsb, - deltaPicOrderCntBottom, deltaPicOrderCnt0, deltaPicOrderCnt1); - isFilling = false; - } - - public void endNalUnit(long position, int offset) { - if (nalUnitType == NAL_UNIT_TYPE_AUD - || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) { - // If the NAL unit ending is the start of a new sample, output the previous one. - if (readingSample) { - int nalUnitLength = (int) (position - nalUnitStartPosition); - outputSample(offset + nalUnitLength); - } - samplePosition = nalUnitStartPosition; - sampleTimeUs = nalUnitTimeUs; - sampleIsKeyframe = false; - readingSample = true; - } - sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes - && nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice()); - } - - private void outputSample(int offset) { - int flags = sampleIsKeyframe ? C.SAMPLE_FLAG_SYNC : 0; - int size = (int) (nalUnitStartPosition - samplePosition); - output.sampleMetadata(sampleTimeUs, flags, size, offset, null); - } - - private static final class SliceHeaderData { - - private static final int SLICE_TYPE_I = 2; - private static final int SLICE_TYPE_ALL_I = 7; - - private boolean isComplete; - private boolean hasSliceType; - - private SpsData spsData; - private int nalRefIdc; - private int sliceType; - private int frameNum; - private int picParameterSetId; - private boolean fieldPicFlag; - private boolean bottomFieldFlagPresent; - private boolean bottomFieldFlag; - private boolean idrPicFlag; - private int idrPicId; - private int picOrderCntLsb; - private int deltaPicOrderCntBottom; - private int deltaPicOrderCnt0; - private int deltaPicOrderCnt1; - - public void clear() { - hasSliceType = false; - isComplete = false; - } - - public void setSliceType(int sliceType) { - this.sliceType = sliceType; - hasSliceType = true; - } - - public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum, - int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent, - boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb, - int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) { - this.spsData = spsData; - this.nalRefIdc = nalRefIdc; - this.sliceType = sliceType; - this.frameNum = frameNum; - this.picParameterSetId = picParameterSetId; - this.fieldPicFlag = fieldPicFlag; - this.bottomFieldFlagPresent = bottomFieldFlagPresent; - this.bottomFieldFlag = bottomFieldFlag; - this.idrPicFlag = idrPicFlag; - this.idrPicId = idrPicId; - this.picOrderCntLsb = picOrderCntLsb; - this.deltaPicOrderCntBottom = deltaPicOrderCntBottom; - this.deltaPicOrderCnt0 = deltaPicOrderCnt0; - this.deltaPicOrderCnt1 = deltaPicOrderCnt1; - isComplete = true; - hasSliceType = true; - } - - public boolean isISlice() { - return hasSliceType && (sliceType == SLICE_TYPE_ALL_I || sliceType == SLICE_TYPE_I); - } - - private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) { - // See ISO 14496-10 subsection 7.4.1.2.4. - return isComplete && (!other.isComplete || frameNum != other.frameNum - || picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag - || (bottomFieldFlagPresent && other.bottomFieldFlagPresent - && bottomFieldFlag != other.bottomFieldFlag) - || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) - || (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0 - && (picOrderCntLsb != other.picOrderCntLsb - || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) - || (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1 - && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 - || deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) - || idrPicFlag != other.idrPicFlag - || (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); - } - - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Id3Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Id3Reader.java deleted file mode 100755 index 627a8b9f988..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/Id3Reader.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * Parses ID3 data and extracts individual text information frames. - */ -/* package */ final class Id3Reader extends ElementaryStreamReader { - - private static final int ID3_HEADER_SIZE = 10; - - private final ParsableByteArray id3Header; - - // State that should be reset on seek. - private boolean writingSample; - - // Per sample state that gets reset at the start of each sample. - private long sampleTimeUs; - private int sampleSize; - private int sampleBytesRead; - - public Id3Reader(TrackOutput output) { - super(output); - output.format(MediaFormat.createId3Format()); - id3Header = new ParsableByteArray(ID3_HEADER_SIZE); - } - - @Override - public void seek() { - writingSample = false; - } - - @Override - public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { - if (!dataAlignmentIndicator) { - return; - } - writingSample = true; - sampleTimeUs = pesTimeUs; - sampleSize = 0; - sampleBytesRead = 0; - } - - @Override - public void consume(ParsableByteArray data) { - if (!writingSample) { - return; - } - int bytesAvailable = data.bytesLeft(); - if (sampleBytesRead < ID3_HEADER_SIZE) { - // We're still reading the ID3 header. - int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead); - System.arraycopy(data.data, data.getPosition(), id3Header.data, sampleBytesRead, - headerBytesAvailable); - if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) { - // We've finished reading the ID3 header. Extract the sample size. - id3Header.setPosition(6); // 'ID3' (3) + version (2) + flags (1) - sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt(); - } - } - // Write data to the output. - int bytesToWrite = Math.min(bytesAvailable, sampleSize - sampleBytesRead); - output.sampleData(data, bytesToWrite); - sampleBytesRead += bytesToWrite; - } - - @Override - public void packetFinished() { - if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) { - return; - } - output.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); - writingSample = false; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PtsTimestampAdjuster.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PtsTimestampAdjuster.java deleted file mode 100755 index 0e55a3a8296..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PtsTimestampAdjuster.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.C; - -/** - * Scales and adjusts MPEG-2 TS presentation timestamps, taking into account an initial offset and - * timestamp rollover. - */ -public final class PtsTimestampAdjuster { - - /** - * A special {@code firstSampleTimestampUs} value indicating that presentation timestamps should - * not be offset. - */ - public static final long DO_NOT_OFFSET = Long.MAX_VALUE; - - /** - * The value one greater than the largest representable (33 bit) presentation timestamp. - */ - private static final long MAX_PTS_PLUS_ONE = 0x200000000L; - - private final long firstSampleTimestampUs; - - private long timestampOffsetUs; - - // Volatile to allow isInitialized to be called on a different thread to adjustTimestamp. - private volatile long lastPts; - - /** - * @param firstSampleTimestampUs The desired result of the first call to - * {@link #adjustTimestamp(long)}, or {@link #DO_NOT_OFFSET} if presentation timestamps - * should not be offset. - */ - public PtsTimestampAdjuster(long firstSampleTimestampUs) { - this.firstSampleTimestampUs = firstSampleTimestampUs; - lastPts = Long.MIN_VALUE; - } - - /** - * Resets the instance to its initial state. - */ - public void reset() { - lastPts = Long.MIN_VALUE; - } - - /** - * Whether this adjuster has been initialized with a first MPEG-2 TS presentation timestamp. - */ - public boolean isInitialized() { - return lastPts != Long.MIN_VALUE; - } - - /** - * Scales and offsets an MPEG-2 TS presentation timestamp. - * - * @param pts The MPEG-2 TS presentation timestamp. - * @return The adjusted timestamp in microseconds. - */ - public long adjustTimestamp(long pts) { - if (lastPts != Long.MIN_VALUE) { - // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1), - // and we need to snap to the one closest to lastPts. - long closestWrapCount = (lastPts + (MAX_PTS_PLUS_ONE / 2)) / MAX_PTS_PLUS_ONE; - long ptsWrapBelow = pts + (MAX_PTS_PLUS_ONE * (closestWrapCount - 1)); - long ptsWrapAbove = pts + (MAX_PTS_PLUS_ONE * closestWrapCount); - pts = Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts) - ? ptsWrapBelow : ptsWrapAbove; - } - // Calculate the corresponding timestamp. - long timeUs = ptsToUs(pts); - if (firstSampleTimestampUs != DO_NOT_OFFSET && lastPts == Long.MIN_VALUE) { - // Calculate the timestamp offset. - timestampOffsetUs = firstSampleTimestampUs - timeUs; - } - // Record the adjusted PTS to adjust for wraparound next time. - lastPts = pts; - return timeUs + timestampOffsetUs; - } - - /** - * Converts a value in MPEG-2 timestamp units to the corresponding value in microseconds. - * - * @param pts A value in MPEG-2 timestamp units. - * @return The corresponding value in microseconds. - */ - public static long ptsToUs(long pts) { - return (pts * C.MICROS_PER_SECOND) / 90000; - } - - /** - * Converts a value in microseconds to the corresponding values in MPEG-2 timestamp units. - * - * @param us A value in microseconds. - * @return The corresponding value in MPEG-2 timestamp units. - */ - public static long usToPts(long us) { - return (us * 90000) / C.MICROS_PER_SECOND; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/SeiReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/SeiReader.java deleted file mode 100755 index 52629314eff..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/SeiReader.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.text.eia608.Eia608Parser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}. - */ -// TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that -// a sample with an earlier timestamp won't be added to it. -/* package */ final class SeiReader { - - private final TrackOutput output; - - public SeiReader(TrackOutput output) { - this.output = output; - output.format(MediaFormat.createTextFormat(null, MimeTypes.APPLICATION_EIA608, - MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, null)); - } - - public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { - int b; - while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { - // Parse payload type. - int payloadType = 0; - do { - b = seiBuffer.readUnsignedByte(); - payloadType += b; - } while (b == 0xFF); - // Parse payload size. - int payloadSize = 0; - do { - b = seiBuffer.readUnsignedByte(); - payloadSize += b; - } while (b == 0xFF); - // Process the payload. - if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { - output.sampleData(seiBuffer, payloadSize); - output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null); - } else { - seiBuffer.skipBytes(payloadSize); - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/TsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/TsExtractor.java deleted file mode 100755 index d13a19aa822..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/TsExtractor.java +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.ts; - -import android.util.Log; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.DummyTrackOutput; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * Facilitates the extraction of data from the MPEG-2 TS container format. - */ -public final class TsExtractor implements Extractor { - - public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1; - public static final int WORKAROUND_IGNORE_AAC_STREAM = 2; - public static final int WORKAROUND_IGNORE_H264_STREAM = 4; - public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8; - - private static final String TAG = "TsExtractor"; - - private static final int TS_PACKET_SIZE = 188; - private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. - private static final int TS_PAT_PID = 0; - - private static final int TS_STREAM_TYPE_MPA = 0x03; - private static final int TS_STREAM_TYPE_MPA_LSF = 0x04; - private static final int TS_STREAM_TYPE_AAC = 0x0F; - private static final int TS_STREAM_TYPE_AC3 = 0x81; - private static final int TS_STREAM_TYPE_DTS = 0x8A; - private static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; - private static final int TS_STREAM_TYPE_E_AC3 = 0x87; - private static final int TS_STREAM_TYPE_H262 = 0x02; - private static final int TS_STREAM_TYPE_H264 = 0x1B; - private static final int TS_STREAM_TYPE_H265 = 0x24; - private static final int TS_STREAM_TYPE_ID3 = 0x15; - private static final int TS_STREAM_TYPE_EIA608 = 0x100; // 0xFF + 1 - - private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); - private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); - private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); - - private final PtsTimestampAdjuster ptsTimestampAdjuster; - private final int workaroundFlags; - private final ParsableByteArray tsPacketBuffer; - private final ParsableBitArray tsScratch; - /* package */ final SparseArray tsPayloadReaders; // Indexed by pid - /* package */ final SparseBooleanArray streamTypes; - - // Accessed only by the loading thread. - private ExtractorOutput output; - /* package */ Id3Reader id3Reader; - - public TsExtractor() { - this(new PtsTimestampAdjuster(0)); - } - - public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { - this(ptsTimestampAdjuster, 0); - } - - public TsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster, int workaroundFlags) { - this.ptsTimestampAdjuster = ptsTimestampAdjuster; - this.workaroundFlags = workaroundFlags; - tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); - tsScratch = new ParsableBitArray(new byte[3]); - tsPayloadReaders = new SparseArray<>(); - tsPayloadReaders.put(TS_PAT_PID, new PatReader()); - streamTypes = new SparseBooleanArray(); - } - - // Extractor implementation. - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - byte[] scratch = new byte[1]; - for (int i = 0; i < 5; i++) { - input.peekFully(scratch, 0, 1); - if ((scratch[0] & 0xFF) != 0x47) { - return false; - } - input.advancePeekPosition(TS_PACKET_SIZE - 1); - } - return true; - } - - @Override - public void init(ExtractorOutput output) { - this.output = output; - output.seekMap(SeekMap.UNSEEKABLE); - } - - @Override - public void seek() { - ptsTimestampAdjuster.reset(); - for (int i = 0; i < tsPayloadReaders.size(); i++) { - tsPayloadReaders.valueAt(i).seek(); - } - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - if (!input.readFully(tsPacketBuffer.data, 0, TS_PACKET_SIZE, true)) { - return RESULT_END_OF_INPUT; - } - - // Note: see ISO/IEC 13818-1, section 2.4.3.2 for detailed information on the format of - // the header. - tsPacketBuffer.setPosition(0); - tsPacketBuffer.setLimit(TS_PACKET_SIZE); - int syncByte = tsPacketBuffer.readUnsignedByte(); - if (syncByte != TS_SYNC_BYTE) { - return RESULT_CONTINUE; - } - - tsPacketBuffer.readBytes(tsScratch, 3); - tsScratch.skipBits(1); // transport_error_indicator - boolean payloadUnitStartIndicator = tsScratch.readBit(); - tsScratch.skipBits(1); // transport_priority - int pid = tsScratch.readBits(13); - tsScratch.skipBits(2); // transport_scrambling_control - boolean adaptationFieldExists = tsScratch.readBit(); - boolean payloadExists = tsScratch.readBit(); - // Last 4 bits of scratch are skipped: continuity_counter - - // Skip the adaptation field. - if (adaptationFieldExists) { - int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); - tsPacketBuffer.skipBytes(adaptationFieldLength); - } - - // Read the payload. - if (payloadExists) { - TsPayloadReader payloadReader = tsPayloadReaders.get(pid); - if (payloadReader != null) { - payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output); - } - } - - return RESULT_CONTINUE; - } - - // Internals. - - /** - * Parses TS packet payload data. - */ - private abstract static class TsPayloadReader { - - /** - * Notifies the reader that a seek has occurred. - *

        - * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, boolean, ExtractorOutput)} will not be a continuation of - * the data that was previously passed. Hence the reader should reset any internal state. - */ - public abstract void seek(); - - /** - * Consumes the payload of a TS packet. - * - * @param data The TS packet. The position will be set to the start of the payload. - * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. - * @param output The output to which parsed data should be written. - */ - public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output); - - } - - /** - * Parses Program Association Table data. - */ - private class PatReader extends TsPayloadReader { - - private final ParsableBitArray patScratch; - - public PatReader() { - patScratch = new ParsableBitArray(new byte[4]); - } - - @Override - public void seek() { - // Do nothing. - } - - @Override - public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output) { - // Skip pointer. - if (payloadUnitStartIndicator) { - int pointerField = data.readUnsignedByte(); - data.skipBytes(pointerField); - } - - data.readBytes(patScratch, 3); - patScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2) - int sectionLength = patScratch.readBits(12); - // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), - // section_number (8), last_section_number (8) - data.skipBytes(5); - - int programCount = (sectionLength - 9) / 4; - for (int i = 0; i < programCount; i++) { - data.readBytes(patScratch, 4); - int programNumber = patScratch.readBits(16); - patScratch.skipBits(3); // reserved (3) - if (programNumber == 0) { - patScratch.skipBits(13); // network_PID (13) - } else { - int pid = patScratch.readBits(13); - tsPayloadReaders.put(pid, new PmtReader()); - } - } - - // Skip CRC_32. - } - - } - - /** - * Parses Program Map Table. - */ - private class PmtReader extends TsPayloadReader { - - private final ParsableBitArray pmtScratch; - private final ParsableByteArray sectionData; - - private int sectionLength; - private int sectionBytesRead; - - public PmtReader() { - pmtScratch = new ParsableBitArray(new byte[5]); - sectionData = new ParsableByteArray(); - } - - @Override - public void seek() { - // Do nothing. - } - - @Override - public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output) { - if (payloadUnitStartIndicator) { - // Skip pointer. - int pointerField = data.readUnsignedByte(); - data.skipBytes(pointerField); - - // Note: see ISO/IEC 13818-1, section 2.4.4.8 for detailed information on the format of - // the header. - data.readBytes(pmtScratch, 3); - pmtScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), 0 (1), reserved (2) - sectionLength = pmtScratch.readBits(12); - - if (sectionData.capacity() < sectionLength) { - sectionData.reset(new byte[sectionLength], sectionLength); - } else { - sectionData.reset(); - sectionData.setLimit(sectionLength); - } - } - - int bytesToRead = Math.min(data.bytesLeft(), sectionLength - sectionBytesRead); - data.readBytes(sectionData.data, sectionBytesRead, bytesToRead); - sectionBytesRead += bytesToRead; - if (sectionBytesRead < sectionLength) { - // Not yet fully read. - return; - } - - // program_number (16), reserved (2), version_number (5), current_next_indicator (1), - // section_number (8), last_section_number (8), reserved (3), PCR_PID (13) - // Skip the rest of the PMT header. - sectionData.skipBytes(7); - - // Read program_info_length. - sectionData.readBytes(pmtScratch, 2); - pmtScratch.skipBits(4); - int programInfoLength = pmtScratch.readBits(12); - - // Skip the descriptors. - sectionData.skipBytes(programInfoLength); - - if (id3Reader == null) { - // Setup an ID3 track regardless of whether there's a corresponding entry, in case one - // appears intermittently during playback. See b/20261500. - id3Reader = new Id3Reader(output.track(TS_STREAM_TYPE_ID3)); - } - - int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */ - - programInfoLength - 4 /* CRC length */; - while (remainingEntriesLength > 0) { - sectionData.readBytes(pmtScratch, 5); - int streamType = pmtScratch.readBits(8); - pmtScratch.skipBits(3); // reserved - int elementaryPid = pmtScratch.readBits(13); - pmtScratch.skipBits(4); // reserved - int esInfoLength = pmtScratch.readBits(12); // ES_info_length - if (streamType == 0x06) { - // Read descriptors in PES packets containing private data. - streamType = readPrivateDataStreamType(sectionData, esInfoLength); - } else { - sectionData.skipBytes(esInfoLength); - } - remainingEntriesLength -= esInfoLength + 5; - if (streamTypes.get(streamType)) { - continue; - } - - ElementaryStreamReader pesPayloadReader; - switch (streamType) { - case TS_STREAM_TYPE_MPA: - pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA)); - break; - case TS_STREAM_TYPE_MPA_LSF: - pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF)); - break; - case TS_STREAM_TYPE_AAC: - pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null - : new AdtsReader(output.track(TS_STREAM_TYPE_AAC), new DummyTrackOutput()); - break; - case TS_STREAM_TYPE_AC3: - pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_AC3), false); - break; - case TS_STREAM_TYPE_E_AC3: - pesPayloadReader = new Ac3Reader(output.track(TS_STREAM_TYPE_E_AC3), true); - break; - case TS_STREAM_TYPE_DTS: - case TS_STREAM_TYPE_HDMV_DTS: - pesPayloadReader = new DtsReader(output.track(TS_STREAM_TYPE_DTS)); - break; - case TS_STREAM_TYPE_H262: - pesPayloadReader = new H262Reader(output.track(TS_STREAM_TYPE_H262)); - break; - case TS_STREAM_TYPE_H264: - pesPayloadReader = (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 ? null - : new H264Reader(output.track(TS_STREAM_TYPE_H264), - new SeiReader(output.track(TS_STREAM_TYPE_EIA608)), - (workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0, - (workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0); - break; - case TS_STREAM_TYPE_H265: - pesPayloadReader = new H265Reader(output.track(TS_STREAM_TYPE_H265), - new SeiReader(output.track(TS_STREAM_TYPE_EIA608))); - break; - case TS_STREAM_TYPE_ID3: - pesPayloadReader = id3Reader; - break; - default: - pesPayloadReader = null; - break; - } - - if (pesPayloadReader != null) { - streamTypes.put(streamType, true); - tsPayloadReaders.put(elementaryPid, - new PesReader(pesPayloadReader, ptsTimestampAdjuster)); - } - } - - output.endTracks(); - } - - /** - * Returns the stream type read from a registration descriptor in private data, or -1 if no - * stream type is present. Sets {@code data}'s position to the end of the descriptors. - * - * @param data A buffer with its position set to the start of the first descriptor. - * @param length The length of descriptors to read from the current position in {@code data}. - * @return The stream type read from a registration descriptor in private data, or -1 if no - * stream type is present. - */ - private int readPrivateDataStreamType(ParsableByteArray data, int length) { - int streamType = -1; - int descriptorsEndPosition = data.getPosition() + length; - while (data.getPosition() < descriptorsEndPosition) { - int descriptorTag = data.readUnsignedByte(); - int descriptorLength = data.readUnsignedByte(); - if (descriptorTag == 0x05) { // registration_descriptor - long formatIdentifier = data.readUnsignedInt(); - if (formatIdentifier == AC3_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_AC3; - } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_E_AC3; - } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { - streamType = TS_STREAM_TYPE_H265; - } - break; - } else if (descriptorTag == 0x6A) { // AC-3_descriptor in DVB (ETSI EN 300 468) - streamType = TS_STREAM_TYPE_AC3; - } else if (descriptorTag == 0x7A) { // enhanced_AC-3_descriptor - streamType = TS_STREAM_TYPE_E_AC3; - } else if (descriptorTag == 0x7B) { // DTS_descriptor - streamType = TS_STREAM_TYPE_DTS; - } - - data.skipBytes(descriptorLength); - } - data.setPosition(descriptorsEndPosition); - return streamType; - } - - } - - /** - * Parses PES packet data and extracts samples. - */ - private static final class PesReader extends TsPayloadReader { - - private static final int STATE_FINDING_HEADER = 0; - private static final int STATE_READING_HEADER = 1; - private static final int STATE_READING_HEADER_EXTENSION = 2; - private static final int STATE_READING_BODY = 3; - - private static final int HEADER_SIZE = 9; - private static final int MAX_HEADER_EXTENSION_SIZE = 10; - private static final int PES_SCRATCH_SIZE = 10; // max(HEADER_SIZE, MAX_HEADER_EXTENSION_SIZE) - - private final ElementaryStreamReader pesPayloadReader; - private final PtsTimestampAdjuster ptsTimestampAdjuster; - private final ParsableBitArray pesScratch; - - private int state; - private int bytesRead; - - private boolean ptsFlag; - private boolean dtsFlag; - private boolean seenFirstDts; - private int extendedHeaderLength; - private int payloadSize; - private boolean dataAlignmentIndicator; - private long timeUs; - - public PesReader(ElementaryStreamReader pesPayloadReader, - PtsTimestampAdjuster ptsTimestampAdjuster) { - this.pesPayloadReader = pesPayloadReader; - this.ptsTimestampAdjuster = ptsTimestampAdjuster; - pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); - state = STATE_FINDING_HEADER; - } - - @Override - public void seek() { - state = STATE_FINDING_HEADER; - bytesRead = 0; - seenFirstDts = false; - pesPayloadReader.seek(); - } - - @Override - public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output) { - if (payloadUnitStartIndicator) { - switch (state) { - case STATE_FINDING_HEADER: - case STATE_READING_HEADER: - // Expected. - break; - case STATE_READING_HEADER_EXTENSION: - Log.w(TAG, "Unexpected start indicator reading extended header"); - break; - case STATE_READING_BODY: - // If payloadSize == -1 then the length of the previous packet was unspecified, and so - // we only know that it's finished now that we've seen the start of the next one. This - // is expected. If payloadSize != -1, then the length of the previous packet was known, - // but we didn't receive that amount of data. This is not expected. - if (payloadSize != -1) { - Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); - } - // Either way, notify the reader that it has now finished. - pesPayloadReader.packetFinished(); - break; - } - setState(STATE_READING_HEADER); - } - - while (data.bytesLeft() > 0) { - switch (state) { - case STATE_FINDING_HEADER: - data.skipBytes(data.bytesLeft()); - break; - case STATE_READING_HEADER: - if (continueRead(data, pesScratch.data, HEADER_SIZE)) { - setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); - } - break; - case STATE_READING_HEADER_EXTENSION: - int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); - // Read as much of the extended header as we're interested in, and skip the rest. - if (continueRead(data, pesScratch.data, readLength) - && continueRead(data, null, extendedHeaderLength)) { - parseHeaderExtension(); - pesPayloadReader.packetStarted(timeUs, dataAlignmentIndicator); - setState(STATE_READING_BODY); - } - break; - case STATE_READING_BODY: - readLength = data.bytesLeft(); - int padding = payloadSize == -1 ? 0 : readLength - payloadSize; - if (padding > 0) { - readLength -= padding; - data.setLimit(data.getPosition() + readLength); - } - pesPayloadReader.consume(data); - if (payloadSize != -1) { - payloadSize -= readLength; - if (payloadSize == 0) { - pesPayloadReader.packetFinished(); - setState(STATE_READING_HEADER); - } - } - break; - } - } - } - - private void setState(int state) { - this.state = state; - bytesRead = 0; - } - - /** - * Continues a read from the provided {@code source} into a given {@code target}. It's assumed - * that the data should be written into {@code target} starting from an offset of zero. - * - * @param source The source from which to read. - * @param target The target into which data is to be read, or {@code null} to skip. - * @param targetLength The target length of the read. - * @return Whether the target length has been reached. - */ - private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { - int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); - if (bytesToRead <= 0) { - return true; - } else if (target == null) { - source.skipBytes(bytesToRead); - } else { - source.readBytes(target, bytesRead, bytesToRead); - } - bytesRead += bytesToRead; - return bytesRead == targetLength; - } - - private boolean parseHeader() { - // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of - // the header. - pesScratch.setPosition(0); - int startCodePrefix = pesScratch.readBits(24); - if (startCodePrefix != 0x000001) { - Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); - payloadSize = -1; - return false; - } - - pesScratch.skipBits(8); // stream_id. - int packetLength = pesScratch.readBits(16); - pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1) - dataAlignmentIndicator = pesScratch.readBit(); - pesScratch.skipBits(2); // copyright (1), original_or_copy (1) - ptsFlag = pesScratch.readBit(); - dtsFlag = pesScratch.readBit(); - // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), - // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) - pesScratch.skipBits(6); - extendedHeaderLength = pesScratch.readBits(8); - - if (packetLength == 0) { - payloadSize = -1; - } else { - payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */ - - HEADER_SIZE - extendedHeaderLength; - } - return true; - } - - private void parseHeaderExtension() { - pesScratch.setPosition(0); - timeUs = C.UNKNOWN_TIME_US; - if (ptsFlag) { - pesScratch.skipBits(4); // '0010' or '0011' - long pts = (long) pesScratch.readBits(3) << 30; - pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBits(15) << 15; - pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBits(15); - pesScratch.skipBits(1); // marker_bit - if (!seenFirstDts && dtsFlag) { - pesScratch.skipBits(4); // '0011' - long dts = (long) pesScratch.readBits(3) << 30; - pesScratch.skipBits(1); // marker_bit - dts |= pesScratch.readBits(15) << 15; - pesScratch.skipBits(1); // marker_bit - dts |= pesScratch.readBits(15); - pesScratch.skipBits(1); // marker_bit - // Subsequent PES packets may have earlier presentation timestamps than this one, but they - // should all be greater than or equal to this packet's decode timestamp. We feed the - // decode timestamp to the adjuster here so that in the case that this is the first to be - // fed, the adjuster will be able to compute an offset to apply such that the adjusted - // presentation timestamps of all future packets are non-negative. - ptsTimestampAdjuster.adjustTimestamp(dts); - seenFirstDts = true; - } - timeUs = ptsTimestampAdjuster.adjustTimestamp(pts); - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavExtractor.java deleted file mode 100755 index ffb1010c290..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavExtractor.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.wav; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.io.IOException; - -/** {@link Extractor} to extract samples from a WAV byte stream. */ -public final class WavExtractor implements Extractor, SeekMap { - - /** Arbitrary maximum input size of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */ - private static final int MAX_INPUT_SIZE = 32 * 1024; - - private ExtractorOutput extractorOutput; - private TrackOutput trackOutput; - private WavHeader wavHeader; - private int bytesPerFrame; - private int pendingBytes; - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - return WavHeaderReader.peek(input) != null; - } - - @Override - public void init(ExtractorOutput output) { - extractorOutput = output; - trackOutput = output.track(0); - wavHeader = null; - output.endTracks(); - } - - @Override - public void seek() { - pendingBytes = 0; - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - - if (wavHeader == null) { - wavHeader = WavHeaderReader.peek(input); - if (wavHeader == null) { - // Someone tried to read a non-WAV or unsupported WAV without sniffing first. - throw new ParserException("Error initializing WavHeader. Did you sniff first?"); - } - bytesPerFrame = wavHeader.getBytesPerFrame(); - } - - // If we haven't read in the data start and size, read and store them. - if (!wavHeader.hasDataBounds()) { - WavHeaderReader.skipToData(input, wavHeader); - - trackOutput.format( - MediaFormat.createAudioFormat( - null, - MimeTypes.AUDIO_RAW, - wavHeader.getBitrate(), - MAX_INPUT_SIZE, - wavHeader.getDurationUs(), - wavHeader.getNumChannels(), - wavHeader.getSampleRateHz(), - null, - null, - wavHeader.getEncoding())); - extractorOutput.seekMap(this); - } - - int bytesAppended = trackOutput.sampleData(input, MAX_INPUT_SIZE - pendingBytes, true); - - if (bytesAppended != RESULT_END_OF_INPUT) { - pendingBytes += bytesAppended; - } - - // Round down the pending number of bytes to the nearest frame. - int frameBytes = pendingBytes / bytesPerFrame * bytesPerFrame; - if (frameBytes > 0) { - long sampleStartPosition = input.getPosition() - pendingBytes; - pendingBytes -= frameBytes; - trackOutput.sampleMetadata( - wavHeader.getTimeUs(sampleStartPosition), - C.SAMPLE_FLAG_SYNC, - frameBytes, - pendingBytes, - null); - } - - if (bytesAppended == RESULT_END_OF_INPUT) { - return RESULT_END_OF_INPUT; - } - - return RESULT_CONTINUE; - } - - // SeekMap implementation. - - @Override - public boolean isSeekable() { - return true; - } - - @Override - public long getPosition(long timeUs) { - return wavHeader.getPosition(timeUs); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/Sniffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/Sniffer.java deleted file mode 100755 index 772ff71bb4e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/Sniffer.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.webm; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; - -/** - * Utility class that peeks from the input stream in order to determine whether it appears to be - * compatible input for this extractor. - */ -/* package */ final class Sniffer { - - /** - * The number of bytes to search for a valid header in {@link #sniff(ExtractorInput)}. - */ - private static final int SEARCH_LENGTH = 1024; - private static final int ID_EBML = 0x1A45DFA3; - - private final ParsableByteArray scratch; - private int peekLength; - - public Sniffer() { - scratch = new ParsableByteArray(8); - } - - /** - * @see Extractor#sniff - */ - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - long inputLength = input.getLength(); - int bytesToSearch = (int) (inputLength == C.LENGTH_UNBOUNDED || inputLength > SEARCH_LENGTH - ? SEARCH_LENGTH : inputLength); - // Find four bytes equal to ID_EBML near the start of the input. - input.peekFully(scratch.data, 0, 4); - long tag = scratch.readUnsignedInt(); - peekLength = 4; - while (tag != ID_EBML) { - if (++peekLength == bytesToSearch) { - return false; - } - input.peekFully(scratch.data, 0, 1); - tag = (tag << 8) & 0xFFFFFF00; - tag |= scratch.data[0] & 0xFF; - } - - // Read the size of the EBML header and make sure it is within the stream. - long headerSize = readUint(input); - long headerStart = peekLength; - if (headerSize == Long.MIN_VALUE - || (inputLength != C.LENGTH_UNBOUNDED && headerStart + headerSize >= inputLength)) { - return false; - } - - // Read the payload elements in the EBML header. - while (peekLength < headerStart + headerSize) { - long id = readUint(input); - if (id == Long.MIN_VALUE) { - return false; - } - long size = readUint(input); - if (size < 0 || size > Integer.MAX_VALUE) { - return false; - } - if (size != 0) { - input.advancePeekPosition((int) size); - peekLength += size; - } - } - return peekLength == headerStart + headerSize; - } - - /** - * Peeks a variable-length unsigned EBML integer from the input. - */ - private long readUint(ExtractorInput input) throws IOException, InterruptedException { - input.peekFully(scratch.data, 0, 1); - int value = scratch.data[0] & 0xFF; - if (value == 0) { - return Long.MIN_VALUE; - } - int mask = 0x80; - int length = 0; - while ((value & mask) == 0) { - mask >>= 1; - length++; - } - value &= ~mask; - input.peekFully(scratch.data, 1, length); - for (int i = 0; i < length; i++) { - value <<= 8; - value += scratch.data[i + 1] & 0xFF; - } - peekLength += length + 1; - return value; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/WebmExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/WebmExtractor.java deleted file mode 100755 index b44830fbe69..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/WebmExtractor.java +++ /dev/null @@ -1,1558 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.extractor.webm; - -import android.util.Pair; -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.extractor.ChunkIndex; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.LongArray; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.UUID; - -/** - * An extractor to facilitate data retrieval from the WebM container format. - *

        - * WebM is a subset of the EBML elements defined for Matroska. More information about EBML and - * Matroska is available here. - * More info about WebM is here. - * RFC on encrypted WebM can be found - * here. - */ -public final class WebmExtractor implements Extractor { - - private static final int BLOCK_STATE_START = 0; - private static final int BLOCK_STATE_HEADER = 1; - private static final int BLOCK_STATE_DATA = 2; - - private static final String DOC_TYPE_WEBM = "webm"; - private static final String DOC_TYPE_MATROSKA = "matroska"; - private static final String CODEC_ID_VP8 = "V_VP8"; - private static final String CODEC_ID_VP9 = "V_VP9"; - private static final String CODEC_ID_MPEG2 = "V_MPEG2"; - private static final String CODEC_ID_MPEG4_SP = "V_MPEG4/ISO/SP"; - private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP"; - private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP"; - private static final String CODEC_ID_H264 = "V_MPEG4/ISO/AVC"; - private static final String CODEC_ID_H265 = "V_MPEGH/ISO/HEVC"; - private static final String CODEC_ID_FOURCC = "V_MS/VFW/FOURCC"; - private static final String CODEC_ID_VORBIS = "A_VORBIS"; - private static final String CODEC_ID_OPUS = "A_OPUS"; - private static final String CODEC_ID_AAC = "A_AAC"; - private static final String CODEC_ID_MP3 = "A_MPEG/L3"; - private static final String CODEC_ID_AC3 = "A_AC3"; - private static final String CODEC_ID_E_AC3 = "A_EAC3"; - private static final String CODEC_ID_TRUEHD = "A_TRUEHD"; - private static final String CODEC_ID_DTS = "A_DTS"; - private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS"; - private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS"; - private static final String CODEC_ID_FLAC = "A_FLAC"; - private static final String CODEC_ID_ACM = "A_MS/ACM"; - private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT"; - private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; - private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; - private static final String CODEC_ID_PGS = "S_HDMV/PGS"; - - private static final int VORBIS_MAX_INPUT_SIZE = 8192; - private static final int OPUS_MAX_INPUT_SIZE = 5760; - private static final int MP3_MAX_INPUT_SIZE = 4096; - private static final int ENCRYPTION_IV_SIZE = 8; - private static final int TRACK_TYPE_AUDIO = 2; - private static final int UNKNOWN = -1; - - private static final int ID_EBML = 0x1A45DFA3; - private static final int ID_EBML_READ_VERSION = 0x42F7; - private static final int ID_DOC_TYPE = 0x4282; - private static final int ID_DOC_TYPE_READ_VERSION = 0x4285; - private static final int ID_SEGMENT = 0x18538067; - private static final int ID_SEGMENT_INFO = 0x1549A966; - private static final int ID_SEEK_HEAD = 0x114D9B74; - private static final int ID_SEEK = 0x4DBB; - private static final int ID_SEEK_ID = 0x53AB; - private static final int ID_SEEK_POSITION = 0x53AC; - private static final int ID_INFO = 0x1549A966; - private static final int ID_TIMECODE_SCALE = 0x2AD7B1; - private static final int ID_DURATION = 0x4489; - private static final int ID_CLUSTER = 0x1F43B675; - private static final int ID_TIME_CODE = 0xE7; - private static final int ID_SIMPLE_BLOCK = 0xA3; - private static final int ID_BLOCK_GROUP = 0xA0; - private static final int ID_BLOCK = 0xA1; - private static final int ID_BLOCK_DURATION = 0x9B; - private static final int ID_REFERENCE_BLOCK = 0xFB; - private static final int ID_TRACKS = 0x1654AE6B; - private static final int ID_TRACK_ENTRY = 0xAE; - private static final int ID_TRACK_NUMBER = 0xD7; - private static final int ID_TRACK_TYPE = 0x83; - private static final int ID_DEFAULT_DURATION = 0x23E383; - private static final int ID_CODEC_ID = 0x86; - private static final int ID_CODEC_PRIVATE = 0x63A2; - private static final int ID_CODEC_DELAY = 0x56AA; - private static final int ID_SEEK_PRE_ROLL = 0x56BB; - private static final int ID_VIDEO = 0xE0; - private static final int ID_PIXEL_WIDTH = 0xB0; - private static final int ID_PIXEL_HEIGHT = 0xBA; - private static final int ID_DISPLAY_WIDTH = 0x54B0; - private static final int ID_DISPLAY_HEIGHT = 0x54BA; - private static final int ID_DISPLAY_UNIT = 0x54B2; - private static final int ID_AUDIO = 0xE1; - private static final int ID_CHANNELS = 0x9F; - private static final int ID_AUDIO_BIT_DEPTH = 0x6264; - private static final int ID_SAMPLING_FREQUENCY = 0xB5; - private static final int ID_CONTENT_ENCODINGS = 0x6D80; - private static final int ID_CONTENT_ENCODING = 0x6240; - private static final int ID_CONTENT_ENCODING_ORDER = 0x5031; - private static final int ID_CONTENT_ENCODING_SCOPE = 0x5032; - private static final int ID_CONTENT_COMPRESSION = 0x5034; - private static final int ID_CONTENT_COMPRESSION_ALGORITHM = 0x4254; - private static final int ID_CONTENT_COMPRESSION_SETTINGS = 0x4255; - private static final int ID_CONTENT_ENCRYPTION = 0x5035; - private static final int ID_CONTENT_ENCRYPTION_ALGORITHM = 0x47E1; - private static final int ID_CONTENT_ENCRYPTION_KEY_ID = 0x47E2; - private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS = 0x47E7; - private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE = 0x47E8; - private static final int ID_CUES = 0x1C53BB6B; - private static final int ID_CUE_POINT = 0xBB; - private static final int ID_CUE_TIME = 0xB3; - private static final int ID_CUE_TRACK_POSITIONS = 0xB7; - private static final int ID_CUE_CLUSTER_POSITION = 0xF1; - private static final int ID_LANGUAGE = 0x22B59C; - - private static final int LACING_NONE = 0; - private static final int LACING_XIPH = 1; - private static final int LACING_FIXED_SIZE = 2; - private static final int LACING_EBML = 3; - - private static final int FOURCC_COMPRESSION_VC1 = 0x31435657; - - /** - * A template for the prefix that must be added to each subrip sample. The 12 byte end timecode - * starting at {@link #SUBRIP_PREFIX_END_TIMECODE_OFFSET} is set to a dummy value, and must be - * replaced with the duration of the subtitle. - *

        - * Equivalent to the UTF-8 string: "1\n00:00:00,000 --> 00:00:00,000\n". - */ - private static final byte[] SUBRIP_PREFIX = new byte[] {49, 10, 48, 48, 58, 48, 48, 58, 48, 48, - 44, 48, 48, 48, 32, 45, 45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 10}; - /** - * A special end timecode indicating that a subtitle should be displayed until the next subtitle, - * or until the end of the media in the case of the last subtitle. - *

        - * Equivalent to the UTF-8 string: " ". - */ - private static final byte[] SUBRIP_TIMECODE_EMPTY = - new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; - /** - * The byte offset of the end timecode in {@link #SUBRIP_PREFIX}. - */ - private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19; - /** - * The length in bytes of a timecode in a subrip prefix. - */ - private static final int SUBRIP_TIMECODE_LENGTH = 12; - - /** - * The length in bytes of a WAVEFORMATEX structure. - */ - private static final int WAVE_FORMAT_SIZE = 18; - /** - * Format tag indicating a WAVEFORMATEXTENSIBLE structure. - */ - private static final int WAVE_FORMAT_EXTENSIBLE = 0xFFFE; - /** - * Format tag for PCM. - */ - private static final int WAVE_FORMAT_PCM = 1; - /** - * Sub format for PCM. - */ - private static final UUID WAVE_SUBFORMAT_PCM = new UUID(0x0100000000001000L, 0x800000AA00389B71L); - - private final EbmlReader reader; - private final VarintReader varintReader; - private final SparseArray tracks; - - // Temporary arrays. - private final ParsableByteArray nalStartCode; - private final ParsableByteArray nalLength; - private final ParsableByteArray scratch; - private final ParsableByteArray vorbisNumPageSamples; - private final ParsableByteArray seekEntryIdBytes; - private final ParsableByteArray sampleStrippedBytes; - private final ParsableByteArray subripSample; - - private long segmentContentPosition = UNKNOWN; - private long segmentContentSize = UNKNOWN; - private long timecodeScale = C.UNKNOWN_TIME_US; - private long durationTimecode = C.UNKNOWN_TIME_US; - private long durationUs = C.UNKNOWN_TIME_US; - - // The track corresponding to the current TrackEntry element, or null. - private Track currentTrack; - - // Whether drm init data has been sent to the output. - private boolean sentDrmInitData; - private boolean sentSeekMap; - - // Master seek entry related elements. - private int seekEntryId; - private long seekEntryPosition; - - // Cue related elements. - private boolean seekForCues; - private long cuesContentPosition = UNKNOWN; - private long seekPositionAfterBuildingCues = UNKNOWN; - private long clusterTimecodeUs = UNKNOWN; - private LongArray cueTimesUs; - private LongArray cueClusterPositions; - private boolean seenClusterPositionForCurrentCuePoint; - - // Block reading state. - private int blockState; - private long blockTimeUs; - private long blockDurationUs; - private int blockLacingSampleIndex; - private int blockLacingSampleCount; - private int[] blockLacingSampleSizes; - private int blockTrackNumber; - private int blockTrackNumberLength; - private int blockFlags; - - // Sample reading state. - private int sampleBytesRead; - private boolean sampleEncodingHandled; - private int sampleCurrentNalBytesRemaining; - private int sampleBytesWritten; - private boolean sampleRead; - private boolean sampleSeenReferenceBlock; - - // Extractor outputs. - private ExtractorOutput extractorOutput; - - public WebmExtractor() { - this(new DefaultEbmlReader()); - } - - /* package */ WebmExtractor(EbmlReader reader) { - this.reader = reader; - this.reader.init(new InnerEbmlReaderOutput()); - varintReader = new VarintReader(); - tracks = new SparseArray<>(); - scratch = new ParsableByteArray(4); - vorbisNumPageSamples = new ParsableByteArray(ByteBuffer.allocate(4).putInt(-1).array()); - seekEntryIdBytes = new ParsableByteArray(4); - nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); - nalLength = new ParsableByteArray(4); - sampleStrippedBytes = new ParsableByteArray(); - subripSample = new ParsableByteArray(); - } - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - return new Sniffer().sniff(input); - } - - @Override - public void init(ExtractorOutput output) { - extractorOutput = output; - } - - @Override - public void seek() { - clusterTimecodeUs = UNKNOWN; - blockState = BLOCK_STATE_START; - reader.reset(); - varintReader.reset(); - resetSample(); - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, - InterruptedException { - sampleRead = false; - boolean continueReading = true; - while (continueReading && !sampleRead) { - continueReading = reader.read(input); - if (continueReading && maybeSeekForCues(seekPosition, input.getPosition())) { - return Extractor.RESULT_SEEK; - } - } - return continueReading ? Extractor.RESULT_CONTINUE : Extractor.RESULT_END_OF_INPUT; - } - - /* package */ int getElementType(int id) { - switch (id) { - case ID_EBML: - case ID_SEGMENT: - case ID_SEEK_HEAD: - case ID_SEEK: - case ID_INFO: - case ID_CLUSTER: - case ID_TRACKS: - case ID_TRACK_ENTRY: - case ID_AUDIO: - case ID_VIDEO: - case ID_CONTENT_ENCODINGS: - case ID_CONTENT_ENCODING: - case ID_CONTENT_COMPRESSION: - case ID_CONTENT_ENCRYPTION: - case ID_CONTENT_ENCRYPTION_AES_SETTINGS: - case ID_CUES: - case ID_CUE_POINT: - case ID_CUE_TRACK_POSITIONS: - case ID_BLOCK_GROUP: - return EbmlReader.TYPE_MASTER; - case ID_EBML_READ_VERSION: - case ID_DOC_TYPE_READ_VERSION: - case ID_SEEK_POSITION: - case ID_TIMECODE_SCALE: - case ID_TIME_CODE: - case ID_BLOCK_DURATION: - case ID_PIXEL_WIDTH: - case ID_PIXEL_HEIGHT: - case ID_DISPLAY_WIDTH: - case ID_DISPLAY_HEIGHT: - case ID_DISPLAY_UNIT: - case ID_TRACK_NUMBER: - case ID_TRACK_TYPE: - case ID_DEFAULT_DURATION: - case ID_CODEC_DELAY: - case ID_SEEK_PRE_ROLL: - case ID_CHANNELS: - case ID_AUDIO_BIT_DEPTH: - case ID_CONTENT_ENCODING_ORDER: - case ID_CONTENT_ENCODING_SCOPE: - case ID_CONTENT_COMPRESSION_ALGORITHM: - case ID_CONTENT_ENCRYPTION_ALGORITHM: - case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: - case ID_CUE_TIME: - case ID_CUE_CLUSTER_POSITION: - case ID_REFERENCE_BLOCK: - return EbmlReader.TYPE_UNSIGNED_INT; - case ID_DOC_TYPE: - case ID_CODEC_ID: - case ID_LANGUAGE: - return EbmlReader.TYPE_STRING; - case ID_SEEK_ID: - case ID_CONTENT_COMPRESSION_SETTINGS: - case ID_CONTENT_ENCRYPTION_KEY_ID: - case ID_SIMPLE_BLOCK: - case ID_BLOCK: - case ID_CODEC_PRIVATE: - return EbmlReader.TYPE_BINARY; - case ID_DURATION: - case ID_SAMPLING_FREQUENCY: - return EbmlReader.TYPE_FLOAT; - default: - return EbmlReader.TYPE_UNKNOWN; - } - } - - /* package */ boolean isLevel1Element(int id) { - return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS; - } - - /* package */ void startMasterElement(int id, long contentPosition, long contentSize) - throws ParserException { - switch (id) { - case ID_SEGMENT: - if (segmentContentPosition != UNKNOWN && segmentContentPosition != contentPosition) { - throw new ParserException("Multiple Segment elements not supported"); - } - segmentContentPosition = contentPosition; - segmentContentSize = contentSize; - return; - case ID_SEEK: - seekEntryId = UNKNOWN; - seekEntryPosition = UNKNOWN; - return; - case ID_CUES: - cueTimesUs = new LongArray(); - cueClusterPositions = new LongArray(); - return; - case ID_CUE_POINT: - seenClusterPositionForCurrentCuePoint = false; - return; - case ID_CLUSTER: - if (!sentSeekMap) { - // We need to build cues before parsing the cluster. - if (cuesContentPosition != UNKNOWN) { - // We know where the Cues element is located. Seek to request it. - seekForCues = true; - } else { - // We don't know where the Cues element is located. It's most likely omitted. Allow - // playback, but disable seeking. - extractorOutput.seekMap(SeekMap.UNSEEKABLE); - sentSeekMap = true; - } - } - return; - case ID_BLOCK_GROUP: - sampleSeenReferenceBlock = false; - return; - case ID_CONTENT_ENCODING: - // TODO: check and fail if more than one content encoding is present. - return; - case ID_CONTENT_ENCRYPTION: - currentTrack.hasContentEncryption = true; - return; - case ID_TRACK_ENTRY: - currentTrack = new Track(); - return; - default: - return; - } - } - - /* package */ void endMasterElement(int id) throws ParserException { - switch (id) { - case ID_SEGMENT_INFO: - if (timecodeScale == C.UNKNOWN_TIME_US) { - // timecodeScale was omitted. Use the default value. - timecodeScale = 1000000; - } - if (durationTimecode != C.UNKNOWN_TIME_US) { - durationUs = scaleTimecodeToUs(durationTimecode); - } - return; - case ID_SEEK: - if (seekEntryId == UNKNOWN || seekEntryPosition == UNKNOWN) { - throw new ParserException("Mandatory element SeekID or SeekPosition not found"); - } - if (seekEntryId == ID_CUES) { - cuesContentPosition = seekEntryPosition; - } - return; - case ID_CUES: - if (!sentSeekMap) { - extractorOutput.seekMap(buildSeekMap()); - sentSeekMap = true; - } else { - // We have already built the cues. Ignore. - } - return; - case ID_BLOCK_GROUP: - if (blockState != BLOCK_STATE_DATA) { - // We've skipped this block (due to incompatible track number). - return; - } - // If the ReferenceBlock element was not found for this sample, then it is a keyframe. - if (!sampleSeenReferenceBlock) { - blockFlags |= C.SAMPLE_FLAG_SYNC; - } - commitSampleToOutput(tracks.get(blockTrackNumber), blockTimeUs); - blockState = BLOCK_STATE_START; - return; - case ID_CONTENT_ENCODING: - if (currentTrack.hasContentEncryption) { - if (currentTrack.encryptionKeyId == null) { - throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); - } - if (!sentDrmInitData) { - extractorOutput.drmInitData(new DrmInitData.Universal( - new SchemeInitData(MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId))); - sentDrmInitData = true; - } - } - return; - case ID_CONTENT_ENCODINGS: - if (currentTrack.hasContentEncryption && currentTrack.sampleStrippedBytes != null) { - throw new ParserException("Combining encryption and compression is not supported"); - } - return; - case ID_TRACK_ENTRY: - if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) { - currentTrack.initializeOutput(extractorOutput, currentTrack.number, durationUs); - tracks.put(currentTrack.number, currentTrack); - } else { - // We've seen this track entry before, or the codec is unsupported. Do nothing. - } - currentTrack = null; - return; - case ID_TRACKS: - if (tracks.size() == 0) { - throw new ParserException("No valid tracks were found"); - } - extractorOutput.endTracks(); - return; - default: - return; - } - } - - /* package */ void integerElement(int id, long value) throws ParserException { - switch (id) { - case ID_EBML_READ_VERSION: - // Validate that EBMLReadVersion is supported. This extractor only supports v1. - if (value != 1) { - throw new ParserException("EBMLReadVersion " + value + " not supported"); - } - return; - case ID_DOC_TYPE_READ_VERSION: - // Validate that DocTypeReadVersion is supported. This extractor only supports up to v2. - if (value < 1 || value > 2) { - throw new ParserException("DocTypeReadVersion " + value + " not supported"); - } - return; - case ID_SEEK_POSITION: - // Seek Position is the relative offset beginning from the Segment. So to get absolute - // offset from the beginning of the file, we need to add segmentContentPosition to it. - seekEntryPosition = value + segmentContentPosition; - return; - case ID_TIMECODE_SCALE: - timecodeScale = value; - return; - case ID_PIXEL_WIDTH: - currentTrack.width = (int) value; - return; - case ID_PIXEL_HEIGHT: - currentTrack.height = (int) value; - return; - case ID_DISPLAY_WIDTH: - currentTrack.displayWidth = (int) value; - return; - case ID_DISPLAY_HEIGHT: - currentTrack.displayHeight = (int) value; - return; - case ID_DISPLAY_UNIT: - currentTrack.displayUnit = (int) value; - return; - case ID_TRACK_NUMBER: - currentTrack.number = (int) value; - return; - case ID_TRACK_TYPE: - currentTrack.type = (int) value; - return; - case ID_DEFAULT_DURATION: - currentTrack.defaultSampleDurationNs = (int) value; - return; - case ID_CODEC_DELAY: - currentTrack.codecDelayNs = value; - return; - case ID_SEEK_PRE_ROLL: - currentTrack.seekPreRollNs = value; - return; - case ID_CHANNELS: - currentTrack.channelCount = (int) value; - return; - case ID_AUDIO_BIT_DEPTH: - currentTrack.audioBitDepth = (int) value; - return; - case ID_REFERENCE_BLOCK: - sampleSeenReferenceBlock = true; - return; - case ID_CONTENT_ENCODING_ORDER: - // This extractor only supports one ContentEncoding element and hence the order has to be 0. - if (value != 0) { - throw new ParserException("ContentEncodingOrder " + value + " not supported"); - } - return; - case ID_CONTENT_ENCODING_SCOPE: - // This extractor only supports the scope of all frames. - if (value != 1) { - throw new ParserException("ContentEncodingScope " + value + " not supported"); - } - return; - case ID_CONTENT_COMPRESSION_ALGORITHM: - // This extractor only supports header stripping. - if (value != 3) { - throw new ParserException("ContentCompAlgo " + value + " not supported"); - } - return; - case ID_CONTENT_ENCRYPTION_ALGORITHM: - // Only the value 5 (AES) is allowed according to the WebM specification. - if (value != 5) { - throw new ParserException("ContentEncAlgo " + value + " not supported"); - } - return; - case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: - // Only the value 1 is allowed according to the WebM specification. - if (value != 1) { - throw new ParserException("AESSettingsCipherMode " + value + " not supported"); - } - return; - case ID_CUE_TIME: - cueTimesUs.add(scaleTimecodeToUs(value)); - return; - case ID_CUE_CLUSTER_POSITION: - if (!seenClusterPositionForCurrentCuePoint) { - // If there's more than one video/audio track, then there could be more than one - // CueTrackPositions within a single CuePoint. In such a case, ignore all but the first - // one (since the cluster position will be quite close for all the tracks). - cueClusterPositions.add(value); - seenClusterPositionForCurrentCuePoint = true; - } - return; - case ID_TIME_CODE: - clusterTimecodeUs = scaleTimecodeToUs(value); - return; - case ID_BLOCK_DURATION: - blockDurationUs = scaleTimecodeToUs(value); - return; - default: - return; - } - } - - /* package */ void floatElement(int id, double value) { - switch (id) { - case ID_DURATION: - durationTimecode = (long) value; - return; - case ID_SAMPLING_FREQUENCY: - currentTrack.sampleRate = (int) value; - return; - default: - return; - } - } - - /* package */ void stringElement(int id, String value) throws ParserException { - switch (id) { - case ID_DOC_TYPE: - // Validate that DocType is supported. - if (!DOC_TYPE_WEBM.equals(value) && !DOC_TYPE_MATROSKA.equals(value)) { - throw new ParserException("DocType " + value + " not supported"); - } - return; - case ID_CODEC_ID: - currentTrack.codecId = value; - return; - case ID_LANGUAGE: - currentTrack.language = value; - return; - default: - return; - } - } - - /* package */ void binaryElement(int id, int contentSize, ExtractorInput input) - throws IOException, InterruptedException { - switch (id) { - case ID_SEEK_ID: - Arrays.fill(seekEntryIdBytes.data, (byte) 0); - input.readFully(seekEntryIdBytes.data, 4 - contentSize, contentSize); - seekEntryIdBytes.setPosition(0); - seekEntryId = (int) seekEntryIdBytes.readUnsignedInt(); - return; - case ID_CODEC_PRIVATE: - currentTrack.codecPrivate = new byte[contentSize]; - input.readFully(currentTrack.codecPrivate, 0, contentSize); - return; - case ID_CONTENT_COMPRESSION_SETTINGS: - // This extractor only supports header stripping, so the payload is the stripped bytes. - currentTrack.sampleStrippedBytes = new byte[contentSize]; - input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); - return; - case ID_CONTENT_ENCRYPTION_KEY_ID: - currentTrack.encryptionKeyId = new byte[contentSize]; - input.readFully(currentTrack.encryptionKeyId, 0, contentSize); - return; - case ID_SIMPLE_BLOCK: - case ID_BLOCK: - // Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure - // and http://matroska.org/technical/specs/index.html#block_structure - // for info about how data is organized in SimpleBlock and Block elements respectively. They - // differ only in the way flags are specified. - - if (blockState == BLOCK_STATE_START) { - blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8); - blockTrackNumberLength = varintReader.getLastLength(); - blockDurationUs = UNKNOWN; - blockState = BLOCK_STATE_HEADER; - scratch.reset(); - } - - Track track = tracks.get(blockTrackNumber); - - // Ignore the block if we don't know about the track to which it belongs. - if (track == null) { - input.skipFully(contentSize - blockTrackNumberLength); - blockState = BLOCK_STATE_START; - return; - } - - if (blockState == BLOCK_STATE_HEADER) { - // Read the relative timecode (2 bytes) and flags (1 byte). - readScratch(input, 3); - int lacing = (scratch.data[2] & 0x06) >> 1; - if (lacing == LACING_NONE) { - blockLacingSampleCount = 1; - blockLacingSampleSizes = ensureArrayCapacity(blockLacingSampleSizes, 1); - blockLacingSampleSizes[0] = contentSize - blockTrackNumberLength - 3; - } else { - if (id != ID_SIMPLE_BLOCK) { - throw new ParserException("Lacing only supported in SimpleBlocks."); - } - - // Read the sample count (1 byte). - readScratch(input, 4); - blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1; - blockLacingSampleSizes = - ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount); - if (lacing == LACING_FIXED_SIZE) { - int blockLacingSampleSize = - (contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount; - Arrays.fill(blockLacingSampleSizes, 0, blockLacingSampleCount, blockLacingSampleSize); - } else if (lacing == LACING_XIPH) { - int totalSamplesSize = 0; - int headerSize = 4; - for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { - blockLacingSampleSizes[sampleIndex] = 0; - int byteValue; - do { - readScratch(input, ++headerSize); - byteValue = scratch.data[headerSize - 1] & 0xFF; - blockLacingSampleSizes[sampleIndex] += byteValue; - } while (byteValue == 0xFF); - totalSamplesSize += blockLacingSampleSizes[sampleIndex]; - } - blockLacingSampleSizes[blockLacingSampleCount - 1] = - contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; - } else if (lacing == LACING_EBML) { - int totalSamplesSize = 0; - int headerSize = 4; - for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { - blockLacingSampleSizes[sampleIndex] = 0; - readScratch(input, ++headerSize); - if (scratch.data[headerSize - 1] == 0) { - throw new ParserException("No valid varint length mask found"); - } - long readValue = 0; - for (int i = 0; i < 8; i++) { - int lengthMask = 1 << (7 - i); - if ((scratch.data[headerSize - 1] & lengthMask) != 0) { - int readPosition = headerSize - 1; - headerSize += i; - readScratch(input, headerSize); - readValue = (scratch.data[readPosition++] & 0xFF) & ~lengthMask; - while (readPosition < headerSize) { - readValue <<= 8; - readValue |= (scratch.data[readPosition++] & 0xFF); - } - // The first read value is the first size. Later values are signed offsets. - if (sampleIndex > 0) { - readValue -= (1L << 6 + i * 7) - 1; - } - break; - } - } - if (readValue < Integer.MIN_VALUE || readValue > Integer.MAX_VALUE) { - throw new ParserException("EBML lacing sample size out of range."); - } - int intReadValue = (int) readValue; - blockLacingSampleSizes[sampleIndex] = sampleIndex == 0 - ? intReadValue : blockLacingSampleSizes[sampleIndex - 1] + intReadValue; - totalSamplesSize += blockLacingSampleSizes[sampleIndex]; - } - blockLacingSampleSizes[blockLacingSampleCount - 1] = - contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; - } else { - // Lacing is always in the range 0--3. - throw new ParserException("Unexpected lacing value: " + lacing); - } - } - - int timecode = (scratch.data[0] << 8) | (scratch.data[1] & 0xFF); - blockTimeUs = clusterTimecodeUs + scaleTimecodeToUs(timecode); - boolean isInvisible = (scratch.data[2] & 0x08) == 0x08; - boolean isKeyframe = track.type == TRACK_TYPE_AUDIO - || (id == ID_SIMPLE_BLOCK && (scratch.data[2] & 0x80) == 0x80); - blockFlags = (isKeyframe ? C.SAMPLE_FLAG_SYNC : 0) - | (isInvisible ? C.SAMPLE_FLAG_DECODE_ONLY : 0); - blockState = BLOCK_STATE_DATA; - blockLacingSampleIndex = 0; - } - - if (id == ID_SIMPLE_BLOCK) { - // For SimpleBlock, we have metadata for each sample here. - while (blockLacingSampleIndex < blockLacingSampleCount) { - writeSampleData(input, track, blockLacingSampleSizes[blockLacingSampleIndex]); - long sampleTimeUs = this.blockTimeUs - + (blockLacingSampleIndex * track.defaultSampleDurationNs) / 1000; - commitSampleToOutput(track, sampleTimeUs); - blockLacingSampleIndex++; - } - blockState = BLOCK_STATE_START; - } else { - // For Block, we send the metadata at the end of the BlockGroup element since we'll know - // if the sample is a keyframe or not only at that point. - writeSampleData(input, track, blockLacingSampleSizes[0]); - } - - return; - default: - throw new ParserException("Unexpected id: " + id); - } - } - - private void commitSampleToOutput(Track track, long timeUs) { - if (CODEC_ID_SUBRIP.equals(track.codecId)) { - writeSubripSample(track); - } - track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.encryptionKeyId); - sampleRead = true; - resetSample(); - } - - private void resetSample() { - sampleBytesRead = 0; - sampleBytesWritten = 0; - sampleCurrentNalBytesRemaining = 0; - sampleEncodingHandled = false; - sampleStrippedBytes.reset(); - } - - /** - * Ensures {@link #scratch} contains at least {@code requiredLength} bytes of data, reading from - * the extractor input if necessary. - */ - private void readScratch(ExtractorInput input, int requiredLength) - throws IOException, InterruptedException { - if (scratch.limit() >= requiredLength) { - return; - } - if (scratch.capacity() < requiredLength) { - scratch.reset(Arrays.copyOf(scratch.data, Math.max(scratch.data.length * 2, requiredLength)), - scratch.limit()); - } - input.readFully(scratch.data, scratch.limit(), requiredLength - scratch.limit()); - scratch.setLimit(requiredLength); - } - - private void writeSampleData(ExtractorInput input, Track track, int size) - throws IOException, InterruptedException { - if (CODEC_ID_SUBRIP.equals(track.codecId)) { - int sizeWithPrefix = SUBRIP_PREFIX.length + size; - if (subripSample.capacity() < sizeWithPrefix) { - // Initialize subripSample to contain the required prefix and have space to hold a subtitle - // twice as long as this one. - subripSample.data = Arrays.copyOf(SUBRIP_PREFIX, sizeWithPrefix + size); - } - input.readFully(subripSample.data, SUBRIP_PREFIX.length, size); - subripSample.setPosition(0); - subripSample.setLimit(sizeWithPrefix); - // Defer writing the data to the track output. We need to modify the sample data by setting - // the correct end timecode, which we might not have yet. - return; - } - - TrackOutput output = track.output; - if (!sampleEncodingHandled) { - if (track.hasContentEncryption) { - // If the sample is encrypted, read its encryption signal byte and set the IV size. - // Clear the encrypted flag. - blockFlags &= ~C.SAMPLE_FLAG_ENCRYPTED; - input.readFully(scratch.data, 0, 1); - sampleBytesRead++; - if ((scratch.data[0] & 0x80) == 0x80) { - throw new ParserException("Extension bit is set in signal byte"); - } - if ((scratch.data[0] & 0x01) == 0x01) { - scratch.data[0] = (byte) ENCRYPTION_IV_SIZE; - scratch.setPosition(0); - output.sampleData(scratch, 1); - sampleBytesWritten++; - blockFlags |= C.SAMPLE_FLAG_ENCRYPTED; - } - } else if (track.sampleStrippedBytes != null) { - // If the sample has header stripping, prepare to read/output the stripped bytes first. - sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length); - } - sampleEncodingHandled = true; - } - size += sampleStrippedBytes.limit(); - - if (CODEC_ID_H264.equals(track.codecId) || CODEC_ID_H265.equals(track.codecId)) { - // TODO: Deduplicate with Mp4Extractor. - - // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case - // they're only 1 or 2 bytes long. - byte[] nalLengthData = nalLength.data; - nalLengthData[0] = 0; - nalLengthData[1] = 0; - nalLengthData[2] = 0; - int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength; - int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; - // NAL units are length delimited, but the decoder requires start code delimited units. - // Loop until we've written the sample to the track output, replacing length delimiters with - // start codes as we encounter them. - while (sampleBytesRead < size) { - if (sampleCurrentNalBytesRemaining == 0) { - // Read the NAL length so that we know where we find the next one. - readToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, - nalUnitLengthFieldLength); - nalLength.setPosition(0); - sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); - // Write a start code for the current NAL unit. - nalStartCode.setPosition(0); - output.sampleData(nalStartCode, 4); - sampleBytesWritten += 4; - } else { - // Write the payload of the NAL unit. - sampleCurrentNalBytesRemaining -= - readToOutput(input, output, sampleCurrentNalBytesRemaining); - } - } - } else { - while (sampleBytesRead < size) { - readToOutput(input, output, size - sampleBytesRead); - } - } - - if (CODEC_ID_VORBIS.equals(track.codecId)) { - // Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the - // number of samples in the current page. This definition holds good only for Ogg and - // irrelevant for WebM. So we always set this to -1 (the decoder will ignore this value if we - // set it to -1). The android platform media extractor [2] does the same. - // [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314 - // [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474 - vorbisNumPageSamples.setPosition(0); - output.sampleData(vorbisNumPageSamples, 4); - sampleBytesWritten += 4; - } - } - - private void writeSubripSample(Track track) { - setSubripSampleEndTimecode(subripSample.data, blockDurationUs); - // Note: If we ever want to support DRM protected subtitles then we'll need to output the - // appropriate encryption data here. - track.output.sampleData(subripSample, subripSample.limit()); - sampleBytesWritten += subripSample.limit(); - } - - private static void setSubripSampleEndTimecode(byte[] subripSampleData, long timeUs) { - byte[] timeCodeData; - if (timeUs == UNKNOWN) { - timeCodeData = SUBRIP_TIMECODE_EMPTY; - } else { - int hours = (int) (timeUs / 3600000000L); - timeUs -= (hours * 3600000000L); - int minutes = (int) (timeUs / 60000000); - timeUs -= (minutes * 60000000); - int seconds = (int) (timeUs / 1000000); - timeUs -= (seconds * 1000000); - int milliseconds = (int) (timeUs / 1000); - timeCodeData = String.format(Locale.US, "%02d:%02d:%02d,%03d", - hours, minutes, seconds, milliseconds).getBytes(); - } - System.arraycopy(timeCodeData, 0, subripSampleData, SUBRIP_PREFIX_END_TIMECODE_OFFSET, - SUBRIP_TIMECODE_LENGTH); - } - - /** - * Writes {@code length} bytes of sample data into {@code target} at {@code offset}, consisting of - * pending {@link #sampleStrippedBytes} and any remaining data read from {@code input}. - */ - private void readToTarget(ExtractorInput input, byte[] target, int offset, int length) - throws IOException, InterruptedException { - int pendingStrippedBytes = Math.min(length, sampleStrippedBytes.bytesLeft()); - input.readFully(target, offset + pendingStrippedBytes, length - pendingStrippedBytes); - if (pendingStrippedBytes > 0) { - sampleStrippedBytes.readBytes(target, offset, pendingStrippedBytes); - } - sampleBytesRead += length; - } - - /** - * Outputs up to {@code length} bytes of sample data to {@code output}, consisting of either - * {@link #sampleStrippedBytes} or data read from {@code input}. - */ - private int readToOutput(ExtractorInput input, TrackOutput output, int length) - throws IOException, InterruptedException { - int bytesRead; - int strippedBytesLeft = sampleStrippedBytes.bytesLeft(); - if (strippedBytesLeft > 0) { - bytesRead = Math.min(length, strippedBytesLeft); - output.sampleData(sampleStrippedBytes, bytesRead); - } else { - bytesRead = output.sampleData(input, length, false); - } - sampleBytesRead += bytesRead; - sampleBytesWritten += bytesRead; - return bytesRead; - } - - /** - * Builds a {@link SeekMap} from the recently gathered Cues information. - * - * @return The built {@link SeekMap}. May be {@link SeekMap#UNSEEKABLE} if cues information was - * missing or incomplete. - */ - private SeekMap buildSeekMap() { - if (segmentContentPosition == UNKNOWN || durationUs == C.UNKNOWN_TIME_US - || cueTimesUs == null || cueTimesUs.size() == 0 - || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { - // Cues information is missing or incomplete. - cueTimesUs = null; - cueClusterPositions = null; - return SeekMap.UNSEEKABLE; - } - int cuePointsSize = cueTimesUs.size(); - int[] sizes = new int[cuePointsSize]; - long[] offsets = new long[cuePointsSize]; - long[] durationsUs = new long[cuePointsSize]; - long[] timesUs = new long[cuePointsSize]; - for (int i = 0; i < cuePointsSize; i++) { - timesUs[i] = cueTimesUs.get(i); - offsets[i] = segmentContentPosition + cueClusterPositions.get(i); - } - for (int i = 0; i < cuePointsSize - 1; i++) { - sizes[i] = (int) (offsets[i + 1] - offsets[i]); - durationsUs[i] = timesUs[i + 1] - timesUs[i]; - } - sizes[cuePointsSize - 1] = - (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); - durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; - cueTimesUs = null; - cueClusterPositions = null; - return new ChunkIndex(sizes, offsets, durationsUs, timesUs); - } - - /** - * Updates the position of the holder to Cues element's position if the extractor configuration - * permits use of master seek entry. After building Cues sets the holder's position back to where - * it was before. - * - * @param seekPosition The holder whose position will be updated. - * @param currentPosition Current position of the input. - * @return true if the seek position was updated, false otherwise. - */ - private boolean maybeSeekForCues(PositionHolder seekPosition, long currentPosition) { - if (seekForCues) { - seekPositionAfterBuildingCues = currentPosition; - seekPosition.position = cuesContentPosition; - seekForCues = false; - return true; - } - // After parsing Cues, seek back to original position if available. We will not do this unless - // we seeked to get to the Cues in the first place. - if (sentSeekMap && seekPositionAfterBuildingCues != UNKNOWN) { - seekPosition.position = seekPositionAfterBuildingCues; - seekPositionAfterBuildingCues = UNKNOWN; - return true; - } - return false; - } - - private long scaleTimecodeToUs(long unscaledTimecode) throws ParserException { - if (timecodeScale == C.UNKNOWN_TIME_US) { - throw new ParserException("Can't scale timecode prior to timecodeScale being set."); - } - return Util.scaleLargeTimestamp(unscaledTimecode, timecodeScale, 1000); - } - - private static boolean isCodecSupported(String codecId) { - return CODEC_ID_VP8.equals(codecId) - || CODEC_ID_VP9.equals(codecId) - || CODEC_ID_MPEG2.equals(codecId) - || CODEC_ID_MPEG4_SP.equals(codecId) - || CODEC_ID_MPEG4_ASP.equals(codecId) - || CODEC_ID_MPEG4_AP.equals(codecId) - || CODEC_ID_H264.equals(codecId) - || CODEC_ID_H265.equals(codecId) - || CODEC_ID_FOURCC.equals(codecId) - || CODEC_ID_OPUS.equals(codecId) - || CODEC_ID_VORBIS.equals(codecId) - || CODEC_ID_AAC.equals(codecId) - || CODEC_ID_MP3.equals(codecId) - || CODEC_ID_AC3.equals(codecId) - || CODEC_ID_E_AC3.equals(codecId) - || CODEC_ID_TRUEHD.equals(codecId) - || CODEC_ID_DTS.equals(codecId) - || CODEC_ID_DTS_EXPRESS.equals(codecId) - || CODEC_ID_DTS_LOSSLESS.equals(codecId) - || CODEC_ID_FLAC.equals(codecId) - || CODEC_ID_ACM.equals(codecId) - || CODEC_ID_PCM_INT_LIT.equals(codecId) - || CODEC_ID_SUBRIP.equals(codecId) - || CODEC_ID_VOBSUB.equals(codecId) - || CODEC_ID_PGS.equals(codecId); - } - - /** - * Returns an array that can store (at least) {@code length} elements, which will be either a new - * array or {@code array} if it's not null and large enough. - */ - private static int[] ensureArrayCapacity(int[] array, int length) { - if (array == null) { - return new int[length]; - } else if (array.length >= length) { - return array; - } else { - // Double the size to avoid allocating constantly if the required length increases gradually. - return new int[Math.max(array.length * 2, length)]; - } - } - - /** - * Passes events through to the outer {@link WebmExtractor}. - */ - private final class InnerEbmlReaderOutput implements EbmlReaderOutput { - - @Override - public int getElementType(int id) { - return WebmExtractor.this.getElementType(id); - } - - @Override - public boolean isLevel1Element(int id) { - return WebmExtractor.this.isLevel1Element(id); - } - - @Override - public void startMasterElement(int id, long contentPosition, long contentSize) - throws ParserException { - WebmExtractor.this.startMasterElement(id, contentPosition, contentSize); - } - - @Override - public void endMasterElement(int id) throws ParserException { - WebmExtractor.this.endMasterElement(id); - } - - @Override - public void integerElement(int id, long value) throws ParserException { - WebmExtractor.this.integerElement(id, value); - } - - @Override - public void floatElement(int id, double value) throws ParserException { - WebmExtractor.this.floatElement(id, value); - } - - @Override - public void stringElement(int id, String value) throws ParserException { - WebmExtractor.this.stringElement(id, value); - } - - @Override - public void binaryElement(int id, int contentsSize, ExtractorInput input) - throws IOException, InterruptedException { - WebmExtractor.this.binaryElement(id, contentsSize, input); - } - - } - - private static final class Track { - - private static final int DISPLAY_UNIT_PIXELS = 0; - - // Common elements. - public String codecId; - public int number; - public int type; - public int defaultSampleDurationNs; - public boolean hasContentEncryption; - public byte[] sampleStrippedBytes; - public byte[] encryptionKeyId; - public byte[] codecPrivate; - - // Video elements. - public int width = MediaFormat.NO_VALUE; - public int height = MediaFormat.NO_VALUE; - public int displayWidth = MediaFormat.NO_VALUE; - public int displayHeight = MediaFormat.NO_VALUE; - public int displayUnit = DISPLAY_UNIT_PIXELS; - - // Audio elements. Initially set to their default values. - public int channelCount = 1; - public int audioBitDepth = -1; - public int sampleRate = 8000; - public long codecDelayNs = 0; - public long seekPreRollNs = 0; - - // Text elements. - private String language = "eng"; - - // Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265. - public TrackOutput output; - public int nalUnitLengthFieldLength; - - /** - * Initializes the track with an output. - */ - public void initializeOutput(ExtractorOutput output, int trackId, long durationUs) - throws ParserException { - String mimeType; - int maxInputSize = MediaFormat.NO_VALUE; - int pcmEncoding = MediaFormat.NO_VALUE; - List initializationData = null; - switch (codecId) { - case CODEC_ID_VP8: - mimeType = MimeTypes.VIDEO_VP8; - break; - case CODEC_ID_VP9: - mimeType = MimeTypes.VIDEO_VP9; - break; - case CODEC_ID_MPEG2: - mimeType = MimeTypes.VIDEO_MPEG2; - break; - case CODEC_ID_MPEG4_SP: - case CODEC_ID_MPEG4_ASP: - case CODEC_ID_MPEG4_AP: - mimeType = MimeTypes.VIDEO_MP4V; - initializationData = - codecPrivate == null ? null : Collections.singletonList(codecPrivate); - break; - case CODEC_ID_H264: - mimeType = MimeTypes.VIDEO_H264; - Pair, Integer> h264Data = parseAvcCodecPrivate( - new ParsableByteArray(codecPrivate)); - initializationData = h264Data.first; - nalUnitLengthFieldLength = h264Data.second; - break; - case CODEC_ID_H265: - mimeType = MimeTypes.VIDEO_H265; - Pair, Integer> hevcData = parseHevcCodecPrivate( - new ParsableByteArray(codecPrivate)); - initializationData = hevcData.first; - nalUnitLengthFieldLength = hevcData.second; - break; - case CODEC_ID_FOURCC: - mimeType = MimeTypes.VIDEO_VC1; - initializationData = parseFourCcVc1Private(new ParsableByteArray(codecPrivate)); - break; - case CODEC_ID_VORBIS: - mimeType = MimeTypes.AUDIO_VORBIS; - maxInputSize = VORBIS_MAX_INPUT_SIZE; - initializationData = parseVorbisCodecPrivate(codecPrivate); - break; - case CODEC_ID_OPUS: - mimeType = MimeTypes.AUDIO_OPUS; - maxInputSize = OPUS_MAX_INPUT_SIZE; - initializationData = new ArrayList<>(3); - initializationData.add(codecPrivate); - initializationData.add( - ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(codecDelayNs).array()); - initializationData.add( - ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(seekPreRollNs).array()); - break; - case CODEC_ID_AAC: - mimeType = MimeTypes.AUDIO_AAC; - initializationData = Collections.singletonList(codecPrivate); - break; - case CODEC_ID_MP3: - mimeType = MimeTypes.AUDIO_MPEG; - maxInputSize = MP3_MAX_INPUT_SIZE; - break; - case CODEC_ID_AC3: - mimeType = MimeTypes.AUDIO_AC3; - break; - case CODEC_ID_E_AC3: - mimeType = MimeTypes.AUDIO_E_AC3; - break; - case CODEC_ID_TRUEHD: - mimeType = MimeTypes.AUDIO_TRUEHD; - break; - case CODEC_ID_DTS: - case CODEC_ID_DTS_EXPRESS: - mimeType = MimeTypes.AUDIO_DTS; - break; - case CODEC_ID_DTS_LOSSLESS: - mimeType = MimeTypes.AUDIO_DTS_HD; - break; - case CODEC_ID_FLAC: - mimeType = MimeTypes.AUDIO_FLAC; - initializationData = Collections.singletonList(codecPrivate); - break; - case CODEC_ID_ACM: - mimeType = MimeTypes.AUDIO_RAW; - if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { - throw new ParserException("Non-PCM MS/ACM is unsupported"); - } - pcmEncoding = Util.getPcmEncoding(audioBitDepth); - if (pcmEncoding == C.ENCODING_INVALID) { - throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); - } - break; - case CODEC_ID_PCM_INT_LIT: - mimeType = MimeTypes.AUDIO_RAW; - pcmEncoding = Util.getPcmEncoding(audioBitDepth); - if (pcmEncoding == C.ENCODING_INVALID) { - throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); - } - break; - case CODEC_ID_SUBRIP: - mimeType = MimeTypes.APPLICATION_SUBRIP; - break; - case CODEC_ID_VOBSUB: - mimeType = MimeTypes.APPLICATION_VOBSUB; - initializationData = Collections.singletonList(codecPrivate); - break; - case CODEC_ID_PGS: - mimeType = MimeTypes.APPLICATION_PGS; - break; - default: - throw new ParserException("Unrecognized codec identifier."); - } - - MediaFormat format; - // TODO: Consider reading the name elements of the tracks and, if present, incorporating them - // into the trackId passed when creating the formats. - if (MimeTypes.isAudio(mimeType)) { - format = MediaFormat.createAudioFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, maxInputSize, durationUs, channelCount, sampleRate, - initializationData, language, pcmEncoding); - } else if (MimeTypes.isVideo(mimeType)) { - if (displayUnit == Track.DISPLAY_UNIT_PIXELS) { - displayWidth = displayWidth == MediaFormat.NO_VALUE ? width : displayWidth; - displayHeight = displayHeight == MediaFormat.NO_VALUE ? height : displayHeight; - } - float pixelWidthHeightRatio = MediaFormat.NO_VALUE; - if (displayWidth != MediaFormat.NO_VALUE && displayHeight != MediaFormat.NO_VALUE) { - pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); - } - format = MediaFormat.createVideoFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, maxInputSize, durationUs, width, height, initializationData, - MediaFormat.NO_VALUE, pixelWidthHeightRatio); - } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { - format = MediaFormat.createTextFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, durationUs, language); - } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) - || MimeTypes.APPLICATION_PGS.equals(mimeType)) { - format = MediaFormat.createImageFormat(Integer.toString(trackId), mimeType, - MediaFormat.NO_VALUE, durationUs, initializationData, language); - } else { - throw new ParserException("Unexpected MIME type."); - } - - this.output = output.track(number); - this.output.format(format); - } - - /** - * Builds initialization data for a {@link MediaFormat} from FourCC codec private data. - *

        - * VC1 is the only supported compression type. - * - * @return The initialization data for the {@link MediaFormat}. - * @throws ParserException If the initialization data could not be built. - */ - private static List parseFourCcVc1Private(ParsableByteArray buffer) - throws ParserException { - try { - buffer.skipBytes(16); // size(4), width(4), height(4), planes(2), bitcount(2). - long compression = buffer.readLittleEndianUnsignedInt(); - if (compression != FOURCC_COMPRESSION_VC1) { - throw new ParserException("Unsupported FourCC compression type: " + compression); - } - - // Search for the initialization data from the end of the BITMAPINFOHEADER. The last 20 - // bytes of which are: sizeImage(4), xPel/m (4), yPel/m (4), clrUsed(4), clrImportant(4). - int startOffset = buffer.getPosition() + 20; - byte[] bufferData = buffer.data; - for (int offset = startOffset; offset < bufferData.length - 4; offset++) { - if (bufferData[offset] == 0x00 && bufferData[offset + 1] == 0x00 - && bufferData[offset + 2] == 0x01 && bufferData[offset + 3] == 0x0F) { - // We've found the initialization data. - byte[] initializationData = Arrays.copyOfRange(bufferData, offset, bufferData.length); - return Collections.singletonList(initializationData); - } - } - - throw new ParserException("Failed to find FourCC VC1 initialization data"); - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing FourCC VC1 codec private"); - } - } - - /** - * Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data. - * - * @return The initialization data for the {@link MediaFormat}. - * @throws ParserException If the initialization data could not be built. - */ - private static Pair, Integer> parseAvcCodecPrivate(ParsableByteArray buffer) - throws ParserException { - try { - // TODO: Deduplicate with AtomParsers.parseAvcCFromParent. - buffer.setPosition(4); - int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1; - if (nalUnitLengthFieldLength == 3) { - throw new ParserException(); - } - List initializationData = new ArrayList<>(); - int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F; - for (int i = 0; i < numSequenceParameterSets; i++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - int numPictureParameterSets = buffer.readUnsignedByte(); - for (int j = 0; j < numPictureParameterSets; j++) { - initializationData.add(NalUnitUtil.parseChildNalUnit(buffer)); - } - return Pair.create(initializationData, nalUnitLengthFieldLength); - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing AVC codec private"); - } - } - - /** - * Builds initialization data for a {@link MediaFormat} from H.265 (HEVC) codec private data. - * - * @return The initialization data for the {@link MediaFormat}. - * @throws ParserException If the initialization data could not be built. - */ - private static Pair, Integer> parseHevcCodecPrivate(ParsableByteArray parent) - throws ParserException { - try { - // TODO: Deduplicate with AtomParsers.parseHvcCFromParent. - parent.setPosition(21); - int lengthSizeMinusOne = parent.readUnsignedByte() & 0x03; - - // Calculate the combined size of all VPS/SPS/PPS bitstreams. - int numberOfArrays = parent.readUnsignedByte(); - int csdLength = 0; - int csdStartPosition = parent.getPosition(); - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - csdLength += 4 + nalUnitLength; // Start code and NAL unit. - parent.skipBytes(nalUnitLength); - } - } - - // Concatenate the codec-specific data into a single buffer. - parent.setPosition(csdStartPosition); - byte[] buffer = new byte[csdLength]; - int bufferPosition = 0; - for (int i = 0; i < numberOfArrays; i++) { - parent.skipBytes(1); // completeness (1), nal_unit_type (7) - int numberOfNalUnits = parent.readUnsignedShort(); - for (int j = 0; j < numberOfNalUnits; j++) { - int nalUnitLength = parent.readUnsignedShort(); - System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, - NalUnitUtil.NAL_START_CODE.length); - bufferPosition += NalUnitUtil.NAL_START_CODE.length; - System.arraycopy(parent.data, parent.getPosition(), buffer, bufferPosition, - nalUnitLength); - bufferPosition += nalUnitLength; - parent.skipBytes(nalUnitLength); - } - } - - List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); - return Pair.create(initializationData, lengthSizeMinusOne + 1); - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing HEVC codec private"); - } - } - - /** - * Builds initialization data for a {@link MediaFormat} from Vorbis codec private data. - * - * @return The initialization data for the {@link MediaFormat}. - * @throws ParserException If the initialization data could not be built. - */ - private static List parseVorbisCodecPrivate(byte[] codecPrivate) - throws ParserException { - try { - if (codecPrivate[0] != 0x02) { - throw new ParserException("Error parsing vorbis codec private"); - } - int offset = 1; - int vorbisInfoLength = 0; - while (codecPrivate[offset] == (byte) 0xFF) { - vorbisInfoLength += 0xFF; - offset++; - } - vorbisInfoLength += codecPrivate[offset++]; - - int vorbisSkipLength = 0; - while (codecPrivate[offset] == (byte) 0xFF) { - vorbisSkipLength += 0xFF; - offset++; - } - vorbisSkipLength += codecPrivate[offset++]; - - if (codecPrivate[offset] != 0x01) { - throw new ParserException("Error parsing vorbis codec private"); - } - byte[] vorbisInfo = new byte[vorbisInfoLength]; - System.arraycopy(codecPrivate, offset, vorbisInfo, 0, vorbisInfoLength); - offset += vorbisInfoLength; - if (codecPrivate[offset] != 0x03) { - throw new ParserException("Error parsing vorbis codec private"); - } - offset += vorbisSkipLength; - if (codecPrivate[offset] != 0x05) { - throw new ParserException("Error parsing vorbis codec private"); - } - byte[] vorbisBooks = new byte[codecPrivate.length - offset]; - System.arraycopy(codecPrivate, offset, vorbisBooks, 0, codecPrivate.length - offset); - List initializationData = new ArrayList<>(2); - initializationData.add(vorbisInfo); - initializationData.add(vorbisBooks); - return initializationData; - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing vorbis codec private"); - } - } - - /** - * Parses an MS/ACM codec private, returning whether it indicates PCM audio. - * - * @return True if the codec private indicates PCM audio. False otherwise. - * @throws ParserException If a parsing error occurs. - */ - private static boolean parseMsAcmCodecPrivate(ParsableByteArray buffer) throws ParserException { - try { - int formatTag = buffer.readLittleEndianUnsignedShort(); - if (formatTag == WAVE_FORMAT_PCM) { - return true; - } else if (formatTag == WAVE_FORMAT_EXTENSIBLE) { - buffer.setPosition(WAVE_FORMAT_SIZE + 6); // unionSamples(2), channelMask(4) - return buffer.readLong() == WAVE_SUBFORMAT_PCM.getMostSignificantBits() - && buffer.readLong() == WAVE_SUBFORMAT_PCM.getLeastSignificantBits(); - } else { - return false; - } - } catch (ArrayIndexOutOfBoundsException e) { - throw new ParserException("Error parsing MS/ACM codec private"); - } - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/DefaultHlsTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/DefaultHlsTrackSelector.java deleted file mode 100755 index 95dddb8675c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/DefaultHlsTrackSelector.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.content.Context; -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.chunk.VideoFormatSelectorUtil; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * A default {@link HlsTrackSelector} implementation. - */ -public final class DefaultHlsTrackSelector implements HlsTrackSelector { - - private static final int TYPE_DEFAULT = 0; - private static final int TYPE_AUDIO = 1; - private static final int TYPE_SUBTITLE = 2; - - private final Context context; - private final int type; - - /** - * Creates a {@link DefaultHlsTrackSelector} that selects the streams defined in the playlist. - * - * @param context A context. - * @return The selector instance. - */ - public static DefaultHlsTrackSelector newDefaultInstance(Context context) { - return new DefaultHlsTrackSelector(context, TYPE_DEFAULT); - } - - /** - * Creates a {@link DefaultHlsTrackSelector} that selects alternate audio renditions. - * - * @return The selector instance. - */ - public static DefaultHlsTrackSelector newAudioInstance() { - return new DefaultHlsTrackSelector(null, TYPE_AUDIO); - } - - /** - * Creates a {@link DefaultHlsTrackSelector} that selects subtitle renditions. - * - * @return The selector instance. - */ - public static DefaultHlsTrackSelector newSubtitleInstance() { - return new DefaultHlsTrackSelector(null, TYPE_SUBTITLE); - } - - private DefaultHlsTrackSelector(Context context, int type) { - this.context = context; - this.type = type; - } - - @Override - public void selectTracks(HlsMasterPlaylist playlist, Output output) throws IOException { - if (type == TYPE_AUDIO || type == TYPE_SUBTITLE) { - List variants = type == TYPE_AUDIO ? playlist.audios : playlist.subtitles; - if (variants != null && !variants.isEmpty()) { - for (int i = 0; i < variants.size(); i++) { - output.fixedTrack(playlist, variants.get(i)); - } - } - return; - } - - // Type is TYPE_DEFAULT. - - ArrayList enabledVariantList = new ArrayList<>(); - int[] variantIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( - context, playlist.variants, null, false); - for (int i = 0; i < variantIndices.length; i++) { - enabledVariantList.add(playlist.variants.get(variantIndices[i])); - } - - ArrayList definiteVideoVariants = new ArrayList<>(); - ArrayList definiteAudioOnlyVariants = new ArrayList<>(); - for (int i = 0; i < enabledVariantList.size(); i++) { - Variant variant = enabledVariantList.get(i); - if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { - definiteVideoVariants.add(variant); - } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { - definiteAudioOnlyVariants.add(variant); - } - } - - if (!definiteVideoVariants.isEmpty()) { - // We've identified some variants as definitely containing video. Assume variants within the - // master playlist are marked consistently, and hence that we have the full set. Filter out - // any other variants, which are likely to be audio only. - enabledVariantList = definiteVideoVariants; - } else if (definiteAudioOnlyVariants.size() < enabledVariantList.size()) { - // We've identified some variants, but not all, as being audio only. Filter them out to leave - // the remaining variants, which are likely to contain video. - enabledVariantList.removeAll(definiteAudioOnlyVariants); - } else { - // Leave the enabled variants unchanged. They're likely either all video or all audio. - } - - if (enabledVariantList.size() > 1) { - Variant[] enabledVariants = new Variant[enabledVariantList.size()]; - enabledVariantList.toArray(enabledVariants); - output.adaptiveTrack(playlist, enabledVariants); - } - for (int i = 0; i < enabledVariantList.size(); i++) { - output.fixedTrack(playlist, enabledVariantList.get(i)); - } - } - - private static boolean variantHasExplicitCodecWithPrefix(Variant variant, String prefix) { - String codecs = variant.format.codecs; - if (TextUtils.isEmpty(codecs)) { - return false; - } - String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); - for (int i = 0; i < codecArray.length; i++) { - if (codecArray[i].startsWith(prefix)) { - return true; - } - } - return false; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsChunkSource.java deleted file mode 100755 index 1a47b1093b7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsChunkSource.java +++ /dev/null @@ -1,949 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.net.Uri; -import android.os.Handler; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Log; -import org.telegram.messenger.exoplayer.BehindLiveWindowException; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.chunk.Chunk; -import org.telegram.messenger.exoplayer.chunk.ChunkOperationHolder; -import org.telegram.messenger.exoplayer.chunk.DataChunk; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.mp3.Mp3Extractor; -import org.telegram.messenger.exoplayer.extractor.ts.AdtsExtractor; -import org.telegram.messenger.exoplayer.extractor.ts.PtsTimestampAdjuster; -import org.telegram.messenger.exoplayer.extractor.ts.TsExtractor; -import org.telegram.messenger.exoplayer.upstream.BandwidthMeter; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.UriUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Locale; - -/** - * A temporary test source of HLS chunks. - */ -public class HlsChunkSource implements HlsTrackSelector.Output { - - /** - * Interface definition for a callback to be notified of {@link HlsChunkSource} events. - */ - public interface EventListener { - - /** - * Invoked when a media playlist has been loaded. - * - * @param rawResponse The raw data of the media playlist - */ - void onMediaPlaylistLoadCompleted(byte[] rawResponse); - - } - - /** - * The default minimum duration of media that needs to be buffered for a switch to a higher - * quality variant to be considered. - */ - public static final long DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS = 5000; - - /** - * The default maximum duration of media that needs to be buffered for a switch to a lower - * quality variant to be considered. - */ - public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000; - - /** - * The default time for which a media playlist should be blacklisted. - */ - public static final long DEFAULT_PLAYLIST_BLACKLIST_MS = 60000; - - /** - * Subtracted value to lookup position when switching between variants in live streams to avoid - * gaps in playback in case playlist drift apart. - */ - private static final double LIVE_VARIANT_SWITCH_SAFETY_EXTRA_SECS = 2.0; - - private static final String TAG = "HlsChunkSource"; - private static final String AAC_FILE_EXTENSION = ".aac"; - private static final String MP3_FILE_EXTENSION = ".mp3"; - private static final String VTT_FILE_EXTENSION = ".vtt"; - private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; - private static final float BANDWIDTH_FRACTION = 0.8f; - - private final boolean isMaster; - private final DataSource dataSource; - private final HlsPlaylistParser playlistParser; - private final HlsMasterPlaylist masterPlaylist; - private final HlsTrackSelector trackSelector; - private final BandwidthMeter bandwidthMeter; - private final PtsTimestampAdjusterProvider timestampAdjusterProvider; - private final String baseUri; - private final long minBufferDurationToSwitchUpUs; - private final long maxBufferDurationToSwitchDownUs; - - // TODO: Expose tracks. - private final ArrayList tracks; - - private int selectedTrackIndex; - - // A list of variants considered during playback, ordered by decreasing bandwidth. The following - // three arrays are of the same length and are ordered in the same way (i.e. variantPlaylists[i], - // variantLastPlaylistLoadTimesMs[i] and variantBlacklistTimes[i] all correspond to variants[i]). - private Variant[] variants; - private HlsMediaPlaylist[] variantPlaylists; - private long[] variantLastPlaylistLoadTimesMs; - private long[] variantBlacklistTimes; - - // The index in variants of the currently selected variant. - private int selectedVariantIndex; - - private boolean prepareCalled; - private byte[] scratchSpace; - private boolean live; - private long durationUs; - private IOException fatalError; - - private Uri encryptionKeyUri; - private byte[] encryptionKey; - private String encryptionIvString; - private byte[] encryptionIv; - private final EventListener eventListener; - private final Handler eventHandler; - - /** - * @param isMaster True if this is the master source for the playback. False otherwise. Each - * playback must have exactly one master source, which should be the source providing video - * chunks (or audio chunks for audio only playbacks). - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param playlist The HLS playlist. - * @param trackSelector Selects tracks to be exposed by this source. - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If - * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the - * same provider. - */ - public HlsChunkSource(boolean isMaster, DataSource dataSource, HlsPlaylist playlist, - HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter, - PtsTimestampAdjusterProvider timestampAdjusterProvider) { - this(isMaster, dataSource, playlist, trackSelector, bandwidthMeter, - timestampAdjusterProvider, DEFAULT_MIN_BUFFER_TO_SWITCH_UP_MS, - DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS, null, null); - } - - /** - * @param isMaster True if this is the master source for the playback. False otherwise. Each - * playback must have exactly one master source, which should be the source providing video - * chunks (or audio chunks for audio only playbacks). - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param playlist The HLS playlist. - * @param trackSelector Selects tracks to be exposed by this source. - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If - * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the - * same provider. - * @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered - * for a switch to a higher quality variant to be considered. - * @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered - * for a switch to a lower quality variant to be considered. - */ - public HlsChunkSource(boolean isMaster, DataSource dataSource, HlsPlaylist playlist, - HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter, - PtsTimestampAdjusterProvider timestampAdjusterProvider, long minBufferDurationToSwitchUpMs, - long maxBufferDurationToSwitchDownMs) { - this(isMaster, dataSource, playlist, trackSelector, bandwidthMeter, - timestampAdjusterProvider, minBufferDurationToSwitchUpMs, - maxBufferDurationToSwitchDownMs, null, null); - } - - /** - * @param isMaster True if this is the master source for the playback. False otherwise. Each - * playback must have exactly one master source, which should be the source providing video - * chunks (or audio chunks for audio only playbacks). - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param playlist The HLS playlist. - * @param trackSelector Selects tracks to be exposed by this source. - * @param bandwidthMeter Provides an estimate of the currently available bandwidth. - * @param timestampAdjusterProvider A provider of {@link PtsTimestampAdjuster} instances. If - * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the - * same provider. - * @param minBufferDurationToSwitchUpMs The minimum duration of media that needs to be buffered - * for a switch to a higher quality variant to be considered. - * @param maxBufferDurationToSwitchDownMs The maximum duration of media that needs to be buffered - * for a switch to a lower quality variant to be considered. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public HlsChunkSource(boolean isMaster, DataSource dataSource, HlsPlaylist playlist, - HlsTrackSelector trackSelector, BandwidthMeter bandwidthMeter, - PtsTimestampAdjusterProvider timestampAdjusterProvider, - long minBufferDurationToSwitchUpMs, long maxBufferDurationToSwitchDownMs, - Handler eventHandler, EventListener eventListener) { - this.isMaster = isMaster; - this.dataSource = dataSource; - this.trackSelector = trackSelector; - this.bandwidthMeter = bandwidthMeter; - this.timestampAdjusterProvider = timestampAdjusterProvider; - this.eventListener = eventListener; - this.eventHandler = eventHandler; - minBufferDurationToSwitchUpUs = minBufferDurationToSwitchUpMs * 1000; - maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000; - baseUri = playlist.baseUri; - playlistParser = new HlsPlaylistParser(); - tracks = new ArrayList<>(); - - if (playlist.type == HlsPlaylist.TYPE_MASTER) { - masterPlaylist = (HlsMasterPlaylist) playlist; - } else { - Format format = new Format("0", MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, -1, null, - null); - List variants = new ArrayList<>(); - variants.add(new Variant(baseUri, format)); - masterPlaylist = new HlsMasterPlaylist(baseUri, variants, - Collections.emptyList(), Collections.emptyList(), null, null); - } - } - - /** - * If the source is currently having difficulty providing chunks, then this method throws the - * underlying error. Otherwise does nothing. - * - * @throws IOException The underlying error. - */ - public void maybeThrowError() throws IOException { - if (fatalError != null) { - throw fatalError; - } - } - - /** - * Prepares the source. - * - * @return True if the source was prepared, false otherwise. - */ - public boolean prepare() { - if (!prepareCalled) { - prepareCalled = true; - try { - trackSelector.selectTracks(masterPlaylist, this); - selectTrack(0); - } catch (IOException e) { - fatalError = e; - } - } - return fatalError == null; - } - - /** - * Returns whether this is a live playback. - *

        - * This method should only be called after the source has been prepared. - * - * @return True if this is a live playback. False otherwise. - */ - public boolean isLive() { - return live; - } - - /** - * Returns the duration of the source, or {@link C#UNKNOWN_TIME_US} if the duration is unknown. - *

        - * This method should only be called after the source has been prepared. - * - * @return The number of tracks. - */ - public long getDurationUs() { - return durationUs; - } - - /** - * Returns the number of tracks exposed by the source. - *

        - * This method should only be called after the source has been prepared. - * - * @return The number of tracks. - */ - public int getTrackCount() { - return tracks.size(); - } - - /** - * Returns the variant corresponding to the fixed track at the specified index, or null if the - * track at the specified index is adaptive. - *

        - * This method should only be called after the source has been prepared. - * - * @param index The track index. - * @return The variant corresponding to the fixed track, or null if the track is adaptive. - */ - public Variant getFixedTrackVariant(int index) { - Variant[] variants = tracks.get(index).variants; - return variants.length == 1 ? variants[0] : null; - } - - /** - * Returns the language of the audio muxed into variants, or null if unknown. - * - * @return The language of the audio muxed into variants, or null if unknown. - */ - public String getMuxedAudioLanguage() { - return masterPlaylist.muxedAudioLanguage; - } - - /** - * Returns the language of the captions muxed into variants, or null if unknown. - * - * @return The language of the captions muxed into variants, or null if unknown. - */ - public String getMuxedCaptionLanguage() { - return masterPlaylist.muxedCaptionLanguage; - } - - /** - * Returns the currently selected track index. - *

        - * This method should only be called after the source has been prepared. - * - * @return The currently selected track index. - */ - public int getSelectedTrackIndex() { - return selectedTrackIndex; - } - - /** - * Selects a track for use. - *

        - * This method should only be called after the source has been prepared. - * - * @param index The track index. - */ - public void selectTrack(int index) { - selectedTrackIndex = index; - ExposedTrack selectedTrack = tracks.get(selectedTrackIndex); - selectedVariantIndex = selectedTrack.defaultVariantIndex; - variants = selectedTrack.variants; - variantPlaylists = new HlsMediaPlaylist[variants.length]; - variantLastPlaylistLoadTimesMs = new long[variants.length]; - variantBlacklistTimes = new long[variants.length]; - } - - /** - * Notifies the source that a seek has occurred. - *

        - * This method should only be called after the source has been prepared. - */ - public void seek() { - if (isMaster) { - timestampAdjusterProvider.reset(); - } - } - - /** - * Resets the source. - *

        - * This method should only be called after the source has been prepared. - */ - public void reset() { - fatalError = null; - } - - /** - * Updates the provided {@link ChunkOperationHolder} to contain the next operation that should - * be performed by the calling {@link HlsSampleSource}. - * - * @param previousTsChunk The previously loaded chunk that the next chunk should follow. - * @param playbackPositionUs The current playback position. If previousTsChunk is null then this - * parameter is the position from which playback is expected to start (or restart) and hence - * should be interpreted as a seek position. - * @param out The holder to populate with the result. {@link ChunkOperationHolder#queueSize} is - * unused. - */ - public void getChunkOperation(TsChunk previousTsChunk, long playbackPositionUs, - ChunkOperationHolder out) { - int previousChunkVariantIndex = - previousTsChunk == null ? -1 : getVariantIndex(previousTsChunk.format); - int nextVariantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs); - boolean switchingVariant = previousTsChunk != null - && previousChunkVariantIndex != nextVariantIndex; - - HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex]; - if (mediaPlaylist == null) { - // We don't have the media playlist for the next variant. Request it now. - out.chunk = newMediaPlaylistChunk(nextVariantIndex); - return; - } - - selectedVariantIndex = nextVariantIndex; - int chunkMediaSequence; - if (live) { - if (previousTsChunk == null) { - chunkMediaSequence = getLiveStartChunkSequenceNumber(selectedVariantIndex); - } else { - chunkMediaSequence = getLiveNextChunkSequenceNumber(previousTsChunk.chunkIndex, - previousChunkVariantIndex, selectedVariantIndex); - if (chunkMediaSequence < mediaPlaylist.mediaSequence) { - fatalError = new BehindLiveWindowException(); - return; - } - } - } else { - // Not live. - if (previousTsChunk == null) { - chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs, - true, true) + mediaPlaylist.mediaSequence; - } else if (switchingVariant) { - chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, - previousTsChunk.startTimeUs, true, true) + mediaPlaylist.mediaSequence; - } else { - chunkMediaSequence = previousTsChunk.getNextChunkIndex(); - } - } - - int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence; - if (chunkIndex >= mediaPlaylist.segments.size()) { - if (!mediaPlaylist.live) { - out.endOfStream = true; - } else if (shouldRerequestLiveMediaPlaylist(selectedVariantIndex)) { - out.chunk = newMediaPlaylistChunk(selectedVariantIndex); - } - return; - } - - HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); - Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); - - // Check if encryption is specified. - if (segment.isEncrypted) { - Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); - if (!keyUri.equals(encryptionKeyUri)) { - // Encryption is specified and the key has changed. - out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, selectedVariantIndex); - return; - } - if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { - setEncryptionData(keyUri, segment.encryptionIV, encryptionKey); - } - } else { - clearEncryptionData(); - } - - // Configure the data source and spec for the chunk. - DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, - null); - - // Compute start and end times, and the sequence number of the next chunk. - long startTimeUs; - if (live) { - if (previousTsChunk == null) { - startTimeUs = 0; - } else { - startTimeUs = previousTsChunk.getAdjustedEndTimeUs() - - (switchingVariant ? previousTsChunk.getDurationUs() : 0); - } - } else /* Not live */ { - startTimeUs = segment.startTimeUs; - } - long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); - int trigger = Chunk.TRIGGER_UNSPECIFIED; - Format format = variants[selectedVariantIndex].format; - - // Configure the extractor that will read the chunk. - HlsExtractorWrapper extractorWrapper; - String lastPathSegment = chunkUri.getLastPathSegment(); - if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { - // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner - // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3 - // case below. - Extractor extractor = new AdtsExtractor(startTimeUs); - extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, - switchingVariant, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE); - } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { - Extractor extractor = new Mp3Extractor(startTimeUs); - extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, - switchingVariant, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE); - } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) - || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { - PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(isMaster, - segment.discontinuitySequenceNumber, startTimeUs); - if (timestampAdjuster == null) { - // The master source has yet to instantiate an adjuster for the discontinuity sequence. - // TODO: There's probably an edge case if the master starts playback at a chunk belonging to - // a discontinuity sequence greater than the one that this source is trying to start at. - return; - } - Extractor extractor = new WebvttExtractor(timestampAdjuster); - extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, - switchingVariant, MediaFormat.NO_VALUE, MediaFormat.NO_VALUE); - } else if (previousTsChunk == null - || previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber - || !format.equals(previousTsChunk.format)) { - // MPEG-2 TS segments, but we need a new extractor. - PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(isMaster, - segment.discontinuitySequenceNumber, startTimeUs); - if (timestampAdjuster == null) { - // The master source has yet to instantiate an adjuster for the discontinuity sequence. - return; - } - int workaroundFlags = 0; - String codecs = format.codecs; - if (!TextUtils.isEmpty(codecs)) { - // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really - // exist. If we know from the codec attribute that they don't exist, then we can explicitly - // ignore them even if they're declared. - if (MimeTypes.getAudioMediaMimeType(codecs) != MimeTypes.AUDIO_AAC) { - workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_AAC_STREAM; - } - if (MimeTypes.getVideoMediaMimeType(codecs) != MimeTypes.VIDEO_H264) { - workaroundFlags |= TsExtractor.WORKAROUND_IGNORE_H264_STREAM; - } - } - Extractor extractor = new TsExtractor(timestampAdjuster, workaroundFlags); - ExposedTrack selectedTrack = tracks.get(selectedTrackIndex); - extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor, - switchingVariant, selectedTrack.adaptiveMaxWidth, selectedTrack.adaptiveMaxHeight); - } else { - // MPEG-2 TS segments, and we need to continue using the same extractor. - extractorWrapper = previousTsChunk.extractorWrapper; - } - out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs, - chunkMediaSequence, segment.discontinuitySequenceNumber, extractorWrapper, encryptionKey, - encryptionIv); - } - - /** - * Invoked when the {@link HlsSampleSource} has finished loading a chunk obtained from this - * source. - * - * @param chunk The chunk whose load has been completed. - */ - public void onChunkLoadCompleted(Chunk chunk) { - if (chunk instanceof MediaPlaylistChunk) { - MediaPlaylistChunk mediaPlaylistChunk = (MediaPlaylistChunk) chunk; - scratchSpace = mediaPlaylistChunk.getDataHolder(); - setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult()); - if (eventHandler != null && eventListener != null) { - final byte[] rawResponse = mediaPlaylistChunk.getRawResponse(); - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onMediaPlaylistLoadCompleted(rawResponse); - } - }); - } - } else if (chunk instanceof EncryptionKeyChunk) { - EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; - scratchSpace = encryptionKeyChunk.getDataHolder(); - setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv, - encryptionKeyChunk.getResult()); - } - } - - /** - * Invoked when the {@link HlsSampleSource} encounters an error loading a chunk obtained from - * this source. - * - * @param chunk The chunk whose load encountered the error. - * @param e The error. - * @return True if the error was handled by the source. False otherwise. - */ - public boolean onChunkLoadError(Chunk chunk, IOException e) { - if (chunk.bytesLoaded() == 0 - && (chunk instanceof TsChunk || chunk instanceof MediaPlaylistChunk - || chunk instanceof EncryptionKeyChunk) - && (e instanceof InvalidResponseCodeException)) { - InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; - int responseCode = responseCodeException.responseCode; - if (responseCode == 404 || responseCode == 410) { - int variantIndex; - if (chunk instanceof TsChunk) { - TsChunk tsChunk = (TsChunk) chunk; - variantIndex = getVariantIndex(tsChunk.format); - } else if (chunk instanceof MediaPlaylistChunk) { - MediaPlaylistChunk playlistChunk = (MediaPlaylistChunk) chunk; - variantIndex = playlistChunk.variantIndex; - } else { - EncryptionKeyChunk encryptionChunk = (EncryptionKeyChunk) chunk; - variantIndex = encryptionChunk.variantIndex; - } - boolean alreadyBlacklisted = variantBlacklistTimes[variantIndex] != 0; - variantBlacklistTimes[variantIndex] = SystemClock.elapsedRealtime(); - if (alreadyBlacklisted) { - // The playlist was already blacklisted. - Log.w(TAG, "Already blacklisted variant (" + responseCode + "): " - + chunk.dataSpec.uri); - return false; - } else if (!allVariantsBlacklisted()) { - // We've handled the 404/410 by blacklisting the variant. - Log.w(TAG, "Blacklisted variant (" + responseCode + "): " - + chunk.dataSpec.uri); - return true; - } else { - // This was the last non-blacklisted playlist. Don't blacklist it. - Log.w(TAG, "Final variant not blacklisted (" + responseCode + "): " - + chunk.dataSpec.uri); - variantBlacklistTimes[variantIndex] = 0; - return false; - } - } - } - return false; - } - - // HlsTrackSelector.Output implementation. - - @Override - public void adaptiveTrack(HlsMasterPlaylist playlist, Variant[] variants) { - Arrays.sort(variants, new Comparator() { - private final Comparator formatComparator = - new Format.DecreasingBandwidthComparator(); - @Override - public int compare(Variant first, Variant second) { - return formatComparator.compare(first.format, second.format); - } - }); - - int defaultVariantIndex = computeDefaultVariantIndex(playlist, variants, bandwidthMeter); - int maxWidth = -1; - int maxHeight = -1; - - for (int i = 0; i < variants.length; i++) { - Format variantFormat = variants[i].format; - maxWidth = Math.max(variantFormat.width, maxWidth); - maxHeight = Math.max(variantFormat.height, maxHeight); - } - // TODO: We should allow the default values to be passed through the constructor. - // TODO: Print a warning if resolution tags are omitted. - maxWidth = maxWidth > 0 ? maxWidth : 1920; - maxHeight = maxHeight > 0 ? maxHeight : 1080; - tracks.add(new ExposedTrack(variants, defaultVariantIndex, maxWidth, maxHeight)); - } - - @Override - public void fixedTrack(HlsMasterPlaylist playlist, Variant variant) { - tracks.add(new ExposedTrack(variant)); - } - - protected int computeDefaultVariantIndex(HlsMasterPlaylist playlist, Variant[] variants, - BandwidthMeter bandwidthMeter) { - int defaultVariantIndex = 0; - int minOriginalVariantIndex = Integer.MAX_VALUE; - - for (int i = 0; i < variants.length; i++) { - int originalVariantIndex = playlist.variants.indexOf(variants[i]); - if (originalVariantIndex < minOriginalVariantIndex) { - minOriginalVariantIndex = originalVariantIndex; - defaultVariantIndex = i; - } - } - - return defaultVariantIndex; - } - - // Private methods. - - private int getLiveStartChunkSequenceNumber(int variantIndex) { - // For live start playback from the third chunk from the end. - HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex]; - int chunkIndex = mediaPlaylist.segments.size() > 3 ? mediaPlaylist.segments.size() - 3 : 0; - return chunkIndex + mediaPlaylist.mediaSequence; - } - - /** - * Returns the media sequence number of a chunk in a new variant for a live stream variant switch. - * - * @param previousChunkIndex The index of the last chunk in the old variant. - * @param oldVariantIndex The index of the old variant. - * @param newVariantIndex The index of the new variant. - * @return Media sequence number of the chunk to switch to in a live stream in the variant that - * corresponds to the given {@code newVariantIndex}. - */ - private int getLiveNextChunkSequenceNumber(int previousChunkIndex, int oldVariantIndex, - int newVariantIndex) { - if (oldVariantIndex == newVariantIndex) { - return previousChunkIndex + 1; - } - HlsMediaPlaylist oldMediaPlaylist = variantPlaylists[oldVariantIndex]; - HlsMediaPlaylist newMediaPlaylist = variantPlaylists[newVariantIndex]; - double offsetToLiveInstantSecs = 0; - for (int i = previousChunkIndex - oldMediaPlaylist.mediaSequence; - i < oldMediaPlaylist.segments.size(); i++) { - offsetToLiveInstantSecs += oldMediaPlaylist.segments.get(i).durationSecs; - } - long currentTimeMs = SystemClock.elapsedRealtime(); - offsetToLiveInstantSecs += - (double) (currentTimeMs - variantLastPlaylistLoadTimesMs[oldVariantIndex]) / 1000; - offsetToLiveInstantSecs += LIVE_VARIANT_SWITCH_SAFETY_EXTRA_SECS; - offsetToLiveInstantSecs -= - (double) (currentTimeMs - variantLastPlaylistLoadTimesMs[newVariantIndex]) / 1000; - if (offsetToLiveInstantSecs < 0) { - // The instant we are looking for is not contained in the playlist, we need it to be - // refreshed. - return newMediaPlaylist.mediaSequence + newMediaPlaylist.segments.size() + 1; - } - for (int i = newMediaPlaylist.segments.size() - 1; i >= 0; i--) { - offsetToLiveInstantSecs -= newMediaPlaylist.segments.get(i).durationSecs; - if (offsetToLiveInstantSecs < 0) { - return newMediaPlaylist.mediaSequence + i; - } - } - // We have fallen behind the live window. - return newMediaPlaylist.mediaSequence - 1; - } - - private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) { - clearStaleBlacklistedVariants(); - long bitrateEstimate = bandwidthMeter.getBitrateEstimate(); - if (variantBlacklistTimes[selectedVariantIndex] != 0) { - // The current variant has been blacklisted, so we have no choice but to re-evaluate. - return getVariantIndexForBandwidth(bitrateEstimate); - } - if (previousTsChunk == null) { - // Don't consider switching if we don't have a previous chunk. - return selectedVariantIndex; - } - if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) { - // Don't consider switching if we don't have a bandwidth estimate. - return selectedVariantIndex; - } - int idealIndex = getVariantIndexForBandwidth(bitrateEstimate); - if (idealIndex == selectedVariantIndex) { - // We're already using the ideal variant. - return selectedVariantIndex; - } - // We're not using the ideal variant for the available bandwidth, but only switch if the - // conditions are appropriate. - long bufferedUs = previousTsChunk.getAdjustedEndTimeUs() - previousTsChunk.getDurationUs() - - playbackPositionUs; - if (variantBlacklistTimes[selectedVariantIndex] != 0 - || (idealIndex > selectedVariantIndex && bufferedUs < maxBufferDurationToSwitchDownUs) - || (idealIndex < selectedVariantIndex && bufferedUs > minBufferDurationToSwitchUpUs)) { - // Switch variant. - return idealIndex; - } - // Stick with the current variant for now. - return selectedVariantIndex; - } - - private int getVariantIndexForBandwidth(long bitrateEstimate) { - if (bitrateEstimate == BandwidthMeter.NO_ESTIMATE) { - // Select the lowest quality. - bitrateEstimate = 0; - } - int effectiveBitrate = (int) (bitrateEstimate * BANDWIDTH_FRACTION); - int lowestQualityEnabledVariantIndex = -1; - for (int i = 0; i < variants.length; i++) { - if (variantBlacklistTimes[i] == 0) { - if (variants[i].format.bitrate <= effectiveBitrate) { - return i; - } - lowestQualityEnabledVariantIndex = i; - } - } - // At least one variant should always be enabled. - Assertions.checkState(lowestQualityEnabledVariantIndex != -1); - return lowestQualityEnabledVariantIndex; - } - - private boolean shouldRerequestLiveMediaPlaylist(int nextVariantIndex) { - // Don't re-request media playlist more often than one-half of the target duration. - HlsMediaPlaylist mediaPlaylist = variantPlaylists[nextVariantIndex]; - long timeSinceLastMediaPlaylistLoadMs = - SystemClock.elapsedRealtime() - variantLastPlaylistLoadTimesMs[nextVariantIndex]; - return timeSinceLastMediaPlaylistLoadMs >= (mediaPlaylist.targetDurationSecs * 1000) / 2; - } - - private MediaPlaylistChunk newMediaPlaylistChunk(int variantIndex) { - Uri mediaPlaylistUri = UriUtil.resolveToUri(baseUri, variants[variantIndex].url); - DataSpec dataSpec = new DataSpec(mediaPlaylistUri, 0, C.LENGTH_UNBOUNDED, null, - DataSpec.FLAG_ALLOW_GZIP); - return new MediaPlaylistChunk(dataSource, dataSpec, scratchSpace, playlistParser, variantIndex, - mediaPlaylistUri.toString()); - } - - private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex) { - DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNBOUNDED, null, DataSpec.FLAG_ALLOW_GZIP); - return new EncryptionKeyChunk(dataSource, dataSpec, scratchSpace, iv, variantIndex); - } - - private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) { - String trimmedIv; - if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { - trimmedIv = iv.substring(2); - } else { - trimmedIv = iv; - } - - byte[] ivData = new BigInteger(trimmedIv, 16).toByteArray(); - byte[] ivDataWithPadding = new byte[16]; - int offset = ivData.length > 16 ? ivData.length - 16 : 0; - System.arraycopy(ivData, offset, ivDataWithPadding, ivDataWithPadding.length - ivData.length - + offset, ivData.length - offset); - - encryptionKeyUri = keyUri; - encryptionKey = secretKey; - encryptionIvString = iv; - encryptionIv = ivDataWithPadding; - } - - private void clearEncryptionData() { - encryptionKeyUri = null; - encryptionKey = null; - encryptionIvString = null; - encryptionIv = null; - } - - private void setMediaPlaylist(int variantIndex, HlsMediaPlaylist mediaPlaylist) { - variantLastPlaylistLoadTimesMs[variantIndex] = SystemClock.elapsedRealtime(); - variantPlaylists[variantIndex] = mediaPlaylist; - live |= mediaPlaylist.live; - durationUs = live ? C.UNKNOWN_TIME_US : mediaPlaylist.durationUs; - } - - private boolean allVariantsBlacklisted() { - for (long variantBlacklistTime : variantBlacklistTimes) { - if (variantBlacklistTime == 0) { - return false; - } - } - return true; - } - - private void clearStaleBlacklistedVariants() { - long currentTime = SystemClock.elapsedRealtime(); - for (int i = 0; i < variantBlacklistTimes.length; i++) { - if (variantBlacklistTimes[i] != 0 - && currentTime - variantBlacklistTimes[i] > DEFAULT_PLAYLIST_BLACKLIST_MS) { - variantBlacklistTimes[i] = 0; - } - } - } - - private int getVariantIndex(Format format) { - for (int i = 0; i < variants.length; i++) { - if (variants[i].format.equals(format)) { - return i; - } - } - // Should never happen. - throw new IllegalStateException("Invalid format: " + format); - } - - // Private classes. - - private static final class ExposedTrack { - - private final Variant[] variants; - private final int defaultVariantIndex; - - private final int adaptiveMaxWidth; - private final int adaptiveMaxHeight; - - public ExposedTrack(Variant fixedVariant) { - this.variants = new Variant[] {fixedVariant}; - this.defaultVariantIndex = 0; - this.adaptiveMaxWidth = MediaFormat.NO_VALUE; - this.adaptiveMaxHeight = MediaFormat.NO_VALUE; - } - - public ExposedTrack(Variant[] adaptiveVariants, int defaultVariantIndex, int maxWidth, - int maxHeight) { - this.variants = adaptiveVariants; - this.defaultVariantIndex = defaultVariantIndex; - this.adaptiveMaxWidth = maxWidth; - this.adaptiveMaxHeight = maxHeight; - } - - } - - private static final class MediaPlaylistChunk extends DataChunk { - - public final int variantIndex; - - private final HlsPlaylistParser playlistParser; - private final String playlistUrl; - - private byte[] rawResponse; - private HlsMediaPlaylist result; - - public MediaPlaylistChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, - HlsPlaylistParser playlistParser, int variantIndex, String playlistUrl) { - super(dataSource, dataSpec, Chunk.TYPE_MANIFEST, Chunk.TRIGGER_UNSPECIFIED, null, - Chunk.NO_PARENT_ID, scratchSpace); - this.variantIndex = variantIndex; - this.playlistParser = playlistParser; - this.playlistUrl = playlistUrl; - } - - @Override - protected void consume(byte[] data, int limit) throws IOException { - rawResponse = Arrays.copyOf(data, limit); - result = (HlsMediaPlaylist) playlistParser.parse(playlistUrl, - new ByteArrayInputStream(rawResponse)); - } - - public byte[] getRawResponse() { - return rawResponse; - } - - public HlsMediaPlaylist getResult() { - return result; - } - - } - - private static final class EncryptionKeyChunk extends DataChunk { - - public final String iv; - public final int variantIndex; - - private byte[] result; - - public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, byte[] scratchSpace, - String iv, int variantIndex) { - super(dataSource, dataSpec, Chunk.TYPE_DRM, Chunk.TRIGGER_UNSPECIFIED, null, - Chunk.NO_PARENT_ID, scratchSpace); - this.iv = iv; - this.variantIndex = variantIndex; - } - - @Override - protected void consume(byte[] data, int limit) throws IOException { - result = Arrays.copyOf(data, limit); - } - - public byte[] getResult() { - return result; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsExtractorWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsExtractorWrapper.java deleted file mode 100755 index 6b87b4e0299..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsExtractorWrapper.java +++ /dev/null @@ -1,276 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.extractor.DefaultTrackOutput; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.io.IOException; - -/** - * Wraps a {@link Extractor}, adding functionality to enable reading of the extracted samples. - */ -public final class HlsExtractorWrapper implements ExtractorOutput { - - public final int trigger; - public final Format format; - public final long startTimeUs; - - private final Extractor extractor; - private final SparseArray sampleQueues; - private final boolean shouldSpliceIn; - private final int adaptiveMaxWidth; - private final int adaptiveMaxHeight; - - private MediaFormat[] sampleQueueFormats; - private Allocator allocator; - - private volatile boolean tracksBuilt; - - // Accessed only by the consuming thread. - private boolean prepared; - private boolean spliceConfigured; - - public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, Extractor extractor, - boolean shouldSpliceIn, int adaptiveMaxWidth, int adaptiveMaxHeight) { - this.trigger = trigger; - this.format = format; - this.startTimeUs = startTimeUs; - this.extractor = extractor; - this.shouldSpliceIn = shouldSpliceIn; - this.adaptiveMaxWidth = adaptiveMaxWidth; - this.adaptiveMaxHeight = adaptiveMaxHeight; - sampleQueues = new SparseArray<>(); - } - - /** - * Initializes the wrapper for use. - * - * @param allocator An allocator for obtaining allocations into which extracted data is written. - */ - public void init(Allocator allocator) { - this.allocator = allocator; - extractor.init(this); - } - - /** - * Whether the extractor is prepared. - * - * @return True if the extractor is prepared. False otherwise. - */ - public boolean isPrepared() { - if (!prepared && tracksBuilt) { - for (int i = 0; i < sampleQueues.size(); i++) { - if (!sampleQueues.valueAt(i).hasFormat()) { - return false; - } - } - prepared = true; - sampleQueueFormats = new MediaFormat[sampleQueues.size()]; - for (int i = 0; i < sampleQueueFormats.length; i++) { - MediaFormat format = sampleQueues.valueAt(i).getFormat(); - if (MimeTypes.isVideo(format.mimeType) && (adaptiveMaxWidth != MediaFormat.NO_VALUE - || adaptiveMaxHeight != MediaFormat.NO_VALUE)) { - format = format.copyWithMaxVideoDimensions(adaptiveMaxWidth, adaptiveMaxHeight); - } - sampleQueueFormats[i] = format; - } - } - return prepared; - } - - /** - * Clears queues for all tracks, returning all allocations to the allocator. - */ - public void clear() { - for (int i = 0; i < sampleQueues.size(); i++) { - sampleQueues.valueAt(i).clear(); - } - } - - /** - * Gets the largest timestamp of any sample parsed by the extractor. - * - * @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed. - */ - public long getLargestParsedTimestampUs() { - long largestParsedTimestampUs = Long.MIN_VALUE; - for (int i = 0; i < sampleQueues.size(); i++) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, - sampleQueues.valueAt(i).getLargestParsedTimestampUs()); - } - return largestParsedTimestampUs; - } - - /** - * Attempts to configure a splice from this extractor to the next. - *

        - * The splice is performed such that for each track the samples read from the next extractor - * start with a keyframe, and continue from where the samples read from this extractor finish. - * A successful splice may discard samples from either or both extractors. - *

        - * Splice configuration may fail if the next extractor is not yet in a state that allows the - * splice to be performed. Calling this method is a noop if the splice has already been - * configured. Hence this method should be called repeatedly during the window within which a - * splice can be performed. - *

        - * This method must only be called after the extractor has been prepared. - * - * @param nextExtractor The extractor being spliced to. - */ - public final void configureSpliceTo(HlsExtractorWrapper nextExtractor) { - Assertions.checkState(isPrepared()); - if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) { - // The splice is already configured, or the next extractor doesn't want to be spliced in, or - // the next extractor isn't ready to be spliced in. - return; - } - boolean spliceConfigured = true; - int trackCount = getTrackCount(); - for (int i = 0; i < trackCount; i++) { - DefaultTrackOutput currentSampleQueue = sampleQueues.valueAt(i); - DefaultTrackOutput nextSampleQueue = nextExtractor.sampleQueues.valueAt(i); - spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue); - } - this.spliceConfigured = spliceConfigured; - return; - } - - /** - * Gets the number of available tracks. - *

        - * This method must only be called after the extractor has been prepared. - * - * @return The number of available tracks. - */ - public int getTrackCount() { - Assertions.checkState(isPrepared()); - return sampleQueues.size(); - } - - /** - * Gets the {@link MediaFormat} of the specified track. - *

        - * This method must only be called after the extractor has been prepared. - * - * @param track The track index. - * @return The corresponding format. - */ - public MediaFormat getMediaFormat(int track) { - Assertions.checkState(isPrepared()); - return sampleQueueFormats[track]; - } - - /** - * Gets the next sample for the specified track. - *

        - * This method must only be called after the extractor has been prepared. - * - * @param track The track from which to read. - * @param holder A {@link SampleHolder} into which the sample should be read. - * @return True if a sample was read. False otherwise. - */ - public boolean getSample(int track, SampleHolder holder) { - Assertions.checkState(isPrepared()); - return sampleQueues.valueAt(track).getSample(holder); - } - - /** - * Discards samples for the specified track up to the specified time. - *

        - * This method must only be called after the extractor has been prepared. - * - * @param track The track from which samples should be discarded. - * @param timeUs The time up to which samples should be discarded, in microseconds. - */ - public void discardUntil(int track, long timeUs) { - Assertions.checkState(isPrepared()); - sampleQueues.valueAt(track).discardUntil(timeUs); - } - - /** - * Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the - * specified track. - *

        - * This method must only be called after the extractor has been prepared. - * - * @return True if samples are available for reading from {@link #getSample(int, SampleHolder)} - * for the specified track. False otherwise. - */ - public boolean hasSamples(int track) { - Assertions.checkState(isPrepared()); - return !sampleQueues.valueAt(track).isEmpty(); - } - - /** - * Reads from the provided {@link ExtractorInput}. - * - * @param input The {@link ExtractorInput} from which to read. - * @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}. - * @throws IOException If an error occurred reading from the source. - * @throws InterruptedException If the thread was interrupted. - */ - public int read(ExtractorInput input) throws IOException, InterruptedException { - int result = extractor.read(input, null); - Assertions.checkState(result != Extractor.RESULT_SEEK); - return result; - } - - public long getAdjustedEndTimeUs() { - long largestAdjustedPtsParsed = Long.MIN_VALUE; - for (int i = 0; i < sampleQueues.size(); i++) { - largestAdjustedPtsParsed = Math.max(largestAdjustedPtsParsed, - sampleQueues.valueAt(i).getLargestParsedTimestampUs()); - } - return largestAdjustedPtsParsed; - } - - // ExtractorOutput implementation. - - @Override - public TrackOutput track(int id) { - DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator); - sampleQueues.put(id, sampleQueue); - return sampleQueue; - } - - @Override - public void endTracks() { - this.tracksBuilt = true; - } - - @Override - public void seekMap(SeekMap seekMap) { - // Do nothing. - } - - @Override - public void drmInitData(DrmInitData drmInit) { - // Do nothing. - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMasterPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMasterPlaylist.java deleted file mode 100755 index b337e65faa2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMasterPlaylist.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import java.util.Collections; -import java.util.List; - -/** - * Represents an HLS master playlist. - */ -public final class HlsMasterPlaylist extends HlsPlaylist { - - public final List variants; - public final List audios; - public final List subtitles; - - public final String muxedAudioLanguage; - public final String muxedCaptionLanguage; - - public HlsMasterPlaylist(String baseUri, List variants, - List audios, List subtitles, String muxedAudioLanguage, - String muxedCaptionLanguage) { - super(baseUri, HlsPlaylist.TYPE_MASTER); - this.variants = Collections.unmodifiableList(variants); - this.audios = Collections.unmodifiableList(audios); - this.subtitles = Collections.unmodifiableList(subtitles); - this.muxedAudioLanguage = muxedAudioLanguage; - this.muxedCaptionLanguage = muxedCaptionLanguage; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMediaPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMediaPlaylist.java deleted file mode 100755 index 154844cfe92..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsMediaPlaylist.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import org.telegram.messenger.exoplayer.C; -import java.util.List; - -/** - * Represents an HLS media playlist. - */ -public final class HlsMediaPlaylist extends HlsPlaylist { - - /** - * Media segment reference. - */ - public static final class Segment implements Comparable { - - public final String url; - public final double durationSecs; - public final int discontinuitySequenceNumber; - public final long startTimeUs; - public final boolean isEncrypted; - public final String encryptionKeyUri; - public final String encryptionIV; - public final long byterangeOffset; - public final long byterangeLength; - - public Segment(String uri, double durationSecs, int discontinuitySequenceNumber, - long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, - long byterangeOffset, long byterangeLength) { - this.url = uri; - this.durationSecs = durationSecs; - this.discontinuitySequenceNumber = discontinuitySequenceNumber; - this.startTimeUs = startTimeUs; - this.isEncrypted = isEncrypted; - this.encryptionKeyUri = encryptionKeyUri; - this.encryptionIV = encryptionIV; - this.byterangeOffset = byterangeOffset; - this.byterangeLength = byterangeLength; - } - - @Override - public int compareTo(Long startTimeUs) { - return this.startTimeUs > startTimeUs ? 1 : (this.startTimeUs < startTimeUs ? -1 : 0); - } - } - - public static final String ENCRYPTION_METHOD_NONE = "NONE"; - public static final String ENCRYPTION_METHOD_AES_128 = "AES-128"; - - public final int mediaSequence; - public final int targetDurationSecs; - public final int version; - public final List segments; - public final boolean live; - public final long durationUs; - - public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version, - boolean live, List segments) { - super(baseUri, HlsPlaylist.TYPE_MEDIA); - this.mediaSequence = mediaSequence; - this.targetDurationSecs = targetDurationSecs; - this.version = version; - this.live = live; - this.segments = segments; - - if (!segments.isEmpty()) { - Segment last = segments.get(segments.size() - 1); - durationUs = last.startTimeUs + (long) (last.durationSecs * C.MICROS_PER_SECOND); - } else { - durationUs = 0; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsParserUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsParserUtil.java deleted file mode 100755 index c54cde2c5b0..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsParserUtil.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import org.telegram.messenger.exoplayer.ParserException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Utility methods for HLS manifest parsing. - */ -/* package */ final class HlsParserUtil { - - private static final String BOOLEAN_YES = "YES"; - private static final String BOOLEAN_NO = "NO"; - - private HlsParserUtil() {} - - public static String parseStringAttr(String line, Pattern pattern, String tag) - throws ParserException { - Matcher matcher = pattern.matcher(line); - if (matcher.find() && matcher.groupCount() == 1) { - return matcher.group(1); - } - throw new ParserException("Couldn't match " + tag + " tag in " + line); - } - - public static int parseIntAttr(String line, Pattern pattern, String tag) - throws ParserException { - return Integer.parseInt(parseStringAttr(line, pattern, tag)); - } - - public static double parseDoubleAttr(String line, Pattern pattern, String tag) - throws ParserException { - return Double.parseDouble(parseStringAttr(line, pattern, tag)); - } - - public static String parseOptionalStringAttr(String line, Pattern pattern) { - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - return matcher.group(1); - } - return null; - } - - public static boolean parseOptionalBooleanAttr(String line, Pattern pattern) { - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - return BOOLEAN_YES.equals(matcher.group(1)); - } - return false; - } - - public static Pattern compileBooleanAttrPattern(String attrName) { - return Pattern.compile(attrName + "=(" + BOOLEAN_YES + "|" + BOOLEAN_NO + ")"); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylist.java deleted file mode 100755 index 16e1fafbeea..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -/** - * Represents an HLS playlist. - */ -public abstract class HlsPlaylist { - - public final static int TYPE_MASTER = 0; - public final static int TYPE_MEDIA = 1; - - public final String baseUri; - public final int type; - - protected HlsPlaylist(String baseUri, int type) { - this.baseUri = baseUri; - this.type = type; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylistParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylistParser.java deleted file mode 100755 index 4c9fddbf42d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsPlaylistParser.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.hls.HlsMediaPlaylist.Segment; -import org.telegram.messenger.exoplayer.upstream.UriLoadable; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.regex.Pattern; - -/** - * HLS playlists parsing logic. - */ -public final class HlsPlaylistParser implements UriLoadable.Parser { - - private static final String VERSION_TAG = "#EXT-X-VERSION"; - private static final String STREAM_INF_TAG = "#EXT-X-STREAM-INF"; - private static final String MEDIA_TAG = "#EXT-X-MEDIA"; - private static final String DISCONTINUITY_TAG = "#EXT-X-DISCONTINUITY"; - private static final String DISCONTINUITY_SEQUENCE_TAG = "#EXT-X-DISCONTINUITY-SEQUENCE"; - private static final String MEDIA_DURATION_TAG = "#EXTINF"; - private static final String MEDIA_SEQUENCE_TAG = "#EXT-X-MEDIA-SEQUENCE"; - private static final String TARGET_DURATION_TAG = "#EXT-X-TARGETDURATION"; - private static final String ENDLIST_TAG = "#EXT-X-ENDLIST"; - private static final String KEY_TAG = "#EXT-X-KEY"; - private static final String BYTERANGE_TAG = "#EXT-X-BYTERANGE"; - - private static final String BANDWIDTH_ATTR = "BANDWIDTH"; - private static final String CODECS_ATTR = "CODECS"; - private static final String RESOLUTION_ATTR = "RESOLUTION"; - private static final String LANGUAGE_ATTR = "LANGUAGE"; - private static final String NAME_ATTR = "NAME"; - private static final String TYPE_ATTR = "TYPE"; - private static final String METHOD_ATTR = "METHOD"; - private static final String URI_ATTR = "URI"; - private static final String IV_ATTR = "IV"; - private static final String INSTREAM_ID_ATTR = "INSTREAM-ID"; - - private static final String AUDIO_TYPE = "AUDIO"; - private static final String VIDEO_TYPE = "VIDEO"; - private static final String SUBTITLES_TYPE = "SUBTITLES"; - private static final String CLOSED_CAPTIONS_TYPE = "CLOSED-CAPTIONS"; - - private static final String METHOD_NONE = "NONE"; - private static final String METHOD_AES128 = "AES-128"; - - private static final Pattern BANDWIDTH_ATTR_REGEX = - Pattern.compile(BANDWIDTH_ATTR + "=(\\d+)\\b"); - private static final Pattern CODECS_ATTR_REGEX = - Pattern.compile(CODECS_ATTR + "=\"(.+?)\""); - private static final Pattern RESOLUTION_ATTR_REGEX = - Pattern.compile(RESOLUTION_ATTR + "=(\\d+x\\d+)"); - private static final Pattern MEDIA_DURATION_REGEX = - Pattern.compile(MEDIA_DURATION_TAG + ":([\\d.]+)\\b"); - private static final Pattern MEDIA_SEQUENCE_REGEX = - Pattern.compile(MEDIA_SEQUENCE_TAG + ":(\\d+)\\b"); - private static final Pattern TARGET_DURATION_REGEX = - Pattern.compile(TARGET_DURATION_TAG + ":(\\d+)\\b"); - private static final Pattern VERSION_REGEX = - Pattern.compile(VERSION_TAG + ":(\\d+)\\b"); - private static final Pattern BYTERANGE_REGEX = - Pattern.compile(BYTERANGE_TAG + ":(\\d+(?:@\\d+)?)\\b"); - - private static final Pattern METHOD_ATTR_REGEX = - Pattern.compile(METHOD_ATTR + "=(" + METHOD_NONE + "|" + METHOD_AES128 + ")"); - private static final Pattern URI_ATTR_REGEX = - Pattern.compile(URI_ATTR + "=\"(.+?)\""); - private static final Pattern IV_ATTR_REGEX = - Pattern.compile(IV_ATTR + "=([^,.*]+)"); - private static final Pattern TYPE_ATTR_REGEX = - Pattern.compile(TYPE_ATTR + "=(" + AUDIO_TYPE + "|" + VIDEO_TYPE + "|" + SUBTITLES_TYPE + "|" - + CLOSED_CAPTIONS_TYPE + ")"); - private static final Pattern LANGUAGE_ATTR_REGEX = - Pattern.compile(LANGUAGE_ATTR + "=\"(.+?)\""); - private static final Pattern NAME_ATTR_REGEX = - Pattern.compile(NAME_ATTR + "=\"(.+?)\""); - private static final Pattern INSTREAM_ID_ATTR_REGEX = - Pattern.compile(INSTREAM_ID_ATTR + "=\"(.+?)\""); - // private static final Pattern AUTOSELECT_ATTR_REGEX = - // HlsParserUtil.compileBooleanAttrPattern(AUTOSELECT_ATTR); - // private static final Pattern DEFAULT_ATTR_REGEX = - // HlsParserUtil.compileBooleanAttrPattern(DEFAULT_ATTR); - - @Override - public HlsPlaylist parse(String connectionUrl, InputStream inputStream) - throws IOException, ParserException { - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - Queue extraLines = new LinkedList<>(); - String line; - try { - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (line.isEmpty()) { - // Do nothing. - } else if (line.startsWith(STREAM_INF_TAG)) { - extraLines.add(line); - return parseMasterPlaylist(new LineIterator(extraLines, reader), connectionUrl); - } else if (line.startsWith(TARGET_DURATION_TAG) - || line.startsWith(MEDIA_SEQUENCE_TAG) - || line.startsWith(MEDIA_DURATION_TAG) - || line.startsWith(KEY_TAG) - || line.startsWith(BYTERANGE_TAG) - || line.equals(DISCONTINUITY_TAG) - || line.equals(DISCONTINUITY_SEQUENCE_TAG) - || line.equals(ENDLIST_TAG)) { - extraLines.add(line); - return parseMediaPlaylist(new LineIterator(extraLines, reader), connectionUrl); - } else { - extraLines.add(line); - } - } - } finally { - reader.close(); - } - throw new ParserException("Failed to parse the playlist, could not identify any tags."); - } - - private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri) - throws IOException { - ArrayList variants = new ArrayList<>(); - ArrayList audios = new ArrayList<>(); - ArrayList subtitles = new ArrayList<>(); - int bitrate = 0; - String codecs = null; - int width = -1; - int height = -1; - String name = null; - String muxedAudioLanguage = null; - String muxedCaptionLanguage = null; - - boolean expectingStreamInfUrl = false; - String line; - while (iterator.hasNext()) { - line = iterator.next(); - if (line.startsWith(MEDIA_TAG)) { - String type = HlsParserUtil.parseStringAttr(line, TYPE_ATTR_REGEX, TYPE_ATTR); - if (CLOSED_CAPTIONS_TYPE.equals(type)) { - String instreamId = HlsParserUtil.parseStringAttr(line, INSTREAM_ID_ATTR_REGEX, - INSTREAM_ID_ATTR); - if ("CC1".equals(instreamId)) { - muxedCaptionLanguage = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX); - } - } else if (SUBTITLES_TYPE.equals(type)) { - // We assume all subtitles belong to the same group. - String subtitleName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR); - String uri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR); - String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX); - Format format = new Format(subtitleName, MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, - -1, language, codecs); - subtitles.add(new Variant(uri, format)); - } else if (AUDIO_TYPE.equals(type)) { - // We assume all audios belong to the same group. - String language = HlsParserUtil.parseOptionalStringAttr(line, LANGUAGE_ATTR_REGEX); - String uri = HlsParserUtil.parseOptionalStringAttr(line, URI_ATTR_REGEX); - if (uri != null) { - String audioName = HlsParserUtil.parseStringAttr(line, NAME_ATTR_REGEX, NAME_ATTR); - Format format = new Format(audioName, MimeTypes.APPLICATION_M3U8, -1, -1, -1, -1, -1, - -1, language, codecs); - audios.add(new Variant(uri, format)); - } else { - muxedAudioLanguage = language; - } - } - } else if (line.startsWith(STREAM_INF_TAG)) { - bitrate = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR); - codecs = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX); - name = HlsParserUtil.parseOptionalStringAttr(line, NAME_ATTR_REGEX); - String resolutionString = HlsParserUtil.parseOptionalStringAttr(line, - RESOLUTION_ATTR_REGEX); - if (resolutionString != null) { - String[] widthAndHeight = resolutionString.split("x"); - width = Integer.parseInt(widthAndHeight[0]); - if (width <= 0) { - // Width was invalid. - width = -1; - } - height = Integer.parseInt(widthAndHeight[1]); - if (height <= 0) { - // Height was invalid. - height = -1; - } - } else { - width = -1; - height = -1; - } - expectingStreamInfUrl = true; - } else if (!line.startsWith("#") && expectingStreamInfUrl) { - if (name == null) { - name = Integer.toString(variants.size()); - } - Format format = new Format(name, MimeTypes.APPLICATION_M3U8, width, height, -1, -1, -1, - bitrate, null, codecs); - variants.add(new Variant(line, format)); - bitrate = 0; - codecs = null; - name = null; - width = -1; - height = -1; - expectingStreamInfUrl = false; - } - } - return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioLanguage, - muxedCaptionLanguage); - } - - private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) - throws IOException { - int mediaSequence = 0; - int targetDurationSecs = 0; - int version = 1; // Default version == 1. - boolean live = true; - List segments = new ArrayList<>(); - - double segmentDurationSecs = 0.0; - int discontinuitySequenceNumber = 0; - long segmentStartTimeUs = 0; - long segmentByterangeOffset = 0; - long segmentByterangeLength = C.LENGTH_UNBOUNDED; - int segmentMediaSequence = 0; - - boolean isEncrypted = false; - String encryptionKeyUri = null; - String encryptionIV = null; - - String line; - while (iterator.hasNext()) { - line = iterator.next(); - if (line.startsWith(TARGET_DURATION_TAG)) { - targetDurationSecs = HlsParserUtil.parseIntAttr(line, TARGET_DURATION_REGEX, - TARGET_DURATION_TAG); - } else if (line.startsWith(MEDIA_SEQUENCE_TAG)) { - mediaSequence = HlsParserUtil.parseIntAttr(line, MEDIA_SEQUENCE_REGEX, MEDIA_SEQUENCE_TAG); - segmentMediaSequence = mediaSequence; - } else if (line.startsWith(VERSION_TAG)) { - version = HlsParserUtil.parseIntAttr(line, VERSION_REGEX, VERSION_TAG); - } else if (line.startsWith(MEDIA_DURATION_TAG)) { - segmentDurationSecs = HlsParserUtil.parseDoubleAttr(line, MEDIA_DURATION_REGEX, - MEDIA_DURATION_TAG); - } else if (line.startsWith(KEY_TAG)) { - String method = HlsParserUtil.parseStringAttr(line, METHOD_ATTR_REGEX, METHOD_ATTR); - isEncrypted = METHOD_AES128.equals(method); - if (isEncrypted) { - encryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, URI_ATTR); - encryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX); - } else { - encryptionKeyUri = null; - encryptionIV = null; - } - } else if (line.startsWith(BYTERANGE_TAG)) { - String byteRange = HlsParserUtil.parseStringAttr(line, BYTERANGE_REGEX, BYTERANGE_TAG); - String[] splitByteRange = byteRange.split("@"); - segmentByterangeLength = Long.parseLong(splitByteRange[0]); - if (splitByteRange.length > 1) { - segmentByterangeOffset = Long.parseLong(splitByteRange[1]); - } - } else if (line.startsWith(DISCONTINUITY_SEQUENCE_TAG)) { - discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1)); - } else if (line.equals(DISCONTINUITY_TAG)) { - discontinuitySequenceNumber++; - } else if (!line.startsWith("#")) { - String segmentEncryptionIV; - if (!isEncrypted) { - segmentEncryptionIV = null; - } else if (encryptionIV != null) { - segmentEncryptionIV = encryptionIV; - } else { - segmentEncryptionIV = Integer.toHexString(segmentMediaSequence); - } - segmentMediaSequence++; - if (segmentByterangeLength == C.LENGTH_UNBOUNDED) { - segmentByterangeOffset = 0; - } - segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber, - segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, - segmentByterangeOffset, segmentByterangeLength)); - segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND); - segmentDurationSecs = 0.0; - if (segmentByterangeLength != C.LENGTH_UNBOUNDED) { - segmentByterangeOffset += segmentByterangeLength; - } - segmentByterangeLength = C.LENGTH_UNBOUNDED; - } else if (line.equals(ENDLIST_TAG)) { - live = false; - } - } - return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, live, - Collections.unmodifiableList(segments)); - } - - private static class LineIterator { - - private final BufferedReader reader; - private final Queue extraLines; - - private String next; - - public LineIterator(Queue extraLines, BufferedReader reader) { - this.extraLines = extraLines; - this.reader = reader; - } - - public boolean hasNext() throws IOException { - if (next != null) { - return true; - } - if (!extraLines.isEmpty()) { - next = extraLines.poll(); - return true; - } - while ((next = reader.readLine()) != null) { - next = next.trim(); - if (!next.isEmpty()) { - return true; - } - } - return false; - } - - public String next() throws IOException { - String result = null; - if (hasNext()) { - result = next; - next = null; - } - return result; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsSampleSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsSampleSource.java deleted file mode 100755 index 451fcc4f77c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsSampleSource.java +++ /dev/null @@ -1,840 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.os.Handler; -import android.os.SystemClock; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.LoadControl; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSource.SampleSourceReader; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.chunk.BaseChunkSampleSourceEventListener; -import org.telegram.messenger.exoplayer.chunk.Chunk; -import org.telegram.messenger.exoplayer.chunk.ChunkOperationHolder; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.io.IOException; -import java.util.Arrays; -import java.util.LinkedList; - -/** - * A {@link SampleSource} for HLS streams. - */ -public final class HlsSampleSource implements SampleSource, SampleSourceReader, Loader.Callback { - - /** - * Interface definition for a callback to be notified of {@link HlsSampleSource} events. - */ - public interface EventListener extends BaseChunkSampleSourceEventListener {} - - /** - * The default minimum number of times to retry loading data prior to failing. - */ - public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; - - private static final long NO_RESET_PENDING = Long.MIN_VALUE; - - private static final int PRIMARY_TYPE_NONE = 0; - private static final int PRIMARY_TYPE_TEXT = 1; - private static final int PRIMARY_TYPE_AUDIO = 2; - private static final int PRIMARY_TYPE_VIDEO = 3; - - private final HlsChunkSource chunkSource; - private final LinkedList extractors; - private final int minLoadableRetryCount; - private final int bufferSizeContribution; - private final ChunkOperationHolder chunkOperationHolder; - - private final int eventSourceId; - private final LoadControl loadControl; - private final Handler eventHandler; - private final EventListener eventListener; - - private int remainingReleaseCount; - private boolean prepared; - private boolean loadControlRegistered; - private int trackCount; - private int enabledTrackCount; - - private Format downstreamFormat; - - // Tracks are complicated in HLS. See documentation of buildTracks for details. - // Indexed by track (as exposed by this source). - private MediaFormat[] trackFormats; - private boolean[] trackEnabledStates; - private boolean[] pendingDiscontinuities; - private MediaFormat[] downstreamMediaFormats; - // Maps track index (as exposed by this source) to the corresponding chunk source track index for - // primary tracks, or to -1 otherwise. - private int[] chunkSourceTrackIndices; - // Maps track index (as exposed by this source) to the corresponding extractor track index. - private int[] extractorTrackIndices; - // Indexed by extractor track index. - private boolean[] extractorTrackEnabledStates; - - private long downstreamPositionUs; - private long lastSeekPositionUs; - private long pendingResetPositionUs; - - private boolean loadingFinished; - private Chunk currentLoadable; - private TsChunk currentTsLoadable; - private TsChunk previousTsLoadable; - - private Loader loader; - private IOException currentLoadableException; - private int currentLoadableExceptionCount; - private long currentLoadableExceptionTimestamp; - private long currentLoadStartTimeMs; - - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution) { - this(chunkSource, loadControl, bufferSizeContribution, null, null, 0); - } - - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, EventListener eventListener, - int eventSourceId) { - this(chunkSource, loadControl, bufferSizeContribution, eventHandler, eventListener, - eventSourceId, DEFAULT_MIN_LOADABLE_RETRY_COUNT); - } - - public HlsSampleSource(HlsChunkSource chunkSource, LoadControl loadControl, - int bufferSizeContribution, Handler eventHandler, EventListener eventListener, - int eventSourceId, int minLoadableRetryCount) { - this.chunkSource = chunkSource; - this.loadControl = loadControl; - this.bufferSizeContribution = bufferSizeContribution; - this.minLoadableRetryCount = minLoadableRetryCount; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.eventSourceId = eventSourceId; - this.pendingResetPositionUs = NO_RESET_PENDING; - extractors = new LinkedList<>(); - chunkOperationHolder = new ChunkOperationHolder(); - } - - @Override - public SampleSourceReader register() { - remainingReleaseCount++; - return this; - } - - @Override - public boolean prepare(long positionUs) { - if (prepared) { - return true; - } else if (!chunkSource.prepare()) { - return false; - } - if (!extractors.isEmpty()) { - while (true) { - // We're not prepared, but we might have loaded what we need. - HlsExtractorWrapper extractor = extractors.getFirst(); - if (extractor.isPrepared()) { - buildTracks(extractor); - prepared = true; - maybeStartLoading(); // Update the load control. - return true; - } else if (extractors.size() > 1) { - extractors.removeFirst().clear(); - } else { - break; - } - } - } - // We're not prepared and we haven't loaded what we need. - if (loader == null) { - loader = new Loader("Loader:HLS"); - loadControl.register(this, bufferSizeContribution); - loadControlRegistered = true; - } - if (!loader.isLoading()) { - // We're going to have to start loading a chunk to get what we need for preparation. We should - // attempt to load the chunk at positionUs, so that we'll already be loading the correct chunk - // in the common case where the renderer is subsequently enabled at this position. - pendingResetPositionUs = positionUs; - downstreamPositionUs = positionUs; - } - maybeStartLoading(); - return false; - } - - @Override - public int getTrackCount() { - Assertions.checkState(prepared); - return trackCount; - } - - @Override - public MediaFormat getFormat(int track) { - Assertions.checkState(prepared); - return trackFormats[track]; - } - - @Override - public void enable(int track, long positionUs) { - Assertions.checkState(prepared); - setTrackEnabledState(track, true); - downstreamMediaFormats[track] = null; - pendingDiscontinuities[track] = false; - downstreamFormat = null; - boolean wasLoadControlRegistered = loadControlRegistered; - if (!loadControlRegistered) { - loadControl.register(this, bufferSizeContribution); - loadControlRegistered = true; - } - // Treat enabling of a live stream as occurring at t=0 in both of the blocks below. - positionUs = chunkSource.isLive() ? 0 : positionUs; - int chunkSourceTrack = chunkSourceTrackIndices[track]; - if (chunkSourceTrack != -1 && chunkSourceTrack != chunkSource.getSelectedTrackIndex()) { - // This is a primary track whose corresponding chunk source track is different to the one - // currently selected. We need to change the selection and restart. Since other exposed tracks - // may be enabled too, we need to implement the restart as a seek so that all downstream - // renderers receive a discontinuity event. - chunkSource.selectTrack(chunkSourceTrack); - seekToInternal(positionUs); - return; - } - if (enabledTrackCount == 1) { - lastSeekPositionUs = positionUs; - if (wasLoadControlRegistered && downstreamPositionUs == positionUs) { - // TODO: Address [Internal: b/21743989] to remove the need for this kind of hack. - // This is the first track to be enabled after preparation and the position is the same as - // was passed to prepare. In this case we can avoid restarting, which would reload the same - // chunks as were loaded during preparation. - maybeStartLoading(); - } else { - downstreamPositionUs = positionUs; - restartFrom(positionUs); - } - } - } - - @Override - public void disable(int track) { - Assertions.checkState(prepared); - setTrackEnabledState(track, false); - if (enabledTrackCount == 0) { - chunkSource.reset(); - downstreamPositionUs = Long.MIN_VALUE; - if (loadControlRegistered) { - loadControl.unregister(this); - loadControlRegistered = false; - } - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - clearState(); - loadControl.trimAllocator(); - } - } - } - - @Override - public boolean continueBuffering(int track, long playbackPositionUs) { - Assertions.checkState(prepared); - Assertions.checkState(trackEnabledStates[track]); - downstreamPositionUs = playbackPositionUs; - if (!extractors.isEmpty()) { - discardSamplesForDisabledTracks(getCurrentExtractor(), downstreamPositionUs); - } - maybeStartLoading(); - if (loadingFinished) { - return true; - } - if (isPendingReset() || extractors.isEmpty()) { - return false; - } - for (int extractorIndex = 0; extractorIndex < extractors.size(); extractorIndex++) { - HlsExtractorWrapper extractor = extractors.get(extractorIndex); - if (!extractor.isPrepared()) { - break; - } - int extractorTrack = extractorTrackIndices[track]; - if (extractor.hasSamples(extractorTrack)) { - return true; - } - } - return false; - } - - @Override - public long readDiscontinuity(int track) { - if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return lastSeekPositionUs; - } - return NO_DISCONTINUITY; - } - - @Override - public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder) { - Assertions.checkState(prepared); - downstreamPositionUs = playbackPositionUs; - - if (pendingDiscontinuities[track] || isPendingReset()) { - return NOTHING_READ; - } - - HlsExtractorWrapper extractor = getCurrentExtractor(); - if (!extractor.isPrepared()) { - return NOTHING_READ; - } - - Format format = extractor.format; - if (!format.equals(downstreamFormat)) { - notifyDownstreamFormatChanged(format, extractor.trigger, extractor.startTimeUs); - } - downstreamFormat = format; - - if (extractors.size() > 1) { - // If there's more than one extractor, attempt to configure a seamless splice from the - // current one to the next one. - extractor.configureSpliceTo(extractors.get(1)); - } - - int extractorTrack = extractorTrackIndices[track]; - int extractorIndex = 0; - while (extractors.size() > extractorIndex + 1 && !extractor.hasSamples(extractorTrack)) { - // We're finished reading from the extractor for this particular track, so advance to the - // next one for the current read. - extractor = extractors.get(++extractorIndex); - if (!extractor.isPrepared()) { - return NOTHING_READ; - } - } - - MediaFormat mediaFormat = extractor.getMediaFormat(extractorTrack); - if (mediaFormat != null) { - if (!mediaFormat.equals(downstreamMediaFormats[track])) { - formatHolder.format = mediaFormat; - downstreamMediaFormats[track] = mediaFormat; - return FORMAT_READ; - } - // If mediaFormat and downstreamMediaFormat[track] are equal but different objects then the - // equality check above will have been expensive, comparing the fields in each format. We - // update downstreamMediaFormat here so that referential equality can be cheaply established - // during subsequent calls. - downstreamMediaFormats[track] = mediaFormat; - } - - if (extractor.getSample(extractorTrack, sampleHolder)) { - boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; - sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0; - return SAMPLE_READ; - } - - if (loadingFinished) { - return END_OF_STREAM; - } - - return NOTHING_READ; - } - - @Override - public void maybeThrowError() throws IOException { - if (currentLoadableException != null && currentLoadableExceptionCount > minLoadableRetryCount) { - throw currentLoadableException; - } else if (currentLoadable == null) { - chunkSource.maybeThrowError(); - } - } - - @Override - public void seekToUs(long positionUs) { - Assertions.checkState(prepared); - Assertions.checkState(enabledTrackCount > 0); - // Treat all seeks into live streams as being to t=0. - positionUs = chunkSource.isLive() ? 0 : positionUs; - - // Ignore seeks to the current position. - long currentPositionUs = isPendingReset() ? pendingResetPositionUs : downstreamPositionUs; - downstreamPositionUs = positionUs; - lastSeekPositionUs = positionUs; - if (currentPositionUs == positionUs) { - return; - } - - seekToInternal(positionUs); - } - - @Override - public long getBufferedPositionUs() { - Assertions.checkState(prepared); - Assertions.checkState(enabledTrackCount > 0); - if (isPendingReset()) { - return pendingResetPositionUs; - } else if (loadingFinished) { - return TrackRenderer.END_OF_TRACK_US; - } else { - long largestParsedTimestampUs = extractors.getLast().getLargestParsedTimestampUs(); - if (extractors.size() > 1) { - // When adapting from one format to the next, the penultimate extractor may have the largest - // parsed timestamp (e.g. if the last extractor hasn't parsed any timestamps yet). - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, - extractors.get(extractors.size() - 2).getLargestParsedTimestampUs()); - } - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; - } - } - - @Override - public void release() { - Assertions.checkState(remainingReleaseCount > 0); - if (--remainingReleaseCount == 0 && loader != null) { - if (loadControlRegistered) { - loadControl.unregister(this); - loadControlRegistered = false; - } - loader.release(); - loader = null; - } - } - - // Loader.Callback implementation. - - @Override - public void onLoadCompleted(Loadable loadable) { - Assertions.checkState(loadable == currentLoadable); - long now = SystemClock.elapsedRealtime(); - long loadDurationMs = now - currentLoadStartTimeMs; - chunkSource.onChunkLoadCompleted(currentLoadable); - if (isTsChunk(currentLoadable)) { - Assertions.checkState(currentLoadable == currentTsLoadable); - previousTsLoadable = currentTsLoadable; - notifyLoadCompleted(currentLoadable.bytesLoaded(), currentTsLoadable.type, - currentTsLoadable.trigger, currentTsLoadable.format, currentTsLoadable.startTimeUs, - currentTsLoadable.endTimeUs, now, loadDurationMs); - } else { - notifyLoadCompleted(currentLoadable.bytesLoaded(), currentLoadable.type, - currentLoadable.trigger, currentLoadable.format, -1, -1, now, loadDurationMs); - } - clearCurrentLoadable(); - maybeStartLoading(); - } - - @Override - public void onLoadCanceled(Loadable loadable) { - notifyLoadCanceled(currentLoadable.bytesLoaded()); - if (enabledTrackCount > 0) { - restartFrom(pendingResetPositionUs); - } else { - clearState(); - loadControl.trimAllocator(); - } - } - - @Override - public void onLoadError(Loadable loadable, IOException e) { - if (chunkSource.onChunkLoadError(currentLoadable, e)) { - // Error handled by source. - if (previousTsLoadable == null && !isPendingReset()) { - pendingResetPositionUs = lastSeekPositionUs; - } - clearCurrentLoadable(); - } else { - currentLoadableException = e; - currentLoadableExceptionCount++; - currentLoadableExceptionTimestamp = SystemClock.elapsedRealtime(); - } - notifyLoadError(e); - maybeStartLoading(); - } - - // Internal stuff. - - /** - * Builds tracks that are exposed by this {@link HlsSampleSource} instance, as well as internal - * data-structures required for operation. - *

        - * Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each - * variant stream typically contains muxed video, audio and (possibly) additional audio, metadata - * and caption tracks. We wish to allow the user to select between an adaptive track that spans - * all variants, as well as each individual variant. If multiple audio tracks are present within - * each variant then we wish to allow the user to select between those also. - *

        - * To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks, - * where N is the number of variants defined in the HLS master playlist. These consist of one - * adaptive track defined to span all variants and a track for each individual variant. The - * adaptive track is initially selected. The extractor is then prepared to discover the tracks - * inside of each variant stream. The two sets of tracks are then combined by this method to - * create a third set, which is the set exposed by this {@link HlsSampleSource}: - *

          - *
        • The extractor tracks are inspected to infer a "primary" track type. If a video track is - * present then it is always the primary type. If not, audio is the primary type if present. - * Else text is the primary type if present. Else there is no primary type.
        • - *
        • If there is exactly one extractor track of the primary type, it's expanded into (N+1) - * exposed tracks, all of which correspond to the primary extractor track and each of which - * corresponds to a different chunk source track. Selecting one of these tracks has the effect - * of switching the selected track on the chunk source.
        • - *
        • All other extractor tracks are exposed directly. Selecting one of these tracks has the - * effect of selecting an extractor track, leaving the selected track on the chunk source - * unchanged.
        • - *
        - * - * @param extractor The prepared extractor. - */ - private void buildTracks(HlsExtractorWrapper extractor) { - // Iterate through the extractor tracks to discover the "primary" track type, and the index - // of the single track of this type. - int primaryExtractorTrackType = PRIMARY_TYPE_NONE; - int primaryExtractorTrackIndex = -1; - int extractorTrackCount = extractor.getTrackCount(); - for (int i = 0; i < extractorTrackCount; i++) { - String mimeType = extractor.getMediaFormat(i).mimeType; - int trackType; - if (MimeTypes.isVideo(mimeType)) { - trackType = PRIMARY_TYPE_VIDEO; - } else if (MimeTypes.isAudio(mimeType)) { - trackType = PRIMARY_TYPE_AUDIO; - } else if (MimeTypes.isText(mimeType)) { - trackType = PRIMARY_TYPE_TEXT; - } else { - trackType = PRIMARY_TYPE_NONE; - } - if (trackType > primaryExtractorTrackType) { - primaryExtractorTrackType = trackType; - primaryExtractorTrackIndex = i; - } else if (trackType == primaryExtractorTrackType && primaryExtractorTrackIndex != -1) { - // We have multiple tracks of the primary type. We only want an index if there only - // exists a single track of the primary type, so set the index back to -1. - primaryExtractorTrackIndex = -1; - } - } - - // Calculate the number of tracks that will be exposed. - int chunkSourceTrackCount = chunkSource.getTrackCount(); - boolean expandPrimaryExtractorTrack = primaryExtractorTrackIndex != -1; - trackCount = extractorTrackCount; - if (expandPrimaryExtractorTrack) { - trackCount += chunkSourceTrackCount - 1; - } - - // Instantiate the necessary internal data-structures. - trackFormats = new MediaFormat[trackCount]; - trackEnabledStates = new boolean[trackCount]; - pendingDiscontinuities = new boolean[trackCount]; - downstreamMediaFormats = new MediaFormat[trackCount]; - chunkSourceTrackIndices = new int[trackCount]; - extractorTrackIndices = new int[trackCount]; - extractorTrackEnabledStates = new boolean[extractorTrackCount]; - - // Construct the set of exposed tracks. - long durationUs = chunkSource.getDurationUs(); - int trackIndex = 0; - for (int i = 0; i < extractorTrackCount; i++) { - MediaFormat format = extractor.getMediaFormat(i).copyWithDurationUs(durationUs); - String language = null; - if (MimeTypes.isAudio(format.mimeType)) { - language = chunkSource.getMuxedAudioLanguage(); - } else if (MimeTypes.APPLICATION_EIA608.equals(format.mimeType)) { - language = chunkSource.getMuxedCaptionLanguage(); - } - if (i == primaryExtractorTrackIndex) { - for (int j = 0; j < chunkSourceTrackCount; j++) { - extractorTrackIndices[trackIndex] = i; - chunkSourceTrackIndices[trackIndex] = j; - Variant fixedTrackVariant = chunkSource.getFixedTrackVariant(j); - trackFormats[trackIndex++] = fixedTrackVariant == null ? format.copyAsAdaptive(null) - : copyWithFixedTrackInfo(format, fixedTrackVariant.format, language); - } - } else { - extractorTrackIndices[trackIndex] = i; - chunkSourceTrackIndices[trackIndex] = -1; - trackFormats[trackIndex++] = format.copyWithLanguage(language); - } - } - } - - /** - * Enables or disables the track at a given index. - * - * @param track The index of the track. - * @param enabledState True if the track is being enabled, or false if it's being disabled. - */ - private void setTrackEnabledState(int track, boolean enabledState) { - Assertions.checkState(trackEnabledStates[track] != enabledState); - int extractorTrack = extractorTrackIndices[track]; - Assertions.checkState(extractorTrackEnabledStates[extractorTrack] != enabledState); - trackEnabledStates[track] = enabledState; - extractorTrackEnabledStates[extractorTrack] = enabledState; - enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1); - } - - /** - * Copies a provided {@link MediaFormat}, incorporating information from the {@link Format} of - * a fixed (i.e. non-adaptive) track, as well as a language. - * - * @param format The {@link MediaFormat} to copy. - * @param fixedTrackFormat The {@link Format} to incorporate into the copy. - * @param languageOverride The language to incorporate into the copy. - * @return The copied {@link MediaFormat}. - */ - private static MediaFormat copyWithFixedTrackInfo(MediaFormat format, Format fixedTrackFormat, - String languageOverride) { - int width = fixedTrackFormat.width == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.width; - int height = fixedTrackFormat.height == -1 ? MediaFormat.NO_VALUE : fixedTrackFormat.height; - String language = languageOverride == null ? fixedTrackFormat.language : languageOverride; - return format.copyWithFixedTrackInfo(fixedTrackFormat.id, fixedTrackFormat.bitrate, width, - height, language); - } - - /** - * Performs a seek. The operation is performed even if the seek is to the current position. - * - * @param positionUs The position to seek to. - */ - private void seekToInternal(long positionUs) { - lastSeekPositionUs = positionUs; - downstreamPositionUs = positionUs; - Arrays.fill(pendingDiscontinuities, true); - chunkSource.seek(); - restartFrom(positionUs); - } - - /** - * Gets the current extractor from which samples should be read. - *

        - * Calling this method discards extractors without any samples from the front of the queue. The - * last extractor is retained even if it doesn't have any samples. - *

        - * This method must not be called unless {@link #extractors} is non-empty. - * - * @return The current extractor from which samples should be read. Guaranteed to be non-null. - */ - private HlsExtractorWrapper getCurrentExtractor() { - HlsExtractorWrapper extractor = extractors.getFirst(); - while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) { - // We're finished reading from the extractor for all tracks, and so can discard it. - extractors.removeFirst().clear(); - extractor = extractors.getFirst(); - } - return extractor; - } - - private void discardSamplesForDisabledTracks(HlsExtractorWrapper extractor, long timeUs) { - if (!extractor.isPrepared()) { - return; - } - for (int i = 0; i < extractorTrackEnabledStates.length; i++) { - if (!extractorTrackEnabledStates[i]) { - extractor.discardUntil(i, timeUs); - } - } - } - - private boolean haveSamplesForEnabledTracks(HlsExtractorWrapper extractor) { - if (!extractor.isPrepared()) { - return false; - } - for (int i = 0; i < extractorTrackEnabledStates.length; i++) { - if (extractorTrackEnabledStates[i] && extractor.hasSamples(i)) { - return true; - } - } - return false; - } - - private void restartFrom(long positionUs) { - pendingResetPositionUs = positionUs; - loadingFinished = false; - if (loader.isLoading()) { - loader.cancelLoading(); - } else { - clearState(); - maybeStartLoading(); - } - } - - private void clearState() { - for (int i = 0; i < extractors.size(); i++) { - extractors.get(i).clear(); - } - extractors.clear(); - clearCurrentLoadable(); - previousTsLoadable = null; - } - - private void clearCurrentLoadable() { - currentTsLoadable = null; - currentLoadable = null; - currentLoadableException = null; - currentLoadableExceptionCount = 0; - } - - private void maybeStartLoading() { - long now = SystemClock.elapsedRealtime(); - long nextLoadPositionUs = getNextLoadPositionUs(); - boolean isBackedOff = currentLoadableException != null; - boolean loadingOrBackedOff = loader.isLoading() || isBackedOff; - - // Update the control with our current state, and determine whether we're the next loader. - boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, - loadingOrBackedOff); - - if (isBackedOff) { - long elapsedMillis = now - currentLoadableExceptionTimestamp; - if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) { - currentLoadableException = null; - loader.startLoading(currentLoadable, this); - } - return; - } - - if (loader.isLoading() || !nextLoader || (prepared && enabledTrackCount == 0)) { - return; - } - - chunkSource.getChunkOperation(previousTsLoadable, - pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs, - chunkOperationHolder); - boolean endOfStream = chunkOperationHolder.endOfStream; - Chunk nextLoadable = chunkOperationHolder.chunk; - chunkOperationHolder.clear(); - - if (endOfStream) { - loadingFinished = true; - loadControl.update(this, downstreamPositionUs, -1, false); - return; - } - if (nextLoadable == null) { - return; - } - - currentLoadStartTimeMs = now; - currentLoadable = nextLoadable; - if (isTsChunk(currentLoadable)) { - TsChunk tsChunk = (TsChunk) currentLoadable; - if (isPendingReset()) { - pendingResetPositionUs = NO_RESET_PENDING; - } - HlsExtractorWrapper extractorWrapper = tsChunk.extractorWrapper; - if (extractors.isEmpty() || extractors.getLast() != extractorWrapper) { - extractorWrapper.init(loadControl.getAllocator()); - extractors.addLast(extractorWrapper); - } - notifyLoadStarted(tsChunk.dataSpec.length, tsChunk.type, tsChunk.trigger, tsChunk.format, - tsChunk.startTimeUs, tsChunk.endTimeUs); - currentTsLoadable = tsChunk; - } else { - notifyLoadStarted(currentLoadable.dataSpec.length, currentLoadable.type, - currentLoadable.trigger, currentLoadable.format, -1, -1); - } - loader.startLoading(currentLoadable, this); - } - - /** - * Gets the next load time, assuming that the next load starts where the previous chunk ended (or - * from the pending reset time, if there is one). - */ - private long getNextLoadPositionUs() { - if (isPendingReset()) { - return pendingResetPositionUs; - } else { - return loadingFinished || (prepared && enabledTrackCount == 0) ? -1 - : currentTsLoadable != null ? currentTsLoadable.endTimeUs : previousTsLoadable.endTimeUs; - } - } - - private boolean isTsChunk(Chunk chunk) { - return chunk instanceof TsChunk; - } - - private boolean isPendingReset() { - return pendingResetPositionUs != NO_RESET_PENDING; - } - - private long getRetryDelayMillis(long errorCount) { - return Math.min((errorCount - 1) * 1000, 5000); - } - - /* package */ long usToMs(long timeUs) { - return timeUs / 1000; - } - - private void notifyLoadStarted(final long length, final int type, final int trigger, - final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadStarted(eventSourceId, length, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs)); - } - }); - } - } - - private void notifyLoadCompleted(final long bytesLoaded, final int type, final int trigger, - final Format format, final long mediaStartTimeUs, final long mediaEndTimeUs, - final long elapsedRealtimeMs, final long loadDurationMs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadCompleted(eventSourceId, bytesLoaded, type, trigger, format, - usToMs(mediaStartTimeUs), usToMs(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs); - } - }); - } - } - - private void notifyLoadCanceled(final long bytesLoaded) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadCanceled(eventSourceId, bytesLoaded); - } - }); - } - } - - private void notifyLoadError(final IOException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onLoadError(eventSourceId, e); - } - }); - } - } - - private void notifyDownstreamFormatChanged(final Format format, final int trigger, - final long positionUs) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onDownstreamFormatChanged(eventSourceId, format, trigger, - usToMs(positionUs)); - } - }); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsTrackSelector.java deleted file mode 100755 index 361ec38d4d7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/HlsTrackSelector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import java.io.IOException; - -/** - * Specifies a track selection from an {@link HlsMasterPlaylist}. - */ -public interface HlsTrackSelector { - - /** - * Defines a selector output. - */ - interface Output { - - /** - * Outputs an adaptive track, covering the specified representations in the specified - * adaptation set. - * - * @param playlist The master playlist being processed. - * @param variants The variants to use for the adaptive track. - */ - void adaptiveTrack(HlsMasterPlaylist playlist, Variant[] variants); - - /** - * Outputs an fixed track corresponding to the specified representation in the specified - * adaptation set. - * - * @param playlist The master playlist being processed. - * @param variant The variant to use for the track. - */ - void fixedTrack(HlsMasterPlaylist playlist, Variant variant); - - } - - /** - * Outputs a track selection for a given period. - * - * @param playlist The master playlist to process. - * @param output The output to receive tracks. - * @throws IOException If an error occurs processing the period. - */ - void selectTracks(HlsMasterPlaylist playlist, Output output) throws IOException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/PtsTimestampAdjusterProvider.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/PtsTimestampAdjusterProvider.java deleted file mode 100755 index 19d79d7e4c7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/PtsTimestampAdjusterProvider.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.extractor.ts.PtsTimestampAdjuster; - -/** - * Provides {@link PtsTimestampAdjuster} instances for use during HLS playbacks. - */ -public final class PtsTimestampAdjusterProvider { - - // TODO: Prevent this array from growing indefinitely large by removing adjusters that are no - // longer required. - private final SparseArray ptsTimestampAdjusters; - - public PtsTimestampAdjusterProvider() { - ptsTimestampAdjusters = new SparseArray<>(); - } - - /** - * Gets a {@link PtsTimestampAdjuster} suitable for adjusting the pts timestamps contained in - * a chunk with a given discontinuity sequence. - *

        - * This method may return null if the master source has yet to initialize a suitable adjuster. - * - * @param isMasterSource True if the calling chunk source is the master. - * @param discontinuitySequence The chunk's discontinuity sequence. - * @param startTimeUs The chunk's start time. - * @return A {@link PtsTimestampAdjuster}. - */ - public PtsTimestampAdjuster getAdjuster(boolean isMasterSource, int discontinuitySequence, - long startTimeUs) { - PtsTimestampAdjuster adjuster = ptsTimestampAdjusters.get(discontinuitySequence); - if (isMasterSource && adjuster == null) { - adjuster = new PtsTimestampAdjuster(startTimeUs); - ptsTimestampAdjusters.put(discontinuitySequence, adjuster); - } - return isMasterSource || (adjuster != null && adjuster.isInitialized()) ? adjuster : null; - } - - /** - * Resets the provider. - */ - public void reset() { - ptsTimestampAdjusters.clear(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/TsChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/TsChunk.java deleted file mode 100755 index db071c2c16e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/TsChunk.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.MediaChunk; -import org.telegram.messenger.exoplayer.extractor.DefaultExtractorInput; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * An MPEG2TS chunk. - */ -public final class TsChunk extends MediaChunk { - - /** - * The discontinuity sequence number of the chunk. - */ - public final int discontinuitySequenceNumber; - - /** - * The wrapped extractor into which this chunk is being consumed. - */ - public final HlsExtractorWrapper extractorWrapper; - - private final boolean isEncrypted; - - private int bytesLoaded; - private long adjustedEndTimeUs; - private volatile boolean loadCanceled; - - /** - * @param dataSource A {@link DataSource} for loading the data. - * @param dataSpec Defines the data to be loaded. - * @param trigger The reason for this chunk being selected. - * @param format The format of the stream to which this chunk belongs. - * @param startTimeUs The start time of the media contained by the chunk, in microseconds. - * @param endTimeUs The end time of the media contained by the chunk, in microseconds. - * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. - * @param chunkIndex The index of the chunk. - * @param extractorWrapper A wrapped extractor to parse samples from the data. - * @param encryptionKey For AES encryption chunks, the encryption key. - * @param encryptionIv For AES encryption chunks, the encryption initialization vector. - */ - public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, Format format, - long startTimeUs, long endTimeUs, int chunkIndex, int discontinuitySequenceNumber, - HlsExtractorWrapper extractorWrapper, byte[] encryptionKey, byte[] encryptionIv) { - super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, trigger, format, - startTimeUs, endTimeUs, chunkIndex); - this.discontinuitySequenceNumber = discontinuitySequenceNumber; - this.extractorWrapper = extractorWrapper; - // Note: this.dataSource and dataSource may be different. - this.isEncrypted = this.dataSource instanceof Aes128DataSource; - adjustedEndTimeUs = startTimeUs; - } - - @Override - public long bytesLoaded() { - return bytesLoaded; - } - - // Loadable implementation - - @Override - public void cancelLoad() { - loadCanceled = true; - } - - @Override - public boolean isLoadCanceled() { - return loadCanceled; - } - - @Override - public void load() throws IOException, InterruptedException { - // If we previously fed part of this chunk to the extractor, we need to skip it this time. For - // encrypted content we need to skip the data by reading it through the source, so as to ensure - // correct decryption of the remainder of the chunk. For clear content, we can request the - // remainder of the chunk directly. - DataSpec loadDataSpec; - boolean skipLoadedBytes; - if (isEncrypted) { - loadDataSpec = dataSpec; - skipLoadedBytes = bytesLoaded != 0; - } else { - loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); - skipLoadedBytes = false; - } - - try { - ExtractorInput input = new DefaultExtractorInput(dataSource, - loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); - if (skipLoadedBytes) { - input.skipFully(bytesLoaded); - } - try { - int result = Extractor.RESULT_CONTINUE; - while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { - result = extractorWrapper.read(input); - } - long tsChunkEndTimeUs = extractorWrapper.getAdjustedEndTimeUs(); - if (tsChunkEndTimeUs != Long.MIN_VALUE) { - adjustedEndTimeUs = tsChunkEndTimeUs; - } - } finally { - bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); - } - } finally { - dataSource.close(); - } - } - - public long getAdjustedEndTimeUs() { - return adjustedEndTimeUs; - } - - // Private methods - - /** - * If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in - * order to decrypt the loaded data. Else returns the original. - */ - private static DataSource buildDataSource(DataSource dataSource, byte[] encryptionKey, - byte[] encryptionIv) { - if (encryptionKey == null || encryptionIv == null) { - return dataSource; - } - return new Aes128DataSource(dataSource, encryptionKey, encryptionIv); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Variant.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Variant.java deleted file mode 100755 index 828e942cd34..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Variant.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.FormatWrapper; - -/** - * Variant stream reference. - */ -public final class Variant implements FormatWrapper { - - public final String url; - public final Format format; - - public Variant(String url, Format format) { - this.url = url; - this.format = format; - } - - @Override - public Format getFormat() { - return format; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/WebvttExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/WebvttExtractor.java deleted file mode 100755 index b300a7b50d6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/WebvttExtractor.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.hls; - -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.extractor.ts.PtsTimestampAdjuster; -import org.telegram.messenger.exoplayer.text.webvtt.WebvttCueParser; -import org.telegram.messenger.exoplayer.text.webvtt.WebvttParserUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.IOException; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A special purpose extractor for WebVTT content in HLS. - *

        - * This extractor passes through non-empty WebVTT files untouched, however derives the correct - * sample timestamp for each by sniffing the X-TIMESTAMP-MAP header along with the start timestamp - * of the first cue header. Empty WebVTT files are not passed through, since it's not possible to - * derive a sample timestamp in this case. - */ -/* package */ final class WebvttExtractor implements Extractor { - - private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)"); - private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)"); - - private final PtsTimestampAdjuster ptsTimestampAdjuster; - private final ParsableByteArray sampleDataWrapper; - - private ExtractorOutput output; - - private byte[] sampleData; - private int sampleSize; - - public WebvttExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { - this.ptsTimestampAdjuster = ptsTimestampAdjuster; - this.sampleDataWrapper = new ParsableByteArray(); - sampleData = new byte[1024]; - } - - // Extractor implementation. - - @Override - public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { - // This extractor is only used for the HLS use case, which should not call this method. - throw new IllegalStateException(); - } - - @Override - public void init(ExtractorOutput output) { - this.output = output; - output.seekMap(SeekMap.UNSEEKABLE); - } - - @Override - public void seek() { - // This extractor is only used for the HLS use case, which should not call this method. - throw new IllegalStateException(); - } - - @Override - public void release() { - // Do nothing - } - - @Override - public int read(ExtractorInput input, PositionHolder seekPosition) - throws IOException, InterruptedException { - int currentFileSize = (int) input.getLength(); - - // Increase the size of sampleData if necessary. - if (sampleSize == sampleData.length) { - sampleData = Arrays.copyOf(sampleData, - (currentFileSize != C.LENGTH_UNBOUNDED ? currentFileSize : sampleData.length) * 3 / 2); - } - - // Consume to the input. - int bytesRead = input.read(sampleData, sampleSize, sampleData.length - sampleSize); - if (bytesRead != C.RESULT_END_OF_INPUT) { - sampleSize += bytesRead; - if (currentFileSize == C.LENGTH_UNBOUNDED || sampleSize != currentFileSize) { - return Extractor.RESULT_CONTINUE; - } - } - - // We've reached the end of the input, which corresponds to the end of the current file. - processSample(); - return Extractor.RESULT_END_OF_INPUT; - } - - private void processSample() throws ParserException { - ParsableByteArray webvttData = new ParsableByteArray(sampleData); - - // Validate the first line of the header. - WebvttParserUtil.validateWebvttHeaderLine(webvttData); - - // Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header. - long vttTimestampUs = 0; - long tsTimestampUs = 0; - - // Parse the remainder of the header looking for X-TIMESTAMP-MAP. - String line; - while (!TextUtils.isEmpty(line = webvttData.readLine())) { - if (line.startsWith("X-TIMESTAMP-MAP")) { - Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line); - if (!localTimestampMatcher.find()) { - throw new ParserException("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line); - } - Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line); - if (!mediaTimestampMatcher.find()) { - throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); - } - vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1)); - tsTimestampUs = PtsTimestampAdjuster.ptsToUs( - Long.parseLong(mediaTimestampMatcher.group(1))); - } - } - - // Find the first cue header and parse the start time. - Matcher cueHeaderMatcher = WebvttCueParser.findNextCueHeader(webvttData); - if (cueHeaderMatcher == null) { - // No cues found. Don't output a sample, but still output a corresponding track. - buildTrackOutput(0); - return; - } - - long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); - long sampleTimeUs = ptsTimestampAdjuster.adjustTimestamp( - PtsTimestampAdjuster.usToPts(firstCueTimeUs + tsTimestampUs - vttTimestampUs)); - long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs; - // Output the track. - TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs); - // Output the sample. - sampleDataWrapper.reset(sampleData, sampleSize); - trackOutput.sampleData(sampleDataWrapper, sampleSize); - trackOutput.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); - } - - private TrackOutput buildTrackOutput(long subsampleOffsetUs) { - TrackOutput trackOutput = output.track(0); - trackOutput.format(MediaFormat.createTextFormat("id", MimeTypes.TEXT_VTT, MediaFormat.NO_VALUE, - C.UNKNOWN_TIME_US, "en", subsampleOffsetUs)); - output.endTracks(); - return trackOutput; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataParser.java deleted file mode 100755 index cd861ae8fcb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataParser.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata; - -import org.telegram.messenger.exoplayer.ParserException; - -/** - * Parses metadata from binary data. - * - * @param The type of the metadata. - */ -public interface MetadataParser { - - /** - * Checks whether the parser supports a given mime type. - * - * @param mimeType A metadata mime type. - * @return Whether the mime type is supported. - */ - public boolean canParse(String mimeType); - - /** - * Parses metadata objects of type from the provided binary data. - * - * @param data The raw binary data from which to parse the metadata. - * @param size The size of the input data. - * @return @return A parsed metadata object of type . - * @throws ParserException If a problem occurred parsing the data. - */ - public T parse(byte[] data, int size) throws ParserException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataTrackRenderer.java deleted file mode 100755 index ffead345ec4..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/MetadataTrackRenderer.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata; - -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Looper; -import android.os.Message; -import org.telegram.messenger.exoplayer.ExoPlaybackException; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSourceTrackRenderer; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; - -/** - * A {@link TrackRenderer} for metadata embedded in a media stream. - * - * @param The type of the metadata. - */ -public final class MetadataTrackRenderer extends SampleSourceTrackRenderer implements Callback { - - /** - * An interface for components that process metadata. - * - * @param The type of the metadata. - */ - public interface MetadataRenderer { - - /** - * Invoked each time there is a metadata associated with current playback time. - * - * @param metadata The metadata to process. - */ - void onMetadata(T metadata); - - } - - private static final int MSG_INVOKE_RENDERER = 0; - - private final MetadataParser metadataParser; - private final MetadataRenderer metadataRenderer; - private final Handler metadataHandler; - private final MediaFormatHolder formatHolder; - private final SampleHolder sampleHolder; - - private boolean inputStreamEnded; - private long pendingMetadataTimestamp; - private T pendingMetadata; - - /** - * @param source A source from which samples containing metadata can be read. - * @param metadataParser A parser for parsing the metadata. - * @param metadataRenderer The metadata renderer to receive the parsed metadata. - * @param metadataRendererLooper The looper associated with the thread on which metadataRenderer - * should be invoked. If the renderer makes use of standard Android UI components, then this - * should normally be the looper associated with the applications' main thread, which can be - * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the - * renderer should be invoked directly on the player's internal rendering thread. - */ - public MetadataTrackRenderer(SampleSource source, MetadataParser metadataParser, - MetadataRenderer metadataRenderer, Looper metadataRendererLooper) { - super(source); - this.metadataParser = Assertions.checkNotNull(metadataParser); - this.metadataRenderer = Assertions.checkNotNull(metadataRenderer); - this.metadataHandler = metadataRendererLooper == null ? null - : new Handler(metadataRendererLooper, this); - formatHolder = new MediaFormatHolder(); - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - } - - @Override - protected boolean handlesTrack(MediaFormat mediaFormat) { - return metadataParser.canParse(mediaFormat.mimeType); - } - - @Override - protected void onDiscontinuity(long positionUs) { - pendingMetadata = null; - inputStreamEnded = false; - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) - throws ExoPlaybackException { - if (!inputStreamEnded && pendingMetadata == null) { - sampleHolder.clearData(); - int result = readSource(positionUs, formatHolder, sampleHolder); - if (result == SampleSource.SAMPLE_READ) { - pendingMetadataTimestamp = sampleHolder.timeUs; - try { - pendingMetadata = metadataParser.parse(sampleHolder.data.array(), sampleHolder.size); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } else if (result == SampleSource.END_OF_STREAM) { - inputStreamEnded = true; - } - } - - if (pendingMetadata != null && pendingMetadataTimestamp <= positionUs) { - invokeRenderer(pendingMetadata); - pendingMetadata = null; - } - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - pendingMetadata = null; - super.onDisabled(); - } - - @Override - protected long getBufferedPositionUs() { - return TrackRenderer.END_OF_TRACK_US; - } - - @Override - protected boolean isEnded() { - return inputStreamEnded; - } - - @Override - protected boolean isReady() { - return true; - } - - private void invokeRenderer(T metadata) { - if (metadataHandler != null) { - metadataHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); - } else { - invokeRendererInternal(metadata); - } - } - - @SuppressWarnings("unchecked") - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_INVOKE_RENDERER: - invokeRendererInternal((T) msg.obj); - return true; - } - return false; - } - - private void invokeRendererInternal(T metadata) { - metadataRenderer.onMetadata(metadata); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/ApicFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/ApicFrame.java deleted file mode 100755 index 0508dd72b0c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/ApicFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * APIC (Attached Picture) ID3 frame. - */ -public final class ApicFrame extends Id3Frame { - - public static final String ID = "APIC"; - - public final String mimeType; - public final String description; - public final int pictureType; - public final byte[] pictureData; - - public ApicFrame(String mimeType, String description, int pictureType, byte[] pictureData) { - super(ID); - this.mimeType = mimeType; - this.description = description; - this.pictureType = pictureType; - this.pictureData = pictureData; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/BinaryFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/BinaryFrame.java deleted file mode 100755 index 0cb6bc2fcde..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/BinaryFrame.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * Binary ID3 frame. - */ -public final class BinaryFrame extends Id3Frame { - - public final byte[] data; - - public BinaryFrame(String type, byte[] data) { - super(type); - this.data = data; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/GeobFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/GeobFrame.java deleted file mode 100755 index a8709cfd2cc..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/GeobFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * GEOB (General Encapsulated Object) ID3 frame. - */ -public final class GeobFrame extends Id3Frame { - - public static final String ID = "GEOB"; - - public final String mimeType; - public final String filename; - public final String description; - public final byte[] data; - - public GeobFrame(String mimeType, String filename, String description, byte[] data) { - super(ID); - this.mimeType = mimeType; - this.filename = filename; - this.description = description; - this.data = data; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Frame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Frame.java deleted file mode 100755 index 22756ea3ed2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Frame.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * Base class for ID3 frames. - */ -public abstract class Id3Frame { - - /** - * The frame ID. - */ - public final String id; - - public Id3Frame(String id) { - this.id = id; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Parser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Parser.java deleted file mode 100755 index 2f6cb6ef7be..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/Id3Parser.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.metadata.MetadataParser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -/** - * Extracts individual TXXX text frames from raw ID3 data. - */ -public final class Id3Parser implements MetadataParser> { - - private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0; - private static final int ID3_TEXT_ENCODING_UTF_16 = 1; - private static final int ID3_TEXT_ENCODING_UTF_16BE = 2; - private static final int ID3_TEXT_ENCODING_UTF_8 = 3; - - @Override - public boolean canParse(String mimeType) { - return mimeType.equals(MimeTypes.APPLICATION_ID3); - } - - @Override - public List parse(byte[] data, int size) throws ParserException { - List id3Frames = new ArrayList<>(); - ParsableByteArray id3Data = new ParsableByteArray(data, size); - int id3Size = parseId3Header(id3Data); - - while (id3Size > 0) { - int frameId0 = id3Data.readUnsignedByte(); - int frameId1 = id3Data.readUnsignedByte(); - int frameId2 = id3Data.readUnsignedByte(); - int frameId3 = id3Data.readUnsignedByte(); - int frameSize = id3Data.readSynchSafeInt(); - if (frameSize <= 1) { - break; - } - - // Skip frame flags. - id3Data.skipBytes(2); - - try { - Id3Frame frame; - if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X' && frameId3 == 'X') { - frame = parseTxxxFrame(id3Data, frameSize); - } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { - frame = parsePrivFrame(id3Data, frameSize); - } else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' && frameId3 == 'B') { - frame = parseGeobFrame(id3Data, frameSize); - } else if (frameId0 == 'A' && frameId1 == 'P' && frameId2 == 'I' && frameId3 == 'C') { - frame = parseApicFrame(id3Data, frameSize); - } else if (frameId0 == 'T') { - String id = String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); - frame = parseTextInformationFrame(id3Data, frameSize, id); - } else { - String id = String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); - frame = parseBinaryFrame(id3Data, frameSize, id); - } - id3Frames.add(frame); - id3Size -= frameSize + 10 /* header size */; - } catch (UnsupportedEncodingException e) { - throw new ParserException(e); - } - } - - return Collections.unmodifiableList(id3Frames); - } - - private static int indexOfEos(byte[] data, int fromIndex, int encoding) { - int terminationPos = indexOfZeroByte(data, fromIndex); - - // For single byte encoding charsets, we're done. - if (encoding == ID3_TEXT_ENCODING_ISO_8859_1 || encoding == ID3_TEXT_ENCODING_UTF_8) { - return terminationPos; - } - - // Otherwise look for a second zero byte. - while (terminationPos < data.length - 1) { - if (data[terminationPos + 1] == (byte) 0) { - return terminationPos; - } - terminationPos = indexOfZeroByte(data, terminationPos + 1); - } - - return data.length; - } - - private static int indexOfZeroByte(byte[] data, int fromIndex) { - for (int i = fromIndex; i < data.length; i++) { - if (data[i] == (byte) 0) { - return i; - } - } - return data.length; - } - - private static int delimiterLength(int encodingByte) { - return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8) - ? 1 : 2; - } - - /** - * Parses an ID3 header. - * - * @param id3Buffer A {@link ParsableByteArray} from which data should be read. - * @return The size of ID3 frames in bytes, excluding the header and footer. - * @throws ParserException If ID3 file identifier != "ID3". - */ - private static int parseId3Header(ParsableByteArray id3Buffer) throws ParserException { - int id1 = id3Buffer.readUnsignedByte(); - int id2 = id3Buffer.readUnsignedByte(); - int id3 = id3Buffer.readUnsignedByte(); - if (id1 != 'I' || id2 != 'D' || id3 != '3') { - throw new ParserException(String.format(Locale.US, - "Unexpected ID3 file identifier, expected \"ID3\", actual \"%c%c%c\".", id1, id2, id3)); - } - id3Buffer.skipBytes(2); // Skip version. - - int flags = id3Buffer.readUnsignedByte(); - int id3Size = id3Buffer.readSynchSafeInt(); - - // Check if extended header presents. - if ((flags & 0x2) != 0) { - int extendedHeaderSize = id3Buffer.readSynchSafeInt(); - if (extendedHeaderSize > 4) { - id3Buffer.skipBytes(extendedHeaderSize - 4); - } - id3Size -= extendedHeaderSize; - } - - // Check if footer presents. - if ((flags & 0x8) != 0) { - id3Size -= 10; - } - - return id3Size; - } - - private static TxxxFrame parseTxxxFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int descriptionEndIndex = indexOfEos(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); - - int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); - int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); - String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); - - return new TxxxFrame(description, value); - } - - private static PrivFrame parsePrivFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { - byte[] data = new byte[frameSize]; - id3Data.readBytes(data, 0, frameSize); - - int ownerEndIndex = indexOfZeroByte(data, 0); - String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); - - int privateDataStartIndex = ownerEndIndex + 1; - byte[] privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); - - return new PrivFrame(owner, privateData); - } - - private static GeobFrame parseGeobFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int mimeTypeEndIndex = indexOfZeroByte(data, 0); - String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); - - int filenameStartIndex = mimeTypeEndIndex + 1; - int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); - String filename = new String(data, filenameStartIndex, filenameEndIndex - filenameStartIndex, - charset); - - int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); - String description = new String(data, descriptionStartIndex, - descriptionEndIndex - descriptionStartIndex, charset); - - int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); - byte[] objectData = Arrays.copyOfRange(data, objectDataStartIndex, data.length); - - return new GeobFrame(mimeType, filename, description, objectData); - } - - private static ApicFrame parseApicFrame(ParsableByteArray id3Data, int frameSize) - throws UnsupportedEncodingException { - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int mimeTypeEndIndex = indexOfZeroByte(data, 0); - String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); - - int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; - - int descriptionStartIndex = mimeTypeEndIndex + 2; - int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); - String description = new String(data, descriptionStartIndex, - descriptionEndIndex - descriptionStartIndex, charset); - - int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding); - byte[] pictureData = Arrays.copyOfRange(data, pictureDataStartIndex, data.length); - - return new ApicFrame(mimeType, description, pictureType, pictureData); - } - - private static TextInformationFrame parseTextInformationFrame(ParsableByteArray id3Data, - int frameSize, String id) throws UnsupportedEncodingException { - int encoding = id3Data.readUnsignedByte(); - String charset = getCharsetName(encoding); - - byte[] data = new byte[frameSize - 1]; - id3Data.readBytes(data, 0, frameSize - 1); - - int descriptionEndIndex = indexOfEos(data, 0, encoding); - String description = new String(data, 0, descriptionEndIndex, charset); - - return new TextInformationFrame(id, description); - } - - private static BinaryFrame parseBinaryFrame(ParsableByteArray id3Data, int frameSize, String id) { - byte[] frame = new byte[frameSize]; - id3Data.readBytes(frame, 0, frameSize); - - return new BinaryFrame(id, frame); - } - - /** - * Maps encoding byte from ID3v2 frame to a Charset. - * @param encodingByte The value of encoding byte from ID3v2 frame. - * @return Charset name. - */ - private static String getCharsetName(int encodingByte) { - switch (encodingByte) { - case ID3_TEXT_ENCODING_ISO_8859_1: - return "ISO-8859-1"; - case ID3_TEXT_ENCODING_UTF_16: - return "UTF-16"; - case ID3_TEXT_ENCODING_UTF_16BE: - return "UTF-16BE"; - case ID3_TEXT_ENCODING_UTF_8: - return "UTF-8"; - default: - return "ISO-8859-1"; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/PrivFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/PrivFrame.java deleted file mode 100755 index 89d05e5a549..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/PrivFrame.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * PRIV (Private) ID3 frame. - */ -public final class PrivFrame extends Id3Frame { - - public static final String ID = "PRIV"; - - public final String owner; - public final byte[] privateData; - - public PrivFrame(String owner, byte[] privateData) { - super(ID); - this.owner = owner; - this.privateData = privateData; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TextInformationFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TextInformationFrame.java deleted file mode 100755 index 1694d240a33..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TextInformationFrame.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame. - */ -public final class TextInformationFrame extends Id3Frame { - - public final String description; - - public TextInformationFrame(String id, String description) { - super(id); - this.description = description; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TxxxFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TxxxFrame.java deleted file mode 100755 index 9bbaef3d1fb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/metadata/id3/TxxxFrame.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.metadata.id3; - -/** - * TXXX (User defined text information) ID3 frame. - */ -public final class TxxxFrame extends Id3Frame { - - public static final String ID = "TXXX"; - - public final String description; - public final String value; - - public TxxxFrame(String description, String value) { - super(ID); - this.description = description; - this.value = value; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.java deleted file mode 100755 index 7b3f935917b..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/DefaultSmoothStreamingTrackSelector.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.smoothstreaming; - -import android.content.Context; -import org.telegram.messenger.exoplayer.chunk.VideoFormatSelectorUtil; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.util.Arrays; - -/** - * A default {@link SmoothStreamingTrackSelector} implementation. - */ -// TODO: Add more configuration options (e.g. ability to disable adaptive track output). -public final class DefaultSmoothStreamingTrackSelector implements SmoothStreamingTrackSelector { - - private final int streamElementType; - - private final Context context; - private final boolean filterVideoRepresentations; - private final boolean filterProtectedHdContent; - - /** - * @param context A context. May be null if {@code filterVideoRepresentations == false}. - * @param filterVideoRepresentations Whether video representations should be filtered according to - * the capabilities of the device. It is strongly recommended to set this to {@code true}, - * unless the application has already verified that all representations are playable. - * @param filterProtectedHdContent Whether video representations that are both drm protected and - * high definition should be filtered when tracks are built. If - * {@code filterVideoRepresentations == false} then this parameter is ignored. - */ - public static DefaultSmoothStreamingTrackSelector newVideoInstance(Context context, - boolean filterVideoRepresentations, boolean filterProtectedHdContent) { - return new DefaultSmoothStreamingTrackSelector(StreamElement.TYPE_VIDEO, context, - filterVideoRepresentations, filterProtectedHdContent); - } - - public static DefaultSmoothStreamingTrackSelector newAudioInstance() { - return new DefaultSmoothStreamingTrackSelector(StreamElement.TYPE_AUDIO, null, false, false); - } - - public static DefaultSmoothStreamingTrackSelector newTextInstance() { - return new DefaultSmoothStreamingTrackSelector(StreamElement.TYPE_TEXT, null, false, false); - } - - private DefaultSmoothStreamingTrackSelector(int streamElementType, Context context, - boolean filterVideoRepresentations, boolean filterProtectedHdContent) { - this.context = context; - this.streamElementType = streamElementType; - this.filterVideoRepresentations = filterVideoRepresentations; - this.filterProtectedHdContent = filterProtectedHdContent; - } - - @Override - public void selectTracks(SmoothStreamingManifest manifest, Output output) throws IOException { - for (int i = 0; i < manifest.streamElements.length; i++) { - TrackElement[] tracks = manifest.streamElements[i].tracks; - if (manifest.streamElements[i].type == streamElementType) { - if (streamElementType == StreamElement.TYPE_VIDEO) { - int[] trackIndices; - if (filterVideoRepresentations) { - trackIndices = VideoFormatSelectorUtil.selectVideoFormatsForDefaultDisplay( - context, Arrays.asList(tracks), null, - filterProtectedHdContent && manifest.protectionElement != null); - } else { - trackIndices = Util.firstIntegersArray(tracks.length); - } - int trackCount = trackIndices.length; - if (trackCount > 1) { - output.adaptiveTrack(manifest, i, trackIndices); - } - for (int j = 0; j < trackCount; j++) { - output.fixedTrack(manifest, i, trackIndices[j]); - } - } else { - for (int j = 0; j < tracks.length; j++) { - output.fixedTrack(manifest, i, j); - } - } - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java deleted file mode 100755 index 31ec2d61518..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java +++ /dev/null @@ -1,556 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.smoothstreaming; - -import android.net.Uri; -import android.os.SystemClock; -import android.util.Base64; -import android.util.SparseArray; -import org.telegram.messenger.exoplayer.BehindLiveWindowException; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.chunk.Chunk; -import org.telegram.messenger.exoplayer.chunk.ChunkExtractorWrapper; -import org.telegram.messenger.exoplayer.chunk.ChunkOperationHolder; -import org.telegram.messenger.exoplayer.chunk.ChunkSource; -import org.telegram.messenger.exoplayer.chunk.ContainerMediaChunk; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.Format.DecreasingBandwidthComparator; -import org.telegram.messenger.exoplayer.chunk.FormatEvaluator; -import org.telegram.messenger.exoplayer.chunk.FormatEvaluator.Evaluation; -import org.telegram.messenger.exoplayer.chunk.MediaChunk; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.extractor.mp4.FragmentedMp4Extractor; -import org.telegram.messenger.exoplayer.extractor.mp4.Track; -import org.telegram.messenger.exoplayer.extractor.mp4.TrackEncryptionBox; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.CodecSpecificDataUtil; -import org.telegram.messenger.exoplayer.util.ManifestFetcher; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * An {@link ChunkSource} for SmoothStreaming. - */ -public class SmoothStreamingChunkSource implements ChunkSource, - SmoothStreamingTrackSelector.Output { - - private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000; - private static final int INITIALIZATION_VECTOR_SIZE = 8; - - private final SmoothStreamingTrackSelector trackSelector; - private final DataSource dataSource; - private final Evaluation evaluation; - private final long liveEdgeLatencyUs; - private final TrackEncryptionBox[] trackEncryptionBoxes; - private final ManifestFetcher manifestFetcher; - private final DrmInitData.Mapped drmInitData; - private final FormatEvaluator adaptiveFormatEvaluator; - private final boolean live; - - // The tracks exposed by this source. - private final ArrayList tracks; - - // Mappings from manifest track key. - private final SparseArray extractorWrappers; - private final SparseArray mediaFormats; - - private boolean prepareCalled; - private SmoothStreamingManifest currentManifest; - private int currentManifestChunkOffset; - private boolean needManifestRefresh; - private ExposedTrack enabledTrack; - private IOException fatalError; - - /** - * Constructor to use for live streaming. - *

        - * May also be used for fixed duration content, in which case the call is equivalent to calling - * the other constructor, passing {@code manifestFetcher.getManifest()} is the first argument. - * - * @param manifestFetcher A fetcher for the manifest, which must have already successfully - * completed an initial load. - * @param trackSelector Selects tracks from the manifest to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - * @param liveEdgeLatencyMs For live streams, the number of milliseconds that the playback should - * lag behind the "live edge" (i.e. the end of the most recently defined media in the - * manifest). Choosing a small value will minimize latency introduced by the player, however - * note that the value sets an upper bound on the length of media that the player can buffer. - * Hence a small value may increase the probability of rebuffering and playback failures. - */ - public SmoothStreamingChunkSource(ManifestFetcher manifestFetcher, - SmoothStreamingTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs) { - this(manifestFetcher, manifestFetcher.getManifest(), trackSelector, dataSource, - adaptiveFormatEvaluator, liveEdgeLatencyMs); - } - - /** - * Constructor to use for fixed duration content. - * - * @param manifest The manifest parsed from {@code baseUrl + "/Manifest"}. - * @param trackSelector Selects tracks from the manifest to be exposed by this source. - * @param dataSource A {@link DataSource} suitable for loading the media data. - * @param adaptiveFormatEvaluator For adaptive tracks, selects from the available formats. - */ - public SmoothStreamingChunkSource(SmoothStreamingManifest manifest, - SmoothStreamingTrackSelector trackSelector, DataSource dataSource, - FormatEvaluator adaptiveFormatEvaluator) { - this(null, manifest, trackSelector, dataSource, adaptiveFormatEvaluator, 0); - } - - private SmoothStreamingChunkSource(ManifestFetcher manifestFetcher, - SmoothStreamingManifest initialManifest, SmoothStreamingTrackSelector trackSelector, - DataSource dataSource, FormatEvaluator adaptiveFormatEvaluator, long liveEdgeLatencyMs) { - this.manifestFetcher = manifestFetcher; - this.currentManifest = initialManifest; - this.trackSelector = trackSelector; - this.dataSource = dataSource; - this.adaptiveFormatEvaluator = adaptiveFormatEvaluator; - this.liveEdgeLatencyUs = liveEdgeLatencyMs * 1000; - evaluation = new Evaluation(); - tracks = new ArrayList<>(); - extractorWrappers = new SparseArray<>(); - mediaFormats = new SparseArray<>(); - live = initialManifest.isLive; - - ProtectionElement protectionElement = initialManifest.protectionElement; - if (protectionElement != null) { - byte[] keyId = getProtectionElementKeyId(protectionElement.data); - trackEncryptionBoxes = new TrackEncryptionBox[1]; - trackEncryptionBoxes[0] = new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId); - drmInitData = new DrmInitData.Mapped(); - drmInitData.put(protectionElement.uuid, - new SchemeInitData(MimeTypes.VIDEO_MP4, protectionElement.data)); - } else { - trackEncryptionBoxes = null; - drmInitData = null; - } - } - - // ChunkSource implementation. - - @Override - public void maybeThrowError() throws IOException { - if (fatalError != null) { - throw fatalError; - } else { - manifestFetcher.maybeThrowError(); - } - } - - @Override - public boolean prepare() { - if (!prepareCalled) { - prepareCalled = true; - try { - trackSelector.selectTracks(currentManifest, this); - } catch (IOException e) { - fatalError = e; - } - } - return fatalError == null; - } - - @Override - public int getTrackCount() { - return tracks.size(); - } - - @Override - public final MediaFormat getFormat(int track) { - return tracks.get(track).trackFormat; - } - - @Override - public void enable(int track) { - enabledTrack = tracks.get(track); - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.enable(); - } - if (manifestFetcher != null) { - manifestFetcher.enable(); - } - } - - @Override - public void continueBuffering(long playbackPositionUs) { - if (manifestFetcher == null || !currentManifest.isLive || fatalError != null) { - return; - } - - SmoothStreamingManifest newManifest = manifestFetcher.getManifest(); - if (currentManifest != newManifest && newManifest != null) { - StreamElement currentElement = currentManifest.streamElements[enabledTrack.elementIndex]; - int currentElementChunkCount = currentElement.chunkCount; - StreamElement newElement = newManifest.streamElements[enabledTrack.elementIndex]; - if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { - // There's no overlap between the old and new elements because at least one is empty. - currentManifestChunkOffset += currentElementChunkCount; - } else { - long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1) - + currentElement.getChunkDurationUs(currentElementChunkCount - 1); - long newElementStartTimeUs = newElement.getStartTimeUs(0); - if (currentElementEndTimeUs <= newElementStartTimeUs) { - // There's no overlap between the old and new elements. - currentManifestChunkOffset += currentElementChunkCount; - } else { - // The new element overlaps with the old one. - currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs); - } - } - currentManifest = newManifest; - needManifestRefresh = false; - } - - if (needManifestRefresh && (SystemClock.elapsedRealtime() - > manifestFetcher.getManifestLoadStartTimestamp() + MINIMUM_MANIFEST_REFRESH_PERIOD_MS)) { - manifestFetcher.requestRefresh(); - } - } - - @Override - public final void getChunkOperation(List queue, long playbackPositionUs, - ChunkOperationHolder out) { - if (fatalError != null) { - out.chunk = null; - return; - } - - evaluation.queueSize = queue.size(); - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, enabledTrack.adaptiveFormats, - evaluation); - } else { - evaluation.format = enabledTrack.fixedFormat; - evaluation.trigger = Chunk.TRIGGER_MANUAL; - } - - Format selectedFormat = evaluation.format; - out.queueSize = evaluation.queueSize; - - if (selectedFormat == null) { - out.chunk = null; - return; - } else if (out.queueSize == queue.size() && out.chunk != null - && out.chunk.format.equals(selectedFormat)) { - // We already have a chunk, and the evaluation hasn't changed either the format or the size - // of the queue. Leave unchanged. - return; - } - - // In all cases where we return before instantiating a new chunk, we want out.chunk to be null. - out.chunk = null; - - StreamElement streamElement = currentManifest.streamElements[enabledTrack.elementIndex]; - if (streamElement.chunkCount == 0) { - if (currentManifest.isLive) { - needManifestRefresh = true; - } else { - out.endOfStream = true; - } - return; - } - - int chunkIndex; - if (queue.isEmpty()) { - if (live) { - playbackPositionUs = getLiveSeekPosition(currentManifest, liveEdgeLatencyUs); - } - chunkIndex = streamElement.getChunkIndex(playbackPositionUs); - } else { - MediaChunk previous = queue.get(out.queueSize - 1); - chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset; - } - - if (live && chunkIndex < 0) { - // This is before the first chunk in the current manifest. - fatalError = new BehindLiveWindowException(); - return; - } else if (currentManifest.isLive) { - if (chunkIndex >= streamElement.chunkCount) { - // This is beyond the last chunk in the current manifest. - needManifestRefresh = true; - return; - } else if (chunkIndex == streamElement.chunkCount - 1) { - // This is the last chunk in the current manifest. Mark the manifest as being finished, - // but continue to return the final chunk. - needManifestRefresh = true; - } - } else if (chunkIndex >= streamElement.chunkCount) { - out.endOfStream = true; - return; - } - - boolean isLastChunk = !currentManifest.isLive && chunkIndex == streamElement.chunkCount - 1; - long chunkStartTimeUs = streamElement.getStartTimeUs(chunkIndex); - long chunkEndTimeUs = isLastChunk ? -1 - : chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex); - int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset; - - int manifestTrackIndex = getManifestTrackIndex(streamElement, selectedFormat); - int manifestTrackKey = getManifestTrackKey(enabledTrack.elementIndex, manifestTrackIndex); - Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); - Chunk mediaChunk = newMediaChunk(selectedFormat, uri, null, - extractorWrappers.get(manifestTrackKey), drmInitData, dataSource, currentAbsoluteChunkIndex, - chunkStartTimeUs, chunkEndTimeUs, evaluation.trigger, mediaFormats.get(manifestTrackKey), - enabledTrack.adaptiveMaxWidth, enabledTrack.adaptiveMaxHeight); - out.chunk = mediaChunk; - } - - @Override - public void onChunkLoadCompleted(Chunk chunk) { - // Do nothing. - } - - @Override - public void onChunkLoadError(Chunk chunk, Exception e) { - // Do nothing. - } - - @Override - public void disable(List queue) { - if (enabledTrack.isAdaptive()) { - adaptiveFormatEvaluator.disable(); - } - if (manifestFetcher != null) { - manifestFetcher.disable(); - } - evaluation.format = null; - fatalError = null; - } - - // SmoothStreamingTrackSelector.Output implementation. - - @Override - public void adaptiveTrack(SmoothStreamingManifest manifest, int element, int[] trackIndices) { - if (adaptiveFormatEvaluator == null) { - // Do nothing. - return; - } - MediaFormat maxHeightMediaFormat = null; - StreamElement streamElement = manifest.streamElements[element]; - int maxWidth = -1; - int maxHeight = -1; - Format[] formats = new Format[trackIndices.length]; - for (int i = 0; i < formats.length; i++) { - int manifestTrackIndex = trackIndices[i]; - formats[i] = streamElement.tracks[manifestTrackIndex].format; - MediaFormat mediaFormat = initManifestTrack(manifest, element, manifestTrackIndex); - if (maxHeightMediaFormat == null || mediaFormat.height > maxHeight) { - maxHeightMediaFormat = mediaFormat; - } - maxWidth = Math.max(maxWidth, mediaFormat.width); - maxHeight = Math.max(maxHeight, mediaFormat.height); - } - Arrays.sort(formats, new DecreasingBandwidthComparator()); - MediaFormat adaptiveMediaFormat = maxHeightMediaFormat.copyAsAdaptive(null); - tracks.add(new ExposedTrack(adaptiveMediaFormat, element, formats, maxWidth, maxHeight)); - } - - @Override - public void fixedTrack(SmoothStreamingManifest manifest, int element, int trackIndex) { - MediaFormat mediaFormat = initManifestTrack(manifest, element, trackIndex); - Format format = manifest.streamElements[element].tracks[trackIndex].format; - tracks.add(new ExposedTrack(mediaFormat, element, format)); - } - - // Private methods. - - private MediaFormat initManifestTrack(SmoothStreamingManifest manifest, int elementIndex, - int trackIndex) { - int manifestTrackKey = getManifestTrackKey(elementIndex, trackIndex); - MediaFormat mediaFormat = mediaFormats.get(manifestTrackKey); - if (mediaFormat != null) { - // Already initialized. - return mediaFormat; - } - - // Build the media format. - long durationUs = live ? C.UNKNOWN_TIME_US : manifest.durationUs; - StreamElement element = manifest.streamElements[elementIndex]; - Format format = element.tracks[trackIndex].format; - byte[][] csdArray = element.tracks[trackIndex].csd; - int mp4TrackType; - switch (element.type) { - case StreamElement.TYPE_VIDEO: - mediaFormat = MediaFormat.createVideoFormat(format.id, format.mimeType, format.bitrate, - MediaFormat.NO_VALUE, durationUs, format.width, format.height, Arrays.asList(csdArray)); - mp4TrackType = Track.TYPE_vide; - break; - case StreamElement.TYPE_AUDIO: - List csd; - if (csdArray != null) { - csd = Arrays.asList(csdArray); - } else { - csd = Collections.singletonList(CodecSpecificDataUtil.buildAacAudioSpecificConfig( - format.audioSamplingRate, format.audioChannels)); - } - mediaFormat = MediaFormat.createAudioFormat(format.id, format.mimeType, format.bitrate, - MediaFormat.NO_VALUE, durationUs, format.audioChannels, format.audioSamplingRate, csd, - format.language); - mp4TrackType = Track.TYPE_soun; - break; - case StreamElement.TYPE_TEXT: - mediaFormat = MediaFormat.createTextFormat(format.id, format.mimeType, format.bitrate, - durationUs, format.language); - mp4TrackType = Track.TYPE_text; - break; - default: - throw new IllegalStateException("Invalid type: " + element.type); - } - - Track mp4Track = new Track(trackIndex, mp4TrackType, element.timescale, C.UNKNOWN_TIME_US, - durationUs, mediaFormat, trackEncryptionBoxes, mp4TrackType == Track.TYPE_vide ? 4 : -1, - null, null); - // Build the extractor. - FragmentedMp4Extractor mp4Extractor = new FragmentedMp4Extractor( - FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME - | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, mp4Track); - - // Store the format and a wrapper around the extractor. - mediaFormats.put(manifestTrackKey, mediaFormat); - extractorWrappers.put(manifestTrackKey, new ChunkExtractorWrapper(mp4Extractor)); - return mediaFormat; - } - - /** - * For live playbacks, determines the seek position that snaps playback to be - * {@code liveEdgeLatencyUs} behind the live edge of the provided manifest. - * - * @param manifest The manifest. - * @param liveEdgeLatencyUs The live edge latency, in microseconds. - * @return The seek position in microseconds. - */ - private static long getLiveSeekPosition(SmoothStreamingManifest manifest, - long liveEdgeLatencyUs) { - long liveEdgeTimestampUs = Long.MIN_VALUE; - for (int i = 0; i < manifest.streamElements.length; i++) { - StreamElement streamElement = manifest.streamElements[i]; - if (streamElement.chunkCount > 0) { - long elementLiveEdgeTimestampUs = - streamElement.getStartTimeUs(streamElement.chunkCount - 1) - + streamElement.getChunkDurationUs(streamElement.chunkCount - 1); - liveEdgeTimestampUs = Math.max(liveEdgeTimestampUs, elementLiveEdgeTimestampUs); - } - } - return liveEdgeTimestampUs - liveEdgeLatencyUs; - } - - private static int getManifestTrackIndex(StreamElement element, Format format) { - TrackElement[] tracks = element.tracks; - for (int i = 0; i < tracks.length; i++) { - if (tracks[i].format.equals(format)) { - return i; - } - } - // Should never happen. - throw new IllegalStateException("Invalid format: " + format); - } - - private static MediaChunk newMediaChunk(Format formatInfo, Uri uri, String cacheKey, - ChunkExtractorWrapper extractorWrapper, DrmInitData drmInitData, DataSource dataSource, - int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, int trigger, - MediaFormat mediaFormat, int adaptiveMaxWidth, int adaptiveMaxHeight) { - long offset = 0; - DataSpec dataSpec = new DataSpec(uri, offset, -1, cacheKey); - // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. - // To convert them the absolute timestamps, we need to set sampleOffsetUs to -chunkStartTimeUs. - return new ContainerMediaChunk(dataSource, dataSpec, trigger, formatInfo, chunkStartTimeUs, - chunkEndTimeUs, chunkIndex, chunkStartTimeUs, extractorWrapper, mediaFormat, - adaptiveMaxWidth, adaptiveMaxHeight, drmInitData, true, Chunk.NO_PARENT_ID); - } - - private static int getManifestTrackKey(int elementIndex, int trackIndex) { - Assertions.checkState(elementIndex <= 65536 && trackIndex <= 65536); - return (elementIndex << 16) | trackIndex; - } - - private static byte[] getProtectionElementKeyId(byte[] initData) { - StringBuilder initDataStringBuilder = new StringBuilder(); - for (int i = 0; i < initData.length; i += 2) { - initDataStringBuilder.append((char) initData[i]); - } - String initDataString = initDataStringBuilder.toString(); - String keyIdString = initDataString.substring( - initDataString.indexOf("") + 5, initDataString.indexOf("")); - byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT); - swap(keyId, 0, 3); - swap(keyId, 1, 2); - swap(keyId, 4, 5); - swap(keyId, 6, 7); - return keyId; - } - - private static void swap(byte[] data, int firstPosition, int secondPosition) { - byte temp = data[firstPosition]; - data[firstPosition] = data[secondPosition]; - data[secondPosition] = temp; - } - - // Private classes. - - private static final class ExposedTrack { - - public final MediaFormat trackFormat; - - private final int elementIndex; - - // Non-adaptive track variables. - private final Format fixedFormat; - - // Adaptive track variables. - private final Format[] adaptiveFormats; - private final int adaptiveMaxWidth; - private final int adaptiveMaxHeight; - - public ExposedTrack(MediaFormat trackFormat, int elementIndex, Format fixedFormat) { - this.trackFormat = trackFormat; - this.elementIndex = elementIndex; - this.fixedFormat = fixedFormat; - this.adaptiveFormats = null; - this.adaptiveMaxWidth = MediaFormat.NO_VALUE; - this.adaptiveMaxHeight = MediaFormat.NO_VALUE; - } - - public ExposedTrack(MediaFormat trackFormat, int elementIndex, Format[] adaptiveFormats, - int adaptiveMaxWidth, int adaptiveMaxHeight) { - this.trackFormat = trackFormat; - this.elementIndex = elementIndex; - this.adaptiveFormats = adaptiveFormats; - this.adaptiveMaxWidth = adaptiveMaxWidth; - this.adaptiveMaxHeight = adaptiveMaxHeight; - this.fixedFormat = null; - } - - public boolean isAdaptive() { - return adaptiveFormats != null; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifest.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifest.java deleted file mode 100755 index a0135e98ec6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifest.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.smoothstreaming; - -import android.net.Uri; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.chunk.FormatWrapper; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.UriUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.List; -import java.util.UUID; - -/** - * Represents a SmoothStreaming manifest. - * - * @see - * IIS Smooth Streaming Client Manifest Format - */ -public class SmoothStreamingManifest { - - /** - * The client manifest major version. - */ - public final int majorVersion; - - /** - * The client manifest minor version. - */ - public final int minorVersion; - - /** - * The number of fragments in a lookahead, or -1 if the lookahead is unspecified. - */ - public final int lookAheadCount; - - /** - * True if the manifest describes a live presentation still in progress. False otherwise. - */ - public final boolean isLive; - - /** - * Content protection information, or null if the content is not protected. - */ - public final ProtectionElement protectionElement; - - /** - * The contained stream elements. - */ - public final StreamElement[] streamElements; - - /** - * The overall presentation duration of the media in microseconds, or {@link C#UNKNOWN_TIME_US} - * if the duration is unknown. - */ - public final long durationUs; - - /** - * The length of the trailing window for a live broadcast in microseconds, or - * {@link C#UNKNOWN_TIME_US} if the stream is not live or if the window length is unspecified. - */ - public final long dvrWindowLengthUs; - - /** - * @param majorVersion The client manifest major version. - * @param minorVersion The client manifest minor version. - * @param timescale The timescale of the media as the number of units that pass in one second. - * @param duration The overall presentation duration in units of the timescale attribute, or 0 - * if the duration is unknown. - * @param dvrWindowLength The length of the trailing window in units of the timescale attribute, - * or 0 if this attribute is unspecified or not applicable. - * @param lookAheadCount The number of fragments in a lookahead, or -1 if this attribute is - * unspecified or not applicable. - * @param isLive True if the manifest describes a live presentation still in progress. False - * otherwise. - * @param protectionElement Content protection information, or null if the content is not - * protected. - * @param streamElements The contained stream elements. - */ - public SmoothStreamingManifest(int majorVersion, int minorVersion, long timescale, long duration, - long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement, - StreamElement[] streamElements) { - this.majorVersion = majorVersion; - this.minorVersion = minorVersion; - this.lookAheadCount = lookAheadCount; - this.isLive = isLive; - this.protectionElement = protectionElement; - this.streamElements = streamElements; - dvrWindowLengthUs = dvrWindowLength == 0 ? C.UNKNOWN_TIME_US - : Util.scaleLargeTimestamp(dvrWindowLength, C.MICROS_PER_SECOND, timescale); - durationUs = duration == 0 ? C.UNKNOWN_TIME_US - : Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale); - } - - /** - * Represents a protection element containing a single header. - */ - public static class ProtectionElement { - - public final UUID uuid; - public final byte[] data; - - public ProtectionElement(UUID uuid, byte[] data) { - this.uuid = uuid; - this.data = data; - } - - } - - /** - * Represents a QualityLevel element. - */ - public static class TrackElement implements FormatWrapper { - - public final Format format; - public final byte[][] csd; - - public TrackElement(int index, int bitrate, String mimeType, byte[][] csd, int maxWidth, - int maxHeight, int sampleRate, int numChannels, String language) { - this.csd = csd; - format = new Format(String.valueOf(index), mimeType, maxWidth, maxHeight, -1, numChannels, - sampleRate, bitrate, language); - } - - @Override - public Format getFormat() { - return format; - } - - } - - /** - * Represents a StreamIndex element. - */ - public static class StreamElement { - - public static final int TYPE_UNKNOWN = -1; - public static final int TYPE_AUDIO = 0; - public static final int TYPE_VIDEO = 1; - public static final int TYPE_TEXT = 2; - - private static final String URL_PLACEHOLDER_START_TIME = "{start time}"; - private static final String URL_PLACEHOLDER_BITRATE = "{bitrate}"; - - public final int type; - public final String subType; - public final long timescale; - public final String name; - public final int qualityLevels; - public final int maxWidth; - public final int maxHeight; - public final int displayWidth; - public final int displayHeight; - public final String language; - public final TrackElement[] tracks; - public final int chunkCount; - - private final String baseUri; - private final String chunkTemplate; - - private final List chunkStartTimes; - private final long[] chunkStartTimesUs; - private final long lastChunkDurationUs; - - public StreamElement(String baseUri, String chunkTemplate, int type, String subType, - long timescale, String name, int qualityLevels, int maxWidth, int maxHeight, - int displayWidth, int displayHeight, String language, TrackElement[] tracks, - List chunkStartTimes, long lastChunkDuration) { - this.baseUri = baseUri; - this.chunkTemplate = chunkTemplate; - this.type = type; - this.subType = subType; - this.timescale = timescale; - this.name = name; - this.qualityLevels = qualityLevels; - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.displayWidth = displayWidth; - this.displayHeight = displayHeight; - this.language = language; - this.tracks = tracks; - this.chunkCount = chunkStartTimes.size(); - this.chunkStartTimes = chunkStartTimes; - lastChunkDurationUs = - Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale); - chunkStartTimesUs = - Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale); - } - - /** - * Gets the index of the chunk that contains the specified time. - * - * @param timeUs The time in microseconds. - * @return The index of the corresponding chunk. - */ - public int getChunkIndex(long timeUs) { - return Util.binarySearchFloor(chunkStartTimesUs, timeUs, true, true); - } - - /** - * Gets the start time of the specified chunk. - * - * @param chunkIndex The index of the chunk. - * @return The start time of the chunk, in microseconds. - */ - public long getStartTimeUs(int chunkIndex) { - return chunkStartTimesUs[chunkIndex]; - } - - /** - * Gets the duration of the specified chunk. - * - * @param chunkIndex The index of the chunk. - * @return The duration of the chunk, in microseconds. - */ - public long getChunkDurationUs(int chunkIndex) { - return (chunkIndex == chunkCount - 1) ? lastChunkDurationUs - : chunkStartTimesUs[chunkIndex + 1] - chunkStartTimesUs[chunkIndex]; - } - - /** - * Builds a uri for requesting the specified chunk of the specified track. - * - * @param track The index of the track for which to build the URL. - * @param chunkIndex The index of the chunk for which to build the URL. - * @return The request uri. - */ - public Uri buildRequestUri(int track, int chunkIndex) { - Assertions.checkState(tracks != null); - Assertions.checkState(chunkStartTimes != null); - Assertions.checkState(chunkIndex < chunkStartTimes.size()); - String chunkUrl = chunkTemplate - .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(tracks[track].format.bitrate)) - .replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString()); - return UriUtil.resolveToUri(baseUri, chunkUrl); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java deleted file mode 100755 index d3f945b700e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingManifestParser.java +++ /dev/null @@ -1,699 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.smoothstreaming; - -import android.util.Base64; -import android.util.Pair; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.mp4.PsshAtomUtil; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement; -import org.telegram.messenger.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement; -import org.telegram.messenger.exoplayer.upstream.UriLoadable; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.CodecSpecificDataUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -/** - * Parses SmoothStreaming client manifests. - * - * @see - * IIS Smooth Streaming Client Manifest Format - */ -public class SmoothStreamingManifestParser implements UriLoadable.Parser { - - private final XmlPullParserFactory xmlParserFactory; - - public SmoothStreamingManifestParser() { - try { - xmlParserFactory = XmlPullParserFactory.newInstance(); - } catch (XmlPullParserException e) { - throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); - } - } - - @Override - public SmoothStreamingManifest parse(String connectionUrl, InputStream inputStream) - throws IOException, ParserException { - try { - XmlPullParser xmlParser = xmlParserFactory.newPullParser(); - xmlParser.setInput(inputStream, null); - SmoothStreamMediaParser smoothStreamMediaParser = - new SmoothStreamMediaParser(null, connectionUrl); - return (SmoothStreamingManifest) smoothStreamMediaParser.parse(xmlParser); - } catch (XmlPullParserException e) { - throw new ParserException(e); - } - } - - /** - * Thrown if a required field is missing. - */ - public static class MissingFieldException extends ParserException { - - public MissingFieldException(String fieldName) { - super("Missing required field: " + fieldName); - } - - } - - /** - * A base class for parsers that parse components of a smooth streaming manifest. - */ - private static abstract class ElementParser { - - private final String baseUri; - private final String tag; - - private final ElementParser parent; - private final List> normalizedAttributes; - - public ElementParser(ElementParser parent, String baseUri, String tag) { - this.parent = parent; - this.baseUri = baseUri; - this.tag = tag; - this.normalizedAttributes = new LinkedList<>(); - } - - public final Object parse(XmlPullParser xmlParser) throws XmlPullParserException, IOException, - ParserException { - String tagName; - boolean foundStartTag = false; - int skippingElementDepth = 0; - while (true) { - int eventType = xmlParser.getEventType(); - switch (eventType) { - case XmlPullParser.START_TAG: - tagName = xmlParser.getName(); - if (tag.equals(tagName)) { - foundStartTag = true; - parseStartTag(xmlParser); - } else if (foundStartTag) { - if (skippingElementDepth > 0) { - skippingElementDepth++; - } else if (handleChildInline(tagName)) { - parseStartTag(xmlParser); - } else { - ElementParser childElementParser = newChildParser(this, tagName, baseUri); - if (childElementParser == null) { - skippingElementDepth = 1; - } else { - addChild(childElementParser.parse(xmlParser)); - } - } - } - break; - case XmlPullParser.TEXT: - if (foundStartTag && skippingElementDepth == 0) { - parseText(xmlParser); - } - break; - case XmlPullParser.END_TAG: - if (foundStartTag) { - if (skippingElementDepth > 0) { - skippingElementDepth--; - } else { - tagName = xmlParser.getName(); - parseEndTag(xmlParser); - if (!handleChildInline(tagName)) { - return build(); - } - } - } - break; - case XmlPullParser.END_DOCUMENT: - return null; - default: - // Do nothing. - break; - } - xmlParser.next(); - } - } - - private ElementParser newChildParser(ElementParser parent, String name, String baseUri) { - if (TrackElementParser.TAG.equals(name)) { - return new TrackElementParser(parent, baseUri); - } else if (ProtectionElementParser.TAG.equals(name)) { - return new ProtectionElementParser(parent, baseUri); - } else if (StreamElementParser.TAG.equals(name)) { - return new StreamElementParser(parent, baseUri); - } - return null; - } - - /** - * Stash an attribute that may be normalized at this level. In other words, an attribute that - * may have been pulled up from the child elements because its value was the same in all - * children. - *

        - * Stashing an attribute allows child element parsers to retrieve the values of normalized - * attributes using {@link #getNormalizedAttribute(String)}. - * - * @param key The name of the attribute. - * @param value The value of the attribute. - */ - protected final void putNormalizedAttribute(String key, Object value) { - normalizedAttributes.add(Pair.create(key, value)); - } - - /** - * Attempt to retrieve a stashed normalized attribute. If there is no stashed attribute with - * the provided name, the parent element parser will be queried, and so on up the chain. - * - * @param key The name of the attribute. - * @return The stashed value, or null if the attribute was not be found. - */ - protected final Object getNormalizedAttribute(String key) { - for (int i = 0; i < normalizedAttributes.size(); i++) { - Pair pair = normalizedAttributes.get(i); - if (pair.first.equals(key)) { - return pair.second; - } - } - return parent == null ? null : parent.getNormalizedAttribute(key); - } - - /** - * Whether this {@link ElementParser} parses a child element inline. - * - * @param tagName The name of the child element. - * @return Whether the child is parsed inline. - */ - protected boolean handleChildInline(String tagName) { - return false; - } - - /** - * @param xmlParser The underlying {@link XmlPullParser} - * @throws ParserException - */ - protected void parseStartTag(XmlPullParser xmlParser) throws ParserException { - // Do nothing. - } - - /** - * @param xmlParser The underlying {@link XmlPullParser} - * @throws ParserException - */ - protected void parseText(XmlPullParser xmlParser) throws ParserException { - // Do nothing. - } - - /** - * @param xmlParser The underlying {@link XmlPullParser} - * @throws ParserException - */ - protected void parseEndTag(XmlPullParser xmlParser) throws ParserException { - // Do nothing. - } - - /** - * @param parsedChild A parsed child object. - */ - protected void addChild(Object parsedChild) { - // Do nothing. - } - - protected abstract Object build(); - - protected final String parseRequiredString(XmlPullParser parser, String key) - throws MissingFieldException { - String value = parser.getAttributeValue(null, key); - if (value != null) { - return value; - } else { - throw new MissingFieldException(key); - } - } - - protected final int parseInt(XmlPullParser parser, String key, int defaultValue) - throws ParserException { - String value = parser.getAttributeValue(null, key); - if (value != null) { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new ParserException(e); - } - } else { - return defaultValue; - } - } - - protected final int parseRequiredInt(XmlPullParser parser, String key) throws ParserException { - String value = parser.getAttributeValue(null, key); - if (value != null) { - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new ParserException(e); - } - } else { - throw new MissingFieldException(key); - } - } - - protected final long parseLong(XmlPullParser parser, String key, long defaultValue) - throws ParserException { - String value = parser.getAttributeValue(null, key); - if (value != null) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new ParserException(e); - } - } else { - return defaultValue; - } - } - - protected final long parseRequiredLong(XmlPullParser parser, String key) - throws ParserException { - String value = parser.getAttributeValue(null, key); - if (value != null) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - throw new ParserException(e); - } - } else { - throw new MissingFieldException(key); - } - } - - protected final boolean parseBoolean(XmlPullParser parser, String key, boolean defaultValue) { - String value = parser.getAttributeValue(null, key); - if (value != null) { - return Boolean.parseBoolean(value); - } else { - return defaultValue; - } - } - - } - - private static class SmoothStreamMediaParser extends ElementParser { - - public static final String TAG = "SmoothStreamingMedia"; - - private static final String KEY_MAJOR_VERSION = "MajorVersion"; - private static final String KEY_MINOR_VERSION = "MinorVersion"; - private static final String KEY_TIME_SCALE = "TimeScale"; - private static final String KEY_DVR_WINDOW_LENGTH = "DVRWindowLength"; - private static final String KEY_DURATION = "Duration"; - private static final String KEY_LOOKAHEAD_COUNT = "LookaheadCount"; - private static final String KEY_IS_LIVE = "IsLive"; - - private int majorVersion; - private int minorVersion; - private long timescale; - private long duration; - private long dvrWindowLength; - private int lookAheadCount; - private boolean isLive; - private ProtectionElement protectionElement; - private List streamElements; - - public SmoothStreamMediaParser(ElementParser parent, String baseUri) { - super(parent, baseUri, TAG); - lookAheadCount = -1; - protectionElement = null; - streamElements = new LinkedList<>(); - } - - @Override - public void parseStartTag(XmlPullParser parser) throws ParserException { - majorVersion = parseRequiredInt(parser, KEY_MAJOR_VERSION); - minorVersion = parseRequiredInt(parser, KEY_MINOR_VERSION); - timescale = parseLong(parser, KEY_TIME_SCALE, 10000000L); - duration = parseRequiredLong(parser, KEY_DURATION); - dvrWindowLength = parseLong(parser, KEY_DVR_WINDOW_LENGTH, 0); - lookAheadCount = parseInt(parser, KEY_LOOKAHEAD_COUNT, -1); - isLive = parseBoolean(parser, KEY_IS_LIVE, false); - putNormalizedAttribute(KEY_TIME_SCALE, timescale); - } - - @Override - public void addChild(Object child) { - if (child instanceof StreamElement) { - streamElements.add((StreamElement) child); - } else if (child instanceof ProtectionElement) { - Assertions.checkState(protectionElement == null); - protectionElement = (ProtectionElement) child; - } - } - - @Override - public Object build() { - StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; - streamElements.toArray(streamElementArray); - return new SmoothStreamingManifest(majorVersion, minorVersion, timescale, duration, - dvrWindowLength, lookAheadCount, isLive, protectionElement, streamElementArray); - } - - } - - private static class ProtectionElementParser extends ElementParser { - - public static final String TAG = "Protection"; - public static final String TAG_PROTECTION_HEADER = "ProtectionHeader"; - - public static final String KEY_SYSTEM_ID = "SystemID"; - - private boolean inProtectionHeader; - private UUID uuid; - private byte[] initData; - - public ProtectionElementParser(ElementParser parent, String baseUri) { - super(parent, baseUri, TAG); - } - - @Override - public boolean handleChildInline(String tag) { - return TAG_PROTECTION_HEADER.equals(tag); - } - - @Override - public void parseStartTag(XmlPullParser parser) { - if (TAG_PROTECTION_HEADER.equals(parser.getName())) { - inProtectionHeader = true; - String uuidString = parser.getAttributeValue(null, KEY_SYSTEM_ID); - uuidString = stripCurlyBraces(uuidString); - uuid = UUID.fromString(uuidString); - } - } - - @Override - public void parseText(XmlPullParser parser) { - if (inProtectionHeader) { - initData = Base64.decode(parser.getText(), Base64.DEFAULT); - } - } - - @Override - public void parseEndTag(XmlPullParser parser) { - if (TAG_PROTECTION_HEADER.equals(parser.getName())) { - inProtectionHeader = false; - } - } - - @Override - public Object build() { - return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData)); - } - - private static String stripCurlyBraces(String uuidString) { - if (uuidString.charAt(0) == '{' && uuidString.charAt(uuidString.length() - 1) == '}') { - uuidString = uuidString.substring(1, uuidString.length() - 1); - } - return uuidString; - } - } - - private static class StreamElementParser extends ElementParser { - - public static final String TAG = "StreamIndex"; - private static final String TAG_STREAM_FRAGMENT = "c"; - - private static final String KEY_TYPE = "Type"; - private static final String KEY_TYPE_AUDIO = "audio"; - private static final String KEY_TYPE_VIDEO = "video"; - private static final String KEY_TYPE_TEXT = "text"; - private static final String KEY_SUB_TYPE = "Subtype"; - private static final String KEY_NAME = "Name"; - private static final String KEY_QUALITY_LEVELS = "QualityLevels"; - private static final String KEY_URL = "Url"; - private static final String KEY_MAX_WIDTH = "MaxWidth"; - private static final String KEY_MAX_HEIGHT = "MaxHeight"; - private static final String KEY_DISPLAY_WIDTH = "DisplayWidth"; - private static final String KEY_DISPLAY_HEIGHT = "DisplayHeight"; - private static final String KEY_LANGUAGE = "Language"; - private static final String KEY_TIME_SCALE = "TimeScale"; - - private static final String KEY_FRAGMENT_DURATION = "d"; - private static final String KEY_FRAGMENT_START_TIME = "t"; - private static final String KEY_FRAGMENT_REPEAT_COUNT = "r"; - - private final String baseUri; - private final List tracks; - - private int type; - private String subType; - private long timescale; - private String name; - private int qualityLevels; - private String url; - private int maxWidth; - private int maxHeight; - private int displayWidth; - private int displayHeight; - private String language; - private ArrayList startTimes; - - private long lastChunkDuration; - - public StreamElementParser(ElementParser parent, String baseUri) { - super(parent, baseUri, TAG); - this.baseUri = baseUri; - tracks = new LinkedList<>(); - } - - @Override - public boolean handleChildInline(String tag) { - return TAG_STREAM_FRAGMENT.equals(tag); - } - - @Override - public void parseStartTag(XmlPullParser parser) throws ParserException { - if (TAG_STREAM_FRAGMENT.equals(parser.getName())) { - parseStreamFragmentStartTag(parser); - } else { - parseStreamElementStartTag(parser); - } - } - - private void parseStreamFragmentStartTag(XmlPullParser parser) throws ParserException { - int chunkIndex = startTimes.size(); - long startTime = parseLong(parser, KEY_FRAGMENT_START_TIME, -1L); - if (startTime == -1L) { - if (chunkIndex == 0) { - // Assume the track starts at t = 0. - startTime = 0; - } else if (lastChunkDuration != -1L) { - // Infer the start time from the previous chunk's start time and duration. - startTime = startTimes.get(chunkIndex - 1) + lastChunkDuration; - } else { - // We don't have the start time, and we're unable to infer it. - throw new ParserException("Unable to infer start time"); - } - } - chunkIndex++; - startTimes.add(startTime); - lastChunkDuration = parseLong(parser, KEY_FRAGMENT_DURATION, -1L); - // Handle repeated chunks. - long repeatCount = parseLong(parser, KEY_FRAGMENT_REPEAT_COUNT, 1L); - if (repeatCount > 1 && lastChunkDuration == -1L) { - throw new ParserException("Repeated chunk with unspecified duration"); - } - for (int i = 1; i < repeatCount; i++) { - chunkIndex++; - startTimes.add(startTime + (lastChunkDuration * i)); - } - } - - private void parseStreamElementStartTag(XmlPullParser parser) throws ParserException { - type = parseType(parser); - putNormalizedAttribute(KEY_TYPE, type); - if (type == StreamElement.TYPE_TEXT) { - subType = parseRequiredString(parser, KEY_SUB_TYPE); - } else { - subType = parser.getAttributeValue(null, KEY_SUB_TYPE); - } - name = parser.getAttributeValue(null, KEY_NAME); - qualityLevels = parseInt(parser, KEY_QUALITY_LEVELS, -1); - url = parseRequiredString(parser, KEY_URL); - maxWidth = parseInt(parser, KEY_MAX_WIDTH, -1); - maxHeight = parseInt(parser, KEY_MAX_HEIGHT, -1); - displayWidth = parseInt(parser, KEY_DISPLAY_WIDTH, -1); - displayHeight = parseInt(parser, KEY_DISPLAY_HEIGHT, -1); - language = parser.getAttributeValue(null, KEY_LANGUAGE); - putNormalizedAttribute(KEY_LANGUAGE, language); - timescale = parseInt(parser, KEY_TIME_SCALE, -1); - if (timescale == -1) { - timescale = (Long) getNormalizedAttribute(KEY_TIME_SCALE); - } - startTimes = new ArrayList<>(); - } - - private int parseType(XmlPullParser parser) throws ParserException { - String value = parser.getAttributeValue(null, KEY_TYPE); - if (value != null) { - if (KEY_TYPE_AUDIO.equalsIgnoreCase(value)) { - return StreamElement.TYPE_AUDIO; - } else if (KEY_TYPE_VIDEO.equalsIgnoreCase(value)) { - return StreamElement.TYPE_VIDEO; - } else if (KEY_TYPE_TEXT.equalsIgnoreCase(value)) { - return StreamElement.TYPE_TEXT; - } else { - throw new ParserException("Invalid key value[" + value + "]"); - } - } - throw new MissingFieldException(KEY_TYPE); - } - - @Override - public void addChild(Object child) { - if (child instanceof TrackElement) { - tracks.add((TrackElement) child); - } - } - - @Override - public Object build() { - TrackElement[] trackElements = new TrackElement[tracks.size()]; - tracks.toArray(trackElements); - return new StreamElement(baseUri, url, type, subType, timescale, name, qualityLevels, - maxWidth, maxHeight, displayWidth, displayHeight, language, trackElements, startTimes, - lastChunkDuration); - } - - } - - private static class TrackElementParser extends ElementParser { - - public static final String TAG = "QualityLevel"; - - private static final String KEY_INDEX = "Index"; - private static final String KEY_BITRATE = "Bitrate"; - private static final String KEY_CODEC_PRIVATE_DATA = "CodecPrivateData"; - private static final String KEY_SAMPLING_RATE = "SamplingRate"; - private static final String KEY_CHANNELS = "Channels"; - private static final String KEY_FOUR_CC = "FourCC"; - private static final String KEY_TYPE = "Type"; - private static final String KEY_LANGUAGE = "Language"; - private static final String KEY_MAX_WIDTH = "MaxWidth"; - private static final String KEY_MAX_HEIGHT = "MaxHeight"; - - private final List csd; - - private int index; - private int bitrate; - private String mimeType; - private int maxWidth; - private int maxHeight; - private int samplingRate; - private int channels; - private String language; - - public TrackElementParser(ElementParser parent, String baseUri) { - super(parent, baseUri, TAG); - this.csd = new LinkedList<>(); - } - - @Override - public void parseStartTag(XmlPullParser parser) throws ParserException { - int type = (Integer) getNormalizedAttribute(KEY_TYPE); - String value; - - index = parseInt(parser, KEY_INDEX, -1); - bitrate = parseRequiredInt(parser, KEY_BITRATE); - language = (String) getNormalizedAttribute(KEY_LANGUAGE); - - if (type == StreamElement.TYPE_VIDEO) { - maxHeight = parseRequiredInt(parser, KEY_MAX_HEIGHT); - maxWidth = parseRequiredInt(parser, KEY_MAX_WIDTH); - mimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); - } else { - maxHeight = -1; - maxWidth = -1; - String fourCC = parser.getAttributeValue(null, KEY_FOUR_CC); - // If fourCC is missing and the stream type is audio, we assume AAC. - mimeType = fourCC != null ? fourCCToMimeType(fourCC) - : type == StreamElement.TYPE_AUDIO ? MimeTypes.AUDIO_AAC : null; - } - - if (type == StreamElement.TYPE_AUDIO) { - samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); - channels = parseRequiredInt(parser, KEY_CHANNELS); - } else { - samplingRate = -1; - channels = -1; - } - - value = parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA); - if (value != null && value.length() > 0) { - byte[] codecPrivateData = Util.getBytesFromHexString(value); - byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); - if (split == null) { - csd.add(codecPrivateData); - } else { - for (int i = 0; i < split.length; i++) { - csd.add(split[i]); - } - } - } - } - - @Override - public Object build() { - byte[][] csdArray = null; - if (!csd.isEmpty()) { - csdArray = new byte[csd.size()][]; - csd.toArray(csdArray); - } - return new TrackElement(index, bitrate, mimeType, csdArray, maxWidth, maxHeight, samplingRate, - channels, language); - } - - private static String fourCCToMimeType(String fourCC) { - if (fourCC.equalsIgnoreCase("H264") || fourCC.equalsIgnoreCase("X264") - || fourCC.equalsIgnoreCase("AVC1") || fourCC.equalsIgnoreCase("DAVC")) { - return MimeTypes.VIDEO_H264; - } else if (fourCC.equalsIgnoreCase("AAC") || fourCC.equalsIgnoreCase("AACL") - || fourCC.equalsIgnoreCase("AACH") || fourCC.equalsIgnoreCase("AACP")) { - return MimeTypes.AUDIO_AAC; - } else if (fourCC.equalsIgnoreCase("TTML")) { - return MimeTypes.APPLICATION_TTML; - } else if (fourCC.equalsIgnoreCase("ac-3") || fourCC.equalsIgnoreCase("dac3")) { - return MimeTypes.AUDIO_AC3; - } else if (fourCC.equalsIgnoreCase("ec-3") || fourCC.equalsIgnoreCase("dec3")) { - return MimeTypes.AUDIO_E_AC3; - } else if (fourCC.equalsIgnoreCase("dtsc")) { - return MimeTypes.AUDIO_DTS; - } else if (fourCC.equalsIgnoreCase("dtsh") || fourCC.equalsIgnoreCase("dtsl")) { - return MimeTypes.AUDIO_DTS_HD; - } else if (fourCC.equalsIgnoreCase("dtse")) { - return MimeTypes.AUDIO_DTS_EXPRESS; - } else if (fourCC.equalsIgnoreCase("opus")) { - return MimeTypes.AUDIO_OPUS; - } - return null; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.java deleted file mode 100755 index 3dd92c87bca..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/smoothstreaming/SmoothStreamingTrackSelector.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.smoothstreaming; - -import java.io.IOException; - -/** - * Specifies a track selection from a {@link SmoothStreamingManifest}. - */ -public interface SmoothStreamingTrackSelector { - - /** - * Defines a selector output. - */ - interface Output { - - /** - * Outputs an adaptive track, covering the specified tracks in the specified element. - * - * @param manifest The manifest being processed. - * @param element The index of the element within which the adaptive tracks are located. - * @param tracks The indices of the tracks within the element. - */ - void adaptiveTrack(SmoothStreamingManifest manifest, int element, int[] tracks); - - /** - * Outputs a fixed track corresponding to the specified track in the specified element. - * - * @param manifest The manifest being processed. - * @param element The index of the element within which the track is located. - * @param track The index of the track within the element. - */ - void fixedTrack(SmoothStreamingManifest manifest, int element, int track); - - } - - /** - * Outputs a track selection for a given manifest. - * - * @param manifest The manifest to process. - * @param output The output to receive tracks. - * @throws IOException If an error occurs processing the manifest. - */ - void selectTracks(SmoothStreamingManifest manifest, Output output) throws IOException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/PlayableSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/PlayableSubtitle.java deleted file mode 100755 index 5c9d3f8a03d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/PlayableSubtitle.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.telegram.messenger.exoplayer.text; - -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.util.List; - -/** - * A subtitle that wraps another subtitle, making it playable by adjusting it to be correctly - * aligned with the playback timebase. - */ -/* package */ final class PlayableSubtitle implements Subtitle { - - /** - * The start time of the subtitle. - *

        - * May be less than {@code getEventTime(0)}, since a subtitle may begin prior to the time of the - * first event. - */ - public final long startTimeUs; - - private final Subtitle subtitle; - private final long offsetUs; - - /** - * @param subtitle The subtitle to wrap. - * @param isRelative True if the wrapped subtitle's timestamps are relative to the start time. - * False if they are absolute. - * @param startTimeUs The start time of the subtitle. - * @param offsetUs An offset to add to the subtitle timestamps. - */ - public PlayableSubtitle(Subtitle subtitle, boolean isRelative, long startTimeUs, long offsetUs) { - this.subtitle = subtitle; - this.startTimeUs = startTimeUs; - this.offsetUs = (isRelative ? startTimeUs : 0) + offsetUs; - } - - @Override - public int getEventTimeCount() { - return subtitle.getEventTimeCount(); - } - - @Override - public long getEventTime(int index) { - return subtitle.getEventTime(index) + offsetUs; - } - - @Override - public long getLastEventTime() { - return subtitle.getLastEventTime() + offsetUs; - } - - @Override - public int getNextEventTimeIndex(long timeUs) { - return subtitle.getNextEventTimeIndex(timeUs - offsetUs); - } - - @Override - public List getCues(long timeUs) { - return subtitle.getCues(timeUs - offsetUs); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Subtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Subtitle.java deleted file mode 100755 index e4a697215fb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Subtitle.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text; - -import java.util.List; - -/** - * A subtitle that contains textual data associated with time indices. - */ -public interface Subtitle { - - /** - * Gets the index of the first event that occurs after a given time (exclusive). - * - * @param timeUs The time in microseconds. - * @return The index of the next event, or -1 if there are no events after the specified time. - */ - public int getNextEventTimeIndex(long timeUs); - - /** - * Gets the number of event times, where events are defined as points in time at which the cues - * returned by {@link #getCues(long)} changes. - * - * @return The number of event times. - */ - public int getEventTimeCount(); - - /** - * Gets the event time at a specified index. - * - * @param index The index of the event time to obtain. - * @return The event time in microseconds. - */ - public long getEventTime(int index); - - /** - * Convenience method for obtaining the last event time. - * - * @return The time of the last event in microseconds, or -1 if {@code getEventTimeCount() == 0}. - */ - public long getLastEventTime(); - - /** - * Retrieve the subtitle cues that should be displayed at a given time. - * - * @param timeUs The time in microseconds. - * @return A list of cues that should be displayed, possibly empty. - */ - public List getCues(long timeUs); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParser.java deleted file mode 100755 index e7cec2ab8ec..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParser.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text; - -import org.telegram.messenger.exoplayer.ParserException; - -/** - * Parses {@link Subtitle}s from a byte array. - */ -public interface SubtitleParser { - - /** - * Checks whether the parser supports a given subtitle mime type. - * - * @param mimeType A subtitle mime type. - * @return Whether the mime type is supported. - */ - public boolean canParse(String mimeType); - - /** - * Parses a {@link Subtitle} from the provided {@code byte[]}. - * - * @param bytes The array holding the subtitle data. - * @param offset The offset of the subtitle data in bytes. - * @param length The length of the subtitle data in bytes. - * @return A parsed representation of the subtitle. - * @throws ParserException If a problem occurred parsing the subtitle data. - */ - public Subtitle parse(byte[] bytes, int offset, int length) throws ParserException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParserHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParserHelper.java deleted file mode 100755 index f49cdb048f8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/SubtitleParserHelper.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text; - -import android.media.MediaCodec; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * Wraps a {@link SubtitleParser}, exposing an interface similar to {@link MediaCodec} for - * asynchronous parsing of subtitles. - */ -/* package */ final class SubtitleParserHelper implements Handler.Callback { - - private static final int MSG_FORMAT = 0; - private static final int MSG_SAMPLE = 1; - - private final SubtitleParser parser; - private final Handler handler; - - private SampleHolder sampleHolder; - private boolean parsing; - private PlayableSubtitle result; - private IOException error; - private RuntimeException runtimeError; - - private boolean subtitlesAreRelative; - private long subtitleOffsetUs; - - /** - * @param looper The {@link Looper} associated with the thread on which parsing should occur. - * @param parser The parser that should be used to parse the raw data. - */ - public SubtitleParserHelper(Looper looper, SubtitleParser parser) { - this.handler = new Handler(looper, this); - this.parser = parser; - flush(); - } - - /** - * Flushes the helper, canceling the current parsing operation, if there is one. - */ - public synchronized void flush() { - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - parsing = false; - result = null; - error = null; - runtimeError = null; - } - - /** - * Whether the helper is currently performing a parsing operation. - * - * @return True if the helper is currently performing a parsing operation. False otherwise. - */ - public synchronized boolean isParsing() { - return parsing; - } - - /** - * Gets the holder that should be populated with data to be parsed. - *

        - * The returned holder will remain valid unless {@link #flush()} is called. If {@link #flush()} - * is called the holder is replaced, and this method should be called again to obtain the new - * holder. - * - * @return The holder that should be populated with data to be parsed. - */ - public synchronized SampleHolder getSampleHolder() { - return sampleHolder; - } - - /** - * Sets the format of subsequent samples. - * - * @param format The format. - */ - public void setFormat(MediaFormat format) { - handler.obtainMessage(MSG_FORMAT, format).sendToTarget(); - } - - /** - * Start a parsing operation. - *

        - * The holder returned by {@link #getSampleHolder()} should be populated with the data to be - * parsed prior to calling this method. - */ - public synchronized void startParseOperation() { - Assertions.checkState(!parsing); - parsing = true; - result = null; - error = null; - runtimeError = null; - handler.obtainMessage(MSG_SAMPLE, Util.getTopInt(sampleHolder.timeUs), - Util.getBottomInt(sampleHolder.timeUs), sampleHolder).sendToTarget(); - } - - /** - * Gets the result of the most recent parsing operation. - *

        - * The result is cleared as a result of calling this method, and so subsequent calls will return - * null until a subsequent parsing operation has finished. - * - * @return The result of the parsing operation, or null. - * @throws IOException If the parsing operation failed. - */ - public synchronized PlayableSubtitle getAndClearResult() throws IOException { - try { - if (error != null) { - throw error; - } else if (runtimeError != null) { - throw runtimeError; - } else { - return result; - } - } finally { - result = null; - error = null; - runtimeError = null; - } - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_FORMAT: - handleFormat((MediaFormat) msg.obj); - break; - case MSG_SAMPLE: - long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2); - SampleHolder holder = (SampleHolder) msg.obj; - handleSample(sampleTimeUs, holder); - break; - } - return true; - } - - private void handleFormat(MediaFormat format) { - subtitlesAreRelative = format.subsampleOffsetUs == MediaFormat.OFFSET_SAMPLE_RELATIVE; - subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs; - } - - private void handleSample(long sampleTimeUs, SampleHolder holder) { - Subtitle parsedSubtitle = null; - ParserException error = null; - RuntimeException runtimeError = null; - try { - parsedSubtitle = parser.parse(holder.data.array(), 0, holder.size); - } catch (ParserException e) { - error = e; - } catch (RuntimeException e) { - runtimeError = e; - } - synchronized (this) { - if (sampleHolder != holder) { - // A flush has occurred since this holder was posted. Do nothing. - } else { - this.result = new PlayableSubtitle(parsedSubtitle, subtitlesAreRelative, sampleTimeUs, - subtitleOffsetUs); - this.error = error; - this.runtimeError = runtimeError; - this.parsing = false; - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextRenderer.java deleted file mode 100755 index 77a0682a54e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextRenderer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text; - -import java.util.List; - -/** - * An interface for components that render text. - */ -public interface TextRenderer { - - /** - * Invoked each time there is a change in the {@link Cue}s to be rendered. - * - * @param cues The {@link Cue}s to be rendered, or an empty list if no cues are to be rendered. - */ - void onCues(List cues); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextTrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextTrackRenderer.java deleted file mode 100755 index 4a206c6e2bf..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/TextTrackRenderer.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text; - -import android.annotation.TargetApi; -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import org.telegram.messenger.exoplayer.ExoPlaybackException; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSourceTrackRenderer; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * A {@link TrackRenderer} for subtitles. Text is parsed from sample data using a - * {@link SubtitleParser}. The actual rendering of each line of text is delegated to a - * {@link TextRenderer}. - *

        - * If no {@link SubtitleParser} instances are passed to the constructor, the subtitle type will be - * detected automatically for the following supported formats: - * - *

          - *
        • WebVTT ({@link org.telegram.messenger.exoplayer.text.webvtt.WebvttParser})
        • - *
        • TTML - * ({@link org.telegram.messenger.exoplayer.text.ttml.TtmlParser})
        • - *
        • SubRip - * ({@link org.telegram.messenger.exoplayer.text.subrip.SubripParser})
        • - *
        • TX3G - * ({@link org.telegram.messenger.exoplayer.text.tx3g.Tx3gParser})
        • - *
        - * - *

        To override the default parsers, pass one or more {@link SubtitleParser} instances to the - * constructor. The first {@link SubtitleParser} that returns {@code true} from - * {@link SubtitleParser#canParse(String)} will be used. - */ -@TargetApi(16) -public final class TextTrackRenderer extends SampleSourceTrackRenderer implements Callback { - - private static final int MSG_UPDATE_OVERLAY = 0; - - /** - * Default parser classes in priority order. They are referred to indirectly so that it is - * possible to remove unused parsers. - */ - private static final List> DEFAULT_PARSER_CLASSES; - static { - DEFAULT_PARSER_CLASSES = new ArrayList<>(); - // Load parsers using reflection so that they can be deleted cleanly. - // Class.forName() appears for each parser so that automated tools like proguard - // can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.text.webvtt.WebvttParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.text.ttml.TtmlParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.text.webvtt.Mp4WebvttParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.text.subrip.SubripParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("org.telegram.messenger.exoplayer.text.tx3g.Tx3gParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - } - - private final Handler textRendererHandler; - private final TextRenderer textRenderer; - private final MediaFormatHolder formatHolder; - private final SubtitleParser[] subtitleParsers; - - private int parserIndex; - private boolean inputStreamEnded; - private PlayableSubtitle subtitle; - private PlayableSubtitle nextSubtitle; - private SubtitleParserHelper parserHelper; - private HandlerThread parserThread; - private int nextSubtitleEventIndex; - - /** - * @param source A source from which samples containing subtitle data can be read. - * @param textRenderer The text renderer. - * @param textRendererLooper The looper associated with the thread on which textRenderer should be - * invoked. If the renderer makes use of standard Android UI components, then this should - * normally be the looper associated with the applications' main thread, which can be - * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the - * renderer should be invoked directly on the player's internal rendering thread. - * @param subtitleParsers {@link SubtitleParser}s to parse text samples, in order of decreasing - * priority. If omitted, the default parsers will be used. - */ - public TextTrackRenderer(SampleSource source, TextRenderer textRenderer, - Looper textRendererLooper, SubtitleParser... subtitleParsers) { - this(new SampleSource[] {source}, textRenderer, textRendererLooper, subtitleParsers); - } - - /** - * @param sources Sources from which samples containing subtitle data can be read. - * @param textRenderer The text renderer. - * @param textRendererLooper The looper associated with the thread on which textRenderer should be - * invoked. If the renderer makes use of standard Android UI components, then this should - * normally be the looper associated with the applications' main thread, which can be - * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the - * renderer should be invoked directly on the player's internal rendering thread. - * @param subtitleParsers {@link SubtitleParser}s to parse text samples, in order of decreasing - * priority. If omitted, the default parsers will be used. - */ - public TextTrackRenderer(SampleSource[] sources, TextRenderer textRenderer, - Looper textRendererLooper, SubtitleParser... subtitleParsers) { - super(sources); - this.textRenderer = Assertions.checkNotNull(textRenderer); - this.textRendererHandler = textRendererLooper == null ? null - : new Handler(textRendererLooper, this); - if (subtitleParsers == null || subtitleParsers.length == 0) { - subtitleParsers = new SubtitleParser[DEFAULT_PARSER_CLASSES.size()]; - for (int i = 0; i < subtitleParsers.length; i++) { - try { - subtitleParsers[i] = DEFAULT_PARSER_CLASSES.get(i).newInstance(); - } catch (InstantiationException e) { - throw new IllegalStateException("Unexpected error creating default parser", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Unexpected error creating default parser", e); - } - } - } - this.subtitleParsers = subtitleParsers; - formatHolder = new MediaFormatHolder(); - } - - @Override - protected boolean handlesTrack(MediaFormat mediaFormat) { - return getParserIndex(mediaFormat) != -1; - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - parserIndex = getParserIndex(getFormat(track)); - parserThread = new HandlerThread("textParser"); - parserThread.start(); - parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]); - } - - @Override - protected void onDiscontinuity(long positionUs) { - inputStreamEnded = false; - subtitle = null; - nextSubtitle = null; - clearTextRenderer(); - if (parserHelper != null) { - parserHelper.flush(); - } - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) - throws ExoPlaybackException { - if (nextSubtitle == null) { - try { - nextSubtitle = parserHelper.getAndClearResult(); - } catch (IOException e) { - throw new ExoPlaybackException(e); - } - } - - if (getState() != TrackRenderer.STATE_STARTED) { - return; - } - - boolean textRendererNeedsUpdate = false; - long subtitleNextEventTimeUs = Long.MAX_VALUE; - if (subtitle != null) { - // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we - // advance to the next event. - subtitleNextEventTimeUs = getNextEventTime(); - while (subtitleNextEventTimeUs <= positionUs) { - nextSubtitleEventIndex++; - subtitleNextEventTimeUs = getNextEventTime(); - textRendererNeedsUpdate = true; - } - } - - if (nextSubtitle != null && nextSubtitle.startTimeUs <= positionUs) { - // Advance to the next subtitle. Sync the next event index and trigger an update. - subtitle = nextSubtitle; - nextSubtitle = null; - nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs); - textRendererNeedsUpdate = true; - } - - if (textRendererNeedsUpdate) { - // textRendererNeedsUpdate is set and we're playing. Update the renderer. - updateTextRenderer(subtitle.getCues(positionUs)); - } - - if (!inputStreamEnded && nextSubtitle == null && !parserHelper.isParsing()) { - // Try and read the next subtitle from the source. - SampleHolder sampleHolder = parserHelper.getSampleHolder(); - sampleHolder.clearData(); - int result = readSource(positionUs, formatHolder, sampleHolder); - if (result == SampleSource.FORMAT_READ) { - parserHelper.setFormat(formatHolder.format); - } else if (result == SampleSource.SAMPLE_READ) { - parserHelper.startParseOperation(); - } else if (result == SampleSource.END_OF_STREAM) { - inputStreamEnded = true; - } - } - } - - @Override - protected void onDisabled() throws ExoPlaybackException { - subtitle = null; - nextSubtitle = null; - parserThread.quit(); - parserThread = null; - parserHelper = null; - clearTextRenderer(); - super.onDisabled(); - } - - @Override - protected long getBufferedPositionUs() { - // Don't block playback whilst subtitles are loading. - return END_OF_TRACK_US; - } - - @Override - protected boolean isEnded() { - return inputStreamEnded && (subtitle == null || getNextEventTime() == Long.MAX_VALUE); - } - - @Override - protected boolean isReady() { - // Don't block playback whilst subtitles are loading. - // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. - return true; - } - - private long getNextEventTime() { - return ((nextSubtitleEventIndex == -1) - || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE - : (subtitle.getEventTime(nextSubtitleEventIndex)); - } - - private void updateTextRenderer(List cues) { - if (textRendererHandler != null) { - textRendererHandler.obtainMessage(MSG_UPDATE_OVERLAY, cues).sendToTarget(); - } else { - invokeRendererInternalCues(cues); - } - } - - private void clearTextRenderer() { - updateTextRenderer(Collections.emptyList()); - } - - @SuppressWarnings("unchecked") - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_UPDATE_OVERLAY: - invokeRendererInternalCues((List) msg.obj); - return true; - } - return false; - } - - private void invokeRendererInternalCues(List cues) { - textRenderer.onCues(cues); - } - - private int getParserIndex(MediaFormat mediaFormat) { - for (int i = 0; i < subtitleParsers.length; i++) { - if (subtitleParsers[i].canParse(mediaFormat.mimeType)) { - return i; - } - } - return -1; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaption.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaption.java deleted file mode 100755 index 4e12566b663..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaption.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -/** - * A Closed Caption that contains textual data associated with time indices. - */ -/* package */ abstract class ClosedCaption { - - /** - * Identifies closed captions with control characters. - */ - public static final int TYPE_CTRL = 0; - /** - * Identifies closed captions with textual information. - */ - public static final int TYPE_TEXT = 1; - - /** - * The type of the closed caption data. - */ - public final int type; - - protected ClosedCaption(int type) { - this.type = type; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionCtrl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionCtrl.java deleted file mode 100755 index 846a6f576eb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionCtrl.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionCtrl extends ClosedCaption { - - /** - * The receipt of the {@link #RESUME_CAPTION_LOADING} command initiates pop-on style captioning. - * Subsequent data should be loaded into a non-displayed memory and held there until the - * {@link #END_OF_CAPTION} command is received, at which point the non-displayed memory becomes - * the displayed memory (and vice versa). - */ - public static final byte RESUME_CAPTION_LOADING = 0x20; - /** - * The receipt of the {@link #ROLL_UP_CAPTIONS_2_ROWS} command initiates roll-up style - * captioning, with the maximum of 2 rows displayed simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_2_ROWS = 0x25; - /** - * The receipt of the {@link #ROLL_UP_CAPTIONS_3_ROWS} command initiates roll-up style - * captioning, with the maximum of 3 rows displayed simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_3_ROWS = 0x26; - /** - * The receipt of the {@link #ROLL_UP_CAPTIONS_4_ROWS} command initiates roll-up style - * captioning, with the maximum of 4 rows displayed simultaneously. - */ - public static final byte ROLL_UP_CAPTIONS_4_ROWS = 0x27; - /** - * The receipt of the {@link #RESUME_DIRECT_CAPTIONING} command initiates paint-on style - * captioning. Subsequent data should be addressed immediately to displayed memory without need - * for the {@link #RESUME_CAPTION_LOADING} command. - */ - public static final byte RESUME_DIRECT_CAPTIONING = 0x29; - /** - * The receipt of the {@link #END_OF_CAPTION} command indicates the end of pop-on style caption, - * at this point already loaded in non-displayed memory caption should become the displayed - * memory (and vice versa). If no {@link #RESUME_CAPTION_LOADING} command has been received, - * {@link #END_OF_CAPTION} command forces the receiver into pop-on style. - */ - public static final byte END_OF_CAPTION = 0x2F; - - public static final byte ERASE_DISPLAYED_MEMORY = 0x2C; - public static final byte CARRIAGE_RETURN = 0x2D; - public static final byte ERASE_NON_DISPLAYED_MEMORY = 0x2E; - - public static final byte BACKSPACE = 0x21; - - - public static final byte MID_ROW_CHAN_1 = 0x11; - public static final byte MID_ROW_CHAN_2 = 0x19; - - public static final byte MISC_CHAN_1 = 0x14; - public static final byte MISC_CHAN_2 = 0x1C; - - public static final byte TAB_OFFSET_CHAN_1 = 0x17; - public static final byte TAB_OFFSET_CHAN_2 = 0x1F; - - public final byte cc1; - public final byte cc2; - - protected ClosedCaptionCtrl(byte cc1, byte cc2) { - super(ClosedCaption.TYPE_CTRL); - this.cc1 = cc1; - this.cc2 = cc2; - } - - public boolean isMidRowCode() { - return (cc1 == MID_ROW_CHAN_1 || cc1 == MID_ROW_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F); - } - - public boolean isMiscCode() { - return (cc1 == MISC_CHAN_1 || cc1 == MISC_CHAN_2) && (cc2 >= 0x20 && cc2 <= 0x2F); - } - - public boolean isTabOffsetCode() { - return (cc1 == TAB_OFFSET_CHAN_1 || cc1 == TAB_OFFSET_CHAN_2) && (cc2 >= 0x21 && cc2 <= 0x23); - } - - public boolean isPreambleAddressCode() { - return (cc1 >= 0x10 && cc1 <= 0x1F) && (cc2 >= 0x40 && cc2 <= 0x7F); - } - - public boolean isRepeatable() { - return cc1 >= 0x10 && cc1 <= 0x1F; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionList.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionList.java deleted file mode 100755 index 022d75116c6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionList.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionList implements Comparable { - - public final long timeUs; - public final boolean decodeOnly; - public final ClosedCaption[] captions; - - public ClosedCaptionList(long timeUs, boolean decodeOnly, ClosedCaption[] captions) { - this.timeUs = timeUs; - this.decodeOnly = decodeOnly; - this.captions = captions; - } - - @Override - public int compareTo(ClosedCaptionList other) { - long delta = timeUs - other.timeUs; - if (delta == 0) { - return 0; - } - return delta > 0 ? 1 : -1; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionText.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionText.java deleted file mode 100755 index a23f85a5032..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/ClosedCaptionText.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -/* package */ final class ClosedCaptionText extends ClosedCaption { - - public final String text; - - public ClosedCaptionText(String text) { - super(ClosedCaption.TYPE_TEXT); - this.text = text; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608Parser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608Parser.java deleted file mode 100755 index 94c50e70b12..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608Parser.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.ArrayList; - -/** - * Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608") - * Closed Captions from the SEI data block from H.264. - */ -public final class Eia608Parser { - - private static final int PAYLOAD_TYPE_CC = 4; - private static final int COUNTRY_CODE = 0xB5; - private static final int PROVIDER_CODE = 0x31; - private static final int USER_ID = 0x47413934; // "GA94" - private static final int USER_DATA_TYPE_CODE = 0x3; - - // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). - private static final int[] BASIC_CHARACTER_SET = new int[] { - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & ' - 0x28, 0x29, // ( ) - 0xE1, // 2A: 225 'á' "Latin small letter A with acute" - 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // + , - . / - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0 1 2 3 4 5 6 7 - 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 8 9 : ; < = > ? - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // @ A B C D E F G - 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // H I J K L M N O - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // P Q R S T U V W - 0x58, 0x59, 0x5A, 0x5B, // X Y Z [ - 0xE9, // 5C: 233 'é' "Latin small letter E with acute" - 0x5D, // ] - 0xED, // 5E: 237 'í' "Latin small letter I with acute" - 0xF3, // 5F: 243 'ó' "Latin small letter O with acute" - 0xFA, // 60: 250 'ú' "Latin small letter U with acute" - 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // a b c d e f g - 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // h i j k l m n o - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // p q r s t u v w - 0x78, 0x79, 0x7A, // x y z - 0xE7, // 7B: 231 'ç' "Latin small letter C with cedilla" - 0xF7, // 7C: 247 '÷' "Division sign" - 0xD1, // 7D: 209 'Ñ' "Latin capital letter N with tilde" - 0xF1, // 7E: 241 'ñ' "Latin small letter N with tilde" - 0x25A0 // 7F: "Black Square" (NB: 2588 = Full Block) - }; - - // Special North American 608 CC char set. - private static final int[] SPECIAL_CHARACTER_SET = new int[] { - 0xAE, // 30: 174 '®' "Registered Sign" - registered trademark symbol - 0xB0, // 31: 176 '°' "Degree Sign" - 0xBD, // 32: 189 '½' "Vulgar Fraction One Half" (1/2 symbol) - 0xBF, // 33: 191 '¿' "Inverted Question Mark" - 0x2122, // 34: "Trade Mark Sign" (tm superscript) - 0xA2, // 35: 162 '¢' "Cent Sign" - 0xA3, // 36: 163 '£' "Pound Sign" - pounds sterling - 0x266A, // 37: "Eighth Note" - music note - 0xE0, // 38: 224 'à' "Latin small letter A with grave" - 0x20, // 39: TRANSPARENT SPACE - for now use ordinary space - 0xE8, // 3A: 232 'è' "Latin small letter E with grave" - 0xE2, // 3B: 226 'â' "Latin small letter A with circumflex" - 0xEA, // 3C: 234 'ê' "Latin small letter E with circumflex" - 0xEE, // 3D: 238 'î' "Latin small letter I with circumflex" - 0xF4, // 3E: 244 'ô' "Latin small letter O with circumflex" - 0xFB // 3F: 251 'û' "Latin small letter U with circumflex" - }; - - // Extended Spanish/Miscellaneous and French char set. - private static final int[] SPECIAL_ES_FR_CHARACTER_SET = new int[] { - // Spanish and misc. - 0xC1, 0xC9, 0xD3, 0xDA, 0xDC, 0xFC, 0x2018, 0xA1, - 0x2A, 0x27, 0x2014, 0xA9, 0x2120, 0x2022, 0x201C, 0x201D, - // French. - 0xC0, 0xC2, 0xC7, 0xC8, 0xCA, 0xCB, 0xEB, 0xCE, - 0xCF, 0xEF, 0xD4, 0xD9, 0xF9, 0xDB, 0xAB, 0xBB - }; - - //Extended Portuguese and German/Danish char set. - private static final int[] SPECIAL_PT_DE_CHARACTER_SET = new int[] { - // Portuguese. - 0xC3, 0xE3, 0xCD, 0xCC, 0xEC, 0xD2, 0xF2, 0xD5, - 0xF5, 0x7B, 0x7D, 0x5C, 0x5E, 0x5F, 0x7C, 0x7E, - // German/Danish. - 0xC4, 0xE4, 0xD6, 0xF6, 0xDF, 0xA5, 0xA4, 0x2502, - 0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518 - }; - - private final ParsableBitArray seiBuffer; - private final StringBuilder stringBuilder; - private final ArrayList captions; - - /* package */ Eia608Parser() { - seiBuffer = new ParsableBitArray(); - stringBuilder = new StringBuilder(); - captions = new ArrayList<>(); - } - - /* package */ boolean canParse(String mimeType) { - return mimeType.equals(MimeTypes.APPLICATION_EIA608); - } - - /* package */ ClosedCaptionList parse(SampleHolder sampleHolder) { - if (sampleHolder.size < 10) { - return null; - } - - captions.clear(); - stringBuilder.setLength(0); - seiBuffer.reset(sampleHolder.data.array()); - - // country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) + - // reserved (1) + process_cc_data_flag (1) + zero_bit (1) - seiBuffer.skipBits(67); - int ccCount = seiBuffer.readBits(5); - seiBuffer.skipBits(8); - - for (int i = 0; i < ccCount; i++) { - seiBuffer.skipBits(5); // one_bit + reserved - boolean ccValid = seiBuffer.readBit(); - if (!ccValid) { - seiBuffer.skipBits(18); - continue; - } - int ccType = seiBuffer.readBits(2); - if (ccType != 0) { - seiBuffer.skipBits(16); - continue; - } - seiBuffer.skipBits(1); - byte ccData1 = (byte) seiBuffer.readBits(7); - seiBuffer.skipBits(1); - byte ccData2 = (byte) seiBuffer.readBits(7); - - // Ignore empty captions. - if (ccData1 == 0 && ccData2 == 0) { - continue; - } - - // Special North American character set. - // ccData2 - P|0|1|1|X|X|X|X - if ((ccData1 == 0x11 || ccData1 == 0x19) - && ((ccData2 & 0x70) == 0x30)) { - stringBuilder.append(getSpecialChar(ccData2)); - continue; - } - - // Extended Spanish/Miscellaneous and French character set. - // ccData2 - P|0|1|X|X|X|X|X - if ((ccData1 == 0x12 || ccData1 == 0x1A) - && ((ccData2 & 0x60) == 0x20)) { - backspace(); // Remove standard equivalent of the special extended char. - stringBuilder.append(getExtendedEsFrChar(ccData2)); - continue; - } - - // Extended Portuguese and German/Danish character set. - // ccData2 - P|0|1|X|X|X|X|X - if ((ccData1 == 0x13 || ccData1 == 0x1B) - && ((ccData2 & 0x60) == 0x20)) { - backspace(); // Remove standard equivalent of the special extended char. - stringBuilder.append(getExtendedPtDeChar(ccData2)); - continue; - } - - // Control character. - if (ccData1 < 0x20) { - addCtrl(ccData1, ccData2); - continue; - } - - // Basic North American character set. - stringBuilder.append(getChar(ccData1)); - if (ccData2 >= 0x20) { - stringBuilder.append(getChar(ccData2)); - } - } - - addBufferedText(); - - if (captions.isEmpty()) { - return null; - } - - ClosedCaption[] captionArray = new ClosedCaption[captions.size()]; - captions.toArray(captionArray); - return new ClosedCaptionList(sampleHolder.timeUs, sampleHolder.isDecodeOnly(), captionArray); - } - - private static char getChar(byte ccData) { - int index = (ccData & 0x7F) - 0x20; - return (char) BASIC_CHARACTER_SET[index]; - } - - private static char getSpecialChar(byte ccData) { - int index = ccData & 0xF; - return (char) SPECIAL_CHARACTER_SET[index]; - } - - private static char getExtendedEsFrChar(byte ccData) { - int index = ccData & 0x1F; - return (char) SPECIAL_ES_FR_CHARACTER_SET[index]; - } - - private static char getExtendedPtDeChar(byte ccData) { - int index = ccData & 0x1F; - return (char) SPECIAL_PT_DE_CHARACTER_SET[index]; - } - - private void addBufferedText() { - if (stringBuilder.length() > 0) { - captions.add(new ClosedCaptionText(stringBuilder.toString())); - stringBuilder.setLength(0); - } - } - - private void addCtrl(byte ccData1, byte ccData2) { - addBufferedText(); - captions.add(new ClosedCaptionCtrl(ccData1, ccData2)); - } - - private void backspace() { - addCtrl((byte) 0x14, ClosedCaptionCtrl.BACKSPACE); - } - - /** - * Inspects an sei message to determine whether it contains EIA-608. - *

        - * The position of {@code payload} is left unchanged. - * - * @param payloadType The payload type of the message. - * @param payloadLength The length of the payload. - * @param payload A {@link ParsableByteArray} containing the payload. - * @return True if the sei message contains EIA-608. False otherwise. - */ - public static boolean isSeiMessageEia608(int payloadType, int payloadLength, - ParsableByteArray payload) { - if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { - return false; - } - int startPosition = payload.getPosition(); - int countryCode = payload.readUnsignedByte(); - int providerCode = payload.readUnsignedShort(); - int userIdentifier = payload.readInt(); - int userDataTypeCode = payload.readUnsignedByte(); - payload.setPosition(startPosition); - return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE - && userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608TrackRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608TrackRenderer.java deleted file mode 100755 index 557f85b8236..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/eia608/Eia608TrackRenderer.java +++ /dev/null @@ -1,361 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.eia608; - -import android.os.Handler; -import android.os.Handler.Callback; -import android.os.Looper; -import android.os.Message; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ExoPlaybackException; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.MediaFormatHolder; -import org.telegram.messenger.exoplayer.SampleHolder; -import org.telegram.messenger.exoplayer.SampleSource; -import org.telegram.messenger.exoplayer.SampleSourceTrackRenderer; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.TextRenderer; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.Collections; -import java.util.TreeSet; - -/** - * A {@link TrackRenderer} for EIA-608 closed captions in a media stream. - */ -public final class Eia608TrackRenderer extends SampleSourceTrackRenderer implements Callback { - - private static final int MSG_INVOKE_RENDERER = 0; - - private static final int CC_MODE_UNKNOWN = 0; - private static final int CC_MODE_ROLL_UP = 1; - private static final int CC_MODE_POP_ON = 2; - private static final int CC_MODE_PAINT_ON = 3; - - // The default number of rows to display in roll-up captions mode. - private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; - // The maximum duration that captions are parsed ahead of the current position. - private static final int MAX_SAMPLE_READAHEAD_US = 5000000; - - private final Eia608Parser eia608Parser; - private final TextRenderer textRenderer; - private final Handler textRendererHandler; - private final MediaFormatHolder formatHolder; - private final SampleHolder sampleHolder; - private final StringBuilder captionStringBuilder; - private final TreeSet pendingCaptionLists; - - private boolean inputStreamEnded; - private int captionMode; - private int captionRowCount; - private String caption; - private String lastRenderedCaption; - private ClosedCaptionCtrl repeatableControl; - - /** - * @param source A source from which samples containing EIA-608 closed captions can be read. - * @param textRenderer The text renderer. - * @param textRendererLooper The looper associated with the thread on which textRenderer should be - * invoked. If the renderer makes use of standard Android UI components, then this should - * normally be the looper associated with the applications' main thread, which can be - * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the - * renderer should be invoked directly on the player's internal rendering thread. - */ - public Eia608TrackRenderer(SampleSource source, TextRenderer textRenderer, - Looper textRendererLooper) { - super(source); - this.textRenderer = Assertions.checkNotNull(textRenderer); - textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this); - eia608Parser = new Eia608Parser(); - formatHolder = new MediaFormatHolder(); - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - captionStringBuilder = new StringBuilder(); - pendingCaptionLists = new TreeSet<>(); - } - - @Override - protected boolean handlesTrack(MediaFormat mediaFormat) { - return eia608Parser.canParse(mediaFormat.mimeType); - } - - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - } - - @Override - protected void onDiscontinuity(long positionUs) { - inputStreamEnded = false; - repeatableControl = null; - pendingCaptionLists.clear(); - clearPendingSample(); - captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; - setCaptionMode(CC_MODE_UNKNOWN); - invokeRenderer(null); - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) - throws ExoPlaybackException { - if (isSamplePending()) { - maybeParsePendingSample(positionUs); - } - - int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ; - while (!isSamplePending() && result == SampleSource.SAMPLE_READ) { - result = readSource(positionUs, formatHolder, sampleHolder); - if (result == SampleSource.SAMPLE_READ) { - maybeParsePendingSample(positionUs); - } else if (result == SampleSource.END_OF_STREAM) { - inputStreamEnded = true; - } - } - - while (!pendingCaptionLists.isEmpty()) { - if (pendingCaptionLists.first().timeUs > positionUs) { - // We're too early to render any of the pending caption lists. - return; - } - // Remove and consume the next caption list. - ClosedCaptionList nextCaptionList = pendingCaptionLists.pollFirst(); - consumeCaptionList(nextCaptionList); - // Update the renderer, unless the caption list was marked for decoding only. - if (!nextCaptionList.decodeOnly) { - invokeRenderer(caption); - } - } - } - - @Override - protected long getBufferedPositionUs() { - return TrackRenderer.END_OF_TRACK_US; - } - - @Override - protected boolean isEnded() { - return inputStreamEnded; - } - - @Override - protected boolean isReady() { - return true; - } - - private void invokeRenderer(String text) { - if (Util.areEqual(lastRenderedCaption, text)) { - // No change. - return; - } - this.lastRenderedCaption = text; - if (textRendererHandler != null) { - textRendererHandler.obtainMessage(MSG_INVOKE_RENDERER, text).sendToTarget(); - } else { - invokeRendererInternal(text); - } - } - - @SuppressWarnings("unchecked") - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_INVOKE_RENDERER: - invokeRendererInternal((String) msg.obj); - return true; - } - return false; - } - - private void invokeRendererInternal(String cueText) { - if (cueText == null) { - textRenderer.onCues(Collections.emptyList()); - } else { - textRenderer.onCues(Collections.singletonList(new Cue(cueText))); - } - } - - private void maybeParsePendingSample(long positionUs) { - if (sampleHolder.timeUs > positionUs + MAX_SAMPLE_READAHEAD_US) { - // We're too early to parse the sample. - return; - } - ClosedCaptionList holder = eia608Parser.parse(sampleHolder); - clearPendingSample(); - if (holder != null) { - pendingCaptionLists.add(holder); - } - } - - private void consumeCaptionList(ClosedCaptionList captionList) { - int captionBufferSize = captionList.captions.length; - if (captionBufferSize == 0) { - return; - } - - boolean isRepeatableControl = false; - for (int i = 0; i < captionBufferSize; i++) { - ClosedCaption caption = captionList.captions[i]; - if (caption.type == ClosedCaption.TYPE_CTRL) { - ClosedCaptionCtrl captionCtrl = (ClosedCaptionCtrl) caption; - isRepeatableControl = captionBufferSize == 1 && captionCtrl.isRepeatable(); - if (isRepeatableControl && repeatableControl != null - && repeatableControl.cc1 == captionCtrl.cc1 - && repeatableControl.cc2 == captionCtrl.cc2) { - repeatableControl = null; - continue; - } else if (isRepeatableControl) { - repeatableControl = captionCtrl; - } - if (captionCtrl.isMiscCode()) { - handleMiscCode(captionCtrl); - } else if (captionCtrl.isPreambleAddressCode()) { - handlePreambleAddressCode(); - } - } else { - handleText((ClosedCaptionText) caption); - } - } - - if (!isRepeatableControl) { - repeatableControl = null; - } - if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { - caption = getDisplayCaption(); - } - } - - private void handleText(ClosedCaptionText captionText) { - if (captionMode != CC_MODE_UNKNOWN) { - captionStringBuilder.append(captionText.text); - } - } - - private void handleMiscCode(ClosedCaptionCtrl captionCtrl) { - switch (captionCtrl.cc2) { - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_2_ROWS: - captionRowCount = 2; - setCaptionMode(CC_MODE_ROLL_UP); - return; - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_3_ROWS: - captionRowCount = 3; - setCaptionMode(CC_MODE_ROLL_UP); - return; - case ClosedCaptionCtrl.ROLL_UP_CAPTIONS_4_ROWS: - captionRowCount = 4; - setCaptionMode(CC_MODE_ROLL_UP); - return; - case ClosedCaptionCtrl.RESUME_CAPTION_LOADING: - setCaptionMode(CC_MODE_POP_ON); - return; - case ClosedCaptionCtrl.RESUME_DIRECT_CAPTIONING: - setCaptionMode(CC_MODE_PAINT_ON); - return; - } - - if (captionMode == CC_MODE_UNKNOWN) { - return; - } - - switch (captionCtrl.cc2) { - case ClosedCaptionCtrl.ERASE_DISPLAYED_MEMORY: - caption = null; - if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { - captionStringBuilder.setLength(0); - } - return; - case ClosedCaptionCtrl.ERASE_NON_DISPLAYED_MEMORY: - captionStringBuilder.setLength(0); - return; - case ClosedCaptionCtrl.END_OF_CAPTION: - caption = getDisplayCaption(); - captionStringBuilder.setLength(0); - return; - case ClosedCaptionCtrl.CARRIAGE_RETURN: - maybeAppendNewline(); - return; - case ClosedCaptionCtrl.BACKSPACE: - if (captionStringBuilder.length() > 0) { - captionStringBuilder.setLength(captionStringBuilder.length() - 1); - } - return; - } - } - - private void handlePreambleAddressCode() { - // TODO: Add better handling of this with specific positioning. - maybeAppendNewline(); - } - - private void setCaptionMode(int captionMode) { - if (this.captionMode == captionMode) { - return; - } - - this.captionMode = captionMode; - // Clear the working memory. - captionStringBuilder.setLength(0); - if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) { - // When switching to roll-up or unknown, we also need to clear the caption. - caption = null; - } - } - - private void maybeAppendNewline() { - int buildLength = captionStringBuilder.length(); - if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') { - captionStringBuilder.append('\n'); - } - } - - private String getDisplayCaption() { - int buildLength = captionStringBuilder.length(); - if (buildLength == 0) { - return null; - } - - boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n'; - if (buildLength == 1 && endsWithNewline) { - return null; - } - - int endIndex = endsWithNewline ? buildLength - 1 : buildLength; - if (captionMode != CC_MODE_ROLL_UP) { - return captionStringBuilder.substring(0, endIndex); - } - - int startIndex = 0; - int searchBackwardFromIndex = endIndex; - for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) { - searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1); - } - if (searchBackwardFromIndex != -1) { - startIndex = searchBackwardFromIndex + 1; - } - captionStringBuilder.delete(0, startIndex); - return captionStringBuilder.substring(0, endIndex - startIndex); - } - - private void clearPendingSample() { - sampleHolder.timeUs = C.UNKNOWN_TIME_US; - sampleHolder.clearData(); - } - - private boolean isSamplePending() { - return sampleHolder.timeUs != C.UNKNOWN_TIME_US; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripParser.java deleted file mode 100755 index cce5676b3a5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripParser.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.subrip; - -import android.text.Html; -import android.text.Spanned; -import android.text.TextUtils; -import android.util.Log; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.SubtitleParser; -import org.telegram.messenger.exoplayer.util.LongArray; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.ArrayList; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A simple SubRip parser. - */ -public final class SubripParser implements SubtitleParser { - - private static final String TAG = "SubripParser"; - - private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("(\\S*)\\s*-->\\s*(\\S*)"); - private static final Pattern SUBRIP_TIMESTAMP = - Pattern.compile("(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"); - - private final StringBuilder textBuilder; - - public SubripParser() { - textBuilder = new StringBuilder(); - } - - @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_SUBRIP.equals(mimeType); - } - - @Override - public SubripSubtitle parse(byte[] bytes, int offset, int length) { - ArrayList cues = new ArrayList<>(); - LongArray cueTimesUs = new LongArray(); - ParsableByteArray subripData = new ParsableByteArray(bytes, offset + length); - subripData.setPosition(offset); - boolean haveEndTimecode; - String currentLine; - - while ((currentLine = subripData.readLine()) != null) { - if (currentLine.length() == 0) { - // Skip blank lines. - continue; - } - - // Parse the index line as a sanity check. - try { - Integer.parseInt(currentLine); - } catch (NumberFormatException e) { - Log.w(TAG, "Skipping invalid index: " + currentLine); - continue; - } - - // Read and parse the timing line. - haveEndTimecode = false; - currentLine = subripData.readLine(); - Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); - if (matcher.find()) { - cueTimesUs.add(parseTimecode(matcher.group(1))); - String endTimecode = matcher.group(2); - if (!TextUtils.isEmpty(endTimecode)) { - haveEndTimecode = true; - cueTimesUs.add(parseTimecode(matcher.group(2))); - } - } else { - Log.w(TAG, "Skipping invalid timing: " + currentLine); - continue; - } - - // Read and parse the text. - textBuilder.setLength(0); - while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { - if (textBuilder.length() > 0) { - textBuilder.append("
        "); - } - textBuilder.append(currentLine.trim()); - } - - Spanned text = Html.fromHtml(textBuilder.toString()); - cues.add(new Cue(text)); - if (haveEndTimecode) { - cues.add(null); - } - } - - Cue[] cuesArray = new Cue[cues.size()]; - cues.toArray(cuesArray); - long[] cueTimesUsArray = cueTimesUs.toArray(); - return new SubripSubtitle(cuesArray, cueTimesUsArray); - } - - private static long parseTimecode(String s) throws NumberFormatException { - Matcher matcher = SUBRIP_TIMESTAMP.matcher(s); - if (!matcher.matches()) { - throw new NumberFormatException("has invalid format"); - } - long timestampMs = Long.parseLong(matcher.group(1)) * 60 * 60 * 1000; - timestampMs += Long.parseLong(matcher.group(2)) * 60 * 1000; - timestampMs += Long.parseLong(matcher.group(3)) * 1000; - timestampMs += Long.parseLong(matcher.group(4)); - return timestampMs * 1000; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlColorParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlColorParser.java deleted file mode 100755 index d11500693c6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlColorParser.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.ttml; - -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.HashMap; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Parser to parse ttml color value expression - * (http://www.w3.org/TR/ttml1/#style-value-color) - */ -/*package*/ final class TtmlColorParser { - - private static final String RGB = "rgb"; - private static final String RGBA = "rgba"; - - private static final Pattern RGB_PATTERN = Pattern.compile( - "^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); - - private static final Pattern RGBA_PATTERN = Pattern.compile( - "^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); - - static final int TRANSPARENT = 0x00000000; - static final int BLACK = 0xFF000000; - static final int SILVER = 0xFFC0C0C0; - static final int GRAY = 0xFF808080; - static final int WHITE = 0xFFFFFFFF; - static final int MAROON = 0xFF800000; - static final int RED = 0xFFFF0000; - static final int PURPLE = 0xFF800080; - static final int FUCHSIA = 0xFFFF00FF; - static final int MAGENTA = FUCHSIA; - static final int GREEN = 0xFF008000; - static final int LIME = 0xFF00FF00; - static final int OLIVE = 0xFF808000; - static final int YELLOW = 0xFFFFFF00; - static final int NAVY = 0xFF000080; - static final int BLUE = 0xFF0000FF; - static final int TEAL = 0xFF008080; - static final int AQUA = 0x00FFFFFF; - static final int CYAN = 0xFF00FFFF; - - private static final Map COLOR_NAME_MAP; - static { - COLOR_NAME_MAP = new HashMap<>(); - COLOR_NAME_MAP.put("transparent", TRANSPARENT); - COLOR_NAME_MAP.put("black", BLACK); - COLOR_NAME_MAP.put("silver", SILVER); - COLOR_NAME_MAP.put("gray", GRAY); - COLOR_NAME_MAP.put("white", WHITE); - COLOR_NAME_MAP.put("maroon", MAROON); - COLOR_NAME_MAP.put("red", RED); - COLOR_NAME_MAP.put("purple", PURPLE); - COLOR_NAME_MAP.put("fuchsia", FUCHSIA); - COLOR_NAME_MAP.put("magenta", MAGENTA); - COLOR_NAME_MAP.put("green", GREEN); - COLOR_NAME_MAP.put("lime", LIME); - COLOR_NAME_MAP.put("olive", OLIVE); - COLOR_NAME_MAP.put("yellow", YELLOW); - COLOR_NAME_MAP.put("navy", NAVY); - COLOR_NAME_MAP.put("blue", BLUE); - COLOR_NAME_MAP.put("teal", TEAL); - COLOR_NAME_MAP.put("aqua", AQUA); - COLOR_NAME_MAP.put("cyan", CYAN); - } - - public static int parseColor(String colorExpression) { - Assertions.checkArgument(!TextUtils.isEmpty(colorExpression)); - colorExpression = colorExpression.replace(" ", ""); - if (colorExpression.charAt(0) == '#') { - // Parse using Long to avoid failure when colorExpression is greater than #7FFFFFFF. - int color = (int) Long.parseLong(colorExpression.substring(1), 16); - if (colorExpression.length() == 7) { - // Set the alpha value - color |= 0xFF000000; - } else if (colorExpression.length() == 9) { - // We have #RRGGBBAA, but we need #AARRGGBB - color = ((color & 0xFF) << 24) | (color >>> 8); - } else { - throw new IllegalArgumentException(); - } - return color; - } else if (colorExpression.startsWith(RGBA)) { - Matcher matcher = RGBA_PATTERN.matcher(colorExpression); - if (matcher.matches()) { - return argb( - 255 - Integer.parseInt(matcher.group(4), 10), - Integer.parseInt(matcher.group(1), 10), - Integer.parseInt(matcher.group(2), 10), - Integer.parseInt(matcher.group(3), 10) - ); - } - } else if (colorExpression.startsWith(RGB)) { - Matcher matcher = RGB_PATTERN.matcher(colorExpression); - if (matcher.matches()) { - return rgb( - Integer.parseInt(matcher.group(1), 10), - Integer.parseInt(matcher.group(2), 10), - Integer.parseInt(matcher.group(3), 10) - ); - } - } else { - // we use our own color map - Integer color = COLOR_NAME_MAP.get(Util.toLowerInvariant(colorExpression)); - if (color != null) { - return color; - } - } - throw new IllegalArgumentException(); - } - - private static int argb(int alpha, int red, int green, int blue) { - return (alpha << 24) | (red << 16) | (green << 8) | blue; - } - - private static int rgb(int red, int green, int blue) { - return argb(0xFF, red, green, blue); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlParser.java deleted file mode 100755 index 97abf5ec624..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlParser.java +++ /dev/null @@ -1,536 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.ttml; - -import android.text.Layout; -import android.util.Log; -import android.util.Pair; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.SubtitleParser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParserUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; - -/** - * A simple TTML parser that supports DFXP presentation profile. - *

        - * Supported features in this parser are: - *

          - *
        • content - *
        • core - *
        • presentation - *
        • profile - *
        • structure - *
        • time-offset - *
        • timing - *
        • tickRate - *
        • time-clock-with-frames - *
        • time-clock - *
        • time-offset-with-frames - *
        • time-offset-with-ticks - *
        - * @see TTML specification - */ -public final class TtmlParser implements SubtitleParser { - - private static final String TAG = "TtmlParser"; - - private static final String TTP = "http://www.w3.org/ns/ttml#parameter"; - - private static final String ATTR_BEGIN = "begin"; - private static final String ATTR_DURATION = "dur"; - private static final String ATTR_END = "end"; - private static final String ATTR_STYLE = "style"; - private static final String ATTR_REGION = "region"; - - private static final Pattern CLOCK_TIME = - Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" - + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$"); - private static final Pattern OFFSET_TIME = - Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$"); - private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); - private static final Pattern PERCENTAGE_COORDINATES = - Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); - - private static final int DEFAULT_FRAME_RATE = 30; - - private static final FrameAndTickRate DEFAULT_FRAME_AND_TICK_RATE = - new FrameAndTickRate(DEFAULT_FRAME_RATE, 1, 1); - - private final XmlPullParserFactory xmlParserFactory; - - public TtmlParser() { - try { - xmlParserFactory = XmlPullParserFactory.newInstance(); - xmlParserFactory.setNamespaceAware(true); - } catch (XmlPullParserException e) { - throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); - } - } - - @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_TTML.equals(mimeType); - } - - @Override - public TtmlSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { - try { - XmlPullParser xmlParser = xmlParserFactory.newPullParser(); - Map globalStyles = new HashMap<>(); - Map regionMap = new HashMap<>(); - regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion()); - ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, offset, length); - xmlParser.setInput(inputStream, null); - TtmlSubtitle ttmlSubtitle = null; - LinkedList nodeStack = new LinkedList<>(); - int unsupportedNodeDepth = 0; - int eventType = xmlParser.getEventType(); - FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; - while (eventType != XmlPullParser.END_DOCUMENT) { - TtmlNode parent = nodeStack.peekLast(); - if (unsupportedNodeDepth == 0) { - String name = xmlParser.getName(); - if (eventType == XmlPullParser.START_TAG) { - if (TtmlNode.TAG_TT.equals(name)) { - frameAndTickRate = parseFrameAndTickRates(xmlParser); - } - if (!isSupportedTag(name)) { - Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); - unsupportedNodeDepth++; - } else if (TtmlNode.TAG_HEAD.equals(name)) { - parseHeader(xmlParser, globalStyles, regionMap); - } else { - try { - TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); - nodeStack.addLast(node); - if (parent != null) { - parent.addChild(node); - } - } catch (ParserException e) { - Log.w(TAG, "Suppressing parser error", e); - // Treat the node (and by extension, all of its children) as unsupported. - unsupportedNodeDepth++; - } - } - } else if (eventType == XmlPullParser.TEXT) { - parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); - } else if (eventType == XmlPullParser.END_TAG) { - if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { - ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap); - } - nodeStack.removeLast(); - } - } else { - if (eventType == XmlPullParser.START_TAG) { - unsupportedNodeDepth++; - } else if (eventType == XmlPullParser.END_TAG) { - unsupportedNodeDepth--; - } - } - xmlParser.next(); - eventType = xmlParser.getEventType(); - } - return ttmlSubtitle; - } catch (XmlPullParserException xppe) { - throw new ParserException("Unable to parse source", xppe); - } catch (IOException e) { - throw new IllegalStateException("Unexpected error when reading input.", e); - } - } - - private FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser) throws ParserException { - int frameRate = DEFAULT_FRAME_RATE; - String frameRateStr = xmlParser.getAttributeValue(TTP, "frameRate"); - if (frameRateStr != null) { - frameRate = Integer.parseInt(frameRateStr); - } - - float frameRateMultiplier = 1; - String frameRateMultiplierStr = xmlParser.getAttributeValue(TTP, "frameRateMultiplier"); - if (frameRateMultiplierStr != null) { - String[] parts = frameRateMultiplierStr.split(" "); - if (parts.length != 2) { - throw new ParserException("frameRateMultiplier doesn't have 2 parts"); - } - float numerator = Integer.parseInt(parts[0]); - float denominator = Integer.parseInt(parts[1]); - frameRateMultiplier = numerator / denominator; - } - - int subFrameRate = DEFAULT_FRAME_AND_TICK_RATE.subFrameRate; - String subFrameRateStr = xmlParser.getAttributeValue(TTP, "subFrameRate"); - if (subFrameRateStr != null) { - subFrameRate = Integer.parseInt(subFrameRateStr); - } - - int tickRate = DEFAULT_FRAME_AND_TICK_RATE.tickRate; - String tickRateStr = xmlParser.getAttributeValue(TTP, "tickRate"); - if (tickRateStr != null) { - tickRate = Integer.parseInt(tickRateStr); - } - return new FrameAndTickRate(frameRate * frameRateMultiplier, subFrameRate, tickRate); - } - - private Map parseHeader(XmlPullParser xmlParser, - Map globalStyles, Map globalRegions) - throws IOException, XmlPullParserException { - do { - xmlParser.next(); - if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) { - String parentStyleId = ParserUtil.getAttributeValue(xmlParser, ATTR_STYLE); - TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle()); - if (parentStyleId != null) { - String[] ids = parseStyleIds(parentStyleId); - for (int i = 0; i < ids.length; i++) { - style.chain(globalStyles.get(ids[i])); - } - } - if (style.getId() != null) { - globalStyles.put(style.getId(), style); - } - } else if (ParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { - Pair ttmlRegionInfo = parseRegionAttributes(xmlParser); - if (ttmlRegionInfo != null) { - globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second); - } - } - } while (!ParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); - return globalStyles; - } - - /** - * Parses a region declaration. Supports origin and extent definition but only when defined in - * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. - */ - private Pair parseRegionAttributes(XmlPullParser xmlParser) { - String regionId = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); - String regionOrigin = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); - String regionExtent = ParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); - if (regionOrigin == null || regionId == null) { - return null; - } - float position = Cue.DIMEN_UNSET; - float line = Cue.DIMEN_UNSET; - Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); - if (originMatcher.matches()) { - try { - position = Float.parseFloat(originMatcher.group(1)) / 100.f; - line = Float.parseFloat(originMatcher.group(2)) / 100.f; - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e); - position = Cue.DIMEN_UNSET; - } - } - float width = Cue.DIMEN_UNSET; - if (regionExtent != null) { - Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); - if (extentMatcher.matches()) { - try { - width = Float.parseFloat(extentMatcher.group(1)) / 100.f; - } catch (NumberFormatException e) { - Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e); - } - } - } - return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, - Cue.LINE_TYPE_FRACTION, width)) : null; - } - - private String[] parseStyleIds(String parentStyleIds) { - return parentStyleIds.split("\\s+"); - } - - private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) { - int attributeCount = parser.getAttributeCount(); - for (int i = 0; i < attributeCount; i++) { - String attributeValue = parser.getAttributeValue(i); - switch (parser.getAttributeName(i)) { - case TtmlNode.ATTR_ID: - if (TtmlNode.TAG_STYLE.equals(parser.getName())) { - style = createIfNull(style).setId(attributeValue); - } - break; - case TtmlNode.ATTR_TTS_BACKGROUND_COLOR: - style = createIfNull(style); - try { - style.setBackgroundColor(TtmlColorParser.parseColor(attributeValue)); - } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); - } - break; - case TtmlNode.ATTR_TTS_COLOR: - style = createIfNull(style); - try { - style.setFontColor(TtmlColorParser.parseColor(attributeValue)); - } catch (IllegalArgumentException e) { - Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); - } - break; - case TtmlNode.ATTR_TTS_FONT_FAMILY: - style = createIfNull(style).setFontFamily(attributeValue); - break; - case TtmlNode.ATTR_TTS_FONT_SIZE: - try { - style = createIfNull(style); - parseFontSize(attributeValue, style); - } catch (ParserException e) { - Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'"); - } - break; - case TtmlNode.ATTR_TTS_FONT_WEIGHT: - style = createIfNull(style).setBold( - TtmlNode.BOLD.equalsIgnoreCase(attributeValue)); - break; - case TtmlNode.ATTR_TTS_FONT_STYLE: - style = createIfNull(style).setItalic( - TtmlNode.ITALIC.equalsIgnoreCase(attributeValue)); - break; - case TtmlNode.ATTR_TTS_TEXT_ALIGN: - switch (Util.toLowerInvariant(attributeValue)) { - case TtmlNode.LEFT: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); - break; - case TtmlNode.START: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); - break; - case TtmlNode.RIGHT: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); - break; - case TtmlNode.END: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); - break; - case TtmlNode.CENTER: - style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER); - break; - } - break; - case TtmlNode.ATTR_TTS_TEXT_DECORATION: - switch (Util.toLowerInvariant(attributeValue)) { - case TtmlNode.LINETHROUGH: - style = createIfNull(style).setLinethrough(true); - break; - case TtmlNode.NO_LINETHROUGH: - style = createIfNull(style).setLinethrough(false); - break; - case TtmlNode.UNDERLINE: - style = createIfNull(style).setUnderline(true); - break; - case TtmlNode.NO_UNDERLINE: - style = createIfNull(style).setUnderline(false); - break; - } - break; - default: - // ignore - break; - } - } - return style; - } - - private TtmlStyle createIfNull(TtmlStyle style) { - return style == null ? new TtmlStyle() : style; - } - - private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent, - Map regionMap, FrameAndTickRate frameAndTickRate) throws ParserException { - long duration = 0; - long startTime = TtmlNode.UNDEFINED_TIME; - long endTime = TtmlNode.UNDEFINED_TIME; - String regionId = TtmlNode.ANONYMOUS_REGION_ID; - String[] styleIds = null; - int attributeCount = parser.getAttributeCount(); - TtmlStyle style = parseStyleAttributes(parser, null); - for (int i = 0; i < attributeCount; i++) { - String attr = parser.getAttributeName(i); - String value = parser.getAttributeValue(i); - if (ATTR_BEGIN.equals(attr)) { - startTime = parseTimeExpression(value, frameAndTickRate); - } else if (ATTR_END.equals(attr)) { - endTime = parseTimeExpression(value, frameAndTickRate); - } else if (ATTR_DURATION.equals(attr)) { - duration = parseTimeExpression(value, frameAndTickRate); - } else if (ATTR_STYLE.equals(attr)) { - // IDREFS: potentially multiple space delimited ids - String[] ids = parseStyleIds(value); - if (ids.length > 0) { - styleIds = ids; - } - } else if (ATTR_REGION.equals(attr) && regionMap.containsKey(value)) { - // If the region has not been correctly declared or does not define a position, we use the - // anonymous region. - regionId = value; - } else { - // Do nothing. - } - } - if (parent != null && parent.startTimeUs != TtmlNode.UNDEFINED_TIME) { - if (startTime != TtmlNode.UNDEFINED_TIME) { - startTime += parent.startTimeUs; - } - if (endTime != TtmlNode.UNDEFINED_TIME) { - endTime += parent.startTimeUs; - } - } - if (endTime == TtmlNode.UNDEFINED_TIME) { - if (duration > 0) { - // Infer the end time from the duration. - endTime = startTime + duration; - } else if (parent != null && parent.endTimeUs != TtmlNode.UNDEFINED_TIME) { - // If the end time remains unspecified, then it should be inherited from the parent. - endTime = parent.endTimeUs; - } - } - return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); - } - - private static boolean isSupportedTag(String tag) { - if (tag.equals(TtmlNode.TAG_TT) - || tag.equals(TtmlNode.TAG_HEAD) - || tag.equals(TtmlNode.TAG_BODY) - || tag.equals(TtmlNode.TAG_DIV) - || tag.equals(TtmlNode.TAG_P) - || tag.equals(TtmlNode.TAG_SPAN) - || tag.equals(TtmlNode.TAG_BR) - || tag.equals(TtmlNode.TAG_STYLE) - || tag.equals(TtmlNode.TAG_STYLING) - || tag.equals(TtmlNode.TAG_LAYOUT) - || tag.equals(TtmlNode.TAG_REGION) - || tag.equals(TtmlNode.TAG_METADATA) - || tag.equals(TtmlNode.TAG_SMPTE_IMAGE) - || tag.equals(TtmlNode.TAG_SMPTE_DATA) - || tag.equals(TtmlNode.TAG_SMPTE_INFORMATION)) { - return true; - } - return false; - } - - private static void parseFontSize(String expression, TtmlStyle out) throws ParserException { - String[] expressions = expression.split("\\s+"); - Matcher matcher; - if (expressions.length == 1) { - matcher = FONT_SIZE.matcher(expression); - } else if (expressions.length == 2){ - matcher = FONT_SIZE.matcher(expressions[1]); - Log.w(TAG, "Multiple values in fontSize attribute. Picking the second value for vertical font" - + " size and ignoring the first."); - } else { - throw new ParserException("Invalid number of entries for fontSize: " + expressions.length - + "."); - } - - if (matcher.matches()) { - String unit = matcher.group(3); - switch (unit) { - case "px": - out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL); - break; - case "em": - out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_EM); - break; - case "%": - out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PERCENT); - break; - default: - throw new ParserException("Invalid unit for fontSize: '" + unit + "'."); - } - out.setFontSize(Float.valueOf(matcher.group(1))); - } else { - throw new ParserException("Invalid expression for fontSize: '" + expression + "'."); - } - } - - /** - * Parses a time expression, returning the parsed timestamp. - *

        - * For the format of a time expression, see: - * timeExpression - * - * @param time A string that includes the time expression. - * @param frameAndTickRate The effective frame and tick rates of the stream. - * @return The parsed timestamp in microseconds. - * @throws ParserException If the given string does not contain a valid time expression. - */ - private static long parseTimeExpression(String time, FrameAndTickRate frameAndTickRate) - throws ParserException { - Matcher matcher = CLOCK_TIME.matcher(time); - if (matcher.matches()) { - String hours = matcher.group(1); - double durationSeconds = Long.parseLong(hours) * 3600; - String minutes = matcher.group(2); - durationSeconds += Long.parseLong(minutes) * 60; - String seconds = matcher.group(3); - durationSeconds += Long.parseLong(seconds); - String fraction = matcher.group(4); - durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0; - String frames = matcher.group(5); - durationSeconds += (frames != null) - ? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0; - String subframes = matcher.group(6); - durationSeconds += (subframes != null) - ? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate - / frameAndTickRate.effectiveFrameRate - : 0; - return (long) (durationSeconds * C.MICROS_PER_SECOND); - } - matcher = OFFSET_TIME.matcher(time); - if (matcher.matches()) { - String timeValue = matcher.group(1); - double offsetSeconds = Double.parseDouble(timeValue); - String unit = matcher.group(2); - if (unit.equals("h")) { - offsetSeconds *= 3600; - } else if (unit.equals("m")) { - offsetSeconds *= 60; - } else if (unit.equals("s")) { - // Do nothing. - } else if (unit.equals("ms")) { - offsetSeconds /= 1000; - } else if (unit.equals("f")) { - offsetSeconds /= frameAndTickRate.effectiveFrameRate; - } else if (unit.equals("t")) { - offsetSeconds /= frameAndTickRate.tickRate; - } - return (long) (offsetSeconds * C.MICROS_PER_SECOND); - } - throw new ParserException("Malformed time expression: " + time); - } - - private static final class FrameAndTickRate { - final float effectiveFrameRate; - final int subFrameRate; - final int tickRate; - - FrameAndTickRate(float effectiveFrameRate, int subFrameRate, int tickRate) { - this.effectiveFrameRate = effectiveFrameRate; - this.subFrameRate = subFrameRate; - this.tickRate = tickRate; - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gParser.java deleted file mode 100755 index 04d08652400..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gParser.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.tx3g; - -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.text.SubtitleParser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; - -/** - * A {@link SubtitleParser} for tx3g. - *

        - * Currently only supports parsing of a single text track. - */ -public final class Tx3gParser implements SubtitleParser { - - private final ParsableByteArray parsableByteArray; - - public Tx3gParser() { - parsableByteArray = new ParsableByteArray(); - } - - @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_TX3G.equals(mimeType); - } - - @Override - public Subtitle parse(byte[] bytes, int offset, int length) { - parsableByteArray.reset(bytes, length); - int textLength = parsableByteArray.readUnsignedShort(); - if (textLength == 0) { - return Tx3gSubtitle.EMPTY; - } - String cueText = parsableByteArray.readString(textLength); - return new Tx3gSubtitle(new Cue(cueText)); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttParser.java deleted file mode 100755 index 4317755aafa..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttParser.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.webvtt; - -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.SubtitleParser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.ArrayList; -import java.util.List; - -/** - * A {@link SubtitleParser} for Webvtt embedded in a Mp4 container file. - */ -public final class Mp4WebvttParser implements SubtitleParser { - - private static final int BOX_HEADER_SIZE = 8; - - private static final int TYPE_payl = Util.getIntegerCodeForString("payl"); - private static final int TYPE_sttg = Util.getIntegerCodeForString("sttg"); - private static final int TYPE_vttc = Util.getIntegerCodeForString("vttc"); - - private final ParsableByteArray sampleData; - private final WebvttCue.Builder builder; - - public Mp4WebvttParser() { - sampleData = new ParsableByteArray(); - builder = new WebvttCue.Builder(); - } - - @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_MP4VTT.equals(mimeType); - } - - @Override - public Mp4WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { - // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: - // first 4 bytes size and then 4 bytes type. - sampleData.reset(bytes, offset + length); - sampleData.setPosition(offset); - List resultingCueList = new ArrayList<>(); - while (sampleData.bytesLeft() > 0) { - if (sampleData.bytesLeft() < BOX_HEADER_SIZE) { - throw new ParserException("Incomplete Mp4Webvtt Top Level box header found."); - } - int boxSize = sampleData.readInt(); - int boxType = sampleData.readInt(); - if (boxType == TYPE_vttc) { - resultingCueList.add(parseVttCueBox(sampleData, builder, boxSize - BOX_HEADER_SIZE)); - } else { - // Peers of the VTTCueBox are still not supported and are skipped. - sampleData.skipBytes(boxSize - BOX_HEADER_SIZE); - } - } - return new Mp4WebvttSubtitle(resultingCueList); - } - - private static Cue parseVttCueBox(ParsableByteArray sampleData, WebvttCue.Builder builder, - int remainingCueBoxBytes) throws ParserException { - builder.reset(); - while (remainingCueBoxBytes > 0) { - if (remainingCueBoxBytes < BOX_HEADER_SIZE) { - throw new ParserException("Incomplete vtt cue box header found."); - } - int boxSize = sampleData.readInt(); - int boxType = sampleData.readInt(); - remainingCueBoxBytes -= BOX_HEADER_SIZE; - int payloadLength = boxSize - BOX_HEADER_SIZE; - String boxPayload = new String(sampleData.data, sampleData.getPosition(), payloadLength); - sampleData.skipBytes(payloadLength); - remainingCueBoxBytes -= payloadLength; - if (boxType == TYPE_sttg) { - WebvttCueParser.parseCueSettingsList(boxPayload, builder); - } else if (boxType == TYPE_payl) { - WebvttCueParser.parseCueText(boxPayload.trim(), builder); - } else { - // Other VTTCueBox children are still not supported and are ignored. - } - } - return builder.build(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCueParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCueParser.java deleted file mode 100755 index 47ee981c617..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCueParser.java +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.webvtt; - -import android.graphics.Typeface; -import android.text.Layout.Alignment; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.style.StyleSpan; -import android.text.style.UnderlineSpan; -import android.util.Log; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.Stack; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Parser for WebVTT cues. (https://w3c.github.io/webvtt/#cues) - */ -public final class WebvttCueParser { - - public static final Pattern CUE_HEADER_PATTERN = Pattern - .compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); - - private static final Pattern COMMENT = Pattern.compile("^NOTE((\u0020|\u0009).*)?$"); - private static final Pattern CUE_SETTING_PATTERN = Pattern.compile("(\\S+?):(\\S+)"); - - private static final char CHAR_LESS_THAN = '<'; - private static final char CHAR_GREATER_THAN = '>'; - private static final char CHAR_SLASH = '/'; - private static final char CHAR_AMPERSAND = '&'; - private static final char CHAR_SEMI_COLON = ';'; - private static final char CHAR_SPACE = ' '; - private static final String SPACE = " "; - - private static final String ENTITY_LESS_THAN = "lt"; - private static final String ENTITY_GREATER_THAN = "gt"; - private static final String ENTITY_AMPERSAND = "amp"; - private static final String ENTITY_NON_BREAK_SPACE = "nbsp"; - - private static final String TAG_BOLD = "b"; - private static final String TAG_ITALIC = "i"; - private static final String TAG_UNDERLINE = "u"; - private static final String TAG_CLASS = "c"; - private static final String TAG_VOICE = "v"; - private static final String TAG_LANG = "lang"; - - private static final int STYLE_BOLD = Typeface.BOLD; - private static final int STYLE_ITALIC = Typeface.ITALIC; - - private static final String TAG = "WebvttCueParser"; - - private final StringBuilder textBuilder; - - public WebvttCueParser() { - textBuilder = new StringBuilder(); - } - - /** - * Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text. - * - * @param webvttData Parsable WebVTT file data. - * @param builder Builder for WebVTT Cues. - * @return True if a valid Cue was found, false otherwise. - */ - /* package */ boolean parseNextValidCue(ParsableByteArray webvttData, WebvttCue.Builder builder) { - Matcher cueHeaderMatcher; - while ((cueHeaderMatcher = findNextCueHeader(webvttData)) != null) { - if (parseCue(cueHeaderMatcher, webvttData, builder, textBuilder)) { - return true; - } - } - return false; - } - - /** - * Parses a string containing a list of cue settings. - * - * @param cueSettingsList String containing the settings for a given cue. - * @param builder The {@link WebvttCue.Builder} where incremental construction takes place. - */ - /* package */ static void parseCueSettingsList(String cueSettingsList, - WebvttCue.Builder builder) { - // Parse the cue settings list. - Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList); - while (cueSettingMatcher.find()) { - String name = cueSettingMatcher.group(1); - String value = cueSettingMatcher.group(2); - try { - if ("line".equals(name)) { - parseLineAttribute(value, builder); - } else if ("align".equals(name)) { - builder.setTextAlignment(parseTextAlignment(value)); - } else if ("position".equals(name)) { - parsePositionAttribute(value, builder); - } else if ("size".equals(name)) { - builder.setWidth(WebvttParserUtil.parsePercentage(value)); - } else { - Log.w(TAG, "Unknown cue setting " + name + ":" + value); - } - } catch (NumberFormatException e) { - Log.w(TAG, "Skipping bad cue setting: " + cueSettingMatcher.group()); - } - } - } - - /** - * Reads lines up to and including the next WebVTT cue header. - * - * @param input The input from which lines should be read. - * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was - * reached without a cue header being found. In the case that a cue header is found, groups 1, - * 2 and 3 of the returned matcher contain the start time, end time and settings list. - */ - public static Matcher findNextCueHeader(ParsableByteArray input) { - String line; - while ((line = input.readLine()) != null) { - if (COMMENT.matcher(line).matches()) { - // Skip until the end of the comment block. - while ((line = input.readLine()) != null && !line.isEmpty()) {} - } else { - Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line); - if (cueHeaderMatcher.matches()) { - return cueHeaderMatcher; - } - } - } - return null; - } - - /** - * Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}. - * - * @param markup The markup text to be parsed. - * @param builder Target builder. - */ - /* package */ static void parseCueText(String markup, WebvttCue.Builder builder) { - SpannableStringBuilder spannedText = new SpannableStringBuilder(); - Stack startTagStack = new Stack<>(); - String[] tagTokens; - int pos = 0; - while (pos < markup.length()) { - char curr = markup.charAt(pos); - switch (curr) { - case CHAR_LESS_THAN: - if (pos + 1 >= markup.length()) { - pos++; - break; // avoid ArrayOutOfBoundsException - } - int ltPos = pos; - boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH; - pos = findEndOfTag(markup, ltPos + 1); - boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH; - - tagTokens = tokenizeTag(markup.substring( - ltPos + (isClosingTag ? 2 : 1), isVoidTag ? pos - 2 : pos - 1)); - if (tagTokens == null || !isSupportedTag(tagTokens[0])) { - continue; - } - if (isClosingTag) { - StartTag startTag; - do { - if (startTagStack.isEmpty()) { - break; - } - startTag = startTagStack.pop(); - applySpansForTag(startTag, spannedText); - } while(!startTag.name.equals(tagTokens[0])); - } else if (!isVoidTag) { - startTagStack.push(new StartTag(tagTokens[0], spannedText.length())); - } - break; - case CHAR_AMPERSAND: - int semiColonEnd = markup.indexOf(CHAR_SEMI_COLON, pos + 1); - int spaceEnd = markup.indexOf(CHAR_SPACE, pos + 1); - int entityEnd = semiColonEnd == -1 ? spaceEnd - : spaceEnd == -1 ? semiColonEnd : Math.min(semiColonEnd, spaceEnd); - if (entityEnd != -1) { - applyEntity(markup.substring(pos + 1, entityEnd), spannedText); - if (entityEnd == spaceEnd) { - spannedText.append(" "); - } - pos = entityEnd + 1; - } else { - spannedText.append(curr); - pos++; - } - break; - default: - spannedText.append(curr); - pos++; - break; - } - } - // apply unclosed tags - while (!startTagStack.isEmpty()) { - applySpansForTag(startTagStack.pop(), spannedText); - } - builder.setText(spannedText); - } - - private static boolean parseCue(Matcher cueHeaderMatcher, ParsableByteArray webvttData, - WebvttCue.Builder builder, StringBuilder textBuilder) { - try { - // Parse the cue start and end times. - builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1))) - .setEndTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2))); - } catch (NumberFormatException e) { - Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group()); - return false; - } - - parseCueSettingsList(cueHeaderMatcher.group(3), builder); - - // Parse the cue text. - textBuilder.setLength(0); - String line; - while ((line = webvttData.readLine()) != null && !line.isEmpty()) { - if (textBuilder.length() > 0) { - textBuilder.append("\n"); - } - textBuilder.append(line.trim()); - } - parseCueText(textBuilder.toString(), builder); - return true; - } - - // Internal methods - - private static void parseLineAttribute(String s, WebvttCue.Builder builder) - throws NumberFormatException { - int commaPosition = s.indexOf(','); - if (commaPosition != -1) { - builder.setLineAnchor(parsePositionAnchor(s.substring(commaPosition + 1))); - s = s.substring(0, commaPosition); - } else { - builder.setLineAnchor(Cue.TYPE_UNSET); - } - if (s.endsWith("%")) { - builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION); - } else { - builder.setLine(Integer.parseInt(s)).setLineType(Cue.LINE_TYPE_NUMBER); - } - } - - private static void parsePositionAttribute(String s, WebvttCue.Builder builder) - throws NumberFormatException { - int commaPosition = s.indexOf(','); - if (commaPosition != -1) { - builder.setPositionAnchor(parsePositionAnchor(s.substring(commaPosition + 1))); - s = s.substring(0, commaPosition); - } else { - builder.setPositionAnchor(Cue.TYPE_UNSET); - } - builder.setPosition(WebvttParserUtil.parsePercentage(s)); - } - - private static int parsePositionAnchor(String s) { - switch (s) { - case "start": - return Cue.ANCHOR_TYPE_START; - case "center": - case "middle": - return Cue.ANCHOR_TYPE_MIDDLE; - case "end": - return Cue.ANCHOR_TYPE_END; - default: - Log.w(TAG, "Invalid anchor value: " + s); - return Cue.TYPE_UNSET; - } - } - - private static Alignment parseTextAlignment(String s) { - switch (s) { - case "start": - case "left": - return Alignment.ALIGN_NORMAL; - case "center": - case "middle": - return Alignment.ALIGN_CENTER; - case "end": - case "right": - return Alignment.ALIGN_OPPOSITE; - default: - Log.w(TAG, "Invalid alignment value: " + s); - return null; - } - } - - /** - * Find end of tag (>). The position returned is the position of the > plus one (exclusive). - * - * @param markup The WebVTT cue markup to be parsed. - * @param startPos the position from where to start searching for the end of tag. - * @return the position of the end of tag plus 1 (one). - */ - private static int findEndOfTag(String markup, int startPos) { - int idx = markup.indexOf(CHAR_GREATER_THAN, startPos); - return idx == -1 ? markup.length() : idx + 1; - } - - private static void applyEntity(String entity, SpannableStringBuilder spannedText) { - switch (entity) { - case ENTITY_LESS_THAN: - spannedText.append('<'); - break; - case ENTITY_GREATER_THAN: - spannedText.append('>'); - break; - case ENTITY_NON_BREAK_SPACE: - spannedText.append(' '); - break; - case ENTITY_AMPERSAND: - spannedText.append('&'); - break; - default: - Log.w(TAG, "ignoring unsupported entity: '&" + entity + ";'"); - break; - } - } - - private static boolean isSupportedTag(String tagName) { - switch (tagName) { - case TAG_BOLD: - case TAG_CLASS: - case TAG_ITALIC: - case TAG_LANG: - case TAG_UNDERLINE: - case TAG_VOICE: - return true; - default: - return false; - } - } - - private static void applySpansForTag(StartTag startTag, SpannableStringBuilder spannedText) { - switch(startTag.name) { - case TAG_BOLD: - spannedText.setSpan(new StyleSpan(STYLE_BOLD), startTag.position, - spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - return; - case TAG_ITALIC: - spannedText.setSpan(new StyleSpan(STYLE_ITALIC), startTag.position, - spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - return; - case TAG_UNDERLINE: - spannedText.setSpan(new UnderlineSpan(), startTag.position, - spannedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - return; - default: - break; - } - } - - /** - * Tokenizes a tag expression into tag name (pos 0) and classes (pos 1..n). - * - * @param fullTagExpression characters between &lt: and &gt; of a start or end tag - * @return an array of Strings with the tag name at pos 0 followed by style classes - * or null if it's an empty tag: '<>' - */ - private static String[] tokenizeTag(String fullTagExpression) { - fullTagExpression = fullTagExpression.replace("\\s+", " ").trim(); - if (fullTagExpression.length() == 0) { - return null; - } - if (fullTagExpression.contains(SPACE)) { - fullTagExpression = fullTagExpression.substring(0, fullTagExpression.indexOf(SPACE)); - } - return fullTagExpression.split("\\."); - } - - private static final class StartTag { - - public final String name; - public final int position; - - public StartTag(String name, int position) { - this.position = position; - this.name = name; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParser.java deleted file mode 100755 index 154959e0643..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParser.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.webvtt; - -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.text.SubtitleParser; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.ArrayList; - -/** - * A simple WebVTT parser. - *

        - * @see WebVTT specification - */ -public final class WebvttParser implements SubtitleParser { - - private final WebvttCueParser cueParser; - private final ParsableByteArray parsableWebvttData; - private final WebvttCue.Builder webvttCueBuilder; - - public WebvttParser() { - cueParser = new WebvttCueParser(); - parsableWebvttData = new ParsableByteArray(); - webvttCueBuilder = new WebvttCue.Builder(); - } - - @Override - public final boolean canParse(String mimeType) { - return MimeTypes.TEXT_VTT.equals(mimeType); - } - - @Override - public final WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { - parsableWebvttData.reset(bytes, offset + length); - parsableWebvttData.setPosition(offset); - webvttCueBuilder.reset(); // In case a previous parse run failed with a ParserException. - - // Validate the first line of the header, and skip the remainder. - WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData); - while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} - - // Extract Cues - ArrayList subtitles = new ArrayList<>(); - while (cueParser.parseNextValidCue(parsableWebvttData, webvttCueBuilder)) { - subtitles.add(webvttCueBuilder.build()); - webvttCueBuilder.reset(); - } - return new WebvttSubtitle(subtitles); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParserUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParserUtil.java deleted file mode 100755 index ed00d0c8f4c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttParserUtil.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.text.webvtt; - -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import java.util.regex.Pattern; - -/** - * Utility methods for parsing WebVTT data. - */ -public final class WebvttParserUtil { - - private static final Pattern HEADER = Pattern.compile("^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"); - - private WebvttParserUtil() {} - - /** - * Reads and validates the first line of a WebVTT file. - * - * @param input The input from which the line should be read. - * @throws ParserException If the line isn't the start of a valid WebVTT file. - */ - public static void validateWebvttHeaderLine(ParsableByteArray input) throws ParserException { - String line = input.readLine(); - if (line == null || !HEADER.matcher(line).matches()) { - throw new ParserException("Expected WEBVTT. Got " + line); - } - } - - /** - * Parses a WebVTT timestamp. - * - * @param timestamp The timestamp string. - * @return The parsed timestamp in microseconds. - * @throws NumberFormatException If the timestamp could not be parsed. - */ - public static long parseTimestampUs(String timestamp) throws NumberFormatException { - long value = 0; - String[] parts = timestamp.split("\\.", 2); - String[] subparts = parts[0].split(":"); - for (int i = 0; i < subparts.length; i++) { - value = value * 60 + Long.parseLong(subparts[i]); - } - return (value * 1000 + Long.parseLong(parts[1])) * 1000; - } - - /** - * Parses a percentage and returns a scaled float. - * @param s contains the number to parse. - * @return a float scaled number. 1.0 represents 100%. - * @throws NumberFormatException if the number format is invalid or does not end with '%'. - */ - public static float parsePercentage(String s) throws NumberFormatException { - if (!s.endsWith("%")) { - throw new NumberFormatException("Percentages must end with %"); - } - return Float.parseFloat(s.substring(0, s.length() - 1)) / 100; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocator.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocator.java deleted file mode 100755 index 85b7a0dc44f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocator.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -/** - * A source of allocations. - */ -public interface Allocator { - - /** - * Obtain an {@link Allocation}. - *

        - * When the caller has finished with the {@link Allocation}, it should be returned by calling - * {@link #release(Allocation)}. - * - * @return The {@link Allocation}. - */ - Allocation allocate(); - - /** - * Return an {@link Allocation}. - * - * @param allocation The {@link Allocation} being returned. - */ - void release(Allocation allocation); - - /** - * Return an array of {@link Allocation}s. - * - * @param allocations The array of {@link Allocation}s being returned. - */ - void release(Allocation[] allocations); - - /** - * Hints to the {@link Allocator} that it should make a best effort to release any memory that it - * has allocated, beyond the specified target number of bytes. - * - * @param targetSize The target size in bytes. - */ - void trim(int targetSize); - - /** - * Blocks execution until the number of bytes allocated is not greater than the limit, or the - * thread is interrupted. - * - * @param limit The limit in bytes. - * @throws InterruptedException If the thread is interrupted. - */ - void blockWhileTotalBytesAllocatedExceeds(int limit) throws InterruptedException; - - /** - * Returns the total number of bytes currently allocated. - */ - int getTotalBytesAllocated(); - - /** - * Returns the length of each individual {@link Allocation}. - */ - int getIndividualAllocationLength(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/AssetDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/AssetDataSource.java deleted file mode 100755 index 76631ac6493..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/AssetDataSource.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.content.Context; -import android.content.res.AssetManager; -import org.telegram.messenger.exoplayer.C; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; - -/** - * A local asset {@link UriDataSource}. - */ -public final class AssetDataSource implements UriDataSource { - - /** - * Thrown when an {@link IOException} is encountered reading a local asset. - */ - public static final class AssetDataSourceException extends IOException { - - public AssetDataSourceException(IOException cause) { - super(cause); - } - - } - - private final AssetManager assetManager; - private final TransferListener listener; - - private String uriString; - private InputStream inputStream; - private long bytesRemaining; - private boolean opened; - - /** - * Constructs a new {@link DataSource} that retrieves data from a local asset. - */ - public AssetDataSource(Context context) { - this(context, null); - } - - /** - * Constructs a new {@link DataSource} that retrieves data from a local asset. - * - * @param listener An optional listener. Specify {@code null} for no listener. - */ - public AssetDataSource(Context context, TransferListener listener) { - this.assetManager = context.getAssets(); - this.listener = listener; - } - - @Override - public long open(DataSpec dataSpec) throws AssetDataSourceException { - try { - uriString = dataSpec.uri.toString(); - String path = dataSpec.uri.getPath(); - if (path.startsWith("/android_asset/")) { - path = path.substring(15); - } else if (path.startsWith("/")) { - path = path.substring(1); - } - uriString = dataSpec.uri.toString(); - inputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); - long skipped = inputStream.skip(dataSpec.position); - if (skipped < dataSpec.position) { - // assetManager.open() returns an AssetInputStream, whose skip() implementation only skips - // fewer bytes than requested if the skip is beyond the end of the asset's data. - throw new EOFException(); - } - if (dataSpec.length != C.LENGTH_UNBOUNDED) { - bytesRemaining = dataSpec.length; - } else { - bytesRemaining = inputStream.available(); - if (bytesRemaining == Integer.MAX_VALUE) { - // assetManager.open() returns an AssetInputStream, whose available() implementation - // returns Integer.MAX_VALUE if the remaining length is greater than (or equal to) - // Integer.MAX_VALUE. We don't know the true length in this case, so treat as unbounded. - bytesRemaining = C.LENGTH_UNBOUNDED; - } - } - } catch (IOException e) { - throw new AssetDataSourceException(e); - } - - opened = true; - if (listener != null) { - listener.onTransferStart(); - } - return bytesRemaining; - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException { - if (bytesRemaining == 0) { - return -1; - } else { - int bytesRead = 0; - try { - int bytesToRead = bytesRemaining == C.LENGTH_UNBOUNDED ? readLength - : (int) Math.min(bytesRemaining, readLength); - bytesRead = inputStream.read(buffer, offset, bytesToRead); - } catch (IOException e) { - throw new AssetDataSourceException(e); - } - - if (bytesRead > 0) { - if (bytesRemaining != C.LENGTH_UNBOUNDED) { - bytesRemaining -= bytesRead; - } - if (listener != null) { - listener.onBytesTransferred(bytesRead); - } - } - - return bytesRead; - } - } - - @Override - public String getUri() { - return uriString; - } - - @Override - public void close() throws AssetDataSourceException { - uriString = null; - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - throw new AssetDataSourceException(e); - } finally { - inputStream = null; - if (opened) { - opened = false; - if (listener != null) { - listener.onTransferEnd(); - } - } - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/BandwidthMeter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/BandwidthMeter.java deleted file mode 100755 index 81d0b817e6d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/BandwidthMeter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -/** - * Provides estimates of the currently available bandwidth. - */ -public interface BandwidthMeter extends TransferListener { - - /** - * Interface definition for a callback to be notified of {@link BandwidthMeter} events. - */ - public interface EventListener { - - /** - * Invoked periodically to indicate that bytes have been transferred. - * - * @param elapsedMs The time taken to transfer the bytes, in milliseconds. - * @param bytes The number of bytes transferred. - * @param bitrate The estimated bitrate in bits/sec, or {@link #NO_ESTIMATE} if no estimate - * is available. Note that this estimate is typically derived from more information than - * {@code bytes} and {@code elapsedMs}. - */ - void onBandwidthSample(int elapsedMs, long bytes, long bitrate); - } - - /** - * Indicates no bandwidth estimate is available. - */ - final long NO_ESTIMATE = -1; - - /** - * Gets the estimated bandwidth, in bits/sec. - * - * @return Estimated bandwidth in bits/sec, or {@link #NO_ESTIMATE} if no estimate is available. - */ - long getBitrateEstimate(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSource.java deleted file mode 100755 index 7d4ac968d86..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSource.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; - -/** - * A {@link DataSource} for reading from a byte array. - */ -public final class ByteArrayDataSource implements DataSource { - - private final byte[] data; - private int readPosition; - private int remainingBytes; - - /** - * @param data The data to be read. - */ - public ByteArrayDataSource(byte[] data) { - Assertions.checkNotNull(data); - Assertions.checkArgument(data.length > 0); - this.data = data; - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - readPosition = (int) dataSpec.position; - remainingBytes = (int) ((dataSpec.length == C.LENGTH_UNBOUNDED) - ? (data.length - dataSpec.position) : dataSpec.length); - if (remainingBytes <= 0 || readPosition + remainingBytes > data.length) { - throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length - + "], length: " + data.length); - } - return remainingBytes; - } - - @Override - public void close() throws IOException { - // Do nothing. - } - - @Override - public int read(byte[] buffer, int offset, int length) throws IOException { - if (remainingBytes == 0) { - return -1; - } - length = Math.min(length, remainingBytes); - System.arraycopy(data, readPosition, buffer, offset, length); - readPosition += length; - remainingBytes -= length; - return length; - } -} - diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ContentDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ContentDataSource.java deleted file mode 100755 index 33e31c08a85..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ContentDataSource.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.AssetFileDescriptor; -import org.telegram.messenger.exoplayer.C; -import java.io.EOFException; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * A content URI {@link UriDataSource}. - */ -public final class ContentDataSource implements UriDataSource { - - /** - * Thrown when an {@link IOException} is encountered reading from a content URI. - */ - public static class ContentDataSourceException extends IOException { - - public ContentDataSourceException(IOException cause) { - super(cause); - } - - } - - private final ContentResolver resolver; - private final TransferListener listener; - - private InputStream inputStream; - private String uriString; - private long bytesRemaining; - private boolean opened; - - /** - * Constructs a new {@link DataSource} that retrieves data from a content provider. - */ - public ContentDataSource(Context context) { - this(context, null); - } - - /** - * Constructs a new {@link DataSource} that retrieves data from a content provider. - * - * @param listener An optional listener. Specify {@code null} for no listener. - */ - public ContentDataSource(Context context, TransferListener listener) { - this.resolver = context.getContentResolver(); - this.listener = listener; - } - - @Override - public long open(DataSpec dataSpec) throws ContentDataSourceException { - try { - uriString = dataSpec.uri.toString(); - AssetFileDescriptor assetFd = resolver.openAssetFileDescriptor(dataSpec.uri, "r"); - inputStream = new FileInputStream(assetFd.getFileDescriptor()); - long skipped = inputStream.skip(dataSpec.position); - if (skipped < dataSpec.position) { - // We expect the skip to be satisfied in full. If it isn't then we're probably trying to - // skip beyond the end of the data. - throw new EOFException(); - } - if (dataSpec.length != C.LENGTH_UNBOUNDED) { - bytesRemaining = dataSpec.length; - } else { - bytesRemaining = inputStream.available(); - if (bytesRemaining == 0) { - // FileInputStream.available() returns 0 if the remaining length cannot be determined, or - // if it's greater than Integer.MAX_VALUE. We don't know the true length in either case, - // so treat as unbounded. - bytesRemaining = C.LENGTH_UNBOUNDED; - } - } - } catch (IOException e) { - throw new ContentDataSourceException(e); - } - - opened = true; - if (listener != null) { - listener.onTransferStart(); - } - - return bytesRemaining; - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException { - if (bytesRemaining == 0) { - return -1; - } else { - int bytesRead = 0; - try { - int bytesToRead = bytesRemaining == C.LENGTH_UNBOUNDED ? readLength - : (int) Math.min(bytesRemaining, readLength); - bytesRead = inputStream.read(buffer, offset, bytesToRead); - } catch (IOException e) { - throw new ContentDataSourceException(e); - } - - if (bytesRead > 0) { - if (bytesRemaining != C.LENGTH_UNBOUNDED) { - bytesRemaining -= bytesRead; - } - if (listener != null) { - listener.onBytesTransferred(bytesRead); - } - } - - return bytesRead; - } - } - - @Override - public String getUri() { - return uriString; - } - - @Override - public void close() throws ContentDataSourceException { - uriString = null; - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - throw new ContentDataSourceException(e); - } finally { - inputStream = null; - if (opened) { - opened = false; - if (listener != null) { - listener.onTransferEnd(); - } - } - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSink.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSink.java deleted file mode 100755 index ed5a5762de8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSink.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import java.io.IOException; - -/** - * A component that consumes media data. - */ -public interface DataSink { - - /** - * Opens the {@link DataSink} to consume the specified data. Calls to {@link #open(DataSpec)} and - * {@link #close()} must be balanced. - * - * @param dataSpec Defines the data to be consumed. - * @return This {@link DataSink}, for convenience. - * @throws IOException - */ - public DataSink open(DataSpec dataSpec) throws IOException; - - /** - * Closes the {@link DataSink}. - * - * @throws IOException - */ - public void close() throws IOException; - - /** - * Consumes the provided data. - * - * @param buffer The buffer from which data should be consumed. - * @param offset The offset of the data to consume in {@code buffer}. - * @param length The length of the data to consume, in bytes. - * @throws IOException - */ - public void write(byte[] buffer, int offset, int length) throws IOException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSource.java deleted file mode 100755 index bc600af0aeb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSource.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.C; -import java.io.IOException; - -/** - * A component that provides media data. - */ -public interface DataSource { - - /** - * Opens the {@link DataSource} to read the specified data. Calls to {@link #open(DataSpec)} and - * {@link #close()} must be balanced. - *

        - * Note: If {@link #open(DataSpec)} throws an {@link IOException}, callers must still call - * {@link #close()} to ensure that any partial effects of the {@link #open(DataSpec)} invocation - * are cleaned up. Implementations of this class can assume that callers will call - * {@link #close()} in this case. - * - * @param dataSpec Defines the data to be read. - * @throws IOException If an error occurs opening the source. - * @return The number of bytes that can be read from the opened source. For unbounded requests - * (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNBOUNDED}) this value - * is the resolved length of the request, or {@link C#LENGTH_UNBOUNDED} if the length is still - * unresolved. For all other requests, the value returned will be equal to the request's - * {@link DataSpec#length}. - */ - long open(DataSpec dataSpec) throws IOException; - - /** - * Closes the {@link DataSource}. - *

        - * Note: This method will be called even if the corresponding call to {@link #open(DataSpec)} - * threw an {@link IOException}. See {@link #open(DataSpec)} for more details. - * - * @throws IOException If an error occurs closing the source. - */ - void close() throws IOException; - - /** - * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at - * index {@code offset}. - *

        - * This method blocks until at least one byte of data can be read, the end of the opened range is - * detected, or an exception is thrown. - * - * @param buffer The buffer into which the read data should be stored. - * @param offset The start offset into {@code buffer} at which data should be written. - * @param readLength The maximum number of bytes to read. - * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened - * range is reached. - * @throws IOException If an error occurs reading from the source. - */ - int read(byte[] buffer, int offset, int readLength) throws IOException; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSourceInputStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSourceInputStream.java deleted file mode 100755 index 36484616762..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSourceInputStream.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; -import java.io.InputStream; - -/** - * Allows data corresponding to a given {@link DataSpec} to be read from a {@link DataSource} and - * consumed as an {@link InputStream}. - */ -public final class DataSourceInputStream extends InputStream { - - private final DataSource dataSource; - private final DataSpec dataSpec; - private final byte[] singleByteArray; - - private boolean opened = false; - private boolean closed = false; - - /** - * @param dataSource The {@link DataSource} from which the data should be read. - * @param dataSpec The {@link DataSpec} defining the data to be read from {@code dataSource}. - */ - public DataSourceInputStream(DataSource dataSource, DataSpec dataSpec) { - this.dataSource = dataSource; - this.dataSpec = dataSpec; - singleByteArray = new byte[1]; - } - - /** - * Optional call to open the underlying {@link DataSource}. - *

        - * Calling this method does nothing if the {@link DataSource} is already open. Calling this - * method is optional, since the read and skip methods will automatically open the underlying - * {@link DataSource} if it's not open already. - * - * @throws IOException If an error occurs opening the {@link DataSource}. - */ - public void open() throws IOException { - checkOpened(); - } - - @Override - public int read() throws IOException { - int length = read(singleByteArray); - if (length == -1) { - return -1; - } - return singleByteArray[0] & 0xFF; - } - - @Override - public int read(byte[] buffer) throws IOException { - return read(buffer, 0, buffer.length); - } - - @Override - public int read(byte[] buffer, int offset, int length) throws IOException { - Assertions.checkState(!closed); - checkOpened(); - return dataSource.read(buffer, offset, length); - } - - @Override - public long skip(long byteCount) throws IOException { - Assertions.checkState(!closed); - checkOpened(); - return super.skip(byteCount); - } - - @Override - public void close() throws IOException { - if (!closed) { - dataSource.close(); - closed = true; - } - } - - private void checkOpened() throws IOException { - if (!opened) { - dataSource.open(dataSpec); - opened = true; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultAllocator.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultAllocator.java deleted file mode 100755 index 4eff5c26ac5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultAllocator.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.util.Arrays; - -/** - * Default implementation of {@link Allocator}. - */ -public final class DefaultAllocator implements Allocator { - - private static final int AVAILABLE_EXTRA_CAPACITY = 100; - - private final int individualAllocationSize; - private final byte[] initialAllocationBlock; - - private int allocatedCount; - private int availableCount; - private Allocation[] availableAllocations; - - /** - * Constructs an initially empty pool. - * - * @param individualAllocationSize The length of each individual allocation. - */ - public DefaultAllocator(int individualAllocationSize) { - this(individualAllocationSize, 0); - } - - /** - * Constructs a pool with some {@link Allocation}s created up front. - *

        - * Note: Initial {@link Allocation}s will never be discarded by {@link #trim(int)}. - * - * @param individualAllocationSize The length of each individual allocation. - * @param initialAllocationCount The number of allocations to create up front. - */ - public DefaultAllocator(int individualAllocationSize, int initialAllocationCount) { - Assertions.checkArgument(individualAllocationSize > 0); - Assertions.checkArgument(initialAllocationCount >= 0); - this.individualAllocationSize = individualAllocationSize; - this.availableCount = initialAllocationCount; - this.availableAllocations = new Allocation[initialAllocationCount + AVAILABLE_EXTRA_CAPACITY]; - if (initialAllocationCount > 0) { - initialAllocationBlock = new byte[initialAllocationCount * individualAllocationSize]; - for (int i = 0; i < initialAllocationCount; i++) { - int allocationOffset = i * individualAllocationSize; - availableAllocations[i] = new Allocation(initialAllocationBlock, allocationOffset); - } - } else { - initialAllocationBlock = null; - } - } - - @Override - public synchronized Allocation allocate() { - allocatedCount++; - Allocation allocation; - if (availableCount > 0) { - allocation = availableAllocations[--availableCount]; - availableAllocations[availableCount] = null; - } else { - allocation = new Allocation(new byte[individualAllocationSize], 0); - } - return allocation; - } - - @Override - public synchronized void release(Allocation allocation) { - // Weak sanity check that the allocation probably originated from this pool. - Assertions.checkArgument(allocation.data == initialAllocationBlock - || allocation.data.length == individualAllocationSize); - allocatedCount--; - if (availableCount == availableAllocations.length) { - availableAllocations = Arrays.copyOf(availableAllocations, availableAllocations.length * 2); - } - availableAllocations[availableCount++] = allocation; - // Wake up threads waiting for the allocated size to drop. - notifyAll(); - } - - @Override - public synchronized void release(Allocation[] allocations) { - if (availableCount + allocations.length >= availableAllocations.length) { - availableAllocations = Arrays.copyOf( - availableAllocations, Math.max( - availableAllocations.length * 2, - availableCount + allocations.length)); - } - for (Allocation allocation : allocations) { - // Weak sanity check that the allocation probably originated from this pool. - Assertions.checkArgument(allocation.data == initialAllocationBlock - || allocation.data.length == individualAllocationSize); - availableAllocations[availableCount++] = allocation; - } - allocatedCount -= allocations.length; - // Wake up threads waiting for the allocated size to drop. - notifyAll(); - } - - @Override - public synchronized void trim(int targetSize) { - int targetAllocationCount = Util.ceilDivide(targetSize, individualAllocationSize); - int targetAvailableCount = Math.max(0, targetAllocationCount - allocatedCount); - if (targetAvailableCount >= availableCount) { - // We're already at or below the target. - return; - } - - if (initialAllocationBlock != null) { - // Some allocations are backed by an initial block. We need to make sure that we hold onto all - // such allocations. Re-order the available allocations so that the ones backed by the initial - // block come first. - int lowIndex = 0; - int highIndex = availableCount - 1; - while (lowIndex <= highIndex) { - Allocation lowAllocation = availableAllocations[lowIndex]; - if (lowAllocation.data == initialAllocationBlock) { - lowIndex++; - } else { - Allocation highAllocation = availableAllocations[lowIndex]; - if (highAllocation.data != initialAllocationBlock) { - highIndex--; - } else { - availableAllocations[lowIndex++] = highAllocation; - availableAllocations[highIndex--] = lowAllocation; - } - } - } - // lowIndex is the index of the first allocation not backed by an initial block. - targetAvailableCount = Math.max(targetAvailableCount, lowIndex); - if (targetAvailableCount >= availableCount) { - // We're already at or below the target. - return; - } - } - - // Discard allocations beyond the target. - Arrays.fill(availableAllocations, targetAvailableCount, availableCount, null); - availableCount = targetAvailableCount; - } - - @Override - public synchronized int getTotalBytesAllocated() { - return allocatedCount * individualAllocationSize; - } - - @Override - public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit) - throws InterruptedException { - while (getTotalBytesAllocated() > limit) { - wait(); - } - } - - @Override - public int getIndividualAllocationLength() { - return individualAllocationSize; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultBandwidthMeter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultBandwidthMeter.java deleted file mode 100755 index 9562fe454e5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultBandwidthMeter.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.os.Handler; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Clock; -import org.telegram.messenger.exoplayer.util.SlidingPercentile; -import org.telegram.messenger.exoplayer.util.SystemClock; - -/** - * Counts transferred bytes while transfers are open and creates a bandwidth sample and updated - * bandwidth estimate each time a transfer ends. - */ -public final class DefaultBandwidthMeter implements BandwidthMeter { - - public static final int DEFAULT_MAX_WEIGHT = 2000; - - private final Handler eventHandler; - private final EventListener eventListener; - private final Clock clock; - private final SlidingPercentile slidingPercentile; - - private long bytesAccumulator; - private long startTimeMs; - private long bitrateEstimate; - private int streamCount; - - public DefaultBandwidthMeter() { - this(null, null); - } - - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { - this(eventHandler, eventListener, new SystemClock()); - } - - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock) { - this(eventHandler, eventListener, clock, DEFAULT_MAX_WEIGHT); - } - - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { - this(eventHandler, eventListener, new SystemClock(), maxWeight); - } - - public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, Clock clock, - int maxWeight) { - this.eventHandler = eventHandler; - this.eventListener = eventListener; - this.clock = clock; - this.slidingPercentile = new SlidingPercentile(maxWeight); - bitrateEstimate = NO_ESTIMATE; - } - - @Override - public synchronized long getBitrateEstimate() { - return bitrateEstimate; - } - - @Override - public synchronized void onTransferStart() { - if (streamCount == 0) { - startTimeMs = clock.elapsedRealtime(); - } - streamCount++; - } - - @Override - public synchronized void onBytesTransferred(int bytes) { - bytesAccumulator += bytes; - } - - @Override - public synchronized void onTransferEnd() { - Assertions.checkState(streamCount > 0); - long nowMs = clock.elapsedRealtime(); - int elapsedMs = (int) (nowMs - startTimeMs); - if (elapsedMs > 0) { - float bitsPerSecond = (bytesAccumulator * 8000) / elapsedMs; - slidingPercentile.addSample((int) Math.sqrt(bytesAccumulator), bitsPerSecond); - float bandwidthEstimateFloat = slidingPercentile.getPercentile(0.5f); - bitrateEstimate = Float.isNaN(bandwidthEstimateFloat) ? NO_ESTIMATE - : (long) bandwidthEstimateFloat; - notifyBandwidthSample(elapsedMs, bytesAccumulator, bitrateEstimate); - } - streamCount--; - if (streamCount > 0) { - startTimeMs = nowMs; - } - bytesAccumulator = 0; - } - - private void notifyBandwidthSample(final int elapsedMs, final long bytes, final long bitrate) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onBandwidthSample(elapsedMs, bytes, bitrate); - } - }); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultUriDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultUriDataSource.java deleted file mode 100755 index 4b8ad66da2a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultUriDataSource.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.content.Context; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; - -/** - * A {@link UriDataSource} that supports multiple URI schemes. The supported schemes are: - * - *

          - *
        • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4). - *
        • file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4, or just - * /path/to/media/media.mp4 because the implementation assumes that a URI without a scheme is a - * local file URI). - *
        • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). - *
        • content: For fetching data from a content URI (e.g. content://authority/path/123). - *
        - */ -public final class DefaultUriDataSource implements UriDataSource { - - private static final String SCHEME_ASSET = "asset"; - private static final String SCHEME_CONTENT = "content"; - - private final UriDataSource httpDataSource; - private final UriDataSource fileDataSource; - private final UriDataSource assetDataSource; - private final UriDataSource contentDataSource; - - /** - * {@code null} if no data source is open. Otherwise, equal to {@link #fileDataSource} if the open - * data source is a file, or {@link #httpDataSource} otherwise. - */ - private UriDataSource dataSource; - - /** - * Constructs a new instance. - *

        - * The constructed instance will not follow cross-protocol redirects (i.e. redirects from HTTP to - * HTTPS or vice versa) when fetching remote data. Cross-protocol redirects can be enabled by - * using {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing - * {@code true} as the final argument. - * - * @param context A context. - * @param userAgent The User-Agent string that should be used when requesting remote data. - */ - public DefaultUriDataSource(Context context, String userAgent) { - this(context, null, userAgent, false); - } - - /** - * Constructs a new instance. - *

        - * The constructed instance will not follow cross-protocol redirects (i.e. redirects from HTTP to - * HTTPS or vice versa) when fetching remote data. Cross-protocol redirects can be enabled by - * using {@link #DefaultUriDataSource(Context, TransferListener, String, boolean)} and passing - * {@code true} as the final argument. - * - * @param context A context. - * @param listener An optional {@link TransferListener}. - * @param userAgent The User-Agent string that should be used when requesting remote data. - */ - public DefaultUriDataSource(Context context, TransferListener listener, String userAgent) { - this(context, listener, userAgent, false); - } - - /** - * Constructs a new instance, optionally configured to follow cross-protocol redirects. - * - * @param context A context. - * @param listener An optional {@link TransferListener}. - * @param userAgent The User-Agent string that should be used when requesting remote data. - * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP - * to HTTPS and vice versa) are enabled when fetching remote data.. - */ - public DefaultUriDataSource(Context context, TransferListener listener, String userAgent, - boolean allowCrossProtocolRedirects) { - this(context, listener, - new DefaultHttpDataSource(userAgent, null, listener, - DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, - DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects)); - } - - /** - * Constructs a new instance, using a provided {@link HttpDataSource} for fetching remote data. - * - * @param context A context. - * @param listener An optional {@link TransferListener}. - * @param httpDataSource {@link UriDataSource} to use for non-file URIs. - */ - public DefaultUriDataSource(Context context, TransferListener listener, - UriDataSource httpDataSource) { - this.httpDataSource = Assertions.checkNotNull(httpDataSource); - this.fileDataSource = new FileDataSource(listener); - this.assetDataSource = new AssetDataSource(context, listener); - this.contentDataSource = new ContentDataSource(context, listener); - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - Assertions.checkState(dataSource == null); - // Choose the correct source for the scheme. - String scheme = dataSpec.uri.getScheme(); - if (Util.isLocalFileUri(dataSpec.uri)) { - if (dataSpec.uri.getPath().startsWith("/android_asset/")) { - dataSource = assetDataSource; - } else { - dataSource = fileDataSource; - } - } else if (SCHEME_ASSET.equals(scheme)) { - dataSource = assetDataSource; - } else if (SCHEME_CONTENT.equals(scheme)) { - dataSource = contentDataSource; - } else { - dataSource = httpDataSource; - } - // Open the source and return. - return dataSource.open(dataSpec); - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws IOException { - return dataSource.read(buffer, offset, readLength); - } - - @Override - public String getUri() { - return dataSource == null ? null : dataSource.getUri(); - } - - @Override - public void close() throws IOException { - if (dataSource != null) { - try { - dataSource.close(); - } finally { - dataSource = null; - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/FileDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/FileDataSource.java deleted file mode 100755 index e93b8773880..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/FileDataSource.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.C; -import java.io.EOFException; -import java.io.IOException; -import java.io.RandomAccessFile; - -/** - * A local file {@link UriDataSource}. - */ -public final class FileDataSource implements UriDataSource { - - /** - * Thrown when IOException is encountered during local file read operation. - */ - public static class FileDataSourceException extends IOException { - - public FileDataSourceException(IOException cause) { - super(cause); - } - - } - - private final TransferListener listener; - - private RandomAccessFile file; - private String uriString; - private long bytesRemaining; - private boolean opened; - - /** - * Constructs a new {@link DataSource} that retrieves data from a file. - */ - public FileDataSource() { - this(null); - } - - /** - * Constructs a new {@link DataSource} that retrieves data from a file. - * - * @param listener An optional listener. Specify {@code null} for no listener. - */ - public FileDataSource(TransferListener listener) { - this.listener = listener; - } - - @Override - public long open(DataSpec dataSpec) throws FileDataSourceException { - try { - uriString = dataSpec.uri.toString(); - file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); - file.seek(dataSpec.position); - bytesRemaining = dataSpec.length == C.LENGTH_UNBOUNDED ? file.length() - dataSpec.position - : dataSpec.length; - if (bytesRemaining < 0) { - throw new EOFException(); - } - } catch (IOException e) { - throw new FileDataSourceException(e); - } - - opened = true; - if (listener != null) { - listener.onTransferStart(); - } - - return bytesRemaining; - } - - @Override - public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException { - if (bytesRemaining == 0) { - return -1; - } else { - int bytesRead = 0; - try { - bytesRead = file.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); - } catch (IOException e) { - throw new FileDataSourceException(e); - } - - if (bytesRead > 0) { - bytesRemaining -= bytesRead; - if (listener != null) { - listener.onBytesTransferred(bytesRead); - } - } - - return bytesRead; - } - } - - @Override - public String getUri() { - return uriString; - } - - @Override - public void close() throws FileDataSourceException { - uriString = null; - if (file != null) { - try { - file.close(); - } catch (IOException e) { - throw new FileDataSourceException(e); - } finally { - file = null; - if (opened) { - opened = false; - if (listener != null) { - listener.onTransferEnd(); - } - } - } - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Loader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Loader.java deleted file mode 100755 index ff9b60a95e7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Loader.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.annotation.SuppressLint; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.util.Log; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.TraceUtil; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.IOException; -import java.util.concurrent.ExecutorService; - -/** - * Manages the background loading of {@link Loadable}s. - */ -public final class Loader { - - /** - * Thrown when an unexpected exception is encountered during loading. - */ - public static final class UnexpectedLoaderException extends IOException { - - public UnexpectedLoaderException(Exception cause) { - super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause); - } - - } - - /** - * Interface definition of an object that can be loaded using a {@link Loader}. - */ - public interface Loadable { - - /** - * Cancels the load. - */ - void cancelLoad(); - - /** - * Whether the load has been canceled. - * - * @return True if the load has been canceled. False otherwise. - */ - boolean isLoadCanceled(); - - /** - * Performs the load, returning on completion or cancelation. - * - * @throws IOException - * @throws InterruptedException - */ - void load() throws IOException, InterruptedException; - - } - - /** - * Interface definition for a callback to be notified of {@link Loader} events. - */ - public interface Callback { - - /** - * Invoked when loading has been canceled. - * - * @param loadable The loadable whose load has been canceled. - */ - void onLoadCanceled(Loadable loadable); - - /** - * Invoked when the data source has been fully loaded. - * - * @param loadable The loadable whose load has completed. - */ - void onLoadCompleted(Loadable loadable); - - /** - * Invoked when the data source is stopped due to an error. - * - * @param loadable The loadable whose load has failed. - */ - void onLoadError(Loadable loadable, IOException exception); - - } - - private static final int MSG_END_OF_SOURCE = 0; - private static final int MSG_IO_EXCEPTION = 1; - private static final int MSG_FATAL_ERROR = 2; - - private final ExecutorService downloadExecutorService; - - private LoadTask currentTask; - private boolean loading; - - /** - * @param threadName A name for the loader's thread. - */ - public Loader(String threadName) { - this.downloadExecutorService = Util.newSingleThreadExecutor(threadName); - } - - /** - * Invokes {@link #startLoading(Looper, Loadable, Callback)}, using the {@link Looper} - * associated with the calling thread. - * - * @param loadable The {@link Loadable} to load. - * @param callback A callback to invoke when the load ends. - * @throws IllegalStateException If the calling thread does not have an associated {@link Looper}. - */ - public void startLoading(Loadable loadable, Callback callback) { - Looper myLooper = Looper.myLooper(); - Assertions.checkState(myLooper != null); - startLoading(myLooper, loadable, callback); - } - - /** - * Start loading a {@link Loadable}. - *

        - * A {@link Loader} instance can only load one {@link Loadable} at a time, and so this method - * must not be called when another load is in progress. - * - * @param looper The looper of the thread on which the callback should be invoked. - * @param loadable The {@link Loadable} to load. - * @param callback A callback to invoke when the load ends. - */ - public void startLoading(Looper looper, Loadable loadable, Callback callback) { - Assertions.checkState(!loading); - loading = true; - currentTask = new LoadTask(looper, loadable, callback); - downloadExecutorService.submit(currentTask); - } - - /** - * Whether the {@link Loader} is currently loading a {@link Loadable}. - * - * @return Whether the {@link Loader} is currently loading a {@link Loadable}. - */ - public boolean isLoading() { - return loading; - } - - /** - * Cancels the current load. - *

        - * This method should only be called when a load is in progress. - */ - public void cancelLoading() { - Assertions.checkState(loading); - currentTask.quit(); - } - - /** - * Releases the {@link Loader}. - *

        - * This method should be called when the {@link Loader} is no longer required. - */ - public void release() { - release(null); - } - - /** - * Releases the {@link Loader}, running {@code postLoadAction} on its thread. - *

        - * This method should be called when the {@link Loader} is no longer required. - * - * @param postLoadAction A {@link Runnable} to run on the loader's thread when - * {@link Loadable#load()} is no longer running. - */ - public void release(Runnable postLoadAction) { - if (loading) { - cancelLoading(); - } - if (postLoadAction != null) { - downloadExecutorService.submit(postLoadAction); - } - downloadExecutorService.shutdown(); - } - - @SuppressLint("HandlerLeak") - private final class LoadTask extends Handler implements Runnable { - - private static final String TAG = "LoadTask"; - - private final Loadable loadable; - private final Loader.Callback callback; - - private volatile Thread executorThread; - - public LoadTask(Looper looper, Loadable loadable, Loader.Callback callback) { - super(looper); - this.loadable = loadable; - this.callback = callback; - } - - public void quit() { - loadable.cancelLoad(); - if (executorThread != null) { - executorThread.interrupt(); - } - } - - @Override - public void run() { - try { - executorThread = Thread.currentThread(); - if (!loadable.isLoadCanceled()) { - TraceUtil.beginSection(loadable.getClass().getSimpleName() + ".load()"); - loadable.load(); - TraceUtil.endSection(); - } - sendEmptyMessage(MSG_END_OF_SOURCE); - } catch (IOException e) { - obtainMessage(MSG_IO_EXCEPTION, e).sendToTarget(); - } catch (InterruptedException e) { - // The load was canceled. - Assertions.checkState(loadable.isLoadCanceled()); - sendEmptyMessage(MSG_END_OF_SOURCE); - } catch (Exception e) { - // This should never happen, but handle it anyway. - Log.e(TAG, "Unexpected exception loading stream", e); - obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); - } catch (Error e) { - // We'd hope that the platform would kill the process if an Error is thrown here, but the - // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from - // the handler thread so that the process dies even if the executor behaves in this way. - Log.e(TAG, "Unexpected error loading stream", e); - obtainMessage(MSG_FATAL_ERROR, e).sendToTarget(); - throw e; - } - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == MSG_FATAL_ERROR) { - throw (Error) msg.obj; - } - onFinished(); - if (loadable.isLoadCanceled()) { - callback.onLoadCanceled(loadable); - return; - } - switch (msg.what) { - case MSG_END_OF_SOURCE: - callback.onLoadCompleted(loadable); - break; - case MSG_IO_EXCEPTION: - callback.onLoadError(loadable, (IOException) msg.obj); - break; - } - } - - private void onFinished() { - loading = false; - currentTask = null; - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/NetworkLock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/NetworkLock.java deleted file mode 100755 index 83e310d83ac..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/NetworkLock.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import java.io.IOException; -import java.util.PriorityQueue; - -/** - * A network task prioritization mechanism. - *

        - * Manages different priority network tasks. A network task that wishes to have its priority - * respected, and respect the priority of other tasks, should register itself with the lock prior - * to making network requests. It should then call one of the lock's proceed methods frequently - * during execution, so as to ensure that it continues only if it is the highest (or equally - * highest) priority task. - *

        - * Note that lower integer values correspond to higher priorities. - */ -public final class NetworkLock { - - /** - * Thrown when a task is attempts to proceed when it does not have the highest priority. - */ - public static class PriorityTooLowException extends IOException { - - public PriorityTooLowException(int priority, int highestPriority) { - super("Priority too low [priority=" + priority + ", highest=" + highestPriority + "]"); - } - - } - - public static final NetworkLock instance = new NetworkLock(); - - /** - * Priority for network tasks associated with media streaming. - */ - public static final int STREAMING_PRIORITY = 0; - /** - * Priority for network tasks associated with background downloads. - */ - public static final int DOWNLOAD_PRIORITY = 10; - - private final Object lock = new Object(); - - /** Guarded by {@link #lock}. */ - private final PriorityQueue queue; - - /** Guarded by {@link #lock}. */ - private int highestPriority; - - private NetworkLock() { - queue = new PriorityQueue<>(); - highestPriority = Integer.MAX_VALUE; - } - - /** - * Blocks until the passed priority is the lowest one (i.e. highest priority). - * - * @param priority The priority of the task that would like to proceed. - */ - public void proceed(int priority) throws InterruptedException { - synchronized (lock) { - while (highestPriority < priority) { - lock.wait(); - } - } - } - - /** - * A non-blocking variant of {@link #proceed(int)}. - * - * @param priority The priority of the task that would like to proceed. - * @return Whether the passed priority is allowed to proceed. - */ - public boolean proceedNonBlocking(int priority) { - synchronized (lock) { - return highestPriority >= priority; - } - } - - /** - * A throwing variant of {@link #proceed(int)}. - * - * @param priority The priority of the task that would like to proceed. - * @throws PriorityTooLowException If the passed priority is not high enough to proceed. - */ - public void proceedOrThrow(int priority) throws PriorityTooLowException { - synchronized (lock) { - if (highestPriority < priority) { - throw new PriorityTooLowException(priority, highestPriority); - } - } - } - - /** - * Register a new task. - *

        - * The task must call {@link #remove(int)} when done. - * - * @param priority The priority of the task. - */ - public void add(int priority) { - synchronized (lock) { - queue.add(priority); - highestPriority = Math.min(highestPriority, priority); - } - } - - /** - * Unregister a task. - * - * @param priority The priority of the task. - */ - public void remove(int priority) { - synchronized (lock) { - queue.remove(priority); - highestPriority = queue.isEmpty() ? Integer.MAX_VALUE : queue.peek(); - lock.notifyAll(); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/PriorityDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/PriorityDataSource.java deleted file mode 100755 index 3f5b74a7e00..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/PriorityDataSource.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.IOException; - -/** - * Allows {@link #open(DataSpec)} and {@link #read(byte[], int, int)} calls only if the specified - * priority is the highest priority of any task. {@link NetworkLock.PriorityTooLowException} is - * thrown when this condition does not hold. - */ -public final class PriorityDataSource implements DataSource { - - private final DataSource upstream; - private final int priority; - - /** - * @param priority The priority of the source. - * @param upstream The upstream {@link DataSource}. - */ - public PriorityDataSource(int priority, DataSource upstream) { - this.priority = priority; - this.upstream = Assertions.checkNotNull(upstream); - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - NetworkLock.instance.proceedOrThrow(priority); - return upstream.open(dataSpec); - } - - @Override - public int read(byte[] buffer, int offset, int max) throws IOException { - NetworkLock.instance.proceedOrThrow(priority); - return upstream.read(buffer, offset, max); - } - - @Override - public void close() throws IOException { - upstream.close(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TransferListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TransferListener.java deleted file mode 100755 index 391754205ff..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TransferListener.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -/** - * Interface definition for a callback to be notified of data transfer events. - */ -public interface TransferListener { - - /** - * Invoked when a transfer starts. - */ - void onTransferStart(); - - /** - * Called incrementally during a transfer. - * - * @param bytesTransferred The number of bytes transferred since the previous call to this - * method (or if the first call, since the transfer was started). - */ - void onBytesTransferred(int bytesTransferred); - - /** - * Invoked when a transfer ends. - */ - void onTransferEnd(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriDataSource.java deleted file mode 100755 index b10431b3c04..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriDataSource.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -/** - * A component that provides media data from a URI. - */ -public interface UriDataSource extends DataSource { - - /** - * When the source is open, returns the URI from which data is being read. - *

        - * If redirection occurred, the URI after redirection is the one returned. - * - * @return When the source is open, the URI from which data is being read. Null otherwise. - */ - String getUri(); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriLoadable.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriLoadable.java deleted file mode 100755 index 637d1b2663c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UriLoadable.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream; - -import android.net.Uri; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import java.io.IOException; -import java.io.InputStream; - -/** - * A {@link Loadable} for loading an object from a URI. - * - * @param The type of the object being loaded. - */ -public final class UriLoadable implements Loadable { - - /** - * Parses an object from loaded data. - */ - public interface Parser { - - /** - * Parses an object from a response. - * - * @param connectionUrl The source of the response, after any redirection. - * @param inputStream An {@link InputStream} from which the response data can be read. - * @return The parsed object. - * @throws ParserException If an error occurs parsing the data. - * @throws IOException If an error occurs reading data from the stream. - */ - T parse(String connectionUrl, InputStream inputStream) throws ParserException, IOException; - - } - - private final DataSpec dataSpec; - private final UriDataSource uriDataSource; - private final Parser parser; - - private volatile T result; - private volatile boolean isCanceled; - - /** - * @param url The url from which the object should be loaded. - * @param uriDataSource A {@link UriDataSource} to use when loading the data. - * @param parser Parses the object from the response. - */ - public UriLoadable(String url, UriDataSource uriDataSource, Parser parser) { - this.uriDataSource = uriDataSource; - this.parser = parser; - dataSpec = new DataSpec(Uri.parse(url), DataSpec.FLAG_ALLOW_GZIP); - } - - /** - * Returns the loaded object, or null if an object has not been loaded. - */ - public final T getResult() { - return result; - } - - @Override - public final void cancelLoad() { - // We don't actually cancel anything, but we need to record the cancellation so that - // isLoadCanceled can return the correct value. - isCanceled = true; - } - - @Override - public final boolean isLoadCanceled() { - return isCanceled; - } - - @Override - public final void load() throws IOException, InterruptedException { - DataSourceInputStream inputStream = new DataSourceInputStream(uriDataSource, dataSpec); - try { - inputStream.open(); - result = parser.parse(uriDataSource.getUri(), inputStream); - } finally { - inputStream.close(); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/Cache.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/Cache.java deleted file mode 100755 index 0cf4b365e8d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/Cache.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream.cache; - -import java.io.File; -import java.util.NavigableSet; -import java.util.Set; - -/** - * An interface for cache. - */ -public interface Cache { - - /** - * Interface definition for a callback to be notified of {@link Cache} events. - */ - public interface Listener { - - /** - * Invoked when a {@link CacheSpan} is added to the cache. - * - * @param cache The source of the event. - * @param span The added {@link CacheSpan}. - */ - void onSpanAdded(Cache cache, CacheSpan span); - - /** - * Invoked when a {@link CacheSpan} is removed from the cache. - * - * @param cache The source of the event. - * @param span The removed {@link CacheSpan}. - */ - void onSpanRemoved(Cache cache, CacheSpan span); - - /** - * Invoked when an existing {@link CacheSpan} is accessed, causing it to be replaced. The new - * {@link CacheSpan} is guaranteed to represent the same data as the one it replaces, however - * {@link CacheSpan#file} and {@link CacheSpan#lastAccessTimestamp} may have changed. - *

        - * Note that for span replacement, {@link #onSpanAdded(Cache, CacheSpan)} and - * {@link #onSpanRemoved(Cache, CacheSpan)} are not invoked in addition to this method. - * - * @param cache The source of the event. - * @param oldSpan The old {@link CacheSpan}, which has been removed from the cache. - * @param newSpan The new {@link CacheSpan}, which has been added to the cache. - */ - void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan); - - } - - /** - * Registers a listener to listen for changes to a given key. - *

        - * No guarantees are made about the thread or threads on which the listener is invoked, but it - * is guaranteed that listener methods will be invoked in a serial fashion (i.e. one at a time) - * and in the same order as events occurred. - * - * @param key The key to listen to. - * @param listener The listener to add. - * @return The current spans for the key. - */ - NavigableSet addListener(String key, Listener listener); - - /** - * Unregisters a listener. - * - * @param key The key to stop listening to. - * @param listener The listener to remove. - */ - void removeListener(String key, Listener listener); - - /** - * Returns the cached spans for a given cache key. - * - * @param key The key for which spans should be returned. - * @return The spans for the key. May be null if there are no such spans. - */ - NavigableSet getCachedSpans(String key); - - /** - * Returns all keys in the cache. - * - * @return All the keys in the cache. - */ - Set getKeys(); - - /** - * Returns the total disk space in bytes used by the cache. - * - * @return The total disk space in bytes. - */ - long getCacheSpace(); - - /** - * A caller should invoke this method when they require data from a given position for a given - * key. - *

        - * If there is a cache entry that overlaps the position, then the returned {@link CacheSpan} - * defines the file in which the data is stored. {@link CacheSpan#isCached} is true. The caller - * may read from the cache file, but does not acquire any locks. - *

        - * If there is no cache entry overlapping {@code offset}, then the returned {@link CacheSpan} - * defines a hole in the cache starting at {@code position} into which the caller may write as it - * obtains the data from some other source. The returned {@link CacheSpan} serves as a lock. - * Whilst the caller holds the lock it may write data into the hole. It may split data into - * multiple files. When the caller has finished writing a file it should commit it to the cache - * by calling {@link #commitFile(File)}. When the caller has finished writing, it must release - * the lock by calling {@link #releaseHoleSpan}. - * - * @param key The key of the data being requested. - * @param position The position of the data being requested. - * @return The {@link CacheSpan}. - * @throws InterruptedException - */ - CacheSpan startReadWrite(String key, long position) throws InterruptedException; - - /** - * Same as {@link #startReadWrite(String, long)}. However, if the cache entry is locked, then - * instead of blocking, this method will return null as the {@link CacheSpan}. - * - * @param key The key of the data being requested. - * @param position The position of the data being requested. - * @return The {@link CacheSpan}. Or null if the cache entry is locked. - */ - CacheSpan startReadWriteNonBlocking(String key, long position); - - /** - * Obtains a cache file into which data can be written. Must only be called when holding a - * corresponding hole {@link CacheSpan} obtained from {@link #startReadWrite(String, long)}. - * - * @param key The cache key for the data. - * @param position The starting position of the data. - * @param length The length of the data to be written. Used only to ensure that there is enough - * space in the cache. - * @return The file into which data should be written. - */ - File startFile(String key, long position, long length); - - /** - * Commits a file into the cache. Must only be called when holding a corresponding hole - * {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} - * - * @param file A newly written cache file. - */ - void commitFile(File file); - - /** - * Releases a {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} which - * corresponded to a hole in the cache. - * - * @param holeSpan The {@link CacheSpan} being released. - */ - void releaseHoleSpan(CacheSpan holeSpan); - - /** - * Removes a cached {@link CacheSpan} from the cache, deleting the underlying file. - * - * @param span The {@link CacheSpan} to remove. - */ - void removeSpan(CacheSpan span); - - /** - * Queries if a range is entirely available in the cache. - * - * @param key The cache key for the data. - * @param position The starting position of the data. - * @param length The length of the data. - * @return true if the data is available in the Cache otherwise false; - */ - boolean isCached(String key, long position, long length); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSink.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSink.java deleted file mode 100755 index 1a25c69bdbc..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSink.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream.cache; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.upstream.DataSink; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * Writes data into a cache. - */ -public final class CacheDataSink implements DataSink { - - private final Cache cache; - private final long maxCacheFileSize; - - private DataSpec dataSpec; - private File file; - private FileOutputStream outputStream; - private long outputStreamBytesWritten; - private long dataSpecBytesWritten; - - /** - * Thrown when IOException is encountered when writing data into sink. - */ - public static class CacheDataSinkException extends IOException { - - public CacheDataSinkException(IOException cause) { - super(cause); - } - - } - - - /** - * @param cache The cache into which data should be written. - * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for - * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into - * multiple cache files. - */ - public CacheDataSink(Cache cache, long maxCacheFileSize) { - this.cache = Assertions.checkNotNull(cache); - this.maxCacheFileSize = maxCacheFileSize; - } - - @Override - public DataSink open(DataSpec dataSpec) throws CacheDataSinkException { - // TODO: Support caching for unbounded requests. See TODO in {@link CacheDataSource} for - // more details. - Assertions.checkState(dataSpec.length != C.LENGTH_UNBOUNDED); - try { - this.dataSpec = dataSpec; - dataSpecBytesWritten = 0; - openNextOutputStream(); - return this; - } catch (FileNotFoundException e) { - throw new CacheDataSinkException(e); - } - } - - @Override - public void write(byte[] buffer, int offset, int length) throws CacheDataSinkException { - try { - int bytesWritten = 0; - while (bytesWritten < length) { - if (outputStreamBytesWritten == maxCacheFileSize) { - closeCurrentOutputStream(); - openNextOutputStream(); - } - int bytesToWrite = (int) Math.min(length - bytesWritten, - maxCacheFileSize - outputStreamBytesWritten); - outputStream.write(buffer, offset + bytesWritten, bytesToWrite); - bytesWritten += bytesToWrite; - outputStreamBytesWritten += bytesToWrite; - dataSpecBytesWritten += bytesToWrite; - } - } catch (IOException e) { - throw new CacheDataSinkException(e); - } - } - - @Override - public void close() throws CacheDataSinkException { - try { - closeCurrentOutputStream(); - } catch (IOException e) { - throw new CacheDataSinkException(e); - } - } - - private void openNextOutputStream() throws FileNotFoundException { - file = cache.startFile(dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, - Math.min(dataSpec.length - dataSpecBytesWritten, maxCacheFileSize)); - outputStream = new FileOutputStream(file); - outputStreamBytesWritten = 0; - } - - private void closeCurrentOutputStream() throws IOException { - if (outputStream == null) { - return; - } - - boolean success = false; - try { - outputStream.flush(); - outputStream.getFD().sync(); - success = true; - } finally { - Util.closeQuietly(outputStream); - if (success) { - cache.commitFile(file); - } else { - file.delete(); - } - outputStream = null; - file = null; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSource.java deleted file mode 100755 index 4cc3e86f13a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheDataSource.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream.cache; - -import android.net.Uri; -import android.util.Log; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.upstream.DataSink; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.upstream.FileDataSource; -import org.telegram.messenger.exoplayer.upstream.TeeDataSource; -import org.telegram.messenger.exoplayer.upstream.cache.CacheDataSink.CacheDataSinkException; -import java.io.IOException; -import java.io.InterruptedIOException; - -/** - * A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache - * when possible. When data is not cached it is requested from an upstream {@link DataSource} and - * written into the cache. - */ -public final class CacheDataSource implements DataSource { - - /** - * Interface definition for a callback to be notified of {@link CacheDataSource} events. - */ - public interface EventListener { - - /** - * Invoked when bytes have been read from the cache. - * - * @param cacheSizeBytes Current cache size in bytes. - * @param cachedBytesRead Total bytes read from the cache since this method was last invoked. - */ - void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead); - - } - - private static final String TAG = "CacheDataSource"; - - private final Cache cache; - private final DataSource cacheReadDataSource; - private final DataSource cacheWriteDataSource; - private final DataSource upstreamDataSource; - private final EventListener eventListener; - - private final boolean blockOnCache; - private final boolean ignoreCacheOnError; - - private DataSource currentDataSource; - private Uri uri; - private int flags; - private String key; - private long readPosition; - private long bytesRemaining; - private CacheSpan lockedSpan; - private boolean ignoreCache; - private long totalCachedBytesRead; - - /** - * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. - */ - public CacheDataSource(Cache cache, DataSource upstream, boolean blockOnCache, - boolean ignoreCacheOnError) { - this(cache, upstream, blockOnCache, ignoreCacheOnError, Long.MAX_VALUE); - } - - /** - * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. The sink is configured to fragment data such that no single - * cache file is greater than maxCacheFileSize bytes. - */ - public CacheDataSource(Cache cache, DataSource upstream, boolean blockOnCache, - boolean ignoreCacheOnError, long maxCacheFileSize) { - this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize), - blockOnCache, ignoreCacheOnError, null); - } - - /** - * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for - * reading and writing the cache. One use of this constructor is to allow data to be transformed - * before it is written to disk. - * - * @param cache The cache. - * @param upstream A {@link DataSource} for reading data not in the cache. - * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. - * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. - * @param blockOnCache A flag indicating whether we will block reads if the cache key is locked. - * If this flag is false, then we will read from upstream if the cache key is locked. - * @param ignoreCacheOnError Whether the cache is bypassed following any cache related error. If - * true, then cache related exceptions may be thrown for one cycle of open, read and close - * calls. Subsequent cycles of these calls will then bypass the cache. - * @param eventListener An optional {@link EventListener} to receive events. - */ - public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, - DataSink cacheWriteDataSink, boolean blockOnCache, boolean ignoreCacheOnError, - EventListener eventListener) { - this.cache = cache; - this.cacheReadDataSource = cacheReadDataSource; - this.blockOnCache = blockOnCache; - this.ignoreCacheOnError = ignoreCacheOnError; - this.upstreamDataSource = upstream; - if (cacheWriteDataSink != null) { - this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); - } else { - this.cacheWriteDataSource = null; - } - this.eventListener = eventListener; - } - - @Override - public long open(DataSpec dataSpec) throws IOException { - try { - uri = dataSpec.uri; - flags = dataSpec.flags; - key = dataSpec.key; - readPosition = dataSpec.position; - bytesRemaining = dataSpec.length; - openNextSource(); - return dataSpec.length; - } catch (IOException e) { - handleBeforeThrow(e); - throw e; - } - } - - @Override - public int read(byte[] buffer, int offset, int max) throws IOException { - try { - int bytesRead = currentDataSource.read(buffer, offset, max); - if (bytesRead >= 0) { - if (currentDataSource == cacheReadDataSource) { - totalCachedBytesRead += bytesRead; - } - readPosition += bytesRead; - if (bytesRemaining != C.LENGTH_UNBOUNDED) { - bytesRemaining -= bytesRead; - } - } else { - closeCurrentSource(); - if (bytesRemaining > 0 && bytesRemaining != C.LENGTH_UNBOUNDED) { - openNextSource(); - return read(buffer, offset, max); - } - } - return bytesRead; - } catch (IOException e) { - handleBeforeThrow(e); - throw e; - } - } - - @Override - public void close() throws IOException { - notifyBytesRead(); - try { - closeCurrentSource(); - } catch (IOException e) { - handleBeforeThrow(e); - throw e; - } - } - - /** - * Opens the next source. If the cache contains data spanning the current read position then - * {@link #cacheReadDataSource} is opened to read from it. Else {@link #upstreamDataSource} is - * opened to read from the upstream source and write into the cache. - */ - private void openNextSource() throws IOException { - DataSpec dataSpec; - CacheSpan span; - if (ignoreCache) { - span = null; - } else if (bytesRemaining == C.LENGTH_UNBOUNDED) { - // TODO: Support caching for unbounded requests. This requires storing the source length - // into the cache (the simplest approach is to incorporate it into each cache file's name). - Log.w(TAG, "Cache bypassed due to unbounded length."); - span = null; - } else if (blockOnCache) { - try { - span = cache.startReadWrite(key, readPosition); - } catch (InterruptedException e) { - throw new InterruptedIOException(); - } - } else { - span = cache.startReadWriteNonBlocking(key, readPosition); - } - if (span == null) { - // The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read - // from upstream. - currentDataSource = upstreamDataSource; - dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags); - } else if (span.isCached) { - // Data is cached, read from cache. - Uri fileUri = Uri.fromFile(span.file); - long filePosition = readPosition - span.position; - long length = Math.min(span.length - filePosition, bytesRemaining); - dataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags); - currentDataSource = cacheReadDataSource; - } else { - // Data is not cached, and data is not locked, read from upstream with cache backing. - lockedSpan = span; - long length = span.isOpenEnded() ? bytesRemaining : Math.min(span.length, bytesRemaining); - dataSpec = new DataSpec(uri, readPosition, length, key, flags); - currentDataSource = cacheWriteDataSource != null ? cacheWriteDataSource - : upstreamDataSource; - } - currentDataSource.open(dataSpec); - } - - private void closeCurrentSource() throws IOException { - if (currentDataSource == null) { - return; - } - try { - currentDataSource.close(); - currentDataSource = null; - } finally { - if (lockedSpan != null) { - cache.releaseHoleSpan(lockedSpan); - lockedSpan = null; - } - } - } - - private void handleBeforeThrow(IOException exception) { - if (ignoreCacheOnError && (currentDataSource == cacheReadDataSource - || exception instanceof CacheDataSinkException)) { - // Ignore the cache from now on. - ignoreCache = true; - } - } - - private void notifyBytesRead() { - if (eventListener != null && totalCachedBytesRead > 0) { - eventListener.onCachedBytesRead(cache.getCacheSpace(), totalCachedBytesRead); - totalCachedBytesRead = 0; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheSpan.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheSpan.java deleted file mode 100755 index 7001949a340..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheSpan.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream.cache; - -import org.telegram.messenger.exoplayer.util.Util; -import java.io.File; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). - */ -public final class CacheSpan implements Comparable { - - private static final String SUFFIX = ".v2.exo"; - private static final Pattern CACHE_FILE_PATTERN_V1 = - Pattern.compile("^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$"); - private static final Pattern CACHE_FILE_PATTERN_V2 = - Pattern.compile("^(.+)\\.(\\d+)\\.(\\d+)\\.v2\\.exo$"); - - /** - * The cache key that uniquely identifies the original stream. - */ - public final String key; - /** - * The position of the {@link CacheSpan} in the original stream. - */ - public final long position; - /** - * The length of the {@link CacheSpan}, or -1 if this is an open-ended hole. - */ - public final long length; - /** - * Whether the {@link CacheSpan} is cached. - */ - public final boolean isCached; - /** - * The file corresponding to this {@link CacheSpan}, or null if {@link #isCached} is false. - */ - public final File file; - /** - * The last access timestamp, or -1 if {@link #isCached} is false. - */ - public final long lastAccessTimestamp; - - public static File getCacheFileName(File cacheDir, String key, long offset, - long lastAccessTimestamp) { - return new File(cacheDir, - Util.escapeFileName(key) + "." + offset + "." + lastAccessTimestamp + SUFFIX); - } - - public static CacheSpan createLookup(String key, long position) { - return new CacheSpan(key, position, -1, false, -1, null); - } - - public static CacheSpan createOpenHole(String key, long position) { - return new CacheSpan(key, position, -1, false, -1, null); - } - - public static CacheSpan createClosedHole(String key, long position, long length) { - return new CacheSpan(key, position, length, false, -1, null); - } - - /** - * Creates a cache span from an underlying cache file. - * - * @param file The cache file. - * @return The span, or null if the file name is not correctly formatted. - */ - public static CacheSpan createCacheEntry(File file) { - Matcher matcher = CACHE_FILE_PATTERN_V2.matcher(file.getName()); - if (!matcher.matches()) { - return null; - } - String key = Util.unescapeFileName(matcher.group(1)); - return key == null ? null : createCacheEntry( - key, Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3)), file); - } - - static File upgradeIfNeeded(File file) { - Matcher matcher = CACHE_FILE_PATTERN_V1.matcher(file.getName()); - if (!matcher.matches()) { - return file; - } - String key = matcher.group(1); // Keys were not escaped in version 1. - File newCacheFile = getCacheFileName(file.getParentFile(), key, - Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3))); - file.renameTo(newCacheFile); - return newCacheFile; - } - - private static CacheSpan createCacheEntry(String key, long position, long lastAccessTimestamp, - File file) { - return new CacheSpan(key, position, file.length(), true, lastAccessTimestamp, file); - } - - // Visible for testing. - CacheSpan(String key, long position, long length, boolean isCached, - long lastAccessTimestamp, File file) { - this.key = key; - this.position = position; - this.length = length; - this.isCached = isCached; - this.file = file; - this.lastAccessTimestamp = lastAccessTimestamp; - } - - /** - * @return True if this is an open-ended {@link CacheSpan}. False otherwise. - */ - public boolean isOpenEnded() { - return length == -1; - } - - /** - * Renames the file underlying this cache span to update its last access time. - * - * @return A {@link CacheSpan} representing the updated cache file. - */ - public CacheSpan touch() { - long now = System.currentTimeMillis(); - File newCacheFile = getCacheFileName(file.getParentFile(), key, position, now); - file.renameTo(newCacheFile); - return CacheSpan.createCacheEntry(key, position, now, newCacheFile); - } - - @Override - public int compareTo(CacheSpan another) { - if (!key.equals(another.key)) { - return key.compareTo(another.key); - } - long startOffsetDiff = position - another.position; - return startOffsetDiff == 0 ? 0 : ((startOffsetDiff < 0) ? -1 : 1); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/SimpleCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/SimpleCache.java deleted file mode 100755 index 7bc5d3a5411..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/SimpleCache.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.upstream.cache; - -import android.os.ConditionVariable; -import org.telegram.messenger.exoplayer.util.Assertions; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.NavigableSet; -import java.util.Set; -import java.util.TreeSet; - -/** - * A {@link Cache} implementation that maintains an in-memory representation. - */ -public final class SimpleCache implements Cache { - - private final File cacheDir; - private final CacheEvictor evictor; - private final HashMap lockedSpans; - private final HashMap> cachedSpans; - private final HashMap> listeners; - private long totalSpace = 0; - - /** - * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence - * the directory cannot be used to store other files. - * - * @param cacheDir A dedicated cache directory. - */ - public SimpleCache(File cacheDir, CacheEvictor evictor) { - this.cacheDir = cacheDir; - this.evictor = evictor; - this.lockedSpans = new HashMap<>(); - this.cachedSpans = new HashMap<>(); - this.listeners = new HashMap<>(); - // Start cache initialization. - final ConditionVariable conditionVariable = new ConditionVariable(); - new Thread("SimpleCache.initialize()") { - @Override - public void run() { - synchronized (SimpleCache.this) { - conditionVariable.open(); - initialize(); - } - } - }.start(); - conditionVariable.block(); - } - - @Override - public synchronized NavigableSet addListener(String key, Listener listener) { - ArrayList listenersForKey = listeners.get(key); - if (listenersForKey == null) { - listenersForKey = new ArrayList<>(); - listeners.put(key, listenersForKey); - } - listenersForKey.add(listener); - return getCachedSpans(key); - } - - @Override - public synchronized void removeListener(String key, Listener listener) { - ArrayList listenersForKey = listeners.get(key); - if (listenersForKey != null) { - listenersForKey.remove(listener); - if (listenersForKey.isEmpty()) { - listeners.remove(key); - } - } - } - - @Override - public synchronized NavigableSet getCachedSpans(String key) { - TreeSet spansForKey = cachedSpans.get(key); - return spansForKey == null ? null : new TreeSet<>(spansForKey); - } - - @Override - public synchronized Set getKeys() { - return new HashSet<>(cachedSpans.keySet()); - } - - @Override - public synchronized long getCacheSpace() { - return totalSpace; - } - - @Override - public synchronized CacheSpan startReadWrite(String key, long position) - throws InterruptedException { - CacheSpan lookupSpan = CacheSpan.createLookup(key, position); - while (true) { - CacheSpan span = startReadWriteNonBlocking(lookupSpan); - if (span != null) { - return span; - } else { - // Write case, lock not available. We'll be woken up when a locked span is released (if the - // released lock is for the requested key then we'll be able to make progress) or when a - // span is added to the cache (if the span is for the requested key and covers the requested - // position, then we'll become a read and be able to make progress). - wait(); - } - } - } - - @Override - public synchronized CacheSpan startReadWriteNonBlocking(String key, long position) { - return startReadWriteNonBlocking(CacheSpan.createLookup(key, position)); - } - - private synchronized CacheSpan startReadWriteNonBlocking(CacheSpan lookupSpan) { - CacheSpan spanningRegion = getSpan(lookupSpan); - - // Read case. - if (spanningRegion.isCached) { - CacheSpan oldCacheSpan = spanningRegion; - // Remove the old span from the in-memory representation. - TreeSet spansForKey = cachedSpans.get(oldCacheSpan.key); - Assertions.checkState(spansForKey.remove(oldCacheSpan)); - // Obtain a new span with updated last access timestamp. - spanningRegion = oldCacheSpan.touch(); - // Add the updated span back into the in-memory representation. - spansForKey.add(spanningRegion); - notifySpanTouched(oldCacheSpan, spanningRegion); - return spanningRegion; - } - - // Write case, lock available. - if (!lockedSpans.containsKey(lookupSpan.key)) { - lockedSpans.put(lookupSpan.key, spanningRegion); - return spanningRegion; - } - - // Write case, lock not available. - return null; - } - - @Override - public synchronized File startFile(String key, long position, long length) { - Assertions.checkState(lockedSpans.containsKey(key)); - if (!cacheDir.exists()) { - // For some reason the cache directory doesn't exist. Make a best effort to create it. - removeStaleSpans(); - cacheDir.mkdirs(); - } - evictor.onStartFile(this, key, position, length); - return CacheSpan.getCacheFileName(cacheDir, key, position, System.currentTimeMillis()); - } - - @Override - public synchronized void commitFile(File file) { - CacheSpan span = CacheSpan.createCacheEntry(file); - Assertions.checkState(span != null); - Assertions.checkState(lockedSpans.containsKey(span.key)); - // If the file doesn't exist, don't add it to the in-memory representation. - if (!file.exists()) { - return; - } - // If the file has length 0, delete it and don't add it to the in-memory representation. - long length = file.length(); - if (length == 0) { - file.delete(); - return; - } - addSpan(span); - notifyAll(); - } - - @Override - public synchronized void releaseHoleSpan(CacheSpan holeSpan) { - Assertions.checkState(holeSpan == lockedSpans.remove(holeSpan.key)); - notifyAll(); - } - - /** - * Returns the cache {@link CacheSpan} corresponding to the provided lookup {@link CacheSpan}. - *

        - * If the lookup position is contained by an existing entry in the cache, then the returned - * {@link CacheSpan} defines the file in which the data is stored. If the lookup position is not - * contained by an existing entry, then the returned {@link CacheSpan} defines the maximum extents - * of the hole in the cache. - * - * @param lookupSpan A lookup {@link CacheSpan} specifying a key and position. - * @return The corresponding cache {@link CacheSpan}. - */ - private CacheSpan getSpan(CacheSpan lookupSpan) { - String key = lookupSpan.key; - long offset = lookupSpan.position; - TreeSet entries = cachedSpans.get(key); - if (entries == null) { - return CacheSpan.createOpenHole(key, lookupSpan.position); - } - CacheSpan floorSpan = entries.floor(lookupSpan); - if (floorSpan != null && - floorSpan.position <= offset && offset < floorSpan.position + floorSpan.length) { - // The lookup position is contained within floorSpan. - if (floorSpan.file.exists()) { - return floorSpan; - } else { - // The file has been deleted from under us. It's likely that other files will have been - // deleted too, so scan the whole in-memory representation. - removeStaleSpans(); - return getSpan(lookupSpan); - } - } - CacheSpan ceilEntry = entries.ceiling(lookupSpan); - return ceilEntry == null ? CacheSpan.createOpenHole(key, lookupSpan.position) : - CacheSpan.createClosedHole(key, lookupSpan.position, - ceilEntry.position - lookupSpan.position); - } - - /** - * Ensures that the cache's in-memory representation has been initialized. - */ - private void initialize() { - if (!cacheDir.exists()) { - cacheDir.mkdirs(); - } - File[] files = cacheDir.listFiles(); - if (files == null) { - return; - } - for (int i = 0; i < files.length; i++) { - File file = files[i]; - if (file.length() == 0) { - file.delete(); - } else { - file = CacheSpan.upgradeIfNeeded(file); - CacheSpan span = CacheSpan.createCacheEntry(file); - if (span == null) { - file.delete(); - } else { - addSpan(span); - } - } - } - evictor.onCacheInitialized(); - } - - /** - * Adds a cached span to the in-memory representation. - * - * @param span The span to be added. - */ - private void addSpan(CacheSpan span) { - TreeSet spansForKey = cachedSpans.get(span.key); - if (spansForKey == null) { - spansForKey = new TreeSet<>(); - cachedSpans.put(span.key, spansForKey); - } - spansForKey.add(span); - totalSpace += span.length; - notifySpanAdded(span); - } - - @Override - public synchronized void removeSpan(CacheSpan span) { - TreeSet spansForKey = cachedSpans.get(span.key); - totalSpace -= span.length; - Assertions.checkState(spansForKey.remove(span)); - span.file.delete(); - if (spansForKey.isEmpty()) { - cachedSpans.remove(span.key); - } - notifySpanRemoved(span); - } - - /** - * Scans all of the cached spans in the in-memory representation, removing any for which files - * no longer exist. - */ - private void removeStaleSpans() { - Iterator>> iterator = cachedSpans.entrySet().iterator(); - while (iterator.hasNext()) { - Entry> next = iterator.next(); - Iterator spanIterator = next.getValue().iterator(); - boolean isEmpty = true; - while (spanIterator.hasNext()) { - CacheSpan span = spanIterator.next(); - if (!span.file.exists()) { - spanIterator.remove(); - if (span.isCached) { - totalSpace -= span.length; - } - notifySpanRemoved(span); - } else { - isEmpty = false; - } - } - if (isEmpty) { - iterator.remove(); - } - } - } - - private void notifySpanRemoved(CacheSpan span) { - ArrayList keyListeners = listeners.get(span.key); - if (keyListeners != null) { - for (int i = keyListeners.size() - 1; i >= 0; i--) { - keyListeners.get(i).onSpanRemoved(this, span); - } - } - evictor.onSpanRemoved(this, span); - } - - private void notifySpanAdded(CacheSpan span) { - ArrayList keyListeners = listeners.get(span.key); - if (keyListeners != null) { - for (int i = keyListeners.size() - 1; i >= 0; i--) { - keyListeners.get(i).onSpanAdded(this, span); - } - } - evictor.onSpanAdded(this, span); - } - - private void notifySpanTouched(CacheSpan oldSpan, CacheSpan newSpan) { - ArrayList keyListeners = listeners.get(oldSpan.key); - if (keyListeners != null) { - for (int i = keyListeners.size() - 1; i >= 0; i--) { - keyListeners.get(i).onSpanTouched(this, oldSpan, newSpan); - } - } - evictor.onSpanTouched(this, oldSpan, newSpan); - } - - @Override - public synchronized boolean isCached(String key, long position, long length) { - TreeSet entries = cachedSpans.get(key); - if (entries == null) { - return false; - } - CacheSpan lookupSpan = CacheSpan.createLookup(key, position); - CacheSpan floorSpan = entries.floor(lookupSpan); - if (floorSpan == null || floorSpan.position + floorSpan.length <= position) { - // We don't have a span covering the start of the queried region. - return false; - } - long queryEndPosition = position + length; - long currentEndPosition = floorSpan.position + floorSpan.length; - if (currentEndPosition >= queryEndPosition) { - // floorSpan covers the queried region. - return true; - } - Iterator iterator = entries.tailSet(floorSpan, false).iterator(); - while (iterator.hasNext()) { - CacheSpan next = iterator.next(); - if (next.position > currentEndPosition) { - // There's a hole in the cache within the queried region. - return false; - } - // We expect currentEndPosition to always equal (next.position + next.length), but - // perform a max check anyway to guard against the existence of overlapping spans. - currentEndPosition = Math.max(currentEndPosition, next.position + next.length); - if (currentEndPosition >= queryEndPosition) { - // We've found spans covering the queried region. - return true; - } - } - // We ran out of spans before covering the queried region. - return false; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Ac3Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Ac3Util.java deleted file mode 100755 index d08678ced7f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Ac3Util.java +++ /dev/null @@ -1,239 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import org.telegram.messenger.exoplayer.MediaFormat; -import java.nio.ByteBuffer; - -/** - * Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams. - */ -public final class Ac3Util { - - /** - * The number of new samples per (E-)AC-3 audio block. - */ - private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256; - /** - * Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1. - */ - private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK; - /** - * Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod. - */ - private static final int[] BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6}; - /** - * Sample rates, indexed by fscod. - */ - private static final int[] SAMPLE_RATE_BY_FSCOD = new int[] {48000, 44100, 32000}; - /** - * Sample rates, indexed by fscod2 (E-AC-3). - */ - private static final int[] SAMPLE_RATE_BY_FSCOD2 = new int[] {24000, 22050, 16000}; - /** - * Channel counts, indexed by acmod. - */ - private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5}; - /** - * Nominal bitrates in kbps, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) - */ - private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96, - 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640}; - /** - * 16-bit words per syncframe, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) - */ - private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 = new int[] {69, 87, 104, - 121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393}; - - /** - * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to - * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. - * - * @param data The AC3SpecificBox to parse. - * @param trackId The track identifier to set on the format, or null. - * @param durationUs The duration to set on the format, in microseconds. - * @param language The language to set on the format. - * @return The AC-3 format parsed from data in the header. - */ - public static MediaFormat parseAc3AnnexFFormat(ParsableByteArray data, String trackId, - long durationUs, String language) { - int fscod = (data.readUnsignedByte() & 0xC0) >> 6; - int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - int nextByte = data.readUnsignedByte(); - int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3]; - if ((nextByte & 0x04) != 0) { // lfeon - channelCount++; - } - return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); - } - - /** - * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to - * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. - * - * @param data The EC3SpecificBox to parse. - * @param trackId The track identifier to set on the format, or null. - * @param durationUs The duration to set on the format, in microseconds. - * @param language The language to set on the format. - * @return The E-AC-3 format parsed from data in the header. - */ - public static MediaFormat parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, - long durationUs, String language) { - data.skipBytes(2); // data_rate, num_ind_sub - - // Read only the first substream. - // TODO: Read later substreams? - int fscod = (data.readUnsignedByte() & 0xC0) >> 6; - int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - int nextByte = data.readUnsignedByte(); - int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; - if ((nextByte & 0x01) != 0) { // lfeon - channelCount++; - } - return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); - } - - /** - * Returns the AC-3 format given {@code data} containing a syncframe. The reading position of - * {@code data} will be modified. - * - * @param data The data to parse, positioned at the start of the syncframe. - * @param trackId The track identifier to set on the format, or null. - * @param durationUs The duration to set on the format, in microseconds. - * @param language The language to set on the format. - * @return The AC-3 format parsed from data in the header. - */ - public static MediaFormat parseAc3SyncframeFormat(ParsableBitArray data, String trackId, - long durationUs, String language) { - data.skipBits(16 + 16); // syncword, crc1 - int fscod = data.readBits(2); - data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod - int acmod = data.readBits(3); - if ((acmod & 0x01) != 0 && acmod != 1) { - data.skipBits(2); // cmixlev - } - if ((acmod & 0x04) != 0) { - data.skipBits(2); // surmixlev - } - if (acmod == 2) { - data.skipBits(2); // dsurmod - } - boolean lfeon = data.readBit(); - return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_AC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), - SAMPLE_RATE_BY_FSCOD[fscod], null, language); - } - - /** - * Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of - * {@code data} will be modified. - * - * @param data The data to parse, positioned at the start of the syncframe. - * @param trackId The track identifier to set on the format, or null. - * @param durationUs The duration to set on the format, in microseconds. - * @param language The language to set on the format. - * @return The E-AC-3 format parsed from data in the header. - */ - public static MediaFormat parseEac3SyncframeFormat(ParsableBitArray data, String trackId, - long durationUs, String language) { - data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz - int sampleRate; - int fscod = data.readBits(2); - if (fscod == 3) { - sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; - } else { - data.skipBits(2); // numblkscod - sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - } - int acmod = data.readBits(3); - boolean lfeon = data.readBit(); - return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_E_AC3, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, durationUs, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), - sampleRate, null, language); - } - - /** - * Returns the size in bytes of the given AC-3 syncframe. - * - * @param data The syncframe to parse. - * @return The syncframe size in bytes. - */ - public static int parseAc3SyncframeSize(byte[] data) { - int fscod = (data[4] & 0xC0) >> 6; - int frmsizecod = data[4] & 0x3F; - return getAc3SyncframeSize(fscod, frmsizecod); - } - - /** - * Returns the size in bytes of the given E-AC-3 syncframe. - * - * @param data The syncframe to parse. - * @return The syncframe size in bytes. - */ - public static int parseEAc3SyncframeSize(byte[] data) { - return 2 * (((data[2] & 0x07) << 8) + (data[3] & 0xFF) + 1); // frmsiz - } - - /** - * Returns the number of audio samples in an AC-3 syncframe. - */ - public static int getAc3SyncframeAudioSampleCount() { - return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT; - } - - /** - * Returns the number of audio samples represented by the given E-AC-3 syncframe. - * - * @param data The syncframe to parse. - * @return The number of audio samples represented by the syncframe. - */ - public static int parseEAc3SyncframeAudioSampleCount(byte[] data) { - // See ETSI TS 102 366 subsection E.1.2.2. - return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (((data[4] & 0xC0) >> 6) == 0x03 ? 6 // fscod - : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(data[4] & 0x30) >> 4]); - } - - /** - * Like {@link #parseEAc3SyncframeAudioSampleCount(byte[])} but reads from a byte buffer. The - * buffer position is not modified. - * - * @see #parseEAc3SyncframeAudioSampleCount(byte[]) - */ - public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { - // See ETSI TS 102 366 subsection E.1.2.2. - int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6; - return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6 - : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]); - } - - private static int getAc3SyncframeSize(int fscod, int frmsizecod) { - int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; - if (sampleRate == 44100) { - return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[frmsizecod / 2] + (frmsizecod % 2)); - } - int bitrate = BITRATE_BY_HALF_FRMSIZECOD[frmsizecod / 2]; - if (sampleRate == 32000) { - return 6 * bitrate; - } else { // sampleRate == 48000 - return 4 * bitrate; - } - } - - private Ac3Util() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Assertions.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Assertions.java deleted file mode 100755 index 990a8384b07..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Assertions.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import android.os.Looper; -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.ExoPlayerLibraryInfo; - -/** - * Provides methods for asserting the truth of expressions and properties. - */ -public final class Assertions { - - private Assertions() {} - - /** - * Ensures the truth of an expression involving one or more arguments passed to the calling - * method. - * - * @param expression A boolean expression. - * @throws IllegalArgumentException If {@code expression} is false. - */ - public static void checkArgument(boolean expression) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { - throw new IllegalArgumentException(); - } - } - - /** - * Ensures the truth of an expression involving one or more arguments passed to the calling - * method. - * - * @param expression A boolean expression. - * @param errorMessage The exception message to use if the check fails. The message is converted - * to a {@link String} using {@link String#valueOf(Object)}. - * @throws IllegalArgumentException If {@code expression} is false. - */ - public static void checkArgument(boolean expression, Object errorMessage) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { - throw new IllegalArgumentException(String.valueOf(errorMessage)); - } - } - - /** - * Ensures the truth of an expression involving the state of the calling instance. - * - * @param expression A boolean expression. - * @throws IllegalStateException If {@code expression} is false. - */ - public static void checkState(boolean expression) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { - throw new IllegalStateException(); - } - } - - /** - * Ensures the truth of an expression involving the state of the calling instance. - * - * @param expression A boolean expression. - * @param errorMessage The exception message to use if the check fails. The message is converted - * to a string using {@link String#valueOf(Object)}. - * @throws IllegalStateException If {@code expression} is false. - */ - public static void checkState(boolean expression, Object errorMessage) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { - throw new IllegalStateException(String.valueOf(errorMessage)); - } - } - - /** - * Ensures that an object reference is not null. - * - * @param reference An object reference. - * @return The non-null reference that was validated. - * @throws NullPointerException If {@code reference} is null. - */ - public static T checkNotNull(T reference) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { - throw new NullPointerException(); - } - return reference; - } - - /** - * Ensures that an object reference is not null. - * - * @param reference An object reference. - * @param errorMessage The exception message to use if the check fails. The message is converted - * to a string using {@link String#valueOf(Object)}. - * @return The non-null reference that was validated. - * @throws NullPointerException If {@code reference} is null. - */ - public static T checkNotNull(T reference, Object errorMessage) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { - throw new NullPointerException(String.valueOf(errorMessage)); - } - return reference; - } - - /** - * Ensures that a string passed as an argument to the calling method is not null or 0-length. - * - * @param string A string. - * @return The non-null, non-empty string that was validated. - * @throws IllegalArgumentException If {@code string} is null or 0-length. - */ - public static String checkNotEmpty(String string) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { - throw new IllegalArgumentException(); - } - return string; - } - - /** - * Ensures that a string passed as an argument to the calling method is not null or 0-length. - * - * @param string A string. - * @param errorMessage The exception message to use if the check fails. The message is converted - * to a string using {@link String#valueOf(Object)}. - * @return The non-null, non-empty string that was validated. - * @throws IllegalArgumentException If {@code string} is null or 0-length. - */ - public static String checkNotEmpty(String string, Object errorMessage) { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { - throw new IllegalArgumentException(String.valueOf(errorMessage)); - } - return string; - } - - /** - * Ensures that the calling thread is the application's main thread. - * - * @throws IllegalStateException If the calling thread is not the application's main thread. - */ - public static void checkMainThread() { - if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && Looper.myLooper() != Looper.getMainLooper()) { - throw new IllegalStateException("Not in applications main thread"); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DebugTextViewHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DebugTextViewHelper.java deleted file mode 100755 index 9b07e05fbec..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DebugTextViewHelper.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import android.widget.TextView; -import org.telegram.messenger.exoplayer.CodecCounters; -import org.telegram.messenger.exoplayer.chunk.Format; -import org.telegram.messenger.exoplayer.upstream.BandwidthMeter; - -/** - * A helper class for periodically updating debug information displayed by a {@link TextView}. - */ -public final class DebugTextViewHelper implements Runnable { - - /** - * Provides debug information about an ongoing playback. - */ - public interface Provider { - - /** - * Returns the current playback position, in milliseconds. - */ - long getCurrentPosition(); - - /** - * Returns a format whose information should be displayed, or null. - */ - Format getFormat(); - - /** - * Returns a {@link BandwidthMeter} whose estimate should be displayed, or null. - */ - BandwidthMeter getBandwidthMeter(); - - /** - * Returns a {@link CodecCounters} whose information should be displayed, or null. - */ - CodecCounters getCodecCounters(); - - } - - private static final int REFRESH_INTERVAL_MS = 1000; - - private final TextView textView; - private final Provider debuggable; - - /** - * @param debuggable The {@link Provider} from which debug information should be obtained. - * @param textView The {@link TextView} that should be updated to display the information. - */ - public DebugTextViewHelper(Provider debuggable, TextView textView) { - this.debuggable = debuggable; - this.textView = textView; - } - - /** - * Starts periodic updates of the {@link TextView}. - *

        - * Should be called from the application's main thread. - */ - public void start() { - stop(); - run(); - } - - /** - * Stops periodic updates of the {@link TextView}. - *

        - * Should be called from the application's main thread. - */ - public void stop() { - textView.removeCallbacks(this); - } - - @Override - public void run() { - textView.setText(getRenderString()); - textView.postDelayed(this, REFRESH_INTERVAL_MS); - } - - private String getRenderString() { - return getTimeString() + " " + getQualityString() + " " + getBandwidthString() + " " - + getVideoCodecCountersString(); - } - - private String getTimeString() { - return "ms(" + debuggable.getCurrentPosition() + ")"; - } - - private String getQualityString() { - Format format = debuggable.getFormat(); - return format == null ? "id:? br:? h:?" - : "id:" + format.id + " br:" + format.bitrate + " h:" + format.height; - } - - private String getBandwidthString() { - BandwidthMeter bandwidthMeter = debuggable.getBandwidthMeter(); - if (bandwidthMeter == null - || bandwidthMeter.getBitrateEstimate() == BandwidthMeter.NO_ESTIMATE) { - return "bw:?"; - } else { - return "bw:" + (bandwidthMeter.getBitrateEstimate() / 1000); - } - } - - private String getVideoCodecCountersString() { - CodecCounters codecCounters = debuggable.getCodecCounters(); - return codecCounters == null ? "" : codecCounters.getDebugString(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DtsUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DtsUtil.java deleted file mode 100755 index 674004b5ec6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/DtsUtil.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import org.telegram.messenger.exoplayer.MediaFormat; -import java.nio.ByteBuffer; - -/** - * Utility methods for parsing DTS frames. - */ -public final class DtsUtil { - - /** - * Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4. - */ - private static final int[] CHANNELS_BY_AMODE = new int[] {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, - 7, 8, 8}; - - /** - * Maps SFREQ to the sampling frequency in Hz. See ETSI TS 102 144 table 5.5. - */ - private static final int[] SAMPLE_RATE_BY_SFREQ = new int[] {-1, 8000, 16000, 32000, -1, -1, - 11025, 22050, 44100, -1, -1, 12000, 24000, 48000, -1, -1}; - - /** - * Maps RATE to 2 * bitrate in kbit/s. See ETSI TS 102 144 table 5.7. - */ - private static final int[] TWICE_BITRATE_KBPS_BY_RATE = new int[] {64, 112, 128, 192, 224, 256, - 384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816, - 2823, 2944, 3072, 3840, 4096, 6144, 7680}; - - private static final ParsableBitArray SCRATCH_BITS = new ParsableBitArray(); - - /** - * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 - * subsections 5.3/5.4. - *

        - * This method may only be called from one thread at a time. - * - * @param frame The DTS frame to parse. - * @param trackId The track identifier to set on the format, or null. - * @param durationUs The duration to set on the format, in microseconds. - * @param language The language to set on the format. - * @return The DTS format parsed from data in the header. - */ - public static MediaFormat parseDtsFormat(byte[] frame, String trackId, long durationUs, - String language) { - ParsableBitArray frameBits = SCRATCH_BITS; - frameBits.reset(frame); - frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE - int amode = frameBits.readBits(6); - int channelCount = CHANNELS_BY_AMODE[amode]; - int sfreq = frameBits.readBits(4); - int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq]; - int rate = frameBits.readBits(5); - int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? MediaFormat.NO_VALUE - : TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2; - frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF - channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF - return MediaFormat.createAudioFormat(trackId, MimeTypes.AUDIO_DTS, bitrate, - MediaFormat.NO_VALUE, durationUs, channelCount, sampleRate, null, language); - } - - /** - * Returns the number of audio samples represented by the given DTS frame. - * - * @param data The frame to parse. - * @return The number of audio samples represented by the frame. - */ - public static int parseDtsAudioSampleCount(byte[] data) { - // See ETSI TS 102 114 subsection 5.4.1. - int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); - return (nblks + 1) * 32; - } - - /** - * Like {@link #parseDtsAudioSampleCount(byte[])} but reads from a byte buffer. The buffer - * position is not modified. - */ - public static int parseDtsAudioSampleCount(ByteBuffer data) { - // See ETSI TS 102 114 subsection 5.4.1. - int position = data.position(); - int nblks = ((data.get(position + 4) & 0x01) << 6) - | ((data.get(position + 5) & 0xFC) >> 2); - return (nblks + 1) * 32; - } - - /** - * Returns the size in bytes of the given DTS frame. - * - * @param data The frame to parse. - * @return The frame's size in bytes. - */ - public static int getDtsFrameSize(byte[] data) { - return (((data[5] & 0x02) << 12) - | ((data[6] & 0xFF) << 4) - | ((data[7] & 0xF0) >> 4)) + 1; - } - - private DtsUtil() {} - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacSeekTable.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacSeekTable.java deleted file mode 100755 index 13ed1cc7ab6..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacSeekTable.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import org.telegram.messenger.exoplayer.extractor.SeekMap; - -/** - * FLAC seek table class - */ -public final class FlacSeekTable { - - private static final int METADATA_LENGTH_OFFSET = 1; - private static final int SEEK_POINT_SIZE = 18; - - private final long[] sampleNumbers; - private final long[] offsets; - - /** - * Parses a FLAC file seek table metadata structure and creates a FlacSeekTable instance. - * - * @param data A ParsableByteArray including whole seek table metadata block. Its position should - * be set to the beginning of the block. - * @return A FlacSeekTable instance keeping seek table data - * @see FLAC format - * METADATA_BLOCK_SEEKTABLE - */ - public static FlacSeekTable parseSeekTable(ParsableByteArray data) { - data.skipBytes(METADATA_LENGTH_OFFSET); - int length = data.readUnsignedInt24(); - int numberOfSeekPoints = length / SEEK_POINT_SIZE; - - long[] sampleNumbers = new long[numberOfSeekPoints]; - long[] offsets = new long[numberOfSeekPoints]; - - for (int i = 0; i < numberOfSeekPoints; i++) { - sampleNumbers[i] = data.readLong(); - offsets[i] = data.readLong(); - data.skipBytes(2); // Skip "Number of samples in the target frame." - } - - return new FlacSeekTable(sampleNumbers, offsets); - } - - private FlacSeekTable(long[] sampleNumbers, long[] offsets) { - this.sampleNumbers = sampleNumbers; - this.offsets = offsets; - } - - /** - * Creates a {@link SeekMap} wrapper for this FlacSeekTable. - * - * @param firstFrameOffset Offset of the first FLAC frame - * @param sampleRate Sample rate of the FLAC file. - * @return A SeekMap wrapper for this FlacSeekTable. - */ - public SeekMap createSeekMap(final long firstFrameOffset, final long sampleRate) { - return new SeekMap() { - @Override - public boolean isSeekable() { - return true; - } - - @Override - public long getPosition(long timeUs) { - long sample = (timeUs * sampleRate) / 1000000L; - - int index = Util.binarySearchFloor(sampleNumbers, sample, true, true); - return firstFrameOffset + offsets[index]; - } - }; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacUtil.java deleted file mode 100755 index 5a48a2560de..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacUtil.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -/** - * Utility functions for FLAC - */ -public final class FlacUtil { - - private static final int FRAME_HEADER_SAMPLE_NUMBER_OFFSET = 4; - - /** - * Prevents initialization. - */ - private FlacUtil() { - } - - /** - * Extracts sample timestamp from the given binary FLAC frame header data structure. - * - * @param streamInfo A {@link FlacStreamInfo} instance - * @param frameData A {@link ParsableByteArray} including binary FLAC frame header data structure. - * Its position should be set to the beginning of the structure. - * @return Sample timestamp - * @see FLAC format FRAME_HEADER - */ - public static long extractSampleTimestamp(FlacStreamInfo streamInfo, - ParsableByteArray frameData) { - frameData.skipBytes(FRAME_HEADER_SAMPLE_NUMBER_OFFSET); - long sampleNumber = frameData.readUTF8EncodedLong(); - if (streamInfo.minBlockSize == streamInfo.maxBlockSize) { - // if fixed block size then sampleNumber is frame number - sampleNumber *= streamInfo.minBlockSize; - } - return (sampleNumber * 1000000L) / streamInfo.sampleRate; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ManifestFetcher.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ManifestFetcher.java deleted file mode 100755 index 9a87f631fcb..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ManifestFetcher.java +++ /dev/null @@ -1,408 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import android.os.Handler; -import android.os.Looper; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.Pair; -import org.telegram.messenger.exoplayer.upstream.Loader; -import org.telegram.messenger.exoplayer.upstream.Loader.Loadable; -import org.telegram.messenger.exoplayer.upstream.UriDataSource; -import org.telegram.messenger.exoplayer.upstream.UriLoadable; -import java.io.IOException; -import java.util.concurrent.CancellationException; - -/** - * Performs both single and repeated loads of media manifests. - *

        - * Client code is responsible for ensuring that only one load is taking place at any one time. - * Typical usage of this class is as follows: - *

          - *
        1. Create an instance.
        2. - *
        3. Obtain an initial manifest by calling {@link #singleLoad(Looper, ManifestCallback)} and - * waiting for the callback to be invoked.
        4. - *
        5. For on-demand playbacks, the loader is no longer required. For live playbacks, the loader - * may be required to periodically refresh the manifest. In this case it is injected into any - * components that require it. These components will call {@link #requestRefresh()} on the - * loader whenever a refresh is required.
        6. - *
        - * - * @param The type of manifest. - */ -public class ManifestFetcher implements Loader.Callback { - - /** - * Thrown when an error occurs trying to fetch a manifest. - */ - public static final class ManifestIOException extends IOException{ - public ManifestIOException(Throwable cause) { super(cause); } - - } - - /** - * Interface definition for a callback to be notified of {@link ManifestFetcher} events. - */ - public interface EventListener { - - public void onManifestRefreshStarted(); - - public void onManifestRefreshed(); - - public void onManifestError(IOException e); - - } - - /** - * Callback for the result of a single load. - * - * @param The type of manifest. - */ - public interface ManifestCallback { - - /** - * Invoked when the load has successfully completed. - * - * @param manifest The loaded manifest. - */ - void onSingleManifest(T manifest); - - /** - * Invoked when the load has failed. - * - * @param e The cause of the failure. - */ - void onSingleManifestError(IOException e); - - } - - /** - * Interface for manifests that are able to specify that subsequent loads should use a different - * URI. - */ - public interface RedirectingManifest { - - /** - * Returns the URI from which subsequent manifests should be requested, or null to continue - * using the current URI. - */ - public String getNextManifestUri(); - - } - - private final UriLoadable.Parser parser; - private final UriDataSource uriDataSource; - private final Handler eventHandler; - private final EventListener eventListener; - - /* package */ volatile String manifestUri; - - private int enabledCount; - private Loader loader; - private UriLoadable currentLoadable; - private long currentLoadStartTimestamp; - - private int loadExceptionCount; - private long loadExceptionTimestamp; - private ManifestIOException loadException; - - private volatile T manifest; - private volatile long manifestLoadStartTimestamp; - private volatile long manifestLoadCompleteTimestamp; - - /** - * @param manifestUri The manifest location. - * @param uriDataSource The {@link UriDataSource} to use when loading the manifest. - * @param parser A parser to parse the loaded manifest data. - */ - public ManifestFetcher(String manifestUri, UriDataSource uriDataSource, - UriLoadable.Parser parser) { - this(manifestUri, uriDataSource, parser, null, null); - } - - /** - * @param manifestUri The manifest location. - * @param uriDataSource The {@link UriDataSource} to use when loading the manifest. - * @param parser A parser to parse the loaded manifest data. - * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be - * null if delivery of events is not required. - * @param eventListener A listener of events. May be null if delivery of events is not required. - */ - public ManifestFetcher(String manifestUri, UriDataSource uriDataSource, - UriLoadable.Parser parser, Handler eventHandler, EventListener eventListener) { - this.parser = parser; - this.manifestUri = manifestUri; - this.uriDataSource = uriDataSource; - this.eventHandler = eventHandler; - this.eventListener = eventListener; - } - - /** - * Updates the manifest location. - * - * @param manifestUri The manifest location. - */ - public void updateManifestUri(String manifestUri) { - this.manifestUri = manifestUri; - } - - /** - * Performs a single manifest load. - * - * @param callbackLooper The looper associated with the thread on which the callback should be - * invoked. - * @param callback The callback to receive the result. - */ - public void singleLoad(Looper callbackLooper, final ManifestCallback callback) { - SingleFetchHelper fetchHelper = new SingleFetchHelper( - new UriLoadable<>(manifestUri, uriDataSource, parser), callbackLooper, callback); - fetchHelper.startLoading(); - } - - /** - * Gets a {@link Pair} containing the most recently loaded manifest together with the timestamp - * at which the load completed. - * - * @return The most recently loaded manifest and the timestamp at which the load completed, or - * null if no manifest has loaded. - */ - public T getManifest() { - return manifest; - } - - /** - * Gets the value of {@link SystemClock#elapsedRealtime()} when the last completed load started. - * - * @return The value of {@link SystemClock#elapsedRealtime()} when the last completed load - * started. - */ - public long getManifestLoadStartTimestamp() { - return manifestLoadStartTimestamp; - } - - /** - * Gets the value of {@link SystemClock#elapsedRealtime()} when the last load completed. - * - * @return The value of {@link SystemClock#elapsedRealtime()} when the last load completed. - */ - public long getManifestLoadCompleteTimestamp() { - return manifestLoadCompleteTimestamp; - } - - /** - * Throws the error that affected the most recent attempt to load the manifest. Does nothing if - * the most recent attempt was successful. - * - * @throws ManifestIOException The error that affected the most recent attempt to load the - * manifest. - */ - public void maybeThrowError() throws ManifestIOException { - // Don't throw an exception until at least 1 retry attempt has been made. - if (loadException == null || loadExceptionCount <= 1) { - return; - } - throw loadException; - } - - /** - * Enables refresh functionality. - */ - public void enable() { - if (enabledCount++ == 0) { - loadExceptionCount = 0; - loadException = null; - } - } - - /** - * Disables refresh functionality. - */ - public void disable() { - if (--enabledCount == 0) { - if (loader != null) { - loader.release(); - loader = null; - } - } - } - - /** - * Should be invoked repeatedly by callers who require an updated manifest. - */ - public void requestRefresh() { - if (loadException != null && SystemClock.elapsedRealtime() - < (loadExceptionTimestamp + getRetryDelayMillis(loadExceptionCount))) { - // The previous load failed, and it's too soon to try again. - return; - } - if (loader == null) { - loader = new Loader("manifestLoader"); - } - if (!loader.isLoading()) { - currentLoadable = new UriLoadable<>(manifestUri, uriDataSource, parser); - currentLoadStartTimestamp = SystemClock.elapsedRealtime(); - loader.startLoading(currentLoadable, this); - notifyManifestRefreshStarted(); - } - } - - @Override - public void onLoadCompleted(Loadable loadable) { - if (currentLoadable != loadable) { - // Stale event. - return; - } - - manifest = currentLoadable.getResult(); - manifestLoadStartTimestamp = currentLoadStartTimestamp; - manifestLoadCompleteTimestamp = SystemClock.elapsedRealtime(); - loadExceptionCount = 0; - loadException = null; - - if (manifest instanceof RedirectingManifest) { - RedirectingManifest redirectingManifest = (RedirectingManifest) manifest; - String nextLocation = redirectingManifest.getNextManifestUri(); - if (!TextUtils.isEmpty(nextLocation)) { - manifestUri = nextLocation; - } - } - - notifyManifestRefreshed(); - } - - @Override - public void onLoadCanceled(Loadable loadable) { - // Do nothing. - } - - @Override - public void onLoadError(Loadable loadable, IOException exception) { - if (currentLoadable != loadable) { - // Stale event. - return; - } - - loadExceptionCount++; - loadExceptionTimestamp = SystemClock.elapsedRealtime(); - loadException = new ManifestIOException(exception); - - notifyManifestError(loadException); - } - - /* package */ void onSingleFetchCompleted(T result, long loadStartTimestamp) { - manifest = result; - manifestLoadStartTimestamp = loadStartTimestamp; - manifestLoadCompleteTimestamp = SystemClock.elapsedRealtime(); - } - - private long getRetryDelayMillis(long errorCount) { - return Math.min((errorCount - 1) * 1000, 5000); - } - - private void notifyManifestRefreshStarted() { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onManifestRefreshStarted(); - } - }); - } - } - - private void notifyManifestRefreshed() { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onManifestRefreshed(); - } - }); - } - } - - private void notifyManifestError(final IOException e) { - if (eventHandler != null && eventListener != null) { - eventHandler.post(new Runnable() { - @Override - public void run() { - eventListener.onManifestError(e); - } - }); - } - } - - private class SingleFetchHelper implements Loader.Callback { - - private final UriLoadable singleUseLoadable; - private final Looper callbackLooper; - private final ManifestCallback wrappedCallback; - private final Loader singleUseLoader; - - private long loadStartTimestamp; - - public SingleFetchHelper(UriLoadable singleUseLoadable, Looper callbackLooper, - ManifestCallback wrappedCallback) { - this.singleUseLoadable = singleUseLoadable; - this.callbackLooper = callbackLooper; - this.wrappedCallback = wrappedCallback; - singleUseLoader = new Loader("manifestLoader:single"); - } - - public void startLoading() { - loadStartTimestamp = SystemClock.elapsedRealtime(); - singleUseLoader.startLoading(callbackLooper, singleUseLoadable, this); - } - - @Override - public void onLoadCompleted(Loadable loadable) { - try { - T result = singleUseLoadable.getResult(); - onSingleFetchCompleted(result, loadStartTimestamp); - wrappedCallback.onSingleManifest(result); - } finally { - releaseLoader(); - } - } - - @Override - public void onLoadCanceled(Loadable loadable) { - // This shouldn't ever happen, but handle it anyway. - try { - IOException exception = new ManifestIOException(new CancellationException()); - wrappedCallback.onSingleManifestError(exception); - } finally { - releaseLoader(); - } - } - - @Override - public void onLoadError(Loadable loadable, IOException exception) { - try { - wrappedCallback.onSingleManifestError(exception); - } finally { - releaseLoader(); - } - } - - private void releaseLoader() { - singleUseLoader.release(); - } - - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MimeTypes.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MimeTypes.java deleted file mode 100755 index a0ce75379a5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MimeTypes.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -/** - * Defines common MIME types and helper methods. - */ -public final class MimeTypes { - - public static final String BASE_TYPE_VIDEO = "video"; - public static final String BASE_TYPE_AUDIO = "audio"; - public static final String BASE_TYPE_TEXT = "text"; - public static final String BASE_TYPE_APPLICATION = "application"; - - public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown"; - public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; - public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; - public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp"; - public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc"; - public static final String VIDEO_H265 = BASE_TYPE_VIDEO + "/hevc"; - public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8"; - public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; - public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; - public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2"; - public static final String VIDEO_VC1 = BASE_TYPE_VIDEO + "/wvc1"; - - public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown"; - public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; - public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; - public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm"; - public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; - public static final String AUDIO_MPEG_L1 = BASE_TYPE_AUDIO + "/mpeg-L1"; - public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2"; - public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; - public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; - public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; - public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; - public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; - public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; - public static final String AUDIO_DTS_EXPRESS = BASE_TYPE_AUDIO + "/vnd.dts.hd;profile=lbr"; - public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis"; - public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus"; - public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; - public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; - public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac"; - - public static final String TEXT_UNKNOWN = BASE_TYPE_TEXT + "/x-unknown"; - public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; - - public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; - public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; - public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; - public static final String APPLICATION_EIA608 = BASE_TYPE_APPLICATION + "/eia-608"; - public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip"; - public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; - public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; - public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g"; - public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4vtt"; - public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub"; - public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; - - private MimeTypes() {} - - /** - * Whether the top-level type of {@code mimeType} is audio. - * - * @param mimeType The mimeType to test. - * @return Whether the top level type is audio. - */ - public static boolean isAudio(String mimeType) { - return getTopLevelType(mimeType).equals(BASE_TYPE_AUDIO); - } - - /** - * Whether the top-level type of {@code mimeType} is video. - * - * @param mimeType The mimeType to test. - * @return Whether the top level type is video. - */ - public static boolean isVideo(String mimeType) { - return getTopLevelType(mimeType).equals(BASE_TYPE_VIDEO); - } - - /** - * Whether the top-level type of {@code mimeType} is text. - * - * @param mimeType The mimeType to test. - * @return Whether the top level type is text. - */ - public static boolean isText(String mimeType) { - return getTopLevelType(mimeType).equals(BASE_TYPE_TEXT); - } - - /** - * Whether the top-level type of {@code mimeType} is application. - * - * @param mimeType The mimeType to test. - * @return Whether the top level type is application. - */ - public static boolean isApplication(String mimeType) { - return getTopLevelType(mimeType).equals(BASE_TYPE_APPLICATION); - } - - /** - * Returns the top-level type of {@code mimeType}. - * - * @param mimeType The mimeType whose top-level type is required. - * @return The top-level type. - */ - private static String getTopLevelType(String mimeType) { - int indexOfSlash = mimeType.indexOf('/'); - if (indexOfSlash == -1) { - throw new IllegalArgumentException("Invalid mime type: " + mimeType); - } - return mimeType.substring(0, indexOfSlash); - } - - /** - * Returns the video mimeType type of {@code codecs}. - * - * @param codecs The codecs for which the video mimeType is required. - * @return The video mimeType. - */ - public static String getVideoMediaMimeType(String codecs) { - if (codecs == null) { - return MimeTypes.VIDEO_UNKNOWN; - } - String[] codecList = codecs.split(","); - for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("avc1") || codec.startsWith("avc3")) { - return MimeTypes.VIDEO_H264; - } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { - return MimeTypes.VIDEO_H265; - } else if (codec.startsWith("vp9")) { - return MimeTypes.VIDEO_VP9; - } else if (codec.startsWith("vp8")) { - return MimeTypes.VIDEO_VP8; - } - } - return MimeTypes.VIDEO_UNKNOWN; - } - - /** - * Returns the audio mimeType type of {@code codecs}. - * - * @param codecs The codecs for which the audio mimeType is required. - * @return The audio mimeType. - */ - public static String getAudioMediaMimeType(String codecs) { - if (codecs == null) { - return MimeTypes.AUDIO_UNKNOWN; - } - String[] codecList = codecs.split(","); - for (String codec : codecList) { - codec = codec.trim(); - if (codec.startsWith("mp4a")) { - return MimeTypes.AUDIO_AAC; - } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { - return MimeTypes.AUDIO_AC3; - } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { - return MimeTypes.AUDIO_E_AC3; - } else if (codec.startsWith("dtsc")) { - return MimeTypes.AUDIO_DTS; - } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { - return MimeTypes.AUDIO_DTS_HD; - } else if (codec.startsWith("dtse")) { - return MimeTypes.AUDIO_DTS_EXPRESS; - } else if (codec.startsWith("opus")) { - return MimeTypes.AUDIO_OPUS; - } else if (codec.startsWith("vorbis")) { - return MimeTypes.AUDIO_VORBIS; - } - } - return MimeTypes.AUDIO_UNKNOWN; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableBitArray.java deleted file mode 100755 index 84ca89f4df8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableBitArray.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -/** - * Wraps a byte array, providing methods that allow it to be read as a bitstream. - */ -public final class ParsableBitArray { - - public byte[] data; - - // The offset within the data, stored as the current byte offset, and the bit offset within that - // byte (from 0 to 7). - private int byteOffset; - private int bitOffset; - private int byteLimit; - - /** Creates a new instance that initially has no backing data. */ - public ParsableBitArray() {} - - /** - * Creates a new instance that wraps an existing array. - * - * @param data The data to wrap. - */ - public ParsableBitArray(byte[] data) { - this(data, data.length); - } - - /** - * Creates a new instance that wraps an existing array. - * - * @param data The data to wrap. - * @param limit The limit in bytes. - */ - public ParsableBitArray(byte[] data, int limit) { - this.data = data; - byteLimit = limit; - } - - /** - * Updates the instance to wrap {@code data}, and resets the position to zero. - * - * @param data The array to wrap. - */ - public void reset(byte[] data) { - reset(data, data.length); - } - - /** - * Updates the instance to wrap {@code data}, and resets the position to zero. - * - * @param data The array to wrap. - * @param limit The limit in bytes. - */ - public void reset(byte[] data, int limit) { - this.data = data; - byteOffset = 0; - bitOffset = 0; - byteLimit = limit; - } - - /** - * Returns the number of bits yet to be read. - */ - public int bitsLeft() { - return (byteLimit - byteOffset) * 8 - bitOffset; - } - - /** - * Gets the current bit offset. - * - * @return The current bit offset. - */ - public int getPosition() { - return byteOffset * 8 + bitOffset; - } - - /** - * Sets the current bit offset. - * - * @param position The position to set. - */ - public void setPosition(int position) { - byteOffset = position / 8; - bitOffset = position - (byteOffset * 8); - assertValidOffset(); - } - - /** - * Skips bits and moves current reading position forward. - * - * @param n The number of bits to skip. - */ - public void skipBits(int n) { - byteOffset += (n / 8); - bitOffset += (n % 8); - if (bitOffset > 7) { - byteOffset++; - bitOffset -= 8; - } - assertValidOffset(); - } - - /** - * Reads a single bit. - * - * @return True if the bit is set. False otherwise. - */ - public boolean readBit() { - return readBits(1) == 1; - } - - /** - * Reads up to 32 bits. - * - * @param numBits The number of bits to read. - * @return An integer whose bottom n bits hold the read data. - */ - public int readBits(int numBits) { - if (numBits == 0) { - return 0; - } - - int returnValue = 0; - - // Read as many whole bytes as we can. - int wholeBytes = (numBits / 8); - for (int i = 0; i < wholeBytes; i++) { - int byteValue; - if (bitOffset != 0) { - byteValue = ((data[byteOffset] & 0xFF) << bitOffset) - | ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset)); - } else { - byteValue = data[byteOffset]; - } - numBits -= 8; - returnValue |= (byteValue & 0xFF) << numBits; - byteOffset++; - } - - // Read any remaining bits. - if (numBits > 0) { - int nextBit = bitOffset + numBits; - byte writeMask = (byte) (0xFF >> (8 - numBits)); - - if (nextBit > 8) { - // Combine bits from current byte and next byte. - returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) - | ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask)); - byteOffset++; - } else { - // Bits to be read only within current byte. - returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); - if (nextBit == 8) { - byteOffset++; - } - } - - bitOffset = nextBit % 8; - } - - assertValidOffset(); - return returnValue; - } - - /** - * Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current - * offset. The offset is not modified. - * - * @return Whether it is possible to read an Exp-Golomb-coded integer. - */ - public boolean canReadExpGolombCodedNum() { - int initialByteOffset = byteOffset; - int initialBitOffset = bitOffset; - int leadingZeros = 0; - while (byteOffset < byteLimit && !readBit()) { - leadingZeros++; - } - boolean hitLimit = byteOffset == byteLimit; - byteOffset = initialByteOffset; - bitOffset = initialBitOffset; - return !hitLimit && bitsLeft() >= leadingZeros * 2 + 1; - } - - /** - * Reads an unsigned Exp-Golomb-coded format integer. - * - * @return The value of the parsed Exp-Golomb-coded integer. - */ - public int readUnsignedExpGolombCodedInt() { - return readExpGolombCodeNum(); - } - - /** - * Reads an signed Exp-Golomb-coded format integer. - * - * @return The value of the parsed Exp-Golomb-coded integer. - */ - public int readSignedExpGolombCodedInt() { - int codeNum = readExpGolombCodeNum(); - return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2); - } - - private int readExpGolombCodeNum() { - int leadingZeros = 0; - while (!readBit()) { - leadingZeros++; - } - return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0); - } - - private void assertValidOffset() { - // It is fine for position to be at the end of the array, but no further. - Assertions.checkState(byteOffset >= 0 - && (bitOffset >= 0 && bitOffset < 8) - && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableByteArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableByteArray.java deleted file mode 100755 index f97cb9a5b23..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParsableByteArray.java +++ /dev/null @@ -1,456 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; - -/** - * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are - * parsed with the assumption that their constituent bytes are in big endian order. - */ -public final class ParsableByteArray { - - public byte[] data; - - private int position; - private int limit; - - /** Creates a new instance that initially has no backing data. */ - public ParsableByteArray() {} - - /** Creates a new instance with {@code length} bytes. */ - public ParsableByteArray(int length) { - this.data = new byte[length]; - limit = data.length; - } - - /** Creates a new instance wrapping {@code data}. */ - public ParsableByteArray(byte[] data) { - this.data = data; - limit = data.length; - } - - /** - * Creates a new instance that wraps an existing array. - * - * @param data The data to wrap. - * @param limit The limit. - */ - public ParsableByteArray(byte[] data, int limit) { - this.data = data; - this.limit = limit; - } - - /** - * Updates the instance to wrap {@code data}, and resets the position to zero. - * - * @param data The array to wrap. - * @param limit The limit. - */ - public void reset(byte[] data, int limit) { - this.data = data; - this.limit = limit; - position = 0; - } - - /** - * Sets the position and limit to zero. - */ - public void reset() { - position = 0; - limit = 0; - } - - /** Returns the number of bytes yet to be read. */ - public int bytesLeft() { - return limit - position; - } - - /** Returns the limit. */ - public int limit() { - return limit; - } - - /** - * Sets the limit. - * - * @param limit The limit to set. - */ - public void setLimit(int limit) { - Assertions.checkArgument(limit >= 0 && limit <= data.length); - this.limit = limit; - } - - /** Returns the current offset in the array, in bytes. */ - public int getPosition() { - return position; - } - - /** Returns the capacity of the array, which may be larger than the limit. */ - public int capacity() { - return data == null ? 0 : data.length; - } - - /** - * Sets the reading offset in the array. - * - * @param position Byte offset in the array from which to read. - * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the - * array. - */ - public void setPosition(int position) { - // It is fine for position to be at the end of the array. - Assertions.checkArgument(position >= 0 && position <= limit); - this.position = position; - } - - /** - * Moves the reading offset by {@code bytes}. - * - * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the - * array. - */ - public void skipBytes(int bytes) { - setPosition(position + bytes); - } - - /** - * Reads the next {@code length} bytes into {@code bitArray}, and resets the position of - * {@code bitArray} to zero. - * - * @param bitArray The {@link ParsableBitArray} into which the bytes should be read. - * @param length The number of bytes to write. - */ - public void readBytes(ParsableBitArray bitArray, int length) { - readBytes(bitArray.data, 0, length); - bitArray.setPosition(0); - } - - /** - * Reads the next {@code length} bytes into {@code buffer} at {@code offset}. - * - * @see System#arraycopy - */ - public void readBytes(byte[] buffer, int offset, int length) { - System.arraycopy(data, position, buffer, offset, length); - position += length; - } - - /** - * Reads the next {@code length} bytes into {@code buffer}. - * - * @see ByteBuffer#put(byte[], int, int) - */ - public void readBytes(ByteBuffer buffer, int length) { - buffer.put(data, position, length); - position += length; - } - - /** Reads the next byte as an unsigned value. */ - public int readUnsignedByte() { - return (data[position++] & 0xFF); - } - - /** Reads the next two bytes as an unsigned value. */ - public int readUnsignedShort() { - return (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF); - } - - /** Reads the next two bytes as an unsigned value. */ - public int readLittleEndianUnsignedShort() { - return (data[position++] & 0xFF) | (data[position++] & 0xFF) << 8; - } - - /** Reads the next two bytes as an signed value. */ - public short readShort() { - return (short) ((data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF)); - } - - /** Reads the next two bytes as a signed value. */ - public short readLittleEndianShort() { - return (short) ((data[position++] & 0xFF) | (data[position++] & 0xFF) << 8); - } - - /** Reads the next three bytes as an unsigned value. */ - public int readUnsignedInt24() { - return (data[position++] & 0xFF) << 16 - | (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF); - } - - /** Reads the next three bytes as a signed value in little endian order. */ - public int readLittleEndianInt24() { - return (data[position++] & 0xFF) - | (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF) << 16; - } - - /** Reads the next three bytes as an unsigned value in little endian order. */ - public int readLittleEndianUnsignedInt24() { - return (data[position++] & 0xFF) - | (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF) << 16; - } - - /** Reads the next four bytes as an unsigned value. */ - public long readUnsignedInt() { - return (data[position++] & 0xFFL) << 24 - | (data[position++] & 0xFFL) << 16 - | (data[position++] & 0xFFL) << 8 - | (data[position++] & 0xFFL); - } - - /** Reads the next four bytes as an unsigned value in little endian order. */ - public long readLittleEndianUnsignedInt() { - return (data[position++] & 0xFFL) - | (data[position++] & 0xFFL) << 8 - | (data[position++] & 0xFFL) << 16 - | (data[position++] & 0xFFL) << 24; - } - - /** Reads the next four bytes as a signed value. */ - public int readInt() { - return (data[position++] & 0xFF) << 24 - | (data[position++] & 0xFF) << 16 - | (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF); - } - - /** Reads the next four bytes as an signed value in little endian order. */ - public int readLittleEndianInt() { - return (data[position++] & 0xFF) - | (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF) << 16 - | (data[position++] & 0xFF) << 24; - } - - /** Reads the next eight bytes as a signed value. */ - public long readLong() { - return (data[position++] & 0xFFL) << 56 - | (data[position++] & 0xFFL) << 48 - | (data[position++] & 0xFFL) << 40 - | (data[position++] & 0xFFL) << 32 - | (data[position++] & 0xFFL) << 24 - | (data[position++] & 0xFFL) << 16 - | (data[position++] & 0xFFL) << 8 - | (data[position++] & 0xFFL); - } - - /** Reads the next eight bytes as a signed value in little endian order. */ - public long readLittleEndianLong() { - return (data[position++] & 0xFFL) - | (data[position++] & 0xFFL) << 8 - | (data[position++] & 0xFFL) << 16 - | (data[position++] & 0xFFL) << 24 - | (data[position++] & 0xFFL) << 32 - | (data[position++] & 0xFFL) << 40 - | (data[position++] & 0xFFL) << 48 - | (data[position++] & 0xFFL) << 56; - } - - /** Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer. */ - public int readUnsignedFixedPoint1616() { - int result = (data[position++] & 0xFF) << 8 - | (data[position++] & 0xFF); - position += 2; // Skip the non-integer portion. - return result; - } - - /** - * Reads a Synchsafe integer. - *

        - * Synchsafe integers keep the highest bit of every byte zeroed. A 32 bit synchsafe integer can - * store 28 bits of information. - * - * @return The parsed value. - */ - public int readSynchSafeInt() { - int b1 = readUnsignedByte(); - int b2 = readUnsignedByte(); - int b3 = readUnsignedByte(); - int b4 = readUnsignedByte(); - return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4; - } - - /** - * Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero. - * - * @throws IllegalStateException Thrown if the top bit of the input data is set. - */ - public int readUnsignedIntToInt() { - int result = readInt(); - if (result < 0) { - throw new IllegalStateException("Top bit not zero: " + result); - } - return result; - } - - /** - * Reads the next four bytes as a little endian unsigned integer into an integer, if the top bit - * is a zero. - * - * @throws IllegalStateException Thrown if the top bit of the input data is set. - */ - public int readLittleEndianUnsignedIntToInt() { - int result = readLittleEndianInt(); - if (result < 0) { - throw new IllegalStateException("Top bit not zero: " + result); - } - return result; - } - - /** - * Reads the next eight bytes as an unsigned long into a long, if the top bit is a zero. - * - * @throws IllegalStateException Thrown if the top bit of the input data is set. - */ - public long readUnsignedLongToLong() { - long result = readLong(); - if (result < 0) { - throw new IllegalStateException("Top bit not zero: " + result); - } - return result; - } - - /** Reads the next eight bytes as a 64-bit floating point value. */ - public double readDouble() { - return Double.longBitsToDouble(readLong()); - } - - /** - * Reads the next {@code length} bytes as UTF-8 characters. - * - * @param length The number of bytes to read. - * @return The string encoded by the bytes. - */ - public String readString(int length) { - return readString(length, Charset.defaultCharset()); - } - - /** - * Reads the next {@code length} bytes as characters in the specified {@link Charset}. - * - * @param length The number of bytes to read. - * @param charset The character set of the encoded characters. - * @return The string encoded by the bytes in the specified character set. - */ - public String readString(int length, Charset charset) { - String result = new String(data, position, length, charset); - position += length; - return result; - } - - /** - * Reads a line of text. - *

        - * A line is considered to be terminated by any one of a carriage return ('\r'), a line feed - * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The system's default - * charset (UTF-8) is used. - * - * @return A String containing the contents of the line, not including any line-termination - * characters, or null if the end of the stream has been reached. - */ - public String readLine() { - if (bytesLeft() == 0) { - return null; - } - int lineLimit = position; - while (lineLimit < limit && data[lineLimit] != '\n' && data[lineLimit] != '\r') { - lineLimit++; - } - if (lineLimit - position >= 3 && data[position] == (byte) 0xEF - && data[position + 1] == (byte) 0xBB && data[position + 2] == (byte) 0xBF) { - // There's a byte order mark at the start of the line. Discard it. - position += 3; - } - String line = new String(data, position, lineLimit - position); - position = lineLimit; - if (position == limit) { - return line; - } - if (data[position] == '\r') { - position++; - if (position == limit) { - return line; - } - } - if (data[position] == '\n') { - position++; - } - return line; - } - - /** - * Reads a null-terminated string using the default character set. - * @return A String, not including any null characters, or null if the end of the stream has - * been reached. - */ - public String readNullTerminatedString() { - if (bytesLeft() == 0) { - return null; - } - int stringLimit = position; - while (stringLimit < limit && data[stringLimit] != 0) { - stringLimit++; - } - final int length = stringLimit - position; - String result = new String(data, position, length, Charset.defaultCharset()); - position = stringLimit; - if (position == limit) { - return result; - } - position++; - return result; - } - - /** - * Reads a long value encoded by UTF-8 encoding - * @throws NumberFormatException if there is a problem with decoding - * @return Decoded long value - */ - public long readUTF8EncodedLong() { - int length = 0; - long value = data[position]; - // find the high most 0 bit - for (int j = 7; j >= 0; j--) { - if ((value & (1 << j)) == 0) { - if (j < 6) { - value &= (1 << j) - 1; - length = 7 - j; - } else if (j == 7) { - length = 1; - } - break; - } - } - if (length == 0) { - throw new NumberFormatException("Invalid UTF-8 sequence first byte: " + value); - } - for (int i = 1; i < length; i++) { - int x = data[position + i]; - if ((x & 0xC0) != 0x80) { // if the high most 0 bit not 7th - throw new NumberFormatException("Invalid UTF-8 sequence continuation byte: " + value); - } - value = (value << 6) | (x & 0x3F); - } - position += length; - return value; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParserUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParserUtil.java deleted file mode 100755 index 88a47714282..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/ParserUtil.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -/** - * Parser utility functions. - */ -public final class ParserUtil { - - private ParserUtil() {} - - public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException { - return isEndTag(xpp) && xpp.getName().equals(name); - } - - public static boolean isEndTag(XmlPullParser xpp) throws XmlPullParserException { - return xpp.getEventType() == XmlPullParser.END_TAG; - } - - public static boolean isStartTag(XmlPullParser xpp, String name) - throws XmlPullParserException { - return isStartTag(xpp) && xpp.getName().equals(name); - } - - public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException { - return xpp.getEventType() == XmlPullParser.START_TAG; - } - - public static String getAttributeValue(XmlPullParser xpp, String attributeName) { - int attributeCount = xpp.getAttributeCount(); - for (int i = 0; i < attributeCount; i++) { - if (attributeName.equals(xpp.getAttributeName(i))) { - return xpp.getAttributeValue(i); - } - } - return null; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PlayerControl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PlayerControl.java deleted file mode 100755 index 9e27898fdc1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PlayerControl.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import android.widget.MediaController.MediaPlayerControl; -import org.telegram.messenger.exoplayer.ExoPlayer; - -/** - * An implementation of {@link MediaPlayerControl} for controlling an {@link ExoPlayer} instance. - *

        - * This class is provided for convenience, however it is expected that most applications will - * implement their own player controls and therefore not require this class. - */ -public class PlayerControl implements MediaPlayerControl { - - private final ExoPlayer exoPlayer; - - public PlayerControl(ExoPlayer exoPlayer) { - this.exoPlayer = exoPlayer; - } - - @Override - public boolean canPause() { - return true; - } - - @Override - public boolean canSeekBackward() { - return true; - } - - @Override - public boolean canSeekForward() { - return true; - } - - /** - * This is an unsupported operation. - *

        - * Application of audio effects is dependent on the audio renderer used. When using - * {@link org.telegram.messenger.exoplayer.MediaCodecAudioTrackRenderer}, the recommended approach is - * to extend the class and override - * {@link org.telegram.messenger.exoplayer.MediaCodecAudioTrackRenderer#onAudioSessionId}. - * - * @throws UnsupportedOperationException Always thrown. - */ - @Override - public int getAudioSessionId() { - throw new UnsupportedOperationException(); - } - - @Override - public int getBufferPercentage() { - return exoPlayer.getBufferedPercentage(); - } - - @Override - public int getCurrentPosition() { - return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : (int) exoPlayer.getCurrentPosition(); - } - - @Override - public int getDuration() { - return exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : (int) exoPlayer.getDuration(); - } - - @Override - public boolean isPlaying() { - return exoPlayer.getPlayWhenReady(); - } - - @Override - public void start() { - exoPlayer.setPlayWhenReady(true); - } - - @Override - public void pause() { - exoPlayer.setPlayWhenReady(false); - } - - @Override - public void seekTo(int timeMillis) { - long seekPosition = exoPlayer.getDuration() == ExoPlayer.UNKNOWN_TIME ? 0 - : Math.min(Math.max(0, timeMillis), getDuration()); - exoPlayer.seekTo(seekPosition); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Util.java deleted file mode 100755 index 6d6f428cb20..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Util.java +++ /dev/null @@ -1,912 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.os.Build; -import android.text.TextUtils; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ExoPlayerLibraryInfo; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Method; -import java.math.BigDecimal; -import java.net.HttpURLConnection; -import java.net.URL; -import java.text.ParseException; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.TimeZone; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Miscellaneous utility functions. - */ -public final class Util { - - /** - * Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently - * overridden for local testing. - */ - public static final int SDK_INT = - (Build.VERSION.SDK_INT == 23 && Build.VERSION.CODENAME.charAt(0) == 'N') ? 24 - : Build.VERSION.SDK_INT; - - /** - * Like {@link Build#DEVICE}, but in a place where it can be conveniently overridden for local - * testing. - */ - public static final String DEVICE = Build.DEVICE; - - /** - * Like {@link Build#MANUFACTURER}, but in a place where it can be conveniently overridden for - * local testing. - */ - public static final String MANUFACTURER = Build.MANUFACTURER; - - /** - * Like {@link Build#MODEL}, but in a place where it can be conveniently overridden for local - * testing. - */ - public static final String MODEL = Build.MODEL; - - /** - * Value returned by {@link #inferContentType(String)} for DASH manifests. - */ - public static final int TYPE_DASH = 0; - - /** - * Value returned by {@link #inferContentType(String)} for Smooth Streaming manifests. - */ - public static final int TYPE_SS = 1; - - /** - * Value returned by {@link #inferContentType(String)} for HLS manifests. - */ - public static final int TYPE_HLS = 2; - - /** - * Value returned by {@link #inferContentType(String)} for files other than DASH, HLS or Smooth - * Streaming manifests. - */ - public static final int TYPE_OTHER = 3; - - private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( - "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" - + "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?" - + "([Zz]|((\\+|\\-)(\\d\\d):(\\d\\d)))?"); - - private static final Pattern XS_DURATION_PATTERN = - Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" - + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); - - private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})"); - - private static final long MAX_BYTES_TO_DRAIN = 2048; - - private Util() {} - - /** - * Returns whether the device is an AndroidTV. - * - * @param context A context. - * @return True if the device is an AndroidTV. False otherwise. - */ - @SuppressLint("InlinedApi") - public static boolean isAndroidTv(Context context) { - return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK); - } - - /** - * Converts the entirety of an {@link InputStream} to a byte array. - * - * @param inputStream the {@link InputStream} to be read. The input stream is not closed by this - * method. - * @return a byte array containing all of the inputStream's bytes. - * @throws IOException if an error occurs reading from the stream. - */ - public static byte[] toByteArray(InputStream inputStream) throws IOException { - byte[] buffer = new byte[1024 * 4]; - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - int bytesRead; - while ((bytesRead = inputStream.read(buffer)) != -1) { - outputStream.write(buffer, 0, bytesRead); - } - return outputStream.toByteArray(); - } - - /** - * Returns true if the URI is a path to a local file or a reference to a local file. - * - * @param uri The uri to test. - */ - public static boolean isLocalFileUri(Uri uri) { - String scheme = uri.getScheme(); - return TextUtils.isEmpty(scheme) || scheme.equals("file"); - } - - /** - * Tests two objects for {@link Object#equals(Object)} equality, handling the case where one or - * both may be null. - * - * @param o1 The first object. - * @param o2 The second object. - * @return {@code o1 == null ? o2 == null : o1.equals(o2)}. - */ - public static boolean areEqual(Object o1, Object o2) { - return o1 == null ? o2 == null : o1.equals(o2); - } - - /** - * Tests whether an {@code items} array contains an object equal to {@code item}, according to - * {@link Object#equals(Object)}. - *

        - * If {@code item} is null then true is returned if and only if {@code items} contains null. - * - * @param items The array of items to search. - * @param item The item to search for. - * @return True if the array contains an object equal to the item being searched for. - */ - public static boolean contains(Object[] items, Object item) { - for (int i = 0; i < items.length; i++) { - if (Util.areEqual(items[i], item)) { - return true; - } - } - return false; - } - - /** - * Instantiates a new single threaded executor whose thread has the specified name. - * - * @param threadName The name of the thread. - * @return The executor. - */ - public static ExecutorService newSingleThreadExecutor(final String threadName) { - return Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, threadName); - } - }); - } - - /** - * Instantiates a new single threaded scheduled executor whose thread has the specified name. - * - * @param threadName The name of the thread. - * @return The executor. - */ - public static ScheduledExecutorService newSingleThreadScheduledExecutor(final String threadName) { - return Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - return new Thread(r, threadName); - } - }); - } - - /** - * Closes a {@link DataSource}, suppressing any {@link IOException} that may occur. - * - * @param dataSource The {@link DataSource} to close. - */ - public static void closeQuietly(DataSource dataSource) { - try { - dataSource.close(); - } catch (IOException e) { - // Ignore. - } - } - - /** - * Closes an {@link OutputStream}, suppressing any {@link IOException} that may occur. - * - * @param outputStream The {@link OutputStream} to close. - */ - public static void closeQuietly(OutputStream outputStream) { - try { - outputStream.close(); - } catch (IOException e) { - // Ignore. - } - } - - /** - * Converts text to lower case using {@link Locale#US}. - * - * @param text The text to convert. - * @return The lower case text, or null if {@code text} is null. - */ - public static String toLowerInvariant(String text) { - return text == null ? null : text.toLowerCase(Locale.US); - } - - /** - * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. - * - * @param numerator The numerator to divide. - * @param denominator The denominator to divide by. - * @return The ceiled result of the division. - */ - public static int ceilDivide(int numerator, int denominator) { - return (numerator + denominator - 1) / denominator; - } - - /** - * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. - * - * @param numerator The numerator to divide. - * @param denominator The denominator to divide by. - * @return The ceiled result of the division. - */ - public static long ceilDivide(long numerator, long denominator) { - return (numerator + denominator - 1) / denominator; - } - - /** - * Returns the index of the largest value in an array that is less than (or optionally equal to) - * a specified key. - *

        - * The search is performed using a binary search algorithm, and so the array must be sorted. - * - * @param a The array to search. - * @param key The key being searched for. - * @param inclusive If the key is present in the array, whether to return the corresponding index. - * If false then the returned index corresponds to the largest value in the array that is - * strictly less than the key. - * @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than - * the smallest value in the array. If false then -1 will be returned. - */ - public static int binarySearchFloor(long[] a, long key, boolean inclusive, boolean stayInBounds) { - int index = Arrays.binarySearch(a, key); - index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); - return stayInBounds ? Math.max(0, index) : index; - } - - /** - * Returns the index of the smallest value in an array that is greater than (or optionally equal - * to) a specified key. - *

        - * The search is performed using a binary search algorithm, and so the array must be sorted. - * - * @param a The array to search. - * @param key The key being searched for. - * @param inclusive If the key is present in the array, whether to return the corresponding index. - * If false then the returned index corresponds to the smallest value in the array that is - * strictly greater than the key. - * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the - * key is greater than the largest value in the array. If false then {@code a.length} will be - * returned. - */ - public static int binarySearchCeil(long[] a, long key, boolean inclusive, boolean stayInBounds) { - int index = Arrays.binarySearch(a, key); - index = index < 0 ? ~index : (inclusive ? index : (index + 1)); - return stayInBounds ? Math.min(a.length - 1, index) : index; - } - - /** - * Returns the index of the largest value in an list that is less than (or optionally equal to) - * a specified key. - *

        - * The search is performed using a binary search algorithm, and so the list must be sorted. - * - * @param list The list to search. - * @param key The key being searched for. - * @param inclusive If the key is present in the list, whether to return the corresponding index. - * If false then the returned index corresponds to the largest value in the list that is - * strictly less than the key. - * @param stayInBounds If true, then 0 will be returned in the case that the key is smaller than - * the smallest value in the list. If false then -1 will be returned. - */ - public static int binarySearchFloor(List> list, T key, - boolean inclusive, boolean stayInBounds) { - int index = Collections.binarySearch(list, key); - index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); - return stayInBounds ? Math.max(0, index) : index; - } - - /** - * Returns the index of the smallest value in an list that is greater than (or optionally equal - * to) a specified key. - *

        - * The search is performed using a binary search algorithm, and so the list must be sorted. - * - * @param list The list to search. - * @param key The key being searched for. - * @param inclusive If the key is present in the list, whether to return the corresponding index. - * If false then the returned index corresponds to the smallest value in the list that is - * strictly greater than the key. - * @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that - * the key is greater than the largest value in the list. If false then {@code list.size()} - * will be returned. - */ - public static int binarySearchCeil(List> list, T key, - boolean inclusive, boolean stayInBounds) { - int index = Collections.binarySearch(list, key); - index = index < 0 ? ~index : (inclusive ? index : (index + 1)); - return stayInBounds ? Math.min(list.size() - 1, index) : index; - } - - /** - * Creates an integer array containing the integers from 0 to {@code length - 1}. - * - * @param length The length of the array. - * @return The array. - */ - public static int[] firstIntegersArray(int length) { - int[] firstIntegers = new int[length]; - for (int i = 0; i < length; i++) { - firstIntegers[i] = i; - } - return firstIntegers; - } - - /** - * Parses an xs:duration attribute value, returning the parsed duration in milliseconds. - * - * @param value The attribute value to parse. - * @return The parsed duration in milliseconds. - */ - public static long parseXsDuration(String value) { - Matcher matcher = XS_DURATION_PATTERN.matcher(value); - if (matcher.matches()) { - boolean negated = !TextUtils.isEmpty(matcher.group(1)); - // Durations containing years and months aren't completely defined. We assume there are - // 30.4368 days in a month, and 365.242 days in a year. - String years = matcher.group(3); - double durationSeconds = (years != null) ? Double.parseDouble(years) * 31556908 : 0; - String months = matcher.group(5); - durationSeconds += (months != null) ? Double.parseDouble(months) * 2629739 : 0; - String days = matcher.group(7); - durationSeconds += (days != null) ? Double.parseDouble(days) * 86400 : 0; - String hours = matcher.group(10); - durationSeconds += (hours != null) ? Double.parseDouble(hours) * 3600 : 0; - String minutes = matcher.group(12); - durationSeconds += (minutes != null) ? Double.parseDouble(minutes) * 60 : 0; - String seconds = matcher.group(14); - durationSeconds += (seconds != null) ? Double.parseDouble(seconds) : 0; - long durationMillis = (long) (durationSeconds * 1000); - return negated ? -durationMillis : durationMillis; - } else { - return (long) (Double.parseDouble(value) * 3600 * 1000); - } - } - - /** - * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since - * the epoch. - * - * @param value The attribute value to parse. - * @return The parsed timestamp in milliseconds since the epoch. - */ - public static long parseXsDateTime(String value) throws ParseException { - Matcher matcher = XS_DATE_TIME_PATTERN.matcher(value); - if (!matcher.matches()) { - throw new ParseException("Invalid date/time format: " + value, 0); - } - - int timezoneShift; - if (matcher.group(9) == null) { - // No time zone specified. - timezoneShift = 0; - } else if (matcher.group(9).equalsIgnoreCase("Z")) { - timezoneShift = 0; - } else { - timezoneShift = ((Integer.parseInt(matcher.group(12)) * 60 - + Integer.parseInt(matcher.group(13)))); - if (matcher.group(11).equals("-")) { - timezoneShift *= -1; - } - } - - Calendar dateTime = new GregorianCalendar(TimeZone.getTimeZone("GMT")); - - dateTime.clear(); - // Note: The month value is 0-based, hence the -1 on group(2) - dateTime.set(Integer.parseInt(matcher.group(1)), - Integer.parseInt(matcher.group(2)) - 1, - Integer.parseInt(matcher.group(3)), - Integer.parseInt(matcher.group(4)), - Integer.parseInt(matcher.group(5)), - Integer.parseInt(matcher.group(6))); - if (!TextUtils.isEmpty(matcher.group(8))) { - final BigDecimal bd = new BigDecimal("0." + matcher.group(8)); - // we care only for milliseconds, so movePointRight(3) - dateTime.set(Calendar.MILLISECOND, bd.movePointRight(3).intValue()); - } - - long time = dateTime.getTimeInMillis(); - if (timezoneShift != 0) { - time -= timezoneShift * 60000; - } - - return time; - } - - /** - * Scales a large timestamp. - *

        - * Logically, scaling consists of a multiplication followed by a division. The actual operations - * performed are designed to minimize the probability of overflow. - * - * @param timestamp The timestamp to scale. - * @param multiplier The multiplier. - * @param divisor The divisor. - * @return The scaled timestamp. - */ - public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) { - if (divisor >= multiplier && (divisor % multiplier) == 0) { - long divisionFactor = divisor / multiplier; - return timestamp / divisionFactor; - } else if (divisor < multiplier && (multiplier % divisor) == 0) { - long multiplicationFactor = multiplier / divisor; - return timestamp * multiplicationFactor; - } else { - double multiplicationFactor = (double) multiplier / divisor; - return (long) (timestamp * multiplicationFactor); - } - } - - /** - * Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps. - * - * @param timestamps The timestamps to scale. - * @param multiplier The multiplier. - * @param divisor The divisor. - * @return The scaled timestamps. - */ - public static long[] scaleLargeTimestamps(List timestamps, long multiplier, long divisor) { - long[] scaledTimestamps = new long[timestamps.size()]; - if (divisor >= multiplier && (divisor % multiplier) == 0) { - long divisionFactor = divisor / multiplier; - for (int i = 0; i < scaledTimestamps.length; i++) { - scaledTimestamps[i] = timestamps.get(i) / divisionFactor; - } - } else if (divisor < multiplier && (multiplier % divisor) == 0) { - long multiplicationFactor = multiplier / divisor; - for (int i = 0; i < scaledTimestamps.length; i++) { - scaledTimestamps[i] = timestamps.get(i) * multiplicationFactor; - } - } else { - double multiplicationFactor = (double) multiplier / divisor; - for (int i = 0; i < scaledTimestamps.length; i++) { - scaledTimestamps[i] = (long) (timestamps.get(i) * multiplicationFactor); - } - } - return scaledTimestamps; - } - - /** - * Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps. - * - * @param timestamps The timestamps to scale. - * @param multiplier The multiplier. - * @param divisor The divisor. - */ - public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) { - if (divisor >= multiplier && (divisor % multiplier) == 0) { - long divisionFactor = divisor / multiplier; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] /= divisionFactor; - } - } else if (divisor < multiplier && (multiplier % divisor) == 0) { - long multiplicationFactor = multiplier / divisor; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] *= multiplicationFactor; - } - } else { - double multiplicationFactor = (double) multiplier / divisor; - for (int i = 0; i < timestamps.length; i++) { - timestamps[i] = (long) (timestamps[i] * multiplicationFactor); - } - } - } - - /** - * Converts a list of integers to a primitive array. - * - * @param list A list of integers. - * @return The list in array form, or null if the input list was null. - */ - public static int[] toArray(List list) { - if (list == null) { - return null; - } - int length = list.size(); - int[] intArray = new int[length]; - for (int i = 0; i < length; i++) { - intArray[i] = list.get(i); - } - return intArray; - } - - /** - * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can - * block for a long time if the stream has a lot of data remaining. Call this method before - * closing the input stream to make a best effort to cause the input stream to encounter an - * unexpected end of input, working around this issue. On other platform API levels, the method - * does nothing. - * - * @param connection The connection whose {@link InputStream} should be terminated. - * @param bytesRemaining The number of bytes remaining to be read from the input stream if its - * length is known. {@link C#LENGTH_UNBOUNDED} otherwise. - */ - public static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) { - if (SDK_INT != 19 && SDK_INT != 20) { - return; - } - - try { - InputStream inputStream = connection.getInputStream(); - if (bytesRemaining == C.LENGTH_UNBOUNDED) { - // If the input stream has already ended, do nothing. The socket may be re-used. - if (inputStream.read() == -1) { - return; - } - } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) { - // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be - // re-used. - return; - } - String className = inputStream.getClass().getName(); - if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream") - || className.equals( - "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) { - Class superclass = inputStream.getClass().getSuperclass(); - Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput"); - unexpectedEndOfInput.setAccessible(true); - unexpectedEndOfInput.invoke(inputStream); - } - } catch (IOException e) { - // The connection didn't ever have an input stream, or it was closed already. - } catch (Exception e) { - // Something went wrong. The device probably isn't using okhttp. - } - } - - /** - * Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec} - * that represents the remainder of the data. - * - * @param dataSpec The original {@link DataSpec}. - * @param bytesLoaded The number of bytes already loaded. - * @return A {@link DataSpec} that represents the remainder of the data. - */ - public static DataSpec getRemainderDataSpec(DataSpec dataSpec, int bytesLoaded) { - if (bytesLoaded == 0) { - return dataSpec; - } else { - long remainingLength = dataSpec.length == C.LENGTH_UNBOUNDED ? C.LENGTH_UNBOUNDED - : dataSpec.length - bytesLoaded; - return new DataSpec(dataSpec.uri, dataSpec.position + bytesLoaded, remainingLength, - dataSpec.key, dataSpec.flags); - } - } - - /** - * Returns the integer equal to the big-endian concatenation of the characters in {@code string} - * as bytes. {@code string} must contain four or fewer characters. - */ - public static int getIntegerCodeForString(String string) { - int length = string.length(); - Assertions.checkArgument(length <= 4); - int result = 0; - for (int i = 0; i < length; i++) { - result <<= 8; - result |= string.charAt(i); - } - return result; - } - - /** - * Returns the top 32 bits of a long as an integer. - */ - public static int getTopInt(long value) { - return (int) (value >>> 32); - } - - /** - * Returns the bottom 32 bits of a long as an integer. - */ - public static int getBottomInt(long value) { - return (int) value; - } - - /** - * Returns a long created by concatenating the bits of two integers. - */ - public static long getLong(int topInteger, int bottomInteger) { - return ((long) topInteger << 32) | (bottomInteger & 0xFFFFFFFFL); - } - - /** - * Returns a hex string representation of the data provided. - * - * @param data The byte array containing the data to be turned into a hex string. - * @param beginIndex The begin index, inclusive. - * @param endIndex The end index, exclusive. - * @return A string containing the hex representation of the data provided. - */ - public static String getHexStringFromBytes(byte[] data, int beginIndex, int endIndex) { - StringBuilder dataStringBuilder = new StringBuilder(endIndex - beginIndex); - for (int i = beginIndex; i < endIndex; i++) { - dataStringBuilder.append(String.format(Locale.US, "%02X", data[i])); - } - return dataStringBuilder.toString(); - } - - /** - * Returns a byte array containing values parsed from the hex string provided. - * - * @param hexString The hex string to convert to bytes. - * @return A byte array containing values parsed from the hex string provided. - */ - public static byte[] getBytesFromHexString(String hexString) { - byte[] data = new byte[hexString.length() / 2]; - for (int i = 0; i < data.length; i++) { - int stringOffset = i * 2; - data[i] = (byte) ((Character.digit(hexString.charAt(stringOffset), 16) << 4) - + Character.digit(hexString.charAt(stringOffset + 1), 16)); - } - return data; - } - - /** - * Returns a string with comma delimited simple names of each object's class. - * - * @param objects The objects whose simple class names should be comma delimited and returned. - * @return A string with comma delimited simple names of each object's class. - */ - public static String getCommaDelimitedSimpleClassNames(T[] objects) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - stringBuilder.append(objects[i].getClass().getSimpleName()); - if (i < objects.length - 1) { - stringBuilder.append(", "); - } - } - return stringBuilder.toString(); - } - - /** - * Returns a user agent string based on the given application name and the library version. - * - * @param context A valid context of the calling application. - * @param applicationName String that will be prefix'ed to the generated user agent. - * @return A user agent string generated using the applicationName and the library version. - */ - public static String getUserAgent(Context context, String applicationName) { - String versionName; - try { - String packageName = context.getPackageName(); - PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - versionName = info.versionName; - } catch (NameNotFoundException e) { - versionName = "?"; - } - return applicationName + "/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE - + ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION; - } - - /** - * Executes a post request using {@link HttpURLConnection}. - * - * @param url The request URL. - * @param data The request body, or null. - * @param requestProperties Request properties, or null. - * @return The response body. - * @throws IOException If an error occurred making the request. - */ - // TODO: Remove this and use HttpDataSource once DataSpec supports inclusion of a POST body. - public static byte[] executePost(String url, byte[] data, Map requestProperties) - throws IOException { - HttpURLConnection urlConnection = null; - try { - urlConnection = (HttpURLConnection) new URL(url).openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setDoOutput(data != null); - urlConnection.setDoInput(true); - if (requestProperties != null) { - for (Map.Entry requestProperty : requestProperties.entrySet()) { - urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue()); - } - } - // Write the request body, if there is one. - if (data != null) { - OutputStream out = urlConnection.getOutputStream(); - try { - out.write(data); - } finally { - out.close(); - } - } - // Read and return the response body. - InputStream inputStream = urlConnection.getInputStream(); - try { - return toByteArray(inputStream); - } finally { - inputStream.close(); - } - } finally { - if (urlConnection != null) { - urlConnection.disconnect(); - } - } - } - - /** - * Converts a sample bit depth to a corresponding PCM encoding constant. - * - * @param bitDepth The bit depth. Supported values are 8, 16, 24 and 32. - * @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT}, - * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and - * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then - * {@link C#ENCODING_INVALID} is returned. - */ - public static int getPcmEncoding(int bitDepth) { - switch (bitDepth) { - case 8: - return C.ENCODING_PCM_8BIT; - case 16: - return C.ENCODING_PCM_16BIT; - case 24: - return C.ENCODING_PCM_24BIT; - case 32: - return C.ENCODING_PCM_32BIT; - default: - return C.ENCODING_INVALID; - } - } - - /** - * Makes a best guess to infer the type from a file name. - * - * @param fileName Name of the file. It can include the path of the file. - * @return One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link #TYPE_HLS} or {@link #TYPE_OTHER}. - */ - public static int inferContentType(String fileName) { - if (fileName == null) { - return TYPE_OTHER; - } else if (fileName.endsWith(".mpd")) { - return TYPE_DASH; - } else if (fileName.endsWith(".ism")) { - return TYPE_SS; - } else if (fileName.endsWith(".m3u8")) { - return TYPE_HLS; - } else { - return TYPE_OTHER; - } - } - - /** - * Escapes a string so that it's safe for use as a file or directory name on at least FAT32 - * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today. - * - *

        For simplicity, this only handles common characters known to be illegal on FAT32: - * <, >, :, ", /, \, |, ?, and *. % is also escaped since it is used as the escape - * character. Escaping is performed in a consistent way so that no collisions occur and - * {@link #unescapeFileName(String)} can be used to retrieve the original file name. - * - * @param fileName File name to be escaped. - * @return An escaped file name which will be safe for use on at least FAT32 filesystems. - */ - public static String escapeFileName(String fileName) { - int length = fileName.length(); - int charactersToEscapeCount = 0; - for (int i = 0; i < length; i++) { - if (shouldEscapeCharacter(fileName.charAt(i))) { - charactersToEscapeCount++; - } - } - if (charactersToEscapeCount == 0) { - return fileName; - } - - int i = 0; - StringBuilder builder = new StringBuilder(length + charactersToEscapeCount * 2); - while (charactersToEscapeCount > 0) { - char c = fileName.charAt(i++); - if (shouldEscapeCharacter(c)) { - builder.append('%').append(Integer.toHexString(c)); - charactersToEscapeCount--; - } else { - builder.append(c); - } - } - if (i < length) { - builder.append(fileName, i, length); - } - return builder.toString(); - } - - private static boolean shouldEscapeCharacter(char c) { - switch (c) { - case '<': - case '>': - case ':': - case '"': - case '/': - case '\\': - case '|': - case '?': - case '*': - case '%': - return true; - default: - return false; - } - } - - /** - * Unescapes an escaped file or directory name back to its original value. - * - *

        See {@link #escapeFileName(String)} for more information. - * - * @param fileName File name to be unescaped. - * @return The original value of the file name before it was escaped, - * or null if the escaped fileName seems invalid. - */ - public static String unescapeFileName(String fileName) { - int length = fileName.length(); - int percentCharacterCount = 0; - for (int i = 0; i < length; i++) { - if (fileName.charAt(i) == '%') { - percentCharacterCount++; - } - } - if (percentCharacterCount == 0) { - return fileName; - } - - int expectedLength = length - percentCharacterCount * 2; - StringBuilder builder = new StringBuilder(expectedLength); - Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName); - int endOfLastMatch = 0; - while (percentCharacterCount > 0 && matcher.find()) { - char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16); - builder.append(fileName, endOfLastMatch, matcher.start()).append(unescapedCharacter); - endOfLastMatch = matcher.end(); - percentCharacterCount--; - } - if (endOfLastMatch < length) { - builder.append(fileName, endOfLastMatch, length); - } - if (builder.length() != expectedLength) { - return null; - } - return builder.toString(); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/VerboseLogUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/VerboseLogUtil.java deleted file mode 100755 index 3dc495a1f1a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/VerboseLogUtil.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util; - -/** - * Utility class for managing a set of tags for which verbose logging should be enabled. - */ -public final class VerboseLogUtil { - - private static volatile String[] enabledTags; - private static volatile boolean enableAllTags; - - private VerboseLogUtil() {} - - /** - * Sets the tags for which verbose logging should be enabled. - * - * @param tags The set of tags. - */ - public static void setEnabledTags(String... tags) { - enabledTags = tags; - enableAllTags = false; - } - - /** - * Specifies whether or not all logging should be enabled. - * - * @param enable True if all logging should be enabled; false if only tags enabled by - * setEnabledTags should have logging enabled. - */ - public static void setEnableAllTags(boolean enable) { - enableAllTags = enable; - } - - /** - * Checks whether verbose logging should be output for a given tag. - * - * @param tag The tag. - * @return Whether verbose logging should be output for the tag. - */ - public static boolean isTagEnabled(String tag) { - if (enableAllTags) { - return true; - } - - // Take a local copy of the array to ensure thread safety. - String[] tags = enabledTags; - if (tags == null || tags.length == 0) { - return false; - } - for (int i = 0; i < tags.length; i++) { - if (tags[i].equals(tag)) { - return true; - } - } - return false; - } - - /** - * Checks whether all logging is enabled; - * - * @return True if all logging is enabled; false otherwise. - */ - public static boolean areAllTagsEnabled() { - return enableAllTags; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Buffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Buffer.java deleted file mode 100755 index 4454bf8a3de..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Buffer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util.extensions; - -/** - * Base class for {@link Decoder} buffers with flags. - */ -public abstract class Buffer { - - /** - * Flag for empty input/output buffers that signal that the end of the stream was reached. - */ - public static final int FLAG_END_OF_STREAM = 1; - /** - * Flag for non-empty input/output buffers that should only be decoded (not rendered). - */ - public static final int FLAG_DECODE_ONLY = 2; - - private int flags; - - public void reset() { - flags = 0; - } - - public final void setFlag(int flag) { - flags |= flag; - } - - public final boolean getFlag(int flag) { - return (flags & flag) == flag; - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/InputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/InputBuffer.java deleted file mode 100755 index 833638f5716..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/InputBuffer.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util.extensions; - -import org.telegram.messenger.exoplayer.SampleHolder; - -/** - * Input buffer to be decoded by a {@link Decoder}. - */ -public class InputBuffer extends Buffer { - - public final SampleHolder sampleHolder; - - public InputBuffer() { - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); - } - - @Override - public void reset() { - super.reset(); - sampleHolder.clearData(); - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/SimpleDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/SimpleDecoder.java deleted file mode 100755 index 69a27c3e850..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/SimpleDecoder.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.telegram.messenger.exoplayer.util.extensions; - -import org.telegram.messenger.exoplayer.util.Assertions; -import java.util.LinkedList; - -/** - * Base class for {@link Decoder}s that use their own decode thread. - */ -public abstract class SimpleDecoder extends Thread implements Decoder { - - /** - * Listener for {@link SimpleDecoder} events. - */ - public interface EventListener { - - /** - * Invoked when the decoder encounters an error. - * - * @param e The corresponding exception. - */ - void onDecoderError(E e); - - } - - private final Object lock; - private final LinkedList queuedInputBuffers; - private final LinkedList queuedOutputBuffers; - private final I[] availableInputBuffers; - private final O[] availableOutputBuffers; - - private int availableInputBufferCount; - private int availableOutputBufferCount; - private I dequeuedInputBuffer; - - private E exception; - private boolean flushed; - private boolean released; - - /** - * @param inputBuffers An array of nulls that will be used to store references to input buffers. - * @param outputBuffers An array of nulls that will be used to store references to output buffers. - */ - protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) { - lock = new Object(); - queuedInputBuffers = new LinkedList<>(); - queuedOutputBuffers = new LinkedList<>(); - availableInputBuffers = inputBuffers; - availableInputBufferCount = inputBuffers.length; - for (int i = 0; i < availableInputBufferCount; i++) { - availableInputBuffers[i] = createInputBuffer(); - } - availableOutputBuffers = outputBuffers; - availableOutputBufferCount = outputBuffers.length; - for (int i = 0; i < availableOutputBufferCount; i++) { - availableOutputBuffers[i] = createOutputBuffer(); - } - } - - /** - * Sets the initial size of each input buffer. - *

        - * This method should only be called before the decoder is used (i.e. before the first call to - * {@link #dequeueInputBuffer()}. - * - * @param size The required input buffer size. - */ - protected final void setInitialInputBufferSize(int size) { - Assertions.checkState(availableInputBufferCount == availableInputBuffers.length); - for (int i = 0; i < availableInputBuffers.length; i++) { - availableInputBuffers[i].sampleHolder.ensureSpaceForWrite(size); - } - } - - @Override - public final I dequeueInputBuffer() throws E { - synchronized (lock) { - maybeThrowException(); - Assertions.checkState(dequeuedInputBuffer == null); - if (availableInputBufferCount == 0) { - return null; - } - I inputBuffer = availableInputBuffers[--availableInputBufferCount]; - inputBuffer.reset(); - dequeuedInputBuffer = inputBuffer; - return inputBuffer; - } - } - - @Override - public final void queueInputBuffer(I inputBuffer) throws E { - synchronized (lock) { - maybeThrowException(); - Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); - queuedInputBuffers.addLast(inputBuffer); - maybeNotifyDecodeLoop(); - dequeuedInputBuffer = null; - } - } - - @Override - public final O dequeueOutputBuffer() throws E { - synchronized (lock) { - maybeThrowException(); - if (queuedOutputBuffers.isEmpty()) { - return null; - } - return queuedOutputBuffers.removeFirst(); - } - } - - /** - * Releases an output buffer back to the decoder. - * - * @param outputBuffer The output buffer being released. - */ - protected void releaseOutputBuffer(O outputBuffer) { - synchronized (lock) { - availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; - maybeNotifyDecodeLoop(); - } - } - - @Override - public final void flush() { - synchronized (lock) { - flushed = true; - if (dequeuedInputBuffer != null) { - availableInputBuffers[availableInputBufferCount++] = dequeuedInputBuffer; - dequeuedInputBuffer = null; - } - while (!queuedInputBuffers.isEmpty()) { - availableInputBuffers[availableInputBufferCount++] = queuedInputBuffers.removeFirst(); - } - while (!queuedOutputBuffers.isEmpty()) { - availableOutputBuffers[availableOutputBufferCount++] = queuedOutputBuffers.removeFirst(); - } - } - } - - @Override - public void release() { - synchronized (lock) { - released = true; - lock.notify(); - } - try { - join(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - /** - * Throws a decode exception, if there is one. - * - * @throws E The decode exception. - */ - private void maybeThrowException() throws E { - if (exception != null) { - throw exception; - } - } - - /** - * Notifies the decode loop if there exists a queued input buffer and an available output buffer - * to decode into. - *

        - * Should only be called whilst synchronized on the lock object. - */ - private void maybeNotifyDecodeLoop() { - if (canDecodeBuffer()) { - lock.notify(); - } - } - - @Override - public final void run() { - try { - while (decode()) { - // Do nothing. - } - } catch (InterruptedException e) { - // Not expected. - throw new IllegalStateException(e); - } - } - - private boolean decode() throws InterruptedException { - I inputBuffer; - O outputBuffer; - boolean resetDecoder; - - // Wait until we have an input buffer to decode, and an output buffer to decode into. - synchronized (lock) { - while (!released && !canDecodeBuffer()) { - lock.wait(); - } - if (released) { - return false; - } - inputBuffer = queuedInputBuffers.removeFirst(); - outputBuffer = availableOutputBuffers[--availableOutputBufferCount]; - resetDecoder = flushed; - flushed = false; - } - - outputBuffer.reset(); - if (inputBuffer.getFlag(Buffer.FLAG_END_OF_STREAM)) { - outputBuffer.setFlag(Buffer.FLAG_END_OF_STREAM); - } else { - if (inputBuffer.getFlag(Buffer.FLAG_DECODE_ONLY)) { - outputBuffer.setFlag(Buffer.FLAG_DECODE_ONLY); - } - exception = decode(inputBuffer, outputBuffer, resetDecoder); - if (exception != null) { - // Memory barrier to ensure that the decoder exception is visible from the playback thread. - synchronized (lock) {} - return false; - } - } - - synchronized (lock) { - if (flushed || outputBuffer.getFlag(Buffer.FLAG_DECODE_ONLY)) { - // If a flush occurred while decoding or the buffer was only for decoding (not presentation) - // then make the output buffer available again rather than queueing it to be consumed. - availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; - } else { - // Queue the decoded output buffer to be consumed. - queuedOutputBuffers.addLast(outputBuffer); - } - // Make the input buffer available again. - availableInputBuffers[availableInputBufferCount++] = inputBuffer; - } - - return true; - } - - private boolean canDecodeBuffer() { - return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0; - } - - /** - * Creates a new input buffer. - */ - protected abstract I createInputBuffer(); - - /** - * Creates a new output buffer. - */ - protected abstract O createOutputBuffer(); - - /** - * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. - * - * @param inputBuffer The buffer to decode. - * @param outputBuffer The output buffer to store decoded data. The flag - * {@link Buffer#FLAG_DECODE_ONLY} will be set if the same flag is set on {@code inputBuffer}, - * but the decoder may set/unset the flag if required. If the flag is set after this method - * returns, any output should not be presented. - * @param reset True if the decoder must be reset before decoding. - * @return A decoder exception if an error occurred, or null if decoding was successful. - */ - protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java new file mode 100755 index 00000000000..b04a0d640d7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/BaseRenderer.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MediaClock; +import java.io.IOException; + +/** + * An abstract base class suitable for most {@link Renderer} implementations. + */ +public abstract class BaseRenderer implements Renderer, RendererCapabilities { + + private final int trackType; + + private int index; + private int state; + private SampleStream stream; + private long streamOffsetUs; + private boolean readEndOfStream; + private boolean streamIsFinal; + + /** + * @param trackType The track type that the renderer handles. One of the {@link C} + * {@code TRACK_TYPE_*} constants. + */ + public BaseRenderer(int trackType) { + this.trackType = trackType; + readEndOfStream = true; + } + + @Override + public final int getTrackType() { + return trackType; + } + + @Override + public final RendererCapabilities getCapabilities() { + return this; + } + + @Override + public final void setIndex(int index) { + this.index = index; + } + + @Override + public MediaClock getMediaClock() { + return null; + } + + @Override + public final int getState() { + return state; + } + + @Override + public final void enable(Format[] formats, SampleStream stream, long positionUs, + boolean joining, long offsetUs) throws ExoPlaybackException { + Assertions.checkState(state == STATE_DISABLED); + state = STATE_ENABLED; + onEnabled(joining); + replaceStream(formats, stream, offsetUs); + onPositionReset(positionUs, joining); + } + + @Override + public final void start() throws ExoPlaybackException { + Assertions.checkState(state == STATE_ENABLED); + state = STATE_STARTED; + onStarted(); + } + + @Override + public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) + throws ExoPlaybackException { + Assertions.checkState(!streamIsFinal); + this.stream = stream; + readEndOfStream = false; + streamOffsetUs = offsetUs; + onStreamChanged(formats); + } + + @Override + public final SampleStream getStream() { + return stream; + } + + @Override + public final boolean hasReadStreamToEnd() { + return readEndOfStream; + } + + @Override + public final void setCurrentStreamIsFinal() { + streamIsFinal = true; + } + + @Override + public final void maybeThrowStreamError() throws IOException { + stream.maybeThrowError(); + } + + @Override + public final void resetPosition(long positionUs) throws ExoPlaybackException { + streamIsFinal = false; + onPositionReset(positionUs, false); + } + + @Override + public final void stop() throws ExoPlaybackException { + Assertions.checkState(state == STATE_STARTED); + state = STATE_ENABLED; + onStopped(); + } + + @Override + public final void disable() { + Assertions.checkState(state == STATE_ENABLED); + state = STATE_DISABLED; + onDisabled(); + stream = null; + streamIsFinal = false; + } + + // RendererCapabilities implementation. + + @Override + public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException { + return ADAPTIVE_NOT_SUPPORTED; + } + + // ExoPlayerComponent implementation. + + @Override + public void handleMessage(int what, Object object) throws ExoPlaybackException { + // Do nothing. + } + + // Methods to be overridden by subclasses. + + /** + * Called when the renderer is enabled. + *

        + * The default implementation is a no-op. + * + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onEnabled(boolean joining) throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer's stream has changed. This occurs when the renderer is enabled after + * {@link #onEnabled(boolean)} has been called, and also when the stream has been replaced whilst + * the renderer is enabled or started. + *

        + * The default implementation is a no-op. + * + * @param formats The enabled formats. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the position is reset. This occurs when the renderer is enabled after + * {@link #onStreamChanged(Format[])} has been called, and also when a position discontinuity + * is encountered. + *

        + * After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples + * starting from a key frame. + *

        + * The default implementation is a no-op. + * + * @param positionUs The new playback position in microseconds. + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @throws ExoPlaybackException If an error occurs. + */ + protected void onPositionReset(long positionUs, boolean joining) + throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is started. + *

        + * The default implementation is a no-op. + * + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStarted() throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is stopped. + *

        + * The default implementation is a no-op. + * + * @throws ExoPlaybackException If an error occurs. + */ + protected void onStopped() throws ExoPlaybackException { + // Do nothing. + } + + /** + * Called when the renderer is disabled. + *

        + * The default implementation is a no-op. + */ + protected void onDisabled() { + // Do nothing. + } + + // Methods to be called by subclasses. + + /** + * Returns the index of the renderer within the player. + * + * @return The index of the renderer within the player. + */ + protected final int getIndex() { + return index; + } + + /** + * Reads from the enabled upstream source. If the upstream source has been read to the end then + * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamIsFinal()} has been + * called. {@link C#RESULT_NOTHING_READ} is returned otherwise. + * + * @see SampleStream#readData(FormatHolder, DecoderInputBuffer) + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + protected final int readSource(FormatHolder formatHolder, DecoderInputBuffer buffer) { + int result = stream.readData(formatHolder, buffer); + if (result == C.RESULT_BUFFER_READ) { + if (buffer.isEndOfStream()) { + readEndOfStream = true; + return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ; + } + buffer.timeUs += streamOffsetUs; + } + return result; + } + + /** + * Returns whether the upstream source is ready. + * + * @return Whether the source is ready. + */ + protected final boolean isSourceReady() { + return readEndOfStream ? streamIsFinal : stream.isReady(); + } + + /** + * Attempts to skip to the keyframe before the specified time. + * + * @param timeUs The specified time. + */ + protected void skipToKeyframeBefore(long timeUs) { + stream.skipToKeyframeBefore(timeUs); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java new file mode 100755 index 00000000000..6de53ea2234 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/C.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.MediaCodec; +import android.support.annotation.IntDef; +import android.view.Surface; +import org.telegram.messenger.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.UUID; + +/** + * Defines constants used by the library. + */ +public final class C { + + private C() {} + + /** + * Special constant representing a time corresponding to the end of a source. Suitable for use in + * any time base. + */ + public static final long TIME_END_OF_SOURCE = Long.MIN_VALUE; + + /** + * Special constant representing an unset or unknown time or duration. Suitable for use in any + * time base. + */ + public static final long TIME_UNSET = Long.MIN_VALUE + 1; + + /** + * Represents an unset or unknown index. + */ + public static final int INDEX_UNSET = -1; + + /** + * Represents an unset or unknown position. + */ + public static final int POSITION_UNSET = -1; + + /** + * Represents an unset or unknown length. + */ + public static final int LENGTH_UNSET = -1; + + /** + * The number of microseconds in one second. + */ + public static final long MICROS_PER_SECOND = 1000000L; + + /** + * The number of nanoseconds in one second. + */ + public static final long NANOS_PER_SECOND = 1000000000L; + + /** + * The name of the UTF-8 charset. + */ + public static final String UTF8_NAME = "UTF-8"; + + /** + * Crypto modes for a codec. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC}) + public @interface CryptoMode {} + /** + * @see MediaCodec#CRYPTO_MODE_UNENCRYPTED + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED; + /** + * @see MediaCodec#CRYPTO_MODE_AES_CTR + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR; + /** + * @see MediaCodec#CRYPTO_MODE_AES_CBC + */ + @SuppressWarnings("InlinedApi") + public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC; + + /** + * Represents an audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT, ENCODING_AC3, ENCODING_E_AC3, ENCODING_DTS, + ENCODING_DTS_HD}) + public @interface Encoding {} + + /** + * Represents a PCM audio encoding, or an invalid or unset value. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, ENCODING_INVALID, ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, + ENCODING_PCM_24BIT, ENCODING_PCM_32BIT}) + public @interface PcmEncoding {} + /** + * @see AudioFormat#ENCODING_INVALID + */ + public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID; + /** + * @see AudioFormat#ENCODING_PCM_8BIT + */ + public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT; + /** + * @see AudioFormat#ENCODING_PCM_16BIT + */ + public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT; + /** + * PCM encoding with 24 bits per sample. + */ + public static final int ENCODING_PCM_24BIT = 0x80000000; + /** + * PCM encoding with 32 bits per sample. + */ + public static final int ENCODING_PCM_32BIT = 0x40000000; + /** + * @see AudioFormat#ENCODING_AC3 + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3; + /** + * @see AudioFormat#ENCODING_E_AC3 + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3; + /** + * @see AudioFormat#ENCODING_DTS + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS; + /** + * @see AudioFormat#ENCODING_DTS_HD + */ + @SuppressWarnings("InlinedApi") + public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD; + + /** + * @see AudioFormat#CHANNEL_OUT_7POINT1_SURROUND + */ + @SuppressWarnings({"InlinedApi", "deprecation"}) + public static final int CHANNEL_OUT_7POINT1_SURROUND = Util.SDK_INT < 23 + ? AudioFormat.CHANNEL_OUT_7POINT1 : AudioFormat.CHANNEL_OUT_7POINT1_SURROUND; + + /** + * Stream types for an {@link android.media.AudioTrack}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STREAM_TYPE_ALARM, STREAM_TYPE_MUSIC, STREAM_TYPE_NOTIFICATION, STREAM_TYPE_RING, + STREAM_TYPE_SYSTEM, STREAM_TYPE_VOICE_CALL}) + public @interface StreamType {} + /** + * @see AudioManager#STREAM_ALARM + */ + public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM; + /** + * @see AudioManager#STREAM_MUSIC + */ + public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC; + /** + * @see AudioManager#STREAM_NOTIFICATION + */ + public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION; + /** + * @see AudioManager#STREAM_RING + */ + public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING; + /** + * @see AudioManager#STREAM_SYSTEM + */ + public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM; + /** + * @see AudioManager#STREAM_VOICE_CALL + */ + public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL; + /** + * The default stream type used by audio renderers. + */ + public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC; + + /** + * Flags which can apply to a buffer containing a media sample. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {BUFFER_FLAG_KEY_FRAME, BUFFER_FLAG_END_OF_STREAM, + BUFFER_FLAG_ENCRYPTED, BUFFER_FLAG_DECODE_ONLY}) + public @interface BufferFlags {} + /** + * Indicates that a buffer holds a synchronization sample. + */ + @SuppressWarnings("InlinedApi") + public static final int BUFFER_FLAG_KEY_FRAME = MediaCodec.BUFFER_FLAG_KEY_FRAME; + /** + * Flag for empty buffers that signal that the end of the stream was reached. + */ + @SuppressWarnings("InlinedApi") + public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM; + /** + * Indicates that a buffer is (at least partially) encrypted. + */ + public static final int BUFFER_FLAG_ENCRYPTED = 0x40000000; + /** + * Indicates that a buffer should be decoded but not rendered. + */ + public static final int BUFFER_FLAG_DECODE_ONLY = 0x80000000; + + /** + * Video scaling modes for {@link MediaCodec}-based {@link Renderer}s. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {VIDEO_SCALING_MODE_SCALE_TO_FIT, VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) + public @interface VideoScalingMode {} + /** + * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT + */ + @SuppressWarnings("InlinedApi") + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = + MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT; + /** + * @see MediaCodec#VIDEO_SCALING_MODE_SCALE_TO_FIT + */ + @SuppressWarnings("InlinedApi") + public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = + MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING; + /** + * A default video scaling mode for {@link MediaCodec}-based {@link Renderer}s. + */ + public static final int VIDEO_SCALING_MODE_DEFAULT = VIDEO_SCALING_MODE_SCALE_TO_FIT; + + /** + * Track selection flags. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {SELECTION_FLAG_DEFAULT, SELECTION_FLAG_FORCED, + SELECTION_FLAG_AUTOSELECT}) + public @interface SelectionFlags {} + /** + * Indicates that the track should be selected if user preferences do not state otherwise. + */ + public static final int SELECTION_FLAG_DEFAULT = 1; + /** + * Indicates that the track must be displayed. Only applies to text tracks. + */ + public static final int SELECTION_FLAG_FORCED = 2; + /** + * Indicates that the player may choose to play the track in absence of an explicit user + * preference. + */ + public static final int SELECTION_FLAG_AUTOSELECT = 4; + + /** + * Represents a streaming or other media type. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_OTHER}) + public @interface ContentType {} + /** + * Value returned by {@link Util#inferContentType(String)} for DASH manifests. + */ + public static final int TYPE_DASH = 0; + /** + * Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. + */ + public static final int TYPE_SS = 1; + /** + * Value returned by {@link Util#inferContentType(String)} for HLS manifests. + */ + public static final int TYPE_HLS = 2; + /** + * Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or + * Smooth Streaming manifests. + */ + public static final int TYPE_OTHER = 3; + + /** + * A return value for methods where the end of an input was encountered. + */ + public static final int RESULT_END_OF_INPUT = -1; + /** + * A return value for methods where the length of parsed data exceeds the maximum length allowed. + */ + public static final int RESULT_MAX_LENGTH_EXCEEDED = -2; + /** + * A return value for methods where nothing was read. + */ + public static final int RESULT_NOTHING_READ = -3; + /** + * A return value for methods where a buffer was read. + */ + public static final int RESULT_BUFFER_READ = -4; + /** + * A return value for methods where a format was read. + */ + public static final int RESULT_FORMAT_READ = -5; + + /** + * A data type constant for data of unknown or unspecified type. + */ + public static final int DATA_TYPE_UNKNOWN = 0; + /** + * A data type constant for media, typically containing media samples. + */ + public static final int DATA_TYPE_MEDIA = 1; + /** + * A data type constant for media, typically containing only initialization data. + */ + public static final int DATA_TYPE_MEDIA_INITIALIZATION = 2; + /** + * A data type constant for drm or encryption data. + */ + public static final int DATA_TYPE_DRM = 3; + /** + * A data type constant for a manifest file. + */ + public static final int DATA_TYPE_MANIFEST = 4; + /** + * A data type constant for time synchronization data. + */ + public static final int DATA_TYPE_TIME_SYNCHRONIZATION = 5; + /** + * Applications or extensions may define custom {@code DATA_TYPE_*} constants greater than or + * equal to this value. + */ + public static final int DATA_TYPE_CUSTOM_BASE = 10000; + + /** + * A type constant for tracks of unknown type. + */ + public static final int TRACK_TYPE_UNKNOWN = -1; + /** + * A type constant for tracks of some default type, where the type itself is unknown. + */ + public static final int TRACK_TYPE_DEFAULT = 0; + /** + * A type constant for audio tracks. + */ + public static final int TRACK_TYPE_AUDIO = 1; + /** + * A type constant for video tracks. + */ + public static final int TRACK_TYPE_VIDEO = 2; + /** + * A type constant for text tracks. + */ + public static final int TRACK_TYPE_TEXT = 3; + /** + * A type constant for metadata tracks. + */ + public static final int TRACK_TYPE_METADATA = 4; + /** + * Applications or extensions may define custom {@code TRACK_TYPE_*} constants greater than or + * equal to this value. + */ + public static final int TRACK_TYPE_CUSTOM_BASE = 10000; + + /** + * A selection reason constant for selections whose reasons are unknown or unspecified. + */ + public static final int SELECTION_REASON_UNKNOWN = 0; + /** + * A selection reason constant for an initial track selection. + */ + public static final int SELECTION_REASON_INITIAL = 1; + /** + * A selection reason constant for an manual (i.e. user initiated) track selection. + */ + public static final int SELECTION_REASON_MANUAL = 2; + /** + * A selection reason constant for an adaptive track selection. + */ + public static final int SELECTION_REASON_ADAPTIVE = 3; + /** + * A selection reason constant for a trick play track selection. + */ + public static final int SELECTION_REASON_TRICK_PLAY = 4; + /** + * Applications or extensions may define custom {@code SELECTION_REASON_*} constants greater than + * or equal to this value. + */ + public static final int SELECTION_REASON_CUSTOM_BASE = 10000; + + /** + * A default size in bytes for an individual allocation that forms part of a larger buffer. + */ + public static final int DEFAULT_BUFFER_SEGMENT_SIZE = 64 * 1024; + + /** + * A default size in bytes for a video buffer. + */ + public static final int DEFAULT_VIDEO_BUFFER_SIZE = 200 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for an audio buffer. + */ + public static final int DEFAULT_AUDIO_BUFFER_SIZE = 54 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a text buffer. + */ + public static final int DEFAULT_TEXT_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a metadata buffer. + */ + public static final int DEFAULT_METADATA_BUFFER_SIZE = 2 * DEFAULT_BUFFER_SEGMENT_SIZE; + + /** + * A default size in bytes for a muxed buffer (e.g. containing video, audio and text). + */ + public static final int DEFAULT_MUXED_BUFFER_SIZE = DEFAULT_VIDEO_BUFFER_SIZE + + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE; + + /** + * The Nil UUID as defined by + * RFC4122. + */ + public static final UUID UUID_NIL = new UUID(0L, 0L); + + /** + * UUID for the Widevine DRM scheme. + *

        + * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up. + */ + public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL); + + /** + * UUID for the PlayReady DRM scheme. + *

        + * PlayReady is supported on all AndroidTV devices. Note that most other Android devices do not + * provide PlayReady support. + */ + public static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L); + + /** + * The type of a message that can be passed to a video {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be the target {@link Surface}, or null. + */ + public static final int MSG_SET_SURFACE = 1; + + /** + * A type of a message that can be passed to an audio {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be a {@link Float} with 0 being silence and 1 being unity gain. + */ + public static final int MSG_SET_VOLUME = 2; + + /** + * A type of a message that can be passed to an audio {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be a {@link android.media.PlaybackParams}, or null, which will be used to configure the + * underlying {@link android.media.AudioTrack}. The message object should not be modified by the + * caller after it has been passed + */ + public static final int MSG_SET_PLAYBACK_PARAMS = 3; + + /** + * A type of a message that can be passed to an audio {@link Renderer} via + * {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message object + * should be one of the integer stream types in {@link C.StreamType}, and will specify the stream + * type of the underlying {@link android.media.AudioTrack}. See also + * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}. If the stream type + * is not set, audio renderers use {@link #STREAM_TYPE_DEFAULT}. + *

        + * Note that when the stream type changes, the AudioTrack must be reinitialized, which can + * introduce a brief gap in audio output. Note also that tracks in the same audio session must + * share the same routing, so a new audio session id will be generated. + */ + public static final int MSG_SET_STREAM_TYPE = 4; + + /** + * The type of a message that can be passed to a {@link MediaCodec}-based video {@link Renderer} + * via {@link ExoPlayer#sendMessages} or {@link ExoPlayer#blockingSendMessages}. The message + * object should be one of the integer scaling modes in {@link C.VideoScalingMode}. + *

        + * Note that the scaling mode only applies if the {@link Surface} targeted by the renderer is + * owned by a {@link android.view.SurfaceView}. + */ + public static final int MSG_SET_SCALING_MODE = 5; + + /** + * Applications or extensions may define custom {@code MSG_*} constants greater than or equal to + * this value. + */ + public static final int MSG_CUSTOM_BASE = 10000; + + /** + * The stereo mode for 360/3D/VR videos. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({Format.NO_VALUE, STEREO_MODE_MONO, STEREO_MODE_TOP_BOTTOM, STEREO_MODE_LEFT_RIGHT}) + public @interface StereoMode {} + /** + * Indicates Monoscopic stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_MONO = 0; + /** + * Indicates Top-Bottom stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_TOP_BOTTOM = 1; + /** + * Indicates Left-Right stereo layout, used with 360/3D/VR videos. + */ + public static final int STEREO_MODE_LEFT_RIGHT = 2; + + /** + * Converts a time in microseconds to the corresponding time in milliseconds, preserving + * {@link #TIME_UNSET} values. + * + * @param timeUs The time in microseconds. + * @return The corresponding time in milliseconds. + */ + public static long usToMs(long timeUs) { + return timeUs == TIME_UNSET ? TIME_UNSET : (timeUs / 1000); + } + + /** + * Converts a time in milliseconds to the corresponding time in microseconds, preserving + * {@link #TIME_UNSET} values. + * + * @param timeMs The time in milliseconds. + * @return The corresponding time in microseconds. + */ + public static long msToUs(long timeMs) { + return timeMs == TIME_UNSET ? TIME_UNSET : (timeMs * 1000); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/DefaultLoadControl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/DefaultLoadControl.java new file mode 100755 index 00000000000..1293e95bb10 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/DefaultLoadControl.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DefaultAllocator; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * The default {@link LoadControl} implementation. + */ +public final class DefaultLoadControl implements LoadControl { + + /** + * The default minimum duration of media that the player will attempt to ensure is buffered at all + * times, in milliseconds. + */ + public static final int DEFAULT_MIN_BUFFER_MS = 15000; + + /** + * The default maximum duration of media that the player will attempt to buffer, in milliseconds. + */ + public static final int DEFAULT_MAX_BUFFER_MS = 30000; + + /** + * The default duration of media that must be buffered for playback to start or resume following a + * user action such as a seek, in milliseconds. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500; + + /** + * The default duration of media that must be buffered for playback to resume after a rebuffer, + * in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user + * action. + */ + public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000; + + private static final int ABOVE_HIGH_WATERMARK = 0; + private static final int BETWEEN_WATERMARKS = 1; + private static final int BELOW_LOW_WATERMARK = 2; + + private final DefaultAllocator allocator; + + private final long minBufferUs; + private final long maxBufferUs; + private final long bufferForPlaybackUs; + private final long bufferForPlaybackAfterRebufferUs; + + private int targetBufferSize; + private boolean isBuffering; + + /** + * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. + */ + public DefaultLoadControl() { + this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE)); + } + + /** + * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. + * + * @param allocator The {@link DefaultAllocator} used by the loader. + */ + public DefaultLoadControl(DefaultAllocator allocator) { + this(allocator, DEFAULT_MIN_BUFFER_MS, DEFAULT_MAX_BUFFER_MS, DEFAULT_BUFFER_FOR_PLAYBACK_MS, + DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS); + } + + /** + * Constructs a new instance. + * + * @param allocator The {@link DefaultAllocator} used by the loader. + * @param minBufferMs The minimum duration of media that the player will attempt to ensure is + * buffered at all times, in milliseconds. + * @param maxBufferMs The maximum duration of media that the player will attempt buffer, in + * milliseconds. + * @param bufferForPlaybackMs The duration of media that must be buffered for playback to start or + * resume following a user action such as a seek, in milliseconds. + * @param bufferForPlaybackAfterRebufferMs The default duration of media that must be buffered for + * playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by + * buffer depletion rather than a user action. + */ + public DefaultLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, + long bufferForPlaybackMs, long bufferForPlaybackAfterRebufferMs) { + this.allocator = allocator; + minBufferUs = minBufferMs * 1000L; + maxBufferUs = maxBufferMs * 1000L; + bufferForPlaybackUs = bufferForPlaybackMs * 1000L; + bufferForPlaybackAfterRebufferUs = bufferForPlaybackAfterRebufferMs * 1000L; + } + + @Override + public void onPrepared() { + reset(false); + } + + @Override + public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections) { + targetBufferSize = 0; + for (int i = 0; i < renderers.length; i++) { + if (trackSelections.get(i) != null) { + targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType()); + } + } + allocator.setTargetBufferSize(targetBufferSize); + } + + @Override + public void onStopped() { + reset(true); + } + + @Override + public void onReleased() { + reset(true); + } + + @Override + public Allocator getAllocator() { + return allocator; + } + + @Override + public boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering) { + long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs; + return minBufferDurationUs <= 0 || bufferedDurationUs >= minBufferDurationUs; + } + + @Override + public boolean shouldContinueLoading(long bufferedDurationUs) { + int bufferTimeState = getBufferTimeState(bufferedDurationUs); + boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize; + isBuffering = bufferTimeState == BELOW_LOW_WATERMARK + || (bufferTimeState == BETWEEN_WATERMARKS && isBuffering && !targetBufferSizeReached); + return isBuffering; + } + + private int getBufferTimeState(long bufferedDurationUs) { + return bufferedDurationUs > maxBufferUs ? ABOVE_HIGH_WATERMARK + : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS); + } + + private void reset(boolean resetAllocator) { + targetBufferSize = 0; + isBuffering = false; + if (resetAllocator) { + allocator.reset(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlaybackException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlaybackException.java new file mode 100755 index 00000000000..391b6c4157c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlaybackException.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when a non-recoverable playback failure occurs. + */ +public final class ExoPlaybackException extends Exception { + + /** + * The type of source that produced the error. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_SOURCE, TYPE_RENDERER, TYPE_UNEXPECTED}) + public @interface Type {} + /** + * The error occurred loading data from a {@link MediaSource}. + *

        + * Call {@link #getSourceException()} to retrieve the underlying cause. + */ + public static final int TYPE_SOURCE = 0; + /** + * The error occurred in a {@link Renderer}. + *

        + * Call {@link #getRendererException()} to retrieve the underlying cause. + */ + public static final int TYPE_RENDERER = 1; + /** + * The error was an unexpected {@link RuntimeException}. + *

        + * Call {@link #getUnexpectedException()} to retrieve the underlying cause. + */ + public static final int TYPE_UNEXPECTED = 2; + + /** + * The type of the playback failure. One of {@link #TYPE_SOURCE}, {@link #TYPE_RENDERER} and + * {@link #TYPE_UNEXPECTED}. + */ + @Type + public final int type; + + /** + * If {@link #type} is {@link #TYPE_RENDERER}, this is the index of the renderer. + */ + public final int rendererIndex; + + /** + * Creates an instance of type {@link #TYPE_RENDERER}. + * + * @param cause The cause of the failure. + * @param rendererIndex The index of the renderer in which the failure occurred. + * @return The created instance. + */ + public static ExoPlaybackException createForRenderer(Exception cause, int rendererIndex) { + return new ExoPlaybackException(TYPE_RENDERER, null, cause, rendererIndex); + } + + /** + * Creates an instance of type {@link #TYPE_SOURCE}. + * + * @param cause The cause of the failure. + * @return The created instance. + */ + public static ExoPlaybackException createForSource(IOException cause) { + return new ExoPlaybackException(TYPE_SOURCE, null, cause, C.INDEX_UNSET); + } + + /** + * Creates an instance of type {@link #TYPE_UNEXPECTED}. + * + * @param cause The cause of the failure. + * @return The created instance. + */ + /* package */ static ExoPlaybackException createForUnexpected(RuntimeException cause) { + return new ExoPlaybackException(TYPE_UNEXPECTED, null, cause, C.INDEX_UNSET); + } + + private ExoPlaybackException(@Type int type, String message, Throwable cause, + int rendererIndex) { + super(message, cause); + this.type = type; + this.rendererIndex = rendererIndex; + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_SOURCE}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_SOURCE}. + */ + public IOException getSourceException() { + Assertions.checkState(type == TYPE_SOURCE); + return (IOException) getCause(); + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_RENDERER}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_RENDERER}. + */ + public Exception getRendererException() { + Assertions.checkState(type == TYPE_RENDERER); + return (Exception) getCause(); + } + + /** + * Retrieves the underlying error when {@link #type} is {@link #TYPE_UNEXPECTED}. + * + * @throws IllegalStateException If {@link #type} is not {@link #TYPE_UNEXPECTED}. + */ + public RuntimeException getUnexpectedException() { + Assertions.checkState(type == TYPE_UNEXPECTED); + return (RuntimeException) getCause(); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java new file mode 100755 index 00000000000..d85ff79a254 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayer.java @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer; +import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer; +import org.telegram.messenger.exoplayer2.source.ConcatenatingMediaSource; +import org.telegram.messenger.exoplayer2.source.ExtractorMediaSource; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.MergingMediaSource; +import org.telegram.messenger.exoplayer2.source.SingleSampleMediaSource; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.dash.DashMediaSource; +import org.telegram.messenger.exoplayer2.source.hls.HlsMediaSource; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.SsMediaSource; +import org.telegram.messenger.exoplayer2.text.TextRenderer; +import org.telegram.messenger.exoplayer2.trackselection.DefaultTrackSelector; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; + +/** + * An extensible media player exposing traditional high-level media player functionality, such as + * the ability to buffer media, play, pause and seek. Instances can be obtained from + * {@link ExoPlayerFactory}. + * + *

        Player composition

        + *

        ExoPlayer is designed to make few assumptions about (and hence impose few restrictions on) the + * type of the media being played, how and where it is stored, and how it is rendered. Rather than + * implementing the loading and rendering of media directly, ExoPlayer implementations delegate this + * work to components that are injected when a player is created or when it's prepared for playback. + * Components common to all ExoPlayer implementations are: + *

          + *
        • A {@link MediaSource} that defines the media to be played, loads the media, and from + * which the loaded media can be read. A MediaSource is injected via {@link #prepare} at the start + * of playback. The library provides default implementations for regular media files + * ({@link ExtractorMediaSource}), DASH ({@link DashMediaSource}), SmoothStreaming + * ({@link SsMediaSource}) and HLS ({@link HlsMediaSource}), implementations for merging + * ({@link MergingMediaSource}) and concatenating ({@link ConcatenatingMediaSource}) other + * MediaSources, and an implementation for loading single samples + * ({@link SingleSampleMediaSource}) most often used for side-loaded subtitle and closed + * caption files.
        • + *
        • {@link Renderer}s that render individual components of the media. The library + * provides default implementations for common media types ({@link MediaCodecVideoRenderer}, + * {@link MediaCodecAudioRenderer}, {@link TextRenderer} and {@link MetadataRenderer}). A Renderer + * consumes media of its corresponding type from the MediaSource being played. Renderers are + * injected when the player is created.
        • + *
        • A {@link TrackSelector} that selects tracks provided by the MediaSource to be + * consumed by each of the available Renderers. The library provides a default implementation + * ({@link DefaultTrackSelector}) suitable for most use cases. A TrackSelector is injected when + * the player is created.
        • + *
        • A {@link LoadControl} that controls when the MediaSource buffers more media, and how + * much media is buffered. The library provides a default implementation + * ({@link DefaultLoadControl}) suitable for most use cases. A LoadControl is injected when the + * player is created.
        • + *
        + *

        An ExoPlayer can be built using the default components provided by the library, but may also + * be built using custom implementations if non-standard behaviors are required. For example a + * custom LoadControl could be injected to change the player's buffering strategy, or a custom + * Renderer could be injected to use a video codec not supported natively by Android. + * + *

        The concept of injecting components that implement pieces of player functionality is present + * throughout the library. The default component implementations listed above delegate work to + * further injected components. This allows many sub-components to be individually replaced with + * custom implementations. For example the default MediaSource implementations require one or more + * {@link DataSource} factories to be injected via their constructors. By providing a custom factory + * it's possible to load data from a non-standard source or through a different network stack. + * + *

        Threading model

        + *

        The figure below shows ExoPlayer's threading model.

        + *

        + * ExoPlayer's threading model + *

        + * + *
          + *
        • It is recommended that ExoPlayer instances are created and accessed from a single application + * thread. The application's main thread is ideal. Accessing an instance from multiple threads is + * discouraged, however if an application does wish to do this then it may do so provided that it + * ensures accesses are synchronized.
        • + *
        • Registered listeners are called on the thread that created the ExoPlayer instance.
        • + *
        • An internal playback thread is responsible for playback. Injected player components such as + * Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this + * thread.
        • + *
        • When the application performs an operation on the player, for example a seek, a message is + * delivered to the internal playback thread via a message queue. The internal playback thread + * consumes messages from the queue and performs the corresponding operations. Similarly, when a + * playback event occurs on the internal playback thread, a message is delivered to the application + * thread via a second message queue. The application thread consumes messages from the queue, + * updating the application visible state and calling corresponding listener methods.
        • + *
        • Injected player components may use additional background threads. For example a MediaSource + * may use a background thread to load data. These are implementation specific.
        • + *
        + */ +public interface ExoPlayer { + + /** + * Listener of changes in player state. + */ + interface EventListener { + + /** + * Called when the timeline and/or manifest has been refreshed. + *

        + * Note that if the timeline has changed then a position discontinuity may also have occurred. + * For example the current period index may have changed as a result of periods being added or + * removed from the timeline. The will not be reported via a separate call to + * {@link #onPositionDiscontinuity()}. + * + * @param timeline The latest timeline, or null if the timeline is being cleared. + * @param manifest The latest manifest, or null if the manifest is being cleared. + */ + void onTimelineChanged(Timeline timeline, Object manifest); + + /** + * Called when the available or selected tracks change. + * + * @param trackGroups The available tracks. Never null, but may be of length zero. + * @param trackSelections The track selections for each {@link Renderer}. Never null and always + * of length {@link #getRendererCount()}, but may contain null elements. + */ + void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections); + + /** + * Called when the player starts or stops loading the source. + * + * @param isLoading Whether the source is currently being loaded. + */ + void onLoadingChanged(boolean isLoading); + + /** + * Called when the value returned from either {@link #getPlayWhenReady()} or + * {@link #getPlaybackState()} changes. + * + * @param playWhenReady Whether playback will proceed when ready. + * @param playbackState One of the {@code STATE} constants defined in the {@link ExoPlayer} + * interface. + */ + void onPlayerStateChanged(boolean playWhenReady, int playbackState); + + /** + * Called when an error occurs. The playback state will transition to {@link #STATE_IDLE} + * immediately after this method is called. The player instance can still be used, and + * {@link #release()} must still be called on the player should it no longer be required. + * + * @param error The error. + */ + void onPlayerError(ExoPlaybackException error); + + /** + * Called when a position discontinuity occurs without a change to the timeline. A position + * discontinuity occurs when the current window or period index changes (as a result of playback + * transitioning from one period in the timeline to the next), or when the playback position + * jumps within the period currently being played (as a result of a seek being performed, or + * when the source introduces a discontinuity internally). + *

        + * When a position discontinuity occurs as a result of a change to the timeline this method is + * not called. {@link #onTimelineChanged(Timeline, Object)} is called in this case. + */ + void onPositionDiscontinuity(); + + } + + /** + * A component of an {@link ExoPlayer} that can receive messages on the playback thread. + *

        + * Messages can be delivered to a component via {@link #sendMessages} and + * {@link #blockingSendMessages}. + */ + interface ExoPlayerComponent { + + /** + * Handles a message delivered to the component. Called on the playback thread. + * + * @param messageType The message type. + * @param message The message. + * @throws ExoPlaybackException If an error occurred whilst handling the message. + */ + void handleMessage(int messageType, Object message) throws ExoPlaybackException; + + } + + /** + * Defines a message and a target {@link ExoPlayerComponent} to receive it. + */ + final class ExoPlayerMessage { + + /** + * The target to receive the message. + */ + public final ExoPlayerComponent target; + /** + * The type of the message. + */ + public final int messageType; + /** + * The message. + */ + public final Object message; + + /** + * @param target The target of the message. + * @param messageType The message type. + * @param message The message. + */ + public ExoPlayerMessage(ExoPlayerComponent target, int messageType, Object message) { + this.target = target; + this.messageType = messageType; + this.message = message; + } + + } + + /** + * The player does not have a source to play, so it is neither buffering nor ready to play. + */ + int STATE_IDLE = 1; + /** + * The player not able to immediately play from the current position. The cause is + * {@link Renderer} specific, but this state typically occurs when more data needs to be + * loaded to be ready to play, or more data needs to be buffered for playback to resume. + */ + int STATE_BUFFERING = 2; + /** + * The player is able to immediately play from the current position. The player will be playing if + * {@link #getPlayWhenReady()} returns true, and paused otherwise. + */ + int STATE_READY = 3; + /** + * The player has finished playing the media. + */ + int STATE_ENDED = 4; + + /** + * Register a listener to receive events from the player. The listener's methods will be called on + * the thread that was used to construct the player. + * + * @param listener The listener to register. + */ + void addListener(EventListener listener); + + /** + * Unregister a listener. The listener will no longer receive events from the player. + * + * @param listener The listener to unregister. + */ + void removeListener(EventListener listener); + + /** + * Returns the current state of the player. + * + * @return One of the {@code STATE} constants defined in this interface. + */ + int getPlaybackState(); + + /** + * Prepares the player to play the provided {@link MediaSource}. Equivalent to + * {@code prepare(mediaSource, true, true)}. + */ + void prepare(MediaSource mediaSource); + + /** + * Prepares the player to play the provided {@link MediaSource}, optionally resetting the playback + * position the default position in the first {@link Timeline.Window}. + * + * @param mediaSource The {@link MediaSource} to play. + * @param resetPosition Whether the playback position should be reset to the default position in + * the first {@link Timeline.Window}. If false, playback will start from the position defined + * by {@link #getCurrentWindowIndex()} and {@link #getCurrentPosition()}. + * @param resetState Whether the timeline, manifest, tracks and track selections should be reset. + * Should be true unless the player is being prepared to play the same media as it was playing + * previously (e.g. if playback failed and is being retried). + */ + void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState); + + /** + * Sets whether playback should proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + *

        + * If the player is already in the ready state then this method can be used to pause and resume + * playback. + * + * @param playWhenReady Whether playback should proceed when ready. + */ + void setPlayWhenReady(boolean playWhenReady); + + /** + * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. + * + * @return Whether playback will proceed when ready. + */ + boolean getPlayWhenReady(); + + /** + * Whether the player is currently loading the source. + * + * @return Whether the player is currently loading the source. + */ + boolean isLoading(); + + /** + * Seeks to the default position associated with the current window. The position can depend on + * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically + * be the live edge of the window. For other streams it will typically be the start of the window. + */ + void seekToDefaultPosition(); + + /** + * Seeks to the default position associated with the specified window. The position can depend on + * the type of source passed to {@link #prepare(MediaSource)}. For live streams it will typically + * be the live edge of the window. For other streams it will typically be the start of the window. + * + * @param windowIndex The index of the window whose associated default position should be seeked + * to. + */ + void seekToDefaultPosition(int windowIndex); + + /** + * Seeks to a position specified in milliseconds in the current window. + * + * @param windowPositionMs The seek position in the current window. + */ + void seekTo(long windowPositionMs); + + /** + * Seeks to a position specified in milliseconds in the specified window. + * + * @param windowIndex The index of the window. + * @param windowPositionMs The seek position in the specified window. + */ + void seekTo(int windowIndex, long windowPositionMs); + + /** + * Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention + * is to pause playback. + *

        + * Calling this method will cause the playback state to transition to {@link #STATE_IDLE}. The + * player instance can still be used, and {@link #release()} must still be called on the player if + * it's no longer required. + *

        + * Calling this method does not reset the playback position. + */ + void stop(); + + /** + * Releases the player. This method must be called when the player is no longer required. The + * player must not be used after calling this method. + */ + void release(); + + /** + * Sends messages to their target components. The messages are delivered on the playback thread. + * If a component throws an {@link ExoPlaybackException} then it is propagated out of the player + * as an error. + * + * @param messages The messages to be sent. + */ + void sendMessages(ExoPlayerMessage... messages); + + /** + * Variant of {@link #sendMessages(ExoPlayerMessage...)} that blocks until after the messages have + * been delivered. + * + * @param messages The messages to be sent. + */ + void blockingSendMessages(ExoPlayerMessage... messages); + + /** + * Returns the number of renderers. + */ + int getRendererCount(); + + /** + * Returns the track type that the renderer at a given index handles. + * + * @see Renderer#getTrackType() + * @param index The index of the renderer. + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getRendererType(int index); + + /** + * Returns the available track groups. + */ + TrackGroupArray getCurrentTrackGroups(); + + /** + * Returns the current track selections for each renderer. + */ + TrackSelectionArray getCurrentTrackSelections(); + + /** + * Returns the current manifest. The type depends on the {@link MediaSource} passed to + * {@link #prepare}. May be null. + */ + Object getCurrentManifest(); + + /** + * Returns the current {@link Timeline}. Never null, but may be empty. + */ + Timeline getCurrentTimeline(); + + /** + * Returns the index of the period currently being played. + */ + int getCurrentPeriodIndex(); + + /** + * Returns the index of the window currently being played. + */ + int getCurrentWindowIndex(); + + /** + * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the + * duration is not known. + */ + long getDuration(); + + /** + * Returns the playback position in the current window, in milliseconds. + */ + long getCurrentPosition(); + + /** + * Returns an estimate of the position in the current window up to which data is buffered, in + * milliseconds. + */ + long getBufferedPosition(); + + /** + * Returns an estimate of the percentage in the current window up to which data is buffered, or 0 + * if no estimate is available. + */ + int getBufferedPercentage(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java new file mode 100755 index 00000000000..1817fbb6920 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerFactory.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.content.Context; +import android.os.Looper; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; + +/** + * A factory for {@link ExoPlayer} instances. + */ +public final class ExoPlayerFactory { + + /** + * The default maximum duration for which a video renderer can attempt to seamlessly join an + * ongoing playback. + */ + public static final long DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS = 5000; + + private ExoPlayerFactory() {} + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl) { + return newSimpleInstance(context, trackSelector, loadControl, null); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. Available extension renderers are not used. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager) { + return newSimpleInstance(context, trackSelector, loadControl, + drmSessionManager, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode, which determines if and how available + * extension renderers are used. Note that extensions must be included in the application + * build for them to be considered available. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager, + @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode) { + return newSimpleInstance(context, trackSelector, loadControl, drmSessionManager, + extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); + } + + /** + * Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param context A {@link Context}. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the instance + * will not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode, which determines if and how available + * extension renderers are used. Note that extensions must be included in the application + * build for them to be considered available. + * @param allowedVideoJoiningTimeMs The maximum duration for which a video renderer can attempt to + * seamlessly join an ongoing playback. + */ + public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector, + LoadControl loadControl, DrmSessionManager drmSessionManager, + @SimpleExoPlayer.ExtensionRendererMode int extensionRendererMode, + long allowedVideoJoiningTimeMs) { + return new SimpleExoPlayer(context, trackSelector, loadControl, drmSessionManager, + extensionRendererMode, allowedVideoJoiningTimeMs); + } + + /** + * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + */ + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector) { + return newInstance(renderers, trackSelector, new DefaultLoadControl()); + } + + /** + * Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated + * {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + public static ExoPlayer newInstance(Renderer[] renderers, TrackSelector trackSelector, + LoadControl loadControl) { + return new ExoPlayerImpl(renderers, trackSelector, loadControl); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java new file mode 100755 index 00000000000..2a1ebd7f44b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImpl.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; +import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.PlaybackInfo; +import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.SourceInfo; +import org.telegram.messenger.exoplayer2.ExoPlayerImplInternal.TrackInfo; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * An {@link ExoPlayer} implementation. Instances can be obtained from {@link ExoPlayerFactory}. + */ +/* package */ final class ExoPlayerImpl implements ExoPlayer { + + private static final String TAG = "ExoPlayerImpl"; + + private final Renderer[] renderers; + private final TrackSelector trackSelector; + private final TrackSelectionArray emptyTrackSelections; + private final Handler eventHandler; + private final ExoPlayerImplInternal internalPlayer; + private final CopyOnWriteArraySet listeners; + private final Timeline.Window window; + private final Timeline.Period period; + + private boolean tracksSelected; + private boolean playWhenReady; + private int playbackState; + private int pendingSeekAcks; + private boolean isLoading; + private Timeline timeline; + private Object manifest; + private TrackGroupArray trackGroups; + private TrackSelectionArray trackSelections; + + // Playback information when there is no pending seek/set source operation. + private PlaybackInfo playbackInfo; + + // Playback information when there is a pending seek/set source operation. + private int maskingWindowIndex; + private long maskingWindowPositionMs; + + /** + * Constructs an instance. Must be called from a thread that has an associated {@link Looper}. + * + * @param renderers The {@link Renderer}s that will be used by the instance. + * @param trackSelector The {@link TrackSelector} that will be used by the instance. + * @param loadControl The {@link LoadControl} that will be used by the instance. + */ + @SuppressLint("HandlerLeak") + public ExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector, LoadControl loadControl) { + Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION + " [" + Util.DEVICE_DEBUG_INFO + "]"); + Assertions.checkState(renderers.length > 0); + this.renderers = Assertions.checkNotNull(renderers); + this.trackSelector = Assertions.checkNotNull(trackSelector); + this.playWhenReady = false; + this.playbackState = STATE_IDLE; + this.listeners = new CopyOnWriteArraySet<>(); + emptyTrackSelections = new TrackSelectionArray(new TrackSelection[renderers.length]); + timeline = Timeline.EMPTY; + window = new Timeline.Window(); + period = new Timeline.Period(); + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; + eventHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + ExoPlayerImpl.this.handleEvent(msg); + } + }; + playbackInfo = new ExoPlayerImplInternal.PlaybackInfo(0, 0); + internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, loadControl, playWhenReady, + eventHandler, playbackInfo); + } + + @Override + public void addListener(EventListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(EventListener listener) { + listeners.remove(listener); + } + + @Override + public int getPlaybackState() { + return playbackState; + } + + @Override + public void prepare(MediaSource mediaSource) { + prepare(mediaSource, true, true); + } + + @Override + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) { + if (resetState) { + if (!timeline.isEmpty() || manifest != null) { + timeline = Timeline.EMPTY; + manifest = null; + for (EventListener listener : listeners) { + listener.onTimelineChanged(null, null); + } + } + if (tracksSelected) { + tracksSelected = false; + trackGroups = TrackGroupArray.EMPTY; + trackSelections = emptyTrackSelections; + trackSelector.onSelectionActivated(null); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } + } + } + internalPlayer.prepare(mediaSource, resetPosition); + } + + @Override + public void setPlayWhenReady(boolean playWhenReady) { + if (this.playWhenReady != playWhenReady) { + this.playWhenReady = playWhenReady; + internalPlayer.setPlayWhenReady(playWhenReady); + for (EventListener listener : listeners) { + listener.onPlayerStateChanged(playWhenReady, playbackState); + } + } + } + + @Override + public boolean getPlayWhenReady() { + return playWhenReady; + } + + @Override + public boolean isLoading() { + return isLoading; + } + + @Override + public void seekToDefaultPosition() { + seekToDefaultPosition(getCurrentWindowIndex()); + } + + @Override + public void seekToDefaultPosition(int windowIndex) { + seekTo(windowIndex, C.TIME_UNSET); + } + + @Override + public void seekTo(long positionMs) { + seekTo(getCurrentWindowIndex(), positionMs); + } + + @Override + public void seekTo(int windowIndex, long positionMs) { + if (windowIndex < 0 || (!timeline.isEmpty() && windowIndex >= timeline.getWindowCount())) { + throw new IndexOutOfBoundsException(); + } + pendingSeekAcks++; + maskingWindowIndex = windowIndex; + if (positionMs == C.TIME_UNSET) { + maskingWindowPositionMs = 0; + internalPlayer.seekTo(timeline, windowIndex, C.TIME_UNSET); + } else { + maskingWindowPositionMs = positionMs; + internalPlayer.seekTo(timeline, windowIndex, C.msToUs(positionMs)); + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + } + + @Override + public void stop() { + internalPlayer.stop(); + } + + @Override + public void release() { + internalPlayer.release(); + eventHandler.removeCallbacksAndMessages(null); + } + + @Override + public void sendMessages(ExoPlayerMessage... messages) { + internalPlayer.sendMessages(messages); + } + + @Override + public void blockingSendMessages(ExoPlayerMessage... messages) { + internalPlayer.blockingSendMessages(messages); + } + + @Override + public int getCurrentPeriodIndex() { + return playbackInfo.periodIndex; + } + + @Override + public int getCurrentWindowIndex() { + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowIndex; + } else { + return timeline.getPeriod(playbackInfo.periodIndex, period).windowIndex; + } + } + + @Override + public long getDuration() { + if (timeline.isEmpty()) { + return C.TIME_UNSET; + } + return timeline.getWindow(getCurrentWindowIndex(), window).getDurationMs(); + } + + @Override + public long getCurrentPosition() { + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowPositionMs; + } else { + timeline.getPeriod(playbackInfo.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.positionUs); + } + } + + @Override + public long getBufferedPosition() { + // TODO - Implement this properly. + if (timeline.isEmpty() || pendingSeekAcks > 0) { + return maskingWindowPositionMs; + } else { + timeline.getPeriod(playbackInfo.periodIndex, period); + return period.getPositionInWindowMs() + C.usToMs(playbackInfo.bufferedPositionUs); + } + } + + @Override + public int getBufferedPercentage() { + if (timeline.isEmpty()) { + return 0; + } + long bufferedPosition = getBufferedPosition(); + long duration = getDuration(); + return (bufferedPosition == C.TIME_UNSET || duration == C.TIME_UNSET) ? 0 + : (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration); + } + + @Override + public int getRendererCount() { + return renderers.length; + } + + @Override + public int getRendererType(int index) { + return renderers[index].getTrackType(); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return trackGroups; + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return trackSelections; + } + + @Override + public Timeline getCurrentTimeline() { + return timeline; + } + + @Override + public Object getCurrentManifest() { + return manifest; + } + + // Not private so it can be called from an inner class without going through a thunk method. + /* package */ void handleEvent(Message msg) { + switch (msg.what) { + case ExoPlayerImplInternal.MSG_STATE_CHANGED: { + playbackState = msg.arg1; + for (EventListener listener : listeners) { + listener.onPlayerStateChanged(playWhenReady, playbackState); + } + break; + } + case ExoPlayerImplInternal.MSG_LOADING_CHANGED: { + isLoading = msg.arg1 != 0; + for (EventListener listener : listeners) { + listener.onLoadingChanged(isLoading); + } + break; + } + case ExoPlayerImplInternal.MSG_TRACKS_CHANGED: { + TrackInfo trackInfo = (TrackInfo) msg.obj; + tracksSelected = true; + trackGroups = trackInfo.groups; + trackSelections = trackInfo.selections; + trackSelector.onSelectionActivated(trackInfo.info); + for (EventListener listener : listeners) { + listener.onTracksChanged(trackGroups, trackSelections); + } + break; + } + case ExoPlayerImplInternal.MSG_SEEK_ACK: { + if (--pendingSeekAcks == 0) { + playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + break; + } + case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { + if (pendingSeekAcks == 0) { + playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj; + for (EventListener listener : listeners) { + listener.onPositionDiscontinuity(); + } + } + break; + } + case ExoPlayerImplInternal.MSG_SOURCE_INFO_REFRESHED: { + SourceInfo sourceInfo = (SourceInfo) msg.obj; + timeline = sourceInfo.timeline; + manifest = sourceInfo.manifest; + playbackInfo = sourceInfo.playbackInfo; + pendingSeekAcks -= sourceInfo.seekAcks; + for (EventListener listener : listeners) { + listener.onTimelineChanged(timeline, manifest); + } + break; + } + case ExoPlayerImplInternal.MSG_ERROR: { + ExoPlaybackException exception = (ExoPlaybackException) msg.obj; + for (EventListener listener : listeners) { + listener.onPlayerError(exception); + } + break; + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java new file mode 100755 index 00000000000..3cc2c5a7eee --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerImplInternal.java @@ -0,0 +1,1465 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerMessage; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MediaClock; +import org.telegram.messenger.exoplayer2.util.PriorityHandlerThread; +import org.telegram.messenger.exoplayer2.util.StandaloneMediaClock; +import org.telegram.messenger.exoplayer2.util.TraceUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Implements the internal behavior of {@link ExoPlayerImpl}. + */ +/* package */ final class ExoPlayerImplInternal implements Handler.Callback, + MediaPeriod.Callback, TrackSelector.InvalidationListener, MediaSource.Listener { + + /** + * Playback position information which is read on the application's thread by + * {@link ExoPlayerImpl} and read/written internally on the player's thread. + */ + public static final class PlaybackInfo { + + public final int periodIndex; + public final long startPositionUs; + + public volatile long positionUs; + public volatile long bufferedPositionUs; + + public PlaybackInfo(int periodIndex, long startPositionUs) { + this.periodIndex = periodIndex; + this.startPositionUs = startPositionUs; + positionUs = startPositionUs; + bufferedPositionUs = startPositionUs; + } + + public PlaybackInfo copyWithPeriodIndex(int periodIndex) { + PlaybackInfo playbackInfo = new PlaybackInfo(periodIndex, startPositionUs); + playbackInfo.positionUs = positionUs; + playbackInfo.bufferedPositionUs = bufferedPositionUs; + return playbackInfo; + } + + } + + public static final class TrackInfo { + + public final TrackGroupArray groups; + public final TrackSelectionArray selections; + public final Object info; + + public TrackInfo(TrackGroupArray groups, TrackSelectionArray selections, Object info) { + this.groups = groups; + this.selections = selections; + this.info = info; + } + + } + + public static final class SourceInfo { + + public final Timeline timeline; + public final Object manifest; + public final PlaybackInfo playbackInfo; + public final int seekAcks; + + public SourceInfo(Timeline timeline, Object manifest, PlaybackInfo playbackInfo, int seekAcks) { + this.timeline = timeline; + this.manifest = manifest; + this.playbackInfo = playbackInfo; + this.seekAcks = seekAcks; + } + + } + + private static final String TAG = "ExoPlayerImplInternal"; + + // External messages + public static final int MSG_STATE_CHANGED = 1; + public static final int MSG_LOADING_CHANGED = 2; + public static final int MSG_TRACKS_CHANGED = 3; + public static final int MSG_SEEK_ACK = 4; + public static final int MSG_POSITION_DISCONTINUITY = 5; + public static final int MSG_SOURCE_INFO_REFRESHED = 6; + public static final int MSG_ERROR = 7; + + // Internal messages + private static final int MSG_PREPARE = 0; + private static final int MSG_SET_PLAY_WHEN_READY = 1; + private static final int MSG_DO_SOME_WORK = 2; + private static final int MSG_SEEK_TO = 3; + private static final int MSG_STOP = 4; + private static final int MSG_RELEASE = 5; + private static final int MSG_REFRESH_SOURCE_INFO = 6; + private static final int MSG_PERIOD_PREPARED = 7; + private static final int MSG_SOURCE_CONTINUE_LOADING_REQUESTED = 8; + private static final int MSG_TRACK_SELECTION_INVALIDATED = 9; + private static final int MSG_CUSTOM = 10; + + private static final int PREPARING_SOURCE_INTERVAL_MS = 10; + private static final int RENDERING_INTERVAL_MS = 10; + private static final int IDLE_INTERVAL_MS = 1000; + + /** + * Limits the maximum number of periods to buffer ahead of the current playing period. The + * buffering policy normally prevents buffering too far ahead, but the policy could allow too many + * small periods to be buffered if the period count were not limited. + */ + private static final int MAXIMUM_BUFFER_AHEAD_PERIODS = 100; + + private final Renderer[] renderers; + private final RendererCapabilities[] rendererCapabilities; + private final TrackSelector trackSelector; + private final LoadControl loadControl; + private final StandaloneMediaClock standaloneMediaClock; + private final Handler handler; + private final HandlerThread internalPlaybackThread; + private final Handler eventHandler; + private final Timeline.Window window; + private final Timeline.Period period; + + private PlaybackInfo playbackInfo; + private Renderer rendererMediaClockSource; + private MediaClock rendererMediaClock; + private MediaSource mediaSource; + private Renderer[] enabledRenderers; + private boolean released; + private boolean playWhenReady; + private boolean rebuffering; + private boolean isLoading; + private int state; + private int customMessagesSent; + private int customMessagesProcessed; + private long elapsedRealtimeUs; + + private int pendingInitialSeekCount; + private SeekPosition pendingSeekPosition; + private long rendererPositionUs; + + private MediaPeriodHolder loadingPeriodHolder; + private MediaPeriodHolder readingPeriodHolder; + private MediaPeriodHolder playingPeriodHolder; + + private Timeline timeline; + + public ExoPlayerImplInternal(Renderer[] renderers, TrackSelector trackSelector, + LoadControl loadControl, boolean playWhenReady, Handler eventHandler, + PlaybackInfo playbackInfo) { + this.renderers = renderers; + this.trackSelector = trackSelector; + this.loadControl = loadControl; + this.playWhenReady = playWhenReady; + this.eventHandler = eventHandler; + this.state = ExoPlayer.STATE_IDLE; + this.playbackInfo = playbackInfo; + + rendererCapabilities = new RendererCapabilities[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + renderers[i].setIndex(i); + rendererCapabilities[i] = renderers[i].getCapabilities(); + } + standaloneMediaClock = new StandaloneMediaClock(); + enabledRenderers = new Renderer[0]; + window = new Timeline.Window(); + period = new Timeline.Period(); + trackSelector.init(this); + + // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can + // not normally change to this priority" is incorrect. + internalPlaybackThread = new PriorityHandlerThread("ExoPlayerImplInternal:Handler", + Process.THREAD_PRIORITY_AUDIO); + internalPlaybackThread.start(); + handler = new Handler(internalPlaybackThread.getLooper(), this); + } + + public void prepare(MediaSource mediaSource, boolean resetPosition) { + handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource) + .sendToTarget(); + } + + public void setPlayWhenReady(boolean playWhenReady) { + handler.obtainMessage(MSG_SET_PLAY_WHEN_READY, playWhenReady ? 1 : 0, 0).sendToTarget(); + } + + public void seekTo(Timeline timeline, int windowIndex, long positionUs) { + handler.obtainMessage(MSG_SEEK_TO, new SeekPosition(timeline, windowIndex, positionUs)) + .sendToTarget(); + } + + public void stop() { + handler.sendEmptyMessage(MSG_STOP); + } + + public void sendMessages(ExoPlayerMessage... messages) { + if (released) { + Log.w(TAG, "Ignoring messages sent after release."); + return; + } + customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + } + + public synchronized void blockingSendMessages(ExoPlayerMessage... messages) { + if (released) { + Log.w(TAG, "Ignoring messages sent after release."); + return; + } + int messageNumber = customMessagesSent++; + handler.obtainMessage(MSG_CUSTOM, messages).sendToTarget(); + while (customMessagesProcessed <= messageNumber) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + + public synchronized void release() { + if (released) { + return; + } + handler.sendEmptyMessage(MSG_RELEASE); + while (!released) { + try { + wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + internalPlaybackThread.quit(); + } + + // MediaSource.Listener implementation. + + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handler.obtainMessage(MSG_REFRESH_SOURCE_INFO, Pair.create(timeline, manifest)).sendToTarget(); + } + + // MediaPeriod.Callback implementation. + + @Override + public void onPrepared(MediaPeriod source) { + handler.obtainMessage(MSG_PERIOD_PREPARED, source).sendToTarget(); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod source) { + handler.obtainMessage(MSG_SOURCE_CONTINUE_LOADING_REQUESTED, source).sendToTarget(); + } + + // TrackSelector.InvalidationListener implementation. + + @Override + public void onTrackSelectionsInvalidated() { + handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED); + } + + // Handler.Callback implementation. + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + try { + switch (msg.what) { + case MSG_PREPARE: { + prepareInternal((MediaSource) msg.obj, msg.arg1 != 0); + return true; + } + case MSG_SET_PLAY_WHEN_READY: { + setPlayWhenReadyInternal(msg.arg1 != 0); + return true; + } + case MSG_DO_SOME_WORK: { + doSomeWork(); + return true; + } + case MSG_SEEK_TO: { + seekToInternal((SeekPosition) msg.obj); + return true; + } + case MSG_STOP: { + stopInternal(); + return true; + } + case MSG_RELEASE: { + releaseInternal(); + return true; + } + case MSG_PERIOD_PREPARED: { + handlePeriodPrepared((MediaPeriod) msg.obj); + return true; + } + case MSG_REFRESH_SOURCE_INFO: { + handleSourceInfoRefreshed((Pair) msg.obj); + return true; + } + case MSG_SOURCE_CONTINUE_LOADING_REQUESTED: { + handleContinueLoadingRequested((MediaPeriod) msg.obj); + return true; + } + case MSG_TRACK_SELECTION_INVALIDATED: { + reselectTracksInternal(); + return true; + } + case MSG_CUSTOM: { + sendMessagesInternal((ExoPlayerMessage[]) msg.obj); + return true; + } + default: + return false; + } + } catch (ExoPlaybackException e) { + Log.e(TAG, "Renderer error.", e); + eventHandler.obtainMessage(MSG_ERROR, e).sendToTarget(); + stopInternal(); + return true; + } catch (IOException e) { + Log.e(TAG, "Source error.", e); + eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForSource(e)).sendToTarget(); + stopInternal(); + return true; + } catch (RuntimeException e) { + Log.e(TAG, "Internal runtime error.", e); + eventHandler.obtainMessage(MSG_ERROR, ExoPlaybackException.createForUnexpected(e)) + .sendToTarget(); + stopInternal(); + return true; + } + } + + // Private methods. + + private void setState(int state) { + if (this.state != state) { + this.state = state; + eventHandler.obtainMessage(MSG_STATE_CHANGED, state, 0).sendToTarget(); + } + } + + private void setIsLoading(boolean isLoading) { + if (this.isLoading != isLoading) { + this.isLoading = isLoading; + eventHandler.obtainMessage(MSG_LOADING_CHANGED, isLoading ? 1 : 0, 0).sendToTarget(); + } + } + + private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { + resetInternal(); + loadControl.onPrepared(); + if (resetPosition) { + playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); + } + this.mediaSource = mediaSource; + mediaSource.prepareSource(this); + setState(ExoPlayer.STATE_BUFFERING); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + + private void setPlayWhenReadyInternal(boolean playWhenReady) throws ExoPlaybackException { + rebuffering = false; + this.playWhenReady = playWhenReady; + if (!playWhenReady) { + stopRenderers(); + updatePlaybackPositions(); + } else { + if (state == ExoPlayer.STATE_READY) { + startRenderers(); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } else if (state == ExoPlayer.STATE_BUFFERING) { + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + } + } + + private void startRenderers() throws ExoPlaybackException { + rebuffering = false; + standaloneMediaClock.start(); + for (Renderer renderer : enabledRenderers) { + renderer.start(); + } + } + + private void stopRenderers() throws ExoPlaybackException { + standaloneMediaClock.stop(); + for (Renderer renderer : enabledRenderers) { + ensureStopped(renderer); + } + } + + private void updatePlaybackPositions() throws ExoPlaybackException { + if (playingPeriodHolder == null) { + return; + } + + // Update the playback position. + long periodPositionUs = playingPeriodHolder.mediaPeriod.readDiscontinuity(); + if (periodPositionUs != C.TIME_UNSET) { + resetRendererPosition(periodPositionUs); + } else { + if (rendererMediaClockSource != null && !rendererMediaClockSource.isEnded()) { + rendererPositionUs = rendererMediaClock.getPositionUs(); + standaloneMediaClock.setPositionUs(rendererPositionUs); + } else { + rendererPositionUs = standaloneMediaClock.getPositionUs(); + } + periodPositionUs = playingPeriodHolder.toPeriodTime(rendererPositionUs); + } + playbackInfo.positionUs = periodPositionUs; + elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000; + + // Update the buffered position. + long bufferedPositionUs = enabledRenderers.length == 0 ? C.TIME_END_OF_SOURCE + : playingPeriodHolder.mediaPeriod.getBufferedPositionUs(); + playbackInfo.bufferedPositionUs = bufferedPositionUs == C.TIME_END_OF_SOURCE + ? timeline.getPeriod(playingPeriodHolder.index, period).getDurationUs() + : bufferedPositionUs; + } + + private void doSomeWork() throws ExoPlaybackException, IOException { + long operationStartTimeMs = SystemClock.elapsedRealtime(); + updatePeriods(); + if (playingPeriodHolder == null) { + // We're still waiting for the first period to be prepared. + maybeThrowPeriodPrepareError(); + scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS); + return; + } + + TraceUtil.beginSection("doSomeWork"); + + updatePlaybackPositions(); + boolean allRenderersEnded = true; + boolean allRenderersReadyOrEnded = true; + for (Renderer renderer : enabledRenderers) { + // TODO: Each renderer should return the maximum delay before which it wishes to be called + // again. The minimum of these values should then be used as the delay before the next + // invocation of this method. + renderer.render(rendererPositionUs, elapsedRealtimeUs); + allRenderersEnded = allRenderersEnded && renderer.isEnded(); + // Determine whether the renderer is ready (or ended). If it's not, throw an error that's + // preventing the renderer from making progress, if such an error exists. + boolean rendererReadyOrEnded = renderer.isReady() || renderer.isEnded(); + if (!rendererReadyOrEnded) { + renderer.maybeThrowStreamError(); + } + allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded; + } + + if (!allRenderersReadyOrEnded) { + maybeThrowPeriodPrepareError(); + } + + long playingPeriodDurationUs = timeline.getPeriod(playingPeriodHolder.index, period) + .getDurationUs(); + if (allRenderersEnded + && (playingPeriodDurationUs == C.TIME_UNSET + || playingPeriodDurationUs <= playbackInfo.positionUs) + && playingPeriodHolder.isLast) { + setState(ExoPlayer.STATE_ENDED); + stopRenderers(); + } else if (state == ExoPlayer.STATE_BUFFERING) { + boolean isNewlyReady = enabledRenderers.length > 0 + ? (allRenderersReadyOrEnded && haveSufficientBuffer(rebuffering)) + : isTimelineReady(playingPeriodDurationUs); + if (isNewlyReady) { + setState(ExoPlayer.STATE_READY); + if (playWhenReady) { + startRenderers(); + } + } + } else if (state == ExoPlayer.STATE_READY) { + boolean isStillReady = enabledRenderers.length > 0 ? allRenderersReadyOrEnded + : isTimelineReady(playingPeriodDurationUs); + if (!isStillReady) { + rebuffering = playWhenReady; + setState(ExoPlayer.STATE_BUFFERING); + stopRenderers(); + } + } + + if (state == ExoPlayer.STATE_BUFFERING) { + for (Renderer renderer : enabledRenderers) { + renderer.maybeThrowStreamError(); + } + } + + if ((playWhenReady && state == ExoPlayer.STATE_READY) || state == ExoPlayer.STATE_BUFFERING) { + scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS); + } else if (enabledRenderers.length != 0) { + scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS); + } else { + handler.removeMessages(MSG_DO_SOME_WORK); + } + + TraceUtil.endSection(); + } + + private void scheduleNextWork(long thisOperationStartTimeMs, long intervalMs) { + handler.removeMessages(MSG_DO_SOME_WORK); + long nextOperationStartTimeMs = thisOperationStartTimeMs + intervalMs; + long nextOperationDelayMs = nextOperationStartTimeMs - SystemClock.elapsedRealtime(); + if (nextOperationDelayMs <= 0) { + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } else { + handler.sendEmptyMessageDelayed(MSG_DO_SOME_WORK, nextOperationDelayMs); + } + } + + private void seekToInternal(SeekPosition seekPosition) throws ExoPlaybackException { + if (timeline == null) { + pendingInitialSeekCount++; + pendingSeekPosition = seekPosition; + return; + } + + Pair periodPosition = resolveSeekPosition(seekPosition); + if (periodPosition == null) { + // TODO: We should probably propagate an error here. + // We failed to resolve the seek position. Stop the player. + stopInternal(); + return; + } + + int periodIndex = periodPosition.first; + long periodPositionUs = periodPosition.second; + + try { + if (periodIndex == playbackInfo.periodIndex + && ((periodPositionUs / 1000) == (playbackInfo.positionUs / 1000))) { + // Seek position equals the current position. Do nothing. + return; + } + periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs); + } finally { + playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs); + eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget(); + } + } + + private long seekToPeriodPosition(int periodIndex, long periodPositionUs) + throws ExoPlaybackException { + stopRenderers(); + rebuffering = false; + setState(ExoPlayer.STATE_BUFFERING); + + MediaPeriodHolder newPlayingPeriodHolder = null; + if (playingPeriodHolder == null) { + // We're still waiting for the first period to be prepared. + if (loadingPeriodHolder != null) { + loadingPeriodHolder.release(); + } + } else { + // Clear the timeline, but keep the requested period if it is already prepared. + MediaPeriodHolder periodHolder = playingPeriodHolder; + while (periodHolder != null) { + if (periodHolder.index == periodIndex && periodHolder.prepared) { + newPlayingPeriodHolder = periodHolder; + } else { + periodHolder.release(); + } + periodHolder = periodHolder.next; + } + } + + // Disable all the renderers if the period being played is changing, or if the renderers are + // reading from a period other than the one being played. + if (playingPeriodHolder != newPlayingPeriodHolder + || playingPeriodHolder != readingPeriodHolder) { + for (Renderer renderer : enabledRenderers) { + renderer.disable(); + } + enabledRenderers = new Renderer[0]; + rendererMediaClock = null; + rendererMediaClockSource = null; + } + + // Update the holders. + if (newPlayingPeriodHolder != null) { + newPlayingPeriodHolder.next = null; + loadingPeriodHolder = newPlayingPeriodHolder; + readingPeriodHolder = newPlayingPeriodHolder; + setPlayingPeriodHolder(newPlayingPeriodHolder); + if (playingPeriodHolder.hasEnabledTracks) { + periodPositionUs = playingPeriodHolder.mediaPeriod.seekToUs(periodPositionUs); + } + resetRendererPosition(periodPositionUs); + maybeContinueLoading(); + } else { + loadingPeriodHolder = null; + readingPeriodHolder = null; + playingPeriodHolder = null; + resetRendererPosition(periodPositionUs); + } + + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + return periodPositionUs; + } + + private void resetRendererPosition(long periodPositionUs) throws ExoPlaybackException { + rendererPositionUs = playingPeriodHolder == null ? periodPositionUs + : playingPeriodHolder.toRendererTime(periodPositionUs); + standaloneMediaClock.setPositionUs(rendererPositionUs); + for (Renderer renderer : enabledRenderers) { + renderer.resetPosition(rendererPositionUs); + } + } + + private void stopInternal() { + resetInternal(); + loadControl.onStopped(); + setState(ExoPlayer.STATE_IDLE); + } + + private void releaseInternal() { + resetInternal(); + loadControl.onReleased(); + setState(ExoPlayer.STATE_IDLE); + synchronized (this) { + released = true; + notifyAll(); + } + } + + private void resetInternal() { + handler.removeMessages(MSG_DO_SOME_WORK); + rebuffering = false; + standaloneMediaClock.stop(); + rendererMediaClock = null; + rendererMediaClockSource = null; + for (Renderer renderer : enabledRenderers) { + try { + ensureStopped(renderer); + renderer.disable(); + } catch (ExoPlaybackException | RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Stop failed.", e); + } + } + enabledRenderers = new Renderer[0]; + releasePeriodHoldersFrom(playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder); + if (mediaSource != null) { + mediaSource.releaseSource(); + mediaSource = null; + } + loadingPeriodHolder = null; + readingPeriodHolder = null; + playingPeriodHolder = null; + timeline = null; + setIsLoading(false); + } + + private void sendMessagesInternal(ExoPlayerMessage[] messages) throws ExoPlaybackException { + try { + for (ExoPlayerMessage message : messages) { + message.target.handleMessage(message.messageType, message.message); + } + if (mediaSource != null) { + // The message may have caused something to change that now requires us to do work. + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + } finally { + synchronized (this) { + customMessagesProcessed++; + notifyAll(); + } + } + } + + private void ensureStopped(Renderer renderer) throws ExoPlaybackException { + if (renderer.getState() == Renderer.STATE_STARTED) { + renderer.stop(); + } + } + + private void reselectTracksInternal() throws ExoPlaybackException { + if (playingPeriodHolder == null) { + // We don't have tracks yet, so we don't care. + return; + } + // Reselect tracks on each period in turn, until the selection changes. + MediaPeriodHolder periodHolder = playingPeriodHolder; + boolean selectionsChangedForReadPeriod = true; + while (true) { + if (periodHolder == null || !periodHolder.prepared) { + // The reselection did not change any prepared periods. + return; + } + if (periodHolder.selectTracks()) { + // Selected tracks have changed for this period. + break; + } + if (periodHolder == readingPeriodHolder) { + // The track reselection didn't affect any period that has been read. + selectionsChangedForReadPeriod = false; + } + periodHolder = periodHolder.next; + } + + if (selectionsChangedForReadPeriod) { + // Update streams and rebuffer for the new selection, recreating all streams if reading ahead. + boolean recreateStreams = readingPeriodHolder != playingPeriodHolder; + releasePeriodHoldersFrom(playingPeriodHolder.next); + playingPeriodHolder.next = null; + loadingPeriodHolder = playingPeriodHolder; + readingPeriodHolder = playingPeriodHolder; + + boolean[] streamResetFlags = new boolean[renderers.length]; + long periodPositionUs = playingPeriodHolder.updatePeriodTrackSelection( + playbackInfo.positionUs, recreateStreams, streamResetFlags); + if (periodPositionUs != playbackInfo.positionUs) { + playbackInfo.positionUs = periodPositionUs; + resetRendererPosition(periodPositionUs); + } + + int enabledRendererCount = 0; + boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; + SampleStream sampleStream = playingPeriodHolder.sampleStreams[i]; + if (sampleStream != null) { + enabledRendererCount++; + } + if (rendererWasEnabledFlags[i]) { + if (sampleStream != renderer.getStream()) { + // We need to disable the renderer. + if (renderer == rendererMediaClockSource) { + // The renderer is providing the media clock. + if (sampleStream == null) { + // The renderer won't be re-enabled. Sync standaloneMediaClock so that it can take + // over timing responsibilities. + standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); + } + rendererMediaClock = null; + rendererMediaClockSource = null; + } + ensureStopped(renderer); + renderer.disable(); + } else if (streamResetFlags[i]) { + // The renderer will continue to consume from its current stream, but needs to be reset. + renderer.resetPosition(rendererPositionUs); + } + } + } + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget(); + enableRenderers(rendererWasEnabledFlags, enabledRendererCount); + } else { + // Release and re-prepare/buffer periods after the one whose selection changed. + loadingPeriodHolder = periodHolder; + periodHolder = loadingPeriodHolder.next; + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; + } + loadingPeriodHolder.next = null; + if (loadingPeriodHolder.prepared) { + long loadingPeriodPositionUs = Math.max(loadingPeriodHolder.startPositionUs, + loadingPeriodHolder.toPeriodTime(rendererPositionUs)); + loadingPeriodHolder.updatePeriodTrackSelection(loadingPeriodPositionUs, false); + } + } + maybeContinueLoading(); + updatePlaybackPositions(); + handler.sendEmptyMessage(MSG_DO_SOME_WORK); + } + + private boolean isTimelineReady(long playingPeriodDurationUs) { + return playingPeriodDurationUs == C.TIME_UNSET + || playbackInfo.positionUs < playingPeriodDurationUs + || (playingPeriodHolder.next != null && playingPeriodHolder.next.prepared); + } + + private boolean haveSufficientBuffer(boolean rebuffering) { + if (loadingPeriodHolder == null) { + return false; + } + long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared + ? loadingPeriodHolder.startPositionUs + : loadingPeriodHolder.mediaPeriod.getBufferedPositionUs(); + if (loadingPeriodBufferedPositionUs == C.TIME_END_OF_SOURCE) { + if (loadingPeriodHolder.isLast) { + return true; + } + loadingPeriodBufferedPositionUs = timeline.getPeriod(loadingPeriodHolder.index, period) + .getDurationUs(); + } + return loadControl.shouldStartPlayback( + loadingPeriodBufferedPositionUs - loadingPeriodHolder.toPeriodTime(rendererPositionUs), + rebuffering); + } + + private void maybeThrowPeriodPrepareError() throws IOException { + if (loadingPeriodHolder != null && !loadingPeriodHolder.prepared + && (readingPeriodHolder == null || readingPeriodHolder.next == loadingPeriodHolder)) { + for (Renderer renderer : enabledRenderers) { + if (!renderer.hasReadStreamToEnd()) { + return; + } + } + loadingPeriodHolder.mediaPeriod.maybeThrowPrepareError(); + } + } + + private void handleSourceInfoRefreshed(Pair timelineAndManifest) + throws ExoPlaybackException { + Timeline oldTimeline = timeline; + timeline = timelineAndManifest.first; + Object manifest = timelineAndManifest.second; + + int processedInitialSeekCount = 0; + if (oldTimeline == null) { + if (pendingInitialSeekCount > 0) { + Pair periodPosition = resolveSeekPosition(pendingSeekPosition); + if (periodPosition == null) { + // We failed to resolve the seek position. Stop the player. + notifySourceInfoRefresh(manifest, 0); + // TODO: We should probably propagate an error here. + stopInternal(); + return; + } + playbackInfo = new PlaybackInfo(periodPosition.first, periodPosition.second); + processedInitialSeekCount = pendingInitialSeekCount; + pendingInitialSeekCount = 0; + pendingSeekPosition = null; + } else if (playbackInfo.startPositionUs == C.TIME_UNSET) { + Pair defaultPosition = getPeriodPosition(0, C.TIME_UNSET); + playbackInfo = new PlaybackInfo(defaultPosition.first, defaultPosition.second); + } + } + + MediaPeriodHolder periodHolder = playingPeriodHolder != null ? playingPeriodHolder + : loadingPeriodHolder; + if (periodHolder == null) { + // We don't have any period holders, so we're done. + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + return; + } + + int periodIndex = timeline.getIndexOfPeriod(periodHolder.uid); + if (periodIndex == C.INDEX_UNSET) { + // We didn't find the current period in the new timeline. Attempt to resolve a subsequent + // period whose window we can restart from. + int newPeriodIndex = resolveSubsequentPeriod(periodHolder.index, oldTimeline, timeline); + if (newPeriodIndex == C.INDEX_UNSET) { + // We failed to resolve a subsequent period. Stop the player. + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + // TODO: We should probably propagate an error here. + stopInternal(); + return; + } + // We resolved a subsequent period. Seek to the default position in the corresponding window. + Pair defaultPosition = getPeriodPosition( + timeline.getPeriod(newPeriodIndex, period).windowIndex, C.TIME_UNSET); + newPeriodIndex = defaultPosition.first; + long newPositionUs = defaultPosition.second; + timeline.getPeriod(newPeriodIndex, period, true); + // Clear the index of each holder that doesn't contain the default position. If a holder + // contains the default position then update its index so it can be re-used when seeking. + Object newPeriodUid = period.uid; + periodHolder.index = C.INDEX_UNSET; + while (periodHolder.next != null) { + periodHolder = periodHolder.next; + periodHolder.index = periodHolder.uid.equals(newPeriodUid) ? newPeriodIndex : C.INDEX_UNSET; + } + // Actually do the seek. + newPositionUs = seekToPeriodPosition(newPeriodIndex, newPositionUs); + playbackInfo = new PlaybackInfo(newPeriodIndex, newPositionUs); + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + return; + } + + // The current period is in the new timeline. Update the holder and playbackInfo. + timeline.getPeriod(periodIndex, period); + boolean isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + periodHolder.setIndex(periodIndex, isLastPeriod); + boolean seenReadingPeriod = periodHolder == readingPeriodHolder; + if (periodIndex != playbackInfo.periodIndex) { + playbackInfo = playbackInfo.copyWithPeriodIndex(periodIndex); + } + + // If there are subsequent holders, update the index for each of them. If we find a holder + // that's inconsistent with the new timeline then take appropriate action. + while (periodHolder.next != null) { + MediaPeriodHolder previousPeriodHolder = periodHolder; + periodHolder = periodHolder.next; + periodIndex++; + timeline.getPeriod(periodIndex, period, true); + isLastPeriod = periodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + if (periodHolder.uid.equals(period.uid)) { + // The holder is consistent with the new timeline. Update its index and continue. + periodHolder.setIndex(periodIndex, isLastPeriod); + seenReadingPeriod |= (periodHolder == readingPeriodHolder); + } else { + // The holder is inconsistent with the new timeline. + if (!seenReadingPeriod) { + // Renderers may have read from a period that's been removed. Seek back to the current + // position of the playing period to make sure none of the removed period is played. + periodIndex = playingPeriodHolder.index; + long newPositionUs = seekToPeriodPosition(periodIndex, playbackInfo.positionUs); + playbackInfo = new PlaybackInfo(periodIndex, newPositionUs); + } else { + // Update the loading period to be the last period that's still valid, and release all + // subsequent periods. + loadingPeriodHolder = previousPeriodHolder; + loadingPeriodHolder.next = null; + // Release the rest of the timeline. + releasePeriodHoldersFrom(periodHolder); + } + break; + } + } + + notifySourceInfoRefresh(manifest, processedInitialSeekCount); + } + + private void notifySourceInfoRefresh(Object manifest, int processedInitialSeekCount) { + eventHandler.obtainMessage(MSG_SOURCE_INFO_REFRESHED, + new SourceInfo(timeline, manifest, playbackInfo, processedInitialSeekCount)).sendToTarget(); + } + + /** + * Given a period index into an old timeline, finds the first subsequent period that also exists + * in a new timeline. The index of this period in the new timeline is returned. + * + * @param oldPeriodIndex The index of the period in the old timeline. + * @param oldTimeline The old timeline. + * @param newTimeline The new timeline. + * @return The index in the new timeline of the first subsequent period, or {@link C#INDEX_UNSET} + * if no such period was found. + */ + private int resolveSubsequentPeriod(int oldPeriodIndex, Timeline oldTimeline, + Timeline newTimeline) { + int newPeriodIndex = C.INDEX_UNSET; + while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { + newPeriodIndex = newTimeline.getIndexOfPeriod( + oldTimeline.getPeriod(++oldPeriodIndex, period, true).uid); + } + return newPeriodIndex; + } + + /** + * Converts a {@link SeekPosition} into the corresponding (periodIndex, periodPositionUs) for the + * internal timeline. + * + * @param seekPosition The position to resolve. + * @return The resolved position, or null if resolution was not successful. + */ + private Pair resolveSeekPosition(SeekPosition seekPosition) { + Timeline seekTimeline = seekPosition.timeline; + if (seekTimeline.isEmpty()) { + // The application performed a blind seek without a non-empty timeline (most likely based on + // knowledge of what the future timeline will be). Use the internal timeline. + seekTimeline = timeline; + Assertions.checkIndex(seekPosition.windowIndex, 0, timeline.getWindowCount()); + } + // Map the SeekPosition to a position in the corresponding timeline. + Pair periodPosition = getPeriodPosition(seekTimeline, seekPosition.windowIndex, + seekPosition.windowPositionUs); + if (timeline == seekTimeline) { + // Our internal timeline is the seek timeline, so the mapped position is correct. + return periodPosition; + } + // Attempt to find the mapped period in the internal timeline. + int periodIndex = timeline.getIndexOfPeriod( + seekTimeline.getPeriod(periodPosition.first, period, true).uid); + if (periodIndex != C.INDEX_UNSET) { + // We successfully located the period in the internal timeline. + return Pair.create(periodIndex, periodPosition.second); + } + // Try and find a subsequent period from the seek timeline in the internal timeline. + periodIndex = resolveSubsequentPeriod(periodPosition.first, seekTimeline, timeline); + if (periodIndex != C.INDEX_UNSET) { + // We found one. Map the SeekPosition onto the corresponding default position. + return getPeriodPosition(timeline.getPeriod(periodIndex, period).windowIndex, C.TIME_UNSET); + } + // We didn't find one. Give up. + return null; + } + + /** + * Calls {@link #getPeriodPosition(Timeline, int, long)} using the current timeline. + */ + private Pair getPeriodPosition(int windowIndex, long windowPositionUs) { + return getPeriodPosition(timeline, windowIndex, windowPositionUs); + } + + /** + * Calls {@link #getPeriodPosition(Timeline, int, long, long)} with a zero default position + * projection. + */ + private Pair getPeriodPosition(Timeline timeline, int windowIndex, + long windowPositionUs) { + return getPeriodPosition(timeline, windowIndex, windowPositionUs, 0); + } + + /** + * Converts (windowIndex, windowPositionUs) to the corresponding (periodIndex, periodPositionUs). + * + * @param timeline The timeline containing the window. + * @param windowIndex The window index. + * @param windowPositionUs The window time, or {@link C#TIME_UNSET} to use the window's default + * start position. + * @param defaultPositionProjectionUs If {@code windowPositionUs} is {@link C#TIME_UNSET}, the + * duration into the future by which the window's position should be projected. + * @return The corresponding (periodIndex, periodPositionUs), or null if {@code #windowPositionUs} + * is {@link C#TIME_UNSET}, {@code defaultPositionProjectionUs} is non-zero, and the window's + * position could not be projected by {@code defaultPositionProjectionUs}. + */ + private Pair getPeriodPosition(Timeline timeline, int windowIndex, + long windowPositionUs, long defaultPositionProjectionUs) { + timeline.getWindow(windowIndex, window, false, defaultPositionProjectionUs); + if (windowPositionUs == C.TIME_UNSET) { + windowPositionUs = window.getDefaultPositionUs(); + if (windowPositionUs == C.TIME_UNSET) { + return null; + } + } + int periodIndex = window.firstPeriodIndex; + long periodPositionUs = window.getPositionInFirstPeriodUs() + windowPositionUs; + long periodDurationUs = timeline.getPeriod(periodIndex, period).getDurationUs(); + while (periodDurationUs != C.TIME_UNSET && periodPositionUs >= periodDurationUs + && periodIndex < window.lastPeriodIndex) { + periodPositionUs -= periodDurationUs; + periodDurationUs = timeline.getPeriod(++periodIndex, period).getDurationUs(); + } + return Pair.create(periodIndex, periodPositionUs); + } + + private void updatePeriods() throws ExoPlaybackException, IOException { + if (timeline == null) { + // We're waiting to get information about periods. + mediaSource.maybeThrowSourceInfoRefreshError(); + return; + } + + if (loadingPeriodHolder == null + || (loadingPeriodHolder.isFullyBuffered() && !loadingPeriodHolder.isLast + && (playingPeriodHolder == null + || loadingPeriodHolder.index - playingPeriodHolder.index < MAXIMUM_BUFFER_AHEAD_PERIODS))) { + // We don't have a loading period or it's fully loaded, so try and create the next one. + int newLoadingPeriodIndex = loadingPeriodHolder == null ? playbackInfo.periodIndex + : loadingPeriodHolder.index + 1; + if (newLoadingPeriodIndex >= timeline.getPeriodCount()) { + // The period is not available yet. + mediaSource.maybeThrowSourceInfoRefreshError(); + } else { + int windowIndex = timeline.getPeriod(newLoadingPeriodIndex, period).windowIndex; + boolean isFirstPeriodInWindow = newLoadingPeriodIndex + == timeline.getWindow(windowIndex, window).firstPeriodIndex; + long periodStartPositionUs; + if (loadingPeriodHolder == null) { + periodStartPositionUs = playbackInfo.startPositionUs; + } else if (!isFirstPeriodInWindow) { + // We're starting to buffer a new period in the current window. Always start from the + // beginning of the period. + periodStartPositionUs = 0; + } else { + // We're starting to buffer a new window. When playback transitions to this window we'll + // want it to be from its default start position. The expected delay until playback + // transitions is equal the duration of media that's currently buffered (assuming no + // interruptions). Hence we project the default start position forward by the duration of + // the buffer, and start buffering from this point. + long defaultPositionProjectionUs = loadingPeriodHolder.getRendererOffset() + + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs() + - rendererPositionUs; + Pair defaultPosition = getPeriodPosition(timeline, windowIndex, + C.TIME_UNSET, Math.max(0, defaultPositionProjectionUs)); + if (defaultPosition == null) { + newLoadingPeriodIndex = C.INDEX_UNSET; + periodStartPositionUs = C.TIME_UNSET; + } else { + newLoadingPeriodIndex = defaultPosition.first; + periodStartPositionUs = defaultPosition.second; + } + } + if (newLoadingPeriodIndex != C.INDEX_UNSET) { + long rendererPositionOffsetUs = loadingPeriodHolder == null ? periodStartPositionUs + : (loadingPeriodHolder.getRendererOffset() + + timeline.getPeriod(loadingPeriodHolder.index, period).getDurationUs()); + timeline.getPeriod(newLoadingPeriodIndex, period, true); + boolean isLastPeriod = newLoadingPeriodIndex == timeline.getPeriodCount() - 1 + && !timeline.getWindow(period.windowIndex, window).isDynamic; + MediaPeriodHolder newPeriodHolder = new MediaPeriodHolder(renderers, rendererCapabilities, + rendererPositionOffsetUs, trackSelector, loadControl, mediaSource, period.uid, + newLoadingPeriodIndex, isLastPeriod, periodStartPositionUs); + if (loadingPeriodHolder != null) { + loadingPeriodHolder.next = newPeriodHolder; + } + loadingPeriodHolder = newPeriodHolder; + loadingPeriodHolder.mediaPeriod.prepare(this); + setIsLoading(true); + } + } + } + + if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) { + setIsLoading(false); + } else if (loadingPeriodHolder != null && loadingPeriodHolder.needsContinueLoading) { + maybeContinueLoading(); + } + + if (playingPeriodHolder == null) { + // We're waiting for the first period to be prepared. + return; + } + + // Update the playing and reading periods. + while (playingPeriodHolder != readingPeriodHolder + && rendererPositionUs >= playingPeriodHolder.next.rendererPositionOffsetUs) { + // All enabled renderers' streams have been read to the end, and the playback position reached + // the end of the playing period, so advance playback to the next period. + playingPeriodHolder.release(); + setPlayingPeriodHolder(playingPeriodHolder.next); + playbackInfo = new PlaybackInfo(playingPeriodHolder.index, + playingPeriodHolder.startPositionUs); + updatePlaybackPositions(); + eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget(); + } + + if (readingPeriodHolder.isLast) { + // The renderers have their final SampleStreams. + for (Renderer renderer : enabledRenderers) { + renderer.setCurrentStreamIsFinal(); + } + return; + } + + for (Renderer renderer : enabledRenderers) { + if (!renderer.hasReadStreamToEnd()) { + return; + } + } + if (readingPeriodHolder.next != null && readingPeriodHolder.next.prepared) { + TrackSelectionArray oldTrackSelections = readingPeriodHolder.trackSelections; + readingPeriodHolder = readingPeriodHolder.next; + TrackSelectionArray newTrackSelections = readingPeriodHolder.trackSelections; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + TrackSelection oldSelection = oldTrackSelections.get(i); + TrackSelection newSelection = newTrackSelections.get(i); + if (oldSelection != null) { + if (newSelection != null) { + // Replace the renderer's SampleStream so the transition to playing the next period can + // be seamless. + Format[] formats = new Format[newSelection.length()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = newSelection.getFormat(j); + } + renderer.replaceStream(formats, readingPeriodHolder.sampleStreams[i], + readingPeriodHolder.getRendererOffset()); + } else { + // The renderer will be disabled when transitioning to playing the next period. Mark the + // SampleStream as final to play out any remaining data. + renderer.setCurrentStreamIsFinal(); + } + } + } + } + } + + private void handlePeriodPrepared(MediaPeriod period) throws ExoPlaybackException { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { + // Stale event. + return; + } + loadingPeriodHolder.handlePrepared(); + if (playingPeriodHolder == null) { + // This is the first prepared period, so start playing it. + readingPeriodHolder = loadingPeriodHolder; + resetRendererPosition(readingPeriodHolder.startPositionUs); + setPlayingPeriodHolder(readingPeriodHolder); + } + maybeContinueLoading(); + } + + private void handleContinueLoadingRequested(MediaPeriod period) { + if (loadingPeriodHolder == null || loadingPeriodHolder.mediaPeriod != period) { + // Stale event. + return; + } + maybeContinueLoading(); + } + + private void maybeContinueLoading() { + long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { + setIsLoading(false); + } else { + long loadingPeriodPositionUs = loadingPeriodHolder.toPeriodTime(rendererPositionUs); + long bufferedDurationUs = nextLoadPositionUs - loadingPeriodPositionUs; + boolean continueLoading = loadControl.shouldContinueLoading(bufferedDurationUs); + setIsLoading(continueLoading); + if (continueLoading) { + loadingPeriodHolder.needsContinueLoading = false; + loadingPeriodHolder.mediaPeriod.continueLoading(loadingPeriodPositionUs); + } else { + loadingPeriodHolder.needsContinueLoading = true; + } + } + } + + private void releasePeriodHoldersFrom(MediaPeriodHolder periodHolder) { + while (periodHolder != null) { + periodHolder.release(); + periodHolder = periodHolder.next; + } + } + + private void setPlayingPeriodHolder(MediaPeriodHolder periodHolder) throws ExoPlaybackException { + playingPeriodHolder = periodHolder; + int enabledRendererCount = 0; + boolean[] rendererWasEnabledFlags = new boolean[renderers.length]; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + rendererWasEnabledFlags[i] = renderer.getState() != Renderer.STATE_DISABLED; + TrackSelection newSelection = periodHolder.trackSelections.get(i); + if (newSelection != null) { + // The renderer should be enabled when playing the new period. + enabledRendererCount++; + } else if (rendererWasEnabledFlags[i]) { + // The renderer should be disabled when playing the new period. + if (renderer == rendererMediaClockSource) { + // Sync standaloneMediaClock so that it can take over timing responsibilities. + standaloneMediaClock.setPositionUs(rendererMediaClock.getPositionUs()); + rendererMediaClock = null; + rendererMediaClockSource = null; + } + ensureStopped(renderer); + renderer.disable(); + } + } + + eventHandler.obtainMessage(MSG_TRACKS_CHANGED, periodHolder.getTrackInfo()).sendToTarget(); + enableRenderers(rendererWasEnabledFlags, enabledRendererCount); + } + + private void enableRenderers(boolean[] rendererWasEnabledFlags, int enabledRendererCount) + throws ExoPlaybackException { + enabledRenderers = new Renderer[enabledRendererCount]; + enabledRendererCount = 0; + for (int i = 0; i < renderers.length; i++) { + Renderer renderer = renderers[i]; + TrackSelection newSelection = playingPeriodHolder.trackSelections.get(i); + if (newSelection != null) { + enabledRenderers[enabledRendererCount++] = renderer; + if (renderer.getState() == Renderer.STATE_DISABLED) { + // The renderer needs enabling with its new track selection. + boolean playing = playWhenReady && state == ExoPlayer.STATE_READY; + // Consider as joining only if the renderer was previously disabled. + boolean joining = !rendererWasEnabledFlags[i] && playing; + // Build an array of formats contained by the selection. + Format[] formats = new Format[newSelection.length()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = newSelection.getFormat(j); + } + // Enable the renderer. + renderer.enable(formats, playingPeriodHolder.sampleStreams[i], rendererPositionUs, + joining, playingPeriodHolder.getRendererOffset()); + MediaClock mediaClock = renderer.getMediaClock(); + if (mediaClock != null) { + if (rendererMediaClock != null) { + throw ExoPlaybackException.createForUnexpected( + new IllegalStateException("Multiple renderer media clocks enabled.")); + } + rendererMediaClock = mediaClock; + rendererMediaClockSource = renderer; + } + // Start the renderer if playing. + if (playing) { + renderer.start(); + } + } + } + } + } + + /** + * Holds a {@link MediaPeriod} with information required to play it as part of a timeline. + */ + private static final class MediaPeriodHolder { + + public final MediaPeriod mediaPeriod; + public final Object uid; + public final SampleStream[] sampleStreams; + public final boolean[] mayRetainStreamFlags; + public final long rendererPositionOffsetUs; + + public int index; + public long startPositionUs; + public boolean isLast; + public boolean prepared; + public boolean hasEnabledTracks; + public MediaPeriodHolder next; + public boolean needsContinueLoading; + + private final Renderer[] renderers; + private final RendererCapabilities[] rendererCapabilities; + private final TrackSelector trackSelector; + private final LoadControl loadControl; + private final MediaSource mediaSource; + + private Object trackSelectionsInfo; + private TrackGroupArray trackGroups; + private TrackSelectionArray trackSelections; + private TrackSelectionArray periodTrackSelections; + + public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, + long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, + MediaSource mediaSource, Object periodUid, int periodIndex, boolean isLastPeriod, + long startPositionUs) { + this.renderers = renderers; + this.rendererCapabilities = rendererCapabilities; + this.rendererPositionOffsetUs = rendererPositionOffsetUs; + this.trackSelector = trackSelector; + this.loadControl = loadControl; + this.mediaSource = mediaSource; + this.uid = Assertions.checkNotNull(periodUid); + this.index = periodIndex; + this.isLast = isLastPeriod; + this.startPositionUs = startPositionUs; + sampleStreams = new SampleStream[renderers.length]; + mayRetainStreamFlags = new boolean[renderers.length]; + mediaPeriod = mediaSource.createPeriod(periodIndex, loadControl.getAllocator(), + startPositionUs); + } + + public long toRendererTime(long periodTimeUs) { + return periodTimeUs + getRendererOffset(); + } + + public long toPeriodTime(long rendererTimeUs) { + return rendererTimeUs - getRendererOffset(); + } + + public long getRendererOffset() { + return rendererPositionOffsetUs - startPositionUs; + } + + public void setIndex(int index, boolean isLast) { + this.index = index; + this.isLast = isLast; + } + + public boolean isFullyBuffered() { + return prepared + && (!hasEnabledTracks || mediaPeriod.getBufferedPositionUs() == C.TIME_END_OF_SOURCE); + } + + public void handlePrepared() throws ExoPlaybackException { + prepared = true; + trackGroups = mediaPeriod.getTrackGroups(); + selectTracks(); + startPositionUs = updatePeriodTrackSelection(startPositionUs, false); + } + + public boolean selectTracks() throws ExoPlaybackException { + Pair selectorResult = trackSelector.selectTracks( + rendererCapabilities, trackGroups); + TrackSelectionArray newTrackSelections = selectorResult.first; + if (newTrackSelections.equals(periodTrackSelections)) { + return false; + } + trackSelections = newTrackSelections; + trackSelectionsInfo = selectorResult.second; + return true; + } + + public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams) { + return updatePeriodTrackSelection(positionUs, forceRecreateStreams, + new boolean[renderers.length]); + } + + public long updatePeriodTrackSelection(long positionUs, boolean forceRecreateStreams, + boolean[] streamResetFlags) { + for (int i = 0; i < trackSelections.length; i++) { + mayRetainStreamFlags[i] = !forceRecreateStreams + && Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i), + trackSelections.get(i)); + } + + // Disable streams on the period and get new streams for updated/newly-enabled tracks. + positionUs = mediaPeriod.selectTracks(trackSelections.getAll(), mayRetainStreamFlags, + sampleStreams, streamResetFlags, positionUs); + periodTrackSelections = trackSelections; + + // Update whether we have enabled tracks and sanity check the expected streams are non-null. + hasEnabledTracks = false; + for (int i = 0; i < sampleStreams.length; i++) { + if (sampleStreams[i] != null) { + Assertions.checkState(trackSelections.get(i) != null); + hasEnabledTracks = true; + } else { + Assertions.checkState(trackSelections.get(i) == null); + } + } + + // The track selection has changed. + loadControl.onTracksSelected(renderers, trackGroups, trackSelections); + return positionUs; + } + + public TrackInfo getTrackInfo() { + return new TrackInfo(trackGroups, trackSelections, trackSelectionsInfo); + } + + public void release() { + try { + mediaSource.releasePeriod(mediaPeriod); + } catch (RuntimeException e) { + // There's nothing we can do. + Log.e(TAG, "Period release failed.", e); + } + } + + } + + private static final class SeekPosition { + + public final Timeline timeline; + public final int windowIndex; + public final long windowPositionUs; + + public SeekPosition(Timeline timeline, int windowIndex, long windowPositionUs) { + this.timeline = timeline; + this.windowIndex = windowIndex; + this.windowPositionUs = windowPositionUs; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java new file mode 100755 index 00000000000..302c7562afd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ExoPlayerLibraryInfo.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +/** + * Information about the ExoPlayer library. + */ +public interface ExoPlayerLibraryInfo { + + /** + * The version of the library, expressed as a string. + */ + String VERSION = "2.0.4"; + + /** + * The version of the library, expressed as an integer. + *

        + * Three digits are used for each component of {@link #VERSION}. For example "1.2.3" has the + * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding + * integer version 123045006 (123-045-006). + */ + int VERSION_INT = 2000004; + + /** + * Whether the library was compiled with {@link org.telegram.messenger.exoplayer2.util.Assertions} + * checks enabled. + */ + boolean ASSERTIONS_ENABLED = true; + + /** + * Whether the library was compiled with {@link org.telegram.messenger.exoplayer2.util.TraceUtil} + * trace enabled. + */ + boolean TRACE_ENABLED = true; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java new file mode 100755 index 00000000000..2868758d952 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Format.java @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.MediaFormat; +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Representation of a media format. + */ +public final class Format implements Parcelable { + + /** + * A value for various fields to indicate that the field's value is unknown or not applicable. + */ + public static final int NO_VALUE = -1; + + /** + * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to + * the timestamps of their parent samples. + */ + public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; + + /** + * An identifier for the format, or null if unknown or not applicable. + */ + public final String id; + /** + * The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int bitrate; + /** + * Codecs of the format as described in RFC 6381, or null if unknown or not applicable. + */ + public final String codecs; + /** + * Metadata, or null if unknown or not applicable. + */ + public final Metadata metadata; + + // Container specific. + + /** + * The mime type of the container, or null if unknown or not applicable. + */ + public final String containerMimeType; + + // Elementary stream specific. + + /** + * The mime type of the elementary stream (i.e. the individual samples), or null if unknown or not + * applicable. + */ + public final String sampleMimeType; + /** + * The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or + * not applicable. + */ + public final int maxInputSize; + /** + * Initialization data that must be provided to the decoder. Will not be null, but may be empty + * if initialization data is not required. + */ + public final List initializationData; + /** + * DRM initialization data if the stream is protected, or null otherwise. + */ + public final DrmInitData drmInitData; + + // Video specific. + + /** + * The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int width; + /** + * The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int height; + /** + * The frame rate in frames per second, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final float frameRate; + /** + * The clockwise rotation that should be applied to the video for it to be rendered in the correct + * orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are + * supported. + */ + public final int rotationDegrees; + /** + * The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not + * applicable. + */ + public final float pixelWidthHeightRatio; + /** + * The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo + * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link + * C#STEREO_MODE_LEFT_RIGHT}. + */ + @C.StereoMode + public final int stereoMode; + /** + * The projection data for 360/VR video, or null if not applicable. + */ + public final byte[] projectionData; + + // Audio specific. + + /** + * The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int channelCount; + /** + * The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. + */ + public final int sampleRate; + /** + * The encoding for PCM audio streams. If {@link #sampleMimeType} is {@link MimeTypes#AUDIO_RAW} + * then one of {@link C#ENCODING_PCM_8BIT}, {@link C#ENCODING_PCM_16BIT}, + * {@link C#ENCODING_PCM_24BIT} and {@link C#ENCODING_PCM_32BIT}. Set to {@link #NO_VALUE} for + * other media types. + */ + @C.PcmEncoding + public final int pcmEncoding; + /** + * The number of samples to trim from the start of the decoded audio stream. + */ + public final int encoderDelay; + /** + * The number of samples to trim from the end of the decoded audio stream. + */ + public final int encoderPadding; + + // Text specific. + + /** + * For samples that contain subsamples, this is an offset that should be added to subsample + * timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are + * relative to the timestamps of their parent samples. + */ + public final long subsampleOffsetUs; + + // Audio and text specific. + + /** + * Track selection flags. + */ + @C.SelectionFlags + public final int selectionFlags; + + /** + * The language, or null if unknown or not applicable. + */ + public final String language; + + /** + * The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. + */ + public final int accessibilityChannel; + + // Lazily initialized hashcode and framework media format. + + private int hashCode; + private MediaFormat frameworkMediaFormat; + + // Video. + + public static Format createVideoContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, int width, int height, + float frameRate, List initializationData) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, + height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, + null); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, DrmInitData drmInitData) { + return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, initializationData, NO_VALUE, NO_VALUE, drmInitData); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, int rotationDegrees, float pixelWidthHeightRatio, + DrmInitData drmInitData) { + return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null, + NO_VALUE, drmInitData); + } + + public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, + List initializationData, int rotationDegrees, float pixelWidthHeightRatio, + byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, + frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, drmInitData, null); + } + + // Audio. + + public static Format createAudioContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, + List initializationData, @C.SelectionFlags int selectionFlags, String language) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, + NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, null, null); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { + return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, + sampleRate, NO_VALUE, initializationData, drmInitData, selectionFlags, language); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language) { + return createAudioSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, channelCount, + sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, initializationData, drmInitData, + selectionFlags, language, null); + } + + public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding, + List initializationData, DrmInitData drmInitData, + @C.SelectionFlags int selectionFlags, String language, Metadata metadata) { + return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, + encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, drmInitData, metadata); + } + + // Text. + + public static Format createTextContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language) { + return createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, + selectionFlags, language, NO_VALUE); + } + + public static Format createTextContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language, int accessibilityChannel) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, + OFFSET_SAMPLE_RELATIVE, null, null, null); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData, + long subsampleOffsetUs) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + NO_VALUE, drmInitData, subsampleOffsetUs); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, null, + drmInitData, null); + } + + // Image. + + public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, List initializationData, String language, DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, + null); + } + + // Generic. + + public static Format createContainerFormat(String id, String containerMimeType, String codecs, + String sampleMimeType, int bitrate) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, null); + } + + public static Format createSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, DrmInitData drmInitData) { + return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, + NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); + } + + /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, + int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, + float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, + int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, + int encoderPadding, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, long subsampleOffsetUs, List initializationData, + DrmInitData drmInitData, Metadata metadata) { + this.id = id; + this.containerMimeType = containerMimeType; + this.sampleMimeType = sampleMimeType; + this.codecs = codecs; + this.bitrate = bitrate; + this.maxInputSize = maxInputSize; + this.width = width; + this.height = height; + this.frameRate = frameRate; + this.rotationDegrees = rotationDegrees; + this.pixelWidthHeightRatio = pixelWidthHeightRatio; + this.projectionData = projectionData; + this.stereoMode = stereoMode; + this.channelCount = channelCount; + this.sampleRate = sampleRate; + this.pcmEncoding = pcmEncoding; + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + this.selectionFlags = selectionFlags; + this.language = language; + this.accessibilityChannel = accessibilityChannel; + this.subsampleOffsetUs = subsampleOffsetUs; + this.initializationData = initializationData == null ? Collections.emptyList() + : initializationData; + this.drmInitData = drmInitData; + this.metadata = metadata; + } + + @SuppressWarnings("ResourceType") + /* package */ Format(Parcel in) { + id = in.readString(); + containerMimeType = in.readString(); + sampleMimeType = in.readString(); + codecs = in.readString(); + bitrate = in.readInt(); + maxInputSize = in.readInt(); + width = in.readInt(); + height = in.readInt(); + frameRate = in.readFloat(); + rotationDegrees = in.readInt(); + pixelWidthHeightRatio = in.readFloat(); + boolean hasProjectionData = in.readInt() != 0; + projectionData = hasProjectionData ? in.createByteArray() : null; + stereoMode = in.readInt(); + channelCount = in.readInt(); + sampleRate = in.readInt(); + pcmEncoding = in.readInt(); + encoderDelay = in.readInt(); + encoderPadding = in.readInt(); + selectionFlags = in.readInt(); + language = in.readString(); + accessibilityChannel = in.readInt(); + subsampleOffsetUs = in.readLong(); + int initializationDataSize = in.readInt(); + initializationData = new ArrayList<>(initializationDataSize); + for (int i = 0; i < initializationDataSize; i++) { + initializationData.add(in.createByteArray()); + } + drmInitData = in.readParcelable(DrmInitData.class.getClassLoader()); + metadata = in.readParcelable(Metadata.class.getClassLoader()); + } + + public Format copyWithMaxInputSize(int maxInputSize) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, + @C.SelectionFlags int selectionFlags, String language) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithManifestFormatInfo(Format manifestFormat, + boolean preferManifestDrmInitData) { + String id = manifestFormat.id; + String codecs = this.codecs == null ? manifestFormat.codecs : this.codecs; + int bitrate = this.bitrate == NO_VALUE ? manifestFormat.bitrate : this.bitrate; + float frameRate = this.frameRate == NO_VALUE ? manifestFormat.frameRate : this.frameRate; + @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; + String language = this.language == null ? manifestFormat.language : this.language; + DrmInitData drmInitData = (preferManifestDrmInitData && manifestFormat.drmInitData != null) + || this.drmInitData == null ? manifestFormat.drmInitData : this.drmInitData; + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, + height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, + channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, + language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, + metadata); + } + + public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithDrmInitData(DrmInitData drmInitData) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + public Format copyWithMetadata(Metadata metadata) { + return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, + width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, + stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); + } + + /** + * Returns the number of pixels if this is a video format whose {@link #width} and {@link #height} + * are known, or {@link #NO_VALUE} otherwise + */ + public int getPixelCount() { + return width == NO_VALUE || height == NO_VALUE ? NO_VALUE : (width * height); + } + + /** + * Returns a {@link MediaFormat} representation of this format. + */ + @SuppressLint("InlinedApi") + @TargetApi(16) + public final MediaFormat getFrameworkMediaFormatV16() { + if (frameworkMediaFormat == null) { + MediaFormat format = new MediaFormat(); + format.setString(MediaFormat.KEY_MIME, sampleMimeType); + maybeSetStringV16(format, MediaFormat.KEY_LANGUAGE, language); + maybeSetIntegerV16(format, MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); + maybeSetIntegerV16(format, MediaFormat.KEY_WIDTH, width); + maybeSetIntegerV16(format, MediaFormat.KEY_HEIGHT, height); + maybeSetFloatV16(format, MediaFormat.KEY_FRAME_RATE, frameRate); + maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); + maybeSetIntegerV16(format, MediaFormat.KEY_CHANNEL_COUNT, channelCount); + maybeSetIntegerV16(format, MediaFormat.KEY_SAMPLE_RATE, sampleRate); + maybeSetIntegerV16(format, "encoder-delay", encoderDelay); + maybeSetIntegerV16(format, "encoder-padding", encoderPadding); + for (int i = 0; i < initializationData.size(); i++) { + format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); + } + frameworkMediaFormat = format; + } + return frameworkMediaFormat; + } + + @Override + public String toString() { + return "Format(" + id + ", " + containerMimeType + ", " + sampleMimeType + ", " + bitrate + ", " + + ", " + language + ", [" + width + ", " + height + ", " + frameRate + "]" + + ", [" + channelCount + ", " + sampleRate + "])"; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (id == null ? 0 : id.hashCode()); + result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode()); + result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode()); + result = 31 * result + (codecs == null ? 0 : codecs.hashCode()); + result = 31 * result + bitrate; + result = 31 * result + width; + result = 31 * result + height; + result = 31 * result + channelCount; + result = 31 * result + sampleRate; + result = 31 * result + (language == null ? 0 : language.hashCode()); + result = 31 * result + accessibilityChannel; + result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode()); + result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Format other = (Format) obj; + if (bitrate != other.bitrate || maxInputSize != other.maxInputSize + || width != other.width || height != other.height || frameRate != other.frameRate + || rotationDegrees != other.rotationDegrees + || pixelWidthHeightRatio != other.pixelWidthHeightRatio || stereoMode != other.stereoMode + || channelCount != other.channelCount || sampleRate != other.sampleRate + || pcmEncoding != other.pcmEncoding || encoderDelay != other.encoderDelay + || encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs + || selectionFlags != other.selectionFlags || !Util.areEqual(id, other.id) + || !Util.areEqual(language, other.language) + || accessibilityChannel != other.accessibilityChannel + || !Util.areEqual(containerMimeType, other.containerMimeType) + || !Util.areEqual(sampleMimeType, other.sampleMimeType) + || !Util.areEqual(codecs, other.codecs) + || !Util.areEqual(drmInitData, other.drmInitData) + || !Util.areEqual(metadata, other.metadata) + || !Arrays.equals(projectionData, other.projectionData) + || initializationData.size() != other.initializationData.size()) { + return false; + } + for (int i = 0; i < initializationData.size(); i++) { + if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) { + return false; + } + } + return true; + } + + @TargetApi(16) + private static void maybeSetStringV16(MediaFormat format, String key, String value) { + if (value != null) { + format.setString(key, value); + } + } + + @TargetApi(16) + private static void maybeSetIntegerV16(MediaFormat format, String key, int value) { + if (value != NO_VALUE) { + format.setInteger(key, value); + } + } + + @TargetApi(16) + private static void maybeSetFloatV16(MediaFormat format, String key, float value) { + if (value != NO_VALUE) { + format.setFloat(key, value); + } + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(containerMimeType); + dest.writeString(sampleMimeType); + dest.writeString(codecs); + dest.writeInt(bitrate); + dest.writeInt(maxInputSize); + dest.writeInt(width); + dest.writeInt(height); + dest.writeFloat(frameRate); + dest.writeInt(rotationDegrees); + dest.writeFloat(pixelWidthHeightRatio); + dest.writeInt(projectionData != null ? 1 : 0); + if (projectionData != null) { + dest.writeByteArray(projectionData); + } + dest.writeInt(stereoMode); + dest.writeInt(channelCount); + dest.writeInt(sampleRate); + dest.writeInt(pcmEncoding); + dest.writeInt(encoderDelay); + dest.writeInt(encoderPadding); + dest.writeInt(selectionFlags); + dest.writeString(language); + dest.writeInt(accessibilityChannel); + dest.writeLong(subsampleOffsetUs); + int initializationDataSize = initializationData.size(); + dest.writeInt(initializationDataSize); + for (int i = 0; i < initializationDataSize; i++) { + dest.writeByteArray(initializationData.get(i)); + } + dest.writeParcelable(drmInitData, 0); + dest.writeParcelable(metadata, 0); + } + + /** + * {@link Creator} implementation. + */ + public static final Creator CREATOR = new Creator() { + + @Override + public Format createFromParcel(Parcel in) { + return new Format(in); + } + + @Override + public Format[] newArray(int size) { + return new Format[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/FormatHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/FormatHolder.java new file mode 100755 index 00000000000..b00dea7f49c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/FormatHolder.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +/** + * Holds a {@link Format}. + */ +public final class FormatHolder { + + /** + * The held {@link Format}. + */ + public Format format; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/LoadControl.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/LoadControl.java new file mode 100755 index 00000000000..902781c01aa --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/LoadControl.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.upstream.Allocator; + +/** + * Controls buffering of media. + */ +public interface LoadControl { + + /** + * Called by the player when prepared with a new source. + */ + void onPrepared(); + + /** + * Called by the player when a track selection occurs. + * + * @param renderers The renderers. + * @param trackGroups The {@link TrackGroup}s from which the selection was made. + * @param trackSelections The track selections that were made. + */ + void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, + TrackSelectionArray trackSelections); + + /** + * Called by the player when stopped. + */ + void onStopped(); + + /** + * Called by the player when released. + */ + void onReleased(); + + /** + * Returns the {@link Allocator} that should be used to obtain media buffer allocations. + */ + Allocator getAllocator(); + + /** + * Called by the player to determine whether sufficient media is buffered for playback to be + * started or resumed. + * + * @param bufferedDurationUs The duration of media that's currently buffered. + * @param rebuffering Whether the player is rebuffering. A rebuffer is defined to be caused by + * buffer depletion rather than a user action. Hence this parameter is false during initial + * buffering and when buffering as a result of a seek operation. + * @return Whether playback should be allowed to start or resume. + */ + boolean shouldStartPlayback(long bufferedDurationUs, boolean rebuffering); + + /** + * Called by the player to determine whether it should continue to load the source. + * + * @param bufferedDurationUs The duration of media that's currently buffered. + * @return Whether the loading should continue. + */ + boolean shouldContinueLoading(long bufferedDurationUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ParserException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ParserException.java new file mode 100755 index 00000000000..36a2ed4b83c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ParserException.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import java.io.IOException; + +/** + * Thrown when an error occurs parsing media data and metadata. + */ +public class ParserException extends IOException { + + public ParserException() { + super(); + } + + /** + * @param message The detail message for the exception. + */ + public ParserException(String message) { + super(message); + } + + /** + * @param cause The cause for the exception. + */ + public ParserException(Throwable cause) { + super(cause); + } + + /** + * @param message The detail message for the exception. + * @param cause The cause for the exception. + */ + public ParserException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Renderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Renderer.java new file mode 100755 index 00000000000..2c244451be4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Renderer.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.ExoPlayer.ExoPlayerComponent; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.util.MediaClock; +import java.io.IOException; + +/** + * Renders media read from a {@link SampleStream}. + *

        + * Internally, a renderer's lifecycle is managed by the owning {@link ExoPlayer}. The renderer is + * transitioned through various states as the overall playback state changes. The valid state + * transitions are shown below, annotated with the methods that are called during each transition. + *

        + * Renderer state transitions + *

        + */ +public interface Renderer extends ExoPlayerComponent { + + /** + * The renderer is disabled. + */ + int STATE_DISABLED = 0; + /** + * The renderer is enabled but not started. A renderer in this state is not actively rendering + * media, but will typically hold resources that it requires for rendering (e.g. media decoders). + */ + int STATE_ENABLED = 1; + /** + * The renderer is started. Calls to {@link #render(long, long)} will cause media to be rendered. + */ + int STATE_STARTED = 2; + + /** + * Returns the track type that the {@link Renderer} handles. For example, a video renderer will + * return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a + * text renderer will return {@link C#TRACK_TYPE_TEXT}, and so on. + * + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getTrackType(); + + /** + * Returns the capabilities of the renderer. + * + * @return The capabilities of the renderer. + */ + RendererCapabilities getCapabilities(); + + /** + * Sets the index of this renderer within the player. + * + * @param index The renderer index. + */ + void setIndex(int index); + + /** + * If the renderer advances its own playback position then this method returns a corresponding + * {@link MediaClock}. If provided, the player will use the returned {@link MediaClock} as its + * source of time during playback. A player may have at most one renderer that returns a + * {@link MediaClock} from this method. + * + * @return The {@link MediaClock} tracking the playback position of the renderer, or null. + */ + MediaClock getMediaClock(); + + /** + * Returns the current state of the renderer. + * + * @return The current state (one of the {@code STATE_*} constants). + */ + int getState(); + + /** + * Enables the renderer to consume from the specified {@link SampleStream}. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_DISABLED}. + * + * @param formats The enabled formats. + * @param stream The {@link SampleStream} from which the renderer should consume. + * @param positionUs The player's current position. + * @param joining Whether this renderer is being enabled to join an ongoing playback. + * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} + * before they are rendered. + * @throws ExoPlaybackException If an error occurs. + */ + void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, + long offsetUs) throws ExoPlaybackException; + + /** + * Starts the renderer, meaning that calls to {@link #render(long, long)} will cause media to be + * rendered. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}. + * + * @throws ExoPlaybackException If an error occurs. + */ + void start() throws ExoPlaybackException; + + /** + * Replaces the {@link SampleStream} from which samples will be consumed. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param formats The enabled formats. + * @param stream The {@link SampleStream} from which the renderer should consume. + * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before + * they are rendered. + * @throws ExoPlaybackException If an error occurs. + */ + void replaceStream(Format[] formats, SampleStream stream, long offsetUs) + throws ExoPlaybackException; + + /** + * Returns the {@link SampleStream} being consumed, or null if the renderer is disabled. + */ + SampleStream getStream(); + + /** + * Returns whether the renderer has read the current {@link SampleStream} to the end. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + */ + boolean hasReadStreamToEnd(); + + /** + * Signals to the renderer that the current {@link SampleStream} will be the final one supplied + * before it is next disabled or reset. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + */ + void setCurrentStreamIsFinal(); + + /** + * Throws an error that's preventing the renderer from reading from its {@link SampleStream}. Does + * nothing if no such error exists. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @throws IOException An error that's preventing the renderer from making progress or buffering + * more data. + */ + void maybeThrowStreamError() throws IOException; + + /** + * Signals to the renderer that a position discontinuity has occurred. + *

        + * After a position discontinuity, the renderer's {@link SampleStream} is guaranteed to provide + * samples starting from a key frame. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param positionUs The new playback position in microseconds. + * @throws ExoPlaybackException If an error occurs handling the reset. + */ + void resetPosition(long positionUs) throws ExoPlaybackException; + + /** + * Incrementally renders the {@link SampleStream}. + *

        + * If the renderer is in the {@link #STATE_ENABLED} state then each call to this method will do + * work toward being ready to render the {@link SampleStream} when the renderer is started. It may + * also render the very start of the media, for example the first frame of a video stream. If the + * renderer is in the {@link #STATE_STARTED} state then calls to this method will render the + * {@link SampleStream} in sync with the specified media positions. + *

        + * This method should return quickly, and should not block if the renderer is unable to make + * useful progress. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @param positionUs The current media time in microseconds, measured at the start of the + * current iteration of the rendering loop. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. + * @throws ExoPlaybackException If an error occurs. + */ + void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException; + + /** + * Whether the renderer is able to immediately render media from the current position. + *

        + * If the renderer is in the {@link #STATE_STARTED} state then returning true indicates that the + * renderer has everything that it needs to continue playback. Returning false indicates that + * the player should pause until the renderer is ready. + *

        + * If the renderer is in the {@link #STATE_ENABLED} state then returning true indicates that the + * renderer is ready for playback to be started. Returning false indicates that it is not. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @return Whether the renderer is ready to render media. + */ + boolean isReady(); + + /** + * Whether the renderer is ready for the {@link ExoPlayer} instance to transition to + * {@link ExoPlayer#STATE_ENDED}. The player will make this transition as soon as {@code true} is + * returned by all of its {@link Renderer}s. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. + * + * @return Whether the renderer is ready for the player to transition to the ended state. + */ + boolean isEnded(); + + /** + * Stops the renderer, transitioning it to the {@link #STATE_ENABLED} state. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_STARTED}. + * + * @throws ExoPlaybackException If an error occurs. + */ + void stop() throws ExoPlaybackException; + + /** + * Disable the renderer, transitioning it to the {@link #STATE_DISABLED} state. + *

        + * This method may be called when the renderer is in the following states: + * {@link #STATE_ENABLED}. + */ + void disable(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java new file mode 100755 index 00000000000..572968c2fdf --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/RendererCapabilities.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import org.telegram.messenger.exoplayer2.util.MimeTypes; + +/** + * Defines the capabilities of a {@link Renderer}. + */ +public interface RendererCapabilities { + + /** + * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of + * {@link #FORMAT_HANDLED}, {@link #FORMAT_EXCEEDS_CAPABILITIES}, + * {@link #FORMAT_UNSUPPORTED_SUBTYPE} and {@link #FORMAT_UNSUPPORTED_TYPE}. + */ + int FORMAT_SUPPORT_MASK = 0b11; + /** + * The {@link Renderer} is capable of rendering the format. + */ + int FORMAT_HANDLED = 0b11; + /** + * The {@link Renderer} is capable of rendering formats with the same mime type, but the + * properties of the format exceed the renderer's capability. + *

        + * Example: The {@link Renderer} is capable of rendering H264 and the format's mime type is + * {@link MimeTypes#VIDEO_H264}, but the format's resolution exceeds the maximum limit supported + * by the underlying H264 decoder. + */ + int FORMAT_EXCEEDS_CAPABILITIES = 0b10; + /** + * The {@link Renderer} is a general purpose renderer for formats of the same top-level type, + * but is not capable of rendering the format or any other format with the same mime type because + * the sub-type is not supported. + *

        + * Example: The {@link Renderer} is a general purpose audio renderer and the format's + * mime type matches audio/[subtype], but there does not exist a suitable decoder for [subtype]. + */ + int FORMAT_UNSUPPORTED_SUBTYPE = 0b01; + /** + * The {@link Renderer} is not capable of rendering the format, either because it does not + * support the format's top-level type, or because it's a specialized renderer for a different + * mime type. + *

        + * Example: The {@link Renderer} is a general purpose video renderer, but the format has an + * audio mime type. + */ + int FORMAT_UNSUPPORTED_TYPE = 0b00; + + /** + * A mask to apply to the result of {@link #supportsFormat(Format)} to obtain one of + * {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and {@link #ADAPTIVE_NOT_SUPPORTED}. + */ + int ADAPTIVE_SUPPORT_MASK = 0b1100; + /** + * The {@link Renderer} can seamlessly adapt between formats. + */ + int ADAPTIVE_SEAMLESS = 0b1000; + /** + * The {@link Renderer} can adapt between formats, but may suffer a brief discontinuity + * (~50-100ms) when adaptation occurs. + */ + int ADAPTIVE_NOT_SEAMLESS = 0b0100; + /** + * The {@link Renderer} does not support adaptation between formats. + */ + int ADAPTIVE_NOT_SUPPORTED = 0b0000; + + /** + * Returns the track type that the {@link Renderer} handles. For example, a video renderer will + * return {@link C#TRACK_TYPE_VIDEO}, an audio renderer will return {@link C#TRACK_TYPE_AUDIO}, a + * text renderer will return {@link C#TRACK_TYPE_TEXT}, and so on. + * + * @see Renderer#getTrackType() + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. + */ + int getTrackType(); + + /** + * Returns the extent to which the {@link Renderer} supports a given format. The returned value is + * the bitwise OR of two properties: + *

          + *
        • The level of support for the format itself. One of {@link #FORMAT_HANDLED}, + * {@link #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_SUBTYPE} and + * {@link #FORMAT_UNSUPPORTED_TYPE}.
        • + *
        • The level of support for adapting from the format to another format of the same mime type. + * One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and + * {@link #ADAPTIVE_NOT_SUPPORTED}.
        • + *
        + * The individual properties can be retrieved by performing a bitwise AND with + * {@link #FORMAT_SUPPORT_MASK} and {@link #ADAPTIVE_SUPPORT_MASK} respectively. + * + * @param format The format. + * @return The extent to which the renderer is capable of supporting the given format. + * @throws ExoPlaybackException If an error occurs. + */ + int supportsFormat(Format format) throws ExoPlaybackException; + + /** + * Returns the extent to which the {@link Renderer} supports adapting between supported formats + * that have different mime types. + * + * @return The extent to which the renderer supports adapting between supported formats that have + * different mime types. One of {@link #ADAPTIVE_SEAMLESS}, {@link #ADAPTIVE_NOT_SEAMLESS} and + * {@link #ADAPTIVE_NOT_SUPPORTED}. + * @throws ExoPlaybackException If an error occurs. + */ + int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java new file mode 100755 index 00000000000..e8d9163b46f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/SimpleExoPlayer.java @@ -0,0 +1,1046 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.media.MediaCodec; +import android.media.PlaybackParams; +import android.os.Handler; +import android.support.annotation.IntDef; +import android.util.Log; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.TextureView; +import org.telegram.messenger.exoplayer2.audio.AudioCapabilities; +import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener; +import org.telegram.messenger.exoplayer2.audio.AudioTrack; +import org.telegram.messenger.exoplayer2.audio.MediaCodecAudioRenderer; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.MetadataRenderer; +import org.telegram.messenger.exoplayer2.metadata.id3.Id3Decoder; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.TextRenderer; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelector; +import org.telegram.messenger.exoplayer2.video.MediaCodecVideoRenderer; +import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +/** + * An {@link ExoPlayer} implementation that uses default {@link Renderer} components. Instances can + * be obtained from {@link ExoPlayerFactory}. + */ +@TargetApi(16) +public class SimpleExoPlayer implements ExoPlayer { + + /** + * A listener for video rendering information from a {@link SimpleExoPlayer}. + */ + public interface VideoListener { + + /** + * Called each time there's a change in the size of the video being rendered. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise + * rotation in degrees that the application should apply for the video for it to be rendered + * in the correct orientation. This value will always be zero on API levels 21 and above, + * since the renderer will apply all necessary rotations internally. On earlier API levels + * this is not possible. Applications that use {@link android.view.TextureView} can apply + * the rotation by calling {@link android.view.TextureView#setTransform}. Applications that + * do not expect to encounter rotated videos can safely ignore this parameter. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case + * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio); + + /** + * Called when a frame is rendered for the first time since setting the surface, and when a + * frame is rendered for the first time since a video track was selected. + */ + void onRenderedFirstFrame(); + + boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture); + void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture); + } + + /** + * Modes for using extension renderers. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EXTENSION_RENDERER_MODE_OFF, EXTENSION_RENDERER_MODE_ON, EXTENSION_RENDERER_MODE_PREFER}) + public @interface ExtensionRendererMode {} + /** + * Do not allow use of extension renderers. + */ + public static final int EXTENSION_RENDERER_MODE_OFF = 0; + /** + * Allow use of extension renderers. Extension renderers are indexed after core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use a core renderer to an extension renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_ON = 1; + /** + * Allow use of extension renderers. Extension renderers are indexed before core renderers of the + * same type. A {@link TrackSelector} that prefers the first suitable renderer will therefore + * prefer to use an extension renderer to a core renderer in the case that both are able to play + * a given track. + */ + public static final int EXTENSION_RENDERER_MODE_PREFER = 2; + + private static final String TAG = "SimpleExoPlayer"; + protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; + + private final ExoPlayer player; + private final Renderer[] renderers; + private final ComponentListener componentListener; + private final Handler mainHandler; + private final int videoRendererCount; + private final int audioRendererCount; + + private boolean needSetSurface = true; + + private Format videoFormat; + private Format audioFormat; + + private Surface surface; + private boolean ownsSurface; + @C.VideoScalingMode + private int videoScalingMode; + private SurfaceHolder surfaceHolder; + private TextureView textureView; + private TextRenderer.Output textOutput; + private MetadataRenderer.Output metadataOutput; + private VideoListener videoListener; + private AudioRendererEventListener audioDebugListener; + private VideoRendererEventListener videoDebugListener; + private DecoderCounters videoDecoderCounters; + private DecoderCounters audioDecoderCounters; + private int audioSessionId; + @C.StreamType + private int audioStreamType; + private float audioVolume; + private PlaybackParamsHolder playbackParamsHolder; + + protected SimpleExoPlayer(Context context, TrackSelector trackSelector, LoadControl loadControl, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs) { + mainHandler = new Handler(); + componentListener = new ComponentListener(); + + // Build the renderers. + ArrayList renderersList = new ArrayList<>(); + buildRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + allowedVideoJoiningTimeMs, renderersList); + renderers = renderersList.toArray(new Renderer[renderersList.size()]); + + // Obtain counts of video and audio renderers. + int videoRendererCount = 0; + int audioRendererCount = 0; + for (Renderer renderer : renderers) { + switch (renderer.getTrackType()) { + case C.TRACK_TYPE_VIDEO: + videoRendererCount++; + break; + case C.TRACK_TYPE_AUDIO: + audioRendererCount++; + break; + } + } + this.videoRendererCount = videoRendererCount; + this.audioRendererCount = audioRendererCount; + + // Set initial values. + audioVolume = 1; + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + audioStreamType = C.STREAM_TYPE_DEFAULT; + videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT; + + // Build the player and associated objects. + player = new ExoPlayerImpl(renderers, trackSelector, loadControl); + } + + /** + * Sets the video scaling mode. + *

        + * Note that the scaling mode only applies if a {@link MediaCodec}-based video {@link Renderer} is + * enabled and if the output surface is owned by a {@link android.view.SurfaceView}. + * + * @param videoScalingMode The video scaling mode. + */ + public void setVideoScalingMode(@C.VideoScalingMode int videoScalingMode) { + this.videoScalingMode = videoScalingMode; + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SCALING_MODE, + videoScalingMode); + } + } + player.sendMessages(messages); + } + + /** + * Returns the video scaling mode. + */ + public @C.VideoScalingMode int getVideoScalingMode() { + return videoScalingMode; + } + + /** + * Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView} + * currently set on the player. + */ + public void clearVideoSurface() { + setVideoSurface(null); + } + + /** + * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for + * tracking the lifecycle of the surface, and must clear the surface by calling + * {@code setVideoSurface(null)} if the surface is destroyed. + *

        + * If the surface is held by a {@link SurfaceView}, {@link TextureView} or {@link SurfaceHolder} + * then it's recommended to use {@link #setVideoSurfaceView(SurfaceView)}, + * {@link #setVideoTextureView(TextureView)} or {@link #setVideoSurfaceHolder(SurfaceHolder)} + * rather than this method, since passing the holder allows the player to track the lifecycle of + * the surface automatically. + * + * @param surface The {@link Surface}. + */ + public void setVideoSurface(Surface surface) { + removeSurfaceCallbacks(); + setVideoSurfaceInternal(surface, false); + } + + /** + * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be + * rendered. The player will track the lifecycle of the surface automatically. + * + * @param surfaceHolder The surface holder. + */ + public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) { + removeSurfaceCallbacks(); + this.surfaceHolder = surfaceHolder; + if (surfaceHolder == null) { + setVideoSurfaceInternal(null, false); + } else { + setVideoSurfaceInternal(surfaceHolder.getSurface(), false); + surfaceHolder.addCallback(componentListener); + } + } + + /** + * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param surfaceView The surface view. + */ + public void setVideoSurfaceView(SurfaceView surfaceView) { + setVideoSurfaceHolder(surfaceView.getHolder()); + } + + /** + * Sets the {@link TextureView} onto which video will be rendered. The player will track the + * lifecycle of the surface automatically. + * + * @param textureView The texture view. + */ + public ComponentListener setVideoTextureView(TextureView textureView) { + removeSurfaceCallbacks(); + this.textureView = textureView; + if (textureView == null) { + setVideoSurfaceInternal(null, true); + } else { + if (textureView.getSurfaceTextureListener() != null) { + Log.w(TAG, "Replacing existing SurfaceTextureListener."); + } + SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); + setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture), true); + if (surfaceTexture != null) { + needSetSurface = false; + } + + textureView.setSurfaceTextureListener(componentListener); + } + return componentListener; + } + + /** + * Sets the stream type for audio playback (see {@link C.StreamType} and + * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type + * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. + *

        + * Note that when the stream type changes, the AudioTrack must be reinitialized, which can + * introduce a brief gap in audio output. Note also that tracks in the same audio session must + * share the same routing, so a new audio session id will be generated. + * + * @param audioStreamType The stream type for audio playback. + */ + public void setAudioStreamType(@C.StreamType int audioStreamType) { + this.audioStreamType = audioStreamType; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_STREAM_TYPE, audioStreamType); + } + } + player.sendMessages(messages); + } + + /** + * Returns the stream type for audio playback. + */ + public @C.StreamType int getAudioStreamType() { + return audioStreamType; + } + + /** + * Sets the audio volume, with 0 being silence and 1 being unity gain. + * + * @param audioVolume The audio volume. + */ + public void setVolume(float audioVolume) { + this.audioVolume = audioVolume; + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_VOLUME, audioVolume); + } + } + player.sendMessages(messages); + } + + /** + * Returns the audio volume, with 0 being silence and 1 being unity gain. + */ + public float getVolume() { + return audioVolume; + } + + /** + * Sets the {@link PlaybackParams} governing audio playback. + * + * @param params The {@link PlaybackParams}, or null to clear any previously set parameters. + */ + @TargetApi(23) + public void setPlaybackParams(PlaybackParams params) { + if (params != null) { + // The audio renderers will call this on the playback thread to ensure they can query + // parameters without failure. We do the same up front, which is redundant except that it + // ensures an immediate call to getPlaybackParams will retrieve the instance with defaults + // allowed, rather than this change becoming visible sometime later once the audio renderers + // receive the parameters. + params.allowDefaults(); + playbackParamsHolder = new PlaybackParamsHolder(params); + } else { + playbackParamsHolder = null; + } + ExoPlayerMessage[] messages = new ExoPlayerMessage[audioRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_AUDIO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_PLAYBACK_PARAMS, params); + } + } + player.sendMessages(messages); + } + + /** + * Returns the {@link PlaybackParams} governing audio playback, or null if not set. + */ + @TargetApi(23) + public PlaybackParams getPlaybackParams() { + return playbackParamsHolder == null ? null : playbackParamsHolder.params; + } + + /** + * Returns the video format currently being played, or null if no video is being played. + */ + public Format getVideoFormat() { + return videoFormat; + } + + /** + * Returns the audio format currently being played, or null if no audio is being played. + */ + public Format getAudioFormat() { + return audioFormat; + } + + /** + * Returns the audio session identifier, or {@code AudioTrack.SESSION_ID_NOT_SET} if not set. + */ + public int getAudioSessionId() { + return audioSessionId; + } + + /** + * Returns {@link DecoderCounters} for video, or null if no video is being played. + */ + public DecoderCounters getVideoDecoderCounters() { + return videoDecoderCounters; + } + + /** + * Returns {@link DecoderCounters} for audio, or null if no audio is being played. + */ + public DecoderCounters getAudioDecoderCounters() { + return audioDecoderCounters; + } + + /** + * Sets a listener to receive video events. + * + * @param listener The listener. + */ + public void setVideoListener(VideoListener listener) { + videoListener = listener; + } + + /** + * Sets a listener to receive debug events from the video renderer. + * + * @param listener The listener. + */ + public void setVideoDebugListener(VideoRendererEventListener listener) { + videoDebugListener = listener; + } + + /** + * Sets a listener to receive debug events from the audio renderer. + * + * @param listener The listener. + */ + public void setAudioDebugListener(AudioRendererEventListener listener) { + audioDebugListener = listener; + } + + /** + * Sets an output to receive text events. + * + * @param output The output. + */ + public void setTextOutput(TextRenderer.Output output) { + textOutput = output; + } + + /** + * @deprecated Use {@link #setMetadataOutput(MetadataRenderer.Output)} instead. + * @param output The output. + */ + @Deprecated + public void setId3Output(MetadataRenderer.Output output) { + setMetadataOutput(output); + } + + /** + * Sets a listener to receive metadata events. + * + * @param output The output. + */ + public void setMetadataOutput(MetadataRenderer.Output output) { + metadataOutput = output; + } + + // ExoPlayer implementation + + @Override + public void addListener(EventListener listener) { + player.addListener(listener); + } + + @Override + public void removeListener(EventListener listener) { + player.removeListener(listener); + } + + @Override + public int getPlaybackState() { + return player.getPlaybackState(); + } + + @Override + public void prepare(MediaSource mediaSource) { + player.prepare(mediaSource); + } + + @Override + public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetTimeline) { + player.prepare(mediaSource, resetPosition, resetTimeline); + } + + @Override + public void setPlayWhenReady(boolean playWhenReady) { + player.setPlayWhenReady(playWhenReady); + } + + @Override + public boolean getPlayWhenReady() { + return player.getPlayWhenReady(); + } + + @Override + public boolean isLoading() { + return player.isLoading(); + } + + @Override + public void seekToDefaultPosition() { + player.seekToDefaultPosition(); + } + + @Override + public void seekToDefaultPosition(int windowIndex) { + player.seekToDefaultPosition(windowIndex); + } + + @Override + public void seekTo(long positionMs) { + player.seekTo(positionMs); + } + + @Override + public void seekTo(int windowIndex, long positionMs) { + player.seekTo(windowIndex, positionMs); + } + + @Override + public void stop() { + player.stop(); + } + + @Override + public void release() { + player.release(); + removeSurfaceCallbacks(); + if (surface != null) { + if (ownsSurface) { + surface.release(); + } + surface = null; + } + } + + @Override + public void sendMessages(ExoPlayerMessage... messages) { + player.sendMessages(messages); + } + + @Override + public void blockingSendMessages(ExoPlayerMessage... messages) { + player.blockingSendMessages(messages); + } + + @Override + public int getCurrentPeriodIndex() { + return player.getCurrentPeriodIndex(); + } + + @Override + public int getCurrentWindowIndex() { + return player.getCurrentWindowIndex(); + } + + @Override + public long getDuration() { + return player.getDuration(); + } + + @Override + public long getCurrentPosition() { + return player.getCurrentPosition(); + } + + @Override + public long getBufferedPosition() { + return player.getBufferedPosition(); + } + + @Override + public int getBufferedPercentage() { + return player.getBufferedPercentage(); + } + + @Override + public int getRendererCount() { + return player.getRendererCount(); + } + + @Override + public int getRendererType(int index) { + return player.getRendererType(index); + } + + @Override + public TrackGroupArray getCurrentTrackGroups() { + return player.getCurrentTrackGroups(); + } + + @Override + public TrackSelectionArray getCurrentTrackSelections() { + return player.getCurrentTrackSelections(); + } + + @Override + public Timeline getCurrentTimeline() { + return player.getCurrentTimeline(); + } + + @Override + public Object getCurrentManifest() { + return player.getCurrentManifest(); + } + + // Renderer building. + + private void buildRenderers(Context context, Handler mainHandler, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, long allowedVideoJoiningTimeMs, + ArrayList out) { + buildVideoRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + componentListener, allowedVideoJoiningTimeMs, out); + buildAudioRenderers(context, mainHandler, drmSessionManager, extensionRendererMode, + componentListener, out); + buildTextRenderers(context, mainHandler, extensionRendererMode, componentListener, out); + buildMetadataRenderers(context, mainHandler, extensionRendererMode, componentListener, out); + buildMiscellaneousRenderers(context, mainHandler, extensionRendererMode, out); + } + + /** + * Builds video renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param mainHandler A handler associated with the main thread's looper. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will + * not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode. + * @param eventListener An event listener. + * @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video renderers + * can attempt to seamlessly join an ongoing playback. + * @param out An array to which the built renderers should be appended. + */ + protected void buildVideoRenderers(Context context, Handler mainHandler, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, VideoRendererEventListener eventListener, + long allowedVideoJoiningTimeMs, ArrayList out) { + out.add(new MediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, + allowedVideoJoiningTimeMs, drmSessionManager, false, mainHandler, eventListener, + MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("org.telegram.messenger.exoplayer2.ext.vp9.LibvpxVideoRenderer"); + Constructor constructor = clazz.getConstructor(boolean.class, long.class, Handler.class, + VideoRendererEventListener.class, int.class); + Renderer renderer = (Renderer) constructor.newInstance(true, allowedVideoJoiningTimeMs, + mainHandler, componentListener, MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibvpxVideoRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds audio renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param mainHandler A handler associated with the main thread's looper. + * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will + * not be used for DRM protected playbacks. + * @param extensionRendererMode The extension renderer mode. + * @param eventListener An event listener. + * @param out An array to which the built renderers should be appended. + */ + protected void buildAudioRenderers(Context context, Handler mainHandler, + DrmSessionManager drmSessionManager, + @ExtensionRendererMode int extensionRendererMode, AudioRendererEventListener eventListener, + ArrayList out) { + out.add(new MediaCodecAudioRenderer(MediaCodecSelector.DEFAULT, drmSessionManager, true, + mainHandler, eventListener, AudioCapabilities.getCapabilities(context))); + + if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) { + return; + } + int extensionRendererIndex = out.size(); + if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) { + extensionRendererIndex--; + } + + try { + Class clazz = + Class.forName("org.telegram.messenger.exoplayer2.ext.opus.LibopusAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class); + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibopusAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("org.telegram.messenger.exoplayer2.ext.flac.LibflacAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class); + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded LibflacAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + Class clazz = + Class.forName("org.telegram.messenger.exoplayer2.ext.ffmpeg.FfmpegAudioRenderer"); + Constructor constructor = clazz.getConstructor(Handler.class, + AudioRendererEventListener.class); + Renderer renderer = (Renderer) constructor.newInstance(mainHandler, componentListener); + out.add(extensionRendererIndex++, renderer); + Log.i(TAG, "Loaded FfmpegAudioRenderer."); + } catch (ClassNotFoundException e) { + // Expected if the app was built without the extension. + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Builds text renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param mainHandler A handler associated with the main thread's looper. + * @param extensionRendererMode The extension renderer mode. + * @param output An output for the renderers. + * @param out An array to which the built renderers should be appended. + */ + protected void buildTextRenderers(Context context, Handler mainHandler, + @ExtensionRendererMode int extensionRendererMode, TextRenderer.Output output, + ArrayList out) { + out.add(new TextRenderer(output, mainHandler.getLooper())); + } + + /** + * Builds metadata renderers for use by the player. + * + * @param context The {@link Context} associated with the player. + * @param mainHandler A handler associated with the main thread's looper. + * @param extensionRendererMode The extension renderer mode. + * @param output An output for the renderers. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMetadataRenderers(Context context, Handler mainHandler, + @ExtensionRendererMode int extensionRendererMode, MetadataRenderer.Output output, + ArrayList out) { + out.add(new MetadataRenderer(output, mainHandler.getLooper(), new Id3Decoder())); + } + + /** + * Builds any miscellaneous renderers used by the player. + * + * @param context The {@link Context} associated with the player. + * @param mainHandler A handler associated with the main thread's looper. + * @param extensionRendererMode The extension renderer mode. + * @param out An array to which the built renderers should be appended. + */ + protected void buildMiscellaneousRenderers(Context context, Handler mainHandler, + @ExtensionRendererMode int extensionRendererMode, ArrayList out) { + // Do nothing. + } + + // Internal methods. + + private void removeSurfaceCallbacks() { + if (textureView != null) { + if (textureView.getSurfaceTextureListener() != componentListener) { + Log.w(TAG, "SurfaceTextureListener already unset or replaced."); + } else { + textureView.setSurfaceTextureListener(null); + } + textureView = null; + } + if (surfaceHolder != null) { + surfaceHolder.removeCallback(componentListener); + surfaceHolder = null; + } + } + + private void setVideoSurfaceInternal(Surface surface, boolean ownsSurface) { + // Note: We don't turn this method into a no-op if the surface is being replaced with itself + // so as to ensure onRenderedFirstFrame callbacks are still called in this case. + ExoPlayerMessage[] messages = new ExoPlayerMessage[videoRendererCount]; + int count = 0; + for (Renderer renderer : renderers) { + if (renderer.getTrackType() == C.TRACK_TYPE_VIDEO) { + messages[count++] = new ExoPlayerMessage(renderer, C.MSG_SET_SURFACE, surface); + } + } + if (this.surface != null && this.surface != surface) { + // If we created this surface, we are responsible for releasing it. + if (this.ownsSurface) { + this.surface.release(); + } + // We're replacing a surface. Block to ensure that it's not accessed after the method returns. + player.blockingSendMessages(messages); + } else { + player.sendMessages(messages); + } + this.surface = surface; + this.ownsSurface = ownsSurface; + } + + public ComponentListener getComponentListener() { + return componentListener; + } + + public final class ComponentListener implements VideoRendererEventListener, + AudioRendererEventListener, TextRenderer.Output, MetadataRenderer.Output, + SurfaceHolder.Callback, TextureView.SurfaceTextureListener { + + // VideoRendererEventListener implementation + + @Override + public void onVideoEnabled(DecoderCounters counters) { + videoDecoderCounters = counters; + if (videoDebugListener != null) { + videoDebugListener.onVideoEnabled(counters); + } + } + + @Override + public void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs) { + if (videoDebugListener != null) { + videoDebugListener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + } + + @Override + public void onVideoInputFormatChanged(Format format) { + videoFormat = format; + if (videoDebugListener != null) { + videoDebugListener.onVideoInputFormatChanged(format); + } + } + + @Override + public void onDroppedFrames(int count, long elapsed) { + if (videoDebugListener != null) { + videoDebugListener.onDroppedFrames(count, elapsed); + } + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio) { + if (videoListener != null) { + videoListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + if (videoDebugListener != null) { + videoDebugListener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + } + + @Override + public void onRenderedFirstFrame(Surface surface) { + if (videoListener != null && SimpleExoPlayer.this.surface == surface) { + videoListener.onRenderedFirstFrame(); + } + if (videoDebugListener != null) { + videoDebugListener.onRenderedFirstFrame(surface); + } + } + + @Override + public void onVideoDisabled(DecoderCounters counters) { + if (videoDebugListener != null) { + videoDebugListener.onVideoDisabled(counters); + } + videoFormat = null; + videoDecoderCounters = null; + } + + // AudioRendererEventListener implementation + + @Override + public void onAudioEnabled(DecoderCounters counters) { + audioDecoderCounters = counters; + if (audioDebugListener != null) { + audioDebugListener.onAudioEnabled(counters); + } + } + + @Override + public void onAudioSessionId(int sessionId) { + audioSessionId = sessionId; + if (audioDebugListener != null) { + audioDebugListener.onAudioSessionId(sessionId); + } + } + + @Override + public void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs) { + if (audioDebugListener != null) { + audioDebugListener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + } + + @Override + public void onAudioInputFormatChanged(Format format) { + audioFormat = format; + if (audioDebugListener != null) { + audioDebugListener.onAudioInputFormatChanged(format); + } + } + + @Override + public void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, + long elapsedSinceLastFeedMs) { + if (audioDebugListener != null) { + audioDebugListener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + } + + @Override + public void onAudioDisabled(DecoderCounters counters) { + if (audioDebugListener != null) { + audioDebugListener.onAudioDisabled(counters); + } + audioFormat = null; + audioDecoderCounters = null; + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + } + + // TextRenderer.Output implementation + + @Override + public void onCues(List cues) { + if (textOutput != null) { + textOutput.onCues(cues); + } + } + + // MetadataRenderer.Output implementation + + @Override + public void onMetadata(Metadata metadata) { + if (metadataOutput != null) { + metadataOutput.onMetadata(metadata); + } + } + + // SurfaceHolder.Callback implementation + + @Override + public void surfaceCreated(SurfaceHolder holder) { + setVideoSurfaceInternal(holder.getSurface(), false); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + // Do nothing. + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + setVideoSurfaceInternal(null, false); + } + + // TextureView.SurfaceTextureListener implementation + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { + if (needSetSurface) { + setVideoSurfaceInternal(new Surface(surfaceTexture), true); + needSetSurface = false; + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) { + // Do nothing. + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + if (videoListener.onSurfaceDestroyed(surfaceTexture)) { + return false; + } + setVideoSurfaceInternal(null, true); + needSetSurface = true; + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + videoListener.onSurfaceTextureUpdated(surfaceTexture); + } + + } + + @TargetApi(23) + private static final class PlaybackParamsHolder { + + public final PlaybackParams params; + + public PlaybackParamsHolder(PlaybackParams params) { + this.params = params; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java new file mode 100755 index 00000000000..a99c92452a5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/Timeline.java @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2; + +/** + * A representation of media currently available for playback. + *

        + * Timeline instances are immutable. For cases where the available media is changing dynamically + * (e.g. live streams) a timeline provides a snapshot of the media currently available. + *

        + * A timeline consists of related {@link Period}s and {@link Window}s. A period defines a single + * logical piece of media, for example a media file. A window spans one or more periods, defining + * the region within those periods that's currently available for playback along with additional + * information such as whether seeking is supported within the window. Each window defines a default + * position, which is the position from which playback will start when the player starts playing the + * window. The following examples illustrate timelines for various use cases. + * + *

        Single media file or on-demand stream

        + *

        + * Example timeline for a single file + *

        + * A timeline for a single media file or on-demand stream consists of a single period and window. + * The window spans the whole period, indicating that all parts of the media are available for + * playback. The window's default position is typically at the start of the period (indicated by the + * black dot in the figure above). + * + *

        Playlist of media files or on-demand streams

        + *

        + * Example timeline for a playlist of files + *

        + * A timeline for a playlist of media files or on-demand streams consists of multiple periods, each + * with its own window. Each window spans the whole of the corresponding period, and typically has a + * default position at the start of the period. The properties of the periods and windows (e.g. + * their durations and whether the window is seekable) will often only become known when the player + * starts buffering the corresponding file or stream. + * + *

        Live stream with limited availability

        + *

        + * Example timeline for a live stream with
+ *       limited availability + *

        + * A timeline for a live stream consists of a period whose duration is unknown, since it's + * continually extending as more content is broadcast. If content only remains available for a + * limited period of time then the window may start at a non-zero position, defining the region of + * content that can still be played. The window will have {@link Window#isDynamic} set to true if + * the stream is still live. Its default position is typically near to the live edge (indicated by + * the black dot in the figure above). + * + *

        Live stream with indefinite availability

        + *

        + * Example timeline for a live stream with
+ *       indefinite availability + *

        + * A timeline for a live stream with indefinite availability is similar to the + * Live stream with limited availability case, except that the window + * starts at the beginning of the period to indicate that all of the previously broadcast content + * can still be played. + * + *

        Live stream with multiple periods

        + *

        + * Example timeline for a live stream
+ *       with multiple periods + *

        + * This case arises when a live stream is explicitly divided into separate periods, for example at + * content and advert boundaries. This case is similar to the Live stream + * with limited availability case, except that the window may span more than one period. + * Multiple periods are also possible in the indefinite availability case. + * + *

        On-demand pre-roll followed by live stream

        + *

        + * Example timeline for an on-demand pre-roll
+ *       followed by a live stream + *

        + * This case is the concatenation of the Single media file or on-demand + * stream and Live stream with multiple periods cases. When playback + * of the pre-roll ends, playback of the live stream will start from its default position near the + * live edge. + */ +public abstract class Timeline { + + /** + * An empty timeline. + */ + public static final Timeline EMPTY = new Timeline() { + + @Override + public int getWindowCount() { + return 0; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getPeriodCount() { + return 0; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + throw new IndexOutOfBoundsException(); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return C.INDEX_UNSET; + } + + }; + + /** + * Returns whether the timeline is empty. + */ + public final boolean isEmpty() { + return getWindowCount() == 0; + } + + /** + * Returns the number of windows in the timeline. + */ + public abstract int getWindowCount(); + + /** + * Populates a {@link Window} with data for the window at the specified index. Does not populate + * {@link Window#id}. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @return The populated {@link Window}, for convenience. + */ + public final Window getWindow(int windowIndex, Window window) { + return getWindow(windowIndex, window, false); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @return The populated {@link Window}, for convenience. + */ + public Window getWindow(int windowIndex, Window window, boolean setIds) { + return getWindow(windowIndex, window, setIds, 0); + } + + /** + * Populates a {@link Window} with data for the window at the specified index. + * + * @param windowIndex The index of the window. + * @param window The {@link Window} to populate. Must not be null. + * @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to + * null. The caller should pass false for efficiency reasons unless the field is required. + * @param defaultPositionProjectionUs A duration into the future that the populated window's + * default start position should be projected. + * @return The populated {@link Window}, for convenience. + */ + public abstract Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs); + + /** + * Returns the number of periods in the timeline. + */ + public abstract int getPeriodCount(); + + /** + * Populates a {@link Period} with data for the period at the specified index. Does not populate + * {@link Period#id} and {@link Period#uid}. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @return The populated {@link Period}, for convenience. + */ + public final Period getPeriod(int periodIndex, Period period) { + return getPeriod(periodIndex, period, false); + } + + /** + * Populates a {@link Period} with data for the period at the specified index. + * + * @param periodIndex The index of the period. + * @param period The {@link Period} to populate. Must not be null. + * @param setIds Whether {@link Period#id} and {@link Period#uid} should be populated. If false, + * the fields will be set to null. The caller should pass false for efficiency reasons unless + * the fields are required. + * @return The populated {@link Period}, for convenience. + */ + public abstract Period getPeriod(int periodIndex, Period period, boolean setIds); + + /** + * Returns the index of the period identified by its unique {@code id}, or {@link C#INDEX_UNSET} + * if the period is not in the timeline. + * + * @param uid A unique identifier for a period. + * @return The index of the period, or {@link C#INDEX_UNSET} if the period was not found. + */ + public abstract int getIndexOfPeriod(Object uid); + + /** + * Holds information about a window in a {@link Timeline}. A window defines a region of media + * currently available for playback along with additional information such as whether seeking is + * supported within the window. See {@link Timeline} for more details. The figure below shows some + * of the information defined by a window, as well as how this information relates to + * corresponding {@link Period}s in the timeline. + *

        + * Information defined by a timeline window + *

        + */ + public static final class Window { + + /** + * An identifier for the window. Not necessarily unique. + */ + public Object id; + + /** + * The start time of the presentation to which this window belongs in milliseconds since the + * epoch, or {@link C#TIME_UNSET} if unknown or not applicable. For informational purposes only. + */ + public long presentationStartTimeMs; + + /** + * The window's start time in milliseconds since the epoch, or {@link C#TIME_UNSET} if unknown + * or not applicable. For informational purposes only. + */ + public long windowStartTimeMs; + + /** + * Whether it's possible to seek within this window. + */ + public boolean isSeekable; + + /** + * Whether this window may change when the timeline is updated. + */ + public boolean isDynamic; + + /** + * The index of the first period that belongs to this window. + */ + public int firstPeriodIndex; + + /** + * The index of the last period that belongs to this window. + */ + public int lastPeriodIndex; + + private long defaultPositionUs; + private long durationUs; + private long positionInFirstPeriodUs; + + /** + * Sets the data held by this window. + */ + public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs, + boolean isSeekable, boolean isDynamic, long defaultPositionUs, long durationUs, + int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { + this.id = id; + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + this.defaultPositionUs = defaultPositionUs; + this.durationUs = durationUs; + this.firstPeriodIndex = firstPeriodIndex; + this.lastPeriodIndex = lastPeriodIndex; + this.positionInFirstPeriodUs = positionInFirstPeriodUs; + return this; + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in milliseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a + * non-zero default position projection, and if the specified projection cannot be performed + * whilst remaining within the bounds of the window. + */ + public long getDefaultPositionMs() { + return C.usToMs(defaultPositionUs); + } + + /** + * Returns the default position relative to the start of the window at which to begin playback, + * in microseconds. May be {@link C#TIME_UNSET} if and only if the window was populated with a + * non-zero default position projection, and if the specified projection cannot be performed + * whilst remaining within the bounds of the window. + */ + public long getDefaultPositionUs() { + return defaultPositionUs; + } + + /** + * Returns the duration of the window in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this window in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in milliseconds. + */ + public long getPositionInFirstPeriodMs() { + return C.usToMs(positionInFirstPeriodUs); + } + + /** + * Returns the position of the start of this window relative to the start of the first period + * belonging to it, in microseconds. + */ + public long getPositionInFirstPeriodUs() { + return positionInFirstPeriodUs; + } + + } + + /** + * Holds information about a period in a {@link Timeline}. A period defines a single logical piece + * of media, for example a a media file. See {@link Timeline} for more details. The figure below + * shows some of the information defined by a period, as well as how this information relates to a + * corresponding {@link Window} in the timeline. + *

        + * Information defined by a period + *

        + */ + public static final class Period { + + /** + * An identifier for the period. Not necessarily unique. + */ + public Object id; + + /** + * A unique identifier for the period. + */ + public Object uid; + + /** + * The index of the window to which this period belongs. + */ + public int windowIndex; + + private long durationUs; + private long positionInWindowUs; + + /** + * Sets the data held by this period. + */ + public Period set(Object id, Object uid, int windowIndex, long durationUs, + long positionInWindowUs) { + this.id = id; + this.uid = uid; + this.windowIndex = windowIndex; + this.durationUs = durationUs; + this.positionInWindowUs = positionInWindowUs; + return this; + } + + /** + * Returns the duration of the period in milliseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationMs() { + return C.usToMs(durationUs); + } + + /** + * Returns the duration of this period in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public long getDurationUs() { + return durationUs; + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in milliseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowMs() { + return C.usToMs(positionInWindowUs); + } + + /** + * Returns the position of the start of this period relative to the start of the window to which + * it belongs, in microseconds. May be negative if the start of the period is not within the + * window. + */ + public long getPositionInWindowUs() { + return positionInWindowUs; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Ac3Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Ac3Util.java new file mode 100755 index 00000000000..73269614cea --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/Ac3Util.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.nio.ByteBuffer; + +/** + * Utility methods for parsing (E-)AC-3 syncframes, which are access units in (E-)AC-3 bitstreams. + */ +public final class Ac3Util { + + /** + * The number of new samples per (E-)AC-3 audio block. + */ + private static final int AUDIO_SAMPLES_PER_AUDIO_BLOCK = 256; + /** + * Each syncframe has 6 blocks that provide 256 new audio samples. See ETSI TS 102 366 4.1. + */ + private static final int AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT = 6 * AUDIO_SAMPLES_PER_AUDIO_BLOCK; + /** + * Number of audio blocks per E-AC-3 syncframe, indexed by numblkscod. + */ + private static final int[] BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD = new int[] {1, 2, 3, 6}; + /** + * Sample rates, indexed by fscod. + */ + private static final int[] SAMPLE_RATE_BY_FSCOD = new int[] {48000, 44100, 32000}; + /** + * Sample rates, indexed by fscod2 (E-AC-3). + */ + private static final int[] SAMPLE_RATE_BY_FSCOD2 = new int[] {24000, 22050, 16000}; + /** + * Channel counts, indexed by acmod. + */ + private static final int[] CHANNEL_COUNT_BY_ACMOD = new int[] {2, 1, 2, 3, 3, 4, 4, 5}; + /** + * Nominal bitrates in kbps, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) + */ + private static final int[] BITRATE_BY_HALF_FRMSIZECOD = new int[] {32, 40, 48, 56, 64, 80, 96, + 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 576, 640}; + /** + * 16-bit words per syncframe, indexed by frmsizecod / 2. (See ETSI TS 102 366 table 4.13.) + */ + private static final int[] SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1 = new int[] {69, 87, 104, + 121, 139, 174, 208, 243, 278, 348, 417, 487, 557, 696, 835, 975, 1114, 1253, 1393}; + + /** + * Returns the AC-3 format given {@code data} containing the AC3SpecificBox according to + * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. + * + * @param data The AC3SpecificBox to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The AC-3 format parsed from data in the header. + */ + public static Format parseAc3AnnexFFormat(ParsableByteArray data, String trackId, + String language, DrmInitData drmInitData) { + int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + int nextByte = data.readUnsignedByte(); + int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x38) >> 3]; + if ((nextByte & 0x04) != 0) { // lfeon + channelCount++; + } + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns the E-AC-3 format given {@code data} containing the EC3SpecificBox according to + * ETSI TS 102 366 Annex F. The reading position of {@code data} will be modified. + * + * @param data The EC3SpecificBox to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The E-AC-3 format parsed from data in the header. + */ + public static Format parseEAc3AnnexFFormat(ParsableByteArray data, String trackId, + String language, DrmInitData drmInitData) { + data.skipBytes(2); // data_rate, num_ind_sub + + // Read only the first substream. + // TODO: Read later substreams? + int fscod = (data.readUnsignedByte() & 0xC0) >> 6; + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + int nextByte = data.readUnsignedByte(); + int channelCount = CHANNEL_COUNT_BY_ACMOD[(nextByte & 0x0E) >> 1]; + if ((nextByte & 0x01) != 0) { // lfeon + channelCount++; + } + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns the AC-3 format given {@code data} containing a syncframe. The reading position of + * {@code data} will be modified. + * + * @param data The data to parse, positioned at the start of the syncframe. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The AC-3 format parsed from data in the header. + */ + public static Format parseAc3SyncframeFormat(ParsableBitArray data, String trackId, + String language, DrmInitData drmInitData) { + data.skipBits(16 + 16); // syncword, crc1 + int fscod = data.readBits(2); + data.skipBits(6 + 5 + 3); // frmsizecod, bsid, bsmod + int acmod = data.readBits(3); + if ((acmod & 0x01) != 0 && acmod != 1) { + data.skipBits(2); // cmixlev + } + if ((acmod & 0x04) != 0) { + data.skipBits(2); // surmixlev + } + if (acmod == 2) { + data.skipBits(2); // dsurmod + } + boolean lfeon = data.readBit(); + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), + SAMPLE_RATE_BY_FSCOD[fscod], null, drmInitData, 0, language); + } + + /** + * Returns the E-AC-3 format given {@code data} containing a syncframe. The reading position of + * {@code data} will be modified. + * + * @param data The data to parse, positioned at the start of the syncframe. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The E-AC-3 format parsed from data in the header. + */ + public static Format parseEac3SyncframeFormat(ParsableBitArray data, String trackId, + String language, DrmInitData drmInitData) { + data.skipBits(16 + 2 + 3 + 11); // syncword, strmtype, substreamid, frmsiz + int sampleRate; + int fscod = data.readBits(2); + if (fscod == 3) { + sampleRate = SAMPLE_RATE_BY_FSCOD2[data.readBits(2)]; + } else { + data.skipBits(2); // numblkscod + sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + } + int acmod = data.readBits(3); + boolean lfeon = data.readBit(); + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_E_AC3, null, Format.NO_VALUE, + Format.NO_VALUE, CHANNEL_COUNT_BY_ACMOD[acmod] + (lfeon ? 1 : 0), sampleRate, null, + drmInitData, 0, language); + } + + /** + * Returns the size in bytes of the given AC-3 syncframe. + * + * @param data The syncframe to parse. + * @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid. + */ + public static int parseAc3SyncframeSize(byte[] data) { + if (data.length < 5) { + return C.LENGTH_UNSET; + } + int fscod = (data[4] & 0xC0) >> 6; + int frmsizecod = data[4] & 0x3F; + return getAc3SyncframeSize(fscod, frmsizecod); + } + + /** + * Returns the size in bytes of the given E-AC-3 syncframe. + * + * @param data The syncframe to parse. + * @return The syncframe size in bytes. + */ + public static int parseEAc3SyncframeSize(byte[] data) { + return 2 * (((data[2] & 0x07) << 8) + (data[3] & 0xFF) + 1); // frmsiz + } + + /** + * Returns the number of audio samples in an AC-3 syncframe. + */ + public static int getAc3SyncframeAudioSampleCount() { + return AC3_SYNCFRAME_AUDIO_SAMPLE_COUNT; + } + + /** + * Returns the number of audio samples represented by the given E-AC-3 syncframe. + * + * @param data The syncframe to parse. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseEAc3SyncframeAudioSampleCount(byte[] data) { + // See ETSI TS 102 366 subsection E.1.2.2. + return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (((data[4] & 0xC0) >> 6) == 0x03 ? 6 // fscod + : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(data[4] & 0x30) >> 4]); + } + + /** + * Like {@link #parseEAc3SyncframeAudioSampleCount(byte[])} but reads from a {@link ByteBuffer}. + * The buffer's position is not modified. + * + * @param buffer The {@link ByteBuffer} from which to read. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseEAc3SyncframeAudioSampleCount(ByteBuffer buffer) { + // See ETSI TS 102 366 subsection E.1.2.2. + int fscod = (buffer.get(buffer.position() + 4) & 0xC0) >> 6; + return AUDIO_SAMPLES_PER_AUDIO_BLOCK * (fscod == 0x03 ? 6 + : BLOCKS_PER_SYNCFRAME_BY_NUMBLKSCOD[(buffer.get(buffer.position() + 4) & 0x30) >> 4]); + } + + private static int getAc3SyncframeSize(int fscod, int frmsizecod) { + int halfFrmsizecod = frmsizecod / 2; + if (fscod < 0 || fscod >= SAMPLE_RATE_BY_FSCOD.length || frmsizecod < 0 + || halfFrmsizecod >= SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1.length) { + // Invalid values provided. + return C.LENGTH_UNSET; + } + int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; + if (sampleRate == 44100) { + return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[halfFrmsizecod] + (frmsizecod % 2)); + } + int bitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod]; + if (sampleRate == 32000) { + return 6 * bitrate; + } else { // sampleRate == 48000 + return 4 * bitrate; + } + } + + private Ac3Util() {} + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilities.java similarity index 86% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilities.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilities.java index c3784192ce8..608b6e84bc7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioCapabilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilities.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.audio; +package org.telegram.messenger.exoplayer2.audio; import android.annotation.SuppressLint; import android.annotation.TargetApi; @@ -25,7 +25,7 @@ import java.util.Arrays; /** - * Represents the set of audio formats a device is capable of playing back. + * Represents the set of audio formats that a device is capable of playing. */ @TargetApi(21) public final class AudioCapabilities { @@ -37,11 +37,10 @@ public final class AudioCapabilities { new AudioCapabilities(new int[] {AudioFormat.ENCODING_PCM_16BIT}, 2); /** - * Gets the current audio capabilities. Note that to be notified when audio capabilities change, - * you can create an instance of {@link AudioCapabilitiesReceiver} and register a listener. + * Returns the current audio capabilities for the device. * - * @param context Context for receiving the initial broadcast. - * @return Current audio capabilities for the device. + * @param context A context for obtaining the current audio capabilities. + * @return The current audio capabilities for the device. */ @SuppressWarnings("InlinedApi") public static AudioCapabilities getCapabilities(Context context) { @@ -89,7 +88,9 @@ public boolean supportsEncoding(int encoding) { return Arrays.binarySearch(supportedEncodings, encoding) >= 0; } - /** Returns the maximum number of channels the device can play at the same time. */ + /** + * Returns the maximum number of channels the device can play at the same time. + */ public int getMaxChannelCount() { return maxChannelCount; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilitiesReceiver.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilitiesReceiver.java new file mode 100755 index 00000000000..5d2495dcd64 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioCapabilitiesReceiver.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Receives broadcast events indicating changes to the device's audio capabilities, notifying a + * {@link Listener} when audio capability changes occur. + */ +public final class AudioCapabilitiesReceiver { + + /** + * Listener notified when audio capabilities change. + */ + public interface Listener { + + /** + * Called when the audio capabilities change. + * + * @param audioCapabilities The current audio capabilities for the device. + */ + void onAudioCapabilitiesChanged(AudioCapabilities audioCapabilities); + + } + + private final Context context; + private final Listener listener; + private final BroadcastReceiver receiver; + + /* package */ AudioCapabilities audioCapabilities; + + /** + * @param context A context for registering the receiver. + * @param listener The listener to notify when audio capabilities change. + */ + public AudioCapabilitiesReceiver(Context context, Listener listener) { + this.context = Assertions.checkNotNull(context); + this.listener = Assertions.checkNotNull(listener); + this.receiver = Util.SDK_INT >= 21 ? new HdmiAudioPlugBroadcastReceiver() : null; + } + + /** + * Registers the receiver, meaning it will notify the listener when audio capability changes + * occur. The current audio capabilities will be returned. It is important to call + * {@link #unregister} when the receiver is no longer required. + * + * @return The current audio capabilities for the device. + */ + @SuppressWarnings("InlinedApi") + public AudioCapabilities register() { + Intent stickyIntent = receiver == null ? null + : context.registerReceiver(receiver, new IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)); + audioCapabilities = AudioCapabilities.getCapabilities(stickyIntent); + return audioCapabilities; + } + + /** + * Unregisters the receiver, meaning it will no longer notify the listener when audio capability + * changes occur. + */ + public void unregister() { + if (receiver != null) { + context.unregisterReceiver(receiver); + } + } + + private final class HdmiAudioPlugBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (!isInitialStickyBroadcast()) { + AudioCapabilities newAudioCapabilities = AudioCapabilities.getCapabilities(intent); + if (!newAudioCapabilities.equals(audioCapabilities)) { + audioCapabilities = newAudioCapabilities; + listener.onAudioCapabilitiesChanged(newAudioCapabilities); + } + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioDecoderException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioDecoderException.java new file mode 100755 index 00000000000..c565e6031f4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioDecoderException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +/** + * Thrown when an audio decoder error occurs. + */ +public abstract class AudioDecoderException extends Exception { + + /** + * @param detailMessage The detail message for this exception. + */ + public AudioDecoderException(String detailMessage) { + super(detailMessage); + } + + /** + * @param detailMessage The detail message for this exception. + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + */ + public AudioDecoderException(String detailMessage, Throwable cause) { + super(detailMessage, cause); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioRendererEventListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioRendererEventListener.java new file mode 100755 index 00000000000..989f4fe4c4f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioRendererEventListener.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import android.os.Handler; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.Renderer; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * Listener of audio {@link Renderer} events. + */ +public interface AudioRendererEventListener { + + /** + * Called when the renderer is enabled. + * + * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it + * remains enabled. + */ + void onAudioEnabled(DecoderCounters counters); + + /** + * Called when the audio session is set. + * + * @param audioSessionId The audio session id. + */ + void onAudioSessionId(int audioSessionId); + + /** + * Called when a decoder is created. + * + * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. + */ + void onAudioDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs); + + /** + * Called when the format of the media being consumed by the renderer changes. + * + * @param format The new format. + */ + void onAudioInputFormatChanged(Format format); + + /** + * Called when an {@link AudioTrack} underrun occurs. + * + * @param bufferSize The size of the {@link AudioTrack}'s buffer, in bytes. + * @param bufferSizeMs The size of the {@link AudioTrack}'s buffer, in milliseconds, if it is + * configured for PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output, + * as the buffered media can have a variable bitrate so the duration may be unknown. + * @param elapsedSinceLastFeedMs The time since the {@link AudioTrack} was last fed data. + */ + void onAudioTrackUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + + /** + * Called when the renderer is disabled. + * + * @param counters {@link DecoderCounters} that were updated by the renderer. + */ + void onAudioDisabled(DecoderCounters counters); + + /** + * Dispatches events to a {@link AudioRendererEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final AudioRendererEventListener listener; + + /** + * @param handler A handler for dispatching events, or null if creating a dummy instance. + * @param listener The listener to which events should be dispatched, or null if creating a + * dummy instance. + */ + public EventDispatcher(Handler handler, AudioRendererEventListener listener) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}. + */ + public void enabled(final DecoderCounters decoderCounters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioEnabled(decoderCounters); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}. + */ + public void decoderInitialized(final String decoderName, + final long initializedTimestampMs, final long initializationDurationMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. + */ + public void inputFormatChanged(final Format format) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioInputFormatChanged(format); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioTrackUnderrun(int, long, long)}. + */ + public void audioTrackUnderrun(final int bufferSize, final long bufferSizeMs, + final long elapsedSinceLastFeedMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. + */ + public void disabled(final DecoderCounters counters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + counters.ensureUpdated(); + listener.onAudioDisabled(counters); + } + }); + } + } + + /** + * Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. + */ + public void audioSessionId(final int audioSessionId) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onAudioSessionId(audioSessionId); + } + }); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioTrack.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java similarity index 79% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioTrack.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java index 451883c5a02..4b54ddef422 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/audio/AudioTrack.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/AudioTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.audio; +package org.telegram.messenger.exoplayer2.audio; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.media.AudioFormat; -import android.media.AudioManager; import android.media.AudioTimestamp; import android.media.PlaybackParams; import android.os.ConditionVariable; import android.os.SystemClock; import android.util.Log; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Ac3Util; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.DtsUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; import java.lang.reflect.Method; import java.nio.ByteBuffer; @@ -36,28 +35,46 @@ * Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles * playback position smoothing, non-blocking writes and reconfiguration. *

        - * Before starting playback, specify the input audio format by calling one of the {@link #configure} - * methods and {@link #initialize} the instance, optionally specifying an audio session. + * Before starting playback, specify the input format by calling + * {@link #configure(String, int, int, int, int)}. Next call {@link #initialize(int)}, optionally + * specifying an audio session. *

        - * Call {@link #handleBuffer(ByteBuffer, int, int, long)} to write data to play back, and - * {@link #handleDiscontinuity()} when a buffer is skipped. Call {@link #play()} to start playing - * back written data. + * Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()} + * when the data being fed is discontinuous. Call {@link #play()} to start playing the written data. *

        - * Call {@link #configure} again whenever the input format changes. If {@link #isInitialized()} - * returns false after calling {@link #configure}, it is necessary to re-{@link #initialize} the - * instance before writing more data. + * Call {@link #configure(String, int, int, int, int)} whenever the input format changes. If + * {@link #isInitialized()} returns {@code false} after the call, it is necessary to call + * {@link #initialize(int)} before writing more data. *

        - * The underlying framework audio track is created by {@link #initialize} and released - * asynchronously by {@link #reset} (and {@link #configure}, unless the format is unchanged). - * Reinitialization blocks until releasing the old audio track completes. It is safe to - * re-{@link #initialize} the instance after calling {@link #reset()}, without reconfiguration. + * The underlying {@link android.media.AudioTrack} is created by {@link #initialize(int)} and + * released by {@link #reset()} (and {@link #configure(String, int, int, int, int)} unless the input + * format is unchanged). It is safe to call {@link #initialize(int)} after calling {@link #reset()} + * without reconfiguration. *

        - * Call {@link #release()} when the instance will no longer be used. + * Call {@link #release()} when the instance is no longer required. */ public final class AudioTrack { /** - * Thrown when a failure occurs instantiating an {@link android.media.AudioTrack}. + * Listener for audio track events. + */ + public interface Listener { + + /** + * Called when the audio track underruns. + * + * @param bufferSize The size of the track's buffer, in bytes. + * @param bufferSizeMs The size of the track's buffer, in milliseconds, if it is configured for + * PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output, as the + * buffered media can have a variable bitrate so the duration may be unknown. + * @param elapsedSinceLastFeedMs The time since the track was last fed data, in milliseconds. + */ + void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs); + + } + + /** + * Thrown when a failure occurs initializing an {@link android.media.AudioTrack}. */ public static final class InitializationException extends Exception { @@ -66,8 +83,14 @@ public static final class InitializationException extends Exception { */ public final int audioTrackState; - public InitializationException( - int audioTrackState, int sampleRate, int channelConfig, int bufferSize) { + /** + * @param audioTrackState The state as reported by {@link android.media.AudioTrack#getState()}. + * @param sampleRate The requested sample rate in Hz. + * @param channelConfig The requested channel configuration. + * @param bufferSize The requested buffer size in bytes. + */ + public InitializationException(int audioTrackState, int sampleRate, int channelConfig, + int bufferSize) { super("AudioTrack init failed: " + audioTrackState + ", Config(" + sampleRate + ", " + channelConfig + ", " + bufferSize + ")"); this.audioTrackState = audioTrackState; @@ -81,10 +104,14 @@ public InitializationException( public static final class WriteException extends Exception { /** - * The value returned from {@link android.media.AudioTrack#write(byte[], int, int)}. + * An error value returned from {@link android.media.AudioTrack#write(byte[], int, int)}. */ public final int errorCode; + /** + * @param errorCode An error value returned from + * {@link android.media.AudioTrack#write(byte[], int, int)}. + */ public WriteException(int errorCode) { super("AudioTrack write failed: " + errorCode); this.errorCode = errorCode; @@ -98,8 +125,11 @@ public WriteException(int errorCode) { */ public static final class InvalidAudioTrackTimestampException extends RuntimeException { - public InvalidAudioTrackTimestampException(String message) { - super(message); + /** + * @param detailMessage The detail message for this exception. + */ + public InvalidAudioTrackTimestampException(String detailMessage) { + super(detailMessage); } } @@ -141,6 +171,40 @@ public InvalidAudioTrackTimestampException(String message) { */ private static final int BUFFER_MULTIPLICATION_FACTOR = 4; + /** + * @see android.media.AudioTrack#PLAYSTATE_STOPPED + */ + private static final int PLAYSTATE_STOPPED = android.media.AudioTrack.PLAYSTATE_STOPPED; + /** + * @see android.media.AudioTrack#PLAYSTATE_PAUSED + */ + private static final int PLAYSTATE_PAUSED = android.media.AudioTrack.PLAYSTATE_PAUSED; + /** + * @see android.media.AudioTrack#PLAYSTATE_PLAYING + */ + private static final int PLAYSTATE_PLAYING = android.media.AudioTrack.PLAYSTATE_PLAYING; + /** + * @see android.media.AudioTrack#ERROR_BAD_VALUE + */ + private static final int ERROR_BAD_VALUE = android.media.AudioTrack.ERROR_BAD_VALUE; + /** + * @see android.media.AudioTrack#MODE_STATIC + */ + private static final int MODE_STATIC = android.media.AudioTrack.MODE_STATIC; + /** + * @see android.media.AudioTrack#MODE_STREAM + */ + private static final int MODE_STREAM = android.media.AudioTrack.MODE_STREAM; + /** + * @see android.media.AudioTrack#STATE_INITIALIZED + */ + private static final int STATE_INITIALIZED = android.media.AudioTrack.STATE_INITIALIZED; + /** + * @see android.media.AudioTrack#WRITE_NON_BLOCKING + */ + @SuppressLint("InlinedApi") + private static final int WRITE_NON_BLOCKING = android.media.AudioTrack.WRITE_NON_BLOCKING; + private static final String TAG = "AudioTrack"; /** @@ -168,7 +232,8 @@ public InvalidAudioTrackTimestampException(String message) { /** * Whether to enable a workaround for an issue where an audio effect does not keep its session - * active across releasing/initializing a new audio track, on platform API version before 21. + * active across releasing/initializing a new audio track, on platform builds where + * {@link Util#SDK_INT} < 21. *

        * The flag must be set before creating a player. */ @@ -184,20 +249,24 @@ public InvalidAudioTrackTimestampException(String message) { public static boolean failOnSpuriousAudioTimestamp = false; private final AudioCapabilities audioCapabilities; - private final int streamType; + private final Listener listener; private final ConditionVariable releasingConditionVariable; private final long[] playheadOffsets; private final AudioTrackUtil audioTrackUtil; /** - * Used to keep the audio session active on pre-V21 builds (see {@link #initialize()}). + * Used to keep the audio session active on pre-V21 builds (see {@link #initialize(int)}). */ private android.media.AudioTrack keepSessionIdAudioTrack; private android.media.AudioTrack audioTrack; private int sampleRate; private int channelConfig; + @C.StreamType + private int streamType; + @C.Encoding private int sourceEncoding; + @C.Encoding private int targetEncoding; private boolean passthrough; private int pcmFrameSize; @@ -223,27 +292,21 @@ public InvalidAudioTrackTimestampException(String message) { private byte[] temporaryBuffer; private int temporaryBufferOffset; - private int bufferBytesRemaining; + private ByteBuffer currentSourceBuffer; private ByteBuffer resampledBuffer; private boolean useResampledBuffer; - /** - * Creates an audio track with default audio capabilities (no encoded audio passthrough support). - */ - public AudioTrack() { - this(null, AudioManager.STREAM_MUSIC); - } + private boolean hasData; + private long lastFeedElapsedRealtimeMs; /** - * Creates an audio track using the specified audio capabilities and stream type. - * - * @param audioCapabilities The current audio playback capabilities. - * @param streamType The type of audio stream for the underlying {@link android.media.AudioTrack}. + * @param audioCapabilities The current audio capabilities. + * @param listener Listener for audio track events. */ - public AudioTrack(AudioCapabilities audioCapabilities, int streamType) { + public AudioTrack(AudioCapabilities audioCapabilities, Listener listener) { this.audioCapabilities = audioCapabilities; - this.streamType = streamType; + this.listener = listener; releasingConditionVariable = new ConditionVariable(true); if (Util.SDK_INT >= 18) { try { @@ -263,11 +326,14 @@ public AudioTrack(AudioCapabilities audioCapabilities, int streamType) { playheadOffsets = new long[MAX_PLAYHEAD_OFFSET_COUNT]; volume = 1.0f; startMediaTimeState = START_NOT_SET; + streamType = C.STREAM_TYPE_DEFAULT; } /** - * Returns whether it is possible to play back input audio in the specified format using encoded - * audio passthrough. + * Returns whether it's possible to play audio in the specified format using encoded passthrough. + * + * @param mimeType The format mime type. + * @return Whether it's possible to play audio in the format using encoded passthrough. */ public boolean isPassthroughSupported(String mimeType) { return audioCapabilities != null @@ -298,7 +364,7 @@ public long getCurrentPositionUs(boolean sourceEnded) { return CURRENT_POSITION_NOT_SET; } - if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PLAYING) { + if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) { maybeSampleSyncParams(); } @@ -332,20 +398,6 @@ public long getCurrentPositionUs(boolean sourceEnded) { return currentPositionUs; } - /** - * Configures (or reconfigures) the audio track, inferring a suitable buffer size automatically. - * - * @param mimeType The mime type. - * @param channelCount The number of channels. - * @param sampleRate The sample rate in Hz. - * @param pcmEncoding For PCM formats, the encoding used. One of {@link C#ENCODING_PCM_16BIT}, - * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and - * {@link C#ENCODING_PCM_32BIT}. - */ - public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding) { - configure(mimeType, channelCount, sampleRate, pcmEncoding, 0); - } - /** * Configures (or reconfigures) the audio track. * @@ -358,8 +410,8 @@ public void configure(String mimeType, int channelCount, int sampleRate, int pcm * @param specifiedBufferSize A specific size for the playback buffer in bytes, or 0 to infer a * suitable buffer size automatically. */ - public void configure(String mimeType, int channelCount, int sampleRate, int pcmEncoding, - int specifiedBufferSize) { + public void configure(String mimeType, int channelCount, int sampleRate, + @C.PcmEncoding int pcmEncoding, int specifiedBufferSize) { int channelConfig; switch (channelCount) { case 1: @@ -391,7 +443,7 @@ public void configure(String mimeType, int channelCount, int sampleRate, int pcm } boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType); - int sourceEncoding; + @C.Encoding int sourceEncoding; if (passthrough) { sourceEncoding = getEncodingForMimeType(mimeType); } else if (pcmEncoding == C.ENCODING_PCM_8BIT || pcmEncoding == C.ENCODING_PCM_16BIT @@ -424,14 +476,14 @@ public void configure(String mimeType, int channelCount, int sampleRate, int pcm if (targetEncoding == C.ENCODING_AC3 || targetEncoding == C.ENCODING_E_AC3) { // AC-3 allows bitrates up to 640 kbit/s. bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 80 * 1024 / C.MICROS_PER_SECOND); - } else /* (targetEncoding == C.ENCODING_DTS || targetEncoding == C.ENCODING_DTS_HD) */ { + } else /* (targetEncoding == C.ENCODING_DTS || targetEncoding == C.ENCODING_DTS_HD */ { // DTS allows an 'open' bitrate, but we assume the maximum listed value: 1536 kbit/s. bufferSize = (int) (PASSTHROUGH_BUFFER_DURATION_US * 192 * 1024 / C.MICROS_PER_SECOND); } } else { int minBufferSize = android.media.AudioTrack.getMinBufferSize(sampleRate, channelConfig, targetEncoding); - Assertions.checkState(minBufferSize != android.media.AudioTrack.ERROR_BAD_VALUE); + Assertions.checkState(minBufferSize != ERROR_BAD_VALUE); int multipliedBufferSize = minBufferSize * BUFFER_MULTIPLICATION_FACTOR; int minAppBufferSize = (int) durationUsToFrames(MIN_BUFFER_DURATION_US) * pcmFrameSize; int maxAppBufferSize = (int) Math.max(minBufferSize, @@ -440,17 +492,7 @@ public void configure(String mimeType, int channelCount, int sampleRate, int pcm : multipliedBufferSize > maxAppBufferSize ? maxAppBufferSize : multipliedBufferSize; } - bufferSizeUs = passthrough ? C.UNKNOWN_TIME_US - : framesToDurationUs(pcmBytesToFrames(bufferSize)); - } - - /** - * Initializes the audio track for writing new buffers using {@link #handleBuffer}. - * - * @return The audio track session identifier. - */ - public int initialize() throws InitializationException { - return initialize(SESSION_ID_NOT_SET); + bufferSizeUs = passthrough ? C.TIME_UNSET : framesToDurationUs(pcmBytesToFrames(bufferSize)); } /** @@ -470,11 +512,11 @@ public int initialize(int sessionId) throws InitializationException { if (sessionId == SESSION_ID_NOT_SET) { audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM); + targetEncoding, bufferSize, MODE_STREAM); } else { // Re-attach to the same audio session. audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig, - targetEncoding, bufferSize, android.media.AudioTrack.MODE_STREAM, sessionId); + targetEncoding, bufferSize, MODE_STREAM, sessionId); } checkAudioTrackInitialized(); @@ -490,46 +532,20 @@ public int initialize(int sessionId) throws InitializationException { if (keepSessionIdAudioTrack == null) { int sampleRate = 4000; // Equal to private android.media.AudioTrack.MIN_SAMPLE_RATE. int channelConfig = AudioFormat.CHANNEL_OUT_MONO; - int encoding = C.ENCODING_PCM_16BIT; + @C.PcmEncoding int encoding = C.ENCODING_PCM_16BIT; int bufferSize = 2; // Use a two byte buffer, as it is not actually used for playback. keepSessionIdAudioTrack = new android.media.AudioTrack(streamType, sampleRate, - channelConfig, encoding, bufferSize, android.media.AudioTrack.MODE_STATIC, sessionId); + channelConfig, encoding, bufferSize, MODE_STATIC, sessionId); } } } audioTrackUtil.reconfigure(audioTrack, needsPassthroughWorkarounds()); setAudioTrackVolume(); - + hasData = false; return sessionId; } - /** - * Returns the size of this {@link AudioTrack}'s buffer in bytes. - *

        - * The value returned from this method may change as a result of calling one of the - * {@link #configure} methods. - * - * @return The size of the buffer in bytes. - */ - public int getBufferSize() { - return bufferSize; - } - - /** - * Returns the size of the buffer in microseconds for PCM {@link AudioTrack}s, or - * {@link C#UNKNOWN_TIME_US} for passthrough {@link AudioTrack}s. - *

        - * The value returned from this method may change as a result of calling one of the - * {@link #configure} methods. - * - * @return The size of the buffer in microseconds for PCM {@link AudioTrack}s, or - * {@link C#UNKNOWN_TIME_US} for passthrough {@link AudioTrack}s. - */ - public long getBufferSizeUs() { - return bufferSizeUs; - } - /** * Starts or resumes playing audio if the audio track has been initialized. */ @@ -551,43 +567,65 @@ public void handleDiscontinuity() { } /** - * Attempts to write {@code size} bytes from {@code buffer} at {@code offset} to the audio track. - * Returns a bit field containing {@link #RESULT_BUFFER_CONSUMED} if the buffer can be released - * (due to having been written), and {@link #RESULT_POSITION_DISCONTINUITY} if the buffer was - * discontinuous with previously written data. + * Attempts to write data from a {@link ByteBuffer} to the audio track, starting from its current + * position and ending at its limit (exclusive). The position of the {@link ByteBuffer} is + * advanced by the number of bytes that were successfully written. + *

        + * Returns a bit field containing {@link #RESULT_BUFFER_CONSUMED} if the data was written in full, + * and {@link #RESULT_POSITION_DISCONTINUITY} if the buffer was discontinuous with previously + * written data. + *

        + * If the data was not written in full then the same {@link ByteBuffer} must be provided to + * subsequent calls until it has been fully consumed, except in the case of an interleaving call + * to {@link #configure} or {@link #reset}. * * @param buffer The buffer containing audio data to play back. - * @param offset The offset in the buffer from which to consume data. - * @param size The number of bytes to consume from {@code buffer}. * @param presentationTimeUs Presentation timestamp of the next buffer in microseconds. * @return A bit field with {@link #RESULT_BUFFER_CONSUMED} if the buffer can be released, and * {@link #RESULT_POSITION_DISCONTINUITY} if the buffer was not contiguous with previously * written data. * @throws WriteException If an error occurs writing the audio data. */ - public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentationTimeUs) - throws WriteException { + public int handleBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException { + boolean hadData = hasData; + hasData = hasPendingData(); + if (hadData && !hasData && audioTrack.getPlayState() != PLAYSTATE_STOPPED) { + long elapsedSinceLastFeedMs = SystemClock.elapsedRealtime() - lastFeedElapsedRealtimeMs; + listener.onUnderrun(bufferSize, C.usToMs(bufferSizeUs), elapsedSinceLastFeedMs); + } + int result = writeBuffer(buffer, presentationTimeUs); + lastFeedElapsedRealtimeMs = SystemClock.elapsedRealtime(); + return result; + } + + private int writeBuffer(ByteBuffer buffer, long presentationTimeUs) throws WriteException { + boolean isNewSourceBuffer = currentSourceBuffer == null; + Assertions.checkState(isNewSourceBuffer || currentSourceBuffer == buffer); + currentSourceBuffer = buffer; + if (needsPassthroughWorkarounds()) { // An AC-3 audio track continues to play data written while it is paused. Stop writing so its // buffer empties. See [Internal: b/18899620]. - if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED) { + if (audioTrack.getPlayState() == PLAYSTATE_PAUSED) { return 0; } // A new AC-3 audio track's playback position continues to increase from the old track's // position for a short time after is has been released. Avoid writing data until the playback // head position actually returns to zero. - if (audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_STOPPED + if (audioTrack.getPlayState() == PLAYSTATE_STOPPED && audioTrackUtil.getPlaybackHeadPosition() != 0) { return 0; } } int result = 0; - if (bufferBytesRemaining == 0) { - // The previous buffer (if there was one) was fully written to the audio track. We're now - // seeing a new buffer for the first time. - if (size == 0) { + if (isNewSourceBuffer) { + // We're seeing this buffer for the first time. + + if (!currentSourceBuffer.hasRemaining()) { + // The buffer is empty. + currentSourceBuffer = null; return RESULT_BUFFER_CONSUMED; } @@ -595,15 +633,10 @@ public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentati if (useResampledBuffer) { Assertions.checkState(targetEncoding == C.ENCODING_PCM_16BIT); // Resample the buffer to get the data in the target encoding. - resampledBuffer = resampleTo16BitPcm(buffer, offset, size, sourceEncoding, resampledBuffer); - // Use the resampled buffer, offset and size. + resampledBuffer = resampleTo16BitPcm(currentSourceBuffer, sourceEncoding, resampledBuffer); buffer = resampledBuffer; - offset = resampledBuffer.position(); - size = resampledBuffer.limit(); } - bufferBytesRemaining = size; - buffer.position(offset); if (passthrough && framesPerEncodedSample == 0) { // If this is the first encoded sample, calculate the sample size in frames. framesPerEncodedSample = getFramesPerEncodedSample(targetEncoding, buffer); @@ -612,32 +645,38 @@ public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentati startMediaTimeUs = Math.max(0, presentationTimeUs); startMediaTimeState = START_IN_SYNC; } else { - // Sanity check that bufferStartTime is consistent with the expected value. - long expectedBufferStartTime = startMediaTimeUs + framesToDurationUs(getSubmittedFrames()); + // Sanity check that presentationTimeUs is consistent with the expected value. + long expectedPresentationTimeUs = startMediaTimeUs + + framesToDurationUs(getSubmittedFrames()); if (startMediaTimeState == START_IN_SYNC - && Math.abs(expectedBufferStartTime - presentationTimeUs) > 200000) { - Log.e(TAG, "Discontinuity detected [expected " + expectedBufferStartTime + ", got " + && Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) { + Log.e(TAG, "Discontinuity detected [expected " + expectedPresentationTimeUs + ", got " + presentationTimeUs + "]"); startMediaTimeState = START_NEED_SYNC; } if (startMediaTimeState == START_NEED_SYNC) { // Adjust startMediaTimeUs to be consistent with the current buffer's start time and the // number of bytes submitted. - startMediaTimeUs += (presentationTimeUs - expectedBufferStartTime); + startMediaTimeUs += (presentationTimeUs - expectedPresentationTimeUs); startMediaTimeState = START_IN_SYNC; result |= RESULT_POSITION_DISCONTINUITY; } } if (Util.SDK_INT < 21) { // Copy {@code buffer} into {@code temporaryBuffer}. - if (temporaryBuffer == null || temporaryBuffer.length < size) { - temporaryBuffer = new byte[size]; + int bytesRemaining = buffer.remaining(); + if (temporaryBuffer == null || temporaryBuffer.length < bytesRemaining) { + temporaryBuffer = new byte[bytesRemaining]; } - buffer.get(temporaryBuffer, 0, size); + int originalPosition = buffer.position(); + buffer.get(temporaryBuffer, 0, bytesRemaining); + buffer.position(originalPosition); temporaryBufferOffset = 0; } } + buffer = useResampledBuffer ? resampledBuffer : buffer; + int bytesRemaining = buffer.remaining(); int bytesWritten = 0; if (Util.SDK_INT < 21) { // passthrough == false // Work out how many bytes we can write without the risk of blocking. @@ -645,37 +684,36 @@ public int handleBuffer(ByteBuffer buffer, int offset, int size, long presentati (int) (submittedPcmBytes - (audioTrackUtil.getPlaybackHeadPosition() * pcmFrameSize)); int bytesToWrite = bufferSize - bytesPending; if (bytesToWrite > 0) { - bytesToWrite = Math.min(bufferBytesRemaining, bytesToWrite); + bytesToWrite = Math.min(bytesRemaining, bytesToWrite); bytesWritten = audioTrack.write(temporaryBuffer, temporaryBufferOffset, bytesToWrite); if (bytesWritten >= 0) { temporaryBufferOffset += bytesWritten; } + buffer.position(buffer.position() + bytesWritten); } } else { - ByteBuffer data = useResampledBuffer ? resampledBuffer : buffer; - bytesWritten = writeNonBlockingV21(audioTrack, data, bufferBytesRemaining); + bytesWritten = writeNonBlockingV21(audioTrack, buffer, bytesRemaining); } if (bytesWritten < 0) { throw new WriteException(bytesWritten); } - bufferBytesRemaining -= bytesWritten; if (!passthrough) { submittedPcmBytes += bytesWritten; } - if (bufferBytesRemaining == 0) { + if (bytesWritten == bytesRemaining) { if (passthrough) { submittedEncodedFrames += framesPerEncodedSample; } + currentSourceBuffer = null; result |= RESULT_BUFFER_CONSUMED; } return result; } /** - * Ensures that the last data passed to {@link #handleBuffer(ByteBuffer, int, int, long)} is - * played out in full. + * Ensures that the last data passed to {@link #handleBuffer(ByteBuffer, long)} is played in full. */ public void handleEndOfStream() { if (isInitialized()) { @@ -693,17 +731,39 @@ public boolean hasPendingData() { } /** - * Sets the playback parameters. Only available for SDK_INT >= 23 + * Sets the playback parameters. Only available for {@link Util#SDK_INT} >= 23 * + * @param playbackParams The playback parameters to be used by the + * {@link android.media.AudioTrack}. * @throws UnsupportedOperationException if the Playback Parameters are not supported. That is, - * SDK_INT < 23. + * {@link Util#SDK_INT} < 23. */ public void setPlaybackParams(PlaybackParams playbackParams) { - audioTrackUtil.setPlaybackParameters(playbackParams); + audioTrackUtil.setPlaybackParams(playbackParams); + } + + /** + * Sets the stream type for audio track. If the stream type has changed, {@link #isInitialized()} + * will return {@code false} and the caller must re-{@link #initialize(int)} the audio track + * before writing more data. The caller must not reuse the audio session identifier when + * re-initializing with a new stream type. + * + * @param streamType The {@link C.StreamType} to use for audio output. + * @return Whether the stream type changed. + */ + public boolean setStreamType(@C.StreamType int streamType) { + if (this.streamType == streamType) { + return false; + } + this.streamType = streamType; + reset(); + return true; } /** * Sets the playback volume. + * + * @param volume A volume in the range [0.0, 1.0]. */ public void setVolume(float volume) { if (this.volume != volume) { @@ -733,21 +793,23 @@ public void pause() { } /** - * Releases the underlying audio track asynchronously. Calling {@link #initialize} will block - * until the audio track has been released, so it is safe to initialize immediately after - * resetting. The audio session may remain active until the instance is {@link #release}d. + * Releases the underlying audio track asynchronously. + *

        + * Calling {@link #initialize(int)} will block until the audio track has been released, so it is + * safe to initialize immediately after a reset. The audio session may remain active until + * {@link #release()} is called. */ public void reset() { if (isInitialized()) { submittedPcmBytes = 0; submittedEncodedFrames = 0; framesPerEncodedSample = 0; - bufferBytesRemaining = 0; + currentSourceBuffer = null; startMediaTimeState = START_NOT_SET; latencyUs = 0; resetSyncParams(); int playState = audioTrack.getPlayState(); - if (playState == android.media.AudioTrack.PLAYSTATE_PLAYING) { + if (playState == PLAYSTATE_PLAYING) { audioTrack.pause(); } // AudioTrack.release can take some time, so we call it on a background thread. @@ -896,7 +958,7 @@ private void maybeSampleSyncParams() { */ private void checkAudioTrackInitialized() throws InitializationException { int state = audioTrack.getState(); - if (state == android.media.AudioTrack.STATE_INITIALIZED) { + if (state == STATE_INITIALIZED) { return; } // The track is not successfully initialized. Release and null the track. @@ -954,7 +1016,7 @@ private boolean needsPassthroughWorkarounds() { */ private boolean overrideHasPendingData() { return needsPassthroughWorkarounds() - && audioTrack.getPlayState() == android.media.AudioTrack.PLAYSTATE_PAUSED + && audioTrack.getPlayState() == PLAYSTATE_PAUSED && audioTrack.getPlaybackHeadPosition() == 0; } @@ -962,15 +1024,17 @@ private boolean overrideHasPendingData() { * Converts the provided buffer into 16-bit PCM. * * @param buffer The buffer containing the data to convert. - * @param offset The offset of the data in the buffer. - * @param size The size in bytes of the data in the buffer. * @param sourceEncoding The data encoding. * @param out A buffer into which the output should be written, if its capacity is sufficient. * @return The 16-bit PCM output. Different to the out parameter if null was passed, or if the * capacity was insufficient for the output. */ - private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int offset, int size, - int sourceEncoding, ByteBuffer out) { + private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, @C.PcmEncoding int sourceEncoding, + ByteBuffer out) { + int offset = buffer.position(); + int limit = buffer.limit(); + int size = limit - offset; + int resampledSize; switch (sourceEncoding) { case C.ENCODING_PCM_8BIT: @@ -982,6 +1046,9 @@ private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int offset, int case C.ENCODING_PCM_32BIT: resampledSize = size / 2; break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: default: // Never happens. throw new IllegalStateException(); @@ -995,7 +1062,6 @@ private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int offset, int resampledBuffer.limit(resampledSize); // Samples are little endian. - int limit = offset + size; switch (sourceEncoding) { case C.ENCODING_PCM_8BIT: // 8->16 bit resampling. Shift each byte from [0, 256) to [-128, 128) and scale up. @@ -1018,6 +1084,9 @@ private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int offset, int resampledBuffer.put(buffer.get(i + 3)); } break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: default: // Never happens. throw new IllegalStateException(); @@ -1027,6 +1096,7 @@ private static ByteBuffer resampleTo16BitPcm(ByteBuffer buffer, int offset, int return resampledBuffer; } + @C.Encoding private static int getEncodingForMimeType(String mimeType) { switch (mimeType) { case MimeTypes.AUDIO_AC3: @@ -1042,7 +1112,7 @@ private static int getEncodingForMimeType(String mimeType) { } } - private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) { + private static int getFramesPerEncodedSample(@C.Encoding int encoding, ByteBuffer buffer) { if (encoding == C.ENCODING_DTS || encoding == C.ENCODING_DTS_HD) { return DtsUtil.parseDtsAudioSampleCount(buffer); } else if (encoding == C.ENCODING_AC3) { @@ -1057,7 +1127,7 @@ private static int getFramesPerEncodedSample(int encoding, ByteBuffer buffer) { @TargetApi(21) private static int writeNonBlockingV21( android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) { - return audioTrack.write(buffer, size, android.media.AudioTrack.WRITE_NON_BLOCKING); + return audioTrack.write(buffer, size, WRITE_NON_BLOCKING); } @TargetApi(21) @@ -1097,7 +1167,7 @@ public void reconfigure(android.media.AudioTrack audioTrack, boolean needsPassthroughWorkaround) { this.audioTrack = audioTrack; this.needsPassthroughWorkaround = needsPassthroughWorkaround; - stopTimestampUs = -1; + stopTimestampUs = C.TIME_UNSET; lastRawPlaybackHeadPosition = 0; rawPlaybackHeadWrapCount = 0; passthroughWorkaroundPauseOffset = 0; @@ -1125,7 +1195,7 @@ public void handleEndOfStream(long submittedFrames) { * this method does nothing. */ public void pause() { - if (stopTimestampUs != -1) { + if (stopTimestampUs != C.TIME_UNSET) { // We don't want to knock the audio track back into the paused state. return; } @@ -1142,7 +1212,7 @@ public void pause() { * expressed as a long. */ public long getPlaybackHeadPosition() { - if (stopTimestampUs != -1) { + if (stopTimestampUs != C.TIME_UNSET) { // Simulate the playback head position up to the total number of frames submitted. long elapsedTimeSinceStopUs = (SystemClock.elapsedRealtime() * 1000) - stopTimestampUs; long framesSinceStop = (elapsedTimeSinceStopUs * sampleRate) / C.MICROS_PER_SECOND; @@ -1150,7 +1220,7 @@ public long getPlaybackHeadPosition() { } int state = audioTrack.getPlayState(); - if (state == android.media.AudioTrack.PLAYSTATE_STOPPED) { + if (state == PLAYSTATE_STOPPED) { // The audio track hasn't been started. return 0; } @@ -1160,7 +1230,7 @@ public long getPlaybackHeadPosition() { // Work around an issue with passthrough/direct AudioTracks on platform API versions 21/22 // where the playback head position jumps back to zero on paused passthrough/direct audio // tracks. See [Internal: b/19187573]. - if (state == android.media.AudioTrack.PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { + if (state == PLAYSTATE_PAUSED && rawPlaybackHeadPosition == 0) { passthroughWorkaroundPauseOffset = lastRawPlaybackHeadPosition; } rawPlaybackHeadPosition += passthroughWorkaroundPauseOffset; @@ -1184,7 +1254,7 @@ public long getPlaybackHeadPositionUs() { * Updates the values returned by {@link #getTimestampNanoTime()} and * {@link #getTimestampFramePosition()}. * - * @return True if the timestamp values were updated. False otherwise. + * @return Whether the timestamp values were updated. */ public boolean updateTimestamp() { return false; @@ -1223,11 +1293,12 @@ public long getTimestampFramePosition() { /** * Sets the Playback Parameters to be used by the underlying {@link android.media.AudioTrack}. * - * @param playbackParams to be used by the {@link android.media.AudioTrack}. + * @param playbackParams The playback parameters to be used by the + * {@link android.media.AudioTrack}. * @throws UnsupportedOperationException If Playback Parameters are not supported - * (i.e. SDK_INT < 23). + * (i.e. {@link Util#SDK_INT} < 23). */ - public void setPlaybackParameters(PlaybackParams playbackParams) { + public void setPlaybackParams(PlaybackParams playbackParams) { throw new UnsupportedOperationException(); } @@ -1310,7 +1381,7 @@ public void reconfigure(android.media.AudioTrack audioTrack, } @Override - public void setPlaybackParameters(PlaybackParams playbackParams) { + public void setPlaybackParams(PlaybackParams playbackParams) { playbackParams = (playbackParams != null ? playbackParams : new PlaybackParams()) .allowDefaults(); this.playbackParams = playbackParams; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/DtsUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/DtsUtil.java new file mode 100755 index 00000000000..b6655d6da37 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/DtsUtil.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import java.nio.ByteBuffer; + +/** + * Utility methods for parsing DTS frames. + */ +public final class DtsUtil { + + /** + * Maps AMODE to the number of channels. See ETSI TS 102 114 table 5.4. + */ + private static final int[] CHANNELS_BY_AMODE = new int[] {1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, + 7, 8, 8}; + + /** + * Maps SFREQ to the sampling frequency in Hz. See ETSI TS 102 144 table 5.5. + */ + private static final int[] SAMPLE_RATE_BY_SFREQ = new int[] {-1, 8000, 16000, 32000, -1, -1, + 11025, 22050, 44100, -1, -1, 12000, 24000, 48000, -1, -1}; + + /** + * Maps RATE to 2 * bitrate in kbit/s. See ETSI TS 102 144 table 5.7. + */ + private static final int[] TWICE_BITRATE_KBPS_BY_RATE = new int[] {64, 112, 128, 192, 224, 256, + 384, 448, 512, 640, 768, 896, 1024, 1152, 1280, 1536, 1920, 2048, 2304, 2560, 2688, 2816, + 2823, 2944, 3072, 3840, 4096, 6144, 7680}; + + /** + * Returns the DTS format given {@code data} containing the DTS frame according to ETSI TS 102 114 + * subsections 5.3/5.4. + * + * @param frame The DTS frame to parse. + * @param trackId The track identifier to set on the format, or null. + * @param language The language to set on the format. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @return The DTS format parsed from data in the header. + */ + public static Format parseDtsFormat(byte[] frame, String trackId, String language, + DrmInitData drmInitData) { + ParsableBitArray frameBits = new ParsableBitArray(frame); + frameBits.skipBits(4 * 8 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE + int amode = frameBits.readBits(6); + int channelCount = CHANNELS_BY_AMODE[amode]; + int sfreq = frameBits.readBits(4); + int sampleRate = SAMPLE_RATE_BY_SFREQ[sfreq]; + int rate = frameBits.readBits(5); + int bitrate = rate >= TWICE_BITRATE_KBPS_BY_RATE.length ? Format.NO_VALUE + : TWICE_BITRATE_KBPS_BY_RATE[rate] * 1000 / 2; + frameBits.skipBits(10); // MIX, DYNF, TIMEF, AUXF, HDCD, EXT_AUDIO_ID, EXT_AUDIO, ASPF + channelCount += frameBits.readBits(2) > 0 ? 1 : 0; // LFF + return Format.createAudioSampleFormat(trackId, MimeTypes.AUDIO_DTS, null, bitrate, + Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, language); + } + + /** + * Returns the number of audio samples represented by the given DTS frame. + * + * @param data The frame to parse. + * @return The number of audio samples represented by the frame. + */ + public static int parseDtsAudioSampleCount(byte[] data) { + // See ETSI TS 102 114 subsection 5.4.1. + int nblks = ((data[4] & 0x01) << 6) | ((data[5] & 0xFC) >> 2); + return (nblks + 1) * 32; + } + + /** + * Like {@link #parseDtsAudioSampleCount(byte[])} but reads from a {@link ByteBuffer}. The + * buffer's position is not modified. + * + * @param buffer The {@link ByteBuffer} from which to read. + * @return The number of audio samples represented by the syncframe. + */ + public static int parseDtsAudioSampleCount(ByteBuffer buffer) { + // See ETSI TS 102 114 subsection 5.4.1. + int position = buffer.position(); + int nblks = ((buffer.get(position + 4) & 0x01) << 6) + | ((buffer.get(position + 5) & 0xFC) >> 2); + return (nblks + 1) * 32; + } + + /** + * Returns the size in bytes of the given DTS frame. + * + * @param data The frame to parse. + * @return The frame's size in bytes. + */ + public static int getDtsFrameSize(byte[] data) { + return (((data[5] & 0x02) << 12) + | ((data[6] & 0xFF) << 4) + | ((data[7] & 0xF0) >> 4)) + 1; + } + + private DtsUtil() {} + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java new file mode 100755 index 00000000000..af04048fa9e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.media.PlaybackParams; +import android.media.audiofx.Virtualizer; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecInfo; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecRenderer; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import org.telegram.messenger.exoplayer2.util.MediaClock; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.nio.ByteBuffer; + +/** + * Decodes and renders audio using {@link MediaCodec} and {@link AudioTrack}. + */ +@TargetApi(16) +public class MediaCodecAudioRenderer extends MediaCodecRenderer implements MediaClock, + AudioTrack.Listener { + + private final EventDispatcher eventDispatcher; + private final AudioTrack audioTrack; + + private boolean passthroughEnabled; + private android.media.MediaFormat passthroughMediaFormat; + private int pcmEncoding; + private int audioSessionId; + private long currentPositionUs; + private boolean allowPositionDiscontinuity; + + /** + * @param mediaCodecSelector A decoder selector. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector) { + this(mediaCodecSelector, null, true); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys) { + this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, Handler eventHandler, + AudioRendererEventListener eventListener) { + this(mediaCodecSelector, null, true, eventHandler, eventListener); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + AudioRendererEventListener eventListener) { + this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, eventHandler, + eventListener, null); + } + + /** + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + */ + public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) { + super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + audioTrack = new AudioTrack(audioCapabilities, this); + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + } + + @Override + protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException { + String mimeType = format.sampleMimeType; + if (!MimeTypes.isAudio(mimeType)) { + return FORMAT_UNSUPPORTED_TYPE; + } + if (allowPassthrough(mimeType) && mediaCodecSelector.getPassthroughDecoderInfo() != null) { + return ADAPTIVE_NOT_SEAMLESS | FORMAT_HANDLED; + } + MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, false); + if (decoderInfo == null) { + return FORMAT_UNSUPPORTED_SUBTYPE; + } + // Note: We assume support for unknown sampleRate and channelCount. + boolean decoderCapable = Util.SDK_INT < 21 + || ((format.sampleRate == Format.NO_VALUE + || decoderInfo.isAudioSampleRateSupportedV21(format.sampleRate)) + && (format.channelCount == Format.NO_VALUE + || decoderInfo.isAudioChannelCountSupportedV21(format.channelCount))); + int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + return ADAPTIVE_NOT_SEAMLESS | formatSupport; + } + + @Override + protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, + Format format, boolean requiresSecureDecoder) throws DecoderQueryException { + if (allowPassthrough(format.sampleMimeType)) { + MediaCodecInfo passthroughDecoderInfo = mediaCodecSelector.getPassthroughDecoderInfo(); + if (passthroughDecoderInfo != null) { + passthroughEnabled = true; + return passthroughDecoderInfo; + } + } + passthroughEnabled = false; + return super.getDecoderInfo(mediaCodecSelector, format, requiresSecureDecoder); + } + + /** + * Returns whether encoded audio passthrough should be used for playing back the input format. + * This implementation returns true if the {@link AudioTrack}'s audio capabilities indicate that + * passthrough is supported. + * + * @param mimeType The type of input media. + * @return Whether passthrough playback should be used. + */ + protected boolean allowPassthrough(String mimeType) { + return audioTrack.isPassthroughSupported(mimeType); + } + + @Override + protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) { + if (passthroughEnabled) { + // Override the MIME type used to configure the codec if we are using a passthrough decoder. + passthroughMediaFormat = format.getFrameworkMediaFormatV16(); + passthroughMediaFormat.setString(MediaFormat.KEY_MIME, MimeTypes.AUDIO_RAW); + codec.configure(passthroughMediaFormat, null, crypto, 0); + passthroughMediaFormat.setString(MediaFormat.KEY_MIME, format.sampleMimeType); + } else { + codec.configure(format.getFrameworkMediaFormatV16(), null, crypto, 0); + passthroughMediaFormat = null; + } + } + + @Override + public MediaClock getMediaClock() { + return this; + } + + @Override + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + } + + @Override + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + super.onInputFormatChanged(newFormat); + eventDispatcher.inputFormatChanged(newFormat); + // If the input format is anything other than PCM then we assume that the audio decoder will + // output 16-bit PCM. + pcmEncoding = MimeTypes.AUDIO_RAW.equals(newFormat.sampleMimeType) ? newFormat.pcmEncoding + : C.ENCODING_PCM_16BIT; + } + + @Override + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { + boolean passthrough = passthroughMediaFormat != null; + String mimeType = passthrough ? passthroughMediaFormat.getString(MediaFormat.KEY_MIME) + : MimeTypes.AUDIO_RAW; + MediaFormat format = passthrough ? passthroughMediaFormat : outputFormat; + int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); + int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); + audioTrack.configure(mimeType, channelCount, sampleRate, pcmEncoding, 0); + } + + /** + * Called when the audio session id becomes known. Once the id is known it will not change (and + * hence this method will not be called again) unless the renderer is disabled and then + * subsequently re-enabled. + *

        + * The default implementation is a no-op. One reason for overriding this method would be to + * instantiate and enable a {@link Virtualizer} in order to spatialize the audio channels. For + * this use case, any {@link Virtualizer} instances should be released in {@link #onDisabled()} + * (if not before). + * + * @param audioSessionId The audio session id. + */ + protected void onAudioSessionId(int audioSessionId) { + // Do nothing. + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + super.onEnabled(joining); + eventDispatcher.enabled(decoderCounters); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + super.onPositionReset(positionUs, joining); + audioTrack.reset(); + currentPositionUs = positionUs; + allowPositionDiscontinuity = true; + } + + @Override + protected void onStarted() { + super.onStarted(); + audioTrack.play(); + } + + @Override + protected void onStopped() { + audioTrack.pause(); + super.onStopped(); + } + + @Override + protected void onDisabled() { + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + try { + audioTrack.release(); + } finally { + try { + super.onDisabled(); + } finally { + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + } + + @Override + public boolean isEnded() { + return super.isEnded() && !audioTrack.hasPendingData(); + } + + @Override + public boolean isReady() { + return audioTrack.hasPendingData() || super.isReady(); + } + + @Override + public long getPositionUs() { + long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded()); + if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) { + currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs + : Math.max(currentPositionUs, newCurrentPositionUs); + allowPositionDiscontinuity = false; + } + return currentPositionUs; + } + + @Override + protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, + ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, + boolean shouldSkip) throws ExoPlaybackException { + if (passthroughEnabled && (bufferFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { + // Discard output buffers from the passthrough (raw) decoder containing codec specific data. + codec.releaseOutputBuffer(bufferIndex, false); + return true; + } + + if (shouldSkip) { + codec.releaseOutputBuffer(bufferIndex, false); + decoderCounters.skippedOutputBufferCount++; + audioTrack.handleDiscontinuity(); + return true; + } + + if (!audioTrack.isInitialized()) { + // Initialize the AudioTrack now. + try { + if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) { + audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET); + eventDispatcher.audioSessionId(audioSessionId); + onAudioSessionId(audioSessionId); + } else { + audioTrack.initialize(audioSessionId); + } + } catch (AudioTrack.InitializationException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + if (getState() == STATE_STARTED) { + audioTrack.play(); + } + } + + int handleBufferResult; + try { + handleBufferResult = audioTrack.handleBuffer(buffer, bufferPresentationTimeUs); + } catch (AudioTrack.WriteException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + + // If we are out of sync, allow currentPositionUs to jump backwards. + if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { + handleAudioTrackDiscontinuity(); + allowPositionDiscontinuity = true; + } + + // Release the buffer if it was consumed. + if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { + codec.releaseOutputBuffer(bufferIndex, false); + decoderCounters.renderedOutputBufferCount++; + return true; + } + + return false; + } + + @Override + protected void onOutputStreamEnded() { + audioTrack.handleEndOfStream(); + } + + protected void handleAudioTrackDiscontinuity() { + // Do nothing + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + switch (messageType) { + case C.MSG_SET_VOLUME: + audioTrack.setVolume((Float) message); + break; + case C.MSG_SET_PLAYBACK_PARAMS: + audioTrack.setPlaybackParams((PlaybackParams) message); + break; + case C.MSG_SET_STREAM_TYPE: + @C.StreamType int streamType = (Integer) message; + if (audioTrack.setStreamType(streamType)) { + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + } + break; + default: + super.handleMessage(messageType, message); + break; + } + } + + // AudioTrack.Listener implementation. + + @Override + public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java new file mode 100755 index 00000000000..e62afc2b842 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/audio/SimpleDecoderAudioRenderer.java @@ -0,0 +1,490 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.audio; + +import android.media.PlaybackParams; +import android.os.Handler; +import android.os.Looper; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.BaseRenderer; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.audio.AudioRendererEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.decoder.SimpleDecoder; +import org.telegram.messenger.exoplayer2.decoder.SimpleOutputBuffer; +import org.telegram.messenger.exoplayer2.drm.DrmSession; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.ExoMediaCrypto; +import org.telegram.messenger.exoplayer2.util.MediaClock; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.TraceUtil; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Decodes and renders audio using a {@link SimpleDecoder}. + */ +public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements MediaClock, + AudioTrack.Listener { + + private final boolean playClearSamplesWithoutKeys; + + private final EventDispatcher eventDispatcher; + private final AudioTrack audioTrack; + private final DrmSessionManager drmSessionManager; + private final FormatHolder formatHolder; + + private DecoderCounters decoderCounters; + private Format inputFormat; + private SimpleDecoder decoder; + private DecoderInputBuffer inputBuffer; + private SimpleOutputBuffer outputBuffer; + private DrmSession drmSession; + private DrmSession pendingDrmSession; + + private long currentPositionUs; + private boolean allowPositionDiscontinuity; + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private boolean waitingForKeys; + + private int audioSessionId; + + public SimpleDecoderAudioRenderer() { + this(null, null); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener) { + this(eventHandler, eventListener, null); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) { + this(eventHandler, eventListener, audioCapabilities, null, false); + } + + /** + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param audioCapabilities The audio capabilities for playback on this device. May be null if the + * default capabilities (no encoded audio passthrough support) should be assumed. + * @param drmSessionManager For use with encrypted media. May be null if support for encrypted + * media is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + */ + public SimpleDecoderAudioRenderer(Handler eventHandler, + AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities, + DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) { + super(C.TRACK_TYPE_AUDIO); + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + audioTrack = new AudioTrack(audioCapabilities, this); + this.drmSessionManager = drmSessionManager; + formatHolder = new FormatHolder(); + this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + } + + @Override + public MediaClock getMediaClock() { + return this; + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (outputStreamEnded) { + return; + } + + // Try and read a format if we don't have one already. + if (inputFormat == null && !readFormat()) { + // We can't make progress without one. + return; + } + + drmSession = pendingDrmSession; + ExoMediaCrypto mediaCrypto = null; + if (drmSession != null) { + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto(); + } else { + // The drm session isn't open yet. + return; + } + } + // If we don't have a decoder yet, we need to instantiate one. + if (decoder == null) { + try { + long codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createAudioDecoder"); + decoder = createDecoder(inputFormat, mediaCrypto); + TraceUtil.endSection(); + long codecInitializedTimestamp = SystemClock.elapsedRealtime(); + eventDispatcher.decoderInitialized(decoder.getName(), codecInitializedTimestamp, + codecInitializedTimestamp - codecInitializingTimestamp); + decoderCounters.decoderInitCount++; + } catch (AudioDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + // Rendering loop. + try { + TraceUtil.beginSection("drainAndFeed"); + while (drainOutputBuffer()) {} + while (feedInputBuffer()) {} + TraceUtil.endSection(); + } catch (AudioTrack.InitializationException | AudioTrack.WriteException + | AudioDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + decoderCounters.ensureUpdated(); + } + + /** + * Creates a decoder for the given format. + * + * @param format The format for which a decoder is required. + * @param mediaCrypto The {@link ExoMediaCrypto} object required for decoding encrypted content. + * Maybe null and can be ignored if decoder does not handle encrypted content. + * @return The decoder. + * @throws AudioDecoderException If an error occurred creating a suitable decoder. + */ + protected abstract SimpleDecoder createDecoder(Format format, ExoMediaCrypto mediaCrypto) + throws AudioDecoderException; + + /** + * Returns the format of audio buffers output by the decoder. Will not be called until the first + * output buffer has been dequeued, so the decoder may use input data to determine the format. + *

        + * The default implementation returns a 16-bit PCM format with the same channel count and sample + * rate as the input. + */ + protected Format getOutputFormat() { + return Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, Format.NO_VALUE, + Format.NO_VALUE, inputFormat.channelCount, inputFormat.sampleRate, C.ENCODING_PCM_16BIT, + null, null, 0, null); + } + + private boolean drainOutputBuffer() throws AudioDecoderException, + AudioTrack.InitializationException, AudioTrack.WriteException { + if (outputStreamEnded) { + return false; + } + + if (outputBuffer == null) { + outputBuffer = decoder.dequeueOutputBuffer(); + if (outputBuffer == null) { + return false; + } + decoderCounters.skippedOutputBufferCount += outputBuffer.skippedOutputBufferCount; + } + + if (outputBuffer.isEndOfStream()) { + outputStreamEnded = true; + audioTrack.handleEndOfStream(); + outputBuffer.release(); + outputBuffer = null; + return false; + } + + if (!audioTrack.isInitialized()) { + Format outputFormat = getOutputFormat(); + audioTrack.configure(outputFormat.sampleMimeType, outputFormat.channelCount, + outputFormat.sampleRate, outputFormat.pcmEncoding, 0); + if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) { + audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET); + eventDispatcher.audioSessionId(audioSessionId); + onAudioSessionId(audioSessionId); + } else { + audioTrack.initialize(audioSessionId); + } + if (getState() == STATE_STARTED) { + audioTrack.play(); + } + } + + int handleBufferResult = audioTrack.handleBuffer(outputBuffer.data, outputBuffer.timeUs); + + // If we are out of sync, allow currentPositionUs to jump backwards. + if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { + allowPositionDiscontinuity = true; + } + + // Release the buffer if it was consumed. + if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { + decoderCounters.renderedOutputBufferCount++; + outputBuffer.release(); + outputBuffer = null; + return true; + } + + return false; + } + + private boolean feedInputBuffer() throws AudioDecoderException, ExoPlaybackException { + if (inputStreamEnded) { + return false; + } + + if (inputBuffer == null) { + inputBuffer = decoder.dequeueInputBuffer(); + if (inputBuffer == null) { + return false; + } + } + + int result; + if (waitingForKeys) { + // We've already read an encrypted sample into buffer, and are waiting for keys. + result = C.RESULT_BUFFER_READ; + } else { + result = readSource(formatHolder, inputBuffer); + } + + if (result == C.RESULT_NOTHING_READ) { + return false; + } + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + return true; + } + if (inputBuffer.isEndOfStream()) { + inputStreamEnded = true; + decoder.queueInputBuffer(inputBuffer); + inputBuffer = null; + return false; + } + boolean bufferEncrypted = inputBuffer.isEncrypted(); + waitingForKeys = shouldWaitForKeys(bufferEncrypted); + if (waitingForKeys) { + return false; + } + inputBuffer.flip(); + decoder.queueInputBuffer(inputBuffer); + decoderCounters.inputBufferCount++; + inputBuffer = null; + return true; + } + + private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { + if (drmSession == null) { + return false; + } + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS + && (bufferEncrypted || !playClearSamplesWithoutKeys); + } + + private void flushDecoder() { + inputBuffer = null; + waitingForKeys = false; + if (outputBuffer != null) { + outputBuffer.release(); + outputBuffer = null; + } + decoder.flush(); + } + + @Override + public boolean isEnded() { + return outputStreamEnded && !audioTrack.hasPendingData(); + } + + @Override + public boolean isReady() { + return audioTrack.hasPendingData() + || (inputFormat != null && !waitingForKeys && (isSourceReady() || outputBuffer != null)); + } + + @Override + public long getPositionUs() { + long newCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded()); + if (newCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) { + currentPositionUs = allowPositionDiscontinuity ? newCurrentPositionUs + : Math.max(currentPositionUs, newCurrentPositionUs); + allowPositionDiscontinuity = false; + } + return currentPositionUs; + } + + /** + * Called when the audio session id becomes known. Once the id is known it will not change (and + * hence this method will not be called again) unless the renderer is disabled and then + * subsequently re-enabled. + *

        + * The default implementation is a no-op. + * + * @param audioSessionId The audio session id. + */ + protected void onAudioSessionId(int audioSessionId) { + // Do nothing. + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + decoderCounters = new DecoderCounters(); + eventDispatcher.enabled(decoderCounters); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + audioTrack.reset(); + currentPositionUs = positionUs; + allowPositionDiscontinuity = true; + inputStreamEnded = false; + outputStreamEnded = false; + if (decoder != null) { + flushDecoder(); + } + } + + @Override + protected void onStarted() { + audioTrack.play(); + } + + @Override + protected void onStopped() { + audioTrack.pause(); + } + + @Override + protected void onDisabled() { + inputBuffer = null; + outputBuffer = null; + inputFormat = null; + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + waitingForKeys = false; + try { + if (decoder != null) { + decoder.release(); + decoder = null; + decoderCounters.decoderReleaseCount++; + } + audioTrack.release(); + } finally { + try { + if (drmSession != null) { + drmSessionManager.releaseSession(drmSession); + } + } finally { + try { + if (pendingDrmSession != null && pendingDrmSession != drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } finally { + drmSession = null; + pendingDrmSession = null; + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + } + } + + private boolean readFormat() throws ExoPlaybackException { + int result = readSource(formatHolder, null); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + return true; + } + return false; + } + + private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + Format oldFormat = inputFormat; + inputFormat = newFormat; + + boolean drmInitDataChanged = !Util.areEqual(inputFormat.drmInitData, oldFormat == null ? null + : oldFormat.drmInitData); + if (drmInitDataChanged) { + if (inputFormat.drmInitData != null) { + if (drmSessionManager == null) { + throw ExoPlaybackException.createForRenderer( + new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); + } + pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), + inputFormat.drmInitData); + if (pendingDrmSession == drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } else { + pendingDrmSession = null; + } + } + + eventDispatcher.inputFormatChanged(newFormat); + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + switch (messageType) { + case C.MSG_SET_VOLUME: + audioTrack.setVolume((Float) message); + break; + case C.MSG_SET_PLAYBACK_PARAMS: + audioTrack.setPlaybackParams((PlaybackParams) message); + break; + case C.MSG_SET_STREAM_TYPE: + @C.StreamType int streamType = (Integer) message; + if (audioTrack.setStreamType(streamType)) { + audioSessionId = AudioTrack.SESSION_ID_NOT_SET; + } + break; + default: + super.handleMessage(messageType, message); + break; + } + } + + // AudioTrack.Listener implementation. + + @Override + public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) { + eventDispatcher.audioTrackUnderrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Buffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Buffer.java new file mode 100755 index 00000000000..2a6fa435045 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Buffer.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +import org.telegram.messenger.exoplayer2.C; + +/** + * Base class for buffers with flags. + */ +public abstract class Buffer { + + @C.BufferFlags + private int flags; + + /** + * Clears the buffer. + */ + public void clear() { + flags = 0; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_DECODE_ONLY} flag is set. + */ + public final boolean isDecodeOnly() { + return getFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_END_OF_STREAM} flag is set. + */ + public final boolean isEndOfStream() { + return getFlag(C.BUFFER_FLAG_END_OF_STREAM); + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_KEY_FRAME} flag is set. + */ + public final boolean isKeyFrame() { + return getFlag(C.BUFFER_FLAG_KEY_FRAME); + } + + /** + * Replaces this buffer's flags with {@code flags}. + * + * @param flags The flags to set, which should be a combination of the {@code C.BUFFER_FLAG_*} + * constants. + */ + public final void setFlags(@C.BufferFlags int flags) { + this.flags = flags; + } + + /** + * Adds the {@code flag} to this buffer's flags. + * + * @param flag The flag to add to this buffer's flags, which should be one of the + * {@code C.BUFFER_FLAG_*} constants. + */ + public final void addFlag(@C.BufferFlags int flag) { + flags |= flag; + } + + /** + * Removes the {@code flag} from this buffer's flags, if it is set. + * + * @param flag The flag to remove. + */ + public final void clearFlag(@C.BufferFlags int flag) { + flags &= ~flag; + } + + /** + * Returns whether the specified flag has been set on this buffer. + * + * @param flag The flag to check. + * @return Whether the flag is set. + */ + protected final boolean getFlag(@C.BufferFlags int flag) { + return (flags & flag) == flag; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java new file mode 100755 index 00000000000..a01adc9deff --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/CryptoInfo.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +import android.annotation.TargetApi; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Compatibility wrapper for {@link android.media.MediaCodec.CryptoInfo}. + */ +public final class CryptoInfo { + + /** + * @see android.media.MediaCodec.CryptoInfo#iv + */ + public byte[] iv; + /** + * @see android.media.MediaCodec.CryptoInfo#key + */ + public byte[] key; + /** + * @see android.media.MediaCodec.CryptoInfo#mode + */ + @C.CryptoMode + public int mode; + /** + * @see android.media.MediaCodec.CryptoInfo#numBytesOfClearData + */ + public int[] numBytesOfClearData; + /** + * @see android.media.MediaCodec.CryptoInfo#numBytesOfEncryptedData + */ + public int[] numBytesOfEncryptedData; + /** + * @see android.media.MediaCodec.CryptoInfo#numSubSamples + */ + public int numSubSamples; + + private final android.media.MediaCodec.CryptoInfo frameworkCryptoInfo; + + public CryptoInfo() { + frameworkCryptoInfo = Util.SDK_INT >= 16 ? newFrameworkCryptoInfoV16() : null; + } + + /** + * @see android.media.MediaCodec.CryptoInfo#set(int, int[], int[], byte[], byte[], int) + */ + public void set(int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, + byte[] key, byte[] iv, @C.CryptoMode int mode) { + this.numSubSamples = numSubSamples; + this.numBytesOfClearData = numBytesOfClearData; + this.numBytesOfEncryptedData = numBytesOfEncryptedData; + this.key = key; + this.iv = iv; + this.mode = mode; + if (Util.SDK_INT >= 16) { + updateFrameworkCryptoInfoV16(); + } + } + + /** + * Returns an equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + *

        + * Successive calls to this method on a single {@link CryptoInfo} will return the same instance. + * Changes to the {@link CryptoInfo} will be reflected in the returned object. The return object + * should not be modified directly. + * + * @return The equivalent {@link android.media.MediaCodec.CryptoInfo} instance. + */ + @TargetApi(16) + public android.media.MediaCodec.CryptoInfo getFrameworkCryptoInfoV16() { + return frameworkCryptoInfo; + } + + @TargetApi(16) + private android.media.MediaCodec.CryptoInfo newFrameworkCryptoInfoV16() { + return new android.media.MediaCodec.CryptoInfo(); + } + + @TargetApi(16) + private void updateFrameworkCryptoInfoV16() { + frameworkCryptoInfo.set(numSubSamples, numBytesOfClearData, numBytesOfEncryptedData, key, iv, + mode); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Decoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Decoder.java similarity index 76% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Decoder.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Decoder.java index 6b36e1f4600..46be86809b4 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/Decoder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util.extensions; +package org.telegram.messenger.exoplayer2.decoder; /** * A media decoder. @@ -24,10 +24,17 @@ */ public interface Decoder { + /** + * Returns the name of the decoder. + * + * @return The name of the decoder. + */ + String getName(); + /** * Dequeues the next input buffer to be filled and queued to the decoder. * - * @return The input buffer, or null if an input buffer isn't available. + * @return The input buffer, which will have been cleared, or null if a buffer isn't available. * @throws E If a decoder error has occurred. */ I dequeueInputBuffer() throws E; @@ -49,9 +56,8 @@ public interface Decoder { O dequeueOutputBuffer() throws E; /** - * Flushes input/output buffers that have not been dequeued yet and returns ownership of any - * dequeued input buffer to the decoder. Flushes any pending output currently in the decoder. The - * caller is still responsible for releasing any dequeued output buffers. + * Flushes the decoder. Ownership of dequeued input buffers is returned to the decoder. The caller + * is still responsible for releasing any dequeued output buffers. */ void flush(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderCounters.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderCounters.java new file mode 100755 index 00000000000..961014b9d8d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderCounters.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +/** + * Maintains decoder event counts, for debugging purposes only. + *

        + * Counters should be written from the playback thread only. Counters may be read from any thread. + * To ensure that the counter values are made visible across threads, users of this class should + * invoke {@link #ensureUpdated()} prior to reading and after writing. + */ +public final class DecoderCounters { + + /** + * The number of times a decoder has been initialized. + */ + public int decoderInitCount; + /** + * The number of times a decoder has been released. + */ + public int decoderReleaseCount; + /** + * The number of queued input buffers. + */ + public int inputBufferCount; + /** + * The number of rendered output buffers. + */ + public int renderedOutputBufferCount; + /** + * The number of skipped output buffers. + *

        + * A skipped output buffer is an output buffer that was deliberately not rendered. + */ + public int skippedOutputBufferCount; + /** + * The number of dropped output buffers. + *

        + * A dropped output buffer is an output buffer that was supposed to be rendered, but was instead + * dropped because it could not be rendered in time. + */ + public int droppedOutputBufferCount; + /** + * The maximum number of dropped output buffers without an interleaving rendered output buffer. + *

        + * Skipped output buffers are ignored for the purposes of calculating this value. + */ + public int maxConsecutiveDroppedOutputBufferCount; + + /** + * Should be called to ensure counter values are made visible across threads. The playback thread + * should call this method after updating the counter values. Any other thread should call this + * method before reading the counters. + */ + public synchronized void ensureUpdated() { + // Do nothing. The use of synchronized ensures a memory barrier should another thread also + // call this method. + } + + /** + * Merges the counts from {@code other} into this instance. + * + * @param other The {@link DecoderCounters} to merge into this instance. + */ + public void merge(DecoderCounters other) { + decoderInitCount += other.decoderInitCount; + decoderReleaseCount += other.decoderReleaseCount; + inputBufferCount += other.inputBufferCount; + renderedOutputBufferCount += other.renderedOutputBufferCount; + skippedOutputBufferCount += other.skippedOutputBufferCount; + droppedOutputBufferCount += other.droppedOutputBufferCount; + maxConsecutiveDroppedOutputBufferCount = Math.max(maxConsecutiveDroppedOutputBufferCount, + other.maxConsecutiveDroppedOutputBufferCount); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderInputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderInputBuffer.java new file mode 100755 index 00000000000..8855a6a07e1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/DecoderInputBuffer.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.C; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.nio.ByteBuffer; + +/** + * Holds input for a decoder. + */ +public class DecoderInputBuffer extends Buffer { + + /** + * The buffer replacement mode, which may disable replacement. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({BUFFER_REPLACEMENT_MODE_DISABLED, BUFFER_REPLACEMENT_MODE_NORMAL, + BUFFER_REPLACEMENT_MODE_DIRECT}) + public @interface BufferReplacementMode {} + /** + * Disallows buffer replacement. + */ + public static final int BUFFER_REPLACEMENT_MODE_DISABLED = 0; + /** + * Allows buffer replacement using {@link ByteBuffer#allocate(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_NORMAL = 1; + /** + * Allows buffer replacement using {@link ByteBuffer#allocateDirect(int)}. + */ + public static final int BUFFER_REPLACEMENT_MODE_DIRECT = 2; + + /** + * {@link CryptoInfo} for encrypted data. + */ + public final CryptoInfo cryptoInfo; + + /** + * The buffer's data, or {@code null} if no data has been set. + */ + public ByteBuffer data; + + /** + * The time at which the sample should be presented. + */ + public long timeUs; + + @BufferReplacementMode + private final int bufferReplacementMode; + + /** + * @param bufferReplacementMode Determines the behavior of {@link #ensureSpaceForWrite(int)}. One + * of {@link #BUFFER_REPLACEMENT_MODE_DISABLED}, {@link #BUFFER_REPLACEMENT_MODE_NORMAL} and + * {@link #BUFFER_REPLACEMENT_MODE_DIRECT}. + */ + public DecoderInputBuffer(@BufferReplacementMode int bufferReplacementMode) { + this.cryptoInfo = new CryptoInfo(); + this.bufferReplacementMode = bufferReplacementMode; + } + + /** + * Ensures that {@link #data} is large enough to accommodate a write of a given length at its + * current position. + *

        + * If the capacity of {@link #data} is sufficient this method does nothing. If the capacity is + * insufficient then an attempt is made to replace {@link #data} with a new {@link ByteBuffer} + * whose capacity is sufficient. Data up to the current position is copied to the new buffer. + * + * @param length The length of the write that must be accommodated, in bytes. + * @throws IllegalStateException If there is insufficient capacity to accommodate the write and + * the buffer replacement mode of the holder is {@link #BUFFER_REPLACEMENT_MODE_DISABLED}. + */ + public void ensureSpaceForWrite(int length) throws IllegalStateException { + if (data == null) { + data = createReplacementByteBuffer(length); + return; + } + // Check whether the current buffer is sufficient. + int capacity = data.capacity(); + int position = data.position(); + int requiredCapacity = position + length; + if (capacity >= requiredCapacity) { + return; + } + // Instantiate a new buffer if possible. + ByteBuffer newData = createReplacementByteBuffer(requiredCapacity); + // Copy data up to the current position from the old buffer to the new one. + if (position > 0) { + data.position(0); + data.limit(position); + newData.put(data); + } + // Set the new buffer. + data = newData; + } + + /** + * Returns whether the {@link C#BUFFER_FLAG_ENCRYPTED} flag is set. + */ + public final boolean isEncrypted() { + return getFlag(C.BUFFER_FLAG_ENCRYPTED); + } + + /** + * Flips {@link #data} in preparation for being queued to a decoder. + * + * @see java.nio.Buffer#flip() + */ + public final void flip() { + data.flip(); + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + } + + private ByteBuffer createReplacementByteBuffer(int requiredCapacity) { + if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_NORMAL) { + return ByteBuffer.allocate(requiredCapacity); + } else if (bufferReplacementMode == BUFFER_REPLACEMENT_MODE_DIRECT) { + return ByteBuffer.allocateDirect(requiredCapacity); + } else { + int currentCapacity = data == null ? 0 : data.capacity(); + throw new IllegalStateException("Buffer too small (" + currentCapacity + " < " + + requiredCapacity + ")"); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/OutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/OutputBuffer.java similarity index 76% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/OutputBuffer.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/OutputBuffer.java index bc29630f1cd..5000ebc7e77 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/extensions/OutputBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/OutputBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util.extensions; +package org.telegram.messenger.exoplayer2.decoder; /** * Output buffer decoded by a {@link Decoder}. @@ -23,7 +23,12 @@ public abstract class OutputBuffer extends Buffer { /** * The presentation timestamp for the buffer, in microseconds. */ - public long timestampUs; + public long timeUs; + + /** + * The number of buffers immediately prior to this one that were skipped in the {@link Decoder}. + */ + public int skippedOutputBufferCount; /** * Releases the output buffer for reuse. Must be called when the buffer is no longer needed. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleDecoder.java new file mode 100755 index 00000000000..b100265bb70 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleDecoder.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.util.LinkedList; + +/** + * Base class for {@link Decoder}s that use their own decode thread. + */ +public abstract class SimpleDecoder implements Decoder { + + private final Thread decodeThread; + + private final Object lock; + private final LinkedList queuedInputBuffers; + private final LinkedList queuedOutputBuffers; + private final I[] availableInputBuffers; + private final O[] availableOutputBuffers; + + private int availableInputBufferCount; + private int availableOutputBufferCount; + private I dequeuedInputBuffer; + + private E exception; + private boolean flushed; + private boolean released; + private int skippedOutputBufferCount; + + /** + * @param inputBuffers An array of nulls that will be used to store references to input buffers. + * @param outputBuffers An array of nulls that will be used to store references to output buffers. + */ + protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) { + lock = new Object(); + queuedInputBuffers = new LinkedList<>(); + queuedOutputBuffers = new LinkedList<>(); + availableInputBuffers = inputBuffers; + availableInputBufferCount = inputBuffers.length; + for (int i = 0; i < availableInputBufferCount; i++) { + availableInputBuffers[i] = createInputBuffer(); + } + availableOutputBuffers = outputBuffers; + availableOutputBufferCount = outputBuffers.length; + for (int i = 0; i < availableOutputBufferCount; i++) { + availableOutputBuffers[i] = createOutputBuffer(); + } + decodeThread = new Thread() { + @Override + public void run() { + SimpleDecoder.this.run(); + } + }; + decodeThread.start(); + } + + /** + * Sets the initial size of each input buffer. + *

        + * This method should only be called before the decoder is used (i.e. before the first call to + * {@link #dequeueInputBuffer()}. + * + * @param size The required input buffer size. + */ + protected final void setInitialInputBufferSize(int size) { + Assertions.checkState(availableInputBufferCount == availableInputBuffers.length); + for (I inputBuffer : availableInputBuffers) { + inputBuffer.ensureSpaceForWrite(size); + } + } + + @Override + public final I dequeueInputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkState(dequeuedInputBuffer == null); + dequeuedInputBuffer = availableInputBufferCount == 0 ? null + : availableInputBuffers[--availableInputBufferCount]; + return dequeuedInputBuffer; + } + } + + @Override + public final void queueInputBuffer(I inputBuffer) throws E { + synchronized (lock) { + maybeThrowException(); + Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); + queuedInputBuffers.addLast(inputBuffer); + maybeNotifyDecodeLoop(); + dequeuedInputBuffer = null; + } + } + + @Override + public final O dequeueOutputBuffer() throws E { + synchronized (lock) { + maybeThrowException(); + if (queuedOutputBuffers.isEmpty()) { + return null; + } + return queuedOutputBuffers.removeFirst(); + } + } + + /** + * Releases an output buffer back to the decoder. + * + * @param outputBuffer The output buffer being released. + */ + protected void releaseOutputBuffer(O outputBuffer) { + synchronized (lock) { + releaseOutputBufferInternal(outputBuffer); + maybeNotifyDecodeLoop(); + } + } + + @Override + public final void flush() { + synchronized (lock) { + flushed = true; + skippedOutputBufferCount = 0; + if (dequeuedInputBuffer != null) { + releaseInputBufferInternal(dequeuedInputBuffer); + dequeuedInputBuffer = null; + } + while (!queuedInputBuffers.isEmpty()) { + releaseInputBufferInternal(queuedInputBuffers.removeFirst()); + } + while (!queuedOutputBuffers.isEmpty()) { + releaseOutputBufferInternal(queuedOutputBuffers.removeFirst()); + } + } + } + + @Override + public void release() { + synchronized (lock) { + released = true; + lock.notify(); + } + try { + decodeThread.join(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + /** + * Throws a decode exception, if there is one. + * + * @throws E The decode exception. + */ + private void maybeThrowException() throws E { + if (exception != null) { + throw exception; + } + } + + /** + * Notifies the decode loop if there exists a queued input buffer and an available output buffer + * to decode into. + *

        + * Should only be called whilst synchronized on the lock object. + */ + private void maybeNotifyDecodeLoop() { + if (canDecodeBuffer()) { + lock.notify(); + } + } + + private void run() { + try { + while (decode()) { + // Do nothing. + } + } catch (InterruptedException e) { + // Not expected. + throw new IllegalStateException(e); + } + } + + private boolean decode() throws InterruptedException { + I inputBuffer; + O outputBuffer; + boolean resetDecoder; + + // Wait until we have an input buffer to decode, and an output buffer to decode into. + synchronized (lock) { + while (!released && !canDecodeBuffer()) { + lock.wait(); + } + if (released) { + return false; + } + inputBuffer = queuedInputBuffers.removeFirst(); + outputBuffer = availableOutputBuffers[--availableOutputBufferCount]; + resetDecoder = flushed; + flushed = false; + } + + if (inputBuffer.isEndOfStream()) { + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + } else { + if (inputBuffer.isDecodeOnly()) { + outputBuffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + exception = decode(inputBuffer, outputBuffer, resetDecoder); + if (exception != null) { + // Memory barrier to ensure that the decoder exception is visible from the playback thread. + synchronized (lock) {} + return false; + } + } + + synchronized (lock) { + if (flushed) { + releaseOutputBufferInternal(outputBuffer); + } else if (outputBuffer.isDecodeOnly()) { + skippedOutputBufferCount++; + releaseOutputBufferInternal(outputBuffer); + } else { + outputBuffer.skippedOutputBufferCount = skippedOutputBufferCount; + skippedOutputBufferCount = 0; + queuedOutputBuffers.addLast(outputBuffer); + } + // Make the input buffer available again. + releaseInputBufferInternal(inputBuffer); + } + + return true; + } + + private boolean canDecodeBuffer() { + return !queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0; + } + + private void releaseInputBufferInternal(I inputBuffer) { + inputBuffer.clear(); + availableInputBuffers[availableInputBufferCount++] = inputBuffer; + } + + private void releaseOutputBufferInternal(O outputBuffer) { + outputBuffer.clear(); + availableOutputBuffers[availableOutputBufferCount++] = outputBuffer; + } + + /** + * Creates a new input buffer. + */ + protected abstract I createInputBuffer(); + + /** + * Creates a new output buffer. + */ + protected abstract O createOutputBuffer(); + + /** + * Decodes the {@code inputBuffer} and stores any decoded output in {@code outputBuffer}. + * + * @param inputBuffer The buffer to decode. + * @param outputBuffer The output buffer to store decoded data. The flag + * {@link C#BUFFER_FLAG_DECODE_ONLY} will be set if the same flag is set on + * {@code inputBuffer}, but may be set/unset as required. If the flag is set when the call + * returns then the output buffer will not be made available to dequeue. The output buffer + * may not have been populated in this case. + * @param reset Whether the decoder must be reset before decoding. + * @return A decoder exception if an error occurred, or null if decoding was successful. + */ + protected abstract E decode(I inputBuffer, O outputBuffer, boolean reset); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java new file mode 100755 index 00000000000..ac51e95a3cb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/decoder/SimpleOutputBuffer.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.decoder; + +import java.nio.ByteBuffer; + +/** + * Buffer for {@link SimpleDecoder} output. + */ +public class SimpleOutputBuffer extends OutputBuffer { + + private final SimpleDecoder owner; + + public ByteBuffer data; + + public SimpleOutputBuffer(SimpleDecoder owner) { + this.owner = owner; + } + + /** + * Initializes the buffer. + * + * @param timeUs The presentation timestamp for the buffer, in microseconds. + * @param size An upper bound on the size of the data that will be written to the buffer. + * @return The {@link #data} buffer, for convenience. + */ + public ByteBuffer init(long timeUs, int size) { + this.timeUs = timeUs; + if (data == null || data.capacity() < size) { + data = ByteBuffer.allocateDirect(size); + } + data.position(0); + data.limit(size); + return data; + } + + @Override + public void clear() { + super.clear(); + if (data != null) { + data.clear(); + } + } + + @Override + public void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java new file mode 100755 index 00000000000..646c8a3b861 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DecryptionException.java @@ -0,0 +1,20 @@ +package org.telegram.messenger.exoplayer2.drm; + +/** + * An exception when doing drm decryption using the In-App Drm + */ +public class DecryptionException extends Exception { + private final int errorCode; + + public DecryptionException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + /** + * Get error code + */ + public int getErrorCode() { + return errorCode; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java new file mode 100755 index 00000000000..7cf53522a07 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmInitData.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.drm; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; + +/** + * Initialization data for one or more DRM schemes. + */ +public final class DrmInitData implements Comparator, Parcelable { + + private final SchemeData[] schemeDatas; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * Number of {@link SchemeData}s. + */ + public final int schemeDataCount; + + /** + * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. + */ + public DrmInitData(List schemeDatas) { + this(false, schemeDatas.toArray(new SchemeData[schemeDatas.size()])); + } + + /** + * @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. + */ + public DrmInitData(SchemeData... schemeDatas) { + this(true, schemeDatas); + } + + private DrmInitData(boolean cloneSchemeDatas, SchemeData... schemeDatas) { + if (cloneSchemeDatas) { + schemeDatas = schemeDatas.clone(); + } + // Sorting ensures that universal scheme data(i.e. data that applies to all schemes) is matched + // last. It's also required by the equals and hashcode implementations. + Arrays.sort(schemeDatas, this); + // Check for no duplicates. + for (int i = 1; i < schemeDatas.length; i++) { + if (schemeDatas[i - 1].uuid.equals(schemeDatas[i].uuid)) { + throw new IllegalArgumentException("Duplicate data for uuid: " + schemeDatas[i].uuid); + } + } + this.schemeDatas = schemeDatas; + schemeDataCount = schemeDatas.length; + } + + /* package */ DrmInitData(Parcel in) { + schemeDatas = in.createTypedArray(SchemeData.CREATOR); + schemeDataCount = schemeDatas.length; + } + + /** + * Retrieves data for a given DRM scheme, specified by its UUID. + * + * @param uuid The DRM scheme's UUID. + * @return The initialization data for the scheme, or null if the scheme is not supported. + */ + public SchemeData get(UUID uuid) { + for (SchemeData schemeData : schemeDatas) { + if (schemeData.matches(uuid)) { + return schemeData; + } + } + return null; + } + + /** + * Retrieves the {@link SchemeData} at a given index. + * + * @param index index of the scheme to return. + * @return The {@link SchemeData} at the index. + */ + public SchemeData get(int index) { + return schemeDatas[index]; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(schemeDatas); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + return Arrays.equals(schemeDatas, ((DrmInitData) obj).schemeDatas); + } + + @Override + public int compare(SchemeData first, SchemeData second) { + return C.UUID_NIL.equals(first.uuid) ? (C.UUID_NIL.equals(second.uuid) ? 0 : 1) + : first.uuid.compareTo(second.uuid); + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedArray(schemeDatas, 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public DrmInitData createFromParcel(Parcel in) { + return new DrmInitData(in); + } + + @Override + public DrmInitData[] newArray(int size) { + return new DrmInitData[size]; + } + + }; + + /** + * Scheme initialization data. + */ + public static final class SchemeData implements Parcelable { + + // Lazily initialized hashcode. + private int hashCode; + + /** + * The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is universal (i.e. + * applies to all schemes). + */ + private final UUID uuid; + /** + * The mimeType of {@link #data}. + */ + public final String mimeType; + /** + * The initialization data. + */ + public final byte[] data; + /** + * Whether secure decryption is required. + */ + public final boolean requiresSecureDecryption; + + /** + * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is + * universal (i.e. applies to all schemes). + * @param mimeType The mimeType of the initialization data. + * @param data The initialization data. + */ + public SchemeData(UUID uuid, String mimeType, byte[] data) { + this(uuid, mimeType, data, false); + } + + /** + * @param uuid The {@link UUID} of the DRM scheme, or {@link C#UUID_NIL} if the data is + * universal (i.e. applies to all schemes). + * @param mimeType The mimeType of the initialization data. + * @param data The initialization data. + * @param requiresSecureDecryption Whether secure decryption is required. + */ + public SchemeData(UUID uuid, String mimeType, byte[] data, boolean requiresSecureDecryption) { + this.uuid = Assertions.checkNotNull(uuid); + this.mimeType = Assertions.checkNotNull(mimeType); + this.data = Assertions.checkNotNull(data); + this.requiresSecureDecryption = requiresSecureDecryption; + } + + /* package */ SchemeData(Parcel in) { + uuid = new UUID(in.readLong(), in.readLong()); + mimeType = in.readString(); + data = in.createByteArray(); + requiresSecureDecryption = in.readByte() != 0; + } + + /** + * Returns whether this initialization data applies to the specified scheme. + * + * @param schemeUuid The scheme {@link UUID}. + * @return Whether this initialization data applies to the specified scheme. + */ + public boolean matches(UUID schemeUuid) { + return C.UUID_NIL.equals(uuid) || schemeUuid.equals(uuid); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SchemeData)) { + return false; + } + if (obj == this) { + return true; + } + SchemeData other = (SchemeData) obj; + return mimeType.equals(other.mimeType) && Util.areEqual(uuid, other.uuid) + && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = uuid.hashCode(); + result = 31 * result + mimeType.hashCode(); + result = 31 * result + Arrays.hashCode(data); + hashCode = result; + } + return hashCode; + } + + // Parcelable implementation. + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(uuid.getMostSignificantBits()); + dest.writeLong(uuid.getLeastSignificantBits()); + dest.writeString(mimeType); + dest.writeByteArray(data); + dest.writeByte((byte) (requiresSecureDecryption ? 1 : 0)); + } + + @SuppressWarnings("hiding") + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SchemeData createFromParcel(Parcel in) { + return new SchemeData(in); + } + + @Override + public SchemeData[] newArray(int size) { + return new SchemeData[size]; + } + + }; + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java new file mode 100755 index 00000000000..bd833e001ce --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSession.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A DRM session. + */ +@TargetApi(16) +public interface DrmSession { + + /** + * The state of the DRM session. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_ERROR, STATE_CLOSED, STATE_OPENING, STATE_OPENED, STATE_OPENED_WITH_KEYS}) + @interface State {} + /** + * The session has encountered an error. {@link #getError()} can be used to retrieve the cause. + */ + int STATE_ERROR = 0; + /** + * The session is closed. + */ + int STATE_CLOSED = 1; + /** + * The session is being opened. + */ + int STATE_OPENING = 2; + /** + * The session is open, but does not yet have the keys required for decryption. + */ + int STATE_OPENED = 3; + /** + * The session is open and has the keys required for decryption. + */ + int STATE_OPENED_WITH_KEYS = 4; + + /** + * Returns the current state of the session. + * + * @return One of {@link #STATE_ERROR}, {@link #STATE_CLOSED}, {@link #STATE_OPENING}, + * {@link #STATE_OPENED} and {@link #STATE_OPENED_WITH_KEYS}. + */ + @State + int getState(); + + /** + * Returns a {@link ExoMediaCrypto} for the open session. + *

        + * This method may be called when the session is in the following states: + * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} + * + * @return A {@link ExoMediaCrypto} for the open session. + * @throws IllegalStateException If called when a session isn't opened. + */ + T getMediaCrypto(); + + /** + * Whether the session requires a secure decoder for the specified mime type. + *

        + * Normally this method should return + * {@link ExoMediaCrypto#requiresSecureDecoderComponent(String)}, however in some cases + * implementations may wish to modify the return value (i.e. to force a secure decoder even when + * one is not required). + *

        + * This method may be called when the session is in the following states: + * {@link #STATE_OPENED}, {@link #STATE_OPENED_WITH_KEYS} + * + * @return Whether the open session requires a secure decoder for the specified mime type. + * @throws IllegalStateException If called when a session isn't opened. + */ + boolean requiresSecureDecoderComponent(String mimeType); + + /** + * Returns the cause of the error state. + *

        + * This method may be called when the session is in any state. + * + * @return An exception if the state is {@link #STATE_ERROR}. Null otherwise. + */ + Exception getError(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java new file mode 100755 index 00000000000..2bd9924163f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/DrmSessionManager.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.os.Looper; + +/** + * Manages a DRM session. + */ +@TargetApi(16) +public interface DrmSessionManager { + + /** + * Acquires a {@link DrmSession} for the specified {@link DrmInitData}. The {@link DrmSession} + * must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. + * + * @param playbackLooper The looper associated with the media playback thread. + * @param drmInitData DRM initialization data. + * @return The DRM session. + */ + DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData); + + /** + * Releases a {@link DrmSession}. + */ + void releaseSession(DrmSession drmSession); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaCrypto.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaCrypto.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaCrypto.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaCrypto.java index 1b0dda39b21..4eaffb8944f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaCrypto.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaCrypto.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; /** * An opaque {@link android.media.MediaCrypto} equivalent. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaDrm.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaDrm.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaDrm.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaDrm.java index dbe7702e4b7..9373cb0693c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/ExoMediaDrm.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/ExoMediaDrm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; import android.media.DeniedByServerException; import android.media.MediaCryptoException; +import android.media.MediaDrm; import android.media.NotProvisionedException; import android.media.ResourceBusyException; import java.util.HashMap; @@ -62,74 +63,74 @@ interface ProvisionRequest { } /** - * @see android.media.MediaDrm#setOnEventListener(android.media.MediaDrm.OnEventListener) + * @see MediaDrm#setOnEventListener(MediaDrm.OnEventListener) */ void setOnEventListener(OnEventListener listener); /** - * @see android.media.MediaDrm#openSession() + * @see MediaDrm#openSession() */ byte[] openSession() throws NotProvisionedException, ResourceBusyException; /** - * @see android.media.MediaDrm#closeSession(byte[]) + * @see MediaDrm#closeSession(byte[]) */ void closeSession(byte[] sessionId); /** - * @see android.media.MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap) + * @see MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap) */ KeyRequest getKeyRequest(byte[] scope, byte[] init, String mimeType, int keyType, HashMap optionalParameters) throws NotProvisionedException; /** - * @see android.media.MediaDrm#provideKeyResponse(byte[], byte[]) + * @see MediaDrm#provideKeyResponse(byte[], byte[]) */ byte[] provideKeyResponse(byte[] scope, byte[] response) throws NotProvisionedException, DeniedByServerException; /** - * @see android.media.MediaDrm#getProvisionRequest() + * @see MediaDrm#getProvisionRequest() */ ProvisionRequest getProvisionRequest(); /** - * @see android.media.MediaDrm#provideProvisionResponse(byte[]) + * @see MediaDrm#provideProvisionResponse(byte[]) */ void provideProvisionResponse(byte[] response) throws DeniedByServerException; /** - * @see android.media.MediaDrm#queryKeyStatus(byte[]). + * @see MediaDrm#queryKeyStatus(byte[]) */ Map queryKeyStatus(byte[] sessionId); /** - * @see android.media.MediaDrm#release(). + * @see MediaDrm#release() */ void release(); /** - * @see android.media.MediaDrm#restoreKeys(byte[], byte[]). + * @see MediaDrm#restoreKeys(byte[], byte[]) */ void restoreKeys(byte[] sessionId, byte[] keySetId); /** - * @see android.media.MediaDrm#getPropertyString(String) + * @see MediaDrm#getPropertyString(String) */ String getPropertyString(String propertyName); /** - * @see android.media.MediaDrm#getPropertyByteArray(String) + * @see MediaDrm#getPropertyByteArray(String) */ byte[] getPropertyByteArray(String propertyName); /** - * @see android.media.MediaDrm#setPropertyString(String, String) + * @see MediaDrm#setPropertyString(String, String) */ void setPropertyString(String propertyName, String value); /** - * @see android.media.MediaDrm#setPropertyByteArray(String, byte[]) + * @see MediaDrm#setPropertyByteArray(String, byte[]) */ void setPropertyByteArray(String propertyName, byte[] value); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaCrypto.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java similarity index 88% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaCrypto.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java index 851e4f0b12b..5e551d0de8e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaCrypto.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaCrypto.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; import android.annotation.TargetApi; import android.media.MediaCrypto; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Assertions; /** * An {@link ExoMediaCrypto} implementation that wraps the framework {@link MediaCrypto}. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaDrm.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaDrm.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java index f087d5802ac..f3b4deb4fab 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/FrameworkMediaDrm.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/FrameworkMediaDrm.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; import android.annotation.TargetApi; import android.media.DeniedByServerException; @@ -23,7 +23,7 @@ import android.media.NotProvisionedException; import android.media.ResourceBusyException; import android.media.UnsupportedSchemeException; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -36,7 +36,24 @@ public final class FrameworkMediaDrm implements ExoMediaDrm PLAYREADY_KEY_REQUEST_PROPERTIES; + static { + PLAYREADY_KEY_REQUEST_PROPERTIES = new HashMap<>(); + PLAYREADY_KEY_REQUEST_PROPERTIES.put("Content-Type", "text/xml"); + PLAYREADY_KEY_REQUEST_PROPERTIES.put("SOAPAction", + "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); + } + + private final HttpDataSource.Factory dataSourceFactory; + private final String defaultUrl; + private final Map keyRequestProperties; + + /** + * @param defaultUrl The default license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + */ + public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) { + this(defaultUrl, dataSourceFactory, null); + } + + /** + * @param defaultUrl The default license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + * @param keyRequestProperties Request properties to set when making key requests, or null. + */ + public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory, + Map keyRequestProperties) { + this.dataSourceFactory = dataSourceFactory; + this.defaultUrl = defaultUrl; + this.keyRequestProperties = keyRequestProperties; + } + + @Override + public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { + String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData()); + return executePost(url, new byte[0], null); + } + + @Override + public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { + String url = request.getDefaultUrl(); + if (TextUtils.isEmpty(url)) { + url = defaultUrl; + } + Map requestProperties = new HashMap<>(); + requestProperties.put("Content-Type", "application/octet-stream"); + if (C.PLAYREADY_UUID.equals(uuid)) { + requestProperties.putAll(PLAYREADY_KEY_REQUEST_PROPERTIES); + } + if (keyRequestProperties != null) { + requestProperties.putAll(keyRequestProperties); + } + return executePost(url, request.getData(), requestProperties); + } + + private byte[] executePost(String url, byte[] data, Map requestProperties) + throws IOException { + HttpDataSource dataSource = dataSourceFactory.createDataSource(); + if (requestProperties != null) { + for (Map.Entry requestProperty : requestProperties.entrySet()) { + dataSource.setRequestProperty(requestProperty.getKey(), requestProperty.getValue()); + } + } + DataSpec dataSpec = new DataSpec(Uri.parse(url), data, 0, 0, C.LENGTH_UNSET, null, + DataSpec.FLAG_ALLOW_GZIP); + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); + try { + return Util.toByteArray(inputStream); + } finally { + inputStream.close(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/KeysExpiredException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/KeysExpiredException.java similarity index 87% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/KeysExpiredException.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/KeysExpiredException.java index 1b83c7eed90..9fb1ade97ea 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/KeysExpiredException.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/KeysExpiredException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; /** * Thrown when the drm keys loaded into an open session expire. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/MediaDrmCallback.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/MediaDrmCallback.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/MediaDrmCallback.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/MediaDrmCallback.java index dc8051dfb1d..32aae796107 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/drm/MediaDrmCallback.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/MediaDrmCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.drm; +package org.telegram.messenger.exoplayer2.drm; -import android.annotation.TargetApi; -import org.telegram.messenger.exoplayer.drm.ExoMediaDrm.KeyRequest; -import org.telegram.messenger.exoplayer.drm.ExoMediaDrm.ProvisionRequest; +import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import java.util.UUID; /** * Performs {@link ExoMediaDrm} key and provisioning requests. */ -@TargetApi(18) public interface MediaDrmCallback { /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/StreamingDrmSessionManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/StreamingDrmSessionManager.java new file mode 100755 index 00000000000..8f85b9ae764 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/StreamingDrmSessionManager.java @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.drm; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.DeniedByServerException; +import android.media.MediaDrm; +import android.media.NotProvisionedException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.OnEventListener; +import org.telegram.messenger.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import org.telegram.messenger.exoplayer2.extractor.mp4.PsshAtomUtil; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.HashMap; +import java.util.UUID; + +/** + * A {@link DrmSessionManager} that supports streaming playbacks using {@link MediaDrm}. + */ +@TargetApi(18) +public class StreamingDrmSessionManager implements DrmSessionManager, + DrmSession { + + /** + * Listener of {@link StreamingDrmSessionManager} events. + */ + public interface EventListener { + + /** + * Called each time keys are loaded. + */ + void onDrmKeysLoaded(); + + /** + * Called when a drm error occurs. + * + * @param e The corresponding exception. + */ + void onDrmSessionManagerError(Exception e); + + } + + /** + * The key to use when passing CustomData to a PlayReady instance in an optional parameter map. + */ + public static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData"; + + private static final int MSG_PROVISION = 0; + private static final int MSG_KEYS = 1; + + private final Handler eventHandler; + private final EventListener eventListener; + private final ExoMediaDrm mediaDrm; + private final HashMap optionalKeyRequestParameters; + + /* package */ final MediaDrmCallback callback; + /* package */ final UUID uuid; + + /* package */ MediaDrmHandler mediaDrmHandler; + /* package */ PostResponseHandler postResponseHandler; + + private Looper playbackLooper; + private HandlerThread requestHandlerThread; + private Handler postRequestHandler; + + private int openCount; + private boolean provisioningInProgress; + @DrmSession.State + private int state; + private T mediaCrypto; + private Exception lastException; + private SchemeData schemeData; + private byte[] sessionId; + + /** + * Instantiates a new instance using the Widevine scheme. + * + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static StreamingDrmSessionManager newWidevineInstance( + MediaDrmCallback callback, HashMap optionalKeyRequestParameters, + Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { + return newFrameworkInstance(C.WIDEVINE_UUID, callback, optionalKeyRequestParameters, + eventHandler, eventListener); + } + + /** + * Instantiates a new instance using the PlayReady scheme. + *

        + * Note that PlayReady is unsupported by most Android devices, with the exception of Android TV + * devices, which do provide support. + * + * @param callback Performs key and provisioning requests. + * @param customData Optional custom data to include in requests generated by the instance. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static StreamingDrmSessionManager newPlayReadyInstance( + MediaDrmCallback callback, String customData, Handler eventHandler, + EventListener eventListener) throws UnsupportedDrmException { + HashMap optionalKeyRequestParameters; + if (!TextUtils.isEmpty(customData)) { + optionalKeyRequestParameters = new HashMap<>(); + optionalKeyRequestParameters.put(PLAYREADY_CUSTOM_DATA_KEY, customData); + } else { + optionalKeyRequestParameters = null; + } + return newFrameworkInstance(C.PLAYREADY_UUID, callback, optionalKeyRequestParameters, + eventHandler, eventListener); + } + + /** + * Instantiates a new instance. + * + * @param uuid The UUID of the drm scheme. + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @throws UnsupportedDrmException If the specified DRM scheme is not supported. + */ + public static StreamingDrmSessionManager newFrameworkInstance( + UUID uuid, MediaDrmCallback callback, HashMap optionalKeyRequestParameters, + Handler eventHandler, EventListener eventListener) throws UnsupportedDrmException { + return new StreamingDrmSessionManager<>(uuid, FrameworkMediaDrm.newInstance(uuid), callback, + optionalKeyRequestParameters, eventHandler, eventListener); + } + + /** + * @param uuid The UUID of the drm scheme. + * @param mediaDrm An underlying {@link ExoMediaDrm} for use by the manager. + * @param callback Performs key and provisioning requests. + * @param optionalKeyRequestParameters An optional map of parameters to pass as the last argument + * to {@link MediaDrm#getKeyRequest(byte[], byte[], String, int, HashMap)}. May be null. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public StreamingDrmSessionManager(UUID uuid, ExoMediaDrm mediaDrm, MediaDrmCallback callback, + HashMap optionalKeyRequestParameters, Handler eventHandler, + EventListener eventListener) { + this.uuid = uuid; + this.mediaDrm = mediaDrm; + this.callback = callback; + this.optionalKeyRequestParameters = optionalKeyRequestParameters; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + mediaDrm.setOnEventListener(new MediaDrmEventListener()); + state = STATE_CLOSED; + } + + /** + * Provides access to {@link MediaDrm#getPropertyString(String)}. + *

        + * This method may be called when the manager is in any state. + * + * @param key The key to request. + * @return The retrieved property. + */ + public final String getPropertyString(String key) { + return mediaDrm.getPropertyString(key); + } + + /** + * Provides access to {@link MediaDrm#setPropertyString(String, String)}. + *

        + * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyString(String key, String value) { + mediaDrm.setPropertyString(key, value); + } + + /** + * Provides access to {@link MediaDrm#getPropertyByteArray(String)}. + *

        + * This method may be called when the manager is in any state. + * + * @param key The key to request. + * @return The retrieved property. + */ + public final byte[] getPropertyByteArray(String key) { + return mediaDrm.getPropertyByteArray(key); + } + + /** + * Provides access to {@link MediaDrm#setPropertyByteArray(String, byte[])}. + *

        + * This method may be called when the manager is in any state. + * + * @param key The property to write. + * @param value The value to write. + */ + public final void setPropertyByteArray(String key, byte[] value) { + mediaDrm.setPropertyByteArray(key, value); + } + + // DrmSessionManager implementation. + + @Override + public DrmSession acquireSession(Looper playbackLooper, DrmInitData drmInitData) { + Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper); + if (++openCount != 1) { + return this; + } + + if (this.playbackLooper == null) { + this.playbackLooper = playbackLooper; + mediaDrmHandler = new MediaDrmHandler(playbackLooper); + postResponseHandler = new PostResponseHandler(playbackLooper); + } + + requestHandlerThread = new HandlerThread("DrmRequestHandler"); + requestHandlerThread.start(); + postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper()); + + schemeData = drmInitData.get(uuid); + if (schemeData == null) { + onError(new IllegalStateException("Media does not support uuid: " + uuid)); + return this; + } + if (Util.SDK_INT < 21) { + // Prior to L the Widevine CDM required data to be extracted from the PSSH atom. + byte[] psshData = PsshAtomUtil.parseSchemeSpecificData(schemeData.data, C.WIDEVINE_UUID); + if (psshData == null) { + // Extraction failed. schemeData isn't a Widevine PSSH atom, so leave it unchanged. + } else { + schemeData = new SchemeData(C.WIDEVINE_UUID, schemeData.mimeType, psshData); + } + } + state = STATE_OPENING; + openInternal(true); + return this; + } + + @Override + public void releaseSession(DrmSession session) { + if (--openCount != 0) { + return; + } + state = STATE_CLOSED; + provisioningInProgress = false; + mediaDrmHandler.removeCallbacksAndMessages(null); + postResponseHandler.removeCallbacksAndMessages(null); + postRequestHandler.removeCallbacksAndMessages(null); + postRequestHandler = null; + requestHandlerThread.quit(); + requestHandlerThread = null; + schemeData = null; + mediaCrypto = null; + lastException = null; + if (sessionId != null) { + mediaDrm.closeSession(sessionId); + sessionId = null; + } + } + + // DrmSession implementation. + + @Override + @DrmSession.State + public final int getState() { + return state; + } + + @Override + public final T getMediaCrypto() { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + throw new IllegalStateException(); + } + return mediaCrypto; + } + + @Override + public boolean requiresSecureDecoderComponent(String mimeType) { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + throw new IllegalStateException(); + } + return mediaCrypto.requiresSecureDecoderComponent(mimeType); + } + + @Override + public final Exception getError() { + return state == STATE_ERROR ? lastException : null; + } + + // Internal methods. + + private void openInternal(boolean allowProvisioning) { + try { + sessionId = mediaDrm.openSession(); + mediaCrypto = mediaDrm.createMediaCrypto(uuid, sessionId); + state = STATE_OPENED; + postKeyRequest(); + } catch (NotProvisionedException e) { + if (allowProvisioning) { + postProvisionRequest(); + } else { + onError(e); + } + } catch (Exception e) { + onError(e); + } + } + + private void postProvisionRequest() { + if (provisioningInProgress) { + return; + } + provisioningInProgress = true; + ProvisionRequest request = mediaDrm.getProvisionRequest(); + postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); + } + + private void onProvisionResponse(Object response) { + provisioningInProgress = false; + if (state != STATE_OPENING && state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + // This event is stale. + return; + } + + if (response instanceof Exception) { + onError((Exception) response); + return; + } + + try { + mediaDrm.provideProvisionResponse((byte[]) response); + if (state == STATE_OPENING) { + openInternal(false); + } else { + postKeyRequest(); + } + } catch (DeniedByServerException e) { + onError(e); + } + } + + private void postKeyRequest() { + KeyRequest keyRequest; + try { + keyRequest = mediaDrm.getKeyRequest(sessionId, schemeData.data, schemeData.mimeType, + MediaDrm.KEY_TYPE_STREAMING, optionalKeyRequestParameters); + postRequestHandler.obtainMessage(MSG_KEYS, keyRequest).sendToTarget(); + } catch (NotProvisionedException e) { + onKeysError(e); + } + } + + private void onKeyResponse(Object response) { + if (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS) { + // This event is stale. + return; + } + + if (response instanceof Exception) { + onKeysError((Exception) response); + return; + } + + try { + mediaDrm.provideKeyResponse(sessionId, (byte[]) response); + state = STATE_OPENED_WITH_KEYS; + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmKeysLoaded(); + } + }); + } + } catch (Exception e) { + onKeysError(e); + } + } + + private void onKeysError(Exception e) { + if (e instanceof NotProvisionedException) { + postProvisionRequest(); + } else { + onError(e); + } + } + + private void onError(final Exception e) { + lastException = e; + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onDrmSessionManagerError(e); + } + }); + } + if (state != STATE_OPENED_WITH_KEYS) { + state = STATE_ERROR; + } + } + + @SuppressLint("HandlerLeak") + private class MediaDrmHandler extends Handler { + + public MediaDrmHandler(Looper looper) { + super(looper); + } + + @SuppressWarnings("deprecation") + @Override + public void handleMessage(Message msg) { + if (openCount == 0 || (state != STATE_OPENED && state != STATE_OPENED_WITH_KEYS)) { + return; + } + switch (msg.what) { + case MediaDrm.EVENT_KEY_REQUIRED: + postKeyRequest(); + break; + case MediaDrm.EVENT_KEY_EXPIRED: + state = STATE_OPENED; + onError(new KeysExpiredException()); + break; + case MediaDrm.EVENT_PROVISION_REQUIRED: + state = STATE_OPENED; + postProvisionRequest(); + break; + } + } + + } + + private class MediaDrmEventListener implements OnEventListener { + + @Override + public void onEvent(ExoMediaDrm md, byte[] sessionId, int event, int extra, + byte[] data) { + mediaDrmHandler.sendEmptyMessage(event); + } + + } + + @SuppressLint("HandlerLeak") + private class PostResponseHandler extends Handler { + + public PostResponseHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PROVISION: + onProvisionResponse(msg.obj); + break; + case MSG_KEYS: + onKeyResponse(msg.obj); + break; + } + } + + } + + @SuppressLint("HandlerLeak") + private class PostRequestHandler extends Handler { + + public PostRequestHandler(Looper backgroundLooper) { + super(backgroundLooper); + } + + @Override + public void handleMessage(Message msg) { + Object response; + try { + switch (msg.what) { + case MSG_PROVISION: + response = callback.executeProvisionRequest(uuid, (ProvisionRequest) msg.obj); + break; + case MSG_KEYS: + response = callback.executeKeyRequest(uuid, (KeyRequest) msg.obj); + break; + default: + throw new RuntimeException(); + } + } catch (Exception e) { + response = e; + } + postResponseHandler.obtainMessage(msg.what, response).sendToTarget(); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/UnsupportedDrmException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/UnsupportedDrmException.java new file mode 100755 index 00000000000..4c20d7fdceb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/drm/UnsupportedDrmException.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.drm; + +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Thrown when the requested DRM scheme is not supported. + */ +public final class UnsupportedDrmException extends Exception { + + /** + * The reason for the exception. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_UNSUPPORTED_SCHEME, REASON_INSTANTIATION_ERROR}) + public @interface Reason {} + /** + * The requested DRM scheme is unsupported by the device. + */ + public static final int REASON_UNSUPPORTED_SCHEME = 1; + /** + * There device advertises support for the requested DRM scheme, but there was an error + * instantiating it. The cause can be retrieved using {@link #getCause()}. + */ + public static final int REASON_INSTANTIATION_ERROR = 2; + + /** + * Either {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + */ + @Reason + public final int reason; + + /** + * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + */ + public UnsupportedDrmException(@Reason int reason) { + this.reason = reason; + } + + /** + * @param reason {@link #REASON_UNSUPPORTED_SCHEME} or {@link #REASON_INSTANTIATION_ERROR}. + * @param cause The cause of this exception. + */ + public UnsupportedDrmException(@Reason int reason, Exception cause) { + super(cause); + this.reason = reason; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ChunkIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ChunkIndex.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ChunkIndex.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ChunkIndex.java index 57b8c816771..6502a5fd558 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ChunkIndex.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ChunkIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor; +package org.telegram.messenger.exoplayer2.extractor; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.util.Util; /** * Defines chunks of samples within a media stream. @@ -47,6 +47,8 @@ public final class ChunkIndex implements SeekMap { */ public final long[] timesUs; + private final long durationUs; + /** * @param sizes The chunk sizes, in bytes. * @param offsets The chunk byte offsets. @@ -54,11 +56,12 @@ public final class ChunkIndex implements SeekMap { * @param timesUs The start time of each chunk, in microseconds. */ public ChunkIndex(int[] sizes, long[] offsets, long[] durationsUs, long[] timesUs) { - this.length = sizes.length; this.sizes = sizes; this.offsets = offsets; this.durationsUs = durationsUs; this.timesUs = timesUs; + length = sizes.length; + durationUs = durationsUs[length - 1] + timesUs[length - 1]; } /** @@ -78,6 +81,11 @@ public boolean isSeekable() { return true; } + @Override + public long getDurationUs() { + return durationUs; + } + @Override public long getPosition(long timeUs) { return offsets[getChunkIndex(timeUs)]; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultExtractorInput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorInput.java similarity index 94% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultExtractorInput.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorInput.java index 3e611279ec8..d495cde1746 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/DefaultExtractorInput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorInput.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor; +package org.telegram.messenger.exoplayer2.extractor; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.upstream.DataSource; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.EOFException; import java.io.IOException; import java.util.Arrays; @@ -39,7 +40,7 @@ public final class DefaultExtractorInput implements ExtractorInput { /** * @param dataSource The wrapped {@link DataSource}. * @param position The initial position in the stream. - * @param length The length of the stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown. + * @param length The length of the stream, or {@link C#LENGTH_UNSET} if it is unknown. */ public DefaultExtractorInput(DataSource dataSource, long position, long length) { this.dataSource = dataSource; @@ -124,7 +125,6 @@ public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException, InterruptedException { ensureSpaceForPeek(length); int bytesPeeked = Math.min(peekBufferLength - peekBufferPosition, length); - peekBufferLength += length - bytesPeeked; while (bytesPeeked < length) { bytesPeeked = readFromDataSource(peekBuffer, peekBufferPosition, length, bytesPeeked, allowEndOfInput); @@ -133,6 +133,7 @@ public boolean advancePeekPosition(int length, boolean allowEndOfInput) } } peekBufferPosition += length; + peekBufferLength = Math.max(peekBufferLength, peekBufferPosition); return true; } @@ -161,6 +162,13 @@ public long getLength() { return streamLength; } + @Override + public void setRetryPosition(long position, E e) throws E { + Assertions.checkArgument(position >= 0); + this.position = position; + throw e; + } + /** * Ensures {@code peekBuffer} is large enough to store at least {@code length} bytes from the * current peek position. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java new file mode 100755 index 00000000000..5709efb9e63 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import java.util.ArrayList; +import java.util.List; + +/** + * An {@link ExtractorsFactory} that provides an array of extractors for the following formats: + * + *

          + *
        • MP4, including M4A ({@link org.telegram.messenger.exoplayer2.extractor.mp4.Mp4Extractor})
        • + *
        • fMP4 ({@link org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor})
        • + *
        • Matroska and WebM ({@link org.telegram.messenger.exoplayer2.extractor.mkv.MatroskaExtractor}) + *
        • + *
        • Ogg Vorbis/FLAC ({@link org.telegram.messenger.exoplayer2.extractor.ogg.OggExtractor}
        • + *
        • MP3 ({@link org.telegram.messenger.exoplayer2.extractor.mp3.Mp3Extractor})
        • + *
        • AAC ({@link org.telegram.messenger.exoplayer2.extractor.ts.AdtsExtractor})
        • + *
        • MPEG TS ({@link org.telegram.messenger.exoplayer2.extractor.ts.TsExtractor})
        • + *
        • MPEG PS ({@link org.telegram.messenger.exoplayer2.extractor.ts.PsExtractor})
        • + *
        • FLV ({@link org.telegram.messenger.exoplayer2.extractor.flv.FlvExtractor})
        • + *
        • WAV ({@link org.telegram.messenger.exoplayer2.extractor.wav.WavExtractor})
        • + *
        • FLAC (only available if the FLAC extension is built and included)
        • + *
        + */ +public final class DefaultExtractorsFactory implements ExtractorsFactory { + + // Lazily initialized default extractor classes in priority order. + private static List> defaultExtractorClasses; + + /** + * Creates a new factory for the default extractors. + */ + public DefaultExtractorsFactory() { + synchronized (DefaultExtractorsFactory.class) { + if (defaultExtractorClasses == null) { + // Lazily initialize defaultExtractorClasses. + List> extractorClasses = new ArrayList<>(); + // We reference extractors using reflection so that they can be deleted cleanly. + // Class.forName is used so that automated tools like proguard can detect the use of + // reflection (see http://proguard.sourceforge.net/FAQ.html#forname). + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.mkv.MatroskaExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.mp4.Mp4Extractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.mp3.Mp3Extractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.ts.AdtsExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.ts.Ac3Extractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.ts.TsExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.flv.FlvExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.ogg.OggExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.ts.PsExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.extractor.wav.WavExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + try { + extractorClasses.add( + Class.forName("org.telegram.messenger.exoplayer2.ext.flac.FlacExtractor") + .asSubclass(Extractor.class)); + } catch (ClassNotFoundException e) { + // Extractor not found. + } + defaultExtractorClasses = extractorClasses; + } + } + } + + @Override + public Extractor[] createExtractors() { + Extractor[] extractors = new Extractor[defaultExtractorClasses.size()]; + for (int i = 0; i < extractors.length; i++) { + try { + extractors[i] = defaultExtractorClasses.get(i).getConstructor().newInstance(); + } catch (Exception e) { + // Should never happen. + throw new IllegalStateException("Unexpected error creating default extractor", e); + } + } + return extractors; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java new file mode 100755 index 00000000000..ec2eb222cca --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DefaultTrackOutput.java @@ -0,0 +1,937 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.upstream.Allocation; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A {@link TrackOutput} that buffers extracted samples in a queue and allows for consumption from + * that queue. + */ +public final class DefaultTrackOutput implements TrackOutput { + + /** + * A listener for changes to the upstream format. + */ + public interface UpstreamFormatChangedListener { + + /** + * Called on the loading thread when an upstream format change occurs. + * + * @param format The new upstream format. + */ + void onUpstreamFormatChanged(Format format); + + } + + private static final int INITIAL_SCRATCH_SIZE = 32; + + private static final int STATE_ENABLED = 0; + private static final int STATE_ENABLED_WRITING = 1; + private static final int STATE_DISABLED = 2; + + private final Allocator allocator; + private final int allocationLength; + + private final InfoQueue infoQueue; + private final LinkedBlockingDeque dataQueue; + private final BufferExtrasHolder extrasHolder; + private final ParsableByteArray scratch; + private final AtomicInteger state; + + // Accessed only by the consuming thread. + private long totalBytesDropped; + private Format downstreamFormat; + + // Accessed only by the loading thread (or the consuming thread when there is no loading thread). + private long sampleOffsetUs; + private long totalBytesWritten; + private Allocation lastAllocation; + private int lastAllocationOffset; + private boolean needKeyframe; + private boolean pendingSplice; + private UpstreamFormatChangedListener upstreamFormatChangeListener; + + /** + * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. + */ + public DefaultTrackOutput(Allocator allocator) { + this.allocator = allocator; + allocationLength = allocator.getIndividualAllocationLength(); + infoQueue = new InfoQueue(); + dataQueue = new LinkedBlockingDeque<>(); + extrasHolder = new BufferExtrasHolder(); + scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE); + state = new AtomicInteger(); + lastAllocationOffset = allocationLength; + needKeyframe = true; + } + + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Resets the output. + * + * @param enable Whether the output should be enabled. False if it should be disabled. + */ + public void reset(boolean enable) { + int previousState = state.getAndSet(enable ? STATE_ENABLED : STATE_DISABLED); + clearSampleData(); + infoQueue.resetLargestParsedTimestamps(); + if (previousState == STATE_DISABLED) { + downstreamFormat = null; + } + } + + /** + * Sets a source identifier for subsequent samples. + * + * @param sourceId The source identifier. + */ + public void sourceId(int sourceId) { + infoQueue.sourceId(sourceId); + } + + /** + * Indicates that samples subsequently queued to the buffer should be spliced into those already + * queued. + */ + public void splice() { + pendingSplice = true; + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return infoQueue.getWriteIndex(); + } + + /** + * Discards samples from the write side of the buffer. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. + */ + public void discardUpstreamSamples(int discardFromIndex) { + totalBytesWritten = infoQueue.discardUpstreamSamples(discardFromIndex); + dropUpstreamFrom(totalBytesWritten); + } + + /** + * Discards data from the write side of the buffer. Data is discarded from the specified absolute + * position. Any allocations that are fully discarded are returned to the allocator. + * + * @param absolutePosition The absolute position (inclusive) from which to discard data. + */ + private void dropUpstreamFrom(long absolutePosition) { + int relativePosition = (int) (absolutePosition - totalBytesDropped); + // Calculate the index of the allocation containing the position, and the offset within it. + int allocationIndex = relativePosition / allocationLength; + int allocationOffset = relativePosition % allocationLength; + // We want to discard any allocations after the one at allocationIdnex. + int allocationDiscardCount = dataQueue.size() - allocationIndex - 1; + if (allocationOffset == 0) { + // If the allocation at allocationIndex is empty, we should discard that one too. + allocationDiscardCount++; + } + // Discard the allocations. + for (int i = 0; i < allocationDiscardCount; i++) { + allocator.release(dataQueue.removeLast()); + } + // Update lastAllocation and lastAllocationOffset to reflect the new position. + lastAllocation = dataQueue.peekLast(); + lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset; + } + + // Called by the consuming thread. + + /** + * Disables buffering of sample data and metadata. + */ + public void disable() { + if (state.getAndSet(STATE_DISABLED) == STATE_ENABLED) { + clearSampleData(); + } + } + + /** + * Returns whether the buffer is empty. + */ + public boolean isEmpty() { + return infoQueue.isEmpty(); + } + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return infoQueue.getReadIndex(); + } + + /** + * Peeks the source id of the next sample, or the current upstream source id if the buffer is + * empty. + * + * @return The source id. + */ + public int peekSourceId() { + return infoQueue.peekSourceId(); + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public Format getUpstreamFormat() { + return infoQueue.getUpstreamFormat(); + } + + /** + * Returns the largest sample timestamp that has been queued since the last {@link #reset}. + *

        + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public long getLargestQueuedTimestampUs() { + return infoQueue.getLargestQueuedTimestampUs(); + } + + /** + * Attempts to skip to the keyframe before the specified time, if it's present in the buffer. + * + * @param timeUs The seek time. + * @return Whether the skip was successful. + */ + public boolean skipToKeyframeBefore(long timeUs) { + long nextOffset = infoQueue.skipToKeyframeBefore(timeUs); + if (nextOffset == C.POSITION_UNSET) { + return false; + } + dropDownstreamTo(nextOffset); + return true; + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @param loadingFinished True if an empty queue should be considered the end of the stream. + * @param decodeOnlyUntilUs If a buffer is read, the {@link C#BUFFER_FLAG_DECODE_ONLY} flag will + * be set if the buffer's timestamp is less than this value. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean loadingFinished, + long decodeOnlyUntilUs) { + switch (infoQueue.readData(formatHolder, buffer, downstreamFormat, extrasHolder)) { + case C.RESULT_NOTHING_READ: + if (loadingFinished) { + buffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } + return C.RESULT_NOTHING_READ; + case C.RESULT_FORMAT_READ: + downstreamFormat = formatHolder.format; + return C.RESULT_FORMAT_READ; + case C.RESULT_BUFFER_READ: + if (buffer.timeUs < decodeOnlyUntilUs) { + buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); + } + // Read encryption data if the sample is encrypted. + if (buffer.isEncrypted()) { + readEncryptionData(buffer, extrasHolder); + } + // Write the sample data into the holder. + buffer.ensureSpaceForWrite(extrasHolder.size); + readData(extrasHolder.offset, buffer.data, extrasHolder.size); + // Advance the read head. + dropDownstreamTo(extrasHolder.nextOffset); + return C.RESULT_BUFFER_READ; + default: + throw new IllegalStateException(); + } + } + + /** + * Reads encryption data for the current sample. + *

        + * The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and + * {@link BufferExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The + * same value is added to {@link BufferExtrasHolder#offset}. + * + * @param buffer The buffer into which the encryption data should be written. + * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. + */ + private void readEncryptionData(DecoderInputBuffer buffer, BufferExtrasHolder extrasHolder) { + long offset = extrasHolder.offset; + + // Read the signal byte. + scratch.reset(1); + readData(offset, scratch.data, 1); + offset++; + byte signalByte = scratch.data[0]; + boolean subsampleEncryption = (signalByte & 0x80) != 0; + int ivSize = signalByte & 0x7F; + + // Read the initialization vector. + if (buffer.cryptoInfo.iv == null) { + buffer.cryptoInfo.iv = new byte[16]; + } + readData(offset, buffer.cryptoInfo.iv, ivSize); + offset += ivSize; + + // Read the subsample count, if present. + int subsampleCount; + if (subsampleEncryption) { + scratch.reset(2); + readData(offset, scratch.data, 2); + offset += 2; + subsampleCount = scratch.readUnsignedShort(); + } else { + subsampleCount = 1; + } + + // Write the clear and encrypted subsample sizes. + int[] clearDataSizes = buffer.cryptoInfo.numBytesOfClearData; + if (clearDataSizes == null || clearDataSizes.length < subsampleCount) { + clearDataSizes = new int[subsampleCount]; + } + int[] encryptedDataSizes = buffer.cryptoInfo.numBytesOfEncryptedData; + if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) { + encryptedDataSizes = new int[subsampleCount]; + } + if (subsampleEncryption) { + int subsampleDataLength = 6 * subsampleCount; + scratch.reset(subsampleDataLength); + readData(offset, scratch.data, subsampleDataLength); + offset += subsampleDataLength; + scratch.setPosition(0); + for (int i = 0; i < subsampleCount; i++) { + clearDataSizes[i] = scratch.readUnsignedShort(); + encryptedDataSizes[i] = scratch.readUnsignedIntToInt(); + } + } else { + clearDataSizes[0] = 0; + encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset); + } + + // Populate the cryptoInfo. + buffer.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes, + extrasHolder.encryptionKeyId, buffer.cryptoInfo.iv, C.CRYPTO_MODE_AES_CTR); + + // Adjust the offset and size to take into account the bytes read. + int bytesRead = (int) (offset - extrasHolder.offset); + extrasHolder.offset += bytesRead; + extrasHolder.size -= bytesRead; + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The buffer into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, ByteBuffer target, int length) { + int remaining = length; + while (remaining > 0) { + dropDownstreamTo(absolutePosition); + int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int toCopy = Math.min(remaining, allocationLength - positionInAllocation); + Allocation allocation = dataQueue.peek(); + target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy); + absolutePosition += toCopy; + remaining -= toCopy; + } + } + + /** + * Reads data from the front of the rolling buffer. + * + * @param absolutePosition The absolute position from which data should be read. + * @param target The array into which data should be written. + * @param length The number of bytes to read. + */ + private void readData(long absolutePosition, byte[] target, int length) { + int bytesRead = 0; + while (bytesRead < length) { + dropDownstreamTo(absolutePosition); + int positionInAllocation = (int) (absolutePosition - totalBytesDropped); + int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation); + Allocation allocation = dataQueue.peek(); + System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target, + bytesRead, toCopy); + absolutePosition += toCopy; + bytesRead += toCopy; + } + } + + /** + * Discard any allocations that hold data prior to the specified absolute position, returning + * them to the allocator. + * + * @param absolutePosition The absolute position up to which allocations can be discarded. + */ + private void dropDownstreamTo(long absolutePosition) { + int relativePosition = (int) (absolutePosition - totalBytesDropped); + int allocationIndex = relativePosition / allocationLength; + for (int i = 0; i < allocationIndex; i++) { + allocator.release(dataQueue.remove()); + totalBytesDropped += allocationLength; + } + } + + // Called by the loading thread. + + /** + * Sets a listener to be notified of changes to the upstream format. + * + * @param listener The listener. + */ + public void setUpstreamFormatChangeListener(UpstreamFormatChangedListener listener) { + upstreamFormatChangeListener = listener; + } + + /** + * Like {@link #format(Format)}, but with an offset that will be added to the timestamps of + * samples subsequently queued to the buffer. The offset is also used to adjust + * {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently + * passed to {@link #format(Format)}. + * + * @param format The format. + * @param sampleOffsetUs The timestamp offset in microseconds. + */ + public void formatWithOffset(Format format, long sampleOffsetUs) { + this.sampleOffsetUs = sampleOffsetUs; + format(format); + } + + @Override + public void format(Format format) { + Format adjustedFormat = getAdjustedSampleFormat(format, sampleOffsetUs); + boolean formatChanged = infoQueue.format(adjustedFormat); + if (upstreamFormatChangeListener != null && formatChanged) { + upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedFormat); + } + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + if (!startWriteOperation()) { + int bytesSkipped = input.skip(length); + if (bytesSkipped == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + return bytesSkipped; + } + try { + length = prepareForAppend(length); + int bytesAppended = input.read(lastAllocation.data, + lastAllocation.translateOffset(lastAllocationOffset), length); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + lastAllocationOffset += bytesAppended; + totalBytesWritten += bytesAppended; + return bytesAppended; + } finally { + endWriteOperation(); + } + } + + @Override + public void sampleData(ParsableByteArray buffer, int length) { + if (!startWriteOperation()) { + buffer.skipBytes(length); + return; + } + while (length > 0) { + int thisAppendLength = prepareForAppend(length); + buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset), + thisAppendLength); + lastAllocationOffset += thisAppendLength; + totalBytesWritten += thisAppendLength; + length -= thisAppendLength; + } + endWriteOperation(); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + if (!startWriteOperation()) { + infoQueue.commitSampleTimestamp(timeUs); + return; + } + try { + if (pendingSplice) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0 || !infoQueue.attemptSplice(timeUs)) { + return; + } + pendingSplice = false; + } + if (needKeyframe) { + if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) { + return; + } + needKeyframe = false; + } + timeUs += sampleOffsetUs; + long absoluteOffset = totalBytesWritten - size - offset; + infoQueue.commitSample(timeUs, flags, absoluteOffset, size, encryptionKey); + } finally { + endWriteOperation(); + } + } + + // Private methods. + + private boolean startWriteOperation() { + return state.compareAndSet(STATE_ENABLED, STATE_ENABLED_WRITING); + } + + private void endWriteOperation() { + if (!state.compareAndSet(STATE_ENABLED_WRITING, STATE_ENABLED)) { + clearSampleData(); + } + } + + private void clearSampleData() { + infoQueue.clearSampleData(); + allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()])); + dataQueue.clear(); + allocator.trim(); + totalBytesDropped = 0; + totalBytesWritten = 0; + lastAllocation = null; + lastAllocationOffset = allocationLength; + needKeyframe = true; + } + + /** + * Prepares the rolling sample buffer for an append of up to {@code length} bytes, returning the + * number of bytes that can actually be appended. + */ + private int prepareForAppend(int length) { + if (lastAllocationOffset == allocationLength) { + lastAllocationOffset = 0; + lastAllocation = allocator.allocate(); + dataQueue.add(lastAllocation); + } + return Math.min(length, allocationLength - lastAllocationOffset); + } + + /** + * Adjusts a {@link Format} to incorporate a sample offset into {@link Format#subsampleOffsetUs}. + * + * @param format The {@link Format} to adjust. + * @param sampleOffsetUs The offset to apply. + * @return The adjusted {@link Format}. + */ + private static Format getAdjustedSampleFormat(Format format, long sampleOffsetUs) { + if (format == null) { + return null; + } + if (sampleOffsetUs != 0 && format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) { + format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + sampleOffsetUs); + } + return format; + } + + /** + * Holds information about the samples in the rolling buffer. + */ + private static final class InfoQueue { + + private static final int SAMPLE_CAPACITY_INCREMENT = 1000; + + private int capacity; + + private int[] sourceIds; + private long[] offsets; + private int[] sizes; + private int[] flags; + private long[] timesUs; + private byte[][] encryptionKeys; + private Format[] formats; + + private int queueSize; + private int absoluteReadIndex; + private int relativeReadIndex; + private int relativeWriteIndex; + + private long largestDequeuedTimestampUs; + private long largestQueuedTimestampUs; + private boolean upstreamFormatRequired; + private Format upstreamFormat; + private int upstreamSourceId; + + public InfoQueue() { + capacity = SAMPLE_CAPACITY_INCREMENT; + sourceIds = new int[capacity]; + offsets = new long[capacity]; + timesUs = new long[capacity]; + flags = new int[capacity]; + sizes = new int[capacity]; + encryptionKeys = new byte[capacity][]; + formats = new Format[capacity]; + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + upstreamFormatRequired = true; + } + + public void clearSampleData() { + absoluteReadIndex = 0; + relativeReadIndex = 0; + relativeWriteIndex = 0; + queueSize = 0; + } + + // Called by the consuming thread, but only when there is no loading thread. + + public void resetLargestParsedTimestamps() { + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; + } + + /** + * Returns the current absolute write index. + */ + public int getWriteIndex() { + return absoluteReadIndex + queueSize; + } + + /** + * Discards samples from the write side of the buffer. + * + * @param discardFromIndex The absolute index of the first sample to be discarded. + * @return The reduced total number of bytes written, after the samples have been discarded. + */ + public long discardUpstreamSamples(int discardFromIndex) { + int discardCount = getWriteIndex() - discardFromIndex; + Assertions.checkArgument(0 <= discardCount && discardCount <= queueSize); + + if (discardCount == 0) { + if (absoluteReadIndex == 0) { + // queueSize == absoluteReadIndex == 0, so nothing has been written to the queue. + return 0; + } + int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; + return offsets[lastWriteIndex] + sizes[lastWriteIndex]; + } + + queueSize -= discardCount; + relativeWriteIndex = (relativeWriteIndex + capacity - discardCount) % capacity; + // Update the largest queued timestamp, assuming that the timestamps prior to a keyframe are + // always less than the timestamp of the keyframe itself, and of subsequent frames. + largestQueuedTimestampUs = Long.MIN_VALUE; + for (int i = queueSize - 1; i >= 0; i--) { + int sampleIndex = (relativeReadIndex + i) % capacity; + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timesUs[sampleIndex]); + if ((flags[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + break; + } + } + return offsets[relativeWriteIndex]; + } + + public void sourceId(int sourceId) { + upstreamSourceId = sourceId; + } + + // Called by the consuming thread. + + /** + * Returns the current absolute read index. + */ + public int getReadIndex() { + return absoluteReadIndex; + } + + /** + * Peeks the source id of the next sample, or the current upstream source id if the queue is + * empty. + */ + public int peekSourceId() { + return queueSize == 0 ? upstreamSourceId : sourceIds[relativeReadIndex]; + } + + /** + * Returns whether the queue is empty. + */ + public synchronized boolean isEmpty() { + return queueSize == 0; + } + + /** + * Returns the upstream {@link Format} in which samples are being queued. + */ + public synchronized Format getUpstreamFormat() { + return upstreamFormatRequired ? null : upstreamFormat; + } + + /** + * Returns the largest sample timestamp that has been queued since the last {@link #reset}. + *

        + * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public synchronized long getLargestQueuedTimestampUs() { + return Math.max(largestDequeuedTimestampUs, largestQueuedTimestampUs); + } + + /** + * Attempts to read from the queue. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If a sample is read then the buffer is populated with information + * about the sample, but not its data. The size and absolute position of the data in the + * rolling buffer is stored in {@code extrasHolder}, along with an encryption id if present + * and the absolute position of the first byte that may still be required after the current + * sample has been read. + * @param downstreamFormat The current downstream {@link Format}. If the format of the next + * sample is different to the current downstream format then a format will be read. + * @param extrasHolder The holder into which extra sample information should be written. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} + * or {@link C#RESULT_BUFFER_READ}. + */ + public synchronized int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, + Format downstreamFormat, BufferExtrasHolder extrasHolder) { + if (queueSize == 0) { + if (upstreamFormat != null && upstreamFormat != downstreamFormat) { + formatHolder.format = upstreamFormat; + return C.RESULT_FORMAT_READ; + } + return C.RESULT_NOTHING_READ; + } + + if (formats[relativeReadIndex] != downstreamFormat) { + formatHolder.format = formats[relativeReadIndex]; + return C.RESULT_FORMAT_READ; + } + + buffer.timeUs = timesUs[relativeReadIndex]; + buffer.setFlags(flags[relativeReadIndex]); + extrasHolder.size = sizes[relativeReadIndex]; + extrasHolder.offset = offsets[relativeReadIndex]; + extrasHolder.encryptionKeyId = encryptionKeys[relativeReadIndex]; + + largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, buffer.timeUs); + queueSize--; + relativeReadIndex++; + absoluteReadIndex++; + if (relativeReadIndex == capacity) { + // Wrap around. + relativeReadIndex = 0; + } + + extrasHolder.nextOffset = queueSize > 0 ? offsets[relativeReadIndex] + : extrasHolder.offset + extrasHolder.size; + return C.RESULT_BUFFER_READ; + } + + /** + * Attempts to locate the keyframe before the specified time, if it's present in the buffer. + * + * @param timeUs The seek time. + * @return The offset of the keyframe's data if the keyframe was present. + * {@link C#POSITION_UNSET} otherwise. + */ + public synchronized long skipToKeyframeBefore(long timeUs) { + if (queueSize == 0 || timeUs < timesUs[relativeReadIndex]) { + return C.POSITION_UNSET; + } + + int lastWriteIndex = (relativeWriteIndex == 0 ? capacity : relativeWriteIndex) - 1; + long lastTimeUs = timesUs[lastWriteIndex]; + if (timeUs > lastTimeUs) { + return C.POSITION_UNSET; + } + + // This could be optimized to use a binary search, however in practice callers to this method + // often pass times near to the start of the buffer. Hence it's unclear whether switching to + // a binary search would yield any real benefit. + int sampleCount = 0; + int sampleCountToKeyframe = -1; + int searchIndex = relativeReadIndex; + while (searchIndex != relativeWriteIndex) { + if (timesUs[searchIndex] > timeUs) { + // We've gone too far. + break; + } else if ((flags[searchIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + // We've found a keyframe, and we're still before the seek position. + sampleCountToKeyframe = sampleCount; + } + searchIndex = (searchIndex + 1) % capacity; + sampleCount++; + } + + if (sampleCountToKeyframe == -1) { + return C.POSITION_UNSET; + } + + queueSize -= sampleCountToKeyframe; + relativeReadIndex = (relativeReadIndex + sampleCountToKeyframe) % capacity; + absoluteReadIndex += sampleCountToKeyframe; + return offsets[relativeReadIndex]; + } + + // Called by the loading thread. + + public synchronized boolean format(Format format) { + if (format == null) { + upstreamFormatRequired = true; + return false; + } + upstreamFormatRequired = false; + if (Util.areEqual(format, upstreamFormat)) { + // Suppress changes between equal formats so we can use referential equality in readData. + return false; + } else { + upstreamFormat = format; + return true; + } + } + + public synchronized void commitSample(long timeUs, @C.BufferFlags int sampleFlags, long offset, + int size, byte[] encryptionKey) { + Assertions.checkState(!upstreamFormatRequired); + commitSampleTimestamp(timeUs); + timesUs[relativeWriteIndex] = timeUs; + offsets[relativeWriteIndex] = offset; + sizes[relativeWriteIndex] = size; + flags[relativeWriteIndex] = sampleFlags; + encryptionKeys[relativeWriteIndex] = encryptionKey; + formats[relativeWriteIndex] = upstreamFormat; + sourceIds[relativeWriteIndex] = upstreamSourceId; + // Increment the write index. + queueSize++; + if (queueSize == capacity) { + // Increase the capacity. + int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT; + int[] newSourceIds = new int[newCapacity]; + long[] newOffsets = new long[newCapacity]; + long[] newTimesUs = new long[newCapacity]; + int[] newFlags = new int[newCapacity]; + int[] newSizes = new int[newCapacity]; + byte[][] newEncryptionKeys = new byte[newCapacity][]; + Format[] newFormats = new Format[newCapacity]; + int beforeWrap = capacity - relativeReadIndex; + System.arraycopy(offsets, relativeReadIndex, newOffsets, 0, beforeWrap); + System.arraycopy(timesUs, relativeReadIndex, newTimesUs, 0, beforeWrap); + System.arraycopy(flags, relativeReadIndex, newFlags, 0, beforeWrap); + System.arraycopy(sizes, relativeReadIndex, newSizes, 0, beforeWrap); + System.arraycopy(encryptionKeys, relativeReadIndex, newEncryptionKeys, 0, beforeWrap); + System.arraycopy(formats, relativeReadIndex, newFormats, 0, beforeWrap); + System.arraycopy(sourceIds, relativeReadIndex, newSourceIds, 0, beforeWrap); + int afterWrap = relativeReadIndex; + System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap); + System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap); + System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap); + System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap); + System.arraycopy(encryptionKeys, 0, newEncryptionKeys, beforeWrap, afterWrap); + System.arraycopy(formats, 0, newFormats, beforeWrap, afterWrap); + System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap); + offsets = newOffsets; + timesUs = newTimesUs; + flags = newFlags; + sizes = newSizes; + encryptionKeys = newEncryptionKeys; + formats = newFormats; + sourceIds = newSourceIds; + relativeReadIndex = 0; + relativeWriteIndex = capacity; + queueSize = capacity; + capacity = newCapacity; + } else { + relativeWriteIndex++; + if (relativeWriteIndex == capacity) { + // Wrap around. + relativeWriteIndex = 0; + } + } + } + + public synchronized void commitSampleTimestamp(long timeUs) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); + } + + /** + * Attempts to discard samples from the tail of the queue to allow samples starting from the + * specified timestamp to be spliced in. + * + * @param timeUs The timestamp at which the splice occurs. + * @return Whether the splice was successful. + */ + public synchronized boolean attemptSplice(long timeUs) { + if (largestDequeuedTimestampUs >= timeUs) { + return false; + } + int retainCount = queueSize; + while (retainCount > 0 + && timesUs[(relativeReadIndex + retainCount - 1) % capacity] >= timeUs) { + retainCount--; + } + discardUpstreamSamples(absoluteReadIndex + retainCount); + return true; + } + + } + + /** + * Holds additional buffer information not held by {@link DecoderInputBuffer}. + */ + private static final class BufferExtrasHolder { + + public int size; + public long offset; + public long nextOffset; + public byte[] encryptionKeyId; + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java new file mode 100755 index 00000000000..2630ef38749 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/DummyTrackOutput.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.EOFException; +import java.io.IOException; + +/** + * A dummy {@link TrackOutput} implementation. + */ +public final class DummyTrackOutput implements TrackOutput { + + @Override + public void format(Format format) { + // Do nothing. + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + int bytesSkipped = input.skip(length); + if (bytesSkipped == C.RESULT_END_OF_INPUT) { + if (allowEndOfInput) { + return C.RESULT_END_OF_INPUT; + } + throw new EOFException(); + } + return bytesSkipped; + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + data.skipBytes(length); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + // Do nothing. + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/Extractor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java index 7e52bf75f25..615e4d0aa15 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/Extractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor; +package org.telegram.messenger.exoplayer2.extractor; -import org.telegram.messenger.exoplayer.C; +import org.telegram.messenger.exoplayer2.C; import java.io.IOException; /** - * Facilitates extraction of data from a container format. + * Extracts media data from a container format. */ public interface Extractor { @@ -28,25 +28,18 @@ public interface Extractor { * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data * continuing from the position in the stream reached by the returning call. */ - public static final int RESULT_CONTINUE = 0; + int RESULT_CONTINUE = 0; /** * Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed * to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting * from a specified position in the stream. */ - public static final int RESULT_SEEK = 1; + int RESULT_SEEK = 1; /** * Returned by {@link #read(ExtractorInput, PositionHolder)} if the end of the * {@link ExtractorInput} was reached. Equal to {@link C#RESULT_END_OF_INPUT}. */ - public static final int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; - - /** - * Initializes the extractor with an {@link ExtractorOutput}. - * - * @param output An {@link ExtractorOutput} to receive extracted data. - */ - void init(ExtractorOutput output); + int RESULT_END_OF_INPUT = C.RESULT_END_OF_INPUT; /** * Returns whether this extractor can extract samples from the {@link ExtractorInput}, which must @@ -62,6 +55,13 @@ public interface Extractor { */ boolean sniff(ExtractorInput input) throws IOException, InterruptedException; + /** + * Initializes the extractor with an {@link ExtractorOutput}. Called at most once. + * + * @param output An {@link ExtractorOutput} to receive extracted data. + */ + void init(ExtractorOutput output); + /** * Extracts data read from a provided {@link ExtractorInput}. *

        @@ -89,16 +89,16 @@ int read(ExtractorInput input, PositionHolder seekPosition) * Notifies the extractor that a seek has occurred. *

        * Following a call to this method, the {@link ExtractorInput} passed to the next invocation of - * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from a - * random access position in the stream. Valid random access positions are the start of the - * stream and positions that can be obtained from any {@link SeekMap} passed to the - * {@link ExtractorOutput}. + * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from {@code + * position} in the stream. Valid random access positions are the start of the stream and + * positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}. + * + * @param position The seek position. */ - void seek(); + void seek(long position); /** * Releases all kept resources. */ void release(); - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorInput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorInput.java similarity index 94% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorInput.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorInput.java index ec15fcf0805..da7b2031770 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ExtractorInput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorInput.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor; +package org.telegram.messenger.exoplayer2.extractor; -import org.telegram.messenger.exoplayer.C; +import org.telegram.messenger.exoplayer2.C; import java.io.EOFException; import java.io.IOException; @@ -127,7 +127,7 @@ boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput * Otherwise an {@link EOFException} is thrown. *

        * Calling {@link #resetPeekPosition()} resets the peek position to equal the current read - * position, so the caller can peek the same data again. Reading and skipping also reset the peek + * position, so the caller can peek the same data again. Reading or skipping also resets the peek * position. * * @param target A target array into which data should be written. @@ -152,7 +152,7 @@ boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput * {@code offset}. The current read position is left unchanged. *

        * Calling {@link #resetPeekPosition()} resets the peek position to equal the current read - * position, so the caller can peek the same data again. Reading or skipping also resets the peek + * position, so the caller can peek the same data again. Reading and skipping also reset the peek * position. * * @param target A target array into which data should be written. @@ -216,10 +216,21 @@ boolean advancePeekPosition(int length, boolean allowEndOfInput) long getPosition(); /** - * Returns the length of the source stream, or {@link C#LENGTH_UNBOUNDED} if it is unknown. + * Returns the length of the source stream, or {@link C#LENGTH_UNSET} if it is unknown. * - * @return The length of the source stream, or {@link C#LENGTH_UNBOUNDED}. + * @return The length of the source stream, or {@link C#LENGTH_UNSET}. */ long getLength(); + /** + * Called when reading fails and the required retry position is different from the last position. + * After setting the retry position it throws the given {@link Throwable}. + * + * @param Type of {@link Throwable} to be thrown. + * @param position The required retry position. + * @param e {@link Throwable} to be thrown. + * @throws E The given {@link Throwable} object. + */ + void setRetryPosition(long position, E e) throws E; + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorOutput.java new file mode 100755 index 00000000000..89f935dbd79 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorOutput.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +/** + * Receives stream level data extracted by an {@link Extractor}. + */ +public interface ExtractorOutput { + + /** + * Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track. + *

        + * The same {@link TrackOutput} is returned if multiple calls are made with the same + * {@code trackId}. + * + * @param trackId A track identifier. + * @return The {@link TrackOutput} for the given track identifier. + */ + TrackOutput track(int trackId); + + /** + * Called when all tracks have been identified, meaning no new {@code trackId} values will be + * passed to {@link #track(int)}. + */ + void endTracks(); + + /** + * Called when a {@link SeekMap} has been extracted from the stream. + * + * @param seekMap The extracted {@link SeekMap}. + */ + void seekMap(SeekMap seekMap); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorsFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorsFactory.java new file mode 100755 index 00000000000..a047617a70e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ExtractorsFactory.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +/** + * Factory for arrays of {@link Extractor}s. + */ +public interface ExtractorsFactory { + + /** + * Returns an array of new {@link Extractor} instances. + */ + Extractor[] createExtractors(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/GaplessInfoHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/GaplessInfoHolder.java new file mode 100755 index 00000000000..dd6dead2415 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/GaplessInfoHolder.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.id3.CommentFrame; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Holder for gapless playback information. + */ +public final class GaplessInfoHolder { + + private static final String GAPLESS_COMMENT_ID = "iTunSMPB"; + private static final Pattern GAPLESS_COMMENT_PATTERN = + Pattern.compile("^ [0-9a-fA-F]{8} ([0-9a-fA-F]{8}) ([0-9a-fA-F]{8})"); + + /** + * The number of samples to trim from the start of the decoded audio stream, or + * {@link Format#NO_VALUE} if not set. + */ + public int encoderDelay; + + /** + * The number of samples to trim from the end of the decoded audio stream, or + * {@link Format#NO_VALUE} if not set. + */ + public int encoderPadding; + + /** + * Creates a new holder for gapless playback information. + */ + public GaplessInfoHolder() { + encoderDelay = Format.NO_VALUE; + encoderPadding = Format.NO_VALUE; + } + + /** + * Populates the holder with data from an MP3 Xing header, if valid and non-zero. + * + * @param value The 24-bit value to decode. + * @return Whether the holder was populated. + */ + public boolean setFromXingHeaderValue(int value) { + int encoderDelay = value >> 12; + int encoderPadding = value & 0x0FFF; + if (encoderDelay > 0 || encoderPadding > 0) { + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + return true; + } + return false; + } + + /** + * Populates the holder with data parsed from ID3 {@link Metadata}. + * + * @param metadata The metadata from which to parse the gapless information. + * @return Whether the holder was populated. + */ + public boolean setFromMetadata(Metadata metadata) { + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof CommentFrame) { + CommentFrame commentFrame = (CommentFrame) entry; + if (setFromComment(commentFrame.description, commentFrame.text)) { + return true; + } + } + } + return false; + } + + /** + * Populates the holder with data parsed from a gapless playback comment (stored in an ID3 header + * or MPEG 4 user data), if valid and non-zero. + * + * @param name The comment's identifier. + * @param data The comment's payload data. + * @return Whether the holder was populated. + */ + private boolean setFromComment(String name, String data) { + if (!GAPLESS_COMMENT_ID.equals(name)) { + return false; + } + Matcher matcher = GAPLESS_COMMENT_PATTERN.matcher(data); + if (matcher.find()) { + try { + int encoderDelay = Integer.parseInt(matcher.group(1), 16); + int encoderPadding = Integer.parseInt(matcher.group(2), 16); + if (encoderDelay > 0 || encoderPadding > 0) { + this.encoderDelay = encoderDelay; + this.encoderPadding = encoderPadding; + return true; + } + } catch (NumberFormatException e) { + // Ignore incorrectly formatted comments. + } + } + return false; + } + + /** + * Returns whether {@link #encoderDelay} and {@link #encoderPadding} have been set. + */ + public boolean hasGaplessInfo() { + return encoderDelay != Format.NO_VALUE && encoderPadding != Format.NO_VALUE; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MpegAudioHeader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/MpegAudioHeader.java similarity index 93% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MpegAudioHeader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/MpegAudioHeader.java index af41179b6a0..146071b5736 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/MpegAudioHeader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/MpegAudioHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.MimeTypes; /** - * Representation of an MPEG audio frame header. + * An MPEG audio frame header. */ public final class MpegAudioHeader { @@ -43,32 +46,33 @@ public final class MpegAudioHeader { {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; /** - * Returns the size of the frame associated with {@code header}, or -1 if it is invalid. + * Returns the size of the frame associated with {@code header}, or {@link C#LENGTH_UNSET} if it + * is invalid. */ public static int getFrameSize(int header) { if ((header & 0xFFE00000) != 0xFFE00000) { - return -1; + return C.LENGTH_UNSET; } int version = (header >>> 19) & 3; if (version == 1) { - return -1; + return C.LENGTH_UNSET; } int layer = (header >>> 17) & 3; if (layer == 0) { - return -1; + return C.LENGTH_UNSET; } int bitrateIndex = (header >>> 12) & 15; if (bitrateIndex == 0 || bitrateIndex == 0xF) { // Disallow "free" bitrate. - return -1; + return C.LENGTH_UNSET; } int samplingRateIndex = (header >>> 10) & 3; if (samplingRateIndex == 3) { - return -1; + return C.LENGTH_UNSET; } int samplingRate = SAMPLING_RATE_V1[samplingRateIndex]; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/PositionHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/PositionHolder.java similarity index 86% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/PositionHolder.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/PositionHolder.java index e34b7d77e66..89473c1d578 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/PositionHolder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/PositionHolder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor; +package org.telegram.messenger.exoplayer2.extractor; /** * Holds a position in the stream. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/SeekMap.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/SeekMap.java new file mode 100755 index 00000000000..2144286dc4c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/SeekMap.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; + +/** + * Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream. + */ +public interface SeekMap { + + /** + * A {@link SeekMap} that does not support seeking. + */ + final class Unseekable implements SeekMap { + + private final long durationUs; + + /** + * @param durationUs The duration of the stream in microseconds, or {@link C#TIME_UNSET} if + * the duration is unknown. + */ + public Unseekable(long durationUs) { + this.durationUs = durationUs; + } + + @Override + public boolean isSeekable() { + return false; + } + + @Override + public long getDurationUs() { + return durationUs; + } + + @Override + public long getPosition(long timeUs) { + return 0; + } + + } + + /** + * Returns whether seeking is supported. + *

        + * If seeking is not supported then the only valid seek position is the start of the file, and so + * {@link #getPosition(long)} will return 0 for all input values. + * + * @return Whether seeking is supported. + */ + boolean isSeekable(); + + /** + * Returns the duration of the stream in microseconds. + * + * @return The duration of the stream in microseconds, or {@link C#TIME_UNSET} if the + * duration is unknown. + */ + long getDurationUs(); + + /** + * Maps a seek position in microseconds to a corresponding position (byte offset) in the stream + * from which data can be provided to the extractor. + * + * @param timeUs A seek position in microseconds. + * @return The corresponding position (byte offset) in the stream from which data can be provided + * to the extractor, or 0 if {@code #isSeekable()} returns false. + */ + long getPosition(long timeUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TimestampAdjuster.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TimestampAdjuster.java new file mode 100755 index 00000000000..da28c5b60ce --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TimestampAdjuster.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; + +/** + * Offsets timestamps according to an initial sample timestamp offset. MPEG-2 TS timestamps scaling + * and adjustment is supported, taking into account timestamp rollover. + */ +public final class TimestampAdjuster { + + /** + * A special {@code firstSampleTimestampUs} value indicating that presentation timestamps should + * not be offset. + */ + public static final long DO_NOT_OFFSET = Long.MAX_VALUE; + + /** + * The value one greater than the largest representable (33 bit) MPEG-2 TS presentation timestamp. + */ + private static final long MAX_PTS_PLUS_ONE = 0x200000000L; + + private final long firstSampleTimestampUs; + + private long timestampOffsetUs; + + // Volatile to allow isInitialized to be called on a different thread to adjustSampleTimestamp. + private volatile long lastSampleTimestamp; + + /** + * @param firstSampleTimestampUs The desired result of the first call to + * {@link #adjustSampleTimestamp(long)}, or {@link #DO_NOT_OFFSET} if presentation timestamps + * should not be offset. + */ + public TimestampAdjuster(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + lastSampleTimestamp = C.TIME_UNSET; + } + + /** + * Resets the instance to its initial state. + */ + public void reset() { + lastSampleTimestamp = C.TIME_UNSET; + } + + /** + * Scales and offsets an MPEG-2 TS presentation timestamp considering wraparound. + * + * @param pts The MPEG-2 TS presentation timestamp. + * @return The adjusted timestamp in microseconds. + */ + public long adjustTsTimestamp(long pts) { + if (lastSampleTimestamp != C.TIME_UNSET) { + // The wrap count for the current PTS may be closestWrapCount or (closestWrapCount - 1), + // and we need to snap to the one closest to lastSampleTimestamp. + long lastPts = usToPts(lastSampleTimestamp); + long closestWrapCount = (lastPts + (MAX_PTS_PLUS_ONE / 2)) / MAX_PTS_PLUS_ONE; + long ptsWrapBelow = pts + (MAX_PTS_PLUS_ONE * (closestWrapCount - 1)); + long ptsWrapAbove = pts + (MAX_PTS_PLUS_ONE * closestWrapCount); + pts = Math.abs(ptsWrapBelow - lastPts) < Math.abs(ptsWrapAbove - lastPts) + ? ptsWrapBelow : ptsWrapAbove; + } + return adjustSampleTimestamp(ptsToUs(pts)); + } + + /** + * Offsets a sample timestamp in microseconds. + * + * @param timeUs The timestamp of a sample to adjust. + * @return The adjusted timestamp in microseconds. + */ + public long adjustSampleTimestamp(long timeUs) { + // Record the adjusted PTS to adjust for wraparound next time. + if (lastSampleTimestamp != C.TIME_UNSET) { + lastSampleTimestamp = timeUs; + } else { + if (firstSampleTimestampUs != DO_NOT_OFFSET) { + // Calculate the timestamp offset. + timestampOffsetUs = firstSampleTimestampUs - timeUs; + } + synchronized (this) { + lastSampleTimestamp = timeUs; + // Notify threads waiting for this adjuster to be initialized. + notifyAll(); + } + } + return timeUs + timestampOffsetUs; + } + + /** + * Blocks the calling thread until this adjuster is initialized. + * + * @throws InterruptedException If the thread was interrupted. + */ + public synchronized void waitUntilInitialized() throws InterruptedException { + while (lastSampleTimestamp == C.TIME_UNSET) { + wait(); + } + } + + /** + * Converts a value in MPEG-2 timestamp units to the corresponding value in microseconds. + * + * @param pts A value in MPEG-2 timestamp units. + * @return The corresponding value in microseconds. + */ + public static long ptsToUs(long pts) { + return (pts * C.MICROS_PER_SECOND) / 90000; + } + + /** + * Converts a value in microseconds to the corresponding values in MPEG-2 timestamp units. + * + * @param us A value in microseconds. + * @return The corresponding value in MPEG-2 timestamp units. + */ + public static long usToPts(long us) { + return (us * 90000) / C.MICROS_PER_SECOND; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java new file mode 100755 index 00000000000..f286682dd33 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/TrackOutput.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.EOFException; +import java.io.IOException; + +/** + * Receives track level data extracted by an {@link Extractor}. + */ +public interface TrackOutput { + + /** + * Called when the {@link Format} of the track has been extracted from the stream. + * + * @param format The extracted {@link Format}. + */ + void format(Format format); + + /** + * Called to write sample data to the output. + * + * @param input An {@link ExtractorInput} from which to read the sample data. + * @param length The maximum length to read from the input. + * @param allowEndOfInput True if encountering the end of the input having read no data is + * allowed, and should result in {@link C#RESULT_END_OF_INPUT} being returned. False if it + * should be considered an error, causing an {@link EOFException} to be thrown. + * @return The number of bytes appended. + * @throws IOException If an error occurred reading from the input. + * @throws InterruptedException If the thread was interrupted. + */ + int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException; + + /** + * Called to write sample data to the output. + * + * @param data A {@link ParsableByteArray} from which to read the sample data. + * @param length The number of bytes to read. + */ + void sampleData(ParsableByteArray data, int length); + + /** + * Called when metadata associated with a sample has been extracted from the stream. + *

        + * The corresponding sample data will have already been passed to the output via calls to + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)}. + * + * @param timeUs The media timestamp associated with the sample, in microseconds. + * @param flags Flags associated with the sample. See {@code C.BUFFER_FLAG_*}. + * @param size The size of the sample data, in bytes. + * @param offset The number of bytes that have been passed to + * {@link #sampleData(ExtractorInput, int, boolean)} or + * {@link #sampleData(ParsableByteArray, int)} since the last byte belonging to the sample + * whose metadata is being passed. + * @param encryptionKey The encryption key associated with the sample. May be null. + */ + void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/AudioTagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/AudioTagPayloadReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java index 30d88274f91..8f764a7db2f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/AudioTagPayloadReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.flv; +package org.telegram.messenger.exoplayer2.extractor.flv; import android.util.Pair; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.CodecSpecificDataUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.CodecSpecificDataUtil; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.util.Collections; /** @@ -84,16 +84,16 @@ protected void parsePayload(ParsableByteArray data, long timeUs) { data.readBytes(audioSpecifiConfig, 0, audioSpecifiConfig.length); Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecifiConfig); - MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, getDurationUs(), audioParams.second, - audioParams.first, Collections.singletonList(audioSpecifiConfig), null); - output.format(mediaFormat); + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecifiConfig), null, 0, null); + output.format(format); hasOutputFormat = true; } else if (packetType == AAC_PACKET_TYPE_AAC_RAW) { // Sample audio AAC frames int bytesToWrite = data.bytesLeft(); output.sampleData(data, bytesToWrite); - output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, bytesToWrite, 0, null); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, bytesToWrite, 0, null); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/FlvExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/FlvExtractor.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/FlvExtractor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/FlvExtractor.java index 0ba438453dc..0884b324cd8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/FlvExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/FlvExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.flv; - -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +package org.telegram.messenger.exoplayer2.extractor.flv; + +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; /** @@ -30,6 +30,18 @@ */ public final class FlvExtractor implements Extractor, SeekMap { + /** + * Factory for {@link FlvExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new FlvExtractor()}; + } + + }; + // Header sizes. private static final int FLV_HEADER_SIZE = 9; private static final int FLV_TAG_HEADER_SIZE = 11; @@ -114,7 +126,7 @@ public void init(ExtractorOutput output) { } @Override - public void seek() { + public void seek(long position) { parserState = STATE_READING_FLV_HEADER; bytesToNextTagHeader = 0; } @@ -241,14 +253,6 @@ private boolean readTagData(ExtractorInput input) throws IOException, Interrupte videoReader.consume(prepareTagData(input), tagTimestampUs); } else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) { metadataReader.consume(prepareTagData(input), tagTimestampUs); - if (metadataReader.getDurationUs() != C.UNKNOWN_TIME_US) { - if (audioReader != null) { - audioReader.setDurationUs(metadataReader.getDurationUs()); - } - if (videoReader != null) { - videoReader.setDurationUs(metadataReader.getDurationUs()); - } - } } else { input.skipFully(tagDataSize); wasConsumed = false; @@ -277,6 +281,11 @@ public boolean isSeekable() { return false; } + @Override + public long getDurationUs() { + return metadataReader.getDurationUs(); + } + @Override public long getPosition(long timeUs) { return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/ScriptTagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/ScriptTagPayloadReader.java similarity index 91% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/ScriptTagPayloadReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/ScriptTagPayloadReader.java index dca4d4ad329..6cb9c130705 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/flv/ScriptTagPayloadReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/ScriptTagPayloadReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.flv; +package org.telegram.messenger.exoplayer2.extractor.flv; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -42,11 +42,18 @@ private static final int AMF_TYPE_STRICT_ARRAY = 10; private static final int AMF_TYPE_DATE = 11; + private long durationUs; + /** * @param output A {@link TrackOutput} to which samples should be written. */ public ScriptTagPayloadReader(TrackOutput output) { super(output); + durationUs = C.TIME_UNSET; + } + + public long getDurationUs() { + return durationUs; } @Override @@ -81,7 +88,7 @@ protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserEx if (metadata.containsKey(KEY_DURATION)) { double durationSeconds = (double) metadata.get(KEY_DURATION); if (durationSeconds > 0.0) { - setDurationUs((long) (durationSeconds * C.MICROS_PER_SECOND)); + durationUs = (long) (durationSeconds * C.MICROS_PER_SECOND); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/TagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/TagPayloadReader.java new file mode 100755 index 00000000000..98dca6e4946 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/TagPayloadReader.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.flv; + +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Extracts individual samples from FLV tags, preserving original order. + */ +/* package */ abstract class TagPayloadReader { + + /** + * Thrown when the format is not supported. + */ + public static final class UnsupportedFormatException extends ParserException { + + public UnsupportedFormatException(String msg) { + super(msg); + } + + } + + protected final TrackOutput output; + + /** + * @param output A {@link TrackOutput} to which samples should be written. + */ + protected TagPayloadReader(TrackOutput output) { + this.output = output; + } + + /** + * Notifies the reader that a seek has occurred. + *

        + * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray, long)} will not be a continuation of the data that + * was previously passed. Hence the reader should reset any internal state. + */ + public abstract void seek(); + + /** + * Consumes payload data. + * + * @param data The payload data to consume. + * @param timeUs The timestamp associated with the payload. + * @throws ParserException If an error occurs parsing the data. + */ + public final void consume(ParsableByteArray data, long timeUs) throws ParserException { + if (parseHeader(data)) { + parsePayload(data, timeUs); + } + } + + /** + * Parses tag header. + * + * @param data Buffer where the tag header is stored. + * @return Whether the header was parsed successfully. + * @throws ParserException If an error occurs parsing the header. + */ + protected abstract boolean parseHeader(ParsableByteArray data) throws ParserException; + + /** + * Parses tag payload. + * + * @param data Buffer where tag payload is stored + * @param timeUs Time position of the frame + * @throws ParserException If an error occurs parsing the payload. + */ + protected abstract void parsePayload(ParsableByteArray data, long timeUs) throws ParserException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/VideoTagPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/VideoTagPayloadReader.java new file mode 100755 index 00000000000..da931de90aa --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/flv/VideoTagPayloadReader.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.flv; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.video.AvcConfig; + +/** + * Parses video tags from an FLV stream and extracts H.264 nal units. + */ +/* package */ final class VideoTagPayloadReader extends TagPayloadReader { + + // Video codec. + private static final int VIDEO_CODEC_AVC = 7; + + // Frame types. + private static final int VIDEO_FRAME_KEYFRAME = 1; + private static final int VIDEO_FRAME_VIDEO_INFO = 5; + + // Packet types. + private static final int AVC_PACKET_TYPE_SEQUENCE_HEADER = 0; + private static final int AVC_PACKET_TYPE_AVC_NALU = 1; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalLength; + private int nalUnitLengthFieldLength; + + // State variables. + private boolean hasOutputFormat; + private int frameType; + + /** + * @param output A {@link TrackOutput} to which samples should be written. + */ + public VideoTagPayloadReader(TrackOutput output) { + super(output); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalLength = new ParsableByteArray(4); + } + + @Override + public void seek() { + // Do nothing. + } + + @Override + protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException { + int header = data.readUnsignedByte(); + int frameType = (header >> 4) & 0x0F; + int videoCodec = (header & 0x0F); + // Support just H.264 encoded content. + if (videoCodec != VIDEO_CODEC_AVC) { + throw new UnsupportedFormatException("Video format not supported: " + videoCodec); + } + this.frameType = frameType; + return (frameType != VIDEO_FRAME_VIDEO_INFO); + } + + @Override + protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException { + int packetType = data.readUnsignedByte(); + int compositionTimeMs = data.readUnsignedInt24(); + timeUs += compositionTimeMs * 1000L; + // Parse avc sequence header in case this was not done before. + if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) { + ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]); + data.readBytes(videoSequence.data, 0, data.bytesLeft()); + AvcConfig avcConfig = AvcConfig.parse(videoSequence); + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + // Construct and output the format. + Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null, + Format.NO_VALUE, Format.NO_VALUE, avcConfig.width, avcConfig.height, Format.NO_VALUE, + avcConfig.initializationData, Format.NO_VALUE, avcConfig.pixelWidthAspectRatio, null); + output.format(format); + hasOutputFormat = true; + } else if (packetType == AVC_PACKET_TYPE_AVC_NALU) { + // TODO: Deduplicate with Mp4Extractor. + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalLengthData = nalLength.data; + nalLengthData[0] = 0; + nalLengthData[1] = 0; + nalLengthData[2] = 0; + int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + int bytesWritten = 0; + int bytesToWrite; + while (data.bytesLeft() > 0) { + // Read the NAL length so that we know where we find the next one. + data.readBytes(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength); + nalLength.setPosition(0); + bytesToWrite = nalLength.readUnsignedIntToInt(); + + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + output.sampleData(nalStartCode, 4); + bytesWritten += 4; + + // Write the payload of the NAL unit. + output.sampleData(data, bytesToWrite); + bytesWritten += bytesToWrite; + } + output.sampleMetadata(timeUs, frameType == VIDEO_FRAME_KEYFRAME ? C.BUFFER_FLAG_KEY_FRAME : 0, + bytesWritten, 0, null); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/DefaultEbmlReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/DefaultEbmlReader.java similarity index 94% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/DefaultEbmlReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/DefaultEbmlReader.java index d3f332f93e5..c18bb3e1b20 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/DefaultEbmlReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/DefaultEbmlReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.webm; +package org.telegram.messenger.exoplayer2.extractor.mkv; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.EOFException; import java.io.IOException; import java.util.Stack; @@ -144,13 +144,13 @@ public boolean read(ExtractorInput input) throws IOException, InterruptedExcepti * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ - private long maybeResyncToNextLevel1Element(ExtractorInput input) throws EOFException, - IOException, InterruptedException { + private long maybeResyncToNextLevel1Element(ExtractorInput input) throws IOException, + InterruptedException { input.resetPeekPosition(); while (true) { input.peekFully(scratch, 0, MAX_ID_BYTES); int varintLength = VarintReader.parseUnsignedVarintLength(scratch[0]); - if (varintLength != -1 && varintLength <= MAX_ID_BYTES) { + if (varintLength != C.LENGTH_UNSET && varintLength <= MAX_ID_BYTES) { int potentialId = (int) VarintReader.assembleVarint(scratch, varintLength, false); if (output.isLevel1Element(potentialId)) { input.skipFully(varintLength); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReader.java similarity index 75% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReader.java index 78c6bd39ebe..2976f5113e3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.webm; +package org.telegram.messenger.exoplayer2.extractor.mkv; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; import java.io.IOException; /** @@ -31,34 +31,34 @@ /** * Type for unknown elements. */ - public static final int TYPE_UNKNOWN = 0; + int TYPE_UNKNOWN = 0; /** * Type for elements that contain child elements. */ - public static final int TYPE_MASTER = 1; + int TYPE_MASTER = 1; /** * Type for integer value elements of up to 8 bytes. */ - public static final int TYPE_UNSIGNED_INT = 2; + int TYPE_UNSIGNED_INT = 2; /** * Type for string elements. */ - public static final int TYPE_STRING = 3; + int TYPE_STRING = 3; /** * Type for binary elements. */ - public static final int TYPE_BINARY = 4; + int TYPE_BINARY = 4; /** * Type for IEEE floating point value elements of either 4 or 8 bytes. */ - public static final int TYPE_FLOAT = 5; + int TYPE_FLOAT = 5; /** * Initializes the extractor with an {@link EbmlReaderOutput}. * * @param output An {@link EbmlReaderOutput} to receive events. */ - public void init(EbmlReaderOutput output); + void init(EbmlReaderOutput output); /** * Resets the state of the reader. @@ -66,7 +66,7 @@ * Subsequent calls to {@link #read(ExtractorInput)} will start reading a new EBML structure * from scratch. */ - public void reset(); + void reset(); /** * Reads from an {@link ExtractorInput}, invoking an event callback if possible. @@ -77,7 +77,6 @@ * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ - public boolean read(ExtractorInput input) throws ParserException, IOException, - InterruptedException; + boolean read(ExtractorInput input) throws IOException, InterruptedException; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReaderOutput.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReaderOutput.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReaderOutput.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReaderOutput.java index a7302c915a5..943800e18cc 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/EbmlReaderOutput.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/EbmlReaderOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.webm; +package org.telegram.messenger.exoplayer2.extractor.mkv; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; import java.io.IOException; /** @@ -39,7 +39,7 @@ * Checks if the given id is that of a level 1 element. * * @param id The element ID. - * @return True the given id is that of a level 1 element. false otherwise. + * @return Whether the given id is that of a level 1 element. */ boolean isLevel1Element(int id); @@ -101,7 +101,7 @@ * Implementations are required to consume the whole remainder of the element, which is * {@code contentSize} bytes in length, before returning. Implementations are permitted to fail * (by throwing an exception) having partially consumed the data, however if they do this, they - * must consume the remainder of the content when invoked again. + * must consume the remainder of the content when called again. * * @param id The element ID. * @param contentsSize The element's content size. @@ -111,6 +111,6 @@ * @throws InterruptedException If the thread is interrupted. */ void binaryElement(int id, int contentsSize, ExtractorInput input) - throws ParserException, IOException, InterruptedException; + throws IOException, InterruptedException; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java new file mode 100755 index 00000000000..3e096d673fe --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -0,0 +1,1611 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mkv; + +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.extractor.ChunkIndex; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.LongArray; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import org.telegram.messenger.exoplayer2.video.AvcConfig; +import org.telegram.messenger.exoplayer2.video.HevcConfig; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +/** + * Extracts data from a Matroska or WebM file. + */ +public final class MatroskaExtractor implements Extractor { + + /** + * Factory for {@link MatroskaExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new MatroskaExtractor()}; + } + + }; + + private static final int UNSET_ENTRY_ID = -1; + + private static final int BLOCK_STATE_START = 0; + private static final int BLOCK_STATE_HEADER = 1; + private static final int BLOCK_STATE_DATA = 2; + + private static final String DOC_TYPE_MATROSKA = "matroska"; + private static final String DOC_TYPE_WEBM = "webm"; + private static final String CODEC_ID_VP8 = "V_VP8"; + private static final String CODEC_ID_VP9 = "V_VP9"; + private static final String CODEC_ID_MPEG2 = "V_MPEG2"; + private static final String CODEC_ID_MPEG4_SP = "V_MPEG4/ISO/SP"; + private static final String CODEC_ID_MPEG4_ASP = "V_MPEG4/ISO/ASP"; + private static final String CODEC_ID_MPEG4_AP = "V_MPEG4/ISO/AP"; + private static final String CODEC_ID_H264 = "V_MPEG4/ISO/AVC"; + private static final String CODEC_ID_H265 = "V_MPEGH/ISO/HEVC"; + private static final String CODEC_ID_FOURCC = "V_MS/VFW/FOURCC"; + private static final String CODEC_ID_THEORA = "V_THEORA"; + private static final String CODEC_ID_VORBIS = "A_VORBIS"; + private static final String CODEC_ID_OPUS = "A_OPUS"; + private static final String CODEC_ID_AAC = "A_AAC"; + private static final String CODEC_ID_MP3 = "A_MPEG/L3"; + private static final String CODEC_ID_AC3 = "A_AC3"; + private static final String CODEC_ID_E_AC3 = "A_EAC3"; + private static final String CODEC_ID_TRUEHD = "A_TRUEHD"; + private static final String CODEC_ID_DTS = "A_DTS"; + private static final String CODEC_ID_DTS_EXPRESS = "A_DTS/EXPRESS"; + private static final String CODEC_ID_DTS_LOSSLESS = "A_DTS/LOSSLESS"; + private static final String CODEC_ID_FLAC = "A_FLAC"; + private static final String CODEC_ID_ACM = "A_MS/ACM"; + private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT"; + private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8"; + private static final String CODEC_ID_VOBSUB = "S_VOBSUB"; + private static final String CODEC_ID_PGS = "S_HDMV/PGS"; + + private static final int VORBIS_MAX_INPUT_SIZE = 8192; + private static final int OPUS_MAX_INPUT_SIZE = 5760; + private static final int MP3_MAX_INPUT_SIZE = 4096; + private static final int ENCRYPTION_IV_SIZE = 8; + private static final int TRACK_TYPE_AUDIO = 2; + + private static final int ID_EBML = 0x1A45DFA3; + private static final int ID_EBML_READ_VERSION = 0x42F7; + private static final int ID_DOC_TYPE = 0x4282; + private static final int ID_DOC_TYPE_READ_VERSION = 0x4285; + private static final int ID_SEGMENT = 0x18538067; + private static final int ID_SEGMENT_INFO = 0x1549A966; + private static final int ID_SEEK_HEAD = 0x114D9B74; + private static final int ID_SEEK = 0x4DBB; + private static final int ID_SEEK_ID = 0x53AB; + private static final int ID_SEEK_POSITION = 0x53AC; + private static final int ID_INFO = 0x1549A966; + private static final int ID_TIMECODE_SCALE = 0x2AD7B1; + private static final int ID_DURATION = 0x4489; + private static final int ID_CLUSTER = 0x1F43B675; + private static final int ID_TIME_CODE = 0xE7; + private static final int ID_SIMPLE_BLOCK = 0xA3; + private static final int ID_BLOCK_GROUP = 0xA0; + private static final int ID_BLOCK = 0xA1; + private static final int ID_BLOCK_DURATION = 0x9B; + private static final int ID_REFERENCE_BLOCK = 0xFB; + private static final int ID_TRACKS = 0x1654AE6B; + private static final int ID_TRACK_ENTRY = 0xAE; + private static final int ID_TRACK_NUMBER = 0xD7; + private static final int ID_TRACK_TYPE = 0x83; + private static final int ID_FLAG_DEFAULT = 0x88; + private static final int ID_FLAG_FORCED = 0x55AA; + private static final int ID_DEFAULT_DURATION = 0x23E383; + private static final int ID_CODEC_ID = 0x86; + private static final int ID_CODEC_PRIVATE = 0x63A2; + private static final int ID_CODEC_DELAY = 0x56AA; + private static final int ID_SEEK_PRE_ROLL = 0x56BB; + private static final int ID_VIDEO = 0xE0; + private static final int ID_PIXEL_WIDTH = 0xB0; + private static final int ID_PIXEL_HEIGHT = 0xBA; + private static final int ID_DISPLAY_WIDTH = 0x54B0; + private static final int ID_DISPLAY_HEIGHT = 0x54BA; + private static final int ID_DISPLAY_UNIT = 0x54B2; + private static final int ID_AUDIO = 0xE1; + private static final int ID_CHANNELS = 0x9F; + private static final int ID_AUDIO_BIT_DEPTH = 0x6264; + private static final int ID_SAMPLING_FREQUENCY = 0xB5; + private static final int ID_CONTENT_ENCODINGS = 0x6D80; + private static final int ID_CONTENT_ENCODING = 0x6240; + private static final int ID_CONTENT_ENCODING_ORDER = 0x5031; + private static final int ID_CONTENT_ENCODING_SCOPE = 0x5032; + private static final int ID_CONTENT_COMPRESSION = 0x5034; + private static final int ID_CONTENT_COMPRESSION_ALGORITHM = 0x4254; + private static final int ID_CONTENT_COMPRESSION_SETTINGS = 0x4255; + private static final int ID_CONTENT_ENCRYPTION = 0x5035; + private static final int ID_CONTENT_ENCRYPTION_ALGORITHM = 0x47E1; + private static final int ID_CONTENT_ENCRYPTION_KEY_ID = 0x47E2; + private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS = 0x47E7; + private static final int ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE = 0x47E8; + private static final int ID_CUES = 0x1C53BB6B; + private static final int ID_CUE_POINT = 0xBB; + private static final int ID_CUE_TIME = 0xB3; + private static final int ID_CUE_TRACK_POSITIONS = 0xB7; + private static final int ID_CUE_CLUSTER_POSITION = 0xF1; + private static final int ID_LANGUAGE = 0x22B59C; + private static final int ID_PROJECTION = 0x7670; + private static final int ID_PROJECTION_PRIVATE = 0x7672; + private static final int ID_STEREO_MODE = 0x53B8; + + private static final int LACING_NONE = 0; + private static final int LACING_XIPH = 1; + private static final int LACING_FIXED_SIZE = 2; + private static final int LACING_EBML = 3; + + private static final int FOURCC_COMPRESSION_VC1 = 0x31435657; + + /** + * A template for the prefix that must be added to each subrip sample. The 12 byte end timecode + * starting at {@link #SUBRIP_PREFIX_END_TIMECODE_OFFSET} is set to a dummy value, and must be + * replaced with the duration of the subtitle. + *

        + * Equivalent to the UTF-8 string: "1\n00:00:00,000 --> 00:00:00,000\n". + */ + private static final byte[] SUBRIP_PREFIX = new byte[] {49, 10, 48, 48, 58, 48, 48, 58, 48, 48, + 44, 48, 48, 48, 32, 45, 45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 10}; + /** + * A special end timecode indicating that a subtitle should be displayed until the next subtitle, + * or until the end of the media in the case of the last subtitle. + *

        + * Equivalent to the UTF-8 string: " ". + */ + private static final byte[] SUBRIP_TIMECODE_EMPTY = + new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}; + /** + * The byte offset of the end timecode in {@link #SUBRIP_PREFIX}. + */ + private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19; + /** + * The length in bytes of a timecode in a subrip prefix. + */ + private static final int SUBRIP_TIMECODE_LENGTH = 12; + + /** + * The length in bytes of a WAVEFORMATEX structure. + */ + private static final int WAVE_FORMAT_SIZE = 18; + /** + * Format tag indicating a WAVEFORMATEXTENSIBLE structure. + */ + private static final int WAVE_FORMAT_EXTENSIBLE = 0xFFFE; + /** + * Format tag for PCM. + */ + private static final int WAVE_FORMAT_PCM = 1; + /** + * Sub format for PCM. + */ + private static final UUID WAVE_SUBFORMAT_PCM = new UUID(0x0100000000001000L, 0x800000AA00389B71L); + + private final EbmlReader reader; + private final VarintReader varintReader; + private final SparseArray tracks; + + // Temporary arrays. + private final ParsableByteArray nalStartCode; + private final ParsableByteArray nalLength; + private final ParsableByteArray scratch; + private final ParsableByteArray vorbisNumPageSamples; + private final ParsableByteArray seekEntryIdBytes; + private final ParsableByteArray sampleStrippedBytes; + private final ParsableByteArray subripSample; + private final ParsableByteArray encryptionInitializationVector; + private final ParsableByteArray encryptionSubsampleData; + private ByteBuffer encryptionSubsampleDataBuffer; + + private long segmentContentSize; + private long segmentContentPosition = C.POSITION_UNSET; + private long timecodeScale = C.TIME_UNSET; + private long durationTimecode = C.TIME_UNSET; + private long durationUs = C.TIME_UNSET; + + // The track corresponding to the current TrackEntry element, or null. + private Track currentTrack; + + // Whether a seek map has been sent to the output. + private boolean sentSeekMap; + + // Master seek entry related elements. + private int seekEntryId; + private long seekEntryPosition; + + // Cue related elements. + private boolean seekForCues; + private long cuesContentPosition = C.POSITION_UNSET; + private long seekPositionAfterBuildingCues = C.POSITION_UNSET; + private long clusterTimecodeUs = C.TIME_UNSET; + private LongArray cueTimesUs; + private LongArray cueClusterPositions; + private boolean seenClusterPositionForCurrentCuePoint; + + // Block reading state. + private int blockState; + private long blockTimeUs; + private long blockDurationUs; + private int blockLacingSampleIndex; + private int blockLacingSampleCount; + private int[] blockLacingSampleSizes; + private int blockTrackNumber; + private int blockTrackNumberLength; + @C.BufferFlags + private int blockFlags; + + // Sample reading state. + private int sampleBytesRead; + private boolean sampleEncodingHandled; + private boolean sampleSignalByteRead; + private boolean sampleInitializationVectorRead; + private boolean samplePartitionCountRead; + private byte sampleSignalByte; + private int samplePartitionCount; + private int sampleCurrentNalBytesRemaining; + private int sampleBytesWritten; + private boolean sampleRead; + private boolean sampleSeenReferenceBlock; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + + public MatroskaExtractor() { + this(new DefaultEbmlReader()); + } + + /* package */ MatroskaExtractor(EbmlReader reader) { + this.reader = reader; + this.reader.init(new InnerEbmlReaderOutput()); + varintReader = new VarintReader(); + tracks = new SparseArray<>(); + scratch = new ParsableByteArray(4); + vorbisNumPageSamples = new ParsableByteArray(ByteBuffer.allocate(4).putInt(-1).array()); + seekEntryIdBytes = new ParsableByteArray(4); + nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); + nalLength = new ParsableByteArray(4); + sampleStrippedBytes = new ParsableByteArray(); + subripSample = new ParsableByteArray(); + encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE); + encryptionSubsampleData = new ParsableByteArray(); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return new Sniffer().sniff(input); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + } + + @Override + public void seek(long position) { + clusterTimecodeUs = C.TIME_UNSET; + blockState = BLOCK_STATE_START; + reader.reset(); + varintReader.reset(); + resetSample(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, + InterruptedException { + sampleRead = false; + boolean continueReading = true; + while (continueReading && !sampleRead) { + continueReading = reader.read(input); + if (continueReading && maybeSeekForCues(seekPosition, input.getPosition())) { + return Extractor.RESULT_SEEK; + } + } + return continueReading ? Extractor.RESULT_CONTINUE : Extractor.RESULT_END_OF_INPUT; + } + + /* package */ int getElementType(int id) { + switch (id) { + case ID_EBML: + case ID_SEGMENT: + case ID_SEEK_HEAD: + case ID_SEEK: + case ID_INFO: + case ID_CLUSTER: + case ID_TRACKS: + case ID_TRACK_ENTRY: + case ID_AUDIO: + case ID_VIDEO: + case ID_CONTENT_ENCODINGS: + case ID_CONTENT_ENCODING: + case ID_CONTENT_COMPRESSION: + case ID_CONTENT_ENCRYPTION: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS: + case ID_CUES: + case ID_CUE_POINT: + case ID_CUE_TRACK_POSITIONS: + case ID_BLOCK_GROUP: + case ID_PROJECTION: + return EbmlReader.TYPE_MASTER; + case ID_EBML_READ_VERSION: + case ID_DOC_TYPE_READ_VERSION: + case ID_SEEK_POSITION: + case ID_TIMECODE_SCALE: + case ID_TIME_CODE: + case ID_BLOCK_DURATION: + case ID_PIXEL_WIDTH: + case ID_PIXEL_HEIGHT: + case ID_DISPLAY_WIDTH: + case ID_DISPLAY_HEIGHT: + case ID_DISPLAY_UNIT: + case ID_TRACK_NUMBER: + case ID_TRACK_TYPE: + case ID_FLAG_DEFAULT: + case ID_FLAG_FORCED: + case ID_DEFAULT_DURATION: + case ID_CODEC_DELAY: + case ID_SEEK_PRE_ROLL: + case ID_CHANNELS: + case ID_AUDIO_BIT_DEPTH: + case ID_CONTENT_ENCODING_ORDER: + case ID_CONTENT_ENCODING_SCOPE: + case ID_CONTENT_COMPRESSION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_ALGORITHM: + case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: + case ID_CUE_TIME: + case ID_CUE_CLUSTER_POSITION: + case ID_REFERENCE_BLOCK: + case ID_STEREO_MODE: + return EbmlReader.TYPE_UNSIGNED_INT; + case ID_DOC_TYPE: + case ID_CODEC_ID: + case ID_LANGUAGE: + return EbmlReader.TYPE_STRING; + case ID_SEEK_ID: + case ID_CONTENT_COMPRESSION_SETTINGS: + case ID_CONTENT_ENCRYPTION_KEY_ID: + case ID_SIMPLE_BLOCK: + case ID_BLOCK: + case ID_CODEC_PRIVATE: + case ID_PROJECTION_PRIVATE: + return EbmlReader.TYPE_BINARY; + case ID_DURATION: + case ID_SAMPLING_FREQUENCY: + return EbmlReader.TYPE_FLOAT; + default: + return EbmlReader.TYPE_UNKNOWN; + } + } + + /* package */ boolean isLevel1Element(int id) { + return id == ID_SEGMENT_INFO || id == ID_CLUSTER || id == ID_CUES || id == ID_TRACKS; + } + + /* package */ void startMasterElement(int id, long contentPosition, long contentSize) + throws ParserException { + switch (id) { + case ID_SEGMENT: + if (segmentContentPosition != C.POSITION_UNSET + && segmentContentPosition != contentPosition) { + throw new ParserException("Multiple Segment elements not supported"); + } + segmentContentPosition = contentPosition; + segmentContentSize = contentSize; + break; + case ID_SEEK: + seekEntryId = UNSET_ENTRY_ID; + seekEntryPosition = C.POSITION_UNSET; + break; + case ID_CUES: + cueTimesUs = new LongArray(); + cueClusterPositions = new LongArray(); + break; + case ID_CUE_POINT: + seenClusterPositionForCurrentCuePoint = false; + break; + case ID_CLUSTER: + if (!sentSeekMap) { + // We need to build cues before parsing the cluster. + if (cuesContentPosition != C.POSITION_UNSET) { + // We know where the Cues element is located. Seek to request it. + seekForCues = true; + } else { + // We don't know where the Cues element is located. It's most likely omitted. Allow + // playback, but disable seeking. + extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); + sentSeekMap = true; + } + } + break; + case ID_BLOCK_GROUP: + sampleSeenReferenceBlock = false; + break; + case ID_CONTENT_ENCODING: + // TODO: check and fail if more than one content encoding is present. + break; + case ID_CONTENT_ENCRYPTION: + currentTrack.hasContentEncryption = true; + break; + case ID_TRACK_ENTRY: + currentTrack = new Track(); + break; + default: + break; + } + } + + /* package */ void endMasterElement(int id) throws ParserException { + switch (id) { + case ID_SEGMENT_INFO: + if (timecodeScale == C.TIME_UNSET) { + // timecodeScale was omitted. Use the default value. + timecodeScale = 1000000; + } + if (durationTimecode != C.TIME_UNSET) { + durationUs = scaleTimecodeToUs(durationTimecode); + } + break; + case ID_SEEK: + if (seekEntryId == UNSET_ENTRY_ID || seekEntryPosition == C.POSITION_UNSET) { + throw new ParserException("Mandatory element SeekID or SeekPosition not found"); + } + if (seekEntryId == ID_CUES) { + cuesContentPosition = seekEntryPosition; + } + break; + case ID_CUES: + if (!sentSeekMap) { + extractorOutput.seekMap(buildSeekMap()); + sentSeekMap = true; + } else { + // We have already built the cues. Ignore. + } + break; + case ID_BLOCK_GROUP: + if (blockState != BLOCK_STATE_DATA) { + // We've skipped this block (due to incompatible track number). + return; + } + // If the ReferenceBlock element was not found for this sample, then it is a keyframe. + if (!sampleSeenReferenceBlock) { + blockFlags |= C.BUFFER_FLAG_KEY_FRAME; + } + commitSampleToOutput(tracks.get(blockTrackNumber), blockTimeUs); + blockState = BLOCK_STATE_START; + break; + case ID_CONTENT_ENCODING: + if (currentTrack.hasContentEncryption) { + if (currentTrack.encryptionKeyId == null) { + throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); + } + currentTrack.drmInitData = new DrmInitData( + new SchemeData(C.UUID_NIL, MimeTypes.VIDEO_WEBM, currentTrack.encryptionKeyId)); + } + break; + case ID_CONTENT_ENCODINGS: + if (currentTrack.hasContentEncryption && currentTrack.sampleStrippedBytes != null) { + throw new ParserException("Combining encryption and compression is not supported"); + } + break; + case ID_TRACK_ENTRY: + if (tracks.get(currentTrack.number) == null && isCodecSupported(currentTrack.codecId)) { + currentTrack.initializeOutput(extractorOutput, currentTrack.number); + tracks.put(currentTrack.number, currentTrack); + } else { + // We've seen this track entry before, or the codec is unsupported. Do nothing. + } + currentTrack = null; + break; + case ID_TRACKS: + if (tracks.size() == 0) { + throw new ParserException("No valid tracks were found"); + } + extractorOutput.endTracks(); + break; + default: + break; + } + } + + /* package */ void integerElement(int id, long value) throws ParserException { + switch (id) { + case ID_EBML_READ_VERSION: + // Validate that EBMLReadVersion is supported. This extractor only supports v1. + if (value != 1) { + throw new ParserException("EBMLReadVersion " + value + " not supported"); + } + break; + case ID_DOC_TYPE_READ_VERSION: + // Validate that DocTypeReadVersion is supported. This extractor only supports up to v2. + if (value < 1 || value > 2) { + throw new ParserException("DocTypeReadVersion " + value + " not supported"); + } + break; + case ID_SEEK_POSITION: + // Seek Position is the relative offset beginning from the Segment. So to get absolute + // offset from the beginning of the file, we need to add segmentContentPosition to it. + seekEntryPosition = value + segmentContentPosition; + break; + case ID_TIMECODE_SCALE: + timecodeScale = value; + break; + case ID_PIXEL_WIDTH: + currentTrack.width = (int) value; + break; + case ID_PIXEL_HEIGHT: + currentTrack.height = (int) value; + break; + case ID_DISPLAY_WIDTH: + currentTrack.displayWidth = (int) value; + break; + case ID_DISPLAY_HEIGHT: + currentTrack.displayHeight = (int) value; + break; + case ID_DISPLAY_UNIT: + currentTrack.displayUnit = (int) value; + break; + case ID_TRACK_NUMBER: + currentTrack.number = (int) value; + break; + case ID_FLAG_DEFAULT: + currentTrack.flagForced = value == 1; + break; + case ID_FLAG_FORCED: + currentTrack.flagDefault = value == 1; + break; + case ID_TRACK_TYPE: + currentTrack.type = (int) value; + break; + case ID_DEFAULT_DURATION: + currentTrack.defaultSampleDurationNs = (int) value; + break; + case ID_CODEC_DELAY: + currentTrack.codecDelayNs = value; + break; + case ID_SEEK_PRE_ROLL: + currentTrack.seekPreRollNs = value; + break; + case ID_CHANNELS: + currentTrack.channelCount = (int) value; + break; + case ID_AUDIO_BIT_DEPTH: + currentTrack.audioBitDepth = (int) value; + break; + case ID_REFERENCE_BLOCK: + sampleSeenReferenceBlock = true; + break; + case ID_CONTENT_ENCODING_ORDER: + // This extractor only supports one ContentEncoding element and hence the order has to be 0. + if (value != 0) { + throw new ParserException("ContentEncodingOrder " + value + " not supported"); + } + break; + case ID_CONTENT_ENCODING_SCOPE: + // This extractor only supports the scope of all frames. + if (value != 1) { + throw new ParserException("ContentEncodingScope " + value + " not supported"); + } + break; + case ID_CONTENT_COMPRESSION_ALGORITHM: + // This extractor only supports header stripping. + if (value != 3) { + throw new ParserException("ContentCompAlgo " + value + " not supported"); + } + break; + case ID_CONTENT_ENCRYPTION_ALGORITHM: + // Only the value 5 (AES) is allowed according to the WebM specification. + if (value != 5) { + throw new ParserException("ContentEncAlgo " + value + " not supported"); + } + break; + case ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE: + // Only the value 1 is allowed according to the WebM specification. + if (value != 1) { + throw new ParserException("AESSettingsCipherMode " + value + " not supported"); + } + break; + case ID_CUE_TIME: + cueTimesUs.add(scaleTimecodeToUs(value)); + break; + case ID_CUE_CLUSTER_POSITION: + if (!seenClusterPositionForCurrentCuePoint) { + // If there's more than one video/audio track, then there could be more than one + // CueTrackPositions within a single CuePoint. In such a case, ignore all but the first + // one (since the cluster position will be quite close for all the tracks). + cueClusterPositions.add(value); + seenClusterPositionForCurrentCuePoint = true; + } + break; + case ID_TIME_CODE: + clusterTimecodeUs = scaleTimecodeToUs(value); + break; + case ID_BLOCK_DURATION: + blockDurationUs = scaleTimecodeToUs(value); + break; + case ID_STEREO_MODE: + int layout = (int) value; + switch (layout) { + case 0: + currentTrack.stereoMode = C.STEREO_MODE_MONO; + break; + case 1: + currentTrack.stereoMode = C.STEREO_MODE_LEFT_RIGHT; + break; + case 3: + currentTrack.stereoMode = C.STEREO_MODE_TOP_BOTTOM; + break; + default: + break; + } + break; + default: + break; + } + } + + /* package */ void floatElement(int id, double value) { + switch (id) { + case ID_DURATION: + durationTimecode = (long) value; + break; + case ID_SAMPLING_FREQUENCY: + currentTrack.sampleRate = (int) value; + break; + default: + break; + } + } + + /* package */ void stringElement(int id, String value) throws ParserException { + switch (id) { + case ID_DOC_TYPE: + // Validate that DocType is supported. + if (!DOC_TYPE_WEBM.equals(value) && !DOC_TYPE_MATROSKA.equals(value)) { + throw new ParserException("DocType " + value + " not supported"); + } + break; + case ID_CODEC_ID: + currentTrack.codecId = value; + break; + case ID_LANGUAGE: + currentTrack.language = value; + break; + default: + break; + } + } + + /* package */ void binaryElement(int id, int contentSize, ExtractorInput input) + throws IOException, InterruptedException { + switch (id) { + case ID_SEEK_ID: + Arrays.fill(seekEntryIdBytes.data, (byte) 0); + input.readFully(seekEntryIdBytes.data, 4 - contentSize, contentSize); + seekEntryIdBytes.setPosition(0); + seekEntryId = (int) seekEntryIdBytes.readUnsignedInt(); + break; + case ID_CODEC_PRIVATE: + currentTrack.codecPrivate = new byte[contentSize]; + input.readFully(currentTrack.codecPrivate, 0, contentSize); + break; + case ID_PROJECTION_PRIVATE: + currentTrack.projectionData = new byte[contentSize]; + input.readFully(currentTrack.projectionData, 0, contentSize); + break; + case ID_CONTENT_COMPRESSION_SETTINGS: + // This extractor only supports header stripping, so the payload is the stripped bytes. + currentTrack.sampleStrippedBytes = new byte[contentSize]; + input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); + break; + case ID_CONTENT_ENCRYPTION_KEY_ID: + currentTrack.encryptionKeyId = new byte[contentSize]; + input.readFully(currentTrack.encryptionKeyId, 0, contentSize); + break; + case ID_SIMPLE_BLOCK: + case ID_BLOCK: + // Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure + // and http://matroska.org/technical/specs/index.html#block_structure + // for info about how data is organized in SimpleBlock and Block elements respectively. They + // differ only in the way flags are specified. + + if (blockState == BLOCK_STATE_START) { + blockTrackNumber = (int) varintReader.readUnsignedVarint(input, false, true, 8); + blockTrackNumberLength = varintReader.getLastLength(); + blockDurationUs = C.TIME_UNSET; + blockState = BLOCK_STATE_HEADER; + scratch.reset(); + } + + Track track = tracks.get(blockTrackNumber); + + // Ignore the block if we don't know about the track to which it belongs. + if (track == null) { + input.skipFully(contentSize - blockTrackNumberLength); + blockState = BLOCK_STATE_START; + return; + } + + if (blockState == BLOCK_STATE_HEADER) { + // Read the relative timecode (2 bytes) and flags (1 byte). + readScratch(input, 3); + int lacing = (scratch.data[2] & 0x06) >> 1; + if (lacing == LACING_NONE) { + blockLacingSampleCount = 1; + blockLacingSampleSizes = ensureArrayCapacity(blockLacingSampleSizes, 1); + blockLacingSampleSizes[0] = contentSize - blockTrackNumberLength - 3; + } else { + if (id != ID_SIMPLE_BLOCK) { + throw new ParserException("Lacing only supported in SimpleBlocks."); + } + + // Read the sample count (1 byte). + readScratch(input, 4); + blockLacingSampleCount = (scratch.data[3] & 0xFF) + 1; + blockLacingSampleSizes = + ensureArrayCapacity(blockLacingSampleSizes, blockLacingSampleCount); + if (lacing == LACING_FIXED_SIZE) { + int blockLacingSampleSize = + (contentSize - blockTrackNumberLength - 4) / blockLacingSampleCount; + Arrays.fill(blockLacingSampleSizes, 0, blockLacingSampleCount, blockLacingSampleSize); + } else if (lacing == LACING_XIPH) { + int totalSamplesSize = 0; + int headerSize = 4; + for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { + blockLacingSampleSizes[sampleIndex] = 0; + int byteValue; + do { + readScratch(input, ++headerSize); + byteValue = scratch.data[headerSize - 1] & 0xFF; + blockLacingSampleSizes[sampleIndex] += byteValue; + } while (byteValue == 0xFF); + totalSamplesSize += blockLacingSampleSizes[sampleIndex]; + } + blockLacingSampleSizes[blockLacingSampleCount - 1] = + contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; + } else if (lacing == LACING_EBML) { + int totalSamplesSize = 0; + int headerSize = 4; + for (int sampleIndex = 0; sampleIndex < blockLacingSampleCount - 1; sampleIndex++) { + blockLacingSampleSizes[sampleIndex] = 0; + readScratch(input, ++headerSize); + if (scratch.data[headerSize - 1] == 0) { + throw new ParserException("No valid varint length mask found"); + } + long readValue = 0; + for (int i = 0; i < 8; i++) { + int lengthMask = 1 << (7 - i); + if ((scratch.data[headerSize - 1] & lengthMask) != 0) { + int readPosition = headerSize - 1; + headerSize += i; + readScratch(input, headerSize); + readValue = (scratch.data[readPosition++] & 0xFF) & ~lengthMask; + while (readPosition < headerSize) { + readValue <<= 8; + readValue |= (scratch.data[readPosition++] & 0xFF); + } + // The first read value is the first size. Later values are signed offsets. + if (sampleIndex > 0) { + readValue -= (1L << (6 + i * 7)) - 1; + } + break; + } + } + if (readValue < Integer.MIN_VALUE || readValue > Integer.MAX_VALUE) { + throw new ParserException("EBML lacing sample size out of range."); + } + int intReadValue = (int) readValue; + blockLacingSampleSizes[sampleIndex] = sampleIndex == 0 + ? intReadValue : blockLacingSampleSizes[sampleIndex - 1] + intReadValue; + totalSamplesSize += blockLacingSampleSizes[sampleIndex]; + } + blockLacingSampleSizes[blockLacingSampleCount - 1] = + contentSize - blockTrackNumberLength - headerSize - totalSamplesSize; + } else { + // Lacing is always in the range 0--3. + throw new ParserException("Unexpected lacing value: " + lacing); + } + } + + int timecode = (scratch.data[0] << 8) | (scratch.data[1] & 0xFF); + blockTimeUs = clusterTimecodeUs + scaleTimecodeToUs(timecode); + boolean isInvisible = (scratch.data[2] & 0x08) == 0x08; + boolean isKeyframe = track.type == TRACK_TYPE_AUDIO + || (id == ID_SIMPLE_BLOCK && (scratch.data[2] & 0x80) == 0x80); + blockFlags = (isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0) + | (isInvisible ? C.BUFFER_FLAG_DECODE_ONLY : 0); + blockState = BLOCK_STATE_DATA; + blockLacingSampleIndex = 0; + } + + if (id == ID_SIMPLE_BLOCK) { + // For SimpleBlock, we have metadata for each sample here. + while (blockLacingSampleIndex < blockLacingSampleCount) { + writeSampleData(input, track, blockLacingSampleSizes[blockLacingSampleIndex]); + long sampleTimeUs = this.blockTimeUs + + (blockLacingSampleIndex * track.defaultSampleDurationNs) / 1000; + commitSampleToOutput(track, sampleTimeUs); + blockLacingSampleIndex++; + } + blockState = BLOCK_STATE_START; + } else { + // For Block, we send the metadata at the end of the BlockGroup element since we'll know + // if the sample is a keyframe or not only at that point. + writeSampleData(input, track, blockLacingSampleSizes[0]); + } + + break; + default: + throw new ParserException("Unexpected id: " + id); + } + } + + private void commitSampleToOutput(Track track, long timeUs) { + if (CODEC_ID_SUBRIP.equals(track.codecId)) { + writeSubripSample(track); + } + track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.encryptionKeyId); + sampleRead = true; + resetSample(); + } + + private void resetSample() { + sampleBytesRead = 0; + sampleBytesWritten = 0; + sampleCurrentNalBytesRemaining = 0; + sampleEncodingHandled = false; + sampleSignalByteRead = false; + samplePartitionCountRead = false; + samplePartitionCount = 0; + sampleSignalByte = (byte) 0; + sampleInitializationVectorRead = false; + sampleStrippedBytes.reset(); + } + + /** + * Ensures {@link #scratch} contains at least {@code requiredLength} bytes of data, reading from + * the extractor input if necessary. + */ + private void readScratch(ExtractorInput input, int requiredLength) + throws IOException, InterruptedException { + if (scratch.limit() >= requiredLength) { + return; + } + if (scratch.capacity() < requiredLength) { + scratch.reset(Arrays.copyOf(scratch.data, Math.max(scratch.data.length * 2, requiredLength)), + scratch.limit()); + } + input.readFully(scratch.data, scratch.limit(), requiredLength - scratch.limit()); + scratch.setLimit(requiredLength); + } + + private void writeSampleData(ExtractorInput input, Track track, int size) + throws IOException, InterruptedException { + if (CODEC_ID_SUBRIP.equals(track.codecId)) { + int sizeWithPrefix = SUBRIP_PREFIX.length + size; + if (subripSample.capacity() < sizeWithPrefix) { + // Initialize subripSample to contain the required prefix and have space to hold a subtitle + // twice as long as this one. + subripSample.data = Arrays.copyOf(SUBRIP_PREFIX, sizeWithPrefix + size); + } + input.readFully(subripSample.data, SUBRIP_PREFIX.length, size); + subripSample.setPosition(0); + subripSample.setLimit(sizeWithPrefix); + // Defer writing the data to the track output. We need to modify the sample data by setting + // the correct end timecode, which we might not have yet. + return; + } + + TrackOutput output = track.output; + if (!sampleEncodingHandled) { + if (track.hasContentEncryption) { + // If the sample is encrypted, read its encryption signal byte and set the IV size. + // Clear the encrypted flag. + blockFlags &= ~C.BUFFER_FLAG_ENCRYPTED; + if (!sampleSignalByteRead) { + input.readFully(scratch.data, 0, 1); + sampleBytesRead++; + if ((scratch.data[0] & 0x80) == 0x80) { + throw new ParserException("Extension bit is set in signal byte"); + } + sampleSignalByte = scratch.data[0]; + sampleSignalByteRead = true; + } + boolean isEncrypted = (sampleSignalByte & 0x01) == 0x01; + if (isEncrypted) { + boolean hasSubsampleEncryption = (sampleSignalByte & 0x02) == 0x02; + blockFlags |= C.BUFFER_FLAG_ENCRYPTED; + if (!sampleInitializationVectorRead) { + input.readFully(encryptionInitializationVector.data, 0, ENCRYPTION_IV_SIZE); + sampleBytesRead += ENCRYPTION_IV_SIZE; + sampleInitializationVectorRead = true; + // Write the signal byte, containing the IV size and the subsample encryption flag. + scratch.data[0] = (byte) (ENCRYPTION_IV_SIZE | (hasSubsampleEncryption ? 0x80 : 0x00)); + scratch.setPosition(0); + output.sampleData(scratch, 1); + sampleBytesWritten++; + // Write the IV. + encryptionInitializationVector.setPosition(0); + output.sampleData(encryptionInitializationVector, ENCRYPTION_IV_SIZE); + sampleBytesWritten += ENCRYPTION_IV_SIZE; + } + if (hasSubsampleEncryption) { + if (!samplePartitionCountRead) { + input.readFully(scratch.data, 0, 1); + sampleBytesRead++; + scratch.setPosition(0); + samplePartitionCount = scratch.readUnsignedByte(); + samplePartitionCountRead = true; + } + int samplePartitionDataSize = samplePartitionCount * 4; + scratch.reset(samplePartitionDataSize); + input.readFully(scratch.data, 0, samplePartitionDataSize); + sampleBytesRead += samplePartitionDataSize; + short subsampleCount = (short) (1 + (samplePartitionCount / 2)); + int subsampleDataSize = 2 + 6 * subsampleCount; + if (encryptionSubsampleDataBuffer == null + || encryptionSubsampleDataBuffer.capacity() < subsampleDataSize) { + encryptionSubsampleDataBuffer = ByteBuffer.allocate(subsampleDataSize); + } + encryptionSubsampleDataBuffer.position(0); + encryptionSubsampleDataBuffer.putShort(subsampleCount); + // Loop through the partition offsets and write out the data in the way ExoPlayer + // wants it (ISO 23001-7 Part 7): + // 2 bytes - sub sample count. + // for each sub sample: + // 2 bytes - clear data size. + // 4 bytes - encrypted data size. + int partitionOffset = 0; + for (int i = 0; i < samplePartitionCount; i++) { + int previousPartitionOffset = partitionOffset; + partitionOffset = scratch.readUnsignedIntToInt(); + if ((i % 2) == 0) { + encryptionSubsampleDataBuffer.putShort( + (short) (partitionOffset - previousPartitionOffset)); + } else { + encryptionSubsampleDataBuffer.putInt(partitionOffset - previousPartitionOffset); + } + } + int finalPartitionSize = size - sampleBytesRead - partitionOffset; + if ((samplePartitionCount % 2) == 1) { + encryptionSubsampleDataBuffer.putInt(finalPartitionSize); + } else { + encryptionSubsampleDataBuffer.putShort((short) finalPartitionSize); + encryptionSubsampleDataBuffer.putInt(0); + } + encryptionSubsampleData.reset(encryptionSubsampleDataBuffer.array(), subsampleDataSize); + output.sampleData(encryptionSubsampleData, subsampleDataSize); + sampleBytesWritten += subsampleDataSize; + } + } + } else if (track.sampleStrippedBytes != null) { + // If the sample has header stripping, prepare to read/output the stripped bytes first. + sampleStrippedBytes.reset(track.sampleStrippedBytes, track.sampleStrippedBytes.length); + } + sampleEncodingHandled = true; + } + size += sampleStrippedBytes.limit(); + + if (CODEC_ID_H264.equals(track.codecId) || CODEC_ID_H265.equals(track.codecId)) { + // TODO: Deduplicate with Mp4Extractor. + + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case + // they're only 1 or 2 bytes long. + byte[] nalLengthData = nalLength.data; + nalLengthData[0] = 0; + nalLengthData[1] = 0; + nalLengthData[2] = 0; + int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength; + int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength; + // NAL units are length delimited, but the decoder requires start code delimited units. + // Loop until we've written the sample to the track output, replacing length delimiters with + // start codes as we encounter them. + while (sampleBytesRead < size) { + if (sampleCurrentNalBytesRemaining == 0) { + // Read the NAL length so that we know where we find the next one. + readToTarget(input, nalLengthData, nalUnitLengthFieldLengthDiff, + nalUnitLengthFieldLength); + nalLength.setPosition(0); + sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt(); + // Write a start code for the current NAL unit. + nalStartCode.setPosition(0); + output.sampleData(nalStartCode, 4); + sampleBytesWritten += 4; + } else { + // Write the payload of the NAL unit. + sampleCurrentNalBytesRemaining -= + readToOutput(input, output, sampleCurrentNalBytesRemaining); + } + } + } else { + while (sampleBytesRead < size) { + readToOutput(input, output, size - sampleBytesRead); + } + } + + if (CODEC_ID_VORBIS.equals(track.codecId)) { + // Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be the + // number of samples in the current page. This definition holds good only for Ogg and + // irrelevant for Matroska. So we always set this to -1 (the decoder will ignore this value if + // we set it to -1). The android platform media extractor [2] does the same. + // [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314 + // [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474 + vorbisNumPageSamples.setPosition(0); + output.sampleData(vorbisNumPageSamples, 4); + sampleBytesWritten += 4; + } + } + + private void writeSubripSample(Track track) { + setSubripSampleEndTimecode(subripSample.data, blockDurationUs); + // Note: If we ever want to support DRM protected subtitles then we'll need to output the + // appropriate encryption data here. + track.output.sampleData(subripSample, subripSample.limit()); + sampleBytesWritten += subripSample.limit(); + } + + private static void setSubripSampleEndTimecode(byte[] subripSampleData, long timeUs) { + byte[] timeCodeData; + if (timeUs == C.TIME_UNSET) { + timeCodeData = SUBRIP_TIMECODE_EMPTY; + } else { + int hours = (int) (timeUs / 3600000000L); + timeUs -= (hours * 3600000000L); + int minutes = (int) (timeUs / 60000000); + timeUs -= (minutes * 60000000); + int seconds = (int) (timeUs / 1000000); + timeUs -= (seconds * 1000000); + int milliseconds = (int) (timeUs / 1000); + timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, "%02d:%02d:%02d,%03d", hours, + minutes, seconds, milliseconds)); + } + System.arraycopy(timeCodeData, 0, subripSampleData, SUBRIP_PREFIX_END_TIMECODE_OFFSET, + SUBRIP_TIMECODE_LENGTH); + } + + /** + * Writes {@code length} bytes of sample data into {@code target} at {@code offset}, consisting of + * pending {@link #sampleStrippedBytes} and any remaining data read from {@code input}. + */ + private void readToTarget(ExtractorInput input, byte[] target, int offset, int length) + throws IOException, InterruptedException { + int pendingStrippedBytes = Math.min(length, sampleStrippedBytes.bytesLeft()); + input.readFully(target, offset + pendingStrippedBytes, length - pendingStrippedBytes); + if (pendingStrippedBytes > 0) { + sampleStrippedBytes.readBytes(target, offset, pendingStrippedBytes); + } + sampleBytesRead += length; + } + + /** + * Outputs up to {@code length} bytes of sample data to {@code output}, consisting of either + * {@link #sampleStrippedBytes} or data read from {@code input}. + */ + private int readToOutput(ExtractorInput input, TrackOutput output, int length) + throws IOException, InterruptedException { + int bytesRead; + int strippedBytesLeft = sampleStrippedBytes.bytesLeft(); + if (strippedBytesLeft > 0) { + bytesRead = Math.min(length, strippedBytesLeft); + output.sampleData(sampleStrippedBytes, bytesRead); + } else { + bytesRead = output.sampleData(input, length, false); + } + sampleBytesRead += bytesRead; + sampleBytesWritten += bytesRead; + return bytesRead; + } + + /** + * Builds a {@link SeekMap} from the recently gathered Cues information. + * + * @return The built {@link SeekMap}. The returned {@link SeekMap} may be unseekable if cues + * information was missing or incomplete. + */ + private SeekMap buildSeekMap() { + if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET + || cueTimesUs == null || cueTimesUs.size() == 0 + || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { + // Cues information is missing or incomplete. + cueTimesUs = null; + cueClusterPositions = null; + return new SeekMap.Unseekable(durationUs); + } + int cuePointsSize = cueTimesUs.size(); + int[] sizes = new int[cuePointsSize]; + long[] offsets = new long[cuePointsSize]; + long[] durationsUs = new long[cuePointsSize]; + long[] timesUs = new long[cuePointsSize]; + for (int i = 0; i < cuePointsSize; i++) { + timesUs[i] = cueTimesUs.get(i); + offsets[i] = segmentContentPosition + cueClusterPositions.get(i); + } + for (int i = 0; i < cuePointsSize - 1; i++) { + sizes[i] = (int) (offsets[i + 1] - offsets[i]); + durationsUs[i] = timesUs[i + 1] - timesUs[i]; + } + sizes[cuePointsSize - 1] = + (int) (segmentContentPosition + segmentContentSize - offsets[cuePointsSize - 1]); + durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1]; + cueTimesUs = null; + cueClusterPositions = null; + return new ChunkIndex(sizes, offsets, durationsUs, timesUs); + } + + /** + * Updates the position of the holder to Cues element's position if the extractor configuration + * permits use of master seek entry. After building Cues sets the holder's position back to where + * it was before. + * + * @param seekPosition The holder whose position will be updated. + * @param currentPosition Current position of the input. + * @return Whether the seek position was updated. + */ + private boolean maybeSeekForCues(PositionHolder seekPosition, long currentPosition) { + if (seekForCues) { + seekPositionAfterBuildingCues = currentPosition; + seekPosition.position = cuesContentPosition; + seekForCues = false; + return true; + } + // After parsing Cues, seek back to original position if available. We will not do this unless + // we seeked to get to the Cues in the first place. + if (sentSeekMap && seekPositionAfterBuildingCues != C.POSITION_UNSET) { + seekPosition.position = seekPositionAfterBuildingCues; + seekPositionAfterBuildingCues = C.POSITION_UNSET; + return true; + } + return false; + } + + private long scaleTimecodeToUs(long unscaledTimecode) throws ParserException { + if (timecodeScale == C.TIME_UNSET) { + throw new ParserException("Can't scale timecode prior to timecodeScale being set."); + } + return Util.scaleLargeTimestamp(unscaledTimecode, timecodeScale, 1000); + } + + private static boolean isCodecSupported(String codecId) { + return CODEC_ID_VP8.equals(codecId) + || CODEC_ID_VP9.equals(codecId) + || CODEC_ID_MPEG2.equals(codecId) + || CODEC_ID_MPEG4_SP.equals(codecId) + || CODEC_ID_MPEG4_ASP.equals(codecId) + || CODEC_ID_MPEG4_AP.equals(codecId) + || CODEC_ID_H264.equals(codecId) + || CODEC_ID_H265.equals(codecId) + || CODEC_ID_FOURCC.equals(codecId) + || CODEC_ID_THEORA.equals(codecId) + || CODEC_ID_OPUS.equals(codecId) + || CODEC_ID_VORBIS.equals(codecId) + || CODEC_ID_AAC.equals(codecId) + || CODEC_ID_MP3.equals(codecId) + || CODEC_ID_AC3.equals(codecId) + || CODEC_ID_E_AC3.equals(codecId) + || CODEC_ID_TRUEHD.equals(codecId) + || CODEC_ID_DTS.equals(codecId) + || CODEC_ID_DTS_EXPRESS.equals(codecId) + || CODEC_ID_DTS_LOSSLESS.equals(codecId) + || CODEC_ID_FLAC.equals(codecId) + || CODEC_ID_ACM.equals(codecId) + || CODEC_ID_PCM_INT_LIT.equals(codecId) + || CODEC_ID_SUBRIP.equals(codecId) + || CODEC_ID_VOBSUB.equals(codecId) + || CODEC_ID_PGS.equals(codecId); + } + + /** + * Returns an array that can store (at least) {@code length} elements, which will be either a new + * array or {@code array} if it's not null and large enough. + */ + private static int[] ensureArrayCapacity(int[] array, int length) { + if (array == null) { + return new int[length]; + } else if (array.length >= length) { + return array; + } else { + // Double the size to avoid allocating constantly if the required length increases gradually. + return new int[Math.max(array.length * 2, length)]; + } + } + + /** + * Passes events through to the outer {@link MatroskaExtractor}. + */ + private final class InnerEbmlReaderOutput implements EbmlReaderOutput { + + @Override + public int getElementType(int id) { + return MatroskaExtractor.this.getElementType(id); + } + + @Override + public boolean isLevel1Element(int id) { + return MatroskaExtractor.this.isLevel1Element(id); + } + + @Override + public void startMasterElement(int id, long contentPosition, long contentSize) + throws ParserException { + MatroskaExtractor.this.startMasterElement(id, contentPosition, contentSize); + } + + @Override + public void endMasterElement(int id) throws ParserException { + MatroskaExtractor.this.endMasterElement(id); + } + + @Override + public void integerElement(int id, long value) throws ParserException { + MatroskaExtractor.this.integerElement(id, value); + } + + @Override + public void floatElement(int id, double value) throws ParserException { + MatroskaExtractor.this.floatElement(id, value); + } + + @Override + public void stringElement(int id, String value) throws ParserException { + MatroskaExtractor.this.stringElement(id, value); + } + + @Override + public void binaryElement(int id, int contentsSize, ExtractorInput input) + throws IOException, InterruptedException { + MatroskaExtractor.this.binaryElement(id, contentsSize, input); + } + + } + + private static final class Track { + + private static final int DISPLAY_UNIT_PIXELS = 0; + + // Common elements. + public String codecId; + public int number; + public int type; + public int defaultSampleDurationNs; + public boolean hasContentEncryption; + public byte[] sampleStrippedBytes; + public byte[] encryptionKeyId; + public byte[] codecPrivate; + public DrmInitData drmInitData; + + // Video elements. + public int width = Format.NO_VALUE; + public int height = Format.NO_VALUE; + public int displayWidth = Format.NO_VALUE; + public int displayHeight = Format.NO_VALUE; + public int displayUnit = DISPLAY_UNIT_PIXELS; + public byte[] projectionData = null; + @C.StereoMode + public int stereoMode = Format.NO_VALUE; + + // Audio elements. Initially set to their default values. + public int channelCount = 1; + public int audioBitDepth = Format.NO_VALUE; + public int sampleRate = 8000; + public long codecDelayNs = 0; + public long seekPreRollNs = 0; + + // Text elements. + public boolean flagForced; + public boolean flagDefault = true; + private String language = "eng"; + + // Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265. + public TrackOutput output; + public int nalUnitLengthFieldLength; + + /** + * Initializes the track with an output. + */ + public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { + String mimeType; + int maxInputSize = Format.NO_VALUE; + @C.PcmEncoding int pcmEncoding = Format.NO_VALUE; + List initializationData = null; + switch (codecId) { + case CODEC_ID_VP8: + mimeType = MimeTypes.VIDEO_VP8; + break; + case CODEC_ID_VP9: + mimeType = MimeTypes.VIDEO_VP9; + break; + case CODEC_ID_MPEG2: + mimeType = MimeTypes.VIDEO_MPEG2; + break; + case CODEC_ID_MPEG4_SP: + case CODEC_ID_MPEG4_ASP: + case CODEC_ID_MPEG4_AP: + mimeType = MimeTypes.VIDEO_MP4V; + initializationData = + codecPrivate == null ? null : Collections.singletonList(codecPrivate); + break; + case CODEC_ID_H264: + mimeType = MimeTypes.VIDEO_H264; + AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = avcConfig.initializationData; + nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + break; + case CODEC_ID_H265: + mimeType = MimeTypes.VIDEO_H265; + HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(codecPrivate)); + initializationData = hevcConfig.initializationData; + nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; + break; + case CODEC_ID_FOURCC: + initializationData = parseFourCcVc1Private(new ParsableByteArray(codecPrivate)); + mimeType = initializationData == null ? MimeTypes.VIDEO_UNKNOWN : MimeTypes.VIDEO_VC1; + break; + case CODEC_ID_THEORA: + // TODO: This can be set to the real mimeType if/when we work out what initializationData + // should be set to for this case. + mimeType = MimeTypes.VIDEO_UNKNOWN; + break; + case CODEC_ID_VORBIS: + mimeType = MimeTypes.AUDIO_VORBIS; + maxInputSize = VORBIS_MAX_INPUT_SIZE; + initializationData = parseVorbisCodecPrivate(codecPrivate); + break; + case CODEC_ID_OPUS: + mimeType = MimeTypes.AUDIO_OPUS; + maxInputSize = OPUS_MAX_INPUT_SIZE; + initializationData = new ArrayList<>(3); + initializationData.add(codecPrivate); + initializationData.add( + ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(codecDelayNs).array()); + initializationData.add( + ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(seekPreRollNs).array()); + break; + case CODEC_ID_AAC: + mimeType = MimeTypes.AUDIO_AAC; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_MP3: + mimeType = MimeTypes.AUDIO_MPEG; + maxInputSize = MP3_MAX_INPUT_SIZE; + break; + case CODEC_ID_AC3: + mimeType = MimeTypes.AUDIO_AC3; + break; + case CODEC_ID_E_AC3: + mimeType = MimeTypes.AUDIO_E_AC3; + break; + case CODEC_ID_TRUEHD: + mimeType = MimeTypes.AUDIO_TRUEHD; + break; + case CODEC_ID_DTS: + case CODEC_ID_DTS_EXPRESS: + mimeType = MimeTypes.AUDIO_DTS; + break; + case CODEC_ID_DTS_LOSSLESS: + mimeType = MimeTypes.AUDIO_DTS_HD; + break; + case CODEC_ID_FLAC: + mimeType = MimeTypes.AUDIO_FLAC; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_ACM: + mimeType = MimeTypes.AUDIO_RAW; + if (!parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { + throw new ParserException("Non-PCM MS/ACM is unsupported"); + } + pcmEncoding = Util.getPcmEncoding(audioBitDepth); + if (pcmEncoding == C.ENCODING_INVALID) { + throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + } + break; + case CODEC_ID_PCM_INT_LIT: + mimeType = MimeTypes.AUDIO_RAW; + pcmEncoding = Util.getPcmEncoding(audioBitDepth); + if (pcmEncoding == C.ENCODING_INVALID) { + throw new ParserException("Unsupported PCM bit depth: " + audioBitDepth); + } + break; + case CODEC_ID_SUBRIP: + mimeType = MimeTypes.APPLICATION_SUBRIP; + break; + case CODEC_ID_VOBSUB: + mimeType = MimeTypes.APPLICATION_VOBSUB; + initializationData = Collections.singletonList(codecPrivate); + break; + case CODEC_ID_PGS: + mimeType = MimeTypes.APPLICATION_PGS; + break; + default: + throw new ParserException("Unrecognized codec identifier."); + } + + Format format; + @C.SelectionFlags int selectionFlags = 0; + selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0; + selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0; + // TODO: Consider reading the name elements of the tracks and, if present, incorporating them + // into the trackId passed when creating the formats. + if (MimeTypes.isAudio(mimeType)) { + format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, maxInputSize, channelCount, sampleRate, pcmEncoding, + initializationData, drmInitData, selectionFlags, language); + } else if (MimeTypes.isVideo(mimeType)) { + if (displayUnit == Track.DISPLAY_UNIT_PIXELS) { + displayWidth = displayWidth == Format.NO_VALUE ? width : displayWidth; + displayHeight = displayHeight == Format.NO_VALUE ? height : displayHeight; + } + float pixelWidthHeightRatio = Format.NO_VALUE; + if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) { + pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); + } + format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, + Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { + format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, selectionFlags, language, drmInitData); + } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) + || MimeTypes.APPLICATION_PGS.equals(mimeType)) { + format = Format.createImageSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, initializationData, language, drmInitData); + } else { + throw new ParserException("Unexpected MIME type."); + } + + this.output = output.track(number); + this.output.format(format); + } + + /** + * Builds initialization data for a {@link Format} from FourCC codec private data. + *

        + * VC1 is the only supported compression type. + * + * @return The initialization data for the {@link Format}, or null if the compression type is + * not VC1. + * @throws ParserException If the initialization data could not be built. + */ + private static List parseFourCcVc1Private(ParsableByteArray buffer) + throws ParserException { + try { + buffer.skipBytes(16); // size(4), width(4), height(4), planes(2), bitcount(2). + long compression = buffer.readLittleEndianUnsignedInt(); + if (compression != FOURCC_COMPRESSION_VC1) { + return null; + } + + // Search for the initialization data from the end of the BITMAPINFOHEADER. The last 20 + // bytes of which are: sizeImage(4), xPel/m (4), yPel/m (4), clrUsed(4), clrImportant(4). + int startOffset = buffer.getPosition() + 20; + byte[] bufferData = buffer.data; + for (int offset = startOffset; offset < bufferData.length - 4; offset++) { + if (bufferData[offset] == 0x00 && bufferData[offset + 1] == 0x00 + && bufferData[offset + 2] == 0x01 && bufferData[offset + 3] == 0x0F) { + // We've found the initialization data. + byte[] initializationData = Arrays.copyOfRange(bufferData, offset, bufferData.length); + return Collections.singletonList(initializationData); + } + } + + throw new ParserException("Failed to find FourCC VC1 initialization data"); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing FourCC VC1 codec private"); + } + } + + /** + * Builds initialization data for a {@link Format} from Vorbis codec private data. + * + * @return The initialization data for the {@link Format}. + * @throws ParserException If the initialization data could not be built. + */ + private static List parseVorbisCodecPrivate(byte[] codecPrivate) + throws ParserException { + try { + if (codecPrivate[0] != 0x02) { + throw new ParserException("Error parsing vorbis codec private"); + } + int offset = 1; + int vorbisInfoLength = 0; + while (codecPrivate[offset] == (byte) 0xFF) { + vorbisInfoLength += 0xFF; + offset++; + } + vorbisInfoLength += codecPrivate[offset++]; + + int vorbisSkipLength = 0; + while (codecPrivate[offset] == (byte) 0xFF) { + vorbisSkipLength += 0xFF; + offset++; + } + vorbisSkipLength += codecPrivate[offset++]; + + if (codecPrivate[offset] != 0x01) { + throw new ParserException("Error parsing vorbis codec private"); + } + byte[] vorbisInfo = new byte[vorbisInfoLength]; + System.arraycopy(codecPrivate, offset, vorbisInfo, 0, vorbisInfoLength); + offset += vorbisInfoLength; + if (codecPrivate[offset] != 0x03) { + throw new ParserException("Error parsing vorbis codec private"); + } + offset += vorbisSkipLength; + if (codecPrivate[offset] != 0x05) { + throw new ParserException("Error parsing vorbis codec private"); + } + byte[] vorbisBooks = new byte[codecPrivate.length - offset]; + System.arraycopy(codecPrivate, offset, vorbisBooks, 0, codecPrivate.length - offset); + List initializationData = new ArrayList<>(2); + initializationData.add(vorbisInfo); + initializationData.add(vorbisBooks); + return initializationData; + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing vorbis codec private"); + } + } + + /** + * Parses an MS/ACM codec private, returning whether it indicates PCM audio. + * + * @return Whether the codec private indicates PCM audio. + * @throws ParserException If a parsing error occurs. + */ + private static boolean parseMsAcmCodecPrivate(ParsableByteArray buffer) throws ParserException { + try { + int formatTag = buffer.readLittleEndianUnsignedShort(); + if (formatTag == WAVE_FORMAT_PCM) { + return true; + } else if (formatTag == WAVE_FORMAT_EXTENSIBLE) { + buffer.setPosition(WAVE_FORMAT_SIZE + 6); // unionSamples(2), channelMask(4) + return buffer.readLong() == WAVE_SUBFORMAT_PCM.getMostSignificantBits() + && buffer.readLong() == WAVE_SUBFORMAT_PCM.getLeastSignificantBits(); + } else { + return false; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing MS/ACM codec private"); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/Sniffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/Sniffer.java new file mode 100755 index 00000000000..a30db4c94b4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/Sniffer.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mkv; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * Utility class that peeks from the input stream in order to determine whether it appears to be + * compatible input for this extractor. + */ +/* package */ final class Sniffer { + + /** + * The number of bytes to search for a valid header in {@link #sniff(ExtractorInput)}. + */ + private static final int SEARCH_LENGTH = 1024; + private static final int ID_EBML = 0x1A45DFA3; + + private final ParsableByteArray scratch; + private int peekLength; + + public Sniffer() { + scratch = new ParsableByteArray(8); + } + + /** + * @see org.telegram.messenger.exoplayer2.extractor.Extractor#sniff(ExtractorInput) + */ + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + long inputLength = input.getLength(); + int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH + ? SEARCH_LENGTH : inputLength); + // Find four bytes equal to ID_EBML near the start of the input. + input.peekFully(scratch.data, 0, 4); + long tag = scratch.readUnsignedInt(); + peekLength = 4; + while (tag != ID_EBML) { + if (++peekLength == bytesToSearch) { + return false; + } + input.peekFully(scratch.data, 0, 1); + tag = (tag << 8) & 0xFFFFFF00; + tag |= scratch.data[0] & 0xFF; + } + + // Read the size of the EBML header and make sure it is within the stream. + long headerSize = readUint(input); + long headerStart = peekLength; + if (headerSize == Long.MIN_VALUE + || (inputLength != C.LENGTH_UNSET && headerStart + headerSize >= inputLength)) { + return false; + } + + // Read the payload elements in the EBML header. + while (peekLength < headerStart + headerSize) { + long id = readUint(input); + if (id == Long.MIN_VALUE) { + return false; + } + long size = readUint(input); + if (size < 0 || size > Integer.MAX_VALUE) { + return false; + } + if (size != 0) { + input.advancePeekPosition((int) size); + peekLength += size; + } + } + return peekLength == headerStart + headerSize; + } + + /** + * Peeks a variable-length unsigned EBML integer from the input. + */ + private long readUint(ExtractorInput input) throws IOException, InterruptedException { + input.peekFully(scratch.data, 0, 1); + int value = scratch.data[0] & 0xFF; + if (value == 0) { + return Long.MIN_VALUE; + } + int mask = 0x80; + int length = 0; + while ((value & mask) == 0) { + mask >>= 1; + length++; + } + value &= ~mask; + input.peekFully(scratch.data, 1, length); + for (int i = 0; i < length; i++) { + value <<= 8; + value += scratch.data[i + 1] & 0xFF; + } + peekLength += length + 1; + return value; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/VarintReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/VarintReader.java similarity index 83% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/VarintReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/VarintReader.java index 3eb2029b7c8..42165f93033 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/webm/VarintReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mkv/VarintReader.java @@ -1,7 +1,22 @@ -package org.telegram.messenger.exoplayer.extractor.webm; +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mkv; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; import java.io.EOFException; import java.io.IOException; @@ -72,7 +87,7 @@ public long readUnsignedVarint(ExtractorInput input, boolean allowEndOfInput, } int firstByte = scratch[0] & 0xFF; length = parseUnsignedVarintLength(firstByte); - if (length == -1) { + if (length == C.LENGTH_UNSET) { throw new IllegalStateException("No valid varint length mask found"); } state = STATE_READ_CONTENTS; @@ -103,10 +118,11 @@ public int getLastLength() { * Parses and the length of the varint given the first byte. * * @param firstByte First byte of the varint. - * @return Length of the varint beginning with the given byte if it was valid, -1 otherwise. + * @return Length of the varint beginning with the given byte if it was valid, + * {@link C#LENGTH_UNSET} otherwise. */ public static int parseUnsignedVarintLength(int firstByte) { - int varIntLength = -1; + int varIntLength = C.LENGTH_UNSET; for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) { if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) { varIntLength = i + 1; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/ConstantBitrateSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/ConstantBitrateSeeker.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java index 2bcba4c2e91..dd0f0f52acc 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/ConstantBitrateSeeker.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/ConstantBitrateSeeker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp3; +package org.telegram.messenger.exoplayer2.extractor.mp3; -import org.telegram.messenger.exoplayer.C; +import org.telegram.messenger.exoplayer2.C; /** * MP3 seeker that doesn't rely on metadata and seeks assuming the source has a constant bitrate. @@ -31,17 +31,17 @@ public ConstantBitrateSeeker(long firstFramePosition, int bitrate, long inputLength) { this.firstFramePosition = firstFramePosition; this.bitrate = bitrate; - durationUs = inputLength == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US : getTimeUs(inputLength); + durationUs = inputLength == C.LENGTH_UNSET ? C.TIME_UNSET : getTimeUs(inputLength); } @Override public boolean isSeekable() { - return durationUs != C.UNKNOWN_TIME_US; + return durationUs != C.TIME_UNSET; } @Override public long getPosition(long timeUs) { - return durationUs == C.UNKNOWN_TIME_US ? 0 + return durationUs == C.TIME_UNSET ? 0 : firstFramePosition + (timeUs * bitrate) / (C.MICROS_PER_SECOND * BITS_PER_BYTE); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java new file mode 100755 index 00000000000..faa4a3f04f8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp3; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.GaplessInfoHolder; +import org.telegram.messenger.exoplayer2.extractor.MpegAudioHeader; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.id3.Id3Decoder; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; + +/** + * Extracts data from an MP3 file. + */ +public final class Mp3Extractor implements Extractor { + + /** + * Factory for {@link Mp3Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Mp3Extractor()}; + } + + }; + + /** + * The maximum number of bytes to search when synchronizing, before giving up. + */ + private static final int MAX_SYNC_BYTES = 128 * 1024; + /** + * The maximum number of bytes to peek when sniffing, excluding the ID3 header, before giving up. + */ + private static final int MAX_SNIFF_BYTES = MpegAudioHeader.MAX_FRAME_SIZE_BYTES; + /** + * Maximum length of data read into {@link #scratch}. + */ + private static final int SCRATCH_LENGTH = 10; + + /** + * Mask that includes the audio header values that must match between frames. + */ + private static final int HEADER_MASK = 0xFFFE0C00; + private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); + private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); + private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); + + private final long forcedFirstSampleTimestampUs; + private final ParsableByteArray scratch; + private final MpegAudioHeader synchronizedHeader; + private final GaplessInfoHolder gaplessInfoHolder; + + // Extractor outputs. + private ExtractorOutput extractorOutput; + private TrackOutput trackOutput; + + private int synchronizedHeaderData; + + private Metadata metadata; + private Seeker seeker; + private long basisTimeUs; + private long samplesRead; + private int sampleBytesRemaining; + + /** + * Constructs a new {@link Mp3Extractor}. + */ + public Mp3Extractor() { + this(C.TIME_UNSET); + } + + /** + * Constructs a new {@link Mp3Extractor}. + * + * @param forcedFirstSampleTimestampUs A timestamp to force for the first sample, or + * {@link C#TIME_UNSET} if forcing is not required. + */ + public Mp3Extractor(long forcedFirstSampleTimestampUs) { + this.forcedFirstSampleTimestampUs = forcedFirstSampleTimestampUs; + scratch = new ParsableByteArray(SCRATCH_LENGTH); + synchronizedHeader = new MpegAudioHeader(); + gaplessInfoHolder = new GaplessInfoHolder(); + basisTimeUs = C.TIME_UNSET; + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return synchronize(input, true); + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + trackOutput = extractorOutput.track(0); + extractorOutput.endTracks(); + } + + @Override + public void seek(long position) { + synchronizedHeaderData = 0; + basisTimeUs = C.TIME_UNSET; + samplesRead = 0; + sampleBytesRemaining = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + if (synchronizedHeaderData == 0) { + try { + synchronize(input, false); + } catch (EOFException e) { + return RESULT_END_OF_INPUT; + } + } + if (seeker == null) { + seeker = setupSeeker(input); + extractorOutput.seekMap(seeker); + trackOutput.format(Format.createAudioSampleFormat(null, synchronizedHeader.mimeType, null, + Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, synchronizedHeader.channels, + synchronizedHeader.sampleRate, Format.NO_VALUE, gaplessInfoHolder.encoderDelay, + gaplessInfoHolder.encoderPadding, null, null, 0, null, metadata)); + } + return readSample(input); + } + + private int readSample(ExtractorInput extractorInput) throws IOException, InterruptedException { + if (sampleBytesRemaining == 0) { + extractorInput.resetPeekPosition(); + if (!extractorInput.peekFully(scratch.data, 0, 4, true)) { + return RESULT_END_OF_INPUT; + } + scratch.setPosition(0); + int sampleHeaderData = scratch.readInt(); + if ((sampleHeaderData & HEADER_MASK) != (synchronizedHeaderData & HEADER_MASK) + || MpegAudioHeader.getFrameSize(sampleHeaderData) == C.LENGTH_UNSET) { + // We have lost synchronization, so attempt to resynchronize starting at the next byte. + extractorInput.skipFully(1); + synchronizedHeaderData = 0; + return RESULT_CONTINUE; + } + MpegAudioHeader.populateHeader(sampleHeaderData, synchronizedHeader); + if (basisTimeUs == C.TIME_UNSET) { + basisTimeUs = seeker.getTimeUs(extractorInput.getPosition()); + if (forcedFirstSampleTimestampUs != C.TIME_UNSET) { + long embeddedFirstSampleTimestampUs = seeker.getTimeUs(0); + basisTimeUs += forcedFirstSampleTimestampUs - embeddedFirstSampleTimestampUs; + } + } + sampleBytesRemaining = synchronizedHeader.frameSize; + } + int bytesAppended = trackOutput.sampleData(extractorInput, sampleBytesRemaining, true); + if (bytesAppended == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + sampleBytesRemaining -= bytesAppended; + if (sampleBytesRemaining > 0) { + return RESULT_CONTINUE; + } + long timeUs = basisTimeUs + (samplesRead * C.MICROS_PER_SECOND / synchronizedHeader.sampleRate); + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, synchronizedHeader.frameSize, 0, + null); + samplesRead += synchronizedHeader.samplesPerFrame; + sampleBytesRemaining = 0; + return RESULT_CONTINUE; + } + + private boolean synchronize(ExtractorInput input, boolean sniffing) + throws IOException, InterruptedException { + int validFrameCount = 0; + int candidateSynchronizedHeaderData = 0; + int peekedId3Bytes = 0; + int searchedBytes = 0; + int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES; + input.resetPeekPosition(); + if (input.getPosition() == 0) { + peekId3Data(input); + peekedId3Bytes = (int) input.getPeekPosition(); + if (!sniffing) { + input.skipFully(peekedId3Bytes); + } + } + while (true) { + if (!input.peekFully(scratch.data, 0, 4, validFrameCount > 0)) { + // We reached the end of the stream but found at least one valid frame. + break; + } + scratch.setPosition(0); + int headerData = scratch.readInt(); + int frameSize; + if ((candidateSynchronizedHeaderData != 0 + && (headerData & HEADER_MASK) != (candidateSynchronizedHeaderData & HEADER_MASK)) + || (frameSize = MpegAudioHeader.getFrameSize(headerData)) == C.LENGTH_UNSET) { + // The header doesn't match the candidate header or is invalid. Try the next byte offset. + if (searchedBytes++ == searchLimitBytes) { + if (!sniffing) { + throw new ParserException("Searched too many bytes."); + } + return false; + } + validFrameCount = 0; + candidateSynchronizedHeaderData = 0; + if (sniffing) { + input.resetPeekPosition(); + input.advancePeekPosition(peekedId3Bytes + searchedBytes); + } else { + input.skipFully(1); + } + } else { + // The header matches the candidate header and/or is valid. + validFrameCount++; + if (validFrameCount == 1) { + MpegAudioHeader.populateHeader(headerData, synchronizedHeader); + candidateSynchronizedHeaderData = headerData; + } else if (validFrameCount == 4) { + break; + } + input.advancePeekPosition(frameSize - 4); + } + } + // Prepare to read the synchronized frame. + if (sniffing) { + input.skipFully(peekedId3Bytes + searchedBytes); + } else { + input.resetPeekPosition(); + } + synchronizedHeaderData = candidateSynchronizedHeaderData; + return true; + } + + /** + * Peeks ID3 data from the input, including gapless playback information. + * + * @param input The {@link ExtractorInput} from which data should be peeked. + * @throws IOException If an error occurred peeking from the input. + * @throws InterruptedException If the thread was interrupted. + */ + private void peekId3Data(ExtractorInput input) throws IOException, InterruptedException { + int peekedId3Bytes = 0; + while (true) { + input.peekFully(scratch.data, 0, Id3Decoder.ID3_HEADER_LENGTH); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != Id3Decoder.ID3_TAG) { + // Not an ID3 tag. + break; + } + scratch.skipBytes(3); // Skip major version, minor version and flags. + int framesLength = scratch.readSynchSafeInt(); + int tagLength = Id3Decoder.ID3_HEADER_LENGTH + framesLength; + + if (metadata == null) { + byte[] id3Data = new byte[tagLength]; + System.arraycopy(scratch.data, 0, id3Data, 0, Id3Decoder.ID3_HEADER_LENGTH); + input.peekFully(id3Data, Id3Decoder.ID3_HEADER_LENGTH, framesLength); + metadata = new Id3Decoder().decode(id3Data, tagLength); + if (metadata != null) { + gaplessInfoHolder.setFromMetadata(metadata); + } + } else { + input.advancePeekPosition(framesLength); + } + + peekedId3Bytes += tagLength; + } + + input.resetPeekPosition(); + input.advancePeekPosition(peekedId3Bytes); + } + + /** + * Returns a {@link Seeker} to seek using metadata read from {@code input}, which should provide + * data from the start of the first frame in the stream. On returning, the input's position will + * be set to the start of the first frame of audio. + * + * @param input The {@link ExtractorInput} from which to read. + * @throws IOException Thrown if there was an error reading from the stream. Not expected if the + * next two frames were already peeked during synchronization. + * @throws InterruptedException Thrown if reading from the stream was interrupted. Not expected if + * the next two frames were already peeked during synchronization. + * @return a {@link Seeker}. + */ + private Seeker setupSeeker(ExtractorInput input) throws IOException, InterruptedException { + // Read the first frame which may contain a Xing or VBRI header with seeking metadata. + ParsableByteArray frame = new ParsableByteArray(synchronizedHeader.frameSize); + input.peekFully(frame.data, 0, synchronizedHeader.frameSize); + + long position = input.getPosition(); + long length = input.getLength(); + int headerData = 0; + Seeker seeker = null; + + // Check if there is a Xing header. + int xingBase = (synchronizedHeader.version & 1) != 0 + ? (synchronizedHeader.channels != 1 ? 36 : 21) // MPEG 1 + : (synchronizedHeader.channels != 1 ? 21 : 13); // MPEG 2 or 2.5 + if (frame.limit() >= xingBase + 4) { + frame.setPosition(xingBase); + headerData = frame.readInt(); + } + if (headerData == XING_HEADER || headerData == INFO_HEADER) { + seeker = XingSeeker.create(synchronizedHeader, frame, position, length); + if (seeker != null && !gaplessInfoHolder.hasGaplessInfo()) { + // If there is a Xing header, read gapless playback metadata at a fixed offset. + input.resetPeekPosition(); + input.advancePeekPosition(xingBase + 141); + input.peekFully(scratch.data, 0, 3); + scratch.setPosition(0); + gaplessInfoHolder.setFromXingHeaderValue(scratch.readUnsignedInt24()); + } + input.skipFully(synchronizedHeader.frameSize); + } else if (frame.limit() >= 40) { + // Check if there is a VBRI header. + frame.setPosition(36); // MPEG audio header (4 bytes) + 32 bytes. + headerData = frame.readInt(); + if (headerData == VBRI_HEADER) { + seeker = VbriSeeker.create(synchronizedHeader, frame, position, length); + input.skipFully(synchronizedHeader.frameSize); + } + } + + if (seeker == null) { + // Repopulate the synchronized header in case we had to skip an invalid seeking header, which + // would give an invalid CBR bitrate. + input.resetPeekPosition(); + input.peekFully(scratch.data, 0, 4); + scratch.setPosition(0); + MpegAudioHeader.populateHeader(scratch.readInt(), synchronizedHeader); + seeker = new ConstantBitrateSeeker(input.getPosition(), synchronizedHeader.bitrate, length); + } + + return seeker; + } + + /** + * {@link SeekMap} that also allows mapping from position (byte offset) back to time, which can be + * used to work out the new sample basis timestamp after seeking and resynchronization. + */ + /* package */ interface Seeker extends SeekMap { + + /** + * Maps a position (byte offset) to a corresponding sample timestamp. + * + * @param position A seek position (byte offset) relative to the start of the stream. + * @return The corresponding timestamp of the next sample to be read, in microseconds. + */ + long getTimeUs(long position); + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/VbriSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/VbriSeeker.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/VbriSeeker.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/VbriSeeker.java index 0dbac52b592..8b63cc42b56 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/VbriSeeker.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/VbriSeeker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp3; +package org.telegram.messenger.exoplayer2.extractor.mp3; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.MpegAudioHeader; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.MpegAudioHeader; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; /** * MP3 seeker that uses metadata from a VBRI header. @@ -82,7 +82,7 @@ public static VbriSeeker create(MpegAudioHeader mpegAudioHeader, ParsableByteArr position += segmentSize * scale; timesUs[index] = index * durationUs / entryCount; positions[index] = - inputLength == C.LENGTH_UNBOUNDED ? position : Math.min(inputLength, position); + inputLength == C.LENGTH_UNSET ? position : Math.min(inputLength, position); } return new VbriSeeker(timesUs, positions, durationUs); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/XingSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/XingSeeker.java similarity index 93% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/XingSeeker.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/XingSeeker.java index 1476ff25800..6bc1d7e23e4 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp3/XingSeeker.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp3/XingSeeker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp3; +package org.telegram.messenger.exoplayer2.extractor.mp3; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.MpegAudioHeader; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.MpegAudioHeader; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; /** * MP3 seeker that uses metadata from a Xing header. @@ -128,7 +128,7 @@ public long getPosition(long timeUs) { } long position = Math.round((1.0 / 256) * fx * sizeBytes) + firstFramePosition; - long maximumPosition = inputLength != C.LENGTH_UNBOUNDED ? inputLength - 1 + long maximumPosition = inputLength != C.LENGTH_UNSET ? inputLength - 1 : firstFramePosition - headerSize + sizeBytes - 1; return Math.min(position, maximumPosition); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Atom.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java similarity index 92% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Atom.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java index f3f8c759c04..a30a709e0b2 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Atom.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Atom.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -52,6 +52,7 @@ public static final int TYPE_d263 = Util.getIntegerCodeForString("d263"); public static final int TYPE_mdat = Util.getIntegerCodeForString("mdat"); public static final int TYPE_mp4a = Util.getIntegerCodeForString("mp4a"); + public static final int TYPE__mp3 = Util.getIntegerCodeForString(".mp3"); public static final int TYPE_wave = Util.getIntegerCodeForString("wave"); public static final int TYPE_lpcm = Util.getIntegerCodeForString("lpcm"); public static final int TYPE_sowt = Util.getIntegerCodeForString("sowt"); @@ -117,6 +118,7 @@ public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g"); public static final int TYPE_wvtt = Util.getIntegerCodeForString("wvtt"); public static final int TYPE_stpp = Util.getIntegerCodeForString("stpp"); + public static final int TYPE_c608 = Util.getIntegerCodeForString("c608"); public static final int TYPE_samr = Util.getIntegerCodeForString("samr"); public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb"); public static final int TYPE_udta = Util.getIntegerCodeForString("udta"); @@ -125,11 +127,13 @@ public static final int TYPE_mean = Util.getIntegerCodeForString("mean"); public static final int TYPE_name = Util.getIntegerCodeForString("name"); public static final int TYPE_data = Util.getIntegerCodeForString("data"); - public static final int TYPE_emsg = Util.getIntegerCodeForString("emsg"); + public static final int TYPE_st3d = Util.getIntegerCodeForString("st3d"); + public static final int TYPE_sv3d = Util.getIntegerCodeForString("sv3d"); + public static final int TYPE_proj = Util.getIntegerCodeForString("proj"); public static final int TYPE_vp08 = Util.getIntegerCodeForString("vp08"); public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09"); public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC"); - public static final int TYPE_DASHES = Util.getIntegerCodeForString("----"); + public static final int TYPE_camm = Util.getIntegerCodeForString("camm"); public final int type; @@ -202,7 +206,7 @@ public void add(ContainerAtom atom) { } /** - * Gets the child leaf of the given type. + * Returns the child leaf of the given type. *

        * If no child exists with the given type then null is returned. If multiple children exist with * the given type then the first one to have been added is returned. @@ -222,7 +226,7 @@ public LeafAtom getLeafAtomOfType(int type) { } /** - * Gets the child container of the given type. + * Returns the child container of the given type. *

        * If no child exists with the given type then null is returned. If multiple children exist with * the given type then the first one to have been added is returned. @@ -269,8 +273,8 @@ public int getChildAtomOfTypeCount(int type) { @Override public String toString() { return getAtomTypeString(type) - + " leaves: " + Arrays.toString(leafChildren.toArray(new LeafAtom[0])) - + " containers: " + Arrays.toString(containerChildren.toArray(new ContainerAtom[0])); + + " leaves: " + Arrays.toString(leafChildren.toArray()) + + " containers: " + Arrays.toString(containerChildren.toArray()); } } @@ -296,7 +300,7 @@ public static int parseFullAtomFlags(int fullAtomInt) { * @return The corresponding four character string. */ public static String getAtomTypeString(int type) { - return "" + (char) (type >> 24) + return "" + (char) ((type >> 24) & 0xFF) + (char) ((type >> 16) & 0xFF) + (char) ((type >> 8) & 0xFF) + (char) (type & 0xFF); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java new file mode 100755 index 00000000000..7e12363046b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/AtomParsers.java @@ -0,0 +1,1287 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.audio.Ac3Util; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.extractor.GaplessInfoHolder; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.CodecSpecificDataUtil; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import org.telegram.messenger.exoplayer2.video.AvcConfig; +import org.telegram.messenger.exoplayer2.video.HevcConfig; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. + */ +/* package */ final class AtomParsers { + + private static final String TAG = "AtomParsers"; + + private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); + private static final int TYPE_soun = Util.getIntegerCodeForString("soun"); + private static final int TYPE_text = Util.getIntegerCodeForString("text"); + private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl"); + private static final int TYPE_subt = Util.getIntegerCodeForString("subt"); + private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp"); + private static final int TYPE_cenc = Util.getIntegerCodeForString("cenc"); + private static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + + /** + * Parses a trak atom (defined in 14496-12). + * + * @param trak Atom to decode. + * @param mvhd Movie header atom, used to get the timescale. + * @param duration The duration in units of the timescale declared in the mvhd atom, or + * {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. + */ + public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, + DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); + int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data); + if (trackType == C.TRACK_TYPE_UNKNOWN) { + return null; + } + + TkhdData tkhdData = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data); + if (duration == C.TIME_UNSET) { + duration = tkhdData.duration; + } + long movieTimescale = parseMvhd(mvhd.data); + long durationUs; + if (duration == C.TIME_UNSET) { + durationUs = C.TIME_UNSET; + } else { + durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, movieTimescale); + } + Atom.ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) + .getContainerAtomOfType(Atom.TYPE_stbl); + + Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); + StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, + tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); + Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); + return stsdData.format == null ? null + : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, + stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes, + stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second); + } + + /** + * Parses an stbl atom (defined in 14496-12). + * + * @param track Track to which this sample table corresponds. + * @param stblAtom stbl (sample table) atom to decode. + * @param gaplessInfoHolder Holder to populate with gapless playback information. + * @return Sample table described by the stbl atom. + * @throws ParserException If the resulting sample sequence does not contain a sync sample. + */ + public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom, + GaplessInfoHolder gaplessInfoHolder) throws ParserException { + SampleSizeBox sampleSizeBox; + Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); + if (stszAtom != null) { + sampleSizeBox = new StszSampleSizeBox(stszAtom); + } else { + Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); + if (stz2Atom == null) { + throw new ParserException("Track has no sample table size information"); + } + sampleSizeBox = new Stz2SampleSizeBox(stz2Atom); + } + + int sampleCount = sampleSizeBox.getSampleCount(); + if (sampleCount == 0) { + return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]); + } + + // Entries are byte offsets of chunks. + boolean chunkOffsetsAreLongs = false; + Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); + if (chunkOffsetsAtom == null) { + chunkOffsetsAreLongs = true; + chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); + } + ParsableByteArray chunkOffsets = chunkOffsetsAtom.data; + // Entries are (chunk number, number of samples per chunk, sample description index). + ParsableByteArray stsc = stblAtom.getLeafAtomOfType(Atom.TYPE_stsc).data; + // Entries are (number of samples, timestamp delta between those samples). + ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; + // Entries are the indices of samples that are synchronization samples. + Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); + ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; + // Entries are (number of samples, timestamp offset). + Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); + ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; + + // Prepare to read chunk information. + ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); + + // Prepare to read sample timestamps. + stts.setPosition(Atom.FULL_HEADER_SIZE); + int remainingTimestampDeltaChanges = stts.readUnsignedIntToInt() - 1; + int remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); + int timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); + + // Prepare to read sample timestamp offsets, if ctts is present. + int remainingSamplesAtTimestampOffset = 0; + int remainingTimestampOffsetChanges = 0; + int timestampOffset = 0; + if (ctts != null) { + ctts.setPosition(Atom.FULL_HEADER_SIZE); + remainingTimestampOffsetChanges = ctts.readUnsignedIntToInt(); + } + + int nextSynchronizationSampleIndex = C.INDEX_UNSET; + int remainingSynchronizationSamples = 0; + if (stss != null) { + stss.setPosition(Atom.FULL_HEADER_SIZE); + remainingSynchronizationSamples = stss.readUnsignedIntToInt(); + if (remainingSynchronizationSamples > 0) { + nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; + } else { + // Ignore empty stss boxes, which causes all samples to be treated as sync samples. + stss = null; + } + } + + // True if we can rechunk fixed-sample-size data. Note that we only rechunk raw audio. + boolean isRechunkable = sampleSizeBox.isFixedSampleSize() + && MimeTypes.AUDIO_RAW.equals(track.format.sampleMimeType) + && remainingTimestampDeltaChanges == 0 && remainingTimestampOffsetChanges == 0 + && remainingSynchronizationSamples == 0; + + long[] offsets; + int[] sizes; + int maximumSize = 0; + long[] timestamps; + int[] flags; + long timestampTimeUnits = 0; + + if (!isRechunkable) { + offsets = new long[sampleCount]; + sizes = new int[sampleCount]; + timestamps = new long[sampleCount]; + flags = new int[sampleCount]; + long offset = 0; + int remainingSamplesInChunk = 0; + + for (int i = 0; i < sampleCount; i++) { + // Advance to the next chunk if necessary. + while (remainingSamplesInChunk == 0) { + Assertions.checkState(chunkIterator.moveNext()); + offset = chunkIterator.offset; + remainingSamplesInChunk = chunkIterator.numSamples; + } + + // Add on the timestamp offset if ctts is present. + if (ctts != null) { + while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) { + remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt(); + // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers + // in version 0 ctts boxes, however some streams violate the spec and use signed + // integers instead. It's safe to always decode sample offsets as signed integers here, + // because unsigned integers will still be parsed correctly (unless their top bit is + // set, which is never true in practice because sample offsets are always small). + timestampOffset = ctts.readInt(); + remainingTimestampOffsetChanges--; + } + remainingSamplesAtTimestampOffset--; + } + + offsets[i] = offset; + sizes[i] = sampleSizeBox.readNextSampleSize(); + if (sizes[i] > maximumSize) { + maximumSize = sizes[i]; + } + timestamps[i] = timestampTimeUnits + timestampOffset; + + // All samples are synchronization samples if the stss is not present. + flags[i] = stss == null ? C.BUFFER_FLAG_KEY_FRAME : 0; + if (i == nextSynchronizationSampleIndex) { + flags[i] = C.BUFFER_FLAG_KEY_FRAME; + remainingSynchronizationSamples--; + if (remainingSynchronizationSamples > 0) { + nextSynchronizationSampleIndex = stss.readUnsignedIntToInt() - 1; + } + } + + // Add on the duration of this sample. + timestampTimeUnits += timestampDeltaInTimeUnits; + remainingSamplesAtTimestampDelta--; + if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) { + remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); + timestampDeltaInTimeUnits = stts.readUnsignedIntToInt(); + remainingTimestampDeltaChanges--; + } + + offset += sizes[i]; + remainingSamplesInChunk--; + } + + Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0); + // Remove trailing ctts entries with 0-valued sample counts. + while (remainingTimestampOffsetChanges > 0) { + Assertions.checkArgument(ctts.readUnsignedIntToInt() == 0); + ctts.readInt(); // Ignore offset. + remainingTimestampOffsetChanges--; + } + + // If the stbl's child boxes are not consistent the container is malformed, but the stream may + // still be playable. + if (remainingSynchronizationSamples != 0 || remainingSamplesAtTimestampDelta != 0 + || remainingSamplesInChunk != 0 || remainingTimestampDeltaChanges != 0) { + Log.w(TAG, "Inconsistent stbl box for track " + track.id + + ": remainingSynchronizationSamples " + remainingSynchronizationSamples + + ", remainingSamplesAtTimestampDelta " + remainingSamplesAtTimestampDelta + + ", remainingSamplesInChunk " + remainingSamplesInChunk + + ", remainingTimestampDeltaChanges " + remainingTimestampDeltaChanges); + } + } else { + long[] chunkOffsetsBytes = new long[chunkIterator.length]; + int[] chunkSampleCounts = new int[chunkIterator.length]; + while (chunkIterator.moveNext()) { + chunkOffsetsBytes[chunkIterator.index] = chunkIterator.offset; + chunkSampleCounts[chunkIterator.index] = chunkIterator.numSamples; + } + int fixedSampleSize = sampleSizeBox.readNextSampleSize(); + FixedSampleSizeRechunker.Results rechunkedResults = FixedSampleSizeRechunker.rechunk( + fixedSampleSize, chunkOffsetsBytes, chunkSampleCounts, timestampDeltaInTimeUnits); + offsets = rechunkedResults.offsets; + sizes = rechunkedResults.sizes; + maximumSize = rechunkedResults.maximumSize; + timestamps = rechunkedResults.timestamps; + flags = rechunkedResults.flags; + } + + if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) { + // There is no edit list, or we are ignoring it as we already have gapless metadata to apply. + // This implementation does not support applying both gapless metadata and an edit list. + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + + // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a + // sync sample after reordering are not supported. Partial audio sample truncation is only + // supported in edit lists with one edit that removes less than one sample from the start/end of + // the track, for gapless audio playback. This implementation handles simple discarding/delaying + // of samples. The extractor may place further restrictions on what edited streams are playable. + + if (track.editListDurations.length == 1 && track.type == C.TRACK_TYPE_AUDIO + && timestamps.length >= 2) { + // Handle the edit by setting gapless playback metadata, if possible. This implementation + // assumes that only one "roll" sample is needed, which is the case for AAC, so the start/end + // points of the edit must lie within the first/last samples respectively. + long editStartTime = track.editListMediaTimes[0]; + long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], + track.timescale, track.movieTimescale); + long lastSampleEndTime = timestampTimeUnits; + if (timestamps[0] <= editStartTime && editStartTime < timestamps[1] + && timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) { + long paddingTimeUnits = lastSampleEndTime - editEndTime; + long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], + track.format.sampleRate, track.timescale); + long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits, + track.format.sampleRate, track.timescale); + if ((encoderDelay != 0 || encoderPadding != 0) && encoderDelay <= Integer.MAX_VALUE + && encoderPadding <= Integer.MAX_VALUE) { + gaplessInfoHolder.encoderDelay = (int) encoderDelay; + gaplessInfoHolder.encoderPadding = (int) encoderPadding; + Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale); + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + } + } + + if (track.editListDurations.length == 1 && track.editListDurations[0] == 0) { + // The current version of the spec leaves handling of an edit with zero segment_duration in + // unfragmented files open to interpretation. We handle this as a special case and include all + // samples in the edit. + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0], + C.MICROS_PER_SECOND, track.timescale); + } + return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags); + } + + // Count the number of samples after applying edits. + int editedSampleCount = 0; + int nextSampleIndex = 0; + boolean copyMetadata = false; + for (int i = 0; i < track.editListDurations.length; i++) { + long mediaTime = track.editListMediaTimes[i]; + if (mediaTime != -1) { + long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale, + track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); + int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, true, false); + editedSampleCount += endIndex - startIndex; + copyMetadata |= nextSampleIndex != startIndex; + nextSampleIndex = endIndex; + } + } + copyMetadata |= editedSampleCount != sampleCount; + + // Calculate edited sample timestamps and update the corresponding metadata arrays. + long[] editedOffsets = copyMetadata ? new long[editedSampleCount] : offsets; + int[] editedSizes = copyMetadata ? new int[editedSampleCount] : sizes; + int editedMaximumSize = copyMetadata ? 0 : maximumSize; + int[] editedFlags = copyMetadata ? new int[editedSampleCount] : flags; + long[] editedTimestamps = new long[editedSampleCount]; + long pts = 0; + int sampleIndex = 0; + for (int i = 0; i < track.editListDurations.length; i++) { + long mediaTime = track.editListMediaTimes[i]; + long duration = track.editListDurations[i]; + if (mediaTime != -1) { + long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale, + track.movieTimescale); + int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true); + int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, true, false); + if (copyMetadata) { + int count = endIndex - startIndex; + System.arraycopy(offsets, startIndex, editedOffsets, sampleIndex, count); + System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count); + System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count); + } + for (int j = startIndex; j < endIndex; j++) { + long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale); + long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime, + C.MICROS_PER_SECOND, track.timescale); + editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs; + if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) { + editedMaximumSize = sizes[j]; + } + sampleIndex++; + } + } + pts += duration; + } + + boolean hasSyncSample = false; + for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) { + hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0; + } + if (!hasSyncSample) { + throw new ParserException("The edited sample sequence does not contain a sync sample."); + } + + return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps, + editedFlags); + } + + /** + * Parses a udta atom. + * + * @param udtaAtom The udta (user data) atom to decode. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return Parsed metadata, or null. + */ + public static Metadata parseUdta(Atom.LeafAtom udtaAtom, boolean isQuickTime) { + if (isQuickTime) { + // Meta boxes are regular boxes rather than full boxes in QuickTime. For now, don't try and + // decode one. + return null; + } + ParsableByteArray udtaData = udtaAtom.data; + udtaData.setPosition(Atom.HEADER_SIZE); + while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) { + int atomPosition = udtaData.getPosition(); + int atomSize = udtaData.readInt(); + int atomType = udtaData.readInt(); + if (atomType == Atom.TYPE_meta) { + udtaData.setPosition(atomPosition); + return parseMetaAtom(udtaData, atomPosition + atomSize); + } + udtaData.skipBytes(atomSize - Atom.HEADER_SIZE); + } + return null; + } + + private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) { + meta.skipBytes(Atom.FULL_HEADER_SIZE); + while (meta.getPosition() < limit) { + int atomPosition = meta.getPosition(); + int atomSize = meta.readInt(); + int atomType = meta.readInt(); + if (atomType == Atom.TYPE_ilst) { + meta.setPosition(atomPosition); + return parseIlst(meta, atomPosition + atomSize); + } + meta.skipBytes(atomSize - Atom.HEADER_SIZE); + } + return null; + } + + private static Metadata parseIlst(ParsableByteArray ilst, int limit) { + ilst.skipBytes(Atom.HEADER_SIZE); + ArrayList entries = new ArrayList<>(); + while (ilst.getPosition() < limit) { + Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); + if (entry != null) { + entries.add(entry); + } + } + return entries.isEmpty() ? null : new Metadata(entries); + } + + /** + * Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. + * + * @param mvhd Contents of the mvhd atom to be parsed. + * @return Timescale for the movie. + */ + private static long parseMvhd(ParsableByteArray mvhd) { + mvhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mvhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + mvhd.skipBytes(version == 0 ? 8 : 16); + return mvhd.readUnsignedInt(); + } + + /** + * Parses a tkhd atom (defined in 14496-12). + * + * @return An object containing the parsed data. + */ + private static TkhdData parseTkhd(ParsableByteArray tkhd) { + tkhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = tkhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + + tkhd.skipBytes(version == 0 ? 8 : 16); + int trackId = tkhd.readInt(); + + tkhd.skipBytes(4); + boolean durationUnknown = true; + int durationPosition = tkhd.getPosition(); + int durationByteCount = version == 0 ? 4 : 8; + for (int i = 0; i < durationByteCount; i++) { + if (tkhd.data[durationPosition + i] != -1) { + durationUnknown = false; + break; + } + } + long duration; + if (durationUnknown) { + tkhd.skipBytes(durationByteCount); + duration = C.TIME_UNSET; + } else { + duration = version == 0 ? tkhd.readUnsignedInt() : tkhd.readUnsignedLongToLong(); + if (duration == 0) { + // 0 duration normally indicates that the file is fully fragmented (i.e. all of the media + // samples are in fragments). Treat as unknown. + duration = C.TIME_UNSET; + } + } + + tkhd.skipBytes(16); + int a00 = tkhd.readInt(); + int a01 = tkhd.readInt(); + tkhd.skipBytes(4); + int a10 = tkhd.readInt(); + int a11 = tkhd.readInt(); + + int rotationDegrees; + int fixedOne = 65536; + if (a00 == 0 && a01 == fixedOne && a10 == -fixedOne && a11 == 0) { + rotationDegrees = 90; + } else if (a00 == 0 && a01 == -fixedOne && a10 == fixedOne && a11 == 0) { + rotationDegrees = 270; + } else if (a00 == -fixedOne && a01 == 0 && a10 == 0 && a11 == -fixedOne) { + rotationDegrees = 180; + } else { + // Only 0, 90, 180 and 270 are supported. Treat anything else as 0. + rotationDegrees = 0; + } + + return new TkhdData(trackId, duration, rotationDegrees); + } + + /** + * Parses an hdlr atom. + * + * @param hdlr The hdlr atom to decode. + * @return The track type. + */ + private static int parseHdlr(ParsableByteArray hdlr) { + hdlr.setPosition(Atom.FULL_HEADER_SIZE + 4); + int trackType = hdlr.readInt(); + if (trackType == TYPE_soun) { + return C.TRACK_TYPE_AUDIO; + } else if (trackType == TYPE_vide) { + return C.TRACK_TYPE_VIDEO; + } else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt + || trackType == TYPE_clcp) { + return C.TRACK_TYPE_TEXT; + } else if (trackType == TYPE_meta) { + return C.TRACK_TYPE_METADATA; + } else { + return C.TRACK_TYPE_UNKNOWN; + } + } + + /** + * Parses an mdhd atom (defined in 14496-12). + * + * @param mdhd The mdhd atom to decode. + * @return A pair consisting of the media timescale defined as the number of time units that pass + * in one second, and the language code. + */ + private static Pair parseMdhd(ParsableByteArray mdhd) { + mdhd.setPosition(Atom.HEADER_SIZE); + int fullAtom = mdhd.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + mdhd.skipBytes(version == 0 ? 8 : 16); + long timescale = mdhd.readUnsignedInt(); + mdhd.skipBytes(version == 0 ? 4 : 8); + int languageCode = mdhd.readUnsignedShort(); + String language = "" + (char) (((languageCode >> 10) & 0x1F) + 0x60) + + (char) (((languageCode >> 5) & 0x1F) + 0x60) + + (char) (((languageCode) & 0x1F) + 0x60); + return Pair.create(timescale, language); + } + + /** + * Parses a stsd atom (defined in 14496-12). + * + * @param stsd The stsd atom to decode. + * @param trackId The track's identifier in its container. + * @param rotationDegrees The rotation of the track in degrees. + * @param language The language of the track. + * @param drmInitData {@link DrmInitData} to be included in the format. + * @param isQuickTime True for QuickTime media. False otherwise. + * @return An object containing the parsed data. + */ + private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees, + String language, DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + stsd.setPosition(Atom.FULL_HEADER_SIZE); + int numberOfEntries = stsd.readInt(); + StsdData out = new StsdData(numberOfEntries); + for (int i = 0; i < numberOfEntries; i++) { + int childStartPosition = stsd.getPosition(); + int childAtomSize = stsd.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = stsd.readInt(); + if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 + || childAtomType == Atom.TYPE_encv || childAtomType == Atom.TYPE_mp4v + || childAtomType == Atom.TYPE_hvc1 || childAtomType == Atom.TYPE_hev1 + || childAtomType == Atom.TYPE_s263 || childAtomType == Atom.TYPE_vp08 + || childAtomType == Atom.TYPE_vp09) { + parseVideoSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + rotationDegrees, drmInitData, out, i); + } else if (childAtomType == Atom.TYPE_mp4a || childAtomType == Atom.TYPE_enca + || childAtomType == Atom.TYPE_ac_3 || childAtomType == Atom.TYPE_ec_3 + || childAtomType == Atom.TYPE_dtsc || childAtomType == Atom.TYPE_dtse + || childAtomType == Atom.TYPE_dtsh || childAtomType == Atom.TYPE_dtsl + || childAtomType == Atom.TYPE_samr || childAtomType == Atom.TYPE_sawb + || childAtomType == Atom.TYPE_lpcm || childAtomType == Atom.TYPE_sowt + || childAtomType == Atom.TYPE__mp3) { + parseAudioSampleEntry(stsd, childAtomType, childStartPosition, childAtomSize, trackId, + language, isQuickTime, drmInitData, out, i); + } else if (childAtomType == Atom.TYPE_TTML) { + out.format = Format.createTextSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData); + } else if (childAtomType == Atom.TYPE_tx3g) { + out.format = Format.createTextSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_TX3G, null, Format.NO_VALUE, 0, language, drmInitData); + } else if (childAtomType == Atom.TYPE_wvtt) { + out.format = Format.createTextSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_MP4VTT, null, Format.NO_VALUE, 0, language, drmInitData); + } else if (childAtomType == Atom.TYPE_stpp) { + out.format = Format.createTextSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData, + 0 /* subsample timing is absolute */); + } else if (childAtomType == Atom.TYPE_c608) { + out.format = Format.createTextSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData); + out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; + } else if (childAtomType == Atom.TYPE_camm) { + out.format = Format.createSampleFormat(Integer.toString(trackId), + MimeTypes.APPLICATION_CAMERA_MOTION, null, Format.NO_VALUE, drmInitData); + } + stsd.setPosition(childStartPosition + childAtomSize); + } + return out; + } + + private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, + int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, + int entryIndex) throws ParserException { + parent.setPosition(position + Atom.HEADER_SIZE); + + parent.skipBytes(24); + int width = parent.readUnsignedShort(); + int height = parent.readUnsignedShort(); + boolean pixelWidthHeightRatioFromPasp = false; + float pixelWidthHeightRatio = 1; + parent.skipBytes(50); + + int childPosition = parent.getPosition(); + if (atomType == Atom.TYPE_encv) { + atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + parent.setPosition(childPosition); + } + + List initializationData = null; + String mimeType = null; + byte[] projectionData = null; + @C.StereoMode + int stereoMode = Format.NO_VALUE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childStartPosition = parent.getPosition(); + int childAtomSize = parent.readInt(); + if (childAtomSize == 0 && parent.getPosition() - position == size) { + // Handle optional terminating four zero bytes in MOV files. + break; + } + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_avcC) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H264; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + AvcConfig avcConfig = AvcConfig.parse(parent); + initializationData = avcConfig.initializationData; + out.nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; + if (!pixelWidthHeightRatioFromPasp) { + pixelWidthHeightRatio = avcConfig.pixelWidthAspectRatio; + } + } else if (childAtomType == Atom.TYPE_hvcC) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H265; + parent.setPosition(childStartPosition + Atom.HEADER_SIZE); + HevcConfig hevcConfig = HevcConfig.parse(parent); + initializationData = hevcConfig.initializationData; + out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; + } else if (childAtomType == Atom.TYPE_vpcC) { + Assertions.checkState(mimeType == null); + mimeType = (atomType == Atom.TYPE_vp08) ? MimeTypes.VIDEO_VP8 : MimeTypes.VIDEO_VP9; + } else if (childAtomType == Atom.TYPE_d263) { + Assertions.checkState(mimeType == null); + mimeType = MimeTypes.VIDEO_H263; + } else if (childAtomType == Atom.TYPE_esds) { + Assertions.checkState(mimeType == null); + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, childStartPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = Collections.singletonList(mimeTypeAndInitializationData.second); + } else if (childAtomType == Atom.TYPE_pasp) { + pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); + pixelWidthHeightRatioFromPasp = true; + } else if (childAtomType == Atom.TYPE_sv3d) { + projectionData = parseProjFromParent(parent, childStartPosition, childAtomSize); + } else if (childAtomType == Atom.TYPE_st3d) { + int version = parent.readUnsignedByte(); + parent.skipBytes(3); // Flags. + if (version == 0) { + int layout = parent.readUnsignedByte(); + switch (layout) { + case 0: + stereoMode = C.STEREO_MODE_MONO; + break; + case 1: + stereoMode = C.STEREO_MODE_TOP_BOTTOM; + break; + case 2: + stereoMode = C.STEREO_MODE_LEFT_RIGHT; + break; + default: + break; + } + } + } + childPosition += childAtomSize; + } + + // If the media type was not recognized, ignore the track. + if (mimeType == null) { + return; + } + + out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData, + rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); + } + + /** + * Parses the edts atom (defined in 14496-12 subsection 8.6.5). + * + * @param edtsAtom edts (edit box) atom to decode. + * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are + * not present. + */ + private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { + Atom.LeafAtom elst; + if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { + return Pair.create(null, null); + } + ParsableByteArray elstData = elst.data; + elstData.setPosition(Atom.HEADER_SIZE); + int fullAtom = elstData.readInt(); + int version = Atom.parseFullAtomVersion(fullAtom); + int entryCount = elstData.readUnsignedIntToInt(); + long[] editListDurations = new long[entryCount]; + long[] editListMediaTimes = new long[entryCount]; + for (int i = 0; i < entryCount; i++) { + editListDurations[i] = + version == 1 ? elstData.readUnsignedLongToLong() : elstData.readUnsignedInt(); + editListMediaTimes[i] = version == 1 ? elstData.readLong() : elstData.readInt(); + int mediaRateInteger = elstData.readShort(); + if (mediaRateInteger != 1) { + // The extractor does not handle dwell edits (mediaRateInteger == 0). + throw new IllegalArgumentException("Unsupported media rate."); + } + elstData.skipBytes(2); + } + return Pair.create(editListDurations, editListMediaTimes); + } + + private static float parsePaspFromParent(ParsableByteArray parent, int position) { + parent.setPosition(position + Atom.HEADER_SIZE); + int hSpacing = parent.readUnsignedIntToInt(); + int vSpacing = parent.readUnsignedIntToInt(); + return (float) hSpacing / vSpacing; + } + + private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, + int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData, + StsdData out, int entryIndex) { + parent.setPosition(position + Atom.HEADER_SIZE); + + int quickTimeSoundDescriptionVersion = 0; + if (isQuickTime) { + parent.skipBytes(8); + quickTimeSoundDescriptionVersion = parent.readUnsignedShort(); + parent.skipBytes(6); + } else { + parent.skipBytes(16); + } + + int channelCount; + int sampleRate; + + if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) { + channelCount = parent.readUnsignedShort(); + parent.skipBytes(6); // sampleSize, compressionId, packetSize. + sampleRate = parent.readUnsignedFixedPoint1616(); + + if (quickTimeSoundDescriptionVersion == 1) { + parent.skipBytes(16); + } + } else if (quickTimeSoundDescriptionVersion == 2) { + parent.skipBytes(16); // always[3,16,Minus2,0,65536], sizeOfStructOnly + + sampleRate = (int) Math.round(parent.readDouble()); + channelCount = parent.readUnsignedIntToInt(); + + // Skip always7F000000, sampleSize, formatSpecificFlags, constBytesPerAudioPacket, + // constLPCMFramesPerAudioPacket. + parent.skipBytes(20); + } else { + // Unsupported version. + return; + } + + int childPosition = parent.getPosition(); + if (atomType == Atom.TYPE_enca) { + atomType = parseSampleEntryEncryptionData(parent, position, size, out, entryIndex); + parent.setPosition(childPosition); + } + + // If the atom type determines a MIME type, set it immediately. + String mimeType = null; + if (atomType == Atom.TYPE_ac_3) { + mimeType = MimeTypes.AUDIO_AC3; + } else if (atomType == Atom.TYPE_ec_3) { + mimeType = MimeTypes.AUDIO_E_AC3; + } else if (atomType == Atom.TYPE_dtsc) { + mimeType = MimeTypes.AUDIO_DTS; + } else if (atomType == Atom.TYPE_dtsh || atomType == Atom.TYPE_dtsl) { + mimeType = MimeTypes.AUDIO_DTS_HD; + } else if (atomType == Atom.TYPE_dtse) { + mimeType = MimeTypes.AUDIO_DTS_EXPRESS; + } else if (atomType == Atom.TYPE_samr) { + mimeType = MimeTypes.AUDIO_AMR_NB; + } else if (atomType == Atom.TYPE_sawb) { + mimeType = MimeTypes.AUDIO_AMR_WB; + } else if (atomType == Atom.TYPE_lpcm || atomType == Atom.TYPE_sowt) { + mimeType = MimeTypes.AUDIO_RAW; + } else if (atomType == Atom.TYPE__mp3) { + mimeType = MimeTypes.AUDIO_MPEG; + } + + byte[] initializationData = null; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) { + int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition + : findEsdsPosition(parent, childPosition, childAtomSize); + if (esdsAtomPosition != C.POSITION_UNSET) { + Pair mimeTypeAndInitializationData = + parseEsdsFromParent(parent, esdsAtomPosition); + mimeType = mimeTypeAndInitializationData.first; + initializationData = mimeTypeAndInitializationData.second; + if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + // TODO: Do we really need to do this? See [Internal: b/10903778] + // Update sampleRate and channelCount from the AudioSpecificConfig initialization data. + Pair audioSpecificConfig = + CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData); + sampleRate = audioSpecificConfig.first; + channelCount = audioSpecificConfig.second; + } + } + } else if (childAtomType == Atom.TYPE_dac3) { + parent.setPosition(Atom.HEADER_SIZE + childPosition); + out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language, + drmInitData); + } else if (childAtomType == Atom.TYPE_dec3) { + parent.setPosition(Atom.HEADER_SIZE + childPosition); + out.format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, + drmInitData); + } else if (childAtomType == Atom.TYPE_ddts) { + out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, null, drmInitData, 0, + language); + } + childPosition += childAtomSize; + } + + if (out.format == null && mimeType != null) { + // TODO: Determine the correct PCM encoding. + @C.PcmEncoding int pcmEncoding = + MimeTypes.AUDIO_RAW.equals(mimeType) ? C.ENCODING_PCM_16BIT : Format.NO_VALUE; + out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding, + initializationData == null ? null : Collections.singletonList(initializationData), + drmInitData, 0, language); + } + } + + /** + * Returns the position of the esds box within a parent, or {@link C#POSITION_UNSET} if no esds + * box is found + */ + private static int findEsdsPosition(ParsableByteArray parent, int position, int size) { + int childAtomPosition = parent.getPosition(); + while (childAtomPosition - position < size) { + parent.setPosition(childAtomPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childType = parent.readInt(); + if (childType == Atom.TYPE_esds) { + return childAtomPosition; + } + childAtomPosition += childAtomSize; + } + return C.POSITION_UNSET; + } + + /** + * Returns codec-specific initialization data contained in an esds box. + */ + private static Pair parseEsdsFromParent(ParsableByteArray parent, int position) { + parent.setPosition(position + Atom.HEADER_SIZE + 4); + // Start of the ES_Descriptor (defined in 14496-1) + parent.skipBytes(1); // ES_Descriptor tag + parseExpandableClassSize(parent); + parent.skipBytes(2); // ES_ID + + int flags = parent.readUnsignedByte(); + if ((flags & 0x80 /* streamDependenceFlag */) != 0) { + parent.skipBytes(2); + } + if ((flags & 0x40 /* URL_Flag */) != 0) { + parent.skipBytes(parent.readUnsignedShort()); + } + if ((flags & 0x20 /* OCRstreamFlag */) != 0) { + parent.skipBytes(2); + } + + // Start of the DecoderConfigDescriptor (defined in 14496-1) + parent.skipBytes(1); // DecoderConfigDescriptor tag + parseExpandableClassSize(parent); + + // Set the MIME type based on the object type indication (14496-1 table 5). + int objectTypeIndication = parent.readUnsignedByte(); + String mimeType; + switch (objectTypeIndication) { + case 0x6B: + mimeType = MimeTypes.AUDIO_MPEG; + return Pair.create(mimeType, null); + case 0x20: + mimeType = MimeTypes.VIDEO_MP4V; + break; + case 0x21: + mimeType = MimeTypes.VIDEO_H264; + break; + case 0x23: + mimeType = MimeTypes.VIDEO_H265; + break; + case 0x40: + case 0x66: + case 0x67: + case 0x68: + mimeType = MimeTypes.AUDIO_AAC; + break; + case 0xA5: + mimeType = MimeTypes.AUDIO_AC3; + break; + case 0xA6: + mimeType = MimeTypes.AUDIO_E_AC3; + break; + case 0xA9: + case 0xAC: + mimeType = MimeTypes.AUDIO_DTS; + return Pair.create(mimeType, null); + case 0xAA: + case 0xAB: + mimeType = MimeTypes.AUDIO_DTS_HD; + return Pair.create(mimeType, null); + default: + mimeType = null; + break; + } + + parent.skipBytes(12); + + // Start of the AudioSpecificConfig. + parent.skipBytes(1); // AudioSpecificConfig tag + int initializationDataSize = parseExpandableClassSize(parent); + byte[] initializationData = new byte[initializationDataSize]; + parent.readBytes(initializationData, 0, initializationDataSize); + return Pair.create(mimeType, initializationData); + } + + /** + * Parses encryption data from an audio/video sample entry, populating {@code out} and returning + * the unencrypted atom type, or 0 if no common encryption sinf atom was present. + */ + private static int parseSampleEntryEncryptionData(ParsableByteArray parent, int position, + int size, StsdData out, int entryIndex) { + int childPosition = parent.getPosition(); + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_sinf) { + Pair result = parseSinfFromParent(parent, childPosition, + childAtomSize); + if (result != null) { + out.trackEncryptionBoxes[entryIndex] = result.second; + return result.first; + } + } + childPosition += childAtomSize; + } + // This enca/encv box does not have a data format so return an invalid atom type. + return 0; + } + + private static Pair parseSinfFromParent(ParsableByteArray parent, + int position, int size) { + int childPosition = position + Atom.HEADER_SIZE; + + boolean isCencScheme = false; + TrackEncryptionBox trackEncryptionBox = null; + Integer dataFormat = null; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_frma) { + dataFormat = parent.readInt(); + } else if (childAtomType == Atom.TYPE_schm) { + parent.skipBytes(4); + isCencScheme = parent.readInt() == TYPE_cenc; + } else if (childAtomType == Atom.TYPE_schi) { + trackEncryptionBox = parseSchiFromParent(parent, childPosition, childAtomSize); + } + childPosition += childAtomSize; + } + + if (isCencScheme) { + Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); + Assertions.checkArgument(trackEncryptionBox != null, "schi->tenc atom is mandatory"); + return Pair.create(dataFormat, trackEncryptionBox); + } else { + return null; + } + } + + private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, + int size) { + int childPosition = position + Atom.HEADER_SIZE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_tenc) { + parent.skipBytes(6); + boolean defaultIsEncrypted = parent.readUnsignedByte() == 1; + int defaultInitVectorSize = parent.readUnsignedByte(); + byte[] defaultKeyId = new byte[16]; + parent.readBytes(defaultKeyId, 0, defaultKeyId.length); + return new TrackEncryptionBox(defaultIsEncrypted, defaultInitVectorSize, defaultKeyId); + } + childPosition += childAtomSize; + } + return null; + } + + /** + * Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media + */ + private static byte[] parseProjFromParent(ParsableByteArray parent, int position, int size) { + int childPosition = position + Atom.HEADER_SIZE; + while (childPosition - position < size) { + parent.setPosition(childPosition); + int childAtomSize = parent.readInt(); + int childAtomType = parent.readInt(); + if (childAtomType == Atom.TYPE_proj) { + return Arrays.copyOfRange(parent.data, childPosition, childPosition + childAtomSize); + } + childPosition += childAtomSize; + } + return null; + } + + /** + * Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3. + */ + private static int parseExpandableClassSize(ParsableByteArray data) { + int currentByte = data.readUnsignedByte(); + int size = currentByte & 0x7F; + while ((currentByte & 0x80) == 0x80) { + currentByte = data.readUnsignedByte(); + size = (size << 7) | (currentByte & 0x7F); + } + return size; + } + + private AtomParsers() { + // Prevent instantiation. + } + + private static final class ChunkIterator { + + public final int length; + + public int index; + public int numSamples; + public long offset; + + private final boolean chunkOffsetsAreLongs; + private final ParsableByteArray chunkOffsets; + private final ParsableByteArray stsc; + + private int nextSamplesPerChunkChangeIndex; + private int remainingSamplesPerChunkChanges; + + public ChunkIterator(ParsableByteArray stsc, ParsableByteArray chunkOffsets, + boolean chunkOffsetsAreLongs) { + this.stsc = stsc; + this.chunkOffsets = chunkOffsets; + this.chunkOffsetsAreLongs = chunkOffsetsAreLongs; + chunkOffsets.setPosition(Atom.FULL_HEADER_SIZE); + length = chunkOffsets.readUnsignedIntToInt(); + stsc.setPosition(Atom.FULL_HEADER_SIZE); + remainingSamplesPerChunkChanges = stsc.readUnsignedIntToInt(); + Assertions.checkState(stsc.readInt() == 1, "first_chunk must be 1"); + index = C.INDEX_UNSET; + } + + public boolean moveNext() { + if (++index == length) { + return false; + } + offset = chunkOffsetsAreLongs ? chunkOffsets.readUnsignedLongToLong() + : chunkOffsets.readUnsignedInt(); + if (index == nextSamplesPerChunkChangeIndex) { + numSamples = stsc.readUnsignedIntToInt(); + stsc.skipBytes(4); // Skip sample_description_index + nextSamplesPerChunkChangeIndex = --remainingSamplesPerChunkChanges > 0 + ? (stsc.readUnsignedIntToInt() - 1) : C.INDEX_UNSET; + } + return true; + } + + } + + /** + * Holds data parsed from a tkhd atom. + */ + private static final class TkhdData { + + private final int id; + private final long duration; + private final int rotationDegrees; + + public TkhdData(int id, long duration, int rotationDegrees) { + this.id = id; + this.duration = duration; + this.rotationDegrees = rotationDegrees; + } + + } + + /** + * Holds data parsed from an stsd atom and its children. + */ + private static final class StsdData { + + public final TrackEncryptionBox[] trackEncryptionBoxes; + + public Format format; + public int nalUnitLengthFieldLength; + @Track.Transformation + public int requiredSampleTransformation; + + public StsdData(int numberOfEntries) { + trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries]; + requiredSampleTransformation = Track.TRANSFORMATION_NONE; + } + + } + + /** + * A box containing sample sizes (e.g. stsz, stz2). + */ + private interface SampleSizeBox { + + /** + * Returns the number of samples. + */ + int getSampleCount(); + + /** + * Returns the size for the next sample. + */ + int readNextSampleSize(); + + /** + * Returns whether samples have a fixed size. + */ + boolean isFixedSampleSize(); + + } + + /** + * An stsz sample size box. + */ + /* package */ static final class StszSampleSizeBox implements SampleSizeBox { + + private final int fixedSampleSize; + private final int sampleCount; + private final ParsableByteArray data; + + public StszSampleSizeBox(Atom.LeafAtom stszAtom) { + data = stszAtom.data; + data.setPosition(Atom.FULL_HEADER_SIZE); + fixedSampleSize = data.readUnsignedIntToInt(); + sampleCount = data.readUnsignedIntToInt(); + } + + @Override + public int getSampleCount() { + return sampleCount; + } + + @Override + public int readNextSampleSize() { + return fixedSampleSize == 0 ? data.readUnsignedIntToInt() : fixedSampleSize; + } + + @Override + public boolean isFixedSampleSize() { + return fixedSampleSize != 0; + } + + } + + /** + * An stz2 sample size box. + */ + /* package */ static final class Stz2SampleSizeBox implements SampleSizeBox { + + private final ParsableByteArray data; + private final int sampleCount; + private final int fieldSize; // Can be 4, 8, or 16. + + // Used only if fieldSize == 4. + private int sampleIndex; + private int currentByte; + + public Stz2SampleSizeBox(Atom.LeafAtom stz2Atom) { + data = stz2Atom.data; + data.setPosition(Atom.FULL_HEADER_SIZE); + fieldSize = data.readUnsignedIntToInt() & 0x000000FF; + sampleCount = data.readUnsignedIntToInt(); + } + + @Override + public int getSampleCount() { + return sampleCount; + } + + @Override + public int readNextSampleSize() { + if (fieldSize == 8) { + return data.readUnsignedByte(); + } else if (fieldSize == 16) { + return data.readUnsignedShort(); + } else { + // fieldSize == 4. + if ((sampleIndex++ % 2) == 0) { + // Read the next byte into our cached byte when we are reading the upper bits. + currentByte = data.readUnsignedByte(); + // Read the upper bits from the byte and shift them to the lower 4 bits. + return (currentByte & 0xF0) >> 4; + } else { + // Mask out the upper 4 bits of the last byte we read. + return currentByte & 0x0F; + } + } + } + + @Override + public boolean isFixedSampleSize() { + return false; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/DefaultSampleValues.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/DefaultSampleValues.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/DefaultSampleValues.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/DefaultSampleValues.java index 3240c47a96d..f07a813333c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/DefaultSampleValues.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/DefaultSampleValues.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; /* package */ final class DefaultSampleValues { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FixedSampleSizeRechunker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FixedSampleSizeRechunker.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java index 20ec3ed0753..13406395012 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FixedSampleSizeRechunker.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FixedSampleSizeRechunker.java @@ -13,34 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Util; -/** Utilities for rechunking fixed sample size data (e.g., uncompressed audio). */ +/** + * Rechunks fixed sample size media in which every sample is a key frame (e.g. uncompressed audio). + */ /* package */ final class FixedSampleSizeRechunker { - /** Maximum number of bytes for each buffer in rechunked output. */ - private static final int MAX_SAMPLE_SIZE = 8 * 1024; - - /** Stores the results (new chunk information) of a rechunking operation. */ + /** + * The result of a rechunking operation. + */ public static final class Results { + public final long[] offsets; public final int[] sizes; public final int maximumSize; public final long[] timestamps; public final int[] flags; - public Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, int[] flags) { + private Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, int[] flags) { this.offsets = offsets; this.sizes = sizes; this.maximumSize = maximumSize; this.timestamps = timestamps; this.flags = flags; } + } + /** + * Maximum number of bytes for each buffer in rechunked output. + */ + private static final int MAX_SAMPLE_SIZE = 8 * 1024; + /** * Rechunk the given fixed sample size input to produce a new sequence of samples. * @@ -49,10 +57,7 @@ public Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, * @param chunkSampleCounts Sample counts for each of the MP4 stream's chunks. * @param timestampDeltaInTimeUnits Timestamp delta between each sample in time units. */ - public static Results rechunk( - int fixedSampleSize, - long[] chunkOffsets, - int[] chunkSampleCounts, + public static Results rechunk(int fixedSampleSize, long[] chunkOffsets, int[] chunkSampleCounts, long timestampDeltaInTimeUnits) { int maxSampleCount = MAX_SAMPLE_SIZE / fixedSampleSize; @@ -81,7 +86,7 @@ public static Results rechunk( sizes[newSampleIndex] = fixedSampleSize * bufferSampleCount; maximumSize = Math.max(maximumSize, sizes[newSampleIndex]); timestamps[newSampleIndex] = (timestampDeltaInTimeUnits * originalSampleIndex); - flags[newSampleIndex] = C.SAMPLE_FLAG_SYNC; + flags[newSampleIndex] = C.BUFFER_FLAG_KEY_FRAME; sampleOffset += sizes[newSampleIndex]; originalSampleIndex += bufferSampleCount; @@ -93,4 +98,5 @@ public static Results rechunk( return new Results(offsets, sizes, maximumSize, timestamps, flags); } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java similarity index 80% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FragmentedMp4Extractor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 7d64aacd3d3..575337f3f27 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,30 +13,36 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; +import android.support.annotation.IntDef; import android.util.Log; import android.util.Pair; import android.util.SparseArray; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.drm.DrmInitData; -import org.telegram.messenger.exoplayer.drm.DrmInitData.SchemeInitData; -import org.telegram.messenger.exoplayer.extractor.ChunkIndex; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.extractor.mp4.Atom.ContainerAtom; -import org.telegram.messenger.exoplayer.extractor.mp4.Atom.LeafAtom; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.extractor.ChunkIndex; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import org.telegram.messenger.exoplayer2.extractor.mp4.Atom.LeafAtom; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Stack; @@ -45,11 +51,30 @@ /** * Facilitates the extraction of data from the fragmented mp4 container format. */ -public class FragmentedMp4Extractor implements Extractor { +public final class FragmentedMp4Extractor implements Extractor { + + /** + * Factory for {@link FragmentedMp4Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new FragmentedMp4Extractor()}; + } + + }; private static final String TAG = "FragmentedMp4Extractor"; private static final int SAMPLE_GROUP_TYPE_seig = Util.getIntegerCodeForString("seig"); + /** + * Flags controlling the behavior of the extractor. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME, + FLAG_WORKAROUND_IGNORE_TFDT_BOX, FLAG_SIDELOADED}) + public @interface Flags {} /** * Flag to work around an issue in some video streams where every frame is marked as a sync frame. * The workaround overrides the sync frame flags in the stream, forcing them to false except for @@ -58,12 +83,10 @@ public class FragmentedMp4Extractor implements Extractor { * This flag does nothing if the stream is not a video stream. */ public static final int FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME = 1; - /** * Flag to ignore any tfdt boxes in the stream. */ public static final int FLAG_WORKAROUND_IGNORE_TFDT_BOX = 2; - /** * Flag to indicate that the {@link Track} was sideloaded, instead of being declared by the MP4 * container. @@ -81,6 +104,7 @@ public class FragmentedMp4Extractor implements Extractor { private static final int STATE_READING_SAMPLE_CONTINUE = 4; // Workarounds. + @Flags private final int flags; private final Track sideloadedTrack; @@ -92,6 +116,9 @@ public class FragmentedMp4Extractor implements Extractor { private final ParsableByteArray nalLength; private final ParsableByteArray encryptionSignalByte; + // Adjusts sample timestamps. + private final TimestampAdjuster timestampAdjuster; + // Parser state. private final ParsableByteArray atomHeader; private final byte[] extendedTypeScratch; @@ -104,6 +131,7 @@ public class FragmentedMp4Extractor implements Extractor { private ParsableByteArray atomData; private long endOfMdatPosition; + private long durationUs; private TrackBundle currentTrackBundle; private int sampleSize; private int sampleBytesWritten; @@ -112,28 +140,32 @@ public class FragmentedMp4Extractor implements Extractor { // Extractor output. private ExtractorOutput extractorOutput; - // Whether extractorOutput.seekMap has been invoked. + // Whether extractorOutput.seekMap has been called. private boolean haveOutputSeekMap; public FragmentedMp4Extractor() { - this(0); + this(0, null); } /** - * @param flags Flags to allow parsing of faulty streams. + * @param flags Flags that control the extractor's behavior. + * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. */ - public FragmentedMp4Extractor(int flags) { - this(flags, null); + public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) { + this(flags, null, timestampAdjuster); } /** - * @param flags Flags to allow parsing of faulty streams. + * @param flags Flags that control the extractor's behavior. * @param sideloadedTrack Sideloaded track information, in the case that the extractor * will not receive a moov box in the input data. + * @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed. */ - public FragmentedMp4Extractor(int flags, Track sideloadedTrack) { + public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack, + TimestampAdjuster timestampAdjuster) { this.sideloadedTrack = sideloadedTrack; this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); + this.timestampAdjuster = timestampAdjuster; atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); @@ -141,16 +173,17 @@ public FragmentedMp4Extractor(int flags, Track sideloadedTrack) { extendedTypeScratch = new byte[16]; containerAtoms = new Stack<>(); trackBundles = new SparseArray<>(); + durationUs = C.TIME_UNSET; enterReadingAtomHeaderState(); } @Override - public final boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { return Sniffer.sniffFragmented(input); } @Override - public final void init(ExtractorOutput output) { + public void init(ExtractorOutput output) { extractorOutput = output; if (sideloadedTrack != null) { TrackBundle bundle = new TrackBundle(output.track(0)); @@ -161,7 +194,7 @@ public final void init(ExtractorOutput output) { } @Override - public final void seek() { + public void seek(long position) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).reset(); @@ -171,12 +204,12 @@ public final void seek() { } @Override - public final void release() { + public void release() { // Do nothing } @Override - public final int read(ExtractorInput input, PositionHolder seekPosition) + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { while (true) { switch (parserState) { @@ -230,6 +263,7 @@ private boolean readAtomHeader(ExtractorInput input) throws IOException, Interru int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { TrackFragment fragment = trackBundles.valueAt(i).fragment; + fragment.atomPosition = atomPosition; fragment.auxiliaryDataPosition = atomPosition; fragment.dataPosition = atomPosition; } @@ -239,7 +273,7 @@ private boolean readAtomHeader(ExtractorInput input) throws IOException, Interru currentTrackBundle = null; endOfMdatPosition = atomPosition + atomSize; if (!haveOutputSeekMap) { - extractorOutput.seekMap(SeekMap.UNSEEKABLE); + extractorOutput.seekMap(new SeekMap.Unseekable(durationUs)); haveOutputSeekMap = true; } parserState = STATE_READING_ENCRYPTION_DATA; @@ -301,8 +335,6 @@ private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserExce ChunkIndex segmentIndex = parseSidx(leaf.data, inputPosition); extractorOutput.seekMap(segmentIndex); haveOutputSeekMap = true; - } else if (leaf.type == Atom.TYPE_emsg) { - parseEmsg(leaf.data, inputPosition); } } @@ -316,36 +348,15 @@ private void onContainerAtomRead(ContainerAtom container) throws ParserException } } - private void onMoovContainerAtomRead(ContainerAtom moov) { + private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { Assertions.checkState(sideloadedTrack == null, "Unexpected moov box."); - List moovLeafChildren = moov.leafChildren; - int moovLeafChildrenSize = moovLeafChildren.size(); - DrmInitData.Mapped drmInitData = null; - for (int i = 0; i < moovLeafChildrenSize; i++) { - LeafAtom child = moovLeafChildren.get(i); - if (child.type == Atom.TYPE_pssh) { - if (drmInitData == null) { - drmInitData = new DrmInitData.Mapped(); - } - byte[] psshData = child.data.data; - UUID uuid = PsshAtomUtil.parseUuid(psshData); - if (uuid == null) { - Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); - } else { - drmInitData.put(PsshAtomUtil.parseUuid(psshData), - new SchemeInitData(MimeTypes.VIDEO_MP4, psshData)); - } - } - } - if (drmInitData != null) { - extractorOutput.drmInitData(drmInitData); - } + DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren); // Read declaration of track fragments in the Moov box. ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); SparseArray defaultSampleValuesArray = new SparseArray<>(); - long duration = -1; + long duration = C.TIME_UNSET; int mvexChildrenSize = mvex.leafChildren.size(); for (int i = 0; i < mvexChildrenSize; i++) { Atom.LeafAtom atom = mvex.leafChildren.get(i); @@ -364,18 +375,20 @@ private void onMoovContainerAtomRead(ContainerAtom moov) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type == Atom.TYPE_trak) { Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), duration, - false); + drmInitData, false); if (track != null) { tracks.put(track.id, track); } } } - int trackCount = tracks.size(); + int trackCount = tracks.size(); if (trackBundles.size() == 0) { // We need to create the track bundles. for (int i = 0; i < trackCount; i++) { - trackBundles.put(tracks.valueAt(i).id, new TrackBundle(extractorOutput.track(i))); + Track track = tracks.valueAt(i); + trackBundles.put(track.id, new TrackBundle(extractorOutput.track(i))); + durationUs = Math.max(durationUs, track.durationUs); } extractorOutput.endTracks(); } else { @@ -391,6 +404,13 @@ private void onMoovContainerAtomRead(ContainerAtom moov) { private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { parseMoof(moof, trackBundles, flags, extendedTypeScratch); + DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); + if (drmInitData != null) { + int trackCount = trackBundles.size(); + for (int i = 0; i < trackCount; i++) { + trackBundles.valueAt(i).updateDrmInitData(drmInitData); + } + } } /** @@ -419,10 +439,11 @@ private static long parseMehd(ParsableByteArray mehd) { } private static void parseMoof(ContainerAtom moof, SparseArray trackBundleArray, - int flags, byte[] extendedTypeScratch) throws ParserException { + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { int moofContainerChildrenSize = moof.containerChildren.size(); for (int i = 0; i < moofContainerChildrenSize; i++) { Atom.ContainerAtom child = moof.containerChildren.get(i); + // TODO: Support multiple traf boxes per track in a single moof. if (child.type == Atom.TYPE_traf) { parseTraf(child, trackBundleArray, flags, extendedTypeScratch); } @@ -433,11 +454,7 @@ private static void parseMoof(ContainerAtom moof, SparseArray track * Parses a traf atom (defined in 14496-12). */ private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray, - int flags, byte[] extendedTypeScratch) throws ParserException { - if (traf.getChildAtomOfTypeCount(Atom.TYPE_trun) != 1) { - throw new ParserException("Trun count in traf != 1 (unsupported)."); - } - + @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray, flags); if (trackBundle == null) { @@ -453,8 +470,7 @@ private static void parseTraf(ContainerAtom traf, SparseArray track decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); } - LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); - parseTrun(trackBundle, decodeTime, flags, trun.data); + parseTruns(traf, trackBundle, decodeTime, flags); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { @@ -479,8 +495,8 @@ private static void parseTraf(ContainerAtom traf, SparseArray track parseSgpd(sbgp.data, sgpd.data, fragment); } - int childrenSize = traf.leafChildren.size(); - for (int i = 0; i < childrenSize; i++) { + int leafChildrenSize = traf.leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { LeafAtom atom = traf.leafChildren.get(i); if (atom.type == Atom.TYPE_uuid) { parseUuid(atom.data, fragment, extendedTypeScratch); @@ -488,6 +504,40 @@ private static void parseTraf(ContainerAtom traf, SparseArray track } } + private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, long decodeTime, + @Flags int flags) { + int trunCount = 0; + int totalSampleCount = 0; + List leafChildren = traf.leafChildren; + int leafChildrenSize = leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom atom = leafChildren.get(i); + if (atom.type == Atom.TYPE_trun) { + ParsableByteArray trunData = atom.data; + trunData.setPosition(Atom.FULL_HEADER_SIZE); + int trunSampleCount = trunData.readUnsignedIntToInt(); + if (trunSampleCount > 0) { + totalSampleCount += trunSampleCount; + trunCount++; + } + } + } + trackBundle.currentTrackRunIndex = 0; + trackBundle.currentSampleInTrackRun = 0; + trackBundle.currentSampleIndex = 0; + trackBundle.fragment.initTables(trunCount, totalSampleCount); + + int trunIndex = 0; + int trunStartPosition = 0; + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom trun = leafChildren.get(i); + if (trun.type == Atom.TYPE_trun) { + trunStartPosition = parseTrun(trackBundle, trunIndex++, decodeTime, flags, trun.data, + trunStartPosition); + } + } + } + private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out) throws ParserException { int vectorSize = encryptionBox.initializationVectorSize; @@ -500,8 +550,8 @@ private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArra int defaultSampleInfoSize = saiz.readUnsignedByte(); int sampleCount = saiz.readUnsignedIntToInt(); - if (sampleCount != out.length) { - throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length); + if (sampleCount != out.sampleCount) { + throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount); } int totalSize = 0; @@ -523,7 +573,7 @@ private static void parseSaiz(TrackEncryptionBox encryptionBox, ParsableByteArra /** * Parses a saio atom (defined in 14496-12). * - * @param saio The saio atom to parse. + * @param saio The saio atom to decode. * @param out The {@link TrackFragment} to populate with data from the saio atom. */ private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException { @@ -550,7 +600,7 @@ private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws * returns the {@link TrackBundle} of the corresponding {@link Track}. If the tfhd does not refer * to any {@link TrackBundle}, {@code null} is returned and no changes are made. * - * @param tfhd The tfhd atom to parse. + * @param tfhd The tfhd atom to decode. * @param trackBundles The track bundles, one of which corresponds to the tfhd atom being parsed. * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd * does not refer to any {@link TrackBundle}. @@ -604,12 +654,14 @@ private static long parseTfdt(ParsableByteArray tfdt) { * * @param trackBundle The {@link TrackBundle} that contains the {@link TrackFragment} into * which parsed data should be placed. + * @param index Index of the track run in the fragment. * @param decodeTime The decode time of the first sample in the fragment run. * @param flags Flags to allow any required workaround to be executed. - * @param trun The trun atom to parse. + * @param trun The trun atom to decode. + * @return The starting position of samples for the next run. */ - private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flags, - ParsableByteArray trun) { + private static int parseTrun(TrackBundle trackBundle, int index, long decodeTime, + @Flags int flags, ParsableByteArray trun, int trackRunStart) { trun.setPosition(Atom.HEADER_SIZE); int fullAtom = trun.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); @@ -618,9 +670,10 @@ private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flag TrackFragment fragment = trackBundle.fragment; DefaultSampleValues defaultSampleValues = fragment.header; - int sampleCount = trun.readUnsignedIntToInt(); + fragment.trunLength[index] = trun.readUnsignedIntToInt(); + fragment.trunDataPosition[index] = fragment.dataPosition; if ((atomFlags & 0x01 /* data_offset_present */) != 0) { - fragment.dataPosition += trun.readInt(); + fragment.trunDataPosition[index] += trun.readInt(); } boolean firstSampleFlagsPresent = (atomFlags & 0x04 /* first_sample_flags_present */) != 0; @@ -646,17 +699,18 @@ private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flag edtsOffset = Util.scaleLargeTimestamp(track.editListMediaTimes[0], 1000, track.timescale); } - fragment.initTables(sampleCount); int[] sampleSizeTable = fragment.sampleSizeTable; int[] sampleCompositionTimeOffsetTable = fragment.sampleCompositionTimeOffsetTable; long[] sampleDecodingTimeTable = fragment.sampleDecodingTimeTable; boolean[] sampleIsSyncFrameTable = fragment.sampleIsSyncFrameTable; - long timescale = track.timescale; - long cumulativeTime = decodeTime; - boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_vide + boolean workaroundEveryVideoFrameIsSyncFrame = track.type == C.TRACK_TYPE_VIDEO && (flags & FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0; - for (int i = 0; i < sampleCount; i++) { + + int trackRunEnd = trackRunStart + fragment.trunLength[index]; + long timescale = track.timescale; + long cumulativeTime = index > 0 ? fragment.nextFragmentDecodeTime : decodeTime; + for (int i = trackRunStart; i < trackRunEnd; i++) { // Use trun values if present, otherwise tfhd, otherwise trex. int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.duration; @@ -666,7 +720,7 @@ private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flag if (sampleCompositionTimeOffsetsPresent) { // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // version 0 trun boxes, however a significant number of streams violate the spec and use - // signed integers instead. It's safe to always parse sample offsets as signed integers + // signed integers instead. It's safe to always decode sample offsets as signed integers // here, because unsigned integers will still be parsed correctly (unless their top bit is // set, which is never true in practice because sample offsets are always small). int sampleOffset = trun.readInt(); @@ -682,6 +736,7 @@ private static void parseTrun(TrackBundle trackBundle, long decodeTime, int flag cumulativeTime += sampleDuration; } fragment.nextFragmentDecodeTime = cumulativeTime; + return trackRunEnd; } private static void parseUuid(ParsableByteArray uuid, TrackFragment out, @@ -717,8 +772,8 @@ private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0; int sampleCount = senc.readUnsignedIntToInt(); - if (sampleCount != out.length) { - throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length); + if (sampleCount != out.sampleCount) { + throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount); } Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption); @@ -771,10 +826,6 @@ private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, Tr out.trackEncryptionBox = new TrackEncryptionBox(isProtected, initVectorSize, keyId); } - protected void parseEmsg(ParsableByteArray atom, long inputPosition) throws ParserException { - // Do nothing. - } - /** * Parses a sidx atom (defined in 14496-12). */ @@ -866,14 +917,14 @@ private void readEncryptionData(ExtractorInput input) throws IOException, Interr * this case the method can be called again to extract the remainder of the sample. * * @param input The {@link ExtractorInput} from which to read data. - * @return True if a sample was extracted. False otherwise. + * @return Whether a sample was extracted. * @throws IOException If an error occurs reading from the input. * @throws InterruptedException If the thread is interrupted. */ private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { if (parserState == STATE_READING_SAMPLE_START) { if (currentTrackBundle == null) { - currentTrackBundle = getNextFragmentRun(trackBundles); + TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles); if (currentTrackBundle == null) { // We've run out of samples in the current mdat. Discard any trailing data and prepare to // read the header of the next atom. @@ -886,13 +937,21 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted return false; } - long nextDataPosition = currentTrackBundle.fragment.dataPosition; + long nextDataPosition = currentTrackBundle.fragment + .trunDataPosition[currentTrackBundle.currentTrackRunIndex]; // We skip bytes preceding the next sample to read. int bytesToSkip = (int) (nextDataPosition - input.getPosition()); if (bytesToSkip < 0) { - throw new ParserException("Offset to sample data was negative."); + if (nextDataPosition == currentTrackBundle.fragment.atomPosition) { + // Assume the sample data must be contiguous in the mdat with no preceeding data. + Log.w(TAG, "Offset to sample data was missing."); + bytesToSkip = 0; + } else { + throw new ParserException("Offset to sample data was negative."); + } } input.skipFully(bytesToSkip); + this.currentTrackBundle = currentTrackBundle; } sampleSize = currentTrackBundle.fragment .sampleSizeTable[currentTrackBundle.currentSampleIndex]; @@ -902,6 +961,10 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted } else { sampleBytesWritten = 0; } + if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { + sampleSize -= Atom.HEADER_SIZE; + input.skipFully(Atom.HEADER_SIZE); + } parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; } @@ -910,8 +973,8 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted Track track = currentTrackBundle.track; TrackOutput output = currentTrackBundle.output; int sampleIndex = currentTrackBundle.currentSampleIndex; - if (track.nalUnitLengthFieldLength != -1) { - // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case + if (track.nalUnitLengthFieldLength != 0) { + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case // they're only 1 or 2 bytes long. byte[] nalLengthData = nalLength.data; nalLengthData[0] = 0; @@ -948,8 +1011,8 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted } long sampleTimeUs = fragment.getSamplePresentationTime(sampleIndex) * 1000L; - int sampleFlags = (fragment.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0) - | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0); + @C.BufferFlags int sampleFlags = (fragment.definesEncryptionData ? C.BUFFER_FLAG_ENCRYPTED : 0) + | (fragment.sampleIsSyncFrameTable[sampleIndex] ? C.BUFFER_FLAG_KEY_FRAME : 0); int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; byte[] encryptionKey = null; if (fragment.definesEncryptionData) { @@ -957,10 +1020,17 @@ private boolean readSample(ExtractorInput input) throws IOException, Interrupted ? fragment.trackEncryptionBox.keyId : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; } + if (timestampAdjuster != null) { + sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); + } output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); currentTrackBundle.currentSampleIndex++; - if (currentTrackBundle.currentSampleIndex == fragment.length) { + currentTrackBundle.currentSampleInTrackRun++; + if (currentTrackBundle.currentSampleInTrackRun + == fragment.trunLength[currentTrackBundle.currentTrackRunIndex]) { + currentTrackBundle.currentTrackRunIndex++; + currentTrackBundle.currentSampleInTrackRun = 0; currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; @@ -978,10 +1048,10 @@ private static TrackBundle getNextFragmentRun(SparseArray trackBund int trackBundlesSize = trackBundles.size(); for (int i = 0; i < trackBundlesSize; i++) { TrackBundle trackBundle = trackBundles.valueAt(i); - if (trackBundle.currentSampleIndex == trackBundle.fragment.length) { + if (trackBundle.currentTrackRunIndex == trackBundle.fragment.trunCount) { // This track fragment contains no more runs in the next mdat box. } else { - long trunOffset = trackBundle.fragment.dataPosition; + long trunOffset = trackBundle.fragment.trunDataPosition[trackBundle.currentTrackRunIndex]; if (trunOffset < nextTrackRunOffset) { nextTrackBundle = trackBundle; nextTrackRunOffset = trunOffset; @@ -1029,18 +1099,41 @@ private int appendSampleEncryptionData(TrackBundle trackBundle) { return 1 + vectorSize + subsampleDataLength; } - /** Returns whether the extractor should parse a leaf atom with type {@code atom}. */ + + /** Returns DrmInitData from leaf atoms. */ + private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { + ArrayList schemeDatas = null; + int leafChildrenSize = leafChildren.size(); + for (int i = 0; i < leafChildrenSize; i++) { + LeafAtom child = leafChildren.get(i); + if (child.type == Atom.TYPE_pssh) { + if (schemeDatas == null) { + schemeDatas = new ArrayList<>(); + } + byte[] psshData = child.data.data; + UUID uuid = PsshAtomUtil.parseUuid(psshData); + if (uuid == null) { + Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); + } else { + schemeDatas.add(new SchemeData(uuid, MimeTypes.VIDEO_MP4, psshData)); + } + } + } + return schemeDatas == null ? null : new DrmInitData(schemeDatas); + } + + /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ private static boolean shouldParseLeafAtom(int atom) { return atom == Atom.TYPE_hdlr || atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_sidx || atom == Atom.TYPE_stsd || atom == Atom.TYPE_tfdt || atom == Atom.TYPE_tfhd || atom == Atom.TYPE_tkhd || atom == Atom.TYPE_trex || atom == Atom.TYPE_trun || atom == Atom.TYPE_pssh || atom == Atom.TYPE_saiz - || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_sbgp - || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_uuid || atom == Atom.TYPE_elst - || atom == Atom.TYPE_mehd || atom == Atom.TYPE_emsg; + || atom == Atom.TYPE_saio || atom == Atom.TYPE_senc || atom == Atom.TYPE_uuid + || atom == Atom.TYPE_sbgp || atom == Atom.TYPE_sgpd || atom == Atom.TYPE_elst + || atom == Atom.TYPE_mehd; } - /** Returns whether the extractor should parse a container atom with type {@code atom}. */ + /** Returns whether the extractor should decode a container atom with type {@code atom}. */ private static boolean shouldParseContainerAtom(int atom) { return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia || atom == Atom.TYPE_minf || atom == Atom.TYPE_stbl || atom == Atom.TYPE_moof @@ -1058,6 +1151,8 @@ private static final class TrackBundle { public Track track; public DefaultSampleValues defaultSampleValues; public int currentSampleIndex; + public int currentSampleInTrackRun; + public int currentTrackRunIndex; public TrackBundle(TrackOutput output) { fragment = new TrackFragment(); @@ -1067,15 +1162,20 @@ public TrackBundle(TrackOutput output) { public void init(Track track, DefaultSampleValues defaultSampleValues) { this.track = Assertions.checkNotNull(track); this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues); - output.format(track.mediaFormat); + output.format(track.format); reset(); } public void reset() { fragment.reset(); currentSampleIndex = 0; + currentTrackRunIndex = 0; + currentSampleInTrackRun = 0; } + public void updateDrmInitData(DrmInitData drmInitData) { + output.format(track.format.copyWithDrmInitData(drmInitData)); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/MetadataUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/MetadataUtil.java new file mode 100755 index 00000000000..bf5f5bb226b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/MetadataUtil.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import android.util.Log; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.id3.ApicFrame; +import org.telegram.messenger.exoplayer2.metadata.id3.CommentFrame; +import org.telegram.messenger.exoplayer2.metadata.id3.Id3Frame; +import org.telegram.messenger.exoplayer2.metadata.id3.TextInformationFrame; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Parses metadata items stored in ilst atoms. + */ +/* package */ final class MetadataUtil { + + private static final String TAG = "MetadataUtil"; + + // Codes that start with the copyright character (omitted) and have equivalent ID3 frames. + private static final int SHORT_TYPE_NAME_1 = Util.getIntegerCodeForString("nam"); + private static final int SHORT_TYPE_NAME_2 = Util.getIntegerCodeForString("trk"); + private static final int SHORT_TYPE_COMMENT = Util.getIntegerCodeForString("cmt"); + private static final int SHORT_TYPE_YEAR = Util.getIntegerCodeForString("day"); + private static final int SHORT_TYPE_ARTIST = Util.getIntegerCodeForString("ART"); + private static final int SHORT_TYPE_ENCODER = Util.getIntegerCodeForString("too"); + private static final int SHORT_TYPE_ALBUM = Util.getIntegerCodeForString("alb"); + private static final int SHORT_TYPE_COMPOSER_1 = Util.getIntegerCodeForString("com"); + private static final int SHORT_TYPE_COMPOSER_2 = Util.getIntegerCodeForString("wrt"); + private static final int SHORT_TYPE_LYRICS = Util.getIntegerCodeForString("lyr"); + private static final int SHORT_TYPE_GENRE = Util.getIntegerCodeForString("gen"); + + // Codes that have equivalent ID3 frames. + private static final int TYPE_COVER_ART = Util.getIntegerCodeForString("covr"); + private static final int TYPE_GENRE = Util.getIntegerCodeForString("gnre"); + private static final int TYPE_GROUPING = Util.getIntegerCodeForString("grp"); + private static final int TYPE_DISK_NUMBER = Util.getIntegerCodeForString("disk"); + private static final int TYPE_TRACK_NUMBER = Util.getIntegerCodeForString("trkn"); + private static final int TYPE_TEMPO = Util.getIntegerCodeForString("tmpo"); + private static final int TYPE_COMPILATION = Util.getIntegerCodeForString("cpil"); + private static final int TYPE_ALBUM_ARTIST = Util.getIntegerCodeForString("aART"); + private static final int TYPE_SORT_TRACK_NAME = Util.getIntegerCodeForString("sonm"); + private static final int TYPE_SORT_ALBUM = Util.getIntegerCodeForString("soal"); + private static final int TYPE_SORT_ARTIST = Util.getIntegerCodeForString("soar"); + private static final int TYPE_SORT_ALBUM_ARTIST = Util.getIntegerCodeForString("soaa"); + private static final int TYPE_SORT_COMPOSER = Util.getIntegerCodeForString("soco"); + + // Types that do not have equivalent ID3 frames. + private static final int TYPE_RATING = Util.getIntegerCodeForString("rtng"); + private static final int TYPE_GAPLESS_ALBUM = Util.getIntegerCodeForString("pgap"); + private static final int TYPE_TV_SORT_SHOW = Util.getIntegerCodeForString("sosn"); + private static final int TYPE_TV_SHOW = Util.getIntegerCodeForString("tvsh"); + + // Type for items that are intended for internal use by the player. + private static final int TYPE_INTERNAL = Util.getIntegerCodeForString("----"); + + // Standard genres. + private static final String[] STANDARD_GENRES = new String[] { + // These are the official ID3v1 genres. + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", + "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", + "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", + "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", + "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", + "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", + "Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", + "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", + "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", + "Hard Rock", + // These were made up by the authors of Winamp and later added to the ID3 spec. + "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", + "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", + "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", + "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", + "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", + "Euro-House", "Dance Hall", + // These were med up by the authors of Winamp but have not been added to the ID3 spec. + "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", + "Polsk Punk", "Beat", "Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover", + "Contemporary Christian", "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", + "Jpop", "Synthpop" + }; + + private static final String LANGUAGE_UNDEFINED = "und"; + + private MetadataUtil() {} + + /** + * Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting + * from the current position of the {@link ParsableByteArray}, and the position is advanced by + * the size of the element. The position is advanced even if the element's type is unrecognized. + * + * @param ilst Holds the data to be parsed. + * @return The parsed element, or null if the element's type was not recognized. + */ + public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) { + int position = ilst.getPosition(); + int endPosition = position + ilst.readInt(); + int type = ilst.readInt(); + int typeTopByte = (type >> 24) & 0xFF; + try { + if (typeTopByte == '\u00A9' /* Copyright char */ + || typeTopByte == '\uFFFD' /* Replacement char */) { + int shortType = type & 0x00FFFFFF; + if (shortType == SHORT_TYPE_COMMENT) { + return parseCommentAttribute(type, ilst); + } else if (shortType == SHORT_TYPE_NAME_1 || shortType == SHORT_TYPE_NAME_2) { + return parseTextAttribute(type, "TIT2", ilst); + } else if (shortType == SHORT_TYPE_COMPOSER_1 || shortType == SHORT_TYPE_COMPOSER_2) { + return parseTextAttribute(type, "TCOM", ilst); + } else if (shortType == SHORT_TYPE_YEAR) { + return parseTextAttribute(type, "TDRC", ilst); + } else if (shortType == SHORT_TYPE_ARTIST) { + return parseTextAttribute(type, "TPE1", ilst); + } else if (shortType == SHORT_TYPE_ENCODER) { + return parseTextAttribute(type, "TSSE", ilst); + } else if (shortType == SHORT_TYPE_ALBUM) { + return parseTextAttribute(type, "TALB", ilst); + } else if (shortType == SHORT_TYPE_LYRICS) { + return parseTextAttribute(type, "USLT", ilst); + } else if (shortType == SHORT_TYPE_GENRE) { + return parseTextAttribute(type, "TCON", ilst); + } else if (shortType == TYPE_GROUPING) { + return parseTextAttribute(type, "TIT1", ilst); + } + } else if (type == TYPE_GENRE) { + return parseStandardGenreAttribute(ilst); + } else if (type == TYPE_DISK_NUMBER) { + return parseIndexAndCountAttribute(type, "TPOS", ilst); + } else if (type == TYPE_TRACK_NUMBER) { + return parseIndexAndCountAttribute(type, "TRCK", ilst); + } else if (type == TYPE_TEMPO) { + return parseUint8Attribute(type, "TBPM", ilst, true, false); + } else if (type == TYPE_COMPILATION) { + return parseUint8Attribute(type, "TCMP", ilst, true, true); + } else if (type == TYPE_COVER_ART) { + return parseCoverArt(ilst); + } else if (type == TYPE_ALBUM_ARTIST) { + return parseTextAttribute(type, "TPE2", ilst); + } else if (type == TYPE_SORT_TRACK_NAME) { + return parseTextAttribute(type, "TSOT", ilst); + } else if (type == TYPE_SORT_ALBUM) { + return parseTextAttribute(type, "TSO2", ilst); + } else if (type == TYPE_SORT_ARTIST) { + return parseTextAttribute(type, "TSOA", ilst); + } else if (type == TYPE_SORT_ALBUM_ARTIST) { + return parseTextAttribute(type, "TSOP", ilst); + } else if (type == TYPE_SORT_COMPOSER) { + return parseTextAttribute(type, "TSOC", ilst); + } else if (type == TYPE_RATING) { + return parseUint8Attribute(type, "ITUNESADVISORY", ilst, false, false); + } else if (type == TYPE_GAPLESS_ALBUM) { + return parseUint8Attribute(type, "ITUNESGAPLESS", ilst, false, true); + } else if (type == TYPE_TV_SORT_SHOW) { + return parseTextAttribute(type, "TVSHOWSORT", ilst); + } else if (type == TYPE_TV_SHOW) { + return parseTextAttribute(type, "TVSHOW", ilst); + } else if (type == TYPE_INTERNAL) { + return parseInternalAttribute(ilst, endPosition); + } + Log.d(TAG, "Skipped unknown metadata entry: " + Atom.getAtomTypeString(type)); + return null; + } finally { + ilst.setPosition(endPosition); + } + } + + private static TextInformationFrame parseTextAttribute(int type, String id, + ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(atomSize - 16); + return new TextInformationFrame(id, value); + } + Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type)); + return null; + } + + private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(atomSize - 16); + return new CommentFrame(LANGUAGE_UNDEFINED, value, value); + } + Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type)); + return null; + } + + private static Id3Frame parseUint8Attribute(int type, String id, ParsableByteArray data, + boolean isTextInformationFrame, boolean isBoolean) { + int value = parseUint8AttributeValue(data); + if (isBoolean) { + value = Math.min(1, value); + } + if (value >= 0) { + return isTextInformationFrame ? new TextInformationFrame(id, Integer.toString(value)) + : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); + } + Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); + return null; + } + + private static TextInformationFrame parseIndexAndCountAttribute(int type, String attributeName, + ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data && atomSize >= 22) { + data.skipBytes(10); // version (1), flags (3), empty (4), empty (2) + int index = data.readUnsignedShort(); + if (index > 0) { + String description = "" + index; + int count = data.readUnsignedShort(); + if (count > 0) { + description += "/" + count; + } + return new TextInformationFrame(attributeName, description); + } + } + Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type)); + return null; + } + + private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { + int genreCode = parseUint8AttributeValue(data); + String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) + ? STANDARD_GENRES[genreCode - 1] : null; + if (genreString != null) { + return new TextInformationFrame("TCON", genreString); + } + Log.w(TAG, "Failed to parse standard genre code"); + return null; + } + + private static ApicFrame parseCoverArt(ParsableByteArray data) { + int atomSize = data.readInt(); + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + int fullVersionInt = data.readInt(); + int flags = Atom.parseFullAtomFlags(fullVersionInt); + String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null; + if (mimeType == null) { + Log.w(TAG, "Unrecognized cover art flags: " + flags); + return null; + } + data.skipBytes(4); // empty (4) + byte[] pictureData = new byte[atomSize - 16]; + data.readBytes(pictureData, 0, pictureData.length); + return new ApicFrame(mimeType, null, 3 /* Cover (front) */, pictureData); + } + Log.w(TAG, "Failed to parse cover art attribute"); + return null; + } + + private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { + String domain = null; + String name = null; + int dataAtomPosition = -1; + int dataAtomSize = -1; + while (data.getPosition() < endPosition) { + int atomPosition = data.getPosition(); + int atomSize = data.readInt(); + int atomType = data.readInt(); + data.skipBytes(4); // version (1), flags (3) + if (atomType == Atom.TYPE_mean) { + domain = data.readNullTerminatedString(atomSize - 12); + } else if (atomType == Atom.TYPE_name) { + name = data.readNullTerminatedString(atomSize - 12); + } else { + if (atomType == Atom.TYPE_data) { + dataAtomPosition = atomPosition; + dataAtomSize = atomSize; + } + data.skipBytes(atomSize - 12); + } + } + if (!"com.apple.iTunes".equals(domain) || !"iTunSMPB".equals(name) || dataAtomPosition == -1) { + // We're only interested in iTunSMPB. + return null; + } + data.setPosition(dataAtomPosition); + data.skipBytes(16); // size (4), type (4), version (1), flags (3), empty (4) + String value = data.readNullTerminatedString(dataAtomSize - 16); + return new CommentFrame(LANGUAGE_UNDEFINED, name, value); + } + + private static int parseUint8AttributeValue(ParsableByteArray data) { + data.skipBytes(4); // atomSize + int atomType = data.readInt(); + if (atomType == Atom.TYPE_data) { + data.skipBytes(8); // version (1), flags (3), empty (4) + return data.readUnsignedByte(); + } + Log.w(TAG, "Failed to parse uint8 attribute value"); + return -1; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Mp4Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Mp4Extractor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java index 25d6c54527a..92a3c0b4bb7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; - -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.GaplessInfo; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.extractor.mp4.Atom.ContainerAtom; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.GaplessInfoHolder; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.mp4.Atom.ContainerAtom; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -39,6 +42,18 @@ */ public final class Mp4Extractor implements Extractor, SeekMap { + /** + * Factory for {@link Mp4Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Mp4Extractor()}; + } + + }; + // Parser states. private static final int STATE_AFTER_SEEK = 0; private static final int STATE_READING_ATOM_HEADER = 1; @@ -67,13 +82,13 @@ public final class Mp4Extractor implements Extractor, SeekMap { private int atomHeaderBytesRead; private ParsableByteArray atomData; - private int sampleSize; private int sampleBytesWritten; private int sampleCurrentNalBytesRemaining; // Extractor outputs. private ExtractorOutput extractorOutput; private Mp4Track[] tracks; + private long durationUs; private boolean isQuickTime; public Mp4Extractor() { @@ -95,7 +110,7 @@ public void init(ExtractorOutput output) { } @Override - public void seek() { + public void seek(long position) { containerAtoms.clear(); atomHeaderBytesRead = 0; sampleBytesWritten = 0; @@ -143,17 +158,22 @@ public boolean isSeekable() { return true; } + @Override + public long getDurationUs() { + return durationUs; + } + @Override public long getPosition(long timeUs) { long earliestSamplePosition = Long.MAX_VALUE; - for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { - TrackSampleTable sampleTable = tracks[trackIndex].sampleTable; + for (Mp4Track track : tracks) { + TrackSampleTable sampleTable = track.sampleTable; int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); - if (sampleIndex == TrackSampleTable.NO_SAMPLE) { + if (sampleIndex == C.INDEX_UNSET) { // Handle the case where the requested time is before the first synchronization sample. sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); } - tracks[trackIndex].sampleIndex = sampleIndex; + track.sampleIndex = sampleIndex; long offset = sampleTable.offsets[sampleIndex]; if (offset < earliestSamplePosition) { @@ -266,7 +286,7 @@ private void processAtomEnded(long atomEndPosition) throws ParserException { * Process an ftyp atom to determine whether the media is QuickTime. * * @param atomData The ftyp atom data. - * @return True if the media is QuickTime. False otherwise. + * @return Whether the media is QuickTime. */ private static boolean processFtypAtom(ParsableByteArray atomData) { atomData.setPosition(Atom.HEADER_SIZE); @@ -287,28 +307,35 @@ private static boolean processFtypAtom(ParsableByteArray atomData) { * Updates the stored track metadata to reflect the contents of the specified moov atom. */ private void processMoovAtom(ContainerAtom moov) throws ParserException { + long durationUs = C.TIME_UNSET; List tracks = new ArrayList<>(); long earliestSampleOffset = Long.MAX_VALUE; - GaplessInfo gaplessInfo = null; + + Metadata metadata = null; + GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { - gaplessInfo = AtomParsers.parseUdta(udta, isQuickTime); + metadata = AtomParsers.parseUdta(udta, isQuickTime); + if (metadata != null) { + gaplessInfoHolder.setFromMetadata(metadata); + } } + for (int i = 0; i < moov.containerChildren.size(); i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type != Atom.TYPE_trak) { continue; } - Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), -1, - isQuickTime); + Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), + C.TIME_UNSET, null, isQuickTime); if (track == null) { continue; } Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia) .getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl); - TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom); + TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder); if (trackSampleTable.sampleCount == 0) { continue; } @@ -317,12 +344,19 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException { // Each sample has up to three bytes of overhead for the start code that replaces its length. // Allow ten source samples per output sample, like the platform extractor. int maxInputSize = trackSampleTable.maximumSize + 3 * 10; - MediaFormat mediaFormat = track.mediaFormat.copyWithMaxInputSize(maxInputSize); - if (gaplessInfo != null) { - mediaFormat = - mediaFormat.copyWithGaplessInfo(gaplessInfo.encoderDelay, gaplessInfo.encoderPadding); + Format format = track.format.copyWithMaxInputSize(maxInputSize); + if (track.type == C.TRACK_TYPE_AUDIO) { + if (gaplessInfoHolder.hasGaplessInfo()) { + format = format.copyWithGaplessInfo(gaplessInfoHolder.encoderDelay, + gaplessInfoHolder.encoderPadding); + } + if (metadata != null) { + format = format.copyWithMetadata(metadata); + } } - mp4Track.trackOutput.format(mediaFormat); + mp4Track.trackOutput.format(format); + + durationUs = Math.max(durationUs, track.durationUs); tracks.add(mp4Track); long firstSampleOffset = trackSampleTable.offsets[0]; @@ -330,7 +364,8 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException { earliestSampleOffset = firstSampleOffset; } } - this.tracks = tracks.toArray(new Mp4Track[0]); + this.durationUs = durationUs; + this.tracks = tracks.toArray(new Mp4Track[tracks.size()]); extractorOutput.endTracks(); extractorOutput.seekMap(this); } @@ -354,22 +389,28 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException { private int readSample(ExtractorInput input, PositionHolder positionHolder) throws IOException, InterruptedException { int trackIndex = getTrackIndexOfEarliestCurrentSample(); - if (trackIndex == TrackSampleTable.NO_SAMPLE) { + if (trackIndex == C.INDEX_UNSET) { return RESULT_END_OF_INPUT; } Mp4Track track = tracks[trackIndex]; TrackOutput trackOutput = track.trackOutput; int sampleIndex = track.sampleIndex; long position = track.sampleTable.offsets[sampleIndex]; + int sampleSize = track.sampleTable.sizes[sampleIndex]; + if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { + // The sample information is contained in a cdat atom. The header must be discarded for + // committing. + position += Atom.HEADER_SIZE; + sampleSize -= Atom.HEADER_SIZE; + } long skipAmount = position - input.getPosition() + sampleBytesWritten; if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { positionHolder.position = position; return RESULT_SEEK; } input.skipFully((int) skipAmount); - sampleSize = track.sampleTable.sizes[sampleIndex]; - if (track.track.nalUnitLengthFieldLength != -1) { - // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case + if (track.track.nalUnitLengthFieldLength != 0) { + // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case // they're only 1 or 2 bytes long. byte[] nalLengthData = nalLength.data; nalLengthData[0] = 0; @@ -415,10 +456,10 @@ private int readSample(ExtractorInput input, PositionHolder positionHolder) /** * Returns the index of the track that contains the earliest current sample, or - * {@link TrackSampleTable#NO_SAMPLE} if no samples remain. + * {@link C#INDEX_UNSET} if no samples remain. */ private int getTrackIndexOfEarliestCurrentSample() { - int earliestSampleTrackIndex = TrackSampleTable.NO_SAMPLE; + int earliestSampleTrackIndex = C.INDEX_UNSET; long earliestSampleOffset = Long.MAX_VALUE; for (int trackIndex = 0; trackIndex < tracks.length; trackIndex++) { Mp4Track track = tracks[trackIndex]; @@ -438,7 +479,7 @@ private int getTrackIndexOfEarliestCurrentSample() { } /** - * Returns whether the extractor should parse a leaf atom with type {@code atom}. + * Returns whether the extractor should decode a leaf atom with type {@code atom}. */ private static boolean shouldParseLeafAtom(int atom) { return atom == Atom.TYPE_mdhd || atom == Atom.TYPE_mvhd || atom == Atom.TYPE_hdlr @@ -450,7 +491,7 @@ private static boolean shouldParseLeafAtom(int atom) { } /** - * Returns whether the extractor should parse a container atom with type {@code atom}. + * Returns whether the extractor should decode a container atom with type {@code atom}. */ private static boolean shouldParseContainerAtom(int atom) { return atom == Atom.TYPE_moov || atom == Atom.TYPE_trak || atom == Atom.TYPE_mdia diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/PsshAtomUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/PsshAtomUtil.java similarity index 96% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/PsshAtomUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/PsshAtomUtil.java index e52a5aa7a30..d5e52bddef2 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/PsshAtomUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/PsshAtomUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; import android.util.Log; import android.util.Pair; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.nio.ByteBuffer; import java.util.UUID; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java new file mode 100755 index 00000000000..1fea1b52485 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Sniffer.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Provides methods that peek data from an {@link ExtractorInput} and return whether the input + * appears to be in MP4 format. + */ +/* package */ final class Sniffer { + + /** + * The maximum number of bytes to peek when sniffing. + */ + private static final int SEARCH_LENGTH = 4 * 1024; + + private static final int[] COMPATIBLE_BRANDS = new int[] { + Util.getIntegerCodeForString("isom"), + Util.getIntegerCodeForString("iso2"), + Util.getIntegerCodeForString("iso3"), + Util.getIntegerCodeForString("iso4"), + Util.getIntegerCodeForString("iso5"), + Util.getIntegerCodeForString("iso6"), + Util.getIntegerCodeForString("avc1"), + Util.getIntegerCodeForString("hvc1"), + Util.getIntegerCodeForString("hev1"), + Util.getIntegerCodeForString("mp41"), + Util.getIntegerCodeForString("mp42"), + Util.getIntegerCodeForString("3g2a"), + Util.getIntegerCodeForString("3g2b"), + Util.getIntegerCodeForString("3gr6"), + Util.getIntegerCodeForString("3gs6"), + Util.getIntegerCodeForString("3ge6"), + Util.getIntegerCodeForString("3gg6"), + Util.getIntegerCodeForString("M4V "), + Util.getIntegerCodeForString("M4A "), + Util.getIntegerCodeForString("f4v "), + Util.getIntegerCodeForString("kddi"), + Util.getIntegerCodeForString("M4VP"), + Util.getIntegerCodeForString("qt "), // Apple QuickTime + Util.getIntegerCodeForString("MSNV"), // Sony PSP + }; + + /** + * Returns whether data peeked from the current position in {@code input} is consistent with the + * input being a fragmented MP4 file. + * + * @param input The extractor input from which to peek data. The peek position will be modified. + * @return Whether the input appears to be in the fragmented MP4 format. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + public static boolean sniffFragmented(ExtractorInput input) + throws IOException, InterruptedException { + return sniffInternal(input, true); + } + + /** + * Returns whether data peeked from the current position in {@code input} is consistent with the + * input being an unfragmented MP4 file. + * + * @param input The extractor input from which to peek data. The peek position will be modified. + * @return Whether the input appears to be in the unfragmented MP4 format. + * @throws IOException If an error occurs reading from the input. + * @throws InterruptedException If the thread has been interrupted. + */ + public static boolean sniffUnfragmented(ExtractorInput input) + throws IOException, InterruptedException { + return sniffInternal(input, false); + } + + private static boolean sniffInternal(ExtractorInput input, boolean fragmented) + throws IOException, InterruptedException { + long inputLength = input.getLength(); + int bytesToSearch = (int) (inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH + ? SEARCH_LENGTH : inputLength); + + ParsableByteArray buffer = new ParsableByteArray(64); + int bytesSearched = 0; + boolean foundGoodFileType = false; + boolean isFragmented = false; + while (bytesSearched < bytesToSearch) { + // Read an atom header. + int headerSize = Atom.HEADER_SIZE; + buffer.reset(headerSize); + input.peekFully(buffer.data, 0, headerSize); + long atomSize = buffer.readUnsignedInt(); + int atomType = buffer.readInt(); + if (atomSize == Atom.LONG_SIZE_PREFIX) { + headerSize = Atom.LONG_HEADER_SIZE; + input.peekFully(buffer.data, Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); + buffer.setLimit(Atom.LONG_HEADER_SIZE); + atomSize = buffer.readUnsignedLongToLong(); + } + + if (atomSize < headerSize) { + // The file is invalid because the atom size is too small for its header. + return false; + } + bytesSearched += headerSize; + + if (atomType == Atom.TYPE_moov) { + // Check for an mvex atom inside the moov atom to identify whether the file is fragmented. + continue; + } + + if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mvex) { + // The movie is fragmented. Stop searching as we must have read any ftyp atom already. + isFragmented = true; + break; + } + + if (bytesSearched + atomSize - headerSize >= bytesToSearch) { + // Stop searching as peeking this atom would exceed the search limit. + break; + } + + int atomDataSize = (int) (atomSize - headerSize); + bytesSearched += atomDataSize; + if (atomType == Atom.TYPE_ftyp) { + // Parse the atom and check the file type/brand is compatible with the extractors. + if (atomDataSize < 8) { + return false; + } + buffer.reset(atomDataSize); + input.peekFully(buffer.data, 0, atomDataSize); + int brandsCount = atomDataSize / 4; + for (int i = 0; i < brandsCount; i++) { + if (i == 1) { + // This index refers to the minorVersion, not a brand, so skip it. + buffer.skipBytes(4); + } else if (isCompatibleBrand(buffer.readInt())) { + foundGoodFileType = true; + break; + } + } + if (!foundGoodFileType) { + // The types were not compatible and there is only one ftyp atom, so reject the file. + return false; + } + } else if (atomDataSize != 0) { + // Skip the atom. + input.advancePeekPosition(atomDataSize); + } + } + return foundGoodFileType && fragmented == isFragmented; + } + + /** + * Returns whether {@code brand} is an ftyp atom brand that is compatible with the MP4 extractors. + */ + private static boolean isCompatibleBrand(int brand) { + // Accept all brands starting '3gp'. + if (brand >>> 8 == Util.getIntegerCodeForString("3gp")) { + return true; + } + for (int compatibleBrand : COMPATIBLE_BRANDS) { + if (compatibleBrand == brand) { + return true; + } + } + return false; + } + + private Sniffer() { + // Prevent instantiation. + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java new file mode 100755 index 00000000000..e784faacabc --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/Track.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Encapsulates information describing an MP4 track. + */ +public final class Track { + + /** + * The transformation to apply to samples in the track, if any. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TRANSFORMATION_NONE, TRANSFORMATION_CEA608_CDAT}) + public @interface Transformation {} + /** + * A no-op sample transformation. + */ + public static final int TRANSFORMATION_NONE = 0; + /** + * A transformation for caption samples in cdat atoms. + */ + public static final int TRANSFORMATION_CEA608_CDAT = 1; + + /** + * The track identifier. + */ + public final int id; + + /** + * One of {@link C#TRACK_TYPE_AUDIO}, {@link C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_TEXT}. + */ + public final int type; + + /** + * The track timescale, defined as the number of time units that pass in one second. + */ + public final long timescale; + + /** + * The movie timescale. + */ + public final long movieTimescale; + + /** + * The duration of the track in microseconds, or {@link C#TIME_UNSET} if unknown. + */ + public final long durationUs; + + /** + * The format. + */ + public final Format format; + + /** + * One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each + * sample. + */ + @Transformation + public final int sampleTransformation; + + /** + * Track encryption boxes for the different track sample descriptions. Entries may be null. + */ + public final TrackEncryptionBox[] sampleDescriptionEncryptionBoxes; + + /** + * Durations of edit list segments in the movie timescale. Null if there is no edit list. + */ + public final long[] editListDurations; + + /** + * Media times for edit list segments in the track timescale. Null if there is no edit list. + */ + public final long[] editListMediaTimes; + + /** + * For H264 video tracks, the length in bytes of the NALUnitLength field in each sample. -1 for + * other track types. + */ + public final int nalUnitLengthFieldLength; + + public Track(int id, int type, long timescale, long movieTimescale, long durationUs, + Format format, @Transformation int sampleTransformation, + TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength, + long[] editListDurations, long[] editListMediaTimes) { + this.id = id; + this.type = type; + this.timescale = timescale; + this.movieTimescale = movieTimescale; + this.durationUs = durationUs; + this.format = format; + this.sampleTransformation = sampleTransformation; + this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.editListDurations = editListDurations; + this.editListMediaTimes = editListMediaTimes; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackEncryptionBox.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java similarity index 93% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackEncryptionBox.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java index 06679653338..92e801c9cc0 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackEncryptionBox.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackEncryptionBox.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; /** * Encapsulates information parsed from a track encryption (tenc) box or sample group description diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackFragment.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackFragment.java new file mode 100755 index 00000000000..aab40d0cfc1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackFragment.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.mp4; + +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * A holder for information corresponding to a single fragment of an mp4 file. + */ +/* package */ final class TrackFragment { + + /** + * The default values for samples from the track fragment header. + */ + public DefaultSampleValues header; + /** + * The position (byte offset) of the start of fragment. + */ + public long atomPosition; + /** + * The position (byte offset) of the start of data contained in the fragment. + */ + public long dataPosition; + /** + * The position (byte offset) of the start of auxiliary data. + */ + public long auxiliaryDataPosition; + /** + * The number of track runs of the fragment. + */ + public int trunCount; + /** + * The total number of samples in the fragment. + */ + public int sampleCount; + /** + * The position (byte offset) of the start of sample data of each track run in the fragment. + */ + public long[] trunDataPosition; + /** + * The number of samples contained by each track run in the fragment. + */ + public int[] trunLength; + /** + * The size of each sample in the fragment. + */ + public int[] sampleSizeTable; + /** + * The composition time offset of each sample in the fragment. + */ + public int[] sampleCompositionTimeOffsetTable; + /** + * The decoding time of each sample in the fragment. + */ + public long[] sampleDecodingTimeTable; + /** + * Indicates which samples are sync frames. + */ + public boolean[] sampleIsSyncFrameTable; + /** + * Whether the fragment defines encryption data. + */ + public boolean definesEncryptionData; + /** + * If {@link #definesEncryptionData} is true, indicates which samples use sub-sample encryption. + * Undefined otherwise. + */ + public boolean[] sampleHasSubsampleEncryptionTable; + /** + * Fragment specific track encryption. May be null. + */ + public TrackEncryptionBox trackEncryptionBox; + /** + * If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data. + * Undefined otherwise. + */ + public int sampleEncryptionDataLength; + /** + * If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined + * otherwise. + */ + public ParsableByteArray sampleEncryptionData; + /** + * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. + */ + public boolean sampleEncryptionDataNeedsFill; + /** + * The absolute decode time of the start of the next fragment. + */ + public long nextFragmentDecodeTime; + + /** + * Resets the fragment. + *

        + * {@link #sampleCount} and {@link #nextFragmentDecodeTime} are set to 0, and both + * {@link #definesEncryptionData} and {@link #sampleEncryptionDataNeedsFill} is set to false, + * and {@link #trackEncryptionBox} is set to null. + */ + public void reset() { + trunCount = 0; + nextFragmentDecodeTime = 0; + definesEncryptionData = false; + sampleEncryptionDataNeedsFill = false; + trackEncryptionBox = null; + } + + /** + * Configures the fragment for the specified number of samples. + *

        + * The {@link #sampleCount} of the fragment is set to the specified sample count, and the + * contained tables are resized if necessary such that they are at least this length. + * + * @param sampleCount The number of samples in the new run. + */ + public void initTables(int trunCount, int sampleCount) { + this.trunCount = trunCount; + this.sampleCount = sampleCount; + if (trunLength == null || trunLength.length < trunCount) { + trunDataPosition = new long[trunCount]; + trunLength = new int[trunCount]; + } + if (sampleSizeTable == null || sampleSizeTable.length < sampleCount) { + // Size the tables 25% larger than needed, so as to make future resize operations less + // likely. The choice of 25% is relatively arbitrary. + int tableSize = (sampleCount * 125) / 100; + sampleSizeTable = new int[tableSize]; + sampleCompositionTimeOffsetTable = new int[tableSize]; + sampleDecodingTimeTable = new long[tableSize]; + sampleIsSyncFrameTable = new boolean[tableSize]; + sampleHasSubsampleEncryptionTable = new boolean[tableSize]; + } + } + + /** + * Configures the fragment to be one that defines encryption data of the specified length. + *

        + * {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to + * the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it + * is at least this length. + * + * @param length The length in bytes of the encryption data. + */ + public void initEncryptionData(int length) { + if (sampleEncryptionData == null || sampleEncryptionData.limit() < length) { + sampleEncryptionData = new ParsableByteArray(length); + } + sampleEncryptionDataLength = length; + definesEncryptionData = true; + sampleEncryptionDataNeedsFill = true; + } + + /** + * Fills {@link #sampleEncryptionData} from the provided input. + * + * @param input An {@link ExtractorInput} from which to read the encryption data. + */ + public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException { + input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + sampleEncryptionData.setPosition(0); + sampleEncryptionDataNeedsFill = false; + } + + /** + * Fills {@link #sampleEncryptionData} from the provided source. + * + * @param source A source from which to read the encryption data. + */ + public void fillEncryptionData(ParsableByteArray source) { + source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + sampleEncryptionData.setPosition(0); + sampleEncryptionDataNeedsFill = false; + } + + public long getSamplePresentationTime(int index) { + return sampleDecodingTimeTable[index] + sampleCompositionTimeOffsetTable[index]; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackSampleTable.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackSampleTable.java similarity index 79% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackSampleTable.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackSampleTable.java index b80a22b59a3..91d181e4cea 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/mp4/TrackSampleTable.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/mp4/TrackSampleTable.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.mp4; +package org.telegram.messenger.exoplayer2.extractor.mp4; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; /** * Sample table for a track in an MP4 file. */ /* package */ final class TrackSampleTable { - /** - * Sample index when no sample is available. - */ - public static final int NO_SAMPLE = -1; - /** * Number of samples. */ @@ -54,7 +49,8 @@ */ public final int[] flags; - TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, int[] flags) { + public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs, + int[] flags) { Assertions.checkArgument(sizes.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length); @@ -72,18 +68,18 @@ * timestamp, if one is available. * * @param timeUs Timestamp adjacent to which to find a synchronization sample. - * @return Index of the synchronization sample, or {@link #NO_SAMPLE} if none. + * @return Index of the synchronization sample, or {@link C#INDEX_UNSET} if none. */ public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) { // Video frame timestamps may not be sorted, so the behavior of this call can be undefined. // Frames are not reordered past synchronization samples so this works in practice. int startIndex = Util.binarySearchFloor(timestampsUs, timeUs, true, false); for (int i = startIndex; i >= 0; i--) { - if ((flags[i] & C.SAMPLE_FLAG_SYNC) != 0) { + if ((flags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0) { return i; } } - return NO_SAMPLE; + return C.INDEX_UNSET; } /** @@ -91,16 +87,16 @@ public int getIndexOfEarlierOrEqualSynchronizationSample(long timeUs) { * if one is available. * * @param timeUs Timestamp adjacent to which to find a synchronization sample. - * @return index Index of the synchronization sample, or {@link #NO_SAMPLE} if none. + * @return index Index of the synchronization sample, or {@link C#INDEX_UNSET} if none. */ public int getIndexOfLaterOrEqualSynchronizationSample(long timeUs) { int startIndex = Util.binarySearchCeil(timestampsUs, timeUs, true, false); for (int i = startIndex; i < timestampsUs.length; i++) { - if ((flags[i] & C.SAMPLE_FLAG_SYNC) != 0) { + if ((flags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0) { return i; } } - return NO_SAMPLE; + return C.INDEX_UNSET; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/DefaultOggSeeker.java new file mode 100755 index 00000000000..c6680c6e826 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/DefaultOggSeeker.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.EOFException; +import java.io.IOException; + +/** + * Used to seek in an Ogg stream. + */ +/* package */ final class DefaultOggSeeker implements OggSeeker { + + //@VisibleForTesting + public static final int MATCH_RANGE = 72000; + //@VisibleForTesting + public static final int MATCH_BYTE_RANGE = 100000; + private static final int DEFAULT_OFFSET = 30000; + + private static final int STATE_SEEK_TO_END = 0; + private static final int STATE_READ_LAST_PAGE = 1; + private static final int STATE_SEEK = 2; + private static final int STATE_IDLE = 3; + + private final OggPageHeader pageHeader = new OggPageHeader(); + private final long startPosition; + private final long endPosition; + private final StreamReader streamReader; + + private int state; + private long totalGranules; + private volatile long queriedGranule; + private long positionBeforeSeekToEnd; + private long targetGranule; + + private long start; + private long end; + private long startGranule; + private long endGranule; + + /** + * Constructs an OggSeeker. + * @param startPosition Start position of the payload (inclusive). + * @param endPosition End position of the payload (exclusive). + * @param streamReader StreamReader instance which owns this OggSeeker + * @param firstPayloadPageSize The total size of the first payload page, in bytes. + * @param firstPayloadPageGranulePosition The granule position of the first payload page. + */ + public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader, + int firstPayloadPageSize, long firstPayloadPageGranulePosition) { + Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition); + this.streamReader = streamReader; + this.startPosition = startPosition; + this.endPosition = endPosition; + if (firstPayloadPageSize == endPosition - startPosition) { + totalGranules = firstPayloadPageGranulePosition; + state = STATE_IDLE; + } else { + state = STATE_SEEK_TO_END; + } + } + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + switch (state) { + case STATE_IDLE: + return -1; + case STATE_SEEK_TO_END: + positionBeforeSeekToEnd = input.getPosition(); + state = STATE_READ_LAST_PAGE; + // Seek to the end just before the last page of stream to get the duration. + long lastPageSearchPosition = endPosition - OggPageHeader.MAX_PAGE_SIZE; + if (lastPageSearchPosition > positionBeforeSeekToEnd) { + return lastPageSearchPosition; + } + // Fall through. + case STATE_READ_LAST_PAGE: + totalGranules = readGranuleOfLastPage(input); + state = STATE_IDLE; + return positionBeforeSeekToEnd; + case STATE_SEEK: + long currentGranule; + if (targetGranule == 0) { + currentGranule = 0; + } else { + long position = getNextSeekPosition(targetGranule, input); + if (position >= 0) { + return position; + } + currentGranule = skipToPageOfGranule(input, targetGranule, -(position + 2)); + } + state = STATE_IDLE; + return -(currentGranule + 2); + default: + // Never happens. + throw new IllegalStateException(); + } + } + + @Override + public long startSeek() { + Assertions.checkArgument(state == STATE_IDLE || state == STATE_SEEK); + targetGranule = queriedGranule; + state = STATE_SEEK; + resetSeeking(); + return targetGranule; + } + + @Override + public OggSeekMap createSeekMap() { + return totalGranules != 0 ? new OggSeekMap() : null; + } + + //@VisibleForTesting + public void resetSeeking() { + start = startPosition; + end = endPosition; + startGranule = 0; + endGranule = totalGranules; + } + + /** + * Returns a position converging to the {@code targetGranule} to which the {@link ExtractorInput} + * has to seek and then be passed for another call until a negative number is returned. If a + * negative number is returned the input is at a position which is before the target page and at + * which it is sensible to just skip pages to the target granule and pre-roll instead of doing + * another seek request. + * + * @param targetGranule the target granule position to seek to. + * @param input the {@link ExtractorInput} to read from. + * @return the position to seek the {@link ExtractorInput} to for a next call or + * -(currentGranule + 2) if it's close enough to skip to the target page. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + public long getNextSeekPosition(long targetGranule, ExtractorInput input) + throws IOException, InterruptedException { + if (start == end) { + return -(startGranule + 2); + } + + long initialPosition = input.getPosition(); + if (!skipToNextPage(input, end)) { + if (start == initialPosition) { + throw new IOException("No ogg page can be found."); + } + return start; + } + + pageHeader.populate(input, false); + input.resetPeekPosition(); + + long granuleDistance = targetGranule - pageHeader.granulePosition; + int pageSize = pageHeader.headerSize + pageHeader.bodySize; + if (granuleDistance < 0 || granuleDistance > MATCH_RANGE) { + if (granuleDistance < 0) { + end = initialPosition; + endGranule = pageHeader.granulePosition; + } else { + start = input.getPosition() + pageSize; + startGranule = pageHeader.granulePosition; + if (end - start + pageSize < MATCH_BYTE_RANGE) { + input.skipFully(pageSize); + return -(startGranule + 2); + } + } + + if (end - start < MATCH_BYTE_RANGE) { + end = start; + return start; + } + + long offset = pageSize * (granuleDistance <= 0 ? 2 : 1); + long nextPosition = input.getPosition() - offset + + (granuleDistance * (end - start) / (endGranule - startGranule)); + + nextPosition = Math.max(nextPosition, start); + nextPosition = Math.min(nextPosition, end - 1); + return nextPosition; + } + + // position accepted (before target granule and within MATCH_RANGE) + input.skipFully(pageSize); + return -(pageHeader.granulePosition + 2); + } + + private long getEstimatedPosition(long position, long granuleDistance, long offset) { + position += (granuleDistance * (endPosition - startPosition) / totalGranules) - offset; + if (position < startPosition) { + position = startPosition; + } + if (position >= endPosition) { + position = endPosition - 1; + } + return position; + } + + private class OggSeekMap implements SeekMap { + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + if (timeUs == 0) { + queriedGranule = 0; + return startPosition; + } + queriedGranule = streamReader.convertTimeToGranule(timeUs); + return getEstimatedPosition(startPosition, queriedGranule, DEFAULT_OFFSET); + } + + @Override + public long getDurationUs() { + return streamReader.convertGranuleToTime(totalGranules); + } + + } + + /** + * Skips to the next page. + * + * @param input The {@code ExtractorInput} to skip to the next page. + * @throws IOException thrown if peeking/reading from the input fails. + * @throws InterruptedException thrown if interrupted while peeking/reading from the input. + * @throws EOFException if the next page can't be found before the end of the input. + */ + //@VisibleForTesting + void skipToNextPage(ExtractorInput input) throws IOException, InterruptedException { + if (!skipToNextPage(input, endPosition)) { + // Not found until eof. + throw new EOFException(); + } + } + + /** + * Skips to the next page. Searches for the next page header. + * + * @param input The {@code ExtractorInput} to skip to the next page. + * @param until Searches until this position. + * @return true if the next page is found. + * @throws IOException thrown if peeking/reading from the input fails. + * @throws InterruptedException thrown if interrupted while peeking/reading from the input. + */ + //@VisibleForTesting + boolean skipToNextPage(ExtractorInput input, long until) + throws IOException, InterruptedException { + until = Math.min(until + 3, endPosition); + byte[] buffer = new byte[2048]; + int peekLength = buffer.length; + while (true) { + if (input.getPosition() + peekLength > until) { + // Make sure to not peek beyond the end of the input. + peekLength = (int) (until - input.getPosition()); + if (peekLength < 4) { + // Not found until end. + return false; + } + } + input.peekFully(buffer, 0, peekLength, false); + for (int i = 0; i < peekLength - 3; i++) { + if (buffer[i] == 'O' && buffer[i + 1] == 'g' && buffer[i + 2] == 'g' + && buffer[i + 3] == 'S') { + // Match! Skip to the start of the pattern. + input.skipFully(i); + return true; + } + } + // Overlap by not skipping the entire peekLength. + input.skipFully(peekLength - 3); + } + } + + /** + * Skips to the last Ogg page in the stream and reads the header's granule field which is the + * total number of samples per channel. + * + * @param input The {@link ExtractorInput} to read from. + * @return the total number of samples of this input. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + long readGranuleOfLastPage(ExtractorInput input) + throws IOException, InterruptedException { + skipToNextPage(input); + pageHeader.reset(); + while ((pageHeader.type & 0x04) != 0x04 && input.getPosition() < endPosition) { + pageHeader.populate(input, false); + input.skipFully(pageHeader.headerSize + pageHeader.bodySize); + } + return pageHeader.granulePosition; + } + + /** + * Skips to the position of the start of the page containing the {@code targetGranule} and + * returns the granule of the page previous to the target page. + * + * @param input the {@link ExtractorInput} to read from. + * @param targetGranule the target granule. + * @param currentGranule the current granule or -1 if it's unknown. + * @return the granule of the prior page or the {@code currentGranule} if there isn't a prior + * page. + * @throws ParserException thrown if populating the page header fails. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from the input. + */ + //@VisibleForTesting + long skipToPageOfGranule(ExtractorInput input, long targetGranule, long currentGranule) + throws IOException, InterruptedException { + pageHeader.populate(input, false); + while (pageHeader.granulePosition < targetGranule) { + input.skipFully(pageHeader.headerSize + pageHeader.bodySize); + // Store in a member field to be able to resume after IOExceptions. + currentGranule = pageHeader.granulePosition; + // Peek next header. + pageHeader.populate(input, false); + } + input.resetPeekPosition(); + return currentGranule; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/FlacReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/FlacReader.java new file mode 100755 index 00000000000..a0a474bceda --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/FlacReader.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.util.FlacStreamInfo; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * {@link StreamReader} to extract Flac data out of Ogg byte stream. + */ +/* package */ final class FlacReader extends StreamReader { + + private static final byte AUDIO_PACKET_TYPE = (byte) 0xFF; + private static final byte SEEKTABLE_PACKET_TYPE = 0x03; + + private static final int FRAME_HEADER_SAMPLE_NUMBER_OFFSET = 4; + + private FlacStreamInfo streamInfo; + private FlacOggSeeker flacOggSeeker; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + return data.bytesLeft() >= 5 && data.readUnsignedByte() == 0x7F && // packet type + data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + streamInfo = null; + flacOggSeeker = null; + } + } + + private static boolean isAudioPacket(byte[] data) { + return data[0] == AUDIO_PACKET_TYPE; + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + if (!isAudioPacket(packet.data)) { + return -1; + } + return getFlacFrameBlockSize(packet); + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + byte[] data = packet.data; + if (streamInfo == null) { + streamInfo = new FlacStreamInfo(data, 17); + byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit()); + metadata[4] = (byte) 0x80; // Set the last metadata block flag, ignore the other blocks + List initializationData = Collections.singletonList(metadata); + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_FLAC, null, + Format.NO_VALUE, streamInfo.bitRate(), streamInfo.channels, streamInfo.sampleRate, + initializationData, null, 0, null); + } else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) { + flacOggSeeker = new FlacOggSeeker(); + flacOggSeeker.parseSeekTable(packet); + } else if (isAudioPacket(data)) { + if (flacOggSeeker != null) { + flacOggSeeker.setFirstFrameOffset(position); + setupData.oggSeeker = flacOggSeeker; + } + return false; + } + return true; + } + + private int getFlacFrameBlockSize(ParsableByteArray packet) { + int blockSizeCode = (packet.data[2] & 0xFF) >> 4; + switch (blockSizeCode) { + case 1: + return 192; + case 2: + case 3: + case 4: + case 5: + return 576 << (blockSizeCode - 2); + case 6: + case 7: + // skip the sample number + packet.skipBytes(FRAME_HEADER_SAMPLE_NUMBER_OFFSET); + packet.readUtf8EncodedLong(); + int value = blockSizeCode == 6 ? packet.readUnsignedByte() : packet.readUnsignedShort(); + packet.setPosition(0); + return value + 1; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + return 256 << (blockSizeCode - 8); + } + return -1; + } + + private class FlacOggSeeker implements OggSeeker, SeekMap { + + private static final int METADATA_LENGTH_OFFSET = 1; + private static final int SEEK_POINT_SIZE = 18; + + private long[] sampleNumbers; + private long[] offsets; + private long firstFrameOffset = -1; + private volatile long queriedGranule; + private volatile long seekedGranule; + private long currentGranule = -1; + + public void setFirstFrameOffset(long firstFrameOffset) { + this.firstFrameOffset = firstFrameOffset; + } + + /** + * Parses a FLAC file seek table metadata structure and initializes internal fields. + * + * @param data + * A ParsableByteArray including whole seek table metadata block. Its position should be set + * to the beginning of the block. + * @see FLAC format + * METADATA_BLOCK_SEEKTABLE + */ + public void parseSeekTable(ParsableByteArray data) { + data.skipBytes(METADATA_LENGTH_OFFSET); + int length = data.readUnsignedInt24(); + int numberOfSeekPoints = length / SEEK_POINT_SIZE; + + sampleNumbers = new long[numberOfSeekPoints]; + offsets = new long[numberOfSeekPoints]; + + for (int i = 0; i < numberOfSeekPoints; i++) { + sampleNumbers[i] = data.readLong(); + offsets[i] = data.readLong(); + data.skipBytes(2); // Skip "Number of samples in the target frame." + } + } + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + if (currentGranule >= 0) { + currentGranule = -currentGranule - 2; + return currentGranule; + } + return -1; + } + + @Override + public synchronized long startSeek() { + currentGranule = seekedGranule; + return queriedGranule; + } + + @Override + public SeekMap createSeekMap() { + return this; + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public synchronized long getPosition(long timeUs) { + queriedGranule = convertTimeToGranule(timeUs); + int index = Util.binarySearchFloor(sampleNumbers, queriedGranule, true, true); + seekedGranule = sampleNumbers[index]; + return firstFrameOffset + offsets[index]; + } + + @Override + public long getDurationUs() { + return streamInfo.durationUs(); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java new file mode 100755 index 00000000000..3677b097a1f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggExtractor.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * Ogg {@link Extractor}. + */ +public class OggExtractor implements Extractor { + + /** + * Factory for {@link OggExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new OggExtractor()}; + } + + }; + + private static final int MAX_VERIFICATION_BYTES = 8; + + private StreamReader streamReader; + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + try { + OggPageHeader header = new OggPageHeader(); + if (!header.populate(input, true) || (header.type & 0x02) != 0x02) { + return false; + } + + int length = Math.min(header.bodySize, MAX_VERIFICATION_BYTES); + ParsableByteArray scratch = new ParsableByteArray(length); + input.peekFully(scratch.data, 0, length); + + if (FlacReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new FlacReader(); + } else if (VorbisReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new VorbisReader(); + } else if (OpusReader.verifyBitstreamType(resetPosition(scratch))) { + streamReader = new OpusReader(); + } else { + return false; + } + return true; + } catch (ParserException e) { + return false; + } + } + + @Override + public void init(ExtractorOutput output) { + TrackOutput trackOutput = output.track(0); + output.endTracks(); + // TODO: fix the case if sniff() isn't called + streamReader.init(output, trackOutput); + } + + @Override + public void seek(long position) { + streamReader.seek(position); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + return streamReader.read(input, seekPosition); + } + + //@VisibleForTesting + /* package */ StreamReader getStreamReader() { + return streamReader; + } + + private static ParsableByteArray resetPosition(ParsableByteArray scratch) { + scratch.setPosition(0); + return scratch; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java new file mode 100755 index 00000000000..53424bc81c6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPacket.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * OGG packet class. + */ +/* package */ final class OggPacket { + + private final OggPageHeader pageHeader = new OggPageHeader(); + private final ParsableByteArray packetArray = + new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0); + + private int currentSegmentIndex = C.INDEX_UNSET; + private int segmentCount; + private boolean populated; + + /** + * Resets this reader. + */ + public void reset() { + pageHeader.reset(); + packetArray.reset(); + currentSegmentIndex = C.INDEX_UNSET; + populated = false; + } + + /** + * Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make + * sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader + * can resume properly from an error while reading a continued packet spanned across multiple + * pages. + * + * @param input the {@link ExtractorInput} to read data from. + * @return {@code true} if the read was successful. {@code false} if the end of the input was + * encountered having read no data. + * @throws IOException thrown if reading from the input fails. + * @throws InterruptedException thrown if interrupted while reading from input. + */ + public boolean populate(ExtractorInput input) throws IOException, InterruptedException { + Assertions.checkState(input != null); + + if (populated) { + populated = false; + packetArray.reset(); + } + + while (!populated) { + if (currentSegmentIndex < 0) { + // We're at the start of a page. + if (!pageHeader.populate(input, true)) { + return false; + } + int segmentIndex = 0; + int bytesToSkip = pageHeader.headerSize; + if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) { + // After seeking, the first packet may be the remainder + // part of a continued packet which has to be discarded. + bytesToSkip += calculatePacketSize(segmentIndex); + segmentIndex += segmentCount; + } + input.skipFully(bytesToSkip); + currentSegmentIndex = segmentIndex; + } + + int size = calculatePacketSize(currentSegmentIndex); + int segmentIndex = currentSegmentIndex + segmentCount; + if (size > 0) { + input.readFully(packetArray.data, packetArray.limit(), size); + packetArray.setLimit(packetArray.limit() + size); + populated = pageHeader.laces[segmentIndex - 1] != 255; + } + // Advance now since we are sure reading didn't throw an exception. + currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? C.INDEX_UNSET + : segmentIndex; + } + return true; + } + + /** + * An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read, + * or an empty header if the packet has yet to be populated. + *

        + * Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent + * calls to {@link #populate(ExtractorInput)}. + * + * @return the {@code PageHeader} of the last page read or an empty header if the packet has yet + * to be populated. + */ + //@VisibleForTesting + public OggPageHeader getPageHeader() { + return pageHeader; + } + + /** + * Returns a {@link ParsableByteArray} containing the packet's payload. + */ + public ParsableByteArray getPayload() { + return packetArray; + } + + /** + * Calculates the size of the packet starting from {@code startSegmentIndex}. + * + * @param startSegmentIndex the index of the first segment of the packet. + * @return Size of the packet. + */ + private int calculatePacketSize(int startSegmentIndex) { + segmentCount = 0; + int size = 0; + while (startSegmentIndex + segmentCount < pageHeader.pageSegmentCount) { + int segmentLength = pageHeader.laces[startSegmentIndex + segmentCount++]; + size += segmentLength; + if (segmentLength != 255) { + // packets end at first lace < 255 + break; + } + } + return size; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPageHeader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPageHeader.java new file mode 100755 index 00000000000..476845a8df0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggPageHeader.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.EOFException; +import java.io.IOException; + +/** + * Data object to store header information. + */ +/* package */ final class OggPageHeader { + + public static final int EMPTY_PAGE_HEADER_SIZE = 27; + public static final int MAX_SEGMENT_COUNT = 255; + public static final int MAX_PAGE_PAYLOAD = 255 * 255; + public static final int MAX_PAGE_SIZE = EMPTY_PAGE_HEADER_SIZE + MAX_SEGMENT_COUNT + + MAX_PAGE_PAYLOAD; + + private static final int TYPE_OGGS = Util.getIntegerCodeForString("OggS"); + + public int revision; + public int type; + public long granulePosition; + public long streamSerialNumber; + public long pageSequenceNumber; + public long pageChecksum; + public int pageSegmentCount; + public int headerSize; + public int bodySize; + /** + * Be aware that {@code laces.length} is always {@link #MAX_SEGMENT_COUNT}. Instead use + * {@link #pageSegmentCount} to iterate. + */ + public final int[] laces = new int[MAX_SEGMENT_COUNT]; + + private final ParsableByteArray scratch = new ParsableByteArray(MAX_SEGMENT_COUNT); + + /** + * Resets all primitive member fields to zero. + */ + public void reset() { + revision = 0; + type = 0; + granulePosition = 0; + streamSerialNumber = 0; + pageSequenceNumber = 0; + pageChecksum = 0; + pageSegmentCount = 0; + headerSize = 0; + bodySize = 0; + } + + /** + * Peeks an Ogg page header and updates this {@link OggPageHeader}. + * + * @param input the {@link ExtractorInput} to read from. + * @param quiet if {@code true} no Exceptions are thrown but {@code false} is return if something + * goes wrong. + * @return {@code true} if the read was successful. {@code false} if the end of the input was + * encountered having read no data. + * @throws IOException thrown if reading data fails or the stream is invalid. + * @throws InterruptedException thrown if thread is interrupted when reading/peeking. + */ + public boolean populate(ExtractorInput input, boolean quiet) + throws IOException, InterruptedException { + scratch.reset(); + reset(); + boolean hasEnoughBytes = input.getLength() == C.LENGTH_UNSET + || input.getLength() - input.getPeekPosition() >= EMPTY_PAGE_HEADER_SIZE; + if (!hasEnoughBytes || !input.peekFully(scratch.data, 0, EMPTY_PAGE_HEADER_SIZE, true)) { + if (quiet) { + return false; + } else { + throw new EOFException(); + } + } + if (scratch.readUnsignedInt() != TYPE_OGGS) { + if (quiet) { + return false; + } else { + throw new ParserException("expected OggS capture pattern at begin of page"); + } + } + + revision = scratch.readUnsignedByte(); + if (revision != 0x00) { + if (quiet) { + return false; + } else { + throw new ParserException("unsupported bit stream revision"); + } + } + type = scratch.readUnsignedByte(); + + granulePosition = scratch.readLittleEndianLong(); + streamSerialNumber = scratch.readLittleEndianUnsignedInt(); + pageSequenceNumber = scratch.readLittleEndianUnsignedInt(); + pageChecksum = scratch.readLittleEndianUnsignedInt(); + pageSegmentCount = scratch.readUnsignedByte(); + headerSize = EMPTY_PAGE_HEADER_SIZE + pageSegmentCount; + + // calculate total size of header including laces + scratch.reset(); + input.peekFully(scratch.data, 0, pageSegmentCount); + for (int i = 0; i < pageSegmentCount; i++) { + laces[i] = scratch.readUnsignedByte(); + bodySize += laces[i]; + } + + return true; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggSeeker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggSeeker.java new file mode 100755 index 00000000000..1140501a643 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OggSeeker.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import java.io.IOException; + +/** + * Used to seek in an Ogg stream. OggSeeker implementation may do direct seeking or progressive + * seeking. OggSeeker works together with a {@link SeekMap} instance to capture the queried position + * and start the seeking with an initial estimated position. + */ +/* package */ interface OggSeeker { + + /** + * Returns a {@link SeekMap} that returns an initial estimated position for progressive seeking + * or the final position for direct seeking. Returns null if {@link #read} has yet to return -1. + */ + SeekMap createSeekMap(); + + /** + * Initializes a seek operation. + * + * @return The granule position targeted by the seek. + */ + long startSeek(); + + /** + * Reads data from the {@link ExtractorInput} to build the {@link SeekMap} or to continue a + * progressive seek. + *

        + * If more data is required or if the position of the input needs to be modified then a position + * from which data should be provided is returned. Else a negative value is returned. If a seek + * has been completed then the value returned is -(currentGranule + 2). Else it is -1. + * + * @param input The {@link ExtractorInput} to read from. + * @return A non-negative position to seek the {@link ExtractorInput} to, or -(currentGranule + 2) + * if the progressive seek has completed, or -1 otherwise. + * @throws IOException If reading from the {@link ExtractorInput} fails. + * @throws InterruptedException If the thread is interrupted. + */ + long read(ExtractorInput input) throws IOException, InterruptedException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OpusReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OpusReader.java new file mode 100755 index 00000000000..01cb4dc5a1f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/OpusReader.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * {@link StreamReader} to extract Opus data out of Ogg byte stream. + */ +/* package */ final class OpusReader extends StreamReader { + + private static final int DEFAULT_SEEK_PRE_ROLL_SAMPLES = 3840; + + /** + * Opus streams are always decoded at 48000 Hz. + */ + private static final int SAMPLE_RATE = 48000; + + private static final int OPUS_CODE = Util.getIntegerCodeForString("Opus"); + private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}; + + private boolean headerRead; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + if (data.bytesLeft() < OPUS_SIGNATURE.length) { + return false; + } + byte[] header = new byte[OPUS_SIGNATURE.length]; + data.readBytes(header, 0, OPUS_SIGNATURE.length); + return Arrays.equals(header, OPUS_SIGNATURE); + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + headerRead = false; + } + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + return convertTimeToGranule(getPacketDurationUs(packet.data)); + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + if (!headerRead) { + byte[] metadata = Arrays.copyOf(packet.data, packet.limit()); + int channelCount = metadata[9] & 0xFF; + int preskip = ((metadata[11] & 0xFF) << 8) | (metadata[10] & 0xFF); + + List initializationData = new ArrayList<>(3); + initializationData.add(metadata); + putNativeOrderLong(initializationData, preskip); + putNativeOrderLong(initializationData, DEFAULT_SEEK_PRE_ROLL_SAMPLES); + + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, + null); + headerRead = true; + } else { + boolean headerPacket = packet.readInt() == OPUS_CODE; + packet.setPosition(0); + return headerPacket; + } + return true; + } + + private void putNativeOrderLong(List initializationData, int samples) { + long ns = (samples * C.NANOS_PER_SECOND) / SAMPLE_RATE; + byte[] array = ByteBuffer.allocate(8).order(ByteOrder.nativeOrder()).putLong(ns).array(); + initializationData.add(array); + } + + /** + * Returns the duration of the given audio packet. + * + * @param packet Contains audio data. + * @return Returns the duration of the given audio packet. + */ + private long getPacketDurationUs(byte[] packet) { + int toc = packet[0] & 0xFF; + int frames; + switch (toc & 0x3) { + case 0: + frames = 1; + break; + case 1: + case 2: + frames = 2; + break; + default: + frames = packet[1] & 0x3F; + break; + } + + int config = toc >> 3; + int length = config & 0x3; + if (config >= 16) { + length = 2500 << length; + } else if (config >= 12) { + length = 10000 << (length & 0x1); + } else if (length == 3) { + length = 60000; + } else { + length = 10000 << length; + } + return frames * length; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java new file mode 100755 index 00000000000..f1fedcb65c8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/StreamReader.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * StreamReader abstract class. + */ +/* package */ abstract class StreamReader { + + private static final int STATE_READ_HEADERS = 0; + private static final int STATE_SKIP_HEADERS = 1; + private static final int STATE_READ_PAYLOAD = 2; + private static final int STATE_END_OF_INPUT = 3; + + static class SetupData { + Format format; + OggSeeker oggSeeker; + } + + private OggPacket oggPacket; + private TrackOutput trackOutput; + private ExtractorOutput extractorOutput; + private OggSeeker oggSeeker; + private long targetGranule; + private long payloadStartPosition; + private long currentGranule; + private int state; + private int sampleRate; + private SetupData setupData; + private long lengthOfReadPacket; + private boolean seekMapSet; + private boolean formatSet; + + void init(ExtractorOutput output, TrackOutput trackOutput) { + this.extractorOutput = output; + this.trackOutput = trackOutput; + this.oggPacket = new OggPacket(); + + reset(true); + } + + /** + * Resets the state of the {@link StreamReader}. + * + * @param headerData Resets parsed header data too. + */ + protected void reset(boolean headerData) { + if (headerData) { + setupData = new SetupData(); + payloadStartPosition = 0; + state = STATE_READ_HEADERS; + } else { + state = STATE_SKIP_HEADERS; + } + targetGranule = -1; + currentGranule = 0; + } + + /** + * @see Extractor#seek(long) + */ + final void seek(long position) { + oggPacket.reset(); + if (position == 0) { + reset(!seekMapSet); + } else { + if (state != STATE_READ_HEADERS) { + targetGranule = oggSeeker.startSeek(); + state = STATE_READ_PAYLOAD; + } + } + } + + /** + * @see Extractor#read(ExtractorInput, PositionHolder) + */ + final int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + switch (state) { + case STATE_READ_HEADERS: + return readHeaders(input); + + case STATE_SKIP_HEADERS: + input.skipFully((int) payloadStartPosition); + state = STATE_READ_PAYLOAD; + return Extractor.RESULT_CONTINUE; + + case STATE_READ_PAYLOAD: + return readPayload(input, seekPosition); + + default: + // Never happens. + throw new IllegalStateException(); + } + } + + private int readHeaders(ExtractorInput input) throws IOException, InterruptedException { + boolean readingHeaders = true; + while (readingHeaders) { + if (!oggPacket.populate(input)) { + state = STATE_END_OF_INPUT; + return Extractor.RESULT_END_OF_INPUT; + } + lengthOfReadPacket = input.getPosition() - payloadStartPosition; + + readingHeaders = readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData); + if (readingHeaders) { + payloadStartPosition = input.getPosition(); + } + } + + sampleRate = setupData.format.sampleRate; + if (!formatSet) { + trackOutput.format(setupData.format); + formatSet = true; + } + + if (setupData.oggSeeker != null) { + oggSeeker = setupData.oggSeeker; + } else if (input.getLength() == C.LENGTH_UNSET) { + oggSeeker = new UnseekableOggSeeker(); + } else { + OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader(); + oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this, + firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize, + firstPayloadPageHeader.granulePosition); + } + + setupData = null; + state = STATE_READ_PAYLOAD; + return Extractor.RESULT_CONTINUE; + } + + private int readPayload(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + long position = oggSeeker.read(input); + if (position >= 0) { + seekPosition.position = position; + return Extractor.RESULT_SEEK; + } else if (position < -1) { + onSeekEnd(-position - 2); + } + if (!seekMapSet) { + SeekMap seekMap = oggSeeker.createSeekMap(); + extractorOutput.seekMap(seekMap); + seekMapSet = true; + } + + if (lengthOfReadPacket > 0 || oggPacket.populate(input)) { + lengthOfReadPacket = 0; + ParsableByteArray payload = oggPacket.getPayload(); + long granulesInPacket = preparePayload(payload); + if (granulesInPacket >= 0 && currentGranule + granulesInPacket >= targetGranule) { + // calculate time and send payload data to codec + long timeUs = convertGranuleToTime(currentGranule); + trackOutput.sampleData(payload, payload.limit()); + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, payload.limit(), 0, null); + targetGranule = -1; + } + currentGranule += granulesInPacket; + } else { + state = STATE_END_OF_INPUT; + return Extractor.RESULT_END_OF_INPUT; + } + return Extractor.RESULT_CONTINUE; + } + + /** + * Converts granule value to time. + * + * @param granule The granule value. + * @return Time in milliseconds. + */ + protected long convertGranuleToTime(long granule) { + return (granule * C.MICROS_PER_SECOND) / sampleRate; + } + + /** + * Converts time value to granule. + * + * @param timeUs Time in milliseconds. + * @return The granule value. + */ + protected long convertTimeToGranule(long timeUs) { + return (sampleRate * timeUs) / C.MICROS_PER_SECOND; + } + + /** + * Prepares payload data in the packet for submitting to TrackOutput and returns number of + * granules in the packet. + * + * @param packet Ogg payload data packet. + * @return Number of granules in the packet or -1 if the packet doesn't contain payload data. + */ + protected abstract long preparePayload(ParsableByteArray packet); + + /** + * Checks if the given packet is a header packet and reads it. + * + * @param packet An ogg packet. + * @param position Position of the given header packet. + * @param setupData Setup data to be filled. + * @return Whether the packet contains header data. + */ + protected abstract boolean readHeaders(ParsableByteArray packet, long position, + SetupData setupData) throws IOException, InterruptedException; + + /** + * Called on end of seeking. + * + * @param currentGranule Current granule at the current position of input. + */ + protected void onSeekEnd(long currentGranule) { + this.currentGranule = currentGranule; + } + + private static final class UnseekableOggSeeker implements OggSeeker { + + @Override + public long read(ExtractorInput input) throws IOException, InterruptedException { + return -1; + } + + @Override + public long startSeek() { + return 0; + } + + @Override + public SeekMap createSeekMap() { + return new SeekMap.Unseekable(C.TIME_UNSET); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisBitArray.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java index 148421005e5..30a1bfea132 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisBitArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisBitArray.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ogg; +package org.telegram.messenger.exoplayer2.extractor.ogg; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Assertions; /** * Wraps a byte array, providing methods that allow it to be read as a vorbis bitstream. @@ -26,7 +26,7 @@ /* package */ final class VorbisBitArray { public final byte[] data; - private int limit; + private final int limit; private int byteOffset; private int bitOffset; @@ -50,7 +50,9 @@ public VorbisBitArray(byte[] data, int limit) { this.limit = limit * 8; } - /** Resets the reading position to zero. */ + /** + * Resets the reading position to zero. + */ public void reset() { byteOffset = 0; bitOffset = 0; @@ -69,7 +71,7 @@ public boolean readBit() { * Reads up to 32 bits. * * @param numBits The number of bits to read. - * @return An int whose bottom {@code numBits} bits hold the read data. + * @return An integer whose bottom {@code numBits} bits hold the read data. */ public int readBits(int numBits) { Assertions.checkState(getPosition() + numBits <= limit); @@ -109,7 +111,7 @@ public int readBits(int numBits) { /** * Skips {@code numberOfBits} bits. * - * @param numberOfBits the number of bits to skip. + * @param numberOfBits The number of bits to skip. */ public void skipBits(int numberOfBits) { Assertions.checkState(getPosition() + numberOfBits <= limit); @@ -122,18 +124,16 @@ public void skipBits(int numberOfBits) { } /** - * Gets the current reading position in bits. - * - * @return the current reading position in bits. + * Returns the reading position in bits. */ public int getPosition() { return byteOffset * 8 + bitOffset; } /** - * Sets the index of the current reading position in bits. + * Sets the reading position in bits. * - * @param position the new reading position in bits. + * @param position The new reading position in bits. */ public void setPosition(int position) { Assertions.checkArgument(position < limit && position >= 0); @@ -142,9 +142,7 @@ public void setPosition(int position) { } /** - * Gets the number of remaining bits. - * - * @return number of remaining bits. + * Returns the number of remaining bits. */ public int bitsLeft() { return limit - getPosition(); @@ -152,8 +150,6 @@ public int bitsLeft() { /** * Returns the limit in bits. - * - * @return the limit in bits. **/ public int limit() { return limit; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java new file mode 100755 index 00000000000..8ee884643b4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisReader.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ogg; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ogg.VorbisUtil.Mode; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; +import java.util.ArrayList; + +/** + * {@link StreamReader} to extract Vorbis data out of Ogg byte stream. + */ +/* package */ final class VorbisReader extends StreamReader { + + private VorbisSetup vorbisSetup; + private int previousPacketBlockSize; + private boolean seenFirstAudioPacket; + + private VorbisUtil.VorbisIdHeader vorbisIdHeader; + private VorbisUtil.CommentHeader commentHeader; + + public static boolean verifyBitstreamType(ParsableByteArray data) { + try { + return VorbisUtil.verifyVorbisHeaderCapturePattern(0x01, data, true); + } catch (ParserException e) { + return false; + } + } + + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + vorbisSetup = null; + vorbisIdHeader = null; + commentHeader = null; + } + previousPacketBlockSize = 0; + seenFirstAudioPacket = false; + } + + @Override + protected void onSeekEnd(long currentGranule) { + super.onSeekEnd(currentGranule); + seenFirstAudioPacket = currentGranule != 0; + previousPacketBlockSize = vorbisIdHeader != null ? vorbisIdHeader.blockSize0 : 0; + } + + @Override + protected long preparePayload(ParsableByteArray packet) { + // if this is not an audio packet... + if ((packet.data[0] & 0x01) == 1) { + return -1; + } + + // ... we need to decode the block size + int packetBlockSize = decodeBlockSize(packet.data[0], vorbisSetup); + // a packet contains samples produced from overlapping the previous and current frame data + // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) + int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 + : 0; + // codec expects the number of samples appended to audio data + appendNumberOfSamples(packet, samplesInPacket); + + // update state in members for next iteration + seenFirstAudioPacket = true; + previousPacketBlockSize = packetBlockSize; + return samplesInPacket; + } + + @Override + protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) + throws IOException, InterruptedException { + if (vorbisSetup != null) { + return false; + } + + vorbisSetup = readSetupHeaders(packet); + if (vorbisSetup == null) { + return true; + } + + ArrayList codecInitialisationData = new ArrayList<>(); + codecInitialisationData.add(vorbisSetup.idHeader.data); + codecInitialisationData.add(vorbisSetup.setupHeaderData); + + setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null, + this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD, + this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, + codecInitialisationData, null, 0, null); + return true; + } + + //@VisibleForTesting + /* package */ VorbisSetup readSetupHeaders(ParsableByteArray scratch) throws IOException { + + if (vorbisIdHeader == null) { + vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); + return null; + } + + if (commentHeader == null) { + commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); + return null; + } + + // the third packet contains the setup header + byte[] setupHeaderData = new byte[scratch.limit()]; + // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 + System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); + // partially decode setup header to get the modes + Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); + // we need the ilog of modes all the time when extracting, so we compute it once + int iLogModes = VorbisUtil.iLog(modes.length - 1); + + return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); + } + + /** + * Reads an int of {@code length} bits from {@code src} starting at + * {@code leastSignificantBitIndex}. + * + * @param src the {@code byte} to read from. + * @param length the length in bits of the int to read. + * @param leastSignificantBitIndex the index of the least significant bit of the int to read. + * @return the int value read. + */ + //@VisibleForTesting + /* package */ static int readBits(byte src, int length, int leastSignificantBitIndex) { + return (src >> leastSignificantBitIndex) & (255 >>> (8 - length)); + } + + //@VisibleForTesting + /* package */ static void appendNumberOfSamples(ParsableByteArray buffer, + long packetSampleCount) { + + buffer.setLimit(buffer.limit() + 4); + // The vorbis decoder expects the number of samples in the packet + // to be appended to the audio data as an int32 + buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF); + buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF); + buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF); + buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF); + } + + private static int decodeBlockSize(byte firstByteOfAudioPacket, VorbisSetup vorbisSetup) { + // read modeNumber (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-730004.3.1) + int modeNumber = readBits(firstByteOfAudioPacket, vorbisSetup.iLogModes, 1); + int currentBlockSize; + if (!vorbisSetup.modes[modeNumber].blockFlag) { + currentBlockSize = vorbisSetup.idHeader.blockSize0; + } else { + currentBlockSize = vorbisSetup.idHeader.blockSize1; + } + return currentBlockSize; + } + + /** + * Class to hold all data read from Vorbis setup headers. + */ + /* package */ static final class VorbisSetup { + + public final VorbisUtil.VorbisIdHeader idHeader; + public final VorbisUtil.CommentHeader commentHeader; + public final byte[] setupHeaderData; + public final Mode[] modes; + public final int iLogModes; + + public VorbisSetup(VorbisUtil.VorbisIdHeader idHeader, VorbisUtil.CommentHeader + commentHeader, byte[] setupHeaderData, Mode[] modes, int iLogModes) { + this.idHeader = idHeader; + this.commentHeader = commentHeader; + this.setupHeaderData = setupHeaderData; + this.modes = modes; + this.iLogModes = iLogModes; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisUtil.java similarity index 96% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisUtil.java index 9663ae28a92..93989e33956 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ogg/VorbisUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ogg/VorbisUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ogg; +package org.telegram.messenger.exoplayer2.extractor.ogg; import android.util.Log; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.util.Arrays; /** @@ -119,15 +119,23 @@ public static CommentHeader readVorbisCommentHeader(ParsableByteArray headerData * * @param headerType the type of the header expected. * @param header the alleged header bytes. - * @param quite if {@code true} no exceptions are thrown. Instead {@code false} is returned. + * @param quiet if {@code true} no exceptions are thrown. Instead {@code false} is returned. * @return the number of bytes read. * @throws ParserException thrown if header type or capture pattern is not as expected. */ public static boolean verifyVorbisHeaderCapturePattern(int headerType, ParsableByteArray header, - boolean quite) + boolean quiet) throws ParserException { + if (header.bytesLeft() < 7) { + if (quiet) { + return false; + } else { + throw new ParserException("too short header: " + header.bytesLeft()); + } + } + if (header.readUnsignedByte() != headerType) { - if (quite) { + if (quiet) { return false; } else { throw new ParserException("expected header type " + Integer.toHexString(headerType)); @@ -140,7 +148,7 @@ public static boolean verifyVorbisHeaderCapturePattern(int headerType, ParsableB && header.readUnsignedByte() == 'b' && header.readUnsignedByte() == 'i' && header.readUnsignedByte() == 's')) { - if (quite) { + if (quiet) { return false; } else { throw new ParserException("expected characters 'vorbis'"); @@ -381,11 +389,9 @@ private static CodeBook readBook(VorbisBitArray bitArray) throws ParserException if (dimensions != 0) { lookupValuesCount = mapType1QuantValues(entries, dimensions); } else { - // TODO no sample file found yet lookupValuesCount = 0; } } else { - // TODO no sample file found yet lookupValuesCount = entries * dimensions; } // discard (no decoding required yet) diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/rawcc/RawCcExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/rawcc/RawCcExtractor.java new file mode 100755 index 00000000000..18034da4ebb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.rawcc; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Extracts CEA data from a RawCC file. + */ +public final class RawCcExtractor implements Extractor { + + private static final int SCRATCH_SIZE = 9; + private static final int HEADER_SIZE = 8; + private static final int HEADER_ID = Util.getIntegerCodeForString("RCC\u0001"); + private static final int TIMESTAMP_SIZE_V0 = 4; + private static final int TIMESTAMP_SIZE_V1 = 8; + + // Parser states. + private static final int STATE_READING_HEADER = 0; + private static final int STATE_READING_TIMESTAMP_AND_COUNT = 1; + private static final int STATE_READING_SAMPLES = 2; + + private final Format format; + + private final ParsableByteArray dataScratch; + + private TrackOutput trackOutput; + + private int parserState; + private int version; + private long timestampUs; + private int remainingSampleCount; + private int sampleBytesWritten; + + public RawCcExtractor(Format format) { + this.format = format; + dataScratch = new ParsableByteArray(SCRATCH_SIZE); + parserState = STATE_READING_HEADER; + } + + @Override + public void init(ExtractorOutput output) { + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + trackOutput = output.track(0); + output.endTracks(); + trackOutput.format(format); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + dataScratch.reset(); + input.peekFully(dataScratch.data, 0, HEADER_SIZE); + return dataScratch.readInt() == HEADER_ID; + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + while (true) { + switch (parserState) { + case STATE_READING_HEADER: + parseHeader(input); + parserState = STATE_READING_TIMESTAMP_AND_COUNT; + break; + case STATE_READING_TIMESTAMP_AND_COUNT: + if (parseTimestampAndSampleCount(input)) { + parserState = STATE_READING_SAMPLES; + } else { + parserState = STATE_READING_HEADER; + return RESULT_END_OF_INPUT; + } + break; + case STATE_READING_SAMPLES: + parseSamples(input); + parserState = STATE_READING_TIMESTAMP_AND_COUNT; + return RESULT_CONTINUE; + default: + throw new IllegalStateException(); + } + } + } + + @Override + public void seek(long position) { + parserState = STATE_READING_HEADER; + } + + @Override + public void release() { + // Do nothing + } + + private void parseHeader(ExtractorInput input) throws IOException, InterruptedException { + dataScratch.reset(); + input.readFully(dataScratch.data, 0, HEADER_SIZE); + if (dataScratch.readInt() != HEADER_ID) { + throw new IOException("Input not RawCC"); + } + version = dataScratch.readUnsignedByte(); + // no versions use the flag fields yet + } + + private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException, + InterruptedException { + dataScratch.reset(); + if (version == 0) { + if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V0 + 1, true)) { + return false; + } + // version 0 timestamps are 45kHz, so we need to convert them into us + timestampUs = dataScratch.readUnsignedInt() * 1000 / 45; + } else if (version == 1) { + if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V1 + 1, true)) { + return false; + } + timestampUs = dataScratch.readLong(); + } else { + throw new ParserException("Unsupported version number: " + version); + } + + remainingSampleCount = dataScratch.readUnsignedByte(); + sampleBytesWritten = 0; + return true; + } + + private void parseSamples(ExtractorInput input) throws IOException, InterruptedException { + for (; remainingSampleCount > 0; remainingSampleCount--) { + dataScratch.reset(); + input.readFully(dataScratch.data, 0, 3); + + trackOutput.sampleData(dataScratch, 3); + sampleBytesWritten += 3; + } + + if (sampleBytesWritten > 0) { + trackOutput.sampleMetadata(timestampUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java new file mode 100755 index 00000000000..5e0be0be0fb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Extractor.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.audio.Ac3Util; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; + +import java.io.IOException; + +/** + * Facilitates the extraction of AC-3 samples from elementary audio files formatted as AC-3 + * bitstreams. + */ +public final class Ac3Extractor implements Extractor { + + /** + * Factory for {@link Ac3Extractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new Ac3Extractor()}; + } + + }; + + /** + * The maximum number of bytes to search when sniffing, excluding ID3 information, before giving + * up. + */ + private static final int MAX_SNIFF_BYTES = 8 * 1024; + private static final int AC3_SYNC_WORD = 0x0B77; + private static final int MAX_SYNC_FRAME_SIZE = 2786; + private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + + private final long firstSampleTimestampUs; + private final ParsableByteArray sampleData; + + private Ac3Reader reader; + private boolean startedPacket; + + public Ac3Extractor() { + this(0); + } + + public Ac3Extractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + sampleData = new ParsableByteArray(MAX_SYNC_FRAME_SIZE); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // Skip any ID3 headers. + ParsableByteArray scratch = new ParsableByteArray(10); + int startPosition = 0; + while (true) { + input.peekFully(scratch.data, 0, 10); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != ID3_TAG) { + break; + } + scratch.skipBytes(3); + int length = scratch.readSynchSafeInt(); + startPosition += 10 + length; + input.advancePeekPosition(length); + } + input.resetPeekPosition(); + input.advancePeekPosition(startPosition); + + int headerPosition = startPosition; + int validFramesCount = 0; + while (true) { + input.peekFully(scratch.data, 0, 5); + scratch.setPosition(0); + int syncBytes = scratch.readUnsignedShort(); + if (syncBytes != AC3_SYNC_WORD) { + validFramesCount = 0; + input.resetPeekPosition(); + if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) { + return false; + } + input.advancePeekPosition(headerPosition); + } else { + if (++validFramesCount >= 4) { + return true; + } + int frameSize = Ac3Util.parseAc3SyncframeSize(scratch.data); + if (frameSize == C.LENGTH_UNSET) { + return false; + } + input.advancePeekPosition(frameSize - 5); + } + } + } + + @Override + public void init(ExtractorOutput output) { + reader = new Ac3Reader(); // TODO: Add support for embedded ID3. + reader.createTracks(output, new TrackIdGenerator(0, 1)); + output.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position) { + startedPacket = false; + reader.seek(); + } + + @Override + public void release() { + // Do nothing. + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, + InterruptedException { + int bytesRead = input.read(sampleData.data, 0, MAX_SYNC_FRAME_SIZE); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + + // Feed whatever data we have to the reader, regardless of whether the read finished or not. + sampleData.setPosition(0); + sampleData.setLimit(bytesRead); + + if (!startedPacket) { + // Pass data to the reader as though it's contained within a single infinitely long packet. + reader.packetStarted(firstSampleTimestampUs, true); + startedPacket = true; + } + // TODO: Make it possible for the reader to consume the dataSource directly, so that it becomes + // unnecessary to copy the data through packetBuffer. + reader.consume(sampleData); + return RESULT_CONTINUE; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Reader.java new file mode 100755 index 00000000000..22a5b0b2f55 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Ac3Reader.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.audio.Ac3Util; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. + */ +/* package */ final class Ac3Reader implements ElementaryStreamReader { + + private static final int STATE_FINDING_SYNC = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_SAMPLE = 2; + + private static final int HEADER_SIZE = 8; + + private final ParsableBitArray headerScratchBits; + private final ParsableByteArray headerScratchBytes; + private final String language; + + private TrackOutput output; + + private int state; + private int bytesRead; + + // Used to find the header. + private boolean lastByteWas0B; + + // Used when parsing the header. + private long sampleDurationUs; + private Format format; + private int sampleSize; + private boolean isEac3; + + // Used when reading the samples. + private long timeUs; + + /** + * Constructs a new reader for (E-)AC-3 elementary streams. + */ + public Ac3Reader() { + this(null); + } + + /** + * Constructs a new reader for (E-)AC-3 elementary streams. + * + * @param language Track language. + */ + public Ac3Reader(String language) { + headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); + headerScratchBytes = new ParsableByteArray(headerScratchBits.data); + state = STATE_FINDING_SYNC; + this.language = language; + } + + @Override + public void seek() { + state = STATE_FINDING_SYNC; + bytesRead = 0; + lastByteWas0B = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { + output = extractorOutput.track(generator.getNextId()); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SYNC: + if (skipToNextSync(data)) { + state = STATE_READING_HEADER; + headerScratchBytes.data[0] = 0x0B; + headerScratchBytes.data[1] = 0x77; + bytesRead = 2; + } + break; + case STATE_READING_HEADER: + if (continueRead(data, headerScratchBytes.data, HEADER_SIZE)) { + parseHeader(); + headerScratchBytes.setPosition(0); + output.sampleData(headerScratchBytes, HEADER_SIZE); + state = STATE_READING_SAMPLE; + } + break; + case STATE_READING_SAMPLE: + int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + output.sampleData(data, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + timeUs += sampleDurationUs; + state = STATE_FINDING_SYNC; + } + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read. + * @param targetLength The target length of the read. + * @return Whether the target length was reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + source.readBytes(target, bytesRead, bytesToRead); + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + /** + * Locates the next syncword, advancing the position to the byte that immediately follows it. If a + * syncword was not located, the position is advanced to the limit. + * + * @param pesBuffer The buffer whose position should be advanced. + * @return Whether a syncword position was found. + */ + private boolean skipToNextSync(ParsableByteArray pesBuffer) { + while (pesBuffer.bytesLeft() > 0) { + if (!lastByteWas0B) { + lastByteWas0B = pesBuffer.readUnsignedByte() == 0x0B; + continue; + } + int secondByte = pesBuffer.readUnsignedByte(); + if (secondByte == 0x77) { + lastByteWas0B = false; + return true; + } else { + lastByteWas0B = secondByte == 0x0B; + } + } + return false; + } + + /** + * Parses the sample header. + */ + private void parseHeader() { + if (format == null) { + // We read ahead to distinguish between AC-3 and E-AC-3. + headerScratchBits.skipBits(40); + isEac3 = headerScratchBits.readBits(5) == 16; + headerScratchBits.setPosition(headerScratchBits.getPosition() - 45); + format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language , null) + : Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null); + output.format(format); + } + sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data) + : Ac3Util.parseAc3SyncframeSize(headerScratchBits.data); + int audioSamplesPerSyncframe = isEac3 + ? Ac3Util.parseEAc3SyncframeAudioSampleCount(headerScratchBits.data) + : Ac3Util.getAc3SyncframeAudioSampleCount(); + // In this class a sample is an access unit (syncframe in AC-3), but the MediaFormat sample rate + // specifies the number of PCM audio samples per second. + sampleDurationUs = + (int) (C.MICROS_PER_SECOND * audioSamplesPerSyncframe / format.sampleRate); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java new file mode 100755 index 00000000000..295fd3c8e04 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsExtractor.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS + * headers. + */ +public final class AdtsExtractor implements Extractor { + + /** + * Factory for {@link AdtsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new AdtsExtractor()}; + } + + }; + + private static final int MAX_PACKET_SIZE = 200; + private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + /** + * The maximum number of bytes to search when sniffing, excluding the header, before giving up. + * Frame sizes are represented by 13-bit fields, so expect a valid frame in the first 8192 bytes. + */ + private static final int MAX_SNIFF_BYTES = 8 * 1024; + + private final long firstSampleTimestampUs; + private final ParsableByteArray packetBuffer; + + // Accessed only by the loading thread. + private AdtsReader reader; + private boolean startedPacket; + + public AdtsExtractor() { + this(0); + } + + public AdtsExtractor(long firstSampleTimestampUs) { + this.firstSampleTimestampUs = firstSampleTimestampUs; + packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); + } + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // Skip any ID3 headers. + ParsableByteArray scratch = new ParsableByteArray(10); + ParsableBitArray scratchBits = new ParsableBitArray(scratch.data); + int startPosition = 0; + while (true) { + input.peekFully(scratch.data, 0, 10); + scratch.setPosition(0); + if (scratch.readUnsignedInt24() != ID3_TAG) { + break; + } + scratch.skipBytes(3); + int length = scratch.readSynchSafeInt(); + startPosition += 10 + length; + input.advancePeekPosition(length); + } + input.resetPeekPosition(); + input.advancePeekPosition(startPosition); + + // Try to find four or more consecutive AAC audio frames, exceeding the MPEG TS packet size. + int headerPosition = startPosition; + int validFramesSize = 0; + int validFramesCount = 0; + while (true) { + input.peekFully(scratch.data, 0, 2); + scratch.setPosition(0); + int syncBytes = scratch.readUnsignedShort(); + if ((syncBytes & 0xFFF6) != 0xFFF0) { + validFramesCount = 0; + validFramesSize = 0; + input.resetPeekPosition(); + if (++headerPosition - startPosition >= MAX_SNIFF_BYTES) { + return false; + } + input.advancePeekPosition(headerPosition); + } else { + if (++validFramesCount >= 4 && validFramesSize > 188) { + return true; + } + + // Skip the frame. + input.peekFully(scratch.data, 0, 4); + scratchBits.setPosition(14); + int frameSize = scratchBits.readBits(13); + // Either the stream is malformed OR we're not parsing an ADTS stream. + if (frameSize <= 6) { + return false; + } + input.advancePeekPosition(frameSize - 6); + validFramesSize += frameSize; + } + } + } + + @Override + public void init(ExtractorOutput output) { + reader = new AdtsReader(true); + reader.createTracks(output, new TrackIdGenerator(0, 1)); + output.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position) { + startedPacket = false; + reader.seek(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + int bytesRead = input.read(packetBuffer.data, 0, MAX_PACKET_SIZE); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + + // Feed whatever data we have to the reader, regardless of whether the read finished or not. + packetBuffer.setPosition(0); + packetBuffer.setLimit(bytesRead); + + if (!startedPacket) { + // Pass data to the reader as though it's contained within a single infinitely long packet. + reader.packetStarted(firstSampleTimestampUs, true); + startedPacket = true; + } + // TODO: Make it possible for reader to consume the dataSource directly, so that it becomes + // unnecessary to copy the data through packetBuffer. + reader.consume(packetBuffer); + return RESULT_CONTINUE; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsReader.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsReader.java index a7b85ff5fc6..0911f92b9c9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/AdtsReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/AdtsReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,24 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; import android.util.Log; import android.util.Pair; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.CodecSpecificDataUtil; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.DummyTrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.CodecSpecificDataUtil; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.util.Arrays; import java.util.Collections; /** * Parses a continuous ADTS byte stream and extracts individual frames. */ -/* package */ final class AdtsReader extends ElementaryStreamReader { +/* package */ final class AdtsReader implements ElementaryStreamReader { private static final String TAG = "AdtsReader"; @@ -53,9 +56,13 @@ private static final int ID3_SIZE_OFFSET = 6; private static final byte[] ID3_IDENTIFIER = {'I', 'D', '3'}; + private final boolean exposeId3; private final ParsableBitArray adtsScratch; private final ParsableByteArray id3HeaderBuffer; - private final TrackOutput id3Output; + private final String language; + + private TrackOutput output; + private TrackOutput id3Output; private int state; private int bytesRead; @@ -76,16 +83,22 @@ private long currentSampleDuration; /** - * @param output A {@link TrackOutput} to which AAC samples should be written. - * @param id3Output A {@link TrackOutput} to which ID3 samples should be written. + * @param exposeId3 True if the reader should expose ID3 information. + */ + public AdtsReader(boolean exposeId3) { + this(exposeId3, null); + } + + /** + * @param exposeId3 True if the reader should expose ID3 information. + * @param language Track language. */ - public AdtsReader(TrackOutput output, TrackOutput id3Output) { - super(output); - this.id3Output = id3Output; - id3Output.format(MediaFormat.createId3Format()); + public AdtsReader(boolean exposeId3, String language) { adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); setFindingSampleState(); + this.exposeId3 = exposeId3; + this.language = language; } @Override @@ -93,6 +106,18 @@ public void seek() { setFindingSampleState(); } + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + if (exposeId3) { + id3Output = extractorOutput.track(idGenerator.getNextId()); + id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, + Format.NO_VALUE, null)); + } else { + id3Output = new DummyTrackOutput(); + } + } + @Override public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { timeUs = pesTimeUs; @@ -275,13 +300,13 @@ private void parseAdtsHeader() { Pair audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( audioSpecificConfig); - MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, MimeTypes.AUDIO_AAC, - MediaFormat.NO_VALUE, MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, audioParams.second, - audioParams.first, Collections.singletonList(audioSpecificConfig), null); + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first, + Collections.singletonList(audioSpecificConfig), null, 0, language); // In this class a sample is an access unit, but the MediaFormat sample rate specifies the // number of PCM audio samples per second. - sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / mediaFormat.sampleRate; - output.format(mediaFormat); + sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate; + output.format(format); hasOutputFormat = true; } else { adtsScratch.skipBits(10); @@ -304,7 +329,7 @@ private void readSample(ParsableByteArray data) { currentOutput.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { - currentOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); + currentOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); timeUs += currentSampleDuration; setFindingSampleState(); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java new file mode 100755 index 00000000000..8280204e623 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.support.annotation.IntDef; +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Default implementation for {@link TsPayloadReader.Factory}. + */ +public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { + + /** + * Flags controlling elementary stream readers behaviour. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, + FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS}) + public @interface Flags { + } + public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; + public static final int FLAG_IGNORE_AAC_STREAM = 2; + public static final int FLAG_IGNORE_H264_STREAM = 4; + public static final int FLAG_DETECT_ACCESS_UNITS = 8; + + @Flags + private final int flags; + + public DefaultTsPayloadReaderFactory() { + this(0); + } + + public DefaultTsPayloadReaderFactory(@Flags int flags) { + this.flags = flags; + } + + @Override + public SparseArray createInitialPayloadReaders() { + return new SparseArray<>(); + } + + @Override + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { + switch (streamType) { + case TsExtractor.TS_STREAM_TYPE_MPA: + case TsExtractor.TS_STREAM_TYPE_MPA_LSF: + return new PesReader(new MpegAudioReader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AAC: + return (flags & FLAG_IGNORE_AAC_STREAM) != 0 ? null + : new PesReader(new AdtsReader(false, esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AC3: + case TsExtractor.TS_STREAM_TYPE_E_AC3: + return new PesReader(new Ac3Reader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_DTS: + case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: + return new PesReader(new DtsReader(esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_H262: + return new PesReader(new H262Reader()); + case TsExtractor.TS_STREAM_TYPE_H264: + return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null + : new PesReader(new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0, + (flags & FLAG_DETECT_ACCESS_UNITS) != 0)); + case TsExtractor.TS_STREAM_TYPE_H265: + return new PesReader(new H265Reader()); + case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO: + return new SectionReader(new SpliceInfoSectionReader()); + case TsExtractor.TS_STREAM_TYPE_ID3: + return new PesReader(new Id3Reader()); + default: + return null; + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/DtsReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DtsReader.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/DtsReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DtsReader.java index 8af21b4f0cd..d146e434d1d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/DtsReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/DtsReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.DtsUtil; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.audio.DtsUtil; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; /** * Parses a continuous DTS byte stream and extracts individual samples. */ -/* package */ final class DtsReader extends ElementaryStreamReader { +/* package */ final class DtsReader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; @@ -35,6 +37,9 @@ private static final int SYNC_VALUE_SIZE = 4; private final ParsableByteArray headerScratchBytes; + private final String language; + + private TrackOutput output; private int state; private int bytesRead; @@ -44,7 +49,7 @@ // Used when parsing the header. private long sampleDurationUs; - private MediaFormat mediaFormat; + private Format format; private int sampleSize; // Used when reading the samples. @@ -53,16 +58,16 @@ /** * Constructs a new reader for DTS elementary streams. * - * @param output Track output for extracted samples. + * @param language Track language. */ - public DtsReader(TrackOutput output) { - super(output); + public DtsReader(String language) { headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); headerScratchBytes.data[2] = (byte) ((SYNC_VALUE >> 8) & 0xFF); headerScratchBytes.data[3] = (byte) (SYNC_VALUE & 0xFF); state = STATE_FINDING_SYNC; + this.language = language; } @Override @@ -72,6 +77,11 @@ public void seek() { syncBytes = 0; } + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + } + @Override public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { timeUs = pesTimeUs; @@ -100,7 +110,7 @@ public void consume(ParsableByteArray data) { output.sampleData(data, bytesToRead); bytesRead += bytesToRead; if (bytesRead == sampleSize) { - output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); timeUs += sampleDurationUs; state = STATE_FINDING_SYNC; } @@ -135,7 +145,7 @@ private boolean continueRead(ParsableByteArray source, byte[] target, int target * follows it. If SYNC was not located, the position is advanced to the limit. * * @param pesBuffer The buffer whose position should be advanced. - * @return True if SYNC was found. False otherwise. + * @return Whether SYNC was found. */ private boolean skipToNextSync(ParsableByteArray pesBuffer) { while (pesBuffer.bytesLeft() > 0) { @@ -154,15 +164,15 @@ private boolean skipToNextSync(ParsableByteArray pesBuffer) { */ private void parseHeader() { byte[] frameData = headerScratchBytes.data; - if (mediaFormat == null) { - mediaFormat = DtsUtil.parseDtsFormat(frameData, null, C.UNKNOWN_TIME_US, null); - output.format(mediaFormat); + if (format == null) { + format = DtsUtil.parseDtsFormat(frameData, null, language, null); + output.format(format); } sampleSize = DtsUtil.getDtsFrameSize(frameData); - // In this class a sample is an access unit (frame in DTS), but the MediaFormat sample rate + // In this class a sample is an access unit (frame in DTS), but the format's sample rate // specifies the number of PCM audio samples per second. sampleDurationUs = (int) (C.MICROS_PER_SECOND - * DtsUtil.parseDtsAudioSampleCount(frameData) / mediaFormat.sampleRate); + * DtsUtil.parseDtsAudioSampleCount(frameData) / format.sampleRate); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/ElementaryStreamReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/ElementaryStreamReader.java new file mode 100755 index 00000000000..cceee253349 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Extracts individual samples from an elementary media stream, preserving original order. + */ +public interface ElementaryStreamReader { + + /** + * Notifies the reader that a seek has occurred. + */ + void seek(); + + /** + * Initializes the reader by providing outputs and ids for the tracks. + * + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void createTracks(ExtractorOutput extractorOutput, PesReader.TrackIdGenerator idGenerator); + + /** + * Called when a packet starts. + * + * @param pesTimeUs The timestamp associated with the packet. + * @param dataAlignmentIndicator The data alignment indicator associated with the packet. + */ + void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); + + /** + * Consumes (possibly partial) data from the current packet. + * + * @param data The data to consume. + */ + void consume(ParsableByteArray data); + + /** + * Called when a packet ends. + */ + void packetFinished(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java new file mode 100755 index 00000000000..a6e3303aa61 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H262Reader.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; +import java.util.Collections; + +/** + * Parses a continuous H262 byte stream and extracts individual frames. + */ +/* package */ final class H262Reader implements ElementaryStreamReader { + + private static final int START_PICTURE = 0x00; + private static final int START_SEQUENCE_HEADER = 0xB3; + private static final int START_EXTENSION = 0xB5; + private static final int START_GROUP = 0xB8; + + private TrackOutput output; + + // Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. + private static final double[] FRAME_RATE_VALUES = new double[] { + 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60}; + + // State that should not be reset on seek. + private boolean hasOutputFormat; + private long frameDurationUs; + + // State that should be reset on seek. + private final boolean[] prefixFlags; + private final CsdBuffer csdBuffer; + private boolean foundFirstFrameInGroup; + private long totalBytesWritten; + + // Per packet state that gets reset at the start of each packet. + private long pesTimeUs; + private boolean pesPtsUsAvailable; + + // Per sample state that gets reset at the start of each frame. + private boolean isKeyframe; + private long framePosition; + private long frameTimeUs; + + public H262Reader() { + prefixFlags = new boolean[4]; + csdBuffer = new CsdBuffer(128); + } + + @Override + public void seek() { + NalUnitUtil.clearPrefixFlags(prefixFlags); + csdBuffer.reset(); + pesPtsUsAvailable = false; + foundFirstFrameInGroup = false; + totalBytesWritten = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + pesPtsUsAvailable = pesTimeUs != C.TIME_UNSET; + if (pesPtsUsAvailable) { + this.pesTimeUs = pesTimeUs; + } + } + + @Override + public void consume(ParsableByteArray data) { + int offset = data.getPosition(); + int limit = data.limit(); + byte[] dataArray = data.data; + + // Append the data to the buffer. + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + + int searchOffset = offset; + while (true) { + int startCodeOffset = NalUnitUtil.findNalUnit(dataArray, searchOffset, limit, prefixFlags); + + if (startCodeOffset == limit) { + // We've scanned to the end of the data without finding another start code. + if (!hasOutputFormat) { + csdBuffer.onData(dataArray, offset, limit); + } + return; + } + + // We've found a start code with the following value. + int startCodeValue = data.data[startCodeOffset + 3] & 0xFF; + + if (!hasOutputFormat) { + // This is the number of bytes from the current offset to the start of the next start + // code. It may be negative if the start code started in the previously consumed data. + int lengthToStartCode = startCodeOffset - offset; + if (lengthToStartCode > 0) { + csdBuffer.onData(dataArray, offset, startCodeOffset); + } + // This is the number of bytes belonging to the next start code that have already been + // passed to csdDataTargetBuffer. + int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; + if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { + // The csd data is complete, so we can decode and output the media format. + Pair result = parseCsdBuffer(csdBuffer); + output.format(result.first); + frameDurationUs = result.second; + hasOutputFormat = true; + } + } + + if (hasOutputFormat && (startCodeValue == START_GROUP || startCodeValue == START_PICTURE)) { + int bytesWrittenPastStartCode = limit - startCodeOffset; + if (foundFirstFrameInGroup) { + @C.BufferFlags int flags = isKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (totalBytesWritten - framePosition) - bytesWrittenPastStartCode; + output.sampleMetadata(frameTimeUs, flags, size, bytesWrittenPastStartCode, null); + isKeyframe = false; + } + if (startCodeValue == START_GROUP) { + foundFirstFrameInGroup = false; + isKeyframe = true; + } else /* startCodeValue == START_PICTURE */ { + frameTimeUs = pesPtsUsAvailable ? pesTimeUs : (frameTimeUs + frameDurationUs); + framePosition = totalBytesWritten - bytesWrittenPastStartCode; + pesPtsUsAvailable = false; + foundFirstFrameInGroup = true; + } + } + + offset = startCodeOffset; + searchOffset = offset + 3; + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Parses the {@link Format} and frame duration from a csd buffer. + * + * @param csdBuffer The csd buffer. + * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or + * 0 if the duration could not be determined. + */ + private static Pair parseCsdBuffer(CsdBuffer csdBuffer) { + byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length); + + int firstByte = csdData[4] & 0xFF; + int secondByte = csdData[5] & 0xFF; + int thirdByte = csdData[6] & 0xFF; + int width = (firstByte << 4) | (secondByte >> 4); + int height = (secondByte & 0x0F) << 8 | thirdByte; + + float pixelWidthHeightRatio = 1f; + int aspectRatioCode = (csdData[7] & 0xF0) >> 4; + switch(aspectRatioCode) { + case 2: + pixelWidthHeightRatio = (4 * height) / (float) (3 * width); + break; + case 3: + pixelWidthHeightRatio = (16 * height) / (float) (9 * width); + break; + case 4: + pixelWidthHeightRatio = (121 * height) / (float) (100 * width); + break; + default: + // Do nothing. + break; + } + + Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, null, + Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, + Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null); + + long frameDurationUs = 0; + int frameRateCodeMinusOne = (csdData[7] & 0x0F) - 1; + if (0 <= frameRateCodeMinusOne && frameRateCodeMinusOne < FRAME_RATE_VALUES.length) { + double frameRate = FRAME_RATE_VALUES[frameRateCodeMinusOne]; + int sequenceExtensionPosition = csdBuffer.sequenceExtensionPosition; + int frameRateExtensionN = (csdData[sequenceExtensionPosition + 9] & 0x60) >> 5; + int frameRateExtensionD = (csdData[sequenceExtensionPosition + 9] & 0x1F); + if (frameRateExtensionN != frameRateExtensionD) { + frameRate *= (frameRateExtensionN + 1d) / (frameRateExtensionD + 1); + } + frameDurationUs = (long) (C.MICROS_PER_SECOND / frameRate); + } + + return Pair.create(format, frameDurationUs); + } + + private static final class CsdBuffer { + + private boolean isFilling; + + public int length; + public int sequenceExtensionPosition; + public byte[] data; + + public CsdBuffer(int initialCapacity) { + data = new byte[initialCapacity]; + } + + /** + * Resets the buffer, clearing any data that it holds. + */ + public void reset() { + isFilling = false; + length = 0; + sequenceExtensionPosition = 0; + } + + /** + * Called when a start code is encountered in the stream. + * + * @param startCodeValue The start code value. + * @param bytesAlreadyPassed The number of bytes of the start code that have already been + * passed to {@link #onData(byte[], int, int)}, or 0. + * @return Whether the csd data is now complete. If true is returned, neither + * this method or {@link #onData(byte[], int, int)} should be called again without an + * interleaving call to {@link #reset()}. + */ + public boolean onStartCode(int startCodeValue, int bytesAlreadyPassed) { + if (isFilling) { + if (sequenceExtensionPosition == 0 && startCodeValue == START_EXTENSION) { + sequenceExtensionPosition = length; + } else { + length -= bytesAlreadyPassed; + isFilling = false; + return true; + } + } else if (startCodeValue == START_SEQUENCE_HEADER) { + isFilling = true; + } + return false; + } + + /** + * Called to pass stream data. + * + * @param newData Holds the data being passed. + * @param offset The offset of the data in {@code data}. + * @param limit The limit (exclusive) of the data in {@code data}. + */ + public void onData(byte[] newData, int offset, int limit) { + if (!isFilling) { + return; + } + int readLength = limit - offset; + if (data.length < length + readLength) { + data = Arrays.copyOf(data, (length + readLength) * 2); + } + System.arraycopy(newData, offset, data, length, readLength); + length += readLength; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java new file mode 100755 index 00000000000..f796632dd38 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H264Reader.java @@ -0,0 +1,516 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil.SpsData; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.ParsableNalUnitBitArray; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Parses a continuous H264 byte stream and extracts individual frames. + */ +/* package */ final class H264Reader implements ElementaryStreamReader { + + private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information + private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set + private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set + + private final boolean allowNonIdrKeyframes; + private final boolean detectAccessUnits; + private final NalUnitTargetBuffer sps; + private final NalUnitTargetBuffer pps; + private final NalUnitTargetBuffer sei; + private long totalBytesWritten; + private final boolean[] prefixFlags; + + private TrackOutput output; + private SeiReader seiReader; + private SampleReader sampleReader; + + // State that should not be reset on seek. + private boolean hasOutputFormat; + + // Per packet state that gets reset at the start of each packet. + private long pesTimeUs; + + // Scratch variables to avoid allocations. + private final ParsableByteArray seiWrapper; + + /** + * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as + * synchronization samples (key-frames). + * @param detectAccessUnits Whether to split the input stream into access units (samples) based on + * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs). + */ + public H264Reader(boolean allowNonIdrKeyframes, boolean detectAccessUnits) { + prefixFlags = new boolean[3]; + this.allowNonIdrKeyframes = allowNonIdrKeyframes; + this.detectAccessUnits = detectAccessUnits; + sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); + pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); + sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); + seiWrapper = new ParsableByteArray(); + } + + @Override + public void seek() { + NalUnitUtil.clearPrefixFlags(prefixFlags); + sps.reset(); + pps.reset(); + sei.reset(); + sampleReader.reset(); + totalBytesWritten = 0; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); + seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + this.pesTimeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + int offset = data.getPosition(); + int limit = data.limit(); + byte[] dataArray = data.data; + + // Append the data to the buffer. + totalBytesWritten += data.bytesLeft(); + output.sampleData(data, data.bytesLeft()); + + // Scan the appended data, processing NAL units as they are encountered + while (true) { + int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags); + + if (nalUnitOffset == limit) { + // We've scanned to the end of the data without finding the start of another NAL unit. + nalUnitData(dataArray, offset, limit); + return; + } + + // We've seen the start of a NAL unit of the following type. + int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset); + + // This is the number of bytes from the current offset to the start of the next NAL unit. + // It may be negative if the NAL unit started in the previously consumed data. + int lengthToNalUnit = nalUnitOffset - offset; + if (lengthToNalUnit > 0) { + nalUnitData(dataArray, offset, nalUnitOffset); + } + int bytesWrittenPastPosition = limit - nalUnitOffset; + long absolutePosition = totalBytesWritten - bytesWrittenPastPosition; + // Indicate the end of the previous NAL unit. If the length to the start of the next unit + // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes + // when notifying that the unit has ended. + endNalUnit(absolutePosition, bytesWrittenPastPosition, + lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs); + // Indicate the start of the next NAL unit. + startNalUnit(absolutePosition, nalUnitType, pesTimeUs); + // Continue scanning the data. + offset = nalUnitOffset + 3; + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + private void startNalUnit(long position, int nalUnitType, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.startNalUnit(nalUnitType); + pps.startNalUnit(nalUnitType); + } + sei.startNalUnit(nalUnitType); + sampleReader.startNalUnit(position, nalUnitType, pesTimeUs); + } + + private void nalUnitData(byte[] dataArray, int offset, int limit) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.appendToNalUnit(dataArray, offset, limit); + pps.appendToNalUnit(dataArray, offset, limit); + } + sei.appendToNalUnit(dataArray, offset, limit); + sampleReader.appendToNalUnit(dataArray, offset, limit); + } + + private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) { + if (!hasOutputFormat || sampleReader.needsSpsPps()) { + sps.endNalUnit(discardPadding); + pps.endNalUnit(discardPadding); + if (!hasOutputFormat) { + if (sps.isCompleted() && pps.isCompleted()) { + List initializationData = new ArrayList<>(); + initializationData.add(Arrays.copyOf(sps.nalData, sps.nalLength)); + initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); + NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); + NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); + output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null, + Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE, + initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null)); + hasOutputFormat = true; + sampleReader.putSps(spsData); + sampleReader.putPps(ppsData); + sps.reset(); + pps.reset(); + } + } else if (sps.isCompleted()) { + NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); + sampleReader.putSps(spsData); + sps.reset(); + } else if (pps.isCompleted()) { + NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength); + sampleReader.putPps(ppsData); + pps.reset(); + } + } + if (sei.endNalUnit(discardPadding)) { + int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength); + seiWrapper.reset(sei.nalData, unescapedLength); + seiWrapper.setPosition(4); // NAL prefix and nal_unit() header. + seiReader.consume(pesTimeUs, seiWrapper); + } + sampleReader.endNalUnit(position, offset); + } + + /** + * Consumes a stream of NAL units and outputs samples. + */ + private static final class SampleReader { + + private static final int DEFAULT_BUFFER_SIZE = 128; + + private static final int NAL_UNIT_TYPE_NON_IDR = 1; // Coded slice of a non-IDR picture + private static final int NAL_UNIT_TYPE_PARTITION_A = 2; // Coded slice data partition A + private static final int NAL_UNIT_TYPE_IDR = 5; // Coded slice of an IDR picture + private static final int NAL_UNIT_TYPE_AUD = 9; // Access unit delimiter + + private final TrackOutput output; + private final boolean allowNonIdrKeyframes; + private final boolean detectAccessUnits; + private final SparseArray sps; + private final SparseArray pps; + private final ParsableNalUnitBitArray bitArray; + + private byte[] buffer; + private int bufferLength; + + // Per NAL unit state. A sample consists of one or more NAL units. + private int nalUnitType; + private long nalUnitStartPosition; + private boolean isFilling; + private long nalUnitTimeUs; + private SliceHeaderData previousSliceHeader; + private SliceHeaderData sliceHeader; + + // Per sample state that gets reset at the start of each sample. + private boolean readingSample; + private long samplePosition; + private long sampleTimeUs; + private boolean sampleIsKeyframe; + + public SampleReader(TrackOutput output, boolean allowNonIdrKeyframes, + boolean detectAccessUnits) { + this.output = output; + this.allowNonIdrKeyframes = allowNonIdrKeyframes; + this.detectAccessUnits = detectAccessUnits; + sps = new SparseArray<>(); + pps = new SparseArray<>(); + previousSliceHeader = new SliceHeaderData(); + sliceHeader = new SliceHeaderData(); + buffer = new byte[DEFAULT_BUFFER_SIZE]; + bitArray = new ParsableNalUnitBitArray(buffer, 0, 0); + reset(); + } + + public boolean needsSpsPps() { + return detectAccessUnits; + } + + public void putSps(NalUnitUtil.SpsData spsData) { + sps.append(spsData.seqParameterSetId, spsData); + } + + public void putPps(NalUnitUtil.PpsData ppsData) { + pps.append(ppsData.picParameterSetId, ppsData); + } + + public void reset() { + isFilling = false; + readingSample = false; + sliceHeader.clear(); + } + + public void startNalUnit(long position, int type, long pesTimeUs) { + nalUnitType = type; + nalUnitTimeUs = pesTimeUs; + nalUnitStartPosition = position; + if ((allowNonIdrKeyframes && nalUnitType == NAL_UNIT_TYPE_NON_IDR) + || (detectAccessUnits && (nalUnitType == NAL_UNIT_TYPE_IDR + || nalUnitType == NAL_UNIT_TYPE_NON_IDR + || nalUnitType == NAL_UNIT_TYPE_PARTITION_A))) { + // Store the previous header and prepare to populate the new one. + SliceHeaderData newSliceHeader = previousSliceHeader; + previousSliceHeader = sliceHeader; + sliceHeader = newSliceHeader; + sliceHeader.clear(); + bufferLength = 0; + isFilling = true; + } + } + + /** + * Called to pass stream data. The data passed should not include the 3 byte start code. + * + * @param data Holds the data being passed. + * @param offset The offset of the data in {@code data}. + * @param limit The limit (exclusive) of the data in {@code data}. + */ + public void appendToNalUnit(byte[] data, int offset, int limit) { + if (!isFilling) { + return; + } + int readLength = limit - offset; + if (buffer.length < bufferLength + readLength) { + buffer = Arrays.copyOf(buffer, (bufferLength + readLength) * 2); + } + System.arraycopy(data, offset, buffer, bufferLength, readLength); + bufferLength += readLength; + + bitArray.reset(buffer, 0, bufferLength); + if (!bitArray.canReadBits(8)) { + return; + } + bitArray.skipBits(1); // forbidden_zero_bit + int nalRefIdc = bitArray.readBits(2); + bitArray.skipBits(5); // nal_unit_type + + // Read the slice header using the syntax defined in ITU-T Recommendation H.264 (2013) + // subsection 7.3.3. + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + bitArray.readUnsignedExpGolombCodedInt(); // first_mb_in_slice + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + int sliceType = bitArray.readUnsignedExpGolombCodedInt(); + if (!detectAccessUnits) { + // There are AUDs in the stream so the rest of the header can be ignored. + isFilling = false; + sliceHeader.setSliceType(sliceType); + return; + } + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + int picParameterSetId = bitArray.readUnsignedExpGolombCodedInt(); + if (pps.indexOfKey(picParameterSetId) < 0) { + // We have not seen the PPS yet, so don't try to decode the slice header. + isFilling = false; + return; + } + NalUnitUtil.PpsData ppsData = pps.get(picParameterSetId); + NalUnitUtil.SpsData spsData = sps.get(ppsData.seqParameterSetId); + if (spsData.separateColorPlaneFlag) { + if (!bitArray.canReadBits(2)) { + return; + } + bitArray.skipBits(2); // colour_plane_id + } + if (!bitArray.canReadBits(spsData.frameNumLength)) { + return; + } + boolean fieldPicFlag = false; + boolean bottomFieldFlagPresent = false; + boolean bottomFieldFlag = false; + int frameNum = bitArray.readBits(spsData.frameNumLength); + if (!spsData.frameMbsOnlyFlag) { + if (!bitArray.canReadBits(1)) { + return; + } + fieldPicFlag = bitArray.readBit(); + if (fieldPicFlag) { + if (!bitArray.canReadBits(1)) { + return; + } + bottomFieldFlag = bitArray.readBit(); + bottomFieldFlagPresent = true; + } + } + boolean idrPicFlag = nalUnitType == NAL_UNIT_TYPE_IDR; + int idrPicId = 0; + if (idrPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + idrPicId = bitArray.readUnsignedExpGolombCodedInt(); + } + int picOrderCntLsb = 0; + int deltaPicOrderCntBottom = 0; + int deltaPicOrderCnt0 = 0; + int deltaPicOrderCnt1 = 0; + if (spsData.picOrderCountType == 0) { + if (!bitArray.canReadBits(spsData.picOrderCntLsbLength)) { + return; + } + picOrderCntLsb = bitArray.readBits(spsData.picOrderCntLsbLength); + if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCntBottom = bitArray.readSignedExpGolombCodedInt(); + } + } else if (spsData.picOrderCountType == 1 + && !spsData.deltaPicOrderAlwaysZeroFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCnt0 = bitArray.readSignedExpGolombCodedInt(); + if (ppsData.bottomFieldPicOrderInFramePresentFlag && !fieldPicFlag) { + if (!bitArray.canReadExpGolombCodedNum()) { + return; + } + deltaPicOrderCnt1 = bitArray.readSignedExpGolombCodedInt(); + } + } + sliceHeader.setAll(spsData, nalRefIdc, sliceType, frameNum, picParameterSetId, fieldPicFlag, + bottomFieldFlagPresent, bottomFieldFlag, idrPicFlag, idrPicId, picOrderCntLsb, + deltaPicOrderCntBottom, deltaPicOrderCnt0, deltaPicOrderCnt1); + isFilling = false; + } + + public void endNalUnit(long position, int offset) { + if (nalUnitType == NAL_UNIT_TYPE_AUD + || (detectAccessUnits && sliceHeader.isFirstVclNalUnitOfPicture(previousSliceHeader))) { + // If the NAL unit ending is the start of a new sample, output the previous one. + if (readingSample) { + int nalUnitLength = (int) (position - nalUnitStartPosition); + outputSample(offset + nalUnitLength); + } + samplePosition = nalUnitStartPosition; + sampleTimeUs = nalUnitTimeUs; + sampleIsKeyframe = false; + readingSample = true; + } + sampleIsKeyframe |= nalUnitType == NAL_UNIT_TYPE_IDR || (allowNonIdrKeyframes + && nalUnitType == NAL_UNIT_TYPE_NON_IDR && sliceHeader.isISlice()); + } + + private void outputSample(int offset) { + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; + int size = (int) (nalUnitStartPosition - samplePosition); + output.sampleMetadata(sampleTimeUs, flags, size, offset, null); + } + + private static final class SliceHeaderData { + + private static final int SLICE_TYPE_I = 2; + private static final int SLICE_TYPE_ALL_I = 7; + + private boolean isComplete; + private boolean hasSliceType; + + private SpsData spsData; + private int nalRefIdc; + private int sliceType; + private int frameNum; + private int picParameterSetId; + private boolean fieldPicFlag; + private boolean bottomFieldFlagPresent; + private boolean bottomFieldFlag; + private boolean idrPicFlag; + private int idrPicId; + private int picOrderCntLsb; + private int deltaPicOrderCntBottom; + private int deltaPicOrderCnt0; + private int deltaPicOrderCnt1; + + public void clear() { + hasSliceType = false; + isComplete = false; + } + + public void setSliceType(int sliceType) { + this.sliceType = sliceType; + hasSliceType = true; + } + + public void setAll(SpsData spsData, int nalRefIdc, int sliceType, int frameNum, + int picParameterSetId, boolean fieldPicFlag, boolean bottomFieldFlagPresent, + boolean bottomFieldFlag, boolean idrPicFlag, int idrPicId, int picOrderCntLsb, + int deltaPicOrderCntBottom, int deltaPicOrderCnt0, int deltaPicOrderCnt1) { + this.spsData = spsData; + this.nalRefIdc = nalRefIdc; + this.sliceType = sliceType; + this.frameNum = frameNum; + this.picParameterSetId = picParameterSetId; + this.fieldPicFlag = fieldPicFlag; + this.bottomFieldFlagPresent = bottomFieldFlagPresent; + this.bottomFieldFlag = bottomFieldFlag; + this.idrPicFlag = idrPicFlag; + this.idrPicId = idrPicId; + this.picOrderCntLsb = picOrderCntLsb; + this.deltaPicOrderCntBottom = deltaPicOrderCntBottom; + this.deltaPicOrderCnt0 = deltaPicOrderCnt0; + this.deltaPicOrderCnt1 = deltaPicOrderCnt1; + isComplete = true; + hasSliceType = true; + } + + public boolean isISlice() { + return hasSliceType && (sliceType == SLICE_TYPE_ALL_I || sliceType == SLICE_TYPE_I); + } + + private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) { + // See ISO 14496-10 subsection 7.4.1.2.4. + return isComplete && (!other.isComplete || frameNum != other.frameNum + || picParameterSetId != other.picParameterSetId || fieldPicFlag != other.fieldPicFlag + || (bottomFieldFlagPresent && other.bottomFieldFlagPresent + && bottomFieldFlag != other.bottomFieldFlag) + || (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0)) + || (spsData.picOrderCountType == 0 && other.spsData.picOrderCountType == 0 + && (picOrderCntLsb != other.picOrderCntLsb + || deltaPicOrderCntBottom != other.deltaPicOrderCntBottom)) + || (spsData.picOrderCountType == 1 && other.spsData.picOrderCountType == 1 + && (deltaPicOrderCnt0 != other.deltaPicOrderCnt0 + || deltaPicOrderCnt1 != other.deltaPicOrderCnt1)) + || idrPicFlag != other.idrPicFlag + || (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId)); + } + + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H265Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java similarity index 90% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H265Reader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java index c2a1a60682b..054eff5606a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/H265Reader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/H265Reader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,22 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; import android.util.Log; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MimeTypes; -import org.telegram.messenger.exoplayer.util.NalUnitUtil; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.ParsableNalUnitBitArray; import java.util.Collections; /** * Parses a continuous H.265 byte stream and extracts individual frames. */ -/* package */ final class H265Reader extends ElementaryStreamReader { +/* package */ final class H265Reader implements ElementaryStreamReader { private static final String TAG = "H265Reader"; @@ -42,18 +44,20 @@ private static final int PREFIX_SEI_NUT = 39; private static final int SUFFIX_SEI_NUT = 40; + private TrackOutput output; + private SampleReader sampleReader; + private SeiReader seiReader; + // State that should not be reset on seek. private boolean hasOutputFormat; // State that should be reset on seek. - private final SeiReader seiReader; private final boolean[] prefixFlags; private final NalUnitTargetBuffer vps; private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer prefixSei; private final NalUnitTargetBuffer suffixSei; // TODO: Are both needed? - private final SampleReader sampleReader; private long totalBytesWritten; // Per packet state that gets reset at the start of each packet. @@ -62,20 +66,13 @@ // Scratch variables to avoid allocations. private final ParsableByteArray seiWrapper; - /** - * @param output A {@link TrackOutput} to which H.265 samples should be written. - * @param seiReader A reader for EIA-608 samples in SEI NAL units. - */ - public H265Reader(TrackOutput output, SeiReader seiReader) { - super(output); - this.seiReader = seiReader; + public H265Reader() { prefixFlags = new boolean[3]; vps = new NalUnitTargetBuffer(VPS_NUT, 128); sps = new NalUnitTargetBuffer(SPS_NUT, 128); pps = new NalUnitTargetBuffer(PPS_NUT, 128); prefixSei = new NalUnitTargetBuffer(PREFIX_SEI_NUT, 128); suffixSei = new NalUnitTargetBuffer(SUFFIX_SEI_NUT, 128); - sampleReader = new SampleReader(output); seiWrapper = new ParsableByteArray(); } @@ -91,6 +88,13 @@ public void seek() { totalBytesWritten = 0; } + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + sampleReader = new SampleReader(output); + seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); + } + @Override public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { this.pesTimeUs = pesTimeUs; @@ -201,7 +205,7 @@ private void endNalUnit(long position, int offset, int discardPadding, long pesT } } - private static MediaFormat parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps, + private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) { // Build codec-specific data. byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength]; @@ -209,9 +213,8 @@ private static MediaFormat parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTarg System.arraycopy(sps.nalData, 0, csd, vps.nalLength, sps.nalLength); System.arraycopy(pps.nalData, 0, csd, vps.nalLength + sps.nalLength, pps.nalLength); - // Unescape and then parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1. - NalUnitUtil.unescapeStream(sps.nalData, sps.nalLength); - ParsableBitArray bitArray = new ParsableBitArray(sps.nalData); + // Parse the SPS NAL unit, as per H.265/HEVC (2014) 7.3.2.2.1. + ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength); bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id int maxSubLayersMinus1 = bitArray.readBits(3); bitArray.skipBits(1); // sps_temporal_id_nesting_flag @@ -308,15 +311,15 @@ private static MediaFormat parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTarg } } - return MediaFormat.createVideoFormat(null, MimeTypes.VIDEO_H265, MediaFormat.NO_VALUE, - MediaFormat.NO_VALUE, C.UNKNOWN_TIME_US, picWidthInLumaSamples, picHeightInLumaSamples, - Collections.singletonList(csd), MediaFormat.NO_VALUE, pixelWidthHeightRatio); + return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, null, Format.NO_VALUE, + Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE, + Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null); } /** * Skips scaling_list_data(). See H.265/HEVC (2014) 7.3.4. */ - private static void skipScalingList(ParsableBitArray bitArray) { + private static void skipScalingList(ParsableNalUnitBitArray bitArray) { for (int sizeId = 0; sizeId < 4; sizeId++) { for (int matrixId = 0; matrixId < 6; matrixId += sizeId == 3 ? 3 : 1) { if (!bitArray.readBit()) { // scaling_list_pred_mode_flag[sizeId][matrixId] @@ -340,11 +343,11 @@ private static void skipScalingList(ParsableBitArray bitArray) { * Reads the number of short term reference picture sets in a SPS as ue(v), then skips all of * them. See H.265/HEVC (2014) 7.3.7. */ - private static void skipShortTermRefPicSets(ParsableBitArray bitArray) { + private static void skipShortTermRefPicSets(ParsableNalUnitBitArray bitArray) { int numShortTermRefPicSets = bitArray.readUnsignedExpGolombCodedInt(); boolean interRefPicSetPredictionFlag = false; - int numNegativePics = 0; - int numPositivePics = 0; + int numNegativePics; + int numPositivePics; // As this method applies in a SPS, the only element of NumDeltaPocs accessed is the previous // one, so we just keep track of that rather than storing the whole array. // RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1) and delta_idx_minus1 is always zero in SPS. @@ -472,7 +475,7 @@ public void endNalUnit(long position, int offset) { } private void outputSample(int offset) { - int flags = sampleIsKeyframe ? C.SAMPLE_FLAG_SYNC : 0; + @C.BufferFlags int flags = sampleIsKeyframe ? C.BUFFER_FLAG_KEY_FRAME : 0; int size = (int) (nalUnitStartPosition - samplePosition); output.sampleMetadata(sampleTimeUs, flags, size, offset, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Id3Reader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Id3Reader.java new file mode 100755 index 00000000000..f6a93d86098 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/Id3Reader.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.Log; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Parses ID3 data and extracts individual text information frames. + */ +/* package */ final class Id3Reader implements ElementaryStreamReader { + + private static final String TAG = "Id3Reader"; + + private static final int ID3_HEADER_SIZE = 10; + + private final ParsableByteArray id3Header; + + private TrackOutput output; + + // State that should be reset on seek. + private boolean writingSample; + + // Per sample state that gets reset at the start of each sample. + private long sampleTimeUs; + private int sampleSize; + private int sampleBytesRead; + + public Id3Reader() { + id3Header = new ParsableByteArray(ID3_HEADER_SIZE); + } + + @Override + public void seek() { + writingSample = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, + null)); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + if (!dataAlignmentIndicator) { + return; + } + writingSample = true; + sampleTimeUs = pesTimeUs; + sampleSize = 0; + sampleBytesRead = 0; + } + + @Override + public void consume(ParsableByteArray data) { + if (!writingSample) { + return; + } + int bytesAvailable = data.bytesLeft(); + if (sampleBytesRead < ID3_HEADER_SIZE) { + // We're still reading the ID3 header. + int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead); + System.arraycopy(data.data, data.getPosition(), id3Header.data, sampleBytesRead, + headerBytesAvailable); + if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) { + // We've finished reading the ID3 header. Extract the sample size. + id3Header.setPosition(0); + if ('I' != id3Header.readUnsignedByte() || 'D' != id3Header.readUnsignedByte() + || '3' != id3Header.readUnsignedByte()) { + Log.w(TAG, "Discarding invalid ID3 tag"); + writingSample = false; + return; + } + id3Header.skipBytes(3); // version (2) + flags (1) + sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt(); + } + } + // Write data to the output. + int bytesToWrite = Math.min(bytesAvailable, sampleSize - sampleBytesRead); + output.sampleData(data, bytesToWrite); + sampleBytesRead += bytesToWrite; + } + + @Override + public void packetFinished() { + if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) { + return; + } + output.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + writingSample = false; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/MpegAudioReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/MpegAudioReader.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/MpegAudioReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/MpegAudioReader.java index 640d1a361ea..56cda3b044f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/MpegAudioReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/MpegAudioReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.extractor.TrackOutput; -import org.telegram.messenger.exoplayer.util.MpegAudioHeader; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.MpegAudioHeader; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; /** * Parses a continuous MPEG Audio byte stream and extracts individual frames. */ -/* package */ final class MpegAudioReader extends ElementaryStreamReader { +/* package */ final class MpegAudioReader implements ElementaryStreamReader { private static final int STATE_FINDING_HEADER = 0; private static final int STATE_READING_HEADER = 1; @@ -34,6 +36,9 @@ private final ParsableByteArray headerScratch; private final MpegAudioHeader header; + private final String language; + + private TrackOutput output; private int state; private int frameBytesRead; @@ -49,13 +54,17 @@ // The timestamp to attach to the next sample in the current packet. private long timeUs; - public MpegAudioReader(TrackOutput output) { - super(output); + public MpegAudioReader() { + this(null); + } + + public MpegAudioReader(String language) { state = STATE_FINDING_HEADER; // The first byte of an MPEG Audio frame header is always 0xFF. headerScratch = new ParsableByteArray(4); headerScratch.data[0] = (byte) 0xFF; header = new MpegAudioHeader(); + this.language = language; } @Override @@ -65,6 +74,11 @@ public void seek() { lastByteWasFF = false; } + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + } + @Override public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { timeUs = pesTimeUs; @@ -162,10 +176,10 @@ private void readHeaderRemainder(ParsableByteArray source) { frameSize = header.frameSize; if (!hasOutputFormat) { frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; - MediaFormat mediaFormat = MediaFormat.createAudioFormat(null, header.mimeType, - MediaFormat.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, - header.channels, header.sampleRate, null, null); - output.format(mediaFormat); + Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE, + MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0, + language); + output.format(format); hasOutputFormat = true; } @@ -195,7 +209,7 @@ private void readFrameRemainder(ParsableByteArray source) { return; } - output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, frameSize, 0, null); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, frameSize, 0, null); timeUs += frameDurationUs; frameBytesRead = 0; state = STATE_FINDING_HEADER; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/NalUnitTargetBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/NalUnitTargetBuffer.java similarity index 85% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/NalUnitTargetBuffer.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/NalUnitTargetBuffer.java index f0a1d5422fd..c1749df7113 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/NalUnitTargetBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/NalUnitTargetBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.Arrays; /** @@ -56,7 +56,7 @@ public boolean isCompleted() { } /** - * Invoked to indicate that a NAL unit has started. + * Called to indicate that a NAL unit has started. * * @param type The type of the NAL unit. */ @@ -71,7 +71,7 @@ public void startNalUnit(int type) { } /** - * Invoked to pass stream data. The data passed should not include the 3 byte start code. + * Called to pass stream data. The data passed should not include the 3 byte start code. * * @param data Holds the data being passed. * @param offset The offset of the data in {@code data}. @@ -90,11 +90,11 @@ public void appendToNalUnit(byte[] data, int offset, int limit) { } /** - * Invoked to indicate that a NAL unit has ended. + * Called to indicate that a NAL unit has ended. * * @param discardPadding The number of excess bytes that were passed to * {@link #appendToNalUnit(byte[], int, int)}, which should be discarded. - * @return True if the ended NAL unit is of the target type. False otherwise. + * @return Whether the ended NAL unit is of the target type. */ public boolean endNalUnit(int discardPadding) { if (!isFilling) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PesReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PesReader.java new file mode 100755 index 00000000000..f2d92aa89c4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PesReader.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.Log; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Parses PES packet data and extracts samples. + */ +public final class PesReader implements TsPayloadReader { + + private static final String TAG = "PesReader"; + + private static final int STATE_FINDING_HEADER = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_HEADER_EXTENSION = 2; + private static final int STATE_READING_BODY = 3; + + private static final int HEADER_SIZE = 9; + private static final int MAX_HEADER_EXTENSION_SIZE = 10; + private static final int PES_SCRATCH_SIZE = 10; // max(HEADER_SIZE, MAX_HEADER_EXTENSION_SIZE) + + private final ElementaryStreamReader reader; + private final ParsableBitArray pesScratch; + + private int state; + private int bytesRead; + + private TimestampAdjuster timestampAdjuster; + private boolean ptsFlag; + private boolean dtsFlag; + private boolean seenFirstDts; + private int extendedHeaderLength; + private int payloadSize; + private boolean dataAlignmentIndicator; + private long timeUs; + + public PesReader(ElementaryStreamReader reader) { + this.reader = reader; + pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); + state = STATE_FINDING_HEADER; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + this.timestampAdjuster = timestampAdjuster; + reader.createTracks(extractorOutput, idGenerator); + } + + // TsPayloadReader implementation. + + @Override + public final void seek() { + state = STATE_FINDING_HEADER; + bytesRead = 0; + seenFirstDts = false; + reader.seek(); + } + + @Override + public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { + if (payloadUnitStartIndicator) { + switch (state) { + case STATE_FINDING_HEADER: + case STATE_READING_HEADER: + // Expected. + break; + case STATE_READING_HEADER_EXTENSION: + Log.w(TAG, "Unexpected start indicator reading extended header"); + break; + case STATE_READING_BODY: + // If payloadSize == -1 then the length of the previous packet was unspecified, and so + // we only know that it's finished now that we've seen the start of the next one. This + // is expected. If payloadSize != -1, then the length of the previous packet was known, + // but we didn't receive that amount of data. This is not expected. + if (payloadSize != -1) { + Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); + } + // Either way, notify the reader that it has now finished. + reader.packetFinished(); + break; + } + setState(STATE_READING_HEADER); + } + + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_HEADER: + data.skipBytes(data.bytesLeft()); + break; + case STATE_READING_HEADER: + if (continueRead(data, pesScratch.data, HEADER_SIZE)) { + setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); + } + break; + case STATE_READING_HEADER_EXTENSION: + int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); + // Read as much of the extended header as we're interested in, and skip the rest. + if (continueRead(data, pesScratch.data, readLength) + && continueRead(data, null, extendedHeaderLength)) { + parseHeaderExtension(); + reader.packetStarted(timeUs, dataAlignmentIndicator); + setState(STATE_READING_BODY); + } + break; + case STATE_READING_BODY: + readLength = data.bytesLeft(); + int padding = payloadSize == -1 ? 0 : readLength - payloadSize; + if (padding > 0) { + readLength -= padding; + data.setLimit(data.getPosition() + readLength); + } + reader.consume(data); + if (payloadSize != -1) { + payloadSize -= readLength; + if (payloadSize == 0) { + reader.packetFinished(); + setState(STATE_READING_HEADER); + } + } + break; + } + } + } + + private void setState(int state) { + this.state = state; + bytesRead = 0; + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read, or {@code null} to skip. + * @param targetLength The target length of the read. + * @return Whether the target length has been reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + if (bytesToRead <= 0) { + return true; + } else if (target == null) { + source.skipBytes(bytesToRead); + } else { + source.readBytes(target, bytesRead, bytesToRead); + } + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + private boolean parseHeader() { + // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of + // the header. + pesScratch.setPosition(0); + int startCodePrefix = pesScratch.readBits(24); + if (startCodePrefix != 0x000001) { + Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); + payloadSize = -1; + return false; + } + + pesScratch.skipBits(8); // stream_id. + int packetLength = pesScratch.readBits(16); + pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1) + dataAlignmentIndicator = pesScratch.readBit(); + pesScratch.skipBits(2); // copyright (1), original_or_copy (1) + ptsFlag = pesScratch.readBit(); + dtsFlag = pesScratch.readBit(); + // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), + // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) + pesScratch.skipBits(6); + extendedHeaderLength = pesScratch.readBits(8); + + if (packetLength == 0) { + payloadSize = -1; + } else { + payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */ + - HEADER_SIZE - extendedHeaderLength; + } + return true; + } + + private void parseHeaderExtension() { + pesScratch.setPosition(0); + timeUs = C.TIME_UNSET; + if (ptsFlag) { + pesScratch.skipBits(4); // '0010' or '0011' + long pts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + if (!seenFirstDts && dtsFlag) { + pesScratch.skipBits(4); // '0011' + long dts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + // Subsequent PES packets may have earlier presentation timestamps than this one, but they + // should all be greater than or equal to this packet's decode timestamp. We feed the + // decode timestamp to the adjuster here so that in the case that this is the first to be + // fed, the adjuster will be able to compute an offset to apply such that the adjusted + // presentation timestamps of all future packets are non-negative. + timestampAdjuster.adjustTsTimestamp(dts); + seenFirstDts = true; + } + timeUs = timestampAdjuster.adjustTsTimestamp(pts); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PsExtractor.java similarity index 80% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PsExtractor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PsExtractor.java index 190428704e9..3fa1e5eda7a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/ts/PsExtractor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/PsExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.ts; +package org.telegram.messenger.exoplayer2.extractor.ts; import android.util.SparseArray; -import org.telegram.messenger.exoplayer.extractor.Extractor; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.extractor.ExtractorOutput; -import org.telegram.messenger.exoplayer.extractor.PositionHolder; -import org.telegram.messenger.exoplayer.extractor.SeekMap; -import org.telegram.messenger.exoplayer.util.ParsableBitArray; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; import java.io.IOException; /** @@ -30,10 +34,23 @@ */ public final class PsExtractor implements Extractor { + /** + * Factory for {@link PsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new PsExtractor()}; + } + + }; + private static final int PACK_START_CODE = 0x000001BA; private static final int SYSTEM_HEADER_START_CODE = 0x000001BB; private static final int PACKET_START_CODE_PREFIX = 0x000001; private static final int MPEG_PROGRAM_END_CODE = 0x000001B9; + private static final int MAX_STREAM_ID_PLUS_ONE = 0x100; private static final long MAX_SEARCH_LENGTH = 1024 * 1024; public static final int PRIVATE_STREAM_1 = 0xBD; @@ -42,7 +59,7 @@ public final class PsExtractor implements Extractor { public static final int VIDEO_STREAM = 0xE0; public static final int VIDEO_STREAM_MASK = 0xF0; - private final PtsTimestampAdjuster ptsTimestampAdjuster; + private final TimestampAdjuster timestampAdjuster; private final SparseArray psPayloadReaders; // Indexed by pid private final ParsableByteArray psPacketBuffer; private boolean foundAllTracks; @@ -53,11 +70,11 @@ public final class PsExtractor implements Extractor { private ExtractorOutput output; public PsExtractor() { - this(new PtsTimestampAdjuster(0)); + this(new TimestampAdjuster(0)); } - public PsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) { - this.ptsTimestampAdjuster = ptsTimestampAdjuster; + public PsExtractor(TimestampAdjuster timestampAdjuster) { + this.timestampAdjuster = timestampAdjuster; psPacketBuffer = new ParsableByteArray(4096); psPayloadReaders = new SparseArray<>(); } @@ -106,12 +123,12 @@ public boolean sniff(ExtractorInput input) throws IOException, InterruptedExcept @Override public void init(ExtractorOutput output) { this.output = output; - output.seekMap(SeekMap.UNSEEKABLE); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); } @Override - public void seek() { - ptsTimestampAdjuster.reset(); + public void seek(long position) { + timestampAdjuster.reset(); for (int i = 0; i < psPayloadReaders.size(); i++) { psPayloadReaders.valueAt(i).seek(); } @@ -139,8 +156,7 @@ public int read(ExtractorInput input, PositionHolder seekPosition) input.peekFully(psPacketBuffer.data, 0, 10); // We only care about the pack_stuffing_length in here, skip the first 77 bits. - psPacketBuffer.setPosition(0); - psPacketBuffer.skipBytes(9); + psPacketBuffer.setPosition(9); // Last 3 bits is the length. int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07; @@ -175,17 +191,19 @@ public int read(ExtractorInput input, PositionHolder seekPosition) // Private stream, used for AC3 audio. // NOTE: This may need further parsing to determine if its DTS, but that's likely only // valid for DVDs. - elementaryStreamReader = new Ac3Reader(output.track(streamId), false); + elementaryStreamReader = new Ac3Reader(); foundAudioTrack = true; } else if (!foundAudioTrack && (streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM) { - elementaryStreamReader = new MpegAudioReader(output.track(streamId)); + elementaryStreamReader = new MpegAudioReader(); foundAudioTrack = true; } else if (!foundVideoTrack && (streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM) { - elementaryStreamReader = new H262Reader(output.track(streamId)); + elementaryStreamReader = new H262Reader(); foundVideoTrack = true; } if (elementaryStreamReader != null) { - payloadReader = new PesReader(elementaryStreamReader, ptsTimestampAdjuster); + TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE); + elementaryStreamReader.createTracks(output, idGenerator); + payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster); psPayloadReaders.put(streamId, payloadReader); } } @@ -195,7 +213,7 @@ public int read(ExtractorInput input, PositionHolder seekPosition) } } - // The next 2 bytes are the length, once we have that we can consume the complete packet. + // The next 2 bytes are the length. Once we have that we can consume the complete packet. input.peekFully(psPacketBuffer.data, 0, 2); psPacketBuffer.setPosition(0); int payloadLength = psPacketBuffer.readUnsignedShort(); @@ -205,15 +223,11 @@ public int read(ExtractorInput input, PositionHolder seekPosition) // Just skip this data. input.skipFully(pesLength); } else { - if (psPacketBuffer.capacity() < pesLength) { - // Reallocate for this and future packets. - psPacketBuffer.reset(new byte[pesLength], pesLength); - } + psPacketBuffer.reset(pesLength); // Read the whole packet and the header for consumption. input.readFully(psPacketBuffer.data, 0, pesLength); psPacketBuffer.setPosition(6); - psPacketBuffer.setLimit(pesLength); - payloadReader.consume(psPacketBuffer, output); + payloadReader.consume(psPacketBuffer); psPacketBuffer.setLimit(psPacketBuffer.capacity()); } @@ -230,7 +244,7 @@ private static final class PesReader { private static final int PES_SCRATCH_SIZE = 64; private final ElementaryStreamReader pesPayloadReader; - private final PtsTimestampAdjuster ptsTimestampAdjuster; + private final TimestampAdjuster timestampAdjuster; private final ParsableBitArray pesScratch; private boolean ptsFlag; @@ -239,10 +253,9 @@ private static final class PesReader { private int extendedHeaderLength; private long timeUs; - public PesReader(ElementaryStreamReader pesPayloadReader, - PtsTimestampAdjuster ptsTimestampAdjuster) { + public PesReader(ElementaryStreamReader pesPayloadReader, TimestampAdjuster timestampAdjuster) { this.pesPayloadReader = pesPayloadReader; - this.ptsTimestampAdjuster = ptsTimestampAdjuster; + this.timestampAdjuster = timestampAdjuster; pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); } @@ -250,8 +263,8 @@ public PesReader(ElementaryStreamReader pesPayloadReader, * Notifies the reader that a seek has occurred. *

        * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, ExtractorOutput)} will not be a continuation of - * the data that was previously passed. Hence the reader should reset any internal state. + * {@link #consume(ParsableByteArray)} will not be a continuation of the data that was + * previously passed. Hence the reader should reset any internal state. */ public void seek() { seenFirstDts = false; @@ -262,9 +275,8 @@ public void seek() { * Consumes the payload of a PS packet. * * @param data The PES packet. The position will be set to the start of the payload. - * @param output The output to which parsed data should be written. */ - public void consume(ParsableByteArray data, ExtractorOutput output) { + public void consume(ParsableByteArray data) { data.readBytes(pesScratch.data, 0, 3); pesScratch.setPosition(0); parseHeader(); @@ -314,10 +326,10 @@ private void parseHeaderExtension() { // decode timestamp to the adjuster here so that in the case that this is the first to be // fed, the adjuster will be able to compute an offset to apply such that the adjusted // presentation timestamps of all future packets are non-negative. - ptsTimestampAdjuster.adjustTimestamp(dts); + timestampAdjuster.adjustTsTimestamp(dts); seenFirstDts = true; } - timeUs = ptsTimestampAdjuster.adjustTimestamp(pts); + timeUs = timestampAdjuster.adjustTsTimestamp(pts); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionPayloadReader.java new file mode 100755 index 00000000000..c93e169bd87 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionPayloadReader.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Reads section data. + */ +public interface SectionPayloadReader { + + /** + * Initializes the section payload reader. + * + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + + /** + * Called by a {@link SectionReader} when a full section is received. + * + * @param sectionData The data belonging to a section, including the section header but excluding + * the CRC_32 field. + */ + void consume(ParsableByteArray sectionData); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionReader.java new file mode 100755 index 00000000000..4f356d9a30d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SectionReader.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Reads section data packets and feeds the whole sections to a given {@link SectionPayloadReader}. + */ +public final class SectionReader implements TsPayloadReader { + + private static final int SECTION_HEADER_LENGTH = 3; + + private final ParsableByteArray sectionData; + private final ParsableBitArray headerScratch; + private final SectionPayloadReader reader; + private int sectionLength; + private int sectionBytesRead; + + public SectionReader(SectionPayloadReader reader) { + this.reader = reader; + sectionData = new ParsableByteArray(); + headerScratch = new ParsableBitArray(new byte[SECTION_HEADER_LENGTH]); + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + reader.init(timestampAdjuster, extractorOutput, idGenerator); + } + + @Override + public void seek() { + // Do nothing. + } + + @Override + public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { + // Skip pointer. + if (payloadUnitStartIndicator) { + int pointerField = data.readUnsignedByte(); + data.skipBytes(pointerField); + + // Note: see ISO/IEC 13818-1, section 2.4.4.3 for detailed information on the format of + // the header. + data.readBytes(headerScratch, SECTION_HEADER_LENGTH); + data.setPosition(data.getPosition() - SECTION_HEADER_LENGTH); + headerScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), 0 (1), reserved (2) + sectionLength = headerScratch.readBits(12) + SECTION_HEADER_LENGTH; + sectionBytesRead = 0; + + sectionData.reset(sectionLength); + } + + int bytesToRead = Math.min(data.bytesLeft(), sectionLength - sectionBytesRead); + data.readBytes(sectionData.data, sectionBytesRead, bytesToRead); + sectionBytesRead += bytesToRead; + if (sectionBytesRead < sectionLength) { + // Not yet fully read. + return; + } + + if (Util.crc(sectionData.data, 0, sectionLength, 0xFFFFFFFF) != 0) { + // CRC Invalid. The section gets discarded. + return; + } + sectionData.setLimit(sectionData.limit() - 4); // Exclude the CRC_32 field. + reader.consume(sectionData); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java new file mode 100755 index 00000000000..08e35ceae34 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SeiReader.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.text.cea.Cea608Decoder; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}. + */ +/* package */ final class SeiReader { + + private final TrackOutput output; + + public SeiReader(TrackOutput output) { + this.output = output; + output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, + Format.NO_VALUE, 0, null, null)); + } + + public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { + int b; + while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { + // Parse payload type. + int payloadType = 0; + do { + b = seiBuffer.readUnsignedByte(); + payloadType += b; + } while (b == 0xFF); + // Parse payload size. + int payloadSize = 0; + do { + b = seiBuffer.readUnsignedByte(); + payloadSize += b; + } while (b == 0xFF); + // Process the payload. + if (Cea608Decoder.isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { + // Ignore country_code (1) + provider_code (2) + user_identifier (4) + // + user_data_type_code (1). + seiBuffer.skipBytes(8); + // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). + int ccCount = seiBuffer.readUnsignedByte() & 0x1F; + seiBuffer.skipBytes(1); + int sampleBytes = 0; + for (int i = 0; i < ccCount; i++) { + int ccValidityAndType = seiBuffer.peekUnsignedByte() & 0x07; + // Check that validity == 1 and type == 0 (i.e. NTSC_CC_FIELD_1). + if (ccValidityAndType != 0x04) { + seiBuffer.skipBytes(3); + } else { + sampleBytes += 3; + output.sampleData(seiBuffer, 3); + } + } + output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null); + // Ignore trailing information in SEI, if any. + seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); + } else { + seiBuffer.skipBytes(payloadSize); + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SpliceInfoSectionReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SpliceInfoSectionReader.java new file mode 100755 index 00000000000..1e3ca1f3e43 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/SpliceInfoSectionReader.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Parses splice info sections as defined by SCTE35. + */ +public final class SpliceInfoSectionReader implements SectionPayloadReader { + + private TrackOutput output; + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TsPayloadReader.TrackIdGenerator idGenerator) { + output = extractorOutput.track(idGenerator.getNextId()); + output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null, + Format.NO_VALUE, null)); + } + + @Override + public void consume(ParsableByteArray sectionData) { + int sampleSize = sectionData.bytesLeft(); + output.sampleData(sectionData, sampleSize); + output.sampleMetadata(0, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java new file mode 100755 index 00000000000..58e6729eb19 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsExtractor.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import android.util.SparseIntArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import org.telegram.messenger.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.Arrays; + +/** + * Facilitates the extraction of data from the MPEG-2 TS container format. + */ +public final class TsExtractor implements Extractor { + + /** + * Factory for {@link TsExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new TsExtractor()}; + } + + }; + + public static final int TS_STREAM_TYPE_MPA = 0x03; + public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; + public static final int TS_STREAM_TYPE_AAC = 0x0F; + public static final int TS_STREAM_TYPE_AC3 = 0x81; + public static final int TS_STREAM_TYPE_DTS = 0x8A; + public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; + public static final int TS_STREAM_TYPE_E_AC3 = 0x87; + public static final int TS_STREAM_TYPE_H262 = 0x02; + public static final int TS_STREAM_TYPE_H264 = 0x1B; + public static final int TS_STREAM_TYPE_H265 = 0x24; + public static final int TS_STREAM_TYPE_ID3 = 0x15; + public static final int TS_STREAM_TYPE_SPLICE_INFO = 0x86; + + private static final int TS_PACKET_SIZE = 188; + private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. + private static final int TS_PAT_PID = 0; + private static final int MAX_PID_PLUS_ONE = 0x2000; + + private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); + private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); + private static final long HEVC_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("HEVC"); + + private static final int BUFFER_PACKET_COUNT = 5; // Should be at least 2 + private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT; + + private final boolean mapByType; + private final TimestampAdjuster timestampAdjuster; + private final ParsableByteArray tsPacketBuffer; + private final ParsableBitArray tsScratch; + private final SparseIntArray continuityCounters; + private final TsPayloadReader.Factory payloadReaderFactory; + private final SparseArray tsPayloadReaders; // Indexed by pid + private final SparseBooleanArray trackIds; + + // Accessed only by the loading thread. + private ExtractorOutput output; + private boolean tracksEnded; + private TsPayloadReader id3Reader; + + public TsExtractor() { + this(new TimestampAdjuster(0)); + } + + /** + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + */ + public TsExtractor(TimestampAdjuster timestampAdjuster) { + this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false); + } + + /** + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param payloadReaderFactory Factory for injecting a custom set of payload readers. + * @param mapByType True if {@link TrackOutput}s should be mapped by their type, false to map them + * by their PID. + */ + public TsExtractor(TimestampAdjuster timestampAdjuster, + TsPayloadReader.Factory payloadReaderFactory, boolean mapByType) { + this.timestampAdjuster = timestampAdjuster; + this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); + this.mapByType = mapByType; + tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); + tsScratch = new ParsableBitArray(new byte[3]); + trackIds = new SparseBooleanArray(); + tsPayloadReaders = new SparseArray<>(); + continuityCounters = new SparseIntArray(); + resetPayloadReaders(); + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + byte[] buffer = tsPacketBuffer.data; + input.peekFully(buffer, 0, BUFFER_SIZE); + for (int j = 0; j < TS_PACKET_SIZE; j++) { + for (int i = 0; true; i++) { + if (i == BUFFER_PACKET_COUNT) { + input.skipFully(j); + return true; + } + if (buffer[j + i * TS_PACKET_SIZE] != TS_SYNC_BYTE) { + break; + } + } + } + return false; + } + + @Override + public void init(ExtractorOutput output) { + this.output = output; + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position) { + timestampAdjuster.reset(); + tsPacketBuffer.reset(); + continuityCounters.clear(); + // Elementary stream readers' state should be cleared to get consistent behaviours when seeking. + resetPayloadReaders(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + byte[] data = tsPacketBuffer.data; + // Shift bytes to the start of the buffer if there isn't enough space left at the end + if (BUFFER_SIZE - tsPacketBuffer.getPosition() < TS_PACKET_SIZE) { + int bytesLeft = tsPacketBuffer.bytesLeft(); + if (bytesLeft > 0) { + System.arraycopy(data, tsPacketBuffer.getPosition(), data, 0, bytesLeft); + } + tsPacketBuffer.reset(data, bytesLeft); + } + // Read more bytes until there is at least one packet size + while (tsPacketBuffer.bytesLeft() < TS_PACKET_SIZE) { + int limit = tsPacketBuffer.limit(); + int read = input.read(data, limit, BUFFER_SIZE - limit); + if (read == C.RESULT_END_OF_INPUT) { + return RESULT_END_OF_INPUT; + } + tsPacketBuffer.setLimit(limit + read); + } + + // Note: see ISO/IEC 13818-1, section 2.4.3.2 for detailed information on the format of + // the header. + final int limit = tsPacketBuffer.limit(); + int position = tsPacketBuffer.getPosition(); + while (position < limit && data[position] != TS_SYNC_BYTE) { + position++; + } + tsPacketBuffer.setPosition(position); + + int endOfPacket = position + TS_PACKET_SIZE; + if (endOfPacket > limit) { + return RESULT_CONTINUE; + } + + tsPacketBuffer.skipBytes(1); + tsPacketBuffer.readBytes(tsScratch, 3); + if (tsScratch.readBit()) { // transport_error_indicator + // There are uncorrectable errors in this packet. + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + boolean payloadUnitStartIndicator = tsScratch.readBit(); + tsScratch.skipBits(1); // transport_priority + int pid = tsScratch.readBits(13); + tsScratch.skipBits(2); // transport_scrambling_control + boolean adaptationFieldExists = tsScratch.readBit(); + boolean payloadExists = tsScratch.readBit(); + boolean discontinuityFound = false; + int continuityCounter = tsScratch.readBits(4); + int previousCounter = continuityCounters.get(pid, continuityCounter - 1); + continuityCounters.put(pid, continuityCounter); + if (previousCounter == continuityCounter) { + if (payloadExists) { + // Duplicate packet found. + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + } else if (continuityCounter != (previousCounter + 1) % 16) { + discontinuityFound = true; + } + + // Skip the adaptation field. + if (adaptationFieldExists) { + int adaptationFieldLength = tsPacketBuffer.readUnsignedByte(); + tsPacketBuffer.skipBytes(adaptationFieldLength); + } + + // Read the payload. + if (payloadExists) { + TsPayloadReader payloadReader = tsPayloadReaders.get(pid); + if (payloadReader != null) { + if (discontinuityFound) { + payloadReader.seek(); + } + tsPacketBuffer.setLimit(endOfPacket); + payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); + Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket); + tsPacketBuffer.setLimit(limit); + } + } + + tsPacketBuffer.setPosition(endOfPacket); + return RESULT_CONTINUE; + } + + // Internals. + + private void resetPayloadReaders() { + trackIds.clear(); + tsPayloadReaders.clear(); + SparseArray initialPayloadReaders = + payloadReaderFactory.createInitialPayloadReaders(); + int initialPayloadReadersSize = initialPayloadReaders.size(); + for (int i = 0; i < initialPayloadReadersSize; i++) { + tsPayloadReaders.put(initialPayloadReaders.keyAt(i), initialPayloadReaders.valueAt(i)); + } + tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader())); + id3Reader = null; + } + + /** + * Parses Program Association Table data. + */ + private class PatReader implements SectionPayloadReader { + + private final ParsableBitArray patScratch; + + public PatReader() { + patScratch = new ParsableBitArray(new byte[4]); + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + + @Override + public void consume(ParsableByteArray sectionData) { + // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), + // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), + // section_number (8), last_section_number (8) + sectionData.skipBytes(8); + + int programCount = sectionData.bytesLeft() / 4; + for (int i = 0; i < programCount; i++) { + sectionData.readBytes(patScratch, 4); + int programNumber = patScratch.readBits(16); + patScratch.skipBits(3); // reserved (3) + if (programNumber == 0) { + patScratch.skipBits(13); // network_PID (13) + } else { + int pid = patScratch.readBits(13); + tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid))); + } + } + } + + } + + /** + * Parses Program Map Table. + */ + private class PmtReader implements SectionPayloadReader { + + private static final int TS_PMT_DESC_REGISTRATION = 0x05; + private static final int TS_PMT_DESC_ISO639_LANG = 0x0A; + private static final int TS_PMT_DESC_AC3 = 0x6A; + private static final int TS_PMT_DESC_EAC3 = 0x7A; + private static final int TS_PMT_DESC_DTS = 0x7B; + + private final ParsableBitArray pmtScratch; + private final int pid; + + public PmtReader(int pid) { + pmtScratch = new ParsableBitArray(new byte[5]); + this.pid = pid; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + + @Override + public void consume(ParsableByteArray sectionData) { + // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), + // program_number (16), reserved (2), version_number (5), current_next_indicator (1), + // section_number (8), last_section_number (8), reserved (3), PCR_PID (13) + // Skip the rest of the PMT header. + sectionData.skipBytes(10); + + // Read program_info_length. + sectionData.readBytes(pmtScratch, 2); + pmtScratch.skipBits(4); + int programInfoLength = pmtScratch.readBits(12); + + // Skip the descriptors. + sectionData.skipBytes(programInfoLength); + + if (mapByType && id3Reader == null) { + // Setup an ID3 track regardless of whether there's a corresponding entry, in case one + // appears intermittently during playback. See [Internal: b/20261500]. + EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); + id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); + id3Reader.init(timestampAdjuster, output, + new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); + } + + int remainingEntriesLength = sectionData.bytesLeft(); + while (remainingEntriesLength > 0) { + sectionData.readBytes(pmtScratch, 5); + int streamType = pmtScratch.readBits(8); + pmtScratch.skipBits(3); // reserved + int elementaryPid = pmtScratch.readBits(13); + pmtScratch.skipBits(4); // reserved + int esInfoLength = pmtScratch.readBits(12); // ES_info_length. + EsInfo esInfo = readEsInfo(sectionData, esInfoLength); + if (streamType == 0x06) { + streamType = esInfo.streamType; + } + remainingEntriesLength -= esInfoLength + 5; + + int trackId = mapByType ? streamType : elementaryPid; + if (trackIds.get(trackId)) { + continue; + } + trackIds.put(trackId, true); + + TsPayloadReader reader; + if (mapByType && streamType == TS_STREAM_TYPE_ID3) { + reader = id3Reader; + } else { + reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); + if (reader != null) { + reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE)); + } + } + + if (reader != null) { + tsPayloadReaders.put(elementaryPid, reader); + } + } + if (mapByType) { + if (!tracksEnded) { + output.endTracks(); + } + } else { + tsPayloadReaders.remove(TS_PAT_PID); + tsPayloadReaders.remove(pid); + output.endTracks(); + } + tracksEnded = true; + } + + /** + * Returns the stream info read from the available descriptors. Sets {@code data}'s position to + * the end of the descriptors. + * + * @param data A buffer with its position set to the start of the first descriptor. + * @param length The length of descriptors to read from the current position in {@code data}. + * @return The stream info read from the available descriptors. + */ + private EsInfo readEsInfo(ParsableByteArray data, int length) { + int descriptorsStartPosition = data.getPosition(); + int descriptorsEndPosition = descriptorsStartPosition + length; + int streamType = -1; + String language = null; + while (data.getPosition() < descriptorsEndPosition) { + int descriptorTag = data.readUnsignedByte(); + int descriptorLength = data.readUnsignedByte(); + int positionOfNextDescriptor = data.getPosition() + descriptorLength; + if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor + long formatIdentifier = data.readUnsignedInt(); + if (formatIdentifier == AC3_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_AC3; + } else if (formatIdentifier == E_AC3_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_E_AC3; + } else if (formatIdentifier == HEVC_FORMAT_IDENTIFIER) { + streamType = TS_STREAM_TYPE_H265; + } + } else if (descriptorTag == TS_PMT_DESC_AC3) { // AC-3_descriptor in DVB (ETSI EN 300 468) + streamType = TS_STREAM_TYPE_AC3; + } else if (descriptorTag == TS_PMT_DESC_EAC3) { // enhanced_AC-3_descriptor + streamType = TS_STREAM_TYPE_E_AC3; + } else if (descriptorTag == TS_PMT_DESC_DTS) { // DTS_descriptor + streamType = TS_STREAM_TYPE_DTS; + } else if (descriptorTag == TS_PMT_DESC_ISO639_LANG) { + language = new String(data.data, data.getPosition(), 3).trim(); + // Audio type is ignored. + } + // Skip unused bytes of current descriptor. + data.skipBytes(positionOfNextDescriptor - data.getPosition()); + } + data.setPosition(descriptorsEndPosition); + return new EsInfo(streamType, language, + Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition)); + } + + } + + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsPayloadReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsPayloadReader.java new file mode 100755 index 00000000000..fccb15a3c18 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/ts/TsPayloadReader.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.ts; + +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Parses TS packet payload data. + */ +public interface TsPayloadReader { + + /** + * Factory of {@link TsPayloadReader} instances. + */ + interface Factory { + + /** + * Returns the initial mapping from PIDs to payload readers. + *

        + * This method allows the injection of payload readers for reserved PIDs, excluding PID 0. + * + * @return A {@link SparseArray} that maps PIDs to payload readers. + */ + SparseArray createInitialPayloadReaders(); + + /** + * Returns a {@link TsPayloadReader} for a given stream type and elementary stream information. + * May return null if the stream type is not supported. + * + * @param streamType Stream type value as defined in the PMT entry or associated descriptors. + * @param esInfo Information associated to the elementary stream provided in the PMT. + * @return A {@link TsPayloadReader} for the packet stream carried by the provided pid. + * {@code null} if the stream is not supported. + */ + TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo); + + } + + /** + * Holds information associated with a PMT entry. + */ + final class EsInfo { + + public final int streamType; + public final String language; + public final byte[] descriptorBytes; + + /** + * @param streamType The type of the stream as defined by the + * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. + * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param descriptorBytes The descriptor bytes associated to the stream. + */ + public EsInfo(int streamType, String language, byte[] descriptorBytes) { + this.streamType = streamType; + this.language = language; + this.descriptorBytes = descriptorBytes; + } + + } + + /** + * Generates track ids for initializing {@link TsPayloadReader}s' {@link TrackOutput}s. + */ + final class TrackIdGenerator { + + private final int firstId; + private final int idIncrement; + private int generatedIdCount; + + public TrackIdGenerator(int firstId, int idIncrement) { + this.firstId = firstId; + this.idIncrement = idIncrement; + } + + public int getNextId() { + return firstId + idIncrement * generatedIdCount++; + } + + } + + /** + * Initializes the payload reader. + * + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the + * {@link TrackOutput}s. + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + + /** + * Notifies the reader that a seek has occurred. + *

        + * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray, boolean)} will not be a continuation of the data that was + * previously passed. Hence the reader should reset any internal state. + */ + void seek(); + + /** + * Consumes the payload of a TS packet. + * + * @param data The TS packet. The position will be set to the start of the payload. + * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. + */ + void consume(ParsableByteArray data, boolean payloadUnitStartIndicator); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavExtractor.java new file mode 100755 index 00000000000..aa302ed146f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavExtractor.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.extractor.wav; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.io.IOException; + +/** {@link Extractor} to extract samples from a WAV byte stream. */ +public final class WavExtractor implements Extractor, SeekMap { + + /** + * Factory for {@link WavExtractor} instances. + */ + public static final ExtractorsFactory FACTORY = new ExtractorsFactory() { + + @Override + public Extractor[] createExtractors() { + return new Extractor[] {new WavExtractor()}; + } + + }; + + /** Arbitrary maximum input size of 32KB, which is ~170ms of 16-bit stereo PCM audio at 48KHz. */ + private static final int MAX_INPUT_SIZE = 32 * 1024; + + private ExtractorOutput extractorOutput; + private TrackOutput trackOutput; + private WavHeader wavHeader; + private int bytesPerFrame; + private int pendingBytes; + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + return WavHeaderReader.peek(input) != null; + } + + @Override + public void init(ExtractorOutput output) { + extractorOutput = output; + trackOutput = output.track(0); + wavHeader = null; + output.endTracks(); + } + + @Override + public void seek(long position) { + pendingBytes = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + if (wavHeader == null) { + wavHeader = WavHeaderReader.peek(input); + if (wavHeader == null) { + // Should only happen if the media wasn't sniffed. + throw new ParserException("Unsupported or unrecognized wav header."); + } + Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_RAW, null, + wavHeader.getBitrate(), MAX_INPUT_SIZE, wavHeader.getNumChannels(), + wavHeader.getSampleRateHz(), wavHeader.getEncoding(), null, null, 0, null); + trackOutput.format(format); + bytesPerFrame = wavHeader.getBytesPerFrame(); + } + + if (!wavHeader.hasDataBounds()) { + WavHeaderReader.skipToData(input, wavHeader); + extractorOutput.seekMap(this); + } + + int bytesAppended = trackOutput.sampleData(input, MAX_INPUT_SIZE - pendingBytes, true); + if (bytesAppended != RESULT_END_OF_INPUT) { + pendingBytes += bytesAppended; + } + + // Samples must consist of a whole number of frames. + int pendingFrames = pendingBytes / bytesPerFrame; + if (pendingFrames > 0) { + long timeUs = wavHeader.getTimeUs(input.getPosition() - pendingBytes); + int size = pendingFrames * bytesPerFrame; + pendingBytes -= size; + trackOutput.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, size, pendingBytes, null); + } + + return bytesAppended == RESULT_END_OF_INPUT ? RESULT_END_OF_INPUT : RESULT_CONTINUE; + } + + // SeekMap implementation. + + @Override + public long getDurationUs() { + return wavHeader.getDurationUs(); + } + + @Override + public boolean isSeekable() { + return true; + } + + @Override + public long getPosition(long timeUs) { + return wavHeader.getPosition(timeUs); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeader.java similarity index 87% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeader.java index 8f5e15c5e58..6b98e5530a9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeader.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.wav; +package org.telegram.messenger.exoplayer2.extractor.wav; -import org.telegram.messenger.exoplayer.C; +import org.telegram.messenger.exoplayer2.C; /** Header for a WAV file. */ /*package*/ final class WavHeader { @@ -31,6 +31,7 @@ /** Bits per sample for the audio data. */ private final int bitsPerSample; /** The PCM encoding */ + @C.PcmEncoding private final int encoding; /** Offset to the start of sample data. */ @@ -38,13 +39,8 @@ /** Total size in bytes of the sample data. */ private long dataSize; - public WavHeader( - int numChannels, - int sampleRateHz, - int averageBytesPerSecond, - int blockAlignment, - int bitsPerSample, - int encoding) { + public WavHeader(int numChannels, int sampleRateHz, int averageBytesPerSecond, int blockAlignment, + int bitsPerSample, @C.PcmEncoding int encoding) { this.numChannels = numChannels; this.sampleRateHz = sampleRateHz; this.averageBytesPerSecond = averageBytesPerSecond; @@ -83,7 +79,8 @@ public int getNumChannels() { public long getPosition(long timeUs) { long unroundedPosition = (timeUs * averageBytesPerSecond) / C.MICROS_PER_SECOND; // Round down to nearest frame. - return (unroundedPosition / blockAlignment) * blockAlignment + dataStartPosition; + long position = (unroundedPosition / blockAlignment) * blockAlignment; + return Math.min(position, dataSize - blockAlignment) + dataStartPosition; } /** Returns the time in microseconds for the given position in bytes in this WAV. */ @@ -103,6 +100,7 @@ public void setDataBounds(long dataStartPosition, long dataSize) { } /** Returns the PCM encoding. **/ + @C.PcmEncoding public int getEncoding() { return encoding; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeaderReader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeaderReader.java similarity index 91% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeaderReader.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeaderReader.java index 39ed82e8714..bb8dd5fe27e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/extractor/wav/WavHeaderReader.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/extractor/wav/WavHeaderReader.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.extractor.wav; +package org.telegram.messenger.exoplayer2.extractor.wav; import android.util.Log; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.ParserException; -import org.telegram.messenger.exoplayer.extractor.ExtractorInput; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.ParsableByteArray; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; /** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */ @@ -38,14 +38,13 @@ * Peeks and returns a {@code WavHeader}. * * @param input Input stream to peek the WAV header from. + * @throws ParserException If the input file is an incorrect RIFF WAV. * @throws IOException If peeking from the input fails. * @throws InterruptedException If interrupted while peeking from input. - * @throws ParserException If the input file is an incorrect RIFF WAV. * @return A new {@code WavHeader} peeked from {@code input}, or null if the input is not a * supported WAV format. */ - public static WavHeader peek(ExtractorInput input) - throws IOException, InterruptedException, ParserException { + public static WavHeader peek(ExtractorInput input) throws IOException, InterruptedException { Assertions.checkNotNull(input); // Allocate a scratch buffer large enough to store the format chunk. @@ -88,7 +87,7 @@ public static WavHeader peek(ExtractorInput input) + blockAlignment); } - int encoding = Util.getPcmEncoding(bitsPerSample); + @C.PcmEncoding int encoding = Util.getPcmEncoding(bitsPerSample); if (encoding == C.ENCODING_INVALID) { Log.e(TAG, "Unsupported WAV bit depth: " + bitsPerSample); return null; @@ -115,12 +114,12 @@ public static WavHeader peek(ExtractorInput input) * @param input Input stream to skip to the data chunk in. Its peek position must be pointing to * a valid chunk header. * @param wavHeader WAV header to populate with data bounds. + * @throws ParserException If an error occurs parsing chunks. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from input. - * @throws ParserException If an error occurs parsing chunks. */ public static void skipToData(ExtractorInput input, WavHeader wavHeader) - throws IOException, InterruptedException, ParserException { + throws IOException, InterruptedException { Assertions.checkNotNull(input); Assertions.checkNotNull(wavHeader); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java new file mode 100755 index 00000000000..9eb8a2fe07e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecInfo.java @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCodecInfo.AudioCapabilities; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.media.MediaCodecInfo.VideoCapabilities; +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Information about a {@link MediaCodec} for a given mime type. + */ +@TargetApi(16) +public final class MediaCodecInfo { + + public static final String TAG = "MediaCodecInfo"; + + /** + * The name of the decoder. + *

        + * May be passed to {@link MediaCodec#createByCodecName(String)} to create an instance of the + * decoder. + */ + public final String name; + + /** + * Whether the decoder supports seamless resolution switches. + * + * @see CodecCapabilities#isFeatureSupported(String) + * @see CodecCapabilities#FEATURE_AdaptivePlayback + */ + public final boolean adaptive; + + private final String mimeType; + private final CodecCapabilities capabilities; + + /** + * Creates an instance representing an audio passthrough decoder. + * + * @param name The name of the {@link MediaCodec}. + * @return The created instance. + */ + public static MediaCodecInfo newPassthroughInstance(String name) { + return new MediaCodecInfo(name, null, null); + } + + /** + * Creates an instance. + * + * @param name The name of the {@link MediaCodec}. + * @param mimeType A mime type supported by the {@link MediaCodec}. + * @param capabilities The capabilities of the {@link MediaCodec} for the specified mime type. + * @return The created instance. + */ + public static MediaCodecInfo newInstance(String name, String mimeType, + CodecCapabilities capabilities) { + return new MediaCodecInfo(name, mimeType, capabilities); + } + + /** + * @param name The name of the decoder. + * @param capabilities The capabilities of the decoder. + */ + private MediaCodecInfo(String name, String mimeType, CodecCapabilities capabilities) { + this.name = Assertions.checkNotNull(name); + this.mimeType = mimeType; + this.capabilities = capabilities; + adaptive = capabilities != null && isAdaptive(capabilities); + } + + /** + * The profile levels supported by the decoder. + * + * @return The profile levels supported by the decoder. + */ + public CodecProfileLevel[] getProfileLevels() { + return capabilities == null || capabilities.profileLevels == null ? new CodecProfileLevel[0] + : capabilities.profileLevels; + } + + /** + * Whether the decoder supports the given {@code codec}. If there is insufficient information to + * decide, returns true. + * + * @param codec Codec string as defined in RFC 6381. + * @return True if the given codec is supported by the decoder. + */ + public boolean isCodecSupported(String codec) { + if (codec == null || mimeType == null) { + return true; + } + String codecMimeType = MimeTypes.getMediaMimeType(codec); + if (codecMimeType == null) { + return true; + } + if (!mimeType.equals(codecMimeType)) { + logNoSupport("codec.mime " + codec + ", " + codecMimeType); + return false; + } + Pair codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(codec); + if (codecProfileAndLevel == null) { + // If we don't know any better, we assume that the profile and level are supported. + return true; + } + for (CodecProfileLevel capabilities : getProfileLevels()) { + if (capabilities.profile == codecProfileAndLevel.first + && capabilities.level >= codecProfileAndLevel.second) { + return true; + } + } + logNoSupport("codec.profileLevel, " + codec + ", " + codecMimeType); + return false; + } + + /** + * Whether the decoder supports video with a specified width and height. + *

        + * Must not be called if the device SDK version is less than 21. + * + * @param width Width in pixels. + * @param height Height in pixels. + * @return Whether the decoder supports video with the given width and height. + */ + @TargetApi(21) + public boolean isVideoSizeSupportedV21(int width, int height) { + if (capabilities == null) { + logNoSupport("size.caps"); + return false; + } + VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities(); + if (videoCapabilities == null) { + logNoSupport("size.vCaps"); + return false; + } + if (!videoCapabilities.isSizeSupported(width, height)) { + // Capabilities are known to be inaccurately reported for vertical resolutions on some devices + // (b/31387661). If the video is vertical and the capabilities indicate support if the width + // and height are swapped, we assume that the vertical resolution is also supported. + if (width >= height || !videoCapabilities.isSizeSupported(height, width)) { + logNoSupport("size.support, " + width + "x" + height); + return false; + } + logAssumedSupport("size.rotated, " + width + "x" + height); + } + return true; + } + + /** + * Whether the decoder supports video with a given width, height and frame rate. + *

        + * Must not be called if the device SDK version is less than 21. + * + * @param width Width in pixels. + * @param height Height in pixels. + * @param frameRate Frame rate in frames per second. + * @return Whether the decoder supports video with the given width, height and frame rate. + */ + @TargetApi(21) + public boolean isVideoSizeAndRateSupportedV21(int width, int height, double frameRate) { + if (capabilities == null) { + logNoSupport("sizeAndRate.caps"); + return false; + } + VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities(); + if (videoCapabilities == null) { + logNoSupport("sizeAndRate.vCaps"); + return false; + } + if (!videoCapabilities.areSizeAndRateSupported(width, height, frameRate)) { + // Capabilities are known to be inaccurately reported for vertical resolutions on some devices + // (b/31387661). If the video is vertical and the capabilities indicate support if the width + // and height are swapped, we assume that the vertical resolution is also supported. + if (width >= height || !videoCapabilities.areSizeAndRateSupported(height, width, frameRate)) { + logNoSupport("sizeAndRate.support, " + width + "x" + height + "x" + frameRate); + return false; + } + logAssumedSupport("sizeAndRate.rotated, " + width + "x" + height + "x" + frameRate); + } + return true; + } + + /** + * Whether the decoder supports audio with a given sample rate. + *

        + * Must not be called if the device SDK version is less than 21. + * + * @param sampleRate The sample rate in Hz. + * @return Whether the decoder supports audio with the given sample rate. + */ + @TargetApi(21) + public boolean isAudioSampleRateSupportedV21(int sampleRate) { + if (capabilities == null) { + logNoSupport("sampleRate.caps"); + return false; + } + AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities(); + if (audioCapabilities == null) { + logNoSupport("sampleRate.aCaps"); + return false; + } + if (!audioCapabilities.isSampleRateSupported(sampleRate)) { + logNoSupport("sampleRate.support, " + sampleRate); + return false; + } + return true; + } + + /** + * Whether the decoder supports audio with a given channel count. + *

        + * Must not be called if the device SDK version is less than 21. + * + * @param channelCount The channel count. + * @return Whether the decoder supports audio with the given channel count. + */ + @TargetApi(21) + public boolean isAudioChannelCountSupportedV21(int channelCount) { + if (capabilities == null) { + logNoSupport("channelCount.caps"); + return false; + } + AudioCapabilities audioCapabilities = capabilities.getAudioCapabilities(); + if (audioCapabilities == null) { + logNoSupport("channelCount.aCaps"); + return false; + } + if (audioCapabilities.getMaxInputChannelCount() < channelCount) { + logNoSupport("channelCount.support, " + channelCount); + return false; + } + return true; + } + + private void logNoSupport(String message) { + Log.d(TAG, "NoSupport [" + message + "] [" + name + ", " + mimeType + "] [" + + Util.DEVICE_DEBUG_INFO + "]"); + } + + private void logAssumedSupport(String message) { + Log.d(TAG, "AssumedSupport [" + message + "] [" + name + ", " + mimeType + "] [" + + Util.DEVICE_DEBUG_INFO + "]"); + } + + private static boolean isAdaptive(CodecCapabilities capabilities) { + return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities); + } + + @TargetApi(19) + private static boolean isAdaptiveV19(CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java new file mode 100755 index 00000000000..dd77059dbfa --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -0,0 +1,1094 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.mediacodec; + +import android.annotation.TargetApi; +import android.media.MediaCodec; +import android.media.MediaCodec.CodecException; +import android.media.MediaCodec.CryptoException; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.os.Looper; +import android.os.SystemClock; +import android.util.Log; +import org.telegram.messenger.exoplayer2.BaseRenderer; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.drm.DrmSession; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.TraceUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * An abstract renderer that uses {@link MediaCodec} to decode samples for rendering. + */ +@TargetApi(16) +public abstract class MediaCodecRenderer extends BaseRenderer { + + /** + * Thrown when a failure occurs instantiating a decoder. + */ + public static class DecoderInitializationException extends Exception { + + private static final int CUSTOM_ERROR_CODE_BASE = -50000; + private static final int NO_SUITABLE_DECODER_ERROR = CUSTOM_ERROR_CODE_BASE + 1; + private static final int DECODER_QUERY_ERROR = CUSTOM_ERROR_CODE_BASE + 2; + + /** + * The mime type for which a decoder was being initialized. + */ + public final String mimeType; + + /** + * Whether it was required that the decoder support a secure output path. + */ + public final boolean secureDecoderRequired; + + /** + * The name of the decoder that failed to initialize. Null if no suitable decoder was found. + */ + public final String decoderName; + + /** + * An optional developer-readable diagnostic information string. May be null. + */ + public final String diagnosticInfo; + + public DecoderInitializationException(Format format, Throwable cause, + boolean secureDecoderRequired, int errorCode) { + super("Decoder init failed: [" + errorCode + "], " + format, cause); + this.mimeType = format.sampleMimeType; + this.secureDecoderRequired = secureDecoderRequired; + this.decoderName = null; + this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode); + } + + public DecoderInitializationException(Format format, Throwable cause, + boolean secureDecoderRequired, String decoderName) { + super("Decoder init failed: " + decoderName + ", " + format, cause); + this.mimeType = format.sampleMimeType; + this.secureDecoderRequired = secureDecoderRequired; + this.decoderName = decoderName; + this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; + } + + @TargetApi(21) + private static String getDiagnosticInfoV21(Throwable cause) { + if (cause instanceof CodecException) { + return ((CodecException) cause).getDiagnosticInfo(); + } + return null; + } + + private static String buildCustomDiagnosticInfo(int errorCode) { + String sign = errorCode < 0 ? "neg_" : ""; + return "com.google.android.exoplayer.MediaCodecTrackRenderer_" + sign + Math.abs(errorCode); + } + + } + + private static final String TAG = "MediaCodecRenderer"; + + /** + * If the {@link MediaCodec} is hotswapped (i.e. replaced during playback), this is the period of + * time during which {@link #isReady()} will report true regardless of whether the new codec has + * output frames that are ready to be rendered. + *

        + * This allows codec hotswapping to be performed seamlessly, without interrupting the playback of + * other renderers, provided the new codec is able to decode some frames within this time period. + */ + private static final long MAX_CODEC_HOTSWAP_TIME_MS = 1000; + + /** + * There is no pending adaptive reconfiguration work. + */ + private static final int RECONFIGURATION_STATE_NONE = 0; + /** + * Codec configuration data needs to be written into the next buffer. + */ + private static final int RECONFIGURATION_STATE_WRITE_PENDING = 1; + /** + * Codec configuration data has been written into the next buffer, but that buffer still needs to + * be returned to the codec. + */ + private static final int RECONFIGURATION_STATE_QUEUE_PENDING = 2; + + /** + * The codec does not need to be re-initialized. + */ + private static final int REINITIALIZATION_STATE_NONE = 0; + /** + * The input format has changed in a way that requires the codec to be re-initialized, but we + * haven't yet signaled an end of stream to the existing codec. We need to do so in order to + * ensure that it outputs any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM = 1; + /** + * The input format has changed in a way that requires the codec to be re-initialized, and we've + * signaled an end of stream to the existing codec. We're waiting for the codec to output an end + * of stream signal to indicate that it has output any remaining buffers before we release it. + */ + private static final int REINITIALIZATION_STATE_WAIT_END_OF_STREAM = 2; + + /** + * H.264/AVC buffer to queue when using the adaptation workaround (see + * {@link #codecNeedsAdaptationWorkaround(String)}. Consists of three NAL units with start codes: + * Baseline sequence/picture parameter sets and a 32 * 32 pixel IDR slice. This stream can be + * queued to force a resolution change when adapting to a new format. + */ + private static final byte[] ADAPTATION_WORKAROUND_BUFFER = Util.getBytesFromHexString( + "0000016742C00BDA259000000168CE0F13200000016588840DCE7118A0002FBF1C31C3275D78"); + private static final int ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT = 32; + + private final MediaCodecSelector mediaCodecSelector; + private final DrmSessionManager drmSessionManager; + private final boolean playClearSamplesWithoutKeys; + private final DecoderInputBuffer buffer; + private final FormatHolder formatHolder; + private final List decodeOnlyPresentationTimestamps; + private final MediaCodec.BufferInfo outputBufferInfo; + + private Format format; + private MediaCodec codec; + private DrmSession drmSession; + private DrmSession pendingDrmSession; + private boolean codecIsAdaptive; + private boolean codecNeedsDiscardToSpsWorkaround; + private boolean codecNeedsFlushWorkaround; + private boolean codecNeedsAdaptationWorkaround; + private boolean codecNeedsEosPropagationWorkaround; + private boolean codecNeedsEosFlushWorkaround; + private boolean codecNeedsMonoChannelCountWorkaround; + private boolean codecNeedsAdaptationWorkaroundBuffer; + private boolean shouldSkipAdaptationWorkaroundOutputBuffer; + private ByteBuffer[] inputBuffers; + private ByteBuffer[] outputBuffers; + private long codecHotswapDeadlineMs; + private int inputIndex; + private int outputIndex; + private boolean shouldSkipOutputBuffer; + private boolean codecReconfigured; + private int codecReconfigurationState; + private int codecReinitializationState; + private boolean codecReceivedBuffers; + private boolean codecReceivedEos; + + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private boolean waitingForKeys; + + protected DecoderCounters decoderCounters; + + /** + * @param trackType The track type that the renderer handles. One of the {@code C.TRACK_TYPE_*} + * constants defined in {@link C}. + * @param mediaCodecSelector A decoder selector. + * @param drmSessionManager For use with encrypted media. May be null if support for encrypted + * media is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + */ + public MediaCodecRenderer(int trackType, MediaCodecSelector mediaCodecSelector, + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys) { + super(trackType); + Assertions.checkState(Util.SDK_INT >= 16); + this.mediaCodecSelector = Assertions.checkNotNull(mediaCodecSelector); + this.drmSessionManager = drmSessionManager; + this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys; + buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DISABLED); + formatHolder = new FormatHolder(); + decodeOnlyPresentationTimestamps = new ArrayList<>(); + outputBufferInfo = new MediaCodec.BufferInfo(); + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + } + + @Override + public final int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException { + return ADAPTIVE_NOT_SEAMLESS; + } + + @Override + public final int supportsFormat(Format format) throws ExoPlaybackException { + try { + return supportsFormat(mediaCodecSelector, format); + } catch (DecoderQueryException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + /** + * Returns the extent to which the renderer is capable of supporting a given format. + * + * @param mediaCodecSelector The decoder selector. + * @param format The format. + * @return The extent to which the renderer is capable of supporting the given format. See + * {@link #supportsFormat(Format)} for more detail. + * @throws DecoderQueryException If there was an error querying decoders. + */ + protected abstract int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException; + + /** + * Returns a {@link MediaCodecInfo} for a given format. + * + * @param mediaCodecSelector The decoder selector. + * @param format The format for which a decoder is required. + * @param requiresSecureDecoder Whether a secure decoder is required. + * @return A {@link MediaCodecInfo} describing the decoder to instantiate, or null if no + * suitable decoder exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + protected MediaCodecInfo getDecoderInfo(MediaCodecSelector mediaCodecSelector, + Format format, boolean requiresSecureDecoder) throws DecoderQueryException { + return mediaCodecSelector.getDecoderInfo(format.sampleMimeType, requiresSecureDecoder); + } + + /** + * Configures a newly created {@link MediaCodec}. + * + * @param codec The {@link MediaCodec} to configure. + * @param format The format for which the codec is being configured. + * @param crypto For drm protected playbacks, a {@link MediaCrypto} to use for decryption. + */ + protected abstract void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto); + + @SuppressWarnings("deprecation") + protected final void maybeInitCodec() throws ExoPlaybackException { + if (!shouldInitCodec()) { + return; + } + + drmSession = pendingDrmSession; + String mimeType = format.sampleMimeType; + MediaCrypto mediaCrypto = null; + boolean drmSessionRequiresSecureDecoder = false; + if (drmSession != null) { + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } else if (drmSessionState == DrmSession.STATE_OPENED + || drmSessionState == DrmSession.STATE_OPENED_WITH_KEYS) { + mediaCrypto = drmSession.getMediaCrypto().getWrappedMediaCrypto(); + drmSessionRequiresSecureDecoder = drmSession.requiresSecureDecoderComponent(mimeType); + } else { + // The drm session isn't open yet. + return; + } + } + + MediaCodecInfo decoderInfo = null; + try { + decoderInfo = getDecoderInfo(mediaCodecSelector, format, drmSessionRequiresSecureDecoder); + if (decoderInfo == null && drmSessionRequiresSecureDecoder) { + // The drm session indicates that a secure decoder is required, but the device does not have + // one. Assuming that supportsFormat indicated support for the media being played, we know + // that it does not require a secure output path. Most CDM implementations allow playback to + // proceed with a non-secure decoder in this case, so we try our luck. + decoderInfo = getDecoderInfo(mediaCodecSelector, format, false); + if (decoderInfo != null) { + Log.w(TAG, "Drm session requires secure decoder for " + mimeType + ", but " + + "no secure decoder available. Trying to proceed with " + decoderInfo.name + "."); + } + } + } catch (DecoderQueryException e) { + throwDecoderInitError(new DecoderInitializationException(format, e, + drmSessionRequiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); + } + + if (decoderInfo == null) { + throwDecoderInitError(new DecoderInitializationException(format, null, + drmSessionRequiresSecureDecoder, + DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); + } + + String codecName = decoderInfo.name; + codecIsAdaptive = decoderInfo.adaptive; + codecNeedsDiscardToSpsWorkaround = codecNeedsDiscardToSpsWorkaround(codecName, format); + codecNeedsFlushWorkaround = codecNeedsFlushWorkaround(codecName); + codecNeedsAdaptationWorkaround = codecNeedsAdaptationWorkaround(codecName); + codecNeedsEosPropagationWorkaround = codecNeedsEosPropagationWorkaround(codecName); + codecNeedsEosFlushWorkaround = codecNeedsEosFlushWorkaround(codecName); + codecNeedsMonoChannelCountWorkaround = codecNeedsMonoChannelCountWorkaround(codecName, format); + try { + long codecInitializingTimestamp = SystemClock.elapsedRealtime(); + TraceUtil.beginSection("createCodec:" + codecName); + codec = MediaCodec.createByCodecName(codecName); + TraceUtil.endSection(); + TraceUtil.beginSection("configureCodec"); + configureCodec(codec, format, mediaCrypto); + TraceUtil.endSection(); + TraceUtil.beginSection("startCodec"); + codec.start(); + TraceUtil.endSection(); + long codecInitializedTimestamp = SystemClock.elapsedRealtime(); + onCodecInitialized(codecName, codecInitializedTimestamp, + codecInitializedTimestamp - codecInitializingTimestamp); + inputBuffers = codec.getInputBuffers(); + outputBuffers = codec.getOutputBuffers(); + } catch (Exception e) { + throwDecoderInitError(new DecoderInitializationException(format, e, + drmSessionRequiresSecureDecoder, codecName)); + } + codecHotswapDeadlineMs = getState() == STATE_STARTED + ? (SystemClock.elapsedRealtime() + MAX_CODEC_HOTSWAP_TIME_MS) : C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + decoderCounters.decoderInitCount++; + } + + private void throwDecoderInitError(DecoderInitializationException e) + throws ExoPlaybackException { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + + protected boolean shouldInitCodec() { + return codec == null && format != null; + } + + protected final MediaCodec getCodec() { + return codec; + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + decoderCounters = new DecoderCounters(); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + inputStreamEnded = false; + outputStreamEnded = false; + if (codec != null) { + flushCodec(); + } + } + + @Override + protected void onDisabled() { + format = null; + try { + releaseCodec(); + } finally { + try { + if (drmSession != null) { + drmSessionManager.releaseSession(drmSession); + } + } finally { + try { + if (pendingDrmSession != null && pendingDrmSession != drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } finally { + drmSession = null; + pendingDrmSession = null; + } + } + } + } + + protected void releaseCodec() { + if (codec != null) { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + inputBuffers = null; + outputBuffers = null; + codecReconfigured = false; + codecReceivedBuffers = false; + codecIsAdaptive = false; + codecNeedsDiscardToSpsWorkaround = false; + codecNeedsFlushWorkaround = false; + codecNeedsAdaptationWorkaround = false; + codecNeedsEosPropagationWorkaround = false; + codecNeedsEosFlushWorkaround = false; + codecNeedsMonoChannelCountWorkaround = false; + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codecReceivedEos = false; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + codecReinitializationState = REINITIALIZATION_STATE_NONE; + decoderCounters.decoderReleaseCount++; + try { + codec.stop(); + } finally { + try { + codec.release(); + } finally { + codec = null; + if (drmSession != null && pendingDrmSession != drmSession) { + try { + drmSessionManager.releaseSession(drmSession); + } finally { + drmSession = null; + } + } + } + } + } + } + + @Override + protected void onStarted() { + // Do nothing. Overridden to remove throws clause. + } + + @Override + protected void onStopped() { + // Do nothing. Overridden to remove throws clause. + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (format == null) { + readFormat(); + } + maybeInitCodec(); + if (codec != null) { + TraceUtil.beginSection("drainAndFeed"); + while (drainOutputBuffer(positionUs, elapsedRealtimeUs)) {} + while (feedInputBuffer()) {} + TraceUtil.endSection(); + } else if (format != null) { + skipToKeyframeBefore(positionUs); + } + decoderCounters.ensureUpdated(); + } + + private void readFormat() throws ExoPlaybackException { + int result = readSource(formatHolder, null); + if (result == C.RESULT_FORMAT_READ) { + onInputFormatChanged(formatHolder.format); + } + } + + protected void flushCodec() throws ExoPlaybackException { + codecHotswapDeadlineMs = C.TIME_UNSET; + inputIndex = C.INDEX_UNSET; + outputIndex = C.INDEX_UNSET; + waitingForKeys = false; + shouldSkipOutputBuffer = false; + decodeOnlyPresentationTimestamps.clear(); + codecNeedsAdaptationWorkaroundBuffer = false; + shouldSkipAdaptationWorkaroundOutputBuffer = false; + if (codecNeedsFlushWorkaround || (codecNeedsEosFlushWorkaround && codecReceivedEos)) { + // Workaround framework bugs. See [Internal: b/8347958, b/8578467, b/8543366, b/23361053]. + releaseCodec(); + maybeInitCodec(); + } else if (codecReinitializationState != REINITIALIZATION_STATE_NONE) { + // We're already waiting to release and re-initialize the codec. Since we're now flushing, + // there's no need to wait any longer. + releaseCodec(); + maybeInitCodec(); + } else { + // We can flush and re-use the existing decoder. + codec.flush(); + codecReceivedBuffers = false; + } + if (codecReconfigured && format != null) { + // Any reconfiguration data that we send shortly before the flush may be discarded. We + // avoid this issue by sending reconfiguration data following every flush. + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + } + + /** + * @return Whether it may be possible to feed more input data. + * @throws ExoPlaybackException If an error occurs feeding the input buffer. + */ + private boolean feedInputBuffer() throws ExoPlaybackException { + if (inputStreamEnded + || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { + // The input stream has ended, or we need to re-initialize the codec but are still waiting + // for the existing codec to output any final output buffers. + return false; + } + + if (inputIndex < 0) { + inputIndex = codec.dequeueInputBuffer(0); + if (inputIndex < 0) { + return false; + } + buffer.data = inputBuffers[inputIndex]; + buffer.clear(); + } + + if (codecReinitializationState == REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM) { + // We need to re-initialize the codec. Send an end of stream signal to the existing codec so + // that it outputs any remaining buffers before we release it. + if (codecNeedsEosPropagationWorkaround) { + // Do nothing. + } else { + codecReceivedEos = true; + codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputIndex = C.INDEX_UNSET; + } + codecReinitializationState = REINITIALIZATION_STATE_WAIT_END_OF_STREAM; + return false; + } + + if (codecNeedsAdaptationWorkaroundBuffer) { + codecNeedsAdaptationWorkaroundBuffer = false; + buffer.data.put(ADAPTATION_WORKAROUND_BUFFER); + codec.queueInputBuffer(inputIndex, 0, ADAPTATION_WORKAROUND_BUFFER.length, 0, 0); + inputIndex = C.INDEX_UNSET; + codecReceivedBuffers = true; + return true; + } + + int result; + int adaptiveReconfigurationBytes = 0; + if (waitingForKeys) { + // We've already read an encrypted sample into buffer, and are waiting for keys. + result = C.RESULT_BUFFER_READ; + } else { + // For adaptive reconfiguration OMX decoders expect all reconfiguration data to be supplied + // at the start of the buffer that also contains the first frame in the new format. + if (codecReconfigurationState == RECONFIGURATION_STATE_WRITE_PENDING) { + for (int i = 0; i < format.initializationData.size(); i++) { + byte[] data = format.initializationData.get(i); + buffer.data.put(data); + } + codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; + } + adaptiveReconfigurationBytes = buffer.data.position(); + result = readSource(formatHolder, buffer); + } + + if (result == C.RESULT_NOTHING_READ) { + return false; + } + if (result == C.RESULT_FORMAT_READ) { + if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { + // We received two formats in a row. Clear the current buffer of any reconfiguration data + // associated with the first format. + buffer.clear(); + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + onInputFormatChanged(formatHolder.format); + return true; + } + + // We've read a buffer. + if (buffer.isEndOfStream()) { + if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { + // We received a new format immediately before the end of the stream. We need to clear + // the corresponding reconfiguration data from the current buffer, but re-write it into + // a subsequent buffer if there are any (e.g. if the user seeks backwards). + buffer.clear(); + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + } + inputStreamEnded = true; + if (!codecReceivedBuffers) { + processEndOfStream(); + return false; + } + try { + if (codecNeedsEosPropagationWorkaround) { + // Do nothing. + } else { + codecReceivedEos = true; + codec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + inputIndex = C.INDEX_UNSET; + } + } catch (CryptoException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return false; + } + boolean bufferEncrypted = buffer.isEncrypted(); + waitingForKeys = shouldWaitForKeys(bufferEncrypted); + if (waitingForKeys) { + return false; + } + if (codecNeedsDiscardToSpsWorkaround && !bufferEncrypted) { + NalUnitUtil.discardToSps(buffer.data); + if (buffer.data.position() == 0) { + return true; + } + codecNeedsDiscardToSpsWorkaround = false; + } + try { + long presentationTimeUs = buffer.timeUs; + if (buffer.isDecodeOnly()) { + decodeOnlyPresentationTimestamps.add(presentationTimeUs); + } + + buffer.flip(); + onQueueInputBuffer(buffer); + + if (bufferEncrypted) { + MediaCodec.CryptoInfo cryptoInfo = getFrameworkCryptoInfo(buffer, + adaptiveReconfigurationBytes); + codec.queueSecureInputBuffer(inputIndex, 0, cryptoInfo, presentationTimeUs, 0); + } else { + codec.queueInputBuffer(inputIndex, 0, buffer.data.limit(), presentationTimeUs, 0); + } + inputIndex = C.INDEX_UNSET; + codecReceivedBuffers = true; + codecReconfigurationState = RECONFIGURATION_STATE_NONE; + decoderCounters.inputBufferCount++; + } catch (CryptoException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + return true; + } + + private static MediaCodec.CryptoInfo getFrameworkCryptoInfo(DecoderInputBuffer buffer, + int adaptiveReconfigurationBytes) { + MediaCodec.CryptoInfo cryptoInfo = buffer.cryptoInfo.getFrameworkCryptoInfoV16(); + if (adaptiveReconfigurationBytes == 0) { + return cryptoInfo; + } + // There must be at least one sub-sample, although numBytesOfClearData is permitted to be + // null if it contains no clear data. Instantiate it if needed, and add the reconfiguration + // bytes to the clear byte count of the first sub-sample. + if (cryptoInfo.numBytesOfClearData == null) { + cryptoInfo.numBytesOfClearData = new int[1]; + } + cryptoInfo.numBytesOfClearData[0] += adaptiveReconfigurationBytes; + return cryptoInfo; + } + + private boolean shouldWaitForKeys(boolean bufferEncrypted) throws ExoPlaybackException { + if (drmSession == null) { + return false; + } + @DrmSession.State int drmSessionState = drmSession.getState(); + if (drmSessionState == DrmSession.STATE_ERROR) { + throw ExoPlaybackException.createForRenderer(drmSession.getError(), getIndex()); + } + return drmSessionState != DrmSession.STATE_OPENED_WITH_KEYS + && (bufferEncrypted || !playClearSamplesWithoutKeys); + } + + /** + * Called when a {@link MediaCodec} has been created and configured. + *

        + * The default implementation is a no-op. + * + * @param name The name of the codec that was initialized. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the codec in milliseconds. + */ + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + // Do nothing. + } + + /** + * Called when a new format is read from the upstream {@link MediaPeriod}. + * + * @param newFormat The new format. + * @throws ExoPlaybackException If an error occurs reinitializing the {@link MediaCodec}. + */ + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + Format oldFormat = format; + format = newFormat; + + boolean drmInitDataChanged = !Util.areEqual(format.drmInitData, oldFormat == null ? null + : oldFormat.drmInitData); + if (drmInitDataChanged) { + if (format.drmInitData != null) { + if (drmSessionManager == null) { + throw ExoPlaybackException.createForRenderer( + new IllegalStateException("Media requires a DrmSessionManager"), getIndex()); + } + pendingDrmSession = drmSessionManager.acquireSession(Looper.myLooper(), format.drmInitData); + if (pendingDrmSession == drmSession) { + drmSessionManager.releaseSession(pendingDrmSession); + } + } else { + pendingDrmSession = null; + } + } + + if (pendingDrmSession == drmSession && codec != null + && canReconfigureCodec(codec, codecIsAdaptive, oldFormat, format)) { + codecReconfigured = true; + codecReconfigurationState = RECONFIGURATION_STATE_WRITE_PENDING; + codecNeedsAdaptationWorkaroundBuffer = codecNeedsAdaptationWorkaround + && format.width == oldFormat.width && format.height == oldFormat.height; + } else { + if (codecReceivedBuffers) { + // Signal end of stream and wait for any final output buffers before re-initialization. + codecReinitializationState = REINITIALIZATION_STATE_SIGNAL_END_OF_STREAM; + } else { + // There aren't any final output buffers, so perform re-initialization immediately. + releaseCodec(); + maybeInitCodec(); + } + } + } + + /** + * Called when the output format of the {@link MediaCodec} changes. + *

        + * The default implementation is a no-op. + * + * @param codec The {@link MediaCodec} instance. + * @param outputFormat The new output format. + */ + protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) { + // Do nothing. + } + + /** + * Called when the output stream ends, meaning that the last output buffer has been processed and + * the {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag has been propagated through the decoder. + *

        + * The default implementation is a no-op. + */ + protected void onOutputStreamEnded() { + // Do nothing. + } + + /** + * Called immediately before an input buffer is queued into the codec. + *

        + * The default implementation is a no-op. + * + * @param buffer The buffer to be queued. + */ + protected void onQueueInputBuffer(DecoderInputBuffer buffer) { + // Do nothing. + } + + /** + * Called when an output buffer is successfully processed. + *

        + * The default implementation is a no-op. + * + * @param presentationTimeUs The timestamp associated with the output buffer. + */ + protected void onProcessedOutputBuffer(long presentationTimeUs) { + // Do nothing. + } + + /** + * Determines whether the existing {@link MediaCodec} should be reconfigured for a new format by + * sending codec specific initialization data at the start of the next input buffer. If true is + * returned then the {@link MediaCodec} instance will be reconfigured in this way. If false is + * returned then the instance will be released, and a new instance will be created for the new + * format. + *

        + * The default implementation returns false. + * + * @param codec The existing {@link MediaCodec} instance. + * @param codecIsAdaptive Whether the codec is adaptive. + * @param oldFormat The format for which the existing instance is configured. + * @param newFormat The new format. + * @return Whether the existing instance can be reconfigured. + */ + protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, Format oldFormat, + Format newFormat) { + return false; + } + + @Override + public boolean isEnded() { + return outputStreamEnded; + } + + @Override + public boolean isReady() { + return format != null && !waitingForKeys && (isSourceReady() || outputIndex >= 0 + || (codecHotswapDeadlineMs != C.TIME_UNSET + && SystemClock.elapsedRealtime() < codecHotswapDeadlineMs)); + } + + /** + * Returns the maximum time to block whilst waiting for a decoded output buffer. + * + * @return The maximum time to block, in microseconds. + */ + protected long getDequeueOutputBufferTimeoutUs() { + return 0; + } + + /** + * @return Whether it may be possible to drain more output data. + * @throws ExoPlaybackException If an error occurs draining the output buffer. + */ + @SuppressWarnings("deprecation") + private boolean drainOutputBuffer(long positionUs, long elapsedRealtimeUs) + throws ExoPlaybackException { + if (outputStreamEnded) { + return false; + } + + if (outputIndex < 0) { + outputIndex = codec.dequeueOutputBuffer(outputBufferInfo, getDequeueOutputBufferTimeoutUs()); + if (outputIndex >= 0) { + // We've dequeued a buffer. + if (shouldSkipAdaptationWorkaroundOutputBuffer) { + shouldSkipAdaptationWorkaroundOutputBuffer = false; + codec.releaseOutputBuffer(outputIndex, false); + outputIndex = C.INDEX_UNSET; + return true; + } + if ((outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { + // The dequeued buffer indicates the end of the stream. Process it immediately. + processEndOfStream(); + outputIndex = C.INDEX_UNSET; + return true; + } else { + // The dequeued buffer is a media buffer. Do some initial setup. The buffer will be + // processed by calling processOutputBuffer (possibly multiple times) below. + ByteBuffer outputBuffer = outputBuffers[outputIndex]; + if (outputBuffer != null) { + outputBuffer.position(outputBufferInfo.offset); + outputBuffer.limit(outputBufferInfo.offset + outputBufferInfo.size); + } + shouldSkipOutputBuffer = shouldSkipOutputBuffer(outputBufferInfo.presentationTimeUs); + } + } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED /* (-2) */) { + processOutputFormat(); + return true; + } else if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED /* (-3) */) { + processOutputBuffersChanged(); + return true; + } else /* MediaCodec.INFO_TRY_AGAIN_LATER (-1) or unknown negative return value */ { + if (codecNeedsEosPropagationWorkaround && (inputStreamEnded + || codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM)) { + processEndOfStream(); + return true; + } + return false; + } + } + + if (processOutputBuffer(positionUs, elapsedRealtimeUs, codec, outputBuffers[outputIndex], + outputIndex, outputBufferInfo.flags, outputBufferInfo.presentationTimeUs, + shouldSkipOutputBuffer)) { + onProcessedOutputBuffer(outputBufferInfo.presentationTimeUs); + outputIndex = C.INDEX_UNSET; + return true; + } + + return false; + } + + /** + * Processes a new output format. + */ + private void processOutputFormat() { + MediaFormat format = codec.getOutputFormat(); + if (codecNeedsAdaptationWorkaround + && format.getInteger(MediaFormat.KEY_WIDTH) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT + && format.getInteger(MediaFormat.KEY_HEIGHT) == ADAPTATION_WORKAROUND_SLICE_WIDTH_HEIGHT) { + // We assume this format changed event was caused by the adaptation workaround. + shouldSkipAdaptationWorkaroundOutputBuffer = true; + return; + } + if (codecNeedsMonoChannelCountWorkaround) { + format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); + } + onOutputFormatChanged(codec, format); + } + + /** + * Processes a change in the output buffers. + */ + @SuppressWarnings("deprecation") + private void processOutputBuffersChanged() { + outputBuffers = codec.getOutputBuffers(); + } + + /** + * Processes an output media buffer. + *

        + * When a new {@link ByteBuffer} is passed to this method its position and limit delineate the + * data to be processed. The return value indicates whether the buffer was processed in full. If + * true is returned then the next call to this method will receive a new buffer to be processed. + * If false is returned then the same buffer will be passed to the next call. An implementation of + * this method is free to modify the buffer and can assume that the buffer will not be externally + * modified between successive calls. Hence an implementation can, for example, modify the + * buffer's position to keep track of how much of the data it has processed. + *

        + * Note that the first call to this method following a call to + * {@link #onPositionReset(long, boolean)} will always receive a new {@link ByteBuffer} to be + * processed. + * + * @param positionUs The current media time in microseconds, measured at the start of the + * current iteration of the rendering loop. + * @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds, + * measured at the start of the current iteration of the rendering loop. + * @param codec The {@link MediaCodec} instance. + * @param buffer The output buffer to process. + * @param bufferIndex The index of the output buffer. + * @param bufferFlags The flags attached to the output buffer. + * @param bufferPresentationTimeUs The presentation time of the output buffer in microseconds. + * @param shouldSkip Whether the buffer should be skipped (i.e. not rendered). + * + * @return Whether the output buffer was fully processed (e.g. rendered or skipped). + * @throws ExoPlaybackException If an error occurs processing the output buffer. + */ + protected abstract boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, + MediaCodec codec, ByteBuffer buffer, int bufferIndex, int bufferFlags, + long bufferPresentationTimeUs, boolean shouldSkip) throws ExoPlaybackException; + + /** + * Processes an end of stream signal. + * + * @throws ExoPlaybackException If an error occurs processing the signal. + */ + private void processEndOfStream() throws ExoPlaybackException { + if (codecReinitializationState == REINITIALIZATION_STATE_WAIT_END_OF_STREAM) { + // We're waiting to re-initialize the codec, and have now processed all final buffers. + releaseCodec(); + maybeInitCodec(); + } else { + outputStreamEnded = true; + onOutputStreamEnded(); + } + } + + private boolean shouldSkipOutputBuffer(long presentationTimeUs) { + // We avoid using decodeOnlyPresentationTimestamps.remove(presentationTimeUs) because it would + // box presentationTimeUs, creating a Long object that would need to be garbage collected. + int size = decodeOnlyPresentationTimestamps.size(); + for (int i = 0; i < size; i++) { + if (decodeOnlyPresentationTimestamps.get(i) == presentationTimeUs) { + decodeOnlyPresentationTimestamps.remove(i); + return true; + } + } + return false; + } + + /** + * Returns whether the decoder is known to fail when flushed. + *

        + * If true is returned, the renderer will work around the issue by releasing the decoder and + * instantiating a new one rather than flushing the current instance. + * + * @param name The name of the decoder. + * @return True if the decoder is known to fail when flushed. + */ + private static boolean codecNeedsFlushWorkaround(String name) { + return Util.SDK_INT < 18 + || (Util.SDK_INT == 18 + && ("OMX.SEC.avc.dec".equals(name) || "OMX.SEC.avc.dec.secure".equals(name))) + || (Util.SDK_INT == 19 && Util.MODEL.startsWith("SM-G800") + && ("OMX.Exynos.avc.dec".equals(name) || "OMX.Exynos.avc.dec.secure".equals(name))); + } + + /** + * Returns whether the decoder is known to get stuck during some adaptations where the resolution + * does not change. + *

        + * If true is returned, the renderer will work around the issue by queueing and discarding a blank + * frame at a different resolution, which resets the codec's internal state. + *

        + * See [Internal: b/27807182]. + * + * @param name The name of the decoder. + * @return True if the decoder is known to get stuck during some adaptations. + */ + private static boolean codecNeedsAdaptationWorkaround(String name) { + return Util.SDK_INT < 24 + && ("OMX.Nvidia.h264.decode".equals(name) || "OMX.Nvidia.h264.decode.secure".equals(name)) + && ("flounder".equals(Util.DEVICE) || "flounder_lte".equals(Util.DEVICE) + || "grouper".equals(Util.DEVICE) || "tilapia".equals(Util.DEVICE)); + } + + /** + * Returns whether the decoder is an H.264/AVC decoder known to fail if NAL units are queued + * before the codec specific data. + *

        + * If true is returned, the renderer will work around the issue by discarding data up to the SPS. + * + * @param name The name of the decoder. + * @param format The format used to configure the decoder. + * @return True if the decoder is known to fail if NAL units are queued before CSD. + */ + private static boolean codecNeedsDiscardToSpsWorkaround(String name, Format format) { + return Util.SDK_INT < 21 && format.initializationData.isEmpty() + && "OMX.MTK.VIDEO.DECODER.AVC".equals(name); + } + + /** + * Returns whether the decoder is known to handle the propagation of the + * {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} flag incorrectly on the host device. + *

        + * If true is returned, the renderer will work around the issue by approximating end of stream + * behavior without relying on the flag being propagated through to an output buffer by the + * underlying decoder. + * + * @param name The name of the decoder. + * @return True if the decoder is known to handle {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} + * propagation incorrectly on the host device. False otherwise. + */ + private static boolean codecNeedsEosPropagationWorkaround(String name) { + return Util.SDK_INT <= 17 && ("OMX.rk.video_decoder.avc".equals(name) + || "OMX.allwinner.video.decoder.avc".equals(name)); + } + + /** + * Returns whether the decoder is known to behave incorrectly if flushed after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. + *

        + * If true is returned, the renderer will work around the issue by instantiating a new decoder + * when this case occurs. + * + * @param name The name of the decoder. + * @return True if the decoder is known to behave incorrectly if flushed after receiving an input + * buffer with {@link MediaCodec#BUFFER_FLAG_END_OF_STREAM} set. False otherwise. + */ + private static boolean codecNeedsEosFlushWorkaround(String name) { + return Util.SDK_INT <= 23 && "OMX.google.vorbis.decoder".equals(name); + } + + /** + * Returns whether the decoder is known to set the number of audio channels in the output format + * to 2 for the given input format, whilst only actually outputting a single channel. + *

        + * If true is returned then we explicitly override the number of channels in the output format, + * setting it to 1. + * + * @param name The decoder name. + * @param format The input format. + * @return True if the device is known to set the number of audio channels in the output format + * to 2 for the given input format, whilst only actually outputting a single channel. False + * otherwise. + */ + private static boolean codecNeedsMonoChannelCountWorkaround(String name, Format format) { + return Util.SDK_INT <= 18 && format.channelCount == 1 + && "OMX.MTK.AUDIO.DECODER.MP3".equals(name); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java new file mode 100755 index 00000000000..8670d38a587 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecSelector.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.mediacodec; + +import android.media.MediaCodec; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; + +/** + * Selector of {@link MediaCodec} instances. + */ +public interface MediaCodecSelector { + + /** + * Default implementation of {@link MediaCodecSelector}. + */ + MediaCodecSelector DEFAULT = new MediaCodecSelector() { + + @Override + public MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) + throws DecoderQueryException { + return MediaCodecUtil.getDecoderInfo(mimeType, requiresSecureDecoder); + } + + @Override + public MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException { + return MediaCodecUtil.getPassthroughDecoderInfo(); + } + + }; + + /** + * Selects a decoder to instantiate for a given mime type. + * + * @param mimeType The mime type for which a decoder is required. + * @param requiresSecureDecoder Whether a secure decoder is required. + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + MediaCodecInfo getDecoderInfo(String mimeType, boolean requiresSecureDecoder) + throws DecoderQueryException; + + /** + * Selects a decoder to instantiate for audio passthrough. + * + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + * @throws DecoderQueryException Thrown if there was an error querying decoders. + */ + MediaCodecInfo getPassthroughDecoderInfo() throws DecoderQueryException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java new file mode 100755 index 00000000000..bf5cd001f10 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/mediacodec/MediaCodecUtil.java @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.mediacodec; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.media.MediaCodecInfo.CodecCapabilities; +import android.media.MediaCodecInfo.CodecProfileLevel; +import android.media.MediaCodecList; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.util.SparseIntArray; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A utility class for querying the available codecs. + */ +@TargetApi(16) +@SuppressLint("InlinedApi") +public final class MediaCodecUtil { + + /** + * Thrown when an error occurs querying the device for its underlying media capabilities. + *

        + * Such failures are not expected in normal operation and are normally temporary (e.g. if the + * mediaserver process has crashed and is yet to restart). + */ + public static class DecoderQueryException extends Exception { + + private DecoderQueryException(Throwable cause) { + super("Failed to query underlying media codecs", cause); + } + + } + + private static final String TAG = "MediaCodecUtil"; + private static final MediaCodecInfo PASSTHROUGH_DECODER_INFO = + MediaCodecInfo.newPassthroughInstance("OMX.google.raw.decoder"); + private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); + + private static final HashMap> decoderInfosCache = new HashMap<>(); + + // Codecs to constant mappings. + // AVC. + private static final SparseIntArray AVC_PROFILE_NUMBER_TO_CONST; + private static final SparseIntArray AVC_LEVEL_NUMBER_TO_CONST; + private static final String CODEC_ID_AVC1 = "avc1"; + private static final String CODEC_ID_AVC2 = "avc2"; + // HEVC. + private static final Map HEVC_CODEC_STRING_TO_PROFILE_LEVEL; + private static final String CODEC_ID_HEV1 = "hev1"; + private static final String CODEC_ID_HVC1 = "hvc1"; + + // Lazily initialized. + private static int maxH264DecodableFrameSize = -1; + + private MediaCodecUtil() {} + + /** + * Optional call to warm the codec cache for a given mime type. + *

        + * Calling this method may speed up subsequent calls to {@link #getDecoderInfo(String, boolean)}. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + */ + public static void warmDecoderInfoCache(String mimeType, boolean secure) { + try { + getDecoderInfos(mimeType, secure); + } catch (DecoderQueryException e) { + // Codec warming is best effort, so we can swallow the exception. + Log.e(TAG, "Codec warming failed", e); + } + } + + /** + * Returns information about a decoder suitable for audio passthrough. + ** + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + */ + public static MediaCodecInfo getPassthroughDecoderInfo() { + // TODO: Return null if the raw decoder doesn't exist. + return PASSTHROUGH_DECODER_INFO; + } + + /** + * Returns information about the preferred decoder for a given mime type. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + * @return A {@link MediaCodecInfo} describing the decoder, or null if no suitable decoder + * exists. + * @throws DecoderQueryException If there was an error querying the available decoders. + */ + public static MediaCodecInfo getDecoderInfo(String mimeType, boolean secure) + throws DecoderQueryException { + List decoderInfos = getDecoderInfos(mimeType, secure); + return decoderInfos.isEmpty() ? null : decoderInfos.get(0); + } + + /** + * Returns all {@link MediaCodecInfo}s for the given mime type, in the order given by + * {@link MediaCodecList}. + * + * @param mimeType The mime type. + * @param secure Whether the decoder is required to support secure decryption. Always pass false + * unless secure decryption really is required. + * @return A list of all @{link MediaCodecInfo}s for the given mime type, in the order + * given by {@link MediaCodecList}. + * @throws DecoderQueryException If there was an error querying the available decoders. + */ + public static synchronized List getDecoderInfos(String mimeType, + boolean secure) throws DecoderQueryException { + CodecKey key = new CodecKey(mimeType, secure); + List decoderInfos = decoderInfosCache.get(key); + if (decoderInfos != null) { + return decoderInfos; + } + MediaCodecListCompat mediaCodecList = Util.SDK_INT >= 21 + ? new MediaCodecListCompatV21(secure) : new MediaCodecListCompatV16(); + decoderInfos = getDecoderInfosInternal(key, mediaCodecList); + if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) { + // Some devices don't list secure decoders on API level 21 [Internal: b/18678462]. Try the + // legacy path. We also try this path on API levels 22 and 23 as a defensive measure. + mediaCodecList = new MediaCodecListCompatV16(); + decoderInfos = getDecoderInfosInternal(key, mediaCodecList); + if (!decoderInfos.isEmpty()) { + Log.w(TAG, "MediaCodecList API didn't list secure decoder for: " + mimeType + + ". Assuming: " + decoderInfos.get(0).name); + } + } + decoderInfos = Collections.unmodifiableList(decoderInfos); + decoderInfosCache.put(key, decoderInfos); + return decoderInfos; + } + + private static List getDecoderInfosInternal( + CodecKey key, MediaCodecListCompat mediaCodecList) throws DecoderQueryException { + try { + List decoderInfos = new ArrayList<>(); + String mimeType = key.mimeType; + int numberOfCodecs = mediaCodecList.getCodecCount(); + boolean secureDecodersExplicit = mediaCodecList.secureDecodersExplicit(); + // Note: MediaCodecList is sorted by the framework such that the best decoders come first. + for (int i = 0; i < numberOfCodecs; i++) { + android.media.MediaCodecInfo codecInfo = mediaCodecList.getCodecInfoAt(i); + String codecName = codecInfo.getName(); + if (isCodecUsableDecoder(codecInfo, codecName, secureDecodersExplicit)) { + for (String supportedType : codecInfo.getSupportedTypes()) { + if (supportedType.equalsIgnoreCase(mimeType)) { + try { + CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(supportedType); + boolean secure = mediaCodecList.isSecurePlaybackSupported(mimeType, capabilities); + if ((secureDecodersExplicit && key.secure == secure) + || (!secureDecodersExplicit && !key.secure)) { + decoderInfos.add( + MediaCodecInfo.newInstance(codecName, mimeType, capabilities)); + } else if (!secureDecodersExplicit && secure) { + decoderInfos.add(MediaCodecInfo.newInstance(codecName + ".secure", + mimeType, capabilities)); + // It only makes sense to have one synthesized secure decoder, return immediately. + return decoderInfos; + } + } catch (Exception e) { + if (Util.SDK_INT <= 23 && !decoderInfos.isEmpty()) { + // Suppress error querying secondary codec capabilities up to API level 23. + Log.e(TAG, "Skipping codec " + codecName + " (failed to query capabilities)"); + } else { + // Rethrow error querying primary codec capabilities, or secondary codec + // capabilities if API level is greater than 23. + Log.e(TAG, "Failed to query codec " + codecName + " (" + supportedType + ")"); + throw e; + } + } + } + } + } + } + return decoderInfos; + } catch (Exception e) { + // If the underlying mediaserver is in a bad state, we may catch an IllegalStateException + // or an IllegalArgumentException here. + throw new DecoderQueryException(e); + } + } + + /** + * Returns whether the specified codec is usable for decoding on the current device. + */ + private static boolean isCodecUsableDecoder(android.media.MediaCodecInfo info, String name, + boolean secureDecodersExplicit) { + if (info.isEncoder() || (!secureDecodersExplicit && name.endsWith(".secure"))) { + return false; + } + + // Work around broken audio decoders. + if (Util.SDK_INT < 21 + && ("CIPAACDecoder".equals(name) + || "CIPMP3Decoder".equals(name) + || "CIPVorbisDecoder".equals(name) + || "CIPAMRNBDecoder".equals(name) + || "AACDecoder".equals(name) + || "MP3Decoder".equals(name))) { + return false; + } + // Work around https://github.com/google/ExoPlayer/issues/398 + if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) { + return false; + } + // Work around https://github.com/google/ExoPlayer/issues/1528 + if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) + && "a70".equals(Util.DEVICE)) { + return false; + } + + // Work around an issue where querying/creating a particular MP3 decoder on some devices on + // platform API version 16 fails. + if (Util.SDK_INT == 16 + && "OMX.qcom.audio.decoder.mp3".equals(name) + && ("dlxu".equals(Util.DEVICE) // HTC Butterfly + || "protou".equals(Util.DEVICE) // HTC Desire X + || "ville".equals(Util.DEVICE) // HTC One S + || "villeplus".equals(Util.DEVICE) + || "villec2".equals(Util.DEVICE) + || Util.DEVICE.startsWith("gee") // LGE Optimus G + || "C6602".equals(Util.DEVICE) // Sony Xperia Z + || "C6603".equals(Util.DEVICE) + || "C6606".equals(Util.DEVICE) + || "C6616".equals(Util.DEVICE) + || "L36h".equals(Util.DEVICE) + || "SO-02E".equals(Util.DEVICE))) { + return false; + } + + // Work around an issue where large timestamps are not propagated correctly. + if (Util.SDK_INT == 16 + && "OMX.qcom.audio.decoder.aac".equals(name) + && ("C1504".equals(Util.DEVICE) // Sony Xperia E + || "C1505".equals(Util.DEVICE) + || "C1604".equals(Util.DEVICE) // Sony Xperia E dual + || "C1605".equals(Util.DEVICE))) { + return false; + } + + // Work around https://github.com/google/ExoPlayer/issues/548 + // VP8 decoder on Samsung Galaxy S3/S4/S4 Mini/Tab 3 does not render video. + if (Util.SDK_INT <= 19 + && (Util.DEVICE.startsWith("d2") || Util.DEVICE.startsWith("serrano") + || Util.DEVICE.startsWith("jflte") || Util.DEVICE.startsWith("santos")) + && "samsung".equals(Util.MANUFACTURER) && "OMX.SEC.vp8.dec".equals(name)) { + return false; + } + // VP8 decoder on Samsung Galaxy S4 cannot be queried. + if (Util.SDK_INT <= 19 && Util.DEVICE.startsWith("jflte") + && "OMX.qcom.video.decoder.vp8".equals(name)) { + return false; + } + + return true; + } + + /** + * Returns the maximum frame size supported by the default H264 decoder. + * + * @return The maximum frame size for an H264 stream that can be decoded on the device. + */ + public static int maxH264DecodableFrameSize() throws DecoderQueryException { + if (maxH264DecodableFrameSize == -1) { + int result = 0; + MediaCodecInfo decoderInfo = getDecoderInfo(MimeTypes.VIDEO_H264, false); + if (decoderInfo != null) { + for (CodecProfileLevel profileLevel : decoderInfo.getProfileLevels()) { + result = Math.max(avcLevelToMaxFrameSize(profileLevel.level), result); + } + // We assume support for at least 480p (SDK_INT >= 21) or 360p (SDK_INT < 21), which are + // the levels mandated by the Android CDD. + result = Math.max(result, Util.SDK_INT >= 21 ? (720 * 480) : (480 * 360)); + } + maxH264DecodableFrameSize = result; + } + return maxH264DecodableFrameSize; + } + + /** + * Returns profile and level (as defined by {@link CodecProfileLevel}) corresponding to the given + * codec description string (as defined by RFC 6381). + * + * @param codec A codec description string, as defined by RFC 6381. + * @return A pair (profile constant, level constant) if {@code codec} is well-formed and + * recognized, or null otherwise + */ + public static Pair getCodecProfileAndLevel(String codec) { + if (codec == null) { + return null; + } + String[] parts = codec.split("\\."); + switch (parts[0]) { + case CODEC_ID_HEV1: + case CODEC_ID_HVC1: + return getHevcProfileAndLevel(codec, parts); + case CODEC_ID_AVC1: + case CODEC_ID_AVC2: + return getAvcProfileAndLevel(codec, parts); + default: + return null; + } + } + + private static Pair getHevcProfileAndLevel(String codec, String[] parts) { + if (parts.length < 4) { + // The codec has fewer parts than required by the HEVC codec string format. + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + // The profile_space gets ignored. + Matcher matcher = PROFILE_PATTERN.matcher(parts[1]); + if (!matcher.matches()) { + Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec); + return null; + } + String profileString = matcher.group(1); + int profile; + if ("1".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain; + } else if ("2".equals(profileString)) { + profile = CodecProfileLevel.HEVCProfileMain10; + } else { + Log.w(TAG, "Unknown HEVC profile string: " + profileString); + return null; + } + Integer level = HEVC_CODEC_STRING_TO_PROFILE_LEVEL.get(parts[3]); + if (level == null) { + Log.w(TAG, "Unknown HEVC level string: " + matcher.group(1)); + return null; + } + return new Pair<>(profile, level); + } + + private static Pair getAvcProfileAndLevel(String codec, String[] codecsParts) { + if (codecsParts.length < 2) { + // The codec has fewer parts than required by the AVC codec string format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + Integer profileInteger; + Integer levelInteger; + try { + if (codecsParts[1].length() == 6) { + // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. + profileInteger = Integer.parseInt(codecsParts[1].substring(0, 2), 16); + levelInteger = Integer.parseInt(codecsParts[1].substring(4), 16); + } else if (codecsParts.length >= 3) { + // Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal. + profileInteger = Integer.parseInt(codecsParts[1]); + levelInteger = Integer.parseInt(codecsParts[2]); + } else { + // We don't recognize the format. + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); + return null; + } + + Integer profile = AVC_PROFILE_NUMBER_TO_CONST.get(profileInteger); + if (profile == null) { + Log.w(TAG, "Unknown AVC profile: " + profileInteger); + return null; + } + Integer level = AVC_LEVEL_NUMBER_TO_CONST.get(levelInteger); + if (level == null) { + Log.w(TAG, "Unknown AVC level: " + levelInteger); + return null; + } + return new Pair<>(profile, level); + } + + /** + * Conversion values taken from ISO 14496-10 Table A-1. + * + * @param avcLevel one of CodecProfileLevel.AVCLevel* constants. + * @return maximum frame size that can be decoded by a decoder with the specified avc level + * (or {@code -1} if the level is not recognized) + */ + private static int avcLevelToMaxFrameSize(int avcLevel) { + switch (avcLevel) { + case CodecProfileLevel.AVCLevel1: return 99 * 16 * 16; + case CodecProfileLevel.AVCLevel1b: return 99 * 16 * 16; + case CodecProfileLevel.AVCLevel12: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel13: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel2: return 396 * 16 * 16; + case CodecProfileLevel.AVCLevel21: return 792 * 16 * 16; + case CodecProfileLevel.AVCLevel22: return 1620 * 16 * 16; + case CodecProfileLevel.AVCLevel3: return 1620 * 16 * 16; + case CodecProfileLevel.AVCLevel31: return 3600 * 16 * 16; + case CodecProfileLevel.AVCLevel32: return 5120 * 16 * 16; + case CodecProfileLevel.AVCLevel4: return 8192 * 16 * 16; + case CodecProfileLevel.AVCLevel41: return 8192 * 16 * 16; + case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16; + case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16; + case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16; + default: return -1; + } + } + + private interface MediaCodecListCompat { + + /** + * The number of codecs in the list. + */ + int getCodecCount(); + + /** + * The info at the specified index in the list. + * + * @param index The index. + */ + android.media.MediaCodecInfo getCodecInfoAt(int index); + + /** + * Returns whether secure decoders are explicitly listed, if present. + */ + boolean secureDecodersExplicit(); + + /** + * Whether secure playback is supported for the given {@link CodecCapabilities}, which should + * have been obtained from a {@link android.media.MediaCodecInfo} obtained from this list. + */ + boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities); + + } + + @TargetApi(21) + private static final class MediaCodecListCompatV21 implements MediaCodecListCompat { + + private final int codecKind; + + private android.media.MediaCodecInfo[] mediaCodecInfos; + + public MediaCodecListCompatV21(boolean includeSecure) { + codecKind = includeSecure ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS; + } + + @Override + public int getCodecCount() { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos.length; + } + + @Override + public android.media.MediaCodecInfo getCodecInfoAt(int index) { + ensureMediaCodecInfosInitialized(); + return mediaCodecInfos[index]; + } + + @Override + public boolean secureDecodersExplicit() { + return true; + } + + @Override + public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { + return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); + } + + private void ensureMediaCodecInfosInitialized() { + if (mediaCodecInfos == null) { + mediaCodecInfos = new MediaCodecList(codecKind).getCodecInfos(); + } + } + + } + + @SuppressWarnings("deprecation") + private static final class MediaCodecListCompatV16 implements MediaCodecListCompat { + + @Override + public int getCodecCount() { + return MediaCodecList.getCodecCount(); + } + + @Override + public android.media.MediaCodecInfo getCodecInfoAt(int index) { + return MediaCodecList.getCodecInfoAt(index); + } + + @Override + public boolean secureDecodersExplicit() { + return false; + } + + @Override + public boolean isSecurePlaybackSupported(String mimeType, CodecCapabilities capabilities) { + // Secure decoders weren't explicitly listed prior to API level 21. We assume that a secure + // H264 decoder exists. + return MimeTypes.VIDEO_H264.equals(mimeType); + } + + } + + private static final class CodecKey { + + public final String mimeType; + public final boolean secure; + + public CodecKey(String mimeType, boolean secure) { + this.mimeType = mimeType; + this.secure = secure; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mimeType == null) ? 0 : mimeType.hashCode()); + result = prime * result + (secure ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != CodecKey.class) { + return false; + } + CodecKey other = (CodecKey) obj; + return TextUtils.equals(mimeType, other.mimeType) && secure == other.secure; + } + + } + + static { + AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray(); + AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline); + AVC_PROFILE_NUMBER_TO_CONST.put(77, CodecProfileLevel.AVCProfileMain); + AVC_PROFILE_NUMBER_TO_CONST.put(88, CodecProfileLevel.AVCProfileExtended); + AVC_PROFILE_NUMBER_TO_CONST.put(100, CodecProfileLevel.AVCProfileHigh); + + AVC_LEVEL_NUMBER_TO_CONST = new SparseIntArray(); + AVC_LEVEL_NUMBER_TO_CONST.put(10, CodecProfileLevel.AVCLevel1); + // TODO: Find int for CodecProfileLevel.AVCLevel1b. + AVC_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AVCLevel11); + AVC_LEVEL_NUMBER_TO_CONST.put(12, CodecProfileLevel.AVCLevel12); + AVC_LEVEL_NUMBER_TO_CONST.put(13, CodecProfileLevel.AVCLevel13); + AVC_LEVEL_NUMBER_TO_CONST.put(20, CodecProfileLevel.AVCLevel2); + AVC_LEVEL_NUMBER_TO_CONST.put(21, CodecProfileLevel.AVCLevel21); + AVC_LEVEL_NUMBER_TO_CONST.put(22, CodecProfileLevel.AVCLevel22); + AVC_LEVEL_NUMBER_TO_CONST.put(30, CodecProfileLevel.AVCLevel3); + AVC_LEVEL_NUMBER_TO_CONST.put(31, CodecProfileLevel.AVCLevel31); + AVC_LEVEL_NUMBER_TO_CONST.put(32, CodecProfileLevel.AVCLevel32); + AVC_LEVEL_NUMBER_TO_CONST.put(40, CodecProfileLevel.AVCLevel4); + AVC_LEVEL_NUMBER_TO_CONST.put(41, CodecProfileLevel.AVCLevel41); + AVC_LEVEL_NUMBER_TO_CONST.put(42, CodecProfileLevel.AVCLevel42); + AVC_LEVEL_NUMBER_TO_CONST.put(50, CodecProfileLevel.AVCLevel5); + AVC_LEVEL_NUMBER_TO_CONST.put(51, CodecProfileLevel.AVCLevel51); + AVC_LEVEL_NUMBER_TO_CONST.put(52, CodecProfileLevel.AVCLevel52); + + HEVC_CODEC_STRING_TO_PROFILE_LEVEL = new HashMap<>(); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L30", CodecProfileLevel.HEVCMainTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L60", CodecProfileLevel.HEVCMainTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L63", CodecProfileLevel.HEVCMainTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L90", CodecProfileLevel.HEVCMainTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L93", CodecProfileLevel.HEVCMainTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L120", CodecProfileLevel.HEVCMainTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L123", CodecProfileLevel.HEVCMainTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L150", CodecProfileLevel.HEVCMainTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L153", CodecProfileLevel.HEVCMainTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L156", CodecProfileLevel.HEVCMainTierLevel52); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L180", CodecProfileLevel.HEVCMainTierLevel6); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L183", CodecProfileLevel.HEVCMainTierLevel61); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("L186", CodecProfileLevel.HEVCMainTierLevel62); + + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H30", CodecProfileLevel.HEVCHighTierLevel1); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H60", CodecProfileLevel.HEVCHighTierLevel2); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H63", CodecProfileLevel.HEVCHighTierLevel21); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H90", CodecProfileLevel.HEVCHighTierLevel3); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H93", CodecProfileLevel.HEVCHighTierLevel31); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H120", CodecProfileLevel.HEVCHighTierLevel4); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H123", CodecProfileLevel.HEVCHighTierLevel41); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H150", CodecProfileLevel.HEVCHighTierLevel5); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H153", CodecProfileLevel.HEVCHighTierLevel51); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H156", CodecProfileLevel.HEVCHighTierLevel52); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H180", CodecProfileLevel.HEVCHighTierLevel6); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H183", CodecProfileLevel.HEVCHighTierLevel61); + HEVC_CODEC_STRING_TO_PROFILE_LEVEL.put("H186", CodecProfileLevel.HEVCHighTierLevel62); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/Metadata.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/Metadata.java new file mode 100755 index 00000000000..fd41e5fb1f9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/Metadata.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Arrays; +import java.util.List; + +/** + * A collection of metadata entries. + */ +public final class Metadata implements Parcelable { + + /** + * A metadata entry. + */ + public interface Entry extends Parcelable {} + + private final Entry[] entries; + + /** + * @param entries The metadata entries. + */ + public Metadata(Entry... entries) { + this.entries = entries == null ? new Entry[0] : entries; + } + + /** + * @param entries The metadata entries. + */ + public Metadata(List entries) { + if (entries != null) { + this.entries = new Entry[entries.size()]; + entries.toArray(this.entries); + } else { + this.entries = new Entry[0]; + } + } + + /* package */ Metadata(Parcel in) { + entries = new Metadata.Entry[in.readInt()]; + for (int i = 0; i < entries.length; i++) { + entries[i] = in.readParcelable(Entry.class.getClassLoader()); + } + } + + /** + * Returns the number of metadata entries. + */ + public int length() { + return entries.length; + } + + /** + * Returns the entry at the specified index. + * + * @param index The index of the entry. + * @return The entry at the specified index. + */ + public Metadata.Entry get(int index) { + return entries[index]; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Metadata other = (Metadata) obj; + return Arrays.equals(entries, other.entries); + } + + @Override + public int hashCode() { + return Arrays.hashCode(entries); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(entries.length); + for (Entry entry : entries) { + dest.writeParcelable(entry, 0); + } + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public Metadata createFromParcel(Parcel in) { + return new Metadata(in); + } + + @Override + public Metadata[] newArray(int size) { + return new Metadata[0]; + } + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoder.java new file mode 100755 index 00000000000..a18afb4eed3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata; + +/** + * Decodes metadata from binary data. + */ +public interface MetadataDecoder { + + /** + * Checks whether the decoder supports a given mime type. + * + * @param mimeType A metadata mime type. + * @return Whether the mime type is supported. + */ + boolean canDecode(String mimeType); + + /** + * Decodes a metadata object from the provided binary data. + * + * @param data The raw binary data from which to decode the metadata. + * @param size The size of the input data. + * @return The decoded metadata object. + * @throws MetadataDecoderException If a problem occurred decoding the data. + */ + Metadata decode(byte[] data, int size) throws MetadataDecoderException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoderException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoderException.java new file mode 100755 index 00000000000..b28bc36d8be --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataDecoderException.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata; + +/** + * Thrown when an error occurs decoding metadata. + */ +public class MetadataDecoderException extends Exception { + + /** + * @param message The detail message for this exception. + */ + public MetadataDecoderException(String message) { + super(message); + } + + /** + * @param message The detail message for this exception. + * @param cause The cause of this exception. + */ + public MetadataDecoderException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java new file mode 100755 index 00000000000..93683cf8808 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/MetadataRenderer.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata; + +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Looper; +import android.os.Message; +import org.telegram.messenger.exoplayer2.BaseRenderer; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.nio.ByteBuffer; + +/** + * A renderer for metadata. + */ +public final class MetadataRenderer extends BaseRenderer implements Callback { + + /** + * Receives output from a {@link MetadataRenderer}. + */ + public interface Output { + + /** + * Called each time there is a metadata associated with current playback time. + * + * @param metadata The metadata. + */ + void onMetadata(Metadata metadata); + + } + + private static final int MSG_INVOKE_RENDERER = 0; + + private final MetadataDecoder metadataDecoder; + private final Output output; + private final Handler outputHandler; + private final FormatHolder formatHolder; + private final DecoderInputBuffer buffer; + + private boolean inputStreamEnded; + private long pendingMetadataTimestamp; + private Metadata pendingMetadata; + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be called. + * If the output makes use of standard Android UI components, then this should normally be the + * looper associated with the application's main thread, which can be obtained using + * {@link android.app.Activity#getMainLooper()}. Null may be passed if the output should be + * called directly on the player's internal rendering thread. + * @param metadataDecoder A decoder for the metadata. + */ + public MetadataRenderer(Output output, Looper outputLooper, MetadataDecoder metadataDecoder) { + super(C.TRACK_TYPE_METADATA); + this.output = Assertions.checkNotNull(output); + this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); + this.metadataDecoder = Assertions.checkNotNull(metadataDecoder); + formatHolder = new FormatHolder(); + buffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + } + + @Override + public int supportsFormat(Format format) { + return metadataDecoder.canDecode(format.sampleMimeType) ? FORMAT_HANDLED + : FORMAT_UNSUPPORTED_TYPE; + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + pendingMetadata = null; + inputStreamEnded = false; + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (!inputStreamEnded && pendingMetadata == null) { + buffer.clear(); + int result = readSource(formatHolder, buffer); + if (result == C.RESULT_BUFFER_READ) { + if (buffer.isEndOfStream()) { + inputStreamEnded = true; + } else { + pendingMetadataTimestamp = buffer.timeUs; + try { + buffer.flip(); + ByteBuffer bufferData = buffer.data; + pendingMetadata = metadataDecoder.decode(bufferData.array(), bufferData.limit()); + } catch (MetadataDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + } + } + + if (pendingMetadata != null && pendingMetadataTimestamp <= positionUs) { + invokeRenderer(pendingMetadata); + pendingMetadata = null; + } + } + + @Override + protected void onDisabled() { + pendingMetadata = null; + super.onDisabled(); + } + + @Override + public boolean isEnded() { + return inputStreamEnded; + } + + @Override + public boolean isReady() { + return true; + } + + private void invokeRenderer(Metadata metadata) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_INVOKE_RENDERER, metadata).sendToTarget(); + } else { + invokeRendererInternal(metadata); + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_INVOKE_RENDERER: + invokeRendererInternal((Metadata) msg.obj); + return true; + } + return false; + } + + private void invokeRendererInternal(Metadata metadata) { + output.onMetadata(metadata); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/ApicFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/ApicFrame.java new file mode 100755 index 00000000000..a6b7f4ae928 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/ApicFrame.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * APIC (Attached Picture) ID3 frame. + */ +public final class ApicFrame extends Id3Frame { + + public static final String ID = "APIC"; + + public final String mimeType; + public final String description; + public final int pictureType; + public final byte[] pictureData; + + public ApicFrame(String mimeType, String description, int pictureType, byte[] pictureData) { + super(ID); + this.mimeType = mimeType; + this.description = description; + this.pictureType = pictureType; + this.pictureData = pictureData; + } + + /* package */ ApicFrame(Parcel in) { + super(ID); + mimeType = in.readString(); + description = in.readString(); + pictureType = in.readInt(); + pictureData = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + ApicFrame other = (ApicFrame) obj; + return pictureType == other.pictureType && Util.areEqual(mimeType, other.mimeType) + && Util.areEqual(description, other.description) + && Arrays.equals(pictureData, other.pictureData); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + pictureType; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + Arrays.hashCode(pictureData); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mimeType); + dest.writeString(description); + dest.writeInt(pictureType); + dest.writeByteArray(pictureData); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public ApicFrame createFromParcel(Parcel in) { + return new ApicFrame(in); + } + + @Override + public ApicFrame[] newArray(int size) { + return new ApicFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/BinaryFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/BinaryFrame.java new file mode 100755 index 00000000000..666f6307b8d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/BinaryFrame.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import java.util.Arrays; + +/** + * Binary ID3 frame. + */ +public final class BinaryFrame extends Id3Frame { + + public final byte[] data; + + public BinaryFrame(String id, byte[] data) { + super(id); + this.data = data; + } + + /* package */ BinaryFrame(Parcel in) { + super(in.readString()); + data = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BinaryFrame other = (BinaryFrame) obj; + return id.equals(other.id) && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeByteArray(data); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public BinaryFrame createFromParcel(Parcel in) { + return new BinaryFrame(in); + } + + @Override + public BinaryFrame[] newArray(int size) { + return new BinaryFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/CommentFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/CommentFrame.java new file mode 100755 index 00000000000..ee89f55b9d8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/CommentFrame.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Comment ID3 frame. + */ +public final class CommentFrame extends Id3Frame { + + public static final String ID = "COMM"; + + public final String language; + public final String description; + public final String text; + + public CommentFrame(String language, String description, String text) { + super(ID); + this.language = language; + this.description = description; + this.text = text; + } + + /* package */ CommentFrame(Parcel in) { + super(ID); + language = in.readString(); + description = in.readString(); + text = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + CommentFrame other = (CommentFrame) obj; + return Util.areEqual(description, other.description) && Util.areEqual(language, other.language) + && Util.areEqual(text, other.text); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (language != null ? language.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (text != null ? text.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(language); + dest.writeString(text); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public CommentFrame createFromParcel(Parcel in) { + return new CommentFrame(in); + } + + @Override + public CommentFrame[] newArray(int size) { + return new CommentFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/GeobFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/GeobFrame.java new file mode 100755 index 00000000000..73b9ec7dbbb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/GeobFrame.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * GEOB (General Encapsulated Object) ID3 frame. + */ +public final class GeobFrame extends Id3Frame { + + public static final String ID = "GEOB"; + + public final String mimeType; + public final String filename; + public final String description; + public final byte[] data; + + public GeobFrame(String mimeType, String filename, String description, byte[] data) { + super(ID); + this.mimeType = mimeType; + this.filename = filename; + this.description = description; + this.data = data; + } + + /* package */ GeobFrame(Parcel in) { + super(ID); + mimeType = in.readString(); + filename = in.readString(); + description = in.readString(); + data = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + GeobFrame other = (GeobFrame) obj; + return Util.areEqual(mimeType, other.mimeType) && Util.areEqual(filename, other.filename) + && Util.areEqual(description, other.description) && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mimeType != null ? mimeType.hashCode() : 0); + result = 31 * result + (filename != null ? filename.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + Arrays.hashCode(data); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mimeType); + dest.writeString(filename); + dest.writeString(description); + dest.writeByteArray(data); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public GeobFrame createFromParcel(Parcel in) { + return new GeobFrame(in); + } + + @Override + public GeobFrame[] newArray(int size) { + return new GeobFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java new file mode 100755 index 00000000000..80b9d2bbe40 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Decoder.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.util.Log; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.MetadataDecoder; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +/** + * Decodes ID3 tags. + */ +public final class Id3Decoder implements MetadataDecoder { + + private static final String TAG = "Id3Decoder"; + + /** + * The first three bytes of a well formed ID3 tag header. + */ + public static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); + /** + * Length of an ID3 tag header. + */ + public static final int ID3_HEADER_LENGTH = 10; + + private static final int ID3_TEXT_ENCODING_ISO_8859_1 = 0; + private static final int ID3_TEXT_ENCODING_UTF_16 = 1; + private static final int ID3_TEXT_ENCODING_UTF_16BE = 2; + private static final int ID3_TEXT_ENCODING_UTF_8 = 3; + + @Override + public boolean canDecode(String mimeType) { + return mimeType.equals(MimeTypes.APPLICATION_ID3); + } + + @Override + public Metadata decode(byte[] data, int size) { + List id3Frames = new ArrayList<>(); + ParsableByteArray id3Data = new ParsableByteArray(data, size); + + Id3Header id3Header = decodeHeader(id3Data); + if (id3Header == null) { + return null; + } + + int startPosition = id3Data.getPosition(); + int framesSize = id3Header.framesSize; + if (id3Header.isUnsynchronized) { + framesSize = removeUnsynchronization(id3Data, id3Header.framesSize); + } + id3Data.setLimit(startPosition + framesSize); + + boolean unsignedIntFrameSizeHack = false; + if (id3Header.majorVersion == 4) { + if (!validateV4Frames(id3Data, false)) { + if (validateV4Frames(id3Data, true)) { + unsignedIntFrameSizeHack = true; + } else { + Log.w(TAG, "Failed to validate V4 ID3 tag"); + return null; + } + } + } + + int frameHeaderSize = id3Header.majorVersion == 2 ? 6 : 10; + while (id3Data.bytesLeft() >= frameHeaderSize) { + Id3Frame frame = decodeFrame(id3Header.majorVersion, id3Data, unsignedIntFrameSizeHack); + if (frame != null) { + id3Frames.add(frame); + } + } + + return new Metadata(id3Frames); + } + + /** + * @param data A {@link ParsableByteArray} from which the header should be read. + * @return The parsed header, or null if the ID3 tag is unsupported. + */ + private static Id3Header decodeHeader(ParsableByteArray data) { + if (data.bytesLeft() < ID3_HEADER_LENGTH) { + Log.w(TAG, "Data too short to be an ID3 tag"); + return null; + } + + int id = data.readUnsignedInt24(); + if (id != ID3_TAG) { + Log.w(TAG, "Unexpected first three bytes of ID3 tag header: " + id); + return null; + } + + int majorVersion = data.readUnsignedByte(); + data.skipBytes(1); // Skip minor version. + int flags = data.readUnsignedByte(); + int framesSize = data.readSynchSafeInt(); + + if (majorVersion == 2) { + boolean isCompressed = (flags & 0x40) != 0; + if (isCompressed) { + Log.w(TAG, "Skipped ID3 tag with majorVersion=2 and undefined compression scheme"); + return null; + } + } else if (majorVersion == 3) { + boolean hasExtendedHeader = (flags & 0x40) != 0; + if (hasExtendedHeader) { + int extendedHeaderSize = data.readInt(); // Size excluding size field. + data.skipBytes(extendedHeaderSize); + framesSize -= (extendedHeaderSize + 4); + } + } else if (majorVersion == 4) { + boolean hasExtendedHeader = (flags & 0x40) != 0; + if (hasExtendedHeader) { + int extendedHeaderSize = data.readSynchSafeInt(); // Size including size field. + data.skipBytes(extendedHeaderSize - 4); + framesSize -= extendedHeaderSize; + } + boolean hasFooter = (flags & 0x10) != 0; + if (hasFooter) { + framesSize -= 10; + } + } else { + Log.w(TAG, "Skipped ID3 tag with unsupported majorVersion=" + majorVersion); + return null; + } + + // isUnsynchronized is advisory only in version 4. Frame level flags are used instead. + boolean isUnsynchronized = majorVersion < 4 && (flags & 0x80) != 0; + return new Id3Header(majorVersion, isUnsynchronized, framesSize); + } + + private static boolean validateV4Frames(ParsableByteArray id3Data, + boolean unsignedIntFrameSizeHack) { + int startPosition = id3Data.getPosition(); + try { + while (id3Data.bytesLeft() >= 10) { + int id = id3Data.readInt(); + int frameSize = id3Data.readUnsignedIntToInt(); + int flags = id3Data.readUnsignedShort(); + if (id == 0 && frameSize == 0 && flags == 0) { + return true; + } else { + if (!unsignedIntFrameSizeHack) { + // Parse the data size as a synchsafe integer, as per the spec. + if ((frameSize & 0x808080L) != 0) { + return false; + } + frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) + | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); + } + int minimumFrameSize = 0; + if ((flags & 0x0040) != 0 /* hasGroupIdentifier */) { + minimumFrameSize++; + } + if ((flags & 0x0001) != 0 /* hasDataLength */) { + minimumFrameSize += 4; + } + if (frameSize < minimumFrameSize) { + return false; + } + if (id3Data.bytesLeft() < frameSize) { + return false; + } + id3Data.skipBytes(frameSize); // flags + } + } + return true; + } finally { + id3Data.setPosition(startPosition); + } + } + + private static Id3Frame decodeFrame(int majorVersion, ParsableByteArray id3Data, + boolean unsignedIntFrameSizeHack) { + int frameId0 = id3Data.readUnsignedByte(); + int frameId1 = id3Data.readUnsignedByte(); + int frameId2 = id3Data.readUnsignedByte(); + int frameId3 = majorVersion >= 3 ? id3Data.readUnsignedByte() : 0; + + int frameSize; + if (majorVersion == 4) { + frameSize = id3Data.readUnsignedIntToInt(); + if (!unsignedIntFrameSizeHack) { + frameSize = (frameSize & 0xFF) | (((frameSize >> 8) & 0xFF) << 7) + | (((frameSize >> 16) & 0xFF) << 14) | (((frameSize >> 24) & 0xFF) << 21); + } + } else if (majorVersion == 3) { + frameSize = id3Data.readUnsignedIntToInt(); + } else /* id3Header.majorVersion == 2 */ { + frameSize = id3Data.readUnsignedInt24(); + } + + int flags = majorVersion >= 3 ? id3Data.readUnsignedShort() : 0; + if (frameId0 == 0 && frameId1 == 0 && frameId2 == 0 && frameId3 == 0 && frameSize == 0 + && flags == 0) { + // We must be reading zero padding at the end of the tag. + id3Data.setPosition(id3Data.limit()); + return null; + } + + int nextFramePosition = id3Data.getPosition() + frameSize; + if (nextFramePosition > id3Data.limit()) { + Log.w(TAG, "Frame size exceeds remaining tag data"); + id3Data.setPosition(id3Data.limit()); + return null; + } + + // Frame flags. + boolean isCompressed = false; + boolean isEncrypted = false; + boolean isUnsynchronized = false; + boolean hasDataLength = false; + boolean hasGroupIdentifier = false; + if (majorVersion == 3) { + isCompressed = (flags & 0x0080) != 0; + isEncrypted = (flags & 0x0040) != 0; + hasGroupIdentifier = (flags & 0x0020) != 0; + hasDataLength = isCompressed; + } else if (majorVersion == 4) { + hasGroupIdentifier = (flags & 0x0040) != 0; + isCompressed = (flags & 0x0008) != 0; + isEncrypted = (flags & 0x0004) != 0; + isUnsynchronized = (flags & 0x0002) != 0; + hasDataLength = (flags & 0x0001) != 0; + } + + if (isCompressed || isEncrypted) { + Log.w(TAG, "Skipping unsupported compressed or encrypted frame"); + id3Data.setPosition(nextFramePosition); + return null; + } + + if (hasGroupIdentifier) { + frameSize--; + id3Data.skipBytes(1); + } + if (hasDataLength) { + frameSize -= 4; + id3Data.skipBytes(4); + } + if (isUnsynchronized) { + frameSize = removeUnsynchronization(id3Data, frameSize); + } + + try { + Id3Frame frame; + if (frameId0 == 'T' && frameId1 == 'X' && frameId2 == 'X' + && (majorVersion == 2 || frameId3 == 'X')) { + frame = decodeTxxxFrame(id3Data, frameSize); + } else if (frameId0 == 'P' && frameId1 == 'R' && frameId2 == 'I' && frameId3 == 'V') { + frame = decodePrivFrame(id3Data, frameSize); + } else if (frameId0 == 'G' && frameId1 == 'E' && frameId2 == 'O' + && (frameId3 == 'B' || majorVersion == 2)) { + frame = decodeGeobFrame(id3Data, frameSize); + } else if (majorVersion == 2 ? (frameId0 == 'P' && frameId1 == 'I' && frameId2 == 'C') + : (frameId0 == 'A' && frameId1 == 'P' && frameId2 == 'I' && frameId3 == 'C')) { + frame = decodeApicFrame(id3Data, frameSize, majorVersion); + } else if (frameId0 == 'T') { + String id = majorVersion == 2 + ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + frame = decodeTextInformationFrame(id3Data, frameSize, id); + } else if (frameId0 == 'C' && frameId1 == 'O' && frameId2 == 'M' + && (frameId3 == 'M' || majorVersion == 2)) { + frame = decodeCommentFrame(id3Data, frameSize); + } else { + String id = majorVersion == 2 + ? String.format(Locale.US, "%c%c%c", frameId0, frameId1, frameId2) + : String.format(Locale.US, "%c%c%c%c", frameId0, frameId1, frameId2, frameId3); + frame = decodeBinaryFrame(id3Data, frameSize, id); + } + return frame; + } catch (UnsupportedEncodingException e) { + Log.w(TAG, "Unsupported character encoding"); + return null; + } finally { + id3Data.setPosition(nextFramePosition); + } + } + + private static TxxxFrame decodeTxxxFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + int valueStartIndex = descriptionEndIndex + delimiterLength(encoding); + int valueEndIndex = indexOfEos(data, valueStartIndex, encoding); + String value = new String(data, valueStartIndex, valueEndIndex - valueStartIndex, charset); + + return new TxxxFrame(description, value); + } + + private static PrivFrame decodePrivFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + byte[] data = new byte[frameSize]; + id3Data.readBytes(data, 0, frameSize); + + int ownerEndIndex = indexOfZeroByte(data, 0); + String owner = new String(data, 0, ownerEndIndex, "ISO-8859-1"); + + int privateDataStartIndex = ownerEndIndex + 1; + byte[] privateData = Arrays.copyOfRange(data, privateDataStartIndex, data.length); + + return new PrivFrame(owner, privateData); + } + + private static GeobFrame decodeGeobFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int mimeTypeEndIndex = indexOfZeroByte(data, 0); + String mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1"); + + int filenameStartIndex = mimeTypeEndIndex + 1; + int filenameEndIndex = indexOfEos(data, filenameStartIndex, encoding); + String filename = new String(data, filenameStartIndex, filenameEndIndex - filenameStartIndex, + charset); + + int descriptionStartIndex = filenameEndIndex + delimiterLength(encoding); + int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + String description = new String(data, descriptionStartIndex, + descriptionEndIndex - descriptionStartIndex, charset); + + int objectDataStartIndex = descriptionEndIndex + delimiterLength(encoding); + byte[] objectData = Arrays.copyOfRange(data, objectDataStartIndex, data.length); + + return new GeobFrame(mimeType, filename, description, objectData); + } + + private static ApicFrame decodeApicFrame(ParsableByteArray id3Data, int frameSize, + int majorVersion) throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + String mimeType; + int mimeTypeEndIndex; + if (majorVersion == 2) { + mimeTypeEndIndex = 2; + mimeType = "image/" + new String(data, 0, 3, "ISO-8859-1").toLowerCase(); + if (mimeType.equals("image/jpg")) { + mimeType = "image/jpeg"; + } + } else { + mimeTypeEndIndex = indexOfZeroByte(data, 0); + mimeType = new String(data, 0, mimeTypeEndIndex, "ISO-8859-1").toLowerCase(); + if (mimeType.indexOf('/') == -1) { + mimeType = "image/" + mimeType; + } + } + + int pictureType = data[mimeTypeEndIndex + 1] & 0xFF; + + int descriptionStartIndex = mimeTypeEndIndex + 2; + int descriptionEndIndex = indexOfEos(data, descriptionStartIndex, encoding); + String description = new String(data, descriptionStartIndex, + descriptionEndIndex - descriptionStartIndex, charset); + + int pictureDataStartIndex = descriptionEndIndex + delimiterLength(encoding); + byte[] pictureData = Arrays.copyOfRange(data, pictureDataStartIndex, data.length); + + return new ApicFrame(mimeType, description, pictureType, pictureData); + } + + private static CommentFrame decodeCommentFrame(ParsableByteArray id3Data, int frameSize) + throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[3]; + id3Data.readBytes(data, 0, 3); + String language = new String(data, 0, 3); + + data = new byte[frameSize - 4]; + id3Data.readBytes(data, 0, frameSize - 4); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + int textStartIndex = descriptionEndIndex + delimiterLength(encoding); + int textEndIndex = indexOfEos(data, textStartIndex, encoding); + String text = new String(data, textStartIndex, textEndIndex - textStartIndex, charset); + + return new CommentFrame(language, description, text); + } + + private static TextInformationFrame decodeTextInformationFrame(ParsableByteArray id3Data, + int frameSize, String id) throws UnsupportedEncodingException { + int encoding = id3Data.readUnsignedByte(); + String charset = getCharsetName(encoding); + + byte[] data = new byte[frameSize - 1]; + id3Data.readBytes(data, 0, frameSize - 1); + + int descriptionEndIndex = indexOfEos(data, 0, encoding); + String description = new String(data, 0, descriptionEndIndex, charset); + + return new TextInformationFrame(id, description); + } + + private static BinaryFrame decodeBinaryFrame(ParsableByteArray id3Data, int frameSize, + String id) { + byte[] frame = new byte[frameSize]; + id3Data.readBytes(frame, 0, frameSize); + + return new BinaryFrame(id, frame); + } + + /** + * Performs in-place removal of unsynchronization for {@code length} bytes starting from + * {@link ParsableByteArray#getPosition()} + * + * @param data Contains the data to be processed. + * @param length The length of the data to be processed. + * @return The length of the data after processing. + */ + private static int removeUnsynchronization(ParsableByteArray data, int length) { + byte[] bytes = data.data; + for (int i = data.getPosition(); i + 1 < length; i++) { + if ((bytes[i] & 0xFF) == 0xFF && bytes[i + 1] == 0x00) { + System.arraycopy(bytes, i + 2, bytes, i + 1, length - i - 2); + length--; + } + } + return length; + } + + /** + * Maps encoding byte from ID3v2 frame to a Charset. + * @param encodingByte The value of encoding byte from ID3v2 frame. + * @return Charset name. + */ + private static String getCharsetName(int encodingByte) { + switch (encodingByte) { + case ID3_TEXT_ENCODING_ISO_8859_1: + return "ISO-8859-1"; + case ID3_TEXT_ENCODING_UTF_16: + return "UTF-16"; + case ID3_TEXT_ENCODING_UTF_16BE: + return "UTF-16BE"; + case ID3_TEXT_ENCODING_UTF_8: + return "UTF-8"; + default: + return "ISO-8859-1"; + } + } + + private static int indexOfEos(byte[] data, int fromIndex, int encoding) { + int terminationPos = indexOfZeroByte(data, fromIndex); + + // For single byte encoding charsets, we're done. + if (encoding == ID3_TEXT_ENCODING_ISO_8859_1 || encoding == ID3_TEXT_ENCODING_UTF_8) { + return terminationPos; + } + + // Otherwise ensure an even index and look for a second zero byte. + while (terminationPos < data.length - 1) { + if (terminationPos % 2 == 0 && data[terminationPos + 1] == (byte) 0) { + return terminationPos; + } + terminationPos = indexOfZeroByte(data, terminationPos + 1); + } + + return data.length; + } + + private static int indexOfZeroByte(byte[] data, int fromIndex) { + for (int i = fromIndex; i < data.length; i++) { + if (data[i] == (byte) 0) { + return i; + } + } + return data.length; + } + + private static int delimiterLength(int encodingByte) { + return (encodingByte == ID3_TEXT_ENCODING_ISO_8859_1 || encodingByte == ID3_TEXT_ENCODING_UTF_8) + ? 1 : 2; + } + + private static final class Id3Header { + + private final int majorVersion; + private final boolean isUnsynchronized; + private final int framesSize; + + public Id3Header(int majorVersion, boolean isUnsynchronized, int framesSize) { + this.majorVersion = majorVersion; + this.isUnsynchronized = isUnsynchronized; + this.framesSize = framesSize; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Frame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Frame.java new file mode 100755 index 00000000000..f2559f12fa2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/Id3Frame.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * Base class for ID3 frames. + */ +public abstract class Id3Frame implements Metadata.Entry { + + /** + * The frame ID. + */ + public final String id; + + public Id3Frame(String id) { + this.id = Assertions.checkNotNull(id); + } + + @Override + public int describeContents() { + return 0; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/PrivFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/PrivFrame.java new file mode 100755 index 00000000000..52168f938ac --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/PrivFrame.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * PRIV (Private) ID3 frame. + */ +public final class PrivFrame extends Id3Frame { + + public static final String ID = "PRIV"; + + public final String owner; + public final byte[] privateData; + + public PrivFrame(String owner, byte[] privateData) { + super(ID); + this.owner = owner; + this.privateData = privateData; + } + + /* package */ PrivFrame(Parcel in) { + super(ID); + owner = in.readString(); + privateData = in.createByteArray(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + PrivFrame other = (PrivFrame) obj; + return Util.areEqual(owner, other.owner) && Arrays.equals(privateData, other.privateData); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (owner != null ? owner.hashCode() : 0); + result = 31 * result + Arrays.hashCode(privateData); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(owner); + dest.writeByteArray(privateData); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public PrivFrame createFromParcel(Parcel in) { + return new PrivFrame(in); + } + + @Override + public PrivFrame[] newArray(int size) { + return new PrivFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TextInformationFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TextInformationFrame.java new file mode 100755 index 00000000000..cf421ae3cb6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TextInformationFrame.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Text information ("T000" - "TZZZ", excluding "TXXX") ID3 frame. + */ +public final class TextInformationFrame extends Id3Frame { + + public final String description; + + public TextInformationFrame(String id, String description) { + super(id); + this.description = description; + } + + /* package */ TextInformationFrame(Parcel in) { + super(in.readString()); + description = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TextInformationFrame other = (TextInformationFrame) obj; + return id.equals(other.id) && Util.areEqual(description, other.description); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + id.hashCode(); + result = 31 * result + (description != null ? description.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeString(description); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public TextInformationFrame createFromParcel(Parcel in) { + return new TextInformationFrame(in); + } + + @Override + public TextInformationFrame[] newArray(int size) { + return new TextInformationFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TxxxFrame.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TxxxFrame.java new file mode 100755 index 00000000000..ce4be0de05b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/id3/TxxxFrame.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.id3; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * TXXX (User defined text information) ID3 frame. + */ +public final class TxxxFrame extends Id3Frame { + + public static final String ID = "TXXX"; + + public final String description; + public final String value; + + public TxxxFrame(String description, String value) { + super(ID); + this.description = description; + this.value = value; + } + + /* package */ TxxxFrame(Parcel in) { + super(ID); + description = in.readString(); + value = in.readString(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TxxxFrame other = (TxxxFrame) obj; + return Util.areEqual(description, other.description) && Util.areEqual(value, other.value); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(description); + dest.writeString(value); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public TxxxFrame createFromParcel(Parcel in) { + return new TxxxFrame(in); + } + + @Override + public TxxxFrame[] newArray(int size) { + return new TxxxFrame[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/PrivateCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/PrivateCommand.java new file mode 100755 index 00000000000..eb820f1b035 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/PrivateCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Represents a private command as defined in SCTE35, Section 9.3.6. + */ +public final class PrivateCommand extends SpliceCommand { + + public final long ptsAdjustment; + public final long identifier; + + public final byte[] commandBytes; + + private PrivateCommand(long identifier, byte[] commandBytes, long ptsAdjustment) { + this.ptsAdjustment = ptsAdjustment; + this.identifier = identifier; + this.commandBytes = commandBytes; + } + + private PrivateCommand(Parcel in) { + ptsAdjustment = in.readLong(); + identifier = in.readLong(); + commandBytes = new byte[in.readInt()]; + in.readByteArray(commandBytes); + } + + /* package */ static PrivateCommand parseFromSection(ParsableByteArray sectionData, + int commandLength, long ptsAdjustment) { + long identifier = sectionData.readUnsignedInt(); + byte[] privateBytes = new byte[commandLength - 4 /* identifier size */]; + sectionData.readBytes(privateBytes, 0, privateBytes.length); + return new PrivateCommand(identifier, privateBytes, ptsAdjustment); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsAdjustment); + dest.writeLong(identifier); + dest.writeInt(commandBytes.length); + dest.writeByteArray(commandBytes); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public PrivateCommand createFromParcel(Parcel in) { + return new PrivateCommand(in); + } + + @Override + public PrivateCommand[] newArray(int size) { + return new PrivateCommand[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceCommand.java new file mode 100755 index 00000000000..239ef833916 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceCommand.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import org.telegram.messenger.exoplayer2.metadata.Metadata; + +/** + * Superclass for SCTE35 splice commands. + */ +public abstract class SpliceCommand implements Metadata.Entry { + + @Override + public int describeContents() { + return 0; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInfoDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInfoDecoder.java new file mode 100755 index 00000000000..208abb928af --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInfoDecoder.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.metadata.Metadata; +import org.telegram.messenger.exoplayer2.metadata.MetadataDecoder; +import org.telegram.messenger.exoplayer2.metadata.MetadataDecoderException; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableBitArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Decodes splice info sections and produces splice commands. + */ +public final class SpliceInfoDecoder implements MetadataDecoder { + + private static final int TYPE_SPLICE_NULL = 0x00; + private static final int TYPE_SPLICE_SCHEDULE = 0x04; + private static final int TYPE_SPLICE_INSERT = 0x05; + private static final int TYPE_TIME_SIGNAL = 0x06; + private static final int TYPE_PRIVATE_COMMAND = 0xFF; + + private final ParsableByteArray sectionData; + private final ParsableBitArray sectionHeader; + + public SpliceInfoDecoder() { + sectionData = new ParsableByteArray(); + sectionHeader = new ParsableBitArray(); + } + + @Override + public boolean canDecode(String mimeType) { + return TextUtils.equals(mimeType, MimeTypes.APPLICATION_SCTE35); + } + + @Override + public Metadata decode(byte[] data, int size) throws MetadataDecoderException { + sectionData.reset(data, size); + sectionHeader.reset(data, size); + // table_id(8), section_syntax_indicator(1), private_indicator(1), reserved(2), + // section_length(12), protocol_version(8), encrypted_packet(1), encryption_algorithm(6). + sectionHeader.skipBits(39); + long ptsAdjustment = sectionHeader.readBits(1); + ptsAdjustment = (ptsAdjustment << 32) | sectionHeader.readBits(32); + // cw_index(8), tier(12). + sectionHeader.skipBits(20); + int spliceCommandLength = sectionHeader.readBits(12); + int spliceCommandType = sectionHeader.readBits(8); + SpliceCommand command = null; + // Go to the start of the command by skipping all fields up to command_type. + sectionData.skipBytes(14); + switch (spliceCommandType) { + case TYPE_SPLICE_NULL: + command = new SpliceNullCommand(); + break; + case TYPE_SPLICE_SCHEDULE: + command = SpliceScheduleCommand.parseFromSection(sectionData); + break; + case TYPE_SPLICE_INSERT: + command = SpliceInsertCommand.parseFromSection(sectionData, ptsAdjustment); + break; + case TYPE_TIME_SIGNAL: + command = TimeSignalCommand.parseFromSection(sectionData, ptsAdjustment); + break; + case TYPE_PRIVATE_COMMAND: + command = PrivateCommand.parseFromSection(sectionData, spliceCommandLength, ptsAdjustment); + break; + } + return command == null ? new Metadata() : new Metadata(command); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInsertCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInsertCommand.java new file mode 100755 index 00000000000..d67c564d4d8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceInsertCommand.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice insert command defined in SCTE35, Section 9.3.3. + */ +public final class SpliceInsertCommand extends SpliceCommand { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final boolean spliceImmediateFlag; + public final long programSplicePts; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private SpliceInsertCommand(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, boolean spliceImmediateFlag, + long programSplicePts, List componentSpliceList, boolean autoReturn, + long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.spliceImmediateFlag = spliceImmediateFlag; + this.programSplicePts = programSplicePts; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private SpliceInsertCommand(Parcel in) { + spliceEventId = in.readLong(); + spliceEventCancelIndicator = in.readByte() == 1; + outOfNetworkIndicator = in.readByte() == 1; + programSpliceFlag = in.readByte() == 1; + spliceImmediateFlag = in.readByte() == 1; + programSplicePts = in.readLong(); + int componentSpliceListSize = in.readInt(); + List componentSpliceList = new ArrayList<>(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + autoReturn = in.readByte() == 1; + breakDuration = in.readLong(); + uniqueProgramId = in.readInt(); + availNum = in.readInt(); + availsExpected = in.readInt(); + } + + /* package */ static SpliceInsertCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + boolean spliceImmediateFlag = false; + long programSplicePts = C.TIME_UNSET; + ArrayList componentSplices = new ArrayList<>(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + spliceImmediateFlag = (headerByte & 0x10) != 0; + if (programSpliceFlag && !spliceImmediateFlag) { + programSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentSplicePts = C.TIME_UNSET; + if (!spliceImmediateFlag) { + componentSplicePts = TimeSignalCommand.parseSpliceTime(sectionData, ptsAdjustment); + } + componentSplices.add(new ComponentSplice(componentTag, componentSplicePts)); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new SpliceInsertCommand(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, spliceImmediateFlag, programSplicePts, componentSplices, autoReturn, + duration, uniqueProgramId, availNum, availsExpected); + } + + /** + * Holds splicing information for specific splice insert command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long componentSplicePts; + + private ComponentSplice(int componentTag, long componentSplicePts) { + this.componentTag = componentTag; + this.componentSplicePts = componentSplicePts; + } + + public void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(componentSplicePts); + } + + public static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong()); + } + + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + dest.writeByte((byte) (spliceImmediateFlag ? 1 : 0)); + dest.writeLong(programSplicePts); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceInsertCommand createFromParcel(Parcel in) { + return new SpliceInsertCommand(in); + } + + @Override + public SpliceInsertCommand[] newArray(int size) { + return new SpliceInsertCommand[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceNullCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceNullCommand.java new file mode 100755 index 00000000000..7fedf85c8bb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceNullCommand.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.os.Parcel; + +/** + * Represents a splice null command as defined in SCTE35, Section 9.3.1. + */ +public final class SpliceNullCommand extends SpliceCommand { + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + // Do nothing. + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public SpliceNullCommand createFromParcel(Parcel in) { + return new SpliceNullCommand(); + } + + @Override + public SpliceNullCommand[] newArray(int size) { + return new SpliceNullCommand[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceScheduleCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceScheduleCommand.java new file mode 100755 index 00000000000..01a1ee8b81e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/SpliceScheduleCommand.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import android.os.Parcelable; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents a splice schedule command as defined in SCTE35, Section 9.3.2. + */ +public final class SpliceScheduleCommand extends SpliceCommand { + + /** + * Represents a splice event as contained in a {@link SpliceScheduleCommand}. + */ + public static final class Event { + + public final long spliceEventId; + public final boolean spliceEventCancelIndicator; + public final boolean outOfNetworkIndicator; + public final boolean programSpliceFlag; + public final long utcSpliceTime; + public final List componentSpliceList; + public final boolean autoReturn; + public final long breakDuration; + public final int uniqueProgramId; + public final int availNum; + public final int availsExpected; + + private Event(long spliceEventId, boolean spliceEventCancelIndicator, + boolean outOfNetworkIndicator, boolean programSpliceFlag, + List componentSpliceList, long utcSpliceTime, boolean autoReturn, + long breakDuration, int uniqueProgramId, int availNum, int availsExpected) { + this.spliceEventId = spliceEventId; + this.spliceEventCancelIndicator = spliceEventCancelIndicator; + this.outOfNetworkIndicator = outOfNetworkIndicator; + this.programSpliceFlag = programSpliceFlag; + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = utcSpliceTime; + this.autoReturn = autoReturn; + this.breakDuration = breakDuration; + this.uniqueProgramId = uniqueProgramId; + this.availNum = availNum; + this.availsExpected = availsExpected; + } + + private Event(Parcel in) { + this.spliceEventId = in.readLong(); + this.spliceEventCancelIndicator = in.readByte() == 1; + this.outOfNetworkIndicator = in.readByte() == 1; + this.programSpliceFlag = in.readByte() == 1; + int componentSpliceListLength = in.readInt(); + ArrayList componentSpliceList = new ArrayList<>(componentSpliceListLength); + for (int i = 0; i < componentSpliceListLength; i++) { + componentSpliceList.add(ComponentSplice.createFromParcel(in)); + } + this.componentSpliceList = Collections.unmodifiableList(componentSpliceList); + this.utcSpliceTime = in.readLong(); + this.autoReturn = in.readByte() == 1; + this.breakDuration = in.readLong(); + this.uniqueProgramId = in.readInt(); + this.availNum = in.readInt(); + this.availsExpected = in.readInt(); + } + + private static Event parseFromSection(ParsableByteArray sectionData) { + long spliceEventId = sectionData.readUnsignedInt(); + // splice_event_cancel_indicator(1), reserved(7). + boolean spliceEventCancelIndicator = (sectionData.readUnsignedByte() & 0x80) != 0; + boolean outOfNetworkIndicator = false; + boolean programSpliceFlag = false; + long utcSpliceTime = C.TIME_UNSET; + ArrayList componentSplices = new ArrayList<>(); + int uniqueProgramId = 0; + int availNum = 0; + int availsExpected = 0; + boolean autoReturn = false; + long duration = C.TIME_UNSET; + if (!spliceEventCancelIndicator) { + int headerByte = sectionData.readUnsignedByte(); + outOfNetworkIndicator = (headerByte & 0x80) != 0; + programSpliceFlag = (headerByte & 0x40) != 0; + boolean durationFlag = (headerByte & 0x20) != 0; + if (programSpliceFlag) { + utcSpliceTime = sectionData.readUnsignedInt(); + } + if (!programSpliceFlag) { + int componentCount = sectionData.readUnsignedByte(); + componentSplices = new ArrayList<>(componentCount); + for (int i = 0; i < componentCount; i++) { + int componentTag = sectionData.readUnsignedByte(); + long componentUtcSpliceTime = sectionData.readUnsignedInt(); + componentSplices.add(new ComponentSplice(componentTag, componentUtcSpliceTime)); + } + } + if (durationFlag) { + long firstByte = sectionData.readUnsignedByte(); + autoReturn = (firstByte & 0x80) != 0; + duration = ((firstByte & 0x01) << 32) | sectionData.readUnsignedInt(); + } + uniqueProgramId = sectionData.readUnsignedShort(); + availNum = sectionData.readUnsignedByte(); + availsExpected = sectionData.readUnsignedByte(); + } + return new Event(spliceEventId, spliceEventCancelIndicator, outOfNetworkIndicator, + programSpliceFlag, componentSplices, utcSpliceTime, autoReturn, duration, uniqueProgramId, + availNum, availsExpected); + } + + private void writeToParcel(Parcel dest) { + dest.writeLong(spliceEventId); + dest.writeByte((byte) (spliceEventCancelIndicator ? 1 : 0)); + dest.writeByte((byte) (outOfNetworkIndicator ? 1 : 0)); + dest.writeByte((byte) (programSpliceFlag ? 1 : 0)); + int componentSpliceListSize = componentSpliceList.size(); + dest.writeInt(componentSpliceListSize); + for (int i = 0; i < componentSpliceListSize; i++) { + componentSpliceList.get(i).writeToParcel(dest); + } + dest.writeLong(utcSpliceTime); + dest.writeByte((byte) (autoReturn ? 1 : 0)); + dest.writeLong(breakDuration); + dest.writeInt(uniqueProgramId); + dest.writeInt(availNum); + dest.writeInt(availsExpected); + } + + private static Event createFromParcel(Parcel in) { + return new Event(in); + } + + } + + /** + * Holds splicing information for specific splice schedule command components. + */ + public static final class ComponentSplice { + + public final int componentTag; + public final long utcSpliceTime; + + private ComponentSplice(int componentTag, long utcSpliceTime) { + this.componentTag = componentTag; + this.utcSpliceTime = utcSpliceTime; + } + + private static ComponentSplice createFromParcel(Parcel in) { + return new ComponentSplice(in.readInt(), in.readLong()); + } + + private void writeToParcel(Parcel dest) { + dest.writeInt(componentTag); + dest.writeLong(utcSpliceTime); + } + + } + + public final List events; + + private SpliceScheduleCommand(List events) { + this.events = Collections.unmodifiableList(events); + } + + private SpliceScheduleCommand(Parcel in) { + int eventsSize = in.readInt(); + ArrayList events = new ArrayList<>(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.add(Event.createFromParcel(in)); + } + this.events = Collections.unmodifiableList(events); + } + + /* package */ static SpliceScheduleCommand parseFromSection(ParsableByteArray sectionData) { + int spliceCount = sectionData.readUnsignedByte(); + ArrayList events = new ArrayList<>(spliceCount); + for (int i = 0; i < spliceCount; i++) { + events.add(Event.parseFromSection(sectionData)); + } + return new SpliceScheduleCommand(events); + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + int eventsSize = events.size(); + dest.writeInt(eventsSize); + for (int i = 0; i < eventsSize; i++) { + events.get(i).writeToParcel(dest); + } + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + + @Override + public SpliceScheduleCommand createFromParcel(Parcel in) { + return new SpliceScheduleCommand(in); + } + + @Override + public SpliceScheduleCommand[] newArray(int size) { + return new SpliceScheduleCommand[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/TimeSignalCommand.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/TimeSignalCommand.java new file mode 100755 index 00000000000..349266581de --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/metadata/scte35/TimeSignalCommand.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.metadata.scte35; + +import android.os.Parcel; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * Represents a time signal command as defined in SCTE35, Section 9.3.4. + */ +public final class TimeSignalCommand extends SpliceCommand { + + public final long ptsTime; + + private TimeSignalCommand(long ptsTime) { + this.ptsTime = ptsTime; + } + + /* package */ static TimeSignalCommand parseFromSection(ParsableByteArray sectionData, + long ptsAdjustment) { + return new TimeSignalCommand(parseSpliceTime(sectionData, ptsAdjustment)); + } + + /** + * Parses pts_time from splice_time(), defined in Section 9.4.1. Returns {@link C#TIME_UNSET}, if + * time_specified_flag is false. + * + * @param sectionData The section data from which the pts_time is parsed. + * @param ptsAdjustment The pts adjustment provided by the splice info section header. + * @return The pts_time defined by splice_time(), or {@link C#TIME_UNSET}, if time_specified_flag + * is false. + */ + /* package */ static long parseSpliceTime(ParsableByteArray sectionData, long ptsAdjustment) { + long firstByte = sectionData.readUnsignedByte(); + long ptsTime = C.TIME_UNSET; + if ((firstByte & 0x80) != 0 /* time_specified_flag */) { + // See SCTE35 9.2.1 for more information about pts adjustment. + ptsTime = (firstByte & 0x01) << 32 | sectionData.readUnsignedInt(); + ptsTime += ptsAdjustment; + ptsTime &= 0x1FFFFFFFFL; + } + return ptsTime; + } + + // Parcelable implementation. + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(ptsTime); + } + + public static final Creator CREATOR = + new Creator() { + + @Override + public TimeSignalCommand createFromParcel(Parcel in) { + return new TimeSignalCommand(in.readLong()); + } + + @Override + public TimeSignalCommand[] newArray(int size) { + return new TimeSignalCommand[size]; + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AdaptiveMediaSourceEventListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AdaptiveMediaSourceEventListener.java new file mode 100755 index 00000000000..735788c385b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/AdaptiveMediaSourceEventListener.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.os.Handler; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Interface for callbacks to be notified of adaptive {@link MediaSource} events. + */ +public interface AdaptiveMediaSourceEventListener { + + /** + * Called when a load begins. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load began. + */ + void onLoadStarted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs); + + /** + * Called when a load ends. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load ended. + * @param loadDurationMs The duration of the load. + * @param bytesLoaded The number of bytes that were loaded. + */ + void onLoadCompleted(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded); + + /** + * Called when a load is canceled. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the load was + * canceled. + * @param loadDurationMs The duration of the load up to the point at which it was canceled. + * @param bytesLoaded The number of bytes that were loaded prior to cancelation. + */ + void onLoadCanceled(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded); + + /** + * Called when a load error occurs. + *

        + * The error may or may not have resulted in the load being canceled, as indicated by the + * {@code wasCanceled} parameter. If the load was canceled, {@link #onLoadCanceled} will + * not be called in addition to this method. + * + * @param dataSpec Defines the data being loaded. + * @param dataType One of the {@link C} {@code DATA_TYPE_*} constants defining the type of data + * being loaded. + * @param trackType One of the {@link C} {@code TRACK_TYPE_*} constants if the data corresponds + * to media of a specific type. {@link C#TRACK_TYPE_UNKNOWN} otherwise. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaStartTimeMs The start time of the media being loaded, or {@link C#TIME_UNSET} if + * the load is not for media data. + * @param mediaEndTimeMs The end time of the media being loaded, or {@link C#TIME_UNSET} if the + * load is not for media data. + * @param elapsedRealtimeMs The value of {@link SystemClock#elapsedRealtime} when the error + * occurred. + * @param loadDurationMs The duration of the load up to the point at which the error occurred. + * @param bytesLoaded The number of bytes that were loaded prior to the error. + * @param error The load error. + * @param wasCanceled Whether the load was canceled as a result of the error. + */ + void onLoadError(DataSpec dataSpec, int dataType, int trackType, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long mediaStartTimeMs, + long mediaEndTimeMs, long elapsedRealtimeMs, long loadDurationMs, long bytesLoaded, + IOException error, boolean wasCanceled); + + /** + * Called when data is removed from the back of a media buffer, typically so that it can be + * re-buffered in a different format. + * + * @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param mediaStartTimeMs The start time of the media being discarded. + * @param mediaEndTimeMs The end time of the media being discarded. + */ + void onUpstreamDiscarded(int trackType, long mediaStartTimeMs, long mediaEndTimeMs); + + /** + * Called when a downstream format change occurs (i.e. when the format of the media being read + * from one or more {@link SampleStream}s provided by the source changes). + * + * @param trackType The type of the media. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param trackFormat The format of the track to which the data belongs. Null if the data does + * not belong to a track. + * @param trackSelectionReason One of the {@link C} {@code SELECTION_REASON_*} constants if the + * data belongs to a track. {@link C#SELECTION_REASON_UNKNOWN} otherwise. + * @param trackSelectionData Optional data associated with the selection of the track to which the + * data belongs. Null if the data does not belong to a track. + * @param mediaTimeMs The media time at which the change occurred. + */ + void onDownstreamFormatChanged(int trackType, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, long mediaTimeMs); + + /** + * Dispatches events to a {@link AdaptiveMediaSourceEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final AdaptiveMediaSourceEventListener listener; + private final long mediaTimeOffsetMs; + + public EventDispatcher(Handler handler, AdaptiveMediaSourceEventListener listener) { + this(handler, listener, 0); + } + + public EventDispatcher(Handler handler, AdaptiveMediaSourceEventListener listener, + long mediaTimeOffsetMs) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + this.mediaTimeOffsetMs = mediaTimeOffsetMs; + } + + public EventDispatcher copyWithMediaTimeOffsetMs(long mediaTimeOffsetMs) { + return new EventDispatcher(handler, listener, mediaTimeOffsetMs); + } + + public void loadStarted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs) { + loadStarted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs); + } + + public void loadStarted(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadStarted(dataSpec, dataType, trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs); + } + }); + } + } + + public void loadCompleted(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + loadCompleted(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + + public void loadCompleted(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadCompleted(dataSpec, dataType, trackType, trackFormat, + trackSelectionReason, trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + }); + } + } + + public void loadCanceled(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded) { + loadCanceled(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + + public void loadCanceled(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadCanceled(dataSpec, dataType, trackType, trackFormat, + trackSelectionReason, trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded); + } + }); + } + } + + public void loadError(DataSpec dataSpec, int dataType, long elapsedRealtimeMs, + long loadDurationMs, long bytesLoaded, IOException error, boolean wasCanceled) { + loadError(dataSpec, dataType, C.TRACK_TYPE_UNKNOWN, null, C.SELECTION_REASON_UNKNOWN, + null, C.TIME_UNSET, C.TIME_UNSET, elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, wasCanceled); + } + + public void loadError(final DataSpec dataSpec, final int dataType, final int trackType, + final Format trackFormat, final int trackSelectionReason, final Object trackSelectionData, + final long mediaStartTimeUs, final long mediaEndTimeUs, final long elapsedRealtimeMs, + final long loadDurationMs, final long bytesLoaded, final IOException error, + final boolean wasCanceled) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onLoadError(dataSpec, dataType, trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs), elapsedRealtimeMs, loadDurationMs, bytesLoaded, + error, wasCanceled); + } + }); + } + } + + public void upstreamDiscarded(final int trackType, final long mediaStartTimeUs, + final long mediaEndTimeUs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onUpstreamDiscarded(trackType, adjustMediaTime(mediaStartTimeUs), + adjustMediaTime(mediaEndTimeUs)); + } + }); + } + } + + public void downstreamFormatChanged(final int trackType, final Format trackFormat, + final int trackSelectionReason, final Object trackSelectionData, + final long mediaTimeUs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onDownstreamFormatChanged(trackType, trackFormat, trackSelectionReason, + trackSelectionData, adjustMediaTime(mediaTimeUs)); + } + }); + } + } + + private long adjustMediaTime(long mediaTimeUs) { + long mediaTimeMs = C.usToMs(mediaTimeUs); + return mediaTimeMs == C.TIME_UNSET ? C.TIME_UNSET : mediaTimeOffsetMs + mediaTimeMs; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/BehindLiveWindowException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/BehindLiveWindowException.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/BehindLiveWindowException.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/BehindLiveWindowException.java index 33ddfbe44f6..2127a202bc5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/BehindLiveWindowException.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/BehindLiveWindowException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer; +package org.telegram.messenger.exoplayer2.source; import java.io.IOException; @@ -26,8 +26,4 @@ public BehindLiveWindowException() { super(); } - public BehindLiveWindowException(String message) { - super(message); - } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java new file mode 100755 index 00000000000..8e53581ca4f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/CompositeSequenceableLoader.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; + +/** + * A {@link SequenceableLoader} that encapsulates multiple other {@link SequenceableLoader}s. + */ +public final class CompositeSequenceableLoader implements SequenceableLoader { + + private final SequenceableLoader[] loaders; + + public CompositeSequenceableLoader(SequenceableLoader[] loaders) { + this.loaders = loaders; + } + + @Override + public long getNextLoadPositionUs() { + long nextLoadPositionUs = Long.MAX_VALUE; + for (SequenceableLoader loader : loaders) { + long loaderNextLoadPositionUs = loader.getNextLoadPositionUs(); + if (loaderNextLoadPositionUs != C.TIME_END_OF_SOURCE) { + nextLoadPositionUs = Math.min(nextLoadPositionUs, loaderNextLoadPositionUs); + } + } + return nextLoadPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : nextLoadPositionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + boolean madeProgress = false; + boolean madeProgressThisIteration; + do { + madeProgressThisIteration = false; + long nextLoadPositionUs = getNextLoadPositionUs(); + if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) { + break; + } + for (SequenceableLoader loader : loaders) { + if (loader.getNextLoadPositionUs() == nextLoadPositionUs) { + madeProgressThisIteration |= loader.continueLoading(positionUs); + } + } + madeProgress |= madeProgressThisIteration; + } while (madeProgressThisIteration); + return madeProgress; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java new file mode 100755 index 00000000000..b4891d1bf15 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ConcatenatingMediaSource.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * Concatenates multiple {@link MediaSource}s. It is valid for the same {@link MediaSource} instance + * to be present more than once in the concatenation. + */ +public final class ConcatenatingMediaSource implements MediaSource { + + private final MediaSource[] mediaSources; + private final Timeline[] timelines; + private final Object[] manifests; + private final Map sourceIndexByMediaPeriod; + private final boolean[] duplicateFlags; + + private Listener listener; + private ConcatenatedTimeline timeline; + + /** + * @param mediaSources The {@link MediaSource}s to concatenate. It is valid for the same + * {@link MediaSource} instance to be present more than once in the array. + */ + public ConcatenatingMediaSource(MediaSource... mediaSources) { + this.mediaSources = mediaSources; + timelines = new Timeline[mediaSources.length]; + manifests = new Object[mediaSources.length]; + sourceIndexByMediaPeriod = new HashMap<>(); + duplicateFlags = buildDuplicateFlags(mediaSources); + } + + @Override + public void prepareSource(Listener listener) { + this.listener = listener; + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + final int index = i; + mediaSources[i].prepareSource(new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handleSourceInfoRefreshed(index, timeline, manifest); + } + }); + } + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + mediaSources[i].maybeThrowSourceInfoRefreshError(); + } + } + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + int sourceIndex = timeline.getSourceIndexForPeriod(index); + int periodIndexInSource = index - timeline.getFirstPeriodIndexInSource(sourceIndex); + MediaPeriod mediaPeriod = mediaSources[sourceIndex].createPeriod(periodIndexInSource, allocator, + positionUs); + sourceIndexByMediaPeriod.put(mediaPeriod, sourceIndex); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + int sourceIndex = sourceIndexByMediaPeriod.get(mediaPeriod); + sourceIndexByMediaPeriod.remove(mediaPeriod); + mediaSources[sourceIndex].releasePeriod(mediaPeriod); + } + + @Override + public void releaseSource() { + for (int i = 0; i < mediaSources.length; i++) { + if (!duplicateFlags[i]) { + mediaSources[i].releaseSource(); + } + } + } + + private void handleSourceInfoRefreshed(int sourceFirstIndex, Timeline sourceTimeline, + Object sourceManifest) { + // Set the timeline and manifest. + timelines[sourceFirstIndex] = sourceTimeline; + manifests[sourceFirstIndex] = sourceManifest; + // Also set the timeline and manifest for any duplicate entries of the same source. + for (int i = sourceFirstIndex + 1; i < mediaSources.length; i++) { + if (mediaSources[i] == mediaSources[sourceFirstIndex]) { + timelines[i] = sourceTimeline; + manifests[i] = sourceManifest; + } + } + for (Timeline timeline : timelines) { + if (timeline == null) { + // Don't invoke the listener until all sources have timelines. + return; + } + } + timeline = new ConcatenatedTimeline(timelines.clone()); + listener.onSourceInfoRefreshed(timeline, manifests.clone()); + } + + private static boolean[] buildDuplicateFlags(MediaSource[] mediaSources) { + boolean[] duplicateFlags = new boolean[mediaSources.length]; + IdentityHashMap sources = new IdentityHashMap<>(mediaSources.length); + for (int i = 0; i < mediaSources.length; i++) { + MediaSource source = mediaSources[i]; + if (!sources.containsKey(source)) { + sources.put(source, null); + } else { + duplicateFlags[i] = true; + } + } + return duplicateFlags; + } + + /** + * A {@link Timeline} that is the concatenation of one or more {@link Timeline}s. + */ + private static final class ConcatenatedTimeline extends Timeline { + + private final Timeline[] timelines; + private final int[] sourcePeriodOffsets; + private final int[] sourceWindowOffsets; + + public ConcatenatedTimeline(Timeline[] timelines) { + int[] sourcePeriodOffsets = new int[timelines.length]; + int[] sourceWindowOffsets = new int[timelines.length]; + int periodCount = 0; + int windowCount = 0; + for (int i = 0; i < timelines.length; i++) { + Timeline timeline = timelines[i]; + periodCount += timeline.getPeriodCount(); + sourcePeriodOffsets[i] = periodCount; + windowCount += timeline.getWindowCount(); + sourceWindowOffsets[i] = windowCount; + } + this.timelines = timelines; + this.sourcePeriodOffsets = sourcePeriodOffsets; + this.sourceWindowOffsets = sourceWindowOffsets; + } + + @Override + public int getWindowCount() { + return sourceWindowOffsets[sourceWindowOffsets.length - 1]; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + int sourceIndex = getSourceIndexForWindow(windowIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + timelines[sourceIndex].getWindow(windowIndex - firstWindowIndexInSource, window, setIds, + defaultPositionProjectionUs); + window.firstPeriodIndex += firstPeriodIndexInSource; + window.lastPeriodIndex += firstPeriodIndexInSource; + return window; + } + + @Override + public int getPeriodCount() { + return sourcePeriodOffsets[sourcePeriodOffsets.length - 1]; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + int sourceIndex = getSourceIndexForPeriod(periodIndex); + int firstWindowIndexInSource = getFirstWindowIndexInSource(sourceIndex); + int firstPeriodIndexInSource = getFirstPeriodIndexInSource(sourceIndex); + timelines[sourceIndex].getPeriod(periodIndex - firstPeriodIndexInSource, period, setIds); + period.windowIndex += firstWindowIndexInSource; + if (setIds) { + period.uid = Pair.create(sourceIndex, period.uid); + } + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { + return C.INDEX_UNSET; + } + Pair sourceIndexAndPeriodId = (Pair) uid; + if (!(sourceIndexAndPeriodId.first instanceof Integer)) { + return C.INDEX_UNSET; + } + int sourceIndex = (Integer) sourceIndexAndPeriodId.first; + Object periodId = sourceIndexAndPeriodId.second; + if (sourceIndex < 0 || sourceIndex >= timelines.length) { + return C.INDEX_UNSET; + } + int periodIndexInSource = timelines[sourceIndex].getIndexOfPeriod(periodId); + return periodIndexInSource == C.INDEX_UNSET ? C.INDEX_UNSET + : getFirstPeriodIndexInSource(sourceIndex) + periodIndexInSource; + } + + private int getSourceIndexForPeriod(int periodIndex) { + return Util.binarySearchFloor(sourcePeriodOffsets, periodIndex, true, false) + 1; + } + + private int getFirstPeriodIndexInSource(int sourceIndex) { + return sourceIndex == 0 ? 0 : sourcePeriodOffsets[sourceIndex - 1]; + } + + private int getSourceIndexForWindow(int windowIndex) { + return Util.binarySearchFloor(sourceWindowOffsets, windowIndex, true, false) + 1; + } + + private int getFirstWindowIndexInSource(int sourceIndex) { + return sourceIndex == 0 ? 0 : sourceWindowOffsets[sourceIndex - 1]; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java new file mode 100755 index 00000000000..8f283c33090 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaPeriod.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.upstream.Loader.Loadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ConditionVariable; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.io.EOFException; +import java.io.IOException; + +/** + * A {@link MediaPeriod} that extracts data using an {@link Extractor}. + */ +/* package */ final class ExtractorMediaPeriod implements MediaPeriod, ExtractorOutput, + Loader.Callback, UpstreamFormatChangedListener { + + /** + * When the source's duration is unknown, it is calculated by adding this value to the largest + * sample timestamp seen when buffering completes. + */ + private static final long DEFAULT_LAST_SAMPLE_DURATION_US = 10000; + + private final Uri uri; + private final DataSource dataSource; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final ExtractorMediaSource.EventListener eventListener; + private final MediaSource.Listener sourceListener; + private final Allocator allocator; + private final Loader loader; + private final ExtractorHolder extractorHolder; + private final ConditionVariable loadCondition; + private final Runnable maybeFinishPrepareRunnable; + private final Runnable onContinueLoadingRequestedRunnable; + private final Handler handler; + private final SparseArray sampleQueues; + + private Callback callback; + private SeekMap seekMap; + private boolean tracksBuilt; + private boolean prepared; + + private boolean seenFirstTrackSelection; + private boolean notifyReset; + private int enabledTrackCount; + private TrackGroupArray tracks; + private long durationUs; + private boolean[] trackEnabledStates; + private boolean[] trackIsAudioVideoFlags; + private boolean haveAudioVideoTracks; + private long length; + + private long lastSeekPositionUs; + private long pendingResetPositionUs; + + private int extractedSamplesCountAtStartOfLoad; + private boolean loadingFinished; + private boolean released; + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSource The data source to read the media. + * @param extractors The extractors to use to read the data source. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param sourceListener A listener to notify when the timeline has been loaded. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + */ + public ExtractorMediaPeriod(Uri uri, DataSource dataSource, Extractor[] extractors, + int minLoadableRetryCount, Handler eventHandler, + ExtractorMediaSource.EventListener eventListener, MediaSource.Listener sourceListener, + Allocator allocator) { + this.uri = uri; + this.dataSource = dataSource; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.sourceListener = sourceListener; + this.allocator = allocator; + loader = new Loader("Loader:ExtractorMediaPeriod"); + extractorHolder = new ExtractorHolder(extractors, this); + loadCondition = new ConditionVariable(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + onContinueLoadingRequestedRunnable = new Runnable() { + @Override + public void run() { + if (!released) { + callback.onContinueLoadingRequested(ExtractorMediaPeriod.this); + } + } + }; + handler = new Handler(); + + pendingResetPositionUs = C.TIME_UNSET; + sampleQueues = new SparseArray<>(); + length = C.LENGTH_UNSET; + } + + public void release() { + final ExtractorHolder extractorHolder = this.extractorHolder; + loader.release(new Runnable() { + @Override + public void run() { + extractorHolder.release(); + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).disable(); + } + } + }); + handler.removeCallbacksAndMessages(null); + released = true; + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + loadCondition.open(); + startLoading(); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return tracks; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + Assertions.checkState(prepared); + // Disable old tracks. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + int track = ((SampleStreamImpl) streams[i]).track; + Assertions.checkState(trackEnabledStates[track]); + enabledTrackCount--; + trackEnabledStates[track] = false; + sampleQueues.valueAt(track).disable(); + streams[i] = null; + } + } + // Enable new tracks. + boolean selectedNewTracks = false; + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + TrackSelection selection = selections[i]; + Assertions.checkState(selection.length() == 1); + Assertions.checkState(selection.getIndexInTrackGroup(0) == 0); + int track = tracks.indexOf(selection.getTrackGroup()); + Assertions.checkState(!trackEnabledStates[track]); + enabledTrackCount++; + trackEnabledStates[track] = true; + streams[i] = new SampleStreamImpl(track); + streamResetFlags[i] = true; + selectedNewTracks = true; + } + } + if (!seenFirstTrackSelection) { + // At the time of the first track selection all queues will be enabled, so we need to disable + // any that are no longer required. + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (!trackEnabledStates[i]) { + sampleQueues.valueAt(i).disable(); + } + } + } + if (enabledTrackCount == 0) { + notifyReset = false; + if (loader.isLoading()) { + loader.cancelLoading(); + } + } else if (seenFirstTrackSelection ? selectedNewTracks : positionUs != 0) { + positionUs = seekToUs(positionUs); + // We'll need to reset renderers consuming from all streams due to the seek. + for (int i = 0; i < streams.length; i++) { + if (streams[i] != null) { + streamResetFlags[i] = true; + } + } + } + seenFirstTrackSelection = true; + return positionUs; + } + + @Override + public boolean continueLoading(long playbackPositionUs) { + if (loadingFinished || (prepared && enabledTrackCount == 0)) { + return false; + } + boolean continuedLoading = loadCondition.open(); + if (!loader.isLoading()) { + startLoading(); + continuedLoading = true; + } + return continuedLoading; + } + + @Override + public long getNextLoadPositionUs() { + return getBufferedPositionUs(); + } + + @Override + public long readDiscontinuity() { + if (notifyReset) { + notifyReset = false; + return lastSeekPositionUs; + } + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } + long largestQueuedTimestampUs; + if (haveAudioVideoTracks) { + // Ignore non-AV tracks, which may be sparse or poorly interleaved. + largestQueuedTimestampUs = Long.MAX_VALUE; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (trackIsAudioVideoFlags[i]) { + largestQueuedTimestampUs = Math.min(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + } + } else { + largestQueuedTimestampUs = getLargestQueuedTimestampUs(); + } + return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs + : largestQueuedTimestampUs; + } + + @Override + public long seekToUs(long positionUs) { + // Treat all seeks into non-seekable media as being to t=0. + positionUs = seekMap.isSeekable() ? positionUs : 0; + lastSeekPositionUs = positionUs; + int trackCount = sampleQueues.size(); + // If we're not pending a reset, see if we can seek within the sample queues. + boolean seekInsideBuffer = !isPendingReset(); + for (int i = 0; seekInsideBuffer && i < trackCount; i++) { + if (trackEnabledStates[i]) { + seekInsideBuffer = sampleQueues.valueAt(i).skipToKeyframeBefore(positionUs); + } + } + // If we failed to seek within the sample queues, we need to restart. + if (!seekInsideBuffer) { + pendingResetPositionUs = positionUs; + loadingFinished = false; + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(trackEnabledStates[i]); + } + } + } + notifyReset = false; + return positionUs; + } + + // SampleStream methods. + + /* package */ boolean isReady(int track) { + return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(track).isEmpty()); + } + + /* package */ void maybeThrowError() throws IOException { + loader.maybeThrowError(); + } + + /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) { + if (notifyReset || isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + + return sampleQueues.valueAt(track).readData(formatHolder, buffer, loadingFinished, + lastSeekPositionUs); + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + copyLengthFromLoader(loadable); + loadingFinished = true; + if (durationUs == C.TIME_UNSET) { + long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); + durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 + : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; + sourceListener.onSourceInfoRefreshed( + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + } + } + + @Override + public void onLoadCanceled(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + copyLengthFromLoader(loadable); + if (!released && enabledTrackCount > 0) { + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(trackEnabledStates[i]); + } + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(ExtractingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + copyLengthFromLoader(loadable); + notifyLoadError(error); + if (isLoadableExceptionFatal(error)) { + return Loader.DONT_RETRY_FATAL; + } + int extractedSamplesCount = getExtractedSamplesCount(); + boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad; + configureRetry(loadable); // May reset the sample queues. + extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); + return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; + } + + // ExtractorOutput implementation. Called by the loading thread. + + @Override + public TrackOutput track(int id) { + DefaultTrackOutput trackOutput = sampleQueues.get(id); + if (trackOutput == null) { + trackOutput = new DefaultTrackOutput(allocator); + trackOutput.setUpstreamFormatChangeListener(this); + sampleQueues.put(id, trackOutput); + } + return trackOutput; + } + + @Override + public void endTracks() { + tracksBuilt = true; + handler.post(maybeFinishPrepareRunnable); + } + + @Override + public void seekMap(SeekMap seekMap) { + this.seekMap = seekMap; + handler.post(maybeFinishPrepareRunnable); + } + + // UpstreamFormatChangedListener implementation. Called by the loading thread. + + @Override + public void onUpstreamFormatChanged(Format format) { + handler.post(maybeFinishPrepareRunnable); + } + + // Internal methods. + + private void maybeFinishPrepare() { + if (released || prepared || seekMap == null || !tracksBuilt) { + return; + } + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + return; + } + } + loadCondition.close(); + TrackGroup[] trackArray = new TrackGroup[trackCount]; + trackIsAudioVideoFlags = new boolean[trackCount]; + trackEnabledStates = new boolean[trackCount]; + durationUs = seekMap.getDurationUs(); + for (int i = 0; i < trackCount; i++) { + Format trackFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + trackArray[i] = new TrackGroup(trackFormat); + String mimeType = trackFormat.sampleMimeType; + boolean isAudioVideo = MimeTypes.isVideo(mimeType) || MimeTypes.isAudio(mimeType); + trackIsAudioVideoFlags[i] = isAudioVideo; + haveAudioVideoTracks |= isAudioVideo; + } + tracks = new TrackGroupArray(trackArray); + prepared = true; + sourceListener.onSourceInfoRefreshed( + new SinglePeriodTimeline(durationUs, seekMap.isSeekable()), null); + callback.onPrepared(this); + } + + private void copyLengthFromLoader(ExtractingLoadable loadable) { + if (length == C.LENGTH_UNSET) { + length = loadable.length; + } + } + + private void startLoading() { + ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, + loadCondition); + if (prepared) { + Assertions.checkState(isPendingReset()); + if (durationUs != C.TIME_UNSET && pendingResetPositionUs >= durationUs) { + loadingFinished = true; + pendingResetPositionUs = C.TIME_UNSET; + return; + } + loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs)); + pendingResetPositionUs = C.TIME_UNSET; + } + extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); + + int minRetryCount = minLoadableRetryCount; + if (minRetryCount == ExtractorMediaSource.MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA) { + // We assume on-demand before we're prepared. + minRetryCount = !prepared || length != C.LENGTH_UNSET + || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET) + ? ExtractorMediaSource.DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND + : ExtractorMediaSource.DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE; + } + loader.startLoading(loadable, this, minRetryCount); + } + + private void configureRetry(ExtractingLoadable loadable) { + if (length != C.LENGTH_UNSET + || (seekMap != null && seekMap.getDurationUs() != C.TIME_UNSET)) { + // We're playing an on-demand stream. Resume the current loadable, which will + // request data starting from the point it left off. + } else { + // We're playing a stream of unknown length and duration. Assume it's live, and + // therefore that the data at the uri is a continuously shifting window of the latest + // available media. For this case there's no way to continue loading from where a + // previous load finished, so it's necessary to load from the start whenever commencing + // a new load. + lastSeekPositionUs = 0; + notifyReset = prepared; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); + } + loadable.setLoadPosition(0); + } + } + + private int getExtractedSamplesCount() { + int extractedSamplesCount = 0; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + extractedSamplesCount += sampleQueues.valueAt(i).getWriteIndex(); + } + return extractedSamplesCount; + } + + private long getLargestQueuedTimestampUs() { + long largestQueuedTimestampUs = Long.MIN_VALUE; + int trackCount = sampleQueues.size(); + for (int i = 0; i < trackCount; i++) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + return largestQueuedTimestampUs; + } + + private boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + private boolean isLoadableExceptionFatal(IOException e) { + return e instanceof ExtractorMediaSource.UnrecognizedInputFormatException; + } + + private void notifyLoadError(final IOException error) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onLoadError(error); + } + }); + } + } + + private final class SampleStreamImpl implements SampleStream { + + private final int track; + + public SampleStreamImpl(int track) { + this.track = track; + } + + @Override + public boolean isReady() { + return ExtractorMediaPeriod.this.isReady(track); + } + + @Override + public void maybeThrowError() throws IOException { + ExtractorMediaPeriod.this.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { + return ExtractorMediaPeriod.this.readData(track, formatHolder, buffer); + } + + @Override + public void skipToKeyframeBefore(long timeUs) { + sampleQueues.valueAt(track).skipToKeyframeBefore(timeUs); + } + + } + + /** + * Loads the media stream and extracts sample data from it. + */ + /* package */ final class ExtractingLoadable implements Loadable { + + /** + * The number of bytes that should be loaded between each each invocation of + * {@link Callback#onContinueLoadingRequested(SequenceableLoader)}. + */ + private static final int CONTINUE_LOADING_CHECK_INTERVAL_BYTES = 1024 * 1024; + + private final Uri uri; + private final DataSource dataSource; + private final ExtractorHolder extractorHolder; + private final ConditionVariable loadCondition; + private final PositionHolder positionHolder; + + private volatile boolean loadCanceled; + + private boolean pendingExtractorSeek; + private long length; + + public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, + ConditionVariable loadCondition) { + this.uri = Assertions.checkNotNull(uri); + this.dataSource = Assertions.checkNotNull(dataSource); + this.extractorHolder = Assertions.checkNotNull(extractorHolder); + this.loadCondition = loadCondition; + this.positionHolder = new PositionHolder(); + this.pendingExtractorSeek = true; + this.length = C.LENGTH_UNSET; + } + + public void setLoadPosition(long position) { + positionHolder.position = position; + pendingExtractorSeek = true; + } + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @Override + public void load() throws IOException, InterruptedException { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + ExtractorInput input = null; + try { + long position = positionHolder.position; + length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNSET, null)); + if (length != C.LENGTH_UNSET) { + length += position; + } + input = new DefaultExtractorInput(dataSource, position, length); + Extractor extractor = extractorHolder.selectExtractor(input); + if (pendingExtractorSeek) { + extractor.seek(position); + pendingExtractorSeek = false; + } + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + loadCondition.block(); + result = extractor.read(input, positionHolder); + if (input.getPosition() > position + CONTINUE_LOADING_CHECK_INTERVAL_BYTES) { + position = input.getPosition(); + loadCondition.close(); + handler.post(onContinueLoadingRequestedRunnable); + } + } + } finally { + if (result == Extractor.RESULT_SEEK) { + result = Extractor.RESULT_CONTINUE; + } else if (input != null) { + positionHolder.position = input.getPosition(); + } + dataSource.close(); + } + } + } + + } + + /** + * Stores a list of extractors and a selected extractor when the format has been detected. + */ + private static final class ExtractorHolder { + + private final Extractor[] extractors; + private final ExtractorOutput extractorOutput; + private Extractor extractor; + + /** + * Creates a holder that will select an extractor and initialize it using the specified output. + * + * @param extractors One or more extractors to choose from. + * @param extractorOutput The output that will be used to initialize the selected extractor. + */ + public ExtractorHolder(Extractor[] extractors, ExtractorOutput extractorOutput) { + this.extractors = extractors; + this.extractorOutput = extractorOutput; + } + + /** + * Returns an initialized extractor for reading {@code input}, and returns the same extractor on + * later calls. + * + * @param input The {@link ExtractorInput} from which data should be read. + * @return An initialized extractor for reading {@code input}. + * @throws ExtractorMediaSource.UnrecognizedInputFormatException Thrown if the input format + * could not be detected. + * @throws IOException Thrown if the input could not be read. + * @throws InterruptedException Thrown if the thread was interrupted. + */ + public Extractor selectExtractor(ExtractorInput input) + throws IOException, InterruptedException { + if (extractor != null) { + return extractor; + } + for (Extractor extractor : extractors) { + try { + if (extractor.sniff(input)) { + this.extractor = extractor; + break; + } + } catch (EOFException e) { + // Do nothing. + } finally { + input.resetPeekPosition(); + } + } + if (extractor == null) { + throw new ExtractorMediaSource.UnrecognizedInputFormatException(extractors); + } + extractor.init(extractorOutput); + return extractor; + } + + public void release() { + if (extractor != null) { + extractor.release(); + extractor = null; + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java new file mode 100755 index 00000000000..7be255d8e1d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/ExtractorMediaSource.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorsFactory; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorsFactory; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * Provides one period that loads data from a {@link Uri} and extracted using an {@link Extractor}. + *

        + * If the possible input stream container formats are known, pass a factory that instantiates + * extractors for them to the constructor. Otherwise, pass a {@link DefaultExtractorsFactory} to + * use the default extractors. When reading a new stream, the first {@link Extractor} in the array + * of extractors created by the factory that returns {@code true} from {@link Extractor#sniff} will + * be used to extract samples from the input stream. + *

        + * Note that the built-in extractors for AAC, MPEG PS/TS and FLV streams do not support seeking. + */ +public final class ExtractorMediaSource implements MediaSource, MediaSource.Listener { + + /** + * Listener of {@link ExtractorMediaSource} events. + */ + public interface EventListener { + + /** + * Called when an error occurs loading media data. + * + * @param error The load error. + */ + void onLoadError(IOException error); + + } + + /** + * Thrown if the input format could not recognized. + */ + public static final class UnrecognizedInputFormatException extends ParserException { + + public UnrecognizedInputFormatException(Extractor[] extractors) { + super("None of the available extractors (" + + Util.getCommaDelimitedSimpleClassNames(extractors) + ") could read the stream."); + } + + } + + /** + * The default minimum number of times to retry loading prior to failing for on-demand streams. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND = 3; + + /** + * The default minimum number of times to retry loading prior to failing for live streams. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE = 6; + + /** + * Value for {@code minLoadableRetryCount} that causes the loader to retry + * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT_LIVE} times for live streams and + * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT_ON_DEMAND} for on-demand streams. + */ + public static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final ExtractorsFactory extractorsFactory; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final Timeline.Period period; + + private MediaSource.Listener sourceListener; + private Timeline timeline; + private boolean timelineHasDuration; + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the + * possible formats are known, pass a factory that instantiates extractors for those formats. + * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, Handler eventHandler, EventListener eventListener) { + this(uri, dataSourceFactory, extractorsFactory, MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA, eventHandler, + eventListener); + } + + /** + * @param uri The {@link Uri} of the media stream. + * @param dataSourceFactory A factory for {@link DataSource}s to read the media. + * @param extractorsFactory A factory for {@link Extractor}s to process the media stream. If the + * possible formats are known, pass a factory that instantiates extractors for those formats. + * Otherwise, pass a {@link DefaultExtractorsFactory} to use default extractors. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public ExtractorMediaSource(Uri uri, DataSource.Factory dataSourceFactory, + ExtractorsFactory extractorsFactory, int minLoadableRetryCount, Handler eventHandler, + EventListener eventListener) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.extractorsFactory = extractorsFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + period = new Timeline.Period(); + } + + @Override + public void prepareSource(MediaSource.Listener listener) { + sourceListener = listener; + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); + listener.onSourceInfoRefreshed(timeline, null); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + // Do nothing. + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new ExtractorMediaPeriod(uri, dataSourceFactory.createDataSource(), + extractorsFactory.createExtractors(), minLoadableRetryCount, eventHandler, eventListener, + this, allocator); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((ExtractorMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + sourceListener = null; + } + + // MediaSource.Listener implementation. + + @Override + public void onSourceInfoRefreshed(Timeline newTimeline, Object manifest) { + long newTimelineDurationUs = newTimeline.getPeriod(0, period).getDurationUs(); + boolean newTimelineHasDuration = newTimelineDurationUs != C.TIME_UNSET; + if (timelineHasDuration && !newTimelineHasDuration) { + // Suppress source info changes that would make the duration unknown when it is already known. + return; + } + timeline = newTimeline; + timelineHasDuration = newTimelineHasDuration; + sourceListener.onSourceInfoRefreshed(timeline, null); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java new file mode 100755 index 00000000000..25d7881cc0b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/LoopingMediaSource.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Loops a {@link MediaSource}. + */ +public final class LoopingMediaSource implements MediaSource { + + private static final String TAG = "LoopingMediaSource"; + + private final MediaSource childSource; + private final int loopCount; + + private int childPeriodCount; + + /** + * Loops the provided source indefinitely. + * + * @param childSource The {@link MediaSource} to loop. + */ + public LoopingMediaSource(MediaSource childSource) { + this(childSource, Integer.MAX_VALUE); + } + + /** + * Loops the provided source a specified number of times. + * + * @param childSource The {@link MediaSource} to loop. + * @param loopCount The desired number of loops. Must be strictly positive. The actual number of + * loops will be capped at the maximum value that can achieved without causing the number of + * periods exposed by the source to exceed {@link Integer#MAX_VALUE}. + */ + public LoopingMediaSource(MediaSource childSource, int loopCount) { + Assertions.checkArgument(loopCount > 0); + this.childSource = childSource; + this.loopCount = loopCount; + } + + @Override + public void prepareSource(final Listener listener) { + childSource.prepareSource(new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + childPeriodCount = timeline.getPeriodCount(); + listener.onSourceInfoRefreshed(new LoopingTimeline(timeline, loopCount), manifest); + } + }); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + childSource.maybeThrowSourceInfoRefreshError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + return childSource.createPeriod(index % childPeriodCount, allocator, positionUs); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + childSource.releasePeriod(mediaPeriod); + } + + @Override + public void releaseSource() { + childSource.releaseSource(); + } + + private static final class LoopingTimeline extends Timeline { + + private final Timeline childTimeline; + private final int childPeriodCount; + private final int childWindowCount; + private final int loopCount; + + public LoopingTimeline(Timeline childTimeline, int loopCount) { + this.childTimeline = childTimeline; + childPeriodCount = childTimeline.getPeriodCount(); + childWindowCount = childTimeline.getWindowCount(); + // This is the maximum number of loops that can be performed without overflow. + int maxLoopCount = Integer.MAX_VALUE / childPeriodCount; + if (loopCount > maxLoopCount) { + if (loopCount != Integer.MAX_VALUE) { + Log.w(TAG, "Capped loops to avoid overflow: " + loopCount + " -> " + maxLoopCount); + } + this.loopCount = maxLoopCount; + } else { + this.loopCount = loopCount; + } + } + + @Override + public int getWindowCount() { + return childWindowCount * loopCount; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + childTimeline.getWindow(windowIndex % childWindowCount, window, setIds, + defaultPositionProjectionUs); + int periodIndexOffset = (windowIndex / childWindowCount) * childPeriodCount; + window.firstPeriodIndex += periodIndexOffset; + window.lastPeriodIndex += periodIndexOffset; + return window; + } + + @Override + public int getPeriodCount() { + return childPeriodCount * loopCount; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + childTimeline.getPeriod(periodIndex % childPeriodCount, period, setIds); + int loopCount = (periodIndex / childPeriodCount); + period.windowIndex += loopCount * childWindowCount; + if (setIds) { + period.uid = Pair.create(loopCount, period.uid); + } + return period; + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Pair)) { + return C.INDEX_UNSET; + } + Pair loopCountAndChildUid = (Pair) uid; + if (!(loopCountAndChildUid.first instanceof Integer)) { + return C.INDEX_UNSET; + } + int loopCount = (Integer) loopCountAndChildUid.first; + int periodIndexOffset = loopCount * childPeriodCount; + return childTimeline.getIndexOfPeriod(loopCountAndChildUid.second) + periodIndexOffset; + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java new file mode 100755 index 00000000000..b5e1454df3b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaPeriod.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import java.io.IOException; + +/** + * A source of a single period of media. + */ +public interface MediaPeriod extends SequenceableLoader { + + /** + * A callback to be notified of {@link MediaPeriod} events. + */ + interface Callback extends SequenceableLoader.Callback { + + /** + * Called when preparation completes. + *

        + * Called on the playback thread. After invoking this method, the {@link MediaPeriod} can expect + * for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[], long)} to be + * called with the initial track selection. + * + * @param mediaPeriod The prepared {@link MediaPeriod}. + */ + void onPrepared(MediaPeriod mediaPeriod); + + } + + /** + * Prepares this media period asynchronously. + *

        + * {@code callback.onPrepared} is called when preparation completes. If preparation fails, + * {@link #maybeThrowPrepareError()} will throw an {@link IOException}. + * + * @param callback Callback to receive updates from this period, including being notified when + * preparation completes. + */ + void prepare(Callback callback); + + /** + * Throws an error that's preventing the period from becoming prepared. Does nothing if no such + * error exists. + *

        + * This method should only be called before the period has completed preparation. + * + * @throws IOException The underlying error. + */ + void maybeThrowPrepareError() throws IOException; + + /** + * Returns the {@link TrackGroup}s exposed by the period. + *

        + * This method should only be called after the period has been prepared. + * + * @return The {@link TrackGroup}s. + */ + TrackGroupArray getTrackGroups(); + + /** + * Performs a track selection. + *

        + * The call receives track {@code selections} for each renderer, {@code mayRetainStreamFlags} + * indicating whether the existing {@code SampleStream} can be retained for each selection, and + * the existing {@code stream}s themselves. The call will update {@code streams} to reflect the + * provided selections, clearing, setting and replacing entries as required. If an existing sample + * stream is retained but with the requirement that the consuming renderer be reset, then the + * corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set + * if a new sample stream is created. + *

        + * This method should only be called after the period has been prepared. + * + * @param selections The renderer track selections. + * @param mayRetainStreamFlags Flags indicating whether the existing sample stream can be retained + * for each selection. A {@code true} value indicates that the selection is unchanged, and + * that the caller does not require that the sample stream be recreated. + * @param streams The existing sample streams, which will be updated to reflect the provided + * selections. + * @param streamResetFlags Will be updated to indicate new sample streams, and sample streams that + * have been retained but with the requirement that the consuming renderer be reset. + * @param positionUs The current playback position in microseconds. + * @return The actual position at which the tracks were enabled, in microseconds. + */ + long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs); + + /** + * Attempts to read a discontinuity. + *

        + * After this method has returned a value other than {@link C#TIME_UNSET}, all + * {@link SampleStream}s provided by the period are guaranteed to start from a key frame. + * + * @return If a discontinuity was read then the playback position in microseconds after the + * discontinuity. Else {@link C#TIME_UNSET}. + */ + long readDiscontinuity(); + + /** + * Returns an estimate of the position up to which data is buffered for the enabled tracks. + *

        + * This method should only be called when at least one track is selected. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered. + */ + long getBufferedPositionUs(); + + /** + * Attempts to seek to the specified position in microseconds. + *

        + * After this method has been called, all {@link SampleStream}s provided by the period are + * guaranteed to start from a key frame. + *

        + * This method should only be called when at least one track is selected. + * + * @param positionUs The seek position in microseconds. + * @return The actual position to which the period was seeked, in microseconds. + */ + long seekToUs(long positionUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java new file mode 100755 index 00000000000..3776db62075 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MediaSource.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import java.io.IOException; + +/** + * A source of media consisting of one or more {@link MediaPeriod}s. + */ +public interface MediaSource { + + /** + * Listener for source events. + */ + interface Listener { + + /** + * Called when manifest and/or timeline has been refreshed. + * + * @param timeline The source's timeline. + * @param manifest The loaded manifest. + */ + void onSourceInfoRefreshed(Timeline timeline, Object manifest); + + } + + /** + * Starts preparation of the source. + * + * @param listener The listener for source events. + */ + void prepareSource(Listener listener); + + /** + * Throws any pending error encountered while loading or refreshing source information. + */ + void maybeThrowSourceInfoRefreshError() throws IOException; + + /** + * Returns a new {@link MediaPeriod} corresponding to the period at the specified {@code index}. + * This method may be called multiple times with the same index without an intervening call to + * {@link #releasePeriod(MediaPeriod)}. + * + * @param index The index of the period. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param positionUs The player's current playback position. + * @return A new {@link MediaPeriod}. + */ + MediaPeriod createPeriod(int index, Allocator allocator, long positionUs); + + /** + * Releases the period. + * + * @param mediaPeriod The period to release. + */ + void releasePeriod(MediaPeriod mediaPeriod); + + /** + * Releases the source. + *

        + * This method should be called when the source is no longer required. It may be called in any + * state. + */ + void releaseSource(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java new file mode 100755 index 00000000000..b96017675b9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaPeriod.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.IdentityHashMap; + +/** + * Merges multiple {@link MediaPeriod}s. + */ +/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback { + + public final MediaPeriod[] periods; + + private final IdentityHashMap streamPeriodIndices; + + private Callback callback; + private int pendingChildPrepareCount; + private TrackGroupArray trackGroups; + + private MediaPeriod[] enabledPeriods; + private SequenceableLoader sequenceableLoader; + + public MergingMediaPeriod(MediaPeriod... periods) { + this.periods = periods; + streamPeriodIndices = new IdentityHashMap<>(); + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + pendingChildPrepareCount = periods.length; + for (MediaPeriod period : periods) { + period.prepare(this); + } + } + + @Override + public void maybeThrowPrepareError() throws IOException { + for (MediaPeriod period : periods) { + period.maybeThrowPrepareError(); + } + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + // Map each selection and stream onto a child period index. + int[] streamChildIndices = new int[selections.length]; + int[] selectionChildIndices = new int[selections.length]; + for (int i = 0; i < selections.length; i++) { + streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET + : streamPeriodIndices.get(streams[i]); + selectionChildIndices[i] = C.INDEX_UNSET; + if (selections[i] != null) { + TrackGroup trackGroup = selections[i].getTrackGroup(); + for (int j = 0; j < periods.length; j++) { + if (periods[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { + selectionChildIndices[i] = j; + break; + } + } + } + } + streamPeriodIndices.clear(); + // Select tracks for each child, copying the resulting streams back into a new streams array. + SampleStream[] newStreams = new SampleStream[selections.length]; + SampleStream[] childStreams = new SampleStream[selections.length]; + TrackSelection[] childSelections = new TrackSelection[selections.length]; + ArrayList enabledPeriodsList = new ArrayList<>(periods.length); + for (int i = 0; i < periods.length; i++) { + for (int j = 0; j < selections.length; j++) { + childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; + childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; + } + long selectPositionUs = periods[i].selectTracks(childSelections, mayRetainStreamFlags, + childStreams, streamResetFlags, positionUs); + if (i == 0) { + positionUs = selectPositionUs; + } else if (selectPositionUs != positionUs) { + throw new IllegalStateException("Children enabled at different positions"); + } + boolean periodEnabled = false; + for (int j = 0; j < selections.length; j++) { + if (selectionChildIndices[j] == i) { + // Assert that the child provided a stream for the selection. + Assertions.checkState(childStreams[j] != null); + newStreams[j] = childStreams[j]; + periodEnabled = true; + streamPeriodIndices.put(childStreams[j], i); + } else if (streamChildIndices[j] == i) { + // Assert that the child cleared any previous stream. + Assertions.checkState(childStreams[j] == null); + } + } + if (periodEnabled) { + enabledPeriodsList.add(periods[i]); + } + } + // Copy the new streams back into the streams array. + System.arraycopy(newStreams, 0, streams, 0, newStreams.length); + // Update the local state. + enabledPeriods = new MediaPeriod[enabledPeriodsList.size()]; + enabledPeriodsList.toArray(enabledPeriods); + sequenceableLoader = new CompositeSequenceableLoader(enabledPeriods); + return positionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + long positionUs = periods[0].readDiscontinuity(); + // Periods other than the first one are not allowed to report discontinuities. + for (int i = 1; i < periods.length; i++) { + if (periods[i].readDiscontinuity() != C.TIME_UNSET) { + throw new IllegalStateException("Child reported discontinuity"); + } + } + // It must be possible to seek enabled periods to the new position, if there is one. + if (positionUs != C.TIME_UNSET) { + for (MediaPeriod enabledPeriod : enabledPeriods) { + if (enabledPeriod != periods[0] + && enabledPeriod.seekToUs(positionUs) != positionUs) { + throw new IllegalStateException("Children seeked to different positions"); + } + } + } + return positionUs; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (MediaPeriod period : enabledPeriods) { + long rendererBufferedPositionUs = period.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + positionUs = enabledPeriods[0].seekToUs(positionUs); + // Additional periods must seek to the same position. + for (int i = 1; i < enabledPeriods.length; i++) { + if (enabledPeriods[i].seekToUs(positionUs) != positionUs) { + throw new IllegalStateException("Children seeked to different positions"); + } + } + return positionUs; + } + + // MediaPeriod.Callback implementation + + @Override + public void onPrepared(MediaPeriod ignored) { + if (--pendingChildPrepareCount > 0) { + return; + } + int totalTrackGroupCount = 0; + for (MediaPeriod period : periods) { + totalTrackGroupCount += period.getTrackGroups().length; + } + TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; + int trackGroupIndex = 0; + for (MediaPeriod period : periods) { + TrackGroupArray periodTrackGroups = period.getTrackGroups(); + int periodTrackGroupCount = periodTrackGroups.length; + for (int j = 0; j < periodTrackGroupCount; j++) { + trackGroupArray[trackGroupIndex++] = periodTrackGroups.get(j); + } + } + trackGroups = new TrackGroupArray(trackGroupArray); + callback.onPrepared(this); + } + + @Override + public void onContinueLoadingRequested(MediaPeriod ignored) { + if (trackGroups == null) { + // Still preparing. + return; + } + callback.onContinueLoadingRequested(this); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java new file mode 100755 index 00000000000..ee8c22b06d1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/MergingMediaSource.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Merges multiple {@link MediaSource}s. + *

        + * The {@link Timeline}s of the sources being merged must have the same number of periods, and must + * not have any dynamic windows. + */ +public final class MergingMediaSource implements MediaSource { + + /** + * Thrown when a {@link MergingMediaSource} cannot merge its sources. + */ + public static final class IllegalMergeException extends IOException { + + /** + * The reason the merge failed. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({REASON_WINDOWS_ARE_DYNAMIC, REASON_PERIOD_COUNT_MISMATCH}) + public @interface Reason {} + /** + * The merge failed because one of the sources being merged has a dynamic window. + */ + public static final int REASON_WINDOWS_ARE_DYNAMIC = 0; + /** + * The merge failed because the sources have different period counts. + */ + public static final int REASON_PERIOD_COUNT_MISMATCH = 1; + + /** + * The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and + * {@link #REASON_PERIOD_COUNT_MISMATCH}. + */ + @Reason + public final int reason; + + /** + * @param reason The reason the merge failed. One of {@link #REASON_WINDOWS_ARE_DYNAMIC} and + * {@link #REASON_PERIOD_COUNT_MISMATCH}. + */ + public IllegalMergeException(@Reason int reason) { + this.reason = reason; + } + + } + + private static final int PERIOD_COUNT_UNSET = -1; + + private final MediaSource[] mediaSources; + private final ArrayList pendingTimelineSources; + private final Timeline.Window window; + + private Listener listener; + private Timeline primaryTimeline; + private Object primaryManifest; + private int periodCount; + private IllegalMergeException mergeError; + + /** + * @param mediaSources The {@link MediaSource}s to merge. + */ + public MergingMediaSource(MediaSource... mediaSources) { + this.mediaSources = mediaSources; + pendingTimelineSources = new ArrayList<>(Arrays.asList(mediaSources)); + window = new Timeline.Window(); + periodCount = PERIOD_COUNT_UNSET; + } + + @Override + public void prepareSource(final Listener listener) { + this.listener = listener; + for (int i = 0; i < mediaSources.length; i++) { + final int sourceIndex = i; + mediaSources[sourceIndex].prepareSource(new Listener() { + @Override + public void onSourceInfoRefreshed(Timeline timeline, Object manifest) { + handleSourceInfoRefreshed(sourceIndex, timeline, manifest); + } + }); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + if (mergeError != null) { + throw mergeError; + } + for (MediaSource mediaSource : mediaSources) { + mediaSource.maybeThrowSourceInfoRefreshError(); + } + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + MediaPeriod[] periods = new MediaPeriod[mediaSources.length]; + for (int i = 0; i < periods.length; i++) { + periods[i] = mediaSources[i].createPeriod(index, allocator, positionUs); + } + return new MergingMediaPeriod(periods); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + MergingMediaPeriod mergingPeriod = (MergingMediaPeriod) mediaPeriod; + for (int i = 0; i < mediaSources.length; i++) { + mediaSources[i].releasePeriod(mergingPeriod.periods[i]); + } + } + + @Override + public void releaseSource() { + for (MediaSource mediaSource : mediaSources) { + mediaSource.releaseSource(); + } + } + + private void handleSourceInfoRefreshed(int sourceIndex, Timeline timeline, Object manifest) { + if (mergeError == null) { + mergeError = checkTimelineMerges(timeline); + } + if (mergeError != null) { + return; + } + pendingTimelineSources.remove(mediaSources[sourceIndex]); + if (sourceIndex == 0) { + primaryTimeline = timeline; + primaryManifest = manifest; + } + if (pendingTimelineSources.isEmpty()) { + listener.onSourceInfoRefreshed(primaryTimeline, primaryManifest); + } + } + + private IllegalMergeException checkTimelineMerges(Timeline timeline) { + int windowCount = timeline.getWindowCount(); + for (int i = 0; i < windowCount; i++) { + if (timeline.getWindow(i, window, false).isDynamic) { + return new IllegalMergeException(IllegalMergeException.REASON_WINDOWS_ARE_DYNAMIC); + } + } + if (periodCount == PERIOD_COUNT_UNSET) { + periodCount = timeline.getPeriodCount(); + } else if (timeline.getPeriodCount() != periodCount) { + return new IllegalMergeException(IllegalMergeException.REASON_PERIOD_COUNT_MISMATCH); + } + return null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleStream.java new file mode 100755 index 00000000000..b9db4364d8e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SampleStream.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import java.io.IOException; + +/** + * A stream of media samples (and associated format information). + */ +public interface SampleStream { + + /** + * Returns whether data is available to be read. + *

        + * Note: If the stream has ended then a buffer with the end of stream flag can always be read from + * {@link #readData(FormatHolder, DecoderInputBuffer)}. Hence an ended stream is always ready. + * + * @return Whether data is available to be read. + */ + boolean isReady(); + + /** + * Throws an error that's preventing data from being read. Does nothing if no such error exists. + * + * @throws IOException The underlying error. + */ + void maybeThrowError() throws IOException; + + /** + * Attempts to read from the stream. + * + * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. + * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the + * end of the stream. If the end of the stream has been reached, the + * {@link C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer. + * @return The result, which can be {@link C#RESULT_NOTHING_READ}, {@link C#RESULT_FORMAT_READ} or + * {@link C#RESULT_BUFFER_READ}. + */ + int readData(FormatHolder formatHolder, DecoderInputBuffer buffer); + + /** + * Attempts to skip to the keyframe before the specified time. + * + * @param timeUs The specified time. + */ + void skipToKeyframeBefore(long timeUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java new file mode 100755 index 00000000000..ed14e637362 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SequenceableLoader.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; + +/** + * A loader that can proceed in approximate synchronization with other loaders. + */ +public interface SequenceableLoader { + + /** + * A callback to be notified of {@link SequenceableLoader} events. + */ + interface Callback { + + /** + * Called by the loader to indicate that it wishes for its {@link #continueLoading(long)} method + * to be called when it can continue to load data. Called on the playback thread. + */ + void onContinueLoadingRequested(T source); + + } + + /** + * Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished. + */ + long getNextLoadPositionUs(); + + /** + * Attempts to continue loading. + * + * @param positionUs The current playback position. + * @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return + * a different value than prior to the call. False otherwise. + */ + boolean continueLoading(long positionUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java new file mode 100755 index 00000000000..d19f96ca2c6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SinglePeriodTimeline.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * A {@link Timeline} consisting of a single period and static window. + */ +public final class SinglePeriodTimeline extends Timeline { + + private static final Object ID = new Object(); + + private final long periodDurationUs; + private final long windowDurationUs; + private final long windowPositionInPeriodUs; + private final long windowDefaultStartPositionUs; + private final boolean isSeekable; + private final boolean isDynamic; + + /** + * Creates a timeline of one period of known duration, and a static window starting at zero and + * extending to that duration. + * + * @param durationUs The duration of the period, in microseconds. + * @param isSeekable Whether seeking is supported within the period. + */ + public SinglePeriodTimeline(long durationUs, boolean isSeekable) { + this(durationUs, durationUs, 0, 0, isSeekable, false); + } + + /** + * Creates a timeline with one period of known duration, and a window of known duration starting + * at a specified position in the period. + * + * @param periodDurationUs The duration of the period in microseconds. + * @param windowDurationUs The duration of the window in microseconds. + * @param windowPositionInPeriodUs The position of the start of the window in the period, in + * microseconds. + * @param windowDefaultStartPositionUs The default position relative to the start of the window at + * which to begin playback, in microseconds. + * @param isSeekable Whether seeking is supported within the window. + * @param isDynamic Whether the window may change when the timeline is updated. + */ + public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs, + long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable, + boolean isDynamic) { + this.periodDurationUs = periodDurationUs; + this.windowDurationUs = windowDurationUs; + this.windowPositionInPeriodUs = windowPositionInPeriodUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; + this.isSeekable = isSeekable; + this.isDynamic = isDynamic; + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIds, + long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, 1); + Object id = setIds ? ID : null; + long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; + if (isDynamic) { + windowDefaultStartPositionUs += defaultPositionProjectionUs; + if (windowDefaultStartPositionUs > windowDurationUs) { + // The projection takes us beyond the end of the live window. + windowDefaultStartPositionUs = C.TIME_UNSET; + } + } + return window.set(id, C.TIME_UNSET, C.TIME_UNSET, isSeekable, isDynamic, + windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); + } + + @Override + public int getPeriodCount() { + return 1; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + Assertions.checkIndex(periodIndex, 0, 1); + Object id = setIds ? ID : null; + return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs); + } + + @Override + public int getIndexOfPeriod(Object uid) { + return ID.equals(uid) ? 0 : C.INDEX_UNSET; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java new file mode 100755 index 00000000000..045f8d49d18 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaPeriod.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.source.SingleSampleMediaSource.EventListener; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.upstream.Loader.Loadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A {@link MediaPeriod} with a single sample. + */ +/* package */ final class SingleSampleMediaPeriod implements MediaPeriod, + Loader.Callback { + + /** + * The initial size of the allocation used to hold the sample data. + */ + private static final int INITIAL_SAMPLE_SIZE = 1024; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final int eventSourceId; + private final TrackGroupArray tracks; + private final ArrayList sampleStreams; + /* package */ final Loader loader; + /* package */ final Format format; + + /* package */ boolean loadingFinished; + /* package */ byte[] sampleData; + /* package */ int sampleSize; + + public SingleSampleMediaPeriod(Uri uri, DataSource.Factory dataSourceFactory, Format format, + int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, + int eventSourceId) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.format = format; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.eventSourceId = eventSourceId; + tracks = new TrackGroupArray(new TrackGroup(format)); + sampleStreams = new ArrayList<>(); + loader = new Loader("Loader:SingleSampleMediaPeriod"); + } + + public void release() { + loader.release(); + } + + @Override + public void prepare(Callback callback) { + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + loader.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return tracks; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + sampleStreams.remove(streams[i]); + streams[i] = null; + } + if (streams[i] == null && selections[i] != null) { + SampleStreamImpl stream = new SampleStreamImpl(); + sampleStreams.add(stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + return positionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + loader.startLoading(new SourceLoadable(uri, dataSourceFactory.createDataSource()), this, + minLoadableRetryCount); + return true; + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getNextLoadPositionUs() { + return loadingFinished || loader.isLoading() ? C.TIME_END_OF_SOURCE : 0; + } + + @Override + public long getBufferedPositionUs() { + return loadingFinished ? C.TIME_END_OF_SOURCE : 0; + } + + @Override + public long seekToUs(long positionUs) { + for (int i = 0; i < sampleStreams.size(); i++) { + sampleStreams.get(i).seekToUs(positionUs); + } + return positionUs; + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(SourceLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + sampleSize = loadable.sampleSize; + sampleData = loadable.sampleData; + loadingFinished = true; + } + + @Override + public void onLoadCanceled(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + // Do nothing. + } + + @Override + public int onLoadError(SourceLoadable loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + notifyLoadError(error); + return Loader.RETRY; + } + + // Internal methods. + + private void notifyLoadError(final IOException e) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onLoadError(eventSourceId, e); + } + }); + } + } + + private final class SampleStreamImpl implements SampleStream { + + private static final int STREAM_STATE_SEND_FORMAT = 0; + private static final int STREAM_STATE_SEND_SAMPLE = 1; + private static final int STREAM_STATE_END_OF_STREAM = 2; + + private int streamState; + + public void seekToUs(long positionUs) { + if (streamState == STREAM_STATE_END_OF_STREAM) { + streamState = STREAM_STATE_SEND_SAMPLE; + } + } + + @Override + public boolean isReady() { + return loadingFinished; + } + + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { + if (streamState == STREAM_STATE_END_OF_STREAM) { + buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + return C.RESULT_BUFFER_READ; + } else if (streamState == STREAM_STATE_SEND_FORMAT) { + formatHolder.format = format; + streamState = STREAM_STATE_SEND_SAMPLE; + return C.RESULT_FORMAT_READ; + } + + Assertions.checkState(streamState == STREAM_STATE_SEND_SAMPLE); + if (!loadingFinished) { + return C.RESULT_NOTHING_READ; + } else { + buffer.timeUs = 0; + buffer.addFlag(C.BUFFER_FLAG_KEY_FRAME); + buffer.ensureSpaceForWrite(sampleSize); + buffer.data.put(sampleData, 0, sampleSize); + streamState = STREAM_STATE_END_OF_STREAM; + return C.RESULT_BUFFER_READ; + } + } + + @Override + public void skipToKeyframeBefore(long timeUs) { + // Do nothing. + } + + } + + /* package */ static final class SourceLoadable implements Loadable { + + private final Uri uri; + private final DataSource dataSource; + + private int sampleSize; + private byte[] sampleData; + + public SourceLoadable(Uri uri, DataSource dataSource) { + this.uri = uri; + this.dataSource = dataSource; + } + + @Override + public void cancelLoad() { + // Never happens. + } + + @Override + public boolean isLoadCanceled() { + return false; + } + + @Override + public void load() throws IOException, InterruptedException { + // We always load from the beginning, so reset the sampleSize to 0. + sampleSize = 0; + try { + // Create and open the input. + dataSource.open(new DataSpec(uri)); + // Load the sample data. + int result = 0; + while (result != C.RESULT_END_OF_INPUT) { + sampleSize += result; + if (sampleData == null) { + sampleData = new byte[INITIAL_SAMPLE_SIZE]; + } else if (sampleSize == sampleData.length) { + sampleData = Arrays.copyOf(sampleData, sampleData.length * 2); + } + result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize); + } + } finally { + dataSource.close(); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java new file mode 100755 index 00000000000..6a07052f9cb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/SingleSampleMediaSource.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import android.net.Uri; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * Loads data at a given {@link Uri} as a single sample belonging to a single {@link MediaPeriod}. + */ +public final class SingleSampleMediaSource implements MediaSource { + + /** + * Listener of {@link SingleSampleMediaSource} events. + */ + public interface EventListener { + + /** + * Called when an error occurs loading media data. + * + * @param sourceId The id of the reporting {@link SingleSampleMediaSource}. + * @param e The cause of the failure. + */ + void onLoadError(int sourceId, IOException e); + + } + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + + private final Uri uri; + private final DataSource.Factory dataSourceFactory; + private final Format format; + private final int minLoadableRetryCount; + private final Handler eventHandler; + private final EventListener eventListener; + private final int eventSourceId; + private final Timeline timeline; + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs) { + this(uri, dataSourceFactory, format, durationUs, DEFAULT_MIN_LOADABLE_RETRY_COUNT); + } + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs, int minLoadableRetryCount) { + this(uri, dataSourceFactory, format, durationUs, minLoadableRetryCount, null, null, 0); + } + + public SingleSampleMediaSource(Uri uri, DataSource.Factory dataSourceFactory, Format format, + long durationUs, int minLoadableRetryCount, Handler eventHandler, EventListener eventListener, + int eventSourceId) { + this.uri = uri; + this.dataSourceFactory = dataSourceFactory; + this.format = format; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.eventSourceId = eventSourceId; + timeline = new SinglePeriodTimeline(durationUs, true); + } + + // MediaSource implementation. + + @Override + public void prepareSource(MediaSource.Listener listener) { + listener.onSourceInfoRefreshed(timeline, null); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + // Do nothing. + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new SingleSampleMediaPeriod(uri, dataSourceFactory, format, minLoadableRetryCount, + eventHandler, eventListener, eventSourceId); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((SingleSampleMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + // Do nothing. + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java new file mode 100755 index 00000000000..bca7c05a726 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroup.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.util.Arrays; + +// TODO: Add an allowMultipleStreams boolean to indicate where the one stream per group restriction +// does not apply. +/** + * Defines a group of tracks exposed by a {@link MediaPeriod}. + *

        + * A {@link MediaPeriod} is only able to provide one {@link SampleStream} corresponding to a group + * at any given time, however this {@link SampleStream} may adapt between multiple tracks within the + * group. + */ +public final class TrackGroup { + + /** + * The number of tracks in the group. + */ + public final int length; + + private final Format[] formats; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param formats The track formats. Must not be null or contain null elements. + */ + public TrackGroup(Format... formats) { + Assertions.checkState(formats.length > 0); + this.formats = formats; + this.length = formats.length; + } + + /** + * Returns the format of the track at a given index. + * + * @param index The index of the track. + * @return The track's format. + */ + public Format getFormat(int index) { + return formats[index]; + } + + /** + * Returns the index of the track with the given format in the group. + * + * @param format The format. + * @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists. + */ + public int indexOf(Format format) { + for (int i = 0; i < formats.length; i++) { + if (format == formats[i]) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + Arrays.hashCode(formats); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroup other = (TrackGroup) obj; + return length == other.length && Arrays.equals(formats, other.formats); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroupArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroupArray.java new file mode 100755 index 00000000000..bb5d24cef4c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/TrackGroupArray.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source; + +import org.telegram.messenger.exoplayer2.C; +import java.util.Arrays; + +/** + * An array of {@link TrackGroup}s exposed by a {@link MediaPeriod}. + */ +public final class TrackGroupArray { + + /** + * The empty array. + */ + public static final TrackGroupArray EMPTY = new TrackGroupArray(); + + /** + * The number of groups in the array. Greater than or equal to zero. + */ + public final int length; + + private final TrackGroup[] trackGroups; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param trackGroups The groups. Must not be null or contain null elements, but may be empty. + */ + public TrackGroupArray(TrackGroup... trackGroups) { + this.trackGroups = trackGroups; + this.length = trackGroups.length; + } + + /** + * Returns the group at a given index. + * + * @param index The index of the group. + * @return The group. + */ + public TrackGroup get(int index) { + return trackGroups[index]; + } + + /** + * Returns the index of a group within the array. + * + * @param group The group. + * @return The index of the group, or {@link C#INDEX_UNSET} if no such group exists. + */ + public int indexOf(TrackGroup group) { + for (int i = 0; i < length; i++) { + if (trackGroups[i] == group) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Arrays.hashCode(trackGroups); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackGroupArray other = (TrackGroupArray) obj; + return length == other.length && Arrays.equals(trackGroups, other.trackGroups); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunk.java new file mode 100755 index 00000000000..1f7ff8670e2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/BaseMediaChunk.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; + +/** + * A base implementation of {@link MediaChunk}, for chunks that contain a single track. + *

        + * Loaded samples are output to a {@link DefaultTrackOutput}. + */ +public abstract class BaseMediaChunk extends MediaChunk { + + private DefaultTrackOutput trackOutput; + private int firstSampleIndex; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + */ + public BaseMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + } + + /** + * Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive + * samples as they are loaded. + * + * @param trackOutput The output that will receive the loaded samples. + */ + public void init(DefaultTrackOutput trackOutput) { + this.trackOutput = trackOutput; + this.firstSampleIndex = trackOutput.getWriteIndex(); + } + + /** + * Returns the index of the first sample in the output that was passed to + * {@link #init(DefaultTrackOutput)} that will originate from this chunk. + */ + public final int getFirstSampleIndex() { + return firstSampleIndex; + } + + /** + * Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}. + */ + protected final DefaultTrackOutput getTrackOutput() { + return trackOutput; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/Chunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/Chunk.java new file mode 100755 index 00000000000..41f3d9fe05c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/Chunk.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.Loader.Loadable; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * An abstract base class for {@link Loadable} implementations that load chunks of data required + * for the playback of streams. + */ +public abstract class Chunk implements Loadable { + + /** + * The {@link DataSpec} that defines the data to be loaded. + */ + public final DataSpec dataSpec; + /** + * The type of the chunk. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For + * reporting only. + */ + public final int type; + /** + * The format of the track to which this chunk belongs, or null if the chunk does not belong to + * a track. + */ + public final Format trackFormat; + /** + * One of the {@link C} {@code SELECTION_REASON_*} constants if the chunk belongs to a track. + * {@link C#SELECTION_REASON_UNKNOWN} if the chunk does not belong to a track. + */ + public final int trackSelectionReason; + /** + * Optional data associated with the selection of the track to which this chunk belongs. Null if + * the chunk does not belong to a track. + */ + public final Object trackSelectionData; + /** + * The start time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data + * being loaded does not contain media samples. + */ + public final long startTimeUs; + /** + * The end time of the media contained by the chunk, or {@link C#TIME_UNSET} if the data being + * loaded does not contain media samples. + */ + public final long endTimeUs; + + protected final DataSource dataSource; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param type See {@link #type}. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs See {@link #startTimeUs}. + * @param endTimeUs See {@link #endTimeUs}. + */ + public Chunk(DataSource dataSource, DataSpec dataSpec, int type, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs) { + this.dataSource = Assertions.checkNotNull(dataSource); + this.dataSpec = Assertions.checkNotNull(dataSpec); + this.type = type; + this.trackFormat = trackFormat; + this.trackSelectionReason = trackSelectionReason; + this.trackSelectionData = trackSelectionData; + this.startTimeUs = startTimeUs; + this.endTimeUs = endTimeUs; + } + + /** + * Returns the duration of the chunk in microseconds. + */ + public final long getDurationUs() { + return endTimeUs - startTimeUs; + } + + /** + * Returns the number of bytes that have been loaded. + */ + public abstract long bytesLoaded(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java new file mode 100755 index 00000000000..17394db6373 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; + +/** + * An {@link Extractor} wrapper for loading chunks containing a single track. + *

        + * The wrapper allows switching of the {@link SingleTrackMetadataOutput} and {@link TrackOutput} + * which receive parsed data. + */ +public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput { + + /** + * Receives metadata associated with the track as extracted by the wrapped {@link Extractor}. + */ + public interface SingleTrackMetadataOutput { + + /** + * @see ExtractorOutput#seekMap(SeekMap) + */ + void seekMap(SeekMap seekMap); + + } + + private final Extractor extractor; + private final Format manifestFormat; + private final boolean preferManifestDrmInitData; + private final boolean resendFormatOnInit; + + private boolean extractorInitialized; + private SingleTrackMetadataOutput metadataOutput; + private TrackOutput trackOutput; + private Format sentFormat; + + // Accessed only on the loader thread. + private boolean seenTrack; + private int seenTrackId; + + /** + * @param extractor The extractor to wrap. + * @param manifestFormat A manifest defined {@link Format} whose data should be merged into any + * sample {@link Format} output from the {@link Extractor}. + * @param preferManifestDrmInitData Whether {@link DrmInitData} defined in {@code manifestFormat} + * should be preferred when the sample and manifest {@link Format}s are merged. + * @param resendFormatOnInit Whether the extractor should resend the previous {@link Format} when + * it is initialized via {@link #init(SingleTrackMetadataOutput, TrackOutput)}. + */ + public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat, + boolean preferManifestDrmInitData, boolean resendFormatOnInit) { + this.extractor = extractor; + this.manifestFormat = manifestFormat; + this.preferManifestDrmInitData = preferManifestDrmInitData; + this.resendFormatOnInit = resendFormatOnInit; + } + + /** + * Initializes the extractor to output to the provided {@link SingleTrackMetadataOutput} and + * {@link TrackOutput} instances, and configures it to receive data from a new chunk. + * + * @param metadataOutput The {@link SingleTrackMetadataOutput} that will receive metadata. + * @param trackOutput The {@link TrackOutput} that will receive sample data. + */ + public void init(SingleTrackMetadataOutput metadataOutput, TrackOutput trackOutput) { + this.metadataOutput = metadataOutput; + this.trackOutput = trackOutput; + if (!extractorInitialized) { + extractor.init(this); + extractorInitialized = true; + } else { + extractor.seek(0); + if (resendFormatOnInit && sentFormat != null) { + trackOutput.format(sentFormat); + } + } + } + + /** + * Reads from the provided {@link ExtractorInput}. + * + * @param input The {@link ExtractorInput} from which to read. + * @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}. + * @throws IOException If an error occurred reading from the source. + * @throws InterruptedException If the thread was interrupted. + */ + public int read(ExtractorInput input) throws IOException, InterruptedException { + int result = extractor.read(input, null); + Assertions.checkState(result != Extractor.RESULT_SEEK); + return result; + } + + // ExtractorOutput implementation. + + @Override + public TrackOutput track(int id) { + Assertions.checkState(!seenTrack || seenTrackId == id); + seenTrack = true; + seenTrackId = id; + return this; + } + + @Override + public void endTracks() { + Assertions.checkState(seenTrack); + } + + @Override + public void seekMap(SeekMap seekMap) { + metadataOutput.seekMap(seekMap); + } + + // TrackOutput implementation. + + @Override + public void format(Format format) { + sentFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData); + trackOutput.format(sentFormat); + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + return trackOutput.sampleData(input, length, allowEndOfInput); + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + trackOutput.sampleData(data, length); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + trackOutput.sampleMetadata(timeUs, flags, size, offset, encryptionKey); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkHolder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkHolder.java new file mode 100755 index 00000000000..4faf070055f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkHolder.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +/** + * Holds a chunk or an indication that the end of the stream has been reached. + */ +public final class ChunkHolder { + + /** + * The chunk. + */ + public Chunk chunk; + + /** + * Indicates that the end of the stream has been reached. + */ + public boolean endOfStream; + + /** + * Clears the holder. + */ + public void clear() { + chunk = null; + endOfStream = false; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java new file mode 100755 index 00000000000..754b42ee025 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSampleStream.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.SequenceableLoader; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * A {@link SampleStream} that loads media in {@link Chunk}s, obtained from a {@link ChunkSource}. + */ +public class ChunkSampleStream implements SampleStream, SequenceableLoader, + Loader.Callback { + + private final int trackType; + private final T chunkSource; + private final SequenceableLoader.Callback> callback; + private final EventDispatcher eventDispatcher; + private final int minLoadableRetryCount; + private final LinkedList mediaChunks; + private final List readOnlyMediaChunks; + private final DefaultTrackOutput sampleQueue; + private final ChunkHolder nextChunkHolder; + private final Loader loader; + + private Format downstreamTrackFormat; + + private long lastSeekPositionUs; + private long pendingResetPositionUs; + + private boolean loadingFinished; + + /** + * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param chunkSource A {@link ChunkSource} from which chunks to load are obtained. + * @param callback An {@link Callback} for the stream. + * @param allocator An {@link Allocator} from which allocations can be obtained. + * @param positionUs The position from which to start loading media. + * @param minLoadableRetryCount The minimum number of times that the source should retry a load + * before propagating an error. + * @param eventDispatcher A dispatcher to notify of events. + */ + public ChunkSampleStream(int trackType, T chunkSource, + SequenceableLoader.Callback> callback, Allocator allocator, + long positionUs, int minLoadableRetryCount, EventDispatcher eventDispatcher) { + this.trackType = trackType; + this.chunkSource = chunkSource; + this.callback = callback; + this.eventDispatcher = eventDispatcher; + this.minLoadableRetryCount = minLoadableRetryCount; + loader = new Loader("Loader:ChunkSampleStream"); + nextChunkHolder = new ChunkHolder(); + mediaChunks = new LinkedList<>(); + readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); + sampleQueue = new DefaultTrackOutput(allocator); + lastSeekPositionUs = positionUs; + pendingResetPositionUs = positionUs; + } + + /** + * Returns the {@link ChunkSource} used by this stream. + * + * @return The {@link ChunkSource}. + */ + public T getChunkSource() { + return chunkSource; + } + + /** + * Returns an estimate of the position up to which data is buffered. + * + * @return An estimate of the absolute position in microseconds up to which data is buffered, or + * {@link C#TIME_END_OF_SOURCE} if the track is fully buffered. + */ + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long bufferedPositionUs = lastSeekPositionUs; + BaseMediaChunk lastMediaChunk = mediaChunks.getLast(); + BaseMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk + : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null; + if (lastCompletedMediaChunk != null) { + bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); + } + return Math.max(bufferedPositionUs, sampleQueue.getLargestQueuedTimestampUs()); + } + } + + /** + * Seeks to the specified position in microseconds. + * + * @param positionUs The seek position in microseconds. + */ + public void seekToUs(long positionUs) { + lastSeekPositionUs = positionUs; + // If we're not pending a reset, see if we can seek within the sample queue. + boolean seekInsideBuffer = !isPendingReset() && sampleQueue.skipToKeyframeBefore(positionUs); + if (seekInsideBuffer) { + // We succeeded. All we need to do is discard any chunks that we've moved past. + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { + mediaChunks.removeFirst(); + } + } else { + // We failed, and need to restart. + pendingResetPositionUs = positionUs; + loadingFinished = false; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + sampleQueue.reset(true); + } + } + } + + /** + * Releases the stream. + *

        + * This method should be called when the stream is no longer required. + */ + public void release() { + sampleQueue.disable(); + loader.release(); + } + + // SampleStream implementation. + + @Override + public boolean isReady() { + return loadingFinished || (!isPendingReset() && !sampleQueue.isEmpty()); + } + + @Override + public void maybeThrowError() throws IOException { + loader.maybeThrowError(); + if (!loader.isLoading()) { + chunkSource.maybeThrowError(); + } + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + + while (mediaChunks.size() > 1 + && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) { + mediaChunks.removeFirst(); + } + BaseMediaChunk currentChunk = mediaChunks.getFirst(); + + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(downstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(trackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + downstreamTrackFormat = trackFormat; + return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs); + } + + @Override + public void skipToKeyframeBefore(long timeUs) { + sampleQueue.skipToKeyframeBefore(timeUs); + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { + chunkSource.onChunkLoadCompleted(loadable); + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + callback.onContinueLoadingRequested(this); + } + + @Override + public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + if (!released) { + sampleQueue.reset(true); + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + long bytesLoaded = loadable.bytesLoaded(); + boolean isMediaChunk = isMediaChunk(loadable); + boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1; + boolean canceled = false; + if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { + canceled = true; + if (isMediaChunk) { + BaseMediaChunk removed = mediaChunks.removeLast(); + Assertions.checkState(removed == loadable); + sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex()); + if (mediaChunks.isEmpty()) { + pendingResetPositionUs = lastSeekPositionUs; + } + } + } + eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, bytesLoaded, error, + canceled); + if (canceled) { + callback.onContinueLoadingRequested(this); + return Loader.DONT_RETRY; + } else { + return Loader.RETRY; + } + } + + // SequenceableLoader implementation + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + + chunkSource.getNextChunk(mediaChunks.isEmpty() ? null : mediaChunks.getLast(), + pendingResetPositionUs != C.TIME_UNSET ? pendingResetPositionUs : positionUs, + nextChunkHolder); + boolean endOfStream = nextChunkHolder.endOfStream; + Chunk loadable = nextChunkHolder.chunk; + nextChunkHolder.clear(); + + if (endOfStream) { + loadingFinished = true; + return true; + } + + if (loadable == null) { + return false; + } + + if (isMediaChunk(loadable)) { + pendingResetPositionUs = C.TIME_UNSET; + BaseMediaChunk mediaChunk = (BaseMediaChunk) loadable; + mediaChunk.init(sampleQueue); + mediaChunks.add(mediaChunk); + } + long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs); + return true; + } + + @Override + public long getNextLoadPositionUs() { + if (isPendingReset()) { + return pendingResetPositionUs; + } else { + return loadingFinished ? C.TIME_END_OF_SOURCE : mediaChunks.getLast().endTimeUs; + } + } + + // Internal methods + + // TODO[REFACTOR]: Call maybeDiscardUpstream for DASH and SmoothStreaming. + /** + * Discards media chunks from the back of the buffer if conditions have changed such that it's + * preferable to re-buffer the media at a different quality. + * + * @param positionUs The current playback position in microseconds. + */ + private void maybeDiscardUpstream(long positionUs) { + int queueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks); + discardUpstreamMediaChunks(Math.max(1, queueSize)); + } + + private boolean isMediaChunk(Chunk chunk) { + return chunk instanceof BaseMediaChunk; + } + + private boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + /** + * Discard upstream media chunks until the queue length is equal to the length specified. + * + * @param queueLength The desired length of the queue. + * @return Whether chunks were discarded. + */ + private boolean discardUpstreamMediaChunks(int queueLength) { + if (mediaChunks.size() <= queueLength) { + return false; + } + long startTimeUs = 0; + long endTimeUs = mediaChunks.getLast().endTimeUs; + + BaseMediaChunk removed = null; + while (mediaChunks.size() > queueLength) { + removed = mediaChunks.removeLast(); + startTimeUs = removed.startTimeUs; + loadingFinished = false; + } + sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex()); + eventDispatcher.upstreamDiscarded(trackType, startTimeUs, endTimeUs); + return true; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSource.java new file mode 100755 index 00000000000..0fc81bdc046 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkSource.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import java.io.IOException; +import java.util.List; + +/** + * A provider of {@link Chunk}s for a {@link ChunkSampleStream} to load. + */ +public interface ChunkSource { + + /** + * If the source is currently having difficulty providing chunks, then this method throws the + * underlying error. Otherwise does nothing. + *

        + * This method should only be called after the source has been prepared. + * + * @throws IOException The underlying error. + */ + void maybeThrowError() throws IOException; + + /** + * Evaluates whether {@link MediaChunk}s should be removed from the back of the queue. + *

        + * Removing {@link MediaChunk}s from the back of the queue can be useful if they could be replaced + * with chunks of a significantly higher quality (e.g. because the available bandwidth has + * substantially increased). + * + * @param playbackPositionUs The current playback position. + * @param queue The queue of buffered {@link MediaChunk}s. + * @return The preferred queue size. + */ + int getPreferredQueueSize(long playbackPositionUs, List queue); + + /** + * Returns the next chunk to load. + *

        + * If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has + * been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the + * end of the stream has not been reached, the {@link ChunkHolder} is not modified. + * + * @param previous The most recently loaded media chunk. + * @param playbackPositionUs The current playback position. If {@code previous} is null then this + * parameter is the position from which playback is expected to start (or restart) and hence + * should be interpreted as a seek position. + * @param out A holder to populate. + */ + void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out); + + /** + * Called when the {@link ChunkSampleStream} has finished loading a chunk obtained from this + * source. + *

        + * This method should only be called when the source is enabled. + * + * @param chunk The chunk whose load has been completed. + */ + void onChunkLoadCompleted(Chunk chunk); + + /** + * Called when the {@link ChunkSampleStream} encounters an error loading a chunk obtained from + * this source. + *

        + * This method should only be called when the source is enabled. + * + * @param chunk The chunk whose load encountered the error. + * @param cancelable Whether the load can be canceled. + * @param e The error. + * @return Whether the load should be canceled. + */ + boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java new file mode 100755 index 00000000000..bf79be62f77 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ChunkedTrackBlacklistUtil.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import android.util.Log; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; + +/** + * Helper class for blacklisting tracks in a {@link TrackSelection} when 404 (Not Found) and 410 + * (Gone) HTTP response codes are encountered. + */ +public final class ChunkedTrackBlacklistUtil { + + /** + * The default duration for which a track is blacklisted in milliseconds. + */ + public static final long DEFAULT_TRACK_BLACKLIST_MS = 60000; + + private static final String TAG = "ChunkedTrackBlacklist"; + + /** + * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for + * {@link #DEFAULT_TRACK_BLACKLIST_MS} if {@code e} is an {@link InvalidResponseCodeException} + * with {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. + * Note that blacklisting will fail if the track is the only non-blacklisted track in the + * selection. + * + * @param trackSelection The track selection. + * @param trackSelectionIndex The index in the selection to consider blacklisting. + * @param e The error to inspect. + * @return Whether the track was blacklisted in the selection. + */ + public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, + Exception e) { + return maybeBlacklistTrack(trackSelection, trackSelectionIndex, e, DEFAULT_TRACK_BLACKLIST_MS); + } + + /** + * Blacklists {@code trackSelectionIndex} in {@code trackSelection} for + * {@code blacklistDurationMs} if {@code e} is an {@link InvalidResponseCodeException} with + * {@link InvalidResponseCodeException#responseCode} equal to 404 or 410. Else does nothing. Note + * that blacklisting will fail if the track is the only non-blacklisted track in the selection. + * + * @param trackSelection The track selection. + * @param trackSelectionIndex The index in the selection to consider blacklisting. + * @param e The error to inspect. + * @param blacklistDurationMs The duration to blacklist the track for, if it is blacklisted. + * @return Whether the track was blacklisted. + */ + public static boolean maybeBlacklistTrack(TrackSelection trackSelection, int trackSelectionIndex, + Exception e, long blacklistDurationMs) { + if (trackSelection.length() == 1) { + // Blacklisting won't ever work if there's only one track in the selection. + return false; + } + if (e instanceof InvalidResponseCodeException) { + InvalidResponseCodeException responseCodeException = (InvalidResponseCodeException) e; + int responseCode = responseCodeException.responseCode; + if (responseCode == 404 || responseCode == 410) { + boolean blacklisted = trackSelection.blacklist(trackSelectionIndex, blacklistDurationMs); + if (blacklisted) { + Log.w(TAG, "Blacklisted: duration=" + blacklistDurationMs + ", responseCode=" + + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); + } else { + Log.w(TAG, "Blacklisting failed (cannot blacklist last enabled track): responseCode=" + + responseCode + ", format=" + trackSelection.getFormat(trackSelectionIndex)); + } + return blacklisted; + } + } + return false; + } + + private ChunkedTrackBlacklistUtil() {} + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java new file mode 100755 index 00000000000..3d116e53bf3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/ContainerMediaChunk.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link BaseMediaChunk} that uses an {@link Extractor} to decode sample data. + */ +public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMetadataOutput { + + private final int chunkCount; + private final long sampleOffsetUs; + private final ChunkExtractorWrapper extractorWrapper; + private final Format sampleFormat; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + * @param chunkCount The number of chunks in the underlying media that are spanned by this + * instance. Normally equal to one, but may be larger if multiple chunks as defined by the + * underlying media are being merged into a single load. + * @param sampleOffsetUs An offset to add to the sample timestamps parsed by the extractor. + * @param extractorWrapper A wrapped extractor to use for parsing the data. + * @param sampleFormat The {@link Format} of the samples in the chunk, if known. May be null if + * the data is known to define its own sample format. + */ + public ContainerMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex, int chunkCount, long sampleOffsetUs, ChunkExtractorWrapper extractorWrapper, + Format sampleFormat) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + this.chunkCount = chunkCount; + this.sampleOffsetUs = sampleOffsetUs; + this.extractorWrapper = extractorWrapper; + this.sampleFormat = sampleFormat; + } + + @Override + public int getNextChunkIndex() { + return chunkIndex + chunkCount; + } + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public final long bytesLoaded() { + return bytesLoaded; + } + + // SingleTrackMetadataOutput implementation. + + @Override + public final void seekMap(SeekMap seekMap) { + // Do nothing. + } + + // Loadable implementation. + + @Override + public final void cancelLoad() { + loadCanceled = true; + } + + @Override + public final boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public final void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (bytesLoaded == 0) { + // Set the target to ourselves. + DefaultTrackOutput trackOutput = getTrackOutput(); + trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs); + extractorWrapper.init(this, trackOutput); + } + // Load and decode the sample data. + try { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractorWrapper.read(input); + } + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + dataSource.close(); + } + loadCompleted = true; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/DataChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/DataChunk.java similarity index 75% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/DataChunk.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/DataChunk.java index 8ef5a80e406..c244c5cd6d3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/chunk/DataChunk.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/DataChunk.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.chunk; +package org.telegram.messenger.exoplayer2.source.chunk; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; import java.io.IOException; import java.util.Arrays; @@ -35,19 +37,17 @@ public abstract class DataChunk extends Chunk { /** * @param dataSource The source from which the data should be loaded. - * @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed - * {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then - * the length resolved by {@code dataSource.open(dataSpec)} must not exceed - * {@link Integer#MAX_VALUE}. + * @param dataSpec Defines the data to be loaded. * @param type See {@link #type}. - * @param trigger See {@link #trigger}. - * @param format See {@link #format}. - * @param parentId Identifier for a parent from which this chunk originates. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. * @param data An optional recycled array that can be used as a holder for the data. */ - public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, int trigger, Format format, - int parentId, byte[] data) { - super(dataSource, dataSpec, type, trigger, format, parentId); + public DataChunk(DataSource dataSource, DataSpec dataSpec, int type, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, byte[] data) { + super(dataSource, dataSpec, type, trackFormat, trackSelectionReason, trackSelectionData, + C.TIME_UNSET, C.TIME_UNSET); this.data = data; } @@ -85,7 +85,7 @@ public final void load() throws IOException, InterruptedException { dataSource.open(dataSpec); limit = 0; int bytesRead = 0; - while (bytesRead != -1 && !loadCanceled) { + while (bytesRead != C.RESULT_END_OF_INPUT && !loadCanceled) { maybeExpandData(); bytesRead = dataSource.read(data, limit, READ_GRANULARITY); if (bytesRead != -1) { @@ -101,7 +101,7 @@ public final void load() throws IOException, InterruptedException { } /** - * Invoked by {@link #load()}. Implementations should override this method to consume the loaded + * Called by {@link #load()}. Implementations should override this method to consume the loaded * data. * * @param data An array containing the data. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java new file mode 100755 index 00000000000..36b50e7dd69 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/InitializationChunk.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link Chunk} that uses an {@link Extractor} to decode initialization data for single track. + */ +public final class InitializationChunk extends Chunk implements SingleTrackMetadataOutput, + TrackOutput { + + private final ChunkExtractorWrapper extractorWrapper; + + // Initialization results. Set by the loader thread and read by any thread that knows loading + // has completed. These variables do not need to be volatile, since a memory barrier must occur + // for the reading thread to know that loading has completed. + private Format sampleFormat; + private SeekMap seekMap; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param extractorWrapper A wrapped extractor to use for parsing the initialization data. + */ + public InitializationChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, + ChunkExtractorWrapper extractorWrapper) { + super(dataSource, dataSpec, C.DATA_TYPE_MEDIA_INITIALIZATION, trackFormat, trackSelectionReason, + trackSelectionData, C.TIME_UNSET, C.TIME_UNSET); + this.extractorWrapper = extractorWrapper; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + /** + * Returns a {@link Format} parsed from the chunk, or null. + *

        + * Should be called after loading has completed. + */ + public Format getSampleFormat() { + return sampleFormat; + } + + /** + * Returns a {@link SeekMap} parsed from the chunk, or null. + *

        + * Should be called after loading has completed. + */ + public SeekMap getSeekMap() { + return seekMap; + } + + // SingleTrackMetadataOutput implementation. + + @Override + public void seekMap(SeekMap seekMap) { + this.seekMap = seekMap; + } + + // TrackOutput implementation. + + @Override + public void format(Format format) { + this.sampleFormat = format; + } + + @Override + public int sampleData(ExtractorInput input, int length, boolean allowEndOfInput) + throws IOException, InterruptedException { + throw new IllegalStateException("Unexpected sample data in initialization chunk"); + } + + @Override + public void sampleData(ParsableByteArray data, int length) { + throw new IllegalStateException("Unexpected sample data in initialization chunk"); + } + + @Override + public void sampleMetadata(long timeUs, @C.BufferFlags int flags, int size, int offset, + byte[] encryptionKey) { + throw new IllegalStateException("Unexpected sample data in initialization chunk"); + } + + // Loadable implementation. + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (bytesLoaded == 0) { + // Set the target to ourselves. + extractorWrapper.init(this, this); + } + // Load and decode the initialization data. + try { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractorWrapper.read(input); + } + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + dataSource.close(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/MediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/MediaChunk.java new file mode 100755 index 00000000000..c2f3442782d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/MediaChunk.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * An abstract base class for {@link Chunk}s that contain media samples. + */ +public abstract class MediaChunk extends Chunk { + + /** + * The chunk index. + */ + public final int chunkIndex; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + */ + public MediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex) { + super(dataSource, dataSpec, C.DATA_TYPE_MEDIA, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs); + Assertions.checkNotNull(trackFormat); + this.chunkIndex = chunkIndex; + } + + /** + * Returns the next chunk index. + */ + public int getNextChunkIndex() { + return chunkIndex + 1; + } + + /** + * Returns whether the chunk has been fully loaded. + */ + public abstract boolean isLoadCompleted(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java new file mode 100755 index 00000000000..e184385af1d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/chunk/SingleSampleMediaChunk.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.chunk; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link BaseMediaChunk} for chunks consisting of a single raw sample. + */ +public final class SingleSampleMediaChunk extends BaseMediaChunk { + + private final Format sampleFormat; + + private volatile int bytesLoaded; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param trackFormat See {@link #trackFormat}. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param startTimeUs The start time of the media contained by the chunk, in microseconds. + * @param endTimeUs The end time of the media contained by the chunk, in microseconds. + * @param chunkIndex The index of the chunk. + */ + public SingleSampleMediaChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, long startTimeUs, long endTimeUs, + int chunkIndex, Format sampleFormat) { + super(dataSource, dataSpec, trackFormat, trackSelectionReason, trackSelectionData, startTimeUs, + endTimeUs, chunkIndex); + this.sampleFormat = sampleFormat; + } + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation. + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @SuppressWarnings("NonAtomicVolatileUpdate") + @Override + public void load() throws IOException, InterruptedException { + DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + try { + // Create and open the input. + long length = dataSource.open(loadDataSpec); + if (length != C.LENGTH_UNSET) { + length += bytesLoaded; + } + ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length); + DefaultTrackOutput trackOutput = getTrackOutput(); + trackOutput.formatWithOffset(sampleFormat, 0); + // Load the sample data. + int result = 0; + while (result != C.RESULT_END_OF_INPUT) { + bytesLoaded += result; + result = trackOutput.sampleData(extractorInput, Integer.MAX_VALUE, true); + } + int sampleSize = bytesLoaded; + trackOutput.sampleMetadata(startTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } finally { + dataSource.close(); + } + loadCompleted = true; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java new file mode 100755 index 00000000000..9824e49ec44 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashChunkSource.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash; + +import org.telegram.messenger.exoplayer2.source.chunk.ChunkSource; +import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifest; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; + +/** + * An {@link ChunkSource} for DASH streams. + */ +public interface DashChunkSource extends ChunkSource { + + interface Factory { + + DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, + TrackSelection trackSelection, long elapsedRealtimeOffsetMs); + + } + + void updateManifest(DashManifest newManifest, int periodIndex); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java new file mode 100755 index 00000000000..db64cb32177 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaPeriod.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.CompositeSequenceableLoader; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.SequenceableLoader; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkSampleStream; +import org.telegram.messenger.exoplayer2.source.dash.manifest.AdaptationSet; +import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifest; +import org.telegram.messenger.exoplayer2.source.dash.manifest.Period; +import org.telegram.messenger.exoplayer2.source.dash.manifest.Representation; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * A DASH {@link MediaPeriod}. + */ +/* package */ final class DashMediaPeriod implements MediaPeriod, + SequenceableLoader.Callback> { + + /* package */ final int id; + private final DashChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final long elapsedRealtimeOffset; + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final Allocator allocator; + private final TrackGroupArray trackGroups; + + private Callback callback; + private ChunkSampleStream[] sampleStreams; + private CompositeSequenceableLoader sequenceableLoader; + private DashManifest manifest; + private int index; + private Period period; + + public DashMediaPeriod(int id, DashManifest manifest, int index, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + EventDispatcher eventDispatcher, long elapsedRealtimeOffset, + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + this.id = id; + this.manifest = manifest; + this.index = index; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.elapsedRealtimeOffset = elapsedRealtimeOffset; + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.allocator = allocator; + sampleStreams = newSampleStreamArray(0); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + period = manifest.getPeriod(index); + trackGroups = buildTrackGroups(period); + } + + public void updateManifest(DashManifest manifest, int index) { + this.manifest = manifest; + this.index = index; + period = manifest.getPeriod(index); + if (sampleStreams != null) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.getChunkSource().updateManifest(manifest, index); + } + callback.onContinueLoadingRequested(this); + } + } + + public void release() { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.release(); + } + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + ArrayList> sampleStreamsList = new ArrayList<>(); + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = (ChunkSampleStream) streams[i]; + if (selections[i] == null || !mayRetainStreamFlags[i]) { + stream.release(); + streams[i] = null; + } else { + sampleStreamsList.add(stream); + } + } + if (streams[i] == null && selections[i] != null) { + ChunkSampleStream stream = buildSampleStream(selections[i], positionUs); + sampleStreamsList.add(stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + sampleStreams = newSampleStreamArray(sampleStreamsList.size()); + sampleStreamsList.toArray(sampleStreams); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + return positionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkSampleStream sampleStream : sampleStreams) { + long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.seekToUs(positionUs); + } + return positionUs; + } + + // SequenceableLoader.Callback implementation. + + @Override + public void onContinueLoadingRequested(ChunkSampleStream sampleStream) { + callback.onContinueLoadingRequested(this); + } + + // Internal methods. + + private static TrackGroupArray buildTrackGroups(Period period) { + TrackGroup[] trackGroupArray = new TrackGroup[period.adaptationSets.size()]; + for (int i = 0; i < period.adaptationSets.size(); i++) { + AdaptationSet adaptationSet = period.adaptationSets.get(i); + List representations = adaptationSet.representations; + Format[] formats = new Format[representations.size()]; + for (int j = 0; j < formats.length; j++) { + formats[j] = representations.get(j).format; + } + trackGroupArray[i] = new TrackGroup(formats); + } + return new TrackGroupArray(trackGroupArray); + } + + private ChunkSampleStream buildSampleStream(TrackSelection selection, + long positionUs) { + int adaptationSetIndex = trackGroups.indexOf(selection.getTrackGroup()); + AdaptationSet adaptationSet = period.adaptationSets.get(adaptationSetIndex); + DashChunkSource chunkSource = chunkSourceFactory.createDashChunkSource( + manifestLoaderErrorThrower, manifest, index, adaptationSetIndex, selection, + elapsedRealtimeOffset); + return new ChunkSampleStream<>(adaptationSet.type, chunkSource, this, allocator, positionUs, + minLoadableRetryCount, eventDispatcher); + } + + @SuppressWarnings("unchecked") + private static ChunkSampleStream[] newSampleStreamArray(int length) { + return new ChunkSampleStream[length]; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java new file mode 100755 index 00000000000..4db9ef729ed --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashMediaSource.java @@ -0,0 +1,789 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash; + +import android.net.Uri; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifest; +import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifestParser; +import org.telegram.messenger.exoplayer2.source.dash.manifest.UtcTimingElement; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.TimeZone; + +/** + * A DASH {@link MediaSource}. + */ +public final class DashMediaSource implements MediaSource { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + /** + * A constant indicating that the presentation delay for live streams should be set to + * {@link DashManifest#suggestedPresentationDelay} if specified by the manifest, or + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS} otherwise. The presentation delay is the + * duration by which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS = -1; + /** + * A fixed default presentation delay for live streams. The presentation delay is the duration + * by which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS = 30000; + + /** + * The interval in milliseconds between invocations of + * {@link MediaSource.Listener#onSourceInfoRefreshed(Timeline, Object)} when the source's + * {@link Timeline} is changing dynamically (for example, for incomplete live streams). + */ + private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000; + /** + * The minimum default start position for live streams, relative to the start of the live window. + */ + private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000; + + private static final String TAG = "DashMediaSource"; + + private final boolean sideloadedManifest; + private final DataSource.Factory manifestDataSourceFactory; + private final DashChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final long livePresentationDelayMs; + private final EventDispatcher eventDispatcher; + private final DashManifestParser manifestParser; + private final ManifestCallback manifestCallback; + private final Object manifestUriLock; + private final SparseArray periodsById; + private final Runnable refreshManifestRunnable; + private final Runnable simulateManifestRefreshRunnable; + + private MediaSource.Listener sourceListener; + private DataSource dataSource; + private Loader loader; + private LoaderErrorThrower loaderErrorThrower; + + private Uri manifestUri; + private long manifestLoadStartTimestamp; + private long manifestLoadEndTimestamp; + private DashManifest manifest; + private Handler handler; + private long elapsedRealtimeOffsetMs; + + private int firstPeriodId; + + /** + * Constructs an instance to play a given {@link DashManifest}, which must be static. + * + * @param manifest The manifest. {@link DashManifest#dynamic} must be false. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory, + Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { + this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, + eventListener); + } + + /** + * Constructs an instance to play a given {@link DashManifest}, which must be static. + * + * @param manifest The manifest. {@link DashManifest#dynamic} must be false. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(DashManifest manifest, DashChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, Handler eventHandler, AdaptiveMediaSourceEventListener + eventListener) { + this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + DashChunkSource.Factory chunkSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, + DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS, + eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. Use + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by + * the manifest, if present. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, new DashManifestParser(), chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be dynamic or + * static. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param manifestParser A parser for loaded manifest data. + * @param chunkSourceFactory A factory for {@link DashChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. Use + * {@link #DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS} to use the value specified by + * the manifest, if present. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public DashMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + DashManifestParser manifestParser, DashChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + private DashMediaSource(DashManifest manifest, Uri manifestUri, + DataSource.Factory manifestDataSourceFactory, DashManifestParser manifestParser, + DashChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this.manifest = manifest; + this.manifestUri = manifestUri; + this.manifestDataSourceFactory = manifestDataSourceFactory; + this.manifestParser = manifestParser; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.livePresentationDelayMs = livePresentationDelayMs; + sideloadedManifest = manifest != null; + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + manifestUriLock = new Object(); + periodsById = new SparseArray<>(); + if (sideloadedManifest) { + Assertions.checkState(!manifest.dynamic); + manifestCallback = null; + refreshManifestRunnable = null; + simulateManifestRefreshRunnable = null; + } else { + manifestCallback = new ManifestCallback(); + refreshManifestRunnable = new Runnable() { + @Override + public void run() { + startLoadingManifest(); + } + }; + simulateManifestRefreshRunnable = new Runnable() { + @Override + public void run() { + processManifest(false); + } + }; + } + } + + /** + * Manually replaces the manifest {@link Uri}. + * + * @param manifestUri The replacement manifest {@link Uri}. + */ + public void replaceManifestUri(Uri manifestUri) { + synchronized (manifestUriLock) { + this.manifestUri = manifestUri; + } + } + + // MediaSource implementation. + + @Override + public void prepareSource(MediaSource.Listener listener) { + sourceListener = listener; + if (sideloadedManifest) { + loaderErrorThrower = new LoaderErrorThrower.Dummy(); + processManifest(false); + } else { + dataSource = manifestDataSourceFactory.createDataSource(); + loader = new Loader("Loader:DashMediaSource"); + loaderErrorThrower = loader; + handler = new Handler(); + startLoadingManifest(); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + loaderErrorThrower.maybeThrowError(); + } + + @Override + public MediaPeriod createPeriod(int periodIndex, Allocator allocator, long positionUs) { + EventDispatcher periodEventDispatcher = eventDispatcher.copyWithMediaTimeOffsetMs( + manifest.getPeriod(periodIndex).startMs); + DashMediaPeriod mediaPeriod = new DashMediaPeriod(firstPeriodId + periodIndex, manifest, + periodIndex, chunkSourceFactory, minLoadableRetryCount, periodEventDispatcher, + elapsedRealtimeOffsetMs, loaderErrorThrower, allocator); + periodsById.put(mediaPeriod.id, mediaPeriod); + return mediaPeriod; + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + DashMediaPeriod dashMediaPeriod = (DashMediaPeriod) mediaPeriod; + dashMediaPeriod.release(); + periodsById.remove(dashMediaPeriod.id); + } + + @Override + public void releaseSource() { + dataSource = null; + loaderErrorThrower = null; + if (loader != null) { + loader.release(); + loader = null; + } + manifestLoadStartTimestamp = 0; + manifestLoadEndTimestamp = 0; + manifest = null; + if (handler != null) { + handler.removeCallbacksAndMessages(null); + handler = null; + } + elapsedRealtimeOffsetMs = 0; + periodsById.clear(); + } + + // Loadable callbacks. + + /* package */ void onManifestLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + DashManifest newManifest = loadable.getResult(); + + int periodCount = manifest == null ? 0 : manifest.getPeriodCount(); + int removedPeriodCount = 0; + long newFirstPeriodStartTimeMs = newManifest.getPeriod(0).startMs; + while (removedPeriodCount < periodCount + && manifest.getPeriod(removedPeriodCount).startMs < newFirstPeriodStartTimeMs) { + removedPeriodCount++; + } + + // After discarding old periods, we should never have more periods than listed in the new + // manifest. That would mean that a previously announced period is no longer advertised. If + // this condition occurs, assume that we are hitting a manifest server that is out of sync and + // behind, discard this manifest, and try again later. + if (periodCount - removedPeriodCount > newManifest.getPeriodCount()) { + Log.w(TAG, "Out of sync manifest"); + scheduleManifestRefresh(); + return; + } + + manifest = newManifest; + manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs; + manifestLoadEndTimestamp = elapsedRealtimeMs; + if (manifest.location != null) { + synchronized (manifestUriLock) { + // This condition checks that replaceManifestUri wasn't called between the start and end of + // this load. If it was, we ignore the manifest location and prefer the manual replacement. + if (loadable.dataSpec.uri == manifestUri) { + manifestUri = manifest.location; + } + } + } + + if (periodCount == 0) { + if (manifest.utcTiming != null) { + resolveUtcTimingElement(manifest.utcTiming); + } else { + processManifest(true); + } + } else { + firstPeriodId += removedPeriodCount; + processManifest(true); + } + } + + /* package */ int onManifestLoadError(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + /* package */ void onUtcTimestampLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + onUtcTimestampResolved(loadable.getResult() - elapsedRealtimeMs); + } + + /* package */ int onUtcTimestampLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, true); + onUtcTimestampResolutionError(error); + return Loader.DONT_RETRY; + } + + /* package */ void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + // Internal methods. + + private void startLoadingManifest() { + Uri manifestUri; + synchronized (manifestUriLock) { + manifestUri = this.manifestUri; + } + startLoading(new ParsingLoadable<>(dataSource, manifestUri, C.DATA_TYPE_MANIFEST, + manifestParser), manifestCallback, minLoadableRetryCount); + } + + private void resolveUtcTimingElement(UtcTimingElement timingElement) { + String scheme = timingElement.schemeIdUri; + if (Util.areEqual(scheme, "urn:mpeg:dash:utc:direct:2012")) { + resolveUtcTimingElementDirect(timingElement); + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-iso:2014")) { + resolveUtcTimingElementHttp(timingElement, new Iso8601Parser()); + } else if (Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2012") + || Util.areEqual(scheme, "urn:mpeg:dash:utc:http-xsdate:2014")) { + resolveUtcTimingElementHttp(timingElement, new XsDateTimeParser()); + } else { + // Unsupported scheme. + onUtcTimestampResolutionError(new IOException("Unsupported UTC timing scheme")); + } + } + + private void resolveUtcTimingElementDirect(UtcTimingElement timingElement) { + try { + long utcTimestamp = Util.parseXsDateTime(timingElement.value); + onUtcTimestampResolved(utcTimestamp - manifestLoadEndTimestamp); + } catch (ParserException e) { + onUtcTimestampResolutionError(e); + } + } + + private void resolveUtcTimingElementHttp(UtcTimingElement timingElement, + ParsingLoadable.Parser parser) { + startLoading(new ParsingLoadable<>(dataSource, Uri.parse(timingElement.value), + C.DATA_TYPE_TIME_SYNCHRONIZATION, parser), new UtcTimestampCallback(), 1); + } + + private void onUtcTimestampResolved(long elapsedRealtimeOffsetMs) { + this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; + processManifest(true); + } + + private void onUtcTimestampResolutionError(IOException error) { + Log.e(TAG, "Failed to resolve UtcTiming element.", error); + // Be optimistic and continue in the hope that the device clock is correct. + processManifest(true); + } + + private void processManifest(boolean scheduleRefresh) { + // Update any periods. + for (int i = 0; i < periodsById.size(); i++) { + int id = periodsById.keyAt(i); + if (id >= firstPeriodId) { + periodsById.valueAt(i).updateManifest(manifest, id - firstPeriodId); + } else { + // This period has been removed from the manifest so it doesn't need to be updated. + } + } + // Update the window. + boolean windowChangingImplicitly = false; + int lastPeriodIndex = manifest.getPeriodCount() - 1; + PeriodSeekInfo firstPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo(manifest.getPeriod(0), + manifest.getPeriodDurationUs(0)); + PeriodSeekInfo lastPeriodSeekInfo = PeriodSeekInfo.createPeriodSeekInfo( + manifest.getPeriod(lastPeriodIndex), manifest.getPeriodDurationUs(lastPeriodIndex)); + // Get the period-relative start/end times. + long currentStartTimeUs = firstPeriodSeekInfo.availableStartTimeUs; + long currentEndTimeUs = lastPeriodSeekInfo.availableEndTimeUs; + if (manifest.dynamic && !lastPeriodSeekInfo.isIndexExplicit) { + // The manifest describes an incomplete live stream. Update the start/end times to reflect the + // live stream duration and the manifest's time shift buffer depth. + long liveStreamDurationUs = getNowUnixTimeUs() - C.msToUs(manifest.availabilityStartTime); + long liveStreamEndPositionInLastPeriodUs = liveStreamDurationUs + - C.msToUs(manifest.getPeriod(lastPeriodIndex).startMs); + currentEndTimeUs = Math.min(liveStreamEndPositionInLastPeriodUs, currentEndTimeUs); + if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { + long timeShiftBufferDepthUs = C.msToUs(manifest.timeShiftBufferDepth); + long offsetInPeriodUs = currentEndTimeUs - timeShiftBufferDepthUs; + int periodIndex = lastPeriodIndex; + while (offsetInPeriodUs < 0 && periodIndex > 0) { + offsetInPeriodUs += manifest.getPeriodDurationUs(--periodIndex); + } + if (periodIndex == 0) { + currentStartTimeUs = Math.max(currentStartTimeUs, offsetInPeriodUs); + } else { + // The time shift buffer starts after the earliest period. + // TODO: Does this ever happen? + currentStartTimeUs = manifest.getPeriodDurationUs(0); + } + } + windowChangingImplicitly = true; + } + long windowDurationUs = currentEndTimeUs - currentStartTimeUs; + for (int i = 0; i < manifest.getPeriodCount() - 1; i++) { + windowDurationUs += manifest.getPeriodDurationUs(i); + } + long windowDefaultStartPositionUs = 0; + if (manifest.dynamic) { + long presentationDelayForManifestMs = livePresentationDelayMs; + if (presentationDelayForManifestMs == DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS) { + presentationDelayForManifestMs = manifest.suggestedPresentationDelay != C.TIME_UNSET + ? manifest.suggestedPresentationDelay : DEFAULT_LIVE_PRESENTATION_DELAY_FIXED_MS; + } + // Snap the default position to the start of the segment containing it. + windowDefaultStartPositionUs = windowDurationUs - C.msToUs(presentationDelayForManifestMs); + if (windowDefaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { + // The default start position is too close to the start of the live window. Set it to the + // minimum default start position provided the window is at least twice as big. Else set + // it to the middle of the window. + windowDefaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, + windowDurationUs / 2); + } + } + long windowStartTimeMs = manifest.availabilityStartTime + + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs); + DashTimeline timeline = new DashTimeline(manifest.availabilityStartTime, windowStartTimeMs, + firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, + manifest); + sourceListener.onSourceInfoRefreshed(timeline, manifest); + + if (!sideloadedManifest) { + // Remove any pending simulated refresh. + handler.removeCallbacks(simulateManifestRefreshRunnable); + // If the window is changing implicitly, post a simulated manifest refresh to update it. + if (windowChangingImplicitly) { + handler.postDelayed(simulateManifestRefreshRunnable, NOTIFY_MANIFEST_INTERVAL_MS); + } + // Schedule an explicit refresh if needed. + if (scheduleRefresh) { + scheduleManifestRefresh(); + } + } + } + + private void scheduleManifestRefresh() { + if (!manifest.dynamic) { + return; + } + long minUpdatePeriod = manifest.minUpdatePeriod; + if (minUpdatePeriod == 0) { + // TODO: This is a temporary hack to avoid constantly refreshing the MPD in cases where + // minUpdatePeriod is set to 0. In such cases we shouldn't refresh unless there is explicit + // signaling in the stream, according to: + // http://azure.microsoft.com/blog/2014/09/13/dash-live-streaming-with-azure-media-service/ + minUpdatePeriod = 5000; + } + long nextLoadTimestamp = manifestLoadStartTimestamp + minUpdatePeriod; + long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime()); + handler.postDelayed(refreshManifestRunnable, delayUntilNextLoad); + } + + private void startLoading(ParsingLoadable loadable, + Loader.Callback> callback, int minRetryCount) { + long elapsedRealtimeMs = loader.startLoading(loadable, callback, minRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); + } + + private long getNowUnixTimeUs() { + if (elapsedRealtimeOffsetMs != 0) { + return C.msToUs(SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs); + } else { + return C.msToUs(System.currentTimeMillis()); + } + } + + private static final class PeriodSeekInfo { + + public static PeriodSeekInfo createPeriodSeekInfo( + org.telegram.messenger.exoplayer2.source.dash.manifest.Period period, long durationUs) { + int adaptationSetCount = period.adaptationSets.size(); + long availableStartTimeUs = 0; + long availableEndTimeUs = Long.MAX_VALUE; + boolean isIndexExplicit = false; + for (int i = 0; i < adaptationSetCount; i++) { + DashSegmentIndex index = period.adaptationSets.get(i).representations.get(0).getIndex(); + if (index == null) { + return new PeriodSeekInfo(true, 0, durationUs); + } + int firstSegmentNum = index.getFirstSegmentNum(); + int lastSegmentNum = index.getLastSegmentNum(durationUs); + isIndexExplicit |= index.isExplicit(); + long adaptationSetAvailableStartTimeUs = index.getTimeUs(firstSegmentNum); + availableStartTimeUs = Math.max(availableStartTimeUs, adaptationSetAvailableStartTimeUs); + if (lastSegmentNum != DashSegmentIndex.INDEX_UNBOUNDED) { + long adaptationSetAvailableEndTimeUs = index.getTimeUs(lastSegmentNum) + + index.getDurationUs(lastSegmentNum, durationUs); + availableEndTimeUs = Math.min(availableEndTimeUs, adaptationSetAvailableEndTimeUs); + } else { + // The available end time is unmodified, because this index is unbounded. + } + } + return new PeriodSeekInfo(isIndexExplicit, availableStartTimeUs, availableEndTimeUs); + } + + public final boolean isIndexExplicit; + public final long availableStartTimeUs; + public final long availableEndTimeUs; + + private PeriodSeekInfo(boolean isIndexExplicit, long availableStartTimeUs, + long availableEndTimeUs) { + this.isIndexExplicit = isIndexExplicit; + this.availableStartTimeUs = availableStartTimeUs; + this.availableEndTimeUs = availableEndTimeUs; + } + + } + + private static final class DashTimeline extends Timeline { + + private final long presentationStartTimeMs; + private final long windowStartTimeMs; + + private final int firstPeriodId; + private final long offsetInFirstPeriodUs; + private final long windowDurationUs; + private final long windowDefaultStartPositionUs; + private final DashManifest manifest; + + public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, + int firstPeriodId, long offsetInFirstPeriodUs, long windowDurationUs, + long windowDefaultStartPositionUs, DashManifest manifest) { + this.presentationStartTimeMs = presentationStartTimeMs; + this.windowStartTimeMs = windowStartTimeMs; + this.firstPeriodId = firstPeriodId; + this.offsetInFirstPeriodUs = offsetInFirstPeriodUs; + this.windowDurationUs = windowDurationUs; + this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; + this.manifest = manifest; + } + + @Override + public int getPeriodCount() { + return manifest.getPeriodCount(); + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIdentifiers) { + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()); + Object id = setIdentifiers ? manifest.getPeriod(periodIndex).id : null; + Object uid = setIdentifiers ? firstPeriodId + + Assertions.checkIndex(periodIndex, 0, manifest.getPeriodCount()) : null; + return period.set(id, uid, 0, manifest.getPeriodDurationUs(periodIndex), + C.msToUs(manifest.getPeriod(periodIndex).startMs - manifest.getPeriod(0).startMs) + - offsetInFirstPeriodUs); + } + + @Override + public int getWindowCount() { + return 1; + } + + @Override + public Window getWindow(int windowIndex, Window window, boolean setIdentifier, + long defaultPositionProjectionUs) { + Assertions.checkIndex(windowIndex, 0, 1); + long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( + defaultPositionProjectionUs); + return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */, + manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0, + manifest.getPeriodCount() - 1, offsetInFirstPeriodUs); + } + + @Override + public int getIndexOfPeriod(Object uid) { + if (!(uid instanceof Integer)) { + return C.INDEX_UNSET; + } + int periodId = (int) uid; + return periodId < firstPeriodId || periodId >= firstPeriodId + getPeriodCount() + ? C.INDEX_UNSET : (periodId - firstPeriodId); + } + + private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) { + long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; + if (!manifest.dynamic) { + return windowDefaultStartPositionUs; + } + if (defaultPositionProjectionUs > 0) { + windowDefaultStartPositionUs += defaultPositionProjectionUs; + if (windowDefaultStartPositionUs > windowDurationUs) { + // The projection takes us beyond the end of the live window. + return C.TIME_UNSET; + } + } + // Attempt to snap to the start of the corresponding video segment. + int periodIndex = 0; + long defaultStartPositionInPeriodUs = offsetInFirstPeriodUs + windowDefaultStartPositionUs; + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + while (periodIndex < manifest.getPeriodCount() - 1 + && defaultStartPositionInPeriodUs >= periodDurationUs) { + defaultStartPositionInPeriodUs -= periodDurationUs; + periodIndex++; + periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + } + org.telegram.messenger.exoplayer2.source.dash.manifest.Period period = + manifest.getPeriod(periodIndex); + int videoAdaptationSetIndex = period.getAdaptationSetIndex(C.TRACK_TYPE_VIDEO); + if (videoAdaptationSetIndex == C.INDEX_UNSET) { + // No video adaptation set for snapping. + return windowDefaultStartPositionUs; + } + // If there are multiple video adaptation sets with unaligned segments, the initial time may + // not correspond to the start of a segment in both, but this is an edge case. + DashSegmentIndex snapIndex = period.adaptationSets.get(videoAdaptationSetIndex) + .representations.get(0).getIndex(); + if (snapIndex == null) { + // Video adaptation set does not include an index for snapping. + return windowDefaultStartPositionUs; + } + int segmentNum = snapIndex.getSegmentNum(defaultStartPositionInPeriodUs, periodDurationUs); + return windowDefaultStartPositionUs + snapIndex.getTimeUs(segmentNum) + - defaultStartPositionInPeriodUs; + } + + } + + private final class ManifestCallback implements + Loader.Callback> { + + @Override + public void onLoadCompleted(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs) { + onManifestLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, boolean released) { + DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public int onLoadError(ParsingLoadable loadable, + long elapsedRealtimeMs, long loadDurationMs, IOException error) { + return onManifestLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); + } + + } + + private final class UtcTimestampCallback implements Loader.Callback> { + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + onUtcTimestampLoadCompleted(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + DashMediaSource.this.onLoadCanceled(loadable, elapsedRealtimeMs, loadDurationMs); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + return onUtcTimestampLoadError(loadable, elapsedRealtimeMs, loadDurationMs, error); + } + + } + + private static final class XsDateTimeParser implements ParsingLoadable.Parser { + + @Override + public Long parse(Uri uri, InputStream inputStream) throws IOException { + String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); + return Util.parseXsDateTime(firstLine); + } + + } + + private static final class Iso8601Parser implements ParsingLoadable.Parser { + + @Override + public Long parse(Uri uri, InputStream inputStream) throws IOException { + String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine(); + try { + // TODO: It may be necessary to handle timestamp offsets from UTC. + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format.parse(firstLine).getTime(); + } catch (ParseException e) { + throw new ParserException(e); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashSegmentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashSegmentIndex.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashSegmentIndex.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashSegmentIndex.java index 78535569c6b..43e8ff78bfc 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashSegmentIndex.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashSegmentIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash; +package org.telegram.messenger.exoplayer2.source.dash; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.dash.mpd.RangedUri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.source.dash.manifest.RangedUri; /** * Indexes the segments within a media stream. - * - * TODO: Generalize to cover all chunk streaming modes (e.g. SmoothStreaming) if possible. */ public interface DashSegmentIndex { @@ -37,7 +35,7 @@ public interface DashSegmentIndex { * * @param timeUs The time in microseconds. * @param periodDurationUs The duration of the enclosing period in microseconds, or - * {@link C#UNKNOWN_TIME_US} if the period's duration is not yet known. + * {@link C#TIME_UNSET} if the period's duration is not yet known. * @return The segment number of the corresponding segment. */ int getSegmentNum(long timeUs, long periodDurationUs); @@ -55,7 +53,7 @@ public interface DashSegmentIndex { * * @param segmentNum The segment number. * @param periodDurationUs The duration of the enclosing period in microseconds, or - * {@link C#UNKNOWN_TIME_US} if the period's duration is not yet known. + * {@link C#TIME_UNSET} if the period's duration is not yet known. * @return The duration of the segment, in microseconds. */ long getDurationUs(int segmentNum, long periodDurationUs); @@ -83,7 +81,7 @@ public interface DashSegmentIndex { * must manually determine the window of currently available segments. * * @param periodDurationUs The duration of the enclosing period in microseconds, or - * {@link C#UNKNOWN_TIME_US} if the period's duration is not yet known. + * {@link C#TIME_UNSET} if the period's duration is not yet known. * @return The segment number of the last segment, or {@link #INDEX_UNBOUNDED}. */ int getLastSegmentNum(long periodDurationUs); @@ -98,7 +96,7 @@ public interface DashSegmentIndex { * segment duration. If the presentation is dynamic, it's possible that only a subset of the * segments are available. * - * @return True if segments are defined explicitly by the index. False otherwise. + * @return Whether segments are defined explicitly by the index. */ boolean isExplicit(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashWrappingSegmentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashWrappingSegmentIndex.java similarity index 82% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashWrappingSegmentIndex.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashWrappingSegmentIndex.java index 349e184d6e8..a04adfc86b5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/DashWrappingSegmentIndex.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DashWrappingSegmentIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash; +package org.telegram.messenger.exoplayer2.source.dash; -import org.telegram.messenger.exoplayer.dash.mpd.RangedUri; -import org.telegram.messenger.exoplayer.extractor.ChunkIndex; +import org.telegram.messenger.exoplayer2.extractor.ChunkIndex; +import org.telegram.messenger.exoplayer2.source.dash.manifest.RangedUri; /** * An implementation of {@link DashSegmentIndex} that wraps a {@link ChunkIndex} parsed from a @@ -25,7 +25,6 @@ /* package */ final class DashWrappingSegmentIndex implements DashSegmentIndex { private final ChunkIndex chunkIndex; - private final String uri; /** * @param chunkIndex The {@link ChunkIndex} to wrap. @@ -33,7 +32,6 @@ */ public DashWrappingSegmentIndex(ChunkIndex chunkIndex, String uri) { this.chunkIndex = chunkIndex; - this.uri = uri; } @Override @@ -58,7 +56,7 @@ public long getDurationUs(int segmentNum, long periodDurationUs) { @Override public RangedUri getSegmentUrl(int segmentNum) { - return new RangedUri(uri, null, chunkIndex.offsets[segmentNum], chunkIndex.sizes[segmentNum]); + return new RangedUri(null, chunkIndex.offsets[segmentNum], chunkIndex.sizes[segmentNum]); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java new file mode 100755 index 00000000000..1a74804828e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -0,0 +1,478 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash; + +import android.net.Uri; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.ChunkIndex; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.mkv.MatroskaExtractor; +import org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import org.telegram.messenger.exoplayer2.extractor.rawcc.RawCcExtractor; +import org.telegram.messenger.exoplayer2.source.BehindLiveWindowException; +import org.telegram.messenger.exoplayer2.source.chunk.Chunk; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkHolder; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import org.telegram.messenger.exoplayer2.source.chunk.ContainerMediaChunk; +import org.telegram.messenger.exoplayer2.source.chunk.InitializationChunk; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import org.telegram.messenger.exoplayer2.source.chunk.SingleSampleMediaChunk; +import org.telegram.messenger.exoplayer2.source.dash.manifest.DashManifest; +import org.telegram.messenger.exoplayer2.source.dash.manifest.RangedUri; +import org.telegram.messenger.exoplayer2.source.dash.manifest.Representation; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.List; + +/** + * A default {@link DashChunkSource} implementation. + */ +public class DefaultDashChunkSource implements DashChunkSource { + + public static final class Factory implements DashChunkSource.Factory { + + private final DataSource.Factory dataSourceFactory; + private final int maxSegmentsPerLoad; + + public Factory(DataSource.Factory dataSourceFactory) { + this(dataSourceFactory, 1); + } + + public Factory(DataSource.Factory dataSourceFactory, int maxSegmentsPerLoad) { + this.dataSourceFactory = dataSourceFactory; + this.maxSegmentsPerLoad = maxSegmentsPerLoad; + } + + @Override + public DashChunkSource createDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, + TrackSelection trackSelection, long elapsedRealtimeOffsetMs) { + DataSource dataSource = dataSourceFactory.createDataSource(); + return new DefaultDashChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, + adaptationSetIndex, trackSelection, dataSource, elapsedRealtimeOffsetMs, + maxSegmentsPerLoad); + } + + } + + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int adaptationSetIndex; + private final TrackSelection trackSelection; + private final RepresentationHolder[] representationHolders; + private final DataSource dataSource; + private final long elapsedRealtimeOffsetMs; + private final int maxSegmentsPerLoad; + + private DashManifest manifest; + private int periodIndex; + + private IOException fatalError; + private boolean missingLastSegment; + + /** + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param periodIndex The index of the period in the manifest. + * @param adaptationSetIndex The index of the adaptation set in the period. + * @param trackSelection The track selection. + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between + * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified + * as the server's unix time minus the local elapsed time. If unknown, set to 0. + * @param maxSegmentsPerLoad The maximum number of segments to combine into a single request. + * Note that segments will only be combined if their {@link Uri}s are the same and if their + * data ranges are adjacent. + */ + public DefaultDashChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + DashManifest manifest, int periodIndex, int adaptationSetIndex, TrackSelection trackSelection, + DataSource dataSource, long elapsedRealtimeOffsetMs, int maxSegmentsPerLoad) { + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.manifest = manifest; + this.adaptationSetIndex = adaptationSetIndex; + this.trackSelection = trackSelection; + this.dataSource = dataSource; + this.periodIndex = periodIndex; + this.elapsedRealtimeOffsetMs = elapsedRealtimeOffsetMs; + this.maxSegmentsPerLoad = maxSegmentsPerLoad; + + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + List representations = getRepresentations(); + representationHolders = new RepresentationHolder[trackSelection.length()]; + for (int i = 0; i < representationHolders.length; i++) { + Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); + representationHolders[i] = new RepresentationHolder(periodDurationUs, representation); + } + } + + @Override + public void updateManifest(DashManifest newManifest, int newPeriodIndex) { + try { + manifest = newManifest; + periodIndex = newPeriodIndex; + long periodDurationUs = manifest.getPeriodDurationUs(periodIndex); + List representations = getRepresentations(); + for (int i = 0; i < representationHolders.length; i++) { + Representation representation = representations.get(trackSelection.getIndexInTrackGroup(i)); + representationHolders[i].updateRepresentation(periodDurationUs, representation); + } + } catch (BehindLiveWindowException e) { + fatalError = e; + } + } + + @Override + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } else { + manifestLoaderErrorThrower.maybeThrowError(); + } + } + + @Override + public int getPreferredQueueSize(long playbackPositionUs, List queue) { + if (fatalError != null || trackSelection.length() < 2) { + return queue.size(); + } + return trackSelection.evaluateQueueSize(playbackPositionUs, queue); + } + + @Override + public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { + if (fatalError != null) { + return; + } + + long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; + trackSelection.updateSelectedTrack(bufferedDurationUs); + + RepresentationHolder representationHolder = + representationHolders[trackSelection.getSelectedIndex()]; + Representation selectedRepresentation = representationHolder.representation; + DashSegmentIndex segmentIndex = representationHolder.segmentIndex; + + RangedUri pendingInitializationUri = null; + RangedUri pendingIndexUri = null; + Format sampleFormat = representationHolder.sampleFormat; + if (sampleFormat == null) { + pendingInitializationUri = selectedRepresentation.getInitializationUri(); + } + if (segmentIndex == null) { + pendingIndexUri = selectedRepresentation.getIndexUri(); + } + if (pendingInitializationUri != null || pendingIndexUri != null) { + // We have initialization and/or index requests to make. + Chunk initializationChunk = newInitializationChunk(representationHolder, dataSource, + trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), pendingInitializationUri, pendingIndexUri); + out.chunk = initializationChunk; + return; + } + + long nowUs = getNowUnixTimeUs(); + int firstAvailableSegmentNum = representationHolder.getFirstSegmentNum(); + int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); + boolean indexUnbounded = lastAvailableSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED; + if (indexUnbounded) { + // The index is itself unbounded. We need to use the current time to calculate the range of + // available segments. + long liveEdgeTimeUs = nowUs - manifest.availabilityStartTime * 1000; + long periodStartUs = manifest.getPeriod(periodIndex).startMs * 1000; + long liveEdgeTimeInPeriodUs = liveEdgeTimeUs - periodStartUs; + if (manifest.timeShiftBufferDepth != C.TIME_UNSET) { + long bufferDepthUs = manifest.timeShiftBufferDepth * 1000; + firstAvailableSegmentNum = Math.max(firstAvailableSegmentNum, + representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs - bufferDepthUs)); + } + // getSegmentNum(liveEdgeTimestampUs) will not be completed yet, so subtract one to get the + // index of the last completed segment. + lastAvailableSegmentNum = representationHolder.getSegmentNum(liveEdgeTimeInPeriodUs) - 1; + } + + int segmentNum; + if (previous == null) { + segmentNum = Util.constrainValue(representationHolder.getSegmentNum(playbackPositionUs), + firstAvailableSegmentNum, lastAvailableSegmentNum); + } else { + segmentNum = previous.getNextChunkIndex(); + if (segmentNum < firstAvailableSegmentNum) { + // This is before the first chunk in the current manifest. + fatalError = new BehindLiveWindowException(); + return; + } + } + + if (segmentNum > lastAvailableSegmentNum + || (missingLastSegment && segmentNum >= lastAvailableSegmentNum)) { + // This is beyond the last chunk in the current manifest. + out.endOfStream = !manifest.dynamic || (periodIndex < manifest.getPeriodCount() - 1); + return; + } + + int maxSegmentCount = Math.min(maxSegmentsPerLoad, lastAvailableSegmentNum - segmentNum + 1); + Chunk nextMediaChunk = newMediaChunk(representationHolder, dataSource, + trackSelection.getSelectedFormat(), trackSelection.getSelectionReason(), + trackSelection.getSelectionData(), sampleFormat, segmentNum, maxSegmentCount); + out.chunk = nextMediaChunk; + } + + @Override + public void onChunkLoadCompleted(Chunk chunk) { + if (chunk instanceof InitializationChunk) { + InitializationChunk initializationChunk = (InitializationChunk) chunk; + RepresentationHolder representationHolder = + representationHolders[trackSelection.indexOf(initializationChunk.trackFormat)]; + Format sampleFormat = initializationChunk.getSampleFormat(); + if (sampleFormat != null) { + representationHolder.setSampleFormat(sampleFormat); + } + // The null check avoids overwriting an index obtained from the manifest with one obtained + // from the stream. If the manifest defines an index then the stream shouldn't, but in cases + // where it does we should ignore it. + if (representationHolder.segmentIndex == null) { + SeekMap seekMap = initializationChunk.getSeekMap(); + if (seekMap != null) { + representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap, + initializationChunk.dataSpec.uri.toString()); + } + } + } + } + + @Override + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + if (!cancelable) { + return false; + } + // Workaround for missing segment at the end of the period + if (!manifest.dynamic && chunk instanceof MediaChunk + && e instanceof InvalidResponseCodeException + && ((InvalidResponseCodeException) e).responseCode == 404) { + RepresentationHolder representationHolder = + representationHolders[trackSelection.indexOf(chunk.trackFormat)]; + int lastAvailableSegmentNum = representationHolder.getLastSegmentNum(); + if (((MediaChunk) chunk).getNextChunkIndex() > lastAvailableSegmentNum) { + missingLastSegment = true; + return true; + } + } + // Blacklist if appropriate. + return ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(chunk.trackFormat), e); + } + + // Private methods. + + private List getRepresentations() { + return manifest.getPeriod(periodIndex).adaptationSets.get(adaptationSetIndex).representations; + } + + private long getNowUnixTimeUs() { + if (elapsedRealtimeOffsetMs != 0) { + return (SystemClock.elapsedRealtime() + elapsedRealtimeOffsetMs) * 1000; + } else { + return System.currentTimeMillis() * 1000; + } + } + + private static Chunk newInitializationChunk(RepresentationHolder representationHolder, + DataSource dataSource, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, RangedUri initializationUri, RangedUri indexUri) { + RangedUri requestUri; + String baseUrl = representationHolder.representation.baseUrl; + if (initializationUri != null) { + // It's common for initialization and index data to be stored adjacently. Attempt to merge + // the two requests together to request both at once. + requestUri = initializationUri.attemptMerge(indexUri, baseUrl); + if (requestUri == null) { + requestUri = initializationUri; + } + } else { + requestUri = indexUri; + } + DataSpec dataSpec = new DataSpec(requestUri.resolveUri(baseUrl), requestUri.start, + requestUri.length, representationHolder.representation.getCacheKey()); + return new InitializationChunk(dataSource, dataSpec, trackFormat, + trackSelectionReason, trackSelectionData, representationHolder.extractorWrapper); + } + + private static Chunk newMediaChunk(RepresentationHolder representationHolder, + DataSource dataSource, Format trackFormat, int trackSelectionReason, + Object trackSelectionData, Format sampleFormat, int firstSegmentNum, int maxSegmentCount) { + Representation representation = representationHolder.representation; + long startTimeUs = representationHolder.getSegmentStartTimeUs(firstSegmentNum); + RangedUri segmentUri = representationHolder.getSegmentUrl(firstSegmentNum); + String baseUrl = representation.baseUrl; + if (representationHolder.extractorWrapper == null) { + long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum); + DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), + segmentUri.start, segmentUri.length, representation.getCacheKey()); + return new SingleSampleMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, trackFormat); + } else { + int segmentCount = 1; + for (int i = 1; i < maxSegmentCount; i++) { + RangedUri nextSegmentUri = representationHolder.getSegmentUrl(firstSegmentNum + i); + RangedUri mergedSegmentUri = segmentUri.attemptMerge(nextSegmentUri, baseUrl); + if (mergedSegmentUri == null) { + // Unable to merge segment fetches because the URIs do not merge. + break; + } + segmentUri = mergedSegmentUri; + segmentCount++; + } + long endTimeUs = representationHolder.getSegmentEndTimeUs(firstSegmentNum + segmentCount - 1); + DataSpec dataSpec = new DataSpec(segmentUri.resolveUri(baseUrl), + segmentUri.start, segmentUri.length, representation.getCacheKey()); + long sampleOffsetUs = -representation.presentationTimeOffsetUs; + return new ContainerMediaChunk(dataSource, dataSpec, trackFormat, trackSelectionReason, + trackSelectionData, startTimeUs, endTimeUs, firstSegmentNum, segmentCount, + sampleOffsetUs, representationHolder.extractorWrapper, sampleFormat); + } + } + + // Protected classes. + + protected static final class RepresentationHolder { + + public final ChunkExtractorWrapper extractorWrapper; + + public Representation representation; + public DashSegmentIndex segmentIndex; + public Format sampleFormat; + + private long periodDurationUs; + private int segmentNumShift; + + public RepresentationHolder(long periodDurationUs, Representation representation) { + this.periodDurationUs = periodDurationUs; + this.representation = representation; + String containerMimeType = representation.format.containerMimeType; + if (mimeTypeIsRawText(containerMimeType)) { + extractorWrapper = null; + } else { + boolean resendFormatOnInit = false; + Extractor extractor; + if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { + extractor = new RawCcExtractor(representation.format); + resendFormatOnInit = true; + } else if (mimeTypeIsWebm(containerMimeType)) { + extractor = new MatroskaExtractor(); + } else { + extractor = new FragmentedMp4Extractor(); + } + // Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream, + // as per DASH IF Interoperability Recommendations V3.0, 7.5.3. + extractorWrapper = new ChunkExtractorWrapper(extractor, + representation.format, true /* preferManifestDrmInitData */, + resendFormatOnInit); + } + segmentIndex = representation.getIndex(); + } + + public void setSampleFormat(Format sampleFormat) { + this.sampleFormat = sampleFormat; + } + + public void updateRepresentation(long newPeriodDurationUs, Representation newRepresentation) + throws BehindLiveWindowException{ + DashSegmentIndex oldIndex = representation.getIndex(); + DashSegmentIndex newIndex = newRepresentation.getIndex(); + + periodDurationUs = newPeriodDurationUs; + representation = newRepresentation; + if (oldIndex == null) { + // Segment numbers cannot shift if the index isn't defined by the manifest. + return; + } + + segmentIndex = newIndex; + if (!oldIndex.isExplicit()) { + // Segment numbers cannot shift if the index isn't explicit. + return; + } + + int oldIndexLastSegmentNum = oldIndex.getLastSegmentNum(periodDurationUs); + long oldIndexEndTimeUs = oldIndex.getTimeUs(oldIndexLastSegmentNum) + + oldIndex.getDurationUs(oldIndexLastSegmentNum, periodDurationUs); + int newIndexFirstSegmentNum = newIndex.getFirstSegmentNum(); + long newIndexStartTimeUs = newIndex.getTimeUs(newIndexFirstSegmentNum); + if (oldIndexEndTimeUs == newIndexStartTimeUs) { + // The new index continues where the old one ended, with no overlap. + segmentNumShift += oldIndex.getLastSegmentNum(periodDurationUs) + 1 + - newIndexFirstSegmentNum; + } else if (oldIndexEndTimeUs < newIndexStartTimeUs) { + // There's a gap between the old index and the new one which means we've slipped behind the + // live window and can't proceed. + throw new BehindLiveWindowException(); + } else { + // The new index overlaps with the old one. + segmentNumShift += oldIndex.getSegmentNum(newIndexStartTimeUs, periodDurationUs) + - newIndexFirstSegmentNum; + } + } + + public int getFirstSegmentNum() { + return segmentIndex.getFirstSegmentNum() + segmentNumShift; + } + + public int getLastSegmentNum() { + int lastSegmentNum = segmentIndex.getLastSegmentNum(periodDurationUs); + if (lastSegmentNum == DashSegmentIndex.INDEX_UNBOUNDED) { + return DashSegmentIndex.INDEX_UNBOUNDED; + } + return lastSegmentNum + segmentNumShift; + } + + public long getSegmentStartTimeUs(int segmentNum) { + return segmentIndex.getTimeUs(segmentNum - segmentNumShift); + } + + public long getSegmentEndTimeUs(int segmentNum) { + return getSegmentStartTimeUs(segmentNum) + + segmentIndex.getDurationUs(segmentNum - segmentNumShift, periodDurationUs); + } + + public int getSegmentNum(long positionUs) { + return segmentIndex.getSegmentNum(positionUs, periodDurationUs) + segmentNumShift; + } + + public RangedUri getSegmentUrl(int segmentNum) { + return segmentIndex.getSegmentUrl(segmentNum - segmentNumShift); + } + + private static boolean mimeTypeIsWebm(String mimeType) { + return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM) + || mimeType.startsWith(MimeTypes.APPLICATION_WEBM); + } + + private static boolean mimeTypeIsRawText(String mimeType) { + return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java new file mode 100755 index 00000000000..cc7cedcb8d1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/AdaptationSet.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import java.util.Collections; +import java.util.List; + +/** + * Represents a set of interchangeable encoded versions of a media content component. + */ +public class AdaptationSet { + + public static final int UNSET_ID = -1; + + public final int id; + + public final int type; + + public final List representations; + + public AdaptationSet(int id, int type, List representations) { + this.id = id; + this.type = type; + this.representations = Collections.unmodifiableList(representations); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java new file mode 100755 index 00000000000..4b6a0afe7bf --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import java.util.Collections; +import java.util.List; + +/** + * Represents a DASH media presentation description (mpd). + */ +public class DashManifest { + + public final long availabilityStartTime; + + public final long duration; + + public final long minBufferTime; + + public final boolean dynamic; + + public final long minUpdatePeriod; + + public final long timeShiftBufferDepth; + + public final long suggestedPresentationDelay; + + public final UtcTimingElement utcTiming; + + public final Uri location; + + private final List periods; + + public DashManifest(long availabilityStartTime, long duration, long minBufferTime, + boolean dynamic, long minUpdatePeriod, long timeShiftBufferDepth, + long suggestedPresentationDelay, UtcTimingElement utcTiming, Uri location, + List periods) { + this.availabilityStartTime = availabilityStartTime; + this.duration = duration; + this.minBufferTime = minBufferTime; + this.dynamic = dynamic; + this.minUpdatePeriod = minUpdatePeriod; + this.timeShiftBufferDepth = timeShiftBufferDepth; + this.suggestedPresentationDelay = suggestedPresentationDelay; + this.utcTiming = utcTiming; + this.location = location; + this.periods = periods == null ? Collections.emptyList() : periods; + } + + public final int getPeriodCount() { + return periods.size(); + } + + public final Period getPeriod(int index) { + return periods.get(index); + } + + public final long getPeriodDurationMs(int index) { + return index == periods.size() - 1 + ? (duration == C.TIME_UNSET ? C.TIME_UNSET : (duration - periods.get(index).startMs)) + : (periods.get(index + 1).startMs - periods.get(index).startMs); + } + + public final long getPeriodDurationUs(int index) { + return C.msToUs(getPeriodDurationMs(index)); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java new file mode 100755 index 00000000000..2aa6068ac83 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -0,0 +1,864 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.extractor.mp4.PsshAtomUtil; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.SegmentList; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.SegmentTemplate; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.SegmentTimelineElement; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.UriUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import org.telegram.messenger.exoplayer2.util.XmlPullParserUtil; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.xml.sax.helpers.DefaultHandler; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * A parser of media presentation description files. + */ +public class DashManifestParser extends DefaultHandler + implements ParsingLoadable.Parser { + + private static final String TAG = "MpdParser"; + + private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); + + private static final Pattern CEA_608_ACCESSIBILITY_PATTERN = Pattern.compile("CC([1-4])=.*"); + private static final Pattern CEA_708_ACCESSIBILITY_PATTERN = + Pattern.compile("([1-9]|[1-5][0-9]|6[0-3])=.*"); + + private final String contentId; + private final XmlPullParserFactory xmlParserFactory; + + /** + * Equivalent to calling {@code new DashManifestParser(null)}. + */ + public DashManifestParser() { + this(null); + } + + /** + * @param contentId An optional content identifier to include in the parsed manifest. + */ + public DashManifestParser(String contentId) { + this.contentId = contentId; + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + // MPD parsing. + + @Override + public DashManifest parse(Uri uri, InputStream inputStream) throws IOException { + try { + XmlPullParser xpp = xmlParserFactory.newPullParser(); + xpp.setInput(inputStream, null); + int eventType = xpp.next(); + if (eventType != XmlPullParser.START_TAG || !"MPD".equals(xpp.getName())) { + throw new ParserException( + "inputStream does not contain a valid media presentation description"); + } + return parseMediaPresentationDescription(xpp, uri.toString()); + } catch (XmlPullParserException e) { + throw new ParserException(e); + } + } + + protected DashManifest parseMediaPresentationDescription(XmlPullParser xpp, + String baseUrl) throws XmlPullParserException, IOException { + long availabilityStartTime = parseDateTime(xpp, "availabilityStartTime", C.TIME_UNSET); + long durationMs = parseDuration(xpp, "mediaPresentationDuration", C.TIME_UNSET); + long minBufferTimeMs = parseDuration(xpp, "minBufferTime", C.TIME_UNSET); + String typeString = xpp.getAttributeValue(null, "type"); + boolean dynamic = typeString != null && typeString.equals("dynamic"); + long minUpdateTimeMs = dynamic ? parseDuration(xpp, "minimumUpdatePeriod", C.TIME_UNSET) + : C.TIME_UNSET; + long timeShiftBufferDepthMs = dynamic + ? parseDuration(xpp, "timeShiftBufferDepth", C.TIME_UNSET) : C.TIME_UNSET; + long suggestedPresentationDelayMs = dynamic + ? parseDuration(xpp, "suggestedPresentationDelay", C.TIME_UNSET) : C.TIME_UNSET; + UtcTimingElement utcTiming = null; + Uri location = null; + + List periods = new ArrayList<>(); + long nextPeriodStartMs = dynamic ? C.TIME_UNSET : 0; + boolean seenEarlyAccessPeriod = false; + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "UTCTiming")) { + utcTiming = parseUtcTiming(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "Location")) { + location = Uri.parse(xpp.nextText()); + } else if (XmlPullParserUtil.isStartTag(xpp, "Period") && !seenEarlyAccessPeriod) { + Pair periodWithDurationMs = parsePeriod(xpp, baseUrl, nextPeriodStartMs); + Period period = periodWithDurationMs.first; + if (period.startMs == C.TIME_UNSET) { + if (dynamic) { + // This is an early access period. Ignore it. All subsequent periods must also be + // early access. + seenEarlyAccessPeriod = true; + } else { + throw new ParserException("Unable to determine start of period " + periods.size()); + } + } else { + long periodDurationMs = periodWithDurationMs.second; + nextPeriodStartMs = periodDurationMs == C.TIME_UNSET ? C.TIME_UNSET + : (period.startMs + periodDurationMs); + periods.add(period); + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "MPD")); + + if (durationMs == C.TIME_UNSET) { + if (nextPeriodStartMs != C.TIME_UNSET) { + // If we know the end time of the final period, we can use it as the duration. + durationMs = nextPeriodStartMs; + } else if (!dynamic) { + throw new ParserException("Unable to determine duration of static manifest."); + } + } + + if (periods.isEmpty()) { + throw new ParserException("No periods found."); + } + + return buildMediaPresentationDescription(availabilityStartTime, durationMs, minBufferTimeMs, + dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, utcTiming, + location, periods); + } + + protected DashManifest buildMediaPresentationDescription(long availabilityStartTime, + long durationMs, long minBufferTimeMs, boolean dynamic, long minUpdateTimeMs, + long timeShiftBufferDepthMs, long suggestedPresentationDelayMs, UtcTimingElement utcTiming, + Uri location, List periods) { + return new DashManifest(availabilityStartTime, durationMs, minBufferTimeMs, + dynamic, minUpdateTimeMs, timeShiftBufferDepthMs, suggestedPresentationDelayMs, utcTiming, + location, periods); + } + + protected UtcTimingElement parseUtcTiming(XmlPullParser xpp) { + String schemeIdUri = xpp.getAttributeValue(null, "schemeIdUri"); + String value = xpp.getAttributeValue(null, "value"); + return buildUtcTimingElement(schemeIdUri, value); + } + + protected UtcTimingElement buildUtcTimingElement(String schemeIdUri, String value) { + return new UtcTimingElement(schemeIdUri, value); + } + + protected Pair parsePeriod(XmlPullParser xpp, String baseUrl, long defaultStartMs) + throws XmlPullParserException, IOException { + String id = xpp.getAttributeValue(null, "id"); + long startMs = parseDuration(xpp, "start", defaultStartMs); + long durationMs = parseDuration(xpp, "duration", C.TIME_UNSET); + SegmentBase segmentBase = null; + List adaptationSets = new ArrayList<>(); + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "AdaptationSet")) { + adaptationSets.add(parseAdaptationSet(xpp, baseUrl, segmentBase)); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, null); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, null); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, null); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "Period")); + + return Pair.create(buildPeriod(id, startMs, adaptationSets), durationMs); + } + + protected Period buildPeriod(String id, long startMs, List adaptationSets) { + return new Period(id, startMs, adaptationSets); + } + + // AdaptationSet parsing. + + protected AdaptationSet parseAdaptationSet(XmlPullParser xpp, String baseUrl, + SegmentBase segmentBase) throws XmlPullParserException, IOException { + int id = parseInt(xpp, "id", AdaptationSet.UNSET_ID); + int contentType = parseContentType(xpp); + + String mimeType = xpp.getAttributeValue(null, "mimeType"); + String codecs = xpp.getAttributeValue(null, "codecs"); + int width = parseInt(xpp, "width", Format.NO_VALUE); + int height = parseInt(xpp, "height", Format.NO_VALUE); + float frameRate = parseFrameRate(xpp, Format.NO_VALUE); + int audioChannels = Format.NO_VALUE; + int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE); + String language = xpp.getAttributeValue(null, "lang"); + int accessibilityChannel = Format.NO_VALUE; + ArrayList drmSchemeDatas = new ArrayList<>(); + List representationInfos = new ArrayList<>(); + + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { + SchemeData contentProtection = parseContentProtection(xpp); + if (contentProtection != null) { + drmSchemeDatas.add(contentProtection); + } + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentComponent")) { + language = checkLanguageConsistency(language, xpp.getAttributeValue(null, "lang")); + contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); + } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { + RepresentationInfo representationInfo = parseRepresentation(xpp, baseUrl, mimeType, codecs, + width, height, frameRate, audioChannels, audioSamplingRate, language, + accessibilityChannel, segmentBase); + contentType = checkContentTypeConsistency(contentType, + getContentType(representationInfo.format)); + representationInfos.add(representationInfo); + } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { + audioChannels = parseAudioChannelConfiguration(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { + accessibilityChannel = parseAccessibilityValue(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp)) { + parseAdaptationSetChild(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "AdaptationSet")); + + List representations = new ArrayList<>(representationInfos.size()); + for (int i = 0; i < representationInfos.size(); i++) { + representations.add(buildRepresentation(representationInfos.get(i), contentId, + drmSchemeDatas)); + } + + return buildAdaptationSet(id, contentType, representations); + } + + protected AdaptationSet buildAdaptationSet(int id, int contentType, + List representations) { + return new AdaptationSet(id, contentType, representations); + } + + protected int parseContentType(XmlPullParser xpp) { + String contentType = xpp.getAttributeValue(null, "contentType"); + return TextUtils.isEmpty(contentType) ? C.TRACK_TYPE_UNKNOWN + : MimeTypes.BASE_TYPE_AUDIO.equals(contentType) ? C.TRACK_TYPE_AUDIO + : MimeTypes.BASE_TYPE_VIDEO.equals(contentType) ? C.TRACK_TYPE_VIDEO + : MimeTypes.BASE_TYPE_TEXT.equals(contentType) ? C.TRACK_TYPE_TEXT + : C.TRACK_TYPE_UNKNOWN; + } + + protected int getContentType(Format format) { + String sampleMimeType = format.sampleMimeType; + if (TextUtils.isEmpty(sampleMimeType)) { + return C.TRACK_TYPE_UNKNOWN; + } else if (MimeTypes.isVideo(sampleMimeType)) { + return C.TRACK_TYPE_VIDEO; + } else if (MimeTypes.isAudio(sampleMimeType)) { + return C.TRACK_TYPE_AUDIO; + } else if (mimeTypeIsRawText(sampleMimeType) + || MimeTypes.APPLICATION_RAWCC.equals(format.containerMimeType)) { + return C.TRACK_TYPE_TEXT; + } + return C.TRACK_TYPE_UNKNOWN; + } + + /** + * Parses a ContentProtection element. + * + * @param xpp The parser from which to read. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + * @return {@link SchemeData} parsed from the ContentProtection element, or null if the element is + * unsupported. + */ + protected SchemeData parseContentProtection(XmlPullParser xpp) throws XmlPullParserException, + IOException { + byte[] data = null; + UUID uuid = null; + boolean seenPsshElement = false; + boolean requiresSecureDecoder = false; + do { + xpp.next(); + // The cenc:pssh element is defined in 23001-7:2015. + if (XmlPullParserUtil.isStartTag(xpp, "cenc:pssh") && xpp.next() == XmlPullParser.TEXT) { + seenPsshElement = true; + data = Base64.decode(xpp.getText(), Base64.DEFAULT); + uuid = PsshAtomUtil.parseUuid(data); + } else if (XmlPullParserUtil.isStartTag(xpp, "widevine:license")) { + String robustnessLevel = xpp.getAttributeValue(null, "robustness_level"); + requiresSecureDecoder = robustnessLevel != null && robustnessLevel.startsWith("HW"); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "ContentProtection")); + if (!seenPsshElement) { + return null; + } else if (uuid != null) { + return new SchemeData(uuid, MimeTypes.VIDEO_MP4, data, requiresSecureDecoder); + } else { + Log.w(TAG, "Skipped unsupported ContentProtection element"); + return null; + } + } + + /** + * Parses children of AdaptationSet elements not specifically parsed elsewhere. + * + * @param xpp The XmpPullParser from which the AdaptationSet child should be parsed. + * @throws XmlPullParserException If an error occurs parsing the element. + * @throws IOException If an error occurs reading the element. + */ + protected void parseAdaptationSetChild(XmlPullParser xpp) + throws XmlPullParserException, IOException { + // pass + } + + // Representation parsing. + + protected RepresentationInfo parseRepresentation(XmlPullParser xpp, String baseUrl, + String adaptationSetMimeType, String adaptationSetCodecs, int adaptationSetWidth, + int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, + int adaptationSetAudioSamplingRate, String adaptationSetLanguage, + int adaptationSetAccessibilityChannel, SegmentBase segmentBase) + throws XmlPullParserException, IOException { + String id = xpp.getAttributeValue(null, "id"); + int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE); + + String mimeType = parseString(xpp, "mimeType", adaptationSetMimeType); + String codecs = parseString(xpp, "codecs", adaptationSetCodecs); + int width = parseInt(xpp, "width", adaptationSetWidth); + int height = parseInt(xpp, "height", adaptationSetHeight); + float frameRate = parseFrameRate(xpp, adaptationSetFrameRate); + int audioChannels = adaptationSetAudioChannels; + int audioSamplingRate = parseInt(xpp, "audioSamplingRate", adaptationSetAudioSamplingRate); + ArrayList drmSchemeDatas = new ArrayList<>(); + + boolean seenFirstBaseUrl = false; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "BaseURL")) { + if (!seenFirstBaseUrl) { + baseUrl = parseBaseUrl(xpp, baseUrl); + seenFirstBaseUrl = true; + } + } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { + audioChannels = parseAudioChannelConfiguration(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { + segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { + segmentBase = parseSegmentList(xpp, (SegmentList) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTemplate")) { + segmentBase = parseSegmentTemplate(xpp, (SegmentTemplate) segmentBase); + } else if (XmlPullParserUtil.isStartTag(xpp, "ContentProtection")) { + SchemeData contentProtection = parseContentProtection(xpp); + if (contentProtection != null) { + drmSchemeDatas.add(contentProtection); + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); + + Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels, + audioSamplingRate, bandwidth, adaptationSetLanguage, adaptationSetAccessibilityChannel, + codecs); + segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl); + + return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeDatas); + } + + protected Format buildFormat(String id, String containerMimeType, int width, int height, + float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language, + int accessiblityChannel, String codecs) { + String sampleMimeType = getSampleMimeType(containerMimeType, codecs); + if (sampleMimeType != null) { + if (MimeTypes.isVideo(sampleMimeType)) { + return Format.createVideoContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, width, height, frameRate, null); + } else if (MimeTypes.isAudio(sampleMimeType)) { + return Format.createAudioContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, audioChannels, audioSamplingRate, null, 0, language); + } else if (mimeTypeIsRawText(sampleMimeType)) { + return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, 0, language, accessiblityChannel); + } else if (containerMimeType.equals(MimeTypes.APPLICATION_RAWCC)) { + return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, 0, language, accessiblityChannel); + } else { + return Format.createContainerFormat(id, containerMimeType, codecs, sampleMimeType, bitrate); + } + } else { + return Format.createContainerFormat(id, containerMimeType, codecs, sampleMimeType, bitrate); + } + } + + protected Representation buildRepresentation(RepresentationInfo representationInfo, + String contentId, ArrayList extraDrmSchemeDatas) { + Format format = representationInfo.format; + ArrayList drmSchemeDatas = representationInfo.drmSchemeDatas; + drmSchemeDatas.addAll(extraDrmSchemeDatas); + if (!drmSchemeDatas.isEmpty()) { + format = format.copyWithDrmInitData(new DrmInitData(drmSchemeDatas)); + } + return Representation.newInstance(contentId, Representation.REVISION_ID_DEFAULT, format, + representationInfo.baseUrl, representationInfo.segmentBase); + } + + // SegmentBase, SegmentList and SegmentTemplate parsing. + + protected SingleSegmentBase parseSegmentBase(XmlPullParser xpp, SingleSegmentBase parent) + throws XmlPullParserException, IOException { + + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + + long indexStart = parent != null ? parent.indexStart : 0; + long indexLength = parent != null ? parent.indexLength : 0; + String indexRangeText = xpp.getAttributeValue(null, "indexRange"); + if (indexRangeText != null) { + String[] indexRange = indexRangeText.split("-"); + indexStart = Long.parseLong(indexRange[0]); + indexLength = Long.parseLong(indexRange[1]) - indexStart + 1; + } + + RangedUri initialization = parent != null ? parent.initialization : null; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentBase")); + + return buildSingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); + } + + protected SingleSegmentBase buildSingleSegmentBase(RangedUri initialization, long timescale, + long presentationTimeOffset, long indexStart, long indexLength) { + return new SingleSegmentBase(initialization, timescale, presentationTimeOffset, indexStart, + indexLength); + } + + protected SegmentList parseSegmentList(XmlPullParser xpp, SegmentList parent) + throws XmlPullParserException, IOException { + + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); + int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); + + RangedUri initialization = null; + List timeline = null; + List segments = null; + + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { + timeline = parseSegmentTimeline(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentURL")) { + if (segments == null) { + segments = new ArrayList<>(); + } + segments.add(parseSegmentUrl(xpp)); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentList")); + + if (parent != null) { + initialization = initialization != null ? initialization : parent.initialization; + timeline = timeline != null ? timeline : parent.segmentTimeline; + segments = segments != null ? segments : parent.mediaSegments; + } + + return buildSegmentList(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, segments); + } + + protected SegmentList buildSegmentList(RangedUri initialization, long timescale, + long presentationTimeOffset, int startNumber, long duration, + List timeline, List segments) { + return new SegmentList(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, segments); + } + + protected SegmentTemplate parseSegmentTemplate(XmlPullParser xpp, SegmentTemplate parent) + throws XmlPullParserException, IOException { + long timescale = parseLong(xpp, "timescale", parent != null ? parent.timescale : 1); + long presentationTimeOffset = parseLong(xpp, "presentationTimeOffset", + parent != null ? parent.presentationTimeOffset : 0); + long duration = parseLong(xpp, "duration", parent != null ? parent.duration : C.TIME_UNSET); + int startNumber = parseInt(xpp, "startNumber", parent != null ? parent.startNumber : 1); + UrlTemplate mediaTemplate = parseUrlTemplate(xpp, "media", + parent != null ? parent.mediaTemplate : null); + UrlTemplate initializationTemplate = parseUrlTemplate(xpp, "initialization", + parent != null ? parent.initializationTemplate : null); + + RangedUri initialization = null; + List timeline = null; + + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "Initialization")) { + initialization = parseInitialization(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentTimeline")) { + timeline = parseSegmentTimeline(xpp); + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTemplate")); + + if (parent != null) { + initialization = initialization != null ? initialization : parent.initialization; + timeline = timeline != null ? timeline : parent.segmentTimeline; + } + + return buildSegmentTemplate(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, initializationTemplate, mediaTemplate); + } + + protected SegmentTemplate buildSegmentTemplate(RangedUri initialization, long timescale, + long presentationTimeOffset, int startNumber, long duration, + List timeline, UrlTemplate initializationTemplate, + UrlTemplate mediaTemplate) { + return new SegmentTemplate(initialization, timescale, presentationTimeOffset, + startNumber, duration, timeline, initializationTemplate, mediaTemplate); + } + + protected List parseSegmentTimeline(XmlPullParser xpp) + throws XmlPullParserException, IOException { + List segmentTimeline = new ArrayList<>(); + long elapsedTime = 0; + do { + xpp.next(); + if (XmlPullParserUtil.isStartTag(xpp, "S")) { + elapsedTime = parseLong(xpp, "t", elapsedTime); + long duration = parseLong(xpp, "d", C.TIME_UNSET); + int count = 1 + parseInt(xpp, "r", 0); + for (int i = 0; i < count; i++) { + segmentTimeline.add(buildSegmentTimelineElement(elapsedTime, duration)); + elapsedTime += duration; + } + } + } while (!XmlPullParserUtil.isEndTag(xpp, "SegmentTimeline")); + return segmentTimeline; + } + + protected SegmentTimelineElement buildSegmentTimelineElement(long elapsedTime, long duration) { + return new SegmentTimelineElement(elapsedTime, duration); + } + + protected UrlTemplate parseUrlTemplate(XmlPullParser xpp, String name, + UrlTemplate defaultValue) { + String valueString = xpp.getAttributeValue(null, name); + if (valueString != null) { + return UrlTemplate.compile(valueString); + } + return defaultValue; + } + + protected RangedUri parseInitialization(XmlPullParser xpp) { + return parseRangedUrl(xpp, "sourceURL", "range"); + } + + protected RangedUri parseSegmentUrl(XmlPullParser xpp) { + return parseRangedUrl(xpp, "media", "mediaRange"); + } + + protected RangedUri parseRangedUrl(XmlPullParser xpp, String urlAttribute, + String rangeAttribute) { + String urlText = xpp.getAttributeValue(null, urlAttribute); + long rangeStart = 0; + long rangeLength = C.LENGTH_UNSET; + String rangeText = xpp.getAttributeValue(null, rangeAttribute); + if (rangeText != null) { + String[] rangeTextArray = rangeText.split("-"); + rangeStart = Long.parseLong(rangeTextArray[0]); + if (rangeTextArray.length == 2) { + rangeLength = Long.parseLong(rangeTextArray[1]) - rangeStart + 1; + } + } + return buildRangedUri(urlText, rangeStart, rangeLength); + } + + protected RangedUri buildRangedUri(String urlText, long rangeStart, long rangeLength) { + return new RangedUri(urlText, rangeStart, rangeLength); + } + + // AudioChannelConfiguration parsing. + + protected int parseAudioChannelConfiguration(XmlPullParser xpp) + throws XmlPullParserException, IOException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + int audioChannels = "urn:mpeg:dash:23003:3:audio_channel_configuration:2011".equals(schemeIdUri) + ? parseInt(xpp, "value", Format.NO_VALUE) : Format.NO_VALUE; + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, "AudioChannelConfiguration")); + return audioChannels; + } + + // Utility methods. + + /** + * Derives a sample mimeType from a container mimeType and codecs attribute. + * + * @param containerMimeType The mimeType of the container. + * @param codecs The codecs attribute. + * @return The derived sample mimeType, or null if it could not be derived. + */ + private static String getSampleMimeType(String containerMimeType, String codecs) { + if (MimeTypes.isAudio(containerMimeType)) { + return MimeTypes.getAudioMediaMimeType(codecs); + } else if (MimeTypes.isVideo(containerMimeType)) { + return MimeTypes.getVideoMediaMimeType(codecs); + } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { + if (codecs != null) { + if (codecs.contains("cea708")) { + return MimeTypes.APPLICATION_CEA708; + } else if (codecs.contains("eia608") || codecs.contains("cea608")) { + return MimeTypes.APPLICATION_CEA608; + } + } + return null; + } else if (mimeTypeIsRawText(containerMimeType)) { + return containerMimeType; + } else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) { + if ("stpp".equals(codecs)) { + return MimeTypes.APPLICATION_TTML; + } else if ("wvtt".equals(codecs)) { + return MimeTypes.APPLICATION_MP4VTT; + } + } + return null; + } + + /** + * Returns whether a mimeType is a text sample mimeType. + * + * @param mimeType The mimeType. + * @return Whether the mimeType is a text sample mimeType. + */ + private static boolean mimeTypeIsRawText(String mimeType) { + return MimeTypes.isText(mimeType) || MimeTypes.APPLICATION_TTML.equals(mimeType); + } + + /** + * Checks two languages for consistency, returning the consistent language, or throwing an + * {@link IllegalStateException} if the languages are inconsistent. + *

        + * Two languages are consistent if they are equal, or if one is null. + * + * @param firstLanguage The first language. + * @param secondLanguage The second language. + * @return The consistent language. + */ + private static String checkLanguageConsistency(String firstLanguage, String secondLanguage) { + if (firstLanguage == null) { + return secondLanguage; + } else if (secondLanguage == null) { + return firstLanguage; + } else { + Assertions.checkState(firstLanguage.equals(secondLanguage)); + return firstLanguage; + } + } + + /** + * Checks two adaptation set content types for consistency, returning the consistent type, or + * throwing an {@link IllegalStateException} if the types are inconsistent. + *

        + * Two types are consistent if they are equal, or if one is {@link C#TRACK_TYPE_UNKNOWN}. + * Where one of the types is {@link C#TRACK_TYPE_UNKNOWN}, the other is returned. + * + * @param firstType The first type. + * @param secondType The second type. + * @return The consistent type. + */ + private static int checkContentTypeConsistency(int firstType, int secondType) { + if (firstType == C.TRACK_TYPE_UNKNOWN) { + return secondType; + } else if (secondType == C.TRACK_TYPE_UNKNOWN) { + return firstType; + } else { + Assertions.checkState(firstType == secondType); + return firstType; + } + } + + private static int parseAccessibilityValue(XmlPullParser xpp) + throws IOException, XmlPullParserException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + String valueString = parseString(xpp, "value", null); + int accessibilityValue; + if (schemeIdUri == null || valueString == null) { + accessibilityValue = Format.NO_VALUE; + } else if ("urn:scte:dash:cc:cea-608:2015".equals(schemeIdUri)) { + accessibilityValue = parseCea608AccessibilityChannel(valueString); + } else if ("urn:scte:dash:cc:cea-708:2015".equals(schemeIdUri)) { + accessibilityValue = parseCea708AccessibilityChannel(valueString); + } else { + accessibilityValue = Format.NO_VALUE; + } + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, "Accessibility")); + return accessibilityValue; + } + + static int parseCea608AccessibilityChannel(String accessibilityValueString) { + if (accessibilityValueString == null) { + return Format.NO_VALUE; + } + Matcher accessibilityValueMatcher = + CEA_608_ACCESSIBILITY_PATTERN.matcher(accessibilityValueString); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse channel number from " + accessibilityValueString); + return Format.NO_VALUE; + } + } + + static int parseCea708AccessibilityChannel(String accessibilityValueString) { + if (accessibilityValueString == null) { + return Format.NO_VALUE; + } + Matcher accessibilityValueMatcher = + CEA_708_ACCESSIBILITY_PATTERN.matcher(accessibilityValueString); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse service block number from " + accessibilityValueString); + return Format.NO_VALUE; + } + } + + protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) { + float frameRate = defaultValue; + String frameRateAttribute = xpp.getAttributeValue(null, "frameRate"); + if (frameRateAttribute != null) { + Matcher frameRateMatcher = FRAME_RATE_PATTERN.matcher(frameRateAttribute); + if (frameRateMatcher.matches()) { + int numerator = Integer.parseInt(frameRateMatcher.group(1)); + String denominatorString = frameRateMatcher.group(2); + if (!TextUtils.isEmpty(denominatorString)) { + frameRate = (float) numerator / Integer.parseInt(denominatorString); + } else { + frameRate = numerator; + } + } + } + return frameRate; + } + + protected static long parseDuration(XmlPullParser xpp, String name, long defaultValue) { + String value = xpp.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Util.parseXsDuration(value); + } + } + + protected static long parseDateTime(XmlPullParser xpp, String name, long defaultValue) + throws ParserException { + String value = xpp.getAttributeValue(null, name); + if (value == null) { + return defaultValue; + } else { + return Util.parseXsDateTime(value); + } + } + + protected static String parseBaseUrl(XmlPullParser xpp, String parentBaseUrl) + throws XmlPullParserException, IOException { + xpp.next(); + return UriUtil.resolve(parentBaseUrl, xpp.getText()); + } + + protected static int parseInt(XmlPullParser xpp, String name, int defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : Integer.parseInt(value); + } + + protected static long parseLong(XmlPullParser xpp, String name, long defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : Long.parseLong(value); + } + + protected static String parseString(XmlPullParser xpp, String name, String defaultValue) { + String value = xpp.getAttributeValue(null, name); + return value == null ? defaultValue : value; + } + + private static final class RepresentationInfo { + + public final Format format; + public final String baseUrl; + public final SegmentBase segmentBase; + public final ArrayList drmSchemeDatas; + + public RepresentationInfo(Format format, String baseUrl, SegmentBase segmentBase, + ArrayList drmSchemeDatas) { + this.format = format; + this.baseUrl = baseUrl; + this.segmentBase = segmentBase; + this.drmSchemeDatas = drmSchemeDatas; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Period.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Period.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Period.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Period.java index b5beabb0bcd..09e5a516e39 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/Period.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Period.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash.mpd; +package org.telegram.messenger.exoplayer2.source.dash.manifest; +import org.telegram.messenger.exoplayer2.C; import java.util.Collections; import java.util.List; @@ -40,21 +41,21 @@ public class Period { /** * @param id The period identifier. May be null. - * @param start The start time of the period in milliseconds. + * @param startMs The start time of the period in milliseconds. * @param adaptationSets The adaptation sets belonging to the period. */ - public Period(String id, long start, List adaptationSets) { + public Period(String id, long startMs, List adaptationSets) { this.id = id; - this.startMs = start; + this.startMs = startMs; this.adaptationSets = Collections.unmodifiableList(adaptationSets); } /** - * Returns the index of the first adaptation set of a given type, or -1 if no adaptation set of - * the specified type exists. + * Returns the index of the first adaptation set of a given type, or {@link C#INDEX_UNSET} if no + * adaptation set of the specified type exists. * * @param type An adaptation set type. - * @return The index of the first adaptation set of the specified type, or -1. + * @return The index of the first adaptation set of the specified type, or {@link C#INDEX_UNSET}. */ public int getAdaptationSetIndex(int type) { int adaptationCount = adaptationSets.size(); @@ -63,7 +64,7 @@ public int getAdaptationSetIndex(int type) { return i; } } - return -1; + return C.INDEX_UNSET; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/RangedUri.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/RangedUri.java new file mode 100755 index 00000000000..020d0a1c9e3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/RangedUri.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.UriUtil; + +/** + * Defines a range of data located at a reference uri. + */ +public final class RangedUri { + + /** + * The (zero based) index of the first byte of the range. + */ + public final long start; + + /** + * The length of the range, or {@link C#LENGTH_UNSET} to indicate that the range is unbounded. + */ + public final long length; + + private final String referenceUri; + + private int hashCode; + + /** + * Constructs an ranged uri. + * + * @param referenceUri The reference uri. + * @param start The (zero based) index of the first byte of the range. + * @param length The length of the range, or {@link C#LENGTH_UNSET} to indicate that the range is + * unbounded. + */ + public RangedUri(String referenceUri, long start, long length) { + this.referenceUri = referenceUri == null ? "" : referenceUri; + this.start = start; + this.length = length; + } + + /** + * Returns the resolved {@link Uri} represented by the instance. + * + * @param baseUri The base Uri. + * @return The {@link Uri} represented by the instance. + */ + public Uri resolveUri(String baseUri) { + return UriUtil.resolveToUri(baseUri, referenceUri); + } + + /** + * Returns the resolved uri represented by the instance as a string. + * + * @param baseUri The base Uri. + * @return The uri represented by the instance. + */ + public String resolveUriString(String baseUri) { + return UriUtil.resolve(baseUri, referenceUri); + } + + /** + * Attempts to merge this {@link RangedUri} with another and an optional common base uri. + *

        + * A merge is successful if both instances define the same {@link Uri} after resolution with the + * base uri, and if one starts the byte after the other ends, forming a contiguous region with + * no overlap. + *

        + * If {@code other} is null then the merge is considered unsuccessful, and null is returned. + * + * @param other The {@link RangedUri} to merge. + * @param baseUri The optional base Uri. + * @return The merged {@link RangedUri} if the merge was successful. Null otherwise. + */ + public RangedUri attemptMerge(RangedUri other, String baseUri) { + final String resolvedUri = resolveUriString(baseUri); + if (other == null || !resolvedUri.equals(other.resolveUriString(baseUri))) { + return null; + } else if (length != C.LENGTH_UNSET && start + length == other.start) { + return new RangedUri(resolvedUri, start, + other.length == C.LENGTH_UNSET ? C.LENGTH_UNSET : length + other.length); + } else if (other.length != C.LENGTH_UNSET && other.start + other.length == start) { + return new RangedUri(resolvedUri, other.start, + length == C.LENGTH_UNSET ? C.LENGTH_UNSET : other.length + length); + } else { + return null; + } + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + (int) start; + result = 31 * result + (int) length; + result = 31 * result + referenceUri.hashCode(); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + RangedUri other = (RangedUri) obj; + return this.start == other.start + && this.length == other.length + && referenceUri.equals(other.referenceUri); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java new file mode 100755 index 00000000000..b36a691b086 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/Representation.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.dash.DashSegmentIndex; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.MultiSegmentBase; +import org.telegram.messenger.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase; + +/** + * A DASH representation. + */ +public abstract class Representation { + + /** + * A default value for {@link #revisionId}. + */ + public static final long REVISION_ID_DEFAULT = -1; + + /** + * Identifies the piece of content to which this {@link Representation} belongs. + *

        + * For example, all {@link Representation}s belonging to a video should have the same content + * identifier that uniquely identifies that video. + */ + public final String contentId; + /** + * Identifies the revision of the content. + *

        + * If the media for a given ({@link #contentId} can change over time without a change to the + * {@link #format}'s {@link Format#id} (e.g. as a result of re-encoding the media with an + * updated encoder), then this identifier must uniquely identify the revision of the media. The + * timestamp at which the media was encoded is often a suitable. + */ + public final long revisionId; + /** + * The format of the representation. + */ + public final Format format; + /** + * The base URL of the representation. + */ + public final String baseUrl; + /** + * The offset of the presentation timestamps in the media stream relative to media time. + */ + public final long presentationTimeOffsetUs; + + private final RangedUri initializationUri; + + /** + * Constructs a new instance. + * + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL. + * @param segmentBase A segment base element for the representation. + * @return The constructed instance. + */ + public static Representation newInstance(String contentId, long revisionId, Format format, + String baseUrl, SegmentBase segmentBase) { + return newInstance(contentId, revisionId, format, baseUrl, segmentBase, null); + } + + /** + * Constructs a new instance. + * + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase A segment base element for the representation. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. This + * parameter is ignored if {@code segmentBase} consists of multiple segments. + * @return The constructed instance. + */ + public static Representation newInstance(String contentId, long revisionId, Format format, + String baseUrl, SegmentBase segmentBase, String customCacheKey) { + if (segmentBase instanceof SingleSegmentBase) { + return new SingleSegmentRepresentation(contentId, revisionId, format, baseUrl, + (SingleSegmentBase) segmentBase, customCacheKey, C.LENGTH_UNSET); + } else if (segmentBase instanceof MultiSegmentBase) { + return new MultiSegmentRepresentation(contentId, revisionId, format, baseUrl, + (MultiSegmentBase) segmentBase); + } else { + throw new IllegalArgumentException("segmentBase must be of type SingleSegmentBase or " + + "MultiSegmentBase"); + } + } + + private Representation(String contentId, long revisionId, Format format, String baseUrl, + SegmentBase segmentBase) { + this.contentId = contentId; + this.revisionId = revisionId; + this.format = format; + this.baseUrl = baseUrl; + initializationUri = segmentBase.getInitialization(this); + presentationTimeOffsetUs = segmentBase.getPresentationTimeOffsetUs(); + } + + /** + * Returns a {@link RangedUri} defining the location of the representation's initialization data, + * or null if no initialization data exists. + */ + public RangedUri getInitializationUri() { + return initializationUri; + } + + /** + * Returns a {@link RangedUri} defining the location of the representation's segment index, or + * null if the representation provides an index directly. + */ + public abstract RangedUri getIndexUri(); + + /** + * Returns an index if the representation provides one directly, or null otherwise. + */ + public abstract DashSegmentIndex getIndex(); + + /** + * Returns a cache key for the representation if a custom cache key or content id has been + * provided and there is only single segment. + */ + public abstract String getCacheKey(); + + /** + * A DASH representation consisting of a single segment. + */ + public static class SingleSegmentRepresentation extends Representation { + + /** + * The uri of the single segment. + */ + public final Uri uri; + + /** + * The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public final long contentLength; + + private final String cacheKey; + private final RangedUri indexUri; + private final SingleSegmentIndex segmentIndex; + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param uri The uri of the media. + * @param initializationStart The offset of the first byte of initialization data. + * @param initializationEnd The offset of the last byte of initialization data. + * @param indexStart The offset of the first byte of index data. + * @param indexEnd The offset of the last byte of index data. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public static SingleSegmentRepresentation newInstance(String contentId, long revisionId, + Format format, String uri, long initializationStart, long initializationEnd, + long indexStart, long indexEnd, String customCacheKey, long contentLength) { + RangedUri rangedUri = new RangedUri(null, initializationStart, + initializationEnd - initializationStart + 1); + SingleSegmentBase segmentBase = new SingleSegmentBase(rangedUri, 1, 0, indexStart, + indexEnd - indexStart + 1); + return new SingleSegmentRepresentation(contentId, revisionId, + format, uri, segmentBase, customCacheKey, contentLength); + } + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase The segment base underlying the representation. + * @param customCacheKey A custom value to be returned from {@link #getCacheKey()}, or null. + * @param contentLength The content length, or {@link C#LENGTH_UNSET} if unknown. + */ + public SingleSegmentRepresentation(String contentId, long revisionId, Format format, + String baseUrl, SingleSegmentBase segmentBase, String customCacheKey, long contentLength) { + super(contentId, revisionId, format, baseUrl, segmentBase); + this.uri = Uri.parse(baseUrl); + this.indexUri = segmentBase.getIndex(); + this.cacheKey = customCacheKey != null ? customCacheKey + : contentId != null ? contentId + "." + format.id + "." + revisionId : null; + this.contentLength = contentLength; + // If we have an index uri then the index is defined externally, and we shouldn't return one + // directly. If we don't, then we can't do better than an index defining a single segment. + segmentIndex = indexUri != null ? null + : new SingleSegmentIndex(new RangedUri(null, 0, contentLength)); + } + + @Override + public RangedUri getIndexUri() { + return indexUri; + } + + @Override + public DashSegmentIndex getIndex() { + return segmentIndex; + } + + @Override + public String getCacheKey() { + return cacheKey; + } + + } + + /** + * A DASH representation consisting of multiple segments. + */ + public static class MultiSegmentRepresentation extends Representation + implements DashSegmentIndex { + + private final MultiSegmentBase segmentBase; + + /** + * @param contentId Identifies the piece of content to which this representation belongs. + * @param revisionId Identifies the revision of the content. + * @param format The format of the representation. + * @param baseUrl The base URL of the representation. + * @param segmentBase The segment base underlying the representation. + */ + public MultiSegmentRepresentation(String contentId, long revisionId, Format format, + String baseUrl, MultiSegmentBase segmentBase) { + super(contentId, revisionId, format, baseUrl, segmentBase); + this.segmentBase = segmentBase; + } + + @Override + public RangedUri getIndexUri() { + return null; + } + + @Override + public DashSegmentIndex getIndex() { + return this; + } + + @Override + public String getCacheKey() { + return null; + } + + // DashSegmentIndex implementation. + + @Override + public RangedUri getSegmentUrl(int segmentIndex) { + return segmentBase.getSegmentUrl(this, segmentIndex); + } + + @Override + public int getSegmentNum(long timeUs, long periodDurationUs) { + return segmentBase.getSegmentNum(timeUs, periodDurationUs); + } + + @Override + public long getTimeUs(int segmentIndex) { + return segmentBase.getSegmentTimeUs(segmentIndex); + } + + @Override + public long getDurationUs(int segmentIndex, long periodDurationUs) { + return segmentBase.getSegmentDurationUs(segmentIndex, periodDurationUs); + } + + @Override + public int getFirstSegmentNum() { + return segmentBase.getFirstSegmentNum(); + } + + @Override + public int getLastSegmentNum(long periodDurationUs) { + return segmentBase.getLastSegmentNum(periodDurationUs); + } + + @Override + public boolean isExplicit() { + return segmentBase.isExplicit(); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/SegmentBase.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SegmentBase.java similarity index 90% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/SegmentBase.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SegmentBase.java index b0dfb701cb4..cf6337e8cf5 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/SegmentBase.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SegmentBase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash.mpd; +package org.telegram.messenger.exoplayer2.source.dash.manifest; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.dash.DashSegmentIndex; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.source.dash.DashSegmentIndex; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.List; /** @@ -43,8 +43,8 @@ public SegmentBase(RangedUri initialization, long timescale, long presentationTi } /** - * Gets the {@link RangedUri} defining the location of initialization data for a given - * representation. May be null if no initialization data exists. + * Returns the {@link RangedUri} defining the location of initialization data for a given + * representation, or null if no initialization data exists. * * @param representation The {@link Representation} for which initialization data is required. * @return A {@link RangedUri} defining the location of the initialization data, or null. @@ -54,9 +54,7 @@ public RangedUri getInitialization(Representation representation) { } /** - * Gets the presentation time offset, in microseconds. - * - * @return The presentation time offset, in microseconds. + * Returns the presentation time offset, in microseconds. */ public long getPresentationTimeOffsetUs() { return Util.scaleLargeTimestamp(presentationTimeOffset, C.MICROS_PER_SECOND, timescale); @@ -67,11 +65,6 @@ public long getPresentationTimeOffsetUs() { */ public static class SingleSegmentBase extends SegmentBase { - /** - * The uri of the segment. - */ - public final String uri; - /* package */ final long indexStart; /* package */ final long indexLength; @@ -81,27 +74,22 @@ public static class SingleSegmentBase extends SegmentBase { * @param timescale The timescale in units per second. * @param presentationTimeOffset The presentation time offset. The value in seconds is the * division of this value and {@code timescale}. - * @param uri The uri of the segment. * @param indexStart The byte offset of the index data in the segment. * @param indexLength The length of the index data in bytes. */ public SingleSegmentBase(RangedUri initialization, long timescale, long presentationTimeOffset, - String uri, long indexStart, long indexLength) { + long indexStart, long indexLength) { super(initialization, timescale, presentationTimeOffset); - this.uri = uri; this.indexStart = indexStart; this.indexLength = indexLength; } - /** - * @param uri The uri of the segment. - */ public SingleSegmentBase(String uri) { - this(null, 1, 0, uri, 0, -1); + this(null, 1, 0, 0, 0); } public RangedUri getIndex() { - return indexLength <= 0 ? null : new RangedUri(uri, null, indexStart, indexLength); + return indexLength <= 0 ? null : new RangedUri(null, indexStart, indexLength); } } @@ -281,8 +269,6 @@ public static class SegmentTemplate extends MultiSegmentBase { /* package */ final UrlTemplate initializationTemplate; /* package */ final UrlTemplate mediaTemplate; - private final String baseUrl; - /** * @param initialization A {@link RangedUri} corresponding to initialization data, if such data * exists. The value of this parameter is ignored if {@code initializationTemplate} is @@ -301,16 +287,14 @@ public static class SegmentTemplate extends MultiSegmentBase { * such data exists. If non-null then the {@code initialization} parameter is ignored. If * null then {@code initialization} will be used. * @param mediaTemplate A template defining the location of each media segment. - * @param baseUrl A url to use as the base for relative urls generated by the templates. */ public SegmentTemplate(RangedUri initialization, long timescale, long presentationTimeOffset, int startNumber, long duration, List segmentTimeline, - UrlTemplate initializationTemplate, UrlTemplate mediaTemplate, String baseUrl) { + UrlTemplate initializationTemplate, UrlTemplate mediaTemplate) { super(initialization, timescale, presentationTimeOffset, startNumber, duration, segmentTimeline); this.initializationTemplate = initializationTemplate; this.mediaTemplate = mediaTemplate; - this.baseUrl = baseUrl; } @Override @@ -318,7 +302,7 @@ public RangedUri getInitialization(Representation representation) { if (initializationTemplate != null) { String urlString = initializationTemplate.buildUri(representation.format.id, 0, representation.format.bitrate, 0); - return new RangedUri(baseUrl, urlString, 0, -1); + return new RangedUri(urlString, 0, C.LENGTH_UNSET); } else { return super.getInitialization(representation); } @@ -326,7 +310,7 @@ public RangedUri getInitialization(Representation representation) { @Override public RangedUri getSegmentUrl(Representation representation, int sequenceNumber) { - long time = 0; + long time; if (segmentTimeline != null) { time = segmentTimeline.get(sequenceNumber - startNumber).startTime; } else { @@ -334,14 +318,14 @@ public RangedUri getSegmentUrl(Representation representation, int sequenceNumber } String uriString = mediaTemplate.buildUri(representation.format.id, sequenceNumber, representation.format.bitrate, time); - return new RangedUri(baseUrl, uriString, 0, -1); + return new RangedUri(uriString, 0, C.LENGTH_UNSET); } @Override public int getLastSegmentNum(long periodDurationUs) { if (segmentTimeline != null) { return segmentTimeline.size() + startNumber - 1; - } else if (periodDurationUs == C.UNKNOWN_TIME_US) { + } else if (periodDurationUs == C.TIME_UNSET) { return DashSegmentIndex.INDEX_UNBOUNDED; } else { long durationUs = (duration * C.MICROS_PER_SECOND) / timescale; @@ -356,8 +340,8 @@ public int getLastSegmentNum(long periodDurationUs) { */ public static class SegmentTimelineElement { - /* package */ long startTime; - /* package */ long duration; + /* package */ final long startTime; + /* package */ final long duration; /** * @param startTime The start time of the element. The value in seconds is the division of this diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SingleSegmentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SingleSegmentIndex.java new file mode 100755 index 00000000000..bccc4538c44 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/SingleSegmentIndex.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.dash.manifest; + +import org.telegram.messenger.exoplayer2.source.dash.DashSegmentIndex; + +/** + * A {@link DashSegmentIndex} that defines a single segment. + */ +/* package */ final class SingleSegmentIndex implements DashSegmentIndex { + + private final RangedUri uri; + + /** + * @param uri A {@link RangedUri} defining the location of the segment data. + */ + public SingleSegmentIndex(RangedUri uri) { + this.uri = uri; + } + + @Override + public int getSegmentNum(long timeUs, long periodDurationUs) { + return 0; + } + + @Override + public long getTimeUs(int segmentNum) { + return 0; + } + + @Override + public long getDurationUs(int segmentNum, long periodDurationUs) { + return periodDurationUs; + } + + @Override + public RangedUri getSegmentUrl(int segmentNum) { + return uri; + } + + @Override + public int getFirstSegmentNum() { + return 0; + } + + @Override + public int getLastSegmentNum(long periodDurationUs) { + return 0; + } + + @Override + public boolean isExplicit() { + return true; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UrlTemplate.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UrlTemplate.java similarity index 92% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UrlTemplate.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UrlTemplate.java index 21a79b2902c..5ed8471290e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UrlTemplate.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UrlTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash.mpd; +package org.telegram.messenger.exoplayer2.source.dash.manifest; import java.util.Locale; @@ -144,14 +144,18 @@ private static int parseTemplate(String template, String[] urlPieces, int[] iden } identifier = identifier.substring(0, formatTagIndex); } - if (identifier.equals(NUMBER)) { - identifiers[identifierCount] = NUMBER_ID; - } else if (identifier.equals(BANDWIDTH)) { - identifiers[identifierCount] = BANDWIDTH_ID; - } else if (identifier.equals(TIME)) { - identifiers[identifierCount] = TIME_ID; - } else { - throw new IllegalArgumentException("Invalid template: " + template); + switch (identifier) { + case NUMBER: + identifiers[identifierCount] = NUMBER_ID; + break; + case BANDWIDTH: + identifiers[identifierCount] = BANDWIDTH_ID; + break; + case TIME: + identifiers[identifierCount] = TIME_ID; + break; + default: + throw new IllegalArgumentException("Invalid template: " + template); } identifierFormatTags[identifierCount] = formatTag; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElement.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UtcTimingElement.java similarity index 88% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElement.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UtcTimingElement.java index 18daa8be2ab..cc216c03853 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/dash/mpd/UtcTimingElement.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/dash/manifest/UtcTimingElement.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.dash.mpd; +package org.telegram.messenger.exoplayer2.source.dash.manifest; /** * Represents a UTCTiming element. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Aes128DataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/Aes128DataSource.java similarity index 80% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Aes128DataSource.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/Aes128DataSource.java index aaa9aa21fdf..46266e68bf0 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/hls/Aes128DataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/Aes128DataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.hls; +package org.telegram.messenger.exoplayer2.source.hls; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DataSourceInputStream; -import org.telegram.messenger.exoplayer.upstream.DataSpec; -import org.telegram.messenger.exoplayer.util.Assertions; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSourceInputStream; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -64,9 +65,7 @@ public long open(DataSpec dataSpec) throws IOException { Cipher cipher; try { cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } catch (NoSuchPaddingException e) { + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { throw new RuntimeException(e); } @@ -75,16 +74,14 @@ public long open(DataSpec dataSpec) throws IOException { try { cipher.init(Cipher.DECRYPT_MODE, cipherKey, cipherIV); - } catch (InvalidKeyException e) { - throw new RuntimeException(e); - } catch (InvalidAlgorithmParameterException e) { + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } cipherInputStream = new CipherInputStream( new DataSourceInputStream(upstream, dataSpec), cipher); - return C.LENGTH_UNBOUNDED; + return C.LENGTH_UNSET; } @Override @@ -98,9 +95,14 @@ public int read(byte[] buffer, int offset, int readLength) throws IOException { Assertions.checkState(cipherInputStream != null); int bytesRead = cipherInputStream.read(buffer, offset, readLength); if (bytesRead < 0) { - return -1; + return C.RESULT_END_OF_INPUT; } return bytesRead; } + @Override + public Uri getUri() { + return upstream.getUri(); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java new file mode 100755 index 00000000000..27cd3ba6388 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsChunkSource.java @@ -0,0 +1,446 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.net.Uri; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.source.BehindLiveWindowException; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.chunk.Chunk; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import org.telegram.messenger.exoplayer2.source.chunk.DataChunk; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import org.telegram.messenger.exoplayer2.trackselection.BaseTrackSelection; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.UriUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Locale; + +/** + * Source of Hls (possibly adaptive) chunks. + */ +/* package */ class HlsChunkSource { + + /** + * Chunk holder that allows the scheduling of retries. + */ + public static final class HlsChunkHolder { + + public HlsChunkHolder() { + clear(); + } + + /** + * The chunk to be loaded next. + */ + public Chunk chunk; + + /** + * Indicates that the end of the stream has been reached. + */ + public boolean endOfStream; + + /** + * Indicates that the chunk source is waiting for the referred playlist to be refreshed. + */ + public HlsUrl playlist; + + /** + * Clears the holder. + */ + public void clear() { + chunk = null; + endOfStream = false; + playlist = null; + } + + } + + private final DataSource dataSource; + private final TimestampAdjusterProvider timestampAdjusterProvider; + private final HlsUrl[] variants; + private final HlsPlaylistTracker playlistTracker; + private final TrackGroup trackGroup; + + private boolean isTimestampMaster; + private byte[] scratchSpace; + private IOException fatalError; + + private Uri encryptionKeyUri; + private byte[] encryptionKey; + private String encryptionIvString; + private byte[] encryptionIv; + + // Note: The track group in the selection is typically *not* equal to trackGroup. This is due to + // the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods + // in TrackSelection to avoid unexpected behavior. + private TrackSelection trackSelection; + + /** + * @param playlistTracker The {@link HlsPlaylistTracker} from which to obtain media playlists. + * @param variants The available variants. + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If + * multiple {@link HlsChunkSource}s are used for a single playback, they should all share the + * same provider. + */ + public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants, + DataSource dataSource, TimestampAdjusterProvider timestampAdjusterProvider) { + this.playlistTracker = playlistTracker; + this.variants = variants; + this.dataSource = dataSource; + this.timestampAdjusterProvider = timestampAdjusterProvider; + + Format[] variantFormats = new Format[variants.length]; + int[] initialTrackSelection = new int[variants.length]; + for (int i = 0; i < variants.length; i++) { + variantFormats[i] = variants[i].format; + initialTrackSelection[i] = i; + } + trackGroup = new TrackGroup(variantFormats); + trackSelection = new InitializationTrackSelection(trackGroup, initialTrackSelection); + } + + /** + * If the source is currently having difficulty providing chunks, then this method throws the + * underlying error. Otherwise does nothing. + * + * @throws IOException The underlying error. + */ + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } + } + + /** + * Returns the track group exposed by the source. + */ + public TrackGroup getTrackGroup() { + return trackGroup; + } + + /** + * Selects tracks for use. + * + * @param trackSelection The track selection. + */ + public void selectTracks(TrackSelection trackSelection) { + this.trackSelection = trackSelection; + } + + /** + * Resets the source. + */ + public void reset() { + fatalError = null; + } + + /** + * Sets whether this chunk source is responsible for initializing timestamp adjusters. + * + * @param isTimestampMaster True if this chunk source is responsible for initializing timestamp + * adjusters. + */ + public void setIsTimestampMaster(boolean isTimestampMaster) { + this.isTimestampMaster = isTimestampMaster; + } + + /** + * Returns the next chunk to load. + *

        + * If a chunk is available then {@link HlsChunkHolder#chunk} is set. If the end of the stream has + * been reached then {@link HlsChunkHolder#endOfStream} is set. If a chunk is not available but + * the end of the stream has not been reached, {@link HlsChunkHolder#playlist} is set to + * contain the {@link HlsUrl} that refers to the playlist that needs refreshing. + * + * @param previous The most recently loaded media chunk. + * @param playbackPositionUs The current playback position. If {@code previous} is null then this + * parameter is the position from which playback is expected to start (or restart) and hence + * should be interpreted as a seek position. + * @param out A holder to populate. + */ + public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) { + int oldVariantIndex = previous == null ? C.INDEX_UNSET + : trackGroup.indexOf(previous.trackFormat); + // Use start time of the previous chunk rather than its end time because switching format will + // require downloading overlapping segments. + long bufferedDurationUs = previous == null ? 0 + : Math.max(0, previous.getAdjustedStartTimeUs() - playbackPositionUs); + + // Select the variant. + trackSelection.updateSelectedTrack(bufferedDurationUs); + int newVariantIndex = trackSelection.getSelectedIndexInTrackGroup(); + + boolean switchingVariant = oldVariantIndex != newVariantIndex; + HlsMediaPlaylist mediaPlaylist = playlistTracker.getPlaylistSnapshot(variants[newVariantIndex]); + if (mediaPlaylist == null) { + out.playlist = variants[newVariantIndex]; + // Retry when playlist is refreshed. + return; + } + + // Select the chunk. + int chunkMediaSequence; + if (previous == null || switchingVariant) { + long targetPositionUs = previous == null ? playbackPositionUs : previous.startTimeUs; + if (!mediaPlaylist.hasEndTag && targetPositionUs > mediaPlaylist.getEndTimeUs()) { + // If the playlist is too old to contain the chunk, we need to refresh it. + chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size(); + } else { + chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, targetPositionUs, true, + !playlistTracker.isLive() || previous == null) + mediaPlaylist.mediaSequence; + if (chunkMediaSequence < mediaPlaylist.mediaSequence && previous != null) { + // We try getting the next chunk without adapting in case that's the reason for falling + // behind the live window. + newVariantIndex = oldVariantIndex; + mediaPlaylist = playlistTracker.getPlaylistSnapshot(variants[newVariantIndex]); + chunkMediaSequence = previous.getNextChunkIndex(); + } + } + } else { + chunkMediaSequence = previous.getNextChunkIndex(); + } + if (chunkMediaSequence < mediaPlaylist.mediaSequence) { + fatalError = new BehindLiveWindowException(); + return; + } + + int chunkIndex = chunkMediaSequence - mediaPlaylist.mediaSequence; + if (chunkIndex >= mediaPlaylist.segments.size()) { + if (mediaPlaylist.hasEndTag) { + out.endOfStream = true; + } else /* Live */ { + out.playlist = variants[newVariantIndex]; + } + return; + } + + // Handle encryption. + HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); + + // Check if encryption is specified. + if (segment.isEncrypted) { + Uri keyUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.encryptionKeyUri); + if (!keyUri.equals(encryptionKeyUri)) { + // Encryption is specified and the key has changed. + out.chunk = newEncryptionKeyChunk(keyUri, segment.encryptionIV, newVariantIndex, + trackSelection.getSelectionReason(), trackSelection.getSelectionData()); + return; + } + if (!Util.areEqual(segment.encryptionIV, encryptionIvString)) { + setEncryptionData(keyUri, segment.encryptionIV, encryptionKey); + } + } else { + clearEncryptionData(); + } + + // Compute start time and sequence number of the next chunk. + long startTimeUs = segment.startTimeUs; + if (previous != null && !switchingVariant) { + startTimeUs = previous.getAdjustedEndTimeUs(); + } + Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); + + TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster( + segment.discontinuitySequenceNumber, startTimeUs); + + DataSpec initDataSpec = null; + Segment initSegment = mediaPlaylist.initializationSegment; + if (initSegment != null) { + Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url); + initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset, + initSegment.byterangeLength, null); + } + + // Configure the data source and spec for the chunk. + DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, + null); + out.chunk = new HlsMediaChunk(dataSource, dataSpec, initDataSpec, variants[newVariantIndex], + trackSelection.getSelectionReason(), trackSelection.getSelectionData(), segment, + chunkMediaSequence, isTimestampMaster, timestampAdjuster, previous, encryptionKey, + encryptionIv); + } + + /** + * Called when the {@link HlsSampleStreamWrapper} has finished loading a chunk obtained from this + * source. + * + * @param chunk The chunk whose load has been completed. + */ + public void onChunkLoadCompleted(Chunk chunk) { + if (chunk instanceof HlsMediaChunk) { + HlsMediaChunk mediaChunk = (HlsMediaChunk) chunk; + playlistTracker.onChunkLoaded(mediaChunk.hlsUrl, mediaChunk.chunkIndex, + mediaChunk.getAdjustedStartTimeUs()); + } else if (chunk instanceof EncryptionKeyChunk) { + EncryptionKeyChunk encryptionKeyChunk = (EncryptionKeyChunk) chunk; + scratchSpace = encryptionKeyChunk.getDataHolder(); + setEncryptionData(encryptionKeyChunk.dataSpec.uri, encryptionKeyChunk.iv, + encryptionKeyChunk.getResult()); + } + } + + /** + * Called when the {@link HlsSampleStreamWrapper} encounters an error loading a chunk obtained + * from this source. + * + * @param chunk The chunk whose load encountered the error. + * @param cancelable Whether the load can be canceled. + * @param error The error. + * @return Whether the load should be canceled. + */ + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, IOException error) { + return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(trackGroup.indexOf(chunk.trackFormat)), error); + } + + /** + * Called when an error is encountered while loading a playlist. + * + * @param url The url that references the playlist whose load encountered the error. + * @param error The error. + */ + public void onPlaylistLoadError(HlsUrl url, IOException error) { + int trackGroupIndex = trackGroup.indexOf(url.format); + if (trackGroupIndex == C.INDEX_UNSET) { + // The url is not handled by this chunk source. + return; + } + ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(trackGroupIndex), error); + } + + // Private methods. + + private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex, + int trackSelectionReason, Object trackSelectionData) { + DataSpec dataSpec = new DataSpec(keyUri, 0, C.LENGTH_UNSET, null, DataSpec.FLAG_ALLOW_GZIP); + return new EncryptionKeyChunk(dataSource, dataSpec, variants[variantIndex].format, + trackSelectionReason, trackSelectionData, scratchSpace, iv); + } + + private void setEncryptionData(Uri keyUri, String iv, byte[] secretKey) { + String trimmedIv; + if (iv.toLowerCase(Locale.getDefault()).startsWith("0x")) { + trimmedIv = iv.substring(2); + } else { + trimmedIv = iv; + } + + byte[] ivData = new BigInteger(trimmedIv, 16).toByteArray(); + byte[] ivDataWithPadding = new byte[16]; + int offset = ivData.length > 16 ? ivData.length - 16 : 0; + System.arraycopy(ivData, offset, ivDataWithPadding, ivDataWithPadding.length - ivData.length + + offset, ivData.length - offset); + + encryptionKeyUri = keyUri; + encryptionKey = secretKey; + encryptionIvString = iv; + encryptionIv = ivDataWithPadding; + } + + private void clearEncryptionData() { + encryptionKeyUri = null; + encryptionKey = null; + encryptionIvString = null; + encryptionIv = null; + } + + // Private classes. + + /** + * A {@link TrackSelection} to use for initialization. + */ + private static final class InitializationTrackSelection extends BaseTrackSelection { + + private int selectedIndex; + + public InitializationTrackSelection(TrackGroup group, int[] tracks) { + super(group, tracks); + selectedIndex = indexOf(group.getFormat(0)); + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + long nowMs = SystemClock.elapsedRealtime(); + if (!isBlacklisted(selectedIndex, nowMs)) { + return; + } + // Try from lowest bitrate to highest. + for (int i = length - 1; i >= 0; i--) { + if (!isBlacklisted(i, nowMs)) { + selectedIndex = i; + return; + } + } + // Should never happen. + throw new IllegalStateException(); + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return C.SELECTION_REASON_UNKNOWN; + } + + @Override + public Object getSelectionData() { + return null; + } + + } + + private static final class EncryptionKeyChunk extends DataChunk { + + public final String iv; + + private byte[] result; + + public EncryptionKeyChunk(DataSource dataSource, DataSpec dataSpec, Format trackFormat, + int trackSelectionReason, Object trackSelectionData, byte[] scratchSpace, String iv) { + super(dataSource, dataSpec, C.DATA_TYPE_DRM, trackFormat, trackSelectionReason, + trackSelectionData, scratchSpace); + this.iv = iv; + } + + @Override + protected void consume(byte[] data, int limit) throws IOException { + result = Arrays.copyOf(data, limit); + } + + public byte[] getResult() { + return result; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java new file mode 100755 index 00000000000..e3239448d42 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaChunk.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.mp3.Mp3Extractor; +import org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import org.telegram.messenger.exoplayer2.extractor.ts.Ac3Extractor; +import org.telegram.messenger.exoplayer2.extractor.ts.AdtsExtractor; +import org.telegram.messenger.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; +import org.telegram.messenger.exoplayer2.extractor.ts.TsExtractor; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An HLS {@link MediaChunk}. + */ +/* package */ final class HlsMediaChunk extends MediaChunk { + + private static final AtomicInteger UID_SOURCE = new AtomicInteger(); + + private static final String AAC_FILE_EXTENSION = ".aac"; + private static final String AC3_FILE_EXTENSION = ".ac3"; + private static final String EC3_FILE_EXTENSION = ".ec3"; + private static final String MP3_FILE_EXTENSION = ".mp3"; + private static final String MP4_FILE_EXTENSION = ".mp4"; + private static final String VTT_FILE_EXTENSION = ".vtt"; + private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; + + /** + * A unique identifier for the chunk. + */ + public final int uid; + + /** + * The discontinuity sequence number of the chunk. + */ + public final int discontinuitySequenceNumber; + + /** + * The url of the playlist from which this chunk was obtained. + */ + public final HlsUrl hlsUrl; + + private final DataSource initDataSource; + private final DataSpec initDataSpec; + private final boolean isEncrypted; + private final boolean isMasterTimestampSource; + private final TimestampAdjuster timestampAdjuster; + private final HlsMediaChunk previousChunk; + + private Extractor extractor; + private int initSegmentBytesLoaded; + private int bytesLoaded; + private boolean initLoadCompleted; + private HlsSampleStreamWrapper extractorOutput; + private long adjustedEndTimeUs; + private volatile boolean loadCanceled; + private volatile boolean loadCompleted; + + /** + * @param dataSource The source from which the data should be loaded. + * @param dataSpec Defines the data to be loaded. + * @param initDataSpec Defines the initialization data to be fed to new extractors. May be null. + * @param hlsUrl The url of the playlist from which this chunk was obtained. + * @param trackSelectionReason See {@link #trackSelectionReason}. + * @param trackSelectionData See {@link #trackSelectionData}. + * @param segment The {@link Segment} for which this media chunk is created. + * @param chunkIndex The media sequence number of the chunk. + * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. + * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. + * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. + * @param encryptionKey For AES encryption chunks, the encryption key. + * @param encryptionIv For AES encryption chunks, the encryption initialization vector. + */ + public HlsMediaChunk(DataSource dataSource, DataSpec dataSpec, DataSpec initDataSpec, + HlsUrl hlsUrl, int trackSelectionReason, Object trackSelectionData, Segment segment, + int chunkIndex, boolean isMasterTimestampSource, TimestampAdjuster timestampAdjuster, + HlsMediaChunk previousChunk, byte[] encryptionKey, byte[] encryptionIv) { + super(buildDataSource(dataSource, encryptionKey, encryptionIv), dataSpec, hlsUrl.format, + trackSelectionReason, trackSelectionData, segment.startTimeUs, + segment.startTimeUs + segment.durationUs, chunkIndex); + this.initDataSpec = initDataSpec; + this.hlsUrl = hlsUrl; + this.isMasterTimestampSource = isMasterTimestampSource; + this.timestampAdjuster = timestampAdjuster; + this.previousChunk = previousChunk; + // Note: this.dataSource and dataSource may be different. + this.isEncrypted = this.dataSource instanceof Aes128DataSource; + initDataSource = dataSource; + discontinuitySequenceNumber = segment.discontinuitySequenceNumber; + adjustedEndTimeUs = endTimeUs; + uid = UID_SOURCE.getAndIncrement(); + } + + /** + * Initializes the chunk for loading, setting the {@link HlsSampleStreamWrapper} that will receive + * samples as they are loaded. + * + * @param output The output that will receive the loaded samples. + */ + public void init(HlsSampleStreamWrapper output) { + extractorOutput = output; + output.init(uid, previousChunk != null && previousChunk.hlsUrl != hlsUrl); + } + + /** + * Returns the presentation time in microseconds of the first sample in the chunk. + */ + public long getAdjustedStartTimeUs() { + return adjustedEndTimeUs - getDurationUs(); + } + + /** + * Returns the presentation time in microseconds of the last sample in the chunk + */ + public long getAdjustedEndTimeUs() { + return adjustedEndTimeUs; + } + + @Override + public boolean isLoadCompleted() { + return loadCompleted; + } + + @Override + public long bytesLoaded() { + return bytesLoaded; + } + + // Loadable implementation + + @Override + public void cancelLoad() { + loadCanceled = true; + } + + @Override + public boolean isLoadCanceled() { + return loadCanceled; + } + + @Override + public void load() throws IOException, InterruptedException { + if (extractor == null) { + extractor = buildExtractor(); + } + maybeLoadInitData(); + if (!loadCanceled) { + loadMedia(); + } + } + + // Private methods. + + private Extractor buildExtractor() { + // Set the extractor that will read the chunk. + Extractor extractor; + boolean needNewExtractor = previousChunk == null + || previousChunk.discontinuitySequenceNumber != discontinuitySequenceNumber + || trackFormat != previousChunk.trackFormat; + boolean usingNewExtractor = true; + String lastPathSegment = dataSpec.uri.getLastPathSegment(); + if (lastPathSegment.endsWith(AAC_FILE_EXTENSION)) { + // TODO: Inject a timestamp adjuster and use it along with ID3 PRIV tag values with owner + // identifier com.apple.streaming.transportStreamTimestamp. This may also apply to the MP3 + // case below. + extractor = new AdtsExtractor(startTimeUs); + } else if (lastPathSegment.endsWith(AC3_FILE_EXTENSION) + || lastPathSegment.endsWith(EC3_FILE_EXTENSION)) { + extractor = new Ac3Extractor(startTimeUs); + } else if (lastPathSegment.endsWith(MP3_FILE_EXTENSION)) { + extractor = new Mp3Extractor(startTimeUs); + } else if (lastPathSegment.endsWith(WEBVTT_FILE_EXTENSION) + || lastPathSegment.endsWith(VTT_FILE_EXTENSION)) { + extractor = new WebvttExtractor(trackFormat.language, timestampAdjuster); + } else if (!needNewExtractor) { + // Only reuse TS and fMP4 extractors. + usingNewExtractor = false; + extractor = previousChunk.extractor; + } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) { + extractor = new FragmentedMp4Extractor(0, timestampAdjuster); + } else { + // MPEG-2 TS segments, but we need a new extractor. + // This flag ensures the change of pid between streams does not affect the sample queues. + @DefaultTsPayloadReaderFactory.Flags + int esReaderFactoryFlags = 0; + String codecs = trackFormat.codecs; + if (!TextUtils.isEmpty(codecs)) { + // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really + // exist. If we know from the codec attribute that they don't exist, then we can + // explicitly ignore them even if they're declared. + if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; + } + if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; + } + } + extractor = new TsExtractor(timestampAdjuster, + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); + } + if (usingNewExtractor) { + extractor.init(extractorOutput); + } + return extractor; + } + + private void maybeLoadInitData() throws IOException, InterruptedException { + if (previousChunk == null || previousChunk.extractor != extractor || initLoadCompleted + || initDataSpec == null) { + return; + } + DataSpec initSegmentDataSpec = Util.getRemainderDataSpec(initDataSpec, initSegmentBytesLoaded); + try { + ExtractorInput input = new DefaultExtractorInput(initDataSource, + initSegmentDataSpec.absoluteStreamPosition, initDataSource.open(initSegmentDataSpec)); + try { + int result = Extractor.RESULT_CONTINUE; + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + } finally { + initSegmentBytesLoaded += (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + initLoadCompleted = true; + } + + private void loadMedia() throws IOException, InterruptedException { + // If we previously fed part of this chunk to the extractor, we need to skip it this time. For + // encrypted content we need to skip the data by reading it through the source, so as to ensure + // correct decryption of the remainder of the chunk. For clear content, we can request the + // remainder of the chunk directly. + DataSpec loadDataSpec; + boolean skipLoadedBytes; + if (isEncrypted) { + loadDataSpec = dataSpec; + skipLoadedBytes = bytesLoaded != 0; + } else { + loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded); + skipLoadedBytes = false; + } + try { + ExtractorInput input = new DefaultExtractorInput(dataSource, + loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); + if (skipLoadedBytes) { + input.skipFully(bytesLoaded); + } + try { + int result = Extractor.RESULT_CONTINUE; + if (!isMasterTimestampSource && timestampAdjuster != null) { + timestampAdjuster.waitUntilInitialized(); + } + while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { + result = extractor.read(input, null); + } + long adjustedEndTimeUs = extractorOutput.getLargestQueuedTimestampUs(); + if (adjustedEndTimeUs != Long.MIN_VALUE) { + this.adjustedEndTimeUs = adjustedEndTimeUs; + } + } finally { + bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition); + } + } finally { + Util.closeQuietly(dataSource); + } + loadCompleted = true; + } + + /** + * If the content is encrypted, returns an {@link Aes128DataSource} that wraps the original in + * order to decrypt the loaded data. Else returns the original. + */ + private static DataSource buildDataSource(DataSource dataSource, byte[] encryptionKey, + byte[] encryptionIv) { + if (encryptionKey == null || encryptionIv == null) { + return dataSource; + } + return new Aes128DataSource(dataSource, encryptionKey, encryptionIv); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java new file mode 100755 index 00000000000..7c39fe75aca --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaPeriod.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.os.Handler; +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.CompositeSequenceableLoader; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +/** + * A {@link MediaPeriod} that loads an HLS stream. + */ +public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper.Callback, + HlsPlaylistTracker.PlaylistRefreshCallback { + + private final HlsPlaylistTracker playlistTracker; + private final DataSource.Factory dataSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final Allocator allocator; + private final IdentityHashMap streamWrapperIndices; + private final TimestampAdjusterProvider timestampAdjusterProvider; + private final Handler continueLoadingHandler; + private final Loader manifestFetcher; + private final long preparePositionUs; + + private Callback callback; + private int pendingPrepareCount; + private boolean seenFirstTrackSelection; + private TrackGroupArray trackGroups; + private HlsSampleStreamWrapper[] sampleStreamWrappers; + private HlsSampleStreamWrapper[] enabledSampleStreamWrappers; + private CompositeSequenceableLoader sequenceableLoader; + + public HlsMediaPeriod(HlsPlaylistTracker playlistTracker, DataSource.Factory dataSourceFactory, + int minLoadableRetryCount, EventDispatcher eventDispatcher, Allocator allocator, + long positionUs) { + this.playlistTracker = playlistTracker; + this.dataSourceFactory = dataSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.allocator = allocator; + streamWrapperIndices = new IdentityHashMap<>(); + timestampAdjusterProvider = new TimestampAdjusterProvider(); + continueLoadingHandler = new Handler(); + manifestFetcher = new Loader("Loader:ManifestFetcher"); + preparePositionUs = positionUs; + } + + public void release() { + continueLoadingHandler.removeCallbacksAndMessages(null); + manifestFetcher.release(); + if (sampleStreamWrappers != null) { + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.release(); + } + } + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + buildAndPrepareSampleStreamWrappers(); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + if (sampleStreamWrappers == null) { + manifestFetcher.maybeThrowError(); + } else { + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + sampleStreamWrapper.maybeThrowPrepareError(); + } + } + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + // Map each selection and stream onto a child period index. + int[] streamChildIndices = new int[selections.length]; + int[] selectionChildIndices = new int[selections.length]; + for (int i = 0; i < selections.length; i++) { + streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET + : streamWrapperIndices.get(streams[i]); + selectionChildIndices[i] = C.INDEX_UNSET; + if (selections[i] != null) { + TrackGroup trackGroup = selections[i].getTrackGroup(); + for (int j = 0; j < sampleStreamWrappers.length; j++) { + if (sampleStreamWrappers[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { + selectionChildIndices[i] = j; + break; + } + } + } + } + boolean selectedNewTracks = false; + streamWrapperIndices.clear(); + // Select tracks for each child, copying the resulting streams back into a new streams array. + SampleStream[] newStreams = new SampleStream[selections.length]; + SampleStream[] childStreams = new SampleStream[selections.length]; + TrackSelection[] childSelections = new TrackSelection[selections.length]; + ArrayList enabledSampleStreamWrapperList = new ArrayList<>( + sampleStreamWrappers.length); + for (int i = 0; i < sampleStreamWrappers.length; i++) { + for (int j = 0; j < selections.length; j++) { + childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; + childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; + } + selectedNewTracks |= sampleStreamWrappers[i].selectTracks(childSelections, + mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection); + boolean wrapperEnabled = false; + for (int j = 0; j < selections.length; j++) { + if (selectionChildIndices[j] == i) { + // Assert that the child provided a stream for the selection. + Assertions.checkState(childStreams[j] != null); + newStreams[j] = childStreams[j]; + wrapperEnabled = true; + streamWrapperIndices.put(childStreams[j], i); + } else if (streamChildIndices[j] == i) { + // Assert that the child cleared any previous stream. + Assertions.checkState(childStreams[j] == null); + } + } + if (wrapperEnabled) { + enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]); + } + } + // Copy the new streams back into the streams array. + System.arraycopy(newStreams, 0, streams, 0, newStreams.length); + // Update the local state. + enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; + enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); + + // The first enabled sample stream wrapper is responsible for intializing the timestamp + // adjuster. This way, if present, variants are responsible. Otherwise, audio renditions are. + // If only subtitles are present, then text renditions are used for timestamp adjustment + // initialization. + if (enabledSampleStreamWrappers.length > 0) { + enabledSampleStreamWrappers[0].setIsTimestampMaster(true); + for (int i = 1; i < enabledSampleStreamWrappers.length; i++) { + enabledSampleStreamWrappers[i].setIsTimestampMaster(false); + } + } + + sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); + if (seenFirstTrackSelection && selectedNewTracks) { + seekToUs(positionUs); + // We'll need to reset renderers consuming from all streams due to the seek. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null) { + streamResetFlags[i] = true; + } + } + } + seenFirstTrackSelection = true; + return positionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + long rendererBufferedPositionUs = sampleStreamWrapper.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + timestampAdjusterProvider.reset(); + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + sampleStreamWrapper.seekTo(positionUs); + } + return positionUs; + } + + // HlsSampleStreamWrapper.Callback implementation. + + @Override + public void onPrepared() { + if (--pendingPrepareCount > 0) { + return; + } + + int totalTrackGroupCount = 0; + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + totalTrackGroupCount += sampleStreamWrapper.getTrackGroups().length; + } + TrackGroup[] trackGroupArray = new TrackGroup[totalTrackGroupCount]; + int trackGroupIndex = 0; + for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) { + int wrapperTrackGroupCount = sampleStreamWrapper.getTrackGroups().length; + for (int j = 0; j < wrapperTrackGroupCount; j++) { + trackGroupArray[trackGroupIndex++] = sampleStreamWrapper.getTrackGroups().get(j); + } + } + trackGroups = new TrackGroupArray(trackGroupArray); + callback.onPrepared(this); + } + + @Override + public void onPlaylistRefreshRequired(HlsUrl url) { + playlistTracker.refreshPlaylist(url, this); + } + + @Override + public void onContinueLoadingRequested(HlsSampleStreamWrapper sampleStreamWrapper) { + if (trackGroups == null) { + // Still preparing. + return; + } + callback.onContinueLoadingRequested(this); + } + + // PlaylistListener implementation. + + @Override + public void onPlaylistChanged() { + if (trackGroups != null) { + callback.onContinueLoadingRequested(this); + } else { + // Some of the wrappers were waiting for their media playlist to prepare. + for (HlsSampleStreamWrapper wrapper : sampleStreamWrappers) { + wrapper.continuePreparing(); + } + } + } + + @Override + public void onPlaylistLoadError(HlsUrl url, IOException error) { + for (HlsSampleStreamWrapper sampleStreamWrapper : enabledSampleStreamWrappers) { + sampleStreamWrapper.onPlaylistLoadError(url, error); + } + callback.onContinueLoadingRequested(this); + } + + // Internal methods. + + private void buildAndPrepareSampleStreamWrappers() { + HlsMasterPlaylist masterPlaylist = playlistTracker.getMasterPlaylist(); + // Build the default stream wrapper. + List selectedVariants = new ArrayList<>(masterPlaylist.variants); + ArrayList definiteVideoVariants = new ArrayList<>(); + ArrayList definiteAudioOnlyVariants = new ArrayList<>(); + for (int i = 0; i < selectedVariants.size(); i++) { + HlsUrl variant = selectedVariants.get(i); + if (variant.format.height > 0 || variantHasExplicitCodecWithPrefix(variant, "avc")) { + definiteVideoVariants.add(variant); + } else if (variantHasExplicitCodecWithPrefix(variant, "mp4a")) { + definiteAudioOnlyVariants.add(variant); + } + } + if (!definiteVideoVariants.isEmpty()) { + // We've identified some variants as definitely containing video. Assume variants within the + // master playlist are marked consistently, and hence that we have the full set. Filter out + // any other variants, which are likely to be audio only. + selectedVariants = definiteVideoVariants; + } else if (definiteAudioOnlyVariants.size() < selectedVariants.size()) { + // We've identified some variants, but not all, as being audio only. Filter them out to leave + // the remaining variants, which are likely to contain video. + selectedVariants.removeAll(definiteAudioOnlyVariants); + } else { + // Leave the enabled variants unchanged. They're likely either all video or all audio. + } + List audioRenditions = masterPlaylist.audios; + List subtitleRenditions = masterPlaylist.subtitles; + sampleStreamWrappers = new HlsSampleStreamWrapper[1 /* variants */ + audioRenditions.size() + + subtitleRenditions.size()]; + int currentWrapperIndex = 0; + pendingPrepareCount = sampleStreamWrappers.length; + + Assertions.checkArgument(!selectedVariants.isEmpty()); + HlsUrl[] variants = new HlsMasterPlaylist.HlsUrl[selectedVariants.size()]; + selectedVariants.toArray(variants); + HlsSampleStreamWrapper sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_DEFAULT, + variants, masterPlaylist.muxedAudioFormat, masterPlaylist.muxedCaptionFormat); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + sampleStreamWrapper.setIsTimestampMaster(true); + sampleStreamWrapper.continuePreparing(); + + // TODO: Build video stream wrappers here. + + // Build audio stream wrappers. + for (int i = 0; i < audioRenditions.size(); i++) { + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_AUDIO, + new HlsUrl[] {audioRenditions.get(i)}, null, null); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + sampleStreamWrapper.continuePreparing(); + } + + // Build subtitle stream wrappers. + for (int i = 0; i < subtitleRenditions.size(); i++) { + HlsUrl url = subtitleRenditions.get(i); + sampleStreamWrapper = buildSampleStreamWrapper(C.TRACK_TYPE_TEXT, new HlsUrl[] {url}, null, + null); + sampleStreamWrapper.prepareSingleTrack(url.format); + sampleStreamWrappers[currentWrapperIndex++] = sampleStreamWrapper; + } + } + + private HlsSampleStreamWrapper buildSampleStreamWrapper(int trackType, HlsUrl[] variants, + Format muxedAudioFormat, Format muxedCaptionFormat) { + DataSource dataSource = dataSourceFactory.createDataSource(); + HlsChunkSource defaultChunkSource = new HlsChunkSource(playlistTracker, variants, dataSource, + timestampAdjusterProvider); + return new HlsSampleStreamWrapper(trackType, this, defaultChunkSource, allocator, + preparePositionUs, muxedAudioFormat, muxedCaptionFormat, minLoadableRetryCount, + eventDispatcher); + } + + private static boolean variantHasExplicitCodecWithPrefix(HlsUrl variant, String prefix) { + String codecs = variant.format.codecs; + if (TextUtils.isEmpty(codecs)) { + return false; + } + String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); + for (String codec : codecArray) { + if (codec.startsWith(prefix)) { + return true; + } + } + return false; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java new file mode 100755 index 00000000000..6f64354d9da --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsMediaSource.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.net.Uri; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.SinglePeriodTimeline; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsPlaylistTracker; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.util.List; + +/** + * An HLS {@link MediaSource}. + */ +public final class HlsMediaSource implements MediaSource, + HlsPlaylistTracker.PrimaryPlaylistListener { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + + private final Uri manifestUri; + private final DataSource.Factory dataSourceFactory; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + + private HlsPlaylistTracker playlistTracker; + private MediaSource.Listener sourceListener; + + public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, dataSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, eventHandler, + eventListener); + } + + public HlsMediaSource(Uri manifestUri, DataSource.Factory dataSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this.manifestUri = manifestUri; + this.dataSourceFactory = dataSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + } + + @Override + public void prepareSource(MediaSource.Listener listener) { + Assertions.checkState(playlistTracker == null); + playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher, + minLoadableRetryCount, this); + sourceListener = listener; + playlistTracker.start(); + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + playlistTracker.maybeThrowPrimaryPlaylistRefreshError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + return new HlsMediaPeriod(playlistTracker, dataSourceFactory, minLoadableRetryCount, + eventDispatcher, allocator, positionUs); + } + + @Override + public void releasePeriod(MediaPeriod mediaPeriod) { + ((HlsMediaPeriod) mediaPeriod).release(); + } + + @Override + public void releaseSource() { + playlistTracker.release(); + playlistTracker = null; + sourceListener = null; + } + + @Override + public void onPrimaryPlaylistRefreshed(HlsMediaPlaylist playlist) { + SinglePeriodTimeline timeline; + if (playlistTracker.isLive()) { + // TODO: fix windowPositionInPeriodUs when playlist is empty. + long windowPositionInPeriodUs = playlist.getStartTimeUs(); + List segments = playlist.segments; + long windowDefaultStartPositionUs = segments.isEmpty() ? 0 + : segments.get(Math.max(0, segments.size() - 3)).startTimeUs - windowPositionInPeriodUs; + timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs, + windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag); + } else /* not live */ { + timeline = new SinglePeriodTimeline(playlist.durationUs, playlist.durationUs, 0, 0, true, + false); + } + sourceListener.onSourceInfoRefreshed(timeline, playlist); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStream.java new file mode 100755 index 00000000000..37ef1539af0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStream.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import java.io.IOException; + +/** + * {@link SampleStream} for a particular track group in HLS. + */ +/* package */ final class HlsSampleStream implements SampleStream { + + public final int group; + + private final HlsSampleStreamWrapper sampleStreamWrapper; + + public HlsSampleStream(HlsSampleStreamWrapper sampleStreamWrapper, int group) { + this.sampleStreamWrapper = sampleStreamWrapper; + this.group = group; + } + + @Override + public boolean isReady() { + return sampleStreamWrapper.isReady(group); + } + + @Override + public void maybeThrowError() throws IOException { + sampleStreamWrapper.maybeThrowError(); + } + + @Override + public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { + return sampleStreamWrapper.readData(group, formatHolder, buffer); + } + + @Override + public void skipToKeyframeBefore(long timeUs) { + sampleStreamWrapper.skipToKeyframeBefore(group, timeUs); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java new file mode 100755 index 00000000000..8129bce7c8f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -0,0 +1,675 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.os.Handler; +import android.text.TextUtils; +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput; +import org.telegram.messenger.exoplayer2.extractor.DefaultTrackOutput.UpstreamFormatChangedListener; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.SequenceableLoader; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.chunk.Chunk; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.io.IOException; +import java.util.LinkedList; + +/** + * Loads {@link HlsMediaChunk}s obtained from a {@link HlsChunkSource}, and provides + * {@link SampleStream}s from which the loaded media can be consumed. + */ +/* package */ final class HlsSampleStreamWrapper implements Loader.Callback, + SequenceableLoader, ExtractorOutput, UpstreamFormatChangedListener { + + /** + * A callback to be notified of events. + */ + public interface Callback extends SequenceableLoader.Callback { + + /** + * Called when the wrapper has been prepared. + */ + void onPrepared(); + + /** + * Called to schedule a {@link #continueLoading(long)} call when the playlist referred by the + * given url changes. + */ + void onPlaylistRefreshRequired(HlsMasterPlaylist.HlsUrl playlistUrl); + + } + + private static final int PRIMARY_TYPE_NONE = 0; + private static final int PRIMARY_TYPE_TEXT = 1; + private static final int PRIMARY_TYPE_AUDIO = 2; + private static final int PRIMARY_TYPE_VIDEO = 3; + + private final int trackType; + private final Callback callback; + private final HlsChunkSource chunkSource; + private final Allocator allocator; + private final Format muxedAudioFormat; + private final Format muxedCaptionFormat; + private final int minLoadableRetryCount; + private final Loader loader; + private final EventDispatcher eventDispatcher; + private final HlsChunkSource.HlsChunkHolder nextChunkHolder; + private final SparseArray sampleQueues; + private final LinkedList mediaChunks; + private final Runnable maybeFinishPrepareRunnable; + private final Handler handler; + + private boolean sampleQueuesBuilt; + private boolean prepared; + private int enabledTrackCount; + private Format downstreamTrackFormat; + private int upstreamChunkUid; + private boolean released; + + // Tracks are complicated in HLS. See documentation of buildTracks for details. + // Indexed by track (as exposed by this source). + private TrackGroupArray trackGroups; + private int primaryTrackGroupIndex; + // Indexed by group. + private boolean[] groupEnabledStates; + + private long lastSeekPositionUs; + private long pendingResetPositionUs; + + private boolean loadingFinished; + + /** + * @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants. + * @param callback A callback for the wrapper. + * @param chunkSource A {@link HlsChunkSource} from which chunks to load are obtained. + * @param allocator An {@link Allocator} from which to obtain media buffer allocations. + * @param positionUs The position from which to start loading media. + * @param muxedAudioFormat If HLS master playlist indicates that the stream contains muxed audio, + * this is the audio {@link Format} as defined by the playlist. + * @param muxedCaptionFormat If HLS master playlist indicates that the stream contains muxed + * captions, this is the audio {@link Format} as defined by the playlist. + * @param minLoadableRetryCount The minimum number of times that the source should retry a load + * before propagating an error. + * @param eventDispatcher A dispatcher to notify of events. + */ + public HlsSampleStreamWrapper(int trackType, Callback callback, HlsChunkSource chunkSource, + Allocator allocator, long positionUs, Format muxedAudioFormat, Format muxedCaptionFormat, + int minLoadableRetryCount, EventDispatcher eventDispatcher) { + this.trackType = trackType; + this.callback = callback; + this.chunkSource = chunkSource; + this.allocator = allocator; + this.muxedAudioFormat = muxedAudioFormat; + this.muxedCaptionFormat = muxedCaptionFormat; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + loader = new Loader("Loader:HlsSampleStreamWrapper"); + nextChunkHolder = new HlsChunkSource.HlsChunkHolder(); + sampleQueues = new SparseArray<>(); + mediaChunks = new LinkedList<>(); + maybeFinishPrepareRunnable = new Runnable() { + @Override + public void run() { + maybeFinishPrepare(); + } + }; + handler = new Handler(); + lastSeekPositionUs = positionUs; + pendingResetPositionUs = positionUs; + } + + public void continuePreparing() { + if (!prepared) { + continueLoading(lastSeekPositionUs); + } + } + + /** + * Prepares a sample stream wrapper for which the master playlist provides enough information to + * prepare. + */ + public void prepareSingleTrack(Format format) { + track(0).format(format); + sampleQueuesBuilt = true; + maybeFinishPrepare(); + } + + public void maybeThrowPrepareError() throws IOException { + maybeThrowError(); + } + + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + public boolean selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, boolean isFirstTrackSelection) { + Assertions.checkState(prepared); + // Disable old tracks. + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { + int group = ((HlsSampleStream) streams[i]).group; + setTrackGroupEnabledState(group, false); + sampleQueues.valueAt(group).disable(); + streams[i] = null; + } + } + // Enable new tracks. + boolean selectedNewTracks = false; + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + TrackSelection selection = selections[i]; + int group = trackGroups.indexOf(selection.getTrackGroup()); + setTrackGroupEnabledState(group, true); + if (group == primaryTrackGroupIndex) { + chunkSource.selectTracks(selection); + } + streams[i] = new HlsSampleStream(this, group); + streamResetFlags[i] = true; + selectedNewTracks = true; + } + } + if (isFirstTrackSelection) { + // At the time of the first track selection all queues will be enabled, so we need to disable + // any that are no longer required. + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + if (!groupEnabledStates[i]) { + sampleQueues.valueAt(i).disable(); + } + } + } + // Cancel requests if necessary. + if (enabledTrackCount == 0) { + chunkSource.reset(); + downstreamTrackFormat = null; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } + } + return selectedNewTracks; + } + + public void seekTo(long positionUs) { + lastSeekPositionUs = positionUs; + pendingResetPositionUs = positionUs; + loadingFinished = false; + mediaChunks.clear(); + if (loader.isLoading()) { + loader.cancelLoading(); + } else { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).reset(groupEnabledStates[i]); + } + } + } + + public long getBufferedPositionUs() { + if (loadingFinished) { + return C.TIME_END_OF_SOURCE; + } else if (isPendingReset()) { + return pendingResetPositionUs; + } else { + long bufferedPositionUs = lastSeekPositionUs; + HlsMediaChunk lastMediaChunk = mediaChunks.getLast(); + HlsMediaChunk lastCompletedMediaChunk = lastMediaChunk.isLoadCompleted() ? lastMediaChunk + : mediaChunks.size() > 1 ? mediaChunks.get(mediaChunks.size() - 2) : null; + if (lastCompletedMediaChunk != null) { + bufferedPositionUs = Math.max(bufferedPositionUs, lastCompletedMediaChunk.endTimeUs); + } + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + bufferedPositionUs = Math.max(bufferedPositionUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + return bufferedPositionUs; + } + } + + public void release() { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).disable(); + } + loader.release(); + handler.removeCallbacksAndMessages(null); + released = true; + } + + public long getLargestQueuedTimestampUs() { + long largestQueuedTimestampUs = Long.MIN_VALUE; + for (int i = 0; i < sampleQueues.size(); i++) { + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); + } + return largestQueuedTimestampUs; + } + + public void setIsTimestampMaster(boolean isTimestampMaster) { + chunkSource.setIsTimestampMaster(isTimestampMaster); + } + + public void onPlaylistLoadError(HlsUrl url, IOException error) { + chunkSource.onPlaylistLoadError(url, error); + } + + // SampleStream implementation. + + /* package */ boolean isReady(int group) { + return loadingFinished || (!isPendingReset() && !sampleQueues.valueAt(group).isEmpty()); + } + + /* package */ void maybeThrowError() throws IOException { + loader.maybeThrowError(); + chunkSource.maybeThrowError(); + } + + /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) { + if (isPendingReset()) { + return C.RESULT_NOTHING_READ; + } + + while (mediaChunks.size() > 1 && finishedReadingChunk(mediaChunks.getFirst())) { + mediaChunks.removeFirst(); + } + HlsMediaChunk currentChunk = mediaChunks.getFirst(); + Format trackFormat = currentChunk.trackFormat; + if (!trackFormat.equals(downstreamTrackFormat)) { + eventDispatcher.downstreamFormatChanged(trackType, trackFormat, + currentChunk.trackSelectionReason, currentChunk.trackSelectionData, + currentChunk.startTimeUs); + } + downstreamTrackFormat = trackFormat; + + return sampleQueues.valueAt(group).readData(formatHolder, buffer, loadingFinished, + lastSeekPositionUs); + } + + /* package */ void skipToKeyframeBefore(int group, long timeUs) { + sampleQueues.valueAt(group).skipToKeyframeBefore(timeUs); + } + + private boolean finishedReadingChunk(HlsMediaChunk chunk) { + int chunkUid = chunk.uid; + for (int i = 0; i < sampleQueues.size(); i++) { + if (groupEnabledStates[i] && sampleQueues.valueAt(i).peekSourceId() == chunkUid) { + return false; + } + } + return true; + } + + // SequenceableLoader implementation + + @Override + public boolean continueLoading(long positionUs) { + if (loadingFinished || loader.isLoading()) { + return false; + } + + chunkSource.getNextChunk(mediaChunks.isEmpty() ? null : mediaChunks.getLast(), + pendingResetPositionUs != C.TIME_UNSET ? pendingResetPositionUs : positionUs, + nextChunkHolder); + boolean endOfStream = nextChunkHolder.endOfStream; + Chunk loadable = nextChunkHolder.chunk; + HlsMasterPlaylist.HlsUrl playlistToLoad = nextChunkHolder.playlist; + nextChunkHolder.clear(); + + if (endOfStream) { + loadingFinished = true; + return true; + } + + if (loadable == null) { + if (playlistToLoad != null) { + callback.onPlaylistRefreshRequired(playlistToLoad); + } + return false; + } + + if (isMediaChunk(loadable)) { + pendingResetPositionUs = C.TIME_UNSET; + HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable; + mediaChunk.init(this); + mediaChunks.add(mediaChunk); + } + long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs); + return true; + } + + @Override + public long getNextLoadPositionUs() { + if (isPendingReset()) { + return pendingResetPositionUs; + } else { + return loadingFinished ? C.TIME_END_OF_SOURCE : mediaChunks.getLast().endTimeUs; + } + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs) { + chunkSource.onChunkLoadCompleted(loadable); + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + if (!prepared) { + continueLoading(lastSeekPositionUs); + } else { + callback.onContinueLoadingRequested(this); + } + } + + @Override + public void onLoadCanceled(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded()); + if (!released) { + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + sampleQueues.valueAt(i).reset(groupEnabledStates[i]); + } + callback.onContinueLoadingRequested(this); + } + } + + @Override + public int onLoadError(Chunk loadable, long elapsedRealtimeMs, long loadDurationMs, + IOException error) { + long bytesLoaded = loadable.bytesLoaded(); + boolean isMediaChunk = isMediaChunk(loadable); + boolean cancelable = !isMediaChunk || bytesLoaded == 0; + boolean canceled = false; + if (chunkSource.onChunkLoadError(loadable, cancelable, error)) { + if (isMediaChunk) { + HlsMediaChunk removed = mediaChunks.removeLast(); + Assertions.checkState(removed == loadable); + if (mediaChunks.isEmpty()) { + pendingResetPositionUs = lastSeekPositionUs; + } + } + canceled = true; + } + eventDispatcher.loadError(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, + loadable.trackSelectionReason, loadable.trackSelectionData, loadable.startTimeUs, + loadable.endTimeUs, elapsedRealtimeMs, loadDurationMs, loadable.bytesLoaded(), error, + canceled); + if (canceled) { + if (!prepared) { + continueLoading(lastSeekPositionUs); + } else { + callback.onContinueLoadingRequested(this); + } + return Loader.DONT_RETRY; + } else { + return Loader.RETRY; + } + } + + // Called by the consuming thread, but only when there is no loading thread. + + /** + * Initializes the wrapper for loading a chunk. + * + * @param chunkUid The chunk's uid. + * @param shouldSpliceIn Whether the samples parsed from the chunk should be spliced into any + * samples already queued to the wrapper. + */ + public void init(int chunkUid, boolean shouldSpliceIn) { + upstreamChunkUid = chunkUid; + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).sourceId(chunkUid); + } + if (shouldSpliceIn) { + for (int i = 0; i < sampleQueues.size(); i++) { + sampleQueues.valueAt(i).splice(); + } + } + } + + // ExtractorOutput implementation. Called by the loading thread. + + @Override + public DefaultTrackOutput track(int id) { + if (sampleQueues.indexOfKey(id) >= 0) { + return sampleQueues.get(id); + } + DefaultTrackOutput trackOutput = new DefaultTrackOutput(allocator); + trackOutput.setUpstreamFormatChangeListener(this); + trackOutput.sourceId(upstreamChunkUid); + sampleQueues.put(id, trackOutput); + return trackOutput; + } + + @Override + public void endTracks() { + sampleQueuesBuilt = true; + handler.post(maybeFinishPrepareRunnable); + } + + @Override + public void seekMap(SeekMap seekMap) { + // Do nothing. + } + + // UpstreamFormatChangedListener implementation. Called by the loading thread. + + @Override + public void onUpstreamFormatChanged(Format format) { + handler.post(maybeFinishPrepareRunnable); + } + + // Internal methods. + + private void maybeFinishPrepare() { + if (released || prepared || !sampleQueuesBuilt) { + return; + } + int sampleQueueCount = sampleQueues.size(); + for (int i = 0; i < sampleQueueCount; i++) { + if (sampleQueues.valueAt(i).getUpstreamFormat() == null) { + return; + } + } + buildTracks(); + prepared = true; + callback.onPrepared(); + } + + /** + * Builds tracks that are exposed by this {@link HlsSampleStreamWrapper} instance, as well as + * internal data-structures required for operation. + *

        + * Tracks in HLS are complicated. A HLS master playlist contains a number of "variants". Each + * variant stream typically contains muxed video, audio and (possibly) additional audio, metadata + * and caption tracks. We wish to allow the user to select between an adaptive track that spans + * all variants, as well as each individual variant. If multiple audio tracks are present within + * each variant then we wish to allow the user to select between those also. + *

        + * To do this, tracks are constructed as follows. The {@link HlsChunkSource} exposes (N+1) tracks, + * where N is the number of variants defined in the HLS master playlist. These consist of one + * adaptive track defined to span all variants and a track for each individual variant. The + * adaptive track is initially selected. The extractor is then prepared to discover the tracks + * inside of each variant stream. The two sets of tracks are then combined by this method to + * create a third set, which is the set exposed by this {@link HlsSampleStreamWrapper}: + *

          + *
        • The extractor tracks are inspected to infer a "primary" track type. If a video track is + * present then it is always the primary type. If not, audio is the primary type if present. + * Else text is the primary type if present. Else there is no primary type.
        • + *
        • If there is exactly one extractor track of the primary type, it's expanded into (N+1) + * exposed tracks, all of which correspond to the primary extractor track and each of which + * corresponds to a different chunk source track. Selecting one of these tracks has the effect + * of switching the selected track on the chunk source.
        • + *
        • All other extractor tracks are exposed directly. Selecting one of these tracks has the + * effect of selecting an extractor track, leaving the selected track on the chunk source + * unchanged.
        • + *
        + */ + private void buildTracks() { + // Iterate through the extractor tracks to discover the "primary" track type, and the index + // of the single track of this type. + int primaryExtractorTrackType = PRIMARY_TYPE_NONE; + int primaryExtractorTrackIndex = C.INDEX_UNSET; + int extractorTrackCount = sampleQueues.size(); + for (int i = 0; i < extractorTrackCount; i++) { + String sampleMimeType = sampleQueues.valueAt(i).getUpstreamFormat().sampleMimeType; + int trackType; + if (MimeTypes.isVideo(sampleMimeType)) { + trackType = PRIMARY_TYPE_VIDEO; + } else if (MimeTypes.isAudio(sampleMimeType)) { + trackType = PRIMARY_TYPE_AUDIO; + } else if (MimeTypes.isText(sampleMimeType)) { + trackType = PRIMARY_TYPE_TEXT; + } else { + trackType = PRIMARY_TYPE_NONE; + } + if (trackType > primaryExtractorTrackType) { + primaryExtractorTrackType = trackType; + primaryExtractorTrackIndex = i; + } else if (trackType == primaryExtractorTrackType + && primaryExtractorTrackIndex != C.INDEX_UNSET) { + // We have multiple tracks of the primary type. We only want an index if there only exists a + // single track of the primary type, so unset the index again. + primaryExtractorTrackIndex = C.INDEX_UNSET; + } + } + + TrackGroup chunkSourceTrackGroup = chunkSource.getTrackGroup(); + int chunkSourceTrackCount = chunkSourceTrackGroup.length; + + // Instantiate the necessary internal data-structures. + primaryTrackGroupIndex = C.INDEX_UNSET; + groupEnabledStates = new boolean[extractorTrackCount]; + + // Construct the set of exposed track groups. + TrackGroup[] trackGroups = new TrackGroup[extractorTrackCount]; + for (int i = 0; i < extractorTrackCount; i++) { + Format sampleFormat = sampleQueues.valueAt(i).getUpstreamFormat(); + if (i == primaryExtractorTrackIndex) { + Format[] formats = new Format[chunkSourceTrackCount]; + for (int j = 0; j < chunkSourceTrackCount; j++) { + formats[j] = deriveFormat(chunkSourceTrackGroup.getFormat(j), sampleFormat); + } + trackGroups[i] = new TrackGroup(formats); + primaryTrackGroupIndex = i; + } else { + Format trackFormat = null; + if (primaryExtractorTrackType == PRIMARY_TYPE_VIDEO) { + if (MimeTypes.isAudio(sampleFormat.sampleMimeType)) { + trackFormat = muxedAudioFormat; + } else if (MimeTypes.APPLICATION_CEA608.equals(sampleFormat.sampleMimeType)) { + trackFormat = muxedCaptionFormat; + } + } + trackGroups[i] = new TrackGroup(deriveFormat(trackFormat, sampleFormat)); + } + } + this.trackGroups = new TrackGroupArray(trackGroups); + } + + /** + * Enables or disables a specified track group. + * + * @param group The index of the track group. + * @param enabledState True if the group is being enabled, or false if it's being disabled. + */ + private void setTrackGroupEnabledState(int group, boolean enabledState) { + Assertions.checkState(groupEnabledStates[group] != enabledState); + groupEnabledStates[group] = enabledState; + enabledTrackCount = enabledTrackCount + (enabledState ? 1 : -1); + } + + /** + * Derives a track format corresponding to a given container format, by combining it with sample + * level information obtained from the samples. + * + * @param containerFormat The container format for which the track format should be derived. + * @param sampleFormat A sample format from which to obtain sample level information. + * @return The derived track format. + */ + private static Format deriveFormat(Format containerFormat, Format sampleFormat) { + if (containerFormat == null) { + return sampleFormat; + } + String codecs = null; + int sampleTrackType = MimeTypes.getTrackType(sampleFormat.sampleMimeType); + if (sampleTrackType == C.TRACK_TYPE_AUDIO) { + codecs = getAudioCodecs(containerFormat.codecs); + } else if (sampleTrackType == C.TRACK_TYPE_VIDEO) { + codecs = getVideoCodecs(containerFormat.codecs); + } + return sampleFormat.copyWithContainerInfo(containerFormat.id, codecs, containerFormat.bitrate, + containerFormat.width, containerFormat.height, containerFormat.selectionFlags, + containerFormat.language); + } + + private boolean isMediaChunk(Chunk chunk) { + return chunk instanceof HlsMediaChunk; + } + + private boolean isPendingReset() { + return pendingResetPositionUs != C.TIME_UNSET; + } + + private static String getAudioCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_AUDIO); + } + + private static String getVideoCodecs(String codecs) { + return getCodecsOfType(codecs, C.TRACK_TYPE_VIDEO); + } + + private static String getCodecsOfType(String codecs, int trackType) { + if (TextUtils.isEmpty(codecs)) { + return null; + } + String[] codecArray = codecs.split("(\\s*,\\s*)|(\\s*$)"); + StringBuilder builder = new StringBuilder(); + for (String codec : codecArray) { + if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) { + if (builder.length() > 0) { + builder.append(","); + } + builder.append(codec); + } + } + return builder.length() > 0 ? builder.toString() : null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/TimestampAdjusterProvider.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/TimestampAdjusterProvider.java new file mode 100755 index 00000000000..ce7e5d09b6f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/TimestampAdjusterProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; + +/** + * Provides {@link TimestampAdjuster} instances for use during HLS playbacks. + */ +public final class TimestampAdjusterProvider { + + // TODO: Prevent this array from growing indefinitely large by removing adjusters that are no + // longer required. + private final SparseArray timestampAdjusters; + + public TimestampAdjusterProvider() { + timestampAdjusters = new SparseArray<>(); + } + + /** + * Returns a {@link TimestampAdjuster} suitable for adjusting the pts timestamps contained in + * a chunk with a given discontinuity sequence. + * + * @param discontinuitySequence The chunk's discontinuity sequence. + * @param startTimeUs The chunk's start time. + * @return A {@link TimestampAdjuster}. + */ + public TimestampAdjuster getAdjuster(int discontinuitySequence, long startTimeUs) { + TimestampAdjuster adjuster = timestampAdjusters.get(discontinuitySequence); + if (adjuster == null) { + adjuster = new TimestampAdjuster(startTimeUs); + timestampAdjusters.put(discontinuitySequence, adjuster); + } + return adjuster; + } + + /** + * Resets the provider. + */ + public void reset() { + timestampAdjusters.clear(); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java new file mode 100755 index 00000000000..212c451e8ef --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/WebvttExtractor.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.extractor.Extractor; +import org.telegram.messenger.exoplayer2.extractor.ExtractorInput; +import org.telegram.messenger.exoplayer2.extractor.ExtractorOutput; +import org.telegram.messenger.exoplayer2.extractor.PositionHolder; +import org.telegram.messenger.exoplayer2.extractor.SeekMap; +import org.telegram.messenger.exoplayer2.extractor.TimestampAdjuster; +import org.telegram.messenger.exoplayer2.extractor.TrackOutput; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.text.webvtt.WebvttParserUtil; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.io.IOException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A special purpose extractor for WebVTT content in HLS. + *

        + * This extractor passes through non-empty WebVTT files untouched, however derives the correct + * sample timestamp for each by sniffing the X-TIMESTAMP-MAP header along with the start timestamp + * of the first cue header. Empty WebVTT files are not passed through, since it's not possible to + * derive a sample timestamp in this case. + */ +/* package */ final class WebvttExtractor implements Extractor { + + private static final Pattern LOCAL_TIMESTAMP = Pattern.compile("LOCAL:([^,]+)"); + private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:(\\d+)"); + + private final String language; + private final TimestampAdjuster timestampAdjuster; + private final ParsableByteArray sampleDataWrapper; + + private ExtractorOutput output; + + private byte[] sampleData; + private int sampleSize; + + public WebvttExtractor(String language, TimestampAdjuster timestampAdjuster) { + this.language = language; + this.timestampAdjuster = timestampAdjuster; + this.sampleDataWrapper = new ParsableByteArray(); + sampleData = new byte[1024]; + } + + // Extractor implementation. + + @Override + public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { + // This extractor is only used for the HLS use case, which should not call this method. + throw new IllegalStateException(); + } + + @Override + public void init(ExtractorOutput output) { + this.output = output; + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + } + + @Override + public void seek(long position) { + // This extractor is only used for the HLS use case, which should not call this method. + throw new IllegalStateException(); + } + + @Override + public void release() { + // Do nothing + } + + @Override + public int read(ExtractorInput input, PositionHolder seekPosition) + throws IOException, InterruptedException { + int currentFileSize = (int) input.getLength(); + + // Increase the size of sampleData if necessary. + if (sampleSize == sampleData.length) { + sampleData = Arrays.copyOf(sampleData, + (currentFileSize != C.LENGTH_UNSET ? currentFileSize : sampleData.length) * 3 / 2); + } + + // Consume to the input. + int bytesRead = input.read(sampleData, sampleSize, sampleData.length - sampleSize); + if (bytesRead != C.RESULT_END_OF_INPUT) { + sampleSize += bytesRead; + if (currentFileSize == C.LENGTH_UNSET || sampleSize != currentFileSize) { + return Extractor.RESULT_CONTINUE; + } + } + + // We've reached the end of the input, which corresponds to the end of the current file. + processSample(); + return Extractor.RESULT_END_OF_INPUT; + } + + private void processSample() throws ParserException { + ParsableByteArray webvttData = new ParsableByteArray(sampleData); + + // Validate the first line of the header. + try { + WebvttParserUtil.validateWebvttHeaderLine(webvttData); + } catch (SubtitleDecoderException e) { + throw new ParserException(e); + } + + // Defaults to use if the header doesn't contain an X-TIMESTAMP-MAP header. + long vttTimestampUs = 0; + long tsTimestampUs = 0; + + // Parse the remainder of the header looking for X-TIMESTAMP-MAP. + String line; + while (!TextUtils.isEmpty(line = webvttData.readLine())) { + if (line.startsWith("X-TIMESTAMP-MAP")) { + Matcher localTimestampMatcher = LOCAL_TIMESTAMP.matcher(line); + if (!localTimestampMatcher.find()) { + throw new ParserException("X-TIMESTAMP-MAP doesn't contain local timestamp: " + line); + } + Matcher mediaTimestampMatcher = MEDIA_TIMESTAMP.matcher(line); + if (!mediaTimestampMatcher.find()) { + throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); + } + vttTimestampUs = WebvttParserUtil.parseTimestampUs(localTimestampMatcher.group(1)); + tsTimestampUs = TimestampAdjuster.ptsToUs( + Long.parseLong(mediaTimestampMatcher.group(1))); + } + } + + // Find the first cue header and parse the start time. + Matcher cueHeaderMatcher = WebvttParserUtil.findNextCueHeader(webvttData); + if (cueHeaderMatcher == null) { + // No cues found. Don't output a sample, but still output a corresponding track. + buildTrackOutput(0); + return; + } + + long firstCueTimeUs = WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1)); + long sampleTimeUs = timestampAdjuster.adjustSampleTimestamp( + firstCueTimeUs + tsTimestampUs - vttTimestampUs); + long subsampleOffsetUs = sampleTimeUs - firstCueTimeUs; + // Output the track. + TrackOutput trackOutput = buildTrackOutput(subsampleOffsetUs); + // Output the sample. + sampleDataWrapper.reset(sampleData, sampleSize); + trackOutput.sampleData(sampleDataWrapper, sampleSize); + trackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, 0, null); + } + + private TrackOutput buildTrackOutput(long subsampleOffsetUs) { + TrackOutput trackOutput = output.track(0); + trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.TEXT_VTT, null, + Format.NO_VALUE, 0, language, null, subsampleOffsetUs)); + output.endTracks(); + return trackOutput; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java new file mode 100755 index 00000000000..db8b539a2d1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMasterPlaylist.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls.playlist; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.MimeTypes; + +import java.util.Collections; +import java.util.List; + +/** + * Represents an HLS master playlist. + */ +public final class HlsMasterPlaylist extends HlsPlaylist { + + /** + * Represents a url in an HLS master playlist. + */ + public static final class HlsUrl { + + public final String name; + public final String url; + public final Format format; + public final Format videoFormat; + public final Format audioFormat; + public final Format[] textFormats; + + public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) { + Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null, + Format.NO_VALUE); + return new HlsUrl(null, baseUri, format, null, null, null); + } + + public HlsUrl(String name, String url, Format format, Format videoFormat, Format audioFormat, + Format[] textFormats) { + this.name = name; + this.url = url; + this.format = format; + this.videoFormat = videoFormat; + this.audioFormat = audioFormat; + this.textFormats = textFormats; + } + + } + + public final List variants; + public final List audios; + public final List subtitles; + + public final Format muxedAudioFormat; + public final Format muxedCaptionFormat; + + public HlsMasterPlaylist(String baseUri, List variants, List audios, + List subtitles, Format muxedAudioFormat, Format muxedCaptionFormat) { + super(baseUri, HlsPlaylist.TYPE_MASTER); + this.variants = Collections.unmodifiableList(variants); + this.audios = Collections.unmodifiableList(audios); + this.subtitles = Collections.unmodifiableList(subtitles); + this.muxedAudioFormat = muxedAudioFormat; + this.muxedCaptionFormat = muxedCaptionFormat; + } + + public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) { + List variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri)); + List emptyList = Collections.emptyList(); + return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, null); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java new file mode 100755 index 00000000000..f056ae38d61 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls.playlist; + +import org.telegram.messenger.exoplayer2.C; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Represents an HLS media playlist. + */ +public final class HlsMediaPlaylist extends HlsPlaylist { + + /** + * Media segment reference. + */ + public static final class Segment implements Comparable { + + public final String url; + public final long durationUs; + public final int discontinuitySequenceNumber; + public final long startTimeUs; + public final boolean isEncrypted; + public final String encryptionKeyUri; + public final String encryptionIV; + public final long byterangeOffset; + public final long byterangeLength; + + public Segment(String uri, long byterangeOffset, long byterangeLength) { + this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength); + } + + public Segment(String uri, long durationUs, int discontinuitySequenceNumber, + long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, + long byterangeOffset, long byterangeLength) { + this.url = uri; + this.durationUs = durationUs; + this.discontinuitySequenceNumber = discontinuitySequenceNumber; + this.startTimeUs = startTimeUs; + this.isEncrypted = isEncrypted; + this.encryptionKeyUri = encryptionKeyUri; + this.encryptionIV = encryptionIV; + this.byterangeOffset = byterangeOffset; + this.byterangeLength = byterangeLength; + } + + @Override + public int compareTo(Long startTimeUs) { + return this.startTimeUs > startTimeUs ? 1 : (this.startTimeUs < startTimeUs ? -1 : 0); + } + + public Segment copyWithStartTimeUs(long startTimeUs) { + return new Segment(url, durationUs, discontinuitySequenceNumber, startTimeUs, isEncrypted, + encryptionKeyUri, encryptionIV, byterangeOffset, byterangeLength); + } + + } + + public final int mediaSequence; + public final int version; + public final Segment initializationSegment; + public final List segments; + public final boolean hasEndTag; + public final long durationUs; + + public HlsMediaPlaylist(String baseUri, int mediaSequence, int version, + boolean hasEndTag, Segment initializationSegment, List segments) { + super(baseUri, HlsPlaylist.TYPE_MEDIA); + this.mediaSequence = mediaSequence; + this.version = version; + this.hasEndTag = hasEndTag; + this.initializationSegment = initializationSegment; + this.segments = Collections.unmodifiableList(segments); + + if (!segments.isEmpty()) { + Segment first = segments.get(0); + Segment last = segments.get(segments.size() - 1); + durationUs = last.startTimeUs + last.durationUs - first.startTimeUs; + } else { + durationUs = 0; + } + } + + public long getStartTimeUs() { + return segments.isEmpty() ? 0 : segments.get(0).startTimeUs; + } + + public long getEndTimeUs() { + return getStartTimeUs() + durationUs; + } + + public HlsMediaPlaylist copyWithStartTimeUs(long newStartTimeUs) { + long startTimeOffsetUs = newStartTimeUs - getStartTimeUs(); + int segmentsSize = segments.size(); + List newSegments = new ArrayList<>(segmentsSize); + for (int i = 0; i < segmentsSize; i++) { + Segment segment = segments.get(i); + newSegments.add(segment.copyWithStartTimeUs(segment.startTimeUs + startTimeOffsetUs)); + } + return copyWithSegments(newSegments); + } + + public HlsMediaPlaylist copyWithSegments(List segments) { + return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag, + initializationSegment, segments); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java new file mode 100755 index 00000000000..2fb70bda2fe --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylist.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls.playlist; + +import android.support.annotation.IntDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Represents an HLS playlist. + */ +public abstract class HlsPlaylist { + + /** + * The type of playlist. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_MASTER, TYPE_MEDIA}) + public @interface Type {} + public static final int TYPE_MASTER = 0; + public static final int TYPE_MEDIA = 1; + + public final String baseUri; + @Type + public final int type; + + protected HlsPlaylist(String baseUri, @Type int type) { + this.baseUri = baseUri; + this.type = type; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java new file mode 100755 index 00000000000..51943033dd2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistParser.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls.playlist; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HLS playlists parsing logic. + */ +public final class HlsPlaylistParser implements ParsingLoadable.Parser { + + private static final String TAG_VERSION = "#EXT-X-VERSION"; + private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; + private static final String TAG_MEDIA = "#EXT-X-MEDIA"; + private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; + private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE"; + private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP"; + private static final String TAG_MEDIA_DURATION = "#EXTINF"; + private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; + private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; + private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; + private static final String TAG_KEY = "#EXT-X-KEY"; + private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; + + private static final String TYPE_AUDIO = "AUDIO"; + private static final String TYPE_VIDEO = "VIDEO"; + private static final String TYPE_SUBTITLES = "SUBTITLES"; + private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS"; + + private static final String METHOD_NONE = "NONE"; + private static final String METHOD_AES128 = "AES-128"; + + private static final String BOOLEAN_TRUE = "YES"; + private static final String BOOLEAN_FALSE = "NO"; + + private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\""); + private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\""); + private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); + private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\""); + private static final Pattern REGEX_SUBTITLES = Pattern.compile("SUBTITLES=\"(.+?)\""); + private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b"); + private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); + private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); + private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b"); + private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION + + ":(\\d+)\\b"); + private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE + + ":(\\d+)\\b"); + private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION + + ":([\\d\\.]+)\\b"); + private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE + + ":(\\d+(?:@\\d+)?)\\b"); + private static final Pattern REGEX_ATTR_BYTERANGE = + Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\""); + private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|" + + METHOD_AES128 + ")"); + private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); + private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)"); + private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO + + "|" + TYPE_SUBTITLES + "|" + TYPE_CLOSED_CAPTIONS + ")"); + private static final Pattern REGEX_LANGUAGE = Pattern.compile("LANGUAGE=\"(.+?)\""); + private static final Pattern REGEX_NAME = Pattern.compile("NAME=\"(.+?)\""); + private static final Pattern REGEX_INSTREAM_ID = Pattern.compile("INSTREAM-ID=\"(.+?)\""); + private static final Pattern REGEX_AUTOSELECT = compileBooleanAttrPattern("AUTOSELECT"); + private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT"); + private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED"); + + @Override + public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + Queue extraLines = new LinkedList<>(); + String line; + try { + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) { + // Do nothing. + } else if (line.startsWith(TAG_STREAM_INF)) { + extraLines.add(line); + return parseMasterPlaylist(new LineIterator(extraLines, reader), uri.toString()); + } else if (line.startsWith(TAG_TARGET_DURATION) + || line.startsWith(TAG_MEDIA_SEQUENCE) + || line.startsWith(TAG_MEDIA_DURATION) + || line.startsWith(TAG_KEY) + || line.startsWith(TAG_BYTERANGE) + || line.equals(TAG_DISCONTINUITY) + || line.equals(TAG_DISCONTINUITY_SEQUENCE) + || line.equals(TAG_ENDLIST)) { + extraLines.add(line); + return parseMediaPlaylist(new LineIterator(extraLines, reader), uri.toString()); + } else { + extraLines.add(line); + } + } + } finally { + reader.close(); + } + throw new ParserException("Failed to parse the playlist, could not identify any tags."); + } + + private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri) + throws IOException { + ArrayList variants = new ArrayList<>(); + ArrayList audios = new ArrayList<>(); + ArrayList subtitles = new ArrayList<>(); + Format muxedAudioFormat = null; + Format muxedCaptionFormat = null; + + String line; + while (iterator.hasNext()) { + line = iterator.next(); + if (line.startsWith(TAG_MEDIA)) { + @C.SelectionFlags int selectionFlags = parseSelectionFlags(line); + String uri = parseOptionalStringAttr(line, REGEX_URI); + String name = parseStringAttr(line, REGEX_NAME); + String language = parseOptionalStringAttr(line, REGEX_LANGUAGE); + Format format; + switch (parseStringAttr(line, REGEX_TYPE)) { + case TYPE_AUDIO: + format = Format.createAudioContainerFormat(name, MimeTypes.APPLICATION_M3U8, + null, null, Format.NO_VALUE, Format.NO_VALUE, Format.NO_VALUE, null, selectionFlags, + language); + if (uri == null) { + muxedAudioFormat = format; + } else { + audios.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null)); + } + break; + case TYPE_SUBTITLES: + format = Format.createTextContainerFormat(name, MimeTypes.APPLICATION_M3U8, + MimeTypes.TEXT_VTT, null, Format.NO_VALUE, selectionFlags, language); + subtitles.add(new HlsMasterPlaylist.HlsUrl(name, uri, format, null, format, null)); + break; + case TYPE_CLOSED_CAPTIONS: + if ("CC1".equals(parseOptionalStringAttr(line, REGEX_INSTREAM_ID))) { + muxedCaptionFormat = Format.createTextContainerFormat(name, + MimeTypes.APPLICATION_M3U8, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, + selectionFlags, language); + } + break; + default: + // Do nothing. + break; + } + } else if (line.startsWith(TAG_STREAM_INF)) { + int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); + String codecs = parseOptionalStringAttr(line, REGEX_CODECS); + String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION); + int width; + int height; + if (resolutionString != null) { + String[] widthAndHeight = resolutionString.split("x"); + width = Integer.parseInt(widthAndHeight[0]); + height = Integer.parseInt(widthAndHeight[1]); + if (width <= 0 || height <= 0) { + // Resolution string is invalid. + width = Format.NO_VALUE; + height = Format.NO_VALUE; + } + } else { + width = Format.NO_VALUE; + height = Format.NO_VALUE; + } + line = iterator.next(); + String name = Integer.toString(variants.size()); + Format format = Format.createVideoContainerFormat(name, MimeTypes.APPLICATION_M3U8, null, + codecs, bitrate, width, height, Format.NO_VALUE, null); + variants.add(new HlsMasterPlaylist.HlsUrl(name, line, format, null, null, null)); + } + } + return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat, + muxedCaptionFormat); + } + + @C.SelectionFlags + private static int parseSelectionFlags(String line) { + return (parseBooleanAttribute(line, REGEX_DEFAULT, false) ? C.SELECTION_FLAG_DEFAULT : 0) + | (parseBooleanAttribute(line, REGEX_FORCED, false) ? C.SELECTION_FLAG_FORCED : 0) + | (parseBooleanAttribute(line, REGEX_AUTOSELECT, false) ? C.SELECTION_FLAG_AUTOSELECT : 0); + } + + private static HlsMediaPlaylist parseMediaPlaylist(LineIterator iterator, String baseUri) + throws IOException { + int mediaSequence = 0; + int targetDurationSecs = 0; + int version = 1; // Default version == 1. + boolean hasEndTag = false; + Segment initializationSegment = null; + List segments = new ArrayList<>(); + + long segmentDurationUs = 0; + int discontinuitySequenceNumber = 0; + long segmentStartTimeUs = 0; + long segmentByteRangeOffset = 0; + long segmentByteRangeLength = C.LENGTH_UNSET; + int segmentMediaSequence = 0; + + boolean isEncrypted = false; + String encryptionKeyUri = null; + String encryptionIV = null; + + String line; + while (iterator.hasNext()) { + line = iterator.next(); + if (line.startsWith(TAG_INIT_SEGMENT)) { + String uri = parseStringAttr(line, REGEX_URI); + String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE); + if (byteRange != null) { + String[] splitByteRange = byteRange.split("@"); + segmentByteRangeLength = Long.parseLong(splitByteRange[0]); + if (splitByteRange.length > 1) { + segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); + } + } + initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength); + segmentByteRangeOffset = 0; + segmentByteRangeLength = C.LENGTH_UNSET; + } else if (line.startsWith(TAG_TARGET_DURATION)) { + targetDurationSecs = parseIntAttr(line, REGEX_TARGET_DURATION); + } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { + mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE); + segmentMediaSequence = mediaSequence; + } else if (line.startsWith(TAG_VERSION)) { + version = parseIntAttr(line, REGEX_VERSION); + } else if (line.startsWith(TAG_MEDIA_DURATION)) { + segmentDurationUs = + (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND); + } else if (line.startsWith(TAG_KEY)) { + String method = parseStringAttr(line, REGEX_METHOD); + isEncrypted = METHOD_AES128.equals(method); + if (isEncrypted) { + encryptionKeyUri = parseStringAttr(line, REGEX_URI); + encryptionIV = parseOptionalStringAttr(line, REGEX_IV); + } else { + encryptionKeyUri = null; + encryptionIV = null; + } + } else if (line.startsWith(TAG_BYTERANGE)) { + String byteRange = parseStringAttr(line, REGEX_BYTERANGE); + String[] splitByteRange = byteRange.split("@"); + segmentByteRangeLength = Long.parseLong(splitByteRange[0]); + if (splitByteRange.length > 1) { + segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); + } + } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) { + discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1)); + } else if (line.equals(TAG_DISCONTINUITY)) { + discontinuitySequenceNumber++; + } else if (!line.startsWith("#")) { + String segmentEncryptionIV; + if (!isEncrypted) { + segmentEncryptionIV = null; + } else if (encryptionIV != null) { + segmentEncryptionIV = encryptionIV; + } else { + segmentEncryptionIV = Integer.toHexString(segmentMediaSequence); + } + segmentMediaSequence++; + if (segmentByteRangeLength == C.LENGTH_UNSET) { + segmentByteRangeOffset = 0; + } + segments.add(new Segment(line, segmentDurationUs, discontinuitySequenceNumber, + segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, + segmentByteRangeOffset, segmentByteRangeLength)); + segmentStartTimeUs += segmentDurationUs; + segmentDurationUs = 0; + if (segmentByteRangeLength != C.LENGTH_UNSET) { + segmentByteRangeOffset += segmentByteRangeLength; + } + segmentByteRangeLength = C.LENGTH_UNSET; + } else if (line.equals(TAG_ENDLIST)) { + hasEndTag = true; + } + } + return new HlsMediaPlaylist(baseUri, mediaSequence, version, hasEndTag, + initializationSegment, segments); + } + + private static String parseStringAttr(String line, Pattern pattern) throws ParserException { + Matcher matcher = pattern.matcher(line); + if (matcher.find() && matcher.groupCount() == 1) { + return matcher.group(1); + } + throw new ParserException("Couldn't match " + pattern.pattern() + " in " + line); + } + + private static int parseIntAttr(String line, Pattern pattern) throws ParserException { + return Integer.parseInt(parseStringAttr(line, pattern)); + } + + private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException { + return Double.parseDouble(parseStringAttr(line, pattern)); + } + + private static String parseOptionalStringAttr(String line, Pattern pattern) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } + + private static boolean parseBooleanAttribute(String line, Pattern pattern, boolean defaultValue) { + Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(1).equals(BOOLEAN_TRUE); + } + return defaultValue; + } + + private static Pattern compileBooleanAttrPattern(String attribute) { + return Pattern.compile(attribute + "=(" + BOOLEAN_FALSE + "|" + BOOLEAN_TRUE + ")"); + } + + private static class LineIterator { + + private final BufferedReader reader; + private final Queue extraLines; + + private String next; + + public LineIterator(Queue extraLines, BufferedReader reader) { + this.extraLines = extraLines; + this.reader = reader; + } + + public boolean hasNext() throws IOException { + if (next != null) { + return true; + } + if (!extraLines.isEmpty()) { + next = extraLines.poll(); + return true; + } + while ((next = reader.readLine()) != null) { + next = next.trim(); + if (!next.isEmpty()) { + return true; + } + } + return false; + } + + public String next() throws IOException { + String result = null; + if (hasNext()) { + result = next; + next = null; + } + return result; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java new file mode 100755 index 00000000000..8f07bb41e6e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.hls.playlist; + +import android.net.Uri; +import android.os.Handler; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMasterPlaylist.HlsUrl; +import org.telegram.messenger.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.UriUtil; +import java.io.IOException; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; + +/** + * Tracks playlists linked to a provided playlist url. The provided url might reference an HLS + * master playlist or a media playlist. + */ +public final class HlsPlaylistTracker implements Loader.Callback> { + + /** + * Listener for primary playlist changes. + */ + public interface PrimaryPlaylistListener { + + /** + * Called when the primary playlist changes. + * + * @param mediaPlaylist The primary playlist new snapshot. + */ + void onPrimaryPlaylistRefreshed(HlsMediaPlaylist mediaPlaylist); + + } + + /** + * Called when the playlist changes. + */ + public interface PlaylistRefreshCallback { + + /** + * Called when the target playlist changes. + */ + void onPlaylistChanged(); + + /** + * Called if an error is encountered while loading the target playlist. + * + * @param url The loaded url that caused the error. + * @param error The loading error. + */ + void onPlaylistLoadError(HlsUrl url, IOException error); + + } + + /** + * Determines the minimum amount of time by which a media playlist segment's start time has to + * drift from the actual start time of the chunk it refers to for it to be adjusted. + */ + private static final long TIMESTAMP_ADJUSTMENT_THRESHOLD_US = 500000; + + /** + * Period for refreshing playlists. + */ + private static final long PLAYLIST_REFRESH_PERIOD_MS = 5000; + + private final Uri initialPlaylistUri; + private final DataSource.Factory dataSourceFactory; + private final HlsPlaylistParser playlistParser; + private final int minRetryCount; + private final IdentityHashMap playlistBundles; + private final Handler playlistRefreshHandler; + private final PrimaryPlaylistListener primaryPlaylistListener; + private final Loader initialPlaylistLoader; + private final EventDispatcher eventDispatcher; + + private HlsMasterPlaylist masterPlaylist; + private HlsUrl primaryHlsUrl; + private boolean isLive; + + /** + * @param initialPlaylistUri Uri for the initial playlist of the stream. Can refer a media + * playlist or a master playlist. + * @param dataSourceFactory A factory for {@link DataSource} instances. + * @param eventDispatcher A dispatcher to notify of events. + * @param minRetryCount The minimum number of times the load must be retried before blacklisting a + * playlist. + * @param primaryPlaylistListener A callback for the primary playlist change events. + */ + public HlsPlaylistTracker(Uri initialPlaylistUri, DataSource.Factory dataSourceFactory, + EventDispatcher eventDispatcher, int minRetryCount, + PrimaryPlaylistListener primaryPlaylistListener) { + this.initialPlaylistUri = initialPlaylistUri; + this.dataSourceFactory = dataSourceFactory; + this.eventDispatcher = eventDispatcher; + this.minRetryCount = minRetryCount; + this.primaryPlaylistListener = primaryPlaylistListener; + initialPlaylistLoader = new Loader("HlsPlaylistTracker:MasterPlaylist"); + playlistParser = new HlsPlaylistParser(); + playlistBundles = new IdentityHashMap<>(); + playlistRefreshHandler = new Handler(); + } + + /** + * Starts tracking all the playlists related to the provided Uri. + */ + public void start() { + ParsingLoadable masterPlaylistLoadable = new ParsingLoadable<>( + dataSourceFactory.createDataSource(), initialPlaylistUri, C.DATA_TYPE_MANIFEST, + playlistParser); + initialPlaylistLoader.startLoading(masterPlaylistLoadable, this, minRetryCount); + } + + /** + * Returns the master playlist. + * + * @return The master playlist. Null if the initial playlist has yet to be loaded. + */ + public HlsMasterPlaylist getMasterPlaylist() { + return masterPlaylist; + } + + /** + * Returns the most recent snapshot available of the playlist referenced by the provided + * {@link HlsUrl}. + * + * @param url The {@link HlsUrl} corresponding to the requested media playlist. + * @return The most recent snapshot of the playlist referenced by the provided {@link HlsUrl}. May + * be null if no snapshot has been loaded yet. + */ + public HlsMediaPlaylist getPlaylistSnapshot(HlsUrl url) { + return playlistBundles.get(url).latestPlaylistSnapshot; + } + + /** + * Releases the playlist tracker. + */ + public void release() { + initialPlaylistLoader.release(); + for (MediaPlaylistBundle bundle : playlistBundles.values()) { + bundle.release(); + } + playlistRefreshHandler.removeCallbacksAndMessages(null); + playlistBundles.clear(); + } + + /** + * If the tracker is having trouble refreshing the primary playlist, this method throws the + * underlying error. Otherwise, does nothing. + * + * @throws IOException The underlying error. + */ + public void maybeThrowPrimaryPlaylistRefreshError() throws IOException { + initialPlaylistLoader.maybeThrowError(); + if (primaryHlsUrl != null) { + playlistBundles.get(primaryHlsUrl).mediaPlaylistLoader.maybeThrowError(); + } + } + + /** + * Triggers a playlist refresh and sets the callback to be called once the playlist referenced by + * the provided {@link HlsUrl} changes. + * + * @param key The {@link HlsUrl} of the playlist to be refreshed. + * @param callback The callback. + */ + public void refreshPlaylist(HlsUrl key, PlaylistRefreshCallback callback) { + MediaPlaylistBundle bundle = playlistBundles.get(key); + bundle.setCallback(callback); + bundle.loadPlaylist(); + } + + /** + * Returns whether this is live content. + * + * @return True if the content is live. False otherwise. + */ + public boolean isLive() { + return isLive; + } + + /** + * Called when a chunk from a media playlist is loaded. + * + * @param hlsUrl The url of the playlist from which the chunk was obtained. + * @param chunkMediaSequence The media sequence number of the loaded chunk. + * @param adjustedStartTimeUs The adjusted start time of the loaded chunk. + */ + public void onChunkLoaded(HlsUrl hlsUrl, int chunkMediaSequence, long adjustedStartTimeUs) { + playlistBundles.get(hlsUrl).adjustTimestampsOfPlaylist(chunkMediaSequence, adjustedStartTimeUs); + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + HlsPlaylist result = loadable.getResult(); + HlsMasterPlaylist masterPlaylist; + boolean isMediaPlaylist = result instanceof HlsMediaPlaylist; + if (isMediaPlaylist) { + masterPlaylist = HlsMasterPlaylist.createSingleVariantMasterPlaylist(result.baseUri); + } else /* result instanceof HlsMasterPlaylist */ { + masterPlaylist = (HlsMasterPlaylist) result; + } + this.masterPlaylist = masterPlaylist; + primaryHlsUrl = masterPlaylist.variants.get(0); + ArrayList urls = new ArrayList<>(); + urls.addAll(masterPlaylist.variants); + urls.addAll(masterPlaylist.audios); + urls.addAll(masterPlaylist.subtitles); + createBundles(urls); + MediaPlaylistBundle primaryBundle = playlistBundles.get(primaryHlsUrl); + if (isMediaPlaylist) { + // We don't need to load the playlist again. We can use the same result. + primaryBundle.processLoadedPlaylist((HlsMediaPlaylist) result); + } else { + primaryBundle.loadPlaylist(); + } + eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + // Internal methods. + + private void createBundles(List urls) { + int listSize = urls.size(); + for (int i = 0; i < listSize; i++) { + HlsUrl url = urls.get(i); + MediaPlaylistBundle bundle = new MediaPlaylistBundle(url); + playlistBundles.put(urls.get(i), bundle); + } + } + + /** + * Called by the bundles when a snapshot changes. + * + * @param url The url of the playlist. + * @param newSnapshot The new snapshot. + * @param isFirstSnapshot Whether this is the first snapshot for the given playlist. + * @return True if a refresh should be scheduled. + */ + private boolean onPlaylistUpdated(HlsUrl url, HlsMediaPlaylist newSnapshot, + boolean isFirstSnapshot) { + if (url == primaryHlsUrl) { + if (isFirstSnapshot) { + isLive = !newSnapshot.hasEndTag; + } + primaryPlaylistListener.onPrimaryPlaylistRefreshed(newSnapshot); + // If the primary playlist is not the final one, we should schedule a refresh. + return !newSnapshot.hasEndTag; + } + return false; + } + + /** + * TODO: Track discontinuities for media playlists that don't include the discontinuity number. + */ + private HlsMediaPlaylist adjustPlaylistTimestamps(HlsMediaPlaylist oldPlaylist, + HlsMediaPlaylist newPlaylist) { + HlsMediaPlaylist primaryPlaylistSnapshot = + playlistBundles.get(primaryHlsUrl).latestPlaylistSnapshot; + if (oldPlaylist == null) { + if (primaryPlaylistSnapshot == null) { + // Playback has just started so no adjustment is needed. + return newPlaylist; + } else { + return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.getStartTimeUs()); + } + } + List oldSegments = oldPlaylist.segments; + int oldPlaylistSize = oldSegments.size(); + int newPlaylistSize = newPlaylist.segments.size(); + int mediaSequenceOffset = newPlaylist.mediaSequence - oldPlaylist.mediaSequence; + if (newPlaylistSize == oldPlaylistSize && mediaSequenceOffset == 0 + && oldPlaylist.hasEndTag == newPlaylist.hasEndTag) { + // Playlist has not changed. + return oldPlaylist; + } + if (mediaSequenceOffset < 0) { + // Playlist has changed but media sequence has regressed. + return oldPlaylist; + } + if (mediaSequenceOffset <= oldPlaylistSize) { + // We can extrapolate the start time of new segments from the segments of the old snapshot. + ArrayList newSegments = new ArrayList<>(newPlaylistSize); + for (int i = mediaSequenceOffset; i < oldPlaylistSize; i++) { + newSegments.add(oldSegments.get(i)); + } + HlsMediaPlaylist.Segment lastSegment = oldSegments.get(oldPlaylistSize - 1); + for (int i = newSegments.size(); i < newPlaylistSize; i++) { + lastSegment = newPlaylist.segments.get(i).copyWithStartTimeUs( + lastSegment.startTimeUs + lastSegment.durationUs); + newSegments.add(lastSegment); + } + return newPlaylist.copyWithSegments(newSegments); + } else { + // No segments overlap, we assume the new playlist start coincides with the primary playlist. + return newPlaylist.copyWithStartTimeUs(primaryPlaylistSnapshot.getStartTimeUs()); + } + } + + /** + * Holds all information related to a specific Media Playlist. + */ + private final class MediaPlaylistBundle implements Loader.Callback>, + Runnable { + + private final HlsUrl playlistUrl; + private final Loader mediaPlaylistLoader; + private final ParsingLoadable mediaPlaylistLoadable; + + private PlaylistRefreshCallback callback; + private HlsMediaPlaylist latestPlaylistSnapshot; + + public MediaPlaylistBundle(HlsUrl playlistUrl) { + this(playlistUrl, null); + } + + public MediaPlaylistBundle(HlsUrl playlistUrl, HlsMediaPlaylist initialSnapshot) { + this.playlistUrl = playlistUrl; + latestPlaylistSnapshot = initialSnapshot; + mediaPlaylistLoader = new Loader("HlsPlaylistTracker:MediaPlaylist"); + mediaPlaylistLoadable = new ParsingLoadable<>(dataSourceFactory.createDataSource(), + UriUtil.resolveToUri(masterPlaylist.baseUri, playlistUrl.url), C.DATA_TYPE_MANIFEST, + playlistParser); + } + + public void release() { + mediaPlaylistLoader.release(); + } + + public void loadPlaylist() { + if (!mediaPlaylistLoader.isLoading()) { + mediaPlaylistLoader.startLoading(mediaPlaylistLoadable, this, minRetryCount); + } + } + + public void setCallback(PlaylistRefreshCallback callback) { + this.callback = callback; + } + + public void adjustTimestampsOfPlaylist(int chunkMediaSequence, long adjustedStartTimeUs) { + ArrayList segments = new ArrayList<>(latestPlaylistSnapshot.segments); + int indexOfChunk = chunkMediaSequence - latestPlaylistSnapshot.mediaSequence; + if (indexOfChunk < 0) { + return; + } + Segment actualSegment = segments.get(indexOfChunk); + long timestampDriftUs = Math.abs(actualSegment.startTimeUs - adjustedStartTimeUs); + if (timestampDriftUs < TIMESTAMP_ADJUSTMENT_THRESHOLD_US) { + return; + } + segments.set(indexOfChunk, actualSegment.copyWithStartTimeUs(adjustedStartTimeUs)); + // Propagate the adjustment backwards. + for (int i = indexOfChunk - 1; i >= 0; i--) { + Segment segment = segments.get(i); + segments.set(i, + segment.copyWithStartTimeUs(segments.get(i + 1).startTimeUs - segment.durationUs)); + } + // Propagate the adjustment forward. + int segmentsSize = segments.size(); + for (int i = indexOfChunk + 1; i < segmentsSize; i++) { + Segment segment = segments.get(i); + segments.set(i, + segment.copyWithStartTimeUs(segments.get(i - 1).startTimeUs + segment.durationUs)); + } + latestPlaylistSnapshot = latestPlaylistSnapshot.copyWithSegments(segments); + } + + // Loader.Callback implementation. + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + processLoadedPlaylist((HlsMediaPlaylist) loadable.getResult()); + eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCanceled(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + // TODO: Change primary playlist if this is the primary playlist bundle. + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded(), error, isFatal); + if (callback != null) { + callback.onPlaylistLoadError(playlistUrl, error); + } + if (isFatal) { + return Loader.DONT_RETRY_FATAL; + } else { + return primaryHlsUrl == playlistUrl ? Loader.RETRY : Loader.DONT_RETRY; + } + } + + // Runnable implementation. + + @Override + public void run() { + loadPlaylist(); + } + + // Internal methods. + + private void processLoadedPlaylist(HlsMediaPlaylist loadedMediaPlaylist) { + HlsMediaPlaylist oldPlaylist = latestPlaylistSnapshot; + latestPlaylistSnapshot = adjustPlaylistTimestamps(oldPlaylist, loadedMediaPlaylist); + boolean shouldScheduleRefresh; + if (oldPlaylist != latestPlaylistSnapshot) { + if (callback != null) { + callback.onPlaylistChanged(); + callback = null; + } + shouldScheduleRefresh = onPlaylistUpdated(playlistUrl, latestPlaylistSnapshot, + oldPlaylist == null); + } else { + shouldScheduleRefresh = !loadedMediaPlaylist.hasEndTag; + } + if (shouldScheduleRefresh) { + playlistRefreshHandler.postDelayed(this, PLAYLIST_REFRESH_PERIOD_MS); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java new file mode 100755 index 00000000000..5c20c466344 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/DefaultSsChunkSource.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.extractor.mp4.FragmentedMp4Extractor; +import org.telegram.messenger.exoplayer2.extractor.mp4.Track; +import org.telegram.messenger.exoplayer2.extractor.mp4.TrackEncryptionBox; +import org.telegram.messenger.exoplayer2.source.BehindLiveWindowException; +import org.telegram.messenger.exoplayer2.source.chunk.Chunk; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkExtractorWrapper; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkHolder; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; +import org.telegram.messenger.exoplayer2.source.chunk.ContainerMediaChunk; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import java.io.IOException; +import java.util.List; + +/** + * A default {@link SsChunkSource} implementation. + */ +public class DefaultSsChunkSource implements SsChunkSource { + + public static final class Factory implements SsChunkSource.Factory { + + private final DataSource.Factory dataSourceFactory; + + public Factory(DataSource.Factory dataSourceFactory) { + this.dataSourceFactory = dataSourceFactory; + } + + @Override + public SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, int elementIndex, TrackSelection trackSelection, + TrackEncryptionBox[] trackEncryptionBoxes) { + DataSource dataSource = dataSourceFactory.createDataSource(); + return new DefaultSsChunkSource(manifestLoaderErrorThrower, manifest, elementIndex, + trackSelection, dataSource, trackEncryptionBoxes); + } + + } + + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int elementIndex; + private final TrackSelection trackSelection; + private final ChunkExtractorWrapper[] extractorWrappers; + private final DataSource dataSource; + + private SsManifest manifest; + private int currentManifestChunkOffset; + + private IOException fatalError; + + /** + * @param manifestLoaderErrorThrower Throws errors affecting loading of manifests. + * @param manifest The initial manifest. + * @param elementIndex The index of the stream element in the manifest. + * @param trackSelection The track selection. + * @param dataSource A {@link DataSource} suitable for loading the media data. + * @param trackEncryptionBoxes Track encryption boxes for the stream. + */ + public DefaultSsChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, SsManifest manifest, + int elementIndex, TrackSelection trackSelection, DataSource dataSource, + TrackEncryptionBox[] trackEncryptionBoxes) { + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.manifest = manifest; + this.elementIndex = elementIndex; + this.trackSelection = trackSelection; + this.dataSource = dataSource; + + StreamElement streamElement = manifest.streamElements[elementIndex]; + + extractorWrappers = new ChunkExtractorWrapper[trackSelection.length()]; + for (int i = 0; i < extractorWrappers.length; i++) { + int manifestTrackIndex = trackSelection.getIndexInTrackGroup(i); + Format format = streamElement.formats[manifestTrackIndex]; + int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : 0; + Track track = new Track(manifestTrackIndex, streamElement.type, streamElement.timescale, + C.TIME_UNSET, manifest.durationUs, format, Track.TRANSFORMATION_NONE, + trackEncryptionBoxes, nalUnitLengthFieldLength, null, null); + FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( + FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME + | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track, null); + extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false, false); + } + } + + @Override + public void updateManifest(SsManifest newManifest) { + StreamElement currentElement = manifest.streamElements[elementIndex]; + int currentElementChunkCount = currentElement.chunkCount; + StreamElement newElement = newManifest.streamElements[elementIndex]; + if (currentElementChunkCount == 0 || newElement.chunkCount == 0) { + // There's no overlap between the old and new elements because at least one is empty. + currentManifestChunkOffset += currentElementChunkCount; + } else { + long currentElementEndTimeUs = currentElement.getStartTimeUs(currentElementChunkCount - 1) + + currentElement.getChunkDurationUs(currentElementChunkCount - 1); + long newElementStartTimeUs = newElement.getStartTimeUs(0); + if (currentElementEndTimeUs <= newElementStartTimeUs) { + // There's no overlap between the old and new elements. + currentManifestChunkOffset += currentElementChunkCount; + } else { + // The new element overlaps with the old one. + currentManifestChunkOffset += currentElement.getChunkIndex(newElementStartTimeUs); + } + } + manifest = newManifest; + } + + // ChunkSource implementation. + + @Override + public void maybeThrowError() throws IOException { + if (fatalError != null) { + throw fatalError; + } else { + manifestLoaderErrorThrower.maybeThrowError(); + } + } + + @Override + public int getPreferredQueueSize(long playbackPositionUs, List queue) { + if (fatalError != null || trackSelection.length() < 2) { + return queue.size(); + } + return trackSelection.evaluateQueueSize(playbackPositionUs, queue); + } + + @Override + public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) { + if (fatalError != null) { + return; + } + + long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0; + trackSelection.updateSelectedTrack(bufferedDurationUs); + + StreamElement streamElement = manifest.streamElements[elementIndex]; + if (streamElement.chunkCount == 0) { + // There aren't any chunks for us to load. + out.endOfStream = !manifest.isLive; + return; + } + + int chunkIndex; + if (previous == null) { + chunkIndex = streamElement.getChunkIndex(playbackPositionUs); + } else { + chunkIndex = previous.getNextChunkIndex() - currentManifestChunkOffset; + if (chunkIndex < 0) { + // This is before the first chunk in the current manifest. + fatalError = new BehindLiveWindowException(); + return; + } + } + + if (chunkIndex >= streamElement.chunkCount) { + // This is beyond the last chunk in the current manifest. + out.endOfStream = !manifest.isLive; + return; + } + + long chunkStartTimeUs = streamElement.getStartTimeUs(chunkIndex); + long chunkEndTimeUs = chunkStartTimeUs + streamElement.getChunkDurationUs(chunkIndex); + int currentAbsoluteChunkIndex = chunkIndex + currentManifestChunkOffset; + + int trackSelectionIndex = trackSelection.getSelectedIndex(); + ChunkExtractorWrapper extractorWrapper = extractorWrappers[trackSelectionIndex]; + + int manifestTrackIndex = trackSelection.getIndexInTrackGroup(trackSelectionIndex); + Uri uri = streamElement.buildRequestUri(manifestTrackIndex, chunkIndex); + + out.chunk = newMediaChunk(trackSelection.getSelectedFormat(), dataSource, uri, null, + currentAbsoluteChunkIndex, chunkStartTimeUs, chunkEndTimeUs, + trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractorWrapper); + } + + @Override + public void onChunkLoadCompleted(Chunk chunk) { + // Do nothing. + } + + @Override + public boolean onChunkLoadError(Chunk chunk, boolean cancelable, Exception e) { + return cancelable && ChunkedTrackBlacklistUtil.maybeBlacklistTrack(trackSelection, + trackSelection.indexOf(chunk.trackFormat), e); + } + + // Private methods. + + private static MediaChunk newMediaChunk(Format format, DataSource dataSource, Uri uri, + String cacheKey, int chunkIndex, long chunkStartTimeUs, long chunkEndTimeUs, + int trackSelectionReason, Object trackSelectionData, ChunkExtractorWrapper extractorWrapper) { + DataSpec dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, cacheKey); + // In SmoothStreaming each chunk contains sample timestamps relative to the start of the chunk. + // To convert them the absolute timestamps, we need to set sampleOffsetUs to chunkStartTimeUs. + long sampleOffsetUs = chunkStartTimeUs; + return new ContainerMediaChunk(dataSource, dataSpec, format, trackSelectionReason, + trackSelectionData, chunkStartTimeUs, chunkEndTimeUs, chunkIndex, 1, sampleOffsetUs, + extractorWrapper, format); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsChunkSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsChunkSource.java new file mode 100755 index 00000000000..bf6155f6d8b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsChunkSource.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming; + +import org.telegram.messenger.exoplayer2.extractor.mp4.TrackEncryptionBox; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkSource; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; + +/** + * A {@link ChunkSource} for SmoothStreaming. + */ +public interface SsChunkSource extends ChunkSource { + + interface Factory { + + SsChunkSource createChunkSource(LoaderErrorThrower manifestLoaderErrorThrower, + SsManifest manifest, int elementIndex, TrackSelection trackSelection, + TrackEncryptionBox[] trackEncryptionBoxes); + + } + + void updateManifest(SsManifest newManifest); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java new file mode 100755 index 00000000000..8ae2da4d434 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaPeriod.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming; + +import android.util.Base64; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.extractor.mp4.TrackEncryptionBox; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.CompositeSequenceableLoader; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.SampleStream; +import org.telegram.messenger.exoplayer2.source.SequenceableLoader; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.chunk.ChunkSampleStream; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A SmoothStreaming {@link MediaPeriod}. + */ +/* package */ final class SsMediaPeriod implements MediaPeriod, + SequenceableLoader.Callback> { + + private static final int INITIALIZATION_VECTOR_SIZE = 8; + + private final SsChunkSource.Factory chunkSourceFactory; + private final LoaderErrorThrower manifestLoaderErrorThrower; + private final int minLoadableRetryCount; + private final EventDispatcher eventDispatcher; + private final Allocator allocator; + private final TrackGroupArray trackGroups; + private final TrackEncryptionBox[] trackEncryptionBoxes; + + private Callback callback; + private SsManifest manifest; + private ChunkSampleStream[] sampleStreams; + private CompositeSequenceableLoader sequenceableLoader; + + public SsMediaPeriod(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, EventDispatcher eventDispatcher, + LoaderErrorThrower manifestLoaderErrorThrower, Allocator allocator) { + this.chunkSourceFactory = chunkSourceFactory; + this.manifestLoaderErrorThrower = manifestLoaderErrorThrower; + this.minLoadableRetryCount = minLoadableRetryCount; + this.eventDispatcher = eventDispatcher; + this.allocator = allocator; + + trackGroups = buildTrackGroups(manifest); + ProtectionElement protectionElement = manifest.protectionElement; + if (protectionElement != null) { + byte[] keyId = getProtectionElementKeyId(protectionElement.data); + trackEncryptionBoxes = new TrackEncryptionBox[] { + new TrackEncryptionBox(true, INITIALIZATION_VECTOR_SIZE, keyId)}; + } else { + trackEncryptionBoxes = null; + } + this.manifest = manifest; + sampleStreams = newSampleStreamArray(0); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + } + + public void updateManifest(SsManifest manifest) { + this.manifest = manifest; + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.getChunkSource().updateManifest(manifest); + } + callback.onContinueLoadingRequested(this); + } + + public void release() { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.release(); + } + } + + @Override + public void prepare(Callback callback) { + this.callback = callback; + callback.onPrepared(this); + } + + @Override + public void maybeThrowPrepareError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public TrackGroupArray getTrackGroups() { + return trackGroups; + } + + @Override + public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, + SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { + ArrayList> sampleStreamsList = new ArrayList<>(); + for (int i = 0; i < selections.length; i++) { + if (streams[i] != null) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = (ChunkSampleStream) streams[i]; + if (selections[i] == null || !mayRetainStreamFlags[i]) { + stream.release(); + streams[i] = null; + } else { + sampleStreamsList.add(stream); + } + } + if (streams[i] == null && selections[i] != null) { + ChunkSampleStream stream = buildSampleStream(selections[i], positionUs); + sampleStreamsList.add(stream); + streams[i] = stream; + streamResetFlags[i] = true; + } + } + sampleStreams = newSampleStreamArray(sampleStreamsList.size()); + sampleStreamsList.toArray(sampleStreams); + sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); + return positionUs; + } + + @Override + public boolean continueLoading(long positionUs) { + return sequenceableLoader.continueLoading(positionUs); + } + + @Override + public long getNextLoadPositionUs() { + return sequenceableLoader.getNextLoadPositionUs(); + } + + @Override + public long readDiscontinuity() { + return C.TIME_UNSET; + } + + @Override + public long getBufferedPositionUs() { + long bufferedPositionUs = Long.MAX_VALUE; + for (ChunkSampleStream sampleStream : sampleStreams) { + long rendererBufferedPositionUs = sampleStream.getBufferedPositionUs(); + if (rendererBufferedPositionUs != C.TIME_END_OF_SOURCE) { + bufferedPositionUs = Math.min(bufferedPositionUs, rendererBufferedPositionUs); + } + } + return bufferedPositionUs == Long.MAX_VALUE ? C.TIME_END_OF_SOURCE : bufferedPositionUs; + } + + @Override + public long seekToUs(long positionUs) { + for (ChunkSampleStream sampleStream : sampleStreams) { + sampleStream.seekToUs(positionUs); + } + return positionUs; + } + + // SequenceableLoader.Callback implementation + + @Override + public void onContinueLoadingRequested(ChunkSampleStream sampleStream) { + callback.onContinueLoadingRequested(this); + } + + // Private methods. + + private ChunkSampleStream buildSampleStream(TrackSelection selection, + long positionUs) { + int streamElementIndex = trackGroups.indexOf(selection.getTrackGroup()); + SsChunkSource chunkSource = chunkSourceFactory.createChunkSource(manifestLoaderErrorThrower, + manifest, streamElementIndex, selection, trackEncryptionBoxes); + return new ChunkSampleStream<>(manifest.streamElements[streamElementIndex].type, chunkSource, + this, allocator, positionUs, minLoadableRetryCount, eventDispatcher); + } + + private static TrackGroupArray buildTrackGroups(SsManifest manifest) { + TrackGroup[] trackGroups = new TrackGroup[manifest.streamElements.length]; + for (int i = 0; i < manifest.streamElements.length; i++) { + trackGroups[i] = new TrackGroup(manifest.streamElements[i].formats); + } + return new TrackGroupArray(trackGroups); + } + + @SuppressWarnings("unchecked") + private static ChunkSampleStream[] newSampleStreamArray(int length) { + return new ChunkSampleStream[length]; + } + + private static byte[] getProtectionElementKeyId(byte[] initData) { + StringBuilder initDataStringBuilder = new StringBuilder(); + for (int i = 0; i < initData.length; i += 2) { + initDataStringBuilder.append((char) initData[i]); + } + String initDataString = initDataStringBuilder.toString(); + String keyIdString = initDataString.substring( + initDataString.indexOf("") + 5, initDataString.indexOf("")); + byte[] keyId = Base64.decode(keyIdString, Base64.DEFAULT); + swap(keyId, 0, 3); + swap(keyId, 1, 2); + swap(keyId, 4, 5); + swap(keyId, 6, 7); + return keyId; + } + + private static void swap(byte[] data, int firstPosition, int secondPosition) { + byte temp = data[firstPosition]; + data[firstPosition] = data[secondPosition]; + data[secondPosition] = temp; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java new file mode 100755 index 00000000000..65635aa46b0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming; + +import android.net.Uri; +import android.os.Handler; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener; +import org.telegram.messenger.exoplayer2.source.AdaptiveMediaSourceEventListener.EventDispatcher; +import org.telegram.messenger.exoplayer2.source.MediaPeriod; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.SinglePeriodTimeline; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; +import org.telegram.messenger.exoplayer2.upstream.Allocator; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.Loader; +import org.telegram.messenger.exoplayer2.upstream.LoaderErrorThrower; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.ArrayList; + +/** + * A SmoothStreaming {@link MediaSource}. + */ +public final class SsMediaSource implements MediaSource, + Loader.Callback> { + + /** + * The default minimum number of times to retry loading data prior to failing. + */ + public static final int DEFAULT_MIN_LOADABLE_RETRY_COUNT = 3; + /** + * The default presentation delay for live streams. The presentation delay is the duration by + * which the default start position precedes the end of the live window. + */ + public static final long DEFAULT_LIVE_PRESENTATION_DELAY_MS = 30000; + + /** + * The minimum period between manifest refreshes. + */ + private static final int MINIMUM_MANIFEST_REFRESH_PERIOD_MS = 5000; + /** + * The minimum default start position for live streams, relative to the start of the live window. + */ + private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000; + + private final Uri manifestUri; + private final DataSource.Factory manifestDataSourceFactory; + private final SsChunkSource.Factory chunkSourceFactory; + private final int minLoadableRetryCount; + private final long livePresentationDelayMs; + private final EventDispatcher eventDispatcher; + private final SsManifestParser manifestParser; + private final ArrayList mediaPeriods; + + private MediaSource.Listener sourceListener; + private DataSource manifestDataSource; + private Loader manifestLoader; + private LoaderErrorThrower manifestLoaderErrorThrower; + + private long manifestLoadStartTimestamp; + private SsManifest manifest; + + private Handler manifestRefreshHandler; + + /** + * Constructs an instance to play a given {@link SsManifest}, which must not be live. + * + * @param manifest The manifest. {@link SsManifest#isLive} must be false. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + Handler eventHandler, AdaptiveMediaSourceEventListener eventListener) { + this(manifest, chunkSourceFactory, DEFAULT_MIN_LOADABLE_RETRY_COUNT, + eventHandler, eventListener); + } + + /** + * Constructs an instance to play a given {@link SsManifest}, which must not be live. + * + * @param manifest The manifest. {@link SsManifest#isLive} must be false. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(SsManifest manifest, SsChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifest, null, null, null, chunkSourceFactory, minLoadableRetryCount, + DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + SsChunkSource.Factory chunkSourceFactory, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, chunkSourceFactory, + DEFAULT_MIN_LOADABLE_RETRY_COUNT, DEFAULT_LIVE_PRESENTATION_DELAY_MS, eventHandler, + eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(manifestUri, manifestDataSourceFactory, new SsManifestParser(), chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + /** + * Constructs an instance to play the manifest at a given {@link Uri}, which may be live or + * on-demand. + * + * @param manifestUri The manifest {@link Uri}. + * @param manifestDataSourceFactory A factory for {@link DataSource} instances that will be used + * to load (and refresh) the manifest. + * @param manifestParser A parser for loaded manifest data. + * @param chunkSourceFactory A factory for {@link SsChunkSource} instances. + * @param minLoadableRetryCount The minimum number of times to retry if a loading error occurs. + * @param livePresentationDelayMs For live playbacks, the duration in milliseconds by which the + * default start position should precede the end of the live window. + * @param eventHandler A handler for events. May be null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + */ + public SsMediaSource(Uri manifestUri, DataSource.Factory manifestDataSourceFactory, + SsManifestParser manifestParser, SsChunkSource.Factory chunkSourceFactory, + int minLoadableRetryCount, long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + this(null, manifestUri, manifestDataSourceFactory, manifestParser, chunkSourceFactory, + minLoadableRetryCount, livePresentationDelayMs, eventHandler, eventListener); + } + + private SsMediaSource(SsManifest manifest, Uri manifestUri, + DataSource.Factory manifestDataSourceFactory, SsManifestParser manifestParser, + SsChunkSource.Factory chunkSourceFactory, int minLoadableRetryCount, + long livePresentationDelayMs, Handler eventHandler, + AdaptiveMediaSourceEventListener eventListener) { + Assertions.checkState(manifest == null || !manifest.isLive); + this.manifest = manifest; + this.manifestUri = manifestUri == null ? null + : Util.toLowerInvariant(manifestUri.getLastPathSegment()).equals("manifest") ? manifestUri + : Uri.withAppendedPath(manifestUri, "Manifest"); + this.manifestDataSourceFactory = manifestDataSourceFactory; + this.manifestParser = manifestParser; + this.chunkSourceFactory = chunkSourceFactory; + this.minLoadableRetryCount = minLoadableRetryCount; + this.livePresentationDelayMs = livePresentationDelayMs; + this.eventDispatcher = new EventDispatcher(eventHandler, eventListener); + mediaPeriods = new ArrayList<>(); + } + + // MediaSource implementation. + + @Override + public void prepareSource(MediaSource.Listener listener) { + sourceListener = listener; + if (manifest != null) { + manifestLoaderErrorThrower = new LoaderErrorThrower.Dummy(); + processManifest(); + } else { + manifestDataSource = manifestDataSourceFactory.createDataSource(); + manifestLoader = new Loader("Loader:Manifest"); + manifestLoaderErrorThrower = manifestLoader; + manifestRefreshHandler = new Handler(); + startLoadingManifest(); + } + } + + @Override + public void maybeThrowSourceInfoRefreshError() throws IOException { + manifestLoaderErrorThrower.maybeThrowError(); + } + + @Override + public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) { + Assertions.checkArgument(index == 0); + SsMediaPeriod period = new SsMediaPeriod(manifest, chunkSourceFactory, minLoadableRetryCount, + eventDispatcher, manifestLoaderErrorThrower, allocator); + mediaPeriods.add(period); + return period; + } + + @Override + public void releasePeriod(MediaPeriod period) { + ((SsMediaPeriod) period).release(); + mediaPeriods.remove(period); + } + + @Override + public void releaseSource() { + sourceListener = null; + manifest = null; + manifestDataSource = null; + manifestLoadStartTimestamp = 0; + if (manifestLoader != null) { + manifestLoader.release(); + manifestLoader = null; + } + if (manifestRefreshHandler != null) { + manifestRefreshHandler.removeCallbacksAndMessages(null); + manifestRefreshHandler = null; + } + } + + // Loader.Callback implementation + + @Override + public void onLoadCompleted(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + manifest = loadable.getResult(); + manifestLoadStartTimestamp = elapsedRealtimeMs - loadDurationMs; + processManifest(); + scheduleManifestRefresh(); + } + + @Override + public void onLoadCanceled(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, boolean released) { + eventDispatcher.loadCompleted(loadable.dataSpec, loadable.type, elapsedRealtimeMs, + loadDurationMs, loadable.bytesLoaded()); + } + + @Override + public int onLoadError(ParsingLoadable loadable, long elapsedRealtimeMs, + long loadDurationMs, IOException error) { + boolean isFatal = error instanceof ParserException; + eventDispatcher.loadError(loadable.dataSpec, loadable.type, elapsedRealtimeMs, loadDurationMs, + loadable.bytesLoaded(), error, isFatal); + return isFatal ? Loader.DONT_RETRY_FATAL : Loader.RETRY; + } + + // Internal methods + + private void processManifest() { + for (int i = 0; i < mediaPeriods.size(); i++) { + mediaPeriods.get(i).updateManifest(manifest); + } + Timeline timeline; + if (manifest.isLive) { + long startTimeUs = Long.MAX_VALUE; + long endTimeUs = Long.MIN_VALUE; + for (int i = 0; i < manifest.streamElements.length; i++) { + StreamElement element = manifest.streamElements[i]; + if (element.chunkCount > 0) { + startTimeUs = Math.min(startTimeUs, element.getStartTimeUs(0)); + endTimeUs = Math.max(endTimeUs, element.getStartTimeUs(element.chunkCount - 1) + + element.getChunkDurationUs(element.chunkCount - 1)); + } + } + if (startTimeUs == Long.MAX_VALUE) { + timeline = new SinglePeriodTimeline(C.TIME_UNSET, false); + } else { + if (manifest.dvrWindowLengthUs != C.TIME_UNSET + && manifest.dvrWindowLengthUs > 0) { + startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); + } + long durationUs = endTimeUs - startTimeUs; + long defaultStartPositionUs = durationUs - C.msToUs(livePresentationDelayMs); + if (defaultStartPositionUs < MIN_LIVE_DEFAULT_START_POSITION_US) { + // The default start position is too close to the start of the live window. Set it to the + // minimum default start position provided the window is at least twice as big. Else set + // it to the middle of the window. + defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); + } + timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, + defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); + } + } else { + boolean isSeekable = manifest.durationUs != C.TIME_UNSET; + timeline = new SinglePeriodTimeline(manifest.durationUs, isSeekable); + } + sourceListener.onSourceInfoRefreshed(timeline, manifest); + } + + private void scheduleManifestRefresh() { + if (!manifest.isLive) { + return; + } + long nextLoadTimestamp = manifestLoadStartTimestamp + MINIMUM_MANIFEST_REFRESH_PERIOD_MS; + long delayUntilNextLoad = Math.max(0, nextLoadTimestamp - SystemClock.elapsedRealtime()); + manifestRefreshHandler.postDelayed(new Runnable() { + @Override + public void run() { + startLoadingManifest(); + } + }, delayUntilNextLoad); + } + + private void startLoadingManifest() { + ParsingLoadable loadable = new ParsingLoadable<>(manifestDataSource, + manifestUri, C.DATA_TYPE_MANIFEST, manifestParser); + long elapsedRealtimeMs = manifestLoader.startLoading(loadable, this, minLoadableRetryCount); + eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, elapsedRealtimeMs); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifest.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifest.java new file mode 100755 index 00000000000..934e0dd63a8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.UriUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.List; +import java.util.UUID; + +/** + * Represents a SmoothStreaming manifest. + * + * @see + * IIS Smooth Streaming Client Manifest Format + */ +public class SsManifest { + + public static final int UNSET_LOOKAHEAD = -1; + + /** + * The client manifest major version. + */ + public final int majorVersion; + + /** + * The client manifest minor version. + */ + public final int minorVersion; + + /** + * The number of fragments in a lookahead, or {@link #UNSET_LOOKAHEAD} if the lookahead is + * unspecified. + */ + public final int lookAheadCount; + + /** + * Whether the manifest describes a live presentation still in progress. + */ + public final boolean isLive; + + /** + * Content protection information, or null if the content is not protected. + */ + public final ProtectionElement protectionElement; + + /** + * The contained stream elements. + */ + public final StreamElement[] streamElements; + + /** + * The overall presentation duration of the media in microseconds, or {@link C#TIME_UNSET} + * if the duration is unknown. + */ + public final long durationUs; + + /** + * The length of the trailing window for a live broadcast in microseconds, or + * {@link C#TIME_UNSET} if the stream is not live or if the window length is unspecified. + */ + public final long dvrWindowLengthUs; + + /** + * @param majorVersion The client manifest major version. + * @param minorVersion The client manifest minor version. + * @param timescale The timescale of the media as the number of units that pass in one second. + * @param duration The overall presentation duration in units of the timescale attribute, or 0 + * if the duration is unknown. + * @param dvrWindowLength The length of the trailing window in units of the timescale attribute, + * or 0 if this attribute is unspecified or not applicable. + * @param lookAheadCount The number of fragments in a lookahead, or {@link #UNSET_LOOKAHEAD} if + * this attribute is unspecified or not applicable. + * @param isLive True if the manifest describes a live presentation still in progress. False + * otherwise. + * @param protectionElement Content protection information, or null if the content is not + * protected. + * @param streamElements The contained stream elements. + */ + public SsManifest(int majorVersion, int minorVersion, long timescale, long duration, + long dvrWindowLength, int lookAheadCount, boolean isLive, ProtectionElement protectionElement, + StreamElement[] streamElements) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.lookAheadCount = lookAheadCount; + this.isLive = isLive; + this.protectionElement = protectionElement; + this.streamElements = streamElements; + dvrWindowLengthUs = dvrWindowLength == 0 ? C.TIME_UNSET + : Util.scaleLargeTimestamp(dvrWindowLength, C.MICROS_PER_SECOND, timescale); + durationUs = duration == 0 ? C.TIME_UNSET + : Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, timescale); + } + + /** + * Represents a protection element containing a single header. + */ + public static class ProtectionElement { + + public final UUID uuid; + public final byte[] data; + + public ProtectionElement(UUID uuid, byte[] data) { + this.uuid = uuid; + this.data = data; + } + + } + + /** + * Represents a StreamIndex element. + */ + public static class StreamElement { + + private static final String URL_PLACEHOLDER_START_TIME = "{start time}"; + private static final String URL_PLACEHOLDER_BITRATE = "{bitrate}"; + + public final int type; + public final String subType; + public final long timescale; + public final String name; + public final int maxWidth; + public final int maxHeight; + public final int displayWidth; + public final int displayHeight; + public final String language; + public final Format[] formats; + public final int chunkCount; + + private final String baseUri; + private final String chunkTemplate; + + private final List chunkStartTimes; + private final long[] chunkStartTimesUs; + private final long lastChunkDurationUs; + + public StreamElement(String baseUri, String chunkTemplate, int type, String subType, + long timescale, String name, int maxWidth, int maxHeight, int displayWidth, + int displayHeight, String language, Format[] formats, List chunkStartTimes, + long lastChunkDuration) { + this.baseUri = baseUri; + this.chunkTemplate = chunkTemplate; + this.type = type; + this.subType = subType; + this.timescale = timescale; + this.name = name; + this.maxWidth = maxWidth; + this.maxHeight = maxHeight; + this.displayWidth = displayWidth; + this.displayHeight = displayHeight; + this.language = language; + this.formats = formats; + this.chunkCount = chunkStartTimes.size(); + this.chunkStartTimes = chunkStartTimes; + lastChunkDurationUs = + Util.scaleLargeTimestamp(lastChunkDuration, C.MICROS_PER_SECOND, timescale); + chunkStartTimesUs = + Util.scaleLargeTimestamps(chunkStartTimes, C.MICROS_PER_SECOND, timescale); + } + + /** + * Returns the index of the chunk that contains the specified time. + * + * @param timeUs The time in microseconds. + * @return The index of the corresponding chunk. + */ + public int getChunkIndex(long timeUs) { + return Util.binarySearchFloor(chunkStartTimesUs, timeUs, true, true); + } + + /** + * Returns the start time of the specified chunk. + * + * @param chunkIndex The index of the chunk. + * @return The start time of the chunk, in microseconds. + */ + public long getStartTimeUs(int chunkIndex) { + return chunkStartTimesUs[chunkIndex]; + } + + /** + * Returns the duration of the specified chunk. + * + * @param chunkIndex The index of the chunk. + * @return The duration of the chunk, in microseconds. + */ + public long getChunkDurationUs(int chunkIndex) { + return (chunkIndex == chunkCount - 1) ? lastChunkDurationUs + : chunkStartTimesUs[chunkIndex + 1] - chunkStartTimesUs[chunkIndex]; + } + + /** + * Builds a uri for requesting the specified chunk of the specified track. + * + * @param track The index of the track for which to build the URL. + * @param chunkIndex The index of the chunk for which to build the URL. + * @return The request uri. + */ + public Uri buildRequestUri(int track, int chunkIndex) { + Assertions.checkState(formats != null); + Assertions.checkState(chunkStartTimes != null); + Assertions.checkState(chunkIndex < chunkStartTimes.size()); + String chunkUrl = chunkTemplate + .replace(URL_PLACEHOLDER_BITRATE, Integer.toString(formats[track].bitrate)) + .replace(URL_PLACEHOLDER_START_TIME, chunkStartTimes.get(chunkIndex).toString()); + return UriUtil.resolveToUri(baseUri, chunkUrl); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java new file mode 100755 index 00000000000..6496befe5fb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/source/smoothstreaming/manifest/SsManifestParser.java @@ -0,0 +1,698 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest; + +import android.net.Uri; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.drm.DrmInitData.SchemeData; +import org.telegram.messenger.exoplayer2.extractor.mp4.PsshAtomUtil; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest.ProtectionElement; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; +import org.telegram.messenger.exoplayer2.upstream.ParsingLoadable; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.CodecSpecificDataUtil; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.UUID; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * Parses SmoothStreaming client manifests. + * + * @see + * IIS Smooth Streaming Client Manifest Format + */ +public class SsManifestParser implements ParsingLoadable.Parser { + + private final XmlPullParserFactory xmlParserFactory; + + public SsManifestParser() { + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + @Override + public SsManifest parse(Uri uri, InputStream inputStream) throws IOException { + try { + XmlPullParser xmlParser = xmlParserFactory.newPullParser(); + xmlParser.setInput(inputStream, null); + SmoothStreamingMediaParser smoothStreamingMediaParser = + new SmoothStreamingMediaParser(null, uri.toString()); + return (SsManifest) smoothStreamingMediaParser.parse(xmlParser); + } catch (XmlPullParserException e) { + throw new ParserException(e); + } + } + + /** + * Thrown if a required field is missing. + */ + public static class MissingFieldException extends ParserException { + + public MissingFieldException(String fieldName) { + super("Missing required field: " + fieldName); + } + + } + + /** + * A base class for parsers that parse components of a smooth streaming manifest. + */ + private abstract static class ElementParser { + + private final String baseUri; + private final String tag; + + private final ElementParser parent; + private final List> normalizedAttributes; + + public ElementParser(ElementParser parent, String baseUri, String tag) { + this.parent = parent; + this.baseUri = baseUri; + this.tag = tag; + this.normalizedAttributes = new LinkedList<>(); + } + + public final Object parse(XmlPullParser xmlParser) throws XmlPullParserException, IOException { + String tagName; + boolean foundStartTag = false; + int skippingElementDepth = 0; + while (true) { + int eventType = xmlParser.getEventType(); + switch (eventType) { + case XmlPullParser.START_TAG: + tagName = xmlParser.getName(); + if (tag.equals(tagName)) { + foundStartTag = true; + parseStartTag(xmlParser); + } else if (foundStartTag) { + if (skippingElementDepth > 0) { + skippingElementDepth++; + } else if (handleChildInline(tagName)) { + parseStartTag(xmlParser); + } else { + ElementParser childElementParser = newChildParser(this, tagName, baseUri); + if (childElementParser == null) { + skippingElementDepth = 1; + } else { + addChild(childElementParser.parse(xmlParser)); + } + } + } + break; + case XmlPullParser.TEXT: + if (foundStartTag && skippingElementDepth == 0) { + parseText(xmlParser); + } + break; + case XmlPullParser.END_TAG: + if (foundStartTag) { + if (skippingElementDepth > 0) { + skippingElementDepth--; + } else { + tagName = xmlParser.getName(); + parseEndTag(xmlParser); + if (!handleChildInline(tagName)) { + return build(); + } + } + } + break; + case XmlPullParser.END_DOCUMENT: + return null; + default: + // Do nothing. + break; + } + xmlParser.next(); + } + } + + private ElementParser newChildParser(ElementParser parent, String name, String baseUri) { + if (QualityLevelParser.TAG.equals(name)) { + return new QualityLevelParser(parent, baseUri); + } else if (ProtectionParser.TAG.equals(name)) { + return new ProtectionParser(parent, baseUri); + } else if (StreamIndexParser.TAG.equals(name)) { + return new StreamIndexParser(parent, baseUri); + } + return null; + } + + /** + * Stash an attribute that may be normalized at this level. In other words, an attribute that + * may have been pulled up from the child elements because its value was the same in all + * children. + *

        + * Stashing an attribute allows child element parsers to retrieve the values of normalized + * attributes using {@link #getNormalizedAttribute(String)}. + * + * @param key The name of the attribute. + * @param value The value of the attribute. + */ + protected final void putNormalizedAttribute(String key, Object value) { + normalizedAttributes.add(Pair.create(key, value)); + } + + /** + * Attempt to retrieve a stashed normalized attribute. If there is no stashed attribute with + * the provided name, the parent element parser will be queried, and so on up the chain. + * + * @param key The name of the attribute. + * @return The stashed value, or null if the attribute was not be found. + */ + protected final Object getNormalizedAttribute(String key) { + for (int i = 0; i < normalizedAttributes.size(); i++) { + Pair pair = normalizedAttributes.get(i); + if (pair.first.equals(key)) { + return pair.second; + } + } + return parent == null ? null : parent.getNormalizedAttribute(key); + } + + /** + * Whether this {@link ElementParser} parses a child element inline. + * + * @param tagName The name of the child element. + * @return Whether the child is parsed inline. + */ + protected boolean handleChildInline(String tagName) { + return false; + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + * @throws ParserException + */ + protected void parseStartTag(XmlPullParser xmlParser) throws ParserException { + // Do nothing. + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + */ + protected void parseText(XmlPullParser xmlParser) { + // Do nothing. + } + + /** + * @param xmlParser The underlying {@link XmlPullParser} + */ + protected void parseEndTag(XmlPullParser xmlParser) { + // Do nothing. + } + + /** + * @param parsedChild A parsed child object. + */ + protected void addChild(Object parsedChild) { + // Do nothing. + } + + protected abstract Object build(); + + protected final String parseRequiredString(XmlPullParser parser, String key) + throws MissingFieldException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + return value; + } else { + throw new MissingFieldException(key); + } + } + + protected final int parseInt(XmlPullParser parser, String key, int defaultValue) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + return defaultValue; + } + } + + protected final int parseRequiredInt(XmlPullParser parser, String key) throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + throw new MissingFieldException(key); + } + } + + protected final long parseLong(XmlPullParser parser, String key, long defaultValue) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + return defaultValue; + } + } + + protected final long parseRequiredLong(XmlPullParser parser, String key) + throws ParserException { + String value = parser.getAttributeValue(null, key); + if (value != null) { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new ParserException(e); + } + } else { + throw new MissingFieldException(key); + } + } + + protected final boolean parseBoolean(XmlPullParser parser, String key, boolean defaultValue) { + String value = parser.getAttributeValue(null, key); + if (value != null) { + return Boolean.parseBoolean(value); + } else { + return defaultValue; + } + } + + } + + private static class SmoothStreamingMediaParser extends ElementParser { + + public static final String TAG = "SmoothStreamingMedia"; + + private static final String KEY_MAJOR_VERSION = "MajorVersion"; + private static final String KEY_MINOR_VERSION = "MinorVersion"; + private static final String KEY_TIME_SCALE = "TimeScale"; + private static final String KEY_DVR_WINDOW_LENGTH = "DVRWindowLength"; + private static final String KEY_DURATION = "Duration"; + private static final String KEY_LOOKAHEAD_COUNT = "LookaheadCount"; + private static final String KEY_IS_LIVE = "IsLive"; + + private final List streamElements; + + private int majorVersion; + private int minorVersion; + private long timescale; + private long duration; + private long dvrWindowLength; + private int lookAheadCount; + private boolean isLive; + private ProtectionElement protectionElement; + + public SmoothStreamingMediaParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + lookAheadCount = SsManifest.UNSET_LOOKAHEAD; + protectionElement = null; + streamElements = new LinkedList<>(); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + majorVersion = parseRequiredInt(parser, KEY_MAJOR_VERSION); + minorVersion = parseRequiredInt(parser, KEY_MINOR_VERSION); + timescale = parseLong(parser, KEY_TIME_SCALE, 10000000L); + duration = parseRequiredLong(parser, KEY_DURATION); + dvrWindowLength = parseLong(parser, KEY_DVR_WINDOW_LENGTH, 0); + lookAheadCount = parseInt(parser, KEY_LOOKAHEAD_COUNT, SsManifest.UNSET_LOOKAHEAD); + isLive = parseBoolean(parser, KEY_IS_LIVE, false); + putNormalizedAttribute(KEY_TIME_SCALE, timescale); + } + + @Override + public void addChild(Object child) { + if (child instanceof StreamElement) { + streamElements.add((StreamElement) child); + } else if (child instanceof ProtectionElement) { + Assertions.checkState(protectionElement == null); + protectionElement = (ProtectionElement) child; + } + } + + @Override + public Object build() { + StreamElement[] streamElementArray = new StreamElement[streamElements.size()]; + streamElements.toArray(streamElementArray); + if (protectionElement != null) { + DrmInitData drmInitData = new DrmInitData(new SchemeData(protectionElement.uuid, + MimeTypes.VIDEO_MP4, protectionElement.data)); + for (StreamElement streamElement : streamElementArray) { + for (int i = 0; i < streamElement.formats.length; i++) { + streamElement.formats[i] = streamElement.formats[i].copyWithDrmInitData(drmInitData); + } + } + } + return new SsManifest(majorVersion, minorVersion, timescale, duration, dvrWindowLength, + lookAheadCount, isLive, protectionElement, streamElementArray); + } + + } + + private static class ProtectionParser extends ElementParser { + + public static final String TAG = "Protection"; + public static final String TAG_PROTECTION_HEADER = "ProtectionHeader"; + + public static final String KEY_SYSTEM_ID = "SystemID"; + + private boolean inProtectionHeader; + private UUID uuid; + private byte[] initData; + + public ProtectionParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + } + + @Override + public boolean handleChildInline(String tag) { + return TAG_PROTECTION_HEADER.equals(tag); + } + + @Override + public void parseStartTag(XmlPullParser parser) { + if (TAG_PROTECTION_HEADER.equals(parser.getName())) { + inProtectionHeader = true; + String uuidString = parser.getAttributeValue(null, KEY_SYSTEM_ID); + uuidString = stripCurlyBraces(uuidString); + uuid = UUID.fromString(uuidString); + } + } + + @Override + public void parseText(XmlPullParser parser) { + if (inProtectionHeader) { + initData = Base64.decode(parser.getText(), Base64.DEFAULT); + } + } + + @Override + public void parseEndTag(XmlPullParser parser) { + if (TAG_PROTECTION_HEADER.equals(parser.getName())) { + inProtectionHeader = false; + } + } + + @Override + public Object build() { + return new ProtectionElement(uuid, PsshAtomUtil.buildPsshAtom(uuid, initData)); + } + + private static String stripCurlyBraces(String uuidString) { + if (uuidString.charAt(0) == '{' && uuidString.charAt(uuidString.length() - 1) == '}') { + uuidString = uuidString.substring(1, uuidString.length() - 1); + } + return uuidString; + } + } + + private static class StreamIndexParser extends ElementParser { + + public static final String TAG = "StreamIndex"; + private static final String TAG_STREAM_FRAGMENT = "c"; + + private static final String KEY_TYPE = "Type"; + private static final String KEY_TYPE_AUDIO = "audio"; + private static final String KEY_TYPE_VIDEO = "video"; + private static final String KEY_TYPE_TEXT = "text"; + private static final String KEY_SUB_TYPE = "Subtype"; + private static final String KEY_NAME = "Name"; + private static final String KEY_URL = "Url"; + private static final String KEY_MAX_WIDTH = "MaxWidth"; + private static final String KEY_MAX_HEIGHT = "MaxHeight"; + private static final String KEY_DISPLAY_WIDTH = "DisplayWidth"; + private static final String KEY_DISPLAY_HEIGHT = "DisplayHeight"; + private static final String KEY_LANGUAGE = "Language"; + private static final String KEY_TIME_SCALE = "TimeScale"; + + private static final String KEY_FRAGMENT_DURATION = "d"; + private static final String KEY_FRAGMENT_START_TIME = "t"; + private static final String KEY_FRAGMENT_REPEAT_COUNT = "r"; + + private final String baseUri; + private final List formats; + + private int type; + private String subType; + private long timescale; + private String name; + private String url; + private int maxWidth; + private int maxHeight; + private int displayWidth; + private int displayHeight; + private String language; + private ArrayList startTimes; + + private long lastChunkDuration; + + public StreamIndexParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + this.baseUri = baseUri; + formats = new LinkedList<>(); + } + + @Override + public boolean handleChildInline(String tag) { + return TAG_STREAM_FRAGMENT.equals(tag); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + if (TAG_STREAM_FRAGMENT.equals(parser.getName())) { + parseStreamFragmentStartTag(parser); + } else { + parseStreamElementStartTag(parser); + } + } + + private void parseStreamFragmentStartTag(XmlPullParser parser) throws ParserException { + int chunkIndex = startTimes.size(); + long startTime = parseLong(parser, KEY_FRAGMENT_START_TIME, C.TIME_UNSET); + if (startTime == C.TIME_UNSET) { + if (chunkIndex == 0) { + // Assume the track starts at t = 0. + startTime = 0; + } else if (lastChunkDuration != C.INDEX_UNSET) { + // Infer the start time from the previous chunk's start time and duration. + startTime = startTimes.get(chunkIndex - 1) + lastChunkDuration; + } else { + // We don't have the start time, and we're unable to infer it. + throw new ParserException("Unable to infer start time"); + } + } + chunkIndex++; + startTimes.add(startTime); + lastChunkDuration = parseLong(parser, KEY_FRAGMENT_DURATION, C.TIME_UNSET); + // Handle repeated chunks. + long repeatCount = parseLong(parser, KEY_FRAGMENT_REPEAT_COUNT, 1L); + if (repeatCount > 1 && lastChunkDuration == C.TIME_UNSET) { + throw new ParserException("Repeated chunk with unspecified duration"); + } + for (int i = 1; i < repeatCount; i++) { + chunkIndex++; + startTimes.add(startTime + (lastChunkDuration * i)); + } + } + + private void parseStreamElementStartTag(XmlPullParser parser) throws ParserException { + type = parseType(parser); + putNormalizedAttribute(KEY_TYPE, type); + if (type == C.TRACK_TYPE_TEXT) { + subType = parseRequiredString(parser, KEY_SUB_TYPE); + } else { + subType = parser.getAttributeValue(null, KEY_SUB_TYPE); + } + name = parser.getAttributeValue(null, KEY_NAME); + url = parseRequiredString(parser, KEY_URL); + maxWidth = parseInt(parser, KEY_MAX_WIDTH, Format.NO_VALUE); + maxHeight = parseInt(parser, KEY_MAX_HEIGHT, Format.NO_VALUE); + displayWidth = parseInt(parser, KEY_DISPLAY_WIDTH, Format.NO_VALUE); + displayHeight = parseInt(parser, KEY_DISPLAY_HEIGHT, Format.NO_VALUE); + language = parser.getAttributeValue(null, KEY_LANGUAGE); + putNormalizedAttribute(KEY_LANGUAGE, language); + timescale = parseInt(parser, KEY_TIME_SCALE, -1); + if (timescale == -1) { + timescale = (Long) getNormalizedAttribute(KEY_TIME_SCALE); + } + startTimes = new ArrayList<>(); + } + + private int parseType(XmlPullParser parser) throws ParserException { + String value = parser.getAttributeValue(null, KEY_TYPE); + if (value != null) { + if (KEY_TYPE_AUDIO.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_AUDIO; + } else if (KEY_TYPE_VIDEO.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_VIDEO; + } else if (KEY_TYPE_TEXT.equalsIgnoreCase(value)) { + return C.TRACK_TYPE_TEXT; + } else { + throw new ParserException("Invalid key value[" + value + "]"); + } + } + throw new MissingFieldException(KEY_TYPE); + } + + @Override + public void addChild(Object child) { + if (child instanceof Format) { + formats.add((Format) child); + } + } + + @Override + public Object build() { + Format[] formatArray = new Format[formats.size()]; + formats.toArray(formatArray); + return new StreamElement(baseUri, url, type, subType, timescale, name, maxWidth, maxHeight, + displayWidth, displayHeight, language, formatArray, startTimes, lastChunkDuration); + } + + } + + private static class QualityLevelParser extends ElementParser { + + public static final String TAG = "QualityLevel"; + + private static final String KEY_INDEX = "Index"; + private static final String KEY_BITRATE = "Bitrate"; + private static final String KEY_CODEC_PRIVATE_DATA = "CodecPrivateData"; + private static final String KEY_SAMPLING_RATE = "SamplingRate"; + private static final String KEY_CHANNELS = "Channels"; + private static final String KEY_FOUR_CC = "FourCC"; + private static final String KEY_TYPE = "Type"; + private static final String KEY_LANGUAGE = "Language"; + private static final String KEY_MAX_WIDTH = "MaxWidth"; + private static final String KEY_MAX_HEIGHT = "MaxHeight"; + + private Format format; + + public QualityLevelParser(ElementParser parent, String baseUri) { + super(parent, baseUri, TAG); + } + + @Override + public void parseStartTag(XmlPullParser parser) throws ParserException { + int type = (Integer) getNormalizedAttribute(KEY_TYPE); + String id = parser.getAttributeValue(null, KEY_INDEX); + int bitrate = parseRequiredInt(parser, KEY_BITRATE); + String sampleMimeType = fourCCToMimeType(parseRequiredString(parser, KEY_FOUR_CC)); + + if (type == C.TRACK_TYPE_VIDEO) { + int width = parseRequiredInt(parser, KEY_MAX_WIDTH); + int height = parseRequiredInt(parser, KEY_MAX_HEIGHT); + List codecSpecificData = buildCodecSpecificData( + parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA)); + format = Format.createVideoContainerFormat(id, MimeTypes.VIDEO_MP4, sampleMimeType, null, + bitrate, width, height, Format.NO_VALUE, codecSpecificData); + } else if (type == C.TRACK_TYPE_AUDIO) { + sampleMimeType = sampleMimeType == null ? MimeTypes.AUDIO_AAC : sampleMimeType; + int channels = parseRequiredInt(parser, KEY_CHANNELS); + int samplingRate = parseRequiredInt(parser, KEY_SAMPLING_RATE); + List codecSpecificData = buildCodecSpecificData( + parser.getAttributeValue(null, KEY_CODEC_PRIVATE_DATA)); + if (codecSpecificData.isEmpty() && MimeTypes.AUDIO_AAC.equals(sampleMimeType)) { + codecSpecificData = Collections.singletonList( + CodecSpecificDataUtil.buildAacLcAudioSpecificConfig(samplingRate, channels)); + } + String language = (String) getNormalizedAttribute(KEY_LANGUAGE); + format = Format.createAudioContainerFormat(id, MimeTypes.AUDIO_MP4, sampleMimeType, null, + bitrate, channels, samplingRate, codecSpecificData, 0, language); + } else if (type == C.TRACK_TYPE_TEXT) { + String language = (String) getNormalizedAttribute(KEY_LANGUAGE); + format = Format.createTextContainerFormat(id, MimeTypes.APPLICATION_MP4, sampleMimeType, + null, bitrate, 0, language); + } else { + format = Format.createContainerFormat(id, MimeTypes.APPLICATION_MP4, null, sampleMimeType, + bitrate); + } + } + + @Override + public Object build() { + return format; + } + + private static List buildCodecSpecificData(String codecSpecificDataString) { + ArrayList csd = new ArrayList<>(); + if (!TextUtils.isEmpty(codecSpecificDataString)) { + byte[] codecPrivateData = Util.getBytesFromHexString(codecSpecificDataString); + byte[][] split = CodecSpecificDataUtil.splitNalUnits(codecPrivateData); + if (split == null) { + csd.add(codecPrivateData); + } else { + Collections.addAll(csd, split); + } + } + return csd; + } + + private static String fourCCToMimeType(String fourCC) { + if (fourCC.equalsIgnoreCase("H264") || fourCC.equalsIgnoreCase("X264") + || fourCC.equalsIgnoreCase("AVC1") || fourCC.equalsIgnoreCase("DAVC")) { + return MimeTypes.VIDEO_H264; + } else if (fourCC.equalsIgnoreCase("AAC") || fourCC.equalsIgnoreCase("AACL") + || fourCC.equalsIgnoreCase("AACH") || fourCC.equalsIgnoreCase("AACP")) { + return MimeTypes.AUDIO_AAC; + } else if (fourCC.equalsIgnoreCase("TTML")) { + return MimeTypes.APPLICATION_TTML; + } else if (fourCC.equalsIgnoreCase("ac-3") || fourCC.equalsIgnoreCase("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (fourCC.equalsIgnoreCase("ec-3") || fourCC.equalsIgnoreCase("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (fourCC.equalsIgnoreCase("dtsc")) { + return MimeTypes.AUDIO_DTS; + } else if (fourCC.equalsIgnoreCase("dtsh") || fourCC.equalsIgnoreCase("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (fourCC.equalsIgnoreCase("dtse")) { + return MimeTypes.AUDIO_DTS_EXPRESS; + } else if (fourCC.equalsIgnoreCase("opus")) { + return MimeTypes.AUDIO_OPUS; + } + return null; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/CaptionStyleCompat.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/CaptionStyleCompat.java similarity index 87% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/CaptionStyleCompat.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/CaptionStyleCompat.java index c26ef64ca4e..32c798cfaac 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/CaptionStyleCompat.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/CaptionStyleCompat.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,40 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text; +package org.telegram.messenger.exoplayer2.text; import android.annotation.TargetApi; import android.graphics.Color; import android.graphics.Typeface; +import android.support.annotation.IntDef; import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * A compatibility wrapper for {@link CaptionStyle}. */ public final class CaptionStyleCompat { + /** + * The type of edge, which may be none. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({EDGE_TYPE_NONE, EDGE_TYPE_OUTLINE, EDGE_TYPE_DROP_SHADOW, EDGE_TYPE_RAISED, + EDGE_TYPE_DEPRESSED}) + public @interface EdgeType {} /** * Edge type value specifying no character edges. */ public static final int EDGE_TYPE_NONE = 0; - /** * Edge type value specifying uniformly outlined character edges. */ public static final int EDGE_TYPE_OUTLINE = 1; - /** * Edge type value specifying drop-shadowed character edges. */ public static final int EDGE_TYPE_DROP_SHADOW = 2; - /** * Edge type value specifying raised bevel character edges. */ public static final int EDGE_TYPE_RAISED = 3; - /** * Edge type value specifying depressed bevel character edges. */ @@ -88,6 +94,7 @@ public final class CaptionStyleCompat { *

      • {@link #EDGE_TYPE_DEPRESSED} * */ + @EdgeType public final int edgeType; /** @@ -126,8 +133,8 @@ public static CaptionStyleCompat createFromCaptionStyle( * @param edgeColor See {@link #edgeColor}. * @param typeface See {@link #typeface}. */ - public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, int edgeType, - int edgeColor, Typeface typeface) { + public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowColor, + @EdgeType int edgeType, int edgeColor, Typeface typeface) { this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; this.windowColor = windowColor; @@ -137,6 +144,7 @@ public CaptionStyleCompat(int foregroundColor, int backgroundColor, int windowCo } @TargetApi(19) + @SuppressWarnings("ResourceType") private static CaptionStyleCompat createFromCaptionStyleV19( CaptioningManager.CaptionStyle captionStyle) { return new CaptionStyleCompat( @@ -145,6 +153,7 @@ private static CaptionStyleCompat createFromCaptionStyleV19( } @TargetApi(21) + @SuppressWarnings("ResourceType") private static CaptionStyleCompat createFromCaptionStyleV21( CaptioningManager.CaptionStyle captionStyle) { return new CaptionStyleCompat( diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Cue.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Cue.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Cue.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Cue.java index a1a0e650f95..bc0a8b55b6c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/Cue.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Cue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text; +package org.telegram.messenger.exoplayer2.text; +import android.support.annotation.IntDef; import android.text.Layout.Alignment; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Contains information about a specific cue, including textual content and formatting data. @@ -26,6 +29,13 @@ public class Cue { * An unset position or width. */ public static final float DIMEN_UNSET = Float.MIN_VALUE; + + /** + * The type of anchor, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END}) + public @interface AnchorType {} /** * An unset anchor or line type value. */ @@ -44,6 +54,13 @@ public class Cue { * box. */ public static final int ANCHOR_TYPE_END = 2; + + /** + * The type of line, which may be unset. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER}) + public @interface LineType {} /** * Value for {@link #lineType} when {@link #line} is a fractional position. */ @@ -58,7 +75,7 @@ public class Cue { */ public final CharSequence text; /** - * The alignment of the cue text within the cue box. + * The alignment of the cue text within the cue box, or null if the alignment is undefined. */ public final Alignment textAlignment; /** @@ -83,6 +100,7 @@ public class Cue { * -1). For horizontal text the size of the first line of the cue is its height, and the start * and end of the viewport are the top and bottom respectively. */ + @LineType public final int lineType; /** * The cue box anchor positioned by {@link #line}. One of {@link #ANCHOR_TYPE_START}, @@ -92,6 +110,7 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box * respectively. */ + @AnchorType public final int lineAnchor; /** * The fractional position of the {@link #positionAnchor} of the cue box within the viewport in @@ -110,6 +129,7 @@ public class Cue { * and {@link #ANCHOR_TYPE_END} correspond to the left, middle and right of the cue box * respectively. */ + @AnchorType public final int positionAnchor; /** * The size of the cue box in the writing direction specified as a fraction of the viewport size @@ -117,16 +137,28 @@ public class Cue { */ public final float size; - public Cue() { - this(null); - } - + /** + * Constructs a cue whose {@link #textAlignment} is null, whose type parameters are set to + * {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}. + * + * @param text See {@link #text}. + */ public Cue(CharSequence text) { this(text, null, DIMEN_UNSET, TYPE_UNSET, TYPE_UNSET, DIMEN_UNSET, TYPE_UNSET, DIMEN_UNSET); } - public Cue(CharSequence text, Alignment textAlignment, float line, int lineType, - int lineAnchor, float position, int positionAnchor, float size) { + /** + * @param text See {@link #text}. + * @param textAlignment See {@link #textAlignment}. + * @param line See {@link #line}. + * @param lineType See {@link #lineType}. + * @param lineAnchor See {@link #lineAnchor}. + * @param position See {@link #position}. + * @param positionAnchor See {@link #positionAnchor}. + * @param size See {@link #size}. + */ + public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType, + @AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) { this.text = text; this.textAlignment = textAlignment; this.line = line; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleDecoder.java new file mode 100755 index 00000000000..69c2a6074a4 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleDecoder.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.decoder.SimpleDecoder; +import java.nio.ByteBuffer; + +/** + * Base class for subtitle parsers that use their own decode thread. + */ +public abstract class SimpleSubtitleDecoder extends + SimpleDecoder implements + SubtitleDecoder { + + private final String name; + + /** + * @param name The name of the decoder. + */ + protected SimpleSubtitleDecoder(String name) { + super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); + this.name = name; + setInitialInputBufferSize(1024); + } + + @Override + public final String getName() { + return name; + } + + @Override + public void setPositionUs(long timeUs) { + // Do nothing + } + + @Override + protected final SubtitleInputBuffer createInputBuffer() { + return new SubtitleInputBuffer(); + } + + @Override + protected final SubtitleOutputBuffer createOutputBuffer() { + return new SimpleSubtitleOutputBuffer(this); + } + + @Override + protected final void releaseOutputBuffer(SubtitleOutputBuffer buffer) { + super.releaseOutputBuffer(buffer); + } + + @Override + protected final SubtitleDecoderException decode(SubtitleInputBuffer inputBuffer, + SubtitleOutputBuffer outputBuffer, boolean reset) { + try { + ByteBuffer inputData = inputBuffer.data; + Subtitle subtitle = decode(inputData.array(), inputData.limit()); + outputBuffer.setContent(inputBuffer.timeUs, subtitle, inputBuffer.subsampleOffsetUs); + return null; + } catch (SubtitleDecoderException e) { + return e; + } + } + + /** + * Decodes data into a {@link Subtitle}. + * + * @param data An array holding the data to be decoded, starting at position 0. + * @param size The size of the data to be decoded. + * @return The decoded {@link Subtitle}. + * @throws SubtitleDecoderException If a decoding error occurs. + */ + protected abstract Subtitle decode(byte[] data, int size) throws SubtitleDecoderException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleOutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleOutputBuffer.java new file mode 100755 index 00000000000..82ddc7edff0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SimpleSubtitleOutputBuffer.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +/** + * A {@link SubtitleOutputBuffer} for decoders that extend {@link SimpleSubtitleDecoder}. + */ +/* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer { + + private final SimpleSubtitleDecoder owner; + + /** + * @param owner The decoder that owns this buffer. + */ + public SimpleSubtitleOutputBuffer(SimpleSubtitleDecoder owner) { + super(); + this.owner = owner; + } + + @Override + public final void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Subtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Subtitle.java new file mode 100755 index 00000000000..2dbf98f1011 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/Subtitle.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.C; +import java.util.List; + +/** + * A subtitle consisting of timed {@link Cue}s. + */ +public interface Subtitle { + + /** + * Returns the index of the first event that occurs after a given time (exclusive). + * + * @param timeUs The time in microseconds. + * @return The index of the next event, or {@link C#INDEX_UNSET} if there are no events after the + * specified time. + */ + int getNextEventTimeIndex(long timeUs); + + /** + * Returns the number of event times, where events are defined as points in time at which the cues + * returned by {@link #getCues(long)} changes. + * + * @return The number of event times. + */ + int getEventTimeCount(); + + /** + * Returns the event time at a specified index. + * + * @param index The index of the event time to obtain. + * @return The event time in microseconds. + */ + long getEventTime(int index); + + /** + * Retrieve the cues that should be displayed at a given time. + * + * @param timeUs The time in microseconds. + * @return A list of cues that should be displayed, possibly empty. + */ + List getCues(long timeUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoder.java new file mode 100755 index 00000000000..deac4476300 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoder.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.decoder.Decoder; + +/** + * Decodes {@link Subtitle}s from {@link SubtitleInputBuffer}s. + */ +public interface SubtitleDecoder extends + Decoder { + + /** + * Informs the decoder of the current playback position. + *

        + * Must be called prior to each attempt to dequeue output buffers from the decoder. + * + * @param positionUs The current playback position in microseconds. + */ + void setPositionUs(long positionUs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderException.java new file mode 100755 index 00000000000..0fd722d2df1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderException.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +/** + * Thrown when an error occurs decoding subtitle data. + */ +public class SubtitleDecoderException extends Exception { + + /** + * @param message The detail message for this exception. + */ + public SubtitleDecoderException(String message) { + super(message); + } + + /** + * @param message The detail message for this exception. + * @param cause The cause of this exception. + */ + public SubtitleDecoderException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java new file mode 100755 index 00000000000..f6c7ed5d0dd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleDecoderFactory.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.text.cea.Cea608Decoder; +import org.telegram.messenger.exoplayer2.text.subrip.SubripDecoder; +import org.telegram.messenger.exoplayer2.text.ttml.TtmlDecoder; +import org.telegram.messenger.exoplayer2.text.tx3g.Tx3gDecoder; +import org.telegram.messenger.exoplayer2.text.webvtt.Mp4WebvttDecoder; +import org.telegram.messenger.exoplayer2.text.webvtt.WebvttDecoder; +import org.telegram.messenger.exoplayer2.util.MimeTypes; + +/** + * A factory for {@link SubtitleDecoder} instances. + */ +public interface SubtitleDecoderFactory { + + /** + * Returns whether the factory is able to instantiate a {@link SubtitleDecoder} for the given + * {@link Format}. + * + * @param format The {@link Format}. + * @return Whether the factory can instantiate a suitable {@link SubtitleDecoder}. + */ + boolean supportsFormat(Format format); + + /** + * Creates a {@link SubtitleDecoder} for the given {@link Format}. + * + * @param format The {@link Format}. + * @return A new {@link SubtitleDecoder}. + * @throws IllegalArgumentException If the {@link Format} is not supported. + */ + SubtitleDecoder createDecoder(Format format); + + /** + * Default {@link SubtitleDecoderFactory} implementation. + *

        + * The formats supported by this factory are: + *

          + *
        • WebVTT ({@link WebvttDecoder})
        • + *
        • WebVTT (MP4) ({@link Mp4WebvttDecoder})
        • + *
        • TTML ({@link TtmlDecoder})
        • + *
        • SubRip ({@link SubripDecoder})
        • + *
        • TX3G ({@link Tx3gDecoder})
        • + *
        • Cea608 ({@link Cea608Decoder})
        • + *
        + */ + SubtitleDecoderFactory DEFAULT = new SubtitleDecoderFactory() { + + @Override + public boolean supportsFormat(Format format) { + return getDecoderClass(format.sampleMimeType) != null; + } + + @Override + public SubtitleDecoder createDecoder(Format format) { + try { + Class clazz = getDecoderClass(format.sampleMimeType); + if (clazz == null) { + throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); + } + if (clazz == Cea608Decoder.class) { + return clazz.asSubclass(SubtitleDecoder.class) + .getConstructor(Integer.TYPE).newInstance(format.accessibilityChannel); + } else { + return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); + } + } catch (Exception e) { + throw new IllegalStateException("Unexpected error instantiating decoder", e); + } + } + + private Class getDecoderClass(String mimeType) { + if (mimeType == null) { + return null; + } + try { + switch (mimeType) { + case MimeTypes.TEXT_VTT: + return Class.forName("org.telegram.messenger.exoplayer2.text.webvtt.WebvttDecoder"); + case MimeTypes.APPLICATION_TTML: + return Class.forName("org.telegram.messenger.exoplayer2.text.ttml.TtmlDecoder"); + case MimeTypes.APPLICATION_MP4VTT: + return Class.forName("org.telegram.messenger.exoplayer2.text.webvtt.Mp4WebvttDecoder"); + case MimeTypes.APPLICATION_SUBRIP: + return Class.forName("org.telegram.messenger.exoplayer2.text.subrip.SubripDecoder"); + case MimeTypes.APPLICATION_TX3G: + return Class.forName("org.telegram.messenger.exoplayer2.text.tx3g.Tx3gDecoder"); + case MimeTypes.APPLICATION_CEA608: + return Class.forName("org.telegram.messenger.exoplayer2.text.cea.Cea608Decoder"); + default: + return null; + } + } catch (ClassNotFoundException e) { + return null; + } + } + + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java new file mode 100755 index 00000000000..9c3654788a2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleInputBuffer.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.decoder.DecoderInputBuffer; + +/** + * A {@link DecoderInputBuffer} for a {@link SubtitleDecoder}. + */ +public final class SubtitleInputBuffer extends DecoderInputBuffer + implements Comparable { + + /** + * An offset that must be added to the subtitle's event times after it's been decoded, or + * {@link Format#OFFSET_SAMPLE_RELATIVE} if {@link #timeUs} should be added. + */ + public long subsampleOffsetUs; + + public SubtitleInputBuffer() { + super(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_NORMAL); + } + + @Override + public int compareTo(SubtitleInputBuffer other) { + long delta = timeUs - other.timeUs; + if (delta == 0) { + return 0; + } + return delta > 0 ? 1 : -1; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleOutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleOutputBuffer.java new file mode 100755 index 00000000000..efbc0c29812 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/SubtitleOutputBuffer.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.decoder.OutputBuffer; +import java.util.List; + +/** + * Base class for {@link SubtitleDecoder} output buffers. + */ +public abstract class SubtitleOutputBuffer extends OutputBuffer implements Subtitle { + + private Subtitle subtitle; + private long subsampleOffsetUs; + + /** + * Sets the content of the output buffer, consisting of a {@link Subtitle} and associated + * metadata. + * + * @param timeUs The time of the start of the subtitle in microseconds. + * @param subtitle The subtitle. + * @param subsampleOffsetUs An offset that must be added to the subtitle's event times, or + * {@link Format#OFFSET_SAMPLE_RELATIVE} if {@code timeUs} should be added. + */ + public void setContent(long timeUs, Subtitle subtitle, long subsampleOffsetUs) { + this.timeUs = timeUs; + this.subtitle = subtitle; + this.subsampleOffsetUs = subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE ? this.timeUs + : subsampleOffsetUs; + } + + @Override + public int getEventTimeCount() { + return subtitle.getEventTimeCount(); + } + + @Override + public long getEventTime(int index) { + return subtitle.getEventTime(index) + subsampleOffsetUs; + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return subtitle.getNextEventTimeIndex(timeUs - subsampleOffsetUs); + } + + @Override + public List getCues(long timeUs) { + return subtitle.getCues(timeUs - subsampleOffsetUs); + } + + @Override + public abstract void release(); + + @Override + public void clear() { + super.clear(); + subtitle = null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java new file mode 100755 index 00000000000..b4c87f25170 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/TextRenderer.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text; + +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Looper; +import android.os.Message; +import org.telegram.messenger.exoplayer2.BaseRenderer; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.FormatHolder; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import java.util.Collections; +import java.util.List; + +/** + * A renderer for text. + *

        + * {@link Subtitle}s are decoded from sample data using {@link SubtitleDecoder} instances obtained + * from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s is + * delegated to an {@link Output}. + */ +public final class TextRenderer extends BaseRenderer implements Callback { + + /** + * Receives output from a {@link TextRenderer}. + */ + public interface Output { + + /** + * Called each time there is a change in the {@link Cue}s. + * + * @param cues The {@link Cue}s. + */ + void onCues(List cues); + + } + + private static final int MSG_UPDATE_OUTPUT = 0; + + private final Handler outputHandler; + private final Output output; + private final SubtitleDecoderFactory decoderFactory; + private final FormatHolder formatHolder; + + private boolean inputStreamEnded; + private boolean outputStreamEnded; + private SubtitleDecoder decoder; + private SubtitleInputBuffer nextInputBuffer; + private SubtitleOutputBuffer subtitle; + private SubtitleOutputBuffer nextSubtitle; + private int nextSubtitleEventIndex; + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be + * called. If the output makes use of standard Android UI components, then this should + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the output + * should be called directly on the player's internal rendering thread. + */ + public TextRenderer(Output output, Looper outputLooper) { + this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); + } + + /** + * @param output The output. + * @param outputLooper The looper associated with the thread on which the output should be + * called. If the output makes use of standard Android UI components, then this should + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the output + * should be called directly on the player's internal rendering thread. + * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. + */ + public TextRenderer(Output output, Looper outputLooper, SubtitleDecoderFactory decoderFactory) { + super(C.TRACK_TYPE_TEXT); + this.output = Assertions.checkNotNull(output); + this.outputHandler = outputLooper == null ? null : new Handler(outputLooper, this); + this.decoderFactory = decoderFactory; + formatHolder = new FormatHolder(); + } + + @Override + public int supportsFormat(Format format) { + return decoderFactory.supportsFormat(format) ? FORMAT_HANDLED + : (MimeTypes.isText(format.sampleMimeType) ? FORMAT_UNSUPPORTED_SUBTYPE + : FORMAT_UNSUPPORTED_TYPE); + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + if (decoder != null) { + decoder.release(); + nextInputBuffer = null; + } + decoder = decoderFactory.createDecoder(formats[0]); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) { + clearOutput(); + resetBuffers(); + decoder.flush(); + inputStreamEnded = false; + outputStreamEnded = false; + } + + @Override + public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + if (outputStreamEnded) { + return; + } + + if (nextSubtitle == null) { + decoder.setPositionUs(positionUs); + try { + nextSubtitle = decoder.dequeueOutputBuffer(); + } catch (SubtitleDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + if (getState() != STATE_STARTED) { + return; + } + + boolean textRendererNeedsUpdate = false; + if (subtitle != null) { + // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we + // advance to the next event. + long subtitleNextEventTimeUs = getNextEventTime(); + while (subtitleNextEventTimeUs <= positionUs) { + nextSubtitleEventIndex++; + subtitleNextEventTimeUs = getNextEventTime(); + textRendererNeedsUpdate = true; + } + } + + if (nextSubtitle != null) { + if (nextSubtitle.isEndOfStream()) { + if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { + if (subtitle != null) { + subtitle.release(); + subtitle = null; + } + nextSubtitle.release(); + nextSubtitle = null; + outputStreamEnded = true; + } + } else if (nextSubtitle.timeUs <= positionUs) { + // Advance to the next subtitle. Sync the next event index and trigger an update. + if (subtitle != null) { + subtitle.release(); + } + subtitle = nextSubtitle; + nextSubtitle = null; + nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs); + textRendererNeedsUpdate = true; + } + } + + if (textRendererNeedsUpdate) { + // textRendererNeedsUpdate is set and we're playing. Update the renderer. + updateOutput(subtitle.getCues(positionUs)); + } + + try { + while (!inputStreamEnded) { + if (nextInputBuffer == null) { + nextInputBuffer = decoder.dequeueInputBuffer(); + if (nextInputBuffer == null) { + return; + } + } + // Try and read the next subtitle from the source. + int result = readSource(formatHolder, nextInputBuffer); + if (result == C.RESULT_BUFFER_READ) { + // Clear BUFFER_FLAG_DECODE_ONLY (see [Internal: b/27893809]) and queue the buffer. + nextInputBuffer.clearFlag(C.BUFFER_FLAG_DECODE_ONLY); + if (nextInputBuffer.isEndOfStream()) { + inputStreamEnded = true; + } else { + nextInputBuffer.subsampleOffsetUs = formatHolder.format.subsampleOffsetUs; + nextInputBuffer.flip(); + } + decoder.queueInputBuffer(nextInputBuffer); + nextInputBuffer = null; + } else if (result == C.RESULT_NOTHING_READ) { + break; + } + } + } catch (SubtitleDecoderException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); + } + } + + @Override + protected void onDisabled() { + clearOutput(); + resetBuffers(); + decoder.release(); + decoder = null; + super.onDisabled(); + } + + @Override + public boolean isEnded() { + return outputStreamEnded; + } + + @Override + public boolean isReady() { + // Don't block playback whilst subtitles are loading. + // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. + return true; + } + + private void resetBuffers() { + nextInputBuffer = null; + nextSubtitleEventIndex = C.INDEX_UNSET; + if (subtitle != null) { + subtitle.release(); + subtitle = null; + } + if (nextSubtitle != null) { + nextSubtitle.release(); + nextSubtitle = null; + } + } + + private long getNextEventTime() { + return ((nextSubtitleEventIndex == C.INDEX_UNSET) + || (nextSubtitleEventIndex >= subtitle.getEventTimeCount())) ? Long.MAX_VALUE + : (subtitle.getEventTime(nextSubtitleEventIndex)); + } + + private void updateOutput(List cues) { + if (outputHandler != null) { + outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); + } else { + invokeUpdateOutputInternal(cues); + } + } + + private void clearOutput() { + updateOutput(Collections.emptyList()); + } + + @SuppressWarnings("unchecked") + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_OUTPUT: + invokeUpdateOutputInternal((List) msg.obj); + return true; + } + return false; + } + + private void invokeUpdateOutputInternal(List cues) { + output.onCues(cues); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/Cea608Decoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/Cea608Decoder.java new file mode 100755 index 00000000000..100e4e335fa --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/Cea608Decoder.java @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.cea; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.SubtitleInputBuffer; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608"). + */ +public final class Cea608Decoder extends CeaDecoder { + + private static final String TAG = "Cea608Decoder"; + + private static final int CC_VALID_FLAG = 0x04; + private static final int CC_TYPE_FLAG = 0x02; + private static final int CC_FIELD_FLAG = 0x01; + + private static final int NTSC_CC_FIELD_1 = 0x00; + private static final int NTSC_CC_FIELD_2 = 0x01; + private static final int CC_VALID_608_ID = 0x04; + + private static final int PAYLOAD_TYPE_CC = 4; + private static final int COUNTRY_CODE = 0xB5; + private static final int PROVIDER_CODE = 0x31; + private static final int USER_ID = 0x47413934; // "GA94" + private static final int USER_DATA_TYPE_CODE = 0x3; + + private static final int CC_MODE_UNKNOWN = 0; + private static final int CC_MODE_ROLL_UP = 1; + private static final int CC_MODE_POP_ON = 2; + private static final int CC_MODE_PAINT_ON = 3; + + // The default number of rows to display in roll-up captions mode. + private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; + + /** + * Command initiating pop-on style captioning. Subsequent data should be loaded into a + * non-displayed memory and held there until the {@link #CTRL_END_OF_CAPTION} command is received, + * at which point the non-displayed memory becomes the displayed memory (and vice versa). + */ + private static final byte CTRL_RESUME_CAPTION_LOADING = 0x20; + /** + * Command initiating roll-up style captioning, with the maximum of 2 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_2_ROWS = 0x25; + /** + * Command initiating roll-up style captioning, with the maximum of 3 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_3_ROWS = 0x26; + /** + * Command initiating roll-up style captioning, with the maximum of 4 rows displayed + * simultaneously. + */ + private static final byte CTRL_ROLL_UP_CAPTIONS_4_ROWS = 0x27; + /** + * Command initiating paint-on style captioning. Subsequent data should be addressed immediately + * to displayed memory without need for the {@link #CTRL_RESUME_CAPTION_LOADING} command. + */ + private static final byte CTRL_RESUME_DIRECT_CAPTIONING = 0x29; + /** + * Command indicating the end of a pop-on style caption. At this point the caption loaded in + * non-displayed memory should be swapped with the one in displayed memory. If no + * {@link #CTRL_RESUME_CAPTION_LOADING} command has been received, this command forces the + * receiver into pop-on style. + */ + private static final byte CTRL_END_OF_CAPTION = 0x2F; + + private static final byte CTRL_ERASE_DISPLAYED_MEMORY = 0x2C; + private static final byte CTRL_CARRIAGE_RETURN = 0x2D; + private static final byte CTRL_ERASE_NON_DISPLAYED_MEMORY = 0x2E; + + private static final byte CTRL_BACKSPACE = 0x21; + + private static final byte CTRL_MISC_CHAN_1 = 0x14; + private static final byte CTRL_MISC_CHAN_2 = 0x1C; + + // Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20). + private static final int[] BASIC_CHARACTER_SET = new int[] { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, // ! " # $ % & ' + 0x28, 0x29, // ( ) + 0xE1, // 2A: 225 'á' "Latin small letter A with acute" + 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, // + , - . / + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0 1 2 3 4 5 6 7 + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, // 8 9 : ; < = > ? + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, // @ A B C D E F G + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, // H I J K L M N O + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, // P Q R S T U V W + 0x58, 0x59, 0x5A, 0x5B, // X Y Z [ + 0xE9, // 5C: 233 'é' "Latin small letter E with acute" + 0x5D, // ] + 0xED, // 5E: 237 'í' "Latin small letter I with acute" + 0xF3, // 5F: 243 'ó' "Latin small letter O with acute" + 0xFA, // 60: 250 'ú' "Latin small letter U with acute" + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, // a b c d e f g + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, // h i j k l m n o + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, // p q r s t u v w + 0x78, 0x79, 0x7A, // x y z + 0xE7, // 7B: 231 'ç' "Latin small letter C with cedilla" + 0xF7, // 7C: 247 '÷' "Division sign" + 0xD1, // 7D: 209 'Ñ' "Latin capital letter N with tilde" + 0xF1, // 7E: 241 'ñ' "Latin small letter N with tilde" + 0x25A0 // 7F: "Black Square" (NB: 2588 = Full Block) + }; + + // Special North American 608 CC char set. + private static final int[] SPECIAL_CHARACTER_SET = new int[] { + 0xAE, // 30: 174 '®' "Registered Sign" - registered trademark symbol + 0xB0, // 31: 176 '°' "Degree Sign" + 0xBD, // 32: 189 '½' "Vulgar Fraction One Half" (1/2 symbol) + 0xBF, // 33: 191 '¿' "Inverted Question Mark" + 0x2122, // 34: "Trade Mark Sign" (tm superscript) + 0xA2, // 35: 162 '¢' "Cent Sign" + 0xA3, // 36: 163 '£' "Pound Sign" - pounds sterling + 0x266A, // 37: "Eighth Note" - music note + 0xE0, // 38: 224 'à' "Latin small letter A with grave" + 0x20, // 39: TRANSPARENT SPACE - for now use ordinary space + 0xE8, // 3A: 232 'è' "Latin small letter E with grave" + 0xE2, // 3B: 226 'â' "Latin small letter A with circumflex" + 0xEA, // 3C: 234 'ê' "Latin small letter E with circumflex" + 0xEE, // 3D: 238 'î' "Latin small letter I with circumflex" + 0xF4, // 3E: 244 'ô' "Latin small letter O with circumflex" + 0xFB // 3F: 251 'û' "Latin small letter U with circumflex" + }; + + // Extended Spanish/Miscellaneous and French char set. + private static final int[] SPECIAL_ES_FR_CHARACTER_SET = new int[] { + // Spanish and misc. + 0xC1, 0xC9, 0xD3, 0xDA, 0xDC, 0xFC, 0x2018, 0xA1, + 0x2A, 0x27, 0x2014, 0xA9, 0x2120, 0x2022, 0x201C, 0x201D, + // French. + 0xC0, 0xC2, 0xC7, 0xC8, 0xCA, 0xCB, 0xEB, 0xCE, + 0xCF, 0xEF, 0xD4, 0xD9, 0xF9, 0xDB, 0xAB, 0xBB + }; + + //Extended Portuguese and German/Danish char set. + private static final int[] SPECIAL_PT_DE_CHARACTER_SET = new int[] { + // Portuguese. + 0xC3, 0xE3, 0xCD, 0xCC, 0xEC, 0xD2, 0xF2, 0xD5, + 0xF5, 0x7B, 0x7D, 0x5C, 0x5E, 0x5F, 0x7C, 0x7E, + // German/Danish. + 0xC4, 0xE4, 0xD6, 0xF6, 0xDF, 0xA5, 0xA4, 0x2502, + 0xC5, 0xE5, 0xD8, 0xF8, 0x250C, 0x2510, 0x2514, 0x2518 + }; + + private final ParsableByteArray ccData; + + private final StringBuilder captionStringBuilder; + + private final int selectedField; + + private int captionMode; + private int captionRowCount; + private String captionString; + + private String lastCaptionString; + + private boolean repeatableControlSet; + private byte repeatableControlCc1; + private byte repeatableControlCc2; + + public Cea608Decoder(int accessibilityChannel) { + ccData = new ParsableByteArray(); + + captionStringBuilder = new StringBuilder(); + switch (accessibilityChannel) { + case 3: + case 4: + selectedField = 2; + break; + case 1: + case 2: + case Format.NO_VALUE: + default: + selectedField = 1; + } + + setCaptionMode(CC_MODE_UNKNOWN); + captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; + } + + @Override + public String getName() { + return "Cea608Decoder"; + } + + @Override + public void flush() { + super.flush(); + setCaptionMode(CC_MODE_UNKNOWN); + captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; + captionStringBuilder.setLength(0); + captionString = null; + lastCaptionString = null; + repeatableControlSet = false; + repeatableControlCc1 = 0; + repeatableControlCc2 = 0; + } + + @Override + public void release() { + // Do nothing + } + + @Override + protected boolean isNewSubtitleDataAvailable() { + return !TextUtils.equals(captionString, lastCaptionString); + } + + @Override + protected Subtitle createSubtitle() { + lastCaptionString = captionString; + return new CeaSubtitle(new Cue(captionString)); + } + + @Override + protected void decode(SubtitleInputBuffer inputBuffer) { + ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); + boolean captionDataProcessed = false; + boolean isRepeatableControl = false; + while (ccData.bytesLeft() > 0) { + byte ccDataHeader = (byte) ccData.readUnsignedByte(); + byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); + byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); + + // Only examine valid CEA-608 packets + if ((ccDataHeader & (CC_VALID_FLAG | CC_TYPE_FLAG)) != CC_VALID_608_ID) { + continue; + } + + // Only examine packets within the selected field + if ((selectedField == 1 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_1) + || (selectedField == 2 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_2)) { + continue; + } + + // Ignore empty captions. + if (ccData1 == 0 && ccData2 == 0) { + continue; + } + // If we've reached this point then there is data to process; flag that work has been done. + captionDataProcessed = true; + + // Special North American character set. + // ccData1 - P|0|0|1|C|0|0|1 + // ccData2 - P|0|1|1|X|X|X|X + if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) { + // TODO: Make use of the channel bit + captionStringBuilder.append(getSpecialChar(ccData2)); + continue; + } + + // Extended Western European character set. + // ccData1 - P|0|0|1|C|0|1|S + // ccData2 - P|0|1|X|X|X|X|X + if ((ccData2 & 0x60) == 0x20) { + // Extended Spanish/Miscellaneous and French character set (S = 0). + if (ccData1 == 0x12 || ccData1 == 0x1A) { + // TODO: Make use of the channel bit + backspace(); // Remove standard equivalent of the special extended char. + captionStringBuilder.append(getExtendedEsFrChar(ccData2)); + continue; + } + + // Extended Portuguese and German/Danish character set (S = 1). + if (ccData1 == 0x13 || ccData1 == 0x1B) { + // TODO: Make use of the channel bit + backspace(); // Remove standard equivalent of the special extended char. + captionStringBuilder.append(getExtendedPtDeChar(ccData2)); + continue; + } + } + + // Control character. + if (ccData1 < 0x20) { + isRepeatableControl = handleCtrl(ccData1, ccData2); + continue; + } + + // Basic North American character set. + captionStringBuilder.append(getChar(ccData1)); + if (ccData2 >= 0x20) { + captionStringBuilder.append(getChar(ccData2)); + } + } + + if (captionDataProcessed) { + if (!isRepeatableControl) { + repeatableControlSet = false; + } + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + captionString = getDisplayCaption(); + } + } + } + + private boolean handleCtrl(byte cc1, byte cc2) { + boolean isRepeatableControl = isRepeatable(cc1); + if (isRepeatableControl) { + if (repeatableControlSet + && repeatableControlCc1 == cc1 + && repeatableControlCc2 == cc2) { + repeatableControlSet = false; + return true; + } else { + repeatableControlSet = true; + repeatableControlCc1 = cc1; + repeatableControlCc2 = cc2; + } + } + if (isMiscCode(cc1, cc2)) { + handleMiscCode(cc2); + } else if (isPreambleAddressCode(cc1, cc2)) { + // TODO: Add better handling of this with specific positioning. + maybeAppendNewline(); + } + return isRepeatableControl; + } + + private void handleMiscCode(byte cc2) { + switch (cc2) { + case CTRL_ROLL_UP_CAPTIONS_2_ROWS: + captionRowCount = 2; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_ROLL_UP_CAPTIONS_3_ROWS: + captionRowCount = 3; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_ROLL_UP_CAPTIONS_4_ROWS: + captionRowCount = 4; + setCaptionMode(CC_MODE_ROLL_UP); + return; + case CTRL_RESUME_CAPTION_LOADING: + setCaptionMode(CC_MODE_POP_ON); + return; + case CTRL_RESUME_DIRECT_CAPTIONING: + setCaptionMode(CC_MODE_PAINT_ON); + return; + } + + if (captionMode == CC_MODE_UNKNOWN) { + return; + } + + switch (cc2) { + case CTRL_ERASE_DISPLAYED_MEMORY: + captionString = null; + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_PAINT_ON) { + captionStringBuilder.setLength(0); + } + break; + case CTRL_ERASE_NON_DISPLAYED_MEMORY: + captionStringBuilder.setLength(0); + break; + case CTRL_END_OF_CAPTION: + captionString = getDisplayCaption(); + captionStringBuilder.setLength(0); + break; + case CTRL_CARRIAGE_RETURN: + maybeAppendNewline(); + break; + case CTRL_BACKSPACE: + if (captionStringBuilder.length() > 0) { + captionStringBuilder.setLength(captionStringBuilder.length() - 1); + } + break; + } + } + + private void backspace() { + if (captionStringBuilder.length() > 0) { + captionStringBuilder.setLength(captionStringBuilder.length() - 1); + } + } + + private void maybeAppendNewline() { + int buildLength = captionStringBuilder.length(); + if (buildLength > 0 && captionStringBuilder.charAt(buildLength - 1) != '\n') { + captionStringBuilder.append('\n'); + } + } + + private String getDisplayCaption() { + int buildLength = captionStringBuilder.length(); + if (buildLength == 0) { + return null; + } + + boolean endsWithNewline = captionStringBuilder.charAt(buildLength - 1) == '\n'; + if (buildLength == 1 && endsWithNewline) { + return null; + } + + int endIndex = endsWithNewline ? buildLength - 1 : buildLength; + if (captionMode != CC_MODE_ROLL_UP) { + return captionStringBuilder.substring(0, endIndex); + } + + int startIndex = 0; + int searchBackwardFromIndex = endIndex; + for (int i = 0; i < captionRowCount && searchBackwardFromIndex != -1; i++) { + searchBackwardFromIndex = captionStringBuilder.lastIndexOf("\n", searchBackwardFromIndex - 1); + } + if (searchBackwardFromIndex != -1) { + startIndex = searchBackwardFromIndex + 1; + } + captionStringBuilder.delete(0, startIndex); + return captionStringBuilder.substring(0, endIndex - startIndex); + } + + private void setCaptionMode(int captionMode) { + if (this.captionMode == captionMode) { + return; + } + + this.captionMode = captionMode; + // Clear the working memory. + captionStringBuilder.setLength(0); + if (captionMode == CC_MODE_ROLL_UP || captionMode == CC_MODE_UNKNOWN) { + // When switching to roll-up or unknown, we also need to clear the caption. + captionString = null; + } + } + + private static char getChar(byte ccData) { + int index = (ccData & 0x7F) - 0x20; + return (char) BASIC_CHARACTER_SET[index]; + } + + private static char getSpecialChar(byte ccData) { + int index = ccData & 0xF; + return (char) SPECIAL_CHARACTER_SET[index]; + } + + private static char getExtendedEsFrChar(byte ccData) { + int index = ccData & 0x1F; + return (char) SPECIAL_ES_FR_CHARACTER_SET[index]; + } + + private static char getExtendedPtDeChar(byte ccData) { + int index = ccData & 0x1F; + return (char) SPECIAL_PT_DE_CHARACTER_SET[index]; + } + + private static boolean isMiscCode(byte cc1, byte cc2) { + return (cc1 == CTRL_MISC_CHAN_1 || cc1 == CTRL_MISC_CHAN_2) + && (cc2 >= 0x20 && cc2 <= 0x2F); + } + + private static boolean isPreambleAddressCode(byte cc1, byte cc2) { + return (cc1 >= 0x10 && cc1 <= 0x1F) && (cc2 >= 0x40 && cc2 <= 0x7F); + } + + private static boolean isRepeatable(byte cc1) { + return cc1 >= 0x10 && cc1 <= 0x1F; + } + + /** + * Inspects an sei message to determine whether it contains CEA-608. + *

        + * The position of {@code payload} is left unchanged. + * + * @param payloadType The payload type of the message. + * @param payloadLength The length of the payload. + * @param payload A {@link ParsableByteArray} containing the payload. + * @return Whether the sei message contains CEA-608. + */ + public static boolean isSeiMessageCea608(int payloadType, int payloadLength, + ParsableByteArray payload) { + if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { + return false; + } + int startPosition = payload.getPosition(); + int countryCode = payload.readUnsignedByte(); + int providerCode = payload.readUnsignedShort(); + int userIdentifier = payload.readInt(); + int userDataTypeCode = payload.readUnsignedByte(); + payload.setPosition(startPosition); + return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE + && userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java new file mode 100755 index 00000000000..34b4d6a545e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaDecoder.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.cea; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.text.SubtitleInputBuffer; +import org.telegram.messenger.exoplayer2.text.SubtitleOutputBuffer; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.util.LinkedList; +import java.util.TreeSet; + +/** + * Base class for subtitle parsers for CEA captions. + */ +/* package */ abstract class CeaDecoder implements SubtitleDecoder { + + private static final int NUM_INPUT_BUFFERS = 10; + private static final int NUM_OUTPUT_BUFFERS = 2; + + private final LinkedList availableInputBuffers; + private final LinkedList availableOutputBuffers; + private final TreeSet queuedInputBuffers; + + private SubtitleInputBuffer dequeuedInputBuffer; + private long playbackPositionUs; + + public CeaDecoder() { + availableInputBuffers = new LinkedList<>(); + for (int i = 0; i < NUM_INPUT_BUFFERS; i++) { + availableInputBuffers.add(new SubtitleInputBuffer()); + } + availableOutputBuffers = new LinkedList<>(); + for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) { + availableOutputBuffers.add(new CeaOutputBuffer(this)); + } + queuedInputBuffers = new TreeSet<>(); + } + + @Override + public abstract String getName(); + + @Override + public void setPositionUs(long positionUs) { + playbackPositionUs = positionUs; + } + + @Override + public SubtitleInputBuffer dequeueInputBuffer() throws SubtitleDecoderException { + Assertions.checkState(dequeuedInputBuffer == null); + if (availableInputBuffers.isEmpty()) { + return null; + } + dequeuedInputBuffer = availableInputBuffers.pollFirst(); + return dequeuedInputBuffer; + } + + @Override + public void queueInputBuffer(SubtitleInputBuffer inputBuffer) throws SubtitleDecoderException { + Assertions.checkArgument(inputBuffer != null); + Assertions.checkArgument(inputBuffer == dequeuedInputBuffer); + queuedInputBuffers.add(inputBuffer); + dequeuedInputBuffer = null; + } + + @Override + public SubtitleOutputBuffer dequeueOutputBuffer() throws SubtitleDecoderException { + if (availableOutputBuffers.isEmpty()) { + return null; + } + + // iterate through all available input buffers whose timestamps are less than or equal + // to the current playback position; processing input buffers for future content should + // be deferred until they would be applicable + while (!queuedInputBuffers.isEmpty() + && queuedInputBuffers.first().timeUs <= playbackPositionUs) { + SubtitleInputBuffer inputBuffer = queuedInputBuffers.pollFirst(); + + // If the input buffer indicates we've reached the end of the stream, we can + // return immediately with an output buffer propagating that + if (inputBuffer.isEndOfStream()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM); + releaseInputBuffer(inputBuffer); + return outputBuffer; + } + + decode(inputBuffer); + + // check if we have any caption updates to report + if (isNewSubtitleDataAvailable()) { + // Even if the subtitle is decode-only; we need to generate it to consume the data so it + // isn't accidentally prepended to the next subtitle + Subtitle subtitle = createSubtitle(); + if (!inputBuffer.isDecodeOnly()) { + SubtitleOutputBuffer outputBuffer = availableOutputBuffers.pollFirst(); + outputBuffer.setContent(inputBuffer.timeUs, subtitle, 0); + releaseInputBuffer(inputBuffer); + return outputBuffer; + } + } + + releaseInputBuffer(inputBuffer); + } + + return null; + } + + private void releaseInputBuffer(SubtitleInputBuffer inputBuffer) { + inputBuffer.clear(); + availableInputBuffers.add(inputBuffer); + } + + protected void releaseOutputBuffer(SubtitleOutputBuffer outputBuffer) { + outputBuffer.clear(); + availableOutputBuffers.add(outputBuffer); + } + + @Override + public void flush() { + playbackPositionUs = 0; + while (!queuedInputBuffers.isEmpty()) { + releaseInputBuffer(queuedInputBuffers.pollFirst()); + } + if (dequeuedInputBuffer != null) { + releaseInputBuffer(dequeuedInputBuffer); + dequeuedInputBuffer = null; + } + } + + @Override + public void release() { + // Do nothing + } + + /** + * Returns whether there is data available to create a new {@link Subtitle}. + */ + protected abstract boolean isNewSubtitleDataAvailable(); + + /** + * Creates a {@link Subtitle} from the available data. + */ + protected abstract Subtitle createSubtitle(); + + /** + * Filters and processes the raw data, providing {@link Subtitle}s via {@link #createSubtitle()} + * when sufficient data has been processed. + */ + protected abstract void decode(SubtitleInputBuffer inputBuffer); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaOutputBuffer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaOutputBuffer.java new file mode 100755 index 00000000000..23ce566c6f0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaOutputBuffer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.cea; + +import org.telegram.messenger.exoplayer2.text.SubtitleOutputBuffer; + +/** + * A {@link SubtitleOutputBuffer} for {@link CeaDecoder}s. + */ +public final class CeaOutputBuffer extends SubtitleOutputBuffer { + + private final CeaDecoder owner; + + /** + * @param owner The decoder that owns this buffer. + */ + public CeaOutputBuffer(CeaDecoder owner) { + super(); + this.owner = owner; + } + + @Override + public final void release() { + owner.releaseOutputBuffer(this); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaSubtitle.java new file mode 100755 index 00000000000..b189c240cca --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/cea/CeaSubtitle.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.cea; + +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import java.util.Collections; +import java.util.List; + +/** + * A representation of a CEA subtitle. + */ +/* package */ final class CeaSubtitle implements Subtitle { + + private final List cues; + + /** + * @param cue The subtitle cue. + */ + public CeaSubtitle(Cue cue) { + if (cue == null) { + cues = Collections.emptyList(); + } else { + cues = Collections.singletonList(cue); + } + } + + @Override + public int getNextEventTimeIndex(long timeUs) { + return 0; + } + + @Override + public int getEventTimeCount() { + return 1; + } + + @Override + public long getEventTime(int index) { + return 0; + } + + @Override + public List getCues(long timeUs) { + return cues; + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java new file mode 100755 index 00000000000..b5ca9dfe85f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripDecoder.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.subrip; + +import android.text.Html; +import android.text.Spanned; +import android.text.TextUtils; +import android.util.Log; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.util.LongArray; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A {@link SimpleSubtitleDecoder} for SubRip. + */ +public final class SubripDecoder extends SimpleSubtitleDecoder { + + private static final String TAG = "SubripDecoder"; + + private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("(\\S*)\\s*-->\\s*(\\S*)"); + private static final Pattern SUBRIP_TIMESTAMP = + Pattern.compile("(?:(\\d+):)?(\\d+):(\\d+),(\\d+)"); + + private final StringBuilder textBuilder; + + public SubripDecoder() { + super("SubripDecoder"); + textBuilder = new StringBuilder(); + } + + @Override + protected SubripSubtitle decode(byte[] bytes, int length) { + ArrayList cues = new ArrayList<>(); + LongArray cueTimesUs = new LongArray(); + ParsableByteArray subripData = new ParsableByteArray(bytes, length); + boolean haveEndTimecode; + String currentLine; + + while ((currentLine = subripData.readLine()) != null) { + if (currentLine.length() == 0) { + // Skip blank lines. + continue; + } + + // Parse the index line as a sanity check. + try { + Integer.parseInt(currentLine); + } catch (NumberFormatException e) { + Log.w(TAG, "Skipping invalid index: " + currentLine); + continue; + } + + // Read and parse the timing line. + haveEndTimecode = false; + currentLine = subripData.readLine(); + Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine); + if (matcher.find()) { + cueTimesUs.add(parseTimecode(matcher.group(1))); + String endTimecode = matcher.group(2); + if (!TextUtils.isEmpty(endTimecode)) { + haveEndTimecode = true; + cueTimesUs.add(parseTimecode(matcher.group(2))); + } + } else { + Log.w(TAG, "Skipping invalid timing: " + currentLine); + continue; + } + + // Read and parse the text. + textBuilder.setLength(0); + while (!TextUtils.isEmpty(currentLine = subripData.readLine())) { + if (textBuilder.length() > 0) { + textBuilder.append("
        "); + } + textBuilder.append(currentLine.trim()); + } + + Spanned text = Html.fromHtml(textBuilder.toString()); + cues.add(new Cue(text)); + if (haveEndTimecode) { + cues.add(null); + } + } + + Cue[] cuesArray = new Cue[cues.size()]; + cues.toArray(cuesArray); + long[] cueTimesUsArray = cueTimesUs.toArray(); + return new SubripSubtitle(cuesArray, cueTimesUsArray); + } + + private static long parseTimecode(String s) throws NumberFormatException { + Matcher matcher = SUBRIP_TIMESTAMP.matcher(s); + if (!matcher.matches()) { + throw new NumberFormatException("has invalid format"); + } + long timestampMs = Long.parseLong(matcher.group(1)) * 60 * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(2)) * 60 * 1000; + timestampMs += Long.parseLong(matcher.group(3)) * 1000; + timestampMs += Long.parseLong(matcher.group(4)); + return timestampMs * 1000; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripSubtitle.java similarity index 76% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripSubtitle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripSubtitle.java index bcfe5346a0c..b43a28f0a2c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/subrip/SubripSubtitle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/subrip/SubripSubtitle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.subrip; +package org.telegram.messenger.exoplayer2.text.subrip; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.Collections; import java.util.List; @@ -42,7 +43,7 @@ public SubripSubtitle(Cue[] cues, long[] cueTimesUs) { @Override public int getNextEventTimeIndex(long timeUs) { int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false); - return index < cueTimesUs.length ? index : -1; + return index < cueTimesUs.length ? index : C.INDEX_UNSET; } @Override @@ -57,20 +58,12 @@ public long getEventTime(int index) { return cueTimesUs[index]; } - @Override - public long getLastEventTime() { - if (getEventTimeCount() == 0) { - return -1; - } - return cueTimesUs[cueTimesUs.length - 1]; - } - @Override public List getCues(long timeUs) { int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false); if (index == -1 || cues[index] == null) { // timeUs is earlier than the start of the first cue, or we have an empty cue. - return Collections.emptyList(); + return Collections.emptyList(); } else { return Collections.singletonList(cues[index]); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java new file mode 100755 index 00000000000..4eff4831c5c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlDecoder.java @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.ttml; + +import android.text.Layout; +import android.util.Log; +import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.util.ColorParser; +import org.telegram.messenger.exoplayer2.util.Util; +import org.telegram.messenger.exoplayer2.util.XmlPullParserUtil; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +/** + * A {@link SimpleSubtitleDecoder} for TTML supporting the DFXP presentation profile. Features + * supported by this decoder are: + *

          + *
        • content + *
        • core + *
        • presentation + *
        • profile + *
        • structure + *
        • time-offset + *
        • timing + *
        • tickRate + *
        • time-clock-with-frames + *
        • time-clock + *
        • time-offset-with-frames + *
        • time-offset-with-ticks + *
        + * @see TTML specification + */ +public final class TtmlDecoder extends SimpleSubtitleDecoder { + + private static final String TAG = "TtmlDecoder"; + + private static final String TTP = "http://www.w3.org/ns/ttml#parameter"; + + private static final String ATTR_BEGIN = "begin"; + private static final String ATTR_DURATION = "dur"; + private static final String ATTR_END = "end"; + private static final String ATTR_STYLE = "style"; + private static final String ATTR_REGION = "region"; + + private static final Pattern CLOCK_TIME = + Pattern.compile("^([0-9][0-9]+):([0-9][0-9]):([0-9][0-9])" + + "(?:(\\.[0-9]+)|:([0-9][0-9])(?:\\.([0-9]+))?)?$"); + private static final Pattern OFFSET_TIME = + Pattern.compile("^([0-9]+(?:\\.[0-9]+)?)(h|m|s|ms|f|t)$"); + private static final Pattern FONT_SIZE = Pattern.compile("^(([0-9]*.)?[0-9]+)(px|em|%)$"); + private static final Pattern PERCENTAGE_COORDINATES = + Pattern.compile("^(\\d+\\.?\\d*?)% (\\d+\\.?\\d*?)%$"); + + private static final int DEFAULT_FRAME_RATE = 30; + + private static final FrameAndTickRate DEFAULT_FRAME_AND_TICK_RATE = + new FrameAndTickRate(DEFAULT_FRAME_RATE, 1, 1); + + private final XmlPullParserFactory xmlParserFactory; + + public TtmlDecoder() { + super("TtmlDecoder"); + try { + xmlParserFactory = XmlPullParserFactory.newInstance(); + xmlParserFactory.setNamespaceAware(true); + } catch (XmlPullParserException e) { + throw new RuntimeException("Couldn't create XmlPullParserFactory instance", e); + } + } + + @Override + protected TtmlSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + try { + XmlPullParser xmlParser = xmlParserFactory.newPullParser(); + Map globalStyles = new HashMap<>(); + Map regionMap = new HashMap<>(); + regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion()); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); + xmlParser.setInput(inputStream, null); + TtmlSubtitle ttmlSubtitle = null; + LinkedList nodeStack = new LinkedList<>(); + int unsupportedNodeDepth = 0; + int eventType = xmlParser.getEventType(); + FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE; + while (eventType != XmlPullParser.END_DOCUMENT) { + TtmlNode parent = nodeStack.peekLast(); + if (unsupportedNodeDepth == 0) { + String name = xmlParser.getName(); + if (eventType == XmlPullParser.START_TAG) { + if (TtmlNode.TAG_TT.equals(name)) { + frameAndTickRate = parseFrameAndTickRates(xmlParser); + } + if (!isSupportedTag(name)) { + Log.i(TAG, "Ignoring unsupported tag: " + xmlParser.getName()); + unsupportedNodeDepth++; + } else if (TtmlNode.TAG_HEAD.equals(name)) { + parseHeader(xmlParser, globalStyles, regionMap); + } else { + try { + TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate); + nodeStack.addLast(node); + if (parent != null) { + parent.addChild(node); + } + } catch (SubtitleDecoderException e) { + Log.w(TAG, "Suppressing parser error", e); + // Treat the node (and by extension, all of its children) as unsupported. + unsupportedNodeDepth++; + } + } + } else if (eventType == XmlPullParser.TEXT) { + parent.addChild(TtmlNode.buildTextNode(xmlParser.getText())); + } else if (eventType == XmlPullParser.END_TAG) { + if (xmlParser.getName().equals(TtmlNode.TAG_TT)) { + ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap); + } + nodeStack.removeLast(); + } + } else { + if (eventType == XmlPullParser.START_TAG) { + unsupportedNodeDepth++; + } else if (eventType == XmlPullParser.END_TAG) { + unsupportedNodeDepth--; + } + } + xmlParser.next(); + eventType = xmlParser.getEventType(); + } + return ttmlSubtitle; + } catch (XmlPullParserException xppe) { + throw new SubtitleDecoderException("Unable to decode source", xppe); + } catch (IOException e) { + throw new IllegalStateException("Unexpected error when reading input.", e); + } + } + + private FrameAndTickRate parseFrameAndTickRates(XmlPullParser xmlParser) + throws SubtitleDecoderException { + int frameRate = DEFAULT_FRAME_RATE; + String frameRateString = xmlParser.getAttributeValue(TTP, "frameRate"); + if (frameRateString != null) { + frameRate = Integer.parseInt(frameRateString); + } + + float frameRateMultiplier = 1; + String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier"); + if (frameRateMultiplierString != null) { + String[] parts = frameRateMultiplierString.split(" "); + if (parts.length != 2) { + throw new SubtitleDecoderException("frameRateMultiplier doesn't have 2 parts"); + } + float numerator = Integer.parseInt(parts[0]); + float denominator = Integer.parseInt(parts[1]); + frameRateMultiplier = numerator / denominator; + } + + int subFrameRate = DEFAULT_FRAME_AND_TICK_RATE.subFrameRate; + String subFrameRateString = xmlParser.getAttributeValue(TTP, "subFrameRate"); + if (subFrameRateString != null) { + subFrameRate = Integer.parseInt(subFrameRateString); + } + + int tickRate = DEFAULT_FRAME_AND_TICK_RATE.tickRate; + String tickRateString = xmlParser.getAttributeValue(TTP, "tickRate"); + if (tickRateString != null) { + tickRate = Integer.parseInt(tickRateString); + } + return new FrameAndTickRate(frameRate * frameRateMultiplier, subFrameRate, tickRate); + } + + private Map parseHeader(XmlPullParser xmlParser, + Map globalStyles, Map globalRegions) + throws IOException, XmlPullParserException { + do { + xmlParser.next(); + if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_STYLE)) { + String parentStyleId = XmlPullParserUtil.getAttributeValue(xmlParser, ATTR_STYLE); + TtmlStyle style = parseStyleAttributes(xmlParser, new TtmlStyle()); + if (parentStyleId != null) { + for (String id : parseStyleIds(parentStyleId)) { + style.chain(globalStyles.get(id)); + } + } + if (style.getId() != null) { + globalStyles.put(style.getId(), style); + } + } else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) { + Pair ttmlRegionInfo = parseRegionAttributes(xmlParser); + if (ttmlRegionInfo != null) { + globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second); + } + } + } while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD)); + return globalStyles; + } + + /** + * Parses a region declaration. Supports origin and extent definition but only when defined in + * terms of percentage of the viewport. Regions that do not correctly declare origin are ignored. + */ + private Pair parseRegionAttributes(XmlPullParser xmlParser) { + String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID); + String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN); + String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT); + if (regionOrigin == null || regionId == null) { + return null; + } + float position = Cue.DIMEN_UNSET; + float line = Cue.DIMEN_UNSET; + Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin); + if (originMatcher.matches()) { + try { + position = Float.parseFloat(originMatcher.group(1)) / 100.f; + line = Float.parseFloat(originMatcher.group(2)) / 100.f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e); + position = Cue.DIMEN_UNSET; + } + } + float width = Cue.DIMEN_UNSET; + if (regionExtent != null) { + Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent); + if (extentMatcher.matches()) { + try { + width = Float.parseFloat(extentMatcher.group(1)) / 100.f; + } catch (NumberFormatException e) { + Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e); + } + } + } + return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line, + Cue.LINE_TYPE_FRACTION, width)) : null; + } + + private String[] parseStyleIds(String parentStyleIds) { + return parentStyleIds.split("\\s+"); + } + + private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) { + int attributeCount = parser.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + String attributeValue = parser.getAttributeValue(i); + switch (parser.getAttributeName(i)) { + case TtmlNode.ATTR_ID: + if (TtmlNode.TAG_STYLE.equals(parser.getName())) { + style = createIfNull(style).setId(attributeValue); + } + break; + case TtmlNode.ATTR_TTS_BACKGROUND_COLOR: + style = createIfNull(style); + try { + style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue)); + } catch (IllegalArgumentException e) { + Log.w(TAG, "failed parsing background value: '" + attributeValue + "'"); + } + break; + case TtmlNode.ATTR_TTS_COLOR: + style = createIfNull(style); + try { + style.setFontColor(ColorParser.parseTtmlColor(attributeValue)); + } catch (IllegalArgumentException e) { + Log.w(TAG, "failed parsing color value: '" + attributeValue + "'"); + } + break; + case TtmlNode.ATTR_TTS_FONT_FAMILY: + style = createIfNull(style).setFontFamily(attributeValue); + break; + case TtmlNode.ATTR_TTS_FONT_SIZE: + try { + style = createIfNull(style); + parseFontSize(attributeValue, style); + } catch (SubtitleDecoderException e) { + Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'"); + } + break; + case TtmlNode.ATTR_TTS_FONT_WEIGHT: + style = createIfNull(style).setBold( + TtmlNode.BOLD.equalsIgnoreCase(attributeValue)); + break; + case TtmlNode.ATTR_TTS_FONT_STYLE: + style = createIfNull(style).setItalic( + TtmlNode.ITALIC.equalsIgnoreCase(attributeValue)); + break; + case TtmlNode.ATTR_TTS_TEXT_ALIGN: + switch (Util.toLowerInvariant(attributeValue)) { + case TtmlNode.LEFT: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); + break; + case TtmlNode.START: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_NORMAL); + break; + case TtmlNode.RIGHT: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); + break; + case TtmlNode.END: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_OPPOSITE); + break; + case TtmlNode.CENTER: + style = createIfNull(style).setTextAlign(Layout.Alignment.ALIGN_CENTER); + break; + } + break; + case TtmlNode.ATTR_TTS_TEXT_DECORATION: + switch (Util.toLowerInvariant(attributeValue)) { + case TtmlNode.LINETHROUGH: + style = createIfNull(style).setLinethrough(true); + break; + case TtmlNode.NO_LINETHROUGH: + style = createIfNull(style).setLinethrough(false); + break; + case TtmlNode.UNDERLINE: + style = createIfNull(style).setUnderline(true); + break; + case TtmlNode.NO_UNDERLINE: + style = createIfNull(style).setUnderline(false); + break; + } + break; + default: + // ignore + break; + } + } + return style; + } + + private TtmlStyle createIfNull(TtmlStyle style) { + return style == null ? new TtmlStyle() : style; + } + + private TtmlNode parseNode(XmlPullParser parser, TtmlNode parent, + Map regionMap, FrameAndTickRate frameAndTickRate) + throws SubtitleDecoderException { + long duration = C.TIME_UNSET; + long startTime = C.TIME_UNSET; + long endTime = C.TIME_UNSET; + String regionId = TtmlNode.ANONYMOUS_REGION_ID; + String[] styleIds = null; + int attributeCount = parser.getAttributeCount(); + TtmlStyle style = parseStyleAttributes(parser, null); + for (int i = 0; i < attributeCount; i++) { + String attr = parser.getAttributeName(i); + String value = parser.getAttributeValue(i); + switch (attr) { + case ATTR_BEGIN: + startTime = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_END: + endTime = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_DURATION: + duration = parseTimeExpression(value, frameAndTickRate); + break; + case ATTR_STYLE: + // IDREFS: potentially multiple space delimited ids + String[] ids = parseStyleIds(value); + if (ids.length > 0) { + styleIds = ids; + } + break; + case ATTR_REGION: + if (regionMap.containsKey(value)) { + // If the region has not been correctly declared or does not define a position, we use + // the anonymous region. + regionId = value; + } + break; + default: + // Do nothing. + break; + } + } + if (parent != null && parent.startTimeUs != C.TIME_UNSET) { + if (startTime != C.TIME_UNSET) { + startTime += parent.startTimeUs; + } + if (endTime != C.TIME_UNSET) { + endTime += parent.startTimeUs; + } + } + if (endTime == C.TIME_UNSET) { + if (duration != C.TIME_UNSET) { + // Infer the end time from the duration. + endTime = startTime + duration; + } else if (parent != null && parent.endTimeUs != C.TIME_UNSET) { + // If the end time remains unspecified, then it should be inherited from the parent. + endTime = parent.endTimeUs; + } + } + return TtmlNode.buildNode(parser.getName(), startTime, endTime, style, styleIds, regionId); + } + + private static boolean isSupportedTag(String tag) { + return tag.equals(TtmlNode.TAG_TT) + || tag.equals(TtmlNode.TAG_HEAD) + || tag.equals(TtmlNode.TAG_BODY) + || tag.equals(TtmlNode.TAG_DIV) + || tag.equals(TtmlNode.TAG_P) + || tag.equals(TtmlNode.TAG_SPAN) + || tag.equals(TtmlNode.TAG_BR) + || tag.equals(TtmlNode.TAG_STYLE) + || tag.equals(TtmlNode.TAG_STYLING) + || tag.equals(TtmlNode.TAG_LAYOUT) + || tag.equals(TtmlNode.TAG_REGION) + || tag.equals(TtmlNode.TAG_METADATA) + || tag.equals(TtmlNode.TAG_SMPTE_IMAGE) + || tag.equals(TtmlNode.TAG_SMPTE_DATA) + || tag.equals(TtmlNode.TAG_SMPTE_INFORMATION); + } + + private static void parseFontSize(String expression, TtmlStyle out) throws + SubtitleDecoderException { + String[] expressions = expression.split("\\s+"); + Matcher matcher; + if (expressions.length == 1) { + matcher = FONT_SIZE.matcher(expression); + } else if (expressions.length == 2){ + matcher = FONT_SIZE.matcher(expressions[1]); + Log.w(TAG, "Multiple values in fontSize attribute. Picking the second value for vertical font" + + " size and ignoring the first."); + } else { + throw new SubtitleDecoderException("Invalid number of entries for fontSize: " + + expressions.length + "."); + } + + if (matcher.matches()) { + String unit = matcher.group(3); + switch (unit) { + case "px": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PIXEL); + break; + case "em": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_EM); + break; + case "%": + out.setFontSizeUnit(TtmlStyle.FONT_SIZE_UNIT_PERCENT); + break; + default: + throw new SubtitleDecoderException("Invalid unit for fontSize: '" + unit + "'."); + } + out.setFontSize(Float.valueOf(matcher.group(1))); + } else { + throw new SubtitleDecoderException("Invalid expression for fontSize: '" + expression + "'."); + } + } + + /** + * Parses a time expression, returning the parsed timestamp. + *

        + * For the format of a time expression, see: + * timeExpression + * + * @param time A string that includes the time expression. + * @param frameAndTickRate The effective frame and tick rates of the stream. + * @return The parsed timestamp in microseconds. + * @throws SubtitleDecoderException If the given string does not contain a valid time expression. + */ + private static long parseTimeExpression(String time, FrameAndTickRate frameAndTickRate) + throws SubtitleDecoderException { + Matcher matcher = CLOCK_TIME.matcher(time); + if (matcher.matches()) { + String hours = matcher.group(1); + double durationSeconds = Long.parseLong(hours) * 3600; + String minutes = matcher.group(2); + durationSeconds += Long.parseLong(minutes) * 60; + String seconds = matcher.group(3); + durationSeconds += Long.parseLong(seconds); + String fraction = matcher.group(4); + durationSeconds += (fraction != null) ? Double.parseDouble(fraction) : 0; + String frames = matcher.group(5); + durationSeconds += (frames != null) + ? Long.parseLong(frames) / frameAndTickRate.effectiveFrameRate : 0; + String subframes = matcher.group(6); + durationSeconds += (subframes != null) + ? ((double) Long.parseLong(subframes)) / frameAndTickRate.subFrameRate + / frameAndTickRate.effectiveFrameRate + : 0; + return (long) (durationSeconds * C.MICROS_PER_SECOND); + } + matcher = OFFSET_TIME.matcher(time); + if (matcher.matches()) { + String timeValue = matcher.group(1); + double offsetSeconds = Double.parseDouble(timeValue); + String unit = matcher.group(2); + switch (unit) { + case "h": + offsetSeconds *= 3600; + break; + case "m": + offsetSeconds *= 60; + break; + case "s": + // Do nothing. + break; + case "ms": + offsetSeconds /= 1000; + break; + case "f": + offsetSeconds /= frameAndTickRate.effectiveFrameRate; + break; + case "t": + offsetSeconds /= frameAndTickRate.tickRate; + break; + } + return (long) (offsetSeconds * C.MICROS_PER_SECOND); + } + throw new SubtitleDecoderException("Malformed time expression: " + time); + } + + private static final class FrameAndTickRate { + final float effectiveFrameRate; + final int subFrameRate; + final int tickRate; + + FrameAndTickRate(float effectiveFrameRate, int subFrameRate, int tickRate) { + this.effectiveFrameRate = effectiveFrameRate; + this.subFrameRate = subFrameRate; + this.tickRate = tickRate; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlNode.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java similarity index 94% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlNode.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java index 75752bbd17e..fd33b0ff5d9 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlNode.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.ttml; +package org.telegram.messenger.exoplayer2.text.ttml; import android.text.SpannableStringBuilder; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -31,7 +32,6 @@ */ /* package */ final class TtmlNode { - public static final long UNDEFINED_TIME = -1; public static final String TAG_TT = "tt"; public static final String TAG_HEAD = "head"; public static final String TAG_BODY = "body"; @@ -89,8 +89,8 @@ private List children; public static TtmlNode buildTextNode(String text) { - return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), UNDEFINED_TIME, - UNDEFINED_TIME, null, null, ANONYMOUS_REGION_ID); + return new TtmlNode(null, TtmlRenderUtil.applyTextElementSpacePolicy(text), C.TIME_UNSET, + C.TIME_UNSET, null, null, ANONYMOUS_REGION_ID); } public static TtmlNode buildNode(String tag, long startTimeUs, long endTimeUs, @@ -113,9 +113,9 @@ private TtmlNode(String tag, String text, long startTimeUs, long endTimeUs, } public boolean isActive(long timeUs) { - return (startTimeUs == UNDEFINED_TIME && endTimeUs == UNDEFINED_TIME) - || (startTimeUs <= timeUs && endTimeUs == UNDEFINED_TIME) - || (startTimeUs == UNDEFINED_TIME && timeUs < endTimeUs) + return (startTimeUs == C.TIME_UNSET && endTimeUs == C.TIME_UNSET) + || (startTimeUs <= timeUs && endTimeUs == C.TIME_UNSET) + || (startTimeUs == C.TIME_UNSET && timeUs < endTimeUs) || (startTimeUs <= timeUs && timeUs < endTimeUs); } @@ -151,10 +151,10 @@ public long[] getEventTimesUs() { private void getEventTimes(TreeSet out, boolean descendsPNode) { boolean isPNode = TAG_P.equals(tag); if (descendsPNode || isPNode) { - if (startTimeUs != UNDEFINED_TIME) { + if (startTimeUs != C.TIME_UNSET) { out.add(startTimeUs); } - if (endTimeUs != UNDEFINED_TIME) { + if (endTimeUs != C.TIME_UNSET) { out.add(endTimeUs); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRegion.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java similarity index 83% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRegion.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java index 368fa33c81b..cc7f6067f4d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRegion.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRegion.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.ttml; +package org.telegram.messenger.exoplayer2.text.ttml; -import org.telegram.messenger.exoplayer.text.Cue; +import org.telegram.messenger.exoplayer2.text.Cue; /** * Represents a TTML Region. @@ -24,6 +24,7 @@ public final float position; public final float line; + @Cue.LineType public final int lineType; public final float width; @@ -31,7 +32,7 @@ public TtmlRegion() { this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET); } - public TtmlRegion(float position, float line, int lineType, float width) { + public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) { this.position = position; this.line = line; this.lineType = lineType; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRenderUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRenderUtil.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRenderUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRenderUtil.java index 177448adb1e..e8fb8114075 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlRenderUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlRenderUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.ttml; +package org.telegram.messenger.exoplayer2.text.ttml; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -92,26 +92,27 @@ public static void applyStylesToSpan(SpannableStringBuilder builder, builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.getFontSizeUnit() != TtmlStyle.UNSPECIFIED) { - switch (style.getFontSizeUnit()) { - case TtmlStyle.FONT_SIZE_UNIT_PIXEL: - builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case TtmlStyle.FONT_SIZE_UNIT_EM: - builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case TtmlStyle.FONT_SIZE_UNIT_PERCENT: - builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - } + switch (style.getFontSizeUnit()) { + case TtmlStyle.FONT_SIZE_UNIT_PIXEL: + builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_EM: + builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_PERCENT: + builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.UNSPECIFIED: + // Do nothing. + break; } } /** - * Invoked when the end of a paragraph is encountered. Adds a newline if there are one or more + * Called when the end of a paragraph is encountered. Adds a newline if there are one or more * non-space characters since the previous newline. * * @param builder The builder. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlStyle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlStyle.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlStyle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlStyle.java index 19f2c7145ec..04ad04a25eb 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlStyle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlStyle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.ttml; +package org.telegram.messenger.exoplayer2.text.ttml; import android.graphics.Typeface; +import android.support.annotation.IntDef; import android.text.Layout; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** * Style object of a TtmlNode @@ -26,15 +29,25 @@ public static final int UNSPECIFIED = -1; + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} public static final int STYLE_NORMAL = Typeface.NORMAL; public static final int STYLE_BOLD = Typeface.BOLD; public static final int STYLE_ITALIC = Typeface.ITALIC; public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} public static final int FONT_SIZE_UNIT_PIXEL = 1; public static final int FONT_SIZE_UNIT_EM = 2; public static final int FONT_SIZE_UNIT_PERCENT = 3; + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} private static final int OFF = 0; private static final int ON = 1; @@ -43,10 +56,15 @@ private boolean hasFontColor; private int backgroundColor; private boolean hasBackgroundColor; + @OptionalBoolean private int linethrough; + @OptionalBoolean private int underline; + @OptionalBoolean private int bold; + @OptionalBoolean private int italic; + @FontSizeUnit private int fontSizeUnit; private float fontSize; private String id; @@ -67,12 +85,13 @@ public TtmlStyle() { * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} * or {@link #STYLE_BOLD_ITALIC}. */ + @StyleFlags public int getStyle() { if (bold == UNSPECIFIED && italic == UNSPECIFIED) { return UNSPECIFIED; } - return (bold != UNSPECIFIED ? bold : STYLE_NORMAL) - | (italic != UNSPECIFIED ? italic : STYLE_NORMAL); + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); } public boolean isLinethrough() { @@ -95,6 +114,18 @@ public TtmlStyle setUnderline(boolean underline) { return this; } + public TtmlStyle setBold(boolean bold) { + Assertions.checkState(inheritableStyle == null); + this.bold = bold ? ON : OFF; + return this; + } + + public TtmlStyle setItalic(boolean italic) { + Assertions.checkState(inheritableStyle == null); + this.italic = italic ? ON : OFF; + return this; + } + public String getFontFamily() { return fontFamily; } @@ -140,18 +171,6 @@ public boolean hasBackgroundColor() { return hasBackgroundColor; } - public TtmlStyle setBold(boolean isBold) { - Assertions.checkState(inheritableStyle == null); - bold = isBold ? STYLE_BOLD : STYLE_NORMAL; - return this; - } - - public TtmlStyle setItalic(boolean isItalic) { - Assertions.checkState(inheritableStyle == null); - italic = isItalic ? STYLE_ITALIC : STYLE_NORMAL; - return this; - } - /** * Inherits from an ancestor style. Properties like tts:backgroundColor which * are not inheritable are not inherited as well as properties which are already set locally @@ -236,6 +255,7 @@ public TtmlStyle setFontSizeUnit(int fontSizeUnit) { return this; } + @FontSizeUnit public int getFontSizeUnit() { return fontSizeUnit; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlSubtitle.java similarity index 78% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlSubtitle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlSubtitle.java index 80ce51e1c8b..a7e73c5e132 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/ttml/TtmlSubtitle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/ttml/TtmlSubtitle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.ttml; +package org.telegram.messenger.exoplayer2.text.ttml; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.Collections; import java.util.List; import java.util.Map; @@ -25,7 +26,7 @@ /** * A representation of a TTML subtitle. */ -public final class TtmlSubtitle implements Subtitle { +/* package */ final class TtmlSubtitle implements Subtitle { private final TtmlNode root; private final long[] eventTimesUs; @@ -44,7 +45,7 @@ public TtmlSubtitle(TtmlNode root, Map globalStyles, @Override public int getNextEventTimeIndex(long timeUs) { int index = Util.binarySearchCeil(eventTimesUs, timeUs, false, false); - return index < eventTimesUs.length ? index : -1; + return index < eventTimesUs.length ? index : C.INDEX_UNSET; } @Override @@ -57,11 +58,6 @@ public long getEventTime(int index) { return eventTimesUs[index]; } - @Override - public long getLastEventTime() { - return (eventTimesUs.length == 0 ? -1 : eventTimesUs[eventTimesUs.length - 1]); - } - /* @VisibleForTesting */ /* package */ TtmlNode getRoot() { return root; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gDecoder.java new file mode 100755 index 00000000000..587a8206d16 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gDecoder.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.tx3g; + +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; + +/** + * A {@link SimpleSubtitleDecoder} for tx3g. + *

        + * Currently only supports parsing of a single text track. + */ +public final class Tx3gDecoder extends SimpleSubtitleDecoder { + + private final ParsableByteArray parsableByteArray; + + public Tx3gDecoder() { + super("Tx3gDecoder"); + parsableByteArray = new ParsableByteArray(); + } + + @Override + protected Subtitle decode(byte[] bytes, int length) { + parsableByteArray.reset(bytes, length); + int textLength = parsableByteArray.readUnsignedShort(); + if (textLength == 0) { + return Tx3gSubtitle.EMPTY; + } + String cueText = parsableByteArray.readString(textLength); + return new Tx3gSubtitle(new Cue(cueText)); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gSubtitle.java similarity index 78% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gSubtitle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gSubtitle.java index 45ea6bf9926..542290136ac 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/tx3g/Tx3gSubtitle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/tx3g/Tx3gSubtitle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.tx3g; +package org.telegram.messenger.exoplayer2.text.tx3g; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.Collections; import java.util.List; @@ -40,7 +41,7 @@ private Tx3gSubtitle() { @Override public int getNextEventTimeIndex(long timeUs) { - return timeUs < 0 ? 0 : -1; + return timeUs < 0 ? 0 : C.INDEX_UNSET; } @Override @@ -54,11 +55,6 @@ public long getEventTime(int index) { return 0; } - @Override - public long getLastEventTime() { - return 0; - } - @Override public List getCues(long timeUs) { return timeUs >= 0 ? cues : Collections.emptyList(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/CssParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/CssParser.java new file mode 100755 index 00000000000..9fad3c8e372 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/CssParser.java @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.util.ColorParser; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provides a CSS parser for STYLE blocks in Webvtt files. Supports only a subset of the CSS + * features. + */ +/* package */ final class CssParser { + + private static final String PROPERTY_BGCOLOR = "background-color"; + private static final String PROPERTY_FONT_FAMILY = "font-family"; + private static final String PROPERTY_FONT_WEIGHT = "font-weight"; + private static final String PROPERTY_TEXT_DECORATION = "text-decoration"; + private static final String VALUE_BOLD = "bold"; + private static final String VALUE_UNDERLINE = "underline"; + private static final String BLOCK_START = "{"; + private static final String BLOCK_END = "}"; + private static final String PROPERTY_FONT_STYLE = "font-style"; + private static final String VALUE_ITALIC = "italic"; + + private static final Pattern VOICE_NAME_PATTERN = Pattern.compile("\\[voice=\"([^\"]*)\"\\]"); + + // Temporary utility data structures. + private final ParsableByteArray styleInput; + private final StringBuilder stringBuilder; + + public CssParser() { + styleInput = new ParsableByteArray(); + stringBuilder = new StringBuilder(); + } + + /** + * Takes a CSS style block and consumes up to the first empty line found. Attempts to parse the + * contents of the style block and returns a {@link WebvttCssStyle} instance if successful, or + * {@code null} otherwise. + * + * @param input The input from which the style block should be read. + * @return A {@link WebvttCssStyle} that represents the parsed block. + */ + public WebvttCssStyle parseBlock(ParsableByteArray input) { + stringBuilder.setLength(0); + int initialInputPosition = input.getPosition(); + skipStyleBlock(input); + styleInput.reset(input.data, input.getPosition()); + styleInput.setPosition(initialInputPosition); + String selector = parseSelector(styleInput, stringBuilder); + if (selector == null || !BLOCK_START.equals(parseNextToken(styleInput, stringBuilder))) { + return null; + } + WebvttCssStyle style = new WebvttCssStyle(); + applySelectorToStyle(style, selector); + String token = null; + boolean blockEndFound = false; + while (!blockEndFound) { + int position = styleInput.getPosition(); + token = parseNextToken(styleInput, stringBuilder); + blockEndFound = token == null || BLOCK_END.equals(token); + if (!blockEndFound) { + styleInput.setPosition(position); + parseStyleDeclaration(styleInput, style, stringBuilder); + } + } + return BLOCK_END.equals(token) ? style : null; // Check that the style block ended correctly. + } + + /** + * Returns a string containing the selector. The input is expected to have the form + * {@code ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional. + * + * @param input From which the selector is obtained. + * @return A string containing the target, empty string if the selector is universal + * (targets all cues) or null if an error was encountered. + */ + private static String parseSelector(ParsableByteArray input, StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + if (input.bytesLeft() < 5) { + return null; + } + String cueSelector = input.readString(5); + if (!"::cue".equals(cueSelector)) { + return null; + } + int position = input.getPosition(); + String token = parseNextToken(input, stringBuilder); + if (token == null) { + return null; + } + if (BLOCK_START.equals(token)) { + input.setPosition(position); + return ""; + } + String target = null; + if ("(".equals(token)) { + target = readCueTarget(input); + } + token = parseNextToken(input, stringBuilder); + if (!")".equals(token) || token == null) { + return null; + } + return target; + } + + /** + * Reads the contents of ::cue() and returns it as a string. + */ + private static String readCueTarget(ParsableByteArray input) { + int position = input.getPosition(); + int limit = input.limit(); + boolean cueTargetEndFound = false; + while (position < limit && !cueTargetEndFound) { + char c = (char) input.data[position++]; + cueTargetEndFound = c == ')'; + } + return input.readString(--position - input.getPosition()).trim(); + // --offset to return ')' to the input. + } + + private static void parseStyleDeclaration(ParsableByteArray input, WebvttCssStyle style, + StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + String property = parseIdentifier(input, stringBuilder); + if ("".equals(property)) { + return; + } + if (!":".equals(parseNextToken(input, stringBuilder))) { + return; + } + skipWhitespaceAndComments(input); + String value = parsePropertyValue(input, stringBuilder); + if (value == null || "".equals(value)) { + return; + } + int position = input.getPosition(); + String token = parseNextToken(input, stringBuilder); + if (";".equals(token)) { + // The style declaration is well formed. + } else if (BLOCK_END.equals(token)) { + // The style declaration is well formed and we can go on, but the closing bracket had to be + // fed back. + input.setPosition(position); + } else { + // The style declaration is not well formed. + return; + } + // At this point we have a presumably valid declaration, we need to parse it and fill the style. + if ("color".equals(property)) { + style.setFontColor(ColorParser.parseCssColor(value)); + } else if (PROPERTY_BGCOLOR.equals(property)) { + style.setBackgroundColor(ColorParser.parseCssColor(value)); + } else if (PROPERTY_TEXT_DECORATION.equals(property)) { + if (VALUE_UNDERLINE.equals(value)) { + style.setUnderline(true); + } + } else if (PROPERTY_FONT_FAMILY.equals(property)) { + style.setFontFamily(value); + } else if (PROPERTY_FONT_WEIGHT.equals(property)) { + if (VALUE_BOLD.equals(value)) { + style.setBold(true); + } + } else if (PROPERTY_FONT_STYLE.equals(property)) { + if (VALUE_ITALIC.equals(value)) { + style.setItalic(true); + } + } + // TODO: Fill remaining supported styles. + } + + // Visible for testing. + /* package */ static void skipWhitespaceAndComments(ParsableByteArray input) { + boolean skipping = true; + while (input.bytesLeft() > 0 && skipping) { + skipping = maybeSkipWhitespace(input) || maybeSkipComment(input); + } + } + + // Visible for testing. + /* package */ static String parseNextToken(ParsableByteArray input, StringBuilder stringBuilder) { + skipWhitespaceAndComments(input); + if (input.bytesLeft() == 0) { + return null; + } + String identifier = parseIdentifier(input, stringBuilder); + if (!"".equals(identifier)) { + return identifier; + } + // We found a delimiter. + return "" + (char) input.readUnsignedByte(); + } + + private static boolean maybeSkipWhitespace(ParsableByteArray input) { + switch(peekCharAtPosition(input, input.getPosition())) { + case '\t': + case '\r': + case '\n': + case '\f': + case ' ': + input.skipBytes(1); + return true; + default: + return false; + } + } + + // Visible for testing. + /* package */ static void skipStyleBlock(ParsableByteArray input) { + // The style block cannot contain empty lines, so we assume the input ends when a empty line + // is found. + String line; + do { + line = input.readLine(); + } while (!TextUtils.isEmpty(line)); + } + + private static char peekCharAtPosition(ParsableByteArray input, int position) { + return (char) input.data[position]; + } + + private static String parsePropertyValue(ParsableByteArray input, StringBuilder stringBuilder) { + StringBuilder expressionBuilder = new StringBuilder(); + String token; + int position; + boolean expressionEndFound = false; + // TODO: Add support for "Strings in quotes with spaces". + while (!expressionEndFound) { + position = input.getPosition(); + token = parseNextToken(input, stringBuilder); + if (token == null) { + // Syntax error. + return null; + } + if (BLOCK_END.equals(token) || ";".equals(token)) { + input.setPosition(position); + expressionEndFound = true; + } else { + expressionBuilder.append(token); + } + } + return expressionBuilder.toString(); + } + + private static boolean maybeSkipComment(ParsableByteArray input) { + int position = input.getPosition(); + int limit = input.limit(); + byte[] data = input.data; + if (position + 2 <= limit && data[position++] == '/' && data[position++] == '*') { + while (position + 1 < limit) { + char skippedChar = (char) data[position++]; + if (skippedChar == '*') { + if (((char) data[position]) == '/') { + position++; + limit = position; + } + } + } + input.skipBytes(limit - input.getPosition()); + return true; + } + return false; + } + + private static String parseIdentifier(ParsableByteArray input, StringBuilder stringBuilder) { + stringBuilder.setLength(0); + int position = input.getPosition(); + int limit = input.limit(); + boolean identifierEndFound = false; + while (position < limit && !identifierEndFound) { + char c = (char) input.data[position]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '#' + || c == '-' || c == '.' || c == '_') { + position++; + stringBuilder.append(c); + } else { + identifierEndFound = true; + } + } + input.skipBytes(position - input.getPosition()); + return stringBuilder.toString(); + } + + /** + * Sets the target of a {@link WebvttCssStyle} by splitting a selector of the form + * {@code ::cue(tag#id.class1.class2[voice="someone"]}, where every element is optional. + */ + private void applySelectorToStyle(WebvttCssStyle style, String selector) { + if ("".equals(selector)) { + return; // Universal selector. + } + int voiceStartIndex = selector.indexOf('['); + if (voiceStartIndex != -1) { + Matcher matcher = VOICE_NAME_PATTERN.matcher(selector.substring(voiceStartIndex)); + if (matcher.matches()) { + style.setTargetVoice(matcher.group(1)); + } + selector = selector.substring(0, voiceStartIndex); + } + String[] classDivision = selector.split("\\."); + String tagAndIdDivision = classDivision[0]; + int idPrefixIndex = tagAndIdDivision.indexOf('#'); + if (idPrefixIndex != -1) { + style.setTargetTagName(tagAndIdDivision.substring(0, idPrefixIndex)); + style.setTargetId(tagAndIdDivision.substring(idPrefixIndex + 1)); // We discard the '#'. + } else { + style.setTargetTagName(tagAndIdDivision); + } + if (classDivision.length > 1) { + style.setTargetClasses(Arrays.copyOfRange(classDivision, 1, classDivision.length)); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttDecoder.java new file mode 100755 index 00000000000..b25edbe6c06 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttDecoder.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for Webvtt embedded in a Mp4 container file. + */ +public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder { + + private static final int BOX_HEADER_SIZE = 8; + + private static final int TYPE_payl = Util.getIntegerCodeForString("payl"); + private static final int TYPE_sttg = Util.getIntegerCodeForString("sttg"); + private static final int TYPE_vttc = Util.getIntegerCodeForString("vttc"); + + private final ParsableByteArray sampleData; + private final WebvttCue.Builder builder; + + public Mp4WebvttDecoder() { + super("Mp4WebvttDecoder"); + sampleData = new ParsableByteArray(); + builder = new WebvttCue.Builder(); + } + + @Override + protected Mp4WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: + // first 4 bytes size and then 4 bytes type. + sampleData.reset(bytes, length); + List resultingCueList = new ArrayList<>(); + while (sampleData.bytesLeft() > 0) { + if (sampleData.bytesLeft() < BOX_HEADER_SIZE) { + throw new SubtitleDecoderException("Incomplete Mp4Webvtt Top Level box header found."); + } + int boxSize = sampleData.readInt(); + int boxType = sampleData.readInt(); + if (boxType == TYPE_vttc) { + resultingCueList.add(parseVttCueBox(sampleData, builder, boxSize - BOX_HEADER_SIZE)); + } else { + // Peers of the VTTCueBox are still not supported and are skipped. + sampleData.skipBytes(boxSize - BOX_HEADER_SIZE); + } + } + return new Mp4WebvttSubtitle(resultingCueList); + } + + private static Cue parseVttCueBox(ParsableByteArray sampleData, WebvttCue.Builder builder, + int remainingCueBoxBytes) throws SubtitleDecoderException { + builder.reset(); + while (remainingCueBoxBytes > 0) { + if (remainingCueBoxBytes < BOX_HEADER_SIZE) { + throw new SubtitleDecoderException("Incomplete vtt cue box header found."); + } + int boxSize = sampleData.readInt(); + int boxType = sampleData.readInt(); + remainingCueBoxBytes -= BOX_HEADER_SIZE; + int payloadLength = boxSize - BOX_HEADER_SIZE; + String boxPayload = new String(sampleData.data, sampleData.getPosition(), payloadLength); + sampleData.skipBytes(payloadLength); + remainingCueBoxBytes -= payloadLength; + if (boxType == TYPE_sttg) { + WebvttCueParser.parseCueSettingsList(boxPayload, builder); + } else if (boxType == TYPE_payl) { + WebvttCueParser.parseCueText(null, boxPayload.trim(), builder, + Collections.emptyList()); + } else { + // Other VTTCueBox children are still not supported and are ignored. + } + } + return builder.build(); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttSubtitle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java index e01ff421f01..9cbb51ca621 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/Mp4WebvttSubtitle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/Mp4WebvttSubtitle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.webvtt; +package org.telegram.messenger.exoplayer2.text.webvtt; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.util.Collections; import java.util.List; @@ -34,7 +35,7 @@ public Mp4WebvttSubtitle(List cueList) { @Override public int getNextEventTimeIndex(long timeUs) { - return timeUs < 0 ? 0 : -1; + return timeUs < 0 ? 0 : C.INDEX_UNSET; } @Override @@ -48,11 +49,6 @@ public long getEventTime(int index) { return 0; } - @Override - public long getLastEventTime() { - return 0; - } - @Override public List getCues(long timeUs) { return timeUs >= 0 ? cues : Collections.emptyList(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCssStyle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCssStyle.java new file mode 100755 index 00000000000..112c0d472e9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCssStyle.java @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import android.graphics.Typeface; +import android.support.annotation.IntDef; +import android.text.Layout; +import org.telegram.messenger.exoplayer2.util.Util; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Style object of a Css style block in a Webvtt file. + * + * @see W3C specification - Apply + * CSS properties + */ +/* package */ final class WebvttCssStyle { + + public static final int UNSPECIFIED = -1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {UNSPECIFIED, STYLE_NORMAL, STYLE_BOLD, STYLE_ITALIC, + STYLE_BOLD_ITALIC}) + public @interface StyleFlags {} + public static final int STYLE_NORMAL = Typeface.NORMAL; + public static final int STYLE_BOLD = Typeface.BOLD; + public static final int STYLE_ITALIC = Typeface.ITALIC; + public static final int STYLE_BOLD_ITALIC = Typeface.BOLD_ITALIC; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, FONT_SIZE_UNIT_PIXEL, FONT_SIZE_UNIT_EM, FONT_SIZE_UNIT_PERCENT}) + public @interface FontSizeUnit {} + public static final int FONT_SIZE_UNIT_PIXEL = 1; + public static final int FONT_SIZE_UNIT_EM = 2; + public static final int FONT_SIZE_UNIT_PERCENT = 3; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({UNSPECIFIED, OFF, ON}) + private @interface OptionalBoolean {} + private static final int OFF = 0; + private static final int ON = 1; + + // Selector properties. + private String targetId; + private String targetTag; + private List targetClasses; + private String targetVoice; + + // Style properties. + private String fontFamily; + private int fontColor; + private boolean hasFontColor; + private int backgroundColor; + private boolean hasBackgroundColor; + @OptionalBoolean + private int linethrough; + @OptionalBoolean + private int underline; + @OptionalBoolean + private int bold; + @OptionalBoolean + private int italic; + @FontSizeUnit + private int fontSizeUnit; + private float fontSize; + private Layout.Alignment textAlign; + + public WebvttCssStyle() { + reset(); + } + + public void reset() { + targetId = ""; + targetTag = ""; + targetClasses = Collections.emptyList(); + targetVoice = ""; + fontFamily = null; + hasFontColor = false; + hasBackgroundColor = false; + linethrough = UNSPECIFIED; + underline = UNSPECIFIED; + bold = UNSPECIFIED; + italic = UNSPECIFIED; + fontSizeUnit = UNSPECIFIED; + textAlign = null; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public void setTargetTagName(String targetTag) { + this.targetTag = targetTag; + } + + public void setTargetClasses(String[] targetClasses) { + this.targetClasses = Arrays.asList(targetClasses); + } + + public void setTargetVoice(String targetVoice) { + this.targetVoice = targetVoice; + } + + /** + * Returns a value in a score system compliant with the CSS Specificity rules. + * + * @see CSS Cascading + * + * The score works as follows: + *

          + *
        • Id match adds 0x40000000 to the score. + *
        • Each class and voice match adds 4 to the score. + *
        • Tag matching adds 2 to the score. + *
        • Universal selector matching scores 1. + *
        + * + * @param id The id of the cue if present, {@code null} otherwise. + * @param tag Name of the tag, {@code null} if it refers to the entire cue. + * @param classes An array containing the classes the tag belongs to. Must not be null. + * @param voice Annotated voice if present, {@code null} otherwise. + * @return The score of the match, zero if there is no match. + */ + public int getSpecificityScore(String id, String tag, String[] classes, String voice) { + if (targetId.isEmpty() && targetTag.isEmpty() && targetClasses.isEmpty() + && targetVoice.isEmpty()) { + // The selector is universal. It matches with the minimum score if and only if the given + // element is a whole cue. + return tag.isEmpty() ? 1 : 0; + } + int score = 0; + score = updateScoreForMatch(score, targetId, id, 0x40000000); + score = updateScoreForMatch(score, targetTag, tag, 2); + score = updateScoreForMatch(score, targetVoice, voice, 4); + if (score == -1 || !Arrays.asList(classes).containsAll(targetClasses)) { + return 0; + } else { + score += targetClasses.size() * 4; + } + return score; + } + + /** + * Returns the style or {@link #UNSPECIFIED} when no style information is given. + * + * @return {@link #UNSPECIFIED}, {@link #STYLE_NORMAL}, {@link #STYLE_BOLD}, {@link #STYLE_BOLD} + * or {@link #STYLE_BOLD_ITALIC}. + */ + @StyleFlags + public int getStyle() { + if (bold == UNSPECIFIED && italic == UNSPECIFIED) { + return UNSPECIFIED; + } + return (bold == ON ? STYLE_BOLD : STYLE_NORMAL) + | (italic == ON ? STYLE_ITALIC : STYLE_NORMAL); + } + + public boolean isLinethrough() { + return linethrough == ON; + } + + public WebvttCssStyle setLinethrough(boolean linethrough) { + this.linethrough = linethrough ? ON : OFF; + return this; + } + + public boolean isUnderline() { + return underline == ON; + } + + public WebvttCssStyle setUnderline(boolean underline) { + this.underline = underline ? ON : OFF; + return this; + } + public WebvttCssStyle setBold(boolean bold) { + this.bold = bold ? ON : OFF; + return this; + } + + public WebvttCssStyle setItalic(boolean italic) { + this.italic = italic ? ON : OFF; + return this; + } + + public String getFontFamily() { + return fontFamily; + } + + public WebvttCssStyle setFontFamily(String fontFamily) { + this.fontFamily = Util.toLowerInvariant(fontFamily); + return this; + } + + public int getFontColor() { + if (!hasFontColor) { + throw new IllegalStateException("Font color not defined"); + } + return fontColor; + } + + public WebvttCssStyle setFontColor(int color) { + this.fontColor = color; + hasFontColor = true; + return this; + } + + public boolean hasFontColor() { + return hasFontColor; + } + + public int getBackgroundColor() { + if (!hasBackgroundColor) { + throw new IllegalStateException("Background color not defined."); + } + return backgroundColor; + } + + public WebvttCssStyle setBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + hasBackgroundColor = true; + return this; + } + + public boolean hasBackgroundColor() { + return hasBackgroundColor; + } + + public Layout.Alignment getTextAlign() { + return textAlign; + } + + public WebvttCssStyle setTextAlign(Layout.Alignment textAlign) { + this.textAlign = textAlign; + return this; + } + + public WebvttCssStyle setFontSize(float fontSize) { + this.fontSize = fontSize; + return this; + } + + public WebvttCssStyle setFontSizeUnit(short unit) { + this.fontSizeUnit = unit; + return this; + } + + @FontSizeUnit + public int getFontSizeUnit() { + return fontSizeUnit; + } + + public float getFontSize() { + return fontSize; + } + + public void cascadeFrom(WebvttCssStyle style) { + if (style.hasFontColor) { + setFontColor(style.fontColor); + } + if (style.bold != UNSPECIFIED) { + bold = style.bold; + } + if (style.italic != UNSPECIFIED) { + italic = style.italic; + } + if (style.fontFamily != null) { + fontFamily = style.fontFamily; + } + if (linethrough == UNSPECIFIED) { + linethrough = style.linethrough; + } + if (underline == UNSPECIFIED) { + underline = style.underline; + } + if (textAlign == null) { + textAlign = style.textAlign; + } + if (fontSizeUnit == UNSPECIFIED) { + fontSizeUnit = style.fontSizeUnit; + fontSize = style.fontSize; + } + if (style.hasBackgroundColor) { + setBackgroundColor(style.backgroundColor); + } + } + + private static int updateScoreForMatch(int currentScore, String target, String actual, + int score) { + if (target.isEmpty() || currentScore == -1) { + return currentScore; + } + return target.equals(actual) ? currentScore + score : -1; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCue.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCue.java similarity index 88% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCue.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCue.java index b7007d8892c..190ea0b7a02 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttCue.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.webvtt; +package org.telegram.messenger.exoplayer2.text.webvtt; import android.text.Layout.Alignment; +import android.text.SpannableStringBuilder; import android.util.Log; -import org.telegram.messenger.exoplayer.text.Cue; +import org.telegram.messenger.exoplayer2.text.Cue; /** * A representation of a WebVTT cue. @@ -37,7 +38,8 @@ public WebvttCue(long startTime, long endTime, CharSequence text) { } public WebvttCue(long startTime, long endTime, CharSequence text, Alignment textAlignment, - float line, int lineType, int lineAnchor, float position, int positionAnchor, float width) { + float line, @Cue.LineType int lineType, @Cue.AnchorType int lineAnchor, float position, + @Cue.AnchorType int positionAnchor, float width) { super(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, width); this.startTime = startTime; this.endTime = endTime; @@ -47,7 +49,7 @@ public WebvttCue(long startTime, long endTime, CharSequence text, Alignment text * Returns whether or not this cue should be placed in the default position and rolled-up with * the other "normal" cues. * - * @return True if this cue should be placed in the default position; false otherwise. + * @return Whether this cue should be placed in the default position. */ public boolean isNormalCue() { return (line == DIMEN_UNSET && position == DIMEN_UNSET); @@ -63,7 +65,7 @@ public static final class Builder { private long startTime; private long endTime; - private CharSequence text; + private SpannableStringBuilder text; private Alignment textAlignment; private float line; private int lineType; @@ -91,7 +93,7 @@ public void reset() { width = Cue.DIMEN_UNSET; } - // Construction methods + // Construction methods. public WebvttCue build() { if (position != Cue.DIMEN_UNSET && positionAnchor == Cue.TYPE_UNSET) { @@ -111,7 +113,7 @@ public Builder setEndTime(long time) { return this; } - public Builder setText(CharSequence aText) { + public Builder setText(SpannableStringBuilder aText) { text = aText; return this; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java new file mode 100755 index 00000000000..ba559faa49b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttCueParser.java @@ -0,0 +1,525 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import android.graphics.Typeface; +import android.text.Layout.Alignment; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.TypefaceSpan; +import android.text.style.UnderlineSpan; +import android.util.Log; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for WebVTT cues. (https://w3c.github.io/webvtt/#cues) + */ +/* package */ final class WebvttCueParser { + + public static final Pattern CUE_HEADER_PATTERN = Pattern + .compile("^(\\S+)\\s+-->\\s+(\\S+)(.*)?$"); + + private static final Pattern CUE_SETTING_PATTERN = Pattern.compile("(\\S+?):(\\S+)"); + + private static final char CHAR_LESS_THAN = '<'; + private static final char CHAR_GREATER_THAN = '>'; + private static final char CHAR_SLASH = '/'; + private static final char CHAR_AMPERSAND = '&'; + private static final char CHAR_SEMI_COLON = ';'; + private static final char CHAR_SPACE = ' '; + + private static final String ENTITY_LESS_THAN = "lt"; + private static final String ENTITY_GREATER_THAN = "gt"; + private static final String ENTITY_AMPERSAND = "amp"; + private static final String ENTITY_NON_BREAK_SPACE = "nbsp"; + + private static final String TAG_BOLD = "b"; + private static final String TAG_ITALIC = "i"; + private static final String TAG_UNDERLINE = "u"; + private static final String TAG_CLASS = "c"; + private static final String TAG_VOICE = "v"; + private static final String TAG_LANG = "lang"; + + private static final int STYLE_BOLD = Typeface.BOLD; + private static final int STYLE_ITALIC = Typeface.ITALIC; + + private static final String TAG = "WebvttCueParser"; + + private final StringBuilder textBuilder; + + public WebvttCueParser() { + textBuilder = new StringBuilder(); + } + + /** + * Parses the next valid WebVTT cue in a parsable array, including timestamps, settings and text. + * + * @param webvttData Parsable WebVTT file data. + * @param builder Builder for WebVTT Cues. + * @param styles List of styles defined by the CSS style blocks preceeding the cues. + * @return Whether a valid Cue was found. + */ + /* package */ boolean parseCue(ParsableByteArray webvttData, WebvttCue.Builder builder, + List styles) { + String firstLine = webvttData.readLine(); + Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(firstLine); + if (cueHeaderMatcher.matches()) { + // We have found the timestamps in the first line. No id present. + return parseCue(null, cueHeaderMatcher, webvttData, builder, textBuilder, styles); + } else { + // The first line is not the timestamps, but could be the cue id. + String secondLine = webvttData.readLine(); + cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(secondLine); + if (cueHeaderMatcher.matches()) { + // We can do the rest of the parsing, including the id. + return parseCue(firstLine.trim(), cueHeaderMatcher, webvttData, builder, textBuilder, + styles); + } + } + return false; + } + + /** + * Parses a string containing a list of cue settings. + * + * @param cueSettingsList String containing the settings for a given cue. + * @param builder The {@link WebvttCue.Builder} where incremental construction takes place. + */ + /* package */ static void parseCueSettingsList(String cueSettingsList, + WebvttCue.Builder builder) { + // Parse the cue settings list. + Matcher cueSettingMatcher = CUE_SETTING_PATTERN.matcher(cueSettingsList); + while (cueSettingMatcher.find()) { + String name = cueSettingMatcher.group(1); + String value = cueSettingMatcher.group(2); + try { + if ("line".equals(name)) { + parseLineAttribute(value, builder); + } else if ("align".equals(name)) { + builder.setTextAlignment(parseTextAlignment(value)); + } else if ("position".equals(name)) { + parsePositionAttribute(value, builder); + } else if ("size".equals(name)) { + builder.setWidth(WebvttParserUtil.parsePercentage(value)); + } else { + Log.w(TAG, "Unknown cue setting " + name + ":" + value); + } + } catch (NumberFormatException e) { + Log.w(TAG, "Skipping bad cue setting: " + cueSettingMatcher.group()); + } + } + } + + /** + * Parses the text payload of a WebVTT Cue and applies modifications on {@link WebvttCue.Builder}. + * + * @param id Id of the cue, {@code null} if it is not present. + * @param markup The markup text to be parsed. + * @param styles List of styles defined by the CSS style blocks preceeding the cues. + * @param builder Output builder. + */ + /* package */ static void parseCueText(String id, String markup, WebvttCue.Builder builder, + List styles) { + SpannableStringBuilder spannedText = new SpannableStringBuilder(); + Stack startTagStack = new Stack<>(); + List scratchStyleMatches = new ArrayList<>(); + int pos = 0; + while (pos < markup.length()) { + char curr = markup.charAt(pos); + switch (curr) { + case CHAR_LESS_THAN: + if (pos + 1 >= markup.length()) { + pos++; + break; // avoid ArrayOutOfBoundsException + } + int ltPos = pos; + boolean isClosingTag = markup.charAt(ltPos + 1) == CHAR_SLASH; + pos = findEndOfTag(markup, ltPos + 1); + boolean isVoidTag = markup.charAt(pos - 2) == CHAR_SLASH; + String fullTagExpression = markup.substring(ltPos + (isClosingTag ? 2 : 1), + isVoidTag ? pos - 2 : pos - 1); + String tagName = getTagName(fullTagExpression); + if (tagName == null || !isSupportedTag(tagName)) { + continue; + } + if (isClosingTag) { + StartTag startTag; + do { + if (startTagStack.isEmpty()) { + break; + } + startTag = startTagStack.pop(); + applySpansForTag(id, startTag, spannedText, styles, scratchStyleMatches); + } while(!startTag.name.equals(tagName)); + } else if (!isVoidTag) { + startTagStack.push(StartTag.buildStartTag(fullTagExpression, spannedText.length())); + } + break; + case CHAR_AMPERSAND: + int semiColonEndIndex = markup.indexOf(CHAR_SEMI_COLON, pos + 1); + int spaceEndIndex = markup.indexOf(CHAR_SPACE, pos + 1); + int entityEndIndex = semiColonEndIndex == -1 ? spaceEndIndex + : (spaceEndIndex == -1 ? semiColonEndIndex + : Math.min(semiColonEndIndex, spaceEndIndex)); + if (entityEndIndex != -1) { + applyEntity(markup.substring(pos + 1, entityEndIndex), spannedText); + if (entityEndIndex == spaceEndIndex) { + spannedText.append(" "); + } + pos = entityEndIndex + 1; + } else { + spannedText.append(curr); + pos++; + } + break; + default: + spannedText.append(curr); + pos++; + break; + } + } + // apply unclosed tags + while (!startTagStack.isEmpty()) { + applySpansForTag(id, startTagStack.pop(), spannedText, styles, scratchStyleMatches); + } + applySpansForTag(id, StartTag.buildWholeCueVirtualTag(), spannedText, styles, + scratchStyleMatches); + builder.setText(spannedText); + } + + private static boolean parseCue(String id, Matcher cueHeaderMatcher, ParsableByteArray webvttData, + WebvttCue.Builder builder, StringBuilder textBuilder, List styles) { + try { + // Parse the cue start and end times. + builder.setStartTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(1))) + .setEndTime(WebvttParserUtil.parseTimestampUs(cueHeaderMatcher.group(2))); + } catch (NumberFormatException e) { + Log.w(TAG, "Skipping cue with bad header: " + cueHeaderMatcher.group()); + return false; + } + + parseCueSettingsList(cueHeaderMatcher.group(3), builder); + + // Parse the cue text. + textBuilder.setLength(0); + String line; + while ((line = webvttData.readLine()) != null && !line.isEmpty()) { + if (textBuilder.length() > 0) { + textBuilder.append("\n"); + } + textBuilder.append(line.trim()); + } + parseCueText(id, textBuilder.toString(), builder, styles); + return true; + } + + // Internal methods + + private static void parseLineAttribute(String s, WebvttCue.Builder builder) + throws NumberFormatException { + int commaIndex = s.indexOf(','); + if (commaIndex != -1) { + builder.setLineAnchor(parsePositionAnchor(s.substring(commaIndex + 1))); + s = s.substring(0, commaIndex); + } else { + builder.setLineAnchor(Cue.TYPE_UNSET); + } + if (s.endsWith("%")) { + builder.setLine(WebvttParserUtil.parsePercentage(s)).setLineType(Cue.LINE_TYPE_FRACTION); + } else { + builder.setLine(Integer.parseInt(s)).setLineType(Cue.LINE_TYPE_NUMBER); + } + } + + private static void parsePositionAttribute(String s, WebvttCue.Builder builder) + throws NumberFormatException { + int commaIndex = s.indexOf(','); + if (commaIndex != -1) { + builder.setPositionAnchor(parsePositionAnchor(s.substring(commaIndex + 1))); + s = s.substring(0, commaIndex); + } else { + builder.setPositionAnchor(Cue.TYPE_UNSET); + } + builder.setPosition(WebvttParserUtil.parsePercentage(s)); + } + + private static int parsePositionAnchor(String s) { + switch (s) { + case "start": + return Cue.ANCHOR_TYPE_START; + case "center": + case "middle": + return Cue.ANCHOR_TYPE_MIDDLE; + case "end": + return Cue.ANCHOR_TYPE_END; + default: + Log.w(TAG, "Invalid anchor value: " + s); + return Cue.TYPE_UNSET; + } + } + + private static Alignment parseTextAlignment(String s) { + switch (s) { + case "start": + case "left": + return Alignment.ALIGN_NORMAL; + case "center": + case "middle": + return Alignment.ALIGN_CENTER; + case "end": + case "right": + return Alignment.ALIGN_OPPOSITE; + default: + Log.w(TAG, "Invalid alignment value: " + s); + return null; + } + } + + /** + * Find end of tag (>). The position returned is the position of the > plus one (exclusive). + * + * @param markup The WebVTT cue markup to be parsed. + * @param startPos The position from where to start searching for the end of tag. + * @return The position of the end of tag plus 1 (one). + */ + private static int findEndOfTag(String markup, int startPos) { + int index = markup.indexOf(CHAR_GREATER_THAN, startPos); + return index == -1 ? markup.length() : index + 1; + } + + private static void applyEntity(String entity, SpannableStringBuilder spannedText) { + switch (entity) { + case ENTITY_LESS_THAN: + spannedText.append('<'); + break; + case ENTITY_GREATER_THAN: + spannedText.append('>'); + break; + case ENTITY_NON_BREAK_SPACE: + spannedText.append(' '); + break; + case ENTITY_AMPERSAND: + spannedText.append('&'); + break; + default: + Log.w(TAG, "ignoring unsupported entity: '&" + entity + ";'"); + break; + } + } + + private static boolean isSupportedTag(String tagName) { + switch (tagName) { + case TAG_BOLD: + case TAG_CLASS: + case TAG_ITALIC: + case TAG_LANG: + case TAG_UNDERLINE: + case TAG_VOICE: + return true; + default: + return false; + } + } + + private static void applySpansForTag(String cueId, StartTag startTag, SpannableStringBuilder text, + List styles, List scratchStyleMatches) { + int start = startTag.position; + int end = text.length(); + switch(startTag.name) { + case TAG_BOLD: + text.setSpan(new StyleSpan(STYLE_BOLD), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_ITALIC: + text.setSpan(new StyleSpan(STYLE_ITALIC), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_UNDERLINE: + text.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TAG_CLASS: + case TAG_LANG: + case TAG_VOICE: + case "": // Case of the "whole cue" virtual tag. + break; + default: + return; + } + scratchStyleMatches.clear(); + getApplicableStyles(styles, cueId, startTag, scratchStyleMatches); + int styleMatchesCount = scratchStyleMatches.size(); + for (int i = 0; i < styleMatchesCount; i++) { + applyStyleToText(text, scratchStyleMatches.get(i).style, start, end); + } + } + + private static void applyStyleToText(SpannableStringBuilder spannedText, WebvttCssStyle style, + int start, int end) { + if (style == null) { + return; + } + if (style.getStyle() != WebvttCssStyle.UNSPECIFIED) { + spannedText.setSpan(new StyleSpan(style.getStyle()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isLinethrough()) { + spannedText.setSpan(new StrikethroughSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.isUnderline()) { + spannedText.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasFontColor()) { + spannedText.setSpan(new ForegroundColorSpan(style.getFontColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.hasBackgroundColor()) { + spannedText.setSpan(new BackgroundColorSpan(style.getBackgroundColor()), start, end, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getFontFamily() != null) { + spannedText.setSpan(new TypefaceSpan(style.getFontFamily()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (style.getTextAlign() != null) { + spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + switch (style.getFontSizeUnit()) { + case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: + spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_EM: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.UNSPECIFIED: + // Do nothing. + break; + } + } + + /** + * Returns the tag name for the given tag contents. + * + * @param tagExpression Characters between &lt: and &gt; of a start or end tag. + * @return The name of tag. + */ + private static String getTagName(String tagExpression) { + tagExpression = tagExpression.trim(); + if (tagExpression.isEmpty()) { + return null; + } + return tagExpression.split("[ \\.]")[0]; + } + + private static void getApplicableStyles(List declaredStyles, String id, + StartTag tag, List output) { + int styleCount = declaredStyles.size(); + for (int i = 0; i < styleCount; i++) { + WebvttCssStyle style = declaredStyles.get(i); + int score = style.getSpecificityScore(id, tag.name, tag.classes, tag.voice); + if (score > 0) { + output.add(new StyleMatch(score, style)); + } + } + Collections.sort(output); + } + + private static final class StyleMatch implements Comparable { + + public final int score; + public final WebvttCssStyle style; + + public StyleMatch(int score, WebvttCssStyle style) { + this.score = score; + this.style = style; + } + + @Override + public int compareTo(StyleMatch another) { + return this.score - another.score; + } + + } + + private static final class StartTag { + + private static final String[] NO_CLASSES = new String[0]; + + public final String name; + public final int position; + public final String voice; + public final String[] classes; + + private StartTag(String name, int position, String voice, String[] classes) { + this.position = position; + this.name = name; + this.voice = voice; + this.classes = classes; + } + + public static StartTag buildStartTag(String fullTagExpression, int position) { + fullTagExpression = fullTagExpression.trim(); + if (fullTagExpression.isEmpty()) { + return null; + } + int voiceStartIndex = fullTagExpression.indexOf(" "); + String voice; + if (voiceStartIndex == -1) { + voice = ""; + } else { + voice = fullTagExpression.substring(voiceStartIndex).trim(); + fullTagExpression = fullTagExpression.substring(0, voiceStartIndex); + } + String[] nameAndClasses = fullTagExpression.split("\\."); + String name = nameAndClasses[0]; + String[] classes; + if (nameAndClasses.length > 1) { + classes = Arrays.copyOfRange(nameAndClasses, 1, nameAndClasses.length); + } else { + classes = NO_CLASSES; + } + return new StartTag(name, position, voice, classes); + } + + public static StartTag buildWholeCueVirtualTag() { + return new StartTag("", 0, "", new String[0]); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttDecoder.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttDecoder.java new file mode 100755 index 00000000000..50cc43ce94f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttDecoder.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.text.SimpleSubtitleDecoder; +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link SimpleSubtitleDecoder} for WebVTT. + *

        + * @see WebVTT specification + */ +public final class WebvttDecoder extends SimpleSubtitleDecoder { + + private static final int EVENT_NONE = -1; + private static final int EVENT_END_OF_FILE = 0; + private static final int EVENT_COMMENT = 1; + private static final int EVENT_STYLE_BLOCK = 2; + private static final int EVENT_CUE = 3; + + private static final String COMMENT_START = "NOTE"; + private static final String STYLE_START = "STYLE"; + + private final WebvttCueParser cueParser; + private final ParsableByteArray parsableWebvttData; + private final WebvttCue.Builder webvttCueBuilder; + private final CssParser cssParser; + private final List definedStyles; + + public WebvttDecoder() { + super("WebvttDecoder"); + cueParser = new WebvttCueParser(); + parsableWebvttData = new ParsableByteArray(); + webvttCueBuilder = new WebvttCue.Builder(); + cssParser = new CssParser(); + definedStyles = new ArrayList<>(); + } + + @Override + protected WebvttSubtitle decode(byte[] bytes, int length) throws SubtitleDecoderException { + parsableWebvttData.reset(bytes, length); + // Initialization for consistent starting state. + webvttCueBuilder.reset(); + definedStyles.clear(); + + // Validate the first line of the header, and skip the remainder. + WebvttParserUtil.validateWebvttHeaderLine(parsableWebvttData); + while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} + + int event; + ArrayList subtitles = new ArrayList<>(); + while ((event = getNextEvent(parsableWebvttData)) != EVENT_END_OF_FILE) { + if (event == EVENT_COMMENT) { + skipComment(parsableWebvttData); + } else if (event == EVENT_STYLE_BLOCK) { + if (!subtitles.isEmpty()) { + throw new SubtitleDecoderException("A style block was found after the first cue."); + } + parsableWebvttData.readLine(); // Consume the "STYLE" header. + WebvttCssStyle styleBlock = cssParser.parseBlock(parsableWebvttData); + if (styleBlock != null) { + definedStyles.add(styleBlock); + } + } else if (event == EVENT_CUE) { + if (cueParser.parseCue(parsableWebvttData, webvttCueBuilder, definedStyles)) { + subtitles.add(webvttCueBuilder.build()); + webvttCueBuilder.reset(); + } + } + } + return new WebvttSubtitle(subtitles); + } + + /** + * Positions the input right before the next event, and returns the kind of event found. Does not + * consume any data from such event, if any. + * + * @return The kind of event found. + */ + private static int getNextEvent(ParsableByteArray parsableWebvttData) { + int foundEvent = EVENT_NONE; + int currentInputPosition = 0; + while (foundEvent == EVENT_NONE) { + currentInputPosition = parsableWebvttData.getPosition(); + String line = parsableWebvttData.readLine(); + if (line == null) { + foundEvent = EVENT_END_OF_FILE; + } else if (STYLE_START.equals(line)) { + foundEvent = EVENT_STYLE_BLOCK; + } else if (COMMENT_START.startsWith(line)) { + foundEvent = EVENT_COMMENT; + } else { + foundEvent = EVENT_CUE; + } + } + parsableWebvttData.setPosition(currentInputPosition); + return foundEvent; + } + + private static void skipComment(ParsableByteArray parsableWebvttData) { + while (!TextUtils.isEmpty(parsableWebvttData.readLine())) {} + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttParserUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttParserUtil.java new file mode 100755 index 00000000000..5ec5d802769 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttParserUtil.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.text.webvtt; + +import org.telegram.messenger.exoplayer2.text.SubtitleDecoderException; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility methods for parsing WebVTT data. + */ +public final class WebvttParserUtil { + + private static final Pattern COMMENT = Pattern.compile("^NOTE((\u0020|\u0009).*)?$"); + private static final Pattern HEADER = Pattern.compile("^\uFEFF?WEBVTT((\u0020|\u0009).*)?$"); + + private WebvttParserUtil() {} + + /** + * Reads and validates the first line of a WebVTT file. + * + * @param input The input from which the line should be read. + * @throws SubtitleDecoderException If the line isn't the start of a valid WebVTT file. + */ + public static void validateWebvttHeaderLine(ParsableByteArray input) + throws SubtitleDecoderException { + String line = input.readLine(); + if (line == null || !HEADER.matcher(line).matches()) { + throw new SubtitleDecoderException("Expected WEBVTT. Got " + line); + } + } + + /** + * Parses a WebVTT timestamp. + * + * @param timestamp The timestamp string. + * @return The parsed timestamp in microseconds. + * @throws NumberFormatException If the timestamp could not be parsed. + */ + public static long parseTimestampUs(String timestamp) throws NumberFormatException { + long value = 0; + String[] parts = timestamp.split("\\.", 2); + String[] subparts = parts[0].split(":"); + for (String subpart : subparts) { + value = value * 60 + Long.parseLong(subpart); + } + return (value * 1000 + Long.parseLong(parts[1])) * 1000; + } + + /** + * Parses a percentage string. + * + * @param s The percentage string. + * @return The parsed value, where 1.0 represents 100%. + * @throws NumberFormatException If the percentage could not be parsed. + */ + public static float parsePercentage(String s) throws NumberFormatException { + if (!s.endsWith("%")) { + throw new NumberFormatException("Percentages must end with %"); + } + return Float.parseFloat(s.substring(0, s.length() - 1)) / 100; + } + + /** + * Reads lines up to and including the next WebVTT cue header. + * + * @param input The input from which lines should be read. + * @return A {@link Matcher} for the WebVTT cue header, or null if the end of the input was + * reached without a cue header being found. In the case that a cue header is found, groups 1, + * 2 and 3 of the returned matcher contain the start time, end time and settings list. + */ + public static Matcher findNextCueHeader(ParsableByteArray input) { + String line; + while ((line = input.readLine()) != null) { + if (COMMENT.matcher(line).matches()) { + // Skip until the end of the comment block. + while ((line = input.readLine()) != null && !line.isEmpty()) {} + } else { + Matcher cueHeaderMatcher = WebvttCueParser.CUE_HEADER_PATTERN.matcher(line); + if (cueHeaderMatcher.matches()) { + return cueHeaderMatcher; + } + } + } + return null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttSubtitle.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttSubtitle.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttSubtitle.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttSubtitle.java index fd23536b015..3fc2929d7f1 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/text/webvtt/WebvttSubtitle.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/text/webvtt/WebvttSubtitle.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.text.webvtt; +package org.telegram.messenger.exoplayer2.text.webvtt; import android.text.SpannableStringBuilder; -import org.telegram.messenger.exoplayer.text.Cue; -import org.telegram.messenger.exoplayer.text.Subtitle; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.Subtitle; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -28,7 +29,7 @@ /** * A representation of a WebVTT subtitle. */ -public final class WebvttSubtitle implements Subtitle { +/* package */ final class WebvttSubtitle implements Subtitle { private final List cues; private final int numCues; @@ -55,7 +56,7 @@ public WebvttSubtitle(List cues) { @Override public int getNextEventTimeIndex(long timeUs) { int index = Util.binarySearchCeil(sortedCueTimesUs, timeUs, false, false); - return index < sortedCueTimesUs.length ? index : -1; + return index < sortedCueTimesUs.length ? index : C.INDEX_UNSET; } @Override @@ -70,14 +71,6 @@ public long getEventTime(int index) { return sortedCueTimesUs[index]; } - @Override - public long getLastEventTime() { - if (getEventTimeCount() == 0) { - return -1; - } - return sortedCueTimesUs[sortedCueTimesUs.length - 1]; - } - @Override public List getCues(long timeUs) { ArrayList list = null; @@ -118,7 +111,7 @@ public List getCues(long timeUs) { if (list != null) { return list; } else { - return Collections.emptyList(); + return Collections.emptyList(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java new file mode 100755 index 00000000000..75e7a09f9a7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/AdaptiveVideoTrackSelection.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import org.telegram.messenger.exoplayer2.upstream.BandwidthMeter; +import java.util.List; + +/** + * A bandwidth based adaptive {@link TrackSelection} for video, whose selected track is updated to + * be the one of highest quality given the current network conditions and the state of the buffer. + */ +public class AdaptiveVideoTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link AdaptiveVideoTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final BandwidthMeter bandwidthMeter; + private final int maxInitialBitrate; + private final int minDurationForQualityIncreaseMs; + private final int maxDurationForQualityDecreaseMs; + private final int minDurationToRetainAfterDiscardMs; + private final float bandwidthFraction; + + /** + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + */ + public Factory(BandwidthMeter bandwidthMeter) { + this (bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, + DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, + DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, + DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); + } + + /** + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed + * when a bandwidth estimate is unavailable. + * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for + * the selected track to switch to one of higher quality. + * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for + * the selected track to switch to one of lower quality. + * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher + * quality, the selection may indicate that media already buffered at the lower quality can + * be discarded to speed up the switch. This is the minimum duration of media that must be + * retained at the lower quality. + * @param bandwidthFraction The fraction of the available bandwidth that the selection should + * consider available for use. Setting to a value less than 1 is recommended to account + * for inaccuracies in the bandwidth estimator. + */ + public Factory(BandwidthMeter bandwidthMeter, int maxInitialBitrate, + int minDurationForQualityIncreaseMs, int maxDurationForQualityDecreaseMs, + int minDurationToRetainAfterDiscardMs, float bandwidthFraction) { + this.bandwidthMeter = bandwidthMeter; + this.maxInitialBitrate = maxInitialBitrate; + this.minDurationForQualityIncreaseMs = minDurationForQualityIncreaseMs; + this.maxDurationForQualityDecreaseMs = maxDurationForQualityDecreaseMs; + this.minDurationToRetainAfterDiscardMs = minDurationToRetainAfterDiscardMs; + this.bandwidthFraction = bandwidthFraction; + } + + @Override + public AdaptiveVideoTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + return new AdaptiveVideoTrackSelection(group, tracks, bandwidthMeter, maxInitialBitrate, + minDurationForQualityIncreaseMs, maxDurationForQualityDecreaseMs, + minDurationToRetainAfterDiscardMs, bandwidthFraction); + } + + } + + public static final int DEFAULT_MAX_INITIAL_BITRATE = 800000; + public static final int DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS = 10000; + public static final int DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS = 25000; + public static final int DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS = 25000; + public static final float DEFAULT_BANDWIDTH_FRACTION = 0.75f; + + private final BandwidthMeter bandwidthMeter; + private final int maxInitialBitrate; + private final long minDurationForQualityIncreaseUs; + private final long maxDurationForQualityDecreaseUs; + private final long minDurationToRetainAfterDiscardUs; + private final float bandwidthFraction; + + private int selectedIndex; + private int reason; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + */ + public AdaptiveVideoTrackSelection(TrackGroup group, int[] tracks, + BandwidthMeter bandwidthMeter) { + this (group, tracks, bandwidthMeter, DEFAULT_MAX_INITIAL_BITRATE, + DEFAULT_MIN_DURATION_FOR_QUALITY_INCREASE_MS, + DEFAULT_MAX_DURATION_FOR_QUALITY_DECREASE_MS, + DEFAULT_MIN_DURATION_TO_RETAIN_AFTER_DISCARD_MS, DEFAULT_BANDWIDTH_FRACTION); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param bandwidthMeter Provides an estimate of the currently available bandwidth. + * @param maxInitialBitrate The maximum bitrate in bits per second that should be assumed when a + * bandwidth estimate is unavailable. + * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the + * selected track to switch to one of higher quality. + * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the + * selected track to switch to one of lower quality. + * @param minDurationToRetainAfterDiscardMs When switching to a track of significantly higher + * quality, the selection may indicate that media already buffered at the lower quality can + * be discarded to speed up the switch. This is the minimum duration of media that must be + * retained at the lower quality. + * @param bandwidthFraction The fraction of the available bandwidth that the selection should + * consider available for use. Setting to a value less than 1 is recommended to account + * for inaccuracies in the bandwidth estimator. + */ + public AdaptiveVideoTrackSelection(TrackGroup group, int[] tracks, BandwidthMeter bandwidthMeter, + int maxInitialBitrate, long minDurationForQualityIncreaseMs, + long maxDurationForQualityDecreaseMs, long minDurationToRetainAfterDiscardMs, + float bandwidthFraction) { + super(group, tracks); + this.bandwidthMeter = bandwidthMeter; + this.maxInitialBitrate = maxInitialBitrate; + this.minDurationForQualityIncreaseUs = minDurationForQualityIncreaseMs * 1000L; + this.maxDurationForQualityDecreaseUs = maxDurationForQualityDecreaseMs * 1000L; + this.minDurationToRetainAfterDiscardUs = minDurationToRetainAfterDiscardMs * 1000L; + this.bandwidthFraction = bandwidthFraction; + selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE); + reason = C.SELECTION_REASON_INITIAL; + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + long nowMs = SystemClock.elapsedRealtime(); + // Get the current and ideal selections. + int currentSelectedIndex = selectedIndex; + Format currentFormat = getSelectedFormat(); + int idealSelectedIndex = determineIdealSelectedIndex(nowMs); + Format idealFormat = getFormat(idealSelectedIndex); + // Assume we can switch to the ideal selection. + selectedIndex = idealSelectedIndex; + // Revert back to the current selection if conditions are not suitable for switching. + if (currentFormat != null && !isBlacklisted(selectedIndex, nowMs)) { + if (idealFormat.bitrate > currentFormat.bitrate + && bufferedDurationUs < minDurationForQualityIncreaseUs) { + // The ideal track is a higher quality, but we have insufficient buffer to safely switch + // up. Defer switching up for now. + selectedIndex = currentSelectedIndex; + } else if (idealFormat.bitrate < currentFormat.bitrate + && bufferedDurationUs >= maxDurationForQualityDecreaseUs) { + // The ideal track is a lower quality, but we have sufficient buffer to defer switching + // down for now. + selectedIndex = currentSelectedIndex; + } + } + // If we adapted, update the trigger. + if (selectedIndex != currentSelectedIndex) { + reason = C.SELECTION_REASON_ADAPTIVE; + } + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return reason; + } + + @Override + public Object getSelectionData() { + return null; + } + + @Override + public int evaluateQueueSize(long playbackPositionUs, List queue) { + if (queue.isEmpty()) { + return 0; + } + int queueSize = queue.size(); + long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs; + if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) { + return queueSize; + } + int idealSelectedIndex = determineIdealSelectedIndex(SystemClock.elapsedRealtime()); + Format idealFormat = getFormat(idealSelectedIndex); + // Discard from the first SD chunk beyond minDurationToRetainAfterDiscardUs whose resolution and + // bitrate are both lower than the ideal track. + for (int i = 0; i < queueSize; i++) { + MediaChunk chunk = queue.get(i); + long durationBeforeThisChunkUs = chunk.startTimeUs - playbackPositionUs; + if (durationBeforeThisChunkUs >= minDurationToRetainAfterDiscardUs + && chunk.trackFormat.bitrate < idealFormat.bitrate + && chunk.trackFormat.height < idealFormat.height + && chunk.trackFormat.height < 720 && chunk.trackFormat.width < 1280) { + return i; + } + } + return queueSize; + } + + /** + * Computes the ideal selected index ignoring buffer health. + * + * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}, or + * {@link Long#MIN_VALUE} to ignore blacklisting. + */ + private int determineIdealSelectedIndex(long nowMs) { + long bitrateEstimate = bandwidthMeter.getBitrateEstimate(); + long effectiveBitrate = bitrateEstimate == BandwidthMeter.NO_ESTIMATE + ? maxInitialBitrate : (long) (bitrateEstimate * bandwidthFraction); + int lowestBitrateNonBlacklistedIndex = 0; + for (int i = 0; i < length; i++) { + if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { + Format format = getFormat(i); + if (format.bitrate <= effectiveBitrate) { + return i; + } else { + lowestBitrateNonBlacklistedIndex = i; + } + } + } + return lowestBitrateNonBlacklistedIndex; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/BaseTrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/BaseTrackSelection.java new file mode 100755 index 00000000000..f1352bf7100 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/BaseTrackSelection.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +/** + * An abstract base class suitable for most {@link TrackSelection} implementations. + */ +public abstract class BaseTrackSelection implements TrackSelection { + + /** + * The selected {@link TrackGroup}. + */ + protected final TrackGroup group; + /** + * The number of selected tracks within the {@link TrackGroup}. Always greater than zero. + */ + protected final int length; + /** + * The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth. + */ + protected final int[] tracks; + + /** + * The {@link Format}s of the selected tracks, in order of decreasing bandwidth. + */ + private final Format[] formats; + /** + * Selected track blacklist timestamps, in order of decreasing bandwidth. + */ + private final long[] blacklistUntilTimes; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + */ + public BaseTrackSelection(TrackGroup group, int... tracks) { + Assertions.checkState(tracks.length > 0); + this.group = Assertions.checkNotNull(group); + this.length = tracks.length; + // Set the formats, sorted in order of decreasing bandwidth. + formats = new Format[length]; + for (int i = 0; i < tracks.length; i++) { + formats[i] = group.getFormat(tracks[i]); + } + Arrays.sort(formats, new DecreasingBandwidthComparator()); + // Set the format indices in the same order. + this.tracks = new int[length]; + for (int i = 0; i < length; i++) { + this.tracks[i] = group.indexOf(formats[i]); + } + blacklistUntilTimes = new long[length]; + } + + @Override + public final TrackGroup getTrackGroup() { + return group; + } + + @Override + public final int length() { + return tracks.length; + } + + @Override + public final Format getFormat(int index) { + return formats[index]; + } + + @Override + public final int getIndexInTrackGroup(int index) { + return tracks[index]; + } + + @Override + public final int indexOf(Format format) { + for (int i = 0; i < length; i++) { + if (formats[i] == format) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public final int indexOf(int indexInTrackGroup) { + for (int i = 0; i < length; i++) { + if (tracks[i] == indexInTrackGroup) { + return i; + } + } + return C.INDEX_UNSET; + } + + @Override + public final Format getSelectedFormat() { + return formats[getSelectedIndex()]; + } + + @Override + public final int getSelectedIndexInTrackGroup() { + return tracks[getSelectedIndex()]; + } + + @Override + public int evaluateQueueSize(long playbackPositionUs, List queue) { + return queue.size(); + } + + @Override + public final boolean blacklist(int index, long blacklistDurationMs) { + long nowMs = SystemClock.elapsedRealtime(); + boolean canBlacklist = isBlacklisted(index, nowMs); + for (int i = 0; i < length && !canBlacklist; i++) { + canBlacklist = i != index && !isBlacklisted(i, nowMs); + } + if (!canBlacklist) { + return false; + } + blacklistUntilTimes[index] = Math.max(blacklistUntilTimes[index], nowMs + blacklistDurationMs); + return true; + } + + /** + * Returns whether the track at the specified index in the selection is blaclisted. + * + * @param index The index of the track in the selection. + * @param nowMs The current time in the timebase of {@link SystemClock#elapsedRealtime()}. + */ + protected final boolean isBlacklisted(int index, long nowMs) { + return blacklistUntilTimes[index] > nowMs; + } + + // Object overrides. + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = 31 * System.identityHashCode(group) + Arrays.hashCode(tracks); + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + BaseTrackSelection other = (BaseTrackSelection) obj; + return group == other.group && Arrays.equals(tracks, other.tracks); + } + + /** + * Sorts {@link Format} objects in order of decreasing bandwidth. + */ + private static final class DecreasingBandwidthComparator implements Comparator { + + @Override + public int compare(Format a, Format b) { + return b.bitrate - a.bitrate; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java new file mode 100755 index 00000000000..ade96561708 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/DefaultTrackSelector.java @@ -0,0 +1,772 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.content.Context; +import android.graphics.Point; +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.RendererCapabilities; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A {@link MappingTrackSelector} that allows configuration of common parameters. It is safe to call + * the methods of this class from the application thread. See {@link Parameters#Parameters()} for + * default selection parameters. + */ +public class DefaultTrackSelector extends MappingTrackSelector { + + /** + * Holder for available configurations for the {@link DefaultTrackSelector}. + */ + public static final class Parameters { + + // Audio. + public final String preferredAudioLanguage; + + // Text. + public final String preferredTextLanguage; + + // Video. + public final boolean allowMixedMimeAdaptiveness; + public final boolean allowNonSeamlessAdaptiveness; + public final int maxVideoWidth; + public final int maxVideoHeight; + public final boolean exceedVideoConstraintsIfNecessary; + public final int viewportWidth; + public final int viewportHeight; + public final boolean orientationMayChange; + + /** + * Constructor with default selection parameters: + *

          + *
        • No preferred audio language is set.
        • + *
        • No preferred text language is set.
        • + *
        • Adaptation between different mime types is not allowed.
        • + *
        • Non seamless adaptation is allowed.
        • + *
        • No max limit for video width/height.
        • + *
        • Video constraints are ignored if no supported selection can be made otherwise.
        • + *
        • No viewport width/height constraints are set.
        • + *
        + */ + public Parameters() { + this(null, null, false, true, Integer.MAX_VALUE, Integer.MAX_VALUE, true, Integer.MAX_VALUE, + Integer.MAX_VALUE, true); + } + + /** + * @param preferredAudioLanguage The preferred language for audio, as well as for forced text + * tracks as defined by RFC 5646. {@code null} to select the default track, or first track + * if there's no default. + * @param preferredTextLanguage The preferred language for text tracks as defined by RFC 5646. + * {@code null} to select the default track, or first track if there's no default. + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @param maxVideoWidth Maximum allowed video width. + * @param maxVideoHeight Maximum allowed video height. + * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections + * can be made otherwise. False to force constraints anyway. + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + */ + public Parameters(String preferredAudioLanguage, String preferredTextLanguage, + boolean allowMixedMimeAdaptiveness, boolean allowNonSeamlessAdaptiveness, + int maxVideoWidth, int maxVideoHeight, boolean exceedVideoConstraintsIfNecessary, + int viewportWidth, int viewportHeight, boolean orientationMayChange) { + this.preferredAudioLanguage = preferredAudioLanguage; + this.preferredTextLanguage = preferredTextLanguage; + this.allowMixedMimeAdaptiveness = allowMixedMimeAdaptiveness; + this.allowNonSeamlessAdaptiveness = allowNonSeamlessAdaptiveness; + this.maxVideoWidth = maxVideoWidth; + this.maxVideoHeight = maxVideoHeight; + this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + this.orientationMayChange = orientationMayChange; + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + * + * @param preferredAudioLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or first track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for audio and + * forced text tracks. + */ + public Parameters withPreferredAudioLanguage(String preferredAudioLanguage) { + preferredAudioLanguage = Util.normalizeLanguageCode(preferredAudioLanguage); + if (TextUtils.equals(preferredAudioLanguage, this.preferredAudioLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, maxVideoHeight, + exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided preferred language for text tracks. + * + * @param preferredTextLanguage The preferred language as defined by RFC 5646. {@code null} to + * select the default track, or no track if there's no default. + * @return A {@link Parameters} instance with the provided preferred language for text tracks. + */ + public Parameters withPreferredTextLanguage(String preferredTextLanguage) { + preferredTextLanguage = Util.normalizeLanguageCode(preferredTextLanguage); + if (TextUtils.equals(preferredTextLanguage, this.preferredTextLanguage)) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + * + * @param allowMixedMimeAdaptiveness Whether to allow selections to contain mixed mime types. + * @return A {@link Parameters} instance with the provided mixed mime adaptiveness allowance. + */ + public Parameters withAllowMixedMimeAdaptiveness(boolean allowMixedMimeAdaptiveness) { + if (allowMixedMimeAdaptiveness == this.allowMixedMimeAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided seamless adaptiveness allowance. + * + * @param allowNonSeamlessAdaptiveness Whether non-seamless adaptation is allowed. + * @return A {@link Parameters} instance with the provided seamless adaptiveness allowance. + */ + public Parameters withAllowNonSeamlessAdaptiveness(boolean allowNonSeamlessAdaptiveness) { + if (allowNonSeamlessAdaptiveness == this.allowNonSeamlessAdaptiveness) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided max video size. + * + * @param maxVideoWidth The max video width. + * @param maxVideoHeight The max video width. + * @return A {@link Parameters} instance with the provided max video size. + */ + public Parameters withMaxVideoSize(int maxVideoWidth, int maxVideoHeight) { + if (maxVideoWidth == this.maxVideoWidth && maxVideoHeight == this.maxVideoHeight) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Equivalent to {@code withMaxVideoSize(1279, 719)}. + * + * @return A {@link Parameters} instance with maximum standard definition as maximum video size. + */ + public Parameters withMaxVideoSizeSd() { + return withMaxVideoSize(1279, 719); + } + + /** + * Equivalent to {@code withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. + * + * @return A {@link Parameters} instance without video size constraints. + */ + public Parameters withoutVideoSizeConstraints() { + return withMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + /** + * Returns a {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + * + * @param exceedVideoConstraintsIfNecessary True to ignore video constraints when no selections + * can be made otherwise. False to force constraints anyway. + * @return A {@link Parameters} instance with the provided + * {@code exceedVideoConstraintsIfNecessary} value. + */ + public Parameters withExceedVideoConstraintsIfNecessary( + boolean exceedVideoConstraintsIfNecessary) { + if (exceedVideoConstraintsIfNecessary == this.exceedVideoConstraintsIfNecessary) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance with the provided viewport size. + * + * @param viewportWidth Viewport width in pixels. + * @param viewportHeight Viewport height in pixels. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance with the provided viewport size. + */ + public Parameters withViewportSize(int viewportWidth, int viewportHeight, + boolean orientationMayChange) { + if (viewportWidth == this.viewportWidth && viewportHeight == this.viewportHeight + && orientationMayChange == this.orientationMayChange) { + return this; + } + return new Parameters(preferredAudioLanguage, preferredTextLanguage, + allowMixedMimeAdaptiveness, allowNonSeamlessAdaptiveness, maxVideoWidth, + maxVideoHeight, exceedVideoConstraintsIfNecessary, viewportWidth, viewportHeight, + orientationMayChange); + } + + /** + * Returns a {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + * + * @param context The context to obtain the viewport size from. + * @param orientationMayChange Whether orientation may change during playback. + * @return A {@link Parameters} instance where the viewport size is obtained from the provided + * {@link Context}. + */ + public Parameters withViewportSizeFromContext(Context context, boolean orientationMayChange) { + // Assume the viewport is fullscreen. + Point viewportSize = Util.getPhysicalDisplaySize(context); + return withViewportSize(viewportSize.x, viewportSize.y, orientationMayChange); + } + + /** + * Equivalent to {@code withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true)}. + * + * @return A {@link Parameters} instance without viewport size constraints. + */ + public Parameters withoutViewportSizeConstraints() { + return withViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Parameters other = (Parameters) obj; + return allowMixedMimeAdaptiveness == other.allowMixedMimeAdaptiveness + && allowNonSeamlessAdaptiveness == other.allowNonSeamlessAdaptiveness + && maxVideoWidth == other.maxVideoWidth && maxVideoHeight == other.maxVideoHeight + && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary + && orientationMayChange == other.orientationMayChange + && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight + && TextUtils.equals(preferredAudioLanguage, other.preferredAudioLanguage) + && TextUtils.equals(preferredTextLanguage, other.preferredTextLanguage); + } + + @Override + public int hashCode() { + int result = preferredAudioLanguage.hashCode(); + result = 31 * result + preferredTextLanguage.hashCode(); + result = 31 * result + (allowMixedMimeAdaptiveness ? 1 : 0); + result = 31 * result + (allowNonSeamlessAdaptiveness ? 1 : 0); + result = 31 * result + maxVideoWidth; + result = 31 * result + maxVideoHeight; + result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); + result = 31 * result + (orientationMayChange ? 1 : 0); + result = 31 * result + viewportWidth; + result = 31 * result + viewportHeight; + return result; + } + + } + + /** + * If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the + * corresponding viewport dimension, then the video is considered as filling the viewport (in that + * dimension). + */ + private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; + private static final int[] NO_TRACKS = new int[0]; + + private final TrackSelection.Factory adaptiveVideoTrackSelectionFactory; + private final AtomicReference params; + + /** + * Constructs an instance that does not support adaptive video. + */ + public DefaultTrackSelector() { + this(null); + } + + /** + * Constructs an instance that uses a factory to create adaptive video track selections. + * + * @param adaptiveVideoTrackSelectionFactory A factory for adaptive video {@link TrackSelection}s, + * or null if the selector should not support adaptive video. + */ + public DefaultTrackSelector(TrackSelection.Factory adaptiveVideoTrackSelectionFactory) { + this.adaptiveVideoTrackSelectionFactory = adaptiveVideoTrackSelectionFactory; + params = new AtomicReference<>(new Parameters()); + } + + /** + * Atomically sets the provided parameters for track selection. + * + * @param params The parameters for track selection. + */ + public void setParameters(Parameters params) { + if (!this.params.get().equals(params)) { + this.params.set(Assertions.checkNotNull(params)); + invalidate(); + } + } + + /** + * Gets the current selection parameters. + * + * @return The current selection parameters. + */ + public Parameters getParameters() { + return params.get(); + } + + // MappingTrackSelector implementation. + + @Override + protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + throws ExoPlaybackException { + // Make a track selection for each renderer. + TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCapabilities.length]; + Parameters params = this.params.get(); + for (int i = 0; i < rendererCapabilities.length; i++) { + switch (rendererCapabilities[i].getTrackType()) { + case C.TRACK_TYPE_VIDEO: + rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], + rendererTrackGroupArrays[i], rendererFormatSupports[i], params.maxVideoWidth, + params.maxVideoHeight, params.allowNonSeamlessAdaptiveness, + params.allowMixedMimeAdaptiveness, params.viewportWidth, params.viewportHeight, + params.orientationMayChange, adaptiveVideoTrackSelectionFactory, + params.exceedVideoConstraintsIfNecessary); + break; + case C.TRACK_TYPE_AUDIO: + rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params.preferredAudioLanguage); + break; + case C.TRACK_TYPE_TEXT: + rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], + rendererFormatSupports[i], params.preferredTextLanguage, + params.preferredAudioLanguage); + break; + default: + rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), + rendererTrackGroupArrays[i], rendererFormatSupports[i]); + break; + } + } + return rendererTrackSelections; + } + + // Video track selection implementation. + + protected TrackSelection selectVideoTrack(RendererCapabilities rendererCapabilities, + TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, + boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, + int viewportHeight, boolean orientationMayChange, + TrackSelection.Factory adaptiveVideoTrackSelectionFactory, + boolean exceedConstraintsIfNecessary) throws ExoPlaybackException { + TrackSelection selection = null; + if (adaptiveVideoTrackSelectionFactory != null) { + selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, + maxVideoWidth, maxVideoHeight, allowNonSeamlessAdaptiveness, + allowMixedMimeAdaptiveness, viewportWidth, viewportHeight, + orientationMayChange, adaptiveVideoTrackSelectionFactory); + } + if (selection == null) { + selection = selectFixedVideoTrack(groups, formatSupport, maxVideoWidth, maxVideoHeight, + viewportWidth, viewportHeight, orientationMayChange, exceedConstraintsIfNecessary); + } + return selection; + } + + private static TrackSelection selectAdaptiveVideoTrack(RendererCapabilities rendererCapabilities, + TrackGroupArray groups, int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, + boolean allowNonSeamlessAdaptiveness, boolean allowMixedMimeAdaptiveness, int viewportWidth, + int viewportHeight, boolean orientationMayChange, + TrackSelection.Factory adaptiveVideoTrackSelectionFactory) throws ExoPlaybackException { + int requiredAdaptiveSupport = allowNonSeamlessAdaptiveness + ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) + : RendererCapabilities.ADAPTIVE_SEAMLESS; + boolean allowMixedMimeTypes = allowMixedMimeAdaptiveness + && (rendererCapabilities.supportsMixedMimeTypeAdaptation() & requiredAdaptiveSupport) != 0; + for (int i = 0; i < groups.length; i++) { + TrackGroup group = groups.get(i); + int[] adaptiveTracks = getAdaptiveTracksForGroup(group, formatSupport[i], + allowMixedMimeTypes, requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight, + viewportWidth, viewportHeight, orientationMayChange); + if (adaptiveTracks.length > 0) { + return adaptiveVideoTrackSelectionFactory.createTrackSelection(group, adaptiveTracks); + } + } + return null; + } + + private static int[] getAdaptiveTracksForGroup(TrackGroup group, int[] formatSupport, + boolean allowMixedMimeTypes, int requiredAdaptiveSupport, int maxVideoWidth, + int maxVideoHeight, int viewportWidth, int viewportHeight, boolean orientationMayChange) { + if (group.length < 2) { + return NO_TRACKS; + } + + List selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth, + viewportHeight, orientationMayChange); + if (selectedTrackIndices.size() < 2) { + return NO_TRACKS; + } + + String selectedMimeType = null; + if (!allowMixedMimeTypes) { + // Select the mime type for which we have the most adaptive tracks. + HashSet seenMimeTypes = new HashSet<>(); + int selectedMimeTypeTrackCount = 0; + for (int i = 0; i < selectedTrackIndices.size(); i++) { + int trackIndex = selectedTrackIndices.get(i); + String sampleMimeType = group.getFormat(trackIndex).sampleMimeType; + if (!seenMimeTypes.contains(sampleMimeType)) { + seenMimeTypes.add(sampleMimeType); + int countForMimeType = getAdaptiveTrackCountForMimeType(group, formatSupport, + requiredAdaptiveSupport, sampleMimeType, maxVideoWidth, maxVideoHeight, + selectedTrackIndices); + if (countForMimeType > selectedMimeTypeTrackCount) { + selectedMimeType = sampleMimeType; + selectedMimeTypeTrackCount = countForMimeType; + } + } + } + } + + // Filter by the selected mime type. + filterAdaptiveTrackCountForMimeType(group, formatSupport, requiredAdaptiveSupport, + selectedMimeType, maxVideoWidth, maxVideoHeight, selectedTrackIndices); + + return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); + } + + private static int getAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport, + int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight, + List selectedTrackIndices) { + int adaptiveTrackCount = 0; + for (int i = 0; i < selectedTrackIndices.size(); i++) { + int trackIndex = selectedTrackIndices.get(i); + if (isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, + formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) { + adaptiveTrackCount++; + } + } + return adaptiveTrackCount; + } + + private static void filterAdaptiveTrackCountForMimeType(TrackGroup group, int[] formatSupport, + int requiredAdaptiveSupport, String mimeType, int maxVideoWidth, int maxVideoHeight, + List selectedTrackIndices) { + for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { + int trackIndex = selectedTrackIndices.get(i); + if (!isSupportedAdaptiveVideoTrack(group.getFormat(trackIndex), mimeType, + formatSupport[trackIndex], requiredAdaptiveSupport, maxVideoWidth, maxVideoHeight)) { + selectedTrackIndices.remove(i); + } + } + } + + private static boolean isSupportedAdaptiveVideoTrack(Format format, String mimeType, + int formatSupport, int requiredAdaptiveSupport, int maxVideoWidth, int maxVideoHeight) { + return isSupported(formatSupport) && ((formatSupport & requiredAdaptiveSupport) != 0) + && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)) + && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight); + } + + private static TrackSelection selectFixedVideoTrack(TrackGroupArray groups, + int[][] formatSupport, int maxVideoWidth, int maxVideoHeight, int viewportWidth, + int viewportHeight, boolean orientationMayChange, boolean exceedConstraintsIfNecessary) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedPixelCount = Format.NO_VALUE; + boolean selectedIsWithinConstraints = false; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup group = groups.get(groupIndex); + List selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth, + viewportHeight, orientationMayChange); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex])) { + Format format = group.getFormat(trackIndex); + boolean isWithinConstraints = selectedTrackIndices.contains(trackIndex) + && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) + && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight); + int pixelCount = format.getPixelCount(); + boolean selectTrack; + if (selectedIsWithinConstraints) { + selectTrack = isWithinConstraints + && comparePixelCounts(pixelCount, selectedPixelCount) > 0; + } else { + selectTrack = isWithinConstraints || (exceedConstraintsIfNecessary + && (selectedGroup == null + || comparePixelCounts(pixelCount, selectedPixelCount) < 0)); + } + if (selectTrack) { + selectedGroup = group; + selectedTrackIndex = trackIndex; + selectedPixelCount = pixelCount; + selectedIsWithinConstraints = isWithinConstraints; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + /** + * Compares two pixel counts for order. A known pixel count is considered greater than + * {@link Format#NO_VALUE}. + * + * @param first The first pixel count. + * @param second The second pixel count. + * @return A negative integer if the first pixel count is less than the second. Zero if they are + * equal. A positive integer if the first pixel count is greater than the second. + */ + private static int comparePixelCounts(int first, int second) { + return first == Format.NO_VALUE ? (second == Format.NO_VALUE ? 0 : -1) + : (second == Format.NO_VALUE ? 1 : (first - second)); + } + + + // Audio track selection implementation. + + protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, + String preferredAudioLanguage) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex])) { + Format format = trackGroup.getFormat(trackIndex); + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + int trackScore; + if (formatHasLanguage(format, preferredAudioLanguage)) { + if (isDefault) { + trackScore = 4; + } else { + trackScore = 3; + } + } else if (isDefault) { + trackScore = 2; + } else { + trackScore = 1; + } + if (trackScore > selectedTrackScore) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + // Text track selection implementation. + + protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, + String preferredTextLanguage, String preferredAudioLanguage) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex])) { + Format format = trackGroup.getFormat(trackIndex); + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + boolean isForced = (format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0; + int trackScore; + if (formatHasLanguage(format, preferredTextLanguage)) { + if (isDefault) { + trackScore = 6; + } else if (!isForced) { + // Prefer non-forced to forced if a preferred text language has been specified. Where + // both are provided the non-forced track will usually contain the forced subtitles as + // a subset. + trackScore = 5; + } else { + trackScore = 4; + } + } else if (isDefault) { + trackScore = 3; + } else if (isForced) { + if (formatHasLanguage(format, preferredAudioLanguage)) { + trackScore = 2; + } else { + trackScore = 1; + } + } else { + trackScore = 0; + } + if (trackScore > selectedTrackScore) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + // General track selection methods. + + protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, + int[][] formatSupport) { + TrackGroup selectedGroup = null; + int selectedTrackIndex = 0; + int selectedTrackScore = 0; + for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { + TrackGroup trackGroup = groups.get(groupIndex); + int[] trackFormatSupport = formatSupport[groupIndex]; + for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { + if (isSupported(trackFormatSupport[trackIndex])) { + Format format = trackGroup.getFormat(trackIndex); + boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; + int trackScore = isDefault ? 2 : 1; + if (trackScore > selectedTrackScore) { + selectedGroup = trackGroup; + selectedTrackIndex = trackIndex; + selectedTrackScore = trackScore; + } + } + } + } + return selectedGroup == null ? null + : new FixedTrackSelection(selectedGroup, selectedTrackIndex); + } + + private static boolean isSupported(int formatSupport) { + return (formatSupport & RendererCapabilities.FORMAT_SUPPORT_MASK) + == RendererCapabilities.FORMAT_HANDLED; + } + + private static boolean formatHasLanguage(Format format, String language) { + return language != null && language.equals(Util.normalizeLanguageCode(format.language)); + } + + // Viewport size util methods. + + private static List getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth, + int viewportHeight, boolean orientationMayChange) { + // Initially include all indices. + ArrayList selectedTrackIndices = new ArrayList<>(group.length); + for (int i = 0; i < group.length; i++) { + selectedTrackIndices.add(i); + } + + if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) { + // Viewport dimensions not set. Return the full set of indices. + return selectedTrackIndices; + } + + int maxVideoPixelsToRetain = Integer.MAX_VALUE; + for (int i = 0; i < group.length; i++) { + Format format = group.getFormat(i); + // Keep track of the number of pixels of the selected format whose resolution is the + // smallest to exceed the maximum size at which it can be displayed within the viewport. + // We'll discard formats of higher resolution. + if (format.width > 0 && format.height > 0) { + Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange, + viewportWidth, viewportHeight, format.width, format.height); + int videoPixels = format.width * format.height; + if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN) + && format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN) + && videoPixels < maxVideoPixelsToRetain) { + maxVideoPixelsToRetain = videoPixels; + } + } + } + + // Filter out formats that exceed maxVideoPixelsToRetain. These formats have an unnecessarily + // high resolution given the size at which the video will be displayed within the viewport. Also + // filter out formats with unknown dimensions, since we have some whose dimensions are known. + if (maxVideoPixelsToRetain != Integer.MAX_VALUE) { + for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { + Format format = group.getFormat(selectedTrackIndices.get(i)); + int pixelCount = format.getPixelCount(); + if (pixelCount == Format.NO_VALUE || pixelCount > maxVideoPixelsToRetain) { + selectedTrackIndices.remove(i); + } + } + } + + return selectedTrackIndices; + } + + /** + * Given viewport dimensions and video dimensions, computes the maximum size of the video as it + * will be rendered to fit inside of the viewport. + */ + private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth, + int viewportHeight, int videoWidth, int videoHeight) { + if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) { + // Rotation is allowed, and the video will be larger in the rotated viewport. + int tempViewportWidth = viewportWidth; + viewportWidth = viewportHeight; + viewportHeight = tempViewportWidth; + } + + if (videoWidth * viewportHeight >= videoHeight * viewportWidth) { + // Horizontal letter-boxing along top and bottom. + return new Point(viewportWidth, Util.ceilDivide(viewportWidth * videoHeight, videoWidth)); + } else { + // Vertical letter-boxing along edges. + return new Point(Util.ceilDivide(viewportHeight * videoWidth, videoHeight), viewportHeight); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/FixedTrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/FixedTrackSelection.java new file mode 100755 index 00000000000..166c6731107 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/FixedTrackSelection.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * A {@link TrackSelection} consisting of a single track. + */ +public final class FixedTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link FixedTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final int reason; + private final Object data; + + public Factory() { + this.reason = C.SELECTION_REASON_UNKNOWN; + this.data = null; + } + + /** + * @param reason A reason for the track selection. + * @param data Optional data associated with the track selection. + */ + public Factory(int reason, Object data) { + this.reason = reason; + this.data = data; + } + + @Override + public FixedTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + Assertions.checkArgument(tracks.length == 1); + return new FixedTrackSelection(group, tracks[0], reason, data); + } + + } + + private final int reason; + private final Object data; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param track The index of the selected track within the {@link TrackGroup}. + */ + public FixedTrackSelection(TrackGroup group, int track) { + this(group, track, C.SELECTION_REASON_UNKNOWN, null); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param track The index of the selected track within the {@link TrackGroup}. + * @param reason A reason for the track selection. + * @param data Optional data associated with the track selection. + */ + public FixedTrackSelection(TrackGroup group, int track, int reason, Object data) { + super(group, track); + this.reason = reason; + this.data = data; + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + // Do nothing. + } + + @Override + public int getSelectedIndex() { + return 0; + } + + @Override + public int getSelectionReason() { + return reason; + } + + @Override + public Object getSelectionData() { + return data; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java new file mode 100755 index 00000000000..cff62611abf --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/MappingTrackSelector.java @@ -0,0 +1,600 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.util.Pair; +import android.util.SparseArray; +import android.util.SparseBooleanArray; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.RendererCapabilities; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s + * and renderers, and then from that mapping create a {@link TrackSelection} for each renderer. + */ +public abstract class MappingTrackSelector extends TrackSelector { + + /** + * A track selection override. + */ + public static final class SelectionOverride { + + public final TrackSelection.Factory factory; + public final int groupIndex; + public final int[] tracks; + public final int length; + + /** + * @param factory A factory for creating selections from this override. + * @param groupIndex The overriding group index. + * @param tracks The overriding track indices within the group. + */ + public SelectionOverride(TrackSelection.Factory factory, int groupIndex, int... tracks) { + this.factory = factory; + this.groupIndex = groupIndex; + this.tracks = tracks; + this.length = tracks.length; + } + + /** + * Creates an selection from this override. + * + * @param groups The groups whose selection is being overridden. + * @return The selection. + */ + public TrackSelection createTrackSelection(TrackGroupArray groups) { + return factory.createTrackSelection(groups.get(groupIndex), tracks); + } + + /** + * Returns whether this override contains the specified track index. + */ + public boolean containsTrack(int track) { + for (int overrideTrack : tracks) { + if (overrideTrack == track) { + return true; + } + } + return false; + } + + } + + private final SparseArray> selectionOverrides; + private final SparseBooleanArray rendererDisabledFlags; + + private MappedTrackInfo currentMappedTrackInfo; + + public MappingTrackSelector() { + selectionOverrides = new SparseArray<>(); + rendererDisabledFlags = new SparseBooleanArray(); + } + + /** + * Returns the mapping information associated with the current track selections, or null if no + * selection is currently active. + */ + public final MappedTrackInfo getCurrentMappedTrackInfo() { + return currentMappedTrackInfo; + } + + /** + * Sets whether the renderer at the specified index is disabled. + * + * @param rendererIndex The renderer index. + * @param disabled Whether the renderer is disabled. + */ + public final void setRendererDisabled(int rendererIndex, boolean disabled) { + if (rendererDisabledFlags.get(rendererIndex) == disabled) { + // The disabled flag is unchanged. + return; + } + rendererDisabledFlags.put(rendererIndex, disabled); + invalidate(); + } + + /** + * Returns whether the renderer is disabled. + * + * @param rendererIndex The renderer index. + * @return Whether the renderer is disabled. + */ + public final boolean getRendererDisabled(int rendererIndex) { + return rendererDisabledFlags.get(rendererIndex); + } + + /** + * Overrides the track selection for the renderer at a specified index. + *

        + * When the {@link TrackGroupArray} available to the renderer at the specified index matches the + * one provided, the override is applied. When the {@link TrackGroupArray} does not match, the + * override has no effect. The override replaces any previous override for the renderer and the + * provided {@link TrackGroupArray}. + *

        + * Passing a {@code null} override will explicitly disable the renderer. To remove overrides use + * {@link #clearSelectionOverride(int, TrackGroupArray)}, {@link #clearSelectionOverrides(int)} + * or {@link #clearSelectionOverrides()}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray} for which the override should be applied. + * @param override The override. + */ + // TODO - Don't allow overrides that select unsupported tracks, unless some flag has been + // explicitly set by the user to indicate that they want this. + public final void setSelectionOverride(int rendererIndex, TrackGroupArray groups, + SelectionOverride override) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null) { + overrides = new HashMap<>(); + selectionOverrides.put(rendererIndex, overrides); + } + if (overrides.containsKey(groups) && Util.areEqual(overrides.get(groups), override)) { + // The override is unchanged. + return; + } + overrides.put(groups, override); + invalidate(); + } + + /** + * Returns whether there is an override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray}. + * @return Whether there is an override. + */ + public final boolean hasSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + return overrides != null && overrides.containsKey(groups); + } + + /** + * Returns the override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray}. + * @return The override, or null if no override exists. + */ + public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + return overrides != null ? overrides.get(groups) : null; + } + + /** + * Clears a track selection override for the specified renderer and {@link TrackGroupArray}. + * + * @param rendererIndex The renderer index. + * @param groups The {@link TrackGroupArray} for which the override should be cleared. + */ + public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || !overrides.containsKey(groups)) { + // Nothing to clear. + return; + } + overrides.remove(groups); + if (overrides.isEmpty()) { + selectionOverrides.remove(rendererIndex); + } + invalidate(); + } + + /** + * Clears all track selection override for the specified renderer. + * + * @param rendererIndex The renderer index. + */ + public final void clearSelectionOverrides(int rendererIndex) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || overrides.isEmpty()) { + // Nothing to clear. + return; + } + selectionOverrides.remove(rendererIndex); + invalidate(); + } + + /** + * Clears all track selection overrides. + */ + public final void clearSelectionOverrides() { + if (selectionOverrides.size() == 0) { + // Nothing to clear. + return; + } + selectionOverrides.clear(); + invalidate(); + } + + // TrackSelector implementation. + + @Override + public final Pair selectTracks( + RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) + throws ExoPlaybackException { + // Structures into which data will be written during the selection. The extra item at the end + // of each array is to store data associated with track groups that cannot be associated with + // any renderer. + int[] rendererTrackGroupCounts = new int[rendererCapabilities.length + 1]; + TrackGroup[][] rendererTrackGroups = new TrackGroup[rendererCapabilities.length + 1][]; + int[][][] rendererFormatSupports = new int[rendererCapabilities.length + 1][][]; + for (int i = 0; i < rendererTrackGroups.length; i++) { + rendererTrackGroups[i] = new TrackGroup[trackGroups.length]; + rendererFormatSupports[i] = new int[trackGroups.length][]; + } + + // Determine the extent to which each renderer supports mixed mimeType adaptation. + int[] mixedMimeTypeAdaptationSupport = getMixedMimeTypeAdaptationSupport(rendererCapabilities); + + // Associate each track group to a preferred renderer, and evaluate the support that the + // renderer provides for each track in the group. + for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) { + TrackGroup group = trackGroups.get(groupIndex); + // Associate the group to a preferred renderer. + int rendererIndex = findRenderer(rendererCapabilities, group); + // Evaluate the support that the renderer provides for each track in the group. + int[] rendererFormatSupport = rendererIndex == rendererCapabilities.length + ? new int[group.length] : getFormatSupport(rendererCapabilities[rendererIndex], group); + // Stash the results. + int rendererTrackGroupCount = rendererTrackGroupCounts[rendererIndex]; + rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group; + rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport; + rendererTrackGroupCounts[rendererIndex]++; + } + + // Create a track group array for each renderer, and trim each rendererFormatSupports entry. + TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length]; + int[] rendererTrackTypes = new int[rendererCapabilities.length]; + for (int i = 0; i < rendererCapabilities.length; i++) { + int rendererTrackGroupCount = rendererTrackGroupCounts[i]; + rendererTrackGroupArrays[i] = new TrackGroupArray( + Arrays.copyOf(rendererTrackGroups[i], rendererTrackGroupCount)); + rendererFormatSupports[i] = Arrays.copyOf(rendererFormatSupports[i], rendererTrackGroupCount); + rendererTrackTypes[i] = rendererCapabilities[i].getTrackType(); + } + + // Create a track group array for track groups not associated with a renderer. + int unassociatedTrackGroupCount = rendererTrackGroupCounts[rendererCapabilities.length]; + TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( + rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); + + TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays, + rendererFormatSupports); + + // Apply track disabling and overriding. + for (int i = 0; i < rendererCapabilities.length; i++) { + if (rendererDisabledFlags.get(i)) { + trackSelections[i] = null; + } else { + TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; + Map overrides = selectionOverrides.get(i); + SelectionOverride override = overrides == null ? null : overrides.get(rendererTrackGroup); + if (override != null) { + trackSelections[i] = override.createTrackSelection(rendererTrackGroup); + } + } + } + + // Package up the track information and selections. + MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, + rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, + unassociatedTrackGroupArray); + return Pair.create(new TrackSelectionArray(trackSelections), + mappedTrackInfo); + } + + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; + } + + /** + * Given an array of renderers and a set of {@link TrackGroup}s mapped to each of them, provides a + * {@link TrackSelection} per renderer. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. + * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry + * corresponds to the renderer of equal index in {@code renderers}. + * @param rendererFormatSupports Maps every available track to a specific level of support as + * defined by the renderer {@code FORMAT_*} constants. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ + protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, + TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + throws ExoPlaybackException; + + /** + * Finds the renderer to which the provided {@link TrackGroup} should be associated. + *

        + * A {@link TrackGroup} is associated to a renderer that reports + * {@link RendererCapabilities#FORMAT_HANDLED} support for one or more of the tracks in the group, + * or {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} if no such renderer exists, or + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} if again no such renderer exists. In + * the case that two or more renderers report the same level of support, the renderer with the + * lowest index is associated. + *

        + * If all renderers report {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} for all of the + * tracks in the group, then {@code renderers.length} is returned to indicate that no association + * was made. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers. + * @param group The {@link TrackGroup} whose associated renderer is to be found. + * @return The index of the associated renderer, or {@code renderers.length} if no + * association was made. + * @throws ExoPlaybackException If an error occurs finding a renderer. + */ + private static int findRenderer(RendererCapabilities[] rendererCapabilities, TrackGroup group) + throws ExoPlaybackException { + int bestRendererIndex = rendererCapabilities.length; + int bestSupportLevel = RendererCapabilities.FORMAT_UNSUPPORTED_TYPE; + for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) { + RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex]; + for (int trackIndex = 0; trackIndex < group.length; trackIndex++) { + int trackSupportLevel = rendererCapability.supportsFormat(group.getFormat(trackIndex)); + if (trackSupportLevel > bestSupportLevel) { + bestRendererIndex = rendererIndex; + bestSupportLevel = trackSupportLevel; + if (bestSupportLevel == RendererCapabilities.FORMAT_HANDLED) { + // We can't do better. + return bestRendererIndex; + } + } + } + } + return bestRendererIndex; + } + + /** + * Calls {@link RendererCapabilities#supportsFormat} for each track in the specified + * {@link TrackGroup}, returning the results in an array. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderer. + * @param group The {@link TrackGroup} to evaluate. + * @return An array containing the result of calling + * {@link RendererCapabilities#supportsFormat} for each track in the group. + * @throws ExoPlaybackException If an error occurs determining the format support. + */ + private static int[] getFormatSupport(RendererCapabilities rendererCapabilities, TrackGroup group) + throws ExoPlaybackException { + int[] formatSupport = new int[group.length]; + for (int i = 0; i < group.length; i++) { + formatSupport[i] = rendererCapabilities.supportsFormat(group.getFormat(i)); + } + return formatSupport; + } + + /** + * Calls {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer, + * returning the results in an array. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers. + * @return An array containing the result of calling + * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. + * @throws ExoPlaybackException If an error occurs determining the adaptation support. + */ + private static int[] getMixedMimeTypeAdaptationSupport( + RendererCapabilities[] rendererCapabilities) throws ExoPlaybackException { + int[] mixedMimeTypeAdaptationSupport = new int[rendererCapabilities.length]; + for (int i = 0; i < mixedMimeTypeAdaptationSupport.length; i++) { + mixedMimeTypeAdaptationSupport[i] = rendererCapabilities[i].supportsMixedMimeTypeAdaptation(); + } + return mixedMimeTypeAdaptationSupport; + } + + /** + * Provides track information for each renderer. + */ + public static final class MappedTrackInfo { + + /** + * The renderer does not have any associated tracks. + */ + public static final int RENDERER_SUPPORT_NO_TRACKS = 0; + /** + * The renderer has associated tracks, but cannot play any of them. + */ + public static final int RENDERER_SUPPORT_UNPLAYABLE_TRACKS = 1; + /** + * The renderer has associated tracks, and can play at least one of them. + */ + public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 2; + + /** + * The number of renderers to which tracks are mapped. + */ + public final int length; + + private final int[] rendererTrackTypes; + private final TrackGroupArray[] trackGroups; + private final int[] mixedMimeTypeAdaptiveSupport; + private final int[][][] formatSupport; + private final TrackGroupArray unassociatedTrackGroups; + + /** + * @param rendererTrackTypes The track type supported by each renderer. + * @param trackGroups The {@link TrackGroupArray}s for each renderer. + * @param mixedMimeTypeAdaptiveSupport The result of + * {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer. + * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each + * track, indexed by renderer index, group index and track index (in that order). + * @param unassociatedTrackGroups Contains {@link TrackGroup}s not associated with any renderer. + */ + /* package */ MappedTrackInfo(int[] rendererTrackTypes, + TrackGroupArray[] trackGroups, int[] mixedMimeTypeAdaptiveSupport, + int[][][] formatSupport, TrackGroupArray unassociatedTrackGroups) { + this.rendererTrackTypes = rendererTrackTypes; + this.trackGroups = trackGroups; + this.formatSupport = formatSupport; + this.mixedMimeTypeAdaptiveSupport = mixedMimeTypeAdaptiveSupport; + this.unassociatedTrackGroups = unassociatedTrackGroups; + this.length = trackGroups.length; + } + + /** + * Returns the array of {@link TrackGroup}s associated to the renderer at a specified index. + * + * @param rendererIndex The renderer index. + * @return The corresponding {@link TrackGroup}s. + */ + public TrackGroupArray getTrackGroups(int rendererIndex) { + return trackGroups[rendererIndex]; + } + + /** + * Returns the extent to which a renderer can support playback of the tracks associated to it. + * + * @param rendererIndex The renderer index. + * @return One of {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}, + * {@link #RENDERER_SUPPORT_UNPLAYABLE_TRACKS} and {@link #RENDERER_SUPPORT_NO_TRACKS}. + */ + public int getRendererSupport(int rendererIndex) { + boolean hasTracks = false; + int[][] rendererFormatSupport = formatSupport[rendererIndex]; + for (int i = 0; i < rendererFormatSupport.length; i++) { + for (int j = 0; j < rendererFormatSupport[i].length; j++) { + hasTracks = true; + if ((rendererFormatSupport[i][j] & RendererCapabilities.FORMAT_SUPPORT_MASK) + == RendererCapabilities.FORMAT_HANDLED) { + return RENDERER_SUPPORT_PLAYABLE_TRACKS; + } + } + } + return hasTracks ? RENDERER_SUPPORT_UNPLAYABLE_TRACKS : RENDERER_SUPPORT_NO_TRACKS; + } + + /** + * Returns the extent to which the format of an individual track is supported by the renderer. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group to which the track belongs. + * @param trackIndex The index of the track within the group. + * @return One of {@link RendererCapabilities#FORMAT_HANDLED}, + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}, + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} and + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE}. + */ + public int getTrackFormatSupport(int rendererIndex, int groupIndex, int trackIndex) { + return formatSupport[rendererIndex][groupIndex][trackIndex] + & RendererCapabilities.FORMAT_SUPPORT_MASK; + } + + /** + * Returns the extent to which the renderer supports adaptation between supported tracks in a + * specified {@link TrackGroup}. + *

        + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_HANDLED} are always considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_TYPE} or + * {@link RendererCapabilities#FORMAT_UNSUPPORTED_SUBTYPE} are never considered. + * Tracks for which {@link #getTrackFormatSupport(int, int, int)} returns + * {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES} are considered only if + * {@code includeCapabilitiesExceededTracks} is set to {@code true}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group. + * @param includeCapabilitiesExceededTracks True if formats that exceed the capabilities of the + * renderer should be included when determining support. False otherwise. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, + boolean includeCapabilitiesExceededTracks) { + int trackCount = trackGroups[rendererIndex].get(groupIndex).length; + // Iterate over the tracks in the group, recording the indices of those to consider. + int[] trackIndices = new int[trackCount]; + int trackIndexCount = 0; + for (int i = 0; i < trackCount; i++) { + int fixedSupport = getTrackFormatSupport(rendererIndex, groupIndex, i); + if (fixedSupport == RendererCapabilities.FORMAT_HANDLED + || (includeCapabilitiesExceededTracks + && fixedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES)) { + trackIndices[trackIndexCount++] = i; + } + } + trackIndices = Arrays.copyOf(trackIndices, trackIndexCount); + return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices); + } + + /** + * Returns the extent to which the renderer supports adaptation between specified tracks within + * a {@link TrackGroup}. + * + * @param rendererIndex The renderer index. + * @param groupIndex The index of the group. + * @return One of {@link RendererCapabilities#ADAPTIVE_SEAMLESS}, + * {@link RendererCapabilities#ADAPTIVE_NOT_SEAMLESS} and + * {@link RendererCapabilities#ADAPTIVE_NOT_SUPPORTED}. + */ + public int getAdaptiveSupport(int rendererIndex, int groupIndex, int[] trackIndices) { + int handledTrackCount = 0; + int adaptiveSupport = RendererCapabilities.ADAPTIVE_SEAMLESS; + boolean multipleMimeTypes = false; + String firstSampleMimeType = null; + for (int i = 0; i < trackIndices.length; i++) { + int trackIndex = trackIndices[i]; + String sampleMimeType = trackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex) + .sampleMimeType; + if (handledTrackCount++ == 0) { + firstSampleMimeType = sampleMimeType; + } else { + multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType); + } + adaptiveSupport = Math.min(adaptiveSupport, formatSupport[rendererIndex][groupIndex][i] + & RendererCapabilities.ADAPTIVE_SUPPORT_MASK); + } + return multipleMimeTypes + ? Math.min(adaptiveSupport, mixedMimeTypeAdaptiveSupport[rendererIndex]) + : adaptiveSupport; + } + + /** + * Returns the {@link TrackGroup}s not associated with any renderer. + */ + public TrackGroupArray getUnassociatedTrackGroups() { + return unassociatedTrackGroups; + } + + /** + * Returns true if tracks of the specified type exist and have been associated with renderers, + * but are all unplayable. Returns false in all other cases. + * + * @param trackType The track type. + * @return True if tracks of the specified type exist, if at least one renderer exists that + * handles tracks of the specified type, and if all of the tracks if the specified type are + * unplayable. False in all other cases. + */ + public boolean hasOnlyUnplayableTracks(int trackType) { + int rendererSupport = RENDERER_SUPPORT_NO_TRACKS; + for (int i = 0; i < length; i++) { + if (rendererTrackTypes[i] == trackType) { + rendererSupport = Math.max(rendererSupport, getRendererSupport(i)); + } + } + return rendererSupport == RENDERER_SUPPORT_UNPLAYABLE_TRACKS; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/RandomTrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/RandomTrackSelection.java new file mode 100755 index 00000000000..1480c699bdb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/RandomTrackSelection.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import java.util.Random; + +/** + * A {@link TrackSelection} whose selected track is updated randomly. + */ +public final class RandomTrackSelection extends BaseTrackSelection { + + /** + * Factory for {@link RandomTrackSelection} instances. + */ + public static final class Factory implements TrackSelection.Factory { + + private final Random random; + + public Factory() { + random = new Random(); + } + + /** + * @param seed A seed for the {@link Random} instance used by the factory. + */ + public Factory(int seed) { + random = new Random(seed); + } + + @Override + public RandomTrackSelection createTrackSelection(TrackGroup group, int... tracks) { + return new RandomTrackSelection(group, tracks, random); + } + + } + + private final Random random; + + private int selectedIndex; + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + */ + public RandomTrackSelection(TrackGroup group, int... tracks) { + super(group, tracks); + random = new Random(); + selectedIndex = random.nextInt(length); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param seed A seed for the {@link Random} instance used to update the selected track. + */ + public RandomTrackSelection(TrackGroup group, int[] tracks, long seed) { + this(group, tracks, new Random(seed)); + } + + /** + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @param random A source of random numbers. + */ + public RandomTrackSelection(TrackGroup group, int[] tracks, Random random) { + super(group, tracks); + this.random = random; + selectedIndex = random.nextInt(length); + } + + @Override + public void updateSelectedTrack(long bufferedDurationUs) { + // Count the number of non-blacklisted formats. + long nowMs = SystemClock.elapsedRealtime(); + int nonBlacklistedFormatCount = 0; + for (int i = 0; i < length; i++) { + if (!isBlacklisted(i, nowMs)) { + nonBlacklistedFormatCount++; + } + } + + selectedIndex = random.nextInt(nonBlacklistedFormatCount); + if (nonBlacklistedFormatCount != length) { + // Adjust the format index to account for blacklisted formats. + nonBlacklistedFormatCount = 0; + for (int i = 0; i < length; i++) { + if (!isBlacklisted(i, nowMs) && selectedIndex == nonBlacklistedFormatCount++) { + selectedIndex = i; + return; + } + } + } + } + + @Override + public int getSelectedIndex() { + return selectedIndex; + } + + @Override + public int getSelectionReason() { + return C.SELECTION_REASON_ADAPTIVE; + } + + @Override + public Object getSelectionData() { + return null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelection.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelection.java new file mode 100755 index 00000000000..5798b4fb26a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelection.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.source.TrackGroup; +import org.telegram.messenger.exoplayer2.source.chunk.MediaChunk; +import java.util.List; + +/** + * A track selection consisting of a static subset of selected tracks belonging to a + * {@link TrackGroup}, and a possibly varying individual selected track from the subset. + *

        + * Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual selected + * track may change as a result of calling {@link #updateSelectedTrack(long)}. + */ +public interface TrackSelection { + + /** + * Factory for {@link TrackSelection} instances. + */ + interface Factory { + + /** + * Creates a new selection. + * + * @param group The {@link TrackGroup}. Must not be null. + * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be + * null or empty. May be in any order. + * @return The created selection. + */ + TrackSelection createTrackSelection(TrackGroup group, int... tracks); + + } + + /** + * Returns the {@link TrackGroup} to which the selected tracks belong. + */ + TrackGroup getTrackGroup(); + + // Static subset of selected tracks. + + /** + * Returns the number of tracks in the selection. + */ + int length(); + + /** + * Returns the format of the track at a given index in the selection. + * + * @param index The index in the selection. + * @return The format of the selected track. + */ + Format getFormat(int index); + + /** + * Returns the index in the track group of the track at a given index in the selection. + * + * @param index The index in the selection. + * @return The index of the selected track. + */ + int getIndexInTrackGroup(int index); + + /** + * Returns the index in the selection of the track with the specified format. + * + * @param format The format. + * @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified + * format is not part of the selection. + */ + int indexOf(Format format); + + /** + * Returns the index in the selection of the track with the specified index in the track group. + * + * @param indexInTrackGroup The index in the track group. + * @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified + * index is not part of the selection. + */ + int indexOf(int indexInTrackGroup); + + // Individual selected track. + + /** + * Returns the {@link Format} of the individual selected track. + */ + Format getSelectedFormat(); + + /** + * Returns the index in the track group of the individual selected track. + */ + int getSelectedIndexInTrackGroup(); + + /** + * Returns the index of the selected track. + */ + int getSelectedIndex(); + + /** + * Returns the reason for the current track selection. + */ + int getSelectionReason(); + + /** + * Returns optional data associated with the current track selection. + */ + Object getSelectionData(); + + // Adaptation. + + /** + * Updates the selected track. + * + * @param bufferedDurationUs The duration of media currently buffered in microseconds. + */ + void updateSelectedTrack(long bufferedDurationUs); + + /** + * May be called periodically by sources that load media in discrete {@link MediaChunk}s and + * support discarding of buffered chunks in order to re-buffer using a different selected track. + * Returns the number of chunks that should be retained in the queue. + *

        + * To avoid excessive re-buffering, implementations should normally return the size of the queue. + * An example of a case where a smaller value may be returned is if network conditions have + * improved dramatically, allowing chunks to be discarded and re-buffered in a track of + * significantly higher quality. Discarding chunks may allow faster switching to a higher quality + * track in this case. + * + * @param playbackPositionUs The current playback position in microseconds. + * @param queue The queue of buffered {@link MediaChunk}s. Must not be modified. + * @return The number of chunks to retain in the queue. + */ + int evaluateQueueSize(long playbackPositionUs, List queue); + + /** + * Attempts to blacklist the track at the specified index in the selection, making it ineligible + * for selection by calls to {@link #updateSelectedTrack(long)} for the specified period of time. + * Blacklisting will fail if all other tracks are currently blacklisted. If blacklisting the + * currently selected track, note that it will remain selected until the next call to + * {@link #updateSelectedTrack(long)}. + * + * @param index The index of the track in the selection. + * @param blacklistDurationMs The duration of time for which the track should be blacklisted, in + * milliseconds. + * @return Whether blacklisting was successful. + */ + boolean blacklist(int index, long blacklistDurationMs); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectionArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectionArray.java new file mode 100755 index 00000000000..1c9d46884e9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelectionArray.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import java.util.Arrays; + +/** + * The result of a {@link TrackSelector} operation. + */ +public final class TrackSelectionArray { + + /** + * The number of selections in the result. Greater than or equal to zero. + */ + public final int length; + + private final TrackSelection[] trackSelections; + + // Lazily initialized hashcode. + private int hashCode; + + /** + * @param trackSelections The selections. Must not be null, but may contain null elements. + */ + public TrackSelectionArray(TrackSelection... trackSelections) { + this.trackSelections = trackSelections; + this.length = trackSelections.length; + } + + /** + * Returns the selection at a given index. + * + * @param index The index of the selection. + * @return The selection. + */ + public TrackSelection get(int index) { + return trackSelections[index]; + } + + /** + * Returns the selections in a newly allocated array. + */ + public TrackSelection[] getAll() { + return trackSelections.clone(); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = 17; + result = 31 * result + Arrays.hashCode(trackSelections); + hashCode = result; + } + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + TrackSelectionArray other = (TrackSelectionArray) obj; + return Arrays.equals(trackSelections, other.trackSelections); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java new file mode 100755 index 00000000000..d380907b7fd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/trackselection/TrackSelector.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.trackselection; + +import android.util.Pair; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.RendererCapabilities; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; + +/** Selects tracks to be consumed by available renderers. */ +public abstract class TrackSelector { + + /** + * Notified when previous selections by a {@link TrackSelector} are no longer valid. + */ + public interface InvalidationListener { + + /** + * Called by a {@link TrackSelector} when previous selections are no longer valid. + */ + void onTrackSelectionsInvalidated(); + + } + + private InvalidationListener listener; + + /** + * Initializes the selector. + * + * @param listener A listener for the selector. + */ + public final void init(InvalidationListener listener) { + this.listener = listener; + } + + /** + * Generates {@link TrackSelectionArray} for the renderers. + * + * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which + * {@link TrackSelection}s are to be generated. + * @param trackGroups The available track groups. + * @return The track selections, and an implementation specific object that will be returned to + * the selector via {@link #onSelectionActivated(Object)} should the selections be activated. + * @throws ExoPlaybackException If an error occurs selecting tracks. + */ + public abstract Pair selectTracks( + RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) + throws ExoPlaybackException; + + /** + * Called when {@link TrackSelectionArray} previously generated by + * {@link #selectTracks(RendererCapabilities[], TrackGroupArray)} are activated. + * + * @param info The information associated with the selections, or null if the selected tracks are + * being cleared. + */ + public abstract void onSelectionActivated(Object info); + + /** + * Invalidates all previously generated track selections. + */ + protected final void invalidate() { + if (listener != null) { + listener.onTrackSelectionsInvalidated(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java new file mode 100755 index 00000000000..4e8b8618716 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/AspectRatioFrameLayout.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.ui; + +import android.content.Context; +import android.graphics.Matrix; +import android.support.annotation.IntDef; +import android.util.AttributeSet; +import android.view.TextureView; +import android.view.View; +import android.widget.FrameLayout; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link FrameLayout} that resizes itself to match a specified aspect ratio. + */ +public class AspectRatioFrameLayout extends FrameLayout { + + /** + * Resize modes for {@link AspectRatioFrameLayout}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, RESIZE_MODE_FIXED_HEIGHT, RESIZE_MODE_FILL}) + public @interface ResizeMode {} + + /** + * Either the width or height is decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIT = 0; + /** + * The width is fixed and the height is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_WIDTH = 1; + /** + * The height is fixed and the width is increased or decreased to obtain the desired aspect ratio. + */ + public static final int RESIZE_MODE_FIXED_HEIGHT = 2; + /** + * The specified aspect ratio is ignored. + */ + public static final int RESIZE_MODE_FILL = 3; + + /** + * The {@link FrameLayout} will not resize itself if the fractional difference between its natural + * aspect ratio and the requested aspect ratio falls below this threshold. + *

        + * This tolerance allows the view to occupy the whole of the screen when the requested aspect + * ratio is very close, but not exactly equal to, the aspect ratio of the screen. This may reduce + * the number of view layers that need to be composited by the underlying system, which can help + * to reduce power consumption. + */ + private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; + + private float videoAspectRatio; + private int resizeMode; + private int rotation; + private Matrix matrix = new Matrix(); + + public AspectRatioFrameLayout(Context context) { + this(context, null); + } + + public AspectRatioFrameLayout(Context context, AttributeSet attrs) { + super(context, attrs); + resizeMode = RESIZE_MODE_FIT; + } + + /** + * Set the aspect ratio that this view should satisfy. + * + * @param widthHeightRatio The width to height ratio. + */ + public void setAspectRatio(float widthHeightRatio, int rotation) { + if (this.videoAspectRatio != widthHeightRatio || this.rotation != rotation) { + this.videoAspectRatio = widthHeightRatio; + this.rotation = rotation; + requestLayout(); + } + } + + public float getAspectRatio() { + return videoAspectRatio; + } + + public int getVideoRotation() { + return rotation; + } + + /** + * Sets the resize mode. + * + * @param resizeMode The resize mode. + */ + public void setResizeMode(@ResizeMode int resizeMode) { + if (this.resizeMode != resizeMode) { + this.resizeMode = resizeMode; + requestLayout(); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (resizeMode == RESIZE_MODE_FILL || videoAspectRatio <= 0) { + // Aspect ratio not set. + return; + } + + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + float viewAspectRatio = (float) width / height; + float aspectDeformation = videoAspectRatio / viewAspectRatio - 1; + if (Math.abs(aspectDeformation) <= MAX_ASPECT_RATIO_DEFORMATION_FRACTION) { + // We're within the allowed tolerance. + return; + } + + switch (resizeMode) { + case RESIZE_MODE_FIXED_WIDTH: + height = (int) (width / videoAspectRatio); + break; + case RESIZE_MODE_FIXED_HEIGHT: + width = (int) (height * videoAspectRatio); + break; + default: + if (aspectDeformation > 0) { + height = (int) (width / videoAspectRatio); + } else { + width = (int) (height * videoAspectRatio); + } + break; + } + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + if (child instanceof TextureView) { + matrix.reset(); + int px = getWidth() / 2; + int py = getHeight() / 2; + matrix.postRotate(rotation, px, py); + if (rotation == 90 || rotation == 270) { + float ratio = (float) getHeight() / getWidth(); + matrix.postScale(1 / ratio, ratio, px, py); + } + ((TextureView) child).setTransform(matrix); + break; + } + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java new file mode 100755 index 00000000000..7b113d9f9a1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/DebugTextViewHelper.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.ui; + +import android.widget.TextView; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.SimpleExoPlayer; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; + +/** + * A helper class for periodically updating a {@link TextView} with debug information obtained from + * a {@link SimpleExoPlayer}. + */ +public final class DebugTextViewHelper implements Runnable, ExoPlayer.EventListener { + + private static final int REFRESH_INTERVAL_MS = 1000; + + private final SimpleExoPlayer player; + private final TextView textView; + + private boolean started; + + /** + * @param player The {@link SimpleExoPlayer} from which debug information should be obtained. + * @param textView The {@link TextView} that should be updated to display the information. + */ + public DebugTextViewHelper(SimpleExoPlayer player, TextView textView) { + this.player = player; + this.textView = textView; + } + + /** + * Starts periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void start() { + if (started) { + return; + } + started = true; + player.addListener(this); + updateAndPost(); + } + + /** + * Stops periodic updates of the {@link TextView}. Must be called from the application's main + * thread. + */ + public void stop() { + if (!started) { + return; + } + started = false; + player.removeListener(this); + textView.removeCallbacks(this); + } + + // ExoPlayer.EventListener implementation. + + @Override + public void onLoadingChanged(boolean isLoading) { + // Do nothing. + } + + @Override + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + updateAndPost(); + } + + @Override + public void onPositionDiscontinuity() { + updateAndPost(); + } + + @Override + public void onTimelineChanged(Timeline timeline, Object manifest) { + // Do nothing. + } + + @Override + public void onPlayerError(ExoPlaybackException error) { + // Do nothing. + } + + @Override + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { + // Do nothing. + } + + // Runnable implementation. + + @Override + public void run() { + updateAndPost(); + } + + // Private methods. + + private void updateAndPost() { + textView.setText(getPlayerStateString() + getPlayerWindowIndexString() + getVideoString() + + getAudioString()); + textView.removeCallbacks(this); + textView.postDelayed(this, REFRESH_INTERVAL_MS); + } + + private String getPlayerStateString() { + String text = "playWhenReady:" + player.getPlayWhenReady() + " playbackState:"; + switch (player.getPlaybackState()) { + case ExoPlayer.STATE_BUFFERING: + text += "buffering"; + break; + case ExoPlayer.STATE_ENDED: + text += "ended"; + break; + case ExoPlayer.STATE_IDLE: + text += "idle"; + break; + case ExoPlayer.STATE_READY: + text += "ready"; + break; + default: + text += "unknown"; + break; + } + return text; + } + + private String getPlayerWindowIndexString() { + return " window:" + player.getCurrentWindowIndex(); + } + + private String getVideoString() { + Format format = player.getVideoFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " r:" + format.width + "x" + + format.height + getDecoderCountersBufferCountString(player.getVideoDecoderCounters()) + + ")"; + } + + private String getAudioString() { + Format format = player.getAudioFormat(); + if (format == null) { + return ""; + } + return "\n" + format.sampleMimeType + "(id:" + format.id + " hz:" + format.sampleRate + " ch:" + + format.channelCount + + getDecoderCountersBufferCountString(player.getAudioDecoderCounters()) + ")"; + } + + private static String getDecoderCountersBufferCountString(DecoderCounters counters) { + if (counters == null) { + return ""; + } + counters.ensureUpdated(); + return " rb:" + counters.renderedOutputBufferCount + + " sb:" + counters.skippedOutputBufferCount + + " db:" + counters.droppedOutputBufferCount + + " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java new file mode 100755 index 00000000000..673adbcebbc --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitlePainter.java @@ -0,0 +1,357 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.ui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Join; +import android.graphics.Paint.Style; +import android.graphics.RectF; +import android.text.Layout.Alignment; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import org.telegram.messenger.exoplayer2.text.CaptionStyleCompat; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.util.Util; + +/** + * Paints subtitle {@link Cue}s. + */ +/* package */ final class SubtitlePainter { + + private static final String TAG = "SubtitlePainter"; + + /** + * Ratio of inner padding to font size. + */ + private static final float INNER_PADDING_RATIO = 0.125f; + + /** + * Temporary rectangle used for computing line bounds. + */ + private final RectF lineBounds = new RectF(); + + // Styled dimensions. + private final float cornerRadius; + private final float outlineWidth; + private final float shadowRadius; + private final float shadowOffset; + private final float spacingMult; + private final float spacingAdd; + + private final TextPaint textPaint; + private final Paint paint; + + // Previous input variables. + private CharSequence cueText; + private Alignment cueTextAlignment; + private float cueLine; + @Cue.LineType + private int cueLineType; + @Cue.AnchorType + private int cueLineAnchor; + private float cuePosition; + @Cue.AnchorType + private int cuePositionAnchor; + private float cueSize; + private boolean applyEmbeddedStyles; + private int foregroundColor; + private int backgroundColor; + private int windowColor; + private int edgeColor; + @CaptionStyleCompat.EdgeType + private int edgeType; + private float textSizePx; + private float bottomPaddingFraction; + private int parentLeft; + private int parentTop; + private int parentRight; + private int parentBottom; + + // Derived drawing variables. + private StaticLayout textLayout; + private int textLeft; + private int textTop; + private int textPaddingX; + + @SuppressWarnings("ResourceType") + public SubtitlePainter(Context context) { + int[] viewAttr = {android.R.attr.lineSpacingExtra, android.R.attr.lineSpacingMultiplier}; + TypedArray styledAttributes = context.obtainStyledAttributes(null, viewAttr, 0, 0); + spacingAdd = styledAttributes.getDimensionPixelSize(0, 0); + spacingMult = styledAttributes.getFloat(1, 1); + styledAttributes.recycle(); + + Resources resources = context.getResources(); + DisplayMetrics displayMetrics = resources.getDisplayMetrics(); + int twoDpInPx = Math.round((2f * displayMetrics.densityDpi) / DisplayMetrics.DENSITY_DEFAULT); + cornerRadius = twoDpInPx; + outlineWidth = twoDpInPx; + shadowRadius = twoDpInPx; + shadowOffset = twoDpInPx; + + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setSubpixelText(true); + + paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Style.FILL); + } + + /** + * Draws the provided {@link Cue} into a canvas with the specified styling. + *

        + * A call to this method is able to use cached results of calculations made during the previous + * call, and so an instance of this class is able to optimize repeated calls to this method in + * which the same parameters are passed. + * + * @param cue The cue to draw. + * @param applyEmbeddedStyles Whether styling embedded within the cue should be applied. + * @param style The style to use when drawing the cue text. + * @param textSizePx The text size to use when drawing the cue text, in pixels. + * @param bottomPaddingFraction The bottom padding fraction to apply when {@link Cue#line} is + * {@link Cue#DIMEN_UNSET}, as a fraction of the viewport height + * @param canvas The canvas into which to draw. + * @param cueBoxLeft The left position of the enclosing cue box. + * @param cueBoxTop The top position of the enclosing cue box. + * @param cueBoxRight The right position of the enclosing cue box. + * @param cueBoxBottom The bottom position of the enclosing cue box. + */ + public void draw(Cue cue, boolean applyEmbeddedStyles, CaptionStyleCompat style, float textSizePx, + float bottomPaddingFraction, Canvas canvas, int cueBoxLeft, int cueBoxTop, int cueBoxRight, + int cueBoxBottom) { + CharSequence cueText = cue.text; + if (TextUtils.isEmpty(cueText)) { + // Nothing to draw. + return; + } + if (!applyEmbeddedStyles) { + // Strip out any embedded styling. + cueText = cueText.toString(); + } + if (areCharSequencesEqual(this.cueText, cueText) + && Util.areEqual(this.cueTextAlignment, cue.textAlignment) + && this.cueLine == cue.line + && this.cueLineType == cue.lineType + && Util.areEqual(this.cueLineAnchor, cue.lineAnchor) + && this.cuePosition == cue.position + && Util.areEqual(this.cuePositionAnchor, cue.positionAnchor) + && this.cueSize == cue.size + && this.applyEmbeddedStyles == applyEmbeddedStyles + && this.foregroundColor == style.foregroundColor + && this.backgroundColor == style.backgroundColor + && this.windowColor == style.windowColor + && this.edgeType == style.edgeType + && this.edgeColor == style.edgeColor + && Util.areEqual(this.textPaint.getTypeface(), style.typeface) + && this.textSizePx == textSizePx + && this.bottomPaddingFraction == bottomPaddingFraction + && this.parentLeft == cueBoxLeft + && this.parentTop == cueBoxTop + && this.parentRight == cueBoxRight + && this.parentBottom == cueBoxBottom) { + // We can use the cached layout. + drawLayout(canvas); + return; + } + + this.cueText = cueText; + this.cueTextAlignment = cue.textAlignment; + this.cueLine = cue.line; + this.cueLineType = cue.lineType; + this.cueLineAnchor = cue.lineAnchor; + this.cuePosition = cue.position; + this.cuePositionAnchor = cue.positionAnchor; + this.cueSize = cue.size; + this.applyEmbeddedStyles = applyEmbeddedStyles; + this.foregroundColor = style.foregroundColor; + this.backgroundColor = style.backgroundColor; + this.windowColor = style.windowColor; + this.edgeType = style.edgeType; + this.edgeColor = style.edgeColor; + this.textPaint.setTypeface(style.typeface); + this.textSizePx = textSizePx; + this.bottomPaddingFraction = bottomPaddingFraction; + this.parentLeft = cueBoxLeft; + this.parentTop = cueBoxTop; + this.parentRight = cueBoxRight; + this.parentBottom = cueBoxBottom; + + int parentWidth = parentRight - parentLeft; + int parentHeight = parentBottom - parentTop; + + textPaint.setTextSize(textSizePx); + int textPaddingX = (int) (textSizePx * INNER_PADDING_RATIO + 0.5f); + + int availableWidth = parentWidth - textPaddingX * 2; + if (cueSize != Cue.DIMEN_UNSET) { + availableWidth = (int) (availableWidth * cueSize); + } + if (availableWidth <= 0) { + Log.w(TAG, "Skipped drawing subtitle cue (insufficient space)"); + return; + } + + Alignment textAlignment = cueTextAlignment == null ? Alignment.ALIGN_CENTER : cueTextAlignment; + textLayout = new StaticLayout(cueText, textPaint, availableWidth, textAlignment, spacingMult, + spacingAdd, true); + int textHeight = textLayout.getHeight(); + int textWidth = 0; + int lineCount = textLayout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + textWidth = Math.max((int) Math.ceil(textLayout.getLineWidth(i)), textWidth); + } + if (cueSize != Cue.DIMEN_UNSET && textWidth < availableWidth) { + textWidth = availableWidth; + } + textWidth += textPaddingX * 2; + + int textLeft; + int textRight; + if (cuePosition != Cue.DIMEN_UNSET) { + int anchorPosition = Math.round(parentWidth * cuePosition) + parentLeft; + textLeft = cuePositionAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textWidth + : cuePositionAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textWidth) / 2 + : anchorPosition; + textLeft = Math.max(textLeft, parentLeft); + textRight = Math.min(textLeft + textWidth, parentRight); + } else { + textLeft = (parentWidth - textWidth) / 2; + textRight = textLeft + textWidth; + } + + int textTop; + if (cueLine != Cue.DIMEN_UNSET) { + int anchorPosition; + if (cueLineType == Cue.LINE_TYPE_FRACTION) { + anchorPosition = Math.round(parentHeight * cueLine) + parentTop; + } else { + // cueLineType == Cue.LINE_TYPE_NUMBER + int firstLineHeight = textLayout.getLineBottom(0) - textLayout.getLineTop(0); + if (cueLine >= 0) { + anchorPosition = Math.round(cueLine * firstLineHeight) + parentTop; + } else { + anchorPosition = Math.round(cueLine * firstLineHeight) + parentBottom; + } + } + textTop = cueLineAnchor == Cue.ANCHOR_TYPE_END ? anchorPosition - textHeight + : cueLineAnchor == Cue.ANCHOR_TYPE_MIDDLE ? (anchorPosition * 2 - textHeight) / 2 + : anchorPosition; + if (textTop + textHeight > parentBottom) { + textTop = parentBottom - textHeight; + } else if (textTop < parentTop) { + textTop = parentTop; + } + } else { + textTop = parentBottom - textHeight - (int) (parentHeight * bottomPaddingFraction); + } + + textWidth = textRight - textLeft; + + // Update the derived drawing variables. + this.textLayout = new StaticLayout(cueText, textPaint, textWidth, textAlignment, spacingMult, + spacingAdd, true); + this.textLeft = textLeft; + this.textTop = textTop; + this.textPaddingX = textPaddingX; + + drawLayout(canvas); + } + + /** + * Draws {@link #textLayout} into the provided canvas. + * + * @param canvas The canvas into which to draw. + */ + private void drawLayout(Canvas canvas) { + final StaticLayout layout = textLayout; + if (layout == null) { + // Nothing to draw. + return; + } + + int saveCount = canvas.save(); + canvas.translate(textLeft, textTop); + + if (Color.alpha(windowColor) > 0) { + paint.setColor(windowColor); + canvas.drawRect(-textPaddingX, 0, layout.getWidth() + textPaddingX, layout.getHeight(), + paint); + } + + if (Color.alpha(backgroundColor) > 0) { + paint.setColor(backgroundColor); + float previousBottom = layout.getLineTop(0); + int lineCount = layout.getLineCount(); + for (int i = 0; i < lineCount; i++) { + lineBounds.left = layout.getLineLeft(i) - textPaddingX; + lineBounds.right = layout.getLineRight(i) + textPaddingX; + lineBounds.top = previousBottom; + lineBounds.bottom = layout.getLineBottom(i); + previousBottom = lineBounds.bottom; + canvas.drawRoundRect(lineBounds, cornerRadius, cornerRadius, paint); + } + } + + if (edgeType == CaptionStyleCompat.EDGE_TYPE_OUTLINE) { + textPaint.setStrokeJoin(Join.ROUND); + textPaint.setStrokeWidth(outlineWidth); + textPaint.setColor(edgeColor); + textPaint.setStyle(Style.FILL_AND_STROKE); + layout.draw(canvas); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW) { + textPaint.setShadowLayer(shadowRadius, shadowOffset, shadowOffset, edgeColor); + } else if (edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED + || edgeType == CaptionStyleCompat.EDGE_TYPE_DEPRESSED) { + boolean raised = edgeType == CaptionStyleCompat.EDGE_TYPE_RAISED; + int colorUp = raised ? Color.WHITE : edgeColor; + int colorDown = raised ? edgeColor : Color.WHITE; + float offset = shadowRadius / 2f; + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + textPaint.setShadowLayer(shadowRadius, -offset, -offset, colorUp); + layout.draw(canvas); + textPaint.setShadowLayer(shadowRadius, offset, offset, colorDown); + } + + textPaint.setColor(foregroundColor); + textPaint.setStyle(Style.FILL); + layout.draw(canvas); + textPaint.setShadowLayer(0, 0, 0, 0); + + canvas.restoreToCount(saveCount); + } + + /** + * This method is used instead of {@link TextUtils#equals(CharSequence, CharSequence)} because the + * latter only checks the text of each sequence, and does not check for equality of styling that + * may be embedded within the {@link CharSequence}s. + */ + private static boolean areCharSequencesEqual(CharSequence first, CharSequence second) { + // Some CharSequence implementations don't perform a cheap referential equality check in their + // equals methods, so we perform one explicitly here. + return first == second || (first != null && first.equals(second)); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitleView.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitleView.java new file mode 100755 index 00000000000..ee7ae0813f9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/ui/SubtitleView.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.View; +import android.view.accessibility.CaptioningManager; +import org.telegram.messenger.exoplayer2.text.CaptionStyleCompat; +import org.telegram.messenger.exoplayer2.text.Cue; +import org.telegram.messenger.exoplayer2.text.TextRenderer; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.ArrayList; +import java.util.List; + +/** + * A view for displaying subtitle {@link Cue}s. + */ +public final class SubtitleView extends View implements TextRenderer.Output { + + /** + * The default fractional text size. + * + * @see #setFractionalTextSize(float, boolean) + */ + public static final float DEFAULT_TEXT_SIZE_FRACTION = 0.0533f; + + /** + * The default bottom padding to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, as a + * fraction of the viewport height. + * + * @see #setBottomPaddingFraction(float) + */ + public static final float DEFAULT_BOTTOM_PADDING_FRACTION = 0.08f; + + private static final int FRACTIONAL = 0; + private static final int FRACTIONAL_IGNORE_PADDING = 1; + private static final int ABSOLUTE = 2; + + private final List painters; + + private List cues; + private int textSizeType; + private float textSize; + private boolean applyEmbeddedStyles; + private CaptionStyleCompat style; + private float bottomPaddingFraction; + + public SubtitleView(Context context) { + this(context, null); + } + + public SubtitleView(Context context, AttributeSet attrs) { + super(context, attrs); + painters = new ArrayList<>(); + textSizeType = FRACTIONAL; + textSize = DEFAULT_TEXT_SIZE_FRACTION; + applyEmbeddedStyles = true; + style = CaptionStyleCompat.DEFAULT; + bottomPaddingFraction = DEFAULT_BOTTOM_PADDING_FRACTION; + } + + @Override + public void onCues(List cues) { + setCues(cues); + } + + /** + * Sets the cues to be displayed by the view. + * + * @param cues The cues to display. + */ + public void setCues(List cues) { + if (this.cues == cues) { + return; + } + this.cues = cues; + // Ensure we have sufficient painters. + int cueCount = (cues == null) ? 0 : cues.size(); + while (painters.size() < cueCount) { + painters.add(new SubtitlePainter(getContext())); + } + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Set the text size to a given unit and value. + *

        + * See {@link TypedValue} for the possible dimension units. + * + * @param unit The desired dimension unit. + * @param size The desired size in the given units. + */ + public void setFixedTextSize(int unit, float size) { + Context context = getContext(); + Resources resources; + if (context == null) { + resources = Resources.getSystem(); + } else { + resources = context.getResources(); + } + setTextSize(ABSOLUTE, TypedValue.applyDimension(unit, size, resources.getDisplayMetrics())); + } + + /** + * Sets the text size to one derived from {@link CaptioningManager#getFontScale()}, or to a + * default size before API level 19. + */ + public void setUserDefaultTextSize() { + float fontScale = Util.SDK_INT >= 19 && !isInEditMode() ? getUserCaptionFontScaleV19() : 1f; + setFractionalTextSize(DEFAULT_TEXT_SIZE_FRACTION * fontScale); + } + + /** + * Sets the text size to be a fraction of the view's remaining height after its top and bottom + * padding have been subtracted. + *

        + * Equivalent to {@code #setFractionalTextSize(fractionOfHeight, false)}. + * + * @param fractionOfHeight A fraction between 0 and 1. + */ + public void setFractionalTextSize(float fractionOfHeight) { + setFractionalTextSize(fractionOfHeight, false); + } + + /** + * Sets the text size to be a fraction of the height of this view. + * + * @param fractionOfHeight A fraction between 0 and 1. + * @param ignorePadding Set to true if {@code fractionOfHeight} should be interpreted as a + * fraction of this view's height ignoring any top and bottom padding. Set to false if + * {@code fractionOfHeight} should be interpreted as a fraction of this view's remaining + * height after the top and bottom padding has been subtracted. + */ + public void setFractionalTextSize(float fractionOfHeight, boolean ignorePadding) { + setTextSize(ignorePadding ? FRACTIONAL_IGNORE_PADDING : FRACTIONAL, fractionOfHeight); + } + + private void setTextSize(int textSizeType, float textSize) { + if (this.textSizeType == textSizeType && this.textSize == textSize) { + return; + } + this.textSizeType = textSizeType; + this.textSize = textSize; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets whether styling embedded within the cues should be applied. Enabled by default. + * + * @param applyEmbeddedStyles Whether styling embedded within the cues should be applied. + */ + public void setApplyEmbeddedStyles(boolean applyEmbeddedStyles) { + if (this.applyEmbeddedStyles == applyEmbeddedStyles) { + return; + } + this.applyEmbeddedStyles = applyEmbeddedStyles; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the caption style to be equivalent to the one returned by + * {@link CaptioningManager#getUserStyle()}, or to a default style before API level 19. + */ + public void setUserDefaultStyle() { + setStyle(Util.SDK_INT >= 19 && !isInEditMode() + ? getUserCaptionStyleV19() : CaptionStyleCompat.DEFAULT); + } + + /** + * Sets the caption style. + * + * @param style A style for the view. + */ + public void setStyle(CaptionStyleCompat style) { + if (this.style == style) { + return; + } + this.style = style; + // Invalidate to trigger drawing. + invalidate(); + } + + /** + * Sets the bottom padding fraction to apply when {@link Cue#line} is {@link Cue#DIMEN_UNSET}, + * as a fraction of the view's remaining height after its top and bottom padding have been + * subtracted. + *

        + * Note that this padding is applied in addition to any standard view padding. + * + * @param bottomPaddingFraction The bottom padding fraction. + */ + public void setBottomPaddingFraction(float bottomPaddingFraction) { + if (this.bottomPaddingFraction == bottomPaddingFraction) { + return; + } + this.bottomPaddingFraction = bottomPaddingFraction; + // Invalidate to trigger drawing. + invalidate(); + } + + @Override + public void dispatchDraw(Canvas canvas) { + int cueCount = (cues == null) ? 0 : cues.size(); + int rawTop = getTop(); + int rawBottom = getBottom(); + + // Calculate the bounds after padding is taken into account. + int left = getLeft() + getPaddingLeft(); + int top = rawTop + getPaddingTop(); + int right = getRight() + getPaddingRight(); + int bottom = rawBottom - getPaddingBottom(); + if (bottom <= top || right <= left) { + // No space to draw subtitles. + return; + } + + float textSizePx = textSizeType == ABSOLUTE ? textSize + : textSize * (textSizeType == FRACTIONAL ? (bottom - top) : (rawBottom - rawTop)); + if (textSizePx <= 0) { + // Text has no height. + return; + } + + for (int i = 0; i < cueCount; i++) { + painters.get(i).draw(cues.get(i), applyEmbeddedStyles, style, textSizePx, + bottomPaddingFraction, canvas, left, top, right, bottom); + } + } + + @TargetApi(19) + private float getUserCaptionFontScaleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return captioningManager.getFontScale(); + } + + @TargetApi(19) + private CaptionStyleCompat getUserCaptionStyleV19() { + CaptioningManager captioningManager = + (CaptioningManager) getContext().getSystemService(Context.CAPTIONING_SERVICE); + return CaptionStyleCompat.createFromCaptionStyle(captioningManager.getUserStyle()); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocation.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java similarity index 91% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocation.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java index 0d21c9a2bf0..4f44cb896d3 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/Allocation.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; /** * An allocation within a byte array. @@ -24,7 +24,7 @@ public final class Allocation { /** - * The array containing the allocated space. The allocated space may not be at the start of the + * The array containing the allocated space. The allocated space might not be at the start of the * array, and so {@link #translateOffset(int)} method must be used when indexing into it. */ public final byte[] data; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocator.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocator.java new file mode 100755 index 00000000000..3162b571e62 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Allocator.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +/** + * A source of allocations. + */ +public interface Allocator { + + /** + * Obtain an {@link Allocation}. + *

        + * When the caller has finished with the {@link Allocation}, it should be returned by calling + * {@link #release(Allocation)}. + * + * @return The {@link Allocation}. + */ + Allocation allocate(); + + /** + * Releases an {@link Allocation} back to the allocator. + * + * @param allocation The {@link Allocation} being released. + */ + void release(Allocation allocation); + + /** + * Releases an array of {@link Allocation}s back to the allocator. + * + * @param allocations The array of {@link Allocation}s being released. + */ + void release(Allocation[] allocations); + + /** + * Hints to the allocator that it should make a best effort to release any excess + * {@link Allocation}s. + */ + void trim(); + + /** + * Returns the total number of bytes currently allocated. + */ + int getTotalBytesAllocated(); + + /** + * Returns the length of each individual {@link Allocation}. + */ + int getIndividualAllocationLength(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/AssetDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/AssetDataSource.java new file mode 100755 index 00000000000..69bc1b852b3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/AssetDataSource.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.content.Context; +import android.content.res.AssetManager; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading from a local asset. + */ +public final class AssetDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading a local asset. + */ + public static final class AssetDataSourceException extends IOException { + + public AssetDataSourceException(IOException cause) { + super(cause); + } + + } + + private final AssetManager assetManager; + private final TransferListener listener; + + private Uri uri; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public AssetDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public AssetDataSource(Context context, TransferListener listener) { + this.assetManager = context.getAssets(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws AssetDataSourceException { + try { + uri = dataSpec.uri; + String path = uri.getPath(); + if (path.startsWith("/android_asset/")) { + path = path.substring(15); + } else if (path.startsWith("/")) { + path = path.substring(1); + } + inputStream = assetManager.open(path, AssetManager.ACCESS_RANDOM); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // assetManager.open() returns an AssetInputStream, whose skip() implementation only skips + // fewer bytes than requested if the skip is beyond the end of the asset's data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = inputStream.available(); + if (bytesRemaining == Integer.MAX_VALUE) { + // assetManager.open() returns an AssetInputStream, whose available() implementation + // returns Integer.MAX_VALUE if the remaining length is greater than (or equal to) + // Integer.MAX_VALUE. We don't know the true length in this case, so treat as unbounded. + bytesRemaining = C.LENGTH_UNSET; + } + } + } catch (IOException e) { + throw new AssetDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new AssetDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new AssetDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws AssetDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new AssetDataSourceException(e); + } finally { + inputStream = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/BandwidthMeter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/BandwidthMeter.java new file mode 100755 index 00000000000..6817c31e550 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/BandwidthMeter.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +/** + * Provides estimates of the currently available bandwidth. + */ +public interface BandwidthMeter { + + /** + * A listener of {@link BandwidthMeter} events. + */ + interface EventListener { + + /** + * Called periodically to indicate that bytes have been transferred. + *

        + * Note: The estimated bitrate is typically derived from more information than just + * {@code bytes} and {@code elapsedMs}. + * + * @param elapsedMs The time taken to transfer the bytes, in milliseconds. + * @param bytes The number of bytes transferred. + * @param bitrate The estimated bitrate in bits/sec, or {@link #NO_ESTIMATE} if an estimate is + * not available. + */ + void onBandwidthSample(int elapsedMs, long bytes, long bitrate); + + } + + /** + * Indicates no bandwidth estimate is available. + */ + long NO_ESTIMATE = -1; + + /** + * Returns the estimated bandwidth in bits/sec, or {@link #NO_ESTIMATE} if an estimate is not + * available. + */ + long getBitrateEstimate(); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSink.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSink.java similarity index 75% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSink.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSink.java index ceffd99c04b..71c10bef1a8 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/ByteArrayDataSink.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSink.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,14 +28,13 @@ public final class ByteArrayDataSink implements DataSink { private ByteArrayOutputStream stream; @Override - public DataSink open(DataSpec dataSpec) throws IOException { - if (dataSpec.length == C.LENGTH_UNBOUNDED) { + public void open(DataSpec dataSpec) throws IOException { + if (dataSpec.length == C.LENGTH_UNSET) { stream = new ByteArrayOutputStream(); } else { Assertions.checkArgument(dataSpec.length <= Integer.MAX_VALUE); stream = new ByteArrayOutputStream((int) dataSpec.length); } - return this; } @Override @@ -49,9 +48,8 @@ public void write(byte[] buffer, int offset, int length) throws IOException { } /** - * Returns the data written to the sink since the last call to {@link #open(DataSpec)}. - * - * @return The data, or null if {@link #open(DataSpec)} has never been called. + * Returns the data written to the sink since the last call to {@link #open(DataSpec)}, or null if + * {@link #open(DataSpec)} has never been called. */ public byte[] getData() { return stream == null ? null : stream.toByteArray(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSource.java new file mode 100755 index 00000000000..19ed36c4c3e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ByteArrayDataSource.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; + +/** + * A {@link DataSource} for reading from a byte array. + */ +public final class ByteArrayDataSource implements DataSource { + + private final byte[] data; + + private Uri uri; + private int readPosition; + private int bytesRemaining; + + /** + * @param data The data to be read. + */ + public ByteArrayDataSource(byte[] data) { + Assertions.checkNotNull(data); + Assertions.checkArgument(data.length > 0); + this.data = data; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + uri = dataSpec.uri; + readPosition = (int) dataSpec.position; + bytesRemaining = (int) ((dataSpec.length == C.LENGTH_UNSET) + ? (data.length - dataSpec.position) : dataSpec.length); + if (bytesRemaining <= 0 || readPosition + bytesRemaining > data.length) { + throw new IOException("Unsatisfiable range: [" + readPosition + ", " + dataSpec.length + + "], length: " + data.length); + } + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + readLength = Math.min(readLength, bytesRemaining); + System.arraycopy(data, readPosition, buffer, offset, readLength); + readPosition += readLength; + bytesRemaining -= readLength; + return readLength; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws IOException { + uri = null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java new file mode 100755 index 00000000000..ab663a8d3b5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ContentDataSource.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading from a content URI. + */ +public final class ContentDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading from a content URI. + */ + public static class ContentDataSourceException extends IOException { + + public ContentDataSourceException(IOException cause) { + super(cause); + } + + } + + private final ContentResolver resolver; + private final TransferListener listener; + + private Uri uri; + private AssetFileDescriptor assetFileDescriptor; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public ContentDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public ContentDataSource(Context context, TransferListener listener) { + this.resolver = context.getContentResolver(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws ContentDataSourceException { + try { + uri = dataSpec.uri; + assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r"); + inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // We expect the skip to be satisfied in full. If it isn't then we're probably trying to + // skip beyond the end of the data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = inputStream.available(); + if (bytesRemaining == 0) { + // FileInputStream.available() returns 0 if the remaining length cannot be determined, or + // if it's greater than Integer.MAX_VALUE. We don't know the true length in either case, + // so treat as unbounded. + bytesRemaining = C.LENGTH_UNSET; + } + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new ContentDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new ContentDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws ContentDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } finally { + inputStream = null; + try { + if (assetFileDescriptor != null) { + assetFileDescriptor.close(); + } + } catch (IOException e) { + throw new ContentDataSourceException(e); + } finally { + assetFileDescriptor = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSink.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSink.java new file mode 100755 index 00000000000..3f7e310e974 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSink.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import java.io.IOException; + +/** + * A component to which streams of data can be written. + */ +public interface DataSink { + + /** + * A factory for {@link DataSink} instances. + */ + interface Factory { + + /** + * Creates a {@link DataSink} instance. + */ + DataSink createDataSink(); + + } + + /** + * Opens the sink to consume the specified data. + * + * @param dataSpec Defines the data to be consumed. + * @throws IOException If an error occurs opening the sink. + */ + void open(DataSpec dataSpec) throws IOException; + + /** + * Consumes the provided data. + * + * @param buffer The buffer from which data should be consumed. + * @param offset The offset of the data to consume in {@code buffer}. + * @param length The length of the data to consume, in bytes. + * @throws IOException If an error occurs writing to the sink. + */ + void write(byte[] buffer, int offset, int length) throws IOException; + + /** + * Closes the sink. + * + * @throws IOException If an error occurs closing the sink. + */ + void close() throws IOException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSource.java new file mode 100755 index 00000000000..ed7b8431b87 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSource.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import java.io.IOException; + +/** + * A component from which streams of data can be read. + */ +public interface DataSource { + + /** + * A factory for {@link DataSource} instances. + */ + interface Factory { + + /** + * Creates a {@link DataSource} instance. + */ + DataSource createDataSource(); + + } + + /** + * Opens the source to read the specified data. + *

        + * Note: If an {@link IOException} is thrown, callers must still call {@link #close()} to ensure + * that any partial effects of the invocation are cleaned up. + * + * @param dataSpec Defines the data to be read. + * @throws IOException If an error occurs opening the source. {@link DataSourceException} can be + * thrown or used as a cause of the thrown exception to specify the reason of the error. + * @return The number of bytes that can be read from the opened source. For unbounded requests + * (i.e. requests where {@link DataSpec#length} equals {@link C#LENGTH_UNSET}) this value + * is the resolved length of the request, or {@link C#LENGTH_UNSET} if the length is still + * unresolved. For all other requests, the value returned will be equal to the request's + * {@link DataSpec#length}. + */ + long open(DataSpec dataSpec) throws IOException; + + /** + * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at + * index {@code offset}. + *

        + * If {@code length} is zero then 0 is returned. Otherwise, if no data is available because the + * end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned. + * Otherwise, the call will block until at least one byte of data has been read and the number of + * bytes read is returned. + * + * @param buffer The buffer into which the read data should be stored. + * @param offset The start offset into {@code buffer} at which data should be written. + * @param readLength The maximum number of bytes to read. + * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is avaliable + * because the end of the opened range has been reached. + * @throws IOException If an error occurs reading from the source. + */ + int read(byte[] buffer, int offset, int readLength) throws IOException; + + /** + * When the source is open, returns the {@link Uri} from which data is being read. The returned + * {@link Uri} will be identical to the one passed {@link #open(DataSpec)} in the {@link DataSpec} + * unless redirection has occurred. If redirection has occurred, the {@link Uri} after redirection + * is returned. + * + * @return The {@link Uri} from which data is being read, or null if the source is not open. + */ + Uri getUri(); + + /** + * Closes the source. + *

        + * Note: This method must be called even if the corresponding call to {@link #open(DataSpec)} + * threw an {@link IOException}. See {@link #open(DataSpec)} for more details. + * + * @throws IOException If an error occurs closing the source. + */ + void close() throws IOException; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceException.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceException.java new file mode 100755 index 00000000000..80e7680474c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import java.io.IOException; + +/** + * Used to specify reason of a DataSource error. + */ +public final class DataSourceException extends IOException { + + public static final int POSITION_OUT_OF_RANGE = 0; + + /** + * The reason of this {@link DataSourceException}. It can only be {@link #POSITION_OUT_OF_RANGE}. + */ + public final int reason; + + /** + * Constructs a DataSourceException. + * + * @param reason Reason of the error. It can only be {@link #POSITION_OUT_OF_RANGE}. + */ + public DataSourceException(int reason) { + this.reason = reason; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceInputStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceInputStream.java new file mode 100755 index 00000000000..474eb672b86 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSourceInputStream.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.IOException; +import java.io.InputStream; + +/** + * Allows data corresponding to a given {@link DataSpec} to be read from a {@link DataSource} and + * consumed through an {@link InputStream}. + */ +public final class DataSourceInputStream extends InputStream { + + private final DataSource dataSource; + private final DataSpec dataSpec; + private final byte[] singleByteArray; + + private boolean opened = false; + private boolean closed = false; + private long totalBytesRead; + + /** + * @param dataSource The {@link DataSource} from which the data should be read. + * @param dataSpec The {@link DataSpec} defining the data to be read from {@code dataSource}. + */ + public DataSourceInputStream(DataSource dataSource, DataSpec dataSpec) { + this.dataSource = dataSource; + this.dataSpec = dataSpec; + singleByteArray = new byte[1]; + } + + /** + * Returns the total number of bytes that have been read or skipped. + */ + public long bytesRead() { + return totalBytesRead; + } + + /** + * Optional call to open the underlying {@link DataSource}. + *

        + * Calling this method does nothing if the {@link DataSource} is already open. Calling this + * method is optional, since the read and skip methods will automatically open the underlying + * {@link DataSource} if it's not open already. + * + * @throws IOException If an error occurs opening the {@link DataSource}. + */ + public void open() throws IOException { + checkOpened(); + } + + @Override + public int read() throws IOException { + int length = read(singleByteArray); + if (length == -1) { + return -1; + } + totalBytesRead++; + return singleByteArray[0] & 0xFF; + } + + @Override + public int read(byte[] buffer) throws IOException { + int bytesRead = read(buffer, 0, buffer.length); + if (bytesRead != -1) { + totalBytesRead += bytesRead; + } + return bytesRead; + } + + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + Assertions.checkState(!closed); + checkOpened(); + int bytesRead = dataSource.read(buffer, offset, length); + if (bytesRead == C.RESULT_END_OF_INPUT) { + return -1; + } else { + totalBytesRead += bytesRead; + return bytesRead; + } + } + + @Override + public long skip(long byteCount) throws IOException { + Assertions.checkState(!closed); + checkOpened(); + long bytesSkipped = super.skip(byteCount); + totalBytesRead += bytesSkipped; + return bytesSkipped; + } + + @Override + public void close() throws IOException { + if (!closed) { + dataSource.close(); + closed = true; + } + } + + private void checkOpened() throws IOException { + if (!opened) { + dataSource.open(dataSpec); + opened = true; + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSpec.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java similarity index 83% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSpec.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java index d19756d7a62..b76c724b0e7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DataSpec.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DataSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,18 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; import android.net.Uri; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; /** - * Defines a region of media data. + * Defines a region of data. */ public final class DataSpec { + /** + * The flags that apply to any request for data. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_ALLOW_GZIP}) + public @interface Flags {} /** * Permits an underlying network stack to request that the server use gzip compression. *

        @@ -33,13 +42,13 @@ public final class DataSpec { *

        * When a {@link DataSource} is used to request data with this flag set, and if the * {@link DataSource} does make a network request, then the value returned from - * {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNBOUNDED}. The data read - * from {@link DataSource#read(byte[], int, int)} will be the decompressed data. + * {@link DataSource#open(DataSpec)} will typically be {@link C#LENGTH_UNSET}. The data read from + * {@link DataSource#read(byte[], int, int)} will be the decompressed data. */ public static final int FLAG_ALLOW_GZIP = 1; /** - * Identifies the source from which data should be read. + * The source from which data should be read. */ public final Uri uri; /** @@ -58,7 +67,7 @@ public final class DataSpec { */ public final long position; /** - * The length of the data. Greater than zero, or equal to {@link C#LENGTH_UNBOUNDED}. + * The length of the data, or {@link C#LENGTH_UNSET}. */ public final long length; /** @@ -69,6 +78,7 @@ public final class DataSpec { /** * Request flags. Currently {@link #FLAG_ALLOW_GZIP} is the only supported flag. */ + @Flags public final int flags; /** @@ -86,8 +96,8 @@ public DataSpec(Uri uri) { * @param uri {@link #uri}. * @param flags {@link #flags}. */ - public DataSpec(Uri uri, int flags) { - this(uri, 0, C.LENGTH_UNBOUNDED, null, flags); + public DataSpec(Uri uri, @Flags int flags) { + this(uri, 0, C.LENGTH_UNSET, null, flags); } /** @@ -111,7 +121,7 @@ public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key) { * @param key {@link #key}. * @param flags {@link #flags}. */ - public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, int flags) { + public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, @Flags int flags) { this(uri, absoluteStreamPosition, absoluteStreamPosition, length, key, flags); } @@ -127,7 +137,7 @@ public DataSpec(Uri uri, long absoluteStreamPosition, long length, String key, i * @param flags {@link #flags}. */ public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key, - int flags) { + @Flags int flags) { this(uri, null, absoluteStreamPosition, position, length, key, flags); } @@ -144,10 +154,10 @@ public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length * @param flags {@link #flags}. */ public DataSpec(Uri uri, byte[] postBody, long absoluteStreamPosition, long position, long length, - String key, int flags) { + String key, @Flags int flags) { Assertions.checkArgument(absoluteStreamPosition >= 0); Assertions.checkArgument(position >= 0); - Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED); + Assertions.checkArgument(length > 0 || length == C.LENGTH_UNSET); this.uri = uri; this.postBody = postBody; this.absoluteStreamPosition = absoluteStreamPosition; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultAllocator.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultAllocator.java new file mode 100755 index 00000000000..7e0210bafd1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultAllocator.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.util.Arrays; + +/** + * Default implementation of {@link Allocator}. + */ +public final class DefaultAllocator implements Allocator { + + private static final int AVAILABLE_EXTRA_CAPACITY = 100; + + private final boolean trimOnReset; + private final int individualAllocationSize; + private final byte[] initialAllocationBlock; + private final Allocation[] singleAllocationReleaseHolder; + + private int targetBufferSize; + private int allocatedCount; + private int availableCount; + private Allocation[] availableAllocations; + + /** + * Constructs an instance without creating any {@link Allocation}s up front. + * + * @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless + * the allocator will be re-used by multiple player instances. + * @param individualAllocationSize The length of each individual {@link Allocation}. + */ + public DefaultAllocator(boolean trimOnReset, int individualAllocationSize) { + this(trimOnReset, individualAllocationSize, 0); + } + + /** + * Constructs an instance with some {@link Allocation}s created up front. + *

        + * Note: {@link Allocation}s created up front will never be discarded by {@link #trim()}. + * + * @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless + * the allocator will be re-used by multiple player instances. + * @param individualAllocationSize The length of each individual {@link Allocation}. + * @param initialAllocationCount The number of allocations to create up front. + */ + public DefaultAllocator(boolean trimOnReset, int individualAllocationSize, + int initialAllocationCount) { + Assertions.checkArgument(individualAllocationSize > 0); + Assertions.checkArgument(initialAllocationCount >= 0); + this.trimOnReset = trimOnReset; + this.individualAllocationSize = individualAllocationSize; + this.availableCount = initialAllocationCount; + this.availableAllocations = new Allocation[initialAllocationCount + AVAILABLE_EXTRA_CAPACITY]; + if (initialAllocationCount > 0) { + initialAllocationBlock = new byte[initialAllocationCount * individualAllocationSize]; + for (int i = 0; i < initialAllocationCount; i++) { + int allocationOffset = i * individualAllocationSize; + availableAllocations[i] = new Allocation(initialAllocationBlock, allocationOffset); + } + } else { + initialAllocationBlock = null; + } + singleAllocationReleaseHolder = new Allocation[1]; + } + + public synchronized void reset() { + if (trimOnReset) { + setTargetBufferSize(0); + } + } + + public synchronized void setTargetBufferSize(int targetBufferSize) { + boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize; + this.targetBufferSize = targetBufferSize; + if (targetBufferSizeReduced) { + trim(); + } + } + + @Override + public synchronized Allocation allocate() { + allocatedCount++; + Allocation allocation; + if (availableCount > 0) { + allocation = availableAllocations[--availableCount]; + availableAllocations[availableCount] = null; + } else { + allocation = new Allocation(new byte[individualAllocationSize], 0); + } + return allocation; + } + + @Override + public synchronized void release(Allocation allocation) { + singleAllocationReleaseHolder[0] = allocation; + release(singleAllocationReleaseHolder); + } + + @Override + public synchronized void release(Allocation[] allocations) { + if (availableCount + allocations.length >= availableAllocations.length) { + availableAllocations = Arrays.copyOf(availableAllocations, + Math.max(availableAllocations.length * 2, availableCount + allocations.length)); + } + for (Allocation allocation : allocations) { + // Weak sanity check that the allocation probably originated from this pool. + Assertions.checkArgument(allocation.data == initialAllocationBlock + || allocation.data.length == individualAllocationSize); + availableAllocations[availableCount++] = allocation; + } + allocatedCount -= allocations.length; + // Wake up threads waiting for the allocated size to drop. + notifyAll(); + } + + @Override + public synchronized void trim() { + int targetAllocationCount = Util.ceilDivide(targetBufferSize, individualAllocationSize); + int targetAvailableCount = Math.max(0, targetAllocationCount - allocatedCount); + if (targetAvailableCount >= availableCount) { + // We're already at or below the target. + return; + } + + if (initialAllocationBlock != null) { + // Some allocations are backed by an initial block. We need to make sure that we hold onto all + // such allocations. Re-order the available allocations so that the ones backed by the initial + // block come first. + int lowIndex = 0; + int highIndex = availableCount - 1; + while (lowIndex <= highIndex) { + Allocation lowAllocation = availableAllocations[lowIndex]; + if (lowAllocation.data == initialAllocationBlock) { + lowIndex++; + } else { + Allocation highAllocation = availableAllocations[highIndex]; + if (highAllocation.data != initialAllocationBlock) { + highIndex--; + } else { + availableAllocations[lowIndex++] = highAllocation; + availableAllocations[highIndex--] = lowAllocation; + } + } + } + // lowIndex is the index of the first allocation not backed by an initial block. + targetAvailableCount = Math.max(targetAvailableCount, lowIndex); + if (targetAvailableCount >= availableCount) { + // We're already at or below the target. + return; + } + } + + // Discard allocations beyond the target. + Arrays.fill(availableAllocations, targetAvailableCount, availableCount, null); + availableCount = targetAvailableCount; + } + + @Override + public synchronized int getTotalBytesAllocated() { + return allocatedCount * individualAllocationSize; + } + + @Override + public int getIndividualAllocationLength() { + return individualAllocationSize; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java new file mode 100755 index 00000000000..a35f4a308dc --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.os.Handler; +import android.os.SystemClock; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.SlidingPercentile; + +/** + * Estimates bandwidth by listening to data transfers. The bandwidth estimate is calculated using + * a {@link SlidingPercentile} and is updated each time a transfer ends. + */ +public final class DefaultBandwidthMeter implements BandwidthMeter, TransferListener { + + /** + * The default maximum weight for the sliding window. + */ + public static final int DEFAULT_MAX_WEIGHT = 2000; + + private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000; + private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024; + + private final Handler eventHandler; + private final EventListener eventListener; + private final SlidingPercentile slidingPercentile; + + private int streamCount; + private long sampleStartTimeMs; + private long sampleBytesTransferred; + + private long totalElapsedTimeMs; + private long totalBytesTransferred; + private long bitrateEstimate; + + public DefaultBandwidthMeter() { + this(null, null); + } + + public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener) { + this(eventHandler, eventListener, DEFAULT_MAX_WEIGHT); + } + + public DefaultBandwidthMeter(Handler eventHandler, EventListener eventListener, int maxWeight) { + this.eventHandler = eventHandler; + this.eventListener = eventListener; + this.slidingPercentile = new SlidingPercentile(maxWeight); + bitrateEstimate = NO_ESTIMATE; + } + + @Override + public synchronized long getBitrateEstimate() { + return bitrateEstimate; + } + + @Override + public synchronized void onTransferStart(Object source, DataSpec dataSpec) { + if (streamCount == 0) { + sampleStartTimeMs = SystemClock.elapsedRealtime(); + } + streamCount++; + } + + @Override + public synchronized void onBytesTransferred(Object source, int bytes) { + sampleBytesTransferred += bytes; + } + + @Override + public synchronized void onTransferEnd(Object source) { + Assertions.checkState(streamCount > 0); + long nowMs = SystemClock.elapsedRealtime(); + int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); + totalElapsedTimeMs += sampleElapsedTimeMs; + totalBytesTransferred += sampleBytesTransferred; + if (sampleElapsedTimeMs > 0) { + float bitsPerSecond = (sampleBytesTransferred * 8000) / sampleElapsedTimeMs; + slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond); + if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE + || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) { + float bitrateEstimateFloat = slidingPercentile.getPercentile(0.5f); + bitrateEstimate = Float.isNaN(bitrateEstimateFloat) ? NO_ESTIMATE + : (long) bitrateEstimateFloat; + } + } + notifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate); + if (--streamCount > 0) { + sampleStartTimeMs = nowMs; + } + sampleBytesTransferred = 0; + } + + private void notifyBandwidthSample(final int elapsedMs, final long bytes, final long bitrate) { + if (eventHandler != null && eventListener != null) { + eventHandler.post(new Runnable() { + @Override + public void run() { + eventListener.onBandwidthSample(elapsedMs, bytes, bitrate); + } + }); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java new file mode 100755 index 00000000000..44ff80f1ea0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSource.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.content.Context; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A {@link DataSource} that supports multiple URI schemes. The supported schemes are: + * + *
          + *
        • file: For fetching data from a local file (e.g. file:///path/to/media/media.mp4, or just + * /path/to/media/media.mp4 because the implementation assumes that a URI without a scheme is a + * local file URI). + *
        • asset: For fetching data from an asset in the application's apk (e.g. asset:///media.mp4). + *
        • content: For fetching data from a content URI (e.g. content://authority/path/123). + *
        • http(s): For fetching data over HTTP and HTTPS (e.g. https://www.something.com/media.mp4), if + * constructed using {@link #DefaultDataSource(Context, TransferListener, String, boolean)}, or + * any other schemes supported by a base data source if constructed using + * {@link #DefaultDataSource(Context, TransferListener, DataSource)}. + *
        + */ +public final class DefaultDataSource implements DataSource { + + private static final String SCHEME_ASSET = "asset"; + private static final String SCHEME_CONTENT = "content"; + + private final DataSource baseDataSource; + private final DataSource fileDataSource; + private final DataSource assetDataSource; + private final DataSource contentDataSource; + + private DataSource dataSource; + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public DefaultDataSource(Context context, TransferListener listener, + String userAgent, boolean allowCrossProtocolRedirects) { + this(context, listener, userAgent, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, allowCrossProtocolRedirects); + } + + /** + * Constructs a new instance, optionally configured to follow cross-protocol redirects. + * + * @param context A context. + * @param listener An optional listener. + * @param userAgent The User-Agent string that should be used when requesting remote data. + * @param connectTimeoutMillis The connection timeout that should be used when requesting remote + * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout that should be used when requesting remote data, + * in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled when fetching remote data. + */ + public DefaultDataSource(Context context, TransferListener listener, + String userAgent, int connectTimeoutMillis, int readTimeoutMillis, + boolean allowCrossProtocolRedirects) { + this(context, listener, + new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, + readTimeoutMillis, allowCrossProtocolRedirects)); + } + + /** + * Constructs a new instance that delegates to a provided {@link DataSource} for URI schemes other + * than file, asset and content. + * + * @param context A context. + * @param listener An optional listener. + * @param baseDataSource A {@link DataSource} to use for URI schemes other than file, asset and + * content. This {@link DataSource} should normally support at least http(s). + */ + public DefaultDataSource(Context context, TransferListener listener, + DataSource baseDataSource) { + this.baseDataSource = Assertions.checkNotNull(baseDataSource); + this.fileDataSource = new FileDataSource(listener); + this.assetDataSource = new AssetDataSource(context, listener); + this.contentDataSource = new ContentDataSource(context, listener); + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + Assertions.checkState(dataSource == null); + // Choose the correct source for the scheme. + String scheme = dataSpec.uri.getScheme(); + if (Util.isLocalFileUri(dataSpec.uri)) { + if (dataSpec.uri.getPath().startsWith("/android_asset/")) { + dataSource = assetDataSource; + } else { + dataSource = fileDataSource; + } + } else if (SCHEME_ASSET.equals(scheme)) { + dataSource = assetDataSource; + } else if (SCHEME_CONTENT.equals(scheme)) { + dataSource = contentDataSource; + } else { + dataSource = baseDataSource; + } + // Open the source and return. + return dataSource.open(dataSpec); + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + return dataSource.read(buffer, offset, readLength); + } + + @Override + public Uri getUri() { + return dataSource == null ? null : dataSource.getUri(); + } + + @Override + public void close() throws IOException { + if (dataSource != null) { + try { + dataSource.close(); + } finally { + dataSource = null; + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSourceFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSourceFactory.java new file mode 100755 index 00000000000..2c0e6683bdd --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultDataSourceFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.content.Context; +import org.telegram.messenger.exoplayer2.upstream.DataSource.Factory; + +/** + * A {@link Factory} that produces {@link DefaultDataSource} instances that delegate to + * {@link DefaultHttpDataSource}s for non-file/asset/content URIs. + */ +public final class DefaultDataSourceFactory implements Factory { + + private final Context context; + private final TransferListener listener; + private final DataSource.Factory baseDataSourceFactory; + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + */ + public DefaultDataSourceFactory(Context context, String userAgent) { + this(context, userAgent, null); + } + + /** + * @param context A context. + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + */ + public DefaultDataSourceFactory(Context context, String userAgent, + TransferListener listener) { + this(context, listener, new DefaultHttpDataSourceFactory(userAgent, listener)); + } + + /** + * @param context A context. + * @param listener An optional listener. + * @param baseDataSourceFactory A {@link Factory} to be used to create a base {@link DataSource} + * for {@link DefaultDataSource}. + * @see DefaultDataSource#DefaultDataSource(Context, TransferListener, DataSource) + */ + public DefaultDataSourceFactory(Context context, TransferListener listener, + DataSource.Factory baseDataSourceFactory) { + this.context = context.getApplicationContext(); + this.listener = listener; + this.baseDataSourceFactory = baseDataSourceFactory; + } + + @Override + public DefaultDataSource createDataSource() { + return new DefaultDataSource(context, listener, baseDataSourceFactory.createDataSource()); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultHttpDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSource.java similarity index 76% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultHttpDataSource.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSource.java index 554571009a2..3591f619886 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/DefaultHttpDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; +import android.net.Uri; import android.text.TextUtils; import android.util.Log; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; -import org.telegram.messenger.exoplayer.util.Predicate; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Predicate; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; +import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.NoRouteToHostException; import java.net.ProtocolException; @@ -38,7 +40,7 @@ import java.util.regex.Pattern; /** - * A {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. + * An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}. *

        * By default this implementation will not follow cross-protocol redirects (i.e. redirects from * HTTP to HTTPS or vice versa). Cross-protocol redirects can be enabled by using the @@ -56,8 +58,9 @@ public class DefaultHttpDataSource implements HttpDataSource { */ public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000; - private static final int MAX_REDIRECTS = 20; // Same limit as okhttp. private static final String TAG = "DefaultHttpDataSource"; + private static final int MAX_REDIRECTS = 20; // Same limit as okhttp. + private static final long MAX_BYTES_TO_DRAIN = 2048; private static final Pattern CONTENT_RANGE_HEADER = Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$"); private static final AtomicReference skipBufferReference = new AtomicReference<>(); @@ -68,7 +71,7 @@ public class DefaultHttpDataSource implements HttpDataSource { private final String userAgent; private final Predicate contentTypePredicate; private final HashMap requestProperties; - private final TransferListener listener; + private final TransferListener listener; private DataSpec dataSpec; private HttpURLConnection connection; @@ -83,9 +86,9 @@ public class DefaultHttpDataSource implements HttpDataSource { /** * @param userAgent The User-Agent string that should be used. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is - * rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is - * thrown from {@link #open(DataSpec)}. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. */ public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate) { this(userAgent, contentTypePredicate, null); @@ -93,22 +96,22 @@ public DefaultHttpDataSource(String userAgent, Predicate contentTypePred /** * @param userAgent The User-Agent string that should be used. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is - * rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is - * thrown from {@link #open(DataSpec)}. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. * @param listener An optional listener. */ public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, - TransferListener listener) { + TransferListener listener) { this(userAgent, contentTypePredicate, listener, DEFAULT_CONNECT_TIMEOUT_MILLIS, DEFAULT_READ_TIMEOUT_MILLIS); } /** * @param userAgent The User-Agent string that should be used. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is - * rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is - * thrown from {@link #open(DataSpec)}. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. * @param listener An optional listener. * @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is * interpreted as an infinite timeout. @@ -116,15 +119,16 @@ public DefaultHttpDataSource(String userAgent, Predicate contentTypePred * as an infinite timeout. */ public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, - TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis) { + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis) { this(userAgent, contentTypePredicate, listener, connectTimeoutMillis, readTimeoutMillis, false); } /** * @param userAgent The User-Agent string that should be used. - * @param contentTypePredicate An optional {@link Predicate}. If a content type is - * rejected by the predicate then a {@link HttpDataSource.InvalidContentTypeException} is - * thrown from {@link #open(DataSpec)}. + * @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the + * predicate then a {@link HttpDataSource.InvalidContentTypeException} is thrown from + * {@link #open(DataSpec)}. * @param listener An optional listener. * @param connectTimeoutMillis The connection timeout, in milliseconds. A timeout of zero is * interpreted as an infinite timeout. Pass {@link #DEFAULT_CONNECT_TIMEOUT_MILLIS} to use @@ -135,8 +139,8 @@ public DefaultHttpDataSource(String userAgent, Predicate contentTypePred * to HTTPS and vice versa) are enabled. */ public DefaultHttpDataSource(String userAgent, Predicate contentTypePredicate, - TransferListener listener, int connectTimeoutMillis, int readTimeoutMillis, - boolean allowCrossProtocolRedirects) { + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis, boolean allowCrossProtocolRedirects) { this.userAgent = Assertions.checkNotEmpty(userAgent); this.contentTypePredicate = contentTypePredicate; this.listener = listener; @@ -147,8 +151,8 @@ public DefaultHttpDataSource(String userAgent, Predicate contentTypePred } @Override - public String getUri() { - return connection == null ? null : connection.getURL().toString(); + public Uri getUri() { + return connection == null ? null : Uri.parse(connection.getURL().toString()); } @Override @@ -205,7 +209,12 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException { if (responseCode < 200 || responseCode > 299) { Map> headers = connection.getHeaderFields(); closeConnectionQuietly(); - throw new InvalidResponseCodeException(responseCode, headers, dataSpec); + InvalidResponseCodeException exception = + new InvalidResponseCodeException(responseCode, headers, dataSpec); + if (responseCode == 416) { + exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); + } + throw exception; } // Check for a valid content type. @@ -222,10 +231,13 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException { // Determine the length of the data to be read, after skipping. if ((dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) == 0) { - long contentLength = getContentLength(connection); - bytesToRead = dataSpec.length != C.LENGTH_UNBOUNDED ? dataSpec.length - : contentLength != C.LENGTH_UNBOUNDED ? contentLength - bytesToSkip - : C.LENGTH_UNBOUNDED; + if (dataSpec.length != C.LENGTH_UNSET) { + bytesToRead = dataSpec.length; + } else { + long contentLength = getContentLength(connection); + bytesToRead = contentLength != C.LENGTH_UNSET ? (contentLength - bytesToSkip) + : C.LENGTH_UNSET; + } } else { // Gzip is enabled. If the server opts to use gzip then the content length in the response // will be that of the compressed data, which isn't what we want. Furthermore, there isn't a @@ -243,7 +255,7 @@ public long open(DataSpec dataSpec) throws HttpDataSourceException { opened = true; if (listener != null) { - listener.onTransferStart(); + listener.onTransferStart(this, dataSpec); } return bytesToRead; @@ -263,7 +275,7 @@ public int read(byte[] buffer, int offset, int readLength) throws HttpDataSource public void close() throws HttpDataSourceException { try { if (inputStream != null) { - Util.maybeTerminateInputStream(connection, bytesRemaining()); + maybeTerminateInputStream(connection, bytesRemaining()); try { inputStream.close(); } catch (IOException e) { @@ -276,7 +288,7 @@ public void close() throws HttpDataSourceException { if (opened) { opened = false; if (listener != null) { - listener.onTransferEnd(); + listener.onTransferEnd(this); } } } @@ -315,12 +327,12 @@ protected final long bytesRead() { * Returns the number of bytes that are still to be read for the current {@link DataSpec}. *

        * If the total length of the data being read is known, then this length minus {@code bytesRead()} - * is returned. If the total length is unknown, {@link C#LENGTH_UNBOUNDED} is returned. + * is returned. If the total length is unknown, {@link C#LENGTH_UNSET} is returned. * - * @return The remaining length, or {@link C#LENGTH_UNBOUNDED}. + * @return The remaining length, or {@link C#LENGTH_UNSET}. */ protected final long bytesRemaining() { - return bytesToRead == C.LENGTH_UNBOUNDED ? bytesToRead : bytesToRead - bytesRead; + return bytesToRead == C.LENGTH_UNSET ? bytesToRead : bytesToRead - bytesRead; } /** @@ -336,9 +348,7 @@ private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { if (!allowCrossProtocolRedirects) { // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection // automatically. This is the behavior we want, so use it. - HttpURLConnection connection = makeConnection( - url, postBody, position, length, allowGzip, true /* followRedirects */); - return connection; + return makeConnection(url, postBody, position, length, allowGzip, true /* followRedirects */); } // We need to handle redirects ourselves to allow cross-protocol redirects. @@ -375,7 +385,7 @@ private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { * @param url The url to connect to. * @param postBody The body data for a POST request. * @param position The byte offset of the requested data. - * @param length The length of the requested data, or {@link C#LENGTH_UNBOUNDED}. + * @param length The length of the requested data, or {@link C#LENGTH_UNSET}. * @param allowGzip Whether to allow the use of gzip. * @param followRedirects Whether to follow redirects. */ @@ -389,9 +399,9 @@ private HttpURLConnection makeConnection(URL url, byte[] postBody, long position connection.setRequestProperty(property.getKey(), property.getValue()); } } - if (!(position == 0 && length == C.LENGTH_UNBOUNDED)) { + if (!(position == 0 && length == C.LENGTH_UNSET)) { String rangeRequest = "bytes=" + position + "-"; - if (length != C.LENGTH_UNBOUNDED) { + if (length != C.LENGTH_UNSET) { rangeRequest += (position + length - 1); } connection.setRequestProperty("Range", rangeRequest); @@ -403,11 +413,16 @@ private HttpURLConnection makeConnection(URL url, byte[] postBody, long position connection.setInstanceFollowRedirects(followRedirects); connection.setDoOutput(postBody != null); if (postBody != null) { - connection.setFixedLengthStreamingMode(postBody.length); - connection.connect(); - OutputStream os = connection.getOutputStream(); - os.write(postBody); - os.close(); + connection.setRequestMethod("POST"); + if (postBody.length == 0) { + connection.connect(); + } else { + connection.setFixedLengthStreamingMode(postBody.length); + connection.connect(); + OutputStream os = connection.getOutputStream(); + os.write(postBody); + os.close(); + } } else { connection.connect(); } @@ -447,10 +462,10 @@ private static URL handleRedirect(URL originalUrl, String location) throws IOExc * Attempts to extract the length of the content from the response headers of an open connection. * * @param connection The open connection. - * @return The extracted length, or {@link C#LENGTH_UNBOUNDED}. + * @return The extracted length, or {@link C#LENGTH_UNSET}. */ private static long getContentLength(HttpURLConnection connection) { - long contentLength = C.LENGTH_UNBOUNDED; + long contentLength = C.LENGTH_UNSET; String contentLengthHeader = connection.getHeaderField("Content-Length"); if (!TextUtils.isEmpty(contentLengthHeader)) { try { @@ -517,7 +532,7 @@ private void skipInternal() throws IOException { } bytesSkipped += read; if (listener != null) { - listener.onBytesTransferred(read); + listener.onBytesTransferred(this, read); } } @@ -540,17 +555,21 @@ private void skipInternal() throws IOException { * @throws IOException If an error occurs reading from the source. */ private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { - readLength = bytesToRead == C.LENGTH_UNBOUNDED ? readLength - : (int) Math.min(readLength, bytesToRead - bytesRead); if (readLength == 0) { - // We've read all of the requested data. - return C.RESULT_END_OF_INPUT; + return 0; + } + if (bytesToRead != C.LENGTH_UNSET) { + long bytesRemaining = bytesToRead - bytesRead; + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + readLength = (int) Math.min(readLength, bytesRemaining); } int read = inputStream.read(buffer, offset, readLength); if (read == -1) { - if (bytesToRead != C.LENGTH_UNBOUNDED && bytesToRead != bytesRead) { - // The server closed the connection having not sent sufficient data. + if (bytesToRead != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. throw new EOFException(); } return C.RESULT_END_OF_INPUT; @@ -558,11 +577,56 @@ private int readInternal(byte[] buffer, int offset, int readLength) throws IOExc bytesRead += read; if (listener != null) { - listener.onBytesTransferred(read); + listener.onBytesTransferred(this, read); } return read; } + /** + * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can + * block for a long time if the stream has a lot of data remaining. Call this method before + * closing the input stream to make a best effort to cause the input stream to encounter an + * unexpected end of input, working around this issue. On other platform API levels, the method + * does nothing. + * + * @param connection The connection whose {@link InputStream} should be terminated. + * @param bytesRemaining The number of bytes remaining to be read from the input stream if its + * length is known. {@link C#LENGTH_UNSET} otherwise. + */ + private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) { + if (Util.SDK_INT != 19 && Util.SDK_INT != 20) { + return; + } + + try { + InputStream inputStream = connection.getInputStream(); + if (bytesRemaining == C.LENGTH_UNSET) { + // If the input stream has already ended, do nothing. The socket may be re-used. + if (inputStream.read() == -1) { + return; + } + } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) { + // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be + // re-used. + return; + } + String className = inputStream.getClass().getName(); + if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream") + || className.equals( + "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) { + Class superclass = inputStream.getClass().getSuperclass(); + Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput"); + unexpectedEndOfInput.setAccessible(true); + unexpectedEndOfInput.invoke(inputStream); + } + } catch (Exception e) { + // If an IOException then the connection didn't ever have an input stream, or it was closed + // already. If another type of exception then something went wrong, most likely the device + // isn't using okhttp. + } + } + + /** * Closes the current connection quietly, if there is one. */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSourceFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSourceFactory.java new file mode 100755 index 00000000000..1b0c0691076 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/DefaultHttpDataSourceFactory.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import org.telegram.messenger.exoplayer2.upstream.HttpDataSource.Factory; + +/** A {@link Factory} that produces {@link DefaultHttpDataSource} instances. */ +public final class DefaultHttpDataSourceFactory implements Factory { + + private final String userAgent; + private final TransferListener listener; + private final int connectTimeoutMillis; + private final int readTimeoutMillis; + private final boolean allowCrossProtocolRedirects; + + /** + * Constructs a DefaultHttpDataSourceFactory. Sets {@link + * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link + * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables + * cross-protocol redirects. + * + * @param userAgent The User-Agent string that should be used. + */ + public DefaultHttpDataSourceFactory(String userAgent) { + this(userAgent, null); + } + + /** + * Constructs a DefaultHttpDataSourceFactory. Sets {@link + * DefaultHttpDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout, {@link + * DefaultHttpDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout and disables + * cross-protocol redirects. + * + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + * @see #DefaultHttpDataSourceFactory(String, TransferListener, int, int, boolean) + */ + public DefaultHttpDataSourceFactory( + String userAgent, TransferListener listener) { + this(userAgent, listener, DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, false); + } + + /** + * @param userAgent The User-Agent string that should be used. + * @param listener An optional listener. + * @param connectTimeoutMillis The connection timeout that should be used when requesting remote + * data, in milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param readTimeoutMillis The read timeout that should be used when requesting remote data, in + * milliseconds. A timeout of zero is interpreted as an infinite timeout. + * @param allowCrossProtocolRedirects Whether cross-protocol redirects (i.e. redirects from HTTP + * to HTTPS and vice versa) are enabled. + */ + public DefaultHttpDataSourceFactory(String userAgent, + TransferListener listener, int connectTimeoutMillis, + int readTimeoutMillis, boolean allowCrossProtocolRedirects) { + this.userAgent = userAgent; + this.listener = listener; + this.connectTimeoutMillis = connectTimeoutMillis; + this.readTimeoutMillis = readTimeoutMillis; + this.allowCrossProtocolRedirects = allowCrossProtocolRedirects; + } + + @Override + public DefaultHttpDataSource createDataSource() { + return new DefaultHttpDataSource(userAgent, null, listener, connectTimeoutMillis, + readTimeoutMillis, allowCrossProtocolRedirects); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSource.java new file mode 100755 index 00000000000..d5318868a9d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSource.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import java.io.EOFException; +import java.io.IOException; +import java.io.RandomAccessFile; + +/** + * A {@link DataSource} for reading local files. + */ +public final class FileDataSource implements DataSource { + + /** + * Thrown when IOException is encountered during local file read operation. + */ + public static class FileDataSourceException extends IOException { + + public FileDataSourceException(IOException cause) { + super(cause); + } + + } + + private final TransferListener listener; + + private RandomAccessFile file; + private Uri uri; + private long bytesRemaining; + private boolean opened; + + public FileDataSource() { + this(null); + } + + /** + * @param listener An optional listener. + */ + public FileDataSource(TransferListener listener) { + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws FileDataSourceException { + try { + uri = dataSpec.uri; + file = new RandomAccessFile(dataSpec.uri.getPath(), "r"); + file.seek(dataSpec.position); + bytesRemaining = dataSpec.length == C.LENGTH_UNSET ? file.length() - dataSpec.position + : dataSpec.length; + if (bytesRemaining < 0) { + throw new EOFException(); + } + } catch (IOException e) { + throw new FileDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } else { + int bytesRead; + try { + bytesRead = file.read(buffer, offset, (int) Math.min(bytesRemaining, readLength)); + } catch (IOException e) { + throw new FileDataSourceException(e); + } + + if (bytesRead > 0) { + bytesRemaining -= bytesRead; + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + } + + return bytesRead; + } + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws FileDataSourceException { + uri = null; + try { + if (file != null) { + file.close(); + } + } catch (IOException e) { + throw new FileDataSourceException(e); + } finally { + file = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSourceFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSourceFactory.java new file mode 100755 index 00000000000..fde7e94b4ef --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/FileDataSourceFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +/** + * A {@link DataSource.Factory} that produces {@link FileDataSource}. + */ +public final class FileDataSourceFactory implements DataSource.Factory { + + private final TransferListener listener; + + public FileDataSourceFactory() { + this(null); + } + + public FileDataSourceFactory(TransferListener listener) { + this.listener = listener; + } + + @Override + public DataSource createDataSource() { + return new FileDataSource(listener); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/HttpDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/HttpDataSource.java similarity index 80% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/HttpDataSource.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/HttpDataSource.java index c9d58808052..1e2a33ac81e 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/HttpDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/HttpDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; +import android.support.annotation.IntDef; import android.text.TextUtils; -import org.telegram.messenger.exoplayer.util.Predicate; -import org.telegram.messenger.exoplayer.util.Util; +import org.telegram.messenger.exoplayer2.util.Predicate; +import org.telegram.messenger.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Map; /** - * An HTTP specific extension to {@link UriDataSource}. + * An HTTP {@link DataSource}. */ -public interface HttpDataSource extends UriDataSource { +public interface HttpDataSource extends DataSource { + + /** + * A factory for {@link HttpDataSource} instances. + */ + interface Factory extends DataSource.Factory { + + @Override + HttpDataSource createDataSource(); + + } /** * A {@link Predicate} that rejects content types often used for pay-walls. @@ -47,10 +60,14 @@ public boolean evaluate(String contentType) { */ class HttpDataSourceException extends IOException { + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE}) + public @interface Type {} public static final int TYPE_OPEN = 1; public static final int TYPE_READ = 2; public static final int TYPE_CLOSE = 3; + @Type public final int type; /** @@ -58,25 +75,26 @@ class HttpDataSourceException extends IOException { */ public final DataSpec dataSpec; - public HttpDataSourceException(DataSpec dataSpec, int type) { + public HttpDataSourceException(DataSpec dataSpec, @Type int type) { super(); this.dataSpec = dataSpec; this.type = type; } - public HttpDataSourceException(String message, DataSpec dataSpec, int type) { + public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) { super(message); this.dataSpec = dataSpec; this.type = type; } - public HttpDataSourceException(IOException cause, DataSpec dataSpec, int type) { + public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) { super(cause); this.dataSpec = dataSpec; this.type = type; } - public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, int type) { + public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, + @Type int type) { super(message, cause); this.dataSpec = dataSpec; this.type = type; @@ -154,9 +172,8 @@ public InvalidResponseCodeException(int responseCode, Map> void clearAllRequestProperties(); /** - * Gets the headers provided in the response. - * - * @return The response headers, or {@code null} if response headers are unavailable. + * Returns the headers provided in the response, or {@code null} if response headers are + * unavailable. */ Map> getResponseHeaders(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java new file mode 100755 index 00000000000..86a76f31382 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/Loader.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.TraceUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.IOException; +import java.util.concurrent.ExecutorService; + +/** + * Manages the background loading of {@link Loadable}s. + */ +public final class Loader implements LoaderErrorThrower { + + /** + * Thrown when an unexpected exception is encountered during loading. + */ + public static final class UnexpectedLoaderException extends IOException { + + public UnexpectedLoaderException(Exception cause) { + super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause); + } + + } + + /** + * An object that can be loaded using a {@link Loader}. + */ + public interface Loadable { + + /** + * Cancels the load. + */ + void cancelLoad(); + + /** + * Returns whether the load has been canceled. + */ + boolean isLoadCanceled(); + + /** + * Performs the load, returning on completion or cancellation. + * + * @throws IOException + * @throws InterruptedException + */ + void load() throws IOException, InterruptedException; + + } + + /** + * A callback to be notified of {@link Loader} events. + */ + public interface Callback { + + /** + * Called when a load has completed. + *

        + * Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and + * this callback being called. + * + * @param loadable The loadable whose load has completed. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load ended. + * @param loadDurationMs The duration of the load. + */ + void onLoadCompleted(T loadable, long elapsedRealtimeMs, long loadDurationMs); + + /** + * Called when a load has been canceled. + *

        + * Note: If the {@link Loader} has not been released then there is guaranteed to be a memory + * barrier between {@link Loadable#load()} exiting and this callback being called. If the + * {@link Loader} has been released then this callback may be called before + * {@link Loadable#load()} exits. + * + * @param loadable The loadable whose load has been canceled. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the load was canceled. + * @param loadDurationMs The duration of the load up to the point at which it was canceled. + * @param released True if the load was canceled because the {@link Loader} was released. False + * otherwise. + */ + void onLoadCanceled(T loadable, long elapsedRealtimeMs, long loadDurationMs, boolean released); + + /** + * Called when a load encounters an error. + *

        + * Note: There is guaranteed to be a memory barrier between {@link Loadable#load()} exiting and + * this callback being called. + * + * @param loadable The loadable whose load has encountered an error. + * @param elapsedRealtimeMs {@link SystemClock#elapsedRealtime} when the error occurred. + * @param loadDurationMs The duration of the load up to the point at which the error occurred. + * @param error The load error. + * @return The desired retry action. One of {@link Loader#RETRY}, + * {@link Loader#RETRY_RESET_ERROR_COUNT}, {@link Loader#DONT_RETRY} and + * {@link Loader#DONT_RETRY_FATAL}. + */ + int onLoadError(T loadable, long elapsedRealtimeMs, long loadDurationMs, IOException error); + + } + + public static final int RETRY = 0; + public static final int RETRY_RESET_ERROR_COUNT = 1; + public static final int DONT_RETRY = 2; + public static final int DONT_RETRY_FATAL = 3; + + private static final int MSG_START = 0; + private static final int MSG_CANCEL = 1; + private static final int MSG_END_OF_SOURCE = 2; + private static final int MSG_IO_EXCEPTION = 3; + private static final int MSG_FATAL_ERROR = 4; + + private final ExecutorService downloadExecutorService; + + private LoadTask currentTask; + private IOException fatalError; + + /** + * @param threadName A name for the loader's thread. + */ + public Loader(String threadName) { + this.downloadExecutorService = Util.newSingleThreadExecutor(threadName); + } + + /** + * Starts loading a {@link Loadable}. + *

        + * The calling thread must be a {@link Looper} thread, which is the thread on which the + * {@link Callback} will be called. + * + * @param The type of the loadable. + * @param loadable The {@link Loadable} to load. + * @param callback A callback to called when the load ends. + * @param defaultMinRetryCount The minimum number of times the load must be retried before + * {@link #maybeThrowError()} will propagate an error. + * @throws IllegalStateException If the calling thread does not have an associated {@link Looper}. + * @return {@link SystemClock#elapsedRealtime} when the load started. + */ + public long startLoading(T loadable, Callback callback, + int defaultMinRetryCount) { + Looper looper = Looper.myLooper(); + Assertions.checkState(looper != null); + long startTimeMs = SystemClock.elapsedRealtime(); + new LoadTask<>(looper, loadable, callback, defaultMinRetryCount, startTimeMs).start(0); + return startTimeMs; + } + + /** + * Returns whether the {@link Loader} is currently loading a {@link Loadable}. + */ + public boolean isLoading() { + return currentTask != null; + } + + /** + * Cancels the current load. This method should only be called when a load is in progress. + */ + public void cancelLoading() { + currentTask.cancel(false); + } + + /** + * Releases the {@link Loader}. This method should be called when the {@link Loader} is no longer + * required. + */ + public void release() { + release(null); + } + + /** + * Releases the {@link Loader}, running {@code postLoadAction} on its thread. This method should + * be called when the {@link Loader} is no longer required. + * + * @param postLoadAction A {@link Runnable} to run on the loader's thread when + * {@link Loadable#load()} is no longer running. + */ + public void release(Runnable postLoadAction) { + if (currentTask != null) { + currentTask.cancel(true); + } + if (postLoadAction != null) { + downloadExecutorService.submit(postLoadAction); + } + downloadExecutorService.shutdown(); + } + + // LoaderErrorThrower implementation. + + @Override + public void maybeThrowError() throws IOException { + maybeThrowError(Integer.MIN_VALUE); + } + + @Override + public void maybeThrowError(int minRetryCount) throws IOException { + if (fatalError != null) { + throw fatalError; + } else if (currentTask != null) { + currentTask.maybeThrowError(minRetryCount == Integer.MIN_VALUE + ? currentTask.defaultMinRetryCount : minRetryCount); + } + } + + // Internal classes. + + @SuppressLint("HandlerLeak") + private final class LoadTask extends Handler implements Runnable { + + private static final String TAG = "LoadTask"; + + private final T loadable; + private final Loader.Callback callback; + public final int defaultMinRetryCount; + private final long startTimeMs; + + private IOException currentError; + private int errorCount; + + private volatile Thread executorThread; + private volatile boolean released; + + public LoadTask(Looper looper, T loadable, Loader.Callback callback, + int defaultMinRetryCount, long startTimeMs) { + super(looper); + this.loadable = loadable; + this.callback = callback; + this.defaultMinRetryCount = defaultMinRetryCount; + this.startTimeMs = startTimeMs; + } + + public void maybeThrowError(int minRetryCount) throws IOException { + if (currentError != null && errorCount > minRetryCount) { + throw currentError; + } + } + + public void start(long delayMillis) { + Assertions.checkState(currentTask == null); + currentTask = this; + if (delayMillis > 0) { + sendEmptyMessageDelayed(MSG_START, delayMillis); + } else { + submitToExecutor(); + } + } + + public void cancel(boolean released) { + this.released = released; + currentError = null; + if (hasMessages(MSG_START)) { + removeMessages(MSG_START); + if (!released) { + sendEmptyMessage(MSG_CANCEL); + } + } else { + loadable.cancelLoad(); + if (executorThread != null) { + executorThread.interrupt(); + } + } + if (released) { + finish(); + long nowMs = SystemClock.elapsedRealtime(); + callback.onLoadCanceled(loadable, nowMs, nowMs - startTimeMs, true); + } + } + + @Override + public void run() { + try { + executorThread = Thread.currentThread(); + if (!loadable.isLoadCanceled()) { + TraceUtil.beginSection("load:" + loadable.getClass().getSimpleName()); + try { + loadable.load(); + } finally { + TraceUtil.endSection(); + } + } + if (!released) { + sendEmptyMessage(MSG_END_OF_SOURCE); + } + } catch (IOException e) { + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, e).sendToTarget(); + } + } catch (InterruptedException e) { + // The load was canceled. + Assertions.checkState(loadable.isLoadCanceled()); + if (!released) { + sendEmptyMessage(MSG_END_OF_SOURCE); + } + } catch (Exception e) { + // This should never happen, but handle it anyway. + Log.e(TAG, "Unexpected exception loading stream", e); + if (!released) { + obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget(); + } + } catch (Error e) { + // We'd hope that the platform would kill the process if an Error is thrown here, but the + // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from + // the handler thread so that the process dies even if the executor behaves in this way. + Log.e(TAG, "Unexpected error loading stream", e); + if (!released) { + obtainMessage(MSG_FATAL_ERROR, e).sendToTarget(); + } + throw e; + } + } + + @Override + public void handleMessage(Message msg) { + if (released) { + return; + } + if (msg.what == MSG_START) { + submitToExecutor(); + return; + } + if (msg.what == MSG_FATAL_ERROR) { + throw (Error) msg.obj; + } + finish(); + long nowMs = SystemClock.elapsedRealtime(); + long durationMs = nowMs - startTimeMs; + if (loadable.isLoadCanceled()) { + callback.onLoadCanceled(loadable, nowMs, durationMs, false); + return; + } + switch (msg.what) { + case MSG_CANCEL: + callback.onLoadCanceled(loadable, nowMs, durationMs, false); + break; + case MSG_END_OF_SOURCE: + callback.onLoadCompleted(loadable, nowMs, durationMs); + break; + case MSG_IO_EXCEPTION: + currentError = (IOException) msg.obj; + int retryAction = callback.onLoadError(loadable, nowMs, durationMs, currentError); + if (retryAction == DONT_RETRY_FATAL) { + fatalError = currentError; + } else if (retryAction != DONT_RETRY) { + errorCount = retryAction == RETRY_RESET_ERROR_COUNT ? 1 : errorCount + 1; + start(getRetryDelayMillis()); + } + break; + } + } + + private void submitToExecutor() { + currentError = null; + downloadExecutorService.submit(currentTask); + } + + private void finish() { + currentTask = null; + } + + private long getRetryDelayMillis() { + return Math.min((errorCount - 1) * 1000, 5000); + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/LoaderErrorThrower.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/LoaderErrorThrower.java new file mode 100755 index 00000000000..f990b85015e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/LoaderErrorThrower.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import org.telegram.messenger.exoplayer2.upstream.Loader.Loadable; +import java.io.IOException; + +/** + * Conditionally throws errors affecting a {@link Loader}. + */ +public interface LoaderErrorThrower { + + /** + * Throws a fatal error, or a non-fatal error if loading is currently backed off and the current + * {@link Loadable} has incurred a number of errors greater than the {@link Loader}s default + * minimum number of retries. Else does nothing. + * + * @throws IOException The error. + */ + void maybeThrowError() throws IOException; + + /** + * Throws a fatal error, or a non-fatal error if loading is currently backed off and the current + * {@link Loadable} has incurred a number of errors greater than the specified minimum number + * of retries. Else does nothing. + * + * @param minRetryCount A minimum retry count that must be exceeded for a non-fatal error to be + * thrown. Should be non-negative. + * @throws IOException The error. + */ + void maybeThrowError(int minRetryCount) throws IOException; + + /** + * A {@link LoaderErrorThrower} that never throws. + */ + final class Dummy implements LoaderErrorThrower { + + @Override + public void maybeThrowError() throws IOException { + // Do nothing. + } + + @Override + public void maybeThrowError(int minRetryCount) throws IOException { + // Do nothing. + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ParsingLoadable.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ParsingLoadable.java new file mode 100755 index 00000000000..92f2838e988 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/ParsingLoadable.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.upstream.Loader.Loadable; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link Loadable} for objects that can be parsed from binary data using a {@link Parser}. + * + * @param The type of the object being loaded. + */ +public final class ParsingLoadable implements Loadable { + + /** + * Parses an object from loaded data. + */ + public interface Parser { + + /** + * Parses an object from a response. + * + * @param uri The source {@link Uri} of the response, after any redirection. + * @param inputStream An {@link InputStream} from which the response data can be read. + * @return The parsed object. + * @throws ParserException If an error occurs parsing the data. + * @throws IOException If an error occurs reading data from the stream. + */ + T parse(Uri uri, InputStream inputStream) throws IOException; + + } + + /** + * The {@link DataSpec} that defines the data to be loaded. + */ + public final DataSpec dataSpec; + /** + * The type of the data. One of the {@code DATA_TYPE_*} constants defined in {@link C}. For + * reporting only. + */ + public final int type; + + private final DataSource dataSource; + private final Parser parser; + + private volatile T result; + private volatile boolean isCanceled; + private volatile long bytesLoaded; + + /** + * @param dataSource A {@link DataSource} to use when loading the data. + * @param uri The {@link Uri} from which the object should be loaded. + * @param type See {@link #type}. + * @param parser Parses the object from the response. + */ + public ParsingLoadable(DataSource dataSource, Uri uri, int type, Parser parser) { + this.dataSource = dataSource; + this.dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP); + this.type = type; + this.parser = parser; + } + + /** + * Returns the loaded object, or null if an object has not been loaded. + */ + public final T getResult() { + return result; + } + + /** + * Returns the number of bytes loaded. In the case that the network response was compressed, the + * value returned is the size of the data after decompression. + * + * @return The number of bytes loaded. + */ + public long bytesLoaded() { + return bytesLoaded; + } + + @Override + public final void cancelLoad() { + // We don't actually cancel anything, but we need to record the cancellation so that + // isLoadCanceled can return the correct value. + isCanceled = true; + } + + @Override + public final boolean isLoadCanceled() { + return isCanceled; + } + + @Override + public final void load() throws IOException, InterruptedException { + DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); + try { + inputStream.open(); + result = parser.parse(dataSource.getUri(), inputStream); + } finally { + bytesLoaded = inputStream.bytesRead(); + inputStream.close(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/PriorityDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/PriorityDataSource.java new file mode 100755 index 00000000000..71d58c714df --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/PriorityDataSource.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.net.Uri; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.PriorityTaskManager; +import java.io.IOException; + +/** + * A {@link DataSource} that can be used as part of a task registered with a + * {@link PriorityTaskManager}. + *

        + * Calls to {@link #open(DataSpec)} and {@link #read(byte[], int, int)} are allowed to proceed only + * if there are no higher priority tasks registered to the {@link PriorityTaskManager}. If there + * exists a higher priority task then {@link PriorityTaskManager.PriorityTooLowException} is thrown. + *

        + * Instances of this class are intended to be used as parts of (possibly larger) tasks that are + * registered with the {@link PriorityTaskManager}, and hence do not register as tasks + * themselves. + */ +public final class PriorityDataSource implements DataSource { + + private final DataSource upstream; + private final PriorityTaskManager priorityTaskManager; + private final int priority; + + /** + * @param upstream The upstream {@link DataSource}. + * @param priorityTaskManager The priority manager to which the task is registered. + * @param priority The priority of the task. + */ + public PriorityDataSource(DataSource upstream, PriorityTaskManager priorityTaskManager, + int priority) { + this.upstream = Assertions.checkNotNull(upstream); + this.priorityTaskManager = Assertions.checkNotNull(priorityTaskManager); + this.priority = priority; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + priorityTaskManager.proceedOrThrow(priority); + return upstream.open(dataSpec); + } + + @Override + public int read(byte[] buffer, int offset, int max) throws IOException { + priorityTaskManager.proceedOrThrow(priority); + return upstream.read(buffer, offset, max); + } + + @Override + public Uri getUri() { + return upstream.getUri(); + } + + @Override + public void close() throws IOException { + upstream.close(); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/RawResourceDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/RawResourceDataSource.java new file mode 100755 index 00000000000..3e4f905b4ce --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/RawResourceDataSource.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +import android.content.Context; +import android.content.res.AssetFileDescriptor; +import android.content.res.Resources; +import android.net.Uri; +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A {@link DataSource} for reading a raw resource inside the APK. + *

        + * URIs supported by this source are of the form {@code rawresource:///rawResourceId}, where + * rawResourceId is the integer identifier of a raw resource. {@link #buildRawResourceUri(int)} can + * be used to build {@link Uri}s in this format. + */ +public final class RawResourceDataSource implements DataSource { + + /** + * Thrown when an {@link IOException} is encountered reading from a raw resource. + */ + public static class RawResourceDataSourceException extends IOException { + public RawResourceDataSourceException(String message) { + super(message); + } + + public RawResourceDataSourceException(IOException e) { + super(e); + } + } + + /** + * Builds a {@link Uri} for the specified raw resource identifier. + * + * @param rawResourceId A raw resource identifier (i.e. a constant defined in {@code R.raw}). + * @return The corresponding {@link Uri}. + */ + public static Uri buildRawResourceUri(int rawResourceId) { + return Uri.parse(RAW_RESOURCE_SCHEME + ":///" + rawResourceId); + } + + private static final String RAW_RESOURCE_SCHEME = "rawresource"; + + private final Resources resources; + private final TransferListener listener; + + private Uri uri; + private AssetFileDescriptor assetFileDescriptor; + private InputStream inputStream; + private long bytesRemaining; + private boolean opened; + + /** + * @param context A context. + */ + public RawResourceDataSource(Context context) { + this(context, null); + } + + /** + * @param context A context. + * @param listener An optional listener. + */ + public RawResourceDataSource(Context context, + TransferListener listener) { + this.resources = context.getResources(); + this.listener = listener; + } + + @Override + public long open(DataSpec dataSpec) throws RawResourceDataSourceException { + try { + uri = dataSpec.uri; + if (!TextUtils.equals(RAW_RESOURCE_SCHEME, uri.getScheme())) { + throw new RawResourceDataSourceException("URI must use scheme " + RAW_RESOURCE_SCHEME); + } + + int resourceId; + try { + resourceId = Integer.parseInt(uri.getLastPathSegment()); + } catch (NumberFormatException e) { + throw new RawResourceDataSourceException("Resource identifier must be an integer."); + } + + assetFileDescriptor = resources.openRawResourceFd(resourceId); + inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor()); + inputStream.skip(assetFileDescriptor.getStartOffset()); + long skipped = inputStream.skip(dataSpec.position); + if (skipped < dataSpec.position) { + // We expect the skip to be satisfied in full. If it isn't then we're probably trying to + // skip beyond the end of the data. + throw new EOFException(); + } + if (dataSpec.length != C.LENGTH_UNSET) { + bytesRemaining = dataSpec.length; + } else { + long assetFileDescriptorLength = assetFileDescriptor.getLength(); + // If the length is UNKNOWN_LENGTH then the asset extends to the end of the file. + bytesRemaining = assetFileDescriptorLength == AssetFileDescriptor.UNKNOWN_LENGTH + ? C.LENGTH_UNSET : (assetFileDescriptorLength - dataSpec.position); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } + + opened = true; + if (listener != null) { + listener.onTransferStart(this, dataSpec); + } + + return bytesRemaining; + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws RawResourceDataSourceException { + if (readLength == 0) { + return 0; + } else if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + + int bytesRead; + try { + int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength + : (int) Math.min(bytesRemaining, readLength); + bytesRead = inputStream.read(buffer, offset, bytesToRead); + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } + + if (bytesRead == -1) { + if (bytesRemaining != C.LENGTH_UNSET) { + // End of stream reached having not read sufficient data. + throw new RawResourceDataSourceException(new EOFException()); + } + return C.RESULT_END_OF_INPUT; + } + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + if (listener != null) { + listener.onBytesTransferred(this, bytesRead); + } + return bytesRead; + } + + @Override + public Uri getUri() { + return uri; + } + + @Override + public void close() throws RawResourceDataSourceException { + uri = null; + try { + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } finally { + inputStream = null; + try { + if (assetFileDescriptor != null) { + assetFileDescriptor.close(); + } + } catch (IOException e) { + throw new RawResourceDataSourceException(e); + } finally { + assetFileDescriptor = null; + if (opened) { + opened = false; + if (listener != null) { + listener.onTransferEnd(this); + } + } + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TeeDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TeeDataSource.java similarity index 83% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TeeDataSource.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TeeDataSource.java index de31aae5e93..7c32e912708 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/TeeDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TeeDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; -import org.telegram.messenger.exoplayer.C; -import org.telegram.messenger.exoplayer.util.Assertions; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; import java.io.IOException; /** @@ -39,7 +40,7 @@ public TeeDataSource(DataSource upstream, DataSink dataSink) { @Override public long open(DataSpec dataSpec) throws IOException { long dataLength = upstream.open(dataSpec); - if (dataSpec.length == C.LENGTH_UNBOUNDED && dataLength != C.LENGTH_UNBOUNDED) { + if (dataSpec.length == C.LENGTH_UNSET && dataLength != C.LENGTH_UNSET) { // Reconstruct dataSpec in order to provide the resolved length to the sink. dataSpec = new DataSpec(dataSpec.uri, dataSpec.absoluteStreamPosition, dataSpec.position, dataLength, dataSpec.key, dataSpec.flags); @@ -58,6 +59,11 @@ public int read(byte[] buffer, int offset, int max) throws IOException { return num; } + @Override + public Uri getUri() { + return upstream.getUri(); + } + @Override public void close() throws IOException { try { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TransferListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TransferListener.java new file mode 100755 index 00000000000..c641127d3fa --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/TransferListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream; + +/** + * A listener of data transfer events. + */ +public interface TransferListener { + + /** + * Called when a transfer starts. + * + * @param source The source performing the transfer. + * @param dataSpec Describes the data being transferred. + */ + void onTransferStart(S source, DataSpec dataSpec); + + /** + * Called incrementally during a transfer. + * + * @param source The source performing the transfer. + * @param bytesTransferred The number of bytes transferred since the previous call to this + * method (or if the first call, since the transfer was started). + */ + void onBytesTransferred(S source, int bytesTransferred); + + /** + * Called when a transfer ends. + * + * @param source The source performing the transfer. + */ + void onTransferEnd(S source); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UdpDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/UdpDataSource.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UdpDataSource.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/UdpDataSource.java index 6ec8987cdf2..c607bbe6b57 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/UdpDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/UdpDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream; +package org.telegram.messenger.exoplayer2.upstream; -import org.telegram.messenger.exoplayer.C; +import android.net.Uri; +import org.telegram.messenger.exoplayer2.C; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -27,17 +28,13 @@ /** * A UDP {@link DataSource}. */ -public final class UdpDataSource implements UriDataSource { +public final class UdpDataSource implements DataSource { /** * Thrown when an error is encountered when trying to read from a {@link UdpDataSource}. */ public static final class UdpDataSourceException extends IOException { - public UdpDataSourceException(String message) { - super(message); - } - public UdpDataSourceException(IOException cause) { super(cause); } @@ -54,24 +51,24 @@ public UdpDataSourceException(IOException cause) { */ public static final int DEAFULT_SOCKET_TIMEOUT_MILLIS = 8 * 1000; - private final TransferListener listener; - private final DatagramPacket packet; + private final TransferListener listener; private final int socketTimeoutMillis; + private final byte[] packetBuffer; + private final DatagramPacket packet; - private DataSpec dataSpec; + private Uri uri; private DatagramSocket socket; private MulticastSocket multicastSocket; private InetAddress address; private InetSocketAddress socketAddress; private boolean opened; - private byte[] packetBuffer; private int packetRemaining; /** * @param listener An optional listener. */ - public UdpDataSource(TransferListener listener) { + public UdpDataSource(TransferListener listener) { this(listener, DEFAULT_MAX_PACKET_SIZE); } @@ -79,7 +76,7 @@ public UdpDataSource(TransferListener listener) { * @param listener An optional listener. * @param maxPacketSize The maximum datagram packet size, in bytes. */ - public UdpDataSource(TransferListener listener, int maxPacketSize) { + public UdpDataSource(TransferListener listener, int maxPacketSize) { this(listener, maxPacketSize, DEAFULT_SOCKET_TIMEOUT_MILLIS); } @@ -89,7 +86,8 @@ public UdpDataSource(TransferListener listener, int maxPacketSize) { * @param socketTimeoutMillis The socket timeout in milliseconds. A timeout of zero is interpreted * as an infinite timeout. */ - public UdpDataSource(TransferListener listener, int maxPacketSize, int socketTimeoutMillis) { + public UdpDataSource(TransferListener listener, int maxPacketSize, + int socketTimeoutMillis) { this.listener = listener; this.socketTimeoutMillis = socketTimeoutMillis; packetBuffer = new byte[maxPacketSize]; @@ -98,9 +96,9 @@ public UdpDataSource(TransferListener listener, int maxPacketSize, int socketTim @Override public long open(DataSpec dataSpec) throws UdpDataSourceException { - this.dataSpec = dataSpec; - String host = dataSpec.uri.getHost(); - int port = dataSpec.uri.getPort(); + uri = dataSpec.uri; + String host = uri.getHost(); + int port = uri.getPort(); try { address = InetAddress.getByName(host); @@ -124,13 +122,17 @@ public long open(DataSpec dataSpec) throws UdpDataSourceException { opened = true; if (listener != null) { - listener.onTransferStart(); + listener.onTransferStart(this, dataSpec); } - return C.LENGTH_UNBOUNDED; + return C.LENGTH_UNSET; } @Override public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceException { + if (readLength == 0) { + return 0; + } + if (packetRemaining == 0) { // We've read all of the data from the current packet. Get another. try { @@ -138,10 +140,9 @@ public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceE } catch (IOException e) { throw new UdpDataSourceException(e); } - packetRemaining = packet.getLength(); if (listener != null) { - listener.onBytesTransferred(packetRemaining); + listener.onBytesTransferred(this, packetRemaining); } } @@ -152,8 +153,14 @@ public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceE return bytesToRead; } + @Override + public Uri getUri() { + return uri; + } + @Override public void close() { + uri = null; if (multicastSocket != null) { try { multicastSocket.leaveGroup(address); @@ -172,14 +179,9 @@ public void close() { if (opened) { opened = false; if (listener != null) { - listener.onTransferEnd(); + listener.onTransferEnd(this); } } } - @Override - public String getUri() { - return dataSpec == null ? null : dataSpec.uri.toString(); - } - } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/Cache.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/Cache.java new file mode 100755 index 00000000000..eb7bdb96d9b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/Cache.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import java.io.File; +import java.io.IOException; +import java.util.NavigableSet; +import java.util.Set; + +/** + * An interface for cache. + */ +public interface Cache { + + /** + * Listener of {@link Cache} events. + */ + interface Listener { + + /** + * Called when a {@link CacheSpan} is added to the cache. + * + * @param cache The source of the event. + * @param span The added {@link CacheSpan}. + */ + void onSpanAdded(Cache cache, CacheSpan span); + + /** + * Called when a {@link CacheSpan} is removed from the cache. + * + * @param cache The source of the event. + * @param span The removed {@link CacheSpan}. + */ + void onSpanRemoved(Cache cache, CacheSpan span); + + /** + * Called when an existing {@link CacheSpan} is accessed, causing it to be replaced. The new + * {@link CacheSpan} is guaranteed to represent the same data as the one it replaces, however + * {@link CacheSpan#file} and {@link CacheSpan#lastAccessTimestamp} may have changed. + *

        + * Note that for span replacement, {@link #onSpanAdded(Cache, CacheSpan)} and + * {@link #onSpanRemoved(Cache, CacheSpan)} are not called in addition to this method. + * + * @param cache The source of the event. + * @param oldSpan The old {@link CacheSpan}, which has been removed from the cache. + * @param newSpan The new {@link CacheSpan}, which has been added to the cache. + */ + void onSpanTouched(Cache cache, CacheSpan oldSpan, CacheSpan newSpan); + + } + + /** + * Thrown when an error is encountered when writing data. + */ + class CacheException extends IOException { + + public CacheException(String message) { + super(message); + } + + public CacheException(IOException cause) { + super(cause); + } + + } + + /** + * Registers a listener to listen for changes to a given key. + *

        + * No guarantees are made about the thread or threads on which the listener is called, but it is + * guaranteed that listener methods will be called in a serial fashion (i.e. one at a time) and in + * the same order as events occurred. + * + * @param key The key to listen to. + * @param listener The listener to add. + * @return The current spans for the key. + */ + NavigableSet addListener(String key, Listener listener); + + /** + * Unregisters a listener. + * + * @param key The key to stop listening to. + * @param listener The listener to remove. + */ + void removeListener(String key, Listener listener); + + /** + * Returns the cached spans for a given cache key. + * + * @param key The key for which spans should be returned. + * @return The spans for the key. May be null if there are no such spans. + */ + NavigableSet getCachedSpans(String key); + + /** + * Returns all keys in the cache. + * + * @return All the keys in the cache. + */ + Set getKeys(); + + /** + * Returns the total disk space in bytes used by the cache. + * + * @return The total disk space in bytes. + */ + long getCacheSpace(); + + /** + * A caller should invoke this method when they require data from a given position for a given + * key. + *

        + * If there is a cache entry that overlaps the position, then the returned {@link CacheSpan} + * defines the file in which the data is stored. {@link CacheSpan#isCached} is true. The caller + * may read from the cache file, but does not acquire any locks. + *

        + * If there is no cache entry overlapping {@code offset}, then the returned {@link CacheSpan} + * defines a hole in the cache starting at {@code position} into which the caller may write as it + * obtains the data from some other source. The returned {@link CacheSpan} serves as a lock. + * Whilst the caller holds the lock it may write data into the hole. It may split data into + * multiple files. When the caller has finished writing a file it should commit it to the cache + * by calling {@link #commitFile(File)}. When the caller has finished writing, it must release + * the lock by calling {@link #releaseHoleSpan}. + * + * @param key The key of the data being requested. + * @param position The position of the data being requested. + * @return The {@link CacheSpan}. + * @throws InterruptedException + */ + CacheSpan startReadWrite(String key, long position) throws InterruptedException, CacheException; + + /** + * Same as {@link #startReadWrite(String, long)}. However, if the cache entry is locked, then + * instead of blocking, this method will return null as the {@link CacheSpan}. + * + * @param key The key of the data being requested. + * @param position The position of the data being requested. + * @return The {@link CacheSpan}. Or null if the cache entry is locked. + */ + CacheSpan startReadWriteNonBlocking(String key, long position) throws CacheException; + + /** + * Obtains a cache file into which data can be written. Must only be called when holding a + * corresponding hole {@link CacheSpan} obtained from {@link #startReadWrite(String, long)}. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param maxLength The maximum length of the data to be written. Used only to ensure that there + * is enough space in the cache. + * @return The file into which data should be written. + */ + File startFile(String key, long position, long maxLength) throws CacheException; + + /** + * Commits a file into the cache. Must only be called when holding a corresponding hole + * {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} + * + * @param file A newly written cache file. + */ + void commitFile(File file) throws CacheException; + + /** + * Releases a {@link CacheSpan} obtained from {@link #startReadWrite(String, long)} which + * corresponded to a hole in the cache. + * + * @param holeSpan The {@link CacheSpan} being released. + */ + void releaseHoleSpan(CacheSpan holeSpan); + + /** + * Removes a cached {@link CacheSpan} from the cache, deleting the underlying file. + * + * @param span The {@link CacheSpan} to remove. + */ + void removeSpan(CacheSpan span) throws CacheException; + + /** + * Queries if a range is entirely available in the cache. + * + * @param key The cache key for the data. + * @param position The starting position of the data. + * @param length The length of the data. + * @return true if the data is available in the Cache otherwise false; + */ + boolean isCached(String key, long position, long length); + + /** + * Sets the content length for the given key. + * + * @param key The cache key for the data. + * @param length The length of the data. + */ + void setContentLength(String key, long length) throws CacheException; + + /** + * Returns the content length for the given key if one set, or {@link + * org.telegram.messenger.exoplayer2.C#LENGTH_UNSET} otherwise. + * + * @param key The cache key for the data. + */ + long getContentLength(String key); + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSink.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSink.java new file mode 100755 index 00000000000..1907a91ae8e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSink.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.DataSink; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.cache.Cache.CacheException; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.ReusableBufferedOutputStream; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Writes data into a cache. + */ +public final class CacheDataSink implements DataSink { + + private final Cache cache; + private final long maxCacheFileSize; + private final int bufferSize; + + private DataSpec dataSpec; + private File file; + private OutputStream outputStream; + private FileOutputStream underlyingFileOutputStream; + private long outputStreamBytesWritten; + private long dataSpecBytesWritten; + private ReusableBufferedOutputStream bufferedOutputStream; + + /** + * Thrown when IOException is encountered when writing data into sink. + */ + public static class CacheDataSinkException extends CacheException { + + public CacheDataSinkException(IOException cause) { + super(cause); + } + + } + + /** + * @param cache The cache into which data should be written. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for + * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into + * multiple cache files. + */ + public CacheDataSink(Cache cache, long maxCacheFileSize) { + this(cache, maxCacheFileSize, 0); + } + + /** + * @param cache The cache into which data should be written. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the sink is opened for + * a {@link DataSpec} whose size exceeds this value, then the data will be fragmented into + * multiple cache files. + * @param bufferSize The buffer size in bytes for writing to a cache file. A zero or negative + * value disables buffering. + */ + public CacheDataSink(Cache cache, long maxCacheFileSize, int bufferSize) { + this.cache = Assertions.checkNotNull(cache); + this.maxCacheFileSize = maxCacheFileSize; + this.bufferSize = bufferSize; + } + + @Override + public void open(DataSpec dataSpec) throws CacheDataSinkException { + this.dataSpec = dataSpec; + if (dataSpec.length == C.LENGTH_UNSET) { + return; + } + dataSpecBytesWritten = 0; + try { + openNextOutputStream(); + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + @Override + public void write(byte[] buffer, int offset, int length) throws CacheDataSinkException { + if (dataSpec.length == C.LENGTH_UNSET) { + return; + } + try { + int bytesWritten = 0; + while (bytesWritten < length) { + if (outputStreamBytesWritten == maxCacheFileSize) { + closeCurrentOutputStream(); + openNextOutputStream(); + } + int bytesToWrite = (int) Math.min(length - bytesWritten, + maxCacheFileSize - outputStreamBytesWritten); + outputStream.write(buffer, offset + bytesWritten, bytesToWrite); + bytesWritten += bytesToWrite; + outputStreamBytesWritten += bytesToWrite; + dataSpecBytesWritten += bytesToWrite; + } + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + @Override + public void close() throws CacheDataSinkException { + if (dataSpec == null || dataSpec.length == C.LENGTH_UNSET) { + return; + } + try { + closeCurrentOutputStream(); + } catch (IOException e) { + throw new CacheDataSinkException(e); + } + } + + private void openNextOutputStream() throws IOException { + file = cache.startFile(dataSpec.key, dataSpec.absoluteStreamPosition + dataSpecBytesWritten, + Math.min(dataSpec.length - dataSpecBytesWritten, maxCacheFileSize)); + underlyingFileOutputStream = new FileOutputStream(file); + if (bufferSize > 0) { + if (bufferedOutputStream == null) { + bufferedOutputStream = new ReusableBufferedOutputStream(underlyingFileOutputStream, + bufferSize); + } else { + bufferedOutputStream.reset(underlyingFileOutputStream); + } + outputStream = bufferedOutputStream; + } else { + outputStream = underlyingFileOutputStream; + } + outputStreamBytesWritten = 0; + } + + @SuppressWarnings("ThrowFromFinallyBlock") + private void closeCurrentOutputStream() throws IOException { + if (outputStream == null) { + return; + } + + boolean success = false; + try { + outputStream.flush(); + underlyingFileOutputStream.getFD().sync(); + success = true; + } finally { + Util.closeQuietly(outputStream); + outputStream = null; + File fileToCommit = file; + file = null; + if (success) { + cache.commitFile(fileToCommit); + } else { + fileToCommit.delete(); + } + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSinkFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSinkFactory.java new file mode 100755 index 00000000000..6daf7470a7c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSinkFactory.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 The Android Open Sink Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.upstream.DataSink; + +/** + * A {@link DataSink.Factory} that produces {@link CacheDataSink}. + */ +public final class CacheDataSinkFactory implements DataSink.Factory { + + private final Cache cache; + private final long maxCacheFileSize; + + /** + * @see CacheDataSink#CacheDataSink(Cache, long) + */ + public CacheDataSinkFactory(Cache cache, long maxCacheFileSize) { + this.cache = cache; + this.maxCacheFileSize = maxCacheFileSize; + } + + @Override + public DataSink createDataSink() { + return new CacheDataSink(cache, maxCacheFileSize); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java new file mode 100755 index 00000000000..7e81516e1e5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSource.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import android.net.Uri; +import android.support.annotation.IntDef; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.DataSink; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSourceException; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import org.telegram.messenger.exoplayer2.upstream.FileDataSource; +import org.telegram.messenger.exoplayer2.upstream.TeeDataSource; +import org.telegram.messenger.exoplayer2.upstream.cache.Cache.CacheException; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A {@link DataSource} that reads and writes a {@link Cache}. Requests are fulfilled from the cache + * when possible. When data is not cached it is requested from an upstream {@link DataSource} and + * written into the cache. + */ +public final class CacheDataSource implements DataSource { + + /** + * Default maximum single cache file size. + * + * @see #CacheDataSource(Cache, DataSource, int) + * @see #CacheDataSource(Cache, DataSource, int, long) + */ + public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024; + + /** + * Flags controlling the cache's behavior. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, value = {FLAG_BLOCK_ON_CACHE, FLAG_IGNORE_CACHE_ON_ERROR, + FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}) + public @interface Flags {} + /** + * A flag indicating whether we will block reads if the cache key is locked. If this flag is + * set, then we will read from upstream if the cache key is locked. + */ + public static final int FLAG_BLOCK_ON_CACHE = 1 << 0; + + /** + * A flag indicating whether the cache is bypassed following any cache related error. If set + * then cache related exceptions may be thrown for one cycle of open, read and close calls. + * Subsequent cycles of these calls will then bypass the cache. + */ + public static final int FLAG_IGNORE_CACHE_ON_ERROR = 1 << 1; + + /** + * A flag indicating that the cache should be bypassed for requests whose lengths are unset. + */ + public static final int FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS = 1 << 2; + + /** + * Listener of {@link CacheDataSource} events. + */ + public interface EventListener { + + /** + * Called when bytes have been read from the cache. + * + * @param cacheSizeBytes Current cache size in bytes. + * @param cachedBytesRead Total bytes read from the cache since this method was last called. + */ + void onCachedBytesRead(long cacheSizeBytes, long cachedBytesRead); + + } + + private final Cache cache; + private final DataSource cacheReadDataSource; + private final DataSource cacheWriteDataSource; + private final DataSource upstreamDataSource; + private final EventListener eventListener; + + private final boolean blockOnCache; + private final boolean ignoreCacheOnError; + private final boolean ignoreCacheForUnsetLengthRequests; + + private DataSource currentDataSource; + private boolean currentRequestUnbounded; + private Uri uri; + private int flags; + private String key; + private long readPosition; + private long bytesRemaining; + private CacheSpan lockedSpan; + private boolean seenCacheError; + private boolean currentRequestIgnoresCache; + private long totalCachedBytesRead; + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}. + */ + public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) { + this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * Constructs an instance with default {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. The sink is configured to fragment data such that no single + * cache file is greater than maxCacheFileSize bytes. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size + * exceeds this value, then the data will be fragmented into multiple cache files. The + * finer-grained this is the finer-grained the eviction policy can be. + */ + public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags, + long maxCacheFileSize) { + this(cache, upstream, new FileDataSource(), new CacheDataSink(cache, maxCacheFileSize), + flags, null); + } + + /** + * Constructs an instance with arbitrary {@link DataSource} and {@link DataSink} instances for + * reading and writing the cache. One use of this constructor is to allow data to be transformed + * before it is written to disk. + * + * @param cache The cache. + * @param upstream A {@link DataSource} for reading data not in the cache. + * @param cacheReadDataSource A {@link DataSource} for reading data from the cache. + * @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. + * @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link + * #FLAG_IGNORE_CACHE_ON_ERROR} or 0. + * @param eventListener An optional {@link EventListener} to receive events. + */ + public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource, + DataSink cacheWriteDataSink, @Flags int flags, EventListener eventListener) { + this.cache = cache; + this.cacheReadDataSource = cacheReadDataSource; + this.blockOnCache = (flags & FLAG_BLOCK_ON_CACHE) != 0; + this.ignoreCacheOnError = (flags & FLAG_IGNORE_CACHE_ON_ERROR) != 0; + this.ignoreCacheForUnsetLengthRequests = + (flags & FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS) != 0; + this.upstreamDataSource = upstream; + if (cacheWriteDataSink != null) { + this.cacheWriteDataSource = new TeeDataSource(upstream, cacheWriteDataSink); + } else { + this.cacheWriteDataSource = null; + } + this.eventListener = eventListener; + } + + @Override + public long open(DataSpec dataSpec) throws IOException { + try { + uri = dataSpec.uri; + flags = dataSpec.flags; + key = dataSpec.key != null ? dataSpec.key : uri.toString(); + readPosition = dataSpec.position; + currentRequestIgnoresCache = (ignoreCacheOnError && seenCacheError) + || (dataSpec.length == C.LENGTH_UNSET && ignoreCacheForUnsetLengthRequests); + if (dataSpec.length != C.LENGTH_UNSET || currentRequestIgnoresCache) { + bytesRemaining = dataSpec.length; + } else { + bytesRemaining = cache.getContentLength(key); + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= dataSpec.position; + } + } + openNextSource(true); + return bytesRemaining; + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + @Override + public int read(byte[] buffer, int offset, int readLength) throws IOException { + if (readLength == 0) { + return 0; + } + if (bytesRemaining == 0) { + return C.RESULT_END_OF_INPUT; + } + try { + int bytesRead = currentDataSource.read(buffer, offset, readLength); + if (bytesRead >= 0) { + if (currentDataSource == cacheReadDataSource) { + totalCachedBytesRead += bytesRead; + } + readPosition += bytesRead; + if (bytesRemaining != C.LENGTH_UNSET) { + bytesRemaining -= bytesRead; + } + } else { + if (currentRequestUnbounded) { + // We only do unbounded requests to upstream and only when we don't know the actual stream + // length. So we reached the end of stream. + setContentLength(readPosition); + bytesRemaining = 0; + } + closeCurrentSource(); + if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { + if (openNextSource(false)) { + return read(buffer, offset, readLength); + } + } + } + return bytesRead; + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + @Override + public Uri getUri() { + return currentDataSource == upstreamDataSource ? currentDataSource.getUri() : uri; + } + + @Override + public void close() throws IOException { + uri = null; + notifyBytesRead(); + try { + closeCurrentSource(); + } catch (IOException e) { + handleBeforeThrow(e); + throw e; + } + } + + /** + * Opens the next source. If the cache contains data spanning the current read position then + * {@link #cacheReadDataSource} is opened to read from it. Else {@link #upstreamDataSource} is + * opened to read from the upstream source and write into the cache. + * @param initial Whether it is the initial open call. + */ + private boolean openNextSource(boolean initial) throws IOException { + DataSpec dataSpec; + CacheSpan span; + if (currentRequestIgnoresCache) { + span = null; + } else if (blockOnCache) { + try { + span = cache.startReadWrite(key, readPosition); + } catch (InterruptedException e) { + throw new InterruptedIOException(); + } + } else { + span = cache.startReadWriteNonBlocking(key, readPosition); + } + + if (span == null) { + // The data is locked in the cache, or we're ignoring the cache. Bypass the cache and read + // from upstream. + currentDataSource = upstreamDataSource; + dataSpec = new DataSpec(uri, readPosition, bytesRemaining, key, flags); + } else if (span.isCached) { + // Data is cached, read from cache. + Uri fileUri = Uri.fromFile(span.file); + long filePosition = readPosition - span.position; + long length = span.length - filePosition; + if (bytesRemaining != C.LENGTH_UNSET) { + length = Math.min(length, bytesRemaining); + } + dataSpec = new DataSpec(fileUri, readPosition, filePosition, length, key, flags); + currentDataSource = cacheReadDataSource; + } else { + // Data is not cached, and data is not locked, read from upstream with cache backing. + lockedSpan = span; + long length; + if (span.isOpenEnded()) { + length = bytesRemaining; + } else { + length = span.length; + if (bytesRemaining != C.LENGTH_UNSET) { + length = Math.min(length, bytesRemaining); + } + } + dataSpec = new DataSpec(uri, readPosition, length, key, flags); + currentDataSource = cacheWriteDataSource != null ? cacheWriteDataSource + : upstreamDataSource; + } + + currentRequestUnbounded = dataSpec.length == C.LENGTH_UNSET; + boolean successful = false; + long currentBytesRemaining = 0; + try { + currentBytesRemaining = currentDataSource.open(dataSpec); + successful = true; + } catch (IOException e) { + // if this isn't the initial open call (we had read some bytes) and an unbounded range request + // failed because of POSITION_OUT_OF_RANGE then mute the exception. We are trying to find the + // end of the stream. + if (!initial && currentRequestUnbounded) { + Throwable cause = e; + while (cause != null) { + if (cause instanceof DataSourceException) { + int reason = ((DataSourceException) cause).reason; + if (reason == DataSourceException.POSITION_OUT_OF_RANGE) { + e = null; + break; + } + } + cause = cause.getCause(); + } + } + if (e != null) { + throw e; + } + } + + // If we did an unbounded request (which means it's to upstream and + // bytesRemaining == C.LENGTH_UNSET) and got a resolved length from open() request + if (currentRequestUnbounded && currentBytesRemaining != C.LENGTH_UNSET) { + bytesRemaining = currentBytesRemaining; + // If writing into cache + if (lockedSpan != null) { + setContentLength(dataSpec.position + bytesRemaining); + } + } + return successful; + } + + private void setContentLength(long length) throws IOException { + cache.setContentLength(key, length); + } + + private void closeCurrentSource() throws IOException { + if (currentDataSource == null) { + return; + } + try { + currentDataSource.close(); + currentDataSource = null; + currentRequestUnbounded = false; + } finally { + if (lockedSpan != null) { + cache.releaseHoleSpan(lockedSpan); + lockedSpan = null; + } + } + } + + private void handleBeforeThrow(IOException exception) { + if (currentDataSource == cacheReadDataSource || exception instanceof CacheException) { + seenCacheError = true; + } + } + + private void notifyBytesRead() { + if (eventListener != null && totalCachedBytesRead > 0) { + eventListener.onCachedBytesRead(cache.getCacheSpace(), totalCachedBytesRead); + totalCachedBytesRead = 0; + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java new file mode 100755 index 00000000000..983dd312974 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheDataSourceFactory.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.upstream.DataSink; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSource.Factory; +import org.telegram.messenger.exoplayer2.upstream.FileDataSourceFactory; +import org.telegram.messenger.exoplayer2.upstream.cache.CacheDataSource.EventListener; + +/** + * A {@link DataSource.Factory} that produces {@link CacheDataSource}. + */ +public final class CacheDataSourceFactory implements DataSource.Factory { + + private final Cache cache; + private final DataSource.Factory upstreamFactory; + private final DataSource.Factory cacheReadDataSourceFactory; + private final DataSink.Factory cacheWriteDataSinkFactory; + private final int flags; + private final EventListener eventListener; + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, int) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags) { + this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE); + } + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long) + */ + public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags, + long maxCacheFileSize) { + this(cache, upstreamFactory, new FileDataSourceFactory(), + new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null); + } + + /** + * @see CacheDataSource#CacheDataSource(Cache, DataSource, DataSource, DataSink, int, + * EventListener) + */ + public CacheDataSourceFactory(Cache cache, Factory upstreamFactory, + Factory cacheReadDataSourceFactory, + DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) { + this.cache = cache; + this.upstreamFactory = upstreamFactory; + this.cacheReadDataSourceFactory = cacheReadDataSourceFactory; + this.cacheWriteDataSinkFactory = cacheWriteDataSinkFactory; + this.flags = flags; + this.eventListener = eventListener; + } + + @Override + public DataSource createDataSource() { + return new CacheDataSource(cache, upstreamFactory.createDataSource(), + cacheReadDataSourceFactory.createDataSource(), + cacheWriteDataSinkFactory.createDataSink(), flags, eventListener); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheEvictor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheEvictor.java similarity index 77% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheEvictor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheEvictor.java index 9089c497edd..1ad477635b4 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/CacheEvictor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheEvictor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream.cache; +package org.telegram.messenger.exoplayer2.upstream.cache; /** * Evicts data from a {@link Cache}. Implementations should call {@link Cache#removeSpan(CacheSpan)} @@ -21,17 +21,19 @@ */ public interface CacheEvictor extends Cache.Listener { - /** Invoked when cache has beeen initialized. */ + /** + * Called when cache has been initialized. + */ void onCacheInitialized(); /** - * Invoked when a writer starts writing to the cache. + * Called when a writer starts writing to the cache. * * @param cache The source of the event. * @param key The key being written. * @param position The starting position of the data being written. - * @param length The maximum length of the data being written. + * @param maxLength The maximum length of the data being written. */ - void onStartFile(Cache cache, String key, long position, long length); + void onStartFile(Cache cache, String key, long position, long maxLength); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheSpan.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheSpan.java new file mode 100755 index 00000000000..daf042b9b2e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CacheSpan.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.C; +import java.io.File; + +/** + * Defines a span of data that may or may not be cached (as indicated by {@link #isCached}). + */ +public class CacheSpan implements Comparable { + + /** + * The cache key that uniquely identifies the original stream. + */ + public final String key; + /** + * The position of the {@link CacheSpan} in the original stream. + */ + public final long position; + /** + * The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an open-ended hole. + */ + public final long length; + /** + * Whether the {@link CacheSpan} is cached. + */ + public final boolean isCached; + /** + * The file corresponding to this {@link CacheSpan}, or null if {@link #isCached} is false. + */ + public final File file; + /** + * The last access timestamp, or {@link C#TIME_UNSET} if {@link #isCached} is false. + */ + public final long lastAccessTimestamp; + + /** + * Creates a hole CacheSpan which isn't cached, has no last access time and no file associated. + * + * @param key The cache key that uniquely identifies the original stream. + * @param position The position of the {@link CacheSpan} in the original stream. + * @param length The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an + * open-ended hole. + */ + public CacheSpan(String key, long position, long length) { + this(key, position, length, C.TIME_UNSET, null); + } + + /** + * Creates a CacheSpan. + * + * @param key The cache key that uniquely identifies the original stream. + * @param position The position of the {@link CacheSpan} in the original stream. + * @param length The length of the {@link CacheSpan}, or {@link C#LENGTH_UNSET} if this is an + * open-ended hole. + * @param lastAccessTimestamp The last access timestamp, or {@link C#TIME_UNSET} if + * {@link #isCached} is false. + * @param file The file corresponding to this {@link CacheSpan}, or null if it's a hole. + */ + public CacheSpan(String key, long position, long length, long lastAccessTimestamp, File file) { + this.key = key; + this.position = position; + this.length = length; + this.isCached = file != null; + this.file = file; + this.lastAccessTimestamp = lastAccessTimestamp; + } + + /** + * Returns whether this is an open-ended {@link CacheSpan}. + */ + public boolean isOpenEnded() { + return length == C.LENGTH_UNSET; + } + + /** + * Returns whether this is a hole {@link CacheSpan}. + */ + public boolean isHoleSpan() { + return !isCached; + } + + @Override + public int compareTo(CacheSpan another) { + if (!key.equals(another.key)) { + return key.compareTo(another.key); + } + long startOffsetDiff = position - another.position; + return startOffsetDiff == 0 ? 0 : ((startOffsetDiff < 0) ? -1 : 1); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContent.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContent.java new file mode 100755 index 00000000000..d41a992f487 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContent.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.cache.Cache.CacheException; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.TreeSet; + +/** + * Defines the cached content for a single stream. + */ +/*package*/ final class CachedContent { + + /** + * The cache file id that uniquely identifies the original stream. + */ + public final int id; + /** + * The cache key that uniquely identifies the original stream. + */ + public final String key; + /** + * The cached spans of this content. + */ + private final TreeSet cachedSpans; + /** + * The length of the original stream, or {@link C#LENGTH_UNSET} if the length is unknown. + */ + private long length; + + /** + * Reads an instance from a {@link DataInputStream}. + * + * @param input Input stream containing values needed to initialize CachedContent instance. + * @throws IOException If an error occurs during reading values. + */ + public CachedContent(DataInputStream input) throws IOException { + this(input.readInt(), input.readUTF(), input.readLong()); + } + + /** + * Creates a CachedContent. + * + * @param id The cache file id. + * @param key The cache stream key. + * @param length The length of the original stream. + */ + public CachedContent(int id, String key, long length) { + this.id = id; + this.key = key; + this.length = length; + this.cachedSpans = new TreeSet<>(); + } + + /** + * Writes the instance to a {@link DataOutputStream}. + * + * @param output Output stream to store the values. + * @throws IOException If an error occurs during writing values to output. + */ + public void writeToStream(DataOutputStream output) throws IOException { + output.writeInt(id); + output.writeUTF(key); + output.writeLong(length); + } + + /** Returns the length of the content. */ + public long getLength() { + return length; + } + + /** Sets the length of the content. */ + public void setLength(long length) { + this.length = length; + } + + /** Adds the given {@link SimpleCacheSpan} which contains a part of the content. */ + public void addSpan(SimpleCacheSpan span) { + cachedSpans.add(span); + } + + /** Returns a set of all {@link SimpleCacheSpan}s. */ + public TreeSet getSpans() { + return cachedSpans; + } + + /** + * Returns the span containing the position. If there isn't one, it returns a hole span + * which defines the maximum extents of the hole in the cache. + */ + public SimpleCacheSpan getSpan(long position) { + SimpleCacheSpan span = getSpanInternal(position); + if (!span.isCached) { + SimpleCacheSpan ceilEntry = cachedSpans.ceiling(span); + return ceilEntry == null ? SimpleCacheSpan.createOpenHole(key, position) + : SimpleCacheSpan.createClosedHole(key, position, ceilEntry.position - position); + } + return span; + } + + /** Queries if a range is entirely available in the cache. */ + public boolean isCached(long position, long length) { + SimpleCacheSpan floorSpan = getSpanInternal(position); + if (!floorSpan.isCached) { + // We don't have a span covering the start of the queried region. + return false; + } + long queryEndPosition = position + length; + long currentEndPosition = floorSpan.position + floorSpan.length; + if (currentEndPosition >= queryEndPosition) { + // floorSpan covers the queried region. + return true; + } + for (SimpleCacheSpan next : cachedSpans.tailSet(floorSpan, false)) { + if (next.position > currentEndPosition) { + // There's a hole in the cache within the queried region. + return false; + } + // We expect currentEndPosition to always equal (next.position + next.length), but + // perform a max check anyway to guard against the existence of overlapping spans. + currentEndPosition = Math.max(currentEndPosition, next.position + next.length); + if (currentEndPosition >= queryEndPosition) { + // We've found spans covering the queried region. + return true; + } + } + // We ran out of spans before covering the queried region. + return false; + } + + /** + * Copies the given span with an updated last access time. Passed span becomes invalid after this + * call. + * + * @param cacheSpan Span to be copied and updated. + * @return a span with the updated last access time. + * @throws CacheException If renaming of the underlying span file failed. + */ + public SimpleCacheSpan touch(SimpleCacheSpan cacheSpan) throws CacheException { + // Remove the old span from the in-memory representation. + Assertions.checkState(cachedSpans.remove(cacheSpan)); + // Obtain a new span with updated last access timestamp. + SimpleCacheSpan newCacheSpan = cacheSpan.copyWithUpdatedLastAccessTime(id); + // Rename the cache file + if (!cacheSpan.file.renameTo(newCacheSpan.file)) { + throw new CacheException("Renaming of " + cacheSpan.file + " to " + newCacheSpan.file + + " failed."); + } + // Add the updated span back into the in-memory representation. + cachedSpans.add(newCacheSpan); + return newCacheSpan; + } + + /** Returns whether there are any spans cached. */ + public boolean isEmpty() { + return cachedSpans.isEmpty(); + } + + /** Removes the given span from cache. */ + public boolean removeSpan(CacheSpan span) { + if (cachedSpans.remove(span)) { + span.file.delete(); + return true; + } + return false; + } + + /** Calculates a hash code for the header of this {@code CachedContent}. */ + public int headerHashCode() { + int result = id; + result = 31 * result + key.hashCode(); + result = 31 * result + (int) (length ^ (length >>> 32)); + return result; + } + + /** + * Returns the span containing the position. If there isn't one, it returns the lookup span it + * used for searching. + */ + private SimpleCacheSpan getSpanInternal(long position) { + SimpleCacheSpan lookupSpan = SimpleCacheSpan.createLookup(key, position); + SimpleCacheSpan floorSpan = cachedSpans.floor(lookupSpan); + return floorSpan == null || floorSpan.position + floorSpan.length <= position ? lookupSpan + : floorSpan; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java new file mode 100755 index 00000000000..63a0799912d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/CachedContentIndex.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import android.util.SparseArray; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.upstream.cache.Cache.CacheException; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.AtomicFile; +import org.telegram.messenger.exoplayer2.util.ReusableBufferedOutputStream; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Random; +import java.util.Set; +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * This class maintains the index of cached content. + */ +/*package*/ final class CachedContentIndex { + + public static final String FILE_NAME = "cached_content_index.exi"; + + private static final int VERSION = 1; + + private static final int FLAG_ENCRYPTED_INDEX = 1; + + private final HashMap keyToContent; + private final SparseArray idToKey; + private final AtomicFile atomicFile; + private final Cipher cipher; + private final SecretKeySpec secretKeySpec; + private boolean changed; + private ReusableBufferedOutputStream bufferedOutputStream; + + /** Creates a CachedContentIndex which works on the index file in the given cacheDir. */ + public CachedContentIndex(File cacheDir) { + this(cacheDir, null); + } + + /** Creates a CachedContentIndex which works on the index file in the given cacheDir. */ + public CachedContentIndex(File cacheDir, byte[] secretKey) { + if (secretKey != null) { + try { + cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); + secretKeySpec = new SecretKeySpec(secretKey, "AES"); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException(e); // Should never happen. + } + } else { + cipher = null; + secretKeySpec = null; + } + keyToContent = new HashMap<>(); + idToKey = new SparseArray<>(); + atomicFile = new AtomicFile(new File(cacheDir, FILE_NAME)); + } + + /** Loads the index file. */ + public void load() { + Assertions.checkState(!changed); + if (!readFile()) { + atomicFile.delete(); + keyToContent.clear(); + idToKey.clear(); + } + } + + /** Stores the index data to index file if there is a change. */ + public void store() throws CacheException { + if (!changed) { + return; + } + writeFile(); + changed = false; + } + + /** + * Adds the given key to the index if it isn't there already. + * + * @param key The cache key that uniquely identifies the original stream. + * @return A new or existing CachedContent instance with the given key. + */ + public CachedContent add(String key) { + CachedContent cachedContent = keyToContent.get(key); + if (cachedContent == null) { + cachedContent = addNew(key, C.LENGTH_UNSET); + } + return cachedContent; + } + + /** Returns a CachedContent instance with the given key or null if there isn't one. */ + public CachedContent get(String key) { + return keyToContent.get(key); + } + + /** + * Returns a Collection of all CachedContent instances in the index. The collection is backed by + * the {@code keyToContent} map, so changes to the map are reflected in the collection, and + * vice-versa. If the map is modified while an iteration over the collection is in progress + * (except through the iterator's own remove operation), the results of the iteration are + * undefined. + */ + public Collection getAll() { + return keyToContent.values(); + } + + /** Returns an existing or new id assigned to the given key. */ + public int assignIdForKey(String key) { + return add(key).id; + } + + /** Returns the key which has the given id assigned. */ + public String getKeyForId(int id) { + return idToKey.get(id); + } + + /** + * Removes {@link CachedContent} with the given key from index. It shouldn't contain any spans. + * + * @throws IllegalStateException If {@link CachedContent} isn't empty. + */ + public void removeEmpty(String key) { + CachedContent cachedContent = keyToContent.remove(key); + if (cachedContent != null) { + Assertions.checkState(cachedContent.isEmpty()); + idToKey.remove(cachedContent.id); + changed = true; + } + } + + /** Removes empty {@link CachedContent} instances from index. */ + public void removeEmpty() { + LinkedList cachedContentToBeRemoved = new LinkedList<>(); + for (CachedContent cachedContent : keyToContent.values()) { + if (cachedContent.isEmpty()) { + cachedContentToBeRemoved.add(cachedContent.key); + } + } + for (String key : cachedContentToBeRemoved) { + removeEmpty(key); + } + } + + /** + * Returns a set of all content keys. The set is backed by the {@code keyToContent} map, so + * changes to the map are reflected in the set, and vice-versa. If the map is modified while an + * iteration over the set is in progress (except through the iterator's own remove operation), the + * results of the iteration are undefined. + */ + public Set getKeys() { + return keyToContent.keySet(); + } + + /** + * Sets the content length for the given key. A new {@link CachedContent} is added if there isn't + * one already with the given key. + */ + public void setContentLength(String key, long length) { + CachedContent cachedContent = get(key); + if (cachedContent != null) { + if (cachedContent.getLength() != length) { + cachedContent.setLength(length); + changed = true; + } + } else { + addNew(key, length); + } + } + + /** + * Returns the content length for the given key if one set, or {@link + * org.telegram.messenger.exoplayer2.C#LENGTH_UNSET} otherwise. + */ + public long getContentLength(String key) { + CachedContent cachedContent = get(key); + return cachedContent == null ? C.LENGTH_UNSET : cachedContent.getLength(); + } + + private boolean readFile() { + DataInputStream input = null; + try { + InputStream inputStream = new BufferedInputStream(atomicFile.openRead()); + input = new DataInputStream(inputStream); + int version = input.readInt(); + if (version != VERSION) { + // Currently there is no other version + return false; + } + + int flags = input.readInt(); + if ((flags & FLAG_ENCRYPTED_INDEX) != 0) { + if (cipher == null) { + return false; + } + byte[] initializationVector = new byte[16]; + input.read(initializationVector); + IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector); + try { + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalStateException(e); + } + input = new DataInputStream(new CipherInputStream(inputStream, cipher)); + } + + int count = input.readInt(); + int hashCode = 0; + for (int i = 0; i < count; i++) { + CachedContent cachedContent = new CachedContent(input); + addNew(cachedContent); + hashCode += cachedContent.headerHashCode(); + } + if (input.readInt() != hashCode) { + return false; + } + } catch (IOException e) { + return false; + } finally { + if (input != null) { + Util.closeQuietly(input); + } + } + return true; + } + + private void writeFile() throws CacheException { + DataOutputStream output = null; + try { + OutputStream outputStream = atomicFile.startWrite(); + if (bufferedOutputStream == null) { + bufferedOutputStream = new ReusableBufferedOutputStream(outputStream); + } else { + bufferedOutputStream.reset(outputStream); + } + output = new DataOutputStream(bufferedOutputStream); + output.writeInt(VERSION); + + int flags = cipher != null ? FLAG_ENCRYPTED_INDEX : 0; + output.writeInt(flags); + + if (cipher != null) { + byte[] initializationVector = new byte[16]; + new Random().nextBytes(initializationVector); + output.write(initializationVector); + IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector); + try { + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalStateException(e); // Should never happen. + } + output.flush(); + output = new DataOutputStream(new CipherOutputStream(bufferedOutputStream, cipher)); + } + + output.writeInt(keyToContent.size()); + int hashCode = 0; + for (CachedContent cachedContent : keyToContent.values()) { + cachedContent.writeToStream(output); + hashCode += cachedContent.headerHashCode(); + } + output.writeInt(hashCode); + atomicFile.endWrite(output); + } catch (IOException e) { + throw new CacheException(e); + } finally { + Util.closeQuietly(output); + } + } + + /** Adds the given CachedContent to the index. */ + /*package*/ void addNew(CachedContent cachedContent) { + keyToContent.put(cachedContent.key, cachedContent); + idToKey.put(cachedContent.id, cachedContent.key); + changed = true; + } + + private CachedContent addNew(String key, long length) { + int id = getNewId(idToKey); + CachedContent cachedContent = new CachedContent(id, key, length); + addNew(cachedContent); + return cachedContent; + } + + /** + * Returns an id which isn't used in the given array. If the maximum id in the array is smaller + * than {@link java.lang.Integer#MAX_VALUE} it just returns the next bigger integer. Otherwise it + * returns the smallest unused non-negative integer. + */ + //@VisibleForTesting + public static int getNewId(SparseArray idToKey) { + int size = idToKey.size(); + int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1); + if (id < 0) { // In case if we pass max int value. + // TODO optimization: defragmentation or binary search? + for (id = 0; id < size; id++) { + if (id != idToKey.keyAt(id)) { + break; + } + } + } + return id; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java similarity index 85% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java index 9b4b65f31a7..3a9204fc927 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/LeastRecentlyUsedCacheEvictor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/LeastRecentlyUsedCacheEvictor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream.cache; +package org.telegram.messenger.exoplayer2.upstream.cache; +import org.telegram.messenger.exoplayer2.upstream.cache.Cache.CacheException; import java.util.Comparator; import java.util.TreeSet; @@ -39,8 +40,8 @@ public void onCacheInitialized() { } @Override - public void onStartFile(Cache cache, String key, long position, long length) { - evictCache(cache, length); + public void onStartFile(Cache cache, String key, long position, long maxLength) { + evictCache(cache, maxLength); } @Override @@ -74,7 +75,11 @@ public int compare(CacheSpan lhs, CacheSpan rhs) { private void evictCache(Cache cache, long requiredSpace) { while (currentSize + requiredSpace > maxBytes) { - cache.removeSpan(leastRecentlyUsed.first()); + try { + cache.removeSpan(leastRecentlyUsed.first()); + } catch (CacheException e) { + // do nothing. + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/NoOpCacheEvictor.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/NoOpCacheEvictor.java similarity index 90% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/NoOpCacheEvictor.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/NoOpCacheEvictor.java index 9ac2b0340b4..4e234fb4b5f 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/upstream/cache/NoOpCacheEvictor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/NoOpCacheEvictor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.upstream.cache; +package org.telegram.messenger.exoplayer2.upstream.cache; /** @@ -30,7 +30,7 @@ public void onCacheInitialized() { } @Override - public void onStartFile(Cache cache, String key, long position, long length) { + public void onStartFile(Cache cache, String key, long position, long maxLength) { // Do nothing. } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java new file mode 100755 index 00000000000..2f03e9fa862 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCache.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import android.os.ConditionVariable; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.NavigableSet; +import java.util.Set; +import java.util.TreeSet; + +/** + * A {@link Cache} implementation that maintains an in-memory representation. + */ +public final class SimpleCache implements Cache { + + private final File cacheDir; + private final CacheEvictor evictor; + private final HashMap lockedSpans; + private final CachedContentIndex index; + private final HashMap> listeners; + private long totalSpace = 0; + private CacheException initializationException; + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + */ + public SimpleCache(File cacheDir, CacheEvictor evictor) { + this(cacheDir, evictor, null); + } + + /** + * Constructs the cache. The cache will delete any unrecognized files from the directory. Hence + * the directory cannot be used to store other files. + * + * @param cacheDir A dedicated cache directory. + * @param evictor The evictor to be used. + * @param secretKey If not null, cache keys will be stored encrypted on filesystem using AES/CBC. + * The key must be 16 bytes long. + */ + public SimpleCache(File cacheDir, CacheEvictor evictor, byte[] secretKey) { + this.cacheDir = cacheDir; + this.evictor = evictor; + this.lockedSpans = new HashMap<>(); + this.index = new CachedContentIndex(cacheDir, secretKey); + this.listeners = new HashMap<>(); + // Start cache initialization. + final ConditionVariable conditionVariable = new ConditionVariable(); + new Thread("SimpleCache.initialize()") { + @Override + public void run() { + synchronized (SimpleCache.this) { + conditionVariable.open(); + try { + initialize(); + } catch (CacheException e) { + initializationException = e; + } + SimpleCache.this.evictor.onCacheInitialized(); + } + } + }.start(); + conditionVariable.block(); + } + + @Override + public synchronized NavigableSet addListener(String key, Listener listener) { + ArrayList listenersForKey = listeners.get(key); + if (listenersForKey == null) { + listenersForKey = new ArrayList<>(); + listeners.put(key, listenersForKey); + } + listenersForKey.add(listener); + return getCachedSpans(key); + } + + @Override + public synchronized void removeListener(String key, Listener listener) { + ArrayList listenersForKey = listeners.get(key); + if (listenersForKey != null) { + listenersForKey.remove(listener); + if (listenersForKey.isEmpty()) { + listeners.remove(key); + } + } + } + + @Override + public synchronized NavigableSet getCachedSpans(String key) { + CachedContent cachedContent = index.get(key); + return cachedContent == null ? null : new TreeSet(cachedContent.getSpans()); + } + + @Override + public synchronized Set getKeys() { + return new HashSet<>(index.getKeys()); + } + + @Override + public synchronized long getCacheSpace() { + return totalSpace; + } + + @Override + public synchronized SimpleCacheSpan startReadWrite(String key, long position) + throws InterruptedException, CacheException { + while (true) { + SimpleCacheSpan span = startReadWriteNonBlocking(key, position); + if (span != null) { + return span; + } else { + // Write case, lock not available. We'll be woken up when a locked span is released (if the + // released lock is for the requested key then we'll be able to make progress) or when a + // span is added to the cache (if the span is for the requested key and covers the requested + // position, then we'll become a read and be able to make progress). + wait(); + } + } + } + + @Override + public synchronized SimpleCacheSpan startReadWriteNonBlocking(String key, long position) + throws CacheException { + if (initializationException != null) { + throw initializationException; + } + + SimpleCacheSpan cacheSpan = getSpan(key, position); + + // Read case. + if (cacheSpan.isCached) { + // Obtain a new span with updated last access timestamp. + SimpleCacheSpan newCacheSpan = index.get(key).touch(cacheSpan); + notifySpanTouched(cacheSpan, newCacheSpan); + return newCacheSpan; + } + + // Write case, lock available. + if (!lockedSpans.containsKey(key)) { + lockedSpans.put(key, cacheSpan); + return cacheSpan; + } + + // Write case, lock not available. + return null; + } + + @Override + public synchronized File startFile(String key, long position, long maxLength) + throws CacheException { + Assertions.checkState(lockedSpans.containsKey(key)); + if (!cacheDir.exists()) { + // For some reason the cache directory doesn't exist. Make a best effort to create it. + removeStaleSpansAndCachedContents(); + cacheDir.mkdirs(); + } + evictor.onStartFile(this, key, position, maxLength); + return SimpleCacheSpan.getCacheFile(cacheDir, index.assignIdForKey(key), position, + System.currentTimeMillis()); + } + + @Override + public synchronized void commitFile(File file) throws CacheException { + SimpleCacheSpan span = SimpleCacheSpan.createCacheEntry(file, index); + Assertions.checkState(span != null); + Assertions.checkState(lockedSpans.containsKey(span.key)); + // If the file doesn't exist, don't add it to the in-memory representation. + if (!file.exists()) { + return; + } + // If the file has length 0, delete it and don't add it to the in-memory representation. + if (file.length() == 0) { + file.delete(); + return; + } + // Check if the span conflicts with the set content length + Long length = getContentLength(span.key); + if (length != C.LENGTH_UNSET) { + Assertions.checkState((span.position + span.length) <= length); + } + addSpan(span); + index.store(); + notifyAll(); + } + + @Override + public synchronized void releaseHoleSpan(CacheSpan holeSpan) { + Assertions.checkState(holeSpan == lockedSpans.remove(holeSpan.key)); + notifyAll(); + } + + /** + * Returns the cache {@link SimpleCacheSpan} corresponding to the provided lookup {@link + * SimpleCacheSpan}. + * + *

        If the lookup position is contained by an existing entry in the cache, then the returned + * {@link SimpleCacheSpan} defines the file in which the data is stored. If the lookup position is + * not contained by an existing entry, then the returned {@link SimpleCacheSpan} defines the + * maximum extents of the hole in the cache. + * + * @param key The key of the span being requested. + * @param position The position of the span being requested. + * @return The corresponding cache {@link SimpleCacheSpan}. + */ + private SimpleCacheSpan getSpan(String key, long position) throws CacheException { + CachedContent cachedContent = index.get(key); + if (cachedContent == null) { + return SimpleCacheSpan.createOpenHole(key, position); + } + while (true) { + SimpleCacheSpan span = cachedContent.getSpan(position); + if (span.isCached && !span.file.exists()) { + // The file has been deleted from under us. It's likely that other files will have been + // deleted too, so scan the whole in-memory representation. + removeStaleSpansAndCachedContents(); + continue; + } + return span; + } + } + + /** + * Ensures that the cache's in-memory representation has been initialized. + */ + private void initialize() throws CacheException { + if (!cacheDir.exists()) { + cacheDir.mkdirs(); + return; + } + + index.load(); + + File[] files = cacheDir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (file.getName().equals(CachedContentIndex.FILE_NAME)) { + continue; + } + SimpleCacheSpan span = file.length() > 0 + ? SimpleCacheSpan.createCacheEntry(file, index) : null; + if (span != null) { + addSpan(span); + } else { + file.delete(); + } + } + + index.removeEmpty(); + index.store(); + } + + /** + * Adds a cached span to the in-memory representation. + * + * @param span The span to be added. + */ + private void addSpan(SimpleCacheSpan span) { + index.add(span.key).addSpan(span); + totalSpace += span.length; + notifySpanAdded(span); + } + + private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException { + CachedContent cachedContent = index.get(span.key); + Assertions.checkState(cachedContent.removeSpan(span)); + totalSpace -= span.length; + if (removeEmptyCachedContent && cachedContent.isEmpty()) { + index.removeEmpty(cachedContent.key); + index.store(); + } + notifySpanRemoved(span); + } + + @Override + public synchronized void removeSpan(CacheSpan span) throws CacheException { + removeSpan(span, true); + } + + /** + * Scans all of the cached spans in the in-memory representation, removing any for which files + * no longer exist. + */ + private void removeStaleSpansAndCachedContents() throws CacheException { + LinkedList spansToBeRemoved = new LinkedList<>(); + for (CachedContent cachedContent : index.getAll()) { + for (CacheSpan span : cachedContent.getSpans()) { + if (!span.file.exists()) { + spansToBeRemoved.add(span); + } + } + } + for (CacheSpan span : spansToBeRemoved) { + // Remove span but not CachedContent to prevent multiple index.store() calls. + removeSpan(span, false); + } + index.removeEmpty(); + index.store(); + } + + private void notifySpanRemoved(CacheSpan span) { + ArrayList keyListeners = listeners.get(span.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanRemoved(this, span); + } + } + evictor.onSpanRemoved(this, span); + } + + private void notifySpanAdded(SimpleCacheSpan span) { + ArrayList keyListeners = listeners.get(span.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanAdded(this, span); + } + } + evictor.onSpanAdded(this, span); + } + + private void notifySpanTouched(SimpleCacheSpan oldSpan, CacheSpan newSpan) { + ArrayList keyListeners = listeners.get(oldSpan.key); + if (keyListeners != null) { + for (int i = keyListeners.size() - 1; i >= 0; i--) { + keyListeners.get(i).onSpanTouched(this, oldSpan, newSpan); + } + } + evictor.onSpanTouched(this, oldSpan, newSpan); + } + + @Override + public synchronized boolean isCached(String key, long position, long length) { + CachedContent cachedContent = index.get(key); + return cachedContent != null && cachedContent.isCached(position, length); + } + + @Override + public synchronized void setContentLength(String key, long length) throws CacheException { + index.setContentLength(key, length); + index.store(); + } + + @Override + public synchronized long getContentLength(String key) { + return index.getContentLength(key); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCacheSpan.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCacheSpan.java new file mode 100755 index 00000000000..1d29172b9d9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/upstream/cache/SimpleCacheSpan.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.upstream.cache; + +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.util.Assertions; +import org.telegram.messenger.exoplayer2.util.Util; +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class stores span metadata in filename. + */ +/*package*/ final class SimpleCacheSpan extends CacheSpan { + + private static final String SUFFIX = ".v3.exo"; + private static final Pattern CACHE_FILE_PATTERN_V1 = Pattern.compile( + "^(.+)\\.(\\d+)\\.(\\d+)\\.v1\\.exo$", Pattern.DOTALL); + private static final Pattern CACHE_FILE_PATTERN_V2 = Pattern.compile( + "^(.+)\\.(\\d+)\\.(\\d+)\\.v2\\.exo$", Pattern.DOTALL); + private static final Pattern CACHE_FILE_PATTERN_V3 = Pattern.compile( + "^(\\d+)\\.(\\d+)\\.(\\d+)\\.v3\\.exo$", Pattern.DOTALL); + + public static File getCacheFile(File cacheDir, int id, long position, + long lastAccessTimestamp) { + return new File(cacheDir, id + "." + position + "." + lastAccessTimestamp + SUFFIX); + } + + public static SimpleCacheSpan createLookup(String key, long position) { + return new SimpleCacheSpan(key, position, C.LENGTH_UNSET, C.TIME_UNSET, null); + } + + public static SimpleCacheSpan createOpenHole(String key, long position) { + return new SimpleCacheSpan(key, position, C.LENGTH_UNSET, C.TIME_UNSET, null); + } + + public static SimpleCacheSpan createClosedHole(String key, long position, long length) { + return new SimpleCacheSpan(key, position, length, C.TIME_UNSET, null); + } + + /** + * Creates a cache span from an underlying cache file. Upgrades the file if necessary. + * + * @param file The cache file. + * @param index Cached content index. + * @return The span, or null if the file name is not correctly formatted, or if the id is not + * present in the content index. + */ + public static SimpleCacheSpan createCacheEntry(File file, CachedContentIndex index) { + String name = file.getName(); + if (!name.endsWith(SUFFIX)) { + file = upgradeFile(file, index); + if (file == null) { + return null; + } + name = file.getName(); + } + + Matcher matcher = CACHE_FILE_PATTERN_V3.matcher(name); + if (!matcher.matches()) { + return null; + } + long length = file.length(); + int id = Integer.parseInt(matcher.group(1)); + String key = index.getKeyForId(id); + return key == null ? null : new SimpleCacheSpan(key, Long.parseLong(matcher.group(2)), length, + Long.parseLong(matcher.group(3)), file); + } + + private static File upgradeFile(File file, CachedContentIndex index) { + String key; + String filename = file.getName(); + Matcher matcher = CACHE_FILE_PATTERN_V2.matcher(filename); + if (matcher.matches()) { + key = Util.unescapeFileName(matcher.group(1)); + if (key == null) { + return null; + } + } else { + matcher = CACHE_FILE_PATTERN_V1.matcher(filename); + if (!matcher.matches()) { + return null; + } + key = matcher.group(1); // Keys were not escaped in version 1. + } + + File newCacheFile = getCacheFile(file.getParentFile(), index.assignIdForKey(key), + Long.parseLong(matcher.group(2)), Long.parseLong(matcher.group(3))); + if (!file.renameTo(newCacheFile)) { + return null; + } + return newCacheFile; + } + + private SimpleCacheSpan(String key, long position, long length, long lastAccessTimestamp, + File file) { + super(key, position, length, lastAccessTimestamp, file); + } + + /** + * Returns a copy of this CacheSpan whose last access time stamp is set to current time. This + * doesn't copy or change the underlying cache file. + * + * @param id The cache file id. + * @return A {@link SimpleCacheSpan} with updated last access time stamp. + * @throws IllegalStateException If called on a non-cached span (i.e. {@link #isCached} is false). + */ + public SimpleCacheSpan copyWithUpdatedLastAccessTime(int id) { + Assertions.checkState(isCached); + long now = System.currentTimeMillis(); + File newCacheFile = getCacheFile(file.getParentFile(), id, position, now); + return new SimpleCacheSpan(key, position, length, now, newCacheFile); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Assertions.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Assertions.java new file mode 100755 index 00000000000..cd7d4c2f941 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Assertions.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import android.os.Looper; +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; + +/** + * Provides methods for asserting the truth of expressions and properties. + */ +public final class Assertions { + + private Assertions() {} + + /** + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @throws IllegalArgumentException If {@code expression} is false. + */ + public static void checkArgument(boolean expression) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Throws {@link IllegalArgumentException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted + * to a {@link String} using {@link String#valueOf(Object)}. + * @throws IllegalArgumentException If {@code expression} is false. + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Throws {@link IndexOutOfBoundsException} if {@code index} falls outside the specified bounds. + * + * @param index The index to test. + * @param start The start of the allowed range (inclusive). + * @param limit The end of the allowed range (exclusive). + * @return The {@code index} that was validated. + * @throws IndexOutOfBoundsException If {@code index} falls outside the specified bounds. + */ + public static int checkIndex(int index, int start, int limit) { + if (index < start || index >= limit) { + throw new IndexOutOfBoundsException(); + } + return index; + } + + /** + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @throws IllegalStateException If {@code expression} is false. + */ + public static void checkState(boolean expression) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalStateException(); + } + } + + /** + * Throws {@link IllegalStateException} if {@code expression} evaluates to false. + * + * @param expression The expression to evaluate. + * @param errorMessage The exception message if an exception is thrown. The message is converted + * to a {@link String} using {@link String#valueOf(Object)}. + * @throws IllegalStateException If {@code expression} is false. + */ + public static void checkState(boolean expression, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && !expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Throws {@link NullPointerException} if {@code reference} is null. + * + * @param The type of the reference. + * @param reference The reference. + * @return The non-null reference that was validated. + * @throws NullPointerException If {@code reference} is null. + */ + public static T checkNotNull(T reference) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Throws {@link NullPointerException} if {@code reference} is null. + * + * @param The type of the reference. + * @param reference The reference. + * @param errorMessage The exception message to use if the check fails. The message is converted + * to a string using {@link String#valueOf(Object)}. + * @return The non-null reference that was validated. + * @throws NullPointerException If {@code reference} is null. + */ + public static T checkNotNull(T reference, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. + * + * @param string The string to check. + * @return The non-null, non-empty string that was validated. + * @throws IllegalArgumentException If {@code string} is null or 0-length. + */ + public static String checkNotEmpty(String string) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(); + } + return string; + } + + /** + * Throws {@link IllegalArgumentException} if {@code string} is null or zero length. + * + * @param string The string to check. + * @param errorMessage The exception message to use if the check fails. The message is converted + * to a string using {@link String#valueOf(Object)}. + * @return The non-null, non-empty string that was validated. + * @throws IllegalArgumentException If {@code string} is null or 0-length. + */ + public static String checkNotEmpty(String string, Object errorMessage) { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && TextUtils.isEmpty(string)) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + return string; + } + + /** + * Throws {@link IllegalStateException} if the calling thread is not the application's main + * thread. + * + * @throws IllegalStateException If the calling thread is not the application's main thread. + */ + public static void checkMainThread() { + if (ExoPlayerLibraryInfo.ASSERTIONS_ENABLED && Looper.myLooper() != Looper.getMainLooper()) { + throw new IllegalStateException("Not in applications main thread"); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/AtomicFile.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/AtomicFile.java new file mode 100755 index 00000000000..a2251713f43 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/AtomicFile.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.exoplayer2.util; + +import android.util.Log; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * A helper class for performing atomic operations on a file by creating a backup file until a write + * has successfully completed. + * + *

        Atomic file guarantees file integrity by ensuring that a file has been completely written and + * sync'd to disk before removing its backup. As long as the backup file exists, the original file + * is considered to be invalid (left over from a previous attempt to write the file). + * + *

        Atomic file does not confer any file locking semantics. Do not use this class when the file + * may be accessed or modified concurrently by multiple threads or processes. The caller is + * responsible for ensuring appropriate mutual exclusion invariants whenever it accesses the file. + */ +public final class AtomicFile { + + private static final String TAG = "AtomicFile"; + + private final File baseName; + private final File backupName; + + /** + * Create a new AtomicFile for a file located at the given File path. The secondary backup file + * will be the same file path with ".bak" appended. + */ + public AtomicFile(File baseName) { + this.baseName = baseName; + backupName = new File(baseName.getPath() + ".bak"); + } + + /** Delete the atomic file. This deletes both the base and backup files. */ + public void delete() { + baseName.delete(); + backupName.delete(); + } + + /** + * Start a new write operation on the file. This returns an {@link OutputStream} to which you can + * write the new file data. If the whole data is written successfully you must call + * {@link #endWrite(OutputStream)}. On failure you should call {@link OutputStream#close()} + * only to free up resources used by it. + * + *

        Example usage: + * + *

        +   *   DataOutputStream dataOutput = null;
        +   *   try {
        +   *     OutputStream outputStream = atomicFile.startWrite();
        +   *     dataOutput = new DataOutputStream(outputStream); // Wrapper stream
        +   *     dataOutput.write(data1);
        +   *     dataOutput.write(data2);
        +   *     atomicFile.endWrite(dataOutput); // Pass wrapper stream
        +   *   } finally{
        +   *     if (dataOutput != null) {
        +   *       dataOutput.close();
        +   *     }
        +   *   }
        +   * 
        + * + *

        Note that if another thread is currently performing a write, this will simply replace + * whatever that thread is writing with the new file being written by this thread, and when the + * other thread finishes the write the new write operation will no longer be safe (or will be + * lost). You must do your own threading protection for access to AtomicFile. + */ + public OutputStream startWrite() throws IOException { + // Rename the current file so it may be used as a backup during the next read + if (baseName.exists()) { + if (!backupName.exists()) { + if (!baseName.renameTo(backupName)) { + Log.w(TAG, "Couldn't rename file " + baseName + " to backup file " + backupName); + } + } else { + baseName.delete(); + } + } + OutputStream str; + try { + str = new AtomicFileOutputStream(baseName); + } catch (FileNotFoundException e) { + File parent = baseName.getParentFile(); + if (!parent.mkdirs()) { + throw new IOException("Couldn't create directory " + baseName); + } + try { + str = new AtomicFileOutputStream(baseName); + } catch (FileNotFoundException e2) { + throw new IOException("Couldn't create " + baseName); + } + } + return str; + } + + /** + * Call when you have successfully finished writing to the stream returned by {@link + * #startWrite()}. This will close, sync, and commit the new data. The next attempt to read the + * atomic file will return the new file stream. + * + * @param str Outer-most wrapper OutputStream used to write to the stream returned by {@link + * #startWrite()}. + * @see #startWrite() + */ + public void endWrite(OutputStream str) throws IOException { + str.close(); + // If close() throws exception, the next line is skipped. + backupName.delete(); + } + + /** + * Open the atomic file for reading. If there previously was an incomplete write, this will roll + * back to the last good data before opening for read. + * + *

        Note that if another thread is currently performing a write, this will incorrectly consider + * it to be in the state of a bad write and roll back, causing the new data currently being + * written to be dropped. You must do your own threading protection for access to AtomicFile. + */ + public InputStream openRead() throws FileNotFoundException { + restoreBackup(); + return new FileInputStream(baseName); + } + + private void restoreBackup() { + if (backupName.exists()) { + baseName.delete(); + backupName.renameTo(baseName); + } + } + + private static final class AtomicFileOutputStream extends OutputStream { + + private final FileOutputStream fileOutputStream; + private boolean closed = false; + + public AtomicFileOutputStream(File file) throws FileNotFoundException { + fileOutputStream = new FileOutputStream(file); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + flush(); + try { + fileOutputStream.getFD().sync(); + } catch (IOException e) { + Log.w(TAG, "Failed to sync file descriptor:", e); + } + fileOutputStream.close(); + } + + @Override + public void flush() throws IOException { + fileOutputStream.flush(); + } + + @Override + public void write(int b) throws IOException { + fileOutputStream.write(b); + } + + @Override + public void write(byte[] b) throws IOException { + fileOutputStream.write(b); + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + fileOutputStream.write(b, off, len); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Clock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java similarity index 95% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Clock.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java index be6b968f02f..451a4e66c40 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Clock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Clock.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; /** * An interface through which system clocks can be read. The {@link SystemClock} implementation diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/CodecSpecificDataUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/CodecSpecificDataUtil.java similarity index 90% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/CodecSpecificDataUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/CodecSpecificDataUtil.java index e8cfd7becd1..8c7566ac747 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/CodecSpecificDataUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/CodecSpecificDataUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import android.util.Pair; +import org.telegram.messenger.exoplayer2.C; import java.util.ArrayList; import java.util.List; @@ -119,22 +120,6 @@ public static Pair parseAacAudioSpecificConfig(byte[] audioSpe return Pair.create(sampleRate, channelCount); } - /** - * Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 - * - * @param audioObjectType The audio object type. - * @param sampleRateIndex The sample rate index. - * @param channelConfig The channel configuration. - * @return The AudioSpecificConfig. - */ - public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sampleRateIndex, - int channelConfig) { - byte[] audioSpecificConfig = new byte[2]; - audioSpecificConfig[0] = (byte) ((audioObjectType << 3) & 0xF8 | (sampleRateIndex >> 1) & 0x07); - audioSpecificConfig[1] = (byte) ((sampleRateIndex << 7) & 0x80 | (channelConfig << 3) & 0x78); - return audioSpecificConfig; - } - /** * Builds a simple HE-AAC LC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * @@ -142,24 +127,40 @@ public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sample * @param numChannels The number of channels. * @return The AudioSpecificConfig. */ - public static byte[] buildAacAudioSpecificConfig(int sampleRate, int numChannels) { - int sampleRateIndex = -1; + public static byte[] buildAacLcAudioSpecificConfig(int sampleRate, int numChannels) { + int sampleRateIndex = C.INDEX_UNSET; for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; ++i) { if (sampleRate == AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[i]) { sampleRateIndex = i; } } - int channelConfig = -1; + int channelConfig = C.INDEX_UNSET; for (int i = 0; i < AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE.length; ++i) { if (numChannels == AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[i]) { channelConfig = i; } } - // The full specification for AudioSpecificConfig is stated in ISO 14496-3 Section 1.6.2.1 - byte[] csd = new byte[2]; - csd[0] = (byte) ((AUDIO_OBJECT_TYPE_AAC_LC << 3) | (sampleRateIndex >> 1)); - csd[1] = (byte) (((sampleRateIndex & 0x1) << 7) | (channelConfig << 3)); - return csd; + if (sampleRate == C.INDEX_UNSET || channelConfig == C.INDEX_UNSET) { + throw new IllegalArgumentException("Invalid sample rate or number of channels: " + + sampleRate + ", " + numChannels); + } + return buildAacAudioSpecificConfig(AUDIO_OBJECT_TYPE_AAC_LC, sampleRateIndex, channelConfig); + } + + /** + * Builds a simple AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * + * @param audioObjectType The audio object type. + * @param sampleRateIndex The sample rate index. + * @param channelConfig The channel configuration. + * @return The AudioSpecificConfig. + */ + public static byte[] buildAacAudioSpecificConfig(int audioObjectType, int sampleRateIndex, + int channelConfig) { + byte[] specificConfig = new byte[2]; + specificConfig[0] = (byte) (((audioObjectType << 3) & 0xF8) | ((sampleRateIndex >> 1) & 0x07)); + specificConfig[1] = (byte) (((sampleRateIndex << 7) & 0x80) | ((channelConfig << 3) & 0x78)); + return specificConfig; } /** @@ -198,7 +199,7 @@ public static byte[][] splitNalUnits(byte[] data) { do { starts.add(nalUnitIndex); nalUnitIndex = findNalStartCode(data, nalUnitIndex + NAL_START_CODE.length); - } while (nalUnitIndex != -1); + } while (nalUnitIndex != C.INDEX_UNSET); byte[][] split = new byte[starts.size()][]; for (int i = 0; i < starts.size(); i++) { int startIndex = starts.get(i); @@ -215,7 +216,7 @@ public static byte[][] splitNalUnits(byte[] data) { * * @param data The data in which to search. * @param index The first index to test. - * @return The index of the first byte of the found start code, or -1. + * @return The index of the first byte of the found start code, or {@link C#INDEX_UNSET}. */ private static int findNalStartCode(byte[] data, int index) { int endIndex = data.length - NAL_START_CODE.length; @@ -224,7 +225,7 @@ private static int findNalStartCode(byte[] data, int index) { return i; } } - return -1; + return C.INDEX_UNSET; } /** diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ColorParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ColorParser.java new file mode 100755 index 00000000000..11f0dcc0f00 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ColorParser.java @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import android.text.TextUtils; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parser for color expressions found in styling formats, e.g. TTML and CSS. + * + * @see WebVTT CSS Styling + * @see Timed Text Markup Language 2 (TTML2) - 10.3.5 + **/ +public final class ColorParser { + + private static final String RGB = "rgb"; + private static final String RGBA = "rgba"; + + private static final Pattern RGB_PATTERN = Pattern.compile( + "^rgb\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); + + private static final Pattern RGBA_PATTERN_INT_ALPHA = Pattern.compile( + "^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d{1,3})\\)$"); + + private static final Pattern RGBA_PATTERN_FLOAT_ALPHA = Pattern.compile( + "^rgba\\((\\d{1,3}),(\\d{1,3}),(\\d{1,3}),(\\d*\\.?\\d*?)\\)$"); + + private static final Map COLOR_MAP; + + /** + * Parses a TTML color expression. + * + * @param colorExpression The color expression. + * @return The parsed ARGB color. + */ + public static int parseTtmlColor(String colorExpression) { + return parseColorInternal(colorExpression, false); + } + + /** + * Parses a CSS color expression. + * + * @param colorExpression The color expression. + * @return The parsed ARGB color. + */ + public static int parseCssColor(String colorExpression) { + return parseColorInternal(colorExpression, true); + } + + private static int parseColorInternal(String colorExpression, boolean alphaHasFloatFormat) { + Assertions.checkArgument(!TextUtils.isEmpty(colorExpression)); + colorExpression = colorExpression.replace(" ", ""); + if (colorExpression.charAt(0) == '#') { + // Parse using Long to avoid failure when colorExpression is greater than #7FFFFFFF. + int color = (int) Long.parseLong(colorExpression.substring(1), 16); + if (colorExpression.length() == 7) { + // Set the alpha value + color |= 0xFF000000; + } else if (colorExpression.length() == 9) { + // We have #RRGGBBAA, but we need #AARRGGBB + color = ((color & 0xFF) << 24) | (color >>> 8); + } else { + throw new IllegalArgumentException(); + } + return color; + } else if (colorExpression.startsWith(RGBA)) { + Matcher matcher = (alphaHasFloatFormat ? RGBA_PATTERN_FLOAT_ALPHA : RGBA_PATTERN_INT_ALPHA) + .matcher(colorExpression); + if (matcher.matches()) { + return argb( + alphaHasFloatFormat ? (int) (255 * Float.parseFloat(matcher.group(4))) + : Integer.parseInt(matcher.group(4), 10), + Integer.parseInt(matcher.group(1), 10), + Integer.parseInt(matcher.group(2), 10), + Integer.parseInt(matcher.group(3), 10) + ); + } + } else if (colorExpression.startsWith(RGB)) { + Matcher matcher = RGB_PATTERN.matcher(colorExpression); + if (matcher.matches()) { + return rgb( + Integer.parseInt(matcher.group(1), 10), + Integer.parseInt(matcher.group(2), 10), + Integer.parseInt(matcher.group(3), 10) + ); + } + } else { + // we use our own color map + Integer color = COLOR_MAP.get(Util.toLowerInvariant(colorExpression)); + if (color != null) { + return color; + } + } + throw new IllegalArgumentException(); + } + + private static int argb(int alpha, int red, int green, int blue) { + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + private static int rgb(int red, int green, int blue) { + return argb(0xFF, red, green, blue); + } + + static { + COLOR_MAP = new HashMap<>(); + COLOR_MAP.put("aliceblue", 0xFFF0F8FF); + COLOR_MAP.put("antiquewhite", 0xFFFAEBD7); + COLOR_MAP.put("aqua", 0xFF00FFFF); + COLOR_MAP.put("aquamarine", 0xFF7FFFD4); + COLOR_MAP.put("azure", 0xFFF0FFFF); + COLOR_MAP.put("beige", 0xFFF5F5DC); + COLOR_MAP.put("bisque", 0xFFFFE4C4); + COLOR_MAP.put("black", 0xFF000000); + COLOR_MAP.put("blanchedalmond", 0xFFFFEBCD); + COLOR_MAP.put("blue", 0xFF0000FF); + COLOR_MAP.put("blueviolet", 0xFF8A2BE2); + COLOR_MAP.put("brown", 0xFFA52A2A); + COLOR_MAP.put("burlywood", 0xFFDEB887); + COLOR_MAP.put("cadetblue", 0xFF5F9EA0); + COLOR_MAP.put("chartreuse", 0xFF7FFF00); + COLOR_MAP.put("chocolate", 0xFFD2691E); + COLOR_MAP.put("coral", 0xFFFF7F50); + COLOR_MAP.put("cornflowerblue", 0xFF6495ED); + COLOR_MAP.put("cornsilk", 0xFFFFF8DC); + COLOR_MAP.put("crimson", 0xFFDC143C); + COLOR_MAP.put("cyan", 0xFF00FFFF); + COLOR_MAP.put("darkblue", 0xFF00008B); + COLOR_MAP.put("darkcyan", 0xFF008B8B); + COLOR_MAP.put("darkgoldenrod", 0xFFB8860B); + COLOR_MAP.put("darkgray", 0xFFA9A9A9); + COLOR_MAP.put("darkgreen", 0xFF006400); + COLOR_MAP.put("darkgrey", 0xFFA9A9A9); + COLOR_MAP.put("darkkhaki", 0xFFBDB76B); + COLOR_MAP.put("darkmagenta", 0xFF8B008B); + COLOR_MAP.put("darkolivegreen", 0xFF556B2F); + COLOR_MAP.put("darkorange", 0xFFFF8C00); + COLOR_MAP.put("darkorchid", 0xFF9932CC); + COLOR_MAP.put("darkred", 0xFF8B0000); + COLOR_MAP.put("darksalmon", 0xFFE9967A); + COLOR_MAP.put("darkseagreen", 0xFF8FBC8F); + COLOR_MAP.put("darkslateblue", 0xFF483D8B); + COLOR_MAP.put("darkslategray", 0xFF2F4F4F); + COLOR_MAP.put("darkslategrey", 0xFF2F4F4F); + COLOR_MAP.put("darkturquoise", 0xFF00CED1); + COLOR_MAP.put("darkviolet", 0xFF9400D3); + COLOR_MAP.put("deeppink", 0xFFFF1493); + COLOR_MAP.put("deepskyblue", 0xFF00BFFF); + COLOR_MAP.put("dimgray", 0xFF696969); + COLOR_MAP.put("dimgrey", 0xFF696969); + COLOR_MAP.put("dodgerblue", 0xFF1E90FF); + COLOR_MAP.put("firebrick", 0xFFB22222); + COLOR_MAP.put("floralwhite", 0xFFFFFAF0); + COLOR_MAP.put("forestgreen", 0xFF228B22); + COLOR_MAP.put("fuchsia", 0xFFFF00FF); + COLOR_MAP.put("gainsboro", 0xFFDCDCDC); + COLOR_MAP.put("ghostwhite", 0xFFF8F8FF); + COLOR_MAP.put("gold", 0xFFFFD700); + COLOR_MAP.put("goldenrod", 0xFFDAA520); + COLOR_MAP.put("gray", 0xFF808080); + COLOR_MAP.put("green", 0xFF008000); + COLOR_MAP.put("greenyellow", 0xFFADFF2F); + COLOR_MAP.put("grey", 0xFF808080); + COLOR_MAP.put("honeydew", 0xFFF0FFF0); + COLOR_MAP.put("hotpink", 0xFFFF69B4); + COLOR_MAP.put("indianred", 0xFFCD5C5C); + COLOR_MAP.put("indigo", 0xFF4B0082); + COLOR_MAP.put("ivory", 0xFFFFFFF0); + COLOR_MAP.put("khaki", 0xFFF0E68C); + COLOR_MAP.put("lavender", 0xFFE6E6FA); + COLOR_MAP.put("lavenderblush", 0xFFFFF0F5); + COLOR_MAP.put("lawngreen", 0xFF7CFC00); + COLOR_MAP.put("lemonchiffon", 0xFFFFFACD); + COLOR_MAP.put("lightblue", 0xFFADD8E6); + COLOR_MAP.put("lightcoral", 0xFFF08080); + COLOR_MAP.put("lightcyan", 0xFFE0FFFF); + COLOR_MAP.put("lightgoldenrodyellow", 0xFFFAFAD2); + COLOR_MAP.put("lightgray", 0xFFD3D3D3); + COLOR_MAP.put("lightgreen", 0xFF90EE90); + COLOR_MAP.put("lightgrey", 0xFFD3D3D3); + COLOR_MAP.put("lightpink", 0xFFFFB6C1); + COLOR_MAP.put("lightsalmon", 0xFFFFA07A); + COLOR_MAP.put("lightseagreen", 0xFF20B2AA); + COLOR_MAP.put("lightskyblue", 0xFF87CEFA); + COLOR_MAP.put("lightslategray", 0xFF778899); + COLOR_MAP.put("lightslategrey", 0xFF778899); + COLOR_MAP.put("lightsteelblue", 0xFFB0C4DE); + COLOR_MAP.put("lightyellow", 0xFFFFFFE0); + COLOR_MAP.put("lime", 0xFF00FF00); + COLOR_MAP.put("limegreen", 0xFF32CD32); + COLOR_MAP.put("linen", 0xFFFAF0E6); + COLOR_MAP.put("magenta", 0xFFFF00FF); + COLOR_MAP.put("maroon", 0xFF800000); + COLOR_MAP.put("mediumaquamarine", 0xFF66CDAA); + COLOR_MAP.put("mediumblue", 0xFF0000CD); + COLOR_MAP.put("mediumorchid", 0xFFBA55D3); + COLOR_MAP.put("mediumpurple", 0xFF9370DB); + COLOR_MAP.put("mediumseagreen", 0xFF3CB371); + COLOR_MAP.put("mediumslateblue", 0xFF7B68EE); + COLOR_MAP.put("mediumspringgreen", 0xFF00FA9A); + COLOR_MAP.put("mediumturquoise", 0xFF48D1CC); + COLOR_MAP.put("mediumvioletred", 0xFFC71585); + COLOR_MAP.put("midnightblue", 0xFF191970); + COLOR_MAP.put("mintcream", 0xFFF5FFFA); + COLOR_MAP.put("mistyrose", 0xFFFFE4E1); + COLOR_MAP.put("moccasin", 0xFFFFE4B5); + COLOR_MAP.put("navajowhite", 0xFFFFDEAD); + COLOR_MAP.put("navy", 0xFF000080); + COLOR_MAP.put("oldlace", 0xFFFDF5E6); + COLOR_MAP.put("olive", 0xFF808000); + COLOR_MAP.put("olivedrab", 0xFF6B8E23); + COLOR_MAP.put("orange", 0xFFFFA500); + COLOR_MAP.put("orangered", 0xFFFF4500); + COLOR_MAP.put("orchid", 0xFFDA70D6); + COLOR_MAP.put("palegoldenrod", 0xFFEEE8AA); + COLOR_MAP.put("palegreen", 0xFF98FB98); + COLOR_MAP.put("paleturquoise", 0xFFAFEEEE); + COLOR_MAP.put("palevioletred", 0xFFDB7093); + COLOR_MAP.put("papayawhip", 0xFFFFEFD5); + COLOR_MAP.put("peachpuff", 0xFFFFDAB9); + COLOR_MAP.put("peru", 0xFFCD853F); + COLOR_MAP.put("pink", 0xFFFFC0CB); + COLOR_MAP.put("plum", 0xFFDDA0DD); + COLOR_MAP.put("powderblue", 0xFFB0E0E6); + COLOR_MAP.put("purple", 0xFF800080); + COLOR_MAP.put("rebeccapurple", 0xFF663399); + COLOR_MAP.put("red", 0xFFFF0000); + COLOR_MAP.put("rosybrown", 0xFFBC8F8F); + COLOR_MAP.put("royalblue", 0xFF4169E1); + COLOR_MAP.put("saddlebrown", 0xFF8B4513); + COLOR_MAP.put("salmon", 0xFFFA8072); + COLOR_MAP.put("sandybrown", 0xFFF4A460); + COLOR_MAP.put("seagreen", 0xFF2E8B57); + COLOR_MAP.put("seashell", 0xFFFFF5EE); + COLOR_MAP.put("sienna", 0xFFA0522D); + COLOR_MAP.put("silver", 0xFFC0C0C0); + COLOR_MAP.put("skyblue", 0xFF87CEEB); + COLOR_MAP.put("slateblue", 0xFF6A5ACD); + COLOR_MAP.put("slategray", 0xFF708090); + COLOR_MAP.put("slategrey", 0xFF708090); + COLOR_MAP.put("snow", 0xFFFFFAFA); + COLOR_MAP.put("springgreen", 0xFF00FF7F); + COLOR_MAP.put("steelblue", 0xFF4682B4); + COLOR_MAP.put("tan", 0xFFD2B48C); + COLOR_MAP.put("teal", 0xFF008080); + COLOR_MAP.put("thistle", 0xFFD8BFD8); + COLOR_MAP.put("tomato", 0xFFFF6347); + COLOR_MAP.put("transparent", 0x00000000); + COLOR_MAP.put("turquoise", 0xFF40E0D0); + COLOR_MAP.put("violet", 0xFFEE82EE); + COLOR_MAP.put("wheat", 0xFFF5DEB3); + COLOR_MAP.put("white", 0xFFFFFFFF); + COLOR_MAP.put("whitesmoke", 0xFFF5F5F5); + COLOR_MAP.put("yellow", 0xFFFFFF00); + COLOR_MAP.put("yellowgreen", 0xFF9ACD32); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ConditionVariable.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ConditionVariable.java new file mode 100755 index 00000000000..9e81c82c327 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ConditionVariable.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +/** + * A condition variable whose {@link #open()} and {@link #close()} methods return whether they + * resulted in a change of state. + */ +public final class ConditionVariable { + + private boolean isOpen; + + /** + * Opens the condition and releases all threads that are blocked. + * + * @return True if the condition variable was opened. False if it was already open. + */ + public synchronized boolean open() { + if (isOpen) { + return false; + } + isOpen = true; + notifyAll(); + return true; + } + + /** + * Closes the condition. + * + * @return True if the condition variable was closed. False if it was already closed. + */ + public synchronized boolean close() { + boolean wasOpen = isOpen; + isOpen = false; + return wasOpen; + } + + /** + * Blocks until the condition is opened. + * + * @throws InterruptedException If the thread is interrupted. + */ + public synchronized void block() throws InterruptedException { + while (!isOpen) { + wait(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacStreamInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java similarity index 98% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacStreamInfo.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java index cad3e90d10f..36a812ff60c 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/FlacStreamInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/FlacStreamInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; /** * Holder for FLAC stream info. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LibraryLoader.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LibraryLoader.java new file mode 100755 index 00000000000..64bf7e56d33 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LibraryLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +/** + * Configurable loader for native libraries. + */ +public final class LibraryLoader { + + private String[] nativeLibraries; + private boolean loadAttempted; + private boolean isAvailable; + + /** + * @param libraries The names of the libraries to load. + */ + public LibraryLoader(String... libraries) { + nativeLibraries = libraries; + } + + /** + * Overrides the names of the libraries to load. Must be called before any call to + * {@link #isAvailable()}. + */ + public synchronized void setLibraries(String... libraries) { + Assertions.checkState(!loadAttempted, "Cannot set libraries after loading"); + nativeLibraries = libraries; + } + + /** + * Returns whether the underlying libraries are available, loading them if necessary. + */ + public synchronized boolean isAvailable() { + if (loadAttempted) { + return isAvailable; + } + loadAttempted = true; + try { + for (String lib : nativeLibraries) { + System.loadLibrary(lib); + } + isAvailable = true; + } catch (UnsatisfiedLinkError exception) { + // Do nothing. + } + return isAvailable; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/LongArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LongArray.java similarity index 84% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/LongArray.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LongArray.java index e93a9b4df0f..9a565eff051 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/LongArray.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/LongArray.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import java.util.Arrays; @@ -51,24 +51,22 @@ public void add(long value) { } /** - * Gets a value. + * Returns the value at a specified index. * * @param index The index. * @return The corresponding value. * @throws IndexOutOfBoundsException If the index is less than zero, or greater than or equal to - * {@link #size()} + * {@link #size()}. */ public long get(int index) { if (index < 0 || index >= size) { - throw new IndexOutOfBoundsException("Invalid size " + index + ", size is " + size); + throw new IndexOutOfBoundsException("Invalid index " + index + ", size is " + size); } return values[index]; } /** - * Gets the current size of the array. - * - * @return The current size of the array. + * Returns the current size of the array. */ public int size() { return size; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaClock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MediaClock.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaClock.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MediaClock.java index 127650a2bd5..684a18e1c2a 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/MediaClock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MediaClock.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer; +package org.telegram.messenger.exoplayer2.util; /** * Tracks the progression of media time. @@ -21,7 +21,7 @@ public interface MediaClock { /** - * @return The current media position in microseconds. + * Returns the current media position in microseconds. */ long getPositionUs(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java new file mode 100755 index 00000000000..486dfad0aa2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/MimeTypes.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import android.text.TextUtils; +import org.telegram.messenger.exoplayer2.C; + +/** + * Defines common MIME types and helper methods. + */ +public final class MimeTypes { + + public static final String BASE_TYPE_VIDEO = "video"; + public static final String BASE_TYPE_AUDIO = "audio"; + public static final String BASE_TYPE_TEXT = "text"; + public static final String BASE_TYPE_APPLICATION = "application"; + + public static final String VIDEO_MP4 = BASE_TYPE_VIDEO + "/mp4"; + public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm"; + public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp"; + public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc"; + public static final String VIDEO_H265 = BASE_TYPE_VIDEO + "/hevc"; + public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8"; + public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9"; + public static final String VIDEO_MP4V = BASE_TYPE_VIDEO + "/mp4v-es"; + public static final String VIDEO_MPEG2 = BASE_TYPE_VIDEO + "/mpeg2"; + public static final String VIDEO_VC1 = BASE_TYPE_VIDEO + "/wvc1"; + public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown"; + + public static final String AUDIO_MP4 = BASE_TYPE_AUDIO + "/mp4"; + public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; + public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm"; + public static final String AUDIO_MPEG = BASE_TYPE_AUDIO + "/mpeg"; + public static final String AUDIO_MPEG_L1 = BASE_TYPE_AUDIO + "/mpeg-L1"; + public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2"; + public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw"; + public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; + public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3"; + public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd"; + public static final String AUDIO_DTS = BASE_TYPE_AUDIO + "/vnd.dts"; + public static final String AUDIO_DTS_HD = BASE_TYPE_AUDIO + "/vnd.dts.hd"; + public static final String AUDIO_DTS_EXPRESS = BASE_TYPE_AUDIO + "/vnd.dts.hd;profile=lbr"; + public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis"; + public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus"; + public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp"; + public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb"; + public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac"; + + public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; + + public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4"; + public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm"; + public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3"; + public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608"; + public static final String APPLICATION_CEA708 = BASE_TYPE_APPLICATION + "/cea-708"; + public static final String APPLICATION_SUBRIP = BASE_TYPE_APPLICATION + "/x-subrip"; + public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; + public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL"; + public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g"; + public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4vtt"; + public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc"; + public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub"; + public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; + public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35"; + public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion"; + + private MimeTypes() {} + + /** + * Whether the top-level type of {@code mimeType} is audio. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is audio. + */ + public static boolean isAudio(String mimeType) { + return BASE_TYPE_AUDIO.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is video. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is video. + */ + public static boolean isVideo(String mimeType) { + return BASE_TYPE_VIDEO.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is text. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is text. + */ + public static boolean isText(String mimeType) { + return BASE_TYPE_TEXT.equals(getTopLevelType(mimeType)); + } + + /** + * Whether the top-level type of {@code mimeType} is application. + * + * @param mimeType The mimeType to test. + * @return Whether the top level type is application. + */ + public static boolean isApplication(String mimeType) { + return BASE_TYPE_APPLICATION.equals(getTopLevelType(mimeType)); + } + + + /** + * Derives a video sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived video mimeType, or null if it could not be derived. + */ + public static String getVideoMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isVideo(mimeType)) { + return mimeType; + } + } + return null; + } + + /** + * Derives a audio sample mimeType from a codecs attribute. + * + * @param codecs The codecs attribute. + * @return The derived audio mimeType, or null if it could not be derived. + */ + public static String getAudioMediaMimeType(String codecs) { + if (codecs == null) { + return null; + } + String[] codecList = codecs.split(","); + for (String codec : codecList) { + String mimeType = getMediaMimeType(codec); + if (mimeType != null && isAudio(mimeType)) { + return mimeType; + } + } + return null; + } + + /** + * Derives a mimeType from a codec identifier, as defined in RFC 6381. + * + * @param codec The codec identifier to derive. + * @return The mimeType, or null if it could not be derived. + */ + public static String getMediaMimeType(String codec) { + if (codec == null) { + return null; + } + codec = codec.trim(); + if (codec.startsWith("avc1") || codec.startsWith("avc3")) { + return MimeTypes.VIDEO_H264; + } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { + return MimeTypes.VIDEO_H265; + } else if (codec.startsWith("vp9")) { + return MimeTypes.VIDEO_VP9; + } else if (codec.startsWith("vp8")) { + return MimeTypes.VIDEO_VP8; + } else if (codec.startsWith("mp4a")) { + return MimeTypes.AUDIO_AAC; + } else if (codec.startsWith("ac-3") || codec.startsWith("dac3")) { + return MimeTypes.AUDIO_AC3; + } else if (codec.startsWith("ec-3") || codec.startsWith("dec3")) { + return MimeTypes.AUDIO_E_AC3; + } else if (codec.startsWith("dtsc") || codec.startsWith("dtse")) { + return MimeTypes.AUDIO_DTS; + } else if (codec.startsWith("dtsh") || codec.startsWith("dtsl")) { + return MimeTypes.AUDIO_DTS_HD; + } else if (codec.startsWith("opus")) { + return MimeTypes.AUDIO_OPUS; + } else if (codec.startsWith("vorbis")) { + return MimeTypes.AUDIO_VORBIS; + } + return null; + } + + /** + * Returns the {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. + * {@link C#TRACK_TYPE_UNKNOWN} if the mime type is not known or the mapping cannot be + * established. + * + * @param mimeType The mimeType. + * @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified mime type. + */ + public static int getTrackType(String mimeType) { + if (TextUtils.isEmpty(mimeType)) { + return C.TRACK_TYPE_UNKNOWN; + } else if (isAudio(mimeType)) { + return C.TRACK_TYPE_AUDIO; + } else if (isVideo(mimeType)) { + return C.TRACK_TYPE_VIDEO; + } else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType) + || APPLICATION_CEA708.equals(mimeType) || APPLICATION_SUBRIP.equals(mimeType) + || APPLICATION_TTML.equals(mimeType) || APPLICATION_TX3G.equals(mimeType) + || APPLICATION_MP4VTT.equals(mimeType) || APPLICATION_RAWCC.equals(mimeType) + || APPLICATION_VOBSUB.equals(mimeType) || APPLICATION_PGS.equals(mimeType)) { + return C.TRACK_TYPE_TEXT; + } else if (APPLICATION_ID3.equals(mimeType)) { + return C.TRACK_TYPE_METADATA; + } else { + return C.TRACK_TYPE_UNKNOWN; + } + } + + /** + * Equivalent to {@code getTrackType(getMediaMimeType(codec))}. + * + * @param codec The codec. + * @return The {@link C}{@code .TRACK_TYPE_*} constant that corresponds to a specified codec. + */ + public static int getTrackTypeOfCodec(String codec) { + return getTrackType(getMediaMimeType(codec)); + } + + /** + * Returns the top-level type of {@code mimeType}. + * + * @param mimeType The mimeType whose top-level type is required. + * @return The top-level type, or null if the mimeType is null. + */ + private static String getTopLevelType(String mimeType) { + if (mimeType == null) { + return null; + } + int indexOfSlash = mimeType.indexOf('/'); + if (indexOfSlash == -1) { + throw new IllegalArgumentException("Invalid mime type: " + mimeType); + } + return mimeType.substring(0, indexOfSlash); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/NalUnitUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java similarity index 93% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/NalUnitUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java index bd63fbb76a3..1960678b9ef 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/NalUnitUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/NalUnitUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import android.util.Log; import java.nio.ByteBuffer; @@ -198,17 +198,7 @@ public static void discardToSps(ByteBuffer data) { } /** - * Constructs and returns a NAL unit with a start code followed by the data in {@code atom}. - */ - public static byte[] parseChildNalUnit(ParsableByteArray atom) { - int length = atom.readUnsignedShort(); - int offset = atom.getPosition(); - atom.skipBytes(length); - return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length); - } - - /** - * Gets the type of the NAL unit in {@code data} that starts at {@code offset}. + * Returns the type of the NAL unit in {@code data} that starts at {@code offset}. * * @param data The data to search. * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and @@ -220,7 +210,7 @@ public static int getNalUnitType(byte[] data, int offset) { } /** - * Gets the type of the H.265 NAL unit in {@code data} that starts at {@code offset}. + * Returns the type of the H.265 NAL unit in {@code data} that starts at {@code offset}. * * @param data The data to search. * @param offset The start offset of a NAL unit. Must lie between {@code -3} (inclusive) and @@ -235,11 +225,14 @@ public static int getH265NalUnitType(byte[] data, int offset) { * Parses an SPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * 7.3.2.1.1. * - * @param data A {@link ParsableBitArray} containing the SPS data. The position must to set to the - * start of the data (i.e. the first bit of the profile_idc field). + * @param nalData A buffer containing escaped SPS data. + * @param nalOffset The offset of the NAL unit header in {@code nalData}. + * @param nalLimit The limit of the NAL unit in {@code nalData}. * @return A parsed representation of the SPS data. */ - public static SpsData parseSpsNalUnit(ParsableBitArray data) { + public static SpsData parseSpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { + ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); + data.skipBits(8); // nal_unit int profileIdc = data.readBits(8); data.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8) int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); @@ -347,11 +340,14 @@ public static SpsData parseSpsNalUnit(ParsableBitArray data) { * Parses a PPS NAL unit using the syntax defined in ITU-T Recommendation H.264 (2013) subsection * 7.3.2.2. * - * @param data A {@link ParsableBitArray} containing the PPS data. The position must to set to the - * start of the data (i.e. the first bit of the pic_parameter_set_id field). + * @param nalData A buffer containing escaped PPS data. + * @param nalOffset The offset of the NAL unit header in {@code nalData}. + * @param nalLimit The limit of the NAL unit in {@code nalData}. * @return A parsed representation of the PPS data. */ - public static PpsData parsePpsNalUnit(ParsableBitArray data) { + public static PpsData parsePpsNalUnit(byte[] nalData, int nalOffset, int nalLimit) { + ParsableNalUnitBitArray data = new ParsableNalUnitBitArray(nalData, nalOffset, nalLimit); + data.skipBits(8); // nal_unit int picParameterSetId = data.readUnsignedExpGolombCodedInt(); int seqParameterSetId = data.readUnsignedExpGolombCodedInt(); data.skipBits(1); // entropy_coding_mode_flag @@ -458,7 +454,7 @@ private static int findNextUnescapeIndex(byte[] bytes, int offset, int limit) { return limit; } - private static void skipScalingList(ParsableBitArray bitArray, int size) { + private static void skipScalingList(ParsableNalUnitBitArray bitArray, int size) { int lastScale = 8; int nextScale = 8; for (int i = 0; i < size; i++) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java new file mode 100755 index 00000000000..071556a4673 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableBitArray.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +/** + * Wraps a byte array, providing methods that allow it to be read as a bitstream. + */ +public final class ParsableBitArray { + + public byte[] data; + + // The offset within the data, stored as the current byte offset, and the bit offset within that + // byte (from 0 to 7). + private int byteOffset; + private int bitOffset; + private int byteLimit; + + /** + * Creates a new instance that initially has no backing data. + */ + public ParsableBitArray() {} + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + */ + public ParsableBitArray(byte[] data) { + this(data, data.length); + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + * @param limit The limit in bytes. + */ + public ParsableBitArray(byte[] data, int limit) { + this.data = data; + byteLimit = limit; + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + */ + public void reset(byte[] data) { + reset(data, data.length); + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + * @param limit The limit in bytes. + */ + public void reset(byte[] data, int limit) { + this.data = data; + byteOffset = 0; + bitOffset = 0; + byteLimit = limit; + } + + /** + * Returns the number of bits yet to be read. + */ + public int bitsLeft() { + return (byteLimit - byteOffset) * 8 - bitOffset; + } + + /** + * Returns the current bit offset. + */ + public int getPosition() { + return byteOffset * 8 + bitOffset; + } + + /** + * Sets the current bit offset. + * + * @param position The position to set. + */ + public void setPosition(int position) { + byteOffset = position / 8; + bitOffset = position - (byteOffset * 8); + assertValidOffset(); + } + + /** + * Skips bits and moves current reading position forward. + * + * @param n The number of bits to skip. + */ + public void skipBits(int n) { + byteOffset += (n / 8); + bitOffset += (n % 8); + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + assertValidOffset(); + } + + /** + * Reads a single bit. + * + * @return Whether the bit is set. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An integer whose bottom n bits hold the read data. + */ + public int readBits(int numBits) { + if (numBits == 0) { + return 0; + } + + int returnValue = 0; + + // Read as many whole bytes as we can. + int wholeBytes = (numBits / 8); + for (int i = 0; i < wholeBytes; i++) { + int byteValue; + if (bitOffset != 0) { + byteValue = ((data[byteOffset] & 0xFF) << bitOffset) + | ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset)); + } else { + byteValue = data[byteOffset]; + } + numBits -= 8; + returnValue |= (byteValue & 0xFF) << numBits; + byteOffset++; + } + + // Read any remaining bits. + if (numBits > 0) { + int nextBit = bitOffset + numBits; + byte writeMask = (byte) (0xFF >> (8 - numBits)); + + if (nextBit > 8) { + // Combine bits from current byte and next byte. + returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) + | ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask)); + byteOffset++; + } else { + // Bits to be read only within current byte. + returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); + if (nextBit == 8) { + byteOffset++; + } + } + + bitOffset = nextBit % 8; + } + + assertValidOffset(); + return returnValue; + } + + private void assertValidOffset() { + // It is fine for position to be at the end of the array, but no further. + Assertions.checkState(byteOffset >= 0 + && (bitOffset >= 0 && bitOffset < 8) + && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java new file mode 100755 index 00000000000..b4a1eff3a91 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableByteArray.java @@ -0,0 +1,532 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +/** + * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are + * parsed with the assumption that their constituent bytes are in big endian order. + */ +public final class ParsableByteArray { + + public byte[] data; + + private int position; + private int limit; + + /** + * Creates a new instance that initially has no backing data. + */ + public ParsableByteArray() {} + + /** + * Creates a new instance with {@code limit} bytes and sets the limit. + * + * @param limit The limit to set. + */ + public ParsableByteArray(int limit) { + this.data = new byte[limit]; + this.limit = limit; + } + + /** + * Creates a new instance wrapping {@code data}, and sets the limit to {@code data.length}. + * + * @param data The array to wrap. + */ + public ParsableByteArray(byte[] data) { + this.data = data; + limit = data.length; + } + + /** + * Creates a new instance that wraps an existing array. + * + * @param data The data to wrap. + * @param limit The limit to set. + */ + public ParsableByteArray(byte[] data, int limit) { + this.data = data; + this.limit = limit; + } + + /** + * Resets the position to zero and the limit to the specified value. If the limit exceeds the + * capacity, {@code data} is replaced with a new array of sufficient size. + * + * @param limit The limit to set. + */ + public void reset(int limit) { + reset(capacity() < limit ? new byte[limit] : data, limit); + } + + /** + * Updates the instance to wrap {@code data}, and resets the position to zero. + * + * @param data The array to wrap. + * @param limit The limit to set. + */ + public void reset(byte[] data, int limit) { + this.data = data; + this.limit = limit; + position = 0; + } + + /** + * Sets the position and limit to zero. + */ + public void reset() { + position = 0; + limit = 0; + } + + /** + * Returns the number of bytes yet to be read. + */ + public int bytesLeft() { + return limit - position; + } + + /** + * Returns the limit. + */ + public int limit() { + return limit; + } + + /** + * Sets the limit. + * + * @param limit The limit to set. + */ + public void setLimit(int limit) { + Assertions.checkArgument(limit >= 0 && limit <= data.length); + this.limit = limit; + } + + /** + * Returns the current offset in the array, in bytes. + */ + public int getPosition() { + return position; + } + + /** + * Returns the capacity of the array, which may be larger than the limit. + */ + public int capacity() { + return data == null ? 0 : data.length; + } + + /** + * Sets the reading offset in the array. + * + * @param position Byte offset in the array from which to read. + * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the + * array. + */ + public void setPosition(int position) { + // It is fine for position to be at the end of the array. + Assertions.checkArgument(position >= 0 && position <= limit); + this.position = position; + } + + /** + * Moves the reading offset by {@code bytes}. + * + * @param bytes The number of bytes to skip. + * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the + * array. + */ + public void skipBytes(int bytes) { + setPosition(position + bytes); + } + + /** + * Reads the next {@code length} bytes into {@code bitArray}, and resets the position of + * {@code bitArray} to zero. + * + * @param bitArray The {@link ParsableBitArray} into which the bytes should be read. + * @param length The number of bytes to write. + */ + public void readBytes(ParsableBitArray bitArray, int length) { + readBytes(bitArray.data, 0, length); + bitArray.setPosition(0); + } + + /** + * Reads the next {@code length} bytes into {@code buffer} at {@code offset}. + * + * @see System#arraycopy(Object, int, Object, int, int) + * @param buffer The array into which the read data should be written. + * @param offset The offset in {@code buffer} at which the read data should be written. + * @param length The number of bytes to read. + */ + public void readBytes(byte[] buffer, int offset, int length) { + System.arraycopy(data, position, buffer, offset, length); + position += length; + } + + /** + * Reads the next {@code length} bytes into {@code buffer}. + * + * @see ByteBuffer#put(byte[], int, int) + * @param buffer The {@link ByteBuffer} into which the read data should be written. + * @param length The number of bytes to read. + */ + public void readBytes(ByteBuffer buffer, int length) { + buffer.put(data, position, length); + position += length; + } + + /** + * Peeks at the next byte as an unsigned value. + */ + public int peekUnsignedByte() { + return (data[position] & 0xFF); + } + + /** + * Reads the next byte as an unsigned value. + */ + public int readUnsignedByte() { + return (data[position++] & 0xFF); + } + + /** + * Reads the next two bytes as an unsigned value. + */ + public int readUnsignedShort() { + return (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next two bytes as an unsigned value. + */ + public int readLittleEndianUnsignedShort() { + return (data[position++] & 0xFF) | (data[position++] & 0xFF) << 8; + } + + /** + * Reads the next two bytes as an signed value. + */ + public short readShort() { + return (short) ((data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF)); + } + + /** + * Reads the next two bytes as a signed value. + */ + public short readLittleEndianShort() { + return (short) ((data[position++] & 0xFF) | (data[position++] & 0xFF) << 8); + } + + /** + * Reads the next three bytes as an unsigned value. + */ + public int readUnsignedInt24() { + return (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next three bytes as a signed value in little endian order. + */ + public int readLittleEndianInt24() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16; + } + + /** + * Reads the next three bytes as an unsigned value in little endian order. + */ + public int readLittleEndianUnsignedInt24() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16; + } + + /** + * Reads the next four bytes as an unsigned value. + */ + public long readUnsignedInt() { + return (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL); + } + + /** + * Reads the next four bytes as an unsigned value in little endian order. + */ + public long readLittleEndianUnsignedInt() { + return (data[position++] & 0xFFL) + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 24; + } + + /** + * Reads the next four bytes as a signed value + */ + public int readInt() { + return (data[position++] & 0xFF) << 24 + | (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + } + + /** + * Reads the next four bytes as an signed value in little endian order. + */ + public int readLittleEndianInt() { + return (data[position++] & 0xFF) + | (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF) << 16 + | (data[position++] & 0xFF) << 24; + } + + /** + * Reads the next eight bytes as a signed value. + */ + public long readLong() { + return (data[position++] & 0xFFL) << 56 + | (data[position++] & 0xFFL) << 48 + | (data[position++] & 0xFFL) << 40 + | (data[position++] & 0xFFL) << 32 + | (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL); + } + + /** + * Reads the next eight bytes as a signed value in little endian order. + */ + public long readLittleEndianLong() { + return (data[position++] & 0xFFL) + | (data[position++] & 0xFFL) << 8 + | (data[position++] & 0xFFL) << 16 + | (data[position++] & 0xFFL) << 24 + | (data[position++] & 0xFFL) << 32 + | (data[position++] & 0xFFL) << 40 + | (data[position++] & 0xFFL) << 48 + | (data[position++] & 0xFFL) << 56; + } + + /** + * Reads the next four bytes, returning the integer portion of the fixed point 16.16 integer. + */ + public int readUnsignedFixedPoint1616() { + int result = (data[position++] & 0xFF) << 8 + | (data[position++] & 0xFF); + position += 2; // Skip the non-integer portion. + return result; + } + + /** + * Reads a Synchsafe integer. + *

        + * Synchsafe integers keep the highest bit of every byte zeroed. A 32 bit synchsafe integer can + * store 28 bits of information. + * + * @return The parsed value. + */ + public int readSynchSafeInt() { + int b1 = readUnsignedByte(); + int b2 = readUnsignedByte(); + int b3 = readUnsignedByte(); + int b4 = readUnsignedByte(); + return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4; + } + + /** + * Reads the next four bytes as an unsigned integer into an integer, if the top bit is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public int readUnsignedIntToInt() { + int result = readInt(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next four bytes as a little endian unsigned integer into an integer, if the top bit + * is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public int readLittleEndianUnsignedIntToInt() { + int result = readLittleEndianInt(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next eight bytes as an unsigned long into a long, if the top bit is a zero. + * + * @throws IllegalStateException Thrown if the top bit of the input data is set. + */ + public long readUnsignedLongToLong() { + long result = readLong(); + if (result < 0) { + throw new IllegalStateException("Top bit not zero: " + result); + } + return result; + } + + /** + * Reads the next four bytes as a 32-bit floating point value. + */ + public float readFloat() { + return Float.intBitsToFloat(readInt()); + } + + /** + * Reads the next eight bytes as a 64-bit floating point value. + */ + public double readDouble() { + return Double.longBitsToDouble(readLong()); + } + + /** + * Reads the next {@code length} bytes as UTF-8 characters. + * + * @param length The number of bytes to read. + * @return The string encoded by the bytes. + */ + public String readString(int length) { + return readString(length, Charset.defaultCharset()); + } + + /** + * Reads the next {@code length} bytes as UTF-8 characters. A terminating NUL byte is ignored, + * if present. + * + * @param length The number of bytes to read. + * @return The string encoded by the bytes. + */ + public String readNullTerminatedString(int length) { + int stringLength = length; + int lastIndex = position + length - 1; + if (lastIndex < limit && data[lastIndex] == 0) { + stringLength--; + } + String result = new String(data, position, stringLength, Charset.defaultCharset()); + position += length; + return result; + } + + /** + * Reads the next {@code length} bytes as characters in the specified {@link Charset}. + * + * @param length The number of bytes to read. + * @param charset The character set of the encoded characters. + * @return The string encoded by the bytes in the specified character set. + */ + public String readString(int length, Charset charset) { + String result = new String(data, position, length, charset); + position += length; + return result; + } + + /** + * Reads a line of text. + *

        + * A line is considered to be terminated by any one of a carriage return ('\r'), a line feed + * ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The system's default + * charset (UTF-8) is used. + * + * @return A String containing the contents of the line, not including any line-termination + * characters, or null if the end of the stream has been reached. + */ + public String readLine() { + if (bytesLeft() == 0) { + return null; + } + int lineLimit = position; + while (lineLimit < limit && data[lineLimit] != '\n' && data[lineLimit] != '\r') { + lineLimit++; + } + if (lineLimit - position >= 3 && data[position] == (byte) 0xEF + && data[position + 1] == (byte) 0xBB && data[position + 2] == (byte) 0xBF) { + // There's a byte order mark at the start of the line. Discard it. + position += 3; + } + String line = new String(data, position, lineLimit - position); + position = lineLimit; + if (position == limit) { + return line; + } + if (data[position] == '\r') { + position++; + if (position == limit) { + return line; + } + } + if (data[position] == '\n') { + position++; + } + return line; + } + + /** + * Reads a long value encoded by UTF-8 encoding + * + * @throws NumberFormatException if there is a problem with decoding + * @return Decoded long value + */ + public long readUtf8EncodedLong() { + int length = 0; + long value = data[position]; + // find the high most 0 bit + for (int j = 7; j >= 0; j--) { + if ((value & (1 << j)) == 0) { + if (j < 6) { + value &= (1 << j) - 1; + length = 7 - j; + } else if (j == 7) { + length = 1; + } + break; + } + } + if (length == 0) { + throw new NumberFormatException("Invalid UTF-8 sequence first byte: " + value); + } + for (int i = 1; i < length; i++) { + int x = data[position + i]; + if ((x & 0xC0) != 0x80) { // if the high most 0 bit not 7th + throw new NumberFormatException("Invalid UTF-8 sequence continuation byte: " + value); + } + value = (value << 6) | (x & 0x3F); + } + position += length; + return value; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java new file mode 100755 index 00000000000..9d961aa7c20 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ParsableNalUnitBitArray.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +/** + * Wraps a byte array, providing methods that allow it to be read as a NAL unit bitstream. + *

        + * Whenever the byte sequence [0, 0, 3] appears in the wrapped byte array, it is treated as [0, 0] + * for all reading/skipping operations, which makes the bitstream appear to be unescaped. + */ +public final class ParsableNalUnitBitArray { + + private byte[] data; + private int byteLimit; + + // The byte offset is never equal to the offset of the 3rd byte in a subsequence [0, 0, 3]. + private int byteOffset; + private int bitOffset; + + /** + * @param data The data to wrap. + * @param offset The byte offset in {@code data} to start reading from. + * @param limit The byte offset of the end of the bitstream in {@code data}. + */ + public ParsableNalUnitBitArray(byte[] data, int offset, int limit) { + reset(data, offset, limit); + } + + /** + * Resets the wrapped data, limit and offset. + * + * @param data The data to wrap. + * @param offset The byte offset in {@code data} to start reading from. + * @param limit The byte offset of the end of the bitstream in {@code data}. + */ + public void reset(byte[] data, int offset, int limit) { + this.data = data; + byteOffset = offset; + byteLimit = limit; + bitOffset = 0; + assertValidOffset(); + } + + /** + * Skips bits and moves current reading position forward. + * + * @param n The number of bits to skip. + */ + public void skipBits(int n) { + int oldByteOffset = byteOffset; + byteOffset += (n / 8); + bitOffset += (n % 8); + if (bitOffset > 7) { + byteOffset++; + bitOffset -= 8; + } + for (int i = oldByteOffset + 1; i <= byteOffset; i++) { + if (shouldSkipByte(i)) { + // Skip the byte and move forward to check three bytes ahead. + byteOffset++; + i += 2; + } + } + assertValidOffset(); + } + + /** + * Returns whether it's possible to read {@code n} bits starting from the current offset. The + * offset is not modified. + * + * @param n The number of bits. + * @return Whether it is possible to read {@code n} bits. + */ + public boolean canReadBits(int n) { + int oldByteOffset = byteOffset; + int newByteOffset = byteOffset + (n / 8); + int newBitOffset = bitOffset + (n % 8); + if (newBitOffset > 7) { + newByteOffset++; + newBitOffset -= 8; + } + for (int i = oldByteOffset + 1; i <= newByteOffset && newByteOffset < byteLimit; i++) { + if (shouldSkipByte(i)) { + // Skip the byte and move forward to check three bytes ahead. + newByteOffset++; + i += 2; + } + } + return newByteOffset < byteLimit || (newByteOffset == byteLimit && newBitOffset == 0); + } + + /** + * Reads a single bit. + * + * @return Whether the bit is set. + */ + public boolean readBit() { + return readBits(1) == 1; + } + + /** + * Reads up to 32 bits. + * + * @param numBits The number of bits to read. + * @return An integer whose bottom n bits hold the read data. + */ + public int readBits(int numBits) { + if (numBits == 0) { + return 0; + } + + int returnValue = 0; + + // Read as many whole bytes as we can. + int wholeBytes = (numBits / 8); + for (int i = 0; i < wholeBytes; i++) { + int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; + int byteValue; + if (bitOffset != 0) { + byteValue = ((data[byteOffset] & 0xFF) << bitOffset) + | ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset)); + } else { + byteValue = data[byteOffset]; + } + numBits -= 8; + returnValue |= (byteValue & 0xFF) << numBits; + byteOffset = nextByteOffset; + } + + // Read any remaining bits. + if (numBits > 0) { + int nextBit = bitOffset + numBits; + byte writeMask = (byte) (0xFF >> (8 - numBits)); + int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1; + + if (nextBit > 8) { + // Combine bits from current byte and next byte. + returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8) + | ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask)); + byteOffset = nextByteOffset; + } else { + // Bits to be read only within current byte. + returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask); + if (nextBit == 8) { + byteOffset = nextByteOffset; + } + } + + bitOffset = nextBit % 8; + } + + assertValidOffset(); + return returnValue; + } + + /** + * Returns whether it is possible to read an Exp-Golomb-coded integer starting from the current + * offset. The offset is not modified. + * + * @return Whether it is possible to read an Exp-Golomb-coded integer. + */ + public boolean canReadExpGolombCodedNum() { + int initialByteOffset = byteOffset; + int initialBitOffset = bitOffset; + int leadingZeros = 0; + while (byteOffset < byteLimit && !readBit()) { + leadingZeros++; + } + boolean hitLimit = byteOffset == byteLimit; + byteOffset = initialByteOffset; + bitOffset = initialBitOffset; + return !hitLimit && canReadBits(leadingZeros * 2 + 1); + } + + /** + * Reads an unsigned Exp-Golomb-coded format integer. + * + * @return The value of the parsed Exp-Golomb-coded integer. + */ + public int readUnsignedExpGolombCodedInt() { + return readExpGolombCodeNum(); + } + + /** + * Reads an signed Exp-Golomb-coded format integer. + * + * @return The value of the parsed Exp-Golomb-coded integer. + */ + public int readSignedExpGolombCodedInt() { + int codeNum = readExpGolombCodeNum(); + return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2); + } + + private int readExpGolombCodeNum() { + int leadingZeros = 0; + while (!readBit()) { + leadingZeros++; + } + return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0); + } + + private boolean shouldSkipByte(int offset) { + return 2 <= offset && offset < byteLimit && data[offset] == (byte) 0x03 + && data[offset - 2] == (byte) 0x00 && data[offset - 1] == (byte) 0x00; + } + + private void assertValidOffset() { + // It is fine for position to be at the end of the array, but no further. + Assertions.checkState(byteOffset >= 0 + && (bitOffset >= 0 && bitOffset < 8) + && (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0))); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Predicate.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Predicate.java similarity index 89% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Predicate.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Predicate.java index e54ccb6af10..2f30910a86d 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/Predicate.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Predicate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; /** * Determines a true of false value for a given input. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PriorityHandlerThread.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityHandlerThread.java similarity index 91% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PriorityHandlerThread.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityHandlerThread.java index 9bb3a80e460..6a1c4c72b07 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/PriorityHandlerThread.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityHandlerThread.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import android.os.HandlerThread; import android.os.Process; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityTaskManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityTaskManager.java new file mode 100755 index 00000000000..296c4173f06 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/PriorityTaskManager.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import java.io.IOException; +import java.util.Collections; +import java.util.PriorityQueue; + +/** + * Allows tasks with associated priorities to control how they proceed relative to one another. + *

        + * A task should call {@link #add(int)} to register with the manager and {@link #remove(int)} to + * unregister. A registered task will prevent tasks of lower priority from proceeding, and should + * call {@link #proceed(int)}, {@link #proceedNonBlocking(int)} or {@link #proceedOrThrow(int)} each + * time it wishes to check whether it is itself allowed to proceed. + */ +public final class PriorityTaskManager { + + /** + * Thrown when task attempts to proceed when another registered task has a higher priority. + */ + public static class PriorityTooLowException extends IOException { + + public PriorityTooLowException(int priority, int highestPriority) { + super("Priority too low [priority=" + priority + ", highest=" + highestPriority + "]"); + } + + } + + private final Object lock = new Object(); + + // Guarded by lock. + private final PriorityQueue queue; + private int highestPriority; + + public PriorityTaskManager() { + queue = new PriorityQueue<>(10, Collections.reverseOrder()); + highestPriority = Integer.MIN_VALUE; + } + + /** + * Register a new task. The task must call {@link #remove(int)} when done. + * + * @param priority The priority of the task. + */ + public void add(int priority) { + synchronized (lock) { + queue.add(priority); + highestPriority = Math.max(highestPriority, priority); + } + } + + /** + * Blocks until the task is allowed to proceed. + * + * @param priority The priority of the task. + * @throws InterruptedException If the thread is interrupted. + */ + public void proceed(int priority) throws InterruptedException { + synchronized (lock) { + while (highestPriority != priority) { + lock.wait(); + } + } + } + + /** + * A non-blocking variant of {@link #proceed(int)}. + * + * @param priority The priority of the task. + * @return Whether the task is allowed to proceed. + */ + public boolean proceedNonBlocking(int priority) { + synchronized (lock) { + return highestPriority == priority; + } + } + + /** + * A throwing variant of {@link #proceed(int)}. + * + * @param priority The priority of the task. + * @throws PriorityTooLowException If the task is not allowed to proceed. + */ + public void proceedOrThrow(int priority) throws PriorityTooLowException { + synchronized (lock) { + if (highestPriority != priority) { + throw new PriorityTooLowException(priority, highestPriority); + } + } + } + + /** + * Unregister a task. + * + * @param priority The priority of the task. + */ + public void remove(int priority) { + synchronized (lock) { + queue.remove(priority); + highestPriority = queue.isEmpty() ? Integer.MIN_VALUE : queue.peek(); + lock.notifyAll(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ReusableBufferedOutputStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ReusableBufferedOutputStream.java new file mode 100755 index 00000000000..fc8afbb3487 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/ReusableBufferedOutputStream.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * This is a subclass of {@link BufferedOutputStream} with a {@link #reset(OutputStream)} method + * that allows an instance to be re-used with another underlying output stream. + */ +public final class ReusableBufferedOutputStream extends BufferedOutputStream { + + private boolean closed; + + public ReusableBufferedOutputStream(OutputStream out) { + super(out); + } + + public ReusableBufferedOutputStream(OutputStream out, int size) { + super(out, size); + } + + @Override + public void close() throws IOException { + closed = true; + + Throwable thrown = null; + try { + flush(); + } catch (Throwable e) { + thrown = e; + } + try { + out.close(); + } catch (Throwable e) { + if (thrown == null) { + thrown = e; + } + } + if (thrown != null) { + Util.sneakyThrow(thrown); + } + } + + /** + * Resets this stream and uses the given output stream for writing. This stream must be closed + * before resetting. + * + * @param out New output stream to be used for writing. + * @throws IllegalStateException If the stream isn't closed. + */ + public void reset(OutputStream out) { + Assertions.checkState(closed); + this.out = out; + closed = false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SlidingPercentile.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SlidingPercentile.java similarity index 81% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SlidingPercentile.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SlidingPercentile.java index d66ba46e46d..eb1e3843613 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SlidingPercentile.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SlidingPercentile.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; /** - * Calculate any percentile over a sliding window of weighted values. A maximum total weight is - * configured. Once the maximum weight is reached, the oldest value is reduced in weight until it - * reaches zero and is removed. This maintains a constant total weight at steady state. + * Calculate any percentile over a sliding window of weighted values. A maximum weight is + * configured. Once the total weight of the values reaches the maximum weight, the oldest value is + * reduced in weight until it reaches zero and is removed. This maintains a constant total weight, + * equal to the maximum allowed, at the steady state. *

        - * SlidingPercentile can be used for bandwidth estimation based on a sliding window of past - * download rate observations. This is an alternative to sliding mean and exponential averaging - * which suffer from susceptibility to outliers and slow adaptation to step functions. + * This class can be used for bandwidth estimation based on a sliding window of past transfer rate + * observations. This is an alternative to sliding mean and exponential averaging which suffer from + * susceptibility to outliers and slow adaptation to step functions. * * @see Wiki: Moving average * @see Wiki: Selection algorithm @@ -64,6 +65,9 @@ public int compare(Sample a, Sample b) { private int totalWeight; private int recycledSampleCount; + /** + * @param maxWeight The maximum weight. + */ public SlidingPercentile(int maxWeight) { this.maxWeight = maxWeight; recycledSamples = new Sample[MAX_RECYCLED_SAMPLES]; @@ -72,8 +76,7 @@ public SlidingPercentile(int maxWeight) { } /** - * Record a new observation. Respect the configured total weight by reducing in weight or - * removing the oldest observations as required. + * Adds a new weighted value. * * @param weight The weight of the new observation. * @param value The value of the new observation. @@ -106,10 +109,10 @@ public void addSample(int weight, float value) { } /** - * Compute the percentile by integration. + * Computes a percentile by integration. * * @param percentile The desired percentile, expressed as a fraction in the range (0,1]. - * @return The requested percentile value or Float.NaN. + * @return The requested percentile value or {@link Float#NaN} if no samples have been added. */ public float getPercentile(float percentile) { ensureSortedByValue(); @@ -127,7 +130,7 @@ public float getPercentile(float percentile) { } /** - * Sort the samples by index, if not already. + * Sorts the samples by index. */ private void ensureSortedByIndex() { if (currentSortOrder != SORT_ORDER_BY_INDEX) { @@ -137,7 +140,7 @@ private void ensureSortedByIndex() { } /** - * Sort the samples by value, if not already. + * Sorts the samples by value. */ private void ensureSortedByValue() { if (currentSortOrder != SORT_ORDER_BY_VALUE) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/StandaloneMediaClock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/StandaloneMediaClock.java similarity index 92% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/StandaloneMediaClock.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/StandaloneMediaClock.java index af1133eded2..1742e2c5551 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/StandaloneMediaClock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/StandaloneMediaClock.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer; +package org.telegram.messenger.exoplayer2.util; import android.os.SystemClock; @@ -21,7 +21,7 @@ * A standalone {@link MediaClock}. The clock can be started, stopped and its time can be set and * retrieved. When started, this clock is based on {@link SystemClock#elapsedRealtime()}. */ -/* package */ final class StandaloneMediaClock implements MediaClock { +public final class StandaloneMediaClock implements MediaClock { private boolean started; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SystemClock.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java similarity index 94% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SystemClock.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java index fcf045a0703..ecb5e4df142 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/SystemClock.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/SystemClock.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; /** * The standard implementation of {@link Clock}. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/TraceUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/TraceUtil.java similarity index 83% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/TraceUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/TraceUtil.java index f08658ef9d4..7f77a4a64a7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/TraceUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/TraceUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import android.annotation.TargetApi; -import org.telegram.messenger.exoplayer.ExoPlayerLibraryInfo; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; /** * Calls through to {@link android.os.Trace} methods on supported API levels. @@ -29,6 +29,8 @@ private TraceUtil() {} * Writes a trace message to indicate that a given section of code has begun. * * @see android.os.Trace#beginSection(String) + * @param sectionName The name of the code section to appear in the trace. This may be at most 127 + * Unicode code units long. */ public static void beginSection(String sectionName) { if (ExoPlayerLibraryInfo.TRACE_ENABLED && Util.SDK_INT >= 18) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/UriUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/UriUtil.java similarity index 97% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/UriUtil.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/UriUtil.java index 8b1d950c63d..e8f9881b655 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/util/UriUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/UriUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer.util; +package org.telegram.messenger.exoplayer2.util; import android.net.Uri; import android.text.TextUtils; @@ -116,7 +116,7 @@ public static String resolve(String baseUri, String referenceUri) { return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]); } - if (refIndices[PATH] != refIndices[QUERY] && referenceUri.charAt(refIndices[PATH]) == '/') { + if (referenceUri.charAt(refIndices[PATH]) == '/') { // The reference path is rooted. The target is the base scheme and authority (if any), plus // the reference. uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri); @@ -163,7 +163,7 @@ private static String removeDotSegments(StringBuilder uri, int offset, int limit int segmentStart = offset; int i = offset; while (i <= limit) { - int nextSegmentStart = -1; + int nextSegmentStart; if (i == limit) { nextSegmentStart = i; } else if (uri.charAt(i) == '/') { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java new file mode 100755 index 00000000000..77236bbe9c1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/Util.java @@ -0,0 +1,1012 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import android.Manifest.permission; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Point; +import android.net.Uri; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlayerLibraryInfo; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DataSpec; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Miscellaneous utility methods. + */ +public final class Util { + + /** + * Like {@link android.os.Build.VERSION#SDK_INT}, but in a place where it can be conveniently + * overridden for local testing. + */ + public static final int SDK_INT = + (Build.VERSION.SDK_INT == 23 && Build.VERSION.CODENAME.charAt(0) == 'N') ? 24 + : Build.VERSION.SDK_INT; + + /** + * Like {@link Build#DEVICE}, but in a place where it can be conveniently overridden for local + * testing. + */ + public static final String DEVICE = Build.DEVICE; + + /** + * Like {@link Build#MANUFACTURER}, but in a place where it can be conveniently overridden for + * local testing. + */ + public static final String MANUFACTURER = Build.MANUFACTURER; + + /** + * Like {@link Build#MODEL}, but in a place where it can be conveniently overridden for local + * testing. + */ + public static final String MODEL = Build.MODEL; + + /** + * A concise description of the device that it can be useful to log for debugging purposes. + */ + public static final String DEVICE_DEBUG_INFO = DEVICE + ", " + MODEL + ", " + MANUFACTURER + ", " + + SDK_INT; + + private static final String TAG = "Util"; + private static final Pattern XS_DATE_TIME_PATTERN = Pattern.compile( + "(\\d\\d\\d\\d)\\-(\\d\\d)\\-(\\d\\d)[Tt]" + + "(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?" + + "([Zz]|((\\+|\\-)(\\d\\d):(\\d\\d)))?"); + private static final Pattern XS_DURATION_PATTERN = + Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" + + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); + private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})"); + + private Util() {} + + /** + * Converts the entirety of an {@link InputStream} to a byte array. + * + * @param inputStream the {@link InputStream} to be read. The input stream is not closed by this + * method. + * @return a byte array containing all of the inputStream's bytes. + * @throws IOException if an error occurs reading from the stream. + */ + public static byte[] toByteArray(InputStream inputStream) throws IOException { + byte[] buffer = new byte[1024 * 4]; + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + return outputStream.toByteArray(); + } + + /** + * Checks whether it's necessary to request the {@link permission#READ_EXTERNAL_STORAGE} + * permission read the specified {@link Uri}s, requesting the permission if necessary. + * + * @param activity The host activity for checking and requesting the permission. + * @param uris {@link Uri}s that may require {@link permission#READ_EXTERNAL_STORAGE} to read. + * @return Whether a permission request was made. + */ + @TargetApi(23) + public static boolean maybeRequestReadExternalStoragePermission(Activity activity, Uri... uris) { + if (Util.SDK_INT < 23) { + return false; + } + for (Uri uri : uris) { + if (Util.isLocalFileUri(uri)) { + if (activity.checkSelfPermission(permission.READ_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[] {permission.READ_EXTERNAL_STORAGE}, 0); + return true; + } + break; + } + } + return false; + } + + /** + * Returns true if the URI is a path to a local file or a reference to a local file. + * + * @param uri The uri to test. + */ + public static boolean isLocalFileUri(Uri uri) { + String scheme = uri.getScheme(); + return TextUtils.isEmpty(scheme) || scheme.equals("file"); + } + + /** + * Tests two objects for {@link Object#equals(Object)} equality, handling the case where one or + * both may be null. + * + * @param o1 The first object. + * @param o2 The second object. + * @return {@code o1 == null ? o2 == null : o1.equals(o2)}. + */ + public static boolean areEqual(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * Tests whether an {@code items} array contains an object equal to {@code item}, according to + * {@link Object#equals(Object)}. + *

        + * If {@code item} is null then true is returned if and only if {@code items} contains null. + * + * @param items The array of items to search. + * @param item The item to search for. + * @return True if the array contains an object equal to the item being searched for. + */ + public static boolean contains(Object[] items, Object item) { + for (Object arrayItem : items) { + if (Util.areEqual(arrayItem, item)) { + return true; + } + } + return false; + } + + /** + * Instantiates a new single threaded executor whose thread has the specified name. + * + * @param threadName The name of the thread. + * @return The executor. + */ + public static ExecutorService newSingleThreadExecutor(final String threadName) { + return Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, threadName); + } + }); + } + + /** + * Closes a {@link DataSource}, suppressing any {@link IOException} that may occur. + * + * @param dataSource The {@link DataSource} to close. + */ + public static void closeQuietly(DataSource dataSource) { + try { + if (dataSource != null) { + dataSource.close(); + } + } catch (IOException e) { + // Ignore. + } + } + + /** + * Closes a {@link Closeable}, suppressing any {@link IOException} that may occur. Both {@link + * java.io.OutputStream} and {@link InputStream} are {@code Closeable}. + * + * @param closeable The {@link Closeable} to close. + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException e) { + // Ignore. + } + } + + /** + * Returns a normalized RFC 5646 language code. + * + * @param language A possibly non-normalized RFC 5646 language code. + * @return The normalized code, or null if the input was null. + */ + public static String normalizeLanguageCode(String language) { + return language == null ? null : new Locale(language).getLanguage(); + } + + /** + * Returns a new byte array containing the code points of a {@link String} encoded using UTF-8. + * + * @param value The {@link String} whose bytes should be obtained. + * @return The code points encoding using UTF-8. + */ + public static byte[] getUtf8Bytes(String value) { + return value.getBytes(Charset.defaultCharset()); // UTF-8 is the default on Android. + } + + /** + * Converts text to lower case using {@link Locale#US}. + * + * @param text The text to convert. + * @return The lower case text, or null if {@code text} is null. + */ + public static String toLowerInvariant(String text) { + return text == null ? null : text.toLowerCase(Locale.US); + } + + /** + * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. + * + * @param numerator The numerator to divide. + * @param denominator The denominator to divide by. + * @return The ceiled result of the division. + */ + public static int ceilDivide(int numerator, int denominator) { + return (numerator + denominator - 1) / denominator; + } + + /** + * Divides a {@code numerator} by a {@code denominator}, returning the ceiled result. + * + * @param numerator The numerator to divide. + * @param denominator The denominator to divide by. + * @return The ceiled result of the division. + */ + public static long ceilDivide(long numerator, long denominator) { + return (numerator + denominator - 1) / denominator; + } + + /** + * Constrains a value to the specified bounds. + * + * @param value The value to constrain. + * @param min The lower bound. + * @param max The upper bound. + * @return The constrained value {@code Math.max(min, Math.min(value, max))}. + */ + public static int constrainValue(int value, int min, int max) { + return Math.max(min, Math.min(value, max)); + } + + /** + * Returns the index of the largest value in an array that is less than (or optionally equal to) + * a specified value. + *

        + * The search is performed using a binary search algorithm, so the array must be sorted. + * + * @param a The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the largest value in the array that + * is strictly less than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest value in the array. If false then -1 will be returned. + */ + public static int binarySearchFloor(int[] a, int value, boolean inclusive, boolean stayInBounds) { + int index = Arrays.binarySearch(a, value); + index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the largest value in an array that is less than (or optionally equal to) + * a specified value. + *

        + * The search is performed using a binary search algorithm, so the array must be sorted. + * + * @param a The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the largest value in the array that + * is strictly less than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest value in the array. If false then -1 will be returned. + */ + public static int binarySearchFloor(long[] a, long value, boolean inclusive, + boolean stayInBounds) { + int index = Arrays.binarySearch(a, value); + index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest value in an array that is greater than (or optionally equal + * to) a specified value. + *

        + * The search is performed using a binary search algorithm, so the array must be sorted. + * + * @param a The array to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the array, whether to return the corresponding + * index. If false then the returned index corresponds to the largest value in the array that + * is strictly less than the value. + * @param stayInBounds If true, then {@code (a.length - 1)} will be returned in the case that the + * value is greater than the largest value in the array. If false then {@code a.length} will + * be returned. + */ + public static int binarySearchCeil(long[] a, long value, boolean inclusive, + boolean stayInBounds) { + int index = Arrays.binarySearch(a, value); + index = index < 0 ? ~index : (inclusive ? index : (index + 1)); + return stayInBounds ? Math.min(a.length - 1, index) : index; + } + + /** + * Returns the index of the largest value in an list that is less than (or optionally equal to) + * a specified value. + *

        + * The search is performed using a binary search algorithm, so the list must be sorted. + * + * @param The type of values being searched. + * @param list The list to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the list, whether to return the corresponding + * index. If false then the returned index corresponds to the largest value in the list that + * is strictly less than the value. + * @param stayInBounds If true, then 0 will be returned in the case that the value is smaller than + * the smallest value in the list. If false then -1 will be returned. + */ + public static int binarySearchFloor(List> list, T value, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, value); + index = index < 0 ? -(index + 2) : (inclusive ? index : (index - 1)); + return stayInBounds ? Math.max(0, index) : index; + } + + /** + * Returns the index of the smallest value in an list that is greater than (or optionally equal + * to) a specified value. + *

        + * The search is performed using a binary search algorithm, so the list must be sorted. + * + * @param The type of values being searched. + * @param list The list to search. + * @param value The value being searched for. + * @param inclusive If the value is present in the list, whether to return the corresponding + * index. If false then the returned index corresponds to the smallest value in the list that + * is strictly greater than the value. + * @param stayInBounds If true, then {@code (list.size() - 1)} will be returned in the case that + * the value is greater than the largest value in the list. If false then {@code list.size()} + * will be returned. + */ + public static int binarySearchCeil(List> list, T value, + boolean inclusive, boolean stayInBounds) { + int index = Collections.binarySearch(list, value); + index = index < 0 ? ~index : (inclusive ? index : (index + 1)); + return stayInBounds ? Math.min(list.size() - 1, index) : index; + } + + /** + * Parses an xs:duration attribute value, returning the parsed duration in milliseconds. + * + * @param value The attribute value to decode. + * @return The parsed duration in milliseconds. + */ + public static long parseXsDuration(String value) { + Matcher matcher = XS_DURATION_PATTERN.matcher(value); + if (matcher.matches()) { + boolean negated = !TextUtils.isEmpty(matcher.group(1)); + // Durations containing years and months aren't completely defined. We assume there are + // 30.4368 days in a month, and 365.242 days in a year. + String years = matcher.group(3); + double durationSeconds = (years != null) ? Double.parseDouble(years) * 31556908 : 0; + String months = matcher.group(5); + durationSeconds += (months != null) ? Double.parseDouble(months) * 2629739 : 0; + String days = matcher.group(7); + durationSeconds += (days != null) ? Double.parseDouble(days) * 86400 : 0; + String hours = matcher.group(10); + durationSeconds += (hours != null) ? Double.parseDouble(hours) * 3600 : 0; + String minutes = matcher.group(12); + durationSeconds += (minutes != null) ? Double.parseDouble(minutes) * 60 : 0; + String seconds = matcher.group(14); + durationSeconds += (seconds != null) ? Double.parseDouble(seconds) : 0; + long durationMillis = (long) (durationSeconds * 1000); + return negated ? -durationMillis : durationMillis; + } else { + return (long) (Double.parseDouble(value) * 3600 * 1000); + } + } + + /** + * Parses an xs:dateTime attribute value, returning the parsed timestamp in milliseconds since + * the epoch. + * + * @param value The attribute value to decode. + * @return The parsed timestamp in milliseconds since the epoch. + * @throws ParserException if an error occurs parsing the dateTime attribute value. + */ + public static long parseXsDateTime(String value) throws ParserException { + Matcher matcher = XS_DATE_TIME_PATTERN.matcher(value); + if (!matcher.matches()) { + throw new ParserException("Invalid date/time format: " + value); + } + + int timezoneShift; + if (matcher.group(9) == null) { + // No time zone specified. + timezoneShift = 0; + } else if (matcher.group(9).equalsIgnoreCase("Z")) { + timezoneShift = 0; + } else { + timezoneShift = ((Integer.parseInt(matcher.group(12)) * 60 + + Integer.parseInt(matcher.group(13)))); + if (matcher.group(11).equals("-")) { + timezoneShift *= -1; + } + } + + Calendar dateTime = new GregorianCalendar(TimeZone.getTimeZone("GMT")); + + dateTime.clear(); + // Note: The month value is 0-based, hence the -1 on group(2) + dateTime.set(Integer.parseInt(matcher.group(1)), + Integer.parseInt(matcher.group(2)) - 1, + Integer.parseInt(matcher.group(3)), + Integer.parseInt(matcher.group(4)), + Integer.parseInt(matcher.group(5)), + Integer.parseInt(matcher.group(6))); + if (!TextUtils.isEmpty(matcher.group(8))) { + final BigDecimal bd = new BigDecimal("0." + matcher.group(8)); + // we care only for milliseconds, so movePointRight(3) + dateTime.set(Calendar.MILLISECOND, bd.movePointRight(3).intValue()); + } + + long time = dateTime.getTimeInMillis(); + if (timezoneShift != 0) { + time -= timezoneShift * 60000; + } + + return time; + } + + /** + * Scales a large timestamp. + *

        + * Logically, scaling consists of a multiplication followed by a division. The actual operations + * performed are designed to minimize the probability of overflow. + * + * @param timestamp The timestamp to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @return The scaled timestamp. + */ + public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) { + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + return timestamp / divisionFactor; + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + return timestamp * multiplicationFactor; + } else { + double multiplicationFactor = (double) multiplier / divisor; + return (long) (timestamp * multiplicationFactor); + } + } + + /** + * Applies {@link #scaleLargeTimestamp(long, long, long)} to a list of unscaled timestamps. + * + * @param timestamps The timestamps to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + * @return The scaled timestamps. + */ + public static long[] scaleLargeTimestamps(List timestamps, long multiplier, long divisor) { + long[] scaledTimestamps = new long[timestamps.size()]; + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = timestamps.get(i) / divisionFactor; + } + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = timestamps.get(i) * multiplicationFactor; + } + } else { + double multiplicationFactor = (double) multiplier / divisor; + for (int i = 0; i < scaledTimestamps.length; i++) { + scaledTimestamps[i] = (long) (timestamps.get(i) * multiplicationFactor); + } + } + return scaledTimestamps; + } + + /** + * Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps. + * + * @param timestamps The timestamps to scale. + * @param multiplier The multiplier. + * @param divisor The divisor. + */ + public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) { + if (divisor >= multiplier && (divisor % multiplier) == 0) { + long divisionFactor = divisor / multiplier; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] /= divisionFactor; + } + } else if (divisor < multiplier && (multiplier % divisor) == 0) { + long multiplicationFactor = multiplier / divisor; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] *= multiplicationFactor; + } + } else { + double multiplicationFactor = (double) multiplier / divisor; + for (int i = 0; i < timestamps.length; i++) { + timestamps[i] = (long) (timestamps[i] * multiplicationFactor); + } + } + } + + /** + * Converts a list of integers to a primitive array. + * + * @param list A list of integers. + * @return The list in array form, or null if the input list was null. + */ + public static int[] toArray(List list) { + if (list == null) { + return null; + } + int length = list.size(); + int[] intArray = new int[length]; + for (int i = 0; i < length; i++) { + intArray[i] = list.get(i); + } + return intArray; + } + + /** + * Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec} + * that represents the remainder of the data. + * + * @param dataSpec The original {@link DataSpec}. + * @param bytesLoaded The number of bytes already loaded. + * @return A {@link DataSpec} that represents the remainder of the data. + */ + public static DataSpec getRemainderDataSpec(DataSpec dataSpec, int bytesLoaded) { + if (bytesLoaded == 0) { + return dataSpec; + } else { + long remainingLength = dataSpec.length == C.LENGTH_UNSET ? C.LENGTH_UNSET + : dataSpec.length - bytesLoaded; + return new DataSpec(dataSpec.uri, dataSpec.position + bytesLoaded, remainingLength, + dataSpec.key, dataSpec.flags); + } + } + + /** + * Returns the integer equal to the big-endian concatenation of the characters in {@code string} + * as bytes. The string must be no more than four characters long. + * + * @param string A string no more than four characters long. + */ + public static int getIntegerCodeForString(String string) { + int length = string.length(); + Assertions.checkArgument(length <= 4); + int result = 0; + for (int i = 0; i < length; i++) { + result <<= 8; + result |= string.charAt(i); + } + return result; + } + + /** + * Returns a byte array containing values parsed from the hex string provided. + * + * @param hexString The hex string to convert to bytes. + * @return A byte array containing values parsed from the hex string provided. + */ + public static byte[] getBytesFromHexString(String hexString) { + byte[] data = new byte[hexString.length() / 2]; + for (int i = 0; i < data.length; i++) { + int stringOffset = i * 2; + data[i] = (byte) ((Character.digit(hexString.charAt(stringOffset), 16) << 4) + + Character.digit(hexString.charAt(stringOffset + 1), 16)); + } + return data; + } + + /** + * Returns a string with comma delimited simple names of each object's class. + * + * @param objects The objects whose simple class names should be comma delimited and returned. + * @return A string with comma delimited simple names of each object's class. + */ + public static String getCommaDelimitedSimpleClassNames(Object[] objects) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < objects.length; i++) { + stringBuilder.append(objects[i].getClass().getSimpleName()); + if (i < objects.length - 1) { + stringBuilder.append(", "); + } + } + return stringBuilder.toString(); + } + + /** + * Returns a user agent string based on the given application name and the library version. + * + * @param context A valid context of the calling application. + * @param applicationName String that will be prefix'ed to the generated user agent. + * @return A user agent string generated using the applicationName and the library version. + */ + public static String getUserAgent(Context context, String applicationName) { + String versionName; + try { + String packageName = context.getPackageName(); + PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + versionName = info.versionName; + } catch (NameNotFoundException e) { + versionName = "?"; + } + return applicationName + "/" + versionName + " (Linux;Android " + Build.VERSION.RELEASE + + ") " + "ExoPlayerLib/" + ExoPlayerLibraryInfo.VERSION; + } + + /** + * Converts a sample bit depth to a corresponding PCM encoding constant. + * + * @param bitDepth The bit depth. Supported values are 8, 16, 24 and 32. + * @return The corresponding encoding. One of {@link C#ENCODING_PCM_8BIT}, + * {@link C#ENCODING_PCM_16BIT}, {@link C#ENCODING_PCM_24BIT} and + * {@link C#ENCODING_PCM_32BIT}. If the bit depth is unsupported then + * {@link C#ENCODING_INVALID} is returned. + */ + @C.PcmEncoding + public static int getPcmEncoding(int bitDepth) { + switch (bitDepth) { + case 8: + return C.ENCODING_PCM_8BIT; + case 16: + return C.ENCODING_PCM_16BIT; + case 24: + return C.ENCODING_PCM_24BIT; + case 32: + return C.ENCODING_PCM_32BIT; + default: + return C.ENCODING_INVALID; + } + } + + /** + * Makes a best guess to infer the type from a file name. + * + * @param fileName Name of the file. It can include the path of the file. + * @return The content type. + */ + @C.ContentType + public static int inferContentType(String fileName) { + if (fileName == null) { + return C.TYPE_OTHER; + } else if (fileName.endsWith(".mpd")) { + return C.TYPE_DASH; + } else if (fileName.endsWith(".ism") || fileName.endsWith(".isml")) { + return C.TYPE_SS; + } else if (fileName.endsWith(".m3u8")) { + return C.TYPE_HLS; + } else { + return C.TYPE_OTHER; + } + } + + /** + * Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C} + * {@code DEFAULT_*_BUFFER_SIZE} constant. + * + * @param trackType The track type. + * @return The corresponding default buffer size in bytes. + */ + public static int getDefaultBufferSize(int trackType) { + switch (trackType) { + case C.TRACK_TYPE_DEFAULT: + return C.DEFAULT_MUXED_BUFFER_SIZE; + case C.TRACK_TYPE_AUDIO: + return C.DEFAULT_AUDIO_BUFFER_SIZE; + case C.TRACK_TYPE_VIDEO: + return C.DEFAULT_VIDEO_BUFFER_SIZE; + case C.TRACK_TYPE_TEXT: + return C.DEFAULT_TEXT_BUFFER_SIZE; + case C.TRACK_TYPE_METADATA: + return C.DEFAULT_METADATA_BUFFER_SIZE; + default: + throw new IllegalStateException(); + } + } + + /** + * Escapes a string so that it's safe for use as a file or directory name on at least FAT32 + * filesystems. FAT32 is the most restrictive of all filesystems still commonly used today. + * + *

        For simplicity, this only handles common characters known to be illegal on FAT32: + * <, >, :, ", /, \, |, ?, and *. % is also escaped since it is used as the escape + * character. Escaping is performed in a consistent way so that no collisions occur and + * {@link #unescapeFileName(String)} can be used to retrieve the original file name. + * + * @param fileName File name to be escaped. + * @return An escaped file name which will be safe for use on at least FAT32 filesystems. + */ + public static String escapeFileName(String fileName) { + int length = fileName.length(); + int charactersToEscapeCount = 0; + for (int i = 0; i < length; i++) { + if (shouldEscapeCharacter(fileName.charAt(i))) { + charactersToEscapeCount++; + } + } + if (charactersToEscapeCount == 0) { + return fileName; + } + + int i = 0; + StringBuilder builder = new StringBuilder(length + charactersToEscapeCount * 2); + while (charactersToEscapeCount > 0) { + char c = fileName.charAt(i++); + if (shouldEscapeCharacter(c)) { + builder.append('%').append(Integer.toHexString(c)); + charactersToEscapeCount--; + } else { + builder.append(c); + } + } + if (i < length) { + builder.append(fileName, i, length); + } + return builder.toString(); + } + + private static boolean shouldEscapeCharacter(char c) { + switch (c) { + case '<': + case '>': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + case '%': + return true; + default: + return false; + } + } + + /** + * Unescapes an escaped file or directory name back to its original value. + * + *

        See {@link #escapeFileName(String)} for more information. + * + * @param fileName File name to be unescaped. + * @return The original value of the file name before it was escaped, or null if the escaped + * fileName seems invalid. + */ + public static String unescapeFileName(String fileName) { + int length = fileName.length(); + int percentCharacterCount = 0; + for (int i = 0; i < length; i++) { + if (fileName.charAt(i) == '%') { + percentCharacterCount++; + } + } + if (percentCharacterCount == 0) { + return fileName; + } + + int expectedLength = length - percentCharacterCount * 2; + StringBuilder builder = new StringBuilder(expectedLength); + Matcher matcher = ESCAPED_CHARACTER_PATTERN.matcher(fileName); + int endOfLastMatch = 0; + while (percentCharacterCount > 0 && matcher.find()) { + char unescapedCharacter = (char) Integer.parseInt(matcher.group(1), 16); + builder.append(fileName, endOfLastMatch, matcher.start()).append(unescapedCharacter); + endOfLastMatch = matcher.end(); + percentCharacterCount--; + } + if (endOfLastMatch < length) { + builder.append(fileName, endOfLastMatch, length); + } + if (builder.length() != expectedLength) { + return null; + } + return builder.toString(); + } + + /** + * A hacky method that always throws {@code t} even if {@code t} is a checked exception, + * and is not declared to be thrown. + */ + public static void sneakyThrow(Throwable t) { + Util.sneakyThrowInternal(t); + } + + @SuppressWarnings("unchecked") + private static void sneakyThrowInternal(Throwable t) throws T { + throw (T) t; + } + + /** + * Returns the result of updating a CRC with the specified bytes in a "most significant bit first" + * order. + * + * @param bytes Array containing the bytes to update the crc value with. + * @param start The index to the first byte in the byte range to update the crc with. + * @param end The index after the last byte in the byte range to update the crc with. + * @param initialValue The initial value for the crc calculation. + * @return The result of updating the initial value with the specified bytes. + */ + public static int crc(byte[] bytes, int start, int end, int initialValue) { + for (int i = start; i < end; i++) { + initialValue = (initialValue << 8) + ^ CRC32_BYTES_MSBF[((initialValue >>> 24) ^ (bytes[i] & 0xFF)) & 0xFF]; + } + return initialValue; + } + + /** + * Gets the physical size of the default display, in pixels. + * + * @param context Any context. + * @return The physical display size, in pixels. + */ + public static Point getPhysicalDisplaySize(Context context) { + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + return getPhysicalDisplaySize(context, windowManager.getDefaultDisplay()); + } + + /** + * Gets the physical size of the specified display, in pixels. + * + * @param context Any context. + * @param display The display whose size is to be returned. + * @return The physical display size, in pixels. + */ + public static Point getPhysicalDisplaySize(Context context, Display display) { + if (Util.SDK_INT < 25 && display.getDisplayId() == Display.DEFAULT_DISPLAY) { + // Before API 25 the Display object does not provide a working way to identify Android TVs + // that can show 4k resolution in a SurfaceView, so check for supported devices here. + if ("Sony".equals(Util.MANUFACTURER) && Util.MODEL.startsWith("BRAVIA") + && context.getPackageManager().hasSystemFeature("com.sony.dtv.hardware.panel.qfhd")) { + return new Point(3840, 2160); + } else if ("NVIDIA".equals(Util.MANUFACTURER) && Util.MODEL.contains("SHIELD")) { + // Attempt to read sys.display-size. + String sysDisplaySize = null; + try { + Class systemProperties = Class.forName("android.os.SystemProperties"); + Method getMethod = systemProperties.getMethod("get", String.class); + sysDisplaySize = (String) getMethod.invoke(systemProperties, "sys.display-size"); + } catch (Exception e) { + Log.e(TAG, "Failed to read sys.display-size", e); + } + // If we managed to read sys.display-size, attempt to parse it. + if (!TextUtils.isEmpty(sysDisplaySize)) { + try { + String[] sysDisplaySizeParts = sysDisplaySize.trim().split("x"); + if (sysDisplaySizeParts.length == 2) { + int width = Integer.parseInt(sysDisplaySizeParts[0]); + int height = Integer.parseInt(sysDisplaySizeParts[1]); + if (width > 0 && height > 0) { + return new Point(width, height); + } + } + } catch (NumberFormatException e) { + // Do nothing. + } + Log.e(TAG, "Invalid sys.display-size: " + sysDisplaySize); + } + } + } + + Point displaySize = new Point(); + if (Util.SDK_INT >= 23) { + getDisplaySizeV23(display, displaySize); + } else if (Util.SDK_INT >= 17) { + getDisplaySizeV17(display, displaySize); + } else if (Util.SDK_INT >= 16) { + getDisplaySizeV16(display, displaySize); + } else { + getDisplaySizeV9(display, displaySize); + } + return displaySize; + } + + @TargetApi(23) + private static void getDisplaySizeV23(Display display, Point outSize) { + Display.Mode mode = display.getMode(); + outSize.x = mode.getPhysicalWidth(); + outSize.y = mode.getPhysicalHeight(); + } + + @TargetApi(17) + private static void getDisplaySizeV17(Display display, Point outSize) { + display.getRealSize(outSize); + } + + @TargetApi(16) + private static void getDisplaySizeV16(Display display, Point outSize) { + display.getSize(outSize); + } + + @SuppressWarnings("deprecation") + private static void getDisplaySizeV9(Display display, Point outSize) { + outSize.x = display.getWidth(); + outSize.y = display.getHeight(); + } + + /** + * Allows the CRC calculation to be done byte by byte instead of bit per bit being the order + * "most significant bit first". + */ + private static final int[] CRC32_BYTES_MSBF = { + 0X00000000, 0X04C11DB7, 0X09823B6E, 0X0D4326D9, 0X130476DC, 0X17C56B6B, 0X1A864DB2, + 0X1E475005, 0X2608EDB8, 0X22C9F00F, 0X2F8AD6D6, 0X2B4BCB61, 0X350C9B64, 0X31CD86D3, + 0X3C8EA00A, 0X384FBDBD, 0X4C11DB70, 0X48D0C6C7, 0X4593E01E, 0X4152FDA9, 0X5F15ADAC, + 0X5BD4B01B, 0X569796C2, 0X52568B75, 0X6A1936C8, 0X6ED82B7F, 0X639B0DA6, 0X675A1011, + 0X791D4014, 0X7DDC5DA3, 0X709F7B7A, 0X745E66CD, 0X9823B6E0, 0X9CE2AB57, 0X91A18D8E, + 0X95609039, 0X8B27C03C, 0X8FE6DD8B, 0X82A5FB52, 0X8664E6E5, 0XBE2B5B58, 0XBAEA46EF, + 0XB7A96036, 0XB3687D81, 0XAD2F2D84, 0XA9EE3033, 0XA4AD16EA, 0XA06C0B5D, 0XD4326D90, + 0XD0F37027, 0XDDB056FE, 0XD9714B49, 0XC7361B4C, 0XC3F706FB, 0XCEB42022, 0XCA753D95, + 0XF23A8028, 0XF6FB9D9F, 0XFBB8BB46, 0XFF79A6F1, 0XE13EF6F4, 0XE5FFEB43, 0XE8BCCD9A, + 0XEC7DD02D, 0X34867077, 0X30476DC0, 0X3D044B19, 0X39C556AE, 0X278206AB, 0X23431B1C, + 0X2E003DC5, 0X2AC12072, 0X128E9DCF, 0X164F8078, 0X1B0CA6A1, 0X1FCDBB16, 0X018AEB13, + 0X054BF6A4, 0X0808D07D, 0X0CC9CDCA, 0X7897AB07, 0X7C56B6B0, 0X71159069, 0X75D48DDE, + 0X6B93DDDB, 0X6F52C06C, 0X6211E6B5, 0X66D0FB02, 0X5E9F46BF, 0X5A5E5B08, 0X571D7DD1, + 0X53DC6066, 0X4D9B3063, 0X495A2DD4, 0X44190B0D, 0X40D816BA, 0XACA5C697, 0XA864DB20, + 0XA527FDF9, 0XA1E6E04E, 0XBFA1B04B, 0XBB60ADFC, 0XB6238B25, 0XB2E29692, 0X8AAD2B2F, + 0X8E6C3698, 0X832F1041, 0X87EE0DF6, 0X99A95DF3, 0X9D684044, 0X902B669D, 0X94EA7B2A, + 0XE0B41DE7, 0XE4750050, 0XE9362689, 0XEDF73B3E, 0XF3B06B3B, 0XF771768C, 0XFA325055, + 0XFEF34DE2, 0XC6BCF05F, 0XC27DEDE8, 0XCF3ECB31, 0XCBFFD686, 0XD5B88683, 0XD1799B34, + 0XDC3ABDED, 0XD8FBA05A, 0X690CE0EE, 0X6DCDFD59, 0X608EDB80, 0X644FC637, 0X7A089632, + 0X7EC98B85, 0X738AAD5C, 0X774BB0EB, 0X4F040D56, 0X4BC510E1, 0X46863638, 0X42472B8F, + 0X5C007B8A, 0X58C1663D, 0X558240E4, 0X51435D53, 0X251D3B9E, 0X21DC2629, 0X2C9F00F0, + 0X285E1D47, 0X36194D42, 0X32D850F5, 0X3F9B762C, 0X3B5A6B9B, 0X0315D626, 0X07D4CB91, + 0X0A97ED48, 0X0E56F0FF, 0X1011A0FA, 0X14D0BD4D, 0X19939B94, 0X1D528623, 0XF12F560E, + 0XF5EE4BB9, 0XF8AD6D60, 0XFC6C70D7, 0XE22B20D2, 0XE6EA3D65, 0XEBA91BBC, 0XEF68060B, + 0XD727BBB6, 0XD3E6A601, 0XDEA580D8, 0XDA649D6F, 0XC423CD6A, 0XC0E2D0DD, 0XCDA1F604, + 0XC960EBB3, 0XBD3E8D7E, 0XB9FF90C9, 0XB4BCB610, 0XB07DABA7, 0XAE3AFBA2, 0XAAFBE615, + 0XA7B8C0CC, 0XA379DD7B, 0X9B3660C6, 0X9FF77D71, 0X92B45BA8, 0X9675461F, 0X8832161A, + 0X8CF30BAD, 0X81B02D74, 0X857130C3, 0X5D8A9099, 0X594B8D2E, 0X5408ABF7, 0X50C9B640, + 0X4E8EE645, 0X4A4FFBF2, 0X470CDD2B, 0X43CDC09C, 0X7B827D21, 0X7F436096, 0X7200464F, + 0X76C15BF8, 0X68860BFD, 0X6C47164A, 0X61043093, 0X65C52D24, 0X119B4BE9, 0X155A565E, + 0X18197087, 0X1CD86D30, 0X029F3D35, 0X065E2082, 0X0B1D065B, 0X0FDC1BEC, 0X3793A651, + 0X3352BBE6, 0X3E119D3F, 0X3AD08088, 0X2497D08D, 0X2056CD3A, 0X2D15EBE3, 0X29D4F654, + 0XC5A92679, 0XC1683BCE, 0XCC2B1D17, 0XC8EA00A0, 0XD6AD50A5, 0XD26C4D12, 0XDF2F6BCB, + 0XDBEE767C, 0XE3A1CBC1, 0XE760D676, 0XEA23F0AF, 0XEEE2ED18, 0XF0A5BD1D, 0XF464A0AA, + 0XF9278673, 0XFDE69BC4, 0X89B8FD09, 0X8D79E0BE, 0X803AC667, 0X84FBDBD0, 0X9ABC8BD5, + 0X9E7D9662, 0X933EB0BB, 0X97FFAD0C, 0XAFB010B1, 0XAB710D06, 0XA6322BDF, 0XA2F33668, + 0XBCB4666D, 0XB8757BDA, 0XB5365D03, 0XB1F740B4 + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/XmlPullParserUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/XmlPullParserUtil.java new file mode 100755 index 00000000000..db279c878e7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/util/XmlPullParserUtil.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.util; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * {@link XmlPullParser} utility methods. + */ +public final class XmlPullParserUtil { + + private XmlPullParserUtil() {} + + /** + * Returns whether the current event is an end tag with the specified name. + * + * @param xpp The {@link XmlPullParser} to query. + * @param name The specified name. + * @return Whether the current event is an end tag with the specified name. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isEndTag(XmlPullParser xpp, String name) throws XmlPullParserException { + return isEndTag(xpp) && xpp.getName().equals(name); + } + + /** + * Returns whether the current event is an end tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @return Whether the current event is an end tag. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isEndTag(XmlPullParser xpp) throws XmlPullParserException { + return xpp.getEventType() == XmlPullParser.END_TAG; + } + + /** + * Returns whether the current event is a start tag with the specified name. + * + * @param xpp The {@link XmlPullParser} to query. + * @param name The specified name. + * @return Whether the current event is a start tag with the specified name. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isStartTag(XmlPullParser xpp, String name) + throws XmlPullParserException { + return isStartTag(xpp) && xpp.getName().equals(name); + } + + /** + * Returns whether the current event is a start tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @return Whether the current event is a start tag. + * @throws XmlPullParserException If an error occurs querying the parser. + */ + public static boolean isStartTag(XmlPullParser xpp) throws XmlPullParserException { + return xpp.getEventType() == XmlPullParser.START_TAG; + } + + /** + * Returns the value of an attribute of the current start tag. + * + * @param xpp The {@link XmlPullParser} to query. + * @param attributeName The name of the attribute. + * @return The value of the attribute, or null if the current event is not a start tag or if no + * no such attribute was found. + */ + public static String getAttributeValue(XmlPullParser xpp, String attributeName) { + int attributeCount = xpp.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + if (attributeName.equals(xpp.getAttributeName(i))) { + return xpp.getAttributeValue(i); + } + } + return null; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/AvcConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/AvcConfig.java new file mode 100755 index 00000000000..0624ca1fb9c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/AvcConfig.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.video; + +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.util.CodecSpecificDataUtil; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil.SpsData; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.ArrayList; +import java.util.List; + +/** + * AVC configuration data. + */ +public final class AvcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + public final int width; + public final int height; + public final float pixelWidthAspectRatio; + + /** + * Parses AVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the AVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static AvcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(4); // Skip to the AVCDecoderConfigurationRecord (defined in 14496-15) + int nalUnitLengthFieldLength = (data.readUnsignedByte() & 0x3) + 1; + if (nalUnitLengthFieldLength == 3) { + throw new IllegalStateException(); + } + List initializationData = new ArrayList<>(); + int numSequenceParameterSets = data.readUnsignedByte() & 0x1F; + for (int j = 0; j < numSequenceParameterSets; j++) { + initializationData.add(buildNalUnitForChild(data)); + } + int numPictureParameterSets = data.readUnsignedByte(); + for (int j = 0; j < numPictureParameterSets; j++) { + initializationData.add(buildNalUnitForChild(data)); + } + + int width = Format.NO_VALUE; + int height = Format.NO_VALUE; + float pixelWidthAspectRatio = 1; + if (numSequenceParameterSets > 0) { + byte[] sps = initializationData.get(0); + SpsData spsData = NalUnitUtil.parseSpsNalUnit(initializationData.get(0), + nalUnitLengthFieldLength, sps.length); + width = spsData.width; + height = spsData.height; + pixelWidthAspectRatio = spsData.pixelWidthAspectRatio; + } + return new AvcConfig(initializationData, nalUnitLengthFieldLength, width, height, + pixelWidthAspectRatio); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing AVC config", e); + } + } + + private AvcConfig(List initializationData, int nalUnitLengthFieldLength, + int width, int height, float pixelWidthAspectRatio) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + this.width = width; + this.height = height; + this.pixelWidthAspectRatio = pixelWidthAspectRatio; + } + + private static byte[] buildNalUnitForChild(ParsableByteArray data) { + int length = data.readUnsignedShort(); + int offset = data.getPosition(); + data.skipBytes(length); + return CodecSpecificDataUtil.buildNalUnit(data.data, offset, length); + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/HevcConfig.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/HevcConfig.java new file mode 100755 index 00000000000..84db3b1673a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/HevcConfig.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.video; + +import org.telegram.messenger.exoplayer2.ParserException; +import org.telegram.messenger.exoplayer2.util.NalUnitUtil; +import org.telegram.messenger.exoplayer2.util.ParsableByteArray; +import java.util.Collections; +import java.util.List; + +/** + * HEVC configuration data. + */ +public final class HevcConfig { + + public final List initializationData; + public final int nalUnitLengthFieldLength; + + /** + * Parses HEVC configuration data. + * + * @param data A {@link ParsableByteArray}, whose position is set to the start of the HEVC + * configuration data to parse. + * @return A parsed representation of the HEVC configuration data. + * @throws ParserException If an error occurred parsing the data. + */ + public static HevcConfig parse(ParsableByteArray data) throws ParserException { + try { + data.skipBytes(21); // Skip to the NAL unit length size field. + int lengthSizeMinusOne = data.readUnsignedByte() & 0x03; + + // Calculate the combined size of all VPS/SPS/PPS bitstreams. + int numberOfArrays = data.readUnsignedByte(); + int csdLength = 0; + int csdStartPosition = data.getPosition(); + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + csdLength += 4 + nalUnitLength; // Start code and NAL unit. + data.skipBytes(nalUnitLength); + } + } + + // Concatenate the codec-specific data into a single buffer. + data.setPosition(csdStartPosition); + byte[] buffer = new byte[csdLength]; + int bufferPosition = 0; + for (int i = 0; i < numberOfArrays; i++) { + data.skipBytes(1); // completeness (1), nal_unit_type (7) + int numberOfNalUnits = data.readUnsignedShort(); + for (int j = 0; j < numberOfNalUnits; j++) { + int nalUnitLength = data.readUnsignedShort(); + System.arraycopy(NalUnitUtil.NAL_START_CODE, 0, buffer, bufferPosition, + NalUnitUtil.NAL_START_CODE.length); + bufferPosition += NalUnitUtil.NAL_START_CODE.length; + System + .arraycopy(data.data, data.getPosition(), buffer, bufferPosition, nalUnitLength); + bufferPosition += nalUnitLength; + data.skipBytes(nalUnitLength); + } + } + + List initializationData = csdLength == 0 ? null : Collections.singletonList(buffer); + return new HevcConfig(initializationData, lengthSizeMinusOne + 1); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParserException("Error parsing HEVC config", e); + } + } + + private HevcConfig(List initializationData, int nalUnitLengthFieldLength) { + this.initializationData = initializationData; + this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java new file mode 100755 index 00000000000..e81e5c63749 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/MediaCodecVideoRenderer.java @@ -0,0 +1,671 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.video; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.media.MediaCodec; +import android.media.MediaCrypto; +import android.media.MediaFormat; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Log; +import android.view.Surface; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.drm.DrmInitData; +import org.telegram.messenger.exoplayer2.drm.DrmSessionManager; +import org.telegram.messenger.exoplayer2.drm.FrameworkMediaCrypto; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecInfo; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecRenderer; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecSelector; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil; +import org.telegram.messenger.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; +import org.telegram.messenger.exoplayer2.util.MimeTypes; +import org.telegram.messenger.exoplayer2.util.TraceUtil; +import org.telegram.messenger.exoplayer2.util.Util; +import org.telegram.messenger.exoplayer2.video.VideoRendererEventListener.EventDispatcher; +import java.nio.ByteBuffer; + +/** + * Decodes and renders video using {@link MediaCodec}. + */ +@TargetApi(16) +public class MediaCodecVideoRenderer extends MediaCodecRenderer { + + private static final String TAG = "MediaCodecVideoRenderer"; + private static final String KEY_CROP_LEFT = "crop-left"; + private static final String KEY_CROP_RIGHT = "crop-right"; + private static final String KEY_CROP_BOTTOM = "crop-bottom"; + private static final String KEY_CROP_TOP = "crop-top"; + + private final VideoFrameReleaseTimeHelper frameReleaseTimeHelper; + private final EventDispatcher eventDispatcher; + private final long allowedJoiningTimeMs; + private final int maxDroppedFramesToNotify; + private final boolean deviceNeedsAutoFrcWorkaround; + + private Format[] streamFormats; + private CodecMaxValues codecMaxValues; + + private Surface surface; + @C.VideoScalingMode + private int scalingMode; + private boolean renderedFirstFrame; + private long joiningDeadlineMs; + private long droppedFrameAccumulationStartTimeMs; + private int droppedFrames; + private int consecutiveDroppedFrameCount; + + private int pendingRotationDegrees; + private float pendingPixelWidthHeightRatio; + private int currentWidth; + private int currentHeight; + private int currentUnappliedRotationDegrees; + private float currentPixelWidthHeightRatio; + private int lastReportedWidth; + private int lastReportedHeight; + private int lastReportedUnappliedRotationDegrees; + private float lastReportedPixelWidthHeightRatio; + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector) { + this(context, mediaCodecSelector, 0); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs) { + this(context, mediaCodecSelector, allowedJoiningTimeMs, null, null, -1); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between + * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, Handler eventHandler, VideoRendererEventListener eventListener, + int maxDroppedFrameCountToNotify) { + this(context, mediaCodecSelector, allowedJoiningTimeMs, null, false, eventHandler, + eventListener, maxDroppedFrameCountToNotify); + } + + /** + * @param context A context. + * @param mediaCodecSelector A decoder selector. + * @param allowedJoiningTimeMs The maximum duration in milliseconds for which this video renderer + * can attempt to seamlessly join an ongoing playback. + * @param drmSessionManager For use with encrypted content. May be null if support for encrypted + * content is not required. + * @param playClearSamplesWithoutKeys Encrypted media may contain clear (un-encrypted) regions. + * For example a media file may start with a short clear region so as to allow playback to + * begin in parallel with key acquisition. This parameter specifies whether the renderer is + * permitted to play clear regions of encrypted media files before {@code drmSessionManager} + * has obtained the keys necessary to decrypt encrypted regions of the media. + * @param eventHandler A handler to use when delivering events to {@code eventListener}. May be + * null if delivery of events is not required. + * @param eventListener A listener of events. May be null if delivery of events is not required. + * @param maxDroppedFramesToNotify The maximum number of frames that can be dropped between + * invocations of {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public MediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, + long allowedJoiningTimeMs, DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys, Handler eventHandler, + VideoRendererEventListener eventListener, int maxDroppedFramesToNotify) { + super(C.TRACK_TYPE_VIDEO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys); + this.allowedJoiningTimeMs = allowedJoiningTimeMs; + this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; + frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context); + eventDispatcher = new EventDispatcher(eventHandler, eventListener); + deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround(); + joiningDeadlineMs = C.TIME_UNSET; + currentWidth = Format.NO_VALUE; + currentHeight = Format.NO_VALUE; + currentPixelWidthHeightRatio = Format.NO_VALUE; + pendingPixelWidthHeightRatio = Format.NO_VALUE; + scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; + clearLastReportedVideoSize(); + } + + @Override + protected int supportsFormat(MediaCodecSelector mediaCodecSelector, Format format) + throws DecoderQueryException { + String mimeType = format.sampleMimeType; + if (!MimeTypes.isVideo(mimeType)) { + return FORMAT_UNSUPPORTED_TYPE; + } + boolean requiresSecureDecryption = false; + DrmInitData drmInitData = format.drmInitData; + if (drmInitData != null) { + for (int i = 0; i < drmInitData.schemeDataCount; i++) { + requiresSecureDecryption |= drmInitData.get(i).requiresSecureDecryption; + } + } + MediaCodecInfo decoderInfo = mediaCodecSelector.getDecoderInfo(mimeType, + requiresSecureDecryption); + if (decoderInfo == null) { + return FORMAT_UNSUPPORTED_SUBTYPE; + } + + boolean decoderCapable = decoderInfo.isCodecSupported(format.codecs); + if (decoderCapable && format.width > 0 && format.height > 0) { + if (Util.SDK_INT >= 21) { + if (format.frameRate > 0) { + decoderCapable = decoderInfo.isVideoSizeAndRateSupportedV21(format.width, format.height, + format.frameRate); + } else { + decoderCapable = decoderInfo.isVideoSizeSupportedV21(format.width, format.height); + } + } else { + decoderCapable = format.width * format.height <= MediaCodecUtil.maxH264DecodableFrameSize(); + if (!decoderCapable) { + Log.d(TAG, "FalseCheck [legacyFrameSize, " + format.width + "x" + format.height + "] [" + + Util.DEVICE_DEBUG_INFO + "]"); + } + } + } + + int adaptiveSupport = decoderInfo.adaptive ? ADAPTIVE_SEAMLESS : ADAPTIVE_NOT_SEAMLESS; + int formatSupport = decoderCapable ? FORMAT_HANDLED : FORMAT_EXCEEDS_CAPABILITIES; + return adaptiveSupport | formatSupport; + } + + @Override + protected void onEnabled(boolean joining) throws ExoPlaybackException { + super.onEnabled(joining); + eventDispatcher.enabled(decoderCounters); + frameReleaseTimeHelper.enable(); + } + + @Override + protected void onStreamChanged(Format[] formats) throws ExoPlaybackException { + streamFormats = formats; + super.onStreamChanged(formats); + } + + @Override + protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException { + super.onPositionReset(positionUs, joining); + renderedFirstFrame = false; + consecutiveDroppedFrameCount = 0; + joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 + ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; + } + + @Override + public boolean isReady() { + if ((renderedFirstFrame || super.shouldInitCodec()) && super.isReady()) { + // Ready. If we were joining then we've now joined, so clear the joining deadline. + joiningDeadlineMs = C.TIME_UNSET; + return true; + } else if (joiningDeadlineMs == C.TIME_UNSET) { + // Not joining. + return false; + } else if (SystemClock.elapsedRealtime() < joiningDeadlineMs) { + // Joining and still within the joining deadline. + return true; + } else { + // The joining deadline has been exceeded. Give up and clear the deadline. + joiningDeadlineMs = C.TIME_UNSET; + return false; + } + } + + @Override + protected void onStarted() { + super.onStarted(); + droppedFrames = 0; + droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime(); + } + + @Override + protected void onStopped() { + joiningDeadlineMs = C.TIME_UNSET; + maybeNotifyDroppedFrames(); + super.onStopped(); + } + + @Override + protected void onDisabled() { + currentWidth = Format.NO_VALUE; + currentHeight = Format.NO_VALUE; + currentPixelWidthHeightRatio = Format.NO_VALUE; + pendingPixelWidthHeightRatio = Format.NO_VALUE; + clearLastReportedVideoSize(); + frameReleaseTimeHelper.disable(); + try { + super.onDisabled(); + } finally { + decoderCounters.ensureUpdated(); + eventDispatcher.disabled(decoderCounters); + } + } + + @Override + public void handleMessage(int messageType, Object message) throws ExoPlaybackException { + if (messageType == C.MSG_SET_SURFACE) { + setSurface((Surface) message); + } else if (messageType == C.MSG_SET_SCALING_MODE) { + scalingMode = (Integer) message; + MediaCodec codec = getCodec(); + if (codec != null) { + setVideoScalingMode(codec, scalingMode); + } + } else { + super.handleMessage(messageType, message); + } + } + + private void setSurface(Surface surface) throws ExoPlaybackException { + // Clear state so that we always call the event listener with the video size and when a frame + // is rendered, even if the surface hasn't changed. + renderedFirstFrame = false; + clearLastReportedVideoSize(); + // We only need to actually release and reinitialize the codec if the surface has changed. + if (this.surface != surface) { + this.surface = surface; + int state = getState(); + if (state == STATE_ENABLED || state == STATE_STARTED) { + releaseCodec(); + maybeInitCodec(); + } + } + } + + @Override + protected boolean shouldInitCodec() { + return super.shouldInitCodec() && surface != null && surface.isValid(); + } + + @Override + protected void configureCodec(MediaCodec codec, Format format, MediaCrypto crypto) { + codecMaxValues = getCodecMaxValues(format, streamFormats); + MediaFormat mediaFormat = getMediaFormat(format, codecMaxValues, deviceNeedsAutoFrcWorkaround); + codec.configure(mediaFormat, surface, crypto, 0); + } + + @Override + protected void onCodecInitialized(String name, long initializedTimestampMs, + long initializationDurationMs) { + eventDispatcher.decoderInitialized(name, initializedTimestampMs, initializationDurationMs); + } + + @Override + protected void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { + super.onInputFormatChanged(newFormat); + eventDispatcher.inputFormatChanged(newFormat); + pendingPixelWidthHeightRatio = getPixelWidthHeightRatio(newFormat); + pendingRotationDegrees = getRotationDegrees(newFormat); + } + + @Override + protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) { + boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT) + && outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM) + && outputFormat.containsKey(KEY_CROP_TOP); + currentWidth = hasCrop + ? outputFormat.getInteger(KEY_CROP_RIGHT) - outputFormat.getInteger(KEY_CROP_LEFT) + 1 + : outputFormat.getInteger(MediaFormat.KEY_WIDTH); + currentHeight = hasCrop + ? outputFormat.getInteger(KEY_CROP_BOTTOM) - outputFormat.getInteger(KEY_CROP_TOP) + 1 + : outputFormat.getInteger(MediaFormat.KEY_HEIGHT); + currentPixelWidthHeightRatio = pendingPixelWidthHeightRatio; + if (Util.SDK_INT >= 21) { + // On API level 21 and above the decoder applies the rotation when rendering to the surface. + // Hence currentUnappliedRotation should always be 0. For 90 and 270 degree rotations, we need + // to flip the width, height and pixel aspect ratio to reflect the rotation that was applied. + if (pendingRotationDegrees == 90 || pendingRotationDegrees == 270) { + int rotatedHeight = currentWidth; + currentWidth = currentHeight; + currentHeight = rotatedHeight; + currentPixelWidthHeightRatio = 1 / currentPixelWidthHeightRatio; + } + } else { + // On API level 20 and below the decoder does not apply the rotation. + currentUnappliedRotationDegrees = pendingRotationDegrees; + } + // Must be applied each time the output format changes. + setVideoScalingMode(codec, scalingMode); + } + + @Override + protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive, + Format oldFormat, Format newFormat) { + return areAdaptationCompatible(oldFormat, newFormat) + && newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height + && newFormat.maxInputSize <= codecMaxValues.inputSize + && (codecIsAdaptive + || (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height)); + } + + @Override + protected boolean processOutputBuffer(long positionUs, long elapsedRealtimeUs, MediaCodec codec, + ByteBuffer buffer, int bufferIndex, int bufferFlags, long bufferPresentationTimeUs, + boolean shouldSkip) { + if (shouldSkip) { + skipOutputBuffer(codec, bufferIndex); + return true; + } + + if (!renderedFirstFrame) { + if (Util.SDK_INT >= 21) { + renderOutputBufferV21(codec, bufferIndex, System.nanoTime()); + } else { + renderOutputBuffer(codec, bufferIndex); + } + return true; + } + + if (getState() != STATE_STARTED) { + return false; + } + + // Compute how many microseconds it is until the buffer's presentation time. + long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs; + long earlyUs = bufferPresentationTimeUs - positionUs - elapsedSinceStartOfLoopUs; + + // Compute the buffer's desired release time in nanoseconds. + long systemTimeNs = System.nanoTime(); + long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000); + + // Apply a timestamp adjustment, if there is one. + long adjustedReleaseTimeNs = frameReleaseTimeHelper.adjustReleaseTime( + bufferPresentationTimeUs, unadjustedFrameReleaseTimeNs); + earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000; + + if (earlyUs < -30000) { + // We're more than 30ms late rendering the frame. + dropOutputBuffer(codec, bufferIndex); + return true; + } + + if (Util.SDK_INT >= 21) { + // Let the underlying framework time the release. + if (earlyUs < 50000) { + renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs); + return true; + } + } else { + // We need to time the release ourselves. + if (earlyUs < 30000) { + if (earlyUs > 11000) { + // We're a little too early to render the frame. Sleep until the frame can be rendered. + // Note: The 11ms threshold was chosen fairly arbitrarily. + try { + // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms. + Thread.sleep((earlyUs - 10000) / 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + renderOutputBuffer(codec, bufferIndex); + return true; + } + } + + // We're either not playing, or it's not time to render the frame yet. + return false; + } + + private void skipOutputBuffer(MediaCodec codec, int bufferIndex) { + TraceUtil.beginSection("skipVideoBuffer"); + codec.releaseOutputBuffer(bufferIndex, false); + TraceUtil.endSection(); + decoderCounters.skippedOutputBufferCount++; + } + + private void dropOutputBuffer(MediaCodec codec, int bufferIndex) { + TraceUtil.beginSection("dropVideoBuffer"); + codec.releaseOutputBuffer(bufferIndex, false); + TraceUtil.endSection(); + decoderCounters.droppedOutputBufferCount++; + droppedFrames++; + consecutiveDroppedFrameCount++; + decoderCounters.maxConsecutiveDroppedOutputBufferCount = Math.max(consecutiveDroppedFrameCount, + decoderCounters.maxConsecutiveDroppedOutputBufferCount); + if (droppedFrames == maxDroppedFramesToNotify) { + maybeNotifyDroppedFrames(); + } + } + + private void renderOutputBuffer(MediaCodec codec, int bufferIndex) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("releaseOutputBuffer"); + codec.releaseOutputBuffer(bufferIndex, true); + TraceUtil.endSection(); + decoderCounters.renderedOutputBufferCount++; + consecutiveDroppedFrameCount = 0; + if (!renderedFirstFrame) { + renderedFirstFrame = true; + eventDispatcher.renderedFirstFrame(surface); + } + } + + @TargetApi(21) + private void renderOutputBufferV21(MediaCodec codec, int bufferIndex, long releaseTimeNs) { + maybeNotifyVideoSizeChanged(); + TraceUtil.beginSection("releaseOutputBuffer"); + codec.releaseOutputBuffer(bufferIndex, releaseTimeNs); + TraceUtil.endSection(); + decoderCounters.renderedOutputBufferCount++; + consecutiveDroppedFrameCount = 0; + if (!renderedFirstFrame) { + renderedFirstFrame = true; + eventDispatcher.renderedFirstFrame(surface); + } + } + + private void clearLastReportedVideoSize() { + lastReportedWidth = Format.NO_VALUE; + lastReportedHeight = Format.NO_VALUE; + lastReportedPixelWidthHeightRatio = Format.NO_VALUE; + lastReportedUnappliedRotationDegrees = Format.NO_VALUE; + } + + private void maybeNotifyVideoSizeChanged() { + if (lastReportedWidth != currentWidth || lastReportedHeight != currentHeight + || lastReportedUnappliedRotationDegrees != currentUnappliedRotationDegrees + || lastReportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) { + eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees, + currentPixelWidthHeightRatio); + lastReportedWidth = currentWidth; + lastReportedHeight = currentHeight; + lastReportedUnappliedRotationDegrees = currentUnappliedRotationDegrees; + lastReportedPixelWidthHeightRatio = currentPixelWidthHeightRatio; + } + } + + private void maybeNotifyDroppedFrames() { + if (droppedFrames > 0) { + long now = SystemClock.elapsedRealtime(); + long elapsedMs = now - droppedFrameAccumulationStartTimeMs; + eventDispatcher.droppedFrames(droppedFrames, elapsedMs); + droppedFrames = 0; + droppedFrameAccumulationStartTimeMs = now; + } + } + + @SuppressLint("InlinedApi") + private static MediaFormat getMediaFormat(Format format, CodecMaxValues codecMaxValues, + boolean deviceNeedsAutoFrcWorkaround) { + MediaFormat frameworkMediaFormat = format.getFrameworkMediaFormatV16(); + // Set the maximum adaptive video dimensions. + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, codecMaxValues.width); + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, codecMaxValues.height); + // Set the maximum input size. + if (codecMaxValues.inputSize != Format.NO_VALUE) { + frameworkMediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, codecMaxValues.inputSize); + } + // Set FRC workaround. + if (deviceNeedsAutoFrcWorkaround) { + frameworkMediaFormat.setInteger("auto-frc", 0); + } + return frameworkMediaFormat; + } + + /** + * Returns {@link CodecMaxValues} suitable for configuring a codec for {@code format} in a way + * that will allow possible adaptation to other compatible formats in {@code streamFormats}. + * + * @param format The format for which the codec is being configured. + * @param streamFormats The possible stream formats. + * @return Suitable {@link CodecMaxValues}. + */ + private static CodecMaxValues getCodecMaxValues(Format format, Format[] streamFormats) { + int maxWidth = format.width; + int maxHeight = format.height; + int maxInputSize = getMaxInputSize(format); + for (Format streamFormat : streamFormats) { + if (areAdaptationCompatible(format, streamFormat)) { + maxWidth = Math.max(maxWidth, streamFormat.width); + maxHeight = Math.max(maxHeight, streamFormat.height); + maxInputSize = Math.max(maxInputSize, getMaxInputSize(streamFormat)); + } + } + return new CodecMaxValues(maxWidth, maxHeight, maxInputSize); + } + + /** + * Returns a maximum input size for a given format. + * + * @param format The format. + * @return An maximum input size in bytes, or {@link Format#NO_VALUE} if a maximum could not be + * determined. + */ + private static int getMaxInputSize(Format format) { + if (format.maxInputSize != Format.NO_VALUE) { + // The format defines an explicit maximum input size. + return format.maxInputSize; + } + + if (format.width == Format.NO_VALUE || format.height == Format.NO_VALUE) { + // We can't infer a maximum input size without video dimensions. + return Format.NO_VALUE; + } + + // Attempt to infer a maximum input size from the format. + int maxPixels; + int minCompressionRatio; + switch (format.sampleMimeType) { + case MimeTypes.VIDEO_H263: + case MimeTypes.VIDEO_MP4V: + maxPixels = format.width * format.height; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_H264: + if ("BRAVIA 4K 2015".equals(Util.MODEL)) { + // The Sony BRAVIA 4k TV has input buffers that are too small for the calculated 4k video + // maximum input size, so use the default value. + return Format.NO_VALUE; + } + // Round up width/height to an integer number of macroblocks. + maxPixels = ((format.width + 15) / 16) * ((format.height + 15) / 16) * 16 * 16; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_VP8: + // VPX does not specify a ratio so use the values from the platform's SoftVPX.cpp. + maxPixels = format.width * format.height; + minCompressionRatio = 2; + break; + case MimeTypes.VIDEO_H265: + case MimeTypes.VIDEO_VP9: + maxPixels = format.width * format.height; + minCompressionRatio = 4; + break; + default: + // Leave the default max input size. + return Format.NO_VALUE; + } + // Estimate the maximum input size assuming three channel 4:2:0 subsampled input frames. + return (maxPixels * 3) / (2 * minCompressionRatio); + } + + private static void setVideoScalingMode(MediaCodec codec, int scalingMode) { + codec.setVideoScalingMode(scalingMode); + } + + /** + * Returns whether the device is known to enable frame-rate conversion logic that negatively + * impacts ExoPlayer. + *

        + * If true is returned then we explicitly disable the feature. + * + * @return True if the device is known to enable frame-rate conversion logic that negatively + * impacts ExoPlayer. False otherwise. + */ + private static boolean deviceNeedsAutoFrcWorkaround() { + // nVidia Shield prior to M tries to adjust the playback rate to better map the frame-rate of + // content to the refresh rate of the display. For example playback of 23.976fps content is + // adjusted to play at 1.001x speed when the output display is 60Hz. Unfortunately the + // implementation causes ExoPlayer's reported playback position to drift out of sync. Captions + // also lose sync [Internal: b/26453592]. + return Util.SDK_INT <= 22 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER); + } + + /** + * Returns whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation + * between two {@link Format}s. + * + * @param first The first format. + * @param second The second format. + * @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation + * between two {@link Format}s. + */ + private static boolean areAdaptationCompatible(Format first, Format second) { + return first.sampleMimeType.equals(second.sampleMimeType) + && getRotationDegrees(first) == getRotationDegrees(second); + } + + private static float getPixelWidthHeightRatio(Format format) { + return format.pixelWidthHeightRatio == Format.NO_VALUE ? 1 : format.pixelWidthHeightRatio; + } + + private static int getRotationDegrees(Format format) { + return format.rotationDegrees == Format.NO_VALUE ? 0 : format.rotationDegrees; + } + + private static final class CodecMaxValues { + + public final int width; + public final int height; + public final int inputSize; + + public CodecMaxValues(int width, int height, int inputSize) { + this.width = width; + this.height = height; + this.inputSize = inputSize; + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/VideoFrameReleaseTimeHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java similarity index 86% rename from TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/VideoFrameReleaseTimeHelper.java rename to TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java index 81788d169b9..bc08b4520c7 100755 --- a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer/VideoFrameReleaseTimeHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoFrameReleaseTimeHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.telegram.messenger.exoplayer; +package org.telegram.messenger.exoplayer2.video; import android.annotation.TargetApi; import android.content.Context; @@ -23,6 +23,7 @@ import android.view.Choreographer; import android.view.Choreographer.FrameCallback; import android.view.WindowManager; +import org.telegram.messenger.exoplayer2.C; /** * Makes a best effort to adjust frame release timestamps for a smoother visual result. @@ -51,16 +52,16 @@ public final class VideoFrameReleaseTimeHelper { private long frameCount; /** - * Constructs an instance that smoothes frame release but does not snap release to the default - * display's vsync signal. + * Constructs an instance that smoothes frame release timestamps but does not align them with + * the default display's vsync signal. */ public VideoFrameReleaseTimeHelper() { - this(-1, false); + this(-1 /* Value unused */, false); } /** - * Constructs an instance that smoothes frame release and snaps release to the default display's - * vsync signal. + * Constructs an instance that smoothes frame release timestamps and aligns them with the default + * display's vsync signal. * * @param context A context from which information about the default display can be retrieved. */ @@ -68,17 +69,17 @@ public VideoFrameReleaseTimeHelper(Context context) { this(getDefaultDisplayRefreshRate(context), true); } - private VideoFrameReleaseTimeHelper(float defaultDisplayRefreshRate, + private VideoFrameReleaseTimeHelper(double defaultDisplayRefreshRate, boolean useDefaultDisplayVsync) { this.useDefaultDisplayVsync = useDefaultDisplayVsync; if (useDefaultDisplayVsync) { vsyncSampler = VSyncSampler.getInstance(); - vsyncDurationNs = (long) (1000000000d / defaultDisplayRefreshRate); + vsyncDurationNs = (long) (C.NANOS_PER_SECOND / defaultDisplayRefreshRate); vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100; } else { vsyncSampler = null; - vsyncDurationNs = -1; - vsyncOffsetNs = -1; + vsyncDurationNs = -1; // Value unused. + vsyncOffsetNs = -1; // Value unused. } } @@ -102,12 +103,12 @@ public void disable() { } /** - * Called to make a fine-grained adjustment to a frame release time. + * Adjusts a frame release timestamp. * - * @param framePresentationTimeUs The frame's media presentation time, in microseconds. + * @param framePresentationTimeUs The frame's presentation time, in microseconds. * @param unadjustedReleaseTimeNs The frame's unadjusted release time, in nanoseconds and in * the same time base as {@link System#nanoTime()}. - * @return An adjusted release time for the frame, in nanoseconds and in the same time base as + * @return The adjusted frame release timestamp, in nanoseconds and in the same time base as * {@link System#nanoTime()}. */ public long adjustReleaseTime(long framePresentationTimeUs, long unadjustedReleaseTimeNs) { @@ -205,9 +206,9 @@ private static float getDefaultDisplayRefreshRate(Context context) { } /** - * Manages the lifecycle of a single {@link Choreographer} to be shared among all - * {@link VideoFrameReleaseTimeHelper} instances. This is done to avoid a bug fixed in platform - * API version 23 that causes resource leakage. See [Internal: b/12455729]. + * Samples display vsync timestamps. A single instance using a single {@link Choreographer} is + * shared by all {@link VideoFrameReleaseTimeHelper} instances. This is done to avoid a resource + * leak in the platform on API levels prior to 23. See [Internal: b/12455729]. */ private static final class VSyncSampler implements FrameCallback, Handler.Callback { @@ -236,17 +237,16 @@ private VSyncSampler() { } /** - * Tells the {@link VSyncSampler} that there is a new {@link VideoFrameReleaseTimeHelper} - * instance observing the currentSampledVsyncTimeNs value. As a consequence, if necessary, it - * will register itself as a {@code doFrame} callback listener. + * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is observing + * {@link #sampledVsyncTimeNs}, and hence that the value should be periodically updated. */ public void addObserver() { handler.sendEmptyMessage(MSG_ADD_OBSERVER); } /** - * Counterpart of {@code addNewObservingHelper}. This method should be called once the observer - * no longer needs to read {@link #sampledVsyncTimeNs} + * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is no longer observing + * {@link #sampledVsyncTimeNs}. */ public void removeObserver() { handler.sendEmptyMessage(MSG_REMOVE_OBSERVER); @@ -279,7 +279,6 @@ public boolean handleMessage(Message message) { } } - private void createChoreographerInstanceInternal() { choreographer = Choreographer.getInstance(); } @@ -299,7 +298,6 @@ private void removeObserverInternal() { } } - } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoRendererEventListener.java b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoRendererEventListener.java new file mode 100755 index 00000000000..3ef4ef8406e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/exoplayer2/video/VideoRendererEventListener.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.exoplayer2.video; + +import android.os.Handler; +import android.os.SystemClock; +import android.view.Surface; +import android.view.TextureView; +import org.telegram.messenger.exoplayer2.Format; +import org.telegram.messenger.exoplayer2.Renderer; +import org.telegram.messenger.exoplayer2.decoder.DecoderCounters; +import org.telegram.messenger.exoplayer2.util.Assertions; + +/** + * Listener of video {@link Renderer} events. + */ +public interface VideoRendererEventListener { + + /** + * Called when the renderer is enabled. + * + * @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it + * remains enabled. + */ + void onVideoEnabled(DecoderCounters counters); + + /** + * Called when a decoder is created. + * + * @param decoderName The decoder that was created. + * @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization + * finished. + * @param initializationDurationMs The time taken to initialize the decoder in milliseconds. + */ + void onVideoDecoderInitialized(String decoderName, long initializedTimestampMs, + long initializationDurationMs); + + /** + * Called when the format of the media being consumed by the renderer changes. + * + * @param format The new format. + */ + void onVideoInputFormatChanged(Format format); + + /** + * Called to report the number of frames dropped by the renderer. Dropped frames are reported + * whenever the renderer is stopped having dropped frames, and optionally, whenever the count + * reaches a specified threshold whilst the renderer is started. + * + * @param count The number of dropped frames. + * @param elapsedMs The duration in milliseconds over which the frames were dropped. This + * duration is timed from when the renderer was started or from when dropped frames were + * last reported (whichever was more recent), and not from when the first of the reported + * drops occurred. + */ + void onDroppedFrames(int count, long elapsedMs); + + /** + * Called before a frame is rendered for the first time since setting the surface, and each time + * there's a change in the size, rotation or pixel aspect ratio of the video being rendered. + * + * @param width The video width in pixels. + * @param height The video height in pixels. + * @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise + * rotation in degrees that the application should apply for the video for it to be rendered + * in the correct orientation. This value will always be zero on API levels 21 and above, + * since the renderer will apply all necessary rotations internally. On earlier API levels + * this is not possible. Applications that use {@link TextureView} can apply the rotation by + * calling {@link TextureView#setTransform}. Applications that do not expect to encounter + * rotated videos can safely ignore this parameter. + * @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case + * of square pixels this will be equal to 1.0. Different values are indicative of anamorphic + * content. + */ + void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, + float pixelWidthHeightRatio); + + /** + * Called when a frame is rendered for the first time since setting the surface, and when a frame + * is rendered for the first time since the renderer was reset. + * + * @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if + * the renderer renders to something that isn't a {@link Surface}. + */ + void onRenderedFirstFrame(Surface surface); + + /** + * Called when the renderer is disabled. + * + * @param counters {@link DecoderCounters} that were updated by the renderer. + */ + void onVideoDisabled(DecoderCounters counters); + + /** + * Dispatches events to a {@link VideoRendererEventListener}. + */ + final class EventDispatcher { + + private final Handler handler; + private final VideoRendererEventListener listener; + + /** + * @param handler A handler for dispatching events, or null if creating a dummy instance. + * @param listener The listener to which events should be dispatched, or null if creating a + * dummy instance. + */ + public EventDispatcher(Handler handler, VideoRendererEventListener listener) { + this.handler = listener != null ? Assertions.checkNotNull(handler) : null; + this.listener = listener; + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoEnabled(DecoderCounters)}. + */ + public void enabled(final DecoderCounters decoderCounters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoEnabled(decoderCounters); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoDecoderInitialized(String, long, long)}. + */ + public void decoderInitialized(final String decoderName, + final long initializedTimestampMs, final long initializationDurationMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoDecoderInitialized(decoderName, initializedTimestampMs, + initializationDurationMs); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. + */ + public void inputFormatChanged(final Format format) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoInputFormatChanged(format); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onDroppedFrames(int, long)}. + */ + public void droppedFrames(final int droppedFrameCount, final long elapsedMs) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onDroppedFrames(droppedFrameCount, elapsedMs); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. + */ + public void videoSizeChanged(final int width, final int height, + final int unappliedRotationDegrees, final float pixelWidthHeightRatio) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, + pixelWidthHeightRatio); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Surface)}. + */ + public void renderedFirstFrame(final Surface surface) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + listener.onRenderedFirstFrame(surface); + } + }); + } + } + + /** + * Invokes {@link VideoRendererEventListener#onVideoDisabled(DecoderCounters)}. + */ + public void disabled(final DecoderCounters counters) { + if (listener != null) { + handler.post(new Runnable() { + @Override + public void run() { + counters.ensureUpdated(); + listener.onVideoDisabled(counters); + } + }); + } + } + + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java index a3cf4adee55..ba906c62cce 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/BotQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; @@ -89,7 +89,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -132,7 +132,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -176,7 +176,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -199,7 +199,7 @@ public void run() { data.reuse(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/DraftQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/DraftQuery.java index bfd6e574b51..936af83034d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/DraftQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/DraftQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; @@ -175,7 +175,7 @@ public static void saveDraft(final long did, TLRPC.DraftMessage draft, TLRPC.Mes draft.serializeToStream(serializedData); editor.putString("" + did, Utilities.bytesToHex(serializedData.toByteArray())); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (replyToMessage == null) { @@ -258,7 +258,7 @@ public void run(TLObject response, TLRPC.TL_error error) { saveDraftReplyMessage(did, message); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java index 5da513a856b..70bb371aed7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; @@ -26,6 +26,7 @@ import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanUserMention; import java.util.ArrayList; @@ -141,7 +142,7 @@ public void run(TLObject response, TLRPC.TL_error error) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -164,7 +165,7 @@ public void run() { state.dispose(); MessagesStorage.getInstance().getDatabase().commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -265,7 +266,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -372,7 +373,7 @@ public void run(TLObject response, TLRPC.TL_error error) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -409,7 +410,7 @@ public void run() { state.dispose(); MessagesStorage.getInstance().getDatabase().commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -444,6 +445,8 @@ public void run() { m.generatePinMessageText(null, null); } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { m.generateGameMessageText(null); + } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { + m.generatePaymentSentMessageText(null); } } changed = true; @@ -460,6 +463,44 @@ public static void sortEntities(ArrayList entities) { Collections.sort(entities, entityComparator); } + private static boolean checkInclusion(int index, ArrayList entities) { + if (entities == null || entities.isEmpty()) { + return false; + } + int count = entities.size(); + for (int a = 0; a < count; a++) { + TLRPC.MessageEntity entity = entities.get(a); + if (entity.offset <= index && entity.offset + entity.length > index) { + return true; + } + } + return false; + } + + private static boolean checkIntersection(int start, int end, ArrayList entities) { + if (entities == null || entities.isEmpty()) { + return false; + } + int count = entities.size(); + for (int a = 0; a < count; a++) { + TLRPC.MessageEntity entity = entities.get(a); + if (entity.offset > start && entity.offset + entity.length <= end) { + return true; + } + } + return false; + } + + private static void removeOffsetAfter(int start, int countToRemove, ArrayList entities) { + int count = entities.size(); + for (int a = 0; a < count; a++) { + TLRPC.MessageEntity entity = entities.get(a); + if (entity.offset > start) { + entity.offset -= countToRemove; + } + } + } + public static ArrayList getEntities(CharSequence[] message) { if (message == null || message[0] == null) { return null; @@ -471,6 +512,8 @@ public static ArrayList getEntities(CharSequence[] message) boolean isPre = false; final String mono = "`"; final String pre = "```"; + final String bold = "**"; + final String italic = "__"; while ((index = TextUtils.indexOf(message[0], !isPre ? mono : pre, lastIndex)) != -1) { if (start == -1) { isPre = message[0].length() - index > 2 && message[0].charAt(index + 1) == '`' && message[0].charAt(index + 2) == '`'; @@ -503,20 +546,24 @@ public static ArrayList getEntities(CharSequence[] message) if (endMessage.length() != 0) { endMessage = TextUtils.concat("\n", endMessage); } - message[0] = TextUtils.concat(startMessage, content, endMessage); - TLRPC.TL_messageEntityPre entity = new TLRPC.TL_messageEntityPre(); - entity.offset = start + (replacedFirst ? 0 : 1); - entity.length = index - start - 3 + (replacedFirst ? 0 : 1); - entity.language = ""; - entities.add(entity); - lastIndex -= 6; + if (!TextUtils.isEmpty(content)) { + message[0] = TextUtils.concat(startMessage, content, endMessage); + TLRPC.TL_messageEntityPre entity = new TLRPC.TL_messageEntityPre(); + entity.offset = start + (replacedFirst ? 0 : 1); + entity.length = index - start - 3 + (replacedFirst ? 0 : 1); + entity.language = ""; + entities.add(entity); + lastIndex -= 6; + } } else { - message[0] = TextUtils.concat(TextUtils.substring(message[0], 0, start), TextUtils.substring(message[0], start + 1, index), TextUtils.substring(message[0], index + 1, message[0].length())); - TLRPC.TL_messageEntityCode entity = new TLRPC.TL_messageEntityCode(); - entity.offset = start; - entity.length = index - start - 1; - entities.add(entity); - lastIndex -= 2; + if (start + 1 != index) { + message[0] = TextUtils.concat(TextUtils.substring(message[0], 0, start), TextUtils.substring(message[0], start + 1, index), TextUtils.substring(message[0], index + 1, message[0].length())); + TLRPC.TL_messageEntityCode entity = new TLRPC.TL_messageEntityCode(); + entity.offset = start; + entity.length = index - start - 1; + entities.add(entity); + lastIndex -= 2; + } } start = -1; isPre = false; @@ -532,17 +579,44 @@ public static ArrayList getEntities(CharSequence[] message) entity.length = 1; entities.add(entity); } + if (message[0] instanceof Spannable) { Spannable spannable = (Spannable) message[0]; - URLSpanUserMention spans[] = spannable.getSpans(0, message[0].length(), URLSpanUserMention.class); + TypefaceSpan spans[] = spannable.getSpans(0, message[0].length(), TypefaceSpan.class); if (spans != null && spans.length > 0) { - entities = new ArrayList<>(); - for (int b = 0; b < spans.length; b++) { + for (int a = 0; a < spans.length; a++) { + TypefaceSpan span = spans[a]; + int spanStart = spannable.getSpanStart(span); + int spanEnd = spannable.getSpanEnd(span); + if (checkInclusion(spanStart, entities) || checkInclusion(spanEnd, entities) || checkIntersection(spanStart, spanEnd, entities)) { + continue; + } + if (entities == null) { + entities = new ArrayList<>(); + } + TLRPC.MessageEntity entity; + if (span.isBold()) { + entity = new TLRPC.TL_messageEntityBold(); + } else { + entity = new TLRPC.TL_messageEntityItalic(); + } + entity.offset = spanStart; + entity.length = spanEnd - spanStart; + entities.add(entity); + } + } + + URLSpanUserMention spansMentions[] = spannable.getSpans(0, message[0].length(), URLSpanUserMention.class); + if (spansMentions != null && spansMentions.length > 0) { + if (entities == null) { + entities = new ArrayList<>(); + } + for (int b = 0; b < spansMentions.length; b++) { TLRPC.TL_inputMessageEntityMentionName entity = new TLRPC.TL_inputMessageEntityMentionName(); - entity.user_id = MessagesController.getInputUser(Utilities.parseInt(spans[b].getURL())); + entity.user_id = MessagesController.getInputUser(Utilities.parseInt(spansMentions[b].getURL())); if (entity.user_id != null) { - entity.offset = spannable.getSpanStart(spans[b]); - entity.length = Math.min(spannable.getSpanEnd(spans[b]), message[0].length()) - entity.offset; + entity.offset = spannable.getSpanStart(spansMentions[b]); + entity.length = Math.min(spannable.getSpanEnd(spansMentions[b]), message[0].length()) - entity.offset; if (message[0].charAt(entity.offset + entity.length - 1) == ' ') { entity.length--; } @@ -551,6 +625,54 @@ public static ArrayList getEntities(CharSequence[] message) } } } + + for (int c = 0; c < 2; c++) { + lastIndex = 0; + start = -1; + String checkString = c == 0 ? bold : italic; + char checkChar = c == 0 ? '*' : '_'; + while ((index = TextUtils.indexOf(message[0], checkString, lastIndex)) != -1) { + if (start == -1) { + char prevChar = index == 0 ? ' ' : message[0].charAt(index - 1); + if (!checkInclusion(index, entities) && (prevChar == ' ' || prevChar == '\n')) { + start = index; + } + lastIndex = index + 2; + } else { + for (int a = index + 2; a < message[0].length(); a++) { + if (message[0].charAt(a) == checkChar) { + index++; + } else { + break; + } + } + lastIndex = index + 2; + if (checkInclusion(index, entities) || checkIntersection(start, index, entities)) { + start = -1; + continue; + } + if (start + 2 != index) { + if (entities == null) { + entities = new ArrayList<>(); + } + message[0] = TextUtils.concat(TextUtils.substring(message[0], 0, start), TextUtils.substring(message[0], start + 2, index), TextUtils.substring(message[0], index + 2, message[0].length())); + TLRPC.MessageEntity entity; + if (c == 0) { + entity = new TLRPC.TL_messageEntityBold(); + } else { + entity = new TLRPC.TL_messageEntityItalic(); + } + entity.offset = start; + entity.length = index - start - 2; + removeOffsetAfter(entity.offset + entity.length, 4, entities); + entities.add(entity); + lastIndex -= 4; + } + start = -1; + } + } + } + return entities; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java index bfd811a41fa..87f33786b93 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/MessagesSearchQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java index f4e0504201e..ebf9cbb44a9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SearchQuery.java @@ -3,30 +3,54 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.Icon; +import android.os.Build; import android.text.TextUtils; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLitePreparedStatement; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.OpenChatReceiver; +import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.LaunchActivity; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Locale; public class SearchQuery { @@ -37,6 +61,9 @@ public class SearchQuery { private static boolean loaded; private static boolean loading; + private static Paint roundPaint; + private static RectF bitmapRect; + public static void cleanup() { loading = false; loaded = false; @@ -47,6 +74,173 @@ public static void cleanup() { NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); } + public static void buildShortcuts() { + if (Build.VERSION.SDK_INT < 25) { + return; + } + final ArrayList hintsFinal = new ArrayList<>(); + for (int a = 0; a < hints.size(); a++) { + hintsFinal.add(hints.get(a)); + if (hintsFinal.size() == 3) { + break; + } + } + Utilities.globalQueue.postRunnable(new Runnable() { + @SuppressLint("NewApi") + @Override + public void run() { + ShortcutManager shortcutManager = ApplicationLoader.applicationContext.getSystemService(ShortcutManager.class); + List currentShortcuts = shortcutManager.getDynamicShortcuts(); + ArrayList shortcutsToUpdate = new ArrayList<>(); + ArrayList newShortcutsIds = new ArrayList<>(); + ArrayList shortcutsToDelete = new ArrayList<>(); + + if (currentShortcuts != null && !currentShortcuts.isEmpty()) { + newShortcutsIds.add("compose"); + for (int a = 0; a < hintsFinal.size(); a++) { + TLRPC.TL_topPeer hint = hintsFinal.get(a); + long did; + if (hint.peer.user_id != 0) { + did = hint.peer.user_id; + } else { + did = -hint.peer.chat_id; + if (did == 0) { + did = -hint.peer.channel_id; + } + } + newShortcutsIds.add("did" + did); + } + for (int a = 0; a < currentShortcuts.size(); a++) { + String id = currentShortcuts.get(a).getId(); + if (!newShortcutsIds.remove(id)) { + shortcutsToDelete.add(id); + } + shortcutsToUpdate.add(id); + } + if (newShortcutsIds.isEmpty() && shortcutsToDelete.isEmpty()) { + return; + } + } + + Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + intent.setAction("new_dialog"); + ArrayList arrayList = new ArrayList<>(); + arrayList.add(new ShortcutInfo.Builder(ApplicationLoader.applicationContext, "compose") + .setShortLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) + .setLongLabel(LocaleController.getString("NewConversationShortcut", R.string.NewConversationShortcut)) + .setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_compose)) + .setIntent(intent) + .build()); + if (shortcutsToUpdate.contains("compose")) { + shortcutManager.updateShortcuts(arrayList); + } else { + shortcutManager.addDynamicShortcuts(arrayList); + } + arrayList.clear(); + + if (!shortcutsToDelete.isEmpty()) { + shortcutManager.removeDynamicShortcuts(shortcutsToDelete); + } + + for (int a = 0; a < hintsFinal.size(); a++) { + Intent shortcutIntent = new Intent(ApplicationLoader.applicationContext, OpenChatReceiver.class); + TLRPC.TL_topPeer hint = hintsFinal.get(a); + + TLRPC.User user = null; + TLRPC.Chat chat = null; + long did; + if (hint.peer.user_id != 0) { + shortcutIntent.putExtra("userId", hint.peer.user_id); + user = MessagesController.getInstance().getUser(hint.peer.user_id); + did = hint.peer.user_id; + } else { + int chat_id = hint.peer.chat_id; + if (chat_id == 0) { + chat_id = hint.peer.channel_id; + } + chat = MessagesController.getInstance().getChat(chat_id); + shortcutIntent.putExtra("chatId", chat_id); + did = -chat_id; + } + if (user == null && chat == null) { + continue; + } + + String name; + TLRPC.FileLocation photo = null; + + if (user != null) { + name = ContactsController.formatName(user.first_name, user.last_name); + if (user.photo != null) { + photo = user.photo.photo_small; + } + } else { + name = chat.title; + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + } + + shortcutIntent.setAction("com.tmessages.openchat" + did); + shortcutIntent.addFlags(0x4000000); + + Bitmap bitmap = null; + if (photo != null) { + try { + File path = FileLoader.getPathToAttach(photo, true); + bitmap = BitmapFactory.decodeFile(path.toString()); + if (bitmap != null) { + int size = AndroidUtilities.dp(48); + Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + result.eraseColor(Color.TRANSPARENT); + Canvas canvas = new Canvas(result); + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + if (roundPaint == null) { + roundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + bitmapRect = new RectF(); + } + float scale = size / (float) bitmap.getWidth(); + canvas.scale(scale, scale); + roundPaint.setShader(shader); + bitmapRect.set(AndroidUtilities.dp(2), AndroidUtilities.dp(2), AndroidUtilities.dp(46), AndroidUtilities.dp(46)); + canvas.drawRoundRect(bitmapRect, bitmap.getWidth(), bitmap.getHeight(), roundPaint); + try { + canvas.setBitmap(null); + } catch (Exception e) { + //don't promt, this will crash on 2.x + } + bitmap = result; + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + String id = "did" + did; + if (TextUtils.isEmpty(name)) { + name = " "; + } + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(ApplicationLoader.applicationContext, id) + .setShortLabel(name) + .setLongLabel(name) + .setIntent(shortcutIntent); + if (bitmap != null) { + builder.setIcon(Icon.createWithBitmap(bitmap)); + } else { + builder.setIcon(Icon.createWithResource(ApplicationLoader.applicationContext, R.drawable.shortcut_user)); + } + arrayList.add(builder.build()); + if (shortcutsToUpdate.contains(id)) { + shortcutManager.updateShortcuts(arrayList); + } else { + shortcutManager.addDynamicShortcuts(arrayList); + } + arrayList.clear(); + } + } + }); + } + public static void loadHints(boolean cache) { if (loading) { return; @@ -107,6 +301,7 @@ public void run() { hints = hintsNew; inlineBots = inlineBotsNew; inlineDates = inlineDatesNew; + buildShortcuts(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); if (Math.abs(UserConfig.lastHintsSyncTime - (int) (System.currentTimeMillis() / 1000)) >= 24 * 60 * 60) { @@ -115,7 +310,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -149,6 +344,7 @@ public void run() { hints = category.peers; } } + buildShortcuts(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadHints); NotificationCenter.getInstance().postNotificationName(NotificationCenter.reloadInlineHints); final HashMap inlineDatesCopy = new HashMap<>(inlineDates); @@ -200,7 +396,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -328,7 +524,7 @@ public void run() { cursor.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } final double dtFinal = dt; AndroidUtilities.runOnUIThread(new Runnable() { @@ -389,7 +585,7 @@ public void run() { state.step(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -402,7 +598,7 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().executeFast(String.format(Locale.US, "DELETE FROM chat_hints WHERE did = %d AND type = %d", did, type)).stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java index 8f2ccdc3ec1..19cea3fa576 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/SharedMediaQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; @@ -250,7 +250,7 @@ public void run() { state2.step(); state2.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -281,7 +281,7 @@ public void run() { } processLoadedMediaCount(count, uid, type, classGuid, true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -414,7 +414,7 @@ public void run() { res.messages.clear(); res.chats.clear(); res.users.clear(); - FileLog.e("tmessages", e); + FileLog.e(e); } finally { processLoadedMedia(res, uid, offset, count, max_id, type, true, classGuid, isChannel, topReached); } @@ -466,7 +466,7 @@ public void run() { } MessagesStorage.getInstance().getDatabase().commitTransaction(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -494,7 +494,7 @@ public void run() { } cursor.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.runOnUIThread(new Runnable() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java index e200429d882..0eadd22c464 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/query/StickersQuery.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.query; @@ -52,6 +52,8 @@ public class StickersQuery { private static int loadHash[] = new int[2]; private static int loadDate[] = new int[2]; + private static int archivedStickersCount[] = new int[2]; + private static HashMap stickersByEmoji = new HashMap<>(); private static HashMap> allStickers = new HashMap<>(); @@ -140,7 +142,7 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "'").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -173,7 +175,7 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + document.id + "'").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -200,7 +202,7 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM web_recent_v3 WHERE id = '" + old.id + "'").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -328,7 +330,7 @@ public void run() { } }); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -420,7 +422,7 @@ public void run() { database.commitTransaction(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -551,7 +553,7 @@ public void run() { hash = calcFeaturedStickersHash(newStickerArray); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -633,7 +635,7 @@ public void run() { } }); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (!cache) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -688,7 +690,7 @@ public void run() { state.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -731,6 +733,21 @@ public void run(TLObject response, TLRPC.TL_error error) { } } + public static int getFeaturesStickersHashWithoutUnread() { + long acc = 0; + for (int a = 0; a < featuredStickerSets.size(); a++) { + TLRPC.StickerSet set = featuredStickerSets.get(a).set; + if (set.archived) { + continue; + } + int high_id = (int) (set.id >> 32); + int lower_id = (int) set.id; + acc = ((acc * 20261) + 0x80000000L + high_id) % 0x80000000L; + acc = ((acc * 20261) + 0x80000000L + lower_id) % 0x80000000L; + } + return (int) acc; + } + public static void markFaturedStickersByIdAsRead(final long id) { if (!unreadStickerSets.contains(id) || readingStickerSets.contains(id)) { //TODO return; @@ -756,10 +773,49 @@ public void run() { }, 1000); } + public static int getArchivedStickersCount(int type) { + return archivedStickersCount[type]; + } + + public static void loadArchivedStickersCount(final int type, boolean cache) { + if (cache) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int count = preferences.getInt("archivedStickersCount" + type, -1); + if (count == -1) { + loadArchivedStickersCount(type, false); + } else { + archivedStickersCount[type] = count; + NotificationCenter.getInstance().postNotificationName(NotificationCenter.archivedStickersCountDidLoaded, type); + } + } else { + TLRPC.TL_messages_getArchivedStickers req = new TLRPC.TL_messages_getArchivedStickers(); + req.limit = 0; + req.masks = type == TYPE_MASK; + int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + TLRPC.TL_messages_archivedStickers res = (TLRPC.TL_messages_archivedStickers) response; + archivedStickersCount[type] = res.count; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("archivedStickersCount" + type, res.count).commit(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.archivedStickersCountDidLoaded, type); + } + } + }); + } + }); + } + } + public static void loadStickers(final int type, boolean cache, boolean force) { if (loadingStickers[type]) { return; } + loadArchivedStickersCount(type, cache); loadingStickers[type] = true; if (cache) { MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { @@ -786,7 +842,7 @@ public void run() { hash = calcStickersHash(newStickerArray); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.dispose(); @@ -909,7 +965,7 @@ public void run() { state.dispose(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -917,7 +973,15 @@ public void run() { public static String getStickerSetName(long setId) { TLRPC.TL_messages_stickerSet stickerSet = stickerSetsById.get(setId); - return stickerSet != null ? stickerSet.set.short_name : null; + if (stickerSet != null) { + return stickerSet.set.short_name; + + } + TLRPC.StickerSetCovered stickerSetCovered = featuredStickerSetsById.get(setId); + if (stickerSetCovered != null) { + return stickerSetCovered.set.short_name; + } + return null; } public static long getStickerSetId(TLRPC.Document document) { @@ -1045,7 +1109,7 @@ public void run() { } }); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (!cache) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -1129,7 +1193,7 @@ public void run() { Toast.makeText(context, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred), Toast.LENGTH_SHORT).show(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } loadStickers(type, false, true); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompat.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompat.java new file mode 100644 index 00000000000..13b15f4acb2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompat.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package org.telegram.messenger.support.fingerprint; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.os.CancellationSignal; + +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +/** + * A class that coordinates access to the fingerprint hardware. + *

        + * On platforms before {@link android.os.Build.VERSION_CODES#M}, this class behaves as there would + * be no fingerprint hardware available. + */ +public final class FingerprintManagerCompat { + + private Context mContext; + + /** Get a {@link FingerprintManagerCompat} instance for a provided context. */ + public static FingerprintManagerCompat from(Context context) { + return new FingerprintManagerCompat(context); + } + + private FingerprintManagerCompat(Context context) { + mContext = context; + } + + static final FingerprintManagerCompatImpl IMPL; + static { + final int version = Build.VERSION.SDK_INT; + if (version >= 23) { + IMPL = new Api23FingerprintManagerCompatImpl(); + } else { + IMPL = new LegacyFingerprintManagerCompatImpl(); + } + } + + /** + * Determine if there is at least one fingerprint enrolled. + * + * @return true if at least one fingerprint is enrolled, false otherwise + */ + public boolean hasEnrolledFingerprints() { + return IMPL.hasEnrolledFingerprints(mContext); + } + + /** + * Determine if fingerprint hardware is present and functional. + * + * @return true if hardware is present and functional, false otherwise. + */ + public boolean isHardwareDetected() { + return IMPL.isHardwareDetected(mContext); + } + + /** + * Request authentication of a crypto object. This call warms up the fingerprint hardware + * and starts scanning for a fingerprint. It terminates when + * {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)} or + * {@link AuthenticationCallback#onAuthenticationSucceeded(AuthenticationResult) is called, at + * which point the object is no longer valid. The operation can be canceled by using the + * provided cancel object. + * + * @param crypto object associated with the call or null if none required. + * @param flags optional flags; should be 0 + * @param cancel an object that can be used to cancel authentication + * @param callback an object to receive authentication events + * @param handler an optional handler for events + */ + public void authenticate(@Nullable CryptoObject crypto, int flags, + @Nullable CancellationSignal cancel, @NonNull AuthenticationCallback callback, + @Nullable Handler handler) { + IMPL.authenticate(mContext, crypto, flags, cancel, callback, handler); + } + + /** + * A wrapper class for the crypto objects supported by FingerprintManager. Currently the + * framework supports {@link Signature} and {@link Cipher} objects. + */ + public static class CryptoObject { + + private final Signature mSignature; + private final Cipher mCipher; + private final Mac mMac; + + public CryptoObject(Signature signature) { + mSignature = signature; + mCipher = null; + mMac = null; + + } + + public CryptoObject(Cipher cipher) { + mCipher = cipher; + mSignature = null; + mMac = null; + } + + public CryptoObject(Mac mac) { + mMac = mac; + mCipher = null; + mSignature = null; + } + + /** + * Get {@link Signature} object. + * @return {@link Signature} object or null if this doesn't contain one. + */ + public Signature getSignature() { return mSignature; } + + /** + * Get {@link Cipher} object. + * @return {@link Cipher} object or null if this doesn't contain one. + */ + public Cipher getCipher() { return mCipher; } + + /** + * Get {@link Mac} object. + * @return {@link Mac} object or null if this doesn't contain one. + */ + public Mac getMac() { return mMac; } + } + + /** + * Container for callback data from {@link FingerprintManagerCompat#authenticate(CryptoObject, + * int, CancellationSignal, AuthenticationCallback, Handler)}. + */ + public static final class AuthenticationResult { + private CryptoObject mCryptoObject; + + public AuthenticationResult(CryptoObject crypto) { + mCryptoObject = crypto; + } + + /** + * Obtain the crypto object associated with this transaction + * @return crypto object provided to {@link FingerprintManagerCompat#authenticate( + * CryptoObject, int, CancellationSignal, AuthenticationCallback, Handler)}. + */ + public CryptoObject getCryptoObject() { return mCryptoObject; } + } + + /** + * Callback structure provided to {@link FingerprintManagerCompat#authenticate(CryptoObject, + * int, CancellationSignal, AuthenticationCallback, Handler)}. Users of {@link + * FingerprintManagerCompat#authenticate(CryptoObject, int, CancellationSignal, + * AuthenticationCallback, Handler) } must provide an implementation of this for listening to + * fingerprint events. + */ + public static abstract class AuthenticationCallback { + /** + * Called when an unrecoverable error has been encountered and the operation is complete. + * No further callbacks will be made on this object. + * @param errMsgId An integer identifying the error message + * @param errString A human-readable error string that can be shown in UI + */ + public void onAuthenticationError(int errMsgId, CharSequence errString) { } + + /** + * Called when a recoverable error has been encountered during authentication. The help + * string is provided to give the user guidance for what went wrong, such as + * "Sensor dirty, please clean it." + * @param helpMsgId An integer identifying the error message + * @param helpString A human-readable string that can be shown in UI + */ + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { } + + /** + * Called when a fingerprint is recognized. + * @param result An object containing authentication-related data + */ + public void onAuthenticationSucceeded(AuthenticationResult result) { } + + /** + * Called when a fingerprint is valid but not recognized. + */ + public void onAuthenticationFailed() { } + } + + private interface FingerprintManagerCompatImpl { + boolean hasEnrolledFingerprints(Context context); + boolean isHardwareDetected(Context context); + void authenticate(Context context, CryptoObject crypto, int flags, + CancellationSignal cancel, AuthenticationCallback callback, Handler handler); + } + + private static class LegacyFingerprintManagerCompatImpl + implements FingerprintManagerCompatImpl { + + public LegacyFingerprintManagerCompatImpl() { + } + + @Override + public boolean hasEnrolledFingerprints(Context context) { + return false; + } + + @Override + public boolean isHardwareDetected(Context context) { + return false; + } + + @Override + public void authenticate(Context context, CryptoObject crypto, int flags, + CancellationSignal cancel, AuthenticationCallback callback, Handler handler) { + // TODO: Figure out behavior when there is no fingerprint hardware available + } + } + + private static class Api23FingerprintManagerCompatImpl implements FingerprintManagerCompatImpl { + + public Api23FingerprintManagerCompatImpl() { + } + + @Override + public boolean hasEnrolledFingerprints(Context context) { + return FingerprintManagerCompatApi23.hasEnrolledFingerprints(context); + } + + @Override + public boolean isHardwareDetected(Context context) { + return FingerprintManagerCompatApi23.isHardwareDetected(context); + } + + @Override + public void authenticate(Context context, CryptoObject crypto, int flags, + CancellationSignal cancel, AuthenticationCallback callback, Handler handler) { + FingerprintManagerCompatApi23.authenticate(context, wrapCryptoObject(crypto), flags, + cancel != null ? cancel.getCancellationSignalObject() : null, + wrapCallback(callback), handler); + } + + private static FingerprintManagerCompatApi23.CryptoObject wrapCryptoObject( + CryptoObject cryptoObject) { + if (cryptoObject == null) { + return null; + } else if (cryptoObject.getCipher() != null) { + return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getCipher()); + } else if (cryptoObject.getSignature() != null) { + return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getSignature()); + } else if (cryptoObject.getMac() != null) { + return new FingerprintManagerCompatApi23.CryptoObject(cryptoObject.getMac()); + } else { + return null; + } + } + + static CryptoObject unwrapCryptoObject( + FingerprintManagerCompatApi23.CryptoObject cryptoObject) { + if (cryptoObject == null) { + return null; + } else if (cryptoObject.getCipher() != null) { + return new CryptoObject(cryptoObject.getCipher()); + } else if (cryptoObject.getSignature() != null) { + return new CryptoObject(cryptoObject.getSignature()); + } else if (cryptoObject.getMac() != null) { + return new CryptoObject(cryptoObject.getMac()); + } else { + return null; + } + } + + private static FingerprintManagerCompatApi23.AuthenticationCallback wrapCallback( + final AuthenticationCallback callback) { + return new FingerprintManagerCompatApi23.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + callback.onAuthenticationError(errMsgId, errString); + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + callback.onAuthenticationHelp(helpMsgId, helpString); + } + + @Override + public void onAuthenticationSucceeded( + FingerprintManagerCompatApi23.AuthenticationResultInternal result) { + callback.onAuthenticationSucceeded(new AuthenticationResult( + unwrapCryptoObject(result.getCryptoObject()))); + } + + @Override + public void onAuthenticationFailed() { + callback.onAuthenticationFailed(); + } + }; + } + } +} + diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompatApi23.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompatApi23.java new file mode 100644 index 00000000000..2107c755bb5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/fingerprint/FingerprintManagerCompatApi23.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package org.telegram.messenger.support.fingerprint; + +import android.annotation.TargetApi; +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; +import android.os.Handler; + +import org.telegram.messenger.FileLog; + +import java.security.Signature; + +import javax.crypto.Cipher; +import javax.crypto.Mac; + +@TargetApi(23) +public final class FingerprintManagerCompatApi23 { + + private static FingerprintManager getFingerprintManager(Context ctx) { + return ctx.getSystemService(FingerprintManager.class); + } + + public static boolean hasEnrolledFingerprints(Context context) { + try { + return getFingerprintManager(context).hasEnrolledFingerprints(); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + + public static boolean isHardwareDetected(Context context) { + try { + return getFingerprintManager(context).isHardwareDetected(); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + + public static void authenticate(Context context, CryptoObject crypto, int flags, Object cancel, + AuthenticationCallback callback, Handler handler) { + try { + getFingerprintManager(context).authenticate(wrapCryptoObject(crypto), + (android.os.CancellationSignal) cancel, flags, + wrapCallback(callback), handler); + } catch (Exception e) { + FileLog.e(e); + } + } + + private static FingerprintManager.CryptoObject wrapCryptoObject(CryptoObject cryptoObject) { + if (cryptoObject == null) { + return null; + } else if (cryptoObject.getCipher() != null) { + return new FingerprintManager.CryptoObject(cryptoObject.getCipher()); + } else if (cryptoObject.getSignature() != null) { + return new FingerprintManager.CryptoObject(cryptoObject.getSignature()); + } else if (cryptoObject.getMac() != null) { + return new FingerprintManager.CryptoObject(cryptoObject.getMac()); + } else { + return null; + } + } + + private static CryptoObject unwrapCryptoObject(FingerprintManager.CryptoObject cryptoObject) { + if (cryptoObject == null) { + return null; + } else if (cryptoObject.getCipher() != null) { + return new CryptoObject(cryptoObject.getCipher()); + } else if (cryptoObject.getSignature() != null) { + return new CryptoObject(cryptoObject.getSignature()); + } else if (cryptoObject.getMac() != null) { + return new CryptoObject(cryptoObject.getMac()); + } else { + return null; + } + } + + private static FingerprintManager.AuthenticationCallback wrapCallback( + final AuthenticationCallback callback) { + return new FingerprintManager.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errMsgId, CharSequence errString) { + callback.onAuthenticationError(errMsgId, errString); + } + + @Override + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + callback.onAuthenticationHelp(helpMsgId, helpString); + } + + @Override + public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { + callback.onAuthenticationSucceeded(new AuthenticationResultInternal( + unwrapCryptoObject(result.getCryptoObject()))); + } + + @Override + public void onAuthenticationFailed() { + callback.onAuthenticationFailed(); + } + }; + } + + public static class CryptoObject { + + private final Signature mSignature; + private final Cipher mCipher; + private final Mac mMac; + + public CryptoObject(Signature signature) { + mSignature = signature; + mCipher = null; + mMac = null; + } + + public CryptoObject(Cipher cipher) { + mCipher = cipher; + mSignature = null; + mMac = null; + } + + public CryptoObject(Mac mac) { + mMac = mac; + mCipher = null; + mSignature = null; + } + + public Signature getSignature() { + return mSignature; + } + + public Cipher getCipher() { + return mCipher; + } + + public Mac getMac() { + return mMac; + } + } + + public static final class AuthenticationResultInternal { + private CryptoObject mCryptoObject; + + public AuthenticationResultInternal(CryptoObject crypto) { + mCryptoObject = crypto; + } + + public CryptoObject getCryptoObject() { + return mCryptoObject; + } + } + + public static abstract class AuthenticationCallback { + + public void onAuthenticationError(int errMsgId, CharSequence errString) { + } + + public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { + } + + public void onAuthenticationSucceeded(AuthenticationResultInternal result) { + } + + public void onAuthenticationFailed() { + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java index 70e53dd4a96..80af0d8c977 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/AsyncListUtil.java @@ -39,14 +39,14 @@ * Note that this class uses a single thread to load the data, so it suitable to load data from * secondary storage such as disk, but not from network. *

        - * This class is designed to work with {@link android.support.v7.widget.RecyclerView}, but it does + * This class is designed to work with {@link org.telegram.messenger.support.widget.RecyclerView}, but it does * not depend on it and can be used with other list views. * */ public class AsyncListUtil { - private static final String TAG = "AsyncListUtil"; + static final String TAG = "AsyncListUtil"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; final Class mTClass; final int mTileSize; @@ -62,17 +62,17 @@ public class AsyncListUtil { final int[] mPrevRange = new int[2]; final int[] mTmpRangeExtended = new int[2]; - private boolean mAllowScrollHints; + boolean mAllowScrollHints; private int mScrollHint = ViewCallback.HINT_SCROLL_NONE; - private int mItemCount = 0; + int mItemCount = 0; int mDisplayedGeneration = 0; int mRequestedGeneration = mDisplayedGeneration; - final private SparseIntArray mMissingPositions = new SparseIntArray(); + final SparseIntArray mMissingPositions = new SparseIntArray(); - private void log(String s, Object... args) { + void log(String s, Object... args) { Log.d(TAG, "[MAIN] " + String.format(s, args)); } @@ -110,7 +110,7 @@ private boolean isRefreshPending() { *

        * Identifies the data items that have not been loaded yet and initiates loading them in the * background. Should be called from the view's scroll listener (such as - * {@link android.support.v7.widget.RecyclerView.OnScrollListener#onScrolled}). + * {@link org.telegram.messenger.support.widget.RecyclerView.OnScrollListener#onScrolled}). */ public void onRangeChanged() { if (isRefreshPending()) { @@ -171,7 +171,7 @@ public int getItemCount() { return mItemCount; } - private void updateRange() { + void updateRange() { mViewCallback.getItemRangeInto(mTmpRange); if (mTmpRange[0] > mTmpRange[1] || mTmpRange[0] < 0) { return; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/BatchingListUpdateCallback.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/BatchingListUpdateCallback.java new file mode 100644 index 00000000000..5025cb841a8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/BatchingListUpdateCallback.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.support.util; + +/** + * Wraps a {@link ListUpdateCallback} callback and batches operations that can be merged. + *

        + * For instance, when 2 add operations comes that adds 2 consecutive elements, + * BatchingListUpdateCallback merges them and calls the wrapped callback only once. + *

        + * This is a general purpose class and is also used by + * {@link android.support.v7.util.DiffUtil.DiffResult DiffResult} and + * {@link SortedList} to minimize the number of updates that are dispatched. + *

        + * If you use this class to batch updates, you must call {@link #dispatchLastEvent()} when the + * stream of update events drain. + */ +public class BatchingListUpdateCallback implements ListUpdateCallback { + private static final int TYPE_NONE = 0; + private static final int TYPE_ADD = 1; + private static final int TYPE_REMOVE = 2; + private static final int TYPE_CHANGE = 3; + + final ListUpdateCallback mWrapped; + + int mLastEventType = TYPE_NONE; + int mLastEventPosition = -1; + int mLastEventCount = -1; + Object mLastEventPayload = null; + + public BatchingListUpdateCallback(ListUpdateCallback callback) { + mWrapped = callback; + } + + /** + * BatchingListUpdateCallback holds onto the last event to see if it can be merged with the + * next one. When stream of events finish, you should call this method to dispatch the last + * event. + */ + public void dispatchLastEvent() { + if (mLastEventType == TYPE_NONE) { + return; + } + switch (mLastEventType) { + case TYPE_ADD: + mWrapped.onInserted(mLastEventPosition, mLastEventCount); + break; + case TYPE_REMOVE: + mWrapped.onRemoved(mLastEventPosition, mLastEventCount); + break; + case TYPE_CHANGE: + mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload); + break; + } + mLastEventPayload = null; + mLastEventType = TYPE_NONE; + } + + @Override + public void onInserted(int position, int count) { + if (mLastEventType == TYPE_ADD && position >= mLastEventPosition + && position <= mLastEventPosition + mLastEventCount) { + mLastEventCount += count; + mLastEventPosition = Math.min(position, mLastEventPosition); + return; + } + dispatchLastEvent(); + mLastEventPosition = position; + mLastEventCount = count; + mLastEventType = TYPE_ADD; + } + + @Override + public void onRemoved(int position, int count) { + if (mLastEventType == TYPE_REMOVE && mLastEventPosition >= position && + mLastEventPosition <= position + count) { + mLastEventCount += count; + mLastEventPosition = position; + return; + } + dispatchLastEvent(); + mLastEventPosition = position; + mLastEventCount = count; + mLastEventType = TYPE_REMOVE; + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + dispatchLastEvent(); // moves are not merged + mWrapped.onMoved(fromPosition, toPosition); + } + + @Override + public void onChanged(int position, int count, Object payload) { + if (mLastEventType == TYPE_CHANGE && + !(position > mLastEventPosition + mLastEventCount + || position + count < mLastEventPosition || mLastEventPayload != payload)) { + // take potential overlap into account + int previousEnd = mLastEventPosition + mLastEventCount; + mLastEventPosition = Math.min(position, mLastEventPosition); + mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition; + return; + } + dispatchLastEvent(); + mLastEventPosition = position; + mLastEventCount = count; + mLastEventPayload = payload; + mLastEventType = TYPE_CHANGE; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/DiffUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/DiffUtil.java new file mode 100644 index 00000000000..cf8a35984f9 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/DiffUtil.java @@ -0,0 +1,856 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.support.util; + +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import org.telegram.messenger.support.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * DiffUtil is a utility class that can calculate the difference between two lists and output a + * list of update operations that converts the first list into the second one. + *

        + * It can be used to calculate updates for a RecyclerView Adapter. + *

        + * DiffUtil uses Eugene W. Myers's difference algorithm to calculate the minimal number of updates + * to convert one list into another. Myers's algorithm does not handle items that are moved so + * DiffUtil runs a second pass on the result to detect items that were moved. + *

        + * If the lists are large, this operation may take significant time so you are advised to run this + * on a background thread, get the {@link DiffResult} then apply it on the RecyclerView on the main + * thread. + *

        + * This algorithm is optimized for space and uses O(N) space to find the minimal + * number of addition and removal operations between the two lists. It has O(N + D^2) expected time + * performance where D is the length of the edit script. + *

        + * If move detection is enabled, it takes an additional O(N^2) time where N is the total number of + * added and removed items. If your lists are already sorted by the same constraint (e.g. a created + * timestamp for a list of posts), you can disable move detection to improve performance. + *

        + * The actual runtime of the algorithm significantly depends on the number of changes in the list + * and the cost of your comparison methods. Below are some average run times for reference: + * (The test list is composed of random UUID Strings and the tests are run on Nexus 5X with M) + *

          + *
        • 100 items and 10 modifications: avg: 0.39 ms, median: 0.35 ms + *
        • 100 items and 100 modifications: 3.82 ms, median: 3.75 ms + *
        • 100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms + *
        • 1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms + *
        • 1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms + *
        • 1000 items and 200 modifications: 27.07 ms, median: 26.92 ms + *
        • 1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms + *
        + *

        + * Due to implementation constraints, the max size of the list can be 2^26. + */ +public class DiffUtil { + + private DiffUtil() { + // utility class, no instance. + } + + private static final Comparator SNAKE_COMPARATOR = new Comparator() { + @Override + public int compare(Snake o1, Snake o2) { + int cmpX = o1.x - o2.x; + return cmpX == 0 ? o1.y - o2.y : cmpX; + } + }; + + // Myers' algorithm uses two lists as axis labels. In DiffUtil's implementation, `x` axis is + // used for old list and `y` axis is used for new list. + + /** + * Calculates the list of update operations that can covert one list into the other one. + * + * @param cb The callback that acts as a gateway to the backing list data + * + * @return A DiffResult that contains the information about the edit sequence to convert the + * old list into the new list. + */ + public static DiffResult calculateDiff(Callback cb) { + return calculateDiff(cb, true); + } + + /** + * Calculates the list of update operations that can covert one list into the other one. + *

        + * If your old and new lists are sorted by the same constraint and items never move (swap + * positions), you can disable move detection which takes O(N^2) time where + * N is the number of added, moved, removed items. + * + * @param cb The callback that acts as a gateway to the backing list data + * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise. + * + * @return A DiffResult that contains the information about the edit sequence to convert the + * old list into the new list. + */ + public static DiffResult calculateDiff(Callback cb, boolean detectMoves) { + final int oldSize = cb.getOldListSize(); + final int newSize = cb.getNewListSize(); + + final List snakes = new ArrayList<>(); + + // instead of a recursive implementation, we keep our own stack to avoid potential stack + // overflow exceptions + final List stack = new ArrayList<>(); + + stack.add(new Range(0, oldSize, 0, newSize)); + + final int max = oldSize + newSize + Math.abs(oldSize - newSize); + // allocate forward and backward k-lines. K lines are diagonal lines in the matrix. (see the + // paper for details) + // These arrays lines keep the max reachable position for each k-line. + final int[] forward = new int[max * 2]; + final int[] backward = new int[max * 2]; + + // We pool the ranges to avoid allocations for each recursive call. + final List rangePool = new ArrayList<>(); + while (!stack.isEmpty()) { + final Range range = stack.remove(stack.size() - 1); + final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd, + range.newListStart, range.newListEnd, forward, backward, max); + if (snake != null) { + if (snake.size > 0) { + snakes.add(snake); + } + // offset the snake to convert its coordinates from the Range's area to global + snake.x += range.oldListStart; + snake.y += range.newListStart; + + // add new ranges for left and right + final Range left = rangePool.isEmpty() ? new Range() : rangePool.remove( + rangePool.size() - 1); + left.oldListStart = range.oldListStart; + left.newListStart = range.newListStart; + if (snake.reverse) { + left.oldListEnd = snake.x; + left.newListEnd = snake.y; + } else { + if (snake.removal) { + left.oldListEnd = snake.x - 1; + left.newListEnd = snake.y; + } else { + left.oldListEnd = snake.x; + left.newListEnd = snake.y - 1; + } + } + stack.add(left); + + // re-use range for right + //noinspection UnnecessaryLocalVariable + final Range right = range; + if (snake.reverse) { + if (snake.removal) { + right.oldListStart = snake.x + snake.size + 1; + right.newListStart = snake.y + snake.size; + } else { + right.oldListStart = snake.x + snake.size; + right.newListStart = snake.y + snake.size + 1; + } + } else { + right.oldListStart = snake.x + snake.size; + right.newListStart = snake.y + snake.size; + } + stack.add(right); + } else { + rangePool.add(range); + } + + } + // sort snakes + Collections.sort(snakes, SNAKE_COMPARATOR); + + return new DiffResult(cb, snakes, forward, backward, detectMoves); + + } + + private static Snake diffPartial(Callback cb, int startOld, int endOld, + int startNew, int endNew, int[] forward, int[] backward, int kOffset) { + final int oldSize = endOld - startOld; + final int newSize = endNew - startNew; + + if (endOld - startOld < 1 || endNew - startNew < 1) { + return null; + } + + final int delta = oldSize - newSize; + final int dLimit = (oldSize + newSize + 1) / 2; + Arrays.fill(forward, kOffset - dLimit - 1, kOffset + dLimit + 1, 0); + Arrays.fill(backward, kOffset - dLimit - 1 + delta, kOffset + dLimit + 1 + delta, oldSize); + final boolean checkInFwd = delta % 2 != 0; + for (int d = 0; d <= dLimit; d++) { + for (int k = -d; k <= d; k += 2) { + // find forward path + // we can reach k from k - 1 or k + 1. Check which one is further in the graph + int x; + final boolean removal; + if (k == -d || k != d && forward[kOffset + k - 1] < forward[kOffset + k + 1]) { + x = forward[kOffset + k + 1]; + removal = false; + } else { + x = forward[kOffset + k - 1] + 1; + removal = true; + } + // set y based on x + int y = x - k; + // move diagonal as long as items match + while (x < oldSize && y < newSize + && cb.areItemsTheSame(startOld + x, startNew + y)) { + x++; + y++; + } + forward[kOffset + k] = x; + if (checkInFwd && k >= delta - d + 1 && k <= delta + d - 1) { + if (forward[kOffset + k] >= backward[kOffset + k]) { + Snake outSnake = new Snake(); + outSnake.x = backward[kOffset + k]; + outSnake.y = outSnake.x - k; + outSnake.size = forward[kOffset + k] - backward[kOffset + k]; + outSnake.removal = removal; + outSnake.reverse = false; + return outSnake; + } + } + } + for (int k = -d; k <= d; k += 2) { + // find reverse path at k + delta, in reverse + final int backwardK = k + delta; + int x; + final boolean removal; + if (backwardK == d + delta || backwardK != -d + delta + && backward[kOffset + backwardK - 1] < backward[kOffset + backwardK + 1]) { + x = backward[kOffset + backwardK - 1]; + removal = false; + } else { + x = backward[kOffset + backwardK + 1] - 1; + removal = true; + } + + // set y based on x + int y = x - backwardK; + // move diagonal as long as items match + while (x > 0 && y > 0 + && cb.areItemsTheSame(startOld + x - 1, startNew + y - 1)) { + x--; + y--; + } + backward[kOffset + backwardK] = x; + if (!checkInFwd && k + delta >= -d && k + delta <= d) { + if (forward[kOffset + backwardK] >= backward[kOffset + backwardK]) { + Snake outSnake = new Snake(); + outSnake.x = backward[kOffset + backwardK]; + outSnake.y = outSnake.x - backwardK; + outSnake.size = + forward[kOffset + backwardK] - backward[kOffset + backwardK]; + outSnake.removal = removal; + outSnake.reverse = true; + return outSnake; + } + } + } + } + throw new IllegalStateException("DiffUtil hit an unexpected case while trying to calculate" + + " the optimal path. Please make sure your data is not changing during the" + + " diff calculation."); + } + + /** + * A Callback class used by DiffUtil while calculating the diff between two lists. + */ + public abstract static class Callback { + /** + * Returns the size of the old list. + * + * @return The size of the old list. + */ + public abstract int getOldListSize(); + + /** + * Returns the size of the new list. + * + * @return The size of the new list. + */ + public abstract int getNewListSize(); + + /** + * Called by the DiffUtil to decide whether two object represent the same Item. + *

        + * For example, if your items have unique ids, this method should check their id equality. + * + * @param oldItemPosition The position of the item in the old list + * @param newItemPosition The position of the item in the new list + * @return True if the two items represent the same object or false if they are different. + */ + public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); + + /** + * Called by the DiffUtil when it wants to check whether two items have the same data. + * DiffUtil uses this information to detect if the contents of an item has changed. + *

        + * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} + * so that you can change its behavior depending on your UI. + * For example, if you are using DiffUtil with a + * {@link org.telegram.messenger.support.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should + * return whether the items' visual representations are the same. + *

        + * This method is called only if {@link #areItemsTheSame(int, int)} returns + * {@code true} for these items. + * + * @param oldItemPosition The position of the item in the old list + * @param newItemPosition The position of the item in the new list which replaces the + * oldItem + * @return True if the contents of the items are the same or false if they are different. + */ + public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); + + /** + * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and + * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil + * calls this method to get a payload about the change. + *

        + * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the + * particular field that changed in the item and your + * {@link org.telegram.messenger.support.widget.RecyclerView.ItemAnimator ItemAnimator} can use that + * information to run the correct animation. + *

        + * Default implementation returns {@code null}. + * + * @param oldItemPosition The position of the item in the old list + * @param newItemPosition The position of the item in the new list + * + * @return A payload object that represents the change between the two items. + */ + @Nullable + public Object getChangePayload(int oldItemPosition, int newItemPosition) { + return null; + } + } + + /** + * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an + * add or remove operation. See the Myers' paper for details. + */ + static class Snake { + /** + * Position in the old list + */ + int x; + + /** + * Position in the new list + */ + int y; + + /** + * Number of matches. Might be 0. + */ + int size; + + /** + * If true, this is a removal from the original list followed by {@code size} matches. + * If false, this is an addition from the new list followed by {@code size} matches. + */ + boolean removal; + + /** + * If true, the addition or removal is at the end of the snake. + * If false, the addition or removal is at the beginning of the snake. + */ + boolean reverse; + } + + /** + * Represents a range in two lists that needs to be solved. + *

        + * This internal class is used when running Myers' algorithm without recursion. + */ + static class Range { + + int oldListStart, oldListEnd; + + int newListStart, newListEnd; + + public Range() { + } + + public Range(int oldListStart, int oldListEnd, int newListStart, int newListEnd) { + this.oldListStart = oldListStart; + this.oldListEnd = oldListEnd; + this.newListStart = newListStart; + this.newListEnd = newListEnd; + } + } + + /** + * This class holds the information about the result of a + * {@link DiffUtil#calculateDiff(Callback, boolean)} call. + *

        + * You can consume the updates in a DiffResult via + * {@link #dispatchUpdatesTo(ListUpdateCallback)} or directly stream the results into a + * {@link RecyclerView.Adapter} via {@link #dispatchUpdatesTo(RecyclerView.Adapter)}. + */ + public static class DiffResult { + /** + * While reading the flags below, keep in mind that when multiple items move in a list, + * Myers's may pick any of them as the anchor item and consider that one NOT_CHANGED while + * picking others as additions and removals. This is completely fine as we later detect + * all moves. + *

        + * Below, when an item is mentioned to stay in the same "location", it means we won't + * dispatch a move/add/remove for it, it DOES NOT mean the item is still in the same + * position. + */ + // item stayed the same. + private static final int FLAG_NOT_CHANGED = 1; + // item stayed in the same location but changed. + private static final int FLAG_CHANGED = FLAG_NOT_CHANGED << 1; + // Item has moved and also changed. + private static final int FLAG_MOVED_CHANGED = FLAG_CHANGED << 1; + // Item has moved but did not change. + private static final int FLAG_MOVED_NOT_CHANGED = FLAG_MOVED_CHANGED << 1; + // Ignore this update. + // If this is an addition from the new list, it means the item is actually removed from an + // earlier position and its move will be dispatched when we process the matching removal + // from the old list. + // If this is a removal from the old list, it means the item is actually added back to an + // earlier index in the new list and we'll dispatch its move when we are processing that + // addition. + private static final int FLAG_IGNORE = FLAG_MOVED_NOT_CHANGED << 1; + + // since we are re-using the int arrays that were created in the Myers' step, we mask + // change flags + private static final int FLAG_OFFSET = 5; + + private static final int FLAG_MASK = (1 << FLAG_OFFSET) - 1; + + // The Myers' snakes. At this point, we only care about their diagonal sections. + private final List mSnakes; + + // The list to keep oldItemStatuses. As we traverse old items, we assign flags to them + // which also includes whether they were a real removal or a move (and its new index). + private final int[] mOldItemStatuses; + // The list to keep newItemStatuses. As we traverse new items, we assign flags to them + // which also includes whether they were a real addition or a move(and its old index). + private final int[] mNewItemStatuses; + // The callback that was given to calcualte diff method. + private final Callback mCallback; + + private final int mOldListSize; + + private final int mNewListSize; + + private final boolean mDetectMoves; + + /** + * @param callback The callback that was used to calculate the diff + * @param snakes The list of Myers' snakes + * @param oldItemStatuses An int[] that can be re-purposed to keep metadata + * @param newItemStatuses An int[] that can be re-purposed to keep metadata + * @param detectMoves True if this DiffResult will try to detect moved items + */ + DiffResult(Callback callback, List snakes, int[] oldItemStatuses, + int[] newItemStatuses, boolean detectMoves) { + mSnakes = snakes; + mOldItemStatuses = oldItemStatuses; + mNewItemStatuses = newItemStatuses; + Arrays.fill(mOldItemStatuses, 0); + Arrays.fill(mNewItemStatuses, 0); + mCallback = callback; + mOldListSize = callback.getOldListSize(); + mNewListSize = callback.getNewListSize(); + mDetectMoves = detectMoves; + addRootSnake(); + findMatchingItems(); + } + + /** + * We always add a Snake to 0/0 so that we can run loops from end to beginning and be done + * when we run out of snakes. + */ + private void addRootSnake() { + Snake firstSnake = mSnakes.isEmpty() ? null : mSnakes.get(0); + if (firstSnake == null || firstSnake.x != 0 || firstSnake.y != 0) { + Snake root = new Snake(); + root.x = 0; + root.y = 0; + root.removal = false; + root.size = 0; + root.reverse = false; + mSnakes.add(0, root); + } + } + + /** + * This method traverses each addition / removal and tries to match it to a previous + * removal / addition. This is how we detect move operations. + *

        + * This class also flags whether an item has been changed or not. + *

        + * DiffUtil does this pre-processing so that if it is running on a big list, it can be moved + * to background thread where most of the expensive stuff will be calculated and kept in + * the statuses maps. DiffResult uses this pre-calculated information while dispatching + * the updates (which is probably being called on the main thread). + */ + private void findMatchingItems() { + int posOld = mOldListSize; + int posNew = mNewListSize; + // traverse the matrix from right bottom to 0,0. + for (int i = mSnakes.size() - 1; i >= 0; i--) { + final Snake snake = mSnakes.get(i); + final int endX = snake.x + snake.size; + final int endY = snake.y + snake.size; + if (mDetectMoves) { + while (posOld > endX) { + // this is a removal. Check remaining snakes to see if this was added before + findAddition(posOld, posNew, i); + posOld--; + } + while (posNew > endY) { + // this is an addition. Check remaining snakes to see if this was removed + // before + findRemoval(posOld, posNew, i); + posNew--; + } + } + for (int j = 0; j < snake.size; j++) { + // matching items. Check if it is changed or not + final int oldItemPos = snake.x + j; + final int newItemPos = snake.y + j; + final boolean theSame = mCallback + .areContentsTheSame(oldItemPos, newItemPos); + final int changeFlag = theSame ? FLAG_NOT_CHANGED : FLAG_CHANGED; + mOldItemStatuses[oldItemPos] = (newItemPos << FLAG_OFFSET) | changeFlag; + mNewItemStatuses[newItemPos] = (oldItemPos << FLAG_OFFSET) | changeFlag; + } + posOld = snake.x; + posNew = snake.y; + } + } + + private void findAddition(int x, int y, int snakeIndex) { + if (mOldItemStatuses[x - 1] != 0) { + return; // already set by a latter item + } + findMatchingItem(x, y, snakeIndex, false); + } + + private void findRemoval(int x, int y, int snakeIndex) { + if (mNewItemStatuses[y - 1] != 0) { + return; // already set by a latter item + } + findMatchingItem(x, y, snakeIndex, true); + } + + /** + * Finds a matching item that is before the given coordinates in the matrix + * (before : left and above). + * + * @param x The x position in the matrix (position in the old list) + * @param y The y position in the matrix (position in the new list) + * @param snakeIndex The current snake index + * @param removal True if we are looking for a removal, false otherwise + * + * @return True if such item is found. + */ + private boolean findMatchingItem(final int x, final int y, final int snakeIndex, + final boolean removal) { + final int myItemPos; + int curX; + int curY; + if (removal) { + myItemPos = y - 1; + curX = x; + curY = y - 1; + } else { + myItemPos = x - 1; + curX = x - 1; + curY = y; + } + for (int i = snakeIndex; i >= 0; i--) { + final Snake snake = mSnakes.get(i); + final int endX = snake.x + snake.size; + final int endY = snake.y + snake.size; + if (removal) { + // check removals for a match + for (int pos = curX - 1; pos >= endX; pos--) { + if (mCallback.areItemsTheSame(pos, myItemPos)) { + // found! + final boolean theSame = mCallback.areContentsTheSame(pos, myItemPos); + final int changeFlag = theSame ? FLAG_MOVED_NOT_CHANGED + : FLAG_MOVED_CHANGED; + mNewItemStatuses[myItemPos] = (pos << FLAG_OFFSET) | FLAG_IGNORE; + mOldItemStatuses[pos] = (myItemPos << FLAG_OFFSET) | changeFlag; + return true; + } + } + } else { + // check for additions for a match + for (int pos = curY - 1; pos >= endY; pos--) { + if (mCallback.areItemsTheSame(myItemPos, pos)) { + // found + final boolean theSame = mCallback.areContentsTheSame(myItemPos, pos); + final int changeFlag = theSame ? FLAG_MOVED_NOT_CHANGED + : FLAG_MOVED_CHANGED; + mOldItemStatuses[x - 1] = (pos << FLAG_OFFSET) | FLAG_IGNORE; + mNewItemStatuses[pos] = ((x - 1) << FLAG_OFFSET) | changeFlag; + return true; + } + } + } + curX = snake.x; + curY = snake.y; + } + return false; + } + + /** + * Dispatches the update events to the given adapter. + *

        + * For example, if you have an {@link org.telegram.messenger.support.widget.RecyclerView.Adapter Adapter} + * that is backed by a {@link List}, you can swap the list with the new one then call this + * method to dispatch all updates to the RecyclerView. + *

        +         *     List oldList = mAdapter.getData();
        +         *     DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldList, newList));
        +         *     mAdapter.setData(newList);
        +         *     result.dispatchUpdatesTo(mAdapter);
        +         * 
        + *

        + * Note that the RecyclerView requires you to dispatch adapter updates immediately when you + * change the data (you cannot defer {@code notify*} calls). The usage above adheres to this + * rule because updates are sent to the adapter right after the backing data is changed, + * before RecyclerView tries to read it. + *

        + * On the other hand, if you have another + * {@link org.telegram.messenger.support.widget.RecyclerView.AdapterDataObserver AdapterDataObserver} + * that tries to process events synchronously, this may confuse that observer because the + * list is instantly moved to its final state while the adapter updates are dispatched later + * on, one by one. If you have such an + * {@link org.telegram.messenger.support.widget.RecyclerView.AdapterDataObserver AdapterDataObserver}, + * you can use + * {@link #dispatchUpdatesTo(ListUpdateCallback)} to handle each modification + * manually. + * + * @param adapter A RecyclerView adapter which was displaying the old list and will start + * displaying the new list. + */ + public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) { + dispatchUpdatesTo(new ListUpdateCallback() { + @Override + public void onInserted(int position, int count) { + adapter.notifyItemRangeInserted(position, count); + } + + @Override + public void onRemoved(int position, int count) { + adapter.notifyItemRangeRemoved(position, count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + adapter.notifyItemMoved(fromPosition, toPosition); + } + + @Override + public void onChanged(int position, int count, Object payload) { + adapter.notifyItemRangeChanged(position, count, payload); + } + }); + } + + /** + * Dispatches update operations to the given Callback. + *

        + * These updates are atomic such that the first update call effects every update call that + * comes after it (the same as RecyclerView). + * + * @param updateCallback The callback to receive the update operations. + * @see #dispatchUpdatesTo(RecyclerView.Adapter) + */ + public void dispatchUpdatesTo(ListUpdateCallback updateCallback) { + final BatchingListUpdateCallback batchingCallback; + if (updateCallback instanceof BatchingListUpdateCallback) { + batchingCallback = (BatchingListUpdateCallback) updateCallback; + } else { + batchingCallback = new BatchingListUpdateCallback(updateCallback); + // replace updateCallback with a batching callback and override references to + // updateCallback so that we don't call it directly by mistake + //noinspection UnusedAssignment + updateCallback = batchingCallback; + } + // These are add/remove ops that are converted to moves. We track their positions until + // their respective update operations are processed. + final List postponedUpdates = new ArrayList<>(); + int posOld = mOldListSize; + int posNew = mNewListSize; + for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) { + final Snake snake = mSnakes.get(snakeIndex); + final int snakeSize = snake.size; + final int endX = snake.x + snakeSize; + final int endY = snake.y + snakeSize; + if (endX < posOld) { + dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX); + } + + if (endY < posNew) { + dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY, + endY); + } + for (int i = snakeSize - 1; i >= 0; i--) { + if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) { + batchingCallback.onChanged(snake.x + i, 1, + mCallback.getChangePayload(snake.x + i, snake.y + i)); + } + } + posOld = snake.x; + posNew = snake.y; + } + batchingCallback.dispatchLastEvent(); + } + + private static PostponedUpdate removePostponedUpdate(List updates, + int pos, boolean removal) { + for (int i = updates.size() - 1; i >= 0; i--) { + final PostponedUpdate update = updates.get(i); + if (update.posInOwnerList == pos && update.removal == removal) { + updates.remove(i); + for (int j = i; j < updates.size(); j++) { + // offset other ops since they swapped positions + updates.get(j).currentPos += removal ? 1 : -1; + } + return update; + } + } + return null; + } + + private void dispatchAdditions(List postponedUpdates, + ListUpdateCallback updateCallback, int start, int count, int globalIndex) { + if (!mDetectMoves) { + updateCallback.onInserted(start, count); + return; + } + for (int i = count - 1; i >= 0; i--) { + int status = mNewItemStatuses[globalIndex + i] & FLAG_MASK; + switch (status) { + case 0: // real addition + updateCallback.onInserted(start, 1); + for (PostponedUpdate update : postponedUpdates) { + update.currentPos += 1; + } + break; + case FLAG_MOVED_CHANGED: + case FLAG_MOVED_NOT_CHANGED: + final int pos = mNewItemStatuses[globalIndex + i] >> FLAG_OFFSET; + final PostponedUpdate update = removePostponedUpdate(postponedUpdates, pos, + true); + // the item was moved from that position + //noinspection ConstantConditions + updateCallback.onMoved(update.currentPos, start); + if (status == FLAG_MOVED_CHANGED) { + // also dispatch a change + updateCallback.onChanged(start, 1, + mCallback.getChangePayload(pos, globalIndex + i)); + } + break; + case FLAG_IGNORE: // ignoring this + postponedUpdates.add(new PostponedUpdate(globalIndex + i, start, false)); + break; + default: + throw new IllegalStateException( + "unknown flag for pos " + (globalIndex + i) + " " + Long + .toBinaryString(status)); + } + } + } + + private void dispatchRemovals(List postponedUpdates, + ListUpdateCallback updateCallback, int start, int count, int globalIndex) { + if (!mDetectMoves) { + updateCallback.onRemoved(start, count); + return; + } + for (int i = count - 1; i >= 0; i--) { + final int status = mOldItemStatuses[globalIndex + i] & FLAG_MASK; + switch (status) { + case 0: // real removal + updateCallback.onRemoved(start + i, 1); + for (PostponedUpdate update : postponedUpdates) { + update.currentPos -= 1; + } + break; + case FLAG_MOVED_CHANGED: + case FLAG_MOVED_NOT_CHANGED: + final int pos = mOldItemStatuses[globalIndex + i] >> FLAG_OFFSET; + final PostponedUpdate update = removePostponedUpdate(postponedUpdates, pos, + false); + // the item was moved to that position. we do -1 because this is a move not + // add and removing current item offsets the target move by 1 + //noinspection ConstantConditions + updateCallback.onMoved(start + i, update.currentPos - 1); + if (status == FLAG_MOVED_CHANGED) { + // also dispatch a change + updateCallback.onChanged(update.currentPos - 1, 1, + mCallback.getChangePayload(globalIndex + i, pos)); + } + break; + case FLAG_IGNORE: // ignoring this + postponedUpdates.add(new PostponedUpdate(globalIndex + i, start + i, true)); + break; + default: + throw new IllegalStateException( + "unknown flag for pos " + (globalIndex + i) + " " + Long + .toBinaryString(status)); + } + } + } + + @VisibleForTesting + List getSnakes() { + return mSnakes; + } + } + + /** + * Represents an update that we skipped because it was a move. + *

        + * When an update is skipped, it is tracked as other updates are dispatched until the matching + * add/remove operation is found at which point the tracked position is used to dispatch the + * update. + */ + private static class PostponedUpdate { + + int posInOwnerList; + + int currentPos; + + boolean removal; + + public PostponedUpdate(int posInOwnerList, int currentPos, boolean removal) { + this.posInOwnerList = posInOwnerList; + this.currentPos = currentPos; + this.removal = removal; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ListUpdateCallback.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ListUpdateCallback.java new file mode 100644 index 00000000000..40b18b62f76 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/ListUpdateCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.support.util; + +/** + * An interface that can receive Update operations that are applied to a list. + *

        + * This class can be used together with DiffUtil to detect changes between two lists. + */ +public interface ListUpdateCallback { + /** + * Called when {@code count} number of items are inserted at the given position. + * + * @param position The position of the new item. + * @param count The number of items that have been added. + */ + void onInserted(int position, int count); + + /** + * Called when {@code count} number of items are removed from the given position. + * + * @param position The position of the item which has been removed. + * @param count The number of items which have been removed. + */ + void onRemoved(int position, int count); + + /** + * Called when an item changes its position in the list. + * + * @param fromPosition The previous position of the item before the move. + * @param toPosition The new position of the item. + */ + void onMoved(int fromPosition, int toPosition); + + /** + * Called when {@code count} number of items are updated at the given position. + * + * @param position The position of the item which has been updated. + * @param count The number of items which has changed. + */ + void onChanged(int position, int count, Object payload); +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java index bf78e6aa91e..53ea83f0928 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/MessageThreadUtil.java @@ -26,14 +26,15 @@ class MessageThreadUtil implements ThreadUtil { + @Override public MainThreadCallback getMainThreadProxy(final MainThreadCallback callback) { return new MainThreadCallback() { - final private MessageQueue mQueue = new MessageQueue(); + final MessageQueue mQueue = new MessageQueue(); final private Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); - private static final int UPDATE_ITEM_COUNT = 1; - private static final int ADD_TILE = 2; - private static final int REMOVE_TILE = 3; + static final int UPDATE_ITEM_COUNT = 1; + static final int ADD_TILE = 2; + static final int REMOVE_TILE = 3; @Override public void updateItemCount(int generation, int itemCount) { @@ -81,16 +82,17 @@ public void run() { }; } + @Override public BackgroundCallback getBackgroundProxy(final BackgroundCallback callback) { return new BackgroundCallback() { - final private MessageQueue mQueue = new MessageQueue(); + final MessageQueue mQueue = new MessageQueue(); final private Executor mExecutor = ParallelExecutorCompat.getParallelExecutor(); AtomicBoolean mBackgroundRunning = new AtomicBoolean(false); - private static final int REFRESH = 1; - private static final int UPDATE_RANGE = 2; - private static final int LOAD_TILE = 3; - private static final int RECYCLE_TILE = 4; + static final int REFRESH = 1; + static final int UPDATE_RANGE = 2; + static final int LOAD_TILE = 3; + static final int RECYCLE_TILE = 4; @Override public void refresh(int generation) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java index 82c555d09f4..25dd73360aa 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/util/SortedList.java @@ -24,7 +24,7 @@ /** * A Sorted list implementation that can keep items in order and also notify for changes in the * list - * such that it can be bound to a {@link android.support.v7.widget.RecyclerView.Adapter + * such that it can be bound to a {@link org.telegram.messenger.support.widget.RecyclerView.Adapter * RecyclerView.Adapter}. *

        * It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses @@ -145,7 +145,7 @@ public int add(T item) { *

        * @param items Array of items to be added into the list. * @param mayModifyInput If true, SortedList is allowed to modify the input. - * @see {@link SortedList#addAll(T[] items)}. + * @see {@link SortedList#addAll(Object[] items)}. */ public void addAll(T[] items, boolean mayModifyInput) { throwIfMerging(); @@ -681,7 +681,7 @@ public void clear() { * SortedList calls the callback methods on this class to notify changes about the underlying * data. */ - public static abstract class Callback implements Comparator { + public static abstract class Callback implements Comparator, ListUpdateCallback { /** * Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and @@ -694,32 +694,9 @@ public static abstract class Callback implements Comparator { * first argument is less than, equal to, or greater than the * second. */ + @Override abstract public int compare(T2 o1, T2 o2); - /** - * Called by the SortedList when an item is inserted at the given position. - * - * @param position The position of the new item. - * @param count The number of items that have been added. - */ - abstract public void onInserted(int position, int count); - - /** - * Called by the SortedList when an item is removed from the given position. - * - * @param position The position of the item which has been removed. - * @param count The number of items which have been removed. - */ - abstract public void onRemoved(int position, int count); - - /** - * Called by the SortedList when an item changes its position in the list. - * - * @param fromPosition The previous position of the item before the move. - * @param toPosition The new position of the item. - */ - abstract public void onMoved(int fromPosition, int toPosition); - /** * Called by the SortedList when the item at the given position is updated. * @@ -728,6 +705,11 @@ public static abstract class Callback implements Comparator { */ abstract public void onChanged(int position, int count); + @Override + public void onChanged(int position, int count, Object payload) { + onChanged(position, count); + } + /** * Called by the SortedList when it wants to check whether two items have the same data * or not. SortedList uses this information to decide whether it should call @@ -737,7 +719,7 @@ public static abstract class Callback implements Comparator { * so * that you can change its behavior depending on your UI. *

        - * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter + * For example, if you are using SortedList with a {@link org.telegram.messenger.support.widget.RecyclerView.Adapter * RecyclerView.Adapter}, you should * return whether the items' visual representations are the same or not. * @@ -779,17 +761,8 @@ public static abstract class Callback implements Comparator { */ public static class BatchedCallback extends Callback { - private final Callback mWrappedCallback; - static final int TYPE_NONE = 0; - static final int TYPE_ADD = 1; - static final int TYPE_REMOVE = 2; - static final int TYPE_CHANGE = 3; - static final int TYPE_MOVE = 4; - - int mLastEventType = TYPE_NONE; - int mLastEventPosition = -1; - int mLastEventCount = -1; - + final Callback mWrappedCallback; + private final BatchingListUpdateCallback mBatchingListUpdateCallback; /** * Creates a new BatchedCallback that wraps the provided Callback. * @@ -799,6 +772,7 @@ public static class BatchedCallback extends Callback { */ public BatchedCallback(Callback wrappedCallback) { mWrappedCallback = wrappedCallback; + mBatchingListUpdateCallback = new BatchingListUpdateCallback(mWrappedCallback); } @Override @@ -808,51 +782,22 @@ public int compare(T2 o1, T2 o2) { @Override public void onInserted(int position, int count) { - if (mLastEventType == TYPE_ADD && position >= mLastEventPosition - && position <= mLastEventPosition + mLastEventCount) { - mLastEventCount += count; - mLastEventPosition = Math.min(position, mLastEventPosition); - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_ADD; + mBatchingListUpdateCallback.onInserted(position, count); } @Override public void onRemoved(int position, int count) { - if (mLastEventType == TYPE_REMOVE && mLastEventPosition == position) { - mLastEventCount += count; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_REMOVE; + mBatchingListUpdateCallback.onRemoved(position, count); } @Override public void onMoved(int fromPosition, int toPosition) { - dispatchLastEvent();//moves are not merged - mWrappedCallback.onMoved(fromPosition, toPosition); + mBatchingListUpdateCallback.onMoved(fromPosition, toPosition); } @Override public void onChanged(int position, int count) { - if (mLastEventType == TYPE_CHANGE && - !(position > mLastEventPosition + mLastEventCount - || position + count < mLastEventPosition)) { - // take potential overlap into account - int previousEnd = mLastEventPosition + mLastEventCount; - mLastEventPosition = Math.min(position, mLastEventPosition); - mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_CHANGE; + mBatchingListUpdateCallback.onChanged(position, count, null); } @Override @@ -865,27 +810,12 @@ public boolean areItemsTheSame(T2 item1, T2 item2) { return mWrappedCallback.areItemsTheSame(item1, item2); } - /** * This method dispatches any pending event notifications to the wrapped Callback. * You must always call this method after you are done with editing the SortedList. */ public void dispatchLastEvent() { - if (mLastEventType == TYPE_NONE) { - return; - } - switch (mLastEventType) { - case TYPE_ADD: - mWrappedCallback.onInserted(mLastEventPosition, mLastEventCount); - break; - case TYPE_REMOVE: - mWrappedCallback.onRemoved(mLastEventPosition, mLastEventCount); - break; - case TYPE_CHANGE: - mWrappedCallback.onChanged(mLastEventPosition, mLastEventCount); - break; - } - mLastEventType = TYPE_NONE; + mBatchingListUpdateCallback.dispatchLastEvent(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java index 1c40eafc701..b334f206e5a 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/AdapterHelper.java @@ -23,8 +23,6 @@ import java.util.Collections; import java.util.List; -import static org.telegram.messenger.support.widget.RecyclerView.*; - /** * Helper class that can enqueue and process adapter update operations. *

        @@ -138,7 +136,7 @@ private void applyRemove(UpdateOp op) { int type = -1; for (int position = op.positionStart; position < tmpEnd; position++) { boolean typeChanged = false; - ViewHolder vh = mCallback.findViewHolder(position); + RecyclerView.ViewHolder vh = mCallback.findViewHolder(position); if (vh != null || canFindInPreLayout(position)) { // If a ViewHolder exists or this is a newly added item, we can defer this update // to post layout stage. @@ -191,7 +189,7 @@ private void applyUpdate(UpdateOp op) { int tmpEnd = op.positionStart + op.itemCount; int type = -1; for (int position = op.positionStart; position < tmpEnd; position++) { - ViewHolder vh = mCallback.findViewHolder(position); + RecyclerView.ViewHolder vh = mCallback.findViewHolder(position); if (vh != null || canFindInPreLayout(position)) { // deferred if (type == POSITION_TYPE_INVISIBLE) { UpdateOp newOp = obtainUpdateOp(UpdateOp.UPDATE, tmpStart, tmpCount, @@ -502,6 +500,9 @@ int findPositionOffset(int position, int firstPostponedItem) { * @return True if updates should be processed. */ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { + if (itemCount < 1) { + return false; + } mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload)); mExistingUpdateTypes |= UpdateOp.UPDATE; return mPendingUpdates.size() == 1; @@ -511,6 +512,9 @@ boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) { * @return True if updates should be processed. */ boolean onItemRangeInserted(int positionStart, int itemCount) { + if (itemCount < 1) { + return false; + } mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null)); mExistingUpdateTypes |= UpdateOp.ADD; return mPendingUpdates.size() == 1; @@ -520,6 +524,9 @@ boolean onItemRangeInserted(int positionStart, int itemCount) { * @return True if updates should be processed. */ boolean onItemRangeRemoved(int positionStart, int itemCount) { + if (itemCount < 1) { + return false; + } mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null)); mExistingUpdateTypes |= UpdateOp.REMOVE; return mPendingUpdates.size() == 1; @@ -747,9 +754,9 @@ void recycleUpdateOpsAndClearList(List ops) { /** * Contract between AdapterHelper and RecyclerView. */ - static interface Callback { + interface Callback { - ViewHolder findViewHolder(int position); + RecyclerView.ViewHolder findViewHolder(int position); void offsetPositionsForRemovingInvisible(int positionStart, int itemCount); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java index 582b8f245ee..07bd4b18c18 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ChildHelper.java @@ -200,16 +200,16 @@ void removeAllViewsUnfiltered() { * This can be used to find a disappearing view by position. * * @param position The adapter position of the item. - * @param type View type, can be {@link RecyclerView#INVALID_TYPE}. - * @return A hidden view with a valid ViewHolder that matches the position and type. + * @return A hidden view with a valid ViewHolder that matches the position. */ - View findHiddenNonRemovedView(int position, int type) { + View findHiddenNonRemovedView(int position) { final int count = mHiddenViews.size(); for (int i = 0; i < count; i++) { final View view = mHiddenViews.get(i); RecyclerView.ViewHolder holder = mCallback.getChildViewHolder(view); - if (holder.getLayoutPosition() == position && !holder.isInvalid() && !holder.isRemoved() - && (type == RecyclerView.INVALID_TYPE || holder.getItemViewType() == type)) { + if (holder.getLayoutPosition() == position + && !holder.isInvalid() + && !holder.isRemoved()) { return view; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java index 546f93f0afb..9555ab17ec4 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DefaultItemAnimator.java @@ -41,20 +41,20 @@ public class DefaultItemAnimator extends SimpleItemAnimator { private ArrayList mPendingMoves = new ArrayList<>(); private ArrayList mPendingChanges = new ArrayList<>(); - private ArrayList> mAdditionsList = new ArrayList<>(); - private ArrayList> mMovesList = new ArrayList<>(); - private ArrayList> mChangesList = new ArrayList<>(); + ArrayList> mAdditionsList = new ArrayList<>(); + ArrayList> mMovesList = new ArrayList<>(); + ArrayList> mChangesList = new ArrayList<>(); - private ArrayList mAddAnimations = new ArrayList<>(); - private ArrayList mMoveAnimations = new ArrayList<>(); - private ArrayList mRemoveAnimations = new ArrayList<>(); - private ArrayList mChangeAnimations = new ArrayList<>(); + ArrayList mAddAnimations = new ArrayList<>(); + ArrayList mMoveAnimations = new ArrayList<>(); + ArrayList mRemoveAnimations = new ArrayList<>(); + ArrayList mChangeAnimations = new ArrayList<>(); private static class MoveInfo { public ViewHolder holder; public int fromX, fromY, toX, toY; - private MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { + MoveInfo(ViewHolder holder, int fromX, int fromY, int toX, int toY) { this.holder = holder; this.fromX = fromX; this.fromY = fromY; @@ -71,7 +71,7 @@ private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder) { this.newHolder = newHolder; } - private ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, + ChangeInfo(ViewHolder oldHolder, ViewHolder newHolder, int fromX, int fromY, int toX, int toY) { this(oldHolder, newHolder); this.fromX = fromX; @@ -162,6 +162,7 @@ public void run() { mAdditionsList.add(additions); mPendingAdditions.clear(); Runnable adder = new Runnable() { + @Override public void run() { for (ViewHolder holder : additions) { animateAddImpl(holder); @@ -220,7 +221,7 @@ public boolean animateAdd(final ViewHolder holder) { return true; } - private void animateAddImpl(final ViewHolder holder) { + void animateAddImpl(final ViewHolder holder) { final View view = holder.itemView; final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); mAddAnimations.add(holder); @@ -268,7 +269,7 @@ public boolean animateMove(final ViewHolder holder, int fromX, int fromY, return true; } - private void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { + void animateMoveImpl(final ViewHolder holder, int fromX, int fromY, int toX, int toY) { final View view = holder.itemView; final int deltaX = toX - fromX; final int deltaY = toY - fromY; @@ -336,7 +337,7 @@ public boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, return true; } - private void animateChangeImpl(final ChangeInfo changeInfo) { + void animateChangeImpl(final ChangeInfo changeInfo) { final ViewHolder holder = changeInfo.oldHolder; final View view = holder == null ? null : holder.itemView; final ViewHolder newHolder = changeInfo.newHolder; @@ -535,7 +536,7 @@ public boolean isRunning() { * pending/running, call {@link #dispatchAnimationsFinished()} to notify any * listeners. */ - private void dispatchFinishedWhenDone() { + void dispatchFinishedWhenDone() { if (!isRunning()) { dispatchAnimationsFinished(); } @@ -656,6 +657,9 @@ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder, } private static class VpaListenerAdapter implements ViewPropertyAnimatorListener { + VpaListenerAdapter() { + } + @Override public void onAnimationStart(View view) {} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DividerItemDecoration.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DividerItemDecoration.java new file mode 100644 index 00000000000..b406a259d93 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/DividerItemDecoration.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.telegram.messenger.support.widget; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.v4.view.ViewCompat; +import android.view.View; +import android.widget.LinearLayout; + +/** + * DividerItemDecoration is a {@link RecyclerView.ItemDecoration} that can be used as a divider + * between items of a {@link LinearLayoutManager}. It supports both {@link #HORIZONTAL} and + * {@link #VERTICAL} orientations. + * + *

        + *     mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
        + *             mLayoutManager.getOrientation());
        + *     recyclerView.addItemDecoration(mDividerItemDecoration);
        + * 
        + */ +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + public static final int VERTICAL = LinearLayout.VERTICAL; + + private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; + + private Drawable mDivider; + + /** + * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}. + */ + private int mOrientation; + + private final Rect mBounds = new Rect(); + + /** + * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a + * {@link LinearLayoutManager}. + * + * @param context Current context, it will be used to access resources. + * @param orientation Divider orientation. Should be {@link #HORIZONTAL} or {@link #VERTICAL}. + */ + public DividerItemDecoration(Context context, int orientation) { + final TypedArray a = context.obtainStyledAttributes(ATTRS); + mDivider = a.getDrawable(0); + a.recycle(); + setOrientation(orientation); + } + + /** + * Sets the orientation for this divider. This should be called if + * {@link RecyclerView.LayoutManager} changes orientation. + * + * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL} + */ + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL && orientation != VERTICAL) { + throw new IllegalArgumentException( + "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); + } + mOrientation = orientation; + } + + /** + * Sets the {@link Drawable} for this divider. + * + * @param drawable Drawable that should be used as a divider. + */ + public void setDrawable(@NonNull Drawable drawable) { + if (drawable == null) { + throw new IllegalArgumentException("Drawable cannot be null."); + } + mDivider = drawable; + } + + @Override + public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (parent.getLayoutManager() == null) { + return; + } + if (mOrientation == VERTICAL) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + @SuppressLint("NewApi") + private void drawVertical(Canvas canvas, RecyclerView parent) { + canvas.save(); + final int left; + final int right; + if (parent.getClipToPadding()) { + left = parent.getPaddingLeft(); + right = parent.getWidth() - parent.getPaddingRight(); + canvas.clipRect(left, parent.getPaddingTop(), right, + parent.getHeight() - parent.getPaddingBottom()); + } else { + left = 0; + right = parent.getWidth(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child)); + final int top = bottom - mDivider.getIntrinsicHeight(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(canvas); + } + canvas.restore(); + } + + @SuppressLint("NewApi") + private void drawHorizontal(Canvas canvas, RecyclerView parent) { + canvas.save(); + final int top; + final int bottom; + if (parent.getClipToPadding()) { + top = parent.getPaddingTop(); + bottom = parent.getHeight() - parent.getPaddingBottom(); + canvas.clipRect(parent.getPaddingLeft(), top, + parent.getWidth() - parent.getPaddingRight(), bottom); + } else { + top = 0; + bottom = parent.getHeight(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); + final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child)); + final int left = right - mDivider.getIntrinsicWidth(); + mDivider.setBounds(left, top, right, bottom); + mDivider.draw(canvas); + } + canvas.restore(); + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, + RecyclerView.State state) { + if (mOrientation == VERTICAL) { + outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); + } else { + outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GapWorker.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GapWorker.java new file mode 100644 index 00000000000..23e8d456548 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GapWorker.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.telegram.messenger.support.widget; + +import android.support.annotation.Nullable; +import android.support.v4.os.TraceCompat; +import android.view.View; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.concurrent.TimeUnit; + +final class GapWorker implements Runnable { + + static final ThreadLocal sGapWorker = new ThreadLocal<>(); + + ArrayList mRecyclerViews = new ArrayList<>(); + long mPostTimeNs; + long mFrameIntervalNs; + + static class Task { + public boolean immediate; + public int viewVelocity; + public int distanceToItem; + public RecyclerView view; + public int position; + + public void clear() { + immediate = false; + viewVelocity = 0; + distanceToItem = 0; + view = null; + position = 0; + } + } + + /** + * Temporary storage for prefetch Tasks that execute in {@link #prefetch(long)}. Task objects + * are pooled in the ArrayList, and never removed to avoid allocations, but always cleared + * in between calls. + */ + private ArrayList mTasks = new ArrayList<>(); + + /** + * Prefetch information associated with a specific RecyclerView. + */ + static class LayoutPrefetchRegistryImpl + implements RecyclerView.LayoutManager.LayoutPrefetchRegistry { + int mPrefetchDx; + int mPrefetchDy; + int[] mPrefetchArray; + + int mCount; + + void setPrefetchVector(int dx, int dy) { + mPrefetchDx = dx; + mPrefetchDy = dy; + } + + void collectPrefetchPositionsFromView(RecyclerView view, boolean nested) { + mCount = 0; + if (mPrefetchArray != null) { + Arrays.fill(mPrefetchArray, -1); + } + + final RecyclerView.LayoutManager layout = view.mLayout; + if (view.mAdapter != null + && layout != null + && layout.isItemPrefetchEnabled()) { + if (nested) { + // nested prefetch, only if no adapter updates pending. Note: we don't query + // view.hasPendingAdapterUpdates(), as first layout may not have occurred + if (!view.mAdapterHelper.hasPendingUpdates()) { + layout.collectInitialPrefetchPositions(view.mAdapter.getItemCount(), this); + } + } else { + // momentum based prefetch, only if we trust current child/adapter state + if (!view.hasPendingAdapterUpdates()) { + layout.collectAdjacentPrefetchPositions(mPrefetchDx, mPrefetchDy, + view.mState, this); + } + } + + if (mCount > layout.mPrefetchMaxCountObserved) { + layout.mPrefetchMaxCountObserved = mCount; + layout.mPrefetchMaxObservedInInitialPrefetch = nested; + view.mRecycler.updateViewCacheSize(); + } + } + } + + @Override + public void addPosition(int layoutPosition, int pixelDistance) { + if (pixelDistance < 0) { + throw new IllegalArgumentException("Pixel distance must be non-negative"); + } + + // allocate or expand array as needed, doubling when needed + final int storagePosition = mCount * 2; + if (mPrefetchArray == null) { + mPrefetchArray = new int[4]; + Arrays.fill(mPrefetchArray, -1); + } else if (storagePosition >= mPrefetchArray.length) { + final int[] oldArray = mPrefetchArray; + mPrefetchArray = new int[storagePosition * 2]; + System.arraycopy(oldArray, 0, mPrefetchArray, 0, oldArray.length); + } + + // add position + mPrefetchArray[storagePosition] = layoutPosition; + mPrefetchArray[storagePosition + 1] = pixelDistance; + + mCount++; + } + + boolean lastPrefetchIncludedPosition(int position) { + if (mPrefetchArray != null) { + final int count = mCount * 2; + for (int i = 0; i < count; i += 2) { + if (mPrefetchArray[i] == position) return true; + } + } + return false; + } + + /** + * Called when prefetch indices are no longer valid for cache prioritization. + */ + void clearPrefetchPositions() { + if (mPrefetchArray != null) { + Arrays.fill(mPrefetchArray, -1); + } + } + } + + public void add(RecyclerView recyclerView) { + if (RecyclerView.DEBUG && mRecyclerViews.contains(recyclerView)) { + throw new IllegalStateException("RecyclerView already present in worker list!"); + } + mRecyclerViews.add(recyclerView); + } + + public void remove(RecyclerView recyclerView) { + boolean removeSuccess = mRecyclerViews.remove(recyclerView); + if (RecyclerView.DEBUG && !removeSuccess) { + throw new IllegalStateException("RecyclerView removal failed!"); + } + } + + /** + * Schedule a prefetch immediately after the current traversal. + */ + void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) { + if (recyclerView.isAttachedToWindow()) { + if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) { + throw new IllegalStateException("attempting to post unregistered view!"); + } + if (mPostTimeNs == 0) { + mPostTimeNs = recyclerView.getNanoTime(); + recyclerView.post(this); + } + } + + recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy); + } + + static Comparator sTaskComparator = new Comparator() { + @Override + public int compare(Task lhs, Task rhs) { + // first, prioritize non-cleared tasks + if ((lhs.view == null) != (rhs.view == null)) { + return lhs.view == null ? 1 : -1; + } + + // then prioritize immediate + if (lhs.immediate != rhs.immediate) { + return lhs.immediate ? -1 : 1; + } + + // then prioritize _highest_ view velocity + int deltaViewVelocity = rhs.viewVelocity - lhs.viewVelocity; + if (deltaViewVelocity != 0) return deltaViewVelocity; + + // then prioritize _lowest_ distance to item + int deltaDistanceToItem = lhs.distanceToItem - rhs.distanceToItem; + if (deltaDistanceToItem != 0) return deltaDistanceToItem; + + return 0; + } + }; + + private void buildTaskList() { + // Update PrefetchRegistry in each view + final int viewCount = mRecyclerViews.size(); + int totalTaskCount = 0; + for (int i = 0; i < viewCount; i++) { + RecyclerView view = mRecyclerViews.get(i); + view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false); + totalTaskCount += view.mPrefetchRegistry.mCount; + } + + // Populate task list from prefetch data... + mTasks.ensureCapacity(totalTaskCount); + int totalTaskIndex = 0; + for (int i = 0; i < viewCount; i++) { + RecyclerView view = mRecyclerViews.get(i); + LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry; + final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx) + + Math.abs(prefetchRegistry.mPrefetchDy); + for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) { + final Task task; + if (totalTaskIndex >= mTasks.size()) { + task = new Task(); + mTasks.add(task); + } else { + task = mTasks.get(totalTaskIndex); + } + final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1]; + + task.immediate = distanceToItem <= viewVelocity; + task.viewVelocity = viewVelocity; + task.distanceToItem = distanceToItem; + task.view = view; + task.position = prefetchRegistry.mPrefetchArray[j]; + + totalTaskIndex++; + } + } + + // ... and priority sort + Collections.sort(mTasks, sTaskComparator); + } + + static boolean isPrefetchPositionAttached(RecyclerView view, int position) { + final int childCount = view.mChildHelper.getUnfilteredChildCount(); + for (int i = 0; i < childCount; i++) { + View attachedView = view.mChildHelper.getUnfilteredChildAt(i); + RecyclerView.ViewHolder holder = RecyclerView.getChildViewHolderInt(attachedView); + // Note: can use mPosition here because adapter doesn't have pending updates + if (holder.mPosition == position && !holder.isInvalid()) { + return true; + } + } + return false; + } + + private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view, + int position, long deadlineNs) { + if (isPrefetchPositionAttached(view, position)) { + // don't attempt to prefetch attached views + return null; + } + + RecyclerView.Recycler recycler = view.mRecycler; + RecyclerView.ViewHolder holder = recycler.tryGetViewHolderForPositionByDeadline( + position, false, deadlineNs); + + if (holder != null) { + if (holder.isBound()) { + // Only give the view a chance to go into the cache if binding succeeded + // Note that we must use public method, since item may need cleanup + recycler.recycleView(holder.itemView); + } else { + // Didn't bind, so we can't cache the view, but it will stay in the pool until + // next prefetch/traversal. If a View fails to bind, it means we didn't have + // enough time prior to the deadline (and won't for other instances of this + // type, during this GapWorker prefetch pass). + recycler.addViewHolderToRecycledViewPool(holder, false); + } + } + return holder; + } + + private void prefetchInnerRecyclerViewWithDeadline(@Nullable RecyclerView innerView, + long deadlineNs) { + if (innerView == null) { + return; + } + + if (innerView.mDataSetHasChangedAfterLayout + && innerView.mChildHelper.getUnfilteredChildCount() != 0) { + // RecyclerView has new data, but old attached views. Clear everything, so that + // we can prefetch without partially stale data. + innerView.removeAndRecycleViews(); + } + + // do nested prefetch! + final LayoutPrefetchRegistryImpl innerPrefetchRegistry = innerView.mPrefetchRegistry; + innerPrefetchRegistry.collectPrefetchPositionsFromView(innerView, true); + + if (innerPrefetchRegistry.mCount != 0) { + try { + TraceCompat.beginSection(RecyclerView.TRACE_NESTED_PREFETCH_TAG); + innerView.mState.prepareForNestedPrefetch(innerView.mAdapter); + for (int i = 0; i < innerPrefetchRegistry.mCount * 2; i += 2) { + // Note that we ignore immediate flag for inner items because + // we have lower confidence they're needed next frame. + final int innerPosition = innerPrefetchRegistry.mPrefetchArray[i]; + prefetchPositionWithDeadline(innerView, innerPosition, deadlineNs); + } + } finally { + TraceCompat.endSection(); + } + } + } + + private void flushTaskWithDeadline(Task task, long deadlineNs) { + long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs; + RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view, + task.position, taskDeadlineNs); + if (holder != null && holder.mNestedRecyclerView != null) { + prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs); + } + } + + private void flushTasksWithDeadline(long deadlineNs) { + for (int i = 0; i < mTasks.size(); i++) { + final Task task = mTasks.get(i); + if (task.view == null) { + break; // done with populated tasks + } + flushTaskWithDeadline(task, deadlineNs); + task.clear(); + } + } + + void prefetch(long deadlineNs) { + buildTaskList(); + flushTasksWithDeadline(deadlineNs); + } + + @Override + public void run() { + try { + TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG); + + if (mRecyclerViews.isEmpty()) { + // abort - no work to do + return; + } + + // Query last vsync so we can predict next one. Note that drawing time not yet + // valid in animation/input callbacks, so query it here to be safe. + long lastFrameVsyncNs = TimeUnit.MILLISECONDS.toNanos( + mRecyclerViews.get(0).getDrawingTime()); + if (lastFrameVsyncNs == 0) { + // abort - couldn't get last vsync for estimating next + return; + } + + // TODO: consider rebasing deadline if frame was already dropped due to long UI work. + // Next frame will still wait for VSYNC, so we can still use the gap if it exists. + long nextFrameNs = lastFrameVsyncNs + mFrameIntervalNs; + + prefetch(nextFrameNs); + + // TODO: consider rescheduling self, if there's more work to do + } finally { + mPostTimeNs = 0; + TraceCompat.endSection(); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java index 84c3cb49c73..415a5d80396 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/GridLayoutManager.java @@ -10,7 +10,7 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific languag`e governing permissions and + * See the License for the specific language governing permissions and * limitations under the License. */ package org.telegram.messenger.support.widget; @@ -59,20 +59,6 @@ public class GridLayoutManager extends LinearLayoutManager { final Rect mDecorInsets = new Rect(); - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". If spanCount is not specified in the XML, it defaults to a - * single column. - * - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount - */ - public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setSpanCount(properties.spanCount); - } - /** * Creates a vertical GridLayoutManager * @@ -172,9 +158,12 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State validateChildOrder(); } clearPreLayoutSpanMappingCache(); - if (!state.isPreLayout()) { - mPendingSpanCountChange = false; - } + } + + @Override + public void onLayoutCompleted(RecyclerView.State state) { + super.onLayoutCompleted(state); + mPendingSpanCountChange = false; } private void clearPreLayoutSpanMappingCache() { @@ -222,9 +211,9 @@ public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCo public RecyclerView.LayoutParams generateDefaultLayoutParams() { if (mOrientation == HORIZONTAL) { return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.FILL_PARENT); + ViewGroup.LayoutParams.MATCH_PARENT); } else { - return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } } @@ -336,6 +325,15 @@ static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalS return cachedBorders; } + int getSpaceForSpanRange(int startSpan, int spanSize) { + if (mOrientation == VERTICAL && isLayoutRTL()) { + return mCachedBorders[mSpanCount - startSpan] + - mCachedBorders[mSpanCount - startSpan - spanSize]; + } else { + return mCachedBorders[startSpan + spanSize] - mCachedBorders[startSpan]; + } + } + @Override void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) { @@ -407,6 +405,7 @@ View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state final int boundsStart = mOrientationHelper.getStartAfterPadding(); final int boundsEnd = mOrientationHelper.getEndAfterPadding(); final int diff = end > start ? 1 : -1; + for (int i = start; i != end; i += diff) { final View view = getChildAt(i); final int position = getPosition(view); @@ -491,6 +490,21 @@ private int getSpanSize(RecyclerView.Recycler recycler, RecyclerView.State state return mSpanSizeLookup.getSpanSize(adapterPosition); } + @Override + void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, + LayoutPrefetchRegistry layoutPrefetchRegistry) { + int remainingSpan = mSpanCount; + int count = 0; + while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) { + final int pos = layoutState.mCurrentPosition; + layoutPrefetchRegistry.addPosition(pos, layoutState.mScrollingOffset); + final int spanSize = mSpanSizeLookup.getSpanSize(pos); + remainingSpan -= spanSize; + layoutState.mCurrentPosition += layoutState.mItemDirection; + count++; + } + } + @Override void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { @@ -559,29 +573,14 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, addDisappearingView(view, 0); } } + calculateItemDecorationsForChild(view, mDecorInsets); - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - - mCachedBorders[lp.mSpanIndex], otherDirSpecMode, 0, - mOrientation == HORIZONTAL ? lp.height : lp.width, - false); - final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), - mOrientationHelper.getMode(), 0, - mOrientation == VERTICAL ? lp.height : lp.width, true); - // Unless the child has MATCH_PARENT, measure it from its specs before adding insets. - if (mOrientation == VERTICAL) { - @SuppressWarnings("deprecation") - final boolean applyInsets = lp.height == ViewGroup.LayoutParams.FILL_PARENT; - measureChildWithDecorationsAndMargin(view, spec, mainSpec, applyInsets, false); - } else { - //noinspection deprecation - final boolean applyInsets = lp.width == ViewGroup.LayoutParams.FILL_PARENT; - measureChildWithDecorationsAndMargin(view, mainSpec, spec, applyInsets, false); - } + measureChild(view, otherDirSpecMode, false); final int size = mOrientationHelper.getDecoratedMeasurement(view); if (size > maxSize) { maxSize = size; } + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) / lp.mSpanSize; if (otherSize > maxSizeInOther) { @@ -595,40 +594,40 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, maxSize = 0; for (int i = 0; i < count; i++) { View view = mSet[i]; - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - - mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0, - mOrientation == HORIZONTAL ? lp.height : lp.width, false); - final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), - mOrientationHelper.getMode(), 0, - mOrientation == VERTICAL ? lp.height : lp.width, true); - if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, mainSpec, false, true); - } else { - measureChildWithDecorationsAndMargin(view, mainSpec, spec, false, true); - } + measureChild(view, View.MeasureSpec.EXACTLY, true); final int size = mOrientationHelper.getDecoratedMeasurement(view); if (size > maxSize) { maxSize = size; } } } + // Views that did not measure the maxSize has to be re-measured // We will stop doing this once we introduce Gravity in the GLM layout params - final int maxMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize, - View.MeasureSpec.EXACTLY); for (int i = 0; i < count; i ++) { final View view = mSet[i]; if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) { final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] - - mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0, - mOrientation == HORIZONTAL ? lp.height : lp.width, false); + final Rect decorInsets = lp.mDecorInsets; + final int verticalInsets = decorInsets.top + decorInsets.bottom + + lp.topMargin + lp.bottomMargin; + final int horizontalInsets = decorInsets.left + decorInsets.right + + lp.leftMargin + lp.rightMargin; + final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize); + final int wSpec; + final int hSpec; if (mOrientation == VERTICAL) { - measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true, true); + wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, + horizontalInsets, lp.width, false); + hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets, + View.MeasureSpec.EXACTLY); } else { - measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true, true); + wSpec = View.MeasureSpec.makeMeasureSpec(maxSize - horizontalInsets, + View.MeasureSpec.EXACTLY); + hSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY, + verticalInsets, lp.height, false); } + measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true); } } @@ -657,7 +656,7 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutParams params = (LayoutParams) view.getLayoutParams(); if (mOrientation == VERTICAL) { if (isLayoutRTL()) { - right = getPaddingLeft() + mCachedBorders[params.mSpanIndex + params.mSpanSize]; + right = getPaddingLeft() + mCachedBorders[mSpanCount - params.mSpanIndex]; left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft() + mCachedBorders[params.mSpanIndex]; @@ -669,8 +668,7 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, } // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. - layoutDecorated(view, left + params.leftMargin, top + params.topMargin, - right - params.rightMargin, bottom - params.bottomMargin); + layoutDecoratedWithMargins(view, left, top, right, bottom); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" @@ -686,12 +684,45 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, Arrays.fill(mSet, null); } + /** + * Measures a child with currently known information. This is not necessarily the child's final + * measurement. (see fillChunk for details). + * + * @param view The child view to be measured + * @param otherDirParentSpecMode The RV measure spec that should be used in the secondary + * orientation + * @param alreadyMeasured True if we've already measured this view once + */ + private void measureChild(View view, int otherDirParentSpecMode, boolean alreadyMeasured) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final Rect decorInsets = lp.mDecorInsets; + final int verticalInsets = decorInsets.top + decorInsets.bottom + + lp.topMargin + lp.bottomMargin; + final int horizontalInsets = decorInsets.left + decorInsets.right + + lp.leftMargin + lp.rightMargin; + final int availableSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize); + final int wSpec; + final int hSpec; + if (mOrientation == VERTICAL) { + wSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, + horizontalInsets, lp.width, false); + hSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getHeightMode(), + verticalInsets, lp.height, true); + } else { + hSpec = getChildMeasureSpec(availableSpaceInOther, otherDirParentSpecMode, + verticalInsets, lp.height, false); + wSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(), getWidthMode(), + horizontalInsets, lp.width, true); + } + measureChildWithDecorationsAndMargin(view, wSpec, hSpec, alreadyMeasured); + } + /** * This is called after laying out a row (if vertical) or a column (if horizontal) when the * RecyclerView does not have exact measurement specs. *

        * Here we try to assign a best guess width or height and re-do the layout to update other - * views that wanted to FILL_PARENT in the non-scroll orientation. + * views that wanted to MATCH_PARENT in the non-scroll orientation. * * @param maxSizeInOther The maximum size per span ratio from the measurement of the children. * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below. @@ -703,17 +734,8 @@ private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) { } private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec, - boolean capBothSpecs, boolean alreadyMeasured) { - calculateItemDecorationsForChild(child, mDecorInsets); + boolean alreadyMeasured) { RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams(); - if (capBothSpecs || mOrientation == VERTICAL) { - widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mDecorInsets.left, - lp.rightMargin + mDecorInsets.right); - } - if (capBothSpecs || mOrientation == HORIZONTAL) { - heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top, - lp.bottomMargin + mDecorInsets.bottom); - } final boolean measure; if (alreadyMeasured) { measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp); @@ -723,24 +745,13 @@ private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int if (measure) { child.measure(widthSpec, heightSpec); } - - } - - private int updateSpecWithExtra(int spec, int startInset, int endInset) { - if (startInset == 0 && endInset == 0) { - return spec; - } - final int mode = View.MeasureSpec.getMode(spec); - if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) { - return View.MeasureSpec.makeMeasureSpec( - Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode); - } - return spec; } private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count, int consumedSpanCount, boolean layingOutInPrimaryDirection) { - int span, spanDiff, start, end, diff; + // spans are always assigned from 0 to N no matter if it is RTL or not. + // RTL is used only when positioning the view. + int span, start, end, diff; // make sure we traverse from min position to max position if (layingOutInPrimaryDirection) { start = 0; @@ -751,23 +762,13 @@ private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State stat end = -1; diff = -1; } - if (mOrientation == VERTICAL && isLayoutRTL()) { // start from last span - span = mSpanCount - 1; - spanDiff = -1; - } else { - span = 0; - spanDiff = 1; - } + span = 0; for (int i = start; i != end; i += diff) { View view = mSet[i]; LayoutParams params = (LayoutParams) view.getLayoutParams(); params.mSpanSize = getSpanSize(recycler, state, getPosition(view)); - if (spanDiff == -1 && params.mSpanSize > 1) { - params.mSpanIndex = span - (params.mSpanSize - 1); - } else { - params.mSpanIndex = span; - } - span += spanDiff * params.mSpanSize; + params.mSpanIndex = span; + span += params.mSpanSize; } } @@ -801,6 +802,7 @@ public void setSpanCount(int spanCount) { } mSpanCount = spanCount; mSpanSizeLookup.invalidateSpanIndexCache(); + requestLayout(); } /** @@ -1079,9 +1081,9 @@ public static class LayoutParams extends RecyclerView.LayoutParams { */ public static final int INVALID_SPAN_ID = -1; - private int mSpanIndex = INVALID_SPAN_ID; + int mSpanIndex = INVALID_SPAN_ID; - private int mSpanSize = 0; + int mSpanSize = 0; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); @@ -1107,10 +1109,11 @@ public LayoutParams(RecyclerView.LayoutParams source) { * Returns the current span index of this View. If the View is not laid out yet, the return * value is undefined. *

        - * Note that span index may change by whether the RecyclerView is RTL or not. For - * example, if the number of spans is 3 and layout is RTL, the rightmost item will have - * span index of 2. If the layout changes back to LTR, span index for this view will be 0. - * If the item was occupying 2 spans, span indices would be 1 and 0 respectively. + * Starting with RecyclerView 24.2.0, span indices are always indexed from position 0 + * even if the layout is RTL. In a vertical GridLayoutManager, leftmost span is span + * 0 if the layout is LTR and rightmost span is span 0 if the layout is + * RTL. Prior to 24.2.0, it was the opposite which was conflicting with + * {@link SpanSizeLookup#getSpanIndex(int, int)}. *

        * If the View occupies multiple spans, span with the minimum index is returned. * diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java index 9ee6745ead0..e697b8b3de2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LayoutState.java @@ -10,7 +10,7 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific languag`e governing permissions and + * See the License for the specific language governing permissions and * limitations under the License. */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java index b1211068986..3f98bdc34b7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearLayoutManager.java @@ -10,18 +10,20 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific languag`e governing permissions and + * See the License for the specific language governing permissions and * limitations under the License. */ package org.telegram.messenger.support.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static org.telegram.messenger.support.widget.RecyclerView.NO_POSITION; import android.content.Context; import android.graphics.PointF; import android.os.Parcel; import android.os.Parcelable; +import android.support.annotation.RestrictTo; import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityRecordCompat; @@ -36,15 +38,15 @@ import java.util.List; /** - * A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides + * A {@link org.telegram.messenger.support.widget.RecyclerView.LayoutManager} implementation which provides * similar functionality to {@link android.widget.ListView}. */ public class LinearLayoutManager extends RecyclerView.LayoutManager implements - ItemTouchHelper.ViewDropHandler { + ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider { private static final String TAG = "LinearLayoutManager"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; @@ -136,6 +138,16 @@ public class LinearLayoutManager extends RecyclerView.LayoutManager implements * */ final AnchorInfo mAnchorInfo = new AnchorInfo(); + /** + * Stashed to avoid allocation, currently only used in #fill() + */ + private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult(); + + /** + * Number of items to prefetch when first coming on screen with new data. + */ + private int mInitialItemPrefetchCount = 2; + /** * Creates a vertical LinearLayoutManager * @@ -157,23 +169,6 @@ public LinearLayoutManager(Context context, int orientation, boolean reverseLayo setAutoMeasureEnabled(true); } - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". Defaults to vertical orientation. - * - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd - */ - public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setOrientation(properties.orientation); - setReverseLayout(properties.reverseLayout); - setStackFromEnd(properties.stackFromEnd); - setAutoMeasureEnabled(true); - } - /** * {@inheritDoc} */ @@ -199,7 +194,7 @@ public boolean getRecycleChildrenOnDetach() { * RecyclerView. *

        * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set - * this flag to true so that views will be avilable to other RecyclerViews + * this flag to true so that views will be available to other RecyclerViews * immediately. *

        * Note that, setting this flag will result in a performance drop if RecyclerView @@ -304,7 +299,7 @@ public boolean getStackFromEnd() { } /** - * Returns the current orientaion of the layout. + * Returns the current orientation of the layout. * * @return Current orientation, either {@link #HORIZONTAL} or {@link #VERTICAL} * @see #setOrientation(int) @@ -362,8 +357,8 @@ public boolean getReverseLayout() { * laid out at the end of the UI, second item is laid out before it etc. * * For horizontal layouts, it depends on the layout direction. - * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will - * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout + * When set to true, If {@link org.telegram.messenger.support.widget.RecyclerView} is LTR, than it will + * layout from RTL, if {@link org.telegram.messenger.support.widget.RecyclerView}} is RTL, it will layout * from LTR. * * If you are looking for the exact same behavior of @@ -424,17 +419,12 @@ protected int getExtraLayoutSpace(RecyclerView.State state) { public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - return LinearLayoutManager.this - .computeScrollVectorForPosition(targetPosition); - } - }; + new LinearSmoothScroller(recyclerView.getContext()); linearSmoothScroller.setTargetPosition(position); startSmoothScroll(linearSmoothScroller); } + @Override public PointF computeScrollVectorForPosition(int targetPosition) { if (getChildCount() == 0) { return null; @@ -478,10 +468,14 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State // resolve layout direction resolveShouldLayoutReverse(); - mAnchorInfo.reset(); - mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; - // calculate anchor position and coordinate - updateAnchorInfoForLayout(recycler, state, mAnchorInfo); + if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || + mPendingSavedState != null) { + mAnchorInfo.reset(); + mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; + // calculate anchor position and coordinate + updateAnchorInfoForLayout(recycler, state, mAnchorInfo); + mAnchorInfo.mValid = true; + } if (DEBUG) { Log.d(TAG, "Anchor info:" + mAnchorInfo); } @@ -619,17 +613,25 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State } layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); if (!state.isPreLayout()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; mOrientationHelper.onLayoutComplete(); + } else { + mAnchorInfo.reset(); } mLastStackFromEnd = mStackFromEnd; - mPendingSavedState = null; // we don't need this anymore if (DEBUG) { validateChildOrder(); } } + @Override + public void onLayoutCompleted(RecyclerView.State state) { + super.onLayoutCompleted(state); + mPendingSavedState = null; // we don't need this anymore + mPendingScrollPosition = NO_POSITION; + mPendingScrollPositionOffset = INVALID_OFFSET; + mAnchorInfo.reset(); + } + /** * Method called when Anchor position is decided. Extending class can setup accordingly or * even update anchor info if necessary. @@ -914,7 +916,7 @@ private void updateLayoutStateToFillEnd(int itemPosition, int offset) { mLayoutState.mCurrentPosition = itemPosition; mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END; mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; + mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; } private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) { @@ -928,7 +930,7 @@ private void updateLayoutStateToFillStart(int itemPosition, int offset) { LayoutState.ITEM_DIRECTION_HEAD; mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START; mLayoutState.mOffset = offset; - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; + mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; } @@ -1168,6 +1170,111 @@ boolean resolveIsInfinite() { && mOrientationHelper.getEnd() == 0; } + void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState, + LayoutPrefetchRegistry layoutPrefetchRegistry) { + final int pos = layoutState.mCurrentPosition; + if (pos >= 0 && pos < state.getItemCount()) { + layoutPrefetchRegistry.addPosition(pos, layoutState.mScrollingOffset); + } + } + + @Override + public void collectInitialPrefetchPositions(int adapterItemCount, + LayoutPrefetchRegistry layoutPrefetchRegistry) { + final boolean fromEnd; + final int anchorPos; + if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { + // use restored state, since it hasn't been resolved yet + fromEnd = mPendingSavedState.mAnchorLayoutFromEnd; + anchorPos = mPendingSavedState.mAnchorPosition; + } else { + resolveShouldLayoutReverse(); + fromEnd = mShouldReverseLayout; + if (mPendingScrollPosition == NO_POSITION) { + anchorPos = fromEnd ? adapterItemCount - 1 : 0; + } else { + anchorPos = mPendingScrollPosition; + } + } + + final int direction = fromEnd + ? LayoutState.ITEM_DIRECTION_HEAD + : LayoutState.ITEM_DIRECTION_TAIL; + int targetPos = anchorPos; + for (int i = 0; i < mInitialItemPrefetchCount; i++) { + if (targetPos >= 0 && targetPos < adapterItemCount) { + layoutPrefetchRegistry.addPosition(targetPos, 0); + } else { + break; // no more to prefetch + } + targetPos += direction; + } + } + + /** + * Sets the number of items to prefetch in + * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines + * how many inner items should be prefetched when this LayoutManager's RecyclerView + * is nested inside another RecyclerView. + * + *

        Set this value to the number of items this inner LayoutManager will display when it is + * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items + * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.

        + * + *

        For example, take a vertically scrolling RecyclerView with horizontally scrolling inner + * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing + * 4 to this method for each inner RecyclerView's LinearLayoutManager will enable + * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early, + * before it is scrolled on screen, instead of just the default 2.

        + * + *

        Calling this method does nothing unless the LayoutManager is in a RecyclerView + * nested in another RecyclerView.

        + * + *

        Note: Setting this value to be larger than the number of + * views that will be visible in this view can incur unnecessary bind work, and an increase to + * the number of Views created and in active use.

        + * + * @param itemCount Number of items to prefetch + * + * @see #isItemPrefetchEnabled() + * @see #getInitialItemPrefetchCount() + * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) + */ + public void setInitialPrefetchItemCount(int itemCount) { + mInitialItemPrefetchCount = itemCount; + } + + /** + * Gets the number of items to prefetch in + * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines + * how many inner items should be prefetched when this LayoutManager's RecyclerView + * is nested inside another RecyclerView. + * + * @see #isItemPrefetchEnabled() + * @see #setInitialPrefetchItemCount(int) + * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) + * + * @return number of items to prefetch. + */ + public int getInitialItemPrefetchCount() { + return mInitialItemPrefetchCount; + } + + @Override + public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, + LayoutPrefetchRegistry layoutPrefetchRegistry) { + int delta = (mOrientation == HORIZONTAL) ? dx : dy; + if (getChildCount() == 0 || delta == 0) { + // can't support this scroll, so don't bother prefetching + return; + } + + final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; + final int absDy = Math.abs(delta); + updateLayoutState(layoutDirection, absDy, true, state); + collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry); + } + int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getChildCount() == 0 || dy == 0) { return 0; @@ -1227,8 +1334,10 @@ private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int /** * Recycles views that went out of bounds after scrolling towards the end of the layout. + *

        + * Checks both layout position and visible position to guarantee that the view is not visible. * - * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} + * @param recycler Recycler instance of {@link org.telegram.messenger.support.widget.RecyclerView} * @param dt This can be used to add additional padding to the visible area. This is used * to detect children that will go out of bounds after scrolling, without * actually moving them. @@ -1247,7 +1356,9 @@ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { if (mShouldReverseLayout) { for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here + if (mOrientationHelper.getDecoratedEnd(child) > limit + || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { + // stop here recycleChildren(recycler, childCount - 1, i); return; } @@ -1255,7 +1366,9 @@ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { } else { for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here + if (mOrientationHelper.getDecoratedEnd(child) > limit + || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) { + // stop here recycleChildren(recycler, 0, i); return; } @@ -1266,8 +1379,10 @@ private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) { /** * Recycles views that went out of bounds after scrolling towards the start of the layout. + *

        + * Checks both layout position and visible position to guarantee that the view is not visible. * - * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView} + * @param recycler Recycler instance of {@link org.telegram.messenger.support.widget.RecyclerView} * @param dt This can be used to add additional padding to the visible area. This is used * to detect children that will go out of bounds after scrolling, without * actually moving them. @@ -1285,7 +1400,9 @@ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { if (mShouldReverseLayout) { for (int i = 0; i < childCount; i++) { View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here + if (mOrientationHelper.getDecoratedStart(child) < limit + || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { + // stop here recycleChildren(recycler, 0, i); return; } @@ -1293,13 +1410,14 @@ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { } else { for (int i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here + if (mOrientationHelper.getDecoratedStart(child) < limit + || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) { + // stop here recycleChildren(recycler, childCount - 1, i); return; } } } - } /** @@ -1309,8 +1427,8 @@ private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) { * @param layoutState Current layout state. Right now, this object does not change but * we may consider moving it out of this view so passing around as a * parameter for now, rather than accessing {@link #mLayoutState} - * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int) - * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int) + * @see #recycleViewsFromStart(org.telegram.messenger.support.widget.RecyclerView.Recycler, int) + * @see #recycleViewsFromEnd(org.telegram.messenger.support.widget.RecyclerView.Recycler, int) * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection */ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) { @@ -1333,13 +1451,13 @@ private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState la * @param layoutState Configuration on how we should fill out the available space. * @param state Context passed by the RecyclerView to control scroll steps. * @param stopOnFocusable If true, filling stops in the first focusable new child - * @return Number of pixels that it added. Useful for scoll functions. + * @return Number of pixels that it added. Useful for scroll functions. */ int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; - if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { + if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // TODO ugly bug fix. should not happen if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; @@ -1347,7 +1465,7 @@ int fill(RecyclerView.Recycler recycler, LayoutState layoutState, recycleByLayoutState(recycler, layoutState); } int remainingSpace = layoutState.mAvailable + layoutState.mExtra; - LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); + LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); layoutChunk(recycler, state, layoutState, layoutChunkResult); @@ -1368,7 +1486,7 @@ int fill(RecyclerView.Recycler recycler, LayoutState layoutState, remainingSpace -= layoutChunkResult.mConsumed; } - if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { + if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed; if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; @@ -1445,8 +1563,7 @@ void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, } // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. - layoutDecorated(view, left + params.leftMargin, top + params.topMargin, - right - params.rightMargin, bottom - params.bottomMargin); + layoutDecoratedWithMargins(view, left, top, right, bottom); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" @@ -1479,9 +1596,21 @@ && getWidthMode() != View.MeasureSpec.EXACTLY int convertFocusDirectionToLayoutDirection(int focusDirection) { switch (focusDirection) { case View.FOCUS_BACKWARD: - return LayoutState.LAYOUT_START; + if (mOrientation == VERTICAL) { + return LayoutState.LAYOUT_START; + } else if (isLayoutRTL()) { + return LayoutState.LAYOUT_END; + } else { + return LayoutState.LAYOUT_START; + } case View.FOCUS_FORWARD: - return LayoutState.LAYOUT_END; + if (mOrientation == VERTICAL) { + return LayoutState.LAYOUT_END; + } else if (isLayoutRTL()) { + return LayoutState.LAYOUT_START; + } else { + return LayoutState.LAYOUT_END; + } case View.FOCUS_UP: return mOrientation == VERTICAL ? LayoutState.LAYOUT_START : LayoutState.INVALID_LAYOUT; @@ -1766,7 +1895,7 @@ public View onFocusSearchFailed(View focused, int focusDirection, ensureLayoutState(); final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace()); updateLayoutState(layoutDir, maxScroll, false, state); - mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN; + mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN; mLayoutState.mRecycle = false; fill(recycler, mLayoutState, state, true); final View nextFocus; @@ -1853,6 +1982,7 @@ public boolean supportsPredictiveItemAnimations() { /** * @hide This method should be called by ItemTouchHelper only. */ + @RestrictTo(LIBRARY_GROUP) @Override public void prepareForDrop(View view, View target, int x, int y) { assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation"); @@ -1890,7 +2020,7 @@ public void prepareForDrop(View view, View target, int x, int y) { */ static class LayoutState { - final static String TAG = "LinearLayoutManager#LayoutState"; + final static String TAG = "LLM#LayoutState"; final static int LAYOUT_START = -1; @@ -1902,7 +2032,7 @@ static class LayoutState { final static int ITEM_DIRECTION_TAIL = 1; - final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE; + final static int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE; /** * We may not want to recycle children in some cases (e.g. layout) @@ -2071,6 +2201,7 @@ void log() { /** * @hide */ + @RestrictTo(LIBRARY_GROUP) public static class SavedState implements Parcelable { int mAnchorPosition; @@ -2136,10 +2267,17 @@ class AnchorInfo { int mPosition; int mCoordinate; boolean mLayoutFromEnd; + boolean mValid; + + AnchorInfo() { + reset(); + } + void reset() { mPosition = NO_POSITION; mCoordinate = INVALID_OFFSET; mLayoutFromEnd = false; + mValid = false; } /** @@ -2158,10 +2296,11 @@ public String toString() { "mPosition=" + mPosition + ", mCoordinate=" + mCoordinate + ", mLayoutFromEnd=" + mLayoutFromEnd + + ", mValid=" + mValid + '}'; } - private boolean isViewValidAsAnchor(View child, RecyclerView.State state) { + boolean isViewValidAsAnchor(View child, RecyclerView.State state) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0 && lp.getViewLayoutPosition() < state.getItemCount(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java index e5a611180a3..2131bc4b027 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSmoothScroller.java @@ -18,6 +18,7 @@ import android.content.Context; import android.graphics.PointF; +import android.support.annotation.Nullable; import android.util.DisplayMetrics; import android.util.Log; import android.view.View; @@ -25,12 +26,16 @@ import android.view.animation.LinearInterpolator; /** - * {@link RecyclerView.SmoothScroller} implementation which uses - * {@link android.view.animation.LinearInterpolator} until the target position becames a child of - * the RecyclerView and then uses - * {@link android.view.animation.DecelerateInterpolator} to slowly approach to target position. + * {@link RecyclerView.SmoothScroller} implementation which uses a {@link LinearInterpolator} until + * the target position becomes a child of the RecyclerView and then uses a + * {@link DecelerateInterpolator} to slowly approach to target position. + *

        + * If the {@link RecyclerView.LayoutManager} you are using does not implement the + * {@link RecyclerView.SmoothScroller.ScrollVectorProvider} interface, then you must override the + * {@link #computeScrollVectorForPosition(int)} method. All the LayoutManagers bundled with + * the support library implement this interface. */ -abstract public class LinearSmoothScroller extends RecyclerView.SmoothScroller { +public class LinearSmoothScroller extends RecyclerView.SmoothScroller { private static final String TAG = "LinearSmoothScroller"; @@ -226,9 +231,6 @@ protected void updateActionForInterimTarget(Action action) { // find an interim target position PointF scrollVector = computeScrollVectorForPosition(getTargetPosition()); if (scrollVector == null || (scrollVector.x == 0 && scrollVector.y == 0)) { - Log.e(TAG, "To support smooth scrolling, you should override \n" - + "LayoutManager#computeScrollVectorForPosition.\n" - + "Falling back to instant scroll"); final int target = getTargetPosition(); action.jumpTo(target); stop(); @@ -335,5 +337,25 @@ public int calculateDxToMakeVisible(View view, int snapPreference) { return calculateDtToFit(left, right, start, end, snapPreference); } - abstract public PointF computeScrollVectorForPosition(int targetPosition); + /** + * Compute the scroll vector for a given target position. + *

        + * This method can return null if the layout manager cannot calculate a scroll vector + * for the given position (e.g. it has no current scroll position). + * + * @param targetPosition the position to which the scroller is scrolling + * + * @return the scroll vector for a given target position + */ + @Nullable + public PointF computeScrollVectorForPosition(int targetPosition) { + RecyclerView.LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof ScrollVectorProvider) { + return ((ScrollVectorProvider) layoutManager) + .computeScrollVectorForPosition(targetPosition); + } + Log.w(TAG, "You should override computeScrollVectorForPosition when the LayoutManager" + + " does not implement " + ScrollVectorProvider.class.getCanonicalName()); + return null; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSnapHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSnapHelper.java new file mode 100644 index 00000000000..73a99b172c1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/LinearSnapHelper.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific languag`e governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.support.widget; + +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.View; + +/** + * Implementation of the {@link SnapHelper} supporting snapping in either vertical or horizontal + * orientation. + *

        + * The implementation will snap the center of the target child view to the center of + * the attached {@link RecyclerView}. If you intend to change this behavior then override + * {@link SnapHelper#calculateDistanceToFinalSnap}. + */ +public class LinearSnapHelper extends SnapHelper { + + private static final float INVALID_DISTANCE = 1f; + + // Orientation helpers are lazily created per LayoutManager. + @Nullable + private OrientationHelper mVerticalHelper; + @Nullable + private OrientationHelper mHorizontalHelper; + + @Override + public int[] calculateDistanceToFinalSnap( + @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) { + int[] out = new int[2]; + if (layoutManager.canScrollHorizontally()) { + out[0] = distanceToCenter(layoutManager, targetView, + getHorizontalHelper(layoutManager)); + } else { + out[0] = 0; + } + + if (layoutManager.canScrollVertically()) { + out[1] = distanceToCenter(layoutManager, targetView, + getVerticalHelper(layoutManager)); + } else { + out[1] = 0; + } + return out; + } + + @Override + public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, + int velocityY) { + if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { + return RecyclerView.NO_POSITION; + } + + final int itemCount = layoutManager.getItemCount(); + if (itemCount == 0) { + return RecyclerView.NO_POSITION; + } + + final View currentView = findSnapView(layoutManager); + if (currentView == null) { + return RecyclerView.NO_POSITION; + } + + final int currentPosition = layoutManager.getPosition(currentView); + if (currentPosition == RecyclerView.NO_POSITION) { + return RecyclerView.NO_POSITION; + } + + RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = + (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; + // deltaJumps sign comes from the velocity which may not match the order of children in + // the LayoutManager. To overcome this, we ask for a vector from the LayoutManager to + // get the direction. + PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); + if (vectorForEnd == null) { + // cannot get a vector for the given position. + return RecyclerView.NO_POSITION; + } + + int vDeltaJump, hDeltaJump; + if (layoutManager.canScrollHorizontally()) { + hDeltaJump = estimateNextPositionDiffForFling(layoutManager, + getHorizontalHelper(layoutManager), velocityX, 0); + if (vectorForEnd.x < 0) { + hDeltaJump = -hDeltaJump; + } + } else { + hDeltaJump = 0; + } + if (layoutManager.canScrollVertically()) { + vDeltaJump = estimateNextPositionDiffForFling(layoutManager, + getVerticalHelper(layoutManager), 0, velocityY); + if (vectorForEnd.y < 0) { + vDeltaJump = -vDeltaJump; + } + } else { + vDeltaJump = 0; + } + + int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump; + if (deltaJump == 0) { + return RecyclerView.NO_POSITION; + } + + int targetPos = currentPosition + deltaJump; + if (targetPos < 0) { + targetPos = 0; + } + if (targetPos >= itemCount) { + targetPos = itemCount - 1; + } + return targetPos; + } + + @Override + public View findSnapView(RecyclerView.LayoutManager layoutManager) { + if (layoutManager.canScrollVertically()) { + return findCenterView(layoutManager, getVerticalHelper(layoutManager)); + } else if (layoutManager.canScrollHorizontally()) { + return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); + } + return null; + } + + private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, + @NonNull View targetView, OrientationHelper helper) { + final int childCenter = helper.getDecoratedStart(targetView) + + (helper.getDecoratedMeasurement(targetView) / 2); + final int containerCenter; + if (layoutManager.getClipToPadding()) { + containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; + } else { + containerCenter = helper.getEnd() / 2; + } + return childCenter - containerCenter; + } + + /** + * Estimates a position to which SnapHelper will try to scroll to in response to a fling. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param helper The {@link OrientationHelper} that is created from the LayoutManager. + * @param velocityX The velocity on the x axis. + * @param velocityY The velocity on the y axis. + * + * @return The diff between the target scroll position and the current position. + */ + private int estimateNextPositionDiffForFling(RecyclerView.LayoutManager layoutManager, + OrientationHelper helper, int velocityX, int velocityY) { + int[] distances = calculateScrollDistance(velocityX, velocityY); + float distancePerChild = computeDistancePerChild(layoutManager, helper); + if (distancePerChild <= 0) { + return 0; + } + int distance = + Math.abs(distances[0]) > Math.abs(distances[1]) ? distances[0] : distances[1]; + if (distance > 0) { + return (int) Math.floor(distance / distancePerChild); + } else { + return (int) Math.ceil(distance / distancePerChild); + } + } + + /** + * Return the child view that is currently closest to the center of this parent. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. + * + * @return the child view that is currently closest to the center of this parent. + */ + @Nullable + private View findCenterView(RecyclerView.LayoutManager layoutManager, + OrientationHelper helper) { + int childCount = layoutManager.getChildCount(); + if (childCount == 0) { + return null; + } + + View closestChild = null; + final int center; + if (layoutManager.getClipToPadding()) { + center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; + } else { + center = helper.getEnd() / 2; + } + int absClosest = Integer.MAX_VALUE; + + for (int i = 0; i < childCount; i++) { + final View child = layoutManager.getChildAt(i); + int childCenter = helper.getDecoratedStart(child) + + (helper.getDecoratedMeasurement(child) / 2); + int absDistance = Math.abs(childCenter - center); + + /** if child center is closer than previous closest, set it as closest **/ + if (absDistance < absClosest) { + absClosest = absDistance; + closestChild = child; + } + } + return closestChild; + } + + /** + * Computes an average pixel value to pass a single child. + *

        + * Returns a negative value if it cannot be calculated. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param helper The relevant {@link OrientationHelper} for the attached + * {@link RecyclerView.LayoutManager}. + * + * @return A float value that is the average number of pixels needed to scroll by one view in + * the relevant direction. + */ + private float computeDistancePerChild(RecyclerView.LayoutManager layoutManager, + OrientationHelper helper) { + View minPosView = null; + View maxPosView = null; + int minPos = Integer.MAX_VALUE; + int maxPos = Integer.MIN_VALUE; + int childCount = layoutManager.getChildCount(); + if (childCount == 0) { + return INVALID_DISTANCE; + } + + for (int i = 0; i < childCount; i++) { + View child = layoutManager.getChildAt(i); + final int pos = layoutManager.getPosition(child); + if (pos == RecyclerView.NO_POSITION) { + continue; + } + if (pos < minPos) { + minPos = pos; + minPosView = child; + } + if (pos > maxPos) { + maxPos = pos; + maxPosView = child; + } + } + if (minPosView == null || maxPosView == null) { + return INVALID_DISTANCE; + } + int start = Math.min(helper.getDecoratedStart(minPosView), + helper.getDecoratedStart(maxPosView)); + int end = Math.max(helper.getDecoratedEnd(minPosView), + helper.getDecoratedEnd(maxPosView)); + int distance = end - start; + if (distance == 0) { + return INVALID_DISTANCE; + } + return 1f * distance / ((maxPos - minPos) + 1); + } + + @NonNull + private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { + if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { + mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); + } + return mVerticalHelper; + } + + @NonNull + private OrientationHelper getHorizontalHelper( + @NonNull RecyclerView.LayoutManager layoutManager) { + if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { + mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); + } + return mHorizontalHelper; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java index 1c8aa4a9ddc..2e44f012791 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/OrientationHelper.java @@ -16,6 +16,7 @@ package org.telegram.messenger.support.widget; +import android.graphics.Rect; import android.view.View; import android.widget.LinearLayout; @@ -41,6 +42,8 @@ public abstract class OrientationHelper { private int mLastTotalSpace = INVALID_SIZE; + final Rect mTmpRect = new Rect(); + private OrientationHelper(RecyclerView.LayoutManager layoutManager) { mLayoutManager = layoutManager; } @@ -92,6 +95,38 @@ public int getTotalSpaceChange() { */ public abstract int getDecoratedEnd(View view); + /** + * Returns the end of the View after its matrix transformations are applied to its layout + * position. + *

        + * This method is useful when trying to detect the visible edge of a View. + *

        + * It includes the decorations but does not include the margins. + * + * @param view The view whose transformed end will be returned + * @return The end of the View after its decor insets and transformation matrix is applied to + * its position + * + * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) + */ + public abstract int getTransformedEndWithDecoration(View view); + + /** + * Returns the start of the View after its matrix transformations are applied to its layout + * position. + *

        + * This method is useful when trying to detect the visible edge of a View. + *

        + * It includes the decorations but does not include the margins. + * + * @param view The view whose transformed start will be returned + * @return The start of the View after its decor insets and transformation matrix is applied to + * its position + * + * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect) + */ + public abstract int getTransformedStartWithDecoration(View view); + /** * Returns the space occupied by this View in the current orientation including decorations and * margins. @@ -264,6 +299,18 @@ public int getDecoratedStart(View view) { return mLayoutManager.getDecoratedLeft(view) - params.leftMargin; } + @Override + public int getTransformedEndWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.right; + } + + @Override + public int getTransformedStartWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.left; + } + @Override public int getTotalSpace() { return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() @@ -350,6 +397,18 @@ public int getDecoratedStart(View view) { return mLayoutManager.getDecoratedTop(view) - params.topMargin; } + @Override + public int getTransformedEndWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.bottom; + } + + @Override + public int getTransformedStartWithDecoration(View view) { + mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect); + return mTmpRect.top; + } + @Override public int getTotalSpace() { return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/PagerSnapHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/PagerSnapHelper.java new file mode 100644 index 00000000000..a4f4d997c3f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/PagerSnapHelper.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.support.widget; + +import android.graphics.PointF; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.DisplayMetrics; +import android.view.View; + +/** + * Implementation of the {@link SnapHelper} supporting pager style snapping in either vertical or + * horizontal orientation. + * + *

        + * + * PagerSnapHelper can help achieve a similar behavior to {@link android.support.v4.view.ViewPager}. + * Set both {@link RecyclerView} and {@link org.telegram.messenger.support.widget.RecyclerView.Adapter} to have + * MATCH_PARENT height and width and then attach PagerSnapHelper to the {@link RecyclerView} using + * {@link #attachToRecyclerView(RecyclerView)}. + */ +public class PagerSnapHelper extends SnapHelper { + private static final int MAX_SCROLL_ON_FLING_DURATION = 100; // ms + + // Orientation helpers are lazily created per LayoutManager. + @Nullable + private OrientationHelper mVerticalHelper; + @Nullable + private OrientationHelper mHorizontalHelper; + + @Nullable + @Override + public int[] calculateDistanceToFinalSnap(@NonNull RecyclerView.LayoutManager layoutManager, + @NonNull View targetView) { + int[] out = new int[2]; + if (layoutManager.canScrollHorizontally()) { + out[0] = distanceToCenter(layoutManager, targetView, + getHorizontalHelper(layoutManager)); + } else { + out[0] = 0; + } + + if (layoutManager.canScrollVertically()) { + out[1] = distanceToCenter(layoutManager, targetView, + getVerticalHelper(layoutManager)); + } else { + out[1] = 0; + } + return out; + } + + @Nullable + @Override + public View findSnapView(RecyclerView.LayoutManager layoutManager) { + if (layoutManager.canScrollVertically()) { + return findCenterView(layoutManager, getVerticalHelper(layoutManager)); + } else if (layoutManager.canScrollHorizontally()) { + return findCenterView(layoutManager, getHorizontalHelper(layoutManager)); + } + return null; + } + + @Override + public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX, + int velocityY) { + final int itemCount = layoutManager.getItemCount(); + if (itemCount == 0) { + return RecyclerView.NO_POSITION; + } + + View mStartMostChildView = null; + if (layoutManager.canScrollVertically()) { + mStartMostChildView = findStartView(layoutManager, getVerticalHelper(layoutManager)); + } else if (layoutManager.canScrollHorizontally()) { + mStartMostChildView = findStartView(layoutManager, getHorizontalHelper(layoutManager)); + } + + if (mStartMostChildView == null) { + return RecyclerView.NO_POSITION; + } + final int centerPosition = layoutManager.getPosition(mStartMostChildView); + if (centerPosition == RecyclerView.NO_POSITION) { + return RecyclerView.NO_POSITION; + } + + final boolean forwardDirection; + if (layoutManager.canScrollHorizontally()) { + forwardDirection = velocityX > 0; + } else { + forwardDirection = velocityY > 0; + } + boolean reverseLayout = false; + if ((layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { + RecyclerView.SmoothScroller.ScrollVectorProvider vectorProvider = + (RecyclerView.SmoothScroller.ScrollVectorProvider) layoutManager; + PointF vectorForEnd = vectorProvider.computeScrollVectorForPosition(itemCount - 1); + if (vectorForEnd != null) { + reverseLayout = vectorForEnd.x < 0 || vectorForEnd.y < 0; + } + } + return reverseLayout + ? (forwardDirection ? centerPosition - 1 : centerPosition) + : (forwardDirection ? centerPosition + 1 : centerPosition); + } + + @Override + protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) { + if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) { + return null; + } + return new LinearSmoothScroller(mRecyclerView.getContext()) { + @Override + protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { + int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), + targetView); + final int dx = snapDistances[0]; + final int dy = snapDistances[1]; + final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); + if (time > 0) { + action.update(dx, dy, time, mDecelerateInterpolator); + } + } + + @Override + protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { + return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; + } + + @Override + protected int calculateTimeForScrolling(int dx) { + return Math.min(MAX_SCROLL_ON_FLING_DURATION, super.calculateTimeForScrolling(dx)); + } + }; + } + + private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, + @NonNull View targetView, OrientationHelper helper) { + final int childCenter = helper.getDecoratedStart(targetView) + + (helper.getDecoratedMeasurement(targetView) / 2); + final int containerCenter; + if (layoutManager.getClipToPadding()) { + containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; + } else { + containerCenter = helper.getEnd() / 2; + } + return childCenter - containerCenter; + } + + /** + * Return the child view that is currently closest to the center of this parent. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. + * + * @return the child view that is currently closest to the center of this parent. + */ + @Nullable + private View findCenterView(RecyclerView.LayoutManager layoutManager, + OrientationHelper helper) { + int childCount = layoutManager.getChildCount(); + if (childCount == 0) { + return null; + } + + View closestChild = null; + final int center; + if (layoutManager.getClipToPadding()) { + center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2; + } else { + center = helper.getEnd() / 2; + } + int absClosest = Integer.MAX_VALUE; + + for (int i = 0; i < childCount; i++) { + final View child = layoutManager.getChildAt(i); + int childCenter = helper.getDecoratedStart(child) + + (helper.getDecoratedMeasurement(child) / 2); + int absDistance = Math.abs(childCenter - center); + + /** if child center is closer than previous closest, set it as closest **/ + if (absDistance < absClosest) { + absClosest = absDistance; + closestChild = child; + } + } + return closestChild; + } + + /** + * Return the child view that is currently closest to the start of this parent. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param helper The relevant {@link OrientationHelper} for the attached {@link RecyclerView}. + * + * @return the child view that is currently closest to the start of this parent. + */ + @Nullable + private View findStartView(RecyclerView.LayoutManager layoutManager, + OrientationHelper helper) { + int childCount = layoutManager.getChildCount(); + if (childCount == 0) { + return null; + } + + View closestChild = null; + int startest = Integer.MAX_VALUE; + + for (int i = 0; i < childCount; i++) { + final View child = layoutManager.getChildAt(i); + int childStart = helper.getDecoratedStart(child); + + /** if child is more to start than previous closest, set it as closest **/ + if (childStart < startest) { + startest = childStart; + closestChild = child; + } + } + return closestChild; + } + + @NonNull + private OrientationHelper getVerticalHelper(@NonNull RecyclerView.LayoutManager layoutManager) { + if (mVerticalHelper == null || mVerticalHelper.mLayoutManager != layoutManager) { + mVerticalHelper = OrientationHelper.createVerticalHelper(layoutManager); + } + return mVerticalHelper; + } + + @NonNull + private OrientationHelper getHorizontalHelper( + @NonNull RecyclerView.LayoutManager layoutManager) { + if (mHorizontalHelper == null || mHorizontalHelper.mLayoutManager != layoutManager) { + mHorizontalHelper = OrientationHelper.createHorizontalHelper(layoutManager); + } + return mHorizontalHelper; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java index 3e46bc8a4aa..f42b8674d89 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerView.java @@ -17,11 +17,16 @@ package org.telegram.messenger.support.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; + import android.content.Context; +import android.content.res.TypedArray; import android.database.Observable; import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Build; import android.os.Bundle; import android.os.Parcel; @@ -31,8 +36,12 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.annotation.VisibleForTesting; +import android.support.v4.os.ParcelableCompat; +import android.support.v4.os.ParcelableCompatCreatorCallbacks; import android.support.v4.os.TraceCompat; +import android.support.v4.view.AbsSavedState; import android.support.v4.view.InputDeviceCompat; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.NestedScrollingChild; @@ -40,7 +49,6 @@ import android.support.v4.view.ScrollingView; import android.support.v4.view.VelocityTrackerCompat; import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewConfigurationCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.support.v4.view.accessibility.AccessibilityRecordCompat; @@ -52,8 +60,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; -import android.util.SparseIntArray; import android.util.TypedValue; +import android.view.Display; import android.view.FocusFinder; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -68,6 +76,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -75,9 +84,6 @@ import java.util.Collections; import java.util.List; -import static org.telegram.messenger.support.widget.AdapterHelper.Callback; -import static org.telegram.messenger.support.widget.AdapterHelper.UpdateOp; - /** * A flexible view for providing a limited window into a large data set. * @@ -147,9 +153,14 @@ */ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild { - private static final String TAG = "RecyclerView"; + static final String TAG = "RecyclerView"; + + static final boolean DEBUG = false; - private static final boolean DEBUG = false; + private static final int[] NESTED_SCROLLING_ATTRS + = {16843830 /* android.R.attr.nestedScrollingEnabled */}; + + private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding}; /** * On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if @@ -158,7 +169,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * recursively traverses itemView and invalidates display list for each ViewGroup that matches * this criteria. */ - private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 + static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18 || Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20; /** * On M+, an unspecified measure spec may include a hint which we can use. On older platforms, @@ -167,6 +178,30 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23; + static final boolean POST_UPDATES_ON_ANIMATION = Build.VERSION.SDK_INT >= 16; + + /** + * On L+, with RenderThread, the UI thread has idle time after it has passed a frame off to + * RenderThread but before the next frame begins. We schedule prefetch work in this window. + */ + private static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21; + + /** + * FocusFinder#findNextFocus is broken on ICS MR1 and older for View.FOCUS_BACKWARD direction. + * We convert it to an absolute direction such as FOCUS_DOWN or FOCUS_LEFT. + */ + private static final boolean FORCE_ABS_FOCUS_SEARCH_DIRECTION = Build.VERSION.SDK_INT <= 15; + + /** + * on API 15-, a focused child can still be considered a focused child of RV even after + * it's being removed or its focusable flag is set to false. This is because when this focused + * child is detached, the reference to this child is not removed in clearFocus. API 16 and above + * properly handle this case by calling ensureInputFocusOnFirstFocusable or rootViewRequestFocus + * to request focus on a new child, which will clear the focus on the old (detached) child as a + * side-effect. + */ + private static final boolean IGNORE_DETACHED_FOCUSED_CHILD = Build.VERSION.SDK_INT <= 15; + static final boolean DISPATCH_TEMP_DETACH = false; public static final int HORIZONTAL = 0; public static final int VERTICAL = 1; @@ -189,14 +224,14 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro */ public static final int TOUCH_SLOP_PAGING = 1; - private static final int MAX_SCROLL_DURATION = 2000; + static final int MAX_SCROLL_DURATION = 2000; /** * RecyclerView is calculating a scroll. * If there are too many of these in Systrace, some Views inside RecyclerView might be causing * it. Try to avoid using EditText, focusable views or handle them with care. */ - private static final String TRACE_SCROLL_TAG = "RV Scroll"; + static final String TRACE_SCROLL_TAG = "RV Scroll"; /** * OnLayout has been called by the View system. @@ -227,7 +262,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * If this is taking a lot of time, consider optimizing your layout or make sure you are not * doing extra operations in onBindViewHolder call. */ - private static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; + static final String TRACE_BIND_VIEW_TAG = "RV OnBindView"; + + /** + * RecyclerView is attempting to pre-populate off screen views. + */ + static final String TRACE_PREFETCH_TAG = "RV Prefetch"; + + /** + * RecyclerView is attempting to pre-populate off screen itemviews within an off screen + * RecyclerView. + */ + static final String TRACE_NESTED_PREFETCH_TAG = "RV Nested Prefetch"; /** * RecyclerView is creating a new View. @@ -242,7 +288,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * - There might be too many itemChange animations and not enough space in RecyclerPool. * >Try increasing your pool size and item cache size. */ - private static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; + static final String TRACE_CREATE_VIEW_TAG = "RV CreateView"; private static final Class[] LAYOUT_MANAGER_CONSTRUCTOR_SIGNATURE = new Class[]{Context.class, AttributeSet.class, int.class, int.class}; @@ -271,7 +317,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * Prior to L, there is no way to query this variable which is why we override the setter and * track it here. */ - private boolean mClipToPadding; + boolean mClipToPadding; /** * Note: this Runnable is only ever posted if: @@ -279,12 +325,18 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * 2) We know we have a fixed size (mHasFixedSize) * 3) We're attached */ - private final Runnable mUpdateChildViewsRunnable = new Runnable() { + final Runnable mUpdateChildViewsRunnable = new Runnable() { + @Override public void run() { if (!mFirstLayoutComplete || isLayoutRequested()) { // a layout request will happen, we should not do layout here. return; } + if (!mIsAttached) { + requestLayout(); + // if we are not attached yet, mark us as requiring layout and skip + return; + } if (mLayoutFrozen) { mLayoutRequestEaten = true; return; //we'll process updates when ice age ends. @@ -293,37 +345,47 @@ public void run() { } }; - private final Rect mTempRect = new Rect(); - private Adapter mAdapter; + final Rect mTempRect = new Rect(); + private final Rect mTempRect2 = new Rect(); + final RectF mTempRectF = new RectF(); + Adapter mAdapter; @VisibleForTesting LayoutManager mLayout; - private RecyclerListener mRecyclerListener; - private final ArrayList mItemDecorations = new ArrayList<>(); + RecyclerListener mRecyclerListener; + final ArrayList mItemDecorations = new ArrayList<>(); private final ArrayList mOnItemTouchListeners = new ArrayList<>(); private OnItemTouchListener mActiveOnItemTouchListener; - private boolean mIsAttached; - private boolean mHasFixedSize; - private boolean mFirstLayoutComplete; + boolean mIsAttached; + boolean mHasFixedSize; + @VisibleForTesting boolean mFirstLayoutComplete; // Counting lock to control whether we should ignore requestLayout calls from children or not. private int mEatRequestLayout = 0; - private boolean mLayoutRequestEaten; - private boolean mLayoutFrozen; + boolean mLayoutRequestEaten; + boolean mLayoutFrozen; private boolean mIgnoreMotionEventTillDown; // binary OR of change events that were eaten during a layout or scroll. private int mEatenAccessibilityChangeFlags; - private boolean mAdapterUpdateDuringMeasure; - private final boolean mPostUpdatesOnAnimation; + boolean mAdapterUpdateDuringMeasure; + private final AccessibilityManager mAccessibilityManager; private List mOnChildAttachStateListeners; /** * Set to true when an adapter data set changed notification is received. - * In that case, we cannot run any animations since we don't know what happened. + * In that case, we cannot run any animations since we don't know what happened until layout. + * + * Attached items are invalid until next layout, at which point layout will animate/replace + * items as necessary, building up content from the (effectively) new adapter from scratch. + * + * Cached items must be discarded when setting this to true, so that the cache may be freely + * used by prefetching until the next layout occurs. + * + * @see #setDataSetChangedAfterLayout() */ - private boolean mDataSetHasChangedAfterLayout = false; + boolean mDataSetHasChangedAfterLayout = false; /** * This variable is incremented during a dispatchLayout and/or scroll. @@ -335,8 +397,15 @@ public void run() { */ private int mLayoutOrScrollCounter = 0; - private int topGlowOffset = 0; - private int glowColor = 0; + /** + * Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception + * (for API compatibility). + *

        + * It is a bad practice for a developer to update the data in a scroll callback since it is + * potentially called during a layout. + */ + private int mDispatchScrollCounter = 0; + private EdgeEffectCompat mLeftGlow, mTopGlow, mRightGlow, mBottomGlow; ItemAnimator mItemAnimator = new DefaultItemAnimator(); @@ -362,6 +431,8 @@ public void run() { */ public static final int SCROLL_STATE_SETTLING = 2; + static final long FOREVER_NS = Long.MAX_VALUE; + // Touch/scrolling handling private int mScrollState = SCROLL_STATE_IDLE; @@ -372,12 +443,18 @@ public void run() { private int mLastTouchX; private int mLastTouchY; private int mTouchSlop; + private OnFlingListener mOnFlingListener; private final int mMinFlingVelocity; private final int mMaxFlingVelocity; // This value is used when handling generic motion events. private float mScrollFactor = Float.MIN_VALUE; + private boolean mPreserveFocusAfterLayout = true; + + final ViewFlinger mViewFlinger = new ViewFlinger(); - private final ViewFlinger mViewFlinger = new ViewFlinger(); + GapWorker mGapWorker; + GapWorker.LayoutPrefetchRegistryImpl mPrefetchRegistry = + ALLOW_THREAD_GAP_WORK ? new GapWorker.LayoutPrefetchRegistryImpl() : null; final State mState = new State(); @@ -389,8 +466,8 @@ public void run() { boolean mItemsChanged = false; private ItemAnimator.ItemAnimatorListener mItemAnimatorListener = new ItemAnimatorRestoreListener(); - private boolean mPostedAnimatorRunner = false; - private RecyclerViewAccessibilityDelegate mAccessibilityDelegate; + boolean mPostedAnimatorRunner = false; + RecyclerViewAccessibilityDelegate mAccessibilityDelegate; private ChildDrawingOrderCallback mChildDrawingOrderCallback; // simple array to keep min and max child position during a layout calculation @@ -402,6 +479,25 @@ public void run() { private final int[] mScrollConsumed = new int[2]; private final int[] mNestedOffsets = new int[2]; + private int topGlowOffset = 0; + private int glowColor = 0; + + public void setTopGlowOffset(int offset) { + topGlowOffset = offset; + } + + public void setGlowColor(int color) { + glowColor = color; + } + + /** + * These are views that had their a11y importance changed during a layout. We defer these events + * until the end of the layout because a11y service may make sync calls back to the RV while + * the View's state is undefined. + */ + @VisibleForTesting + final List mPendingAccessibilityImportanceChange = new ArrayList(); + private Runnable mItemAnimatorRunner = new Runnable() { @Override public void run() { @@ -412,7 +508,8 @@ public void run() { } }; - private static final Interpolator sQuinticInterpolator = new Interpolator() { + static final Interpolator sQuinticInterpolator = new Interpolator() { + @Override public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; @@ -467,16 +564,21 @@ public RecyclerView(Context context, @Nullable AttributeSet attrs) { public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + if (attrs != null) { + TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0); + mClipToPadding = a.getBoolean(0, true); + a.recycle(); + } else { + mClipToPadding = true; + } setScrollContainer(true); setFocusableInTouchMode(true); - final int version = Build.VERSION.SDK_INT; - mPostUpdatesOnAnimation = version >= 16; final ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); - setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); + setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); mItemAnimator.setListener(mItemAnimatorListener); initAdapterManager(); @@ -492,8 +594,13 @@ public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); // Create the layoutManager if specified. + boolean nestedScrollingEnabled = true; + + + setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + // Re-set whether nested scrolling is enabled so that it is set on all API levels - setNestedScrollingEnabled(true); + setNestedScrollingEnabled(nestedScrollingEnabled); } /** @@ -574,7 +681,7 @@ private String getFullClassName(Context context, String className) { if (className.charAt(0) == '.') { return context.getPackageName() + className; } - if (className.indexOf('.') != -1) { + if (className.contains(".")) { return className; } return RecyclerView.class.getPackage().getName() + '.' + className; @@ -666,7 +773,7 @@ public void detachViewFromParent(int offset) { public void onEnteredHiddenState(View child) { final ViewHolder vh = getChildViewHolderInt(child); if (vh != null) { - vh.onEnteredHiddenState(); + vh.onEnteredHiddenState(RecyclerView.this); } } @@ -674,14 +781,14 @@ public void onEnteredHiddenState(View child) { public void onLeftHiddenState(View child) { final ViewHolder vh = getChildViewHolderInt(child); if (vh != null) { - vh.onLeftHiddenState(); + vh.onLeftHiddenState(RecyclerView.this); } } }); } void initAdapterManager() { - mAdapterHelper = new AdapterHelper(new Callback() { + mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() { @Override public ViewHolder findViewHolder(int position) { final ViewHolder vh = findViewHolderForPosition(position, true); @@ -719,30 +826,30 @@ public void markViewHoldersUpdated(int positionStart, int itemCount, Object payl } @Override - public void onDispatchFirstPass(UpdateOp op) { + public void onDispatchFirstPass(AdapterHelper.UpdateOp op) { dispatchUpdate(op); } - void dispatchUpdate(UpdateOp op) { + void dispatchUpdate(AdapterHelper.UpdateOp op) { switch (op.cmd) { - case UpdateOp.ADD: + case AdapterHelper.UpdateOp.ADD: mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount); break; - case UpdateOp.REMOVE: + case AdapterHelper.UpdateOp.REMOVE: mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount); break; - case UpdateOp.UPDATE: + case AdapterHelper.UpdateOp.UPDATE: mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount, op.payload); break; - case UpdateOp.MOVE: + case AdapterHelper.UpdateOp.MOVE: mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1); break; } } @Override - public void onDispatchSecondPass(UpdateOp op) { + public void onDispatchSecondPass(AdapterHelper.UpdateOp op) { dispatchUpdate(op); } @@ -796,6 +903,23 @@ public void setClipToPadding(boolean clipToPadding) { } } + /** + * Returns whether this RecyclerView will clip its children to its padding, and resize (but + * not clip) any EdgeEffect to the padded region, if padding is present. + *

        + * By default, children are clipped to the padding of their parent + * RecyclerView. This clipping behavior is only enabled if padding is non-zero. + * + * @return true if this RecyclerView clips children to its padding and resizes (but doesn't + * clip) any EdgeEffect to the padded region, false otherwise. + * + * @attr name android:clipToPadding + */ + @Override + public boolean getClipToPadding() { + return mClipToPadding; + } + /** * Configure the scrolling touch slop for a specific use case. * @@ -817,7 +941,7 @@ public void setScrollingTouchSlop(int slopConstant) { break; case TOUCH_SLOP_PAGING: - mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(vc); + mTouchSlop = vc.getScaledPagingTouchSlop(); break; } } @@ -854,12 +978,31 @@ public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) */ public void setAdapter(Adapter adapter) { // bail out if layout is frozen - stopScroll(); setLayoutFrozen(false); setAdapterInternal(adapter, false, true); requestLayout(); } + /** + * Removes and recycles all views - both those currently attached, and those in the Recycler. + */ + void removeAndRecycleViews() { + // end all running animations + if (mItemAnimator != null) { + mItemAnimator.endAnimations(); + } + // Since animations are ended, mLayout.children should be equal to + // recyclerView.children. This may not be true if item animator's end does not work as + // expected. (e.g. not release children instantly). It is safer to use mLayout's child + // count. + if (mLayout != null) { + mLayout.removeAndRecycleAllViews(mRecycler); + mLayout.removeAndRecycleScrapInt(mRecycler); + } + // we should clear it here before adapters are swapped to ensure correct callbacks. + mRecycler.clear(); + } + /** * Replaces the current adapter with the new one and triggers listeners. * @param adapter The new adapter @@ -876,20 +1019,7 @@ private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, mAdapter.onDetachedFromRecyclerView(this); } if (!compatibleWithPrevious || removeAndRecycleViews) { - // end all running animations - if (mItemAnimator != null) { - mItemAnimator.endAnimations(); - } - // Since animations are ended, mLayout.children should be equal to - // recyclerView.children. This may not be true if item animator's end does not work as - // expected. (e.g. not release children instantly). It is safer to use mLayout's child - // count. - if (mLayout != null) { - mLayout.removeAndRecycleAllViews(mRecycler); - mLayout.removeAndRecycleScrapInt(mRecycler); - } - // we should clear it here before adapters are swapped to ensure correct callbacks. - mRecycler.clear(); + removeAndRecycleViews(); } mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; @@ -1004,15 +1134,26 @@ public void setLayoutManager(LayoutManager layout) { return; } stopScroll(); - // TODO We should do this switch a dispachLayout pass and animate children. There is a good + // TODO We should do this switch a dispatchLayout pass and animate children. There is a good // chance that LayoutManagers will re-use views. if (mLayout != null) { + // end all running animations + if (mItemAnimator != null) { + mItemAnimator.endAnimations(); + } + mLayout.removeAndRecycleAllViews(mRecycler); + mLayout.removeAndRecycleScrapInt(mRecycler); + mRecycler.clear(); + if (mIsAttached) { mLayout.dispatchDetachedFromWindow(this, mRecycler); } mLayout.setRecyclerView(null); + mLayout = null; + } else { + mRecycler.clear(); } - mRecycler.clear(); + // this is just a defensive measure for faulty item animators. mChildHelper.removeAllViewsUnfiltered(); mLayout = layout; if (layout != null) { @@ -1025,9 +1166,32 @@ public void setLayoutManager(LayoutManager layout) { mLayout.dispatchAttachedToWindow(this); } } + mRecycler.updateViewCacheSize(); requestLayout(); } + /** + * Set a {@link OnFlingListener} for this {@link RecyclerView}. + *

        + * If the {@link OnFlingListener} is set then it will receive + * calls to {@link #fling(int,int)} and will be able to intercept them. + * + * @param onFlingListener The {@link OnFlingListener} instance. + */ + public void setOnFlingListener(@Nullable OnFlingListener onFlingListener) { + mOnFlingListener = onFlingListener; + } + + /** + * Get the current {@link OnFlingListener} from this {@link RecyclerView}. + * + * @return The {@link OnFlingListener} instance currently set (can be null). + */ + @Nullable + public OnFlingListener getOnFlingListener() { + return mOnFlingListener; + } + @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); @@ -1100,7 +1264,7 @@ private void addAnimatingView(ViewHolder viewHolder) { * @see #addAnimatingView(RecyclerView.ViewHolder) * @return true if an animating view is removed */ - private boolean removeAnimatingView(View view) { + boolean removeAnimatingView(View view) { eatRequestLayout(); final boolean removed = mChildHelper.removeViewIfHidden(view); if (removed) { @@ -1186,7 +1350,7 @@ public int getScrollState() { return mScrollState; } - private void setScrollState(int state) { + void setScrollState(int state) { if (state == mScrollState) { return; } @@ -1264,7 +1428,7 @@ public void removeItemDecoration(ItemDecoration decor) { } mItemDecorations.remove(decor); if (mItemDecorations.isEmpty()) { - setWillNotDraw(ViewCompat.getOverScrollMode(this) == ViewCompat.OVER_SCROLL_NEVER); + setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); } markItemDecorInsetsDirty(); requestLayout(); @@ -1343,9 +1507,9 @@ public void clearOnScrollListeners() { * Convenience method to scroll to a certain position. * * RecyclerView does not implement scrolling logic, rather forwards the call to - * {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)} + * {@link org.telegram.messenger.support.widget.RecyclerView.LayoutManager#scrollToPosition(int)} * @param position Scroll to this adapter position - * @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int) + * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#scrollToPosition(int) */ public void scrollToPosition(int position) { if (mLayoutFrozen) { @@ -1361,7 +1525,7 @@ public void scrollToPosition(int position) { awakenScrollBars(); } - private void jumpToPositionForSmoothScroller(int position) { + void jumpToPositionForSmoothScroller(int position) { if (mLayout == null) { return; } @@ -1427,12 +1591,8 @@ public void scrollBy(int x, int y) { *

        * This method consumes all deferred changes to avoid that case. */ - private void consumePendingUpdateOperations() { - if (!mFirstLayoutComplete) { - // a layout request will happen, we should not do layout here. - return; - } - if (mDataSetHasChangedAfterLayout) { + void consumePendingUpdateOperations() { + if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) { TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); @@ -1444,10 +1604,12 @@ private void consumePendingUpdateOperations() { // if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any // of the visible items is affected and if not, just ignore the change. - if (mAdapterHelper.hasAnyUpdateTypes(UpdateOp.UPDATE) && !mAdapterHelper - .hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) { + if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper + .hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE + | AdapterHelper.UpdateOp.MOVE)) { TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); eatRequestLayout(); + onEnterLayoutOrScroll(); mAdapterHelper.preProcess(); if (!mLayoutRequestEaten) { if (hasUpdatedView()) { @@ -1458,6 +1620,7 @@ private void consumePendingUpdateOperations() { } } resumeRequestLayout(true); + onExitLayoutOrScroll(); TraceCompat.endSection(); } else if (mAdapterHelper.hasPendingUpdates()) { TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); @@ -1529,7 +1692,7 @@ boolean scrollByInternal(int x, int y, MotionEvent ev) { } mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; - } else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { + } else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { if (ev != null) { pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); } @@ -1559,8 +1722,8 @@ boolean scrollByInternal(int x, int y, MotionEvent ev) { * LayoutManager.

        * * @return The horizontal offset of the scrollbar's thumb - * @see android.support.v7.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset - * (RecyclerView.Adapter) + * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#computeHorizontalScrollOffset + * (RecyclerView.State) */ @Override public int computeHorizontalScrollOffset() { @@ -1632,8 +1795,8 @@ public int computeHorizontalScrollRange() { * LayoutManager.

        * * @return The vertical offset of the scrollbar's thumb - * @see android.support.v7.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset - * (RecyclerView.Adapter) + * @see org.telegram.messenger.support.widget.RecyclerView.LayoutManager#computeVerticalScrollOffset + * (RecyclerView.State) */ @Override public int computeVerticalScrollOffset() { @@ -1708,7 +1871,7 @@ void resumeRequestLayout(boolean performLayoutChildren) { } if (!performLayoutChildren) { // Reset the layout request eaten counter. - // This is necessary since eatRequest calls can be nested in which case the outher + // This is necessary since eatRequest calls can be nested in which case the other // call will override the inner one. // for instance: // eat layout for process adapter updates @@ -1790,6 +1953,18 @@ public boolean isLayoutFrozen() { * @param dy Pixels to scroll vertically */ public void smoothScrollBy(int dx, int dy) { + smoothScrollBy(dx, dy, null); + } + + /** + * Animate a scroll by the given amount of pixels along either axis. + * + * @param dx Pixels to scroll horizontally + * @param dy Pixels to scroll vertically + * @param interpolator {@link Interpolator} to be used for scrolling. If it is + * {@code null}, RecyclerView is going to use the default interpolator. + */ + public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { if (mLayout == null) { Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " + "Call setLayoutManager with a non-null argument."); @@ -1805,7 +1980,7 @@ public void smoothScrollBy(int dx, int dy) { dy = 0; } if (dx != 0 || dy != 0) { - mViewFlinger.smoothScrollBy(dx, dy); + mViewFlinger.smoothScrollBy(dx, dy, interpolator); } } @@ -1850,6 +2025,10 @@ public boolean fling(int velocityX, int velocityY) { final boolean canScroll = canScrollHorizontal || canScrollVertical; dispatchNestedFling(velocityX, velocityY, canScroll); + if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) { + return true; + } + if (canScroll) { velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); @@ -1943,7 +2122,7 @@ private void releaseGlows() { } } - private void considerReleasingGlowsOnScroll(int dx, int dy) { + void considerReleasingGlowsOnScroll(int dx, int dy) { boolean needsInvalidate = false; if (mLeftGlow != null && !mLeftGlow.isFinished() && dx > 0) { needsInvalidate = mLeftGlow.onRelease(); @@ -2050,7 +2229,7 @@ void applyEdgeEffectColor(EdgeEffectCompat edgeEffectCompat) { edgeEffect.setColor(glowColor); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -2059,23 +2238,163 @@ void invalidateGlows() { mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null; } - // Focus handling - + /** + * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are + * in the Adapter but not visible in the UI), it employs a more involved focus search strategy + * that differs from other ViewGroups. + *

        + * It first does a focus search within the RecyclerView. If this search finds a View that is in + * the focus direction with respect to the currently focused View, RecyclerView returns that + * child as the next focus target. When it cannot find such child, it calls + * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views + * in the focus search direction. If LayoutManager adds a View that matches the + * focus search criteria, it will be returned as the focus search result. Otherwise, + * RecyclerView will call parent to handle the focus search like a regular ViewGroup. + *

        + * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that + * is not in the focus direction is still valid focus target which may not be the desired + * behavior if the Adapter has more children in the focus direction. To handle this case, + * RecyclerView converts the focus direction to an absolute direction and makes a preliminary + * focus search in that direction. If there are no Views to gain focus, it will call + * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a + * focus search with the original (relative) direction. This allows RecyclerView to provide + * better candidates to the focus search while still allowing the view system to take focus from + * the RecyclerView and give it to a more suitable child if such child exists. + * + * @param focused The view that currently has focus + * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN}, + * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD}, + * {@link View#FOCUS_BACKWARD} or 0 for not applicable. + * + * @return A new View that can be the next focus after the focused View + */ @Override public View focusSearch(View focused, int direction) { View result = mLayout.onInterceptFocusSearch(focused, direction); if (result != null) { return result; } + final boolean canRunFocusFailure = mAdapter != null && mLayout != null + && !isComputingLayout() && !mLayoutFrozen; + final FocusFinder ff = FocusFinder.getInstance(); - result = ff.findNextFocus(this, focused, direction); - if (result == null && mAdapter != null && mLayout != null && !isComputingLayout() - && !mLayoutFrozen) { - eatRequestLayout(); - result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); - resumeRequestLayout(false); + if (canRunFocusFailure + && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) { + // convert direction to absolute direction and see if we have a view there and if not + // tell LayoutManager to add if it can. + boolean needsFocusFailureLayout = false; + if (mLayout.canScrollVertically()) { + final int absDir = + direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP; + final View found = ff.findNextFocus(this, focused, absDir); + needsFocusFailureLayout = found == null; + if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { + // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. + direction = absDir; + } + } + if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) { + boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; + final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl + ? View.FOCUS_RIGHT : View.FOCUS_LEFT; + final View found = ff.findNextFocus(this, focused, absDir); + needsFocusFailureLayout = found == null; + if (FORCE_ABS_FOCUS_SEARCH_DIRECTION) { + // Workaround for broken FOCUS_BACKWARD in API 15 and older devices. + direction = absDir; + } + } + if (needsFocusFailureLayout) { + consumePendingUpdateOperations(); + final View focusedItemView = findContainingItemView(focused); + if (focusedItemView == null) { + // panic, focused view is not a child anymore, cannot call super. + return null; + } + eatRequestLayout(); + mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); + resumeRequestLayout(false); + } + result = ff.findNextFocus(this, focused, direction); + } else { + result = ff.findNextFocus(this, focused, direction); + if (result == null && canRunFocusFailure) { + consumePendingUpdateOperations(); + final View focusedItemView = findContainingItemView(focused); + if (focusedItemView == null) { + // panic, focused view is not a child anymore, cannot call super. + return null; + } + eatRequestLayout(); + result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState); + resumeRequestLayout(false); + } } - return result != null ? result : super.focusSearch(focused, direction); + return isPreferredNextFocus(focused, result, direction) + ? result : super.focusSearch(focused, direction); + } + + /** + * Checks if the new focus candidate is a good enough candidate such that RecyclerView will + * assign it as the next focus View instead of letting view hierarchy decide. + * A good candidate means a View that is aligned in the focus direction wrt the focused View + * and is not the RecyclerView itself. + * When this method returns false, RecyclerView will let the parent make the decision so the + * same View may still get the focus as a result of that search. + */ + private boolean isPreferredNextFocus(View focused, View next, int direction) { + if (next == null || next == this) { + return false; + } + if (focused == null) { + return true; + } + + if(direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) { + final boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL; + final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl + ? View.FOCUS_RIGHT : View.FOCUS_LEFT; + if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) { + return true; + } + if (direction == View.FOCUS_FORWARD) { + return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN); + } else { + return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP); + } + } else { + return isPreferredNextFocusAbsolute(focused, next, direction); + } + + } + + /** + * Logic taken from FocusSearch#isCandidate + */ + private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) { + mTempRect.set(0, 0, focused.getWidth(), focused.getHeight()); + mTempRect2.set(0, 0, next.getWidth(), next.getHeight()); + offsetDescendantRectToMyCoords(focused, mTempRect); + offsetDescendantRectToMyCoords(next, mTempRect2); + switch (direction) { + case View.FOCUS_LEFT: + return (mTempRect.right > mTempRect2.right + || mTempRect.left >= mTempRect2.right) + && mTempRect.left > mTempRect2.left; + case View.FOCUS_RIGHT: + return (mTempRect.left < mTempRect2.left + || mTempRect.right <= mTempRect2.left) + && mTempRect.right < mTempRect2.right; + case View.FOCUS_UP: + return (mTempRect.bottom > mTempRect2.bottom + || mTempRect.top >= mTempRect2.bottom) + && mTempRect.top > mTempRect2.top; + case View.FOCUS_DOWN: + return (mTempRect.top < mTempRect2.top + || mTempRect.bottom <= mTempRect2.top) + && mTempRect.bottom < mTempRect2.bottom; + } + throw new IllegalArgumentException("direction must be absolute. received:" + direction); } @Override @@ -2118,16 +2437,48 @@ public void addFocusables(ArrayList views, int direction, int focusableMod } } + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + if (isComputingLayout()) { + // if we are in the middle of a layout calculation, don't let any child take focus. + // RV will handle it after layout calculation is finished. + return false; + } + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mLayoutOrScrollCounter = 0; mIsAttached = true; - mFirstLayoutComplete = false; + mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested(); if (mLayout != null) { mLayout.dispatchAttachedToWindow(this); } mPostedAnimatorRunner = false; + + if (ALLOW_THREAD_GAP_WORK) { + // Register with gap worker + mGapWorker = GapWorker.sGapWorker.get(); + if (mGapWorker == null) { + mGapWorker = new GapWorker(); + + // break 60 fps assumption if data from display appears valid + // NOTE: we only do this query once, statically, because it's very expensive (> 1ms) + Display display = ViewCompat.getDisplay(this); + float refreshRate = 60.0f; + if (!isInEditMode() && display != null) { + float displayRefreshRate = display.getRefreshRate(); + if (displayRefreshRate >= 30.0f) { + refreshRate = displayRefreshRate; + } + } + mGapWorker.mFrameIntervalNs = (long) (1000000000 / refreshRate); + GapWorker.sGapWorker.set(mGapWorker); + } + mGapWorker.add(this); + } } @Override @@ -2136,15 +2487,20 @@ protected void onDetachedFromWindow() { if (mItemAnimator != null) { mItemAnimator.endAnimations(); } - mFirstLayoutComplete = false; - stopScroll(); mIsAttached = false; if (mLayout != null) { mLayout.dispatchDetachedFromWindow(this, mRecycler); } + mPendingAccessibilityImportanceChange.clear(); removeCallbacks(mItemAnimatorRunner); mViewInfoStore.onDetach(); + + if (ALLOW_THREAD_GAP_WORK) { + // Unregister with gap worker + mGapWorker.remove(this); + mGapWorker = null; + } } /** @@ -2188,6 +2544,13 @@ void assertNotInLayoutOrScroll(String message) { } throw new IllegalStateException(message); } + if (mDispatchScrollCounter > 0) { + Log.w(TAG, "Cannot call this method in a scroll callback. Scroll callbacks might be run" + + " during a measure & layout pass where you cannot change the RecyclerView" + + " data. Any method call that might change the structure of the RecyclerView" + + " or the adapter contents should be postponed to the next frame.", + new IllegalStateException("")); + } } /** @@ -2299,7 +2662,7 @@ public boolean onInterceptTouchEvent(MotionEvent e) { if (mIgnoreMotionEventTillDown) { mIgnoreMotionEventTillDown = false; } - mScrollPointerId = MotionEventCompat.getPointerId(e, 0); + mScrollPointerId = e.getPointerId(0); mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); @@ -2322,21 +2685,21 @@ public boolean onInterceptTouchEvent(MotionEvent e) { break; case MotionEventCompat.ACTION_POINTER_DOWN: - mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); + mScrollPointerId = e.getPointerId(actionIndex); + mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); + mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); break; case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); + final int index = e.findPointerIndex(mScrollPointerId); if (index < 0) { Log.e(TAG, "Error processing scroll; pointer index for id " + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); return false; } - final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); - final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); + final int x = (int) (e.getX(index) + 0.5f); + final int y = (int) (e.getY(index) + 0.5f); if (mScrollState != SCROLL_STATE_DRAGGING) { final int dx = x - mInitialTouchX; final int dy = y - mInitialTouchY; @@ -2414,7 +2777,7 @@ public boolean onTouchEvent(MotionEvent e) { switch (action) { case MotionEvent.ACTION_DOWN: { - mScrollPointerId = MotionEventCompat.getPointerId(e, 0); + mScrollPointerId = e.getPointerId(0); mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); @@ -2429,21 +2792,21 @@ public boolean onTouchEvent(MotionEvent e) { } break; case MotionEventCompat.ACTION_POINTER_DOWN: { - mScrollPointerId = MotionEventCompat.getPointerId(e, actionIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, actionIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, actionIndex) + 0.5f); + mScrollPointerId = e.getPointerId(actionIndex); + mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f); + mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f); } break; case MotionEvent.ACTION_MOVE: { - final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); + final int index = e.findPointerIndex(mScrollPointerId); if (index < 0) { Log.e(TAG, "Error processing scroll; pointer index for id " + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); return false; } - final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); - final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); + final int x = (int) (e.getX(index) + 0.5f); + final int y = (int) (e.getY(index) + 0.5f); int dx = mLastTouchX - x; int dy = mLastTouchY - y; @@ -2489,6 +2852,9 @@ public boolean onTouchEvent(MotionEvent e) { vtev)) { getParent().requestDisallowInterceptTouchEvent(true); } + if (mGapWorker != null && (dx != 0 || dy != 0)) { + mGapWorker.postFromTraversal(this, dx, dy); + } } } break; @@ -2538,12 +2904,12 @@ private void cancelTouch() { private void onPointerUp(MotionEvent e) { final int actionIndex = MotionEventCompat.getActionIndex(e); - if (MotionEventCompat.getPointerId(e, actionIndex) == mScrollPointerId) { + if (e.getPointerId(actionIndex) == mScrollPointerId) { // Pick a new pointer to pick up the slack. final int newIndex = actionIndex == 0 ? 1 : 0; - mScrollPointerId = MotionEventCompat.getPointerId(e, newIndex); - mInitialTouchX = mLastTouchX = (int) (MotionEventCompat.getX(e, newIndex) + 0.5f); - mInitialTouchY = mLastTouchY = (int) (MotionEventCompat.getY(e, newIndex) + 0.5f); + mScrollPointerId = e.getPointerId(newIndex); + mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); + mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); } } @@ -2555,7 +2921,7 @@ public boolean onGenericMotionEvent(MotionEvent event) { if (mLayoutFrozen) { return false; } - if ((MotionEventCompat.getSource(event) & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { + if ((event.getSource() & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { if (event.getAction() == MotionEventCompat.ACTION_SCROLL) { final float vScroll, hScroll; if (mLayout.canScrollVertically()) { @@ -2718,11 +3084,11 @@ public void setItemAnimator(ItemAnimator animator) { } } - private void onEnterLayoutOrScroll() { + void onEnterLayoutOrScroll() { mLayoutOrScrollCounter ++; } - private void onExitLayoutOrScroll() { + void onExitLayoutOrScroll() { mLayoutOrScrollCounter --; if (mLayoutOrScrollCounter < 1) { if (DEBUG && mLayoutOrScrollCounter < 0) { @@ -2731,6 +3097,7 @@ private void onExitLayoutOrScroll() { } mLayoutOrScrollCounter = 0; dispatchContentChangedIfNecessary(); + dispatchPendingImportantForAccessibilityChanges(); } } @@ -2820,7 +3187,7 @@ public ItemAnimator getItemAnimator() { * Post a runnable to the next frame to run pending item animations. Only the first such * request will be posted, governed by the mPostedAnimatorRunner flag. */ - private void postAnimationRunner() { + void postAnimationRunner() { if (!mPostedAnimatorRunner && mIsAttached) { ViewCompat.postOnAnimation(this, mItemAnimatorRunner); mPostedAnimatorRunner = true; @@ -2842,7 +3209,6 @@ private void processAdapterUpdatesAndSetAnimationFlags() { // Processing these items have no value since data set changed unexpectedly. // Instead, we just reset it. mAdapterHelper.reset(); - markKnownViewsInvalid(); mLayout.onItemsChanged(this); } // simple animations are a subset of advanced animations (which will cause a @@ -2854,13 +3220,17 @@ private void processAdapterUpdatesAndSetAnimationFlags() { mAdapterHelper.consumeUpdatesInOnePass(); } boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; - mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && - (mDataSetHasChangedAfterLayout || animationTypeSupported || - mLayout.mRequestedSimpleAnimations) && - (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); - mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && - animationTypeSupported && !mDataSetHasChangedAfterLayout && - predictiveItemAnimationsEnabled(); + mState.mRunSimpleAnimations = mFirstLayoutComplete + && mItemAnimator != null + && (mDataSetHasChangedAfterLayout + || animationTypeSupported + || mLayout.mRequestedSimpleAnimations) + && (!mDataSetHasChangedAfterLayout + || mAdapter.hasStableIds()); + mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations + && animationTypeSupported + && !mDataSetHasChangedAfterLayout + && predictiveItemAnimationsEnabled(); } /** @@ -2917,6 +3287,155 @@ void dispatchLayout() { dispatchLayoutStep3(); } + private void saveFocusInfo() { + View child = null; + if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) { + child = getFocusedChild(); + } + + final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child); + if (focusedVh == null) { + resetFocusInfo(); + } else { + mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID; + // mFocusedItemPosition should hold the current adapter position of the previously + // focused item. If the item is removed, we store the previous adapter position of the + // removed item. + mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION : + (focusedVh.isRemoved() ? focusedVh.mOldPosition + : focusedVh.getAdapterPosition()); + mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView); + } + } + + private void resetFocusInfo() { + mState.mFocusedItemId = NO_ID; + mState.mFocusedItemPosition = NO_POSITION; + mState.mFocusedSubChildId = View.NO_ID; + } + + /** + * Finds the best view candidate to request focus on using mFocusedItemPosition index of the + * previously focused item. It first traverses the adapter forward to find a focusable candidate + * and if no such candidate is found, it reverses the focus search direction for the items + * before the mFocusedItemPosition'th index; + * @return The best candidate to request focus on, or null if no such candidate exists. Null + * indicates all the existing adapter items are unfocusable. + */ + @Nullable + private View findNextViewToFocus() { + int startFocusSearchIndex = mState.mFocusedItemPosition != -1 ? mState.mFocusedItemPosition + : 0; + ViewHolder nextFocus; + final int itemCount = mState.getItemCount(); + for (int i = startFocusSearchIndex; i < itemCount; i++) { + nextFocus = findViewHolderForAdapterPosition(i); + if (nextFocus == null) { + break; + } + if (nextFocus.itemView.hasFocusable()) { + return nextFocus.itemView; + } + } + final int limit = Math.min(itemCount, startFocusSearchIndex); + for (int i = limit - 1; i >= 0; i--) { + nextFocus = findViewHolderForAdapterPosition(i); + if (nextFocus == null) { + return null; + } + if (nextFocus.itemView.hasFocusable()) { + return nextFocus.itemView; + } + } + return null; + } + + private void recoverFocusFromState() { + if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus() + || getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS + || (getDescendantFocusability() == FOCUS_BEFORE_DESCENDANTS && isFocused())) { + // No-op if either of these cases happens: + // 1. RV has no focus, or 2. RV blocks focus to its children, or 3. RV takes focus + // before its children and is focused (i.e. it already stole the focus away from its + // descendants). + return; + } + // only recover focus if RV itself has the focus or the focused view is hidden + if (!isFocused()) { + final View focusedChild = getFocusedChild(); + if (IGNORE_DETACHED_FOCUSED_CHILD + && (focusedChild.getParent() == null || !focusedChild.hasFocus())) { + // Special handling of API 15-. A focused child can be invalid because mFocus is not + // cleared when the child is detached (mParent = null), + // This happens because clearFocus on API 15- does not invalidate mFocus of its + // parent when this child is detached. + // For API 16+, this is not an issue because requestFocus takes care of clearing the + // prior detached focused child. For API 15- the problem happens in 2 cases because + // clearChild does not call clearChildFocus on RV: 1. setFocusable(false) is called + // for the current focused item which calls clearChild or 2. when the prior focused + // child is removed, removeDetachedView called in layout step 3 which calls + // clearChild. We should ignore this invalid focused child in all our calculations + // for the next view to receive focus, and apply the focus recovery logic instead. + if (mChildHelper.getChildCount() == 0) { + // No children left. Request focus on the RV itself since one of its children + // was holding focus previously. + requestFocus(); + return; + } + } else if (!mChildHelper.isHidden(focusedChild)) { + // If the currently focused child is hidden, apply the focus recovery logic. + // Otherwise return, i.e. the currently (unhidden) focused child is good enough :/. + return; + } + } + ViewHolder focusTarget = null; + // RV first attempts to locate the previously focused item to request focus on using + // mFocusedItemId. If such an item no longer exists, it then makes a best-effort attempt to + // find the next best candidate to request focus on based on mFocusedItemPosition. + if (mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) { + focusTarget = findViewHolderForItemId(mState.mFocusedItemId); + } + View viewToFocus = null; + if (focusTarget == null || mChildHelper.isHidden(focusTarget.itemView) + || !focusTarget.itemView.hasFocusable()) { + if (mChildHelper.getChildCount() > 0) { + // At this point, RV has focus and either of these conditions are true: + // 1. There's no previously focused item either because RV received focused before + // layout, or the previously focused item was removed, or RV doesn't have stable IDs + // 2. Previous focus child is hidden, or 3. Previous focused child is no longer + // focusable. In either of these cases, we make sure that RV still passes down the + // focus to one of its focusable children using a best-effort algorithm. + viewToFocus = findNextViewToFocus(); + } + } else { + // looks like the focused item has been replaced with another view that represents the + // same item in the adapter. Request focus on that. + viewToFocus = focusTarget.itemView; + } + + if (viewToFocus != null) { + if (mState.mFocusedSubChildId != NO_ID) { + View child = viewToFocus.findViewById(mState.mFocusedSubChildId); + if (child != null && child.isFocusable()) { + viewToFocus = child; + } + } + viewToFocus.requestFocus(); + } + } + + private int getDeepestFocusedViewWithId(View view) { + int lastKnownId = view.getId(); + while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) { + view = ((ViewGroup) view).getFocusedChild(); + final int id = view.getId(); + if (id != View.NO_ID) { + lastKnownId = view.getId(); + } + } + return lastKnownId; + } + /** * The first step of a layout where we; * - process adapter updates @@ -2930,8 +3449,8 @@ private void dispatchLayoutStep1() { eatRequestLayout(); mViewInfoStore.clear(); onEnterLayoutOrScroll(); - processAdapterUpdatesAndSetAnimationFlags(); + saveFocusInfo(); mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged; mItemsAddedOrRemoved = mItemsChanged = false; mState.mInPreLayout = mState.mRunPredictiveAnimations; @@ -3108,12 +3627,23 @@ private void dispatchLayoutStep3() { if (mRecycler.mChangedScrap != null) { mRecycler.mChangedScrap.clear(); } + if (mLayout.mPrefetchMaxObservedInInitialPrefetch) { + // Initial prefetch has expanded cache, so reset until next prefetch. + // This prevents initial prefetches from expanding the cache permanently. + mLayout.mPrefetchMaxCountObserved = 0; + mLayout.mPrefetchMaxObservedInInitialPrefetch = false; + mRecycler.updateViewCacheSize(); + } + + mLayout.onLayoutCompleted(mState); onExitLayoutOrScroll(); resumeRequestLayout(false); mViewInfoStore.clear(); if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { dispatchOnScrolled(0, 0); } + recoverFocusFromState(); + resetFocusInfo(); } /** @@ -3165,7 +3695,7 @@ private void handleMissingPreInfoForChangeError(long key, * Records the animation information for a view holder that was bounced from hidden list. It * also clears the bounce back flag. */ - private void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, + void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, ItemHolderInfo animationInfo) { // looks like this view bounced back from hidden list! viewHolder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); @@ -3180,8 +3710,8 @@ private void recordAnimationInfoIfBouncedHiddenView(ViewHolder viewHolder, private void findMinMaxChildLayoutPositions(int[] into) { final int count = mChildHelper.getChildCount(); if (count == 0) { - into[0] = 0; - into[1] = 0; + into[0] = NO_POSITION; + into[1] = NO_POSITION; return; } int minPositionPreLayout = Integer.MAX_VALUE; @@ -3204,11 +3734,6 @@ private void findMinMaxChildLayoutPositions(int[] into) { } private boolean didChildRangeChange(int minPositionPreLayout, int maxPositionPreLayout) { - int count = mChildHelper.getChildCount(); - if (count == 0) { - return minPositionPreLayout != 0 || maxPositionPreLayout != 0; - } - // get the new min max findMinMaxChildLayoutPositions(mMinMaxLayoutPositions); return mMinMaxLayoutPositions[0] != minPositionPreLayout || mMinMaxLayoutPositions[1] != maxPositionPreLayout; @@ -3237,7 +3762,7 @@ long getChangedHolderKey(ViewHolder holder) { return mAdapter.hasStableIds() ? holder.getItemId() : holder.mPosition; } - private void animateAppearance(@NonNull ViewHolder itemHolder, + void animateAppearance(@NonNull ViewHolder itemHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) { itemHolder.setIsRecyclable(false); if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { @@ -3245,7 +3770,7 @@ private void animateAppearance(@NonNull ViewHolder itemHolder, } } - private void animateDisappearance(@NonNull ViewHolder holder, + void animateDisappearance(@NonNull ViewHolder holder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { addAnimatingView(holder); holder.setIsRecyclable(false); @@ -3294,14 +3819,6 @@ public void requestLayout() { } } - public void setTopGlowOffset(int offset) { - topGlowOffset = offset; - } - - public void setGlowColor(int color) { - glowColor = color; - } - void markItemDecorInsetsDirty() { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { @@ -3319,7 +3836,7 @@ public void draw(Canvas c) { for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } - // TODO If padding is not 0 and chilChildrenToPadding is false, to draw glows properly, we + // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we // need find children closest to edges. Not sure if it is worth the effort. boolean needsInvalidate = false; if (mLeftGlow != null && !mLeftGlow.isFinished()) { @@ -3558,12 +4075,25 @@ void viewRangeUpdate(int positionStart, int itemCount, Object payload) { mRecycler.viewRangeUpdate(positionStart, itemCount); } - private boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { + boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) { return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder, viewHolder.getUnmodifiedPayloads()); } - private void setDataSetChangedAfterLayout() { + + /** + * Call this method to signal that *all* adapter content has changed (generally, because of + * swapAdapter, or notifyDataSetChanged), and that once layout occurs, all attached items should + * be discarded or animated. Note that this work is deferred because RecyclerView requires a + * layout to resolve non-incremental changes to the data set. + * + * Attached items are labeled as position unknown, and may no longer be cached. + * + * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true, + * so calling this method *must* be associated with marking the cache invalid, so that the + * only valid items that remain in the cache, once layout occurs, are prefetched items. + */ + void setDataSetChangedAfterLayout() { if (mDataSetHasChangedAfterLayout) { return; } @@ -3576,6 +4106,10 @@ private void setDataSetChangedAfterLayout() { } } mRecycler.setAdapterPositionsAsUnknown(); + + // immediately mark all views as invalid, so prefetched views can be + // differentiated from views bound to previous data set - both in children, and cache + markKnownViewsInvalid(); } /** @@ -3610,6 +4144,39 @@ public void invalidateItemDecorations() { requestLayout(); } + /** + * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's + * focus even if the View representing the Item is replaced during a layout calculation. + *

        + * By default, this value is {@code true}. + * + * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses + * focus. + * + * @see #setPreserveFocusAfterLayout(boolean) + */ + public boolean getPreserveFocusAfterLayout() { + return mPreserveFocusAfterLayout; + } + + /** + * Set whether the RecyclerView should try to keep the same Item focused after a layout + * calculation or not. + *

        + * Usually, LayoutManagers keep focused views visible before and after layout but sometimes, + * views may lose focus during a layout calculation as their state changes or they are replaced + * with another view due to type change or animation. In these cases, RecyclerView can request + * focus on the new view automatically. + * + * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a + * layout calculations. Defaults to true. + * + * @see #getPreserveFocusAfterLayout() + */ + public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) { + mPreserveFocusAfterLayout = preserveFocusAfterLayout; + } + /** * Retrieve the {@link ViewHolder} for the given child view. * @@ -3739,6 +4306,10 @@ public ViewHolder findViewHolderForPosition(int position) { * next layout calculation. If there are pending adapter updates, the return value of this * method may not match your adapter contents. You can use * #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder. + *

        + * When the ItemAnimator is running a change animation, there might be 2 ViewHolders + * with the same layout position representing the same Item. In this case, the updated + * ViewHolder will be returned. * * @param position The position of the item in the data set of the adapter * @return The ViewHolder at position or null if there is no such item @@ -3757,6 +4328,9 @@ public ViewHolder findViewHolderForLayoutPosition(int position) { *

        * This method checks only the children of RecyclerView. If the item at the given * position is not laid out, it will not create a new one. + *

        + * When the ItemAnimator is running a change animation, there might be 2 ViewHolders + * representing the same Item. In this case, the updated ViewHolder will be returned. * * @param position The position of the item in the data set of the adapter * @return The ViewHolder at position or null if there is no such item @@ -3766,25 +4340,37 @@ public ViewHolder findViewHolderForAdapterPosition(int position) { return null; } final int childCount = mChildHelper.getUnfilteredChildCount(); + // hidden VHs are not preferred but if that is the only one we find, we rather return it + ViewHolder hidden = null; for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) { - return holder; + if (mChildHelper.isHidden(holder.itemView)) { + hidden = holder; + } else { + return holder; + } } } - return null; + return hidden; } ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { final int childCount = mChildHelper.getUnfilteredChildCount(); + ViewHolder hidden = null; for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); if (holder != null && !holder.isRemoved()) { if (checkNewPosition) { - if (holder.mPosition == position) { - return holder; + if (holder.mPosition != position) { + continue; } - } else if (holder.getLayoutPosition() == position) { + } else if (holder.getLayoutPosition() != position) { + continue; + } + if (mChildHelper.isHidden(holder.itemView)) { + hidden = holder; + } else { return holder; } } @@ -3792,7 +4378,7 @@ ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { // This method should not query cached views. It creates a problem during adapter updates // when we are dealing with already laid out views. Also, for the public method, it is more // reasonable to return null if position is not laid out. - return null; + return hidden; } /** @@ -3803,20 +4389,29 @@ ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) { * This method checks only the children of RecyclerView. If the item with the given * id is not laid out, it will not create a new one. * + * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the + * same id. In this case, the updated ViewHolder will be returned. + * * @param id The id for the requested item * @return The ViewHolder with the given id or null if there is no such item */ public ViewHolder findViewHolderForItemId(long id) { + if (mAdapter == null || !mAdapter.hasStableIds()) { + return null; + } final int childCount = mChildHelper.getUnfilteredChildCount(); + ViewHolder hidden = null; for (int i = 0; i < childCount; i++) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i)); - if (holder != null && holder.getItemId() == id) { - return holder; + if (holder != null && !holder.isRemoved() && holder.getItemId() == id) { + if (mChildHelper.isHidden(holder.itemView)) { + hidden = holder; + } else { + return holder; + } } } - // this method should not query cached views. They are not children so they - // should not be returned in this public method - return null; + return hidden; } /** @@ -3898,12 +4493,36 @@ public void offsetChildrenHorizontal(int dx) { } } + /** + * Returns the bounds of the view including its decoration and margins. + * + * @param view The view element to check + * @param outBounds A rect that will receive the bounds of the element including its + * decoration and margins. + */ + public void getDecoratedBoundsWithMargins(View view, Rect outBounds) { + getDecoratedBoundsWithMarginsInt(view, outBounds); + } + + static void getDecoratedBoundsWithMarginsInt(View view, Rect outBounds) { + final LayoutParams lp = (LayoutParams) view.getLayoutParams(); + final Rect insets = lp.mDecorInsets; + outBounds.set(view.getLeft() - insets.left - lp.leftMargin, + view.getTop() - insets.top - lp.topMargin, + view.getRight() + insets.right + lp.rightMargin, + view.getBottom() + insets.bottom + lp.bottomMargin); + } + Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { return lp.mDecorInsets; } + if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) { + // changed/invalid items should not be updated until they are rebound. + return lp.mDecorInsets; + } final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); final int decorCount = mItemDecorations.size(); @@ -3944,6 +4563,7 @@ public void onScrolled(int dx, int dy) { } void dispatchOnScrolled(int hresult, int vresult) { + mDispatchScrollCounter ++; // Pass the current scrollX/scrollY values; no actual change in these properties occurred // but some general-purpose code may choose to respond to changes this way. final int scrollX = getScrollX(); @@ -3963,6 +4583,7 @@ void dispatchOnScrolled(int hresult, int vresult) { mScrollListeners.get(i).onScrolled(this, hresult, vresult); } } + mDispatchScrollCounter --; } /** @@ -4019,11 +4640,11 @@ public boolean hasPendingAdapterUpdates() { || mAdapterHelper.hasPendingUpdates(); } - private class ViewFlinger implements Runnable { + class ViewFlinger implements Runnable { private int mLastFlingX; private int mLastFlingY; private ScrollerCompat mScroller; - private Interpolator mInterpolator = sQuinticInterpolator; + Interpolator mInterpolator = sQuinticInterpolator; // When set to true, postOnAnimation callbacks are delayed until the run method completes @@ -4092,8 +4713,7 @@ public void run() { if (!mItemDecorations.isEmpty()) { invalidate(); } - if (ViewCompat.getOverScrollMode(RecyclerView.this) != - ViewCompat.OVER_SCROLL_NEVER) { + if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { considerReleasingGlowsOnScroll(dx, dy); } if (overscrollX != 0 || overscrollY != 0) { @@ -4109,8 +4729,7 @@ public void run() { velY = overscrollY < 0 ? -vel : overscrollY > 0 ? vel : 0; } - if (ViewCompat.getOverScrollMode(RecyclerView.this) != - ViewCompat.OVER_SCROLL_NEVER) { + if (getOverScrollMode() != View.OVER_SCROLL_NEVER) { absorbGlows(velX, velY); } if ((velX != 0 || overscrollX == x || scroller.getFinalX() == 0) && @@ -4135,8 +4754,14 @@ public void run() { if (scroller.isFinished() || !fullyConsumedAny) { setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. + if (ALLOW_THREAD_GAP_WORK) { + mPrefetchRegistry.clearPrefetchPositions(); + } } else { postOnAnimation(); + if (mGapWorker != null) { + mGapWorker.postFromTraversal(RecyclerView.this, dx, dy); + } } } // call this after the onAnimation is complete not to have inconsistent callbacks etc. @@ -4220,6 +4845,11 @@ public void smoothScrollBy(int dx, int dy, int duration) { smoothScrollBy(dx, dy, duration, sQuinticInterpolator); } + public void smoothScrollBy(int dx, int dy, Interpolator interpolator) { + smoothScrollBy(dx, dy, computeScrollDuration(dx, dy, 0, 0), + interpolator == null ? sQuinticInterpolator : interpolator); + } + public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) { if (mInterpolator != interpolator) { mInterpolator = interpolator; @@ -4238,7 +4868,7 @@ public void stop() { } - private void repositionShadowingViews() { + void repositionShadowingViews() { // Fix up shadow views used by change animations int count = mChildHelper.getChildCount(); for (int i = 0; i < count; i++) { @@ -4259,19 +4889,15 @@ private void repositionShadowingViews() { } private class RecyclerViewDataObserver extends AdapterDataObserver { + RecyclerViewDataObserver() { + } + @Override public void onChanged() { assertNotInLayoutOrScroll(null); - if (mAdapter.hasStableIds()) { - // TODO Determine what actually changed. - // This is more important to implement now since this callback will disable all - // animations because we cannot rely on positions. - mState.mStructureChanged = true; - setDataSetChangedAfterLayout(); - } else { - mState.mStructureChanged = true; - setDataSetChangedAfterLayout(); - } + mState.mStructureChanged = true; + + setDataSetChangedAfterLayout(); if (!mAdapterHelper.hasPendingUpdates()) { requestLayout(); } @@ -4310,7 +4936,7 @@ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { } void triggerUpdateProcessor() { - if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { + if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; @@ -4329,20 +4955,43 @@ void triggerUpdateProcessor() { * */ public static class RecycledViewPool { - private SparseArray> mScrap = - new SparseArray>(); - private SparseIntArray mMaxScrap = new SparseIntArray(); - private int mAttachCount = 0; + private static final int DEFAULT_MAX_SCRAP = 20; - private static final int DEFAULT_MAX_SCRAP = 5; + /** + * Tracks both pooled holders, as well as create/bind timing metadata for the given type. + * + * Note that this tracks running averages of create/bind time across all RecyclerViews + * (and, indirectly, Adapters) that use this pool. + * + * 1) This enables us to track average create and bind times across multiple adapters. Even + * though create (and especially bind) may behave differently for different Adapter + * subclasses, sharing the pool is a strong signal that they'll perform similarly, per type. + * + * 2) If {@link #willBindInTime(int, long, long)} returns false for one view, it will return + * false for all other views of its type for the same deadline. This prevents items + * constructed by {@link GapWorker} prefetch from being bound to a lower priority prefetch. + */ + static class ScrapData { + ArrayList mScrapHeap = new ArrayList<>(); + int mMaxScrap = DEFAULT_MAX_SCRAP; + long mCreateRunningAverageNs = 0; + long mBindRunningAverageNs = 0; + } + SparseArray mScrap = new SparseArray<>(); + + private int mAttachCount = 0; public void clear() { - mScrap.clear(); + for (int i = 0; i < mScrap.size(); i++) { + ScrapData data = mScrap.valueAt(i); + data.mScrapHeap.clear(); + } } public void setMaxRecycledViews(int viewType, int max) { - mMaxScrap.put(viewType, max); - final ArrayList scrapHeap = mScrap.get(viewType); + ScrapData scrapData = getScrapDataForType(viewType); + scrapData.mMaxScrap = max; + final ArrayList scrapHeap = scrapData.mScrapHeap; if (scrapHeap != null) { while (scrapHeap.size() > max) { scrapHeap.remove(scrapHeap.size() - 1); @@ -4350,13 +4999,18 @@ public void setMaxRecycledViews(int viewType, int max) { } } + /** + * Returns the current number of Views held by the RecycledViewPool of the given view type. + */ + public int getRecycledViewCount(int viewType) { + return getScrapDataForType(viewType).mScrapHeap.size(); + } + public ViewHolder getRecycledView(int viewType) { - final ArrayList scrapHeap = mScrap.get(viewType); - if (scrapHeap != null && !scrapHeap.isEmpty()) { - final int index = scrapHeap.size() - 1; - final ViewHolder scrap = scrapHeap.get(index); - scrapHeap.remove(index); - return scrap; + final ScrapData scrapData = mScrap.get(viewType); + if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) { + final ArrayList scrapHeap = scrapData.mScrapHeap; + return scrapHeap.remove(scrapHeap.size() - 1); } return null; } @@ -4364,7 +5018,7 @@ public ViewHolder getRecycledView(int viewType) { int size() { int count = 0; for (int i = 0; i < mScrap.size(); i ++) { - ArrayList viewHolders = mScrap.valueAt(i); + ArrayList viewHolders = mScrap.valueAt(i).mScrapHeap; if (viewHolders != null) { count += viewHolders.size(); } @@ -4374,8 +5028,8 @@ int size() { public void putRecycledView(ViewHolder scrap) { final int viewType = scrap.getItemViewType(); - final ArrayList scrapHeap = getScrapHeapForType(viewType); - if (mMaxScrap.get(viewType) <= scrapHeap.size()) { + final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap; + if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) { return; } if (DEBUG && scrapHeap.contains(scrap)) { @@ -4385,6 +5039,35 @@ public void putRecycledView(ViewHolder scrap) { scrapHeap.add(scrap); } + long runningAverage(long oldAverage, long newValue) { + if (oldAverage == 0) { + return newValue; + } + return (oldAverage / 4 * 3) + (newValue / 4); + } + + void factorInCreateTime(int viewType, long createTimeNs) { + ScrapData scrapData = getScrapDataForType(viewType); + scrapData.mCreateRunningAverageNs = runningAverage( + scrapData.mCreateRunningAverageNs, createTimeNs); + } + + void factorInBindTime(int viewType, long bindTimeNs) { + ScrapData scrapData = getScrapDataForType(viewType); + scrapData.mBindRunningAverageNs = runningAverage( + scrapData.mBindRunningAverageNs, bindTimeNs); + } + + boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) { + long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs; + return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); + } + + boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) { + long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs; + return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs); + } + void attach(Adapter adapter) { mAttachCount++; } @@ -4418,16 +5101,72 @@ void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, } } - private ArrayList getScrapHeapForType(int viewType) { - ArrayList scrap = mScrap.get(viewType); - if (scrap == null) { - scrap = new ArrayList<>(); - mScrap.put(viewType, scrap); - if (mMaxScrap.indexOfKey(viewType) < 0) { - mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP); + private ScrapData getScrapDataForType(int viewType) { + ScrapData scrapData = mScrap.get(viewType); + if (scrapData == null) { + scrapData = new ScrapData(); + mScrap.put(viewType, scrapData); + } + return scrapData; + } + } + + /** + * Utility method for finding an internal RecyclerView, if present + */ + @Nullable + static RecyclerView findNestedRecyclerView(@NonNull View view) { + if (!(view instanceof ViewGroup)) { + return null; + } + if (view instanceof RecyclerView) { + return (RecyclerView) view; + } + final ViewGroup parent = (ViewGroup) view; + final int count = parent.getChildCount(); + for (int i = 0; i < count; i++) { + final View child = parent.getChildAt(i); + final RecyclerView descendant = findNestedRecyclerView(child); + if (descendant != null) { + return descendant; + } + } + return null; + } + + /** + * Utility method for clearing holder's internal RecyclerView, if present + */ + static void clearNestedRecyclerViewIfNotNested(@NonNull ViewHolder holder) { + if (holder.mNestedRecyclerView != null) { + View item = holder.mNestedRecyclerView.get(); + while (item != null) { + if (item == holder.itemView) { + return; // match found, don't need to clear + } + + ViewParent parent = item.getParent(); + if (parent instanceof View) { + item = (View) parent; + } else { + item = null; } } - return scrap; + holder.mNestedRecyclerView = null; // not nested + } + } + + /** + * Time base for deadline-aware work scheduling. Overridable for testing. + * + * Will return 0 to avoid cost of System.nanoTime where deadline-aware work scheduling + * isn't relevant. + */ + long getNanoTime() { + if (ALLOW_THREAD_GAP_WORK) { + return System.nanoTime(); + } else { + return 0; } } @@ -4446,20 +5185,21 @@ private ArrayList getScrapHeapForType(int viewType) { */ public final class Recycler { final ArrayList mAttachedScrap = new ArrayList<>(); - private ArrayList mChangedScrap = null; + ArrayList mChangedScrap = null; final ArrayList mCachedViews = new ArrayList(); private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); - private int mViewCacheMax = DEFAULT_CACHE_SIZE; + private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; + int mViewCacheMax = DEFAULT_CACHE_SIZE; - private RecycledViewPool mRecyclerPool; + RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension; - private static final int DEFAULT_CACHE_SIZE = 2; + static final int DEFAULT_CACHE_SIZE = 2; /** * Clear scrap views out of this recycler. Detached views contained within a @@ -4476,9 +5216,17 @@ public void clear() { * @param viewCount Number of views to keep before sending views to the shared pool */ public void setViewCacheSize(int viewCount) { - mViewCacheMax = viewCount; + mRequestedCacheMax = viewCount; + updateViewCacheSize(); + } + + void updateViewCacheSize() { + int extraCache = mLayout != null ? mLayout.mPrefetchMaxCountObserved : 0; + mViewCacheMax = mRequestedCacheMax + extraCache; + // first, try the views that can be recycled - for (int i = mCachedViews.size() - 1; i >= 0 && mCachedViews.size() > viewCount; i--) { + for (int i = mCachedViews.size() - 1; + i >= 0 && mCachedViews.size() > mViewCacheMax; i--) { recycleCachedViewAt(i); } } @@ -4505,7 +5253,7 @@ boolean validateViewHolderForOffsetPosition(ViewHolder holder) { // if it is not removed, verify the type and id. if (holder.isRemoved()) { if (DEBUG && !mState.isPreLayout()) { - throw new IllegalStateException("should not receive a removed view unelss it" + throw new IllegalStateException("should not receive a removed view unless it" + " is pre layout"); } return mState.isPreLayout(); @@ -4527,6 +5275,38 @@ boolean validateViewHolderForOffsetPosition(ViewHolder holder) { return true; } + /** + * Attempts to bind view, and account for relevant timing information. If + * deadlineNs != FOREVER_NS, this method may fail to bind, and return false. + * + * @param holder Holder to be bound. + * @param offsetPosition Position of item to be bound. + * @param position Pre-layout position of item to be bound. + * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should + * complete. If FOREVER_NS is passed, this method will not fail to + * bind the holder. + * @return + */ + private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, + int position, long deadlineNs) { + holder.mOwnerRecyclerView = RecyclerView.this; + final int viewType = holder.getItemViewType(); + long startBindNs = getNanoTime(); + if (deadlineNs != FOREVER_NS + && !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) { + // abort - we have a deadline we can't meet + return false; + } + mAdapter.bindViewHolder(holder, offsetPosition); + long endBindNs = getNanoTime(); + mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs); + attachAccessibilityDelegate(holder.itemView); + if (mState.isPreLayout()) { + holder.mPreLayoutPosition = position; + } + return true; + } + /** * Binds the given View to the position. The View can be a View previously retrieved via * {@link #getViewForPosition(int)} or created by @@ -4555,12 +5335,7 @@ public void bindViewToPosition(View view, int position) { + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } - holder.mOwnerRecyclerView = RecyclerView.this; - mAdapter.bindViewHolder(holder, offsetPosition); - attachAccessibilityDelegate(view); - if (mState.isPreLayout()) { - holder.mPreLayoutPosition = position; - } + tryBindViewHolderByDeadline(holder, offsetPosition, position, FOREVER_NS); final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; @@ -4627,23 +5402,47 @@ public View getViewForPosition(int position) { } View getViewForPosition(int position, boolean dryRun) { + return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; + } + + /** + * Attempts to get the ViewHolder for the given position, either from the Recycler scrap, + * cache, the RecycledViewPool, or creating it directly. + *

        + * If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return + * rather than constructing or binding a ViewHolder if it doesn't think it has time. + * If a ViewHolder must be constructed and not enough time remains, null is returned. If a + * ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is + * returned. Use {@link ViewHolder#isBound()} on the returned object to check for this. + * + * @param position Position of ViewHolder to be returned. + * @param dryRun True if the ViewHolder should not be removed from scrap/cache/ + * @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should + * complete. If FOREVER_NS is passed, this method will not fail to + * create/bind the holder if needed. + * + * @return ViewHolder for requested position + */ + @Nullable + ViewHolder tryGetViewHolderForPositionByDeadline(int position, + boolean dryRun, long deadlineNs) { if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } - boolean fromScrap = false; + boolean fromScrapOrHiddenOrCache = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); - fromScrap = holder != null; + fromScrapOrHiddenOrCache = holder != null; } - // 1) Find from scrap by position + // 1) Find by position from scrap/hidden list/cache if (holder == null) { - holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); + holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { - // recycle this scrap + // recycle holder (and unscrap if relevant) since it can't be used if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. @@ -4658,7 +5457,7 @@ View getViewForPosition(int position, boolean dryRun) { } holder = null; } else { - fromScrap = true; + fromScrapOrHiddenOrCache = true; } } } @@ -4667,17 +5466,18 @@ View getViewForPosition(int position, boolean dryRun) { if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." - + "state:" + mState.getItemCount() + " tag " + getTag() + " adapter " + getAdapter()); + + "state:" + mState.getItemCount()); } final int type = mAdapter.getItemViewType(offsetPosition); - // 2) Find from scrap via stable ids, if exists + // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { - holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); + holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), + type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; - fromScrap = true; + fromScrapOrHiddenOrCache = true; } } if (holder == null && mViewCacheExtension != null) { @@ -4697,12 +5497,10 @@ View getViewForPosition(int position, boolean dryRun) { } } } - if (holder == null) { // fallback to recycler - // try recycler. - // Head to the shared pool. + if (holder == null) { // fallback to pool if (DEBUG) { - Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " - + "pool"); + Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { @@ -4713,9 +5511,25 @@ View getViewForPosition(int position, boolean dryRun) { } } if (holder == null) { + long start = getNanoTime(); + if (deadlineNs != FOREVER_NS + && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { + // abort - we have a deadline we can't meet + return null; + } holder = mAdapter.createViewHolder(RecyclerView.this, type); + if (ALLOW_THREAD_GAP_WORK) { + // only bother finding nested RV if prefetching + RecyclerView innerView = findNestedRecyclerView(holder.itemView); + if (innerView != null) { + holder.mNestedRecyclerView = new WeakReference<>(innerView); + } + } + + long end = getNanoTime(); + mRecyclerPool.factorInCreateTime(type, end - start); if (DEBUG) { - Log.d(TAG, "getViewForPosition created new ViewHolder"); + Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } @@ -4723,7 +5537,7 @@ View getViewForPosition(int position, boolean dryRun) { // This is very ugly but the only place we can grab this information // before the View is rebound and returned to the LayoutManager for post layout ops. // We don't need this in pre-layout since the VH is not updated by the LM. - if (fromScrap && !mState.isPreLayout() && holder + if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (mState.mRunSimpleAnimations) { @@ -4746,13 +5560,7 @@ View getViewForPosition(int position, boolean dryRun) { + " come here only in pre-layout. Holder: " + holder); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); - holder.mOwnerRecyclerView = RecyclerView.this; - mAdapter.bindViewHolder(holder, offsetPosition); - attachAccessibilityDelegate(holder.itemView); - bound = true; - if (mState.isPreLayout()) { - holder.mPreLayoutPosition = position; - } + bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); @@ -4767,8 +5575,8 @@ View getViewForPosition(int position, boolean dryRun) { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; - rvLayoutParams.mPendingInvalidate = fromScrap && bound; - return holder.itemView; + rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; + return holder; } private void attachAccessibilityDelegate(View itemView) { @@ -4852,6 +5660,9 @@ void recycleAndClearCachedViews() { recycleCachedViewAt(i); } mCachedViews.clear(); + if (ALLOW_THREAD_GAP_WORK) { + mPrefetchRegistry.clearPrefetchPositions(); + } } /** @@ -4873,7 +5684,7 @@ void recycleCachedViewAt(int cachedViewIndex) { if (DEBUG) { Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder); } - addViewHolderToRecycledViewPool(viewHolder); + addViewHolderToRecycledViewPool(viewHolder, true); mCachedViews.remove(cachedViewIndex); } @@ -4912,25 +5723,51 @@ void recycleViewHolderInternal(ViewHolder holder) { holder); } if (forceRecycle || holder.isRecyclable()) { - if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED - | ViewHolder.FLAG_UPDATE)) { + if (mViewCacheMax > 0 + && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID + | ViewHolder.FLAG_REMOVED + | ViewHolder.FLAG_UPDATE + | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // Retire oldest cached view - final int cachedViewSize = mCachedViews.size(); - if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { + int cachedViewSize = mCachedViews.size(); + if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); + cachedViewSize--; } - if (cachedViewSize < mViewCacheMax) { - mCachedViews.add(holder); - cached = true; + + int targetCacheIndex = cachedViewSize; + if (ALLOW_THREAD_GAP_WORK + && cachedViewSize > 0 + && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { + // when adding the view, skip past most recently prefetched views + int cacheIndex = cachedViewSize - 1; + while (cacheIndex >= 0) { + int cachedPos = mCachedViews.get(cacheIndex).mPosition; + if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { + break; + } + cacheIndex--; + } + targetCacheIndex = cacheIndex + 1; } + mCachedViews.add(targetCacheIndex, holder); + cached = true; } if (!cached) { - addViewHolderToRecycledViewPool(holder); + addViewHolderToRecycledViewPool(holder, true); recycled = true; } - } else if (DEBUG) { - Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " - + "re-visit here. We are still removing it from animation lists"); + } else { + // NOTE: A view can fail to be recycled when it is scrolled off while an animation + // runs. In this case, the item is eventually recycled by + // ItemAnimatorRestoreListener#onAnimationFinished. + + // TODO: consider cancelling an animation when an item is removed scrollBy, + // to return it to the pool faster + if (DEBUG) { + Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " + + "re-visit here. We are still removing it from animation lists"); + } } // even if the holder is not removed, we still call this method so that it is removed // from view holder lists. @@ -4940,9 +5777,20 @@ void recycleViewHolderInternal(ViewHolder holder) { } } - void addViewHolderToRecycledViewPool(ViewHolder holder) { + /** + * Prepares the ViewHolder to be removed/recycled, and inserts it into the RecycledViewPool. + * + * Pass false to dispatchRecycled for views that have not been bound. + * + * @param holder Holder to be added to the pool. + * @param dispatchRecycled True to dispatch View recycled callbacks. + */ + void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) { + clearNestedRecyclerViewIfNotNested(holder); ViewCompat.setAccessibilityDelegate(holder.itemView, null); - dispatchViewRecycled(holder); + if (dispatchRecycled) { + dispatchViewRecycled(holder); + } holder.mOwnerRecyclerView = null; getRecycledViewPool().putRecycledView(holder); } @@ -5053,15 +5901,13 @@ ViewHolder getChangedScrapViewForPosition(int position) { } /** - * Returns a scrap view for the position. If type is not INVALID_TYPE, it also checks if - * ViewHolder's type matches the provided type. + * Returns a view for the position either from attach scrap, hidden children, or cache. * * @param position Item position - * @param type View type * @param dryRun Does a dry run, finds the ViewHolder but does not remove * @return a ViewHolder that can be re-used for this position. */ - ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { + ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) { final int scrapCount = mAttachedScrap.size(); // Try first for an exact, non-invalid match from scrap. @@ -5069,19 +5915,13 @@ ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { final ViewHolder holder = mAttachedScrap.get(i); if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position && !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) { - if (type != INVALID_TYPE && holder.getItemViewType() != type) { - Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" + - " wrong view type! (found " + holder.getItemViewType() + - " but expected " + type + ")"); - break; - } holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP); return holder; } } if (!dryRun) { - View view = mChildHelper.findHiddenNonRemovedView(position, type); + View view = mChildHelper.findHiddenNonRemovedView(position); if (view != null) { // This View is good to be used. We just need to unhide, detach and move to the // scrap list. @@ -5105,14 +5945,14 @@ ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { for (int i = 0; i < cacheSize; i++) { final ViewHolder holder = mCachedViews.get(i); // invalid view holders may be in cache if adapter has stable ids as they can be - // retrieved via getScrapViewForId + // retrieved via getScrapOrCachedViewForId if (!holder.isInvalid() && holder.getLayoutPosition() == position) { if (!dryRun) { mCachedViews.remove(i); } if (DEBUG) { - Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type + - ") found match in cache: " + holder); + Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position + + ") found match in cache: " + holder); } return holder; } @@ -5120,7 +5960,7 @@ ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) { return null; } - ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { + ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) { // Look in our attached views first final int count = mAttachedScrap.size(); for (int i = count - 1; i >= 0; i--) { @@ -5166,6 +6006,7 @@ ViewHolder getScrapViewForId(long id, int type, boolean dryRun) { return holder; } else if (!dryRun) { recycleCachedViewAt(i); + return null; } } } @@ -5360,7 +6201,7 @@ void markItemDecorInsetsDirty() { /** * ViewCacheExtension is a helper class to provide an additional layer of view caching that can - * ben controlled by the developer. + * be controlled by the developer. *

        * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and * first level cache to find a matching View. If it cannot find a suitable View, Recycler will @@ -5439,7 +6280,7 @@ public static abstract class Adapter { * have the updated adapter position. * * Override {@link #onBindViewHolder(ViewHolder, int, List)} instead if Adapter can - * handle effcient partial bind. + * handle efficient partial bind. * * @param holder The ViewHolder which should be updated to represent the contents of the * item at the given position in the data set. @@ -5512,6 +6353,10 @@ public final void bindViewHolder(VH holder, int position) { TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); holder.clearPayload(); + final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams(); + if (layoutParams instanceof RecyclerView.LayoutParams) { + ((LayoutParams) layoutParams).mInsetsDirty = true; + } TraceCompat.endSection(); } @@ -5560,7 +6405,7 @@ public long getItemId(int position) { } /** - * Returns the total number of items in the data set hold by the adapter. + * Returns the total number of items in the data set held by the adapter. * * @return The total number of items in this adapter. */ @@ -5653,7 +6498,7 @@ public void onViewAttachedToWindow(VH holder) { * *

        Becoming detached from the window is not necessarily a permanent condition; * the consumer of an Adapter's views may choose to cache views offscreen while they - * are not visible, attaching an detaching them as appropriate.

        + * are not visible, attaching and detaching them as appropriate.

        * * @param holder Holder of the view being detached */ @@ -5674,7 +6519,7 @@ public final boolean hasObservers() { * *

        The adapter may publish a variety of events describing specific changes. * Not all adapters may support all change types and some may fall back to a generic - * {@link android.support.v7.widget.RecyclerView.AdapterDataObserver#onChanged() + * {@link org.telegram.messenger.support.widget.RecyclerView.AdapterDataObserver#onChanged() * "something changed"} event if more specific data is not available.

        * *

        Components registering observers with an adapter are responsible for @@ -5820,7 +6665,7 @@ public void notifyItemRangeChanged(int positionStart, int itemCount) { /** * Notify any registered observers that the itemCount items starting at - * positionpositionStart have changed. An optional payload can be + * position positionStart have changed. An optional payload can be * passed to each changed item. * *

        This is an item change event, not a structural change event. It indicates that any @@ -5934,7 +6779,7 @@ public void notifyItemRangeRemoved(int positionStart, int itemCount) { } } - private void dispatchChildDetached(View child) { + void dispatchChildDetached(View child) { final ViewHolder viewHolder = getChildViewHolderInt(child); onChildDetachedFromWindow(child); if (mAdapter != null && viewHolder != null) { @@ -5948,7 +6793,7 @@ private void dispatchChildDetached(View child) { } } - private void dispatchChildAttached(View child) { + void dispatchChildAttached(View child) { final ViewHolder viewHolder = getChildViewHolderInt(child); onChildAttachedToWindow(child); if (mAdapter != null && viewHolder != null) { @@ -5985,11 +6830,11 @@ public static abstract class LayoutManager { @Nullable SmoothScroller mSmoothScroller; - private boolean mRequestedSimpleAnimations = false; + boolean mRequestedSimpleAnimations = false; boolean mIsAttachedToWindow = false; - private boolean mAutoMeasure = false; + boolean mAutoMeasure = false; /** * LayoutManager has its own more strict measurement cache to avoid re-measuring a child @@ -5997,6 +6842,23 @@ public static abstract class LayoutManager { */ private boolean mMeasurementCacheEnabled = true; + private boolean mItemPrefetchEnabled = true; + + /** + * Written by {@link GapWorker} when prefetches occur to track largest number of view ever + * requested by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)} or + * {@link #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry)} call. + * + * If expanded by a {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, + * will be reset upon layout to prevent initial prefetches (often large, since they're + * proportional to expected child count) from expanding cache permanently. + */ + int mPrefetchMaxCountObserved; + + /** + * If true, mPrefetchMaxCountObserved is only valid until next layout, and should be reset. + */ + boolean mPrefetchMaxObservedInInitialPrefetch; /** * These measure specs might be the measure specs that were passed into RecyclerView's @@ -6012,6 +6874,26 @@ public static abstract class LayoutManager { private int mWidthMode, mHeightMode; private int mWidth, mHeight; + + /** + * Interface for LayoutManagers to request items to be prefetched, based on position, with + * specified distance from viewport, which indicates priority. + * + * @see LayoutManager#collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) + * @see LayoutManager#collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) + */ + public interface LayoutPrefetchRegistry { + /** + * Requests an an item to be prefetched, based on position, with a specified distance, + * indicating priority. + * + * @param layoutPosition Position of the item to prefetch. + * @param pixelDistance Distance from the current viewport to the bounds of the item, + * must be non-negative. + */ + void addPosition(int layoutPosition, int pixelDistance); + } + void setRecyclerView(RecyclerView recyclerView) { if (recyclerView == null) { mRecyclerView = null; @@ -6068,22 +6950,19 @@ void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) { for (int i = 0; i < count; i++) { View child = getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - int left = getDecoratedLeft(child) - lp.leftMargin; - int right = getDecoratedRight(child) + lp.rightMargin; - int top = getDecoratedTop(child) - lp.topMargin; - int bottom = getDecoratedBottom(child) + lp.bottomMargin; - if (left < minX) { - minX = left; + final Rect bounds = mRecyclerView.mTempRect; + getDecoratedBoundsWithMargins(child, bounds); + if (bounds.left < minX) { + minX = bounds.left; } - if (right > maxX) { - maxX = right; + if (bounds.right > maxX) { + maxX = bounds.right; } - if (top < minY) { - minY = top; + if (bounds.top < minY) { + minY = bounds.top; } - if (bottom > maxY) { - maxY = bottom; + if (bounds.bottom > maxY) { + maxY = bounds.bottom; } } mRecyclerView.mTempRect.set(minX, minY, maxX, maxY); @@ -6280,6 +7159,98 @@ public boolean supportsPredictiveItemAnimations() { return false; } + /** + * Sets whether the LayoutManager should be queried for views outside of + * its viewport while the UI thread is idle between frames. + * + *

        If enabled, the LayoutManager will be queried for items to inflate/bind in between + * view system traversals on devices running API 21 or greater. Default value is true.

        + * + *

        On platforms API level 21 and higher, the UI thread is idle between passing a frame + * to RenderThread and the starting up its next frame at the next VSync pulse. By + * prefetching out of window views in this time period, delays from inflation and view + * binding are much less likely to cause jank and stuttering during scrolls and flings.

        + * + *

        While prefetch is enabled, it will have the side effect of expanding the effective + * size of the View cache to hold prefetched views.

        + * + * @param enabled True if items should be prefetched in between traversals. + * + * @see #isItemPrefetchEnabled() + */ + public final void setItemPrefetchEnabled(boolean enabled) { + if (enabled != mItemPrefetchEnabled) { + mItemPrefetchEnabled = enabled; + mPrefetchMaxCountObserved = 0; + if (mRecyclerView != null) { + mRecyclerView.mRecycler.updateViewCacheSize(); + } + } + } + + /** + * Sets whether the LayoutManager should be queried for views outside of + * its viewport while the UI thread is idle between frames. + * + * @see #setItemPrefetchEnabled(boolean) + * + * @return true if item prefetch is enabled, false otherwise + */ + public final boolean isItemPrefetchEnabled() { + return mItemPrefetchEnabled; + } + + /** + * Gather all positions from the LayoutManager to be prefetched, given specified momentum. + * + *

        If item prefetch is enabled, this method is called in between traversals to gather + * which positions the LayoutManager will soon need, given upcoming movement in subsequent + * traversals.

        + * + *

        The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for each + * item to be prepared, and these positions will have their ViewHolders created and bound, + * if there is sufficient time available, in advance of being needed by a + * scroll or layout.

        + * + * @param dx X movement component. + * @param dy Y movement component. + * @param state State of RecyclerView + * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. + * + * @see #isItemPrefetchEnabled() + * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry) + */ + public void collectAdjacentPrefetchPositions(int dx, int dy, State state, + LayoutPrefetchRegistry layoutPrefetchRegistry) {} + + /** + * Gather all positions from the LayoutManager to be prefetched in preperation for its + * RecyclerView to come on screen, due to the movement of another, containing RecyclerView. + * + *

        This method is only called when a RecyclerView is nested in another RecyclerView.

        + * + *

        If item prefetch is enabled for this LayoutManager, as well in another containing + * LayoutManager, this method is called in between draw traversals to gather + * which positions this LayoutManager will first need, once it appears on the screen.

        + * + *

        For example, if this LayoutManager represents a horizontally scrolling list within a + * vertically scrolling LayoutManager, this method would be called when the horizontal list + * is about to come onscreen.

        + * + *

        The LayoutManager should call {@link LayoutPrefetchRegistry#addPosition(int, int)} for each + * item to be prepared, and these positions will have their ViewHolders created and bound, + * if there is sufficient time available, in advance of being needed by a + * scroll or layout.

        + * + * @param adapterItemCount number of items in the associated adapter. + * @param layoutPrefetchRegistry PrefetchRegistry to add prefetch entries into. + * + * @see #isItemPrefetchEnabled() + * @see #collectAdjacentPrefetchPositions(int, int, State, LayoutPrefetchRegistry) + */ + public void collectInitialPrefetchPositions(int adapterItemCount, + LayoutPrefetchRegistry layoutPrefetchRegistry) {} + void dispatchAttachedToWindow(RecyclerView view) { mIsAttachedToWindow = true; onAttachedToWindow(view); @@ -6340,11 +7311,16 @@ public boolean removeCallbacks(Runnable action) { /** * Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView * is attached to a window. - * - *

        Subclass implementations should always call through to the superclass implementation. - *

        + *

        + * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not + * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was + * not requested on the RecyclerView while it was detached. + *

        + * Subclass implementations should always call through to the superclass implementation. * * @param view The RecyclerView this LayoutManager is bound to + * + * @see #onDetachedFromWindow(RecyclerView, Recycler) */ @CallSuper public void onAttachedToWindow(RecyclerView view) { @@ -6362,13 +7338,25 @@ public void onDetachedFromWindow(RecyclerView view) { /** * Called when this LayoutManager is detached from its parent RecyclerView or when * its parent RecyclerView is detached from its window. - * - *

        Subclass implementations should always call through to the superclass implementation. - *

        + *

        + * LayoutManager should clear all of its View references as another LayoutManager might be + * assigned to the RecyclerView. + *

        + * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not + * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was + * not requested on the RecyclerView while it was detached. + *

        + * If your LayoutManager has View references that it cleans in on-detach, it should also + * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when + * RecyclerView is re-attached. + *

        + * Subclass implementations should always call through to the superclass implementation. * * @param view The RecyclerView this LayoutManager is bound to * @param recycler The recycler to use if you prefer to recycle your children instead of * keeping them around. + * + * @see #onAttachedToWindow(RecyclerView) */ @CallSuper public void onDetachedFromWindow(RecyclerView view, Recycler recycler) { @@ -6439,6 +7427,20 @@ public void onLayoutChildren(Recycler recycler, State state) { Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); } + /** + * Called after a full layout calculation is finished. The layout calculation may include + * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or + * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call. + * This method will be called at the end of {@link View#layout(int, int, int, int)} call. + *

        + * This is a good place for the LayoutManager to do some cleanup like pending scroll + * position, saved state etc. + * + * @param state Transient state of RecyclerView + */ + public void onLayoutCompleted(State state) { + } + /** * Create a default LayoutParams object for a child of the RecyclerView. * @@ -6594,7 +7596,7 @@ public void smoothScrollToPosition(RecyclerView recyclerView, State state, /** *

        Starts a smooth scroll using the provided SmoothScroller.

        *

        Calling this method will cancel any previous smooth scroll request.

        - * @param smoothScroller Unstance which defines how smooth scroll should be animated + * @param smoothScroller Instance which defines how smooth scroll should be animated */ public void startSmoothScroll(SmoothScroller smoothScroller) { if (mSmoothScroller != null && smoothScroller != mSmoothScroller @@ -7074,11 +8076,11 @@ public View getChildAt(int index) { * {@link #setAutoMeasureEnabled(boolean)}. *

        * When RecyclerView is running a layout, this value is always set to - * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode. + * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. * * @return Width measure spec mode. * - * @see MeasureSpec#getMode(int) + * @see View.MeasureSpec#getMode(int) * @see View#onMeasure(int, int) */ public int getWidthMode() { @@ -7092,11 +8094,11 @@ public int getWidthMode() { * {@link #setAutoMeasureEnabled(boolean)}. *

        * When RecyclerView is running a layout, this value is always set to - * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode. + * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode. * * @return Height measure spec mode. * - * @see MeasureSpec#getMode(int) + * @see View.MeasureSpec#getMode(int) * @see View#onMeasure(int, int) */ public int getHeightMode() { @@ -7502,7 +8504,7 @@ public void measureChildWithMargins(View child, int widthUsed, int heightUsed) { * * @param parentSize Size of the parent view where the child will be placed * @param padding Total space currently consumed by other elements of the parent - * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT. + * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. * Generally obtained from the child view's LayoutParams * @param canScroll true if the parent RecyclerView can scroll in this dimension * @@ -7520,7 +8522,7 @@ public static int getChildMeasureSpec(int parentSize, int padding, int childDime resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else { - // FILL_PARENT can't be applied since we can scroll in this dimension, wrap + // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap // instead using UNSPECIFIED. resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; @@ -7529,7 +8531,7 @@ public static int getChildMeasureSpec(int parentSize, int padding, int childDime if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.FILL_PARENT) { + } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; // TODO this should be my spec. resultMode = MeasureSpec.EXACTLY; @@ -7547,7 +8549,7 @@ public static int getChildMeasureSpec(int parentSize, int padding, int childDime * @param parentSize Size of the parent view where the child will be placed * @param parentMode The measurement spec mode of the parent * @param padding Total space currently consumed by other elements of parent - * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT. + * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT. * Generally obtained from the child view's LayoutParams * @param canScroll true if the parent RecyclerView can scroll in this dimension * @@ -7562,7 +8564,7 @@ public static int getChildMeasureSpec(int parentSize, int parentMode, int paddin if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.FILL_PARENT){ + } else if (childDimension == LayoutParams.MATCH_PARENT) { switch (parentMode) { case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: @@ -7582,7 +8584,7 @@ public static int getChildMeasureSpec(int parentSize, int parentMode, int paddin if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; - } else if (childDimension == LayoutParams.FILL_PARENT) { + } else if (childDimension == LayoutParams.MATCH_PARENT) { resultSize = size; resultMode = parentMode; } else if (childDimension == LayoutParams.WRAP_CONTENT) { @@ -7636,6 +8638,8 @@ public int getDecoratedMeasuredHeight(View child) { * ignore decoration insets within measurement and layout code. See the following * methods:

        *
          + *
        • {@link #layoutDecoratedWithMargins(View, int, int, int, int)}
        • + *
        • {@link #getDecoratedBoundsWithMargins(View, Rect)}
        • *
        • {@link #measureChild(View, int, int)}
        • *
        • {@link #measureChildWithMargins(View, int, int)}
        • *
        • {@link #getDecoratedLeft(View)}
        • @@ -7653,6 +8657,7 @@ public int getDecoratedMeasuredHeight(View child) { * @param bottom Bottom edge, with item decoration insets included * * @see View#layout(int, int, int, int) + * @see #layoutDecoratedWithMargins(View, int, int, int, int) */ public void layoutDecorated(View child, int left, int top, int right, int bottom) { final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; @@ -7660,6 +8665,92 @@ public void layoutDecorated(View child, int left, int top, int right, int bottom bottom - insets.bottom); } + /** + * Lay out the given child view within the RecyclerView using coordinates that + * include any current {@link ItemDecoration ItemDecorations} and margins. + * + *

          LayoutManagers should prefer working in sizes and coordinates that include + * item decoration insets whenever possible. This allows the LayoutManager to effectively + * ignore decoration insets within measurement and layout code. See the following + * methods:

          + *
            + *
          • {@link #layoutDecorated(View, int, int, int, int)}
          • + *
          • {@link #measureChild(View, int, int)}
          • + *
          • {@link #measureChildWithMargins(View, int, int)}
          • + *
          • {@link #getDecoratedLeft(View)}
          • + *
          • {@link #getDecoratedTop(View)}
          • + *
          • {@link #getDecoratedRight(View)}
          • + *
          • {@link #getDecoratedBottom(View)}
          • + *
          • {@link #getDecoratedMeasuredWidth(View)}
          • + *
          • {@link #getDecoratedMeasuredHeight(View)}
          • + *
          + * + * @param child Child to lay out + * @param left Left edge, with item decoration insets and left margin included + * @param top Top edge, with item decoration insets and top margin included + * @param right Right edge, with item decoration insets and right margin included + * @param bottom Bottom edge, with item decoration insets and bottom margin included + * + * @see View#layout(int, int, int, int) + * @see #layoutDecorated(View, int, int, int, int) + */ + public void layoutDecoratedWithMargins(View child, int left, int top, int right, + int bottom) { + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); + final Rect insets = lp.mDecorInsets; + child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin, + right - insets.right - lp.rightMargin, + bottom - insets.bottom - lp.bottomMargin); + } + + /** + * Calculates the bounding box of the View while taking into account its matrix changes + * (translation, scale etc) with respect to the RecyclerView. + *

          + * If {@code includeDecorInsets} is {@code true}, they are applied first before applying + * the View's matrix so that the decor offsets also go through the same transformation. + * + * @param child The ItemView whose bounding box should be calculated. + * @param includeDecorInsets True if the decor insets should be included in the bounding box + * @param out The rectangle into which the output will be written. + */ + public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) { + if (includeDecorInsets) { + Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets; + out.set(-insets.left, -insets.top, + child.getWidth() + insets.right, child.getHeight() + insets.bottom); + } else { + out.set(0, 0, child.getWidth(), child.getHeight()); + } + + if (mRecyclerView != null) { + final Matrix childMatrix = ViewCompat.getMatrix(child); + if (childMatrix != null && !childMatrix.isIdentity()) { + final RectF tempRectF = mRecyclerView.mTempRectF; + tempRectF.set(out); + childMatrix.mapRect(tempRectF); + out.set( + (int) Math.floor(tempRectF.left), + (int) Math.floor(tempRectF.top), + (int) Math.ceil(tempRectF.right), + (int) Math.ceil(tempRectF.bottom) + ); + } + } + out.offset(child.getLeft(), child.getTop()); + } + + /** + * Returns the bounds of the view including its decoration and margins. + * + * @param view The view element to check + * @param outBounds A rect that will receive the bounds of the element including its + * decoration and margins. + */ + public void getDecoratedBoundsWithMargins(View view, Rect outBounds) { + RecyclerView.getDecoratedBoundsWithMarginsInt(view, outBounds); + } + /** * Returns the left edge of the given child view within its parent, offset by any applied * {@link ItemDecoration ItemDecorations}. @@ -8513,26 +9604,6 @@ public boolean performAccessibilityActionForItem(Recycler recycler, State state, return false; } - /** - * Parse the xml attributes to get the most common properties used by layout managers. - * - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_spanCount - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout - * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd - * - * @return an object containing the properties as specified in the attrs. - */ - public static Properties getProperties(Context context, AttributeSet attrs, - int defStyleAttr, int defStyleRes) { - Properties properties = new Properties(); - properties.orientation = VERTICAL; - properties.spanCount = 1; - properties.reverseLayout = false; - properties.stackFromEnd = false; - return properties; - } - void setExactMeasureSpecsFrom(RecyclerView recyclerView) { setMeasureSpecs( MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY), @@ -8834,6 +9905,7 @@ public interface OnChildAttachStateChangeListener { */ public static abstract class ViewHolder { public final View itemView; + WeakReference mNestedRecyclerView; int mPosition = NO_POSITION; int mOldPosition = NO_POSITION; long mItemId = NO_ID; @@ -8921,6 +9993,8 @@ public static abstract class ViewHolder { */ static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12; + static final int PENDING_ACCESSIBILITY_STATE_NOT_SET = -1; + /** * Used when a ViewHolder starts the layout pass as a hidden ViewHolder but is re-used from * hidden list (as if it was scrap) without being recycled in between. @@ -8955,6 +10029,9 @@ public static abstract class ViewHolder { // marked as unimportant for accessibility. private int mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; + // set if we defer the accessibility state change of the view holder + @VisibleForTesting + int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; /** * Is set when VH is bound from the adapter and cleaned right before it is sent to @@ -9092,7 +10169,7 @@ public final int getOldPosition() { /** * Returns The itemId represented by this ViewHolder. * - * @return The the item's id if adapter has stable ids, {@link RecyclerView#NO_ID} + * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID} * otherwise */ public final long getItemId() { @@ -9219,25 +10296,27 @@ void resetInternal() { mShadowingHolder = null; clearPayload(); mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; + mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; + clearNestedRecyclerViewIfNotNested(this); } /** * Called when the child view enters the hidden state */ - private void onEnteredHiddenState() { + private void onEnteredHiddenState(RecyclerView parent) { // While the view item is in hidden state, make it invisible for the accessibility. mWasImportantForAccessibilityBeforeHidden = ViewCompat.getImportantForAccessibility(itemView); - ViewCompat.setImportantForAccessibility(itemView, + parent.setChildImportantForAccessibilityInternal(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } /** * Called when the child view leaves the hidden state */ - private void onLeftHiddenState() { - ViewCompat.setImportantForAccessibility( - itemView, mWasImportantForAccessibilityBeforeHidden); + private void onLeftHiddenState(RecyclerView parent) { + parent.setChildImportantForAccessibilityInternal(this, + mWasImportantForAccessibilityBeforeHidden); mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; } @@ -9274,6 +10353,8 @@ public String toString() { * * @param recyclable Whether this item is available to be recycled. Default value * is true. + * + * @see #isRecyclable() */ public final void setIsRecyclable(boolean recyclable) { mIsRecyclableCount = recyclable ? mIsRecyclableCount - 1 : mIsRecyclableCount + 1; @@ -9296,9 +10377,9 @@ public final void setIsRecyclable(boolean recyclable) { } /** - * @see {@link #setIsRecyclable(boolean)} - * * @return true if this item is available to be recycled, false otherwise. + * + * @see #setIsRecyclable(boolean) */ public final boolean isRecyclable() { return (mFlags & FLAG_NOT_RECYCLABLE) == 0 && @@ -9314,7 +10395,7 @@ private boolean shouldBeKeptAsChild() { } /** - * @return True if ViewHolder is not refenrenced by RecyclerView animations but has + * @return True if ViewHolder is not referenced by RecyclerView animations but has * transient state which will prevent it from being recycled. */ private boolean doesTransientStatePreventRecycling() { @@ -9326,7 +10407,39 @@ boolean isUpdated() { } } - private int getAdapterPositionFor(ViewHolder viewHolder) { + /** + * This method is here so that we can control the important for a11y changes and test it. + */ + @VisibleForTesting + boolean setChildImportantForAccessibilityInternal(ViewHolder viewHolder, + int importantForAccessibility) { + if (isComputingLayout()) { + viewHolder.mPendingAccessibilityState = importantForAccessibility; + mPendingAccessibilityImportanceChange.add(viewHolder); + return false; + } + ViewCompat.setImportantForAccessibility(viewHolder.itemView, importantForAccessibility); + return true; + } + + void dispatchPendingImportantForAccessibilityChanges() { + for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i --) { + ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); + if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { + continue; + } + int state = viewHolder.mPendingAccessibilityState; + if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { + //noinspection WrongConstant + ViewCompat.setImportantForAccessibility(viewHolder.itemView, state); + viewHolder.mPendingAccessibilityState = + ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET; + } + } + mPendingAccessibilityImportanceChange.clear(); + } + + int getAdapterPositionFor(ViewHolder viewHolder) { if (viewHolder.hasAnyOfTheFlags( ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN) || !viewHolder.isBound()) { @@ -9464,6 +10577,7 @@ public boolean isItemChanged() { /** * @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()} */ + @Deprecated public int getViewPosition() { return mViewHolder.getPosition(); } @@ -9717,10 +10831,10 @@ protected void onChildAttachedToWindow(View child) { * @param scrollVector The vector that points to the target scroll position */ protected void normalize(PointF scrollVector) { - final double magnitute = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y * - scrollVector.y); - scrollVector.x /= magnitute; - scrollVector.y /= magnitute; + final double magnitude = Math.sqrt(scrollVector.x * scrollVector.x + scrollVector.y + * scrollVector.y); + scrollVector.x /= magnitude; + scrollVector.y /= magnitude; } /** @@ -9741,7 +10855,7 @@ protected void normalize(PointF scrollVector) { * provided {@link Action} to define the next scroll.

          * * @param dx Last scroll amount horizontally - * @param dy Last scroll amount verticaully + * @param dy Last scroll amount vertically * @param state Transient state of RecyclerView * @param action If you want to trigger a new smooth scroll and cancel the previous one, * update this object. @@ -9836,7 +10950,7 @@ boolean hasJumpTarget() { return mJumpToPosition >= 0; } - private void runIfNecessary(RecyclerView recyclerView) { + void runIfNecessary(RecyclerView recyclerView) { if (mJumpToPosition >= 0) { final int position = mJumpToPosition; mJumpToPosition = NO_POSITION; @@ -9935,6 +11049,30 @@ public void update(int dx, int dy, int duration, Interpolator interpolator) { changed = true; } } + + /** + * An interface which is optionally implemented by custom {@link RecyclerView.LayoutManager} + * to provide a hint to a {@link SmoothScroller} about the location of the target position. + */ + public interface ScrollVectorProvider { + /** + * Should calculate the vector that points to the direction where the target position + * can be found. + *

          + * This method is used by the {@link LinearSmoothScroller} to initiate a scroll towards + * the target position. + *

          + * The magnitude of the vector is not important. It is always normalized before being + * used by the {@link LinearSmoothScroller}. + *

          + * LayoutManager should not check whether the position exists in the adapter or not. + * + * @param targetPosition the target position to which the returned vector should point + * + * @return the scroll vector for a given position. + */ + PointF computeScrollVectorForPosition(int targetPosition); + } } static class AdapterDataObservable extends Observable { @@ -9997,16 +11135,18 @@ public void notifyItemMoved(int fromPosition, int toPosition) { * This is public so that the CREATOR can be access on cold launch. * @hide */ - public static class SavedState extends android.view.View.BaseSavedState { + @RestrictTo(LIBRARY_GROUP) + public static class SavedState extends AbsSavedState { Parcelable mLayoutState; /** * called by CREATOR */ - SavedState(Parcel in) { - super(in); - mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader()); + SavedState(Parcel in, ClassLoader loader) { + super(in, loader); + mLayoutState = in.readParcelable( + loader != null ? loader : LayoutManager.class.getClassLoader()); } /** @@ -10022,22 +11162,22 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(mLayoutState, 0); } - private void copyFrom(SavedState other) { + void copyFrom(SavedState other) { mLayoutState = other.mLayoutState; } - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - @Override - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } + public static final Creator CREATOR = ParcelableCompat.newCreator( + new ParcelableCompatCreatorCallbacks() { + @Override + public SavedState createFromParcel(Parcel in, ClassLoader loader) { + return new SavedState(in, loader); + } - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; + @Override + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }); } /** *

          Contains useful information about the current RecyclerView state like target scroll @@ -10062,46 +11202,73 @@ void assertLayoutStep(int accepted) { } } - @IntDef(flag = true, value = { - STEP_START, STEP_LAYOUT, STEP_ANIMATIONS - }) - @Retention(RetentionPolicy.SOURCE) - @interface LayoutState {} + /** Owned by SmoothScroller */ private int mTargetPosition = RecyclerView.NO_POSITION; - @LayoutState - private int mLayoutStep = STEP_START; - private SparseArray mData; - /** - * Number of items adapter has. - */ - int mItemCount = 0; + //////////////////////////////////////////////////////////////////////////////////////////// + // Fields below are carried from one layout pass to the next + //////////////////////////////////////////////////////////////////////////////////////////// /** * Number of items adapter had in the previous layout. */ - private int mPreviousLayoutItemCount = 0; + int mPreviousLayoutItemCount = 0; /** * Number of items that were NOT laid out but has been deleted from the adapter after the * previous layout. */ - private int mDeletedInvisibleItemCountSincePreviousLayout = 0; + int mDeletedInvisibleItemCountSincePreviousLayout = 0; + + //////////////////////////////////////////////////////////////////////////////////////////// + // Fields below must be updated or cleared before they are used (generally before a pass) + //////////////////////////////////////////////////////////////////////////////////////////// + + @IntDef(flag = true, value = { + STEP_START, STEP_LAYOUT, STEP_ANIMATIONS + }) + @Retention(RetentionPolicy.SOURCE) + @interface LayoutState {} + + @LayoutState + int mLayoutStep = STEP_START; + + /** + * Number of items adapter has. + */ + int mItemCount = 0; + + boolean mStructureChanged = false; - private boolean mStructureChanged = false; + boolean mInPreLayout = false; - private boolean mInPreLayout = false; + boolean mTrackOldChangeHolders = false; - private boolean mRunSimpleAnimations = false; + boolean mIsMeasuring = false; - private boolean mRunPredictiveAnimations = false; + //////////////////////////////////////////////////////////////////////////////////////////// + // Fields below are always reset outside of the pass (or passes) that use them + //////////////////////////////////////////////////////////////////////////////////////////// - private boolean mTrackOldChangeHolders = false; + boolean mRunSimpleAnimations = false; - private boolean mIsMeasuring = false; + boolean mRunPredictiveAnimations = false; + + /** + * This data is saved before a layout calculation happens. After the layout is finished, + * if the previously focused view has been replaced with another view for the same item, we + * move the focus to the new item automatically. + */ + int mFocusedItemPosition; + long mFocusedItemId; + // when a sub child has focus, record its id and see if we can directly request focus on + // that one instead + int mFocusedSubChildId; + + //////////////////////////////////////////////////////////////////////////////////////////// State reset() { mTargetPosition = RecyclerView.NO_POSITION; @@ -10114,6 +11281,22 @@ State reset() { return this; } + /** + * Prepare for a prefetch occurring on the RecyclerView in between traversals, potentially + * prior to any layout passes. + * + *

          Don't touch any state stored between layout passes, only reset per-layout state, so + * that Recycler#getViewForPosition() can function safely.

          + */ + void prepareForNestedPrefetch(Adapter adapter) { + mLayoutStep = STEP_START; + mItemCount = adapter.getItemCount(); + mStructureChanged = false; + mInPreLayout = false; + mTrackOldChangeHolders = false; + mIsMeasuring = false; + } + /** * Returns true if the RecyclerView is currently measuring the layout. This value is * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView @@ -10279,6 +11462,28 @@ public String toString() { } } + /** + * This class defines the behavior of fling if the developer wishes to handle it. + *

          + * Subclasses of {@link OnFlingListener} can be used to implement custom fling behavior. + * + * @see #setOnFlingListener(OnFlingListener) + */ + public static abstract class OnFlingListener { + + /** + * Override this to handle a fling given the velocities in both x and y directions. + * Note that this method will only be called if the associated {@link LayoutManager} + * supports scrolling and the fling is not handled by nested scrolls first. + * + * @param velocityX the fling velocity on the X axis + * @param velocityY the fling velocity on the Y axis + * + * @return true if the fling washandled, false otherwise. + */ + public abstract boolean onFling(int velocityX, int velocityY); + } + /** * Internal listener that manages items after animations finish. This is how items are * retained (not recycled) during animations, but allowed to be recycled afterwards. @@ -10287,6 +11492,9 @@ public String toString() { */ private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener { + ItemAnimatorRestoreListener() { + } + @Override public void onAnimationFinished(ViewHolder item) { item.setIsRecyclable(true); @@ -10788,7 +11996,7 @@ public abstract boolean animateChange(@NonNull ViewHolder oldHolder, * be a matching {@link #dispatchAnimationFinished(ViewHolder)} call by the subclass. *

          * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()}, sublcass should call this method for both the oldHolder + * animateChange()}, subclass should call this method for both the oldHolder * and newHolder (if they are not the same instance). * * @param viewHolder The ViewHolder whose animation is finished. @@ -10803,7 +12011,7 @@ public final void dispatchAnimationFinished(ViewHolder viewHolder) { /** * Called after {@link #dispatchAnimationFinished(ViewHolder)} is called by the - * ItemAniamtor. + * ItemAnimator. * * @param viewHolder The ViewHolder whose animation is finished. There might still be other * animations running on this ViewHolder. @@ -10825,7 +12033,7 @@ public void onAnimationFinished(ViewHolder viewHolder) { * {@link #dispatchAnimationStarted(ViewHolder)} call by the subclass. *

          * For {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) - * animateChange()}, sublcass should call this method for both the oldHolder + * animateChange()}, subclass should call this method for both the oldHolder * and newHolder (if they are not the same instance). *

          * If your ItemAnimator decides not to animate a ViewHolder, it should call @@ -10852,7 +12060,7 @@ public void onAnimationStarted(ViewHolder viewHolder) { /** * Like {@link #isRunning()}, this method returns whether there are any item - * animations currently running. Addtionally, the listener passed in will be called + * animations currently running. Additionally, the listener passed in will be called * when there are no item animations running, either immediately (before the method * returns) if no animations are currently running, or when the currently running * animations are {@link #dispatchAnimationsFinished() finished}. @@ -10986,7 +12194,7 @@ public interface ItemAnimatorFinishedListener { * structure. You can extend this class if you would like to keep more information about * the Views. *

          - * If you want to provide your own implementation butstill use `super` methods to record + * If you want to provide your own implementation but still use `super` methods to record * basic information, you can override {@link #obtainHolderInfo()} to provide your own * instances. */ diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java index dc3b90194a8..4ca7a17e5ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/RecyclerViewAccessibilityDelegate.java @@ -35,7 +35,7 @@ public RecyclerViewAccessibilityDelegate(RecyclerView recyclerView) { mRecyclerView = recyclerView; } - private boolean shouldIgnore() { + boolean shouldIgnore() { return mRecyclerView.hasPendingAdapterUpdates(); } @@ -72,7 +72,12 @@ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) } } - AccessibilityDelegateCompat getItemDelegate() { + /** + * Gets the AccessibilityDelegate for an individual item in the RecyclerView. + * A basic item delegate is provided by default, but you can override this + * method to provide a custom per-item delegate. + */ + public AccessibilityDelegateCompat getItemDelegate() { return mItemDelegate; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SnapHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SnapHelper.java new file mode 100644 index 00000000000..21fb06faa6c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/SnapHelper.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.telegram.messenger.support.widget; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import org.telegram.messenger.support.widget.RecyclerView.LayoutManager; +import org.telegram.messenger.support.widget.RecyclerView.SmoothScroller.ScrollVectorProvider; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.animation.DecelerateInterpolator; +import android.widget.Scroller; + +/** + * Class intended to support snapping for a {@link RecyclerView}. + *

          + * SnapHelper tries to handle fling as well but for this to work properly, the + * {@link RecyclerView.LayoutManager} must implement the {@link ScrollVectorProvider} interface or + * you should override {@link #onFling(int, int)} and handle fling manually. + */ +public abstract class SnapHelper extends RecyclerView.OnFlingListener { + + static final float MILLISECONDS_PER_INCH = 100f; + + RecyclerView mRecyclerView; + private Scroller mGravityScroller; + + // Handles the snap on scroll case. + private final RecyclerView.OnScrollListener mScrollListener = + new RecyclerView.OnScrollListener() { + boolean mScrolled = false; + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) { + mScrolled = false; + snapToTargetExistingView(); + } + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (dx != 0 || dy != 0) { + mScrolled = true; + } + } + }; + + @Override + public boolean onFling(int velocityX, int velocityY) { + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager == null) { + return false; + } + RecyclerView.Adapter adapter = mRecyclerView.getAdapter(); + if (adapter == null) { + return false; + } + int minFlingVelocity = mRecyclerView.getMinFlingVelocity(); + return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity) + && snapFromFling(layoutManager, velocityX, velocityY); + } + + /** + * Attaches the {@link SnapHelper} to the provided RecyclerView, by calling + * {@link RecyclerView#setOnFlingListener(RecyclerView.OnFlingListener)}. + * You can call this method with {@code null} to detach it from the current RecyclerView. + * + * @param recyclerView The RecyclerView instance to which you want to add this helper or + * {@code null} if you want to remove SnapHelper from the current + * RecyclerView. + * + * @throws IllegalArgumentException if there is already a {@link RecyclerView.OnFlingListener} + * attached to the provided {@link RecyclerView}. + * + */ + public void attachToRecyclerView(@Nullable RecyclerView recyclerView) + throws IllegalStateException { + if (mRecyclerView == recyclerView) { + return; // nothing to do + } + if (mRecyclerView != null) { + destroyCallbacks(); + } + mRecyclerView = recyclerView; + if (mRecyclerView != null) { + setupCallbacks(); + mGravityScroller = new Scroller(mRecyclerView.getContext(), + new DecelerateInterpolator()); + snapToTargetExistingView(); + } + } + + /** + * Called when an instance of a {@link RecyclerView} is attached. + */ + private void setupCallbacks() throws IllegalStateException { + if (mRecyclerView.getOnFlingListener() != null) { + throw new IllegalStateException("An instance of OnFlingListener already set."); + } + mRecyclerView.addOnScrollListener(mScrollListener); + mRecyclerView.setOnFlingListener(this); + } + + /** + * Called when the instance of a {@link RecyclerView} is detached. + */ + private void destroyCallbacks() { + mRecyclerView.removeOnScrollListener(mScrollListener); + mRecyclerView.setOnFlingListener(null); + } + + /** + * Calculated the estimated scroll distance in each direction given velocities on both axes. + * + * @param velocityX Fling velocity on the horizontal axis. + * @param velocityY Fling velocity on the vertical axis. + * + * @return array holding the calculated distances in x and y directions + * respectively. + */ + public int[] calculateScrollDistance(int velocityX, int velocityY) { + int[] outDist = new int[2]; + mGravityScroller.fling(0, 0, velocityX, velocityY, + Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); + outDist[0] = mGravityScroller.getFinalX(); + outDist[1] = mGravityScroller.getFinalY(); + return outDist; + } + + /** + * Helper method to facilitate for snapping triggered by a fling. + * + * @param layoutManager The {@link LayoutManager} associated with the attached + * {@link RecyclerView}. + * @param velocityX Fling velocity on the horizontal axis. + * @param velocityY Fling velocity on the vertical axis. + * + * @return true if it is handled, false otherwise. + */ + private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX, + int velocityY) { + if (!(layoutManager instanceof ScrollVectorProvider)) { + return false; + } + + RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager); + if (smoothScroller == null) { + return false; + } + + int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY); + if (targetPosition == RecyclerView.NO_POSITION) { + return false; + } + + smoothScroller.setTargetPosition(targetPosition); + layoutManager.startSmoothScroll(smoothScroller); + return true; + } + + /** + * Snaps to a target view which currently exists in the attached {@link RecyclerView}. This + * method is used to snap the view when the {@link RecyclerView} is first attached; when + * snapping was triggered by a scroll and when the fling is at its final stages. + */ + void snapToTargetExistingView() { + if (mRecyclerView == null) { + return; + } + LayoutManager layoutManager = mRecyclerView.getLayoutManager(); + if (layoutManager == null) { + return; + } + View snapView = findSnapView(layoutManager); + if (snapView == null) { + return; + } + int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView); + if (snapDistance[0] != 0 || snapDistance[1] != 0) { + mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]); + } + } + + /** + * Creates a scroller to be used in the snapping implementation. + * + * @param layoutManager The {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView}. + * + * @return a {@link LinearSmoothScroller} which will handle the scrolling. + */ + @Nullable + protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) { + if (!(layoutManager instanceof ScrollVectorProvider)) { + return null; + } + return new LinearSmoothScroller(mRecyclerView.getContext()) { + @Override + protected void onTargetFound(View targetView, RecyclerView.State state, Action action) { + int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(), + targetView); + final int dx = snapDistances[0]; + final int dy = snapDistances[1]; + final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy))); + if (time > 0) { + action.update(dx, dy, time, mDecelerateInterpolator); + } + } + + @Override + protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { + return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; + } + }; + } + + /** + * Override this method to snap to a particular point within the target view or the container + * view on any axis. + *

          + * This method is called when the {@link SnapHelper} has intercepted a fling and it needs + * to know the exact distance required to scroll by in order to snap to the target view. + * + * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView} + * @param targetView the target view that is chosen as the view to snap + * + * @return the output coordinates the put the result into. out[0] is the distance + * on horizontal axis and out[1] is the distance on vertical axis. + */ + @SuppressWarnings("WeakerAccess") + @Nullable + public abstract int[] calculateDistanceToFinalSnap(@NonNull LayoutManager layoutManager, + @NonNull View targetView); + + /** + * Override this method to provide a particular target view for snapping. + *

          + * This method is called when the {@link SnapHelper} is ready to start snapping and requires + * a target view to snap to. It will be explicitly called when the scroll state becomes idle + * after a scroll. It will also be called when the {@link SnapHelper} is preparing to snap + * after a fling and requires a reference view from the current set of child views. + *

          + * If this method returns {@code null}, SnapHelper will not snap to any view. + * + * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView} + * + * @return the target view to which to snap on fling or end of scroll + */ + @SuppressWarnings("WeakerAccess") + @Nullable + public abstract View findSnapView(LayoutManager layoutManager); + + /** + * Override to provide a particular adapter target position for snapping. + * + * @param layoutManager the {@link RecyclerView.LayoutManager} associated with the attached + * {@link RecyclerView} + * @param velocityX fling velocity on the horizontal axis + * @param velocityY fling velocity on the vertical axis + * + * @return the target adapter position to you want to snap or {@link RecyclerView#NO_POSITION} + * if no snapping should happen + */ + public abstract int findTargetSnapPosition(LayoutManager layoutManager, int velocityX, + int velocityY); +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java index f93af4fd8a6..9ddb6d2e6fb 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/StaggeredGridLayoutManager.java @@ -16,6 +16,7 @@ package org.telegram.messenger.support.widget; +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_HEAD; import static org.telegram.messenger.support.widget.LayoutState.ITEM_DIRECTION_TAIL; import static org.telegram.messenger.support.widget.LayoutState.LAYOUT_END; @@ -29,6 +30,7 @@ import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.RestrictTo; import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; @@ -52,11 +54,12 @@ * StaggeredGridLayoutManager can offset spans independently or move items between spans. You can * control this behavior via {@link #setGapStrategy(int)}. */ -public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { +public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager implements + RecyclerView.SmoothScroller.ScrollVectorProvider { - public static final String TAG = "StaggeredGridLayoutManager"; + private static final String TAG = "StaggeredGridLayoutManager"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; @@ -67,6 +70,9 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { */ public static final int GAP_HANDLING_NONE = 0; + /** + * @deprecated No longer supported. + */ @SuppressWarnings("unused") @Deprecated public static final int GAP_HANDLING_LAZY = 1; @@ -92,7 +98,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { */ public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; - private static final int INVALID_OFFSET = Integer.MIN_VALUE; + static final int INVALID_OFFSET = Integer.MIN_VALUE; /** * While trying to find next view to focus, LayoutManager will not try to scroll more * than this factor times the total space of the list. If layout is vertical, total space is the @@ -105,7 +111,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { */ private int mSpanCount = -1; - private Span[] mSpans; + Span[] mSpans; /** * Primary orientation is the layout's orientation, secondary orientation is the orientation @@ -126,7 +132,7 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { @NonNull private final LayoutState mLayoutState; - private boolean mReverseLayout = false; + boolean mReverseLayout = false; /** * Aggregated reverse layout value that takes RTL into account. @@ -206,6 +212,12 @@ public class StaggeredGridLayoutManager extends RecyclerView.LayoutManager { */ private boolean mSmoothScrollbarEnabled = true; + /** + * Temporary array used (solely in {@link #collectAdjacentPrefetchPositions}) for stashing and + * sorting distances to views being prefetched. + */ + private int[] mPrefetchDistances; + private final Runnable mCheckForGapsRunnable = new Runnable() { @Override public void run() { @@ -213,22 +225,6 @@ public void run() { } }; - /** - * Constructor used when layout manager is set in XML by RecyclerView attribute - * "layoutManager". Defaults to single column and vertical. - */ - @SuppressWarnings("unused") - public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes); - setOrientation(properties.orientation); - setSpanCount(properties.spanCount); - setReverseLayout(properties.reverseLayout); - setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE); - mLayoutState = new LayoutState(); - createOrientationHelpers(); - } - /** * Creates a StaggeredGridLayoutManager with given parameters. * @@ -256,7 +252,7 @@ private void createOrientationHelpers() { * When a full span item is laid out in reverse direction, it sets a flag which we check when * scroll is stopped (or re-layout happens) and re-layout after first valid item. */ - private boolean checkForGaps() { + boolean checkForGaps() { if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) { return false; } @@ -314,6 +310,8 @@ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycl for (int i = 0; i < mSpanCount; i++) { mSpans[i].clear(); } + // SGLM will require fresh layout call to recover state after detach + view.requestLayout(); } /** @@ -596,25 +594,28 @@ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state, boolean shouldCheckForGaps) { final AnchorInfo anchorInfo = mAnchorInfo; - anchorInfo.reset(); - if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) { if (state.getItemCount() == 0) { removeAndRecycleAllViews(recycler); + anchorInfo.reset(); return; } } - if (mPendingSavedState != null) { - applyPendingSavedState(anchorInfo); - } else { - resolveShouldLayoutReverse(); - anchorInfo.mLayoutFromEnd = mShouldReverseLayout; + boolean recalculateAnchor = !anchorInfo.mValid || mPendingScrollPosition != NO_POSITION || + mPendingSavedState != null; + if (recalculateAnchor) { + anchorInfo.reset(); + if (mPendingSavedState != null) { + applyPendingSavedState(anchorInfo); + } else { + resolveShouldLayoutReverse(); + anchorInfo.mLayoutFromEnd = mShouldReverseLayout; + } + updateAnchorInfoForLayout(state, anchorInfo); + anchorInfo.mValid = true; } - - updateAnchorInfoForLayout(state, anchorInfo); - - if (mPendingSavedState == null) { + if (mPendingSavedState == null && mPendingScrollPosition == NO_POSITION) { if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd || isLayoutRTL() != mLastLayoutRTL) { mLazySpanLookup.clear(); @@ -633,8 +634,18 @@ private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State } } } else { - for (int i = 0; i < mSpanCount; i++) { - mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, anchorInfo.mOffset); + if (recalculateAnchor || mAnchorInfo.mSpanReferenceLines == null) { + for (int i = 0; i < mSpanCount; i++) { + mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout, + anchorInfo.mOffset); + } + mAnchorInfo.saveSpanReferenceLines(mSpans); + } else { + for (int i = 0; i < mSpanCount; i++) { + final Span span = mSpans[i]; + span.clear(); + span.setLine(mAnchorInfo.mSpanReferenceLines[i]); + } } } } @@ -683,17 +694,27 @@ && getChildCount() > 0 hasGaps = true; } } - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; + } + if (state.isPreLayout()) { + mAnchorInfo.reset(); } mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd; mLastLayoutRTL = isLayoutRTL(); - mPendingSavedState = null; // we don't need this anymore if (hasGaps) { + mAnchorInfo.reset(); onLayoutChildren(recycler, state, false); } } + @Override + public void onLayoutCompleted(RecyclerView.State state) { + super.onLayoutCompleted(state); + mPendingScrollPosition = NO_POSITION; + mPendingScrollPositionOffset = INVALID_OFFSET; + mPendingSavedState = null; // we don't need this anymore + mAnchorInfo.reset(); + } + private void repositionToWrapContentIfNecessary() { if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) { return; // nothing to do @@ -829,7 +850,6 @@ boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorI // child anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition(); - if (mPendingScrollPositionOffset != INVALID_OFFSET) { if (anchorInfo.mLayoutFromEnd) { final int target = mPrimaryOrientation.getEndAfterPadding() - @@ -1038,8 +1058,8 @@ private int computeScrollOffset(RecyclerView.State state) { return 0; } return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true), + findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), + findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), this, mSmoothScrollbarEnabled, mShouldReverseLayout); } @@ -1058,8 +1078,8 @@ private int computeScrollExtent(RecyclerView.State state) { return 0; } return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true), + findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), + findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), this, mSmoothScrollbarEnabled); } @@ -1078,8 +1098,8 @@ private int computeScrollRange(RecyclerView.State state) { return 0; } return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation, - findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true) - , findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true), + findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled), + findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled), this, mSmoothScrollbarEnabled); } @@ -1232,8 +1252,8 @@ public void onInitializeAccessibilityEvent(AccessibilityEvent event) { if (getChildCount() > 0) { final AccessibilityRecordCompat record = AccessibilityEventCompat .asRecord(event); - final View start = findFirstVisibleItemClosestToStart(false, true); - final View end = findFirstVisibleItemClosestToEnd(false, true); + final View start = findFirstVisibleItemClosestToStart(false); + final View end = findFirstVisibleItemClosestToEnd(false); if (start == null || end == null) { return; } @@ -1255,8 +1275,8 @@ public void onInitializeAccessibilityEvent(AccessibilityEvent event) { * of returning null. */ int findFirstVisibleItemPositionInt() { - final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true, true) : - findFirstVisibleItemClosestToStart(true, true); + final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) : + findFirstVisibleItemClosestToStart(true); return first == null ? NO_POSITION : getPosition(first); } @@ -1284,7 +1304,7 @@ public int getColumnCountForAccessibility(RecyclerView.Recycler recycler, * This method does not do any sorting based on child's start coordinate, instead, it uses * children order. */ - View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) { + View findFirstVisibleItemClosestToStart(boolean fullyVisible) { final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); final int limit = getChildCount(); @@ -1301,7 +1321,7 @@ View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPart // as long as fully visible is not requested. return child; } - if (acceptPartiallyVisible && partiallyVisible == null) { + if (partiallyVisible == null) { partiallyVisible = child; } } @@ -1314,7 +1334,7 @@ View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPart * This method does not do any sorting based on child's end coordinate, instead, it uses * children order. */ - View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) { + View findFirstVisibleItemClosestToEnd(boolean fullyVisible) { final int boundsStart = mPrimaryOrientation.getStartAfterPadding(); final int boundsEnd = mPrimaryOrientation.getEndAfterPadding(); View partiallyVisible = null; @@ -1330,7 +1350,7 @@ View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartia // as long as fully visible is not requested. return child; } - if (acceptPartiallyVisible && partiallyVisible == null) { + if (partiallyVisible == null) { partiallyVisible = child; } } @@ -1746,18 +1766,6 @@ private void prependViewToAllSpans(View view) { } } - private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) { - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - if (DEBUG) { - Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:" - + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan - + ". l:" + left + ",t:" + top - + ", r:" + right + ", b:" + bottom); - } - layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin - , bottom - lp.bottomMargin); - } - private void updateAllRemainingSpans(int layoutDir, int targetLine) { for (int i = 0; i < mSpanCount; i++) { if (mSpans[i].mViews.isEmpty()) { @@ -1849,7 +1857,8 @@ private int getMinEnd(int def) { private void recycleFromStart(RecyclerView.Recycler recycler, int line) { while (getChildCount() > 0) { View child = getChildAt(0); - if (mPrimaryOrientation.getDecoratedEnd(child) <= line) { + if (mPrimaryOrientation.getDecoratedEnd(child) <= line && + mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Don't recycle the last View in a span not to lose span's start/end lines if (lp.mFullSpan) { @@ -1879,7 +1888,8 @@ private void recycleFromEnd(RecyclerView.Recycler recycler, int line) { int i; for (i = childCount - 1; i >= 0; i--) { View child = getChildAt(i); - if (mPrimaryOrientation.getDecoratedStart(child) >= line) { + if (mPrimaryOrientation.getDecoratedStart(child) >= line && + mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) { LayoutParams lp = (LayoutParams) child.getLayoutParams(); // Don't recycle the last View in a span not to lose span's start/end lines if (lp.mFullSpan) { @@ -1988,23 +1998,27 @@ private int calculateScrollDirectionForPosition(int position) { return position < firstChildPos != mShouldReverseLayout ? LAYOUT_START : LAYOUT_END; } + @Override + public PointF computeScrollVectorForPosition(int targetPosition) { + final int direction = calculateScrollDirectionForPosition(targetPosition); + PointF outVector = new PointF(); + if (direction == 0) { + return null; + } + if (mOrientation == HORIZONTAL) { + outVector.x = direction; + outVector.y = 0; + } else { + outVector.x = 0; + outVector.y = direction; + } + return outVector; + } + @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { - LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - final int direction = calculateScrollDirectionForPosition(targetPosition); - if (direction == 0) { - return null; - } - if (mOrientation == HORIZONTAL) { - return new PointF(direction, 0); - } else { - return new PointF(0, direction); - } - } - }; + LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext()); scroller.setTargetPosition(position); startSmoothScroll(scroller); } @@ -2041,10 +2055,58 @@ public void scrollToPositionWithOffset(int position, int offset) { requestLayout(); } - int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { + /** @hide */ + @Override + public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state, + LayoutPrefetchRegistry layoutPrefetchRegistry) { + /* This method uses the simplifying assumption that the next N items (where N = span count) + * will be assigned, one-to-one, to spans, where ordering is based on which span extends + * least beyond the viewport. + * + * While this simplified model will be incorrect in some cases, it's difficult to know + * item heights, or whether individual items will be full span prior to construction. + * + * While this greedy estimation approach may underestimate the distance to prefetch items, + * it's very unlikely to overestimate them, so distances can be conservatively used to know + * the soonest (in terms of scroll distance) a prefetched view may come on screen. + */ + int delta = (mOrientation == HORIZONTAL) ? dx : dy; + if (getChildCount() == 0 || delta == 0) { + // can't support this scroll, so don't bother prefetching + return; + } + prepareLayoutStateForDelta(delta, state); + + // build sorted list of distances to end of each span (though we don't care which is which) + if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) { + mPrefetchDistances = new int[mSpanCount]; + } + + int itemPrefetchCount = 0; + for (int i = 0; i < mSpanCount; i++) { + // compute number of pixels past the edge of the viewport that the current span extends + int distance = mLayoutState.mItemDirection == LAYOUT_START + ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine) + : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine; + if (distance >= 0) { + // span extends to the edge, so prefetch next item + mPrefetchDistances[itemPrefetchCount] = distance; + itemPrefetchCount++; + } + } + Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount); + + // then assign them in order to the next N views (where N = span count) + for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) { + layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]); + mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; + } + } + + void prepareLayoutStateForDelta(int delta, RecyclerView.State state) { final int referenceChildPosition; final int layoutDir; - if (dt > 0) { // layout towards end + if (delta > 0) { // layout towards end layoutDir = LAYOUT_END; referenceChildPosition = getLastChildPosition(); } else { @@ -2055,11 +2117,19 @@ int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { updateLayoutState(referenceChildPosition, state); setLayoutStateDirection(layoutDir); mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection; - final int absDt = Math.abs(dt); - mLayoutState.mAvailable = absDt; + mLayoutState.mAvailable = Math.abs(delta); + } + + int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { + if (getChildCount() == 0 || dt == 0) { + return 0; + } + + prepareLayoutStateForDelta(dt, state); int consumed = fill(recycler, mLayoutState, state); + final int available = mLayoutState.mAvailable; final int totalScroll; - if (absDt < consumed) { + if (available < consumed) { totalScroll = dt; } else if (dt < 0) { totalScroll = -consumed; @@ -2073,15 +2143,17 @@ int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) { mPrimaryOrientation.offsetChildren(-totalScroll); // always reset this if we scroll for a proper save instance state mLastLayoutFromEnd = mShouldReverseLayout; + mLayoutState.mAvailable = 0; + recycle(recycler, mLayoutState); return totalScroll; } - private int getLastChildPosition() { + int getLastChildPosition() { final int childCount = getChildCount(); return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1)); } - private int getFirstChildPosition() { + int getFirstChildPosition() { final int childCount = getChildCount(); return childCount == 0 ? 0 : getPosition(getChildAt(0)); } @@ -2124,9 +2196,9 @@ private int findLastReferenceChildPosition(int itemCount) { public RecyclerView.LayoutParams generateDefaultLayoutParams() { if (mOrientation == HORIZONTAL) { return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.FILL_PARENT); + ViewGroup.LayoutParams.MATCH_PARENT); } else { - return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } } @@ -2229,9 +2301,21 @@ public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycl private int convertFocusDirectionToLayoutDirection(int focusDirection) { switch (focusDirection) { case View.FOCUS_BACKWARD: - return LayoutState.LAYOUT_START; + if (mOrientation == VERTICAL) { + return LayoutState.LAYOUT_START; + } else if (isLayoutRTL()) { + return LayoutState.LAYOUT_END; + } else { + return LayoutState.LAYOUT_START; + } case View.FOCUS_FORWARD: - return LayoutState.LAYOUT_END; + if (mOrientation == VERTICAL) { + return LayoutState.LAYOUT_END; + } else if (isLayoutRTL()) { + return LayoutState.LAYOUT_START; + } else { + return LayoutState.LAYOUT_END; + } case View.FOCUS_UP: return mOrientation == VERTICAL ? LayoutState.LAYOUT_START : LayoutState.INVALID_LAYOUT; @@ -2332,13 +2416,13 @@ public final int getSpanIndex() { class Span { static final int INVALID_LINE = Integer.MIN_VALUE; - private ArrayList mViews = new ArrayList<>(); + ArrayList mViews = new ArrayList<>(); int mCachedStart = INVALID_LINE; int mCachedEnd = INVALID_LINE; int mDeletedSize = 0; final int mIndex; - private Span(int index) { + Span(int index) { mIndex = index; } @@ -2904,6 +2988,7 @@ public FullSpanItem[] newArray(int size) { /** * @hide */ + @RestrictTo(LIBRARY_GROUP) public static class SavedState implements Parcelable { int mAnchorPosition; @@ -3010,18 +3095,41 @@ public SavedState[] newArray(int size) { /** * Data class to hold the information about an anchor position which is used in onLayout call. */ - private class AnchorInfo { + class AnchorInfo { int mPosition; int mOffset; boolean mLayoutFromEnd; boolean mInvalidateOffsets; + boolean mValid; + // this is where we save span reference lines in case we need to re-use them for multi-pass + // measure steps + int[] mSpanReferenceLines; + + public AnchorInfo() { + reset(); + } void reset() { mPosition = NO_POSITION; mOffset = INVALID_OFFSET; mLayoutFromEnd = false; mInvalidateOffsets = false; + mValid = false; + if (mSpanReferenceLines != null) { + Arrays.fill(mSpanReferenceLines, -1); + } + } + + void saveSpanReferenceLines(Span[] spans) { + int spanCount = spans.length; + if (mSpanReferenceLines == null || mSpanReferenceLines.length < spanCount) { + mSpanReferenceLines = new int[mSpans.length]; + } + for (int i = 0; i < spanCount; i++) { + // does not matter start or end since this is only recorded when span is reset + mSpanReferenceLines[i] = spans[i].getStartLine(Span.INVALID_LINE); + } } void assignCoordinateFromPadding() { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java index 938fe42f325..959e9526002 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/ViewInfoStore.java @@ -21,6 +21,7 @@ import android.support.v4.util.ArrayMap; import android.support.v4.util.LongSparseArray; import android.support.v4.util.Pools; +import android.view.View; import static org.telegram.messenger.support.widget.RecyclerView.ViewHolder; import static org.telegram.messenger.support.widget.RecyclerView.ItemAnimator.ItemHolderInfo; @@ -33,9 +34,7 @@ import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_PRE; import static org.telegram.messenger.support.widget.ViewInfoStore.InfoRecord.FLAG_POST; /** - * This class abstracts all tracking for Views to run animations - * - * @hide + * This class abstracts all tracking for Views to run animations. */ class ViewInfoStore { @@ -136,7 +135,7 @@ void addToOldChangeHolders(long key, ViewHolder holder) { } /** - * Adds the given ViewHolder to the appeared in pre layout list. These are Views added by the + * Adds the given ViewHolder to the onAppeared in pre layout list. These are Views added by the * LayoutManager during a pre-layout pass. We distinguish them from other views that were * already in the pre-layout so that ItemAnimator can choose to run a different animation for * them. diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java index 46b6f9d2a13..2192abdb90c 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/support/widget/helper/ItemTouchHelper.java @@ -143,19 +143,19 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration */ public static final int ANIMATION_TYPE_DRAG = 1 << 3; - private static final String TAG = "ItemTouchHelper"; + static final String TAG = "ItemTouchHelper"; - private static final boolean DEBUG = false; + static final boolean DEBUG = false; - private static final int ACTIVE_POINTER_ID_NONE = -1; + static final int ACTIVE_POINTER_ID_NONE = -1; - private static final int DIRECTION_FLAG_COUNT = 8; + static final int DIRECTION_FLAG_COUNT = 8; private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1; - private static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT; + static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT; - private static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT; + static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT; /** * The unit we are using to track velocity @@ -246,13 +246,13 @@ public class ItemTouchHelper extends RecyclerView.ItemDecoration private int mSlop; - private RecyclerView mRecyclerView; + RecyclerView mRecyclerView; /** * When user drags a view to the edge, we start scrolling the LayoutManager as long as View * is partially out of bounds. */ - private final Runnable mScrollRunnable = new Runnable() { + final Runnable mScrollRunnable = new Runnable() { @Override public void run() { if (mSelected != null && scrollIfNecessary()) { @@ -268,7 +268,7 @@ public void run() { /** * Used for detecting fling swipe */ - private VelocityTracker mVelocityTracker; + VelocityTracker mVelocityTracker; //re-used list for selecting a swap target private List mSwapTargets; @@ -286,19 +286,19 @@ public void run() { * until view reaches its final position (end of recover animation), we keep a reference so * that it can be drawn above other children. */ - private View mOverdrawChild = null; + View mOverdrawChild = null; /** * We cache the position of the overdraw child to avoid recalculating it each time child * position callback is called. This value is invalidated whenever a child is attached or * detached. */ - private int mOverdrawChildPosition = -1; + int mOverdrawChildPosition = -1; /** * Used to detect long press. */ - private GestureDetectorCompat mGestureDetector; + GestureDetectorCompat mGestureDetector; private final OnItemTouchListener mOnItemTouchListener = new OnItemTouchListener() { @@ -310,7 +310,7 @@ public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent even } final int action = MotionEventCompat.getActionMasked(event); if (action == MotionEvent.ACTION_DOWN) { - mActivePointerId = MotionEventCompat.getPointerId(event, 0); + mActivePointerId = event.getPointerId(0); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); obtainVelocityTracker(); @@ -333,7 +333,7 @@ public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent even } else if (mActivePointerId != ACTIVE_POINTER_ID_NONE) { // in a non scroll orientation, if distance change is above threshold, we // can select the item - final int index = MotionEventCompat.findPointerIndex(event, mActivePointerId); + final int index = event.findPointerIndex(mActivePointerId); if (DEBUG) { Log.d(TAG, "pointer index " + index); } @@ -361,8 +361,7 @@ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { return; } final int action = MotionEventCompat.getActionMasked(event); - final int activePointerIndex = MotionEventCompat - .findPointerIndex(event, mActivePointerId); + final int activePointerIndex = event.findPointerIndex(mActivePointerId); if (activePointerIndex >= 0) { checkSelectForSwipe(action, event, activePointerIndex); } @@ -393,12 +392,12 @@ public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { break; case MotionEvent.ACTION_POINTER_UP: { final int pointerIndex = MotionEventCompat.getActionIndex(event); - final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex); + final int pointerId = event.getPointerId(pointerIndex); if (pointerId == mActivePointerId) { // This was our active pointer going up. Choose a new // active pointer and adjust accordingly. final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mActivePointerId = MotionEventCompat.getPointerId(event, newPointerIndex); + mActivePointerId = event.getPointerId(newPointerIndex); updateDxDy(event, mSelectedFlags, pointerIndex); } break; @@ -549,7 +548,7 @@ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { * current action * @param actionState The type of action */ - private void select(ViewHolder selected, int actionState) { + void select(ViewHolder selected, int actionState) { if (selected == mSelected && actionState == mActionState) { return; } @@ -669,7 +668,7 @@ public void onAnimationEnd(ValueAnimatorCompat animation) { mRecyclerView.invalidate(); } - private void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) { + void postDispatchSwipe(final RecoverAnimation anim, final int swipeDir) { // wait until animations are complete. mRecyclerView.post(new Runnable() { @Override @@ -692,7 +691,7 @@ public void run() { }); } - private boolean hasRunningRecoverAnim() { + boolean hasRunningRecoverAnim() { final int size = mRecoverAnimations.size(); for (int i = 0; i < size; i++) { if (!mRecoverAnimations.get(i).mEnded) { @@ -705,7 +704,7 @@ private boolean hasRunningRecoverAnim() { /** * If user drags the view to the edge, trigger a scroll if necessary. */ - private boolean scrollIfNecessary() { + boolean scrollIfNecessary() { if (mSelected == null) { mDragScrollStartTimeInMs = Long.MIN_VALUE; return false; @@ -820,7 +819,7 @@ private List findSwapTargets(ViewHolder viewHolder) { /** * Checks if we should swap w/ another view holder. */ - private void moveIfNecessary(ViewHolder viewHolder) { + void moveIfNecessary(ViewHolder viewHolder) { if (mRecyclerView.isLayoutRequested()) { return; } @@ -880,7 +879,7 @@ public void onChildViewDetachedFromWindow(View view) { /** * Returns the animation type or 0 if cannot be found. */ - private int endRecoverAnimation(ViewHolder viewHolder, boolean override) { + int endRecoverAnimation(ViewHolder viewHolder, boolean override) { final int recoverAnimSize = mRecoverAnimations.size(); for (int i = recoverAnimSize - 1; i >= 0; i--) { final RecoverAnimation anim = mRecoverAnimations.get(i); @@ -902,7 +901,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, outRect.setEmpty(); } - private void obtainVelocityTracker() { + void obtainVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); } @@ -921,9 +920,9 @@ private ViewHolder findSwipedView(MotionEvent motionEvent) { if (mActivePointerId == ACTIVE_POINTER_ID_NONE) { return null; } - final int pointerIndex = MotionEventCompat.findPointerIndex(motionEvent, mActivePointerId); - final float dx = MotionEventCompat.getX(motionEvent, pointerIndex) - mInitialTouchX; - final float dy = MotionEventCompat.getY(motionEvent, pointerIndex) - mInitialTouchY; + final int pointerIndex = motionEvent.findPointerIndex(mActivePointerId); + final float dx = motionEvent.getX(pointerIndex) - mInitialTouchX; + final float dy = motionEvent.getY(pointerIndex) - mInitialTouchY; final float absDx = Math.abs(dx); final float absDy = Math.abs(dy); @@ -945,7 +944,7 @@ private ViewHolder findSwipedView(MotionEvent motionEvent) { /** * Checks whether we should select a View for swiping. */ - private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { + boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { if (mSelected != null || action != MotionEvent.ACTION_MOVE || mActionState == ACTION_STATE_DRAG || !mCallback.isItemViewSwipeEnabled()) { return false; @@ -968,8 +967,8 @@ private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int poi // mDx and mDy are only set in allowed directions. We use custom x/y here instead of // updateDxDy to avoid swiping if user moves more in the other direction - final float x = MotionEventCompat.getX(motionEvent, pointerIndex); - final float y = MotionEventCompat.getY(motionEvent, pointerIndex); + final float x = motionEvent.getX(pointerIndex); + final float y = motionEvent.getY(pointerIndex); // Calculate the distance moved final float dx = x - mInitialTouchX; @@ -998,12 +997,12 @@ private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int poi } } mDx = mDy = 0f; - mActivePointerId = MotionEventCompat.getPointerId(motionEvent, 0); + mActivePointerId = motionEvent.getPointerId(0); select(vh, ACTION_STATE_SWIPE); return true; } - private View findChildView(MotionEvent event) { + View findChildView(MotionEvent event) { // first check elevated views, if none, then call RV final float x = event.getX(); final float y = event.getY(); @@ -1119,7 +1118,7 @@ public void startSwipe(ViewHolder viewHolder) { select(viewHolder, ACTION_STATE_SWIPE); } - private RecoverAnimation findAnimation(MotionEvent event) { + RecoverAnimation findAnimation(MotionEvent event) { if (mRecoverAnimations.isEmpty()) { return null; } @@ -1133,9 +1132,9 @@ private RecoverAnimation findAnimation(MotionEvent event) { return null; } - private void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { - final float x = MotionEventCompat.getX(ev, pointerIndex); - final float y = MotionEventCompat.getY(ev, pointerIndex); + void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { + final float x = ev.getX(pointerIndex); + final float y = ev.getY(pointerIndex); // Calculate the distance moved mDx = x - mInitialTouchX; @@ -1283,7 +1282,7 @@ public int onGetChildDrawingOrder(int childCount, int i) { mRecyclerView.setChildDrawingOrderCallback(mChildDrawingOrderCallback); } - private void removeChildDrawingOrderCallbackIfNecessary(View view) { + void removeChildDrawingOrderCallbackIfNecessary(View view) { if (view == mOverdrawChild) { mOverdrawChild = null; // only remove if we've added @@ -1369,12 +1368,14 @@ public abstract static class Callback { ((LEFT | RIGHT) << (2 * DIRECTION_FLAG_COUNT)); private static final Interpolator sDragScrollInterpolator = new Interpolator() { + @Override public float getInterpolation(float t) { return t * t * t * t * t; } }; private static final Interpolator sDragViewScrollCapInterpolator = new Interpolator() { + @Override public float getInterpolation(float t) { t -= 1.0f; return t * t * t * t * t + 1.0f; @@ -1567,12 +1568,12 @@ final int getAbsoluteMovementFlags(RecyclerView recyclerView, return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView)); } - private boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) { + boolean hasDragFlag(RecyclerView recyclerView, ViewHolder viewHolder) { final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); return (flags & ACTION_MODE_DRAG_MASK) != 0; } - private boolean hasSwipeFlag(RecyclerView recyclerView, + boolean hasSwipeFlag(RecyclerView recyclerView, ViewHolder viewHolder) { final int flags = getAbsoluteMovementFlags(recyclerView, viewHolder); return (flags & ACTION_MODE_SWIPE_MASK) != 0; @@ -1937,7 +1938,7 @@ public void onMoved(final RecyclerView recyclerView, } } - private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, + void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, List recoverAnimationList, int actionState, float dX, float dY) { final int recoverAnimSize = recoverAnimationList.size(); @@ -1956,7 +1957,7 @@ private void onDraw(Canvas c, RecyclerView parent, ViewHolder selected, } } - private void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected, + void onDrawOver(Canvas c, RecyclerView parent, ViewHolder selected, List recoverAnimationList, int actionState, float dX, float dY) { final int recoverAnimSize = recoverAnimationList.size(); @@ -2161,7 +2162,7 @@ public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, * public abstract boolean onMove(RecyclerView recyclerView, * ViewHolder viewHolder, ViewHolder target) { * final int fromPos = viewHolder.getAdapterPosition(); - * final int toPos = viewHolder.getAdapterPosition(); + * final int toPos = target.getAdapterPosition(); * // move item in `fromPos` to `toPos` in adapter. * return true;// true if moved, false otherwise * } @@ -2224,7 +2225,7 @@ public void setDefaultDragDirs(int defaultDragDirs) { * {@link #setDefaultSwipeDirs(int)}. * * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. - * @param viewHolder The RecyclerView for which the swipe drection is queried. + * @param viewHolder The RecyclerView for which the swipe direction is queried. * @return A binary OR of direction flags. */ public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) { @@ -2237,7 +2238,7 @@ public int getSwipeDirs(RecyclerView recyclerView, ViewHolder viewHolder) { * {@link #setDefaultDragDirs(int)}. * * @param recyclerView The RecyclerView to which the ItemTouchHelper is attached to. - * @param viewHolder The RecyclerView for which the swipe drection is queried. + * @param viewHolder The RecyclerView for which the swipe direction is queried. * @return A binary OR of direction flags. */ public int getDragDirs(RecyclerView recyclerView, ViewHolder viewHolder) { @@ -2253,6 +2254,9 @@ public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) { private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { + ItemTouchHelperGestureListener() { + } + @Override public boolean onDown(MotionEvent e) { return true; @@ -2267,15 +2271,14 @@ public void onLongPress(MotionEvent e) { if (!mCallback.hasDragFlag(mRecyclerView, vh)) { return; } - int pointerId = MotionEventCompat.getPointerId(e, 0); + int pointerId = e.getPointerId(0); // Long press is deferred. // Check w/ active pointer id to avoid selecting after motion // event is canceled. if (pointerId == mActivePointerId) { - final int index = MotionEventCompat - .findPointerIndex(e, mActivePointerId); - final float x = MotionEventCompat.getX(e, index); - final float y = MotionEventCompat.getY(e, index); + final int index = e.findPointerIndex(mActivePointerId); + final float x = e.getX(index); + final float y = e.getY(index); mInitialTouchX = x; mInitialTouchY = y; mDx = mDy = 0f; @@ -2308,7 +2311,7 @@ private class RecoverAnimation implements AnimatorListenerCompat { private final ValueAnimatorCompat mValueAnimator; - private final int mAnimationType; + final int mAnimationType; public boolean mIsPendingCleanup; @@ -2320,7 +2323,7 @@ private class RecoverAnimation implements AnimatorListenerCompat { // instantly. boolean mOverridden = false; - private boolean mEnded = false; + boolean mEnded = false; private float mFraction; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java index 943b5883207..e8789a14f49 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MP4Builder.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.video; @@ -16,6 +16,7 @@ import com.coremedia.iso.IsoFile; import com.coremedia.iso.IsoTypeWriter; import com.coremedia.iso.boxes.Box; +import com.coremedia.iso.boxes.CompositionTimeToSample; import com.coremedia.iso.boxes.Container; import com.coremedia.iso.boxes.DataEntryUrlBox; import com.coremedia.iso.boxes.DataInformationBox; @@ -90,7 +91,7 @@ private void flushCurrentMdat() throws Exception { fos.flush(); } - public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean isAudio) throws Exception { + public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean writeLength) throws Exception { if (writeNewMdat) { mdat.setContentSize(0); mdat.getBox(fc); @@ -112,10 +113,10 @@ public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.Bu } currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo); - byteBuf.position(bufferInfo.offset + (isAudio ? 0 : 4)); + byteBuf.position(bufferInfo.offset + (!writeLength ? 0 : 4)); byteBuf.limit(bufferInfo.offset + bufferInfo.size); - if (!isAudio) { + if (writeLength) { sizeBuffer.position(0); sizeBuffer.putInt(bufferInfo.size - 4); sizeBuffer.position(0); @@ -160,8 +161,10 @@ public void finishMovie(boolean error) throws Exception { protected FileTypeBox createFileTypeBox() { LinkedList minorBrands = new LinkedList<>(); minorBrands.add("isom"); - minorBrands.add("3gp4"); - return new FileTypeBox("isom", 0, minorBrands); + minorBrands.add("iso2"); + minorBrands.add("avc1"); + minorBrands.add("mp41"); + return new FileTypeBox("isom", 512, minorBrands); } private class InterleaveChunkMdat implements Box { @@ -258,6 +261,7 @@ protected MovieBox createMovieBox(Mp4Movie movie) { long duration = 0; for (Track track : movie.getTracks()) { + track.prepare(); long tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale(); if (tracksDuration > duration) { duration = tracksDuration; @@ -336,6 +340,7 @@ protected Box createStbl(Track track) { createStsd(track, stbl); createStts(track, stbl); + createCtts(track, stbl); createStss(track, stbl); createStsc(track, stbl); createStsz(track, stbl); @@ -348,11 +353,35 @@ protected void createStsd(Track track, SampleTableBox stbl) { stbl.addBox(track.getSampleDescriptionBox()); } + protected void createCtts(Track track, SampleTableBox stbl) { + int[] sampleCompositions = track.getSampleCompositions(); + if (sampleCompositions == null) { + return; + } + CompositionTimeToSample.Entry lastEntry = null; + List entries = new ArrayList<>(); + + for (int a = 0; a < sampleCompositions.length; a++) { + int offset = sampleCompositions[a]; + if (lastEntry != null && lastEntry.getOffset() == offset) { + lastEntry.setCount(lastEntry.getCount() + 1); + } else { + lastEntry = new CompositionTimeToSample.Entry(1, offset); + entries.add(lastEntry); + } + } + CompositionTimeToSample ctts = new CompositionTimeToSample(); + ctts.setEntries(entries); + stbl.addBox(ctts); + } + protected void createStts(Track track, SampleTableBox stbl) { TimeToSampleBox.Entry lastEntry = null; List entries = new ArrayList<>(); + long[] deltas = track.getSampleDurations(); - for (long delta : track.getSampleDurations()) { + for (int a = 0; a < deltas.length; a++) { + long delta = deltas[a]; if (lastEntry != null && lastEntry.getDelta() == delta) { lastEntry.setCount(lastEntry.getCount() + 1); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java index 81dc67d9288..3b8624b766e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Mp4Movie.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.video; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Sample.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Sample.java index d73c5b29990..94950ae39a3 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Sample.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Sample.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.video; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java index 22762d7d031..f45e08587a5 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/Track.java @@ -3,13 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.messenger.video; import android.annotation.TargetApi; import android.media.MediaCodec; +import android.media.MediaCodecInfo; import android.media.MediaFormat; import com.coremedia.iso.boxes.AbstractMediaHeaderBox; @@ -27,6 +28,8 @@ import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; @@ -34,9 +37,23 @@ @TargetApi(16) public class Track { + + private class SamplePresentationTime { + + private int index; + private long presentationTime; + private long dt; + + public SamplePresentationTime(int idx, long time) { + index = idx; + presentationTime = time; + } + } + private long trackId = 0; private ArrayList samples = new ArrayList<>(); private long duration = 0; + private int[] sampleCompositions; private String handler; private AbstractMediaHeaderBox headerBox = null; private SampleDescriptionBox sampleDescriptionBox = null; @@ -46,10 +63,10 @@ public class Track { private int height; private int width; private float volume = 0; - private ArrayList sampleDurations = new ArrayList<>(); + private long[] sampleDurations; + private ArrayList samplePresentationTimes = new ArrayList<>(); private boolean isAudio = false; private static Map samplingFrequencyIndexMap = new HashMap<>(); - private long lastPresentationTimeUs = 0; private boolean first = true; static { @@ -71,8 +88,8 @@ public Track(int id, MediaFormat format, boolean audio) throws Exception { trackId = id; isAudio = audio; if (!isAudio) { - sampleDurations.add((long) 3015); - duration = 3015; + //sampleDurations.add((long) 3015); + //duration = 3015; width = format.getInteger(MediaFormat.KEY_WIDTH); height = format.getInteger(MediaFormat.KEY_HEIGHT); timeScale = 90000; @@ -111,8 +128,66 @@ public Track(int id, MediaFormat format, boolean audio) throws Exception { avcConfigurationBox.setPictureParameterSets(ppsArray); } - avcConfigurationBox.setAvcLevelIndication(13); - avcConfigurationBox.setAvcProfileIndication(100); + if (format.containsKey("level")) { + int level = format.getInteger("level"); + if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1) { + avcConfigurationBox.setAvcLevelIndication(1); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel2) { + avcConfigurationBox.setAvcLevelIndication(2); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel11) { + avcConfigurationBox.setAvcLevelIndication(11); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel12) { + avcConfigurationBox.setAvcLevelIndication(12); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel13) { + avcConfigurationBox.setAvcLevelIndication(13); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel21) { + avcConfigurationBox.setAvcLevelIndication(21); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel22) { + avcConfigurationBox.setAvcLevelIndication(22); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel3) { + avcConfigurationBox.setAvcLevelIndication(3); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel31) { + avcConfigurationBox.setAvcLevelIndication(31); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel32) { + avcConfigurationBox.setAvcLevelIndication(32); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel4) { + avcConfigurationBox.setAvcLevelIndication(4); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel41) { + avcConfigurationBox.setAvcLevelIndication(41); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel42) { + avcConfigurationBox.setAvcLevelIndication(42); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel5) { + avcConfigurationBox.setAvcLevelIndication(5); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel51) { + avcConfigurationBox.setAvcLevelIndication(51); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel52) { + avcConfigurationBox.setAvcLevelIndication(52); + } else if (level == MediaCodecInfo.CodecProfileLevel.AVCLevel1b) { + avcConfigurationBox.setAvcLevelIndication(0x1b); + } + } else { + avcConfigurationBox.setAvcLevelIndication(13); + } + if (format.containsKey("profile")) { + int profile = format.getInteger("profile"); + if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline) { + avcConfigurationBox.setAvcProfileIndication(66); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileMain) { + avcConfigurationBox.setAvcProfileIndication(77); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileExtended) { + avcConfigurationBox.setAvcProfileIndication(88); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh) { + avcConfigurationBox.setAvcProfileIndication(100); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10) { + avcConfigurationBox.setAvcProfileIndication(110); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422) { + avcConfigurationBox.setAvcProfileIndication(122); + } else if (profile == MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444) { + avcConfigurationBox.setAvcProfileIndication(244); + } + } else { + avcConfigurationBox.setAvcProfileIndication(100); + } avcConfigurationBox.setBitDepthLumaMinus8(-1); avcConfigurationBox.setBitDepthChromaMinus8(-1); avcConfigurationBox.setChromaFormat(-1); @@ -135,8 +210,8 @@ public Track(int id, MediaFormat format, boolean audio) throws Exception { sampleDescriptionBox.addBox(visualSampleEntry); } } else { - sampleDurations.add((long) 1024); - duration = 1024; + //sampleDurations.add((long) 1024); + //duration = 1024; volume = 1; timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); handler = "soun"; @@ -184,23 +259,64 @@ public long getTrackId() { } public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) { - long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs; - if (delta < 0) { - return; - } boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; samples.add(new Sample(offset, bufferInfo.size)); if (syncSamples != null && isSyncFrame) { syncSamples.add(samples.size()); } + samplePresentationTimes.add(new SamplePresentationTime(samplePresentationTimes.size(), (bufferInfo.presentationTimeUs * timeScale + 500000L) / 1000000L)); + } - delta = (delta * timeScale + 500000L) / 1000000L; - lastPresentationTimeUs = bufferInfo.presentationTimeUs; - if (!first) { - sampleDurations.add(sampleDurations.size() - 1, delta); - duration += delta; + public void prepare() { + ArrayList original = new ArrayList<>(samplePresentationTimes); + Collections.sort(samplePresentationTimes, new Comparator() { + @Override + public int compare(SamplePresentationTime o1, SamplePresentationTime o2) { + if (o1.presentationTime > o2.presentationTime) { + return 1; + } else if (o1.presentationTime < o2.presentationTime) { + return -1; + } + return 0; + } + }); + long lastPresentationTimeUs = 0; + sampleDurations = new long[samplePresentationTimes.size()]; + long minDelta = Long.MAX_VALUE; + boolean outOfOrder = false; + for (int a = 0; a < samplePresentationTimes.size(); a++) { + SamplePresentationTime presentationTime = samplePresentationTimes.get(a); + long delta = presentationTime.presentationTime - lastPresentationTimeUs; + lastPresentationTimeUs = presentationTime.presentationTime; + sampleDurations[presentationTime.index] = delta; + if (presentationTime.index != 0) { + duration += delta; + } + if (delta != 0) { + minDelta = Math.min(minDelta, delta); + } + if (presentationTime.index != a) { + outOfOrder = true; + } + } + if (sampleDurations.length > 0) { + sampleDurations[0] = minDelta; + duration += minDelta; } - first = false; + for (int a = 1; a < original.size(); a++) { + original.get(a).dt = sampleDurations[a] + original.get(a - 1).dt; + } + if (outOfOrder) { + sampleCompositions = new int[samplePresentationTimes.size()]; + for (int a = 0; a < samplePresentationTimes.size(); a++) { + SamplePresentationTime presentationTime = samplePresentationTimes.get(a); + sampleCompositions[presentationTime.index] = (int) (presentationTime.presentationTime - presentationTime.dt); + } + } + //if (!first) { + // sampleDurations.add(sampleDurations.size() - 1, delta); + // duration += delta; + //} } public ArrayList getSamples() { @@ -219,6 +335,10 @@ public AbstractMediaHeaderBox getMediaHeaderBox() { return headerBox; } + public int[] getSampleCompositions() { + return sampleCompositions; + } + public SampleDescriptionBox getSampleDescriptionBox() { return sampleDescriptionBox; } @@ -254,7 +374,7 @@ public float getVolume() { return volume; } - public ArrayList getSampleDurations() { + public long[] getSampleDurations() { return sampleDurations; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioRecordJNI.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioRecordJNI.java new file mode 100755 index 00000000000..e8102fd8be5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioRecordJNI.java @@ -0,0 +1,170 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.messenger.voip; + +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.media.audiofx.AcousticEchoCanceler; +import android.media.audiofx.AutomaticGainControl; +import android.media.audiofx.NoiseSuppressor; +import android.os.Build; +import android.util.Log; + +import org.telegram.messenger.FileLog; + +import java.nio.ByteBuffer; + +public class AudioRecordJNI { + + private AudioRecord audioRecord; + private ByteBuffer buffer; + private boolean running; + private Thread thread; + private int bufferSize; + private long nativeInst; + private AutomaticGainControl agc; + private NoiseSuppressor ns; + private AcousticEchoCanceler aec; + + public AudioRecordJNI(long nativeInst) { + this.nativeInst = nativeInst; + } + + private int getBufferSize(int min) { + return Math.max(AudioRecord.getMinBufferSize(48000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT), min); + } + + public void init(int sampleRate, int bitsPerSample, int channels, int bufferSize) { + if (audioRecord != null) { + throw new IllegalStateException("already inited"); + } + int size = getBufferSize(bufferSize); + this.bufferSize = bufferSize; + try{ + audioRecord=new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRate, channels==1 ? AudioFormat.CHANNEL_IN_MONO : AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT, size); + }catch(Exception x){ + FileLog.e("AudioRecord init failed!", x); + } + buffer = ByteBuffer.allocateDirect(bufferSize); + } + + public void stop() { + if(audioRecord!=null) + audioRecord.stop(); + } + + public void release() { + running = false; + if(thread!=null){ + try{ + thread.join(); + }catch(InterruptedException e){ + FileLog.e(e); + } + thread = null; + } + if(audioRecord!=null){ + audioRecord.release(); + audioRecord=null; + } + if(agc!=null){ + agc.release(); + agc=null; + } + if(ns!=null){ + ns.release(); + ns=null; + } + if(aec!=null){ + aec.release(); + aec=null; + } + } + + public boolean start() { + try{ + if(thread==null){ + if(audioRecord==null) + return false; + audioRecord.startRecording(); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN){ + try{ + if(AutomaticGainControl.isAvailable()){ + agc=AutomaticGainControl.create(audioRecord.getAudioSessionId()); + if(agc!=null) + agc.setEnabled(false); + }else{ + FileLog.w("AutomaticGainControl is not available on this device :("); + } + }catch(Throwable x){ + FileLog.e("error creating AutomaticGainControl", x); + } + try{ + if(NoiseSuppressor.isAvailable()){ + ns=NoiseSuppressor.create(audioRecord.getAudioSessionId()); + if(ns!=null) + ns.setEnabled(VoIPServerConfig.getBoolean("user_system_ns", true)); + }else{ + FileLog.w("NoiseSuppressor is not available on this device :("); + } + }catch(Throwable x){ + FileLog.e("error creating NoiseSuppressor", x); + } + try{ + if(AcousticEchoCanceler.isAvailable()){ + aec=AcousticEchoCanceler.create(audioRecord.getAudioSessionId()); + if(aec!=null) + aec.setEnabled(VoIPServerConfig.getBoolean("use_system_aec", true)); + }else{ + FileLog.w("AcousticEchoCanceler is not available on this device"); + } + }catch(Throwable x){ + FileLog.e("error creating AcousticEchoCanceler", x); + } + } + startThread(); + }else{ + audioRecord.startRecording(); + } + return true; + }catch(Exception x){ + FileLog.e("Error initializing AudioRecord", x); + } + return false; + } + + private void startThread() { + if (thread != null) { + throw new IllegalStateException("thread already started"); + } + running = true; + thread = new Thread(new Runnable() { + @Override + public void run() { + while (running) { + try { + audioRecord.read(buffer, 960*2); + if (!running) { + audioRecord.stop(); + break; + } + nativeCallback(buffer); + } catch (Exception e) { + FileLog.e(e); + } + } + Log.i("tg-voip", "audiotrack thread exits"); + } + }); + thread.start(); + } + + private native void nativeCallback(ByteBuffer buf); +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioTrackJNI.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioTrackJNI.java new file mode 100644 index 00000000000..7065ae5681a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/AudioTrackJNI.java @@ -0,0 +1,108 @@ +package org.telegram.messenger.voip; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioRecord; +import android.media.AudioTrack; +import android.media.MediaRecorder; +import android.media.audiofx.AutomaticGainControl; +import android.media.audiofx.NoiseSuppressor; +import android.os.Build; +import android.util.Log; + +import org.telegram.messenger.FileLog; + +import java.nio.ByteBuffer; + +/** + * Created by grishka on 20.12.16. + */ + +public class AudioTrackJNI{ + private AudioTrack audioTrack; + private byte[] buffer=new byte[960*2]; + private boolean running; + private Thread thread; + private int bufferSize; + private long nativeInst; + + public AudioTrackJNI(long nativeInst) { + this.nativeInst = nativeInst; + } + + private int getBufferSize(int min) { + return Math.max(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT), min); + } + + public void init(int sampleRate, int bitsPerSample, int channels, int bufferSize) { + if (audioTrack != null) { + throw new IllegalStateException("already inited"); + } + int size = getBufferSize(bufferSize); + this.bufferSize = bufferSize; + audioTrack=new AudioTrack(AudioManager.STREAM_VOICE_CALL, sampleRate, channels==1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, size, AudioTrack.MODE_STREAM); + } + + public void stop() { + if(audioTrack!=null) + audioTrack.stop(); + } + + public void release() { + running = false; + if(thread!=null){ + try{ + thread.join(); + }catch(InterruptedException e){ + FileLog.e(e); + } + thread=null; + } + if(audioTrack!=null){ + audioTrack.release(); + audioTrack=null; + } + } + + public void start() { + if (thread == null) { + startThread(); + } else { + audioTrack.play(); + } + } + + private void startThread() { + if (thread != null) { + throw new IllegalStateException("thread already started"); + } + running = true; + thread = new Thread(new Runnable() { + @Override + public void run() { + try{ + audioTrack.play(); + }catch(Exception x){ + FileLog.e("error starting AudioTrack", x); + return; + } + while (running) { + try { + nativeCallback(buffer); + audioTrack.write(buffer, 0, 960*2); + if (!running) { + audioTrack.stop(); + break; + } + } catch (Exception e) { + FileLog.e(e); + } + } + Log.i("tg-voip", "audiotrack thread exits"); + } + }); + thread.start(); + } + + private native void nativeCallback(byte[] buf); +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/EncryptionKeyEmojifier.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/EncryptionKeyEmojifier.java new file mode 100644 index 00000000000..1f4a4835970 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/EncryptionKeyEmojifier.java @@ -0,0 +1,69 @@ +package org.telegram.messenger.voip; + +/** + * Created by grishka on 13.03.17. + */ + +public class EncryptionKeyEmojifier{ + private static final String[] emojis={"\uD83D\uDE09","\uD83D\uDE0D","\uD83D\uDE1B","\uD83D\uDE2D","\uD83D\uDE31","\uD83D\uDE21","\uD83D\uDE0E","\uD83D\uDE34", + "\uD83D\uDE35","\uD83D\uDE08","\uD83D\uDE2C","\uD83D\uDE07","\uD83D\uDE0F","\uD83D\uDC6E","\uD83D\uDC77","\uD83D\uDC82","\uD83D\uDC76","\uD83D\uDC68", + "\uD83D\uDC69","\uD83D\uDC74","\uD83D\uDC75","\uD83D\uDE3B","\uD83D\uDE3D","\uD83D\uDE40","\uD83D\uDC7A","\uD83D\uDE48","\uD83D\uDE49","\uD83D\uDE4A", + "\uD83D\uDC80","\uD83D\uDC7D","\uD83D\uDCA9","\uD83D\uDD25","\uD83D\uDCA5","\uD83D\uDCA4","\uD83D\uDC42","\uD83D\uDC40","\uD83D\uDC43","\uD83D\uDC45", + "\uD83D\uDC44","\uD83D\uDC4D","\uD83D\uDC4E","\uD83D\uDC4C","\uD83D\uDC4A","✌","✋","\uD83D\uDC50","\uD83D\uDC46","\uD83D\uDC47","\uD83D\uDC49", + "\uD83D\uDC48","\uD83D\uDE4F","\uD83D\uDC4F","\uD83D\uDCAA","\uD83D\uDEB6","\uD83C\uDFC3","\uD83D\uDC83","\uD83D\uDC6B","\uD83D\uDC6A","\uD83D\uDC6C", + "\uD83D\uDC6D","\uD83D\uDC85","\uD83C\uDFA9","\uD83D\uDC51","\uD83D\uDC52","\uD83D\uDC5F","\uD83D\uDC5E","\uD83D\uDC60","\uD83D\uDC55","\uD83D\uDC57", + "\uD83D\uDC56","\uD83D\uDC59","\uD83D\uDC5C","\uD83D\uDC53","\uD83C\uDF80","\uD83D\uDC84","\uD83D\uDC9B","\uD83D\uDC99","\uD83D\uDC9C","\uD83D\uDC9A", + "\uD83D\uDC8D","\uD83D\uDC8E","\uD83D\uDC36","\uD83D\uDC3A","\uD83D\uDC31","\uD83D\uDC2D","\uD83D\uDC39","\uD83D\uDC30","\uD83D\uDC38","\uD83D\uDC2F", + "\uD83D\uDC28","\uD83D\uDC3B","\uD83D\uDC37","\uD83D\uDC2E","\uD83D\uDC17","\uD83D\uDC34","\uD83D\uDC11","\uD83D\uDC18","\uD83D\uDC3C","\uD83D\uDC27", + "\uD83D\uDC25","\uD83D\uDC14","\uD83D\uDC0D","\uD83D\uDC22","\uD83D\uDC1B","\uD83D\uDC1D","\uD83D\uDC1C","\uD83D\uDC1E","\uD83D\uDC0C","\uD83D\uDC19", + "\uD83D\uDC1A","\uD83D\uDC1F","\uD83D\uDC2C","\uD83D\uDC0B","\uD83D\uDC10","\uD83D\uDC0A","\uD83D\uDC2B","\uD83C\uDF40","\uD83C\uDF39","\uD83C\uDF3B", + "\uD83C\uDF41","\uD83C\uDF3E","\uD83C\uDF44","\uD83C\uDF35","\uD83C\uDF34","\uD83C\uDF33","\uD83C\uDF1E","\uD83C\uDF1A","\uD83C\uDF19","\uD83C\uDF0E", + "\uD83C\uDF0B","⚡","☔","❄","⛄","\uD83C\uDF00","\uD83C\uDF08","\uD83C\uDF0A","\uD83C\uDF93","\uD83C\uDF86","\uD83C\uDF83","\uD83D\uDC7B","\uD83C\uDF85", + "\uD83C\uDF84","\uD83C\uDF81","\uD83C\uDF88","\uD83D\uDD2E","\uD83C\uDFA5","\uD83D\uDCF7","\uD83D\uDCBF","\uD83D\uDCBB","☎","\uD83D\uDCE1","\uD83D\uDCFA", + "\uD83D\uDCFB","\uD83D\uDD09","\uD83D\uDD14","⏳","⏰","⌚","\uD83D\uDD12","\uD83D\uDD11","\uD83D\uDD0E","\uD83D\uDCA1","\uD83D\uDD26","\uD83D\uDD0C", + "\uD83D\uDD0B","\uD83D\uDEBF","\uD83D\uDEBD","\uD83D\uDD27","\uD83D\uDD28","\uD83D\uDEAA","\uD83D\uDEAC","\uD83D\uDCA3","\uD83D\uDD2B","\uD83D\uDD2A", + "\uD83D\uDC8A","\uD83D\uDC89","\uD83D\uDCB0","\uD83D\uDCB5","\uD83D\uDCB3","✉","\uD83D\uDCEB","\uD83D\uDCE6","\uD83D\uDCC5","\uD83D\uDCC1","✂","\uD83D\uDCCC", + "\uD83D\uDCCE","✒","✏","\uD83D\uDCD0","\uD83D\uDCDA","\uD83D\uDD2C","\uD83D\uDD2D","\uD83C\uDFA8","\uD83C\uDFAC","\uD83C\uDFA4","\uD83C\uDFA7","\uD83C\uDFB5", + "\uD83C\uDFB9","\uD83C\uDFBB","\uD83C\uDFBA","\uD83C\uDFB8","\uD83D\uDC7E","\uD83C\uDFAE","\uD83C\uDCCF","\uD83C\uDFB2","\uD83C\uDFAF","\uD83C\uDFC8", + "\uD83C\uDFC0","⚽","⚾","\uD83C\uDFBE","\uD83C\uDFB1","\uD83C\uDFC9","\uD83C\uDFB3","\uD83C\uDFC1","\uD83C\uDFC7","\uD83C\uDFC6","\uD83C\uDFCA","\uD83C\uDFC4", + "☕","\uD83C\uDF7C","\uD83C\uDF7A","\uD83C\uDF77","\uD83C\uDF74","\uD83C\uDF55","\uD83C\uDF54","\uD83C\uDF5F","\uD83C\uDF57","\uD83C\uDF71","\uD83C\uDF5A", + "\uD83C\uDF5C","\uD83C\uDF61","\uD83C\uDF73","\uD83C\uDF5E","\uD83C\uDF69","\uD83C\uDF66","\uD83C\uDF82","\uD83C\uDF70","\uD83C\uDF6A","\uD83C\uDF6B", + "\uD83C\uDF6D","\uD83C\uDF6F","\uD83C\uDF4E","\uD83C\uDF4F","\uD83C\uDF4A","\uD83C\uDF4B","\uD83C\uDF52","\uD83C\uDF47","\uD83C\uDF49","\uD83C\uDF53", + "\uD83C\uDF51","\uD83C\uDF4C","\uD83C\uDF50","\uD83C\uDF4D","\uD83C\uDF46","\uD83C\uDF45","\uD83C\uDF3D","\uD83C\uDFE1","\uD83C\uDFE5","\uD83C\uDFE6", + "⛪","\uD83C\uDFF0","⛺","\uD83C\uDFED","\uD83D\uDDFB","\uD83D\uDDFD","\uD83C\uDFA0","\uD83C\uDFA1","⛲","\uD83C\uDFA2","\uD83D\uDEA2","\uD83D\uDEA4", + "⚓","\uD83D\uDE80","✈","\uD83D\uDE81","\uD83D\uDE82","\uD83D\uDE8B","\uD83D\uDE8E","\uD83D\uDE8C","\uD83D\uDE99","\uD83D\uDE97","\uD83D\uDE95","\uD83D\uDE9B", + "\uD83D\uDEA8","\uD83D\uDE94","\uD83D\uDE92","\uD83D\uDE91","\uD83D\uDEB2","\uD83D\uDEA0","\uD83D\uDE9C","\uD83D\uDEA6","⚠","\uD83D\uDEA7","⛽","\uD83C\uDFB0", + "\uD83D\uDDFF","\uD83C\uDFAA","\uD83C\uDFAD","\uD83C\uDDEF\uD83C\uDDF5","\uD83C\uDDF0\uD83C\uDDF7","\uD83C\uDDE9\uD83C\uDDEA","\uD83C\uDDE8\uD83C\uDDF3", + "\uD83C\uDDFA\uD83C\uDDF8","\uD83C\uDDEB\uD83C\uDDF7","\uD83C\uDDEA\uD83C\uDDF8","\uD83C\uDDEE\uD83C\uDDF9","\uD83C\uDDF7\uD83C\uDDFA","\uD83C\uDDEC\uD83C\uDDE7", + "1⃣","2⃣","3⃣","4⃣","5⃣","6⃣","7⃣","8⃣","9⃣","0⃣","\uD83D\uDD1F","❗","❓","♥","♦","\uD83D\uDCAF","\uD83D\uDD17","\uD83D\uDD31","\uD83D\uDD34", + "\uD83D\uDD35","\uD83D\uDD36","\uD83D\uDD37"}; + private static final int[] offsets={0, 4, 8, 12, 16}; + + private static int bytesToInt(byte[] arr, int offset){ + return (((int)arr[offset] & 0x7F) << 24) | (((int)arr[offset+1] & 0xFF) << 16) | (((int)arr[offset+2] & 0xFF) << 8) | ((int)arr[offset+3] & 0xFF); + } + + private static long bytesToLong(byte[] arr, int offset){ + return (((long)arr[offset] & 0x7F) << 56) | (((long)arr[offset+1] & 0xFF) << 48) | (((long)arr[offset+2] & 0xFF) << 40) | (((long)arr[offset+3] & 0xFF) << 32) | + (((long)arr[offset+4] & 0xFF) << 24) | (((long)arr[offset+5] & 0xFF) << 16) | (((long)arr[offset+6] & 0xFF) << 8) | (((long) arr[offset+7] & 0xFF)); + + } + + public static String[] emojify(byte[] sha256){ + if(sha256.length!=32) + throw new IllegalArgumentException("sha256 needs to be exactly 32 bytes"); + String[] result=new String[5]; + for(int i=0;i<5;i++){ + result[i]=emojis[bytesToInt(sha256, offsets[i])%emojis.length]; + } + return result; + } + + public static String[] emojifyForCall(byte[] sha256){ + String[] result=new String[4]; + for(int i=0;i<4;i++){ + result[i]=emojis[(int)(bytesToLong(sha256, 8*i)%emojis.length)]; + } + return result; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java new file mode 100755 index 00000000000..db701542d6c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/voip/VoIPController.java @@ -0,0 +1,239 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.messenger.voip; + +import android.media.audiofx.AcousticEchoCanceler; +import android.media.audiofx.NoiseSuppressor; +import android.os.Build; +import android.os.SystemClock; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildConfig; +import org.telegram.tgnet.TLRPC; + +import java.io.File; +import java.util.Calendar; +import java.util.Locale; + +public class VoIPController { + + public static final int NET_TYPE_UNKNOWN = 0; + public static final int NET_TYPE_GPRS = 1; + public static final int NET_TYPE_EDGE = 2; + public static final int NET_TYPE_3G = 3; + public static final int NET_TYPE_HSPA = 4; + public static final int NET_TYPE_LTE = 5; + public static final int NET_TYPE_WIFI = 6; + public static final int NET_TYPE_ETHERNET = 7; + public static final int NET_TYPE_OTHER_HIGH_SPEED = 8; + public static final int NET_TYPE_OTHER_LOW_SPEED = 9; + public static final int NET_TYPE_DIALUP = 10; + public static final int NET_TYPE_OTHER_MOBILE = 11; + + public static final int STATE_WAIT_INIT = 1; + public static final int STATE_WAIT_INIT_ACK = 2; + public static final int STATE_ESTABLISHED = 3; + public static final int STATE_FAILED = 4; + + public static final int DATA_SAVING_NEVER=0; + public static final int DATA_SAVING_MOBILE=1; + public static final int DATA_SAVING_ALWAYS=2; + + public static final int ERROR_LOCALIZED=-3; + public static final int ERROR_PRIVACY=-2; + public static final int ERROR_PEER_OUTDATED=-1; + public static final int ERROR_UNKNOWN=0; + public static final int ERROR_INCOMPATIBLE=1; + public static final int ERROR_TIMEOUT=2; + public static final int ERROR_AUDIO_IO=3; + + private long nativeInst = 0; + private long callStartTime; + private ConnectionStateListener listener; + + public VoIPController() { + nativeInst = nativeInit(Build.VERSION.SDK_INT); + } + + public void start() { + ensureNativeInstance(); + nativeStart(nativeInst); + } + + public void connect() { + ensureNativeInstance(); + nativeConnect(nativeInst); + } + + public void setRemoteEndpoints(TLRPC.TL_phoneConnection[] endpoints, boolean allowP2p) { + if (endpoints.length == 0) { + throw new IllegalArgumentException("endpoints size is 0"); + } + for (int a = 0; a < endpoints.length; a++) { + TLRPC.TL_phoneConnection endpoint = endpoints[a]; + if (endpoint.ip == null || endpoint.ip.length() == 0) { + throw new IllegalArgumentException("endpoint " + endpoint + " has empty/null ipv4"); + } + if (endpoint.peer_tag != null && endpoint.peer_tag.length != 16) { + throw new IllegalArgumentException("endpoint " + endpoint + " has peer_tag of wrong length"); + } + } + ensureNativeInstance(); + nativeSetRemoteEndpoints(nativeInst, endpoints, allowP2p); + } + + public void setEncryptionKey(byte[] key, boolean isOutgoing) { + if (key.length != 256) { + throw new IllegalArgumentException("key length must be exactly 256 bytes but is " + key.length); + } + ensureNativeInstance(); + nativeSetEncryptionKey(nativeInst, key, isOutgoing); + } + + public static void setNativeBufferSize(int size) { + nativeSetNativeBufferSize(size); + } + + public void release() { + ensureNativeInstance(); + nativeRelease(nativeInst); + nativeInst = 0; + } + + public String getDebugString() { + ensureNativeInstance(); + return nativeGetDebugString(nativeInst); + } + + private void ensureNativeInstance() { + if (nativeInst == 0) { + throw new IllegalStateException("Native instance is not valid"); + } + } + + public void setConnectionStateListener(ConnectionStateListener connectionStateListener) { + listener = connectionStateListener; + } + + private void handleStateChange(int state) { + callStartTime = SystemClock.elapsedRealtime(); + if (listener != null) { + listener.onConnectionStateChanged(state); + } + } + + public void setNetworkType(int type) { + ensureNativeInstance(); + nativeSetNetworkType(nativeInst, type); + } + + public long getCallDuration() { + return SystemClock.elapsedRealtime() - callStartTime; + } + + public void setMicMute(boolean mute) { + ensureNativeInstance(); + nativeSetMicMute(nativeInst, mute); + } + + public void setConfig(double recvTimeout, double initTimeout, int dataSavingOption){ + ensureNativeInstance(); + boolean sysAecAvailable=false, sysNsAvailable=false; + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN){ + try{ + sysAecAvailable=AcousticEchoCanceler.isAvailable(); + sysNsAvailable=AcousticEchoCanceler.isAvailable(); + }catch(Throwable x){ + + } + } + nativeSetConfig(nativeInst, recvTimeout, initTimeout, dataSavingOption, + Build.VERSION.SDK_INT stateListeners = new ArrayList<>(); + private MediaPlayer ringtonePlayer; + private Vibrator vibrator; + private SoundPool soundPool; + private int spRingbackID, spFailedID, spEndId, spBusyId, spConnectingId; + private int spPlayID; + private boolean needPlayEndSound; + private Runnable timeoutRunnable; + private boolean haveAudioFocus; + private boolean playingSound; + private boolean micMute; + private boolean controllerStarted; + private long lastKnownDuration = 0; + private VoIPController.Stats stats = new VoIPController.Stats(), prevStats = new VoIPController.Stats(); + private NetworkInfo lastNetInfo; + private Boolean mHasEarpiece = null; + private BluetoothAdapter btAdapter; + private boolean isBtHeadsetConnected; + private boolean needSendDebugLog=false; + private boolean endCallAfterRequest=false; + private ArrayList pendingUpdates=new ArrayList<>(); + + public static final String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; + + private BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (ACTION_HEADSET_PLUG.equals(intent.getAction())) { + isHeadsetPlugged = intent.getIntExtra("state", 0) == 1; + if (isHeadsetPlugged && proximityWakelock != null && proximityWakelock.isHeld()) { + proximityWakelock.release(); + } + isProximityNear = false; + } else if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { + updateNetworkType(); + } else if ((getPackageName() + ".END_CALL").equals(intent.getAction())) { + if (intent.getIntExtra("end_hash", 0) == endHash) { + stopForeground(true); + hangUp(); + } + } else if ((getPackageName() + ".DECLINE_CALL").equals(intent.getAction())) { + if (intent.getIntExtra("end_hash", 0) == endHash) { + stopForeground(true); + declineIncomingCall(DISCARD_REASON_LINE_BUSY, null); + } + } else if ((getPackageName() + ".ANSWER_CALL").equals(intent.getAction())) { + if (intent.getIntExtra("end_hash", 0) == endHash) { + showNotification(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + try { + PendingIntent.getActivity(VoIPService.this, 0, new Intent(VoIPService.this, VoIPPermissionActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0).send(); + } catch (Exception x) { + FileLog.e("Error starting permission activity", x); + } + return; + } + acceptIncomingCall(); + try { + PendingIntent.getActivity(VoIPService.this, 0, new Intent(VoIPService.this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP), 0).send(); + } catch (Exception x) { + FileLog.e("Error starting incall activity", x); + } + } + }else if(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())){ + //FileLog.e("bt headset state = "+intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)); + updateBluetoothHeadsetState(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0)==BluetoothProfile.STATE_CONNECTED); + }else if(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED.equals(intent.getAction())){ + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + }else if(TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())){ + String state=intent.getStringExtra(TelephonyManager.EXTRA_STATE); + if(TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)){ + hangUp(); + } + } + } + }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if(sharedInstance!=null){ + FileLog.e("Tried to start the VoIP service when it's already started"); + return START_NOT_STICKY; + } + userID = intent.getIntExtra("user_id", 0); + isOutgoing = intent.getBooleanExtra("is_outgoing", false); + user = MessagesController.getInstance().getUser(userID); + + if(user==null){ + FileLog.w("VoIPService: user==null"); + stopSelf(); + return START_NOT_STICKY; + } + + if (isOutgoing) { + startOutgoingCall(); + if (intent.getBooleanExtra("start_incall_activity", false)) { + startActivity(new Intent(this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } else { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeInCallActivity); + call = callIShouldHavePutIntoIntent; + callIShouldHavePutIntoIntent = null; + acknowledgeCallAndStartRinging(); + } + sharedInstance = this; + + + return START_NOT_STICKY; + } + + @Override + public void onCreate() { + super.onCreate(); + FileLog.d("=============== VoIPService STARTING ==============="); + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)!=null) { + int outFramesPerBuffer = Integer.parseInt(am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)); + VoIPController.setNativeBufferSize(outFramesPerBuffer); + } else { + VoIPController.setNativeBufferSize(AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT) / 2); + } + final SharedPreferences preferences = getSharedPreferences("mainconfig", MODE_PRIVATE); + VoIPServerConfig.setConfig(preferences.getString("voip_server_config", "{}")); + if(System.currentTimeMillis()-preferences.getLong("voip_server_config_updated", 0)>24*3600000){ + ConnectionsManager.getInstance().sendRequest(new TLRPC.TL_phone_getCallConfig(), new RequestDelegate(){ + @Override + public void run(TLObject response, TLRPC.TL_error error){ + if(error==null){ + String data=((TLRPC.TL_dataJSON) response).data; + VoIPServerConfig.setConfig(data); + preferences.edit().putString("voip_server_config", data).putLong("voip_server_config_updated", BuildConfig.DEBUG ? 0 : System.currentTimeMillis()).apply(); + } + } + }); + } + try { + controller = new VoIPController(); + controller.setConnectionStateListener(this); + controller.setConfig(MessagesController.getInstance().callPacketTimeout / 1000.0, MessagesController.getInstance().callConnectTimeout / 1000.0, + preferences.getInt("VoipDataSaving", VoIPController.DATA_SAVING_NEVER)); + + cpuWakelock = ((PowerManager) getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "telegram-voip"); + cpuWakelock.acquire(); + + btAdapter=am.isBluetoothScoAvailableOffCall() ? BluetoothAdapter.getDefaultAdapter() : null; + + IntentFilter filter = new IntentFilter(); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(ACTION_HEADSET_PLUG); + if(btAdapter!=null){ + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED); + } + filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED); + filter.addAction(getPackageName() + ".END_CALL"); + filter.addAction(getPackageName() + ".DECLINE_CALL"); + filter.addAction(getPackageName() + ".ANSWER_CALL"); + registerReceiver(receiver, filter); + + ConnectionsManager.getInstance().setAppPaused(false, false); + + soundPool = new SoundPool(1, AudioManager.STREAM_VOICE_CALL, 0); + spConnectingId = soundPool.load(this, R.raw.voip_connecting, 1); + spRingbackID = soundPool.load(this, R.raw.voip_ringback, 1); + spFailedID = soundPool.load(this, R.raw.voip_failed, 1); + spEndId = soundPool.load(this, R.raw.voip_end, 1); + spBusyId = soundPool.load(this, R.raw.voip_busy, 1); + + am.registerMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); + + if(btAdapter!=null && btAdapter.isEnabled()){ + int headsetState=btAdapter.getProfileConnectionState(BluetoothProfile.HEADSET); + updateBluetoothHeadsetState(headsetState==BluetoothProfile.STATE_CONNECTED); + if(headsetState==BluetoothProfile.STATE_CONNECTED) + am.setBluetoothScoOn(true); + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + } + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.appDidLogout); + } catch (Exception x) { + FileLog.e("error initializing voip controller", x); + callFailed(); + } + } + + @Override + public void onDestroy() { + FileLog.d("=============== VoIPService STOPPING ==============="); + stopForeground(true); + stopRinging(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.appDidLogout); + SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); + Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); + if (proximity != null) { + sm.unregisterListener(this); + } + if (proximityWakelock != null && proximityWakelock.isHeld()) { + proximityWakelock.release(); + } + unregisterReceiver(receiver); + if(timeoutRunnable!=null){ + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable=null; + } + super.onDestroy(); + sharedInstance = null; + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didEndedCall); + } + }); + if (controller != null && controllerStarted) { + lastKnownDuration = controller.getCallDuration(); + updateStats(); + StatsController.getInstance().incrementTotalCallsTime(getStatsNetworkType(), (int) (lastKnownDuration / 1000) % 5); + if(needSendDebugLog){ + String debugLog=controller.getDebugLog(); + TLRPC.TL_phone_saveCallDebug req=new TLRPC.TL_phone_saveCallDebug(); + req.debug=new TLRPC.TL_dataJSON(); + req.debug.data=debugLog; + req.peer=new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash=call.access_hash; + req.peer.id=call.id; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate(){ + @Override + public void run(TLObject response, TLRPC.TL_error error){ + FileLog.d("Sent debug logs, response="+response); + } + }); + } + controller.release(); + controller = null; + } + cpuWakelock.release(); + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + if(isBtHeadsetConnected && !playingSound) + am.stopBluetoothSco(); + am.setMode(AudioManager.MODE_NORMAL); + am.unregisterMediaButtonEventReceiver(new ComponentName(this, VoIPMediaButtonReceiver.class)); + if (haveAudioFocus) + am.abandonAudioFocus(this); + + if (!playingSound) + soundPool.release(); + + ConnectionsManager.getInstance().setAppPaused(true, false); + } + + public static VoIPService getSharedInstance() { + return sharedInstance; + } + + public TLRPC.User getUser() { + return user; + } + + public void setMicMute(boolean mute) { + controller.setMicMute(micMute = mute); + } + + public boolean isMicMute() { + return micMute; + } + + public String getDebugString() { + return controller.getDebugString(); + } + + public long getCallDuration() { + if (!controllerStarted || controller == null) + return lastKnownDuration; + return lastKnownDuration = controller.getCallDuration(); + } + + public void hangUp() { + declineIncomingCall(currentState == STATE_RINGING || (currentState==STATE_WAITING && isOutgoing) ? DISCARD_REASON_MISSED : DISCARD_REASON_HANGUP, null); + } + + public void hangUp(Runnable onDone) { + declineIncomingCall(currentState == STATE_RINGING || (currentState==STATE_WAITING && isOutgoing) ? DISCARD_REASON_MISSED : DISCARD_REASON_HANGUP, onDone); + } + + public void registerStateListener(StateListener l) { + stateListeners.add(l); + if (currentState != 0) + l.onStateChanged(currentState); + } + + public void unregisterStateListener(StateListener l) { + stateListeners.remove(l); + } + + private void startOutgoingCall() { + configureDeviceForCall(); + showNotification(); + startConnectingSound(); + dispatchStateChanged(STATE_REQUESTING); + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didStartedCall); + } + }); + final byte[] salt = new byte[256]; + Utilities.random.nextBytes(salt); + + TLRPC.TL_messages_getDhConfig req = new TLRPC.TL_messages_getDhConfig(); + req.random_length = 256; + req.version = MessagesStorage.lastSecretVersion; + callReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + callReqId = 0; + if (error == null) { + TLRPC.messages_DhConfig res = (TLRPC.messages_DhConfig) response; + if (response instanceof TLRPC.TL_messages_dhConfig) { + if (!Utilities.isGoodPrime(res.p, res.g)) { + callFailed(); + return; + } + MessagesStorage.secretPBytes = res.p; + MessagesStorage.secretG = res.g; + MessagesStorage.lastSecretVersion = res.version; + MessagesStorage.getInstance().saveSecretParams(MessagesStorage.lastSecretVersion, MessagesStorage.secretG, MessagesStorage.secretPBytes); + } + final byte[] salt = new byte[256]; + for (int a = 0; a < 256; a++) { + salt[a] = (byte) ((byte) (Utilities.random.nextDouble() * 256) ^ res.random[a]); + } + + BigInteger i_g_a = BigInteger.valueOf(MessagesStorage.secretG); + i_g_a = i_g_a.modPow(new BigInteger(1, salt), new BigInteger(1, MessagesStorage.secretPBytes)); + byte[] g_a = i_g_a.toByteArray(); + if (g_a.length > 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(g_a, 1, correctedAuth, 0, 256); + g_a = correctedAuth; + } + + TLRPC.TL_phone_requestCall reqCall = new TLRPC.TL_phone_requestCall(); + reqCall.user_id = MessagesController.getInputUser(user); + reqCall.protocol = new TLRPC.TL_phoneCallProtocol(); + reqCall.protocol.udp_p2p = reqCall.protocol.udp_reflector = true; + reqCall.protocol.min_layer = CALL_MIN_LAYER; + reqCall.protocol.max_layer = CALL_MAX_LAYER; + VoIPService.this.g_a=g_a; + reqCall.g_a_hash = Utilities.computeSHA256(g_a, 0, g_a.length); + reqCall.random_id = Utilities.random.nextInt(); + + ConnectionsManager.getInstance().sendRequest(reqCall, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + if (error == null) { + call = ((TLRPC.TL_phone_phoneCall) response).phone_call; + a_or_b = salt; + dispatchStateChanged(STATE_WAITING); + if(endCallAfterRequest){ + hangUp(); + return; + } + if(pendingUpdates.size()>0 && call!=null){ + for(TLRPC.PhoneCall call:pendingUpdates){ + onCallUpdated(call); + } + pendingUpdates.clear(); + } + timeoutRunnable = new Runnable() { + @Override + public void run() { + timeoutRunnable=null; + TLRPC.TL_phone_discardCall req = new TLRPC.TL_phone_discardCall(); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = call.access_hash; + req.peer.id = call.id; + req.reason=new TLRPC.TL_phoneCallDiscardReasonMissed(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + FileLog.e("error on phone.discardCall: " + error); + } else { + FileLog.d("phone.discardCall " + response); + } + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + callFailed(); + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + }; + AndroidUtilities.runOnUIThread(timeoutRunnable, MessagesController.getInstance().callReceiveTimeout); + } else { + if (error.code == 400 && "PARTICIPANT_VERSION_OUTDATED".equals(error.text)) { + callFailed(VoIPController.ERROR_PEER_OUTDATED); + } else if(error.code==403 && "USER_PRIVACY_RESTRICTED".equals(error.text)){ + callFailed(VoIPController.ERROR_PRIVACY); + }else if(error.code==406){ + callFailed(VoIPController.ERROR_LOCALIZED); + }else { + FileLog.e("Error on phone.requestCall: " + error); + callFailed(); + } + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } else { + FileLog.e("Error on getDhConfig " + error); + callFailed(); + } + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private void acknowledgeCallAndStartRinging(){ + if(call instanceof TLRPC.TL_phoneCallDiscarded){ + FileLog.w("Call "+call.id+" was discarded before the service started, stopping"); + stopSelf(); + return; + } + TLRPC.TL_phone_receivedCall req = new TLRPC.TL_phone_receivedCall(); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.id = call.id; + req.peer.access_hash = call.access_hash; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + if(sharedInstance==null) + return; + FileLog.w("receivedCall response = " + response); + if (error != null){ + FileLog.e("error on receivedCall: "+error); + stopSelf(); + }else{ + startRinging(); + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private void startRinging() { + FileLog.d("starting ringing for call "+call.id); + dispatchStateChanged(STATE_WAITING_INCOMING); + //ringtone=RingtoneManager.getRingtone(this, Settings.System.DEFAULT_RINGTONE_URI); + //ringtone.play(); + SharedPreferences prefs = getSharedPreferences("Notifications", MODE_PRIVATE); + ringtonePlayer = new MediaPlayer(); + ringtonePlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(MediaPlayer mediaPlayer) { + ringtonePlayer.start(); + } + }); + ringtonePlayer.setLooping(true); + ringtonePlayer.setAudioStreamType(AudioManager.STREAM_RING); + try { + String notificationUri; + if (prefs.getBoolean("custom_" + user.id, false)) + notificationUri = prefs.getString("ringtone_path_" + user.id, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString()); + else + notificationUri = prefs.getString("CallsRingtonePath", RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE).toString()); + ringtonePlayer.setDataSource(this, Uri.parse(notificationUri)); + ringtonePlayer.prepareAsync(); + } catch (Exception e) { + FileLog.e(e); + if(ringtonePlayer!=null){ + ringtonePlayer.release(); + ringtonePlayer=null; + } + } + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + int vibrate; + if (prefs.getBoolean("custom_" + user.id, false)) + vibrate = prefs.getInt("calls_vibrate_" + user.id, 0); + else + vibrate = prefs.getInt("vibrate_calls", 0); + if ((vibrate != 2 && vibrate != 4 && (am.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE || am.getRingerMode() == AudioManager.RINGER_MODE_NORMAL)) || + (vibrate == 4 && am.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE)) { + vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); + long duration = 700; + if (vibrate == 1) + duration /= 2; + else if (vibrate == 3) + duration *= 2; + vibrator.vibrate(new long[]{0, duration, 500}, 0); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && !((KeyguardManager) getSystemService(KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode() && NotificationManagerCompat.from(this).areNotificationsEnabled()) { + showIncomingNotification(); + FileLog.d("Showing incoming call notification"); + } else { + FileLog.d("Starting incall activity for incoming call"); + try { + PendingIntent.getActivity(VoIPService.this, 12345, new Intent(VoIPService.this, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0).send(); + } catch (Exception x) { + FileLog.e("Error starting incall activity", x); + } + } + + } + + public void acceptIncomingCall() { + stopRinging(); + showNotification(); + configureDeviceForCall(); + startConnectingSound(); + dispatchStateChanged(STATE_EXCHANGING_KEYS); + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didStartedCall); + } + }); + TLRPC.TL_messages_getDhConfig req = new TLRPC.TL_messages_getDhConfig(); + req.random_length = 256; + req.version = MessagesStorage.lastSecretVersion; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error == null) { + TLRPC.messages_DhConfig res = (TLRPC.messages_DhConfig) response; + if (response instanceof TLRPC.TL_messages_dhConfig) { + if (!Utilities.isGoodPrime(res.p, res.g)) { + /*acceptingChats.remove(encryptedChat.id); + declineSecretChat(encryptedChat.id);*/ + FileLog.e("stopping VoIP service, bad prime"); + callFailed(); + return; + } + + MessagesStorage.secretPBytes = res.p; + MessagesStorage.secretG = res.g; + MessagesStorage.lastSecretVersion = res.version; + MessagesStorage.getInstance().saveSecretParams(MessagesStorage.lastSecretVersion, MessagesStorage.secretG, MessagesStorage.secretPBytes); + } + byte[] salt = new byte[256]; + for (int a = 0; a < 256; a++) { + salt[a] = (byte) ((byte) (Utilities.random.nextDouble() * 256) ^ res.random[a]); + } + a_or_b = salt; + BigInteger g_b = BigInteger.valueOf(MessagesStorage.secretG); + BigInteger p = new BigInteger(1, MessagesStorage.secretPBytes); + g_b = g_b.modPow(new BigInteger(1, salt), p); + g_a_hash=call.g_a_hash; + + byte[] g_b_bytes = g_b.toByteArray(); + if (g_b_bytes.length > 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(g_b_bytes, 1, correctedAuth, 0, 256); + g_b_bytes = correctedAuth; + } + + TLRPC.TL_phone_acceptCall req = new TLRPC.TL_phone_acceptCall(); + req.g_b = g_b_bytes; + //req.key_fingerprint = Utilities.bytesToLong(authKeyId); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.id = call.id; + req.peer.access_hash = call.access_hash; + req.protocol = new TLRPC.TL_phoneCallProtocol(); + req.protocol.udp_p2p = req.protocol.udp_reflector = true; + req.protocol.min_layer = CALL_MIN_LAYER; + req.protocol.max_layer = CALL_MAX_LAYER; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + if (error == null) { + FileLog.w("accept call ok! " + response); + call = ((TLRPC.TL_phone_phoneCall) response).phone_call; + if(call instanceof TLRPC.TL_phoneCallDiscarded){ + onCallUpdated(call); + }/*else{ + initiateActualEncryptedCall(); + }*/ + } else { + FileLog.e("Error on phone.acceptCall: " + error); + callFailed(); + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } else { + //acceptingChats.remove(encryptedChat.id); + callFailed(); + } + } + }); + } + + public void declineIncomingCall() { + declineIncomingCall(DISCARD_REASON_HANGUP, null); + } + + public void declineIncomingCall(int reason, final Runnable onDone) { + if(currentState==STATE_REQUESTING){ + endCallAfterRequest=true; + return; + } + if (currentState == STATE_HANGING_UP || currentState == STATE_ENDED) + return; + dispatchStateChanged(STATE_HANGING_UP); + if (call == null) { + if (onDone != null) + onDone.run(); + callEnded(); + if (callReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(callReqId, false); + callReqId = 0; + } + return; + } + TLRPC.TL_phone_discardCall req = new TLRPC.TL_phone_discardCall(); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = call.access_hash; + req.peer.id = call.id; + req.duration = controller != null && controllerStarted ? (int) (controller.getCallDuration() / 1000) : 0; + req.connection_id = controller != null && controllerStarted ? controller.getPreferredRelayID() : 0; + switch (reason) { + case DISCARD_REASON_DISCONNECT: + req.reason = new TLRPC.TL_phoneCallDiscardReasonDisconnect(); + break; + case DISCARD_REASON_MISSED: + req.reason = new TLRPC.TL_phoneCallDiscardReasonMissed(); + break; + case DISCARD_REASON_LINE_BUSY: + req.reason = new TLRPC.TL_phoneCallDiscardReasonBusy(); + break; + case DISCARD_REASON_HANGUP: + default: + req.reason = new TLRPC.TL_phoneCallDiscardReasonHangup(); + break; + } + final boolean wasNotConnected=ConnectionsManager.getInstance().getConnectionState()!=ConnectionsManager.ConnectionStateConnected; + if(wasNotConnected){ + if (onDone != null) + onDone.run(); + callEnded(); + } + final Runnable stopper=new Runnable(){ + private boolean done=false; + @Override + public void run(){ + if(done) + return; + done=true; + if(onDone!=null) + onDone.run(); + callEnded(); + } + }; + AndroidUtilities.runOnUIThread(stopper, (int)(VoIPServerConfig.getDouble("hangup_ui_timeout", 5)*1000)); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + FileLog.e("error on phone.discardCall: " + error); + } else { + if (response instanceof TLRPC.TL_updates) { + TLRPC.TL_updates updates = (TLRPC.TL_updates) response; + MessagesController.getInstance().processUpdates(updates, false); + } + FileLog.d("phone.discardCall " + response); + } + if (!wasNotConnected){ + AndroidUtilities.cancelRunOnUIThread(stopper); + if(onDone!=null) + onDone.run(); + } + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private void dumpCallObject(){ + try{ + Field[] flds=TLRPC.PhoneCall.class.getFields(); + for(Field f:flds){ + FileLog.d(f.getName()+" = "+f.get(call)); + } + }catch(Exception x){ + FileLog.e(x); + } + } + + public void onCallUpdated(TLRPC.PhoneCall call) { + if(this.call==null){ + pendingUpdates.add(call); + return; + } + if(call==null) + return; + if(call.id!=this.call.id){ + if(BuildVars.DEBUG_VERSION) + FileLog.w("onCallUpdated called with wrong call id (got "+call.id+", expected "+this.call.id+")"); + return; + } + if(call.access_hash==0) + call.access_hash=this.call.access_hash; + if(BuildVars.DEBUG_VERSION){ + FileLog.d("Call updated: "+call); + dumpCallObject(); + } + this.call = call; + if (call instanceof TLRPC.TL_phoneCallDiscarded) { + needSendDebugLog=call.need_debug; + FileLog.d("call discarded, stopping service"); + if (call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { + dispatchStateChanged(STATE_BUSY); + playingSound = true; + soundPool.play(spBusyId, 1, 1, 0, -1, 1); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + soundPool.release(); + if(isBtHeadsetConnected) + ((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco(); + } + }, 2500); + stopSelf(); + } else { + callEnded(); + } + if (call.need_rating) { + startRatingActivity(); + } + } else if (call instanceof TLRPC.TL_phoneCall && authKey == null){ + if(call.g_a_or_b==null){ + FileLog.w("stopping VoIP service, Ga == null"); + callFailed(); + return; + } + if(!Arrays.equals(g_a_hash, Utilities.computeSHA256(call.g_a_or_b, 0, call.g_a_or_b.length))){ + FileLog.w("stopping VoIP service, Ga hash doesn't match"); + callFailed(); + return; + } + g_a=call.g_a_or_b; + BigInteger g_a = new BigInteger(1, call.g_a_or_b); + BigInteger p = new BigInteger(1, MessagesStorage.secretPBytes); + + if (!Utilities.isGoodGaAndGb(g_a, p)) { + FileLog.w("stopping VoIP service, bad Ga and Gb (accepting)"); + callFailed(); + return; + } + g_a = g_a.modPow(new BigInteger(1, a_or_b), p); + + byte[] authKey = g_a.toByteArray(); + if (authKey.length > 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(authKey, authKey.length - 256, correctedAuth, 0, 256); + authKey = correctedAuth; + } else if (authKey.length < 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(authKey, 0, correctedAuth, 256 - authKey.length, authKey.length); + for (int a = 0; a < 256 - authKey.length; a++) { + authKey[a] = 0; + } + authKey = correctedAuth; + } + byte[] authKeyHash = Utilities.computeSHA1(authKey); + byte[] authKeyId = new byte[8]; + System.arraycopy(authKeyHash, authKeyHash.length - 8, authKeyId, 0, 8); + VoIPService.this.authKey = authKey; + keyFingerprint = Utilities.bytesToLong(authKeyId); + + if(keyFingerprint!=call.key_fingerprint){ + FileLog.w("key fingerprints don't match"); + callFailed(); + return; + } + + initiateActualEncryptedCall(); + } else if(call instanceof TLRPC.TL_phoneCallAccepted && authKey==null){ + processAcceptedCall(); + } else { + if (currentState == STATE_WAITING && call.receive_date != 0) { + dispatchStateChanged(STATE_RINGING); + FileLog.d("!!!!!! CALL RECEIVED"); + if (spPlayID != 0) + soundPool.stop(spPlayID); + spPlayID = soundPool.play(spRingbackID, 1, 1, 0, -1, 1); + if (timeoutRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable=null; + } + timeoutRunnable = new Runnable() { + @Override + public void run() { + timeoutRunnable=null; + declineIncomingCall(DISCARD_REASON_MISSED, null); + } + }; + AndroidUtilities.runOnUIThread(timeoutRunnable, MessagesController.getInstance().callRingTimeout); + } + } + } + + private void startRatingActivity() { + try { + PendingIntent.getActivity(VoIPService.this, 0, new Intent(VoIPService.this, VoIPFeedbackActivity.class) + .putExtra("call_id", call.id) + .putExtra("call_access_hash", call.access_hash) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP), 0).send(); + } catch (Exception x) { + FileLog.e("Error starting incall activity", x); + } + } + + public void stopRinging() { + if (ringtonePlayer != null) { + ringtonePlayer.stop(); + ringtonePlayer.release(); + ringtonePlayer = null; + } + if (vibrator != null) { + vibrator.cancel(); + vibrator = null; + } + } + + public byte[] getEncryptionKey() { + return authKey; + } + + private void processAcceptedCall() { + dispatchStateChanged(STATE_EXCHANGING_KEYS); + BigInteger p = new BigInteger(1, MessagesStorage.secretPBytes); + BigInteger i_authKey = new BigInteger(1, call.g_b); + + if (!Utilities.isGoodGaAndGb(i_authKey, p)) { + FileLog.w("stopping VoIP service, bad Ga and Gb"); + callFailed(); + return; + } + + i_authKey = i_authKey.modPow(new BigInteger(1, a_or_b), p); + + byte[] authKey = i_authKey.toByteArray(); + if (authKey.length > 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(authKey, authKey.length - 256, correctedAuth, 0, 256); + authKey = correctedAuth; + } else if (authKey.length < 256) { + byte[] correctedAuth = new byte[256]; + System.arraycopy(authKey, 0, correctedAuth, 256 - authKey.length, authKey.length); + for (int a = 0; a < 256 - authKey.length; a++) { + authKey[a] = 0; + } + authKey = correctedAuth; + } + byte[] authKeyHash = Utilities.computeSHA1(authKey); + byte[] authKeyId = new byte[8]; + System.arraycopy(authKeyHash, authKeyHash.length - 8, authKeyId, 0, 8); + long fingerprint = Utilities.bytesToLong(authKeyId); + this.authKey=authKey; + keyFingerprint=fingerprint; + TLRPC.TL_phone_confirmCall req=new TLRPC.TL_phone_confirmCall(); + req.g_a=g_a; + req.key_fingerprint=fingerprint; + req.peer=new TLRPC.TL_inputPhoneCall(); + req.peer.id=call.id; + req.peer.access_hash=call.access_hash; + req.protocol=new TLRPC.TL_phoneCallProtocol(); + req.protocol.max_layer=CALL_MAX_LAYER; + req.protocol.min_layer=CALL_MIN_LAYER; + req.protocol.udp_p2p=req.protocol.udp_reflector=true; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate(){ + @Override + public void run(final TLObject response, final TLRPC.TL_error error){ + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + if(error!=null){ + callFailed(); + }else{ + call=((TLRPC.TL_phone_phoneCall)response).phone_call; + initiateActualEncryptedCall(); + } + } + }); + } + }); + } + + private void initiateActualEncryptedCall() { + if (timeoutRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable = null; + } + try { + FileLog.d("InitCall: keyID=" + keyFingerprint); + controller.setEncryptionKey(authKey, isOutgoing); + TLRPC.TL_phoneConnection[] endpoints = new TLRPC.TL_phoneConnection[1 + call.alternative_connections.size()]; + endpoints[0] = call.connection; + for (int i = 0; i < call.alternative_connections.size(); i++) + endpoints[i + 1] = call.alternative_connections.get(i); + + controller.setRemoteEndpoints(endpoints, call.protocol.udp_p2p); + controller.start(); + updateNetworkType(); + controller.connect(); + controllerStarted = true; + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (controller == null) + return; + updateStats(); + AndroidUtilities.runOnUIThread(this, 5000); + } + }, 5000); + } catch (Exception x) { + FileLog.e("error starting call", x); + callFailed(); + } + } + + private void showNotification() { + Intent intent = new Intent(this, VoIPActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + Notification.Builder builder = new Notification.Builder(this) + .setContentTitle(LocaleController.getString("VoipOutgoingCall", R.string.VoipOutgoingCall)) + .setContentText(ContactsController.formatName(user.first_name, user.last_name)) + .setSmallIcon(R.drawable.notification) + .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + Intent endIntent = new Intent(); + endIntent.setAction(getPackageName() + ".END_CALL"); + endIntent.putExtra("end_hash", endHash = Utilities.random.nextInt()); + builder.addAction(R.drawable.ic_call_end_white_24dp, LocaleController.getString("VoipEndCall", R.string.VoipEndCall), PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + builder.setPriority(Notification.PRIORITY_MAX); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setShowWhen(false); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setColor(0xff2ca5e0); + } + if (user.photo != null) { + TLRPC.FileLocation photoPath = user.photo.photo_small; + if (photoPath != null) { + BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); + if (img != null) { + builder.setLargeIcon(img.getBitmap()); + } else { + try { + float scaleFactor = 160.0f / AndroidUtilities.dp(50); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; + Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photoPath, true).toString(), options); + if (bitmap != null) { + builder.setLargeIcon(bitmap); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + } + } + ongoingCallNotification = builder.getNotification(); + startForeground(ID_ONGOING_CALL_NOTIFICATION, ongoingCallNotification); + } + + private void showIncomingNotification() { + Intent intent = new Intent(this, VoIPActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + Notification.Builder builder = new Notification.Builder(this) + .setContentTitle(LocaleController.getString("VoipInCallBranding", R.string.VoipInCallBranding)) + .setContentText(ContactsController.formatName(user.first_name, user.last_name)) + .setSmallIcon(R.drawable.notification) + .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + endHash = Utilities.random.nextInt(); + Intent endIntent = new Intent(); + endIntent.setAction(getPackageName() + ".DECLINE_CALL"); + endIntent.putExtra("end_hash", endHash); + CharSequence endTitle=LocaleController.getString("VoipDeclineCall", R.string.VoipDeclineCall); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ + endTitle=new SpannableString(endTitle); + ((SpannableString)endTitle).setSpan(new ForegroundColorSpan(0xFFF44336), 0, endTitle.length(), 0); + } + builder.addAction(R.drawable.ic_call_end_white_24dp, endTitle, PendingIntent.getBroadcast(this, 0, endIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + Intent answerIntent = new Intent(); + answerIntent.setAction(getPackageName() + ".ANSWER_CALL"); + answerIntent.putExtra("end_hash", endHash); + CharSequence answerTitle=LocaleController.getString("VoipAnswerCall", R.string.VoipAnswerCall); + if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){ + answerTitle=new SpannableString(answerTitle); + ((SpannableString)answerTitle).setSpan(new ForegroundColorSpan(0xFF00AA00), 0, answerTitle.length(), 0); + } + builder.addAction(R.drawable.ic_call_white_24dp, answerTitle, PendingIntent.getBroadcast(this, 0, answerIntent, PendingIntent.FLAG_UPDATE_CURRENT)); + builder.setPriority(Notification.PRIORITY_MAX); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + builder.setShowWhen(false); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setColor(0xff2ca5e0); + builder.setVibrate(new long[0]); + builder.setCategory(Notification.CATEGORY_CALL); + builder.setFullScreenIntent(PendingIntent.getActivity(this, 0, intent, 0), true); + } + if (user.photo != null) { + TLRPC.FileLocation photoPath = user.photo.photo_small; + if (photoPath != null) { + BitmapDrawable img = ImageLoader.getInstance().getImageFromMemory(photoPath, null, "50_50"); + if (img != null) { + builder.setLargeIcon(img.getBitmap()); + } else { + try { + float scaleFactor = 160.0f / AndroidUtilities.dp(50); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inSampleSize = scaleFactor < 1 ? 1 : (int) scaleFactor; + Bitmap bitmap = BitmapFactory.decodeFile(FileLoader.getPathToAttach(photoPath, true).toString(), options); + if (bitmap != null) { + builder.setLargeIcon(bitmap); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + } + } + Notification incomingNotification = builder.getNotification(); + startForeground(ID_INCOMING_CALL_NOTIFICATION, incomingNotification); + } + + private void startConnectingSound() { + if (spPlayID != 0) + soundPool.stop(spPlayID); + spPlayID = soundPool.play(spConnectingId, 1, 1, 0, -1, 1); + if (spPlayID == 0) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (sharedInstance == null) + return; + if (spPlayID == 0) + spPlayID = soundPool.play(spConnectingId, 1, 1, 0, -1, 1); + if (spPlayID == 0) + AndroidUtilities.runOnUIThread(this, 100); + } + }, 100); + } + } + + private void callFailed() { + callFailed(controller != null && controllerStarted ? controller.getLastError() : VoIPController.ERROR_UNKNOWN); + } + + private void callFailed(int errorCode) { + try{ + throw new Exception("Call "+(call!=null ? call.id : 0)+" failed with error code "+errorCode); + }catch(Exception x){ + FileLog.e(x); + } + lastError = errorCode; + if (call != null) { + FileLog.d("Discarding failed call"); + TLRPC.TL_phone_discardCall req = new TLRPC.TL_phone_discardCall(); + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = call.access_hash; + req.peer.id = call.id; + req.duration = controller != null && controllerStarted ? (int) (controller.getCallDuration() / 1000) : 0; + req.connection_id = controller != null && controllerStarted ? controller.getPreferredRelayID() : 0; + req.reason = new TLRPC.TL_phoneCallDiscardReasonDisconnect(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (error != null) { + FileLog.e("error on phone.discardCall: " + error); + } else { + FileLog.d("phone.discardCall " + response); + } + } + }); + } + dispatchStateChanged(STATE_FAILED); + if(errorCode!=VoIPController.ERROR_LOCALIZED && soundPool!=null){ + playingSound=true; + soundPool.play(spFailedID, 1, 1, 0, 0, 1); + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + soundPool.release(); + if(isBtHeadsetConnected) + ((AudioManager) ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco(); + } + }, 1000); + } + stopSelf(); + } + + private void callEnded() { + FileLog.d("Call "+(call!=null ? call.id : 0)+" ended"); + dispatchStateChanged(STATE_ENDED); + if (needPlayEndSound) { + playingSound = true; + soundPool.play(spEndId, 1, 1, 0, 0, 1); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + soundPool.release(); + if(isBtHeadsetConnected) + ((AudioManager)ApplicationLoader.applicationContext.getSystemService(AUDIO_SERVICE)).stopBluetoothSco(); + } + }, 1000); + } + if(timeoutRunnable!=null){ + AndroidUtilities.cancelRunOnUIThread(timeoutRunnable); + timeoutRunnable=null; + } + stopSelf(); + } + + @Override + public void onConnectionStateChanged(int newState) { + if (newState == STATE_FAILED) { + callFailed(); + return; + } + if (newState == STATE_ESTABLISHED) { + if (spPlayID != 0) { + soundPool.stop(spPlayID); + spPlayID = 0; + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (controller == null) + return; + int netType = StatsController.TYPE_WIFI; + if (lastNetInfo != null) { + if (lastNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) + netType = lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE; + } + StatsController.getInstance().incrementTotalCallsTime(netType, 5); + AndroidUtilities.runOnUIThread(this, 5000); + } + }, 5000); + if (isOutgoing) + StatsController.getInstance().incrementSentItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); + else + StatsController.getInstance().incrementReceivedItemsCount(getStatsNetworkType(), StatsController.TYPE_CALLS, 1); + } + dispatchStateChanged(newState); + } + + public boolean isOutgoing(){ + return isOutgoing; + } + + @SuppressLint("NewApi") + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) { + AudioManager am=(AudioManager) getSystemService(AUDIO_SERVICE); + if (isHeadsetPlugged || am.isSpeakerphoneOn() || (isBluetoothHeadsetConnected() && am.isBluetoothScoOn())) { + return; + } + boolean newIsNear = event.values[0] < Math.min(event.sensor.getMaximumRange(), 3); + if (newIsNear != isProximityNear) { + FileLog.d("proximity " + newIsNear); + isProximityNear = newIsNear; + try{ + if(isProximityNear){ + proximityWakelock.acquire(); + }else{ + proximityWakelock.release(1); // this is non-public API before L + } + }catch(Exception x){ + FileLog.e(x); + } + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + + } + + private void updateNetworkType() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + lastNetInfo = info; + int type = VoIPController.NET_TYPE_UNKNOWN; + if (info != null) { + switch (info.getType()) { + case ConnectivityManager.TYPE_MOBILE: + switch (info.getSubtype()) { + case TelephonyManager.NETWORK_TYPE_GPRS: + type = VoIPController.NET_TYPE_GPRS; + break; + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_1xRTT: + type = VoIPController.NET_TYPE_EDGE; + break; + case TelephonyManager.NETWORK_TYPE_UMTS: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + type = VoIPController.NET_TYPE_3G; + break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + type = VoIPController.NET_TYPE_HSPA; + break; + case TelephonyManager.NETWORK_TYPE_LTE: + type = VoIPController.NET_TYPE_LTE; + break; + default: + type = VoIPController.NET_TYPE_OTHER_MOBILE; + break; + } + break; + case ConnectivityManager.TYPE_WIFI: + type = VoIPController.NET_TYPE_WIFI; + break; + case ConnectivityManager.TYPE_ETHERNET: + type = VoIPController.NET_TYPE_ETHERNET; + break; + } + } + if (controller != null) { + controller.setNetworkType(type); + } + } + + private void updateBluetoothHeadsetState(boolean connected){ + if(connected==isBtHeadsetConnected) + return; + isBtHeadsetConnected=connected; + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + if(connected) + am.startBluetoothSco(); + else + am.stopBluetoothSco(); + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + } + + public boolean isBluetoothHeadsetConnected(){ + return isBtHeadsetConnected; + } + + private void configureDeviceForCall() { + needPlayEndSound = true; + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + am.setMode(AudioManager.MODE_IN_COMMUNICATION); + am.setSpeakerphoneOn(false); + am.requestAudioFocus(this, AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN); + + SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE); + Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY); + try{ + if(proximity!=null){ + proximityWakelock=((PowerManager) getSystemService(Context.POWER_SERVICE)).newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, "telegram-voip-prx"); + sm.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL); + } + }catch(Exception x){ + FileLog.e("Error initializing proximity sensor", x); + } + } + + private void dispatchStateChanged(int state) { + FileLog.d("== Call "+(call!=null ? call.id : 0)+" state changed to "+state+" =="); + currentState = state; + for (int a = 0; a < stateListeners.size(); a++) { + StateListener l = stateListeners.get(a); + l.onStateChanged(state); + } + } + + @Override + public void onAudioFocusChange(int focusChange) { + if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + haveAudioFocus = true; + } else { + haveAudioFocus = false; + } + } + + public void onUIForegroundStateChanged(boolean isForeground) { + if (currentState == STATE_WAITING_INCOMING) { + if (isForeground) { + stopForeground(true); + } else { + if (!((KeyguardManager) getSystemService(KEYGUARD_SERVICE)).inKeyguardRestrictedInputMode()) { + showIncomingNotification(); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + Intent intent = new Intent(VoIPService.this, VoIPActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + try { + PendingIntent.getActivity(VoIPService.this, 0, intent, 0).send(); + } catch (PendingIntent.CanceledException e) { + FileLog.e("error restarting activity", e); + } + } + }, 500); + } + } + } + } + + /*package*/ void onMediaButtonEvent(KeyEvent ev) { + if (ev.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) { + if (ev.getAction() == KeyEvent.ACTION_UP) { + if (currentState == STATE_WAITING_INCOMING) { + acceptIncomingCall(); + } else { + setMicMute(!isMicMute()); + for (StateListener l : stateListeners) + l.onAudioSettingsChanged(); + } + } + } + } + + public void debugCtl(int request, int param) { + if (controller != null) + controller.debugCtl(request, param); + } + + public int getLastError() { + return lastError; + } + + private void updateStats() { + controller.getStats(stats); + long wifiSentDiff = stats.bytesSentWifi - prevStats.bytesSentWifi; + long wifiRecvdDiff = stats.bytesRecvdWifi - prevStats.bytesRecvdWifi; + long mobileSentDiff = stats.bytesSentMobile - prevStats.bytesSentMobile; + long mobileRecvdDiff = stats.bytesRecvdMobile - prevStats.bytesRecvdMobile; + VoIPController.Stats tmp = stats; + stats = prevStats; + prevStats = tmp; + if (wifiSentDiff > 0) + StatsController.getInstance().incrementSentBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiSentDiff); + if (wifiRecvdDiff > 0) + StatsController.getInstance().incrementReceivedBytesCount(StatsController.TYPE_WIFI, StatsController.TYPE_CALLS, wifiRecvdDiff); + if (mobileSentDiff > 0) + StatsController.getInstance().incrementSentBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, + StatsController.TYPE_CALLS, mobileSentDiff); + if (mobileRecvdDiff > 0) + StatsController.getInstance().incrementReceivedBytesCount(lastNetInfo != null && lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE, + StatsController.TYPE_CALLS, mobileRecvdDiff); + } + + private int getStatsNetworkType() { + int netType = StatsController.TYPE_WIFI; + if (lastNetInfo != null) { + if (lastNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) + netType = lastNetInfo.isRoaming() ? StatsController.TYPE_ROAMING : StatsController.TYPE_MOBILE; + } + return netType; + } + + public boolean hasEarpiece() { + if(((TelephonyManager)getSystemService(TELEPHONY_SERVICE)).getPhoneType()!=TelephonyManager.PHONE_TYPE_NONE) + return true; + if (mHasEarpiece != null) { + return mHasEarpiece; + } + + // not calculated yet, do it now + try { + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE); + Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE"); + int earpieceFlag = field.getInt(null); + int bitmaskResult = (int) method.invoke(am, AudioManager.STREAM_VOICE_CALL); + + // check if masked by the earpiece flag + if ((bitmaskResult & earpieceFlag) == earpieceFlag) { + mHasEarpiece = Boolean.TRUE; + } else { + mHasEarpiece = Boolean.FALSE; + } + } catch (Throwable error) { + FileLog.e("Error while checking earpiece! ", error); + mHasEarpiece = Boolean.TRUE; + } + + return mHasEarpiece; + } + + public int getCallState(){ + return currentState; + } + + public byte[] getGA(){ + return g_a; + } + + @Override + public void didReceivedNotification(int id, Object... args){ + if(id==NotificationCenter.appDidLogout){ + callEnded(); + } + } + + public interface StateListener { + void onStateChanged(int state); + + void onAudioSettingsChanged(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/AuthFailureError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/AuthFailureError.java deleted file mode 100644 index 30d2ec1a186..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/AuthFailureError.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.content.Intent; - -/** - * Error indicating that there was an authentication failure when performing a Request. - */ -@SuppressWarnings("serial") -public class AuthFailureError extends VolleyError { - /** An intent that can be used to resolve this exception. (Brings up the password dialog.) */ - private Intent mResolutionIntent; - - public AuthFailureError() { } - - public AuthFailureError(Intent intent) { - mResolutionIntent = intent; - } - - public AuthFailureError(NetworkResponse response) { - super(response); - } - - public AuthFailureError(String message) { - super(message); - } - - public AuthFailureError(String message, Exception reason) { - super(message, reason); - } - - public Intent getResolutionIntent() { - return mResolutionIntent; - } - - @Override - public String getMessage() { - if (mResolutionIntent != null) { - return "User needs to (re)enter credentials."; - } - return super.getMessage(); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Cache.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/Cache.java deleted file mode 100644 index 619f69b2ca1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Cache.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import java.util.Collections; -import java.util.Map; - -/** - * An interface for a cache keyed by a String with a byte array as data. - */ -public interface Cache { - /** - * Retrieves an entry from the cache. - * @param key Cache key - * @return An {@link Entry} or null in the event of a cache miss - */ - public Entry get(String key); - - /** - * Adds or replaces an entry to the cache. - * @param key Cache key - * @param entry Data to store and metadata for cache coherency, TTL, etc. - */ - public void put(String key, Entry entry); - - /** - * Performs any potentially long-running actions needed to initialize the cache; - * will be called from a worker thread. - */ - public void initialize(); - - /** - * Invalidates an entry in the cache. - * @param key Cache key - * @param fullExpire True to fully expire the entry, false to soft expire - */ - public void invalidate(String key, boolean fullExpire); - - /** - * Removes an entry from the cache. - * @param key Cache key - */ - public void remove(String key); - - /** - * Empties the cache. - */ - public void clear(); - - /** - * Data and metadata for an entry returned by the cache. - */ - public static class Entry { - /** The data returned from cache. */ - public byte[] data; - - /** ETag for cache coherency. */ - public String etag; - - /** Date of this response as reported by the server. */ - public long serverDate; - - /** The last modified date for the requested object. */ - public long lastModified; - - /** TTL for this record. */ - public long ttl; - - /** Soft TTL for this record. */ - public long softTtl; - - /** Immutable response headers as received from server; must be non-null. */ - public Map responseHeaders = Collections.emptyMap(); - - /** True if the entry is expired. */ - public boolean isExpired() { - return this.ttl < System.currentTimeMillis(); - } - - /** True if a refresh is needed from the original data source. */ - public boolean refreshNeeded() { - return this.softTtl < System.currentTimeMillis(); - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/CacheDispatcher.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/CacheDispatcher.java deleted file mode 100644 index 5809df81790..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/CacheDispatcher.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.os.Process; - -import java.util.concurrent.BlockingQueue; - -/** - * Provides a thread for performing cache triage on a queue of requests. - * - * Requests added to the specified cache queue are resolved from cache. - * Any deliverable response is posted back to the caller via a - * {@link ResponseDelivery}. Cache misses and responses that require - * refresh are enqueued on the specified network queue for processing - * by a {@link NetworkDispatcher}. - */ -public class CacheDispatcher extends Thread { - - private static final boolean DEBUG = VolleyLog.DEBUG; - - /** The queue of requests coming in for triage. */ - private final BlockingQueue> mCacheQueue; - - /** The queue of requests going out to the network. */ - private final BlockingQueue> mNetworkQueue; - - /** The cache to read from. */ - private final Cache mCache; - - /** For posting responses. */ - private final ResponseDelivery mDelivery; - - /** Used for telling us to die. */ - private volatile boolean mQuit = false; - - /** - * Creates a new cache triage dispatcher thread. You must call {@link #start()} - * in order to begin processing. - * - * @param cacheQueue Queue of incoming requests for triage - * @param networkQueue Queue to post requests that require network to - * @param cache Cache interface to use for resolution - * @param delivery Delivery interface to use for posting responses - */ - public CacheDispatcher( - BlockingQueue> cacheQueue, BlockingQueue> networkQueue, - Cache cache, ResponseDelivery delivery) { - mCacheQueue = cacheQueue; - mNetworkQueue = networkQueue; - mCache = cache; - mDelivery = delivery; - } - - /** - * Forces this dispatcher to quit immediately. If any requests are still in - * the queue, they are not guaranteed to be processed. - */ - public void quit() { - mQuit = true; - interrupt(); - } - - @Override - public void run() { - if (DEBUG) VolleyLog.v("start new dispatcher"); - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - - // Make a blocking call to initialize the cache. - mCache.initialize(); - - while (true) { - try { - // Get a request from the cache triage queue, blocking until - // at least one is available. - final Request request = mCacheQueue.take(); - request.addMarker("cache-queue-take"); - - // If the request has been canceled, don't bother dispatching it. - if (request.isCanceled()) { - request.finish("cache-discard-canceled"); - continue; - } - - // Attempt to retrieve this item from cache. - Cache.Entry entry = mCache.get(request.getCacheKey()); - if (entry == null) { - request.addMarker("cache-miss"); - // Cache miss; send off to the network dispatcher. - mNetworkQueue.put(request); - continue; - } - - // If it is completely expired, just send it to the network. - if (entry.isExpired()) { - request.addMarker("cache-hit-expired"); - request.setCacheEntry(entry); - mNetworkQueue.put(request); - continue; - } - - // We have a cache hit; parse its data for delivery back to the request. - request.addMarker("cache-hit"); - Response response = request.parseNetworkResponse( - new NetworkResponse(entry.data, entry.responseHeaders)); - request.addMarker("cache-hit-parsed"); - - if (!entry.refreshNeeded()) { - // Completely unexpired cache hit. Just deliver the response. - mDelivery.postResponse(request, response); - } else { - // Soft-expired cache hit. We can deliver the cached response, - // but we need to also send the request to the network for - // refreshing. - request.addMarker("cache-hit-refresh-needed"); - request.setCacheEntry(entry); - - // Mark the response as intermediate. - response.intermediate = true; - - // Post the intermediate response back to the user and have - // the delivery then forward the request along to the network. - mDelivery.postResponse(request, response, new Runnable() { - @Override - public void run() { - try { - mNetworkQueue.put(request); - } catch (InterruptedException e) { - // Not much we can do about this. - } - } - }); - } - - } catch (InterruptedException e) { - // We may have been interrupted because it was time to quit. - if (mQuit) { - return; - } - continue; - } - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/DefaultRetryPolicy.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/DefaultRetryPolicy.java deleted file mode 100644 index 28e0059f8d5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/DefaultRetryPolicy.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Default retry policy for requests. - */ -public class DefaultRetryPolicy implements RetryPolicy { - /** The current timeout in milliseconds. */ - private int mCurrentTimeoutMs; - - /** The current retry count. */ - private int mCurrentRetryCount; - - /** The maximum number of attempts. */ - private final int mMaxNumRetries; - - /** The backoff multiplier for the policy. */ - private final float mBackoffMultiplier; - - /** The default socket timeout in milliseconds */ - public static final int DEFAULT_TIMEOUT_MS = 2500; - - /** The default number of retries */ - public static final int DEFAULT_MAX_RETRIES = 1; - - /** The default backoff multiplier */ - public static final float DEFAULT_BACKOFF_MULT = 1f; - - /** - * Constructs a new retry policy using the default timeouts. - */ - public DefaultRetryPolicy() { - this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); - } - - /** - * Constructs a new retry policy. - * @param initialTimeoutMs The initial timeout for the policy. - * @param maxNumRetries The maximum number of retries. - * @param backoffMultiplier Backoff multiplier for the policy. - */ - public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { - mCurrentTimeoutMs = initialTimeoutMs; - mMaxNumRetries = maxNumRetries; - mBackoffMultiplier = backoffMultiplier; - } - - /** - * Returns the current timeout. - */ - @Override - public int getCurrentTimeout() { - return mCurrentTimeoutMs; - } - - /** - * Returns the current retry count. - */ - @Override - public int getCurrentRetryCount() { - return mCurrentRetryCount; - } - - /** - * Returns the backoff multiplier for the policy. - */ - public float getBackoffMultiplier() { - return mBackoffMultiplier; - } - - /** - * Prepares for the next retry by applying a backoff to the timeout. - * @param error The error code of the last attempt. - */ - @Override - public void retry(VolleyError error) throws VolleyError { - mCurrentRetryCount++; - mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier); - if (!hasAttemptRemaining()) { - throw error; - } - } - - /** - * Returns true if this policy has attempts remaining, false otherwise. - */ - protected boolean hasAttemptRemaining() { - return mCurrentRetryCount <= mMaxNumRetries; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ExecutorDelivery.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/ExecutorDelivery.java deleted file mode 100644 index 790d190cd05..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ExecutorDelivery.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.os.Handler; - -import java.util.concurrent.Executor; - -/** - * Delivers responses and errors. - */ -public class ExecutorDelivery implements ResponseDelivery { - /** Used for posting responses, typically to the main thread. */ - private final Executor mResponsePoster; - - /** - * Creates a new response delivery interface. - * @param handler {@link Handler} to post responses on - */ - public ExecutorDelivery(final Handler handler) { - // Make an Executor that just wraps the handler. - mResponsePoster = new Executor() { - @Override - public void execute(Runnable command) { - handler.post(command); - } - }; - } - - /** - * Creates a new response delivery interface, mockable version - * for testing. - * @param executor For running delivery tasks - */ - public ExecutorDelivery(Executor executor) { - mResponsePoster = executor; - } - - @Override - public void postResponse(Request request, Response response) { - postResponse(request, response, null); - } - - @Override - public void postResponse(Request request, Response response, Runnable runnable) { - request.markDelivered(); - request.addMarker("post-response"); - mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); - } - - @Override - public void postError(Request request, VolleyError error) { - request.addMarker("post-error"); - Response response = Response.error(error); - mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); - } - - /** - * A Runnable used for delivering network responses to a listener on the - * main thread. - */ - @SuppressWarnings("rawtypes") - private class ResponseDeliveryRunnable implements Runnable { - private final Request mRequest; - private final Response mResponse; - private final Runnable mRunnable; - - public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { - mRequest = request; - mResponse = response; - mRunnable = runnable; - } - - @SuppressWarnings("unchecked") - @Override - public void run() { - // If this request has canceled, finish it and don't deliver. - if (mRequest.isCanceled()) { - mRequest.finish("canceled-at-delivery"); - return; - } - - // Deliver a normal response or error, depending. - if (mResponse.isSuccess()) { - mRequest.deliverResponse(mResponse.result); - } else { - mRequest.deliverError(mResponse.error); - } - - // If this is an intermediate response, add a marker, otherwise we're done - // and the request can be finished. - if (mResponse.intermediate) { - mRequest.addMarker("intermediate-response"); - } else { - mRequest.finish("done"); - } - - // If we have been provided a post-delivery runnable, run it. - if (mRunnable != null) { - mRunnable.run(); - } - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Network.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/Network.java deleted file mode 100644 index 561c04140ee..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Network.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * An interface for performing requests. - */ -public interface Network { - /** - * Performs the specified request. - * @param request Request to process - * @return A {@link NetworkResponse} with data and caching metadata; will never be null - * @throws VolleyError on errors - */ - public NetworkResponse performRequest(Request request) throws VolleyError; -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkDispatcher.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkDispatcher.java deleted file mode 100644 index b16c3aaa6a0..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkDispatcher.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.annotation.TargetApi; -import android.net.TrafficStats; -import android.os.Build; -import android.os.Process; -import android.os.SystemClock; - -import java.util.concurrent.BlockingQueue; - -/** - * Provides a thread for performing network dispatch from a queue of requests. - * - * Requests added to the specified queue are processed from the network via a - * specified {@link Network} interface. Responses are committed to cache, if - * eligible, using a specified {@link Cache} interface. Valid responses and - * errors are posted back to the caller via a {@link ResponseDelivery}. - */ -public class NetworkDispatcher extends Thread { - /** The queue of requests to service. */ - private final BlockingQueue> mQueue; - /** The network interface for processing requests. */ - private final Network mNetwork; - /** The cache to write to. */ - private final Cache mCache; - /** For posting responses and errors. */ - private final ResponseDelivery mDelivery; - /** Used for telling us to die. */ - private volatile boolean mQuit = false; - - /** - * Creates a new network dispatcher thread. You must call {@link #start()} - * in order to begin processing. - * - * @param queue Queue of incoming requests for triage - * @param network Network interface to use for performing requests - * @param cache Cache interface to use for writing responses to cache - * @param delivery Delivery interface to use for posting responses - */ - public NetworkDispatcher(BlockingQueue> queue, - Network network, Cache cache, - ResponseDelivery delivery) { - mQueue = queue; - mNetwork = network; - mCache = cache; - mDelivery = delivery; - } - - /** - * Forces this dispatcher to quit immediately. If any requests are still in - * the queue, they are not guaranteed to be processed. - */ - public void quit() { - mQuit = true; - interrupt(); - } - - @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) - private void addTrafficStatsTag(Request request) { - // Tag the request (if API >= 14) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - TrafficStats.setThreadStatsTag(request.getTrafficStatsTag()); - } - } - - @Override - public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - while (true) { - long startTimeMs = SystemClock.elapsedRealtime(); - Request request; - try { - // Take a request from the queue. - request = mQueue.take(); - } catch (InterruptedException e) { - // We may have been interrupted because it was time to quit. - if (mQuit) { - return; - } - continue; - } - - try { - request.addMarker("network-queue-take"); - - // If the request was cancelled already, do not perform the - // network request. - if (request.isCanceled()) { - request.finish("network-discard-cancelled"); - continue; - } - - addTrafficStatsTag(request); - - // Perform the network request. - NetworkResponse networkResponse = mNetwork.performRequest(request); - request.addMarker("network-http-complete"); - - // If the server returned 304 AND we delivered a response already, - // we're done -- don't deliver a second identical response. - if (networkResponse.notModified && request.hasHadResponseDelivered()) { - request.finish("not-modified"); - continue; - } - - // Parse the response here on the worker thread. - Response response = request.parseNetworkResponse(networkResponse); - request.addMarker("network-parse-complete"); - - // Write to cache if applicable. - // TODO: Only update cache metadata instead of entire record for 304s. - if (request.shouldCache() && response.cacheEntry != null) { - mCache.put(request.getCacheKey(), response.cacheEntry); - request.addMarker("network-cache-written"); - } - - // Post the response back. - request.markDelivered(); - mDelivery.postResponse(request, response); - } catch (VolleyError volleyError) { - volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); - parseAndDeliverNetworkError(request, volleyError); - } catch (Exception e) { - VolleyLog.e(e, "Unhandled exception %s", e.toString()); - VolleyError volleyError = new VolleyError(e); - volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs); - mDelivery.postError(request, volleyError); - } - } - } - - private void parseAndDeliverNetworkError(Request request, VolleyError error) { - error = request.parseNetworkError(error); - mDelivery.postError(request, error); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkError.java deleted file mode 100644 index 3c4e2d4fabf..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkError.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Indicates that there was a network error when performing a Volley request. - */ -@SuppressWarnings("serial") -public class NetworkError extends VolleyError { - public NetworkError() { - super(); - } - - public NetworkError(Throwable cause) { - super(cause); - } - - public NetworkError(NetworkResponse networkResponse) { - super(networkResponse); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkResponse.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkResponse.java deleted file mode 100644 index a216694b737..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NetworkResponse.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import org.apache.http.HttpStatus; - -import java.util.Collections; -import java.util.Map; - -/** - * Data and headers returned from {@link Network#performRequest(Request)}. - */ -public class NetworkResponse { - /** - * Creates a new network response. - * @param statusCode the HTTP status code - * @param data Response body - * @param headers Headers returned with this response, or null for none - * @param notModified True if the server returned a 304 and the data was already in cache - * @param networkTimeMs Round-trip network time to receive network response - */ - public NetworkResponse(int statusCode, byte[] data, Map headers, - boolean notModified, long networkTimeMs) { - this.statusCode = statusCode; - this.data = data; - this.headers = headers; - this.notModified = notModified; - this.networkTimeMs = networkTimeMs; - } - - public NetworkResponse(int statusCode, byte[] data, Map headers, - boolean notModified) { - this(statusCode, data, headers, notModified, 0); - } - - public NetworkResponse(byte[] data) { - this(HttpStatus.SC_OK, data, Collections.emptyMap(), false, 0); - } - - public NetworkResponse(byte[] data, Map headers) { - this(HttpStatus.SC_OK, data, headers, false, 0); - } - - /** The HTTP status code. */ - public final int statusCode; - - /** Raw data from this response. */ - public final byte[] data; - - /** Response headers. */ - public final Map headers; - - /** True if the server returned a 304 (Not Modified). */ - public final boolean notModified; - - /** Network roundtrip time in milliseconds. */ - public final long networkTimeMs; -} - diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NoConnectionError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/NoConnectionError.java deleted file mode 100644 index 48bb39650b1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/NoConnectionError.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Error indicating that no connection could be established when performing a Volley request. - */ -@SuppressWarnings("serial") -public class NoConnectionError extends NetworkError { - public NoConnectionError() { - super(); - } - - public NoConnectionError(Throwable reason) { - super(reason); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ParseError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/ParseError.java deleted file mode 100644 index d70d1caa23d..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ParseError.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Indicates that the server's response could not be parsed. - */ -@SuppressWarnings("serial") -public class ParseError extends VolleyError { - public ParseError() { } - - public ParseError(NetworkResponse networkResponse) { - super(networkResponse); - } - - public ParseError(Throwable cause) { - super(cause); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Request.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/Request.java deleted file mode 100644 index 472ac03d7f5..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Request.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.net.TrafficStats; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; - -import org.telegram.messenger.volley.VolleyLog.MarkerLog; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Collections; -import java.util.Map; - -/** - * Base class for all network requests. - * - * @param The type of parsed response this request expects. - */ -public abstract class Request implements Comparable> { - - /** - * Default encoding for POST or PUT parameters. See {@link #getParamsEncoding()}. - */ - private static final String DEFAULT_PARAMS_ENCODING = "UTF-8"; - - /** - * Supported request methods. - */ - public interface Method { - int DEPRECATED_GET_OR_POST = -1; - int GET = 0; - int POST = 1; - int PUT = 2; - int DELETE = 3; - int HEAD = 4; - int OPTIONS = 5; - int TRACE = 6; - int PATCH = 7; - } - - /** An event log tracing the lifetime of this request; for debugging. */ - private final MarkerLog mEventLog = MarkerLog.ENABLED ? new MarkerLog() : null; - - /** - * Request method of this request. Currently supports GET, POST, PUT, DELETE, HEAD, OPTIONS, - * TRACE, and PATCH. - */ - private final int mMethod; - - /** URL of this request. */ - private final String mUrl; - - /** Default tag for {@link TrafficStats}. */ - private final int mDefaultTrafficStatsTag; - - /** Listener interface for errors. */ - private final Response.ErrorListener mErrorListener; - - /** Sequence number of this request, used to enforce FIFO ordering. */ - private Integer mSequence; - - /** The request queue this request is associated with. */ - private RequestQueue mRequestQueue; - - /** Whether or not responses to this request should be cached. */ - private boolean mShouldCache = true; - - /** Whether or not this request has been canceled. */ - private boolean mCanceled = false; - - /** Whether or not a response has been delivered for this request yet. */ - private boolean mResponseDelivered = false; - - /** The retry policy for this request. */ - private RetryPolicy mRetryPolicy; - - /** - * When a request can be retrieved from cache but must be refreshed from - * the network, the cache entry will be stored here so that in the event of - * a "Not Modified" response, we can be sure it hasn't been evicted from cache. - */ - private Cache.Entry mCacheEntry = null; - - /** An opaque token tagging this request; used for bulk cancellation. */ - private Object mTag; - - /** - * Creates a new request with the given URL and error listener. Note that - * the normal response listener is not provided here as delivery of responses - * is provided by subclasses, who have a better idea of how to deliver an - * already-parsed response. - * - * @deprecated Use {@link #Request(int, String, com.android.volley.Response.ErrorListener)}. - */ - @Deprecated - public Request(String url, Response.ErrorListener listener) { - this(Method.DEPRECATED_GET_OR_POST, url, listener); - } - - /** - * Creates a new request with the given method (one of the values from {@link Method}), - * URL, and error listener. Note that the normal response listener is not provided here as - * delivery of responses is provided by subclasses, who have a better idea of how to deliver - * an already-parsed response. - */ - public Request(int method, String url, Response.ErrorListener listener) { - mMethod = method; - mUrl = url; - mErrorListener = listener; - setRetryPolicy(new DefaultRetryPolicy()); - - mDefaultTrafficStatsTag = findDefaultTrafficStatsTag(url); - } - - /** - * Return the method for this request. Can be one of the values in {@link Method}. - */ - public int getMethod() { - return mMethod; - } - - /** - * Set a tag on this request. Can be used to cancel all requests with this - * tag by {@link RequestQueue#cancelAll(Object)}. - * - * @return This Request object to allow for chaining. - */ - public Request setTag(Object tag) { - mTag = tag; - return this; - } - - /** - * Returns this request's tag. - * @see Request#setTag(Object) - */ - public Object getTag() { - return mTag; - } - - /** - * @return this request's {@link com.android.volley.Response.ErrorListener}. - */ - public Response.ErrorListener getErrorListener() { - return mErrorListener; - } - - /** - * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} - */ - public int getTrafficStatsTag() { - return mDefaultTrafficStatsTag; - } - - /** - * @return The hashcode of the URL's host component, or 0 if there is none. - */ - private static int findDefaultTrafficStatsTag(String url) { - if (!TextUtils.isEmpty(url)) { - Uri uri = Uri.parse(url); - if (uri != null) { - String host = uri.getHost(); - if (host != null) { - return host.hashCode(); - } - } - } - return 0; - } - - /** - * Sets the retry policy for this request. - * - * @return This Request object to allow for chaining. - */ - public Request setRetryPolicy(RetryPolicy retryPolicy) { - mRetryPolicy = retryPolicy; - return this; - } - - /** - * Adds an event to this request's event log; for debugging. - */ - public void addMarker(String tag) { - if (MarkerLog.ENABLED) { - mEventLog.add(tag, Thread.currentThread().getId()); - } - } - - /** - * Notifies the request queue that this request has finished (successfully or with error). - * - *

          Also dumps all events from this request's event log; for debugging.

          - */ - void finish(final String tag) { - if (mRequestQueue != null) { - mRequestQueue.finish(this); - } - if (MarkerLog.ENABLED) { - final long threadId = Thread.currentThread().getId(); - if (Looper.myLooper() != Looper.getMainLooper()) { - // If we finish marking off of the main thread, we need to - // actually do it on the main thread to ensure correct ordering. - Handler mainThread = new Handler(Looper.getMainLooper()); - mainThread.post(new Runnable() { - @Override - public void run() { - mEventLog.add(tag, threadId); - mEventLog.finish(this.toString()); - } - }); - return; - } - - mEventLog.add(tag, threadId); - mEventLog.finish(this.toString()); - } - } - - /** - * Associates this request with the given queue. The request queue will be notified when this - * request has finished. - * - * @return This Request object to allow for chaining. - */ - public Request setRequestQueue(RequestQueue requestQueue) { - mRequestQueue = requestQueue; - return this; - } - - /** - * Sets the sequence number of this request. Used by {@link RequestQueue}. - * - * @return This Request object to allow for chaining. - */ - public final Request setSequence(int sequence) { - mSequence = sequence; - return this; - } - - /** - * Returns the sequence number of this request. - */ - public final int getSequence() { - if (mSequence == null) { - throw new IllegalStateException("getSequence called before setSequence"); - } - return mSequence; - } - - /** - * Returns the URL of this request. - */ - public String getUrl() { - return mUrl; - } - - /** - * Returns the cache key for this request. By default, this is the URL. - */ - public String getCacheKey() { - return getUrl(); - } - - /** - * Annotates this request with an entry retrieved for it from cache. - * Used for cache coherency support. - * - * @return This Request object to allow for chaining. - */ - public Request setCacheEntry(Cache.Entry entry) { - mCacheEntry = entry; - return this; - } - - /** - * Returns the annotated cache entry, or null if there isn't one. - */ - public Cache.Entry getCacheEntry() { - return mCacheEntry; - } - - /** - * Mark this request as canceled. No callback will be delivered. - */ - public void cancel() { - mCanceled = true; - } - - /** - * Returns true if this request has been canceled. - */ - public boolean isCanceled() { - return mCanceled; - } - - /** - * Returns a list of extra HTTP headers to go along with this request. Can - * throw {@link AuthFailureError} as authentication may be required to - * provide these values. - * @throws AuthFailureError In the event of auth failure - */ - public Map getHeaders() throws AuthFailureError { - return Collections.emptyMap(); - } - - /** - * Returns a Map of POST parameters to be used for this request, or null if - * a simple GET should be used. Can throw {@link AuthFailureError} as - * authentication may be required to provide these values. - * - *

          Note that only one of getPostParams() and getPostBody() can return a non-null - * value.

          - * @throws AuthFailureError In the event of auth failure - * - * @deprecated Use {@link #getParams()} instead. - */ - @Deprecated - protected Map getPostParams() throws AuthFailureError { - return getParams(); - } - - /** - * Returns which encoding should be used when converting POST parameters returned by - * {@link #getPostParams()} into a raw POST body. - * - *

          This controls both encodings: - *

            - *
          1. The string encoding used when converting parameter names and values into bytes prior - * to URL encoding them.
          2. - *
          3. The string encoding used when converting the URL encoded parameters into a raw - * byte array.
          4. - *
          - * - * @deprecated Use {@link #getParamsEncoding()} instead. - */ - @Deprecated - protected String getPostParamsEncoding() { - return getParamsEncoding(); - } - - /** - * @deprecated Use {@link #getBodyContentType()} instead. - */ - @Deprecated - public String getPostBodyContentType() { - return getBodyContentType(); - } - - /** - * Returns the raw POST body to be sent. - * - * @throws AuthFailureError In the event of auth failure - * - * @deprecated Use {@link #getBody()} instead. - */ - @Deprecated - public byte[] getPostBody() throws AuthFailureError { - // Note: For compatibility with legacy clients of volley, this implementation must remain - // here instead of simply calling the getBody() function because this function must - // call getPostParams() and getPostParamsEncoding() since legacy clients would have - // overridden these two member functions for POST requests. - Map postParams = getPostParams(); - if (postParams != null && postParams.size() > 0) { - return encodeParameters(postParams, getPostParamsEncoding()); - } - return null; - } - - /** - * Returns a Map of parameters to be used for a POST or PUT request. Can throw - * {@link AuthFailureError} as authentication may be required to provide these values. - * - *

          Note that you can directly override {@link #getBody()} for custom data.

          - * - * @throws AuthFailureError in the event of auth failure - */ - protected Map getParams() throws AuthFailureError { - return null; - } - - /** - * Returns which encoding should be used when converting POST or PUT parameters returned by - * {@link #getParams()} into a raw POST or PUT body. - * - *

          This controls both encodings: - *

            - *
          1. The string encoding used when converting parameter names and values into bytes prior - * to URL encoding them.
          2. - *
          3. The string encoding used when converting the URL encoded parameters into a raw - * byte array.
          4. - *
          - */ - protected String getParamsEncoding() { - return DEFAULT_PARAMS_ENCODING; - } - - /** - * Returns the content type of the POST or PUT body. - */ - public String getBodyContentType() { - return "application/x-www-form-urlencoded; charset=" + getParamsEncoding(); - } - - /** - * Returns the raw POST or PUT body to be sent. - * - *

          By default, the body consists of the request parameters in - * application/x-www-form-urlencoded format. When overriding this method, consider overriding - * {@link #getBodyContentType()} as well to match the new body format. - * - * @throws AuthFailureError in the event of auth failure - */ - public byte[] getBody() throws AuthFailureError { - Map params = getParams(); - if (params != null && params.size() > 0) { - return encodeParameters(params, getParamsEncoding()); - } - return null; - } - - /** - * Converts params into an application/x-www-form-urlencoded encoded string. - */ - private byte[] encodeParameters(Map params, String paramsEncoding) { - StringBuilder encodedParams = new StringBuilder(); - try { - for (Map.Entry entry : params.entrySet()) { - encodedParams.append(URLEncoder.encode(entry.getKey(), paramsEncoding)); - encodedParams.append('='); - encodedParams.append(URLEncoder.encode(entry.getValue(), paramsEncoding)); - encodedParams.append('&'); - } - return encodedParams.toString().getBytes(paramsEncoding); - } catch (UnsupportedEncodingException uee) { - throw new RuntimeException("Encoding not supported: " + paramsEncoding, uee); - } - } - - /** - * Set whether or not responses to this request should be cached. - * - * @return This Request object to allow for chaining. - */ - public final Request setShouldCache(boolean shouldCache) { - mShouldCache = shouldCache; - return this; - } - - /** - * Returns true if responses to this request should be cached. - */ - public final boolean shouldCache() { - return mShouldCache; - } - - /** - * Priority values. Requests will be processed from higher priorities to - * lower priorities, in FIFO order. - */ - public enum Priority { - LOW, - NORMAL, - HIGH, - IMMEDIATE - } - - /** - * Returns the {@link Priority} of this request; {@link Priority#NORMAL} by default. - */ - public Priority getPriority() { - return Priority.NORMAL; - } - - /** - * Returns the socket timeout in milliseconds per retry attempt. (This value can be changed - * per retry attempt if a backoff is specified via backoffTimeout()). If there are no retry - * attempts remaining, this will cause delivery of a {@link TimeoutError} error. - */ - public final int getTimeoutMs() { - return mRetryPolicy.getCurrentTimeout(); - } - - /** - * Returns the retry policy that should be used for this request. - */ - public RetryPolicy getRetryPolicy() { - return mRetryPolicy; - } - - /** - * Mark this request as having a response delivered on it. This can be used - * later in the request's lifetime for suppressing identical responses. - */ - public void markDelivered() { - mResponseDelivered = true; - } - - /** - * Returns true if this request has had a response delivered for it. - */ - public boolean hasHadResponseDelivered() { - return mResponseDelivered; - } - - /** - * Subclasses must implement this to parse the raw network response - * and return an appropriate response type. This method will be - * called from a worker thread. The response will not be delivered - * if you return null. - * @param response Response from the network - * @return The parsed response, or null in the case of an error - */ - abstract protected Response parseNetworkResponse(NetworkResponse response); - - /** - * Subclasses can override this method to parse 'networkError' and return a more specific error. - * - *

          The default implementation just returns the passed 'networkError'.

          - * - * @param volleyError the error retrieved from the network - * @return an NetworkError augmented with additional information - */ - protected VolleyError parseNetworkError(VolleyError volleyError) { - return volleyError; - } - - /** - * Subclasses must implement this to perform delivery of the parsed - * response to their listeners. The given response is guaranteed to - * be non-null; responses that fail to parse are not delivered. - * @param response The parsed response returned by - * {@link #parseNetworkResponse(NetworkResponse)} - */ - abstract protected void deliverResponse(T response); - - /** - * Delivers error message to the ErrorListener that the Request was - * initialized with. - * - * @param error Error details - */ - public void deliverError(VolleyError error) { - if (mErrorListener != null) { - mErrorListener.onErrorResponse(error); - } - } - - /** - * Our comparator sorts from high to low priority, and secondarily by - * sequence number to provide FIFO ordering. - */ - @Override - public int compareTo(Request other) { - Priority left = this.getPriority(); - Priority right = other.getPriority(); - - // High-priority requests are "lesser" so they are sorted to the front. - // Equal priorities are sorted by sequence number to provide FIFO ordering. - return left == right ? - this.mSequence - other.mSequence : - right.ordinal() - left.ordinal(); - } - - @Override - public String toString() { - String trafficStatsTag = "0x" + Integer.toHexString(getTrafficStatsTag()); - return (mCanceled ? "[X] " : "[ ] ") + getUrl() + " " + trafficStatsTag + " " - + getPriority() + " " + mSequence; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/RequestQueue.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/RequestQueue.java deleted file mode 100644 index b694dbc9516..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/RequestQueue.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.os.Handler; -import android.os.Looper; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A request dispatch queue with a thread pool of dispatchers. - * - * Calling {@link #add(Request)} will enqueue the given Request for dispatch, - * resolving from either cache or network on a worker thread, and then delivering - * a parsed response on the main thread. - */ -public class RequestQueue { - - /** Callback interface for completed requests. */ - public static interface RequestFinishedListener { - /** Called when a request has finished processing. */ - public void onRequestFinished(Request request); - } - - /** Used for generating monotonically-increasing sequence numbers for requests. */ - private AtomicInteger mSequenceGenerator = new AtomicInteger(); - - /** - * Staging area for requests that already have a duplicate request in flight. - * - *
            - *
          • containsKey(cacheKey) indicates that there is a request in flight for the given cache - * key.
          • - *
          • get(cacheKey) returns waiting requests for the given cache key. The in flight request - * is not contained in that list. Is null if no requests are staged.
          • - *
          - */ - private final Map>> mWaitingRequests = - new HashMap>>(); - - /** - * The set of all requests currently being processed by this RequestQueue. A Request - * will be in this set if it is waiting in any queue or currently being processed by - * any dispatcher. - */ - private final Set> mCurrentRequests = new HashSet>(); - - /** The cache triage queue. */ - private final PriorityBlockingQueue> mCacheQueue = - new PriorityBlockingQueue>(); - - /** The queue of requests that are actually going out to the network. */ - private final PriorityBlockingQueue> mNetworkQueue = - new PriorityBlockingQueue>(); - - /** Number of network request dispatcher threads to start. */ - private static final int DEFAULT_NETWORK_THREAD_POOL_SIZE = 4; - - /** Cache interface for retrieving and storing responses. */ - private final Cache mCache; - - /** Network interface for performing requests. */ - private final Network mNetwork; - - /** Response delivery mechanism. */ - private final ResponseDelivery mDelivery; - - /** The network dispatchers. */ - private NetworkDispatcher[] mDispatchers; - - /** The cache dispatcher. */ - private CacheDispatcher mCacheDispatcher; - - private List mFinishedListeners = - new ArrayList(); - - /** - * Creates the worker pool. Processing will not begin until {@link #start()} is called. - * - * @param cache A Cache to use for persisting responses to disk - * @param network A Network interface for performing HTTP requests - * @param threadPoolSize Number of network dispatcher threads to create - * @param delivery A ResponseDelivery interface for posting responses and errors - */ - public RequestQueue(Cache cache, Network network, int threadPoolSize, - ResponseDelivery delivery) { - mCache = cache; - mNetwork = network; - mDispatchers = new NetworkDispatcher[threadPoolSize]; - mDelivery = delivery; - } - - /** - * Creates the worker pool. Processing will not begin until {@link #start()} is called. - * - * @param cache A Cache to use for persisting responses to disk - * @param network A Network interface for performing HTTP requests - * @param threadPoolSize Number of network dispatcher threads to create - */ - public RequestQueue(Cache cache, Network network, int threadPoolSize) { - this(cache, network, threadPoolSize, - new ExecutorDelivery(new Handler(Looper.getMainLooper()))); - } - - /** - * Creates the worker pool. Processing will not begin until {@link #start()} is called. - * - * @param cache A Cache to use for persisting responses to disk - * @param network A Network interface for performing HTTP requests - */ - public RequestQueue(Cache cache, Network network) { - this(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE); - } - - /** - * Starts the dispatchers in this queue. - */ - public void start() { - stop(); // Make sure any currently running dispatchers are stopped. - // Create the cache dispatcher and start it. - mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery); - mCacheDispatcher.start(); - - // Create network dispatchers (and corresponding threads) up to the pool size. - for (int i = 0; i < mDispatchers.length; i++) { - NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork, - mCache, mDelivery); - mDispatchers[i] = networkDispatcher; - networkDispatcher.start(); - } - } - - /** - * Stops the cache and network dispatchers. - */ - public void stop() { - if (mCacheDispatcher != null) { - mCacheDispatcher.quit(); - } - for (int i = 0; i < mDispatchers.length; i++) { - if (mDispatchers[i] != null) { - mDispatchers[i].quit(); - } - } - } - - /** - * Gets a sequence number. - */ - public int getSequenceNumber() { - return mSequenceGenerator.incrementAndGet(); - } - - /** - * Gets the {@link Cache} instance being used. - */ - public Cache getCache() { - return mCache; - } - - /** - * A simple predicate or filter interface for Requests, for use by - * {@link RequestQueue#cancelAll(RequestFilter)}. - */ - public interface RequestFilter { - public boolean apply(Request request); - } - - /** - * Cancels all requests in this queue for which the given filter applies. - * @param filter The filtering function to use - */ - public void cancelAll(RequestFilter filter) { - synchronized (mCurrentRequests) { - for (Request request : mCurrentRequests) { - if (filter.apply(request)) { - request.cancel(); - } - } - } - } - - /** - * Cancels all requests in this queue with the given tag. Tag must be non-null - * and equality is by identity. - */ - public void cancelAll(final Object tag) { - if (tag == null) { - throw new IllegalArgumentException("Cannot cancelAll with a null tag"); - } - cancelAll(new RequestFilter() { - @Override - public boolean apply(Request request) { - return request.getTag() == tag; - } - }); - } - - /** - * Adds a Request to the dispatch queue. - * @param request The request to service - * @return The passed-in request - */ - public Request add(Request request) { - // Tag the request as belonging to this queue and add it to the set of current requests. - request.setRequestQueue(this); - synchronized (mCurrentRequests) { - mCurrentRequests.add(request); - } - - // Process requests in the order they are added. - request.setSequence(getSequenceNumber()); - request.addMarker("add-to-queue"); - - // If the request is uncacheable, skip the cache queue and go straight to the network. - if (!request.shouldCache()) { - mNetworkQueue.add(request); - return request; - } - - // Insert request into stage if there's already a request with the same cache key in flight. - synchronized (mWaitingRequests) { - String cacheKey = request.getCacheKey(); - if (mWaitingRequests.containsKey(cacheKey)) { - // There is already a request in flight. Queue up. - Queue> stagedRequests = mWaitingRequests.get(cacheKey); - if (stagedRequests == null) { - stagedRequests = new LinkedList>(); - } - stagedRequests.add(request); - mWaitingRequests.put(cacheKey, stagedRequests); - if (VolleyLog.DEBUG) { - VolleyLog.v("Request for cacheKey=%s is in flight, putting on hold.", cacheKey); - } - } else { - // Insert 'null' queue for this cacheKey, indicating there is now a request in - // flight. - mWaitingRequests.put(cacheKey, null); - mCacheQueue.add(request); - } - return request; - } - } - - /** - * Called from {@link Request#finish(String)}, indicating that processing of the given request - * has finished. - * - *

          Releases waiting requests for request.getCacheKey() if - * request.shouldCache().

          - */ - void finish(Request request) { - // Remove from the set of requests currently being processed. - synchronized (mCurrentRequests) { - mCurrentRequests.remove(request); - } - synchronized (mFinishedListeners) { - for (RequestFinishedListener listener : mFinishedListeners) { - listener.onRequestFinished(request); - } - } - - if (request.shouldCache()) { - synchronized (mWaitingRequests) { - String cacheKey = request.getCacheKey(); - Queue> waitingRequests = mWaitingRequests.remove(cacheKey); - if (waitingRequests != null) { - if (VolleyLog.DEBUG) { - VolleyLog.v("Releasing %d waiting requests for cacheKey=%s.", - waitingRequests.size(), cacheKey); - } - // Process all queued up requests. They won't be considered as in flight, but - // that's not a problem as the cache has been primed by 'request'. - mCacheQueue.addAll(waitingRequests); - } - } - } - } - - public void addRequestFinishedListener(RequestFinishedListener listener) { - synchronized (mFinishedListeners) { - mFinishedListeners.add(listener); - } - } - - /** - * Remove a RequestFinishedListener. Has no effect if listener was not previously added. - */ - public void removeRequestFinishedListener(RequestFinishedListener listener) { - synchronized (mFinishedListeners) { - mFinishedListeners.remove(listener); - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Response.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/Response.java deleted file mode 100644 index aadf2d783ff..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/Response.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Encapsulates a parsed response for delivery. - * - * @param Parsed type of this response - */ -public class Response { - - /** Callback interface for delivering parsed responses. */ - public interface Listener { - /** Called when a response is received. */ - public void onResponse(T response); - } - - /** Callback interface for delivering error responses. */ - public interface ErrorListener { - /** - * Callback method that an error has been occurred with the - * provided error code and optional user-readable message. - */ - public void onErrorResponse(VolleyError error); - } - - /** Returns a successful response containing the parsed result. */ - public static Response success(T result, Cache.Entry cacheEntry) { - return new Response(result, cacheEntry); - } - - /** - * Returns a failed response containing the given error code and an optional - * localized message displayed to the user. - */ - public static Response error(VolleyError error) { - return new Response(error); - } - - /** Parsed response, or null in the case of error. */ - public final T result; - - /** Cache metadata for this response, or null in the case of error. */ - public final Cache.Entry cacheEntry; - - /** Detailed error information if errorCode != OK. */ - public final VolleyError error; - - /** True if this response was a soft-expired one and a second one MAY be coming. */ - public boolean intermediate = false; - - /** - * Returns whether this response is considered successful. - */ - public boolean isSuccess() { - return error == null; - } - - - private Response(T result, Cache.Entry cacheEntry) { - this.result = result; - this.cacheEntry = cacheEntry; - this.error = null; - } - - private Response(VolleyError error) { - this.result = null; - this.cacheEntry = null; - this.error = error; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ResponseDelivery.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/ResponseDelivery.java deleted file mode 100644 index 38e67cf58c2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ResponseDelivery.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -public interface ResponseDelivery { - /** - * Parses a response from the network or cache and delivers it. - */ - public void postResponse(Request request, Response response); - - /** - * Parses a response from the network or cache and delivers it. The provided - * Runnable will be executed after delivery. - */ - public void postResponse(Request request, Response response, Runnable runnable); - - /** - * Posts an error for the given request. - */ - public void postError(Request request, VolleyError error); -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/RetryPolicy.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/RetryPolicy.java deleted file mode 100644 index 02f279a7088..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/RetryPolicy.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Retry policy for a request. - */ -public interface RetryPolicy { - - /** - * Returns the current timeout (used for logging). - */ - public int getCurrentTimeout(); - - /** - * Returns the current retry count (used for logging). - */ - public int getCurrentRetryCount(); - - /** - * Prepares for the next retry by applying a backoff to the timeout. - * @param error The error code of the last attempt. - * @throws VolleyError In the event that the retry could not be performed (for example if we - * ran out of attempts), the passed in error is thrown. - */ - public void retry(VolleyError error) throws VolleyError; -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ServerError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/ServerError.java deleted file mode 100644 index bff6a4cc580..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/ServerError.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Indicates that the server responded with an error response. - */ -@SuppressWarnings("serial") -public class ServerError extends VolleyError { - public ServerError(NetworkResponse networkResponse) { - super(networkResponse); - } - - public ServerError() { - super(); - } -} - diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/TimeoutError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/TimeoutError.java deleted file mode 100644 index ca3b185b775..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/TimeoutError.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Indicates that the connection or the socket timed out. - */ -@SuppressWarnings("serial") -public class TimeoutError extends VolleyError { } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyError.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyError.java deleted file mode 100644 index 78b5c71bfd7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyError.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -/** - * Exception style class encapsulating Volley errors - */ -@SuppressWarnings("serial") -public class VolleyError extends Exception { - public final NetworkResponse networkResponse; - private long networkTimeMs; - - public VolleyError() { - networkResponse = null; - } - - public VolleyError(NetworkResponse response) { - networkResponse = response; - } - - public VolleyError(String exceptionMessage) { - super(exceptionMessage); - networkResponse = null; - } - - public VolleyError(String exceptionMessage, Throwable reason) { - super(exceptionMessage, reason); - networkResponse = null; - } - - public VolleyError(Throwable cause) { - super(cause); - networkResponse = null; - } - - /* package */ void setNetworkTimeMs(long networkTimeMs) { - this.networkTimeMs = networkTimeMs; - } - - public long getNetworkTimeMs() { - return networkTimeMs; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyLog.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyLog.java deleted file mode 100644 index 1834358a901..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/VolleyLog.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley; - -import android.os.SystemClock; -import android.util.Log; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; - -/** - * Logging helper class. - *

          - * to see Volley logs call:
          - * {@code /platform-tools/adb shell setprop log.tag.Volley VERBOSE} - */ -public class VolleyLog { - public static String TAG = "Volley"; - - public static boolean DEBUG = Log.isLoggable(TAG, Log.VERBOSE); - - /** - * Customize the log tag for your application, so that other apps - * using Volley don't mix their logs with yours. - *
          - * Enable the log property for your tag before starting your app: - *
          - * {@code adb shell setprop log.tag.<tag>} - */ - public static void setTag(String tag) { - d("Changing log tag to %s", tag); - TAG = tag; - - // Reinitialize the DEBUG "constant" - DEBUG = Log.isLoggable(TAG, Log.VERBOSE); - } - - public static void v(String format, Object... args) { - if (DEBUG) { - Log.v(TAG, buildMessage(format, args)); - } - } - - public static void d(String format, Object... args) { - Log.d(TAG, buildMessage(format, args)); - } - - public static void e(String format, Object... args) { - Log.e(TAG, buildMessage(format, args)); - } - - public static void e(Throwable tr, String format, Object... args) { - Log.e(TAG, buildMessage(format, args), tr); - } - - public static void wtf(String format, Object... args) { - Log.wtf(TAG, buildMessage(format, args)); - } - - public static void wtf(Throwable tr, String format, Object... args) { - Log.wtf(TAG, buildMessage(format, args), tr); - } - - /** - * Formats the caller's provided message and prepends useful info like - * calling thread ID and method name. - */ - private static String buildMessage(String format, Object... args) { - String msg = (args == null) ? format : String.format(Locale.US, format, args); - StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace(); - - String caller = ""; - // Walk up the stack looking for the first caller outside of VolleyLog. - // It will be at least two frames up, so start there. - for (int i = 2; i < trace.length; i++) { - Class clazz = trace[i].getClass(); - if (!clazz.equals(VolleyLog.class)) { - String callingClass = trace[i].getClassName(); - callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1); - callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1); - - caller = callingClass + "." + trace[i].getMethodName(); - break; - } - } - return String.format(Locale.US, "[%d] %s: %s", - Thread.currentThread().getId(), caller, msg); - } - - /** - * A simple event log with records containing a name, thread ID, and timestamp. - */ - static class MarkerLog { - public static final boolean ENABLED = VolleyLog.DEBUG; - - /** Minimum duration from first marker to last in an marker log to warrant logging. */ - private static final long MIN_DURATION_FOR_LOGGING_MS = 0; - - private static class Marker { - public final String name; - public final long thread; - public final long time; - - public Marker(String name, long thread, long time) { - this.name = name; - this.thread = thread; - this.time = time; - } - } - - private final List mMarkers = new ArrayList(); - private boolean mFinished = false; - - /** Adds a marker to this log with the specified name. */ - public synchronized void add(String name, long threadId) { - if (mFinished) { - throw new IllegalStateException("Marker added to finished log"); - } - - mMarkers.add(new Marker(name, threadId, SystemClock.elapsedRealtime())); - } - - /** - * Closes the log, dumping it to logcat if the time difference between - * the first and last markers is greater than {@link #MIN_DURATION_FOR_LOGGING_MS}. - * @param header Header string to print above the marker log. - */ - public synchronized void finish(String header) { - mFinished = true; - - long duration = getTotalDuration(); - if (duration <= MIN_DURATION_FOR_LOGGING_MS) { - return; - } - - long prevTime = mMarkers.get(0).time; - d("(%-4d ms) %s", duration, header); - for (Marker marker : mMarkers) { - long thisTime = marker.time; - d("(+%-4d) [%2d] %s", (thisTime - prevTime), marker.thread, marker.name); - prevTime = thisTime; - } - } - - @Override - protected void finalize() throws Throwable { - // Catch requests that have been collected (and hence end-of-lifed) - // but had no debugging output printed for them. - if (!mFinished) { - finish("Request on the loose"); - e("Marker log finalized without finish() - uncaught exit point for request"); - } - } - - /** Returns the time difference between the first and last events in this log. */ - private long getTotalDuration() { - if (mMarkers.size() == 0) { - return 0; - } - - long first = mMarkers.get(0).time; - long last = mMarkers.get(mMarkers.size() - 1).time; - return last - first; - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/AndroidAuthenticator.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/AndroidAuthenticator.java deleted file mode 100644 index 780c643d1fd..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/AndroidAuthenticator.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.AuthFailureError; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerFuture; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; - -/** - * An Authenticator that uses {@link AccountManager} to get auth - * tokens of a specified type for a specified account. - */ -public class AndroidAuthenticator implements Authenticator { - private final AccountManager mAccountManager; - private final Account mAccount; - private final String mAuthTokenType; - private final boolean mNotifyAuthFailure; - - /** - * Creates a new authenticator. - * @param context Context for accessing AccountManager - * @param account Account to authenticate as - * @param authTokenType Auth token type passed to AccountManager - */ - public AndroidAuthenticator(Context context, Account account, String authTokenType) { - this(context, account, authTokenType, false); - } - - /** - * Creates a new authenticator. - * @param context Context for accessing AccountManager - * @param account Account to authenticate as - * @param authTokenType Auth token type passed to AccountManager - * @param notifyAuthFailure Whether to raise a notification upon auth failure - */ - public AndroidAuthenticator(Context context, Account account, String authTokenType, - boolean notifyAuthFailure) { - this(AccountManager.get(context), account, authTokenType, notifyAuthFailure); - } - - // Visible for testing. Allows injection of a mock AccountManager. - AndroidAuthenticator(AccountManager accountManager, Account account, - String authTokenType, boolean notifyAuthFailure) { - mAccountManager = accountManager; - mAccount = account; - mAuthTokenType = authTokenType; - mNotifyAuthFailure = notifyAuthFailure; - } - - /** - * Returns the Account being used by this authenticator. - */ - public Account getAccount() { - return mAccount; - } - - // TODO: Figure out what to do about notifyAuthFailure - @SuppressWarnings("deprecation") - @Override - public String getAuthToken() throws AuthFailureError { - AccountManagerFuture future = mAccountManager.getAuthToken(mAccount, - mAuthTokenType, mNotifyAuthFailure, null, null); - Bundle result; - try { - result = future.getResult(); - } catch (Exception e) { - throw new AuthFailureError("Error while retrieving auth token", e); - } - String authToken = null; - if (future.isDone() && !future.isCancelled()) { - if (result.containsKey(AccountManager.KEY_INTENT)) { - Intent intent = result.getParcelable(AccountManager.KEY_INTENT); - throw new AuthFailureError(intent); - } - authToken = result.getString(AccountManager.KEY_AUTHTOKEN); - } - if (authToken == null) { - throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType); - } - - return authToken; - } - - @Override - public void invalidateAuthToken(String authToken) { - mAccountManager.invalidateAuthToken(mAccount.type, authToken); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Authenticator.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Authenticator.java deleted file mode 100644 index 9118a3bf061..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Authenticator.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.AuthFailureError; - -/** - * An interface for interacting with auth tokens. - */ -public interface Authenticator { - /** - * Synchronously retrieves an auth token. - * - * @throws AuthFailureError If authentication did not succeed - */ - public String getAuthToken() throws AuthFailureError; - - /** - * Invalidates the provided auth token. - */ - public void invalidateAuthToken(String authToken); -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/BasicNetwork.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/BasicNetwork.java deleted file mode 100644 index 0ca76037632..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/BasicNetwork.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import android.os.SystemClock; - -import org.telegram.messenger.volley.AuthFailureError; -import org.telegram.messenger.volley.Cache; -import org.telegram.messenger.volley.Cache.Entry; -import org.telegram.messenger.volley.Network; -import org.telegram.messenger.volley.NetworkError; -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.NoConnectionError; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.RetryPolicy; -import org.telegram.messenger.volley.ServerError; -import org.telegram.messenger.volley.TimeoutError; -import org.telegram.messenger.volley.VolleyError; -import org.telegram.messenger.volley.VolleyLog; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.conn.ConnectTimeoutException; -import org.apache.http.impl.cookie.DateUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.SocketTimeoutException; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -/** - * A network performing Volley requests over an {@link HttpStack}. - */ -public class BasicNetwork implements Network { - protected static final boolean DEBUG = VolleyLog.DEBUG; - - private static int SLOW_REQUEST_THRESHOLD_MS = 3000; - - private static int DEFAULT_POOL_SIZE = 4096; - - protected final HttpStack mHttpStack; - - protected final ByteArrayPool mPool; - - /** - * @param httpStack HTTP stack to be used - */ - public BasicNetwork(HttpStack httpStack) { - // If a pool isn't passed in, then build a small default pool that will give us a lot of - // benefit and not use too much memory. - this(httpStack, new ByteArrayPool(DEFAULT_POOL_SIZE)); - } - - /** - * @param httpStack HTTP stack to be used - * @param pool a buffer pool that improves GC performance in copy operations - */ - public BasicNetwork(HttpStack httpStack, ByteArrayPool pool) { - mHttpStack = httpStack; - mPool = pool; - } - - @Override - public NetworkResponse performRequest(Request request) throws VolleyError { - long requestStart = SystemClock.elapsedRealtime(); - while (true) { - HttpResponse httpResponse = null; - byte[] responseContents = null; - Map responseHeaders = Collections.emptyMap(); - try { - // Gather headers. - Map headers = new HashMap(); - addCacheHeaders(headers, request.getCacheEntry()); - httpResponse = mHttpStack.performRequest(request, headers); - StatusLine statusLine = httpResponse.getStatusLine(); - int statusCode = statusLine.getStatusCode(); - - responseHeaders = convertHeaders(httpResponse.getAllHeaders()); - // Handle cache validation. - if (statusCode == HttpStatus.SC_NOT_MODIFIED) { - - Entry entry = request.getCacheEntry(); - if (entry == null) { - return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, - responseHeaders, true, - SystemClock.elapsedRealtime() - requestStart); - } - - // A HTTP 304 response does not have all header fields. We - // have to use the header fields from the cache entry plus - // the new ones from the response. - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 - entry.responseHeaders.putAll(responseHeaders); - return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, - entry.responseHeaders, true, - SystemClock.elapsedRealtime() - requestStart); - } - - // Some responses such as 204s do not have content. We must check. - if (httpResponse.getEntity() != null) { - responseContents = entityToBytes(httpResponse.getEntity()); - } else { - // Add 0 byte response as a way of honestly representing a - // no-content request. - responseContents = new byte[0]; - } - - // if the request is slow, log it. - long requestLifetime = SystemClock.elapsedRealtime() - requestStart; - logSlowRequests(requestLifetime, request, responseContents, statusLine); - - if (statusCode < 200 || statusCode > 299) { - throw new IOException(); - } - return new NetworkResponse(statusCode, responseContents, responseHeaders, false, - SystemClock.elapsedRealtime() - requestStart); - } catch (SocketTimeoutException e) { - attemptRetryOnException("socket", request, new TimeoutError()); - } catch (ConnectTimeoutException e) { - attemptRetryOnException("connection", request, new TimeoutError()); - } catch (MalformedURLException e) { - throw new RuntimeException("Bad URL " + request.getUrl(), e); - } catch (IOException e) { - int statusCode = 0; - NetworkResponse networkResponse = null; - if (httpResponse != null) { - statusCode = httpResponse.getStatusLine().getStatusCode(); - } else { - throw new NoConnectionError(e); - } - VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl()); - if (responseContents != null) { - networkResponse = new NetworkResponse(statusCode, responseContents, - responseHeaders, false, SystemClock.elapsedRealtime() - requestStart); - if (statusCode == HttpStatus.SC_UNAUTHORIZED || - statusCode == HttpStatus.SC_FORBIDDEN) { - attemptRetryOnException("auth", - request, new AuthFailureError(networkResponse)); - } else { - // TODO: Only throw ServerError for 5xx status codes. - throw new ServerError(networkResponse); - } - } else { - throw new NetworkError(networkResponse); - } - } - } - } - - /** - * Logs requests that took over SLOW_REQUEST_THRESHOLD_MS to complete. - */ - private void logSlowRequests(long requestLifetime, Request request, - byte[] responseContents, StatusLine statusLine) { - if (DEBUG || requestLifetime > SLOW_REQUEST_THRESHOLD_MS) { - VolleyLog.d("HTTP response for request=<%s> [lifetime=%d], [size=%s], " + - "[rc=%d], [retryCount=%s]", request, requestLifetime, - responseContents != null ? responseContents.length : "null", - statusLine.getStatusCode(), request.getRetryPolicy().getCurrentRetryCount()); - } - } - - /** - * Attempts to prepare the request for a retry. If there are no more attempts remaining in the - * request's retry policy, a timeout exception is thrown. - * @param request The request to use. - */ - private static void attemptRetryOnException(String logPrefix, Request request, - VolleyError exception) throws VolleyError { - RetryPolicy retryPolicy = request.getRetryPolicy(); - int oldTimeout = request.getTimeoutMs(); - - try { - retryPolicy.retry(exception); - } catch (VolleyError e) { - request.addMarker( - String.format("%s-timeout-giveup [timeout=%s]", logPrefix, oldTimeout)); - throw e; - } - request.addMarker(String.format("%s-retry [timeout=%s]", logPrefix, oldTimeout)); - } - - private void addCacheHeaders(Map headers, Cache.Entry entry) { - // If there's no cache entry, we're done. - if (entry == null) { - return; - } - - if (entry.etag != null) { - headers.put("If-None-Match", entry.etag); - } - - if (entry.lastModified > 0) { - Date refTime = new Date(entry.lastModified); - headers.put("If-Modified-Since", DateUtils.formatDate(refTime)); - } - } - - protected void logError(String what, String url, long start) { - long now = SystemClock.elapsedRealtime(); - VolleyLog.v("HTTP ERROR(%s) %d ms to fetch %s", what, (now - start), url); - } - - /** Reads the contents of HttpEntity into a byte[]. */ - private byte[] entityToBytes(HttpEntity entity) throws IOException, ServerError { - PoolingByteArrayOutputStream bytes = - new PoolingByteArrayOutputStream(mPool, (int) entity.getContentLength()); - byte[] buffer = null; - try { - InputStream in = entity.getContent(); - if (in == null) { - throw new ServerError(); - } - buffer = mPool.getBuf(1024); - int count; - while ((count = in.read(buffer)) != -1) { - bytes.write(buffer, 0, count); - } - return bytes.toByteArray(); - } finally { - try { - // Close the InputStream and release the resources by "consuming the content". - entity.consumeContent(); - } catch (IOException e) { - // This can happen if there was an exception above that left the entity in - // an invalid state. - VolleyLog.v("Error occured when calling consumingContent"); - } - mPool.returnBuf(buffer); - bytes.close(); - } - } - - /** - * Converts Headers[] to Map. - */ - protected static Map convertHeaders(Header[] headers) { - Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER); - for (int i = 0; i < headers.length; i++) { - result.put(headers[i].getName(), headers[i].getValue()); - } - return result; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ByteArrayPool.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ByteArrayPool.java deleted file mode 100644 index 047a9181748..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ByteArrayPool.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; - -/** - * ByteArrayPool is a source and repository of byte[] objects. Its purpose is to - * supply those buffers to consumers who need to use them for a short period of time and then - * dispose of them. Simply creating and disposing such buffers in the conventional manner can - * considerable heap churn and garbage collection delays on Android, which lacks good management of - * short-lived heap objects. It may be advantageous to trade off some memory in the form of a - * permanently allocated pool of buffers in order to gain heap performance improvements; that is - * what this class does. - *

          - * A good candidate user for this class is something like an I/O system that uses large temporary - * byte[] buffers to copy data around. In these use cases, often the consumer wants - * the buffer to be a certain minimum size to ensure good performance (e.g. when copying data chunks - * off of a stream), but doesn't mind if the buffer is larger than the minimum. Taking this into - * account and also to maximize the odds of being able to reuse a recycled buffer, this class is - * free to return buffers larger than the requested size. The caller needs to be able to gracefully - * deal with getting buffers any size over the minimum. - *

          - * If there is not a suitably-sized buffer in its recycling pool when a buffer is requested, this - * class will allocate a new buffer and return it. - *

          - * This class has no special ownership of buffers it creates; the caller is free to take a buffer - * it receives from this pool, use it permanently, and never return it to the pool; additionally, - * it is not harmful to return to this pool a buffer that was allocated elsewhere, provided there - * are no other lingering references to it. - *

          - * This class ensures that the total size of the buffers in its recycling pool never exceeds a - * certain byte limit. When a buffer is returned that would cause the pool to exceed the limit, - * least-recently-used buffers are disposed. - */ -public class ByteArrayPool { - /** The buffer pool, arranged both by last use and by buffer size */ - private List mBuffersByLastUse = new LinkedList(); - private List mBuffersBySize = new ArrayList(64); - - /** The total size of the buffers in the pool */ - private int mCurrentSize = 0; - - /** - * The maximum aggregate size of the buffers in the pool. Old buffers are discarded to stay - * under this limit. - */ - private final int mSizeLimit; - - /** Compares buffers by size */ - protected static final Comparator BUF_COMPARATOR = new Comparator() { - @Override - public int compare(byte[] lhs, byte[] rhs) { - return lhs.length - rhs.length; - } - }; - - /** - * @param sizeLimit the maximum size of the pool, in bytes - */ - public ByteArrayPool(int sizeLimit) { - mSizeLimit = sizeLimit; - } - - /** - * Returns a buffer from the pool if one is available in the requested size, or allocates a new - * one if a pooled one is not available. - * - * @param len the minimum size, in bytes, of the requested buffer. The returned buffer may be - * larger. - * @return a byte[] buffer is always returned. - */ - public synchronized byte[] getBuf(int len) { - for (int i = 0; i < mBuffersBySize.size(); i++) { - byte[] buf = mBuffersBySize.get(i); - if (buf.length >= len) { - mCurrentSize -= buf.length; - mBuffersBySize.remove(i); - mBuffersByLastUse.remove(buf); - return buf; - } - } - return new byte[len]; - } - - /** - * Returns a buffer to the pool, throwing away old buffers if the pool would exceed its allotted - * size. - * - * @param buf the buffer to return to the pool. - */ - public synchronized void returnBuf(byte[] buf) { - if (buf == null || buf.length > mSizeLimit) { - return; - } - mBuffersByLastUse.add(buf); - int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR); - if (pos < 0) { - pos = -pos - 1; - } - mBuffersBySize.add(pos, buf); - mCurrentSize += buf.length; - trim(); - } - - /** - * Removes buffers from the pool until it is under its size limit. - */ - private synchronized void trim() { - while (mCurrentSize > mSizeLimit) { - byte[] buf = mBuffersByLastUse.remove(0); - mBuffersBySize.remove(buf); - mCurrentSize -= buf.length; - } - } - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ClearCacheRequest.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ClearCacheRequest.java deleted file mode 100644 index 22999cf4623..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/ClearCacheRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.Cache; -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Response; - -import android.os.Handler; -import android.os.Looper; - -/** - * A synthetic request used for clearing the cache. - */ -public class ClearCacheRequest extends Request { - private final Cache mCache; - private final Runnable mCallback; - - /** - * Creates a synthetic request for clearing the cache. - * @param cache Cache to clear - * @param callback Callback to make on the main thread once the cache is clear, - * or null for none - */ - public ClearCacheRequest(Cache cache, Runnable callback) { - super(Method.GET, null, null); - mCache = cache; - mCallback = callback; - } - - @Override - public boolean isCanceled() { - // This is a little bit of a hack, but hey, why not. - mCache.clear(); - if (mCallback != null) { - Handler handler = new Handler(Looper.getMainLooper()); - handler.postAtFrontOfQueue(mCallback); - } - return true; - } - - @Override - public Priority getPriority() { - return Priority.IMMEDIATE; - } - - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - return null; - } - - @Override - protected void deliverResponse(Object response) { - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/DiskBasedCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/DiskBasedCache.java deleted file mode 100644 index 3ecfaf30d73..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/DiskBasedCache.java +++ /dev/null @@ -1,571 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import android.os.SystemClock; - -import org.telegram.messenger.volley.Cache; -import org.telegram.messenger.volley.VolleyLog; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Cache implementation that caches files directly onto the hard disk in the specified - * directory. The default disk usage size is 5MB, but is configurable. - */ -public class DiskBasedCache implements Cache { - - /** Map of the Key, CacheHeader pairs */ - private final Map mEntries = - new LinkedHashMap(16, .75f, true); - - /** Total amount of space currently used by the cache in bytes. */ - private long mTotalSize = 0; - - /** The root directory to use for the cache. */ - private final File mRootDirectory; - - /** The maximum size of the cache in bytes. */ - private final int mMaxCacheSizeInBytes; - - /** Default maximum disk usage in bytes. */ - private static final int DEFAULT_DISK_USAGE_BYTES = 5 * 1024 * 1024; - - /** High water mark percentage for the cache */ - private static final float HYSTERESIS_FACTOR = 0.9f; - - /** Magic number for current version of cache file format. */ - private static final int CACHE_MAGIC = 0x20150306; - - /** - * Constructs an instance of the DiskBasedCache at the specified directory. - * @param rootDirectory The root directory of the cache. - * @param maxCacheSizeInBytes The maximum size of the cache in bytes. - */ - public DiskBasedCache(File rootDirectory, int maxCacheSizeInBytes) { - mRootDirectory = rootDirectory; - mMaxCacheSizeInBytes = maxCacheSizeInBytes; - } - - /** - * Constructs an instance of the DiskBasedCache at the specified directory using - * the default maximum cache size of 5MB. - * @param rootDirectory The root directory of the cache. - */ - public DiskBasedCache(File rootDirectory) { - this(rootDirectory, DEFAULT_DISK_USAGE_BYTES); - } - - /** - * Clears the cache. Deletes all cached files from disk. - */ - @Override - public synchronized void clear() { - File[] files = mRootDirectory.listFiles(); - if (files != null) { - for (File file : files) { - file.delete(); - } - } - mEntries.clear(); - mTotalSize = 0; - VolleyLog.d("Cache cleared."); - } - - /** - * Returns the cache entry with the specified key if it exists, null otherwise. - */ - @Override - public synchronized Entry get(String key) { - CacheHeader entry = mEntries.get(key); - // if the entry does not exist, return. - if (entry == null) { - return null; - } - - File file = getFileForKey(key); - CountingInputStream cis = null; - try { - cis = new CountingInputStream(new BufferedInputStream(new FileInputStream(file))); - CacheHeader.readHeader(cis); // eat header - byte[] data = streamToBytes(cis, (int) (file.length() - cis.bytesRead)); - return entry.toCacheEntry(data); - } catch (IOException e) { - VolleyLog.d("%s: %s", file.getAbsolutePath(), e.toString()); - remove(key); - return null; - } finally { - if (cis != null) { - try { - cis.close(); - } catch (IOException ioe) { - return null; - } - } - } - } - - /** - * Initializes the DiskBasedCache by scanning for all files currently in the - * specified root directory. Creates the root directory if necessary. - */ - @Override - public synchronized void initialize() { - if (!mRootDirectory.exists()) { - if (!mRootDirectory.mkdirs()) { - VolleyLog.e("Unable to create cache dir %s", mRootDirectory.getAbsolutePath()); - } - return; - } - - File[] files = mRootDirectory.listFiles(); - if (files == null) { - return; - } - for (File file : files) { - BufferedInputStream fis = null; - try { - fis = new BufferedInputStream(new FileInputStream(file)); - CacheHeader entry = CacheHeader.readHeader(fis); - entry.size = file.length(); - putEntry(entry.key, entry); - } catch (IOException e) { - if (file != null) { - file.delete(); - } - } finally { - try { - if (fis != null) { - fis.close(); - } - } catch (IOException ignored) { } - } - } - } - - /** - * Invalidates an entry in the cache. - * @param key Cache key - * @param fullExpire True to fully expire the entry, false to soft expire - */ - @Override - public synchronized void invalidate(String key, boolean fullExpire) { - Entry entry = get(key); - if (entry != null) { - entry.softTtl = 0; - if (fullExpire) { - entry.ttl = 0; - } - put(key, entry); - } - - } - - /** - * Puts the entry with the specified key into the cache. - */ - @Override - public synchronized void put(String key, Entry entry) { - pruneIfNeeded(entry.data.length); - File file = getFileForKey(key); - try { - BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(file)); - CacheHeader e = new CacheHeader(key, entry); - boolean success = e.writeHeader(fos); - if (!success) { - fos.close(); - VolleyLog.d("Failed to write header for %s", file.getAbsolutePath()); - throw new IOException(); - } - fos.write(entry.data); - fos.close(); - putEntry(key, e); - return; - } catch (IOException e) { - } - boolean deleted = file.delete(); - if (!deleted) { - VolleyLog.d("Could not clean up file %s", file.getAbsolutePath()); - } - } - - /** - * Removes the specified key from the cache if it exists. - */ - @Override - public synchronized void remove(String key) { - boolean deleted = getFileForKey(key).delete(); - removeEntry(key); - if (!deleted) { - VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", - key, getFilenameForKey(key)); - } - } - - /** - * Creates a pseudo-unique filename for the specified cache key. - * @param key The key to generate a file name for. - * @return A pseudo-unique filename. - */ - private String getFilenameForKey(String key) { - int firstHalfLength = key.length() / 2; - String localFilename = String.valueOf(key.substring(0, firstHalfLength).hashCode()); - localFilename += String.valueOf(key.substring(firstHalfLength).hashCode()); - return localFilename; - } - - /** - * Returns a file object for the given cache key. - */ - public File getFileForKey(String key) { - return new File(mRootDirectory, getFilenameForKey(key)); - } - - /** - * Prunes the cache to fit the amount of bytes specified. - * @param neededSpace The amount of bytes we are trying to fit into the cache. - */ - private void pruneIfNeeded(int neededSpace) { - if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes) { - return; - } - if (VolleyLog.DEBUG) { - VolleyLog.v("Pruning old cache entries."); - } - - long before = mTotalSize; - int prunedFiles = 0; - long startTime = SystemClock.elapsedRealtime(); - - Iterator> iterator = mEntries.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - CacheHeader e = entry.getValue(); - boolean deleted = getFileForKey(e.key).delete(); - if (deleted) { - mTotalSize -= e.size; - } else { - VolleyLog.d("Could not delete cache entry for key=%s, filename=%s", - e.key, getFilenameForKey(e.key)); - } - iterator.remove(); - prunedFiles++; - - if ((mTotalSize + neededSpace) < mMaxCacheSizeInBytes * HYSTERESIS_FACTOR) { - break; - } - } - - if (VolleyLog.DEBUG) { - VolleyLog.v("pruned %d files, %d bytes, %d ms", - prunedFiles, (mTotalSize - before), SystemClock.elapsedRealtime() - startTime); - } - } - - /** - * Puts the entry with the specified key into the cache. - * @param key The key to identify the entry by. - * @param entry The entry to cache. - */ - private void putEntry(String key, CacheHeader entry) { - if (!mEntries.containsKey(key)) { - mTotalSize += entry.size; - } else { - CacheHeader oldEntry = mEntries.get(key); - mTotalSize += (entry.size - oldEntry.size); - } - mEntries.put(key, entry); - } - - /** - * Removes the entry identified by 'key' from the cache. - */ - private void removeEntry(String key) { - CacheHeader entry = mEntries.get(key); - if (entry != null) { - mTotalSize -= entry.size; - mEntries.remove(key); - } - } - - /** - * Reads the contents of an InputStream into a byte[]. - * */ - private static byte[] streamToBytes(InputStream in, int length) throws IOException { - byte[] bytes = new byte[length]; - int count; - int pos = 0; - while (pos < length && ((count = in.read(bytes, pos, length - pos)) != -1)) { - pos += count; - } - if (pos != length) { - throw new IOException("Expected " + length + " bytes, read " + pos + " bytes"); - } - return bytes; - } - - /** - * Handles holding onto the cache headers for an entry. - */ - // Visible for testing. - static class CacheHeader { - /** The size of the data identified by this CacheHeader. (This is not - * serialized to disk. */ - public long size; - - /** The key that identifies the cache entry. */ - public String key; - - /** ETag for cache coherence. */ - public String etag; - - /** Date of this response as reported by the server. */ - public long serverDate; - - /** The last modified date for the requested object. */ - public long lastModified; - - /** TTL for this record. */ - public long ttl; - - /** Soft TTL for this record. */ - public long softTtl; - - /** Headers from the response resulting in this cache entry. */ - public Map responseHeaders; - - private CacheHeader() { } - - /** - * Instantiates a new CacheHeader object - * @param key The key that identifies the cache entry - * @param entry The cache entry. - */ - public CacheHeader(String key, Entry entry) { - this.key = key; - this.size = entry.data.length; - this.etag = entry.etag; - this.serverDate = entry.serverDate; - this.lastModified = entry.lastModified; - this.ttl = entry.ttl; - this.softTtl = entry.softTtl; - this.responseHeaders = entry.responseHeaders; - } - - /** - * Reads the header off of an InputStream and returns a CacheHeader object. - * @param is The InputStream to read from. - * @throws IOException - */ - public static CacheHeader readHeader(InputStream is) throws IOException { - CacheHeader entry = new CacheHeader(); - int magic = readInt(is); - if (magic != CACHE_MAGIC) { - // don't bother deleting, it'll get pruned eventually - throw new IOException(); - } - entry.key = readString(is); - entry.etag = readString(is); - if (entry.etag.equals("")) { - entry.etag = null; - } - entry.serverDate = readLong(is); - entry.lastModified = readLong(is); - entry.ttl = readLong(is); - entry.softTtl = readLong(is); - entry.responseHeaders = readStringStringMap(is); - - return entry; - } - - /** - * Creates a cache entry for the specified data. - */ - public Entry toCacheEntry(byte[] data) { - Entry e = new Entry(); - e.data = data; - e.etag = etag; - e.serverDate = serverDate; - e.lastModified = lastModified; - e.ttl = ttl; - e.softTtl = softTtl; - e.responseHeaders = responseHeaders; - return e; - } - - - /** - * Writes the contents of this CacheHeader to the specified OutputStream. - */ - public boolean writeHeader(OutputStream os) { - try { - writeInt(os, CACHE_MAGIC); - writeString(os, key); - writeString(os, etag == null ? "" : etag); - writeLong(os, serverDate); - writeLong(os, lastModified); - writeLong(os, ttl); - writeLong(os, softTtl); - writeStringStringMap(responseHeaders, os); - os.flush(); - return true; - } catch (IOException e) { - VolleyLog.d("%s", e.toString()); - return false; - } - } - - } - - private static class CountingInputStream extends FilterInputStream { - private int bytesRead = 0; - - private CountingInputStream(InputStream in) { - super(in); - } - - @Override - public int read() throws IOException { - int result = super.read(); - if (result != -1) { - bytesRead++; - } - return result; - } - - @Override - public int read(byte[] buffer, int offset, int count) throws IOException { - int result = super.read(buffer, offset, count); - if (result != -1) { - bytesRead += result; - } - return result; - } - } - - /* - * Homebrewed simple serialization system used for reading and writing cache - * headers on disk. Once upon a time, this used the standard Java - * Object{Input,Output}Stream, but the default implementation relies heavily - * on reflection (even for standard types) and generates a ton of garbage. - */ - - /** - * Simple wrapper around {@link InputStream#read()} that throws EOFException - * instead of returning -1. - */ - private static int read(InputStream is) throws IOException { - int b = is.read(); - if (b == -1) { - throw new EOFException(); - } - return b; - } - - static void writeInt(OutputStream os, int n) throws IOException { - os.write((n >> 0) & 0xff); - os.write((n >> 8) & 0xff); - os.write((n >> 16) & 0xff); - os.write((n >> 24) & 0xff); - } - - static int readInt(InputStream is) throws IOException { - int n = 0; - n |= (read(is) << 0); - n |= (read(is) << 8); - n |= (read(is) << 16); - n |= (read(is) << 24); - return n; - } - - static void writeLong(OutputStream os, long n) throws IOException { - os.write((byte)(n >>> 0)); - os.write((byte)(n >>> 8)); - os.write((byte)(n >>> 16)); - os.write((byte)(n >>> 24)); - os.write((byte)(n >>> 32)); - os.write((byte)(n >>> 40)); - os.write((byte)(n >>> 48)); - os.write((byte)(n >>> 56)); - } - - static long readLong(InputStream is) throws IOException { - long n = 0; - n |= ((read(is) & 0xFFL) << 0); - n |= ((read(is) & 0xFFL) << 8); - n |= ((read(is) & 0xFFL) << 16); - n |= ((read(is) & 0xFFL) << 24); - n |= ((read(is) & 0xFFL) << 32); - n |= ((read(is) & 0xFFL) << 40); - n |= ((read(is) & 0xFFL) << 48); - n |= ((read(is) & 0xFFL) << 56); - return n; - } - - static void writeString(OutputStream os, String s) throws IOException { - byte[] b = s.getBytes("UTF-8"); - writeLong(os, b.length); - os.write(b, 0, b.length); - } - - static String readString(InputStream is) throws IOException { - int n = (int) readLong(is); - byte[] b = streamToBytes(is, n); - return new String(b, "UTF-8"); - } - - static void writeStringStringMap(Map map, OutputStream os) throws IOException { - if (map != null) { - writeInt(os, map.size()); - for (Map.Entry entry : map.entrySet()) { - writeString(os, entry.getKey()); - writeString(os, entry.getValue()); - } - } else { - writeInt(os, 0); - } - } - - static Map readStringStringMap(InputStream is) throws IOException { - int size = readInt(is); - Map result = (size == 0) - ? Collections.emptyMap() - : new HashMap(size); - for (int i = 0; i < size; i++) { - String key = readString(is).intern(); - String value = readString(is).intern(); - result.put(key, value); - } - return result; - } - - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpClientStack.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpClientStack.java deleted file mode 100644 index 4827ada7a2f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpClientStack.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.AuthFailureError; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Request.Method; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpTrace; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; - -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - * An HttpStack that performs request over an {@link HttpClient}. - */ -public class HttpClientStack implements HttpStack { - protected final HttpClient mClient; - - private final static String HEADER_CONTENT_TYPE = "Content-Type"; - - public HttpClientStack(HttpClient client) { - mClient = client; - } - - private static void addHeaders(HttpUriRequest httpRequest, Map headers) { - for (String key : headers.keySet()) { - httpRequest.setHeader(key, headers.get(key)); - } - } - - @SuppressWarnings("unused") - private static List getPostParameterPairs(Map postParams) { - List result = new ArrayList(postParams.size()); - for (String key : postParams.keySet()) { - result.add(new BasicNameValuePair(key, postParams.get(key))); - } - return result; - } - - @Override - public HttpResponse performRequest(Request request, Map additionalHeaders) - throws IOException, AuthFailureError { - HttpUriRequest httpRequest = createHttpRequest(request, additionalHeaders); - addHeaders(httpRequest, additionalHeaders); - addHeaders(httpRequest, request.getHeaders()); - onPrepareRequest(httpRequest); - HttpParams httpParams = httpRequest.getParams(); - int timeoutMs = request.getTimeoutMs(); - // TODO: Reevaluate this connection timeout based on more wide-scale - // data collection and possibly different for wifi vs. 3G. - HttpConnectionParams.setConnectionTimeout(httpParams, 5000); - HttpConnectionParams.setSoTimeout(httpParams, timeoutMs); - return mClient.execute(httpRequest); - } - - /** - * Creates the appropriate subclass of HttpUriRequest for passed in request. - */ - @SuppressWarnings("deprecation") - /* protected */ static HttpUriRequest createHttpRequest(Request request, - Map additionalHeaders) throws AuthFailureError { - switch (request.getMethod()) { - case Method.DEPRECATED_GET_OR_POST: { - // This is the deprecated way that needs to be handled for backwards compatibility. - // If the request's post body is null, then the assumption is that the request is - // GET. Otherwise, it is assumed that the request is a POST. - byte[] postBody = request.getPostBody(); - if (postBody != null) { - HttpPost postRequest = new HttpPost(request.getUrl()); - postRequest.addHeader(HEADER_CONTENT_TYPE, request.getPostBodyContentType()); - HttpEntity entity; - entity = new ByteArrayEntity(postBody); - postRequest.setEntity(entity); - return postRequest; - } else { - return new HttpGet(request.getUrl()); - } - } - case Method.GET: - return new HttpGet(request.getUrl()); - case Method.DELETE: - return new HttpDelete(request.getUrl()); - case Method.POST: { - HttpPost postRequest = new HttpPost(request.getUrl()); - postRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); - setEntityIfNonEmptyBody(postRequest, request); - return postRequest; - } - case Method.PUT: { - HttpPut putRequest = new HttpPut(request.getUrl()); - putRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); - setEntityIfNonEmptyBody(putRequest, request); - return putRequest; - } - case Method.HEAD: - return new HttpHead(request.getUrl()); - case Method.OPTIONS: - return new HttpOptions(request.getUrl()); - case Method.TRACE: - return new HttpTrace(request.getUrl()); - case Method.PATCH: { - HttpPatch patchRequest = new HttpPatch(request.getUrl()); - patchRequest.addHeader(HEADER_CONTENT_TYPE, request.getBodyContentType()); - setEntityIfNonEmptyBody(patchRequest, request); - return patchRequest; - } - default: - throw new IllegalStateException("Unknown request method."); - } - } - - private static void setEntityIfNonEmptyBody(HttpEntityEnclosingRequestBase httpRequest, - Request request) throws AuthFailureError { - byte[] body = request.getBody(); - if (body != null) { - HttpEntity entity = new ByteArrayEntity(body); - httpRequest.setEntity(entity); - } - } - - /** - * Called before the request is executed using the underlying HttpClient. - * - *

          Overwrite in subclasses to augment the request.

          - */ - protected void onPrepareRequest(HttpUriRequest request) throws IOException { - // Nothing. - } - - /** - * The HttpPatch class does not exist in the Android framework, so this has been defined here. - */ - public static final class HttpPatch extends HttpEntityEnclosingRequestBase { - - public final static String METHOD_NAME = "PATCH"; - - public HttpPatch() { - super(); - } - - public HttpPatch(final URI uri) { - super(); - setURI(uri); - } - - /** - * @throws IllegalArgumentException if the uri is invalid. - */ - public HttpPatch(final String uri) { - super(); - setURI(URI.create(uri)); - } - - @Override - public String getMethod() { - return METHOD_NAME; - } - - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpHeaderParser.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpHeaderParser.java deleted file mode 100644 index 9aefea9389e..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpHeaderParser.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.Cache; -import org.telegram.messenger.volley.NetworkResponse; - -import org.apache.http.impl.cookie.DateParseException; -import org.apache.http.impl.cookie.DateUtils; -import org.apache.http.protocol.HTTP; - -import java.util.Map; - -/** - * Utility methods for parsing HTTP headers. - */ -public class HttpHeaderParser { - - /** - * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. - * - * @param response The network response to parse headers from - * @return a cache entry for the given response, or null if the response is not cacheable. - */ - public static Cache.Entry parseCacheHeaders(NetworkResponse response) { - long now = System.currentTimeMillis(); - - Map headers = response.headers; - - long serverDate = 0; - long lastModified = 0; - long serverExpires = 0; - long softExpire = 0; - long finalExpire = 0; - long maxAge = 0; - long staleWhileRevalidate = 0; - boolean hasCacheControl = false; - boolean mustRevalidate = false; - - String serverEtag = null; - String headerValue; - - headerValue = headers.get("Date"); - if (headerValue != null) { - serverDate = parseDateAsEpoch(headerValue); - } - - headerValue = headers.get("Cache-Control"); - if (headerValue != null) { - hasCacheControl = true; - String[] tokens = headerValue.split(","); - for (int i = 0; i < tokens.length; i++) { - String token = tokens[i].trim(); - if (token.equals("no-cache") || token.equals("no-store")) { - return null; - } else if (token.startsWith("max-age=")) { - try { - maxAge = Long.parseLong(token.substring(8)); - } catch (Exception e) { - } - } else if (token.startsWith("stale-while-revalidate=")) { - try { - staleWhileRevalidate = Long.parseLong(token.substring(23)); - } catch (Exception e) { - } - } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { - mustRevalidate = true; - } - } - } - - headerValue = headers.get("Expires"); - if (headerValue != null) { - serverExpires = parseDateAsEpoch(headerValue); - } - - headerValue = headers.get("Last-Modified"); - if (headerValue != null) { - lastModified = parseDateAsEpoch(headerValue); - } - - serverEtag = headers.get("ETag"); - - // Cache-Control takes precedence over an Expires header, even if both exist and Expires - // is more restrictive. - if (hasCacheControl) { - softExpire = now + maxAge * 1000; - finalExpire = mustRevalidate - ? softExpire - : softExpire + staleWhileRevalidate * 1000; - } else if (serverDate > 0 && serverExpires >= serverDate) { - // Default semantic for Expire header in HTTP specification is softExpire. - softExpire = now + (serverExpires - serverDate); - finalExpire = softExpire; - } - - Cache.Entry entry = new Cache.Entry(); - entry.data = response.data; - entry.etag = serverEtag; - entry.softTtl = softExpire; - entry.ttl = finalExpire; - entry.serverDate = serverDate; - entry.lastModified = lastModified; - entry.responseHeaders = headers; - - return entry; - } - - /** - * Parse date in RFC1123 format, and return its value as epoch - */ - public static long parseDateAsEpoch(String dateStr) { - try { - // Parse date in RFC1123 format if this header contains one - return DateUtils.parseDate(dateStr).getTime(); - } catch (DateParseException e) { - // Date in invalid format, fallback to 0 - return 0; - } - } - - /** - * Retrieve a charset from headers - * - * @param headers An {@link java.util.Map} of headers - * @param defaultCharset Charset to return if none can be found - * @return Returns the charset specified in the Content-Type of this header, - * or the defaultCharset if none can be found. - */ - public static String parseCharset(Map headers, String defaultCharset) { - String contentType = headers.get(HTTP.CONTENT_TYPE); - if (contentType != null) { - String[] params = contentType.split(";"); - for (int i = 1; i < params.length; i++) { - String[] pair = params[i].trim().split("="); - if (pair.length == 2) { - if (pair[0].equals("charset")) { - return pair[1]; - } - } - } - } - - return defaultCharset; - } - - /** - * Returns the charset specified in the Content-Type of this header, - * or the HTTP default (ISO-8859-1) if none can be found. - */ - public static String parseCharset(Map headers) { - return parseCharset(headers, HTTP.DEFAULT_CONTENT_CHARSET); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpStack.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpStack.java deleted file mode 100644 index 253c1a70b07..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HttpStack.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.AuthFailureError; -import org.telegram.messenger.volley.Request; - -import org.apache.http.HttpResponse; - -import java.io.IOException; -import java.util.Map; - -/** - * An HTTP stack abstraction. - */ -public interface HttpStack { - /** - * Performs an HTTP request with the given parameters. - * - *

          A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, - * and the Content-Type header is set to request.getPostBodyContentType().

          - * - * @param request the request to perform - * @param additionalHeaders additional headers to be sent together with - * {@link Request#getHeaders()} - * @return the HTTP response - */ - public HttpResponse performRequest(Request request, Map additionalHeaders) - throws IOException, AuthFailureError; - -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HurlStack.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HurlStack.java deleted file mode 100644 index 83b1d338979..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/HurlStack.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.AuthFailureError; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Request.Method; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.ProtocolVersion; -import org.apache.http.StatusLine; -import org.apache.http.entity.BasicHttpEntity; -import org.apache.http.message.BasicHeader; -import org.apache.http.message.BasicHttpResponse; -import org.apache.http.message.BasicStatusLine; - -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; - -/** - * An {@link HttpStack} based on {@link HttpURLConnection}. - */ -public class HurlStack implements HttpStack { - - private static final String HEADER_CONTENT_TYPE = "Content-Type"; - - /** - * An interface for transforming URLs before use. - */ - public interface UrlRewriter { - /** - * Returns a URL to use instead of the provided one, or null to indicate - * this URL should not be used at all. - */ - public String rewriteUrl(String originalUrl); - } - - private final UrlRewriter mUrlRewriter; - private final SSLSocketFactory mSslSocketFactory; - - public HurlStack() { - this(null); - } - - /** - * @param urlRewriter Rewriter to use for request URLs - */ - public HurlStack(UrlRewriter urlRewriter) { - this(urlRewriter, null); - } - - /** - * @param urlRewriter Rewriter to use for request URLs - * @param sslSocketFactory SSL factory to use for HTTPS connections - */ - public HurlStack(UrlRewriter urlRewriter, SSLSocketFactory sslSocketFactory) { - mUrlRewriter = urlRewriter; - mSslSocketFactory = sslSocketFactory; - } - - @Override - public HttpResponse performRequest(Request request, Map additionalHeaders) - throws IOException, AuthFailureError { - String url = request.getUrl(); - HashMap map = new HashMap(); - map.putAll(request.getHeaders()); - map.putAll(additionalHeaders); - if (mUrlRewriter != null) { - String rewritten = mUrlRewriter.rewriteUrl(url); - if (rewritten == null) { - throw new IOException("URL blocked by rewriter: " + url); - } - url = rewritten; - } - URL parsedUrl = new URL(url); - HttpURLConnection connection = openConnection(parsedUrl, request); - for (String headerName : map.keySet()) { - connection.addRequestProperty(headerName, map.get(headerName)); - } - setConnectionParametersForRequest(connection, request); - // Initialize HttpResponse with data from the HttpURLConnection. - ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); - int responseCode = connection.getResponseCode(); - if (responseCode == -1) { - // -1 is returned by getResponseCode() if the response code could not be retrieved. - // Signal to the caller that something was wrong with the connection. - throw new IOException("Could not retrieve response code from HttpUrlConnection."); - } - StatusLine responseStatus = new BasicStatusLine(protocolVersion, - connection.getResponseCode(), connection.getResponseMessage()); - BasicHttpResponse response = new BasicHttpResponse(responseStatus); - if (hasResponseBody(request.getMethod(), responseStatus.getStatusCode())) { - response.setEntity(entityFromConnection(connection)); - } - for (Entry> header : connection.getHeaderFields().entrySet()) { - if (header.getKey() != null) { - Header h = new BasicHeader(header.getKey(), header.getValue().get(0)); - response.addHeader(h); - } - } - return response; - } - - /** - * Checks if a response message contains a body. - * @see RFC 7230 section 3.3 - * @param requestMethod request method - * @param responseCode response status code - * @return whether the response has a body - */ - private static boolean hasResponseBody(int requestMethod, int responseCode) { - return requestMethod != Request.Method.HEAD - && !(HttpStatus.SC_CONTINUE <= responseCode && responseCode < HttpStatus.SC_OK) - && responseCode != HttpStatus.SC_NO_CONTENT - && responseCode != HttpStatus.SC_NOT_MODIFIED; - } - - /** - * Initializes an {@link HttpEntity} from the given {@link HttpURLConnection}. - * @param connection - * @return an HttpEntity populated with data from connection. - */ - private static HttpEntity entityFromConnection(HttpURLConnection connection) { - BasicHttpEntity entity = new BasicHttpEntity(); - InputStream inputStream; - try { - inputStream = connection.getInputStream(); - } catch (IOException ioe) { - inputStream = connection.getErrorStream(); - } - entity.setContent(inputStream); - entity.setContentLength(connection.getContentLength()); - entity.setContentEncoding(connection.getContentEncoding()); - entity.setContentType(connection.getContentType()); - return entity; - } - - /** - * Create an {@link HttpURLConnection} for the specified {@code url}. - */ - protected HttpURLConnection createConnection(URL url) throws IOException { - return (HttpURLConnection) url.openConnection(); - } - - /** - * Opens an {@link HttpURLConnection} with parameters. - * @param url - * @return an open connection - * @throws IOException - */ - private HttpURLConnection openConnection(URL url, Request request) throws IOException { - HttpURLConnection connection = createConnection(url); - - int timeoutMs = request.getTimeoutMs(); - connection.setConnectTimeout(timeoutMs); - connection.setReadTimeout(timeoutMs); - connection.setUseCaches(false); - connection.setDoInput(true); - - // use caller-provided custom SslSocketFactory, if any, for HTTPS - if ("https".equals(url.getProtocol()) && mSslSocketFactory != null) { - ((HttpsURLConnection)connection).setSSLSocketFactory(mSslSocketFactory); - } - - return connection; - } - - @SuppressWarnings("deprecation") - /* package */ static void setConnectionParametersForRequest(HttpURLConnection connection, - Request request) throws IOException, AuthFailureError { - switch (request.getMethod()) { - case Method.DEPRECATED_GET_OR_POST: - // This is the deprecated way that needs to be handled for backwards compatibility. - // If the request's post body is null, then the assumption is that the request is - // GET. Otherwise, it is assumed that the request is a POST. - byte[] postBody = request.getPostBody(); - if (postBody != null) { - // Prepare output. There is no need to set Content-Length explicitly, - // since this is handled by HttpURLConnection using the size of the prepared - // output stream. - connection.setDoOutput(true); - connection.setRequestMethod("POST"); - connection.addRequestProperty(HEADER_CONTENT_TYPE, - request.getPostBodyContentType()); - DataOutputStream out = new DataOutputStream(connection.getOutputStream()); - out.write(postBody); - out.close(); - } - break; - case Method.GET: - // Not necessary to set the request method because connection defaults to GET but - // being explicit here. - connection.setRequestMethod("GET"); - break; - case Method.DELETE: - connection.setRequestMethod("DELETE"); - break; - case Method.POST: - connection.setRequestMethod("POST"); - addBodyIfExists(connection, request); - break; - case Method.PUT: - connection.setRequestMethod("PUT"); - addBodyIfExists(connection, request); - break; - case Method.HEAD: - connection.setRequestMethod("HEAD"); - break; - case Method.OPTIONS: - connection.setRequestMethod("OPTIONS"); - break; - case Method.TRACE: - connection.setRequestMethod("TRACE"); - break; - case Method.PATCH: - connection.setRequestMethod("PATCH"); - addBodyIfExists(connection, request); - break; - default: - throw new IllegalStateException("Unknown method type."); - } - } - - private static void addBodyIfExists(HttpURLConnection connection, Request request) - throws IOException, AuthFailureError { - byte[] body = request.getBody(); - if (body != null) { - connection.setDoOutput(true); - connection.addRequestProperty(HEADER_CONTENT_TYPE, request.getBodyContentType()); - DataOutputStream out = new DataOutputStream(connection.getOutputStream()); - out.write(body); - out.close(); - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonArrayRequest.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonArrayRequest.java deleted file mode 100644 index 7206d7f2f5c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonArrayRequest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.ParseError; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.Response.ErrorListener; -import org.telegram.messenger.volley.Response.Listener; - -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.UnsupportedEncodingException; - -/** - * A request for retrieving a {@link JSONArray} response body at a given URL. - */ -public class JsonArrayRequest extends JsonRequest { - - /** - * Creates a new request. - * @param url URL to fetch the JSON from - * @param listener Listener to receive the JSON response - * @param errorListener Error listener, or null to ignore errors. - */ - public JsonArrayRequest(String url, Listener listener, ErrorListener errorListener) { - super(Method.GET, url, null, listener, errorListener); - } - - /** - * Creates a new request. - * @param method the HTTP method to use - * @param url URL to fetch the JSON from - * @param jsonRequest A {@link JSONArray} to post with the request. Null is allowed and - * indicates no parameters will be posted along with request. - * @param listener Listener to receive the JSON response - * @param errorListener Error listener, or null to ignore errors. - */ - public JsonArrayRequest(int method, String url, JSONArray jsonRequest, - Listener listener, ErrorListener errorListener) { - super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, - errorListener); - } - - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - try { - String jsonString = new String(response.data, - HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET)); - return Response.success(new JSONArray(jsonString), - HttpHeaderParser.parseCacheHeaders(response)); - } catch (UnsupportedEncodingException e) { - return Response.error(new ParseError(e)); - } catch (JSONException je) { - return Response.error(new ParseError(je)); - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonObjectRequest.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonObjectRequest.java deleted file mode 100644 index e6ba9808d27..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonObjectRequest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.ParseError; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.Response.ErrorListener; -import org.telegram.messenger.volley.Response.Listener; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.UnsupportedEncodingException; - -/** - * A request for retrieving a {@link JSONObject} response body at a given URL, allowing for an - * optional {@link JSONObject} to be passed in as part of the request body. - */ -public class JsonObjectRequest extends JsonRequest { - - /** - * Creates a new request. - * @param method the HTTP method to use - * @param url URL to fetch the JSON from - * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and - * indicates no parameters will be posted along with request. - * @param listener Listener to receive the JSON response - * @param errorListener Error listener, or null to ignore errors. - */ - public JsonObjectRequest(int method, String url, JSONObject jsonRequest, - Listener listener, ErrorListener errorListener) { - super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener, - errorListener); - } - - /** - * Constructor which defaults to GET if jsonRequest is - * null, POST otherwise. - * - * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener) - */ - public JsonObjectRequest(String url, JSONObject jsonRequest, Listener listener, - ErrorListener errorListener) { - this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest, - listener, errorListener); - } - - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - try { - String jsonString = new String(response.data, - HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET)); - return Response.success(new JSONObject(jsonString), - HttpHeaderParser.parseCacheHeaders(response)); - } catch (UnsupportedEncodingException e) { - return Response.error(new ParseError(e)); - } catch (JSONException je) { - return Response.error(new ParseError(je)); - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonRequest.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonRequest.java deleted file mode 100644 index 8233d35fe3c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/JsonRequest.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.Response.ErrorListener; -import org.telegram.messenger.volley.Response.Listener; -import org.telegram.messenger.volley.VolleyLog; - -import java.io.UnsupportedEncodingException; - -/** - * A request for retrieving a T type response body at a given URL that also - * optionally sends along a JSON body in the request specified. - * - * @param JSON type of response expected - */ -public abstract class JsonRequest extends Request { - /** Default charset for JSON request. */ - protected static final String PROTOCOL_CHARSET = "utf-8"; - - /** Content type for request. */ - private static final String PROTOCOL_CONTENT_TYPE = - String.format("application/json; charset=%s", PROTOCOL_CHARSET); - - private final Listener mListener; - private final String mRequestBody; - - /** - * Deprecated constructor for a JsonRequest which defaults to GET unless {@link #getPostBody()} - * or {@link #getPostParams()} is overridden (which defaults to POST). - * - * @deprecated Use {@link #JsonRequest(int, String, String, Listener, ErrorListener)}. - */ - public JsonRequest(String url, String requestBody, Listener listener, - ErrorListener errorListener) { - this(Method.DEPRECATED_GET_OR_POST, url, requestBody, listener, errorListener); - } - - public JsonRequest(int method, String url, String requestBody, Listener listener, - ErrorListener errorListener) { - super(method, url, errorListener); - mListener = listener; - mRequestBody = requestBody; - } - - @Override - protected void deliverResponse(T response) { - mListener.onResponse(response); - } - - @Override - abstract protected Response parseNetworkResponse(NetworkResponse response); - - /** - * @deprecated Use {@link #getBodyContentType()}. - */ - @Override - public String getPostBodyContentType() { - return getBodyContentType(); - } - - /** - * @deprecated Use {@link #getBody()}. - */ - @Override - public byte[] getPostBody() { - return getBody(); - } - - @Override - public String getBodyContentType() { - return PROTOCOL_CONTENT_TYPE; - } - - @Override - public byte[] getBody() { - try { - return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET); - } catch (UnsupportedEncodingException uee) { - VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", - mRequestBody, PROTOCOL_CHARSET); - return null; - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/NoCache.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/NoCache.java deleted file mode 100644 index 8db5f2e7a50..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/NoCache.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.Cache; - -/** - * A cache that doesn't. - */ -public class NoCache implements Cache { - @Override - public void clear() { - } - - @Override - public Entry get(String key) { - return null; - } - - @Override - public void put(String key, Entry entry) { - } - - @Override - public void invalidate(String key, boolean fullExpire) { - } - - @Override - public void remove(String key) { - } - - @Override - public void initialize() { - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/PoolingByteArrayOutputStream.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/PoolingByteArrayOutputStream.java deleted file mode 100644 index b2f66709474..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/PoolingByteArrayOutputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * A variation of {@link java.io.ByteArrayOutputStream} that uses a pool of byte[] buffers instead - * of always allocating them fresh, saving on heap churn. - */ -public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { - /** - * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is - * the default size to which the underlying byte array is initialized. - */ - private static final int DEFAULT_SIZE = 256; - - private final ByteArrayPool mPool; - - /** - * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written - * to this instance, the underlying byte array will expand. - */ - public PoolingByteArrayOutputStream(ByteArrayPool pool) { - this(pool, DEFAULT_SIZE); - } - - /** - * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If - * more than {@code size} bytes are written to this instance, the underlying byte array will - * expand. - * - * @param size initial size for the underlying byte array. The value will be pinned to a default - * minimum size. - */ - public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { - mPool = pool; - buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); - } - - @Override - public void close() throws IOException { - mPool.returnBuf(buf); - buf = null; - super.close(); - } - - @Override - public void finalize() { - mPool.returnBuf(buf); - } - - /** - * Ensures there is enough space in the buffer for the given number of additional bytes. - */ - private void expand(int i) { - /* Can the buffer handle @i more bytes, if not expand it */ - if (count + i <= buf.length) { - return; - } - byte[] newbuf = mPool.getBuf((count + i) * 2); - System.arraycopy(buf, 0, newbuf, 0, count); - mPool.returnBuf(buf); - buf = newbuf; - } - - @Override - public synchronized void write(byte[] buffer, int offset, int len) { - expand(len); - super.write(buffer, offset, len); - } - - @Override - public synchronized void write(int oneByte) { - expand(1); - super.write(oneByte); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/RequestFuture.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/RequestFuture.java deleted file mode 100644 index 2badb8600ec..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/RequestFuture.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.VolleyError; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -/** - * A Future that represents a Volley request. - * - * Used by providing as your response and error listeners. For example: - *
          - * RequestFuture<JSONObject> future = RequestFuture.newFuture();
          - * MyRequest request = new MyRequest(URL, future, future);
          - *
          - * // If you want to be able to cancel the request:
          - * future.setRequest(requestQueue.add(request));
          - *
          - * // Otherwise:
          - * requestQueue.add(request);
          - *
          - * try {
          - *   JSONObject response = future.get();
          - *   // do something with response
          - * } catch (InterruptedException e) {
          - *   // handle the error
          - * } catch (ExecutionException e) {
          - *   // handle the error
          - * }
          - * 
          - * - * @param The type of parsed response this future expects. - */ -public class RequestFuture implements Future, Response.Listener, - Response.ErrorListener { - private Request mRequest; - private boolean mResultReceived = false; - private T mResult; - private VolleyError mException; - - public static RequestFuture newFuture() { - return new RequestFuture(); - } - - private RequestFuture() {} - - public void setRequest(Request request) { - mRequest = request; - } - - @Override - public synchronized boolean cancel(boolean mayInterruptIfRunning) { - if (mRequest == null) { - return false; - } - - if (!isDone()) { - mRequest.cancel(); - return true; - } else { - return false; - } - } - - @Override - public T get() throws InterruptedException, ExecutionException { - try { - return doGet(null); - } catch (TimeoutException e) { - throw new AssertionError(e); - } - } - - @Override - public T get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - return doGet(TimeUnit.MILLISECONDS.convert(timeout, unit)); - } - - private synchronized T doGet(Long timeoutMs) - throws InterruptedException, ExecutionException, TimeoutException { - if (mException != null) { - throw new ExecutionException(mException); - } - - if (mResultReceived) { - return mResult; - } - - if (timeoutMs == null) { - wait(0); - } else if (timeoutMs > 0) { - wait(timeoutMs); - } - - if (mException != null) { - throw new ExecutionException(mException); - } - - if (!mResultReceived) { - throw new TimeoutException(); - } - - return mResult; - } - - @Override - public boolean isCancelled() { - if (mRequest == null) { - return false; - } - return mRequest.isCanceled(); - } - - @Override - public synchronized boolean isDone() { - return mResultReceived || mException != null || isCancelled(); - } - - @Override - public synchronized void onResponse(T response) { - mResultReceived = true; - mResult = response; - notifyAll(); - } - - @Override - public synchronized void onErrorResponse(VolleyError error) { - mException = error; - notifyAll(); - } -} - diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/StringRequest.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/StringRequest.java deleted file mode 100644 index 3a7f9150cc4..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/StringRequest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import org.telegram.messenger.volley.NetworkResponse; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.Response.ErrorListener; -import org.telegram.messenger.volley.Response.Listener; - -import java.io.UnsupportedEncodingException; - -/** - * A canned request for retrieving the response body at a given URL as a String. - */ -public class StringRequest extends Request { - private final Listener mListener; - - /** - * Creates a new request with the given method. - * - * @param method the request {@link Method} to use - * @param url URL to fetch the string at - * @param listener Listener to receive the String response - * @param errorListener Error listener, or null to ignore errors - */ - public StringRequest(int method, String url, Listener listener, - ErrorListener errorListener) { - super(method, url, errorListener); - mListener = listener; - } - - /** - * Creates a new GET request. - * - * @param url URL to fetch the string at - * @param listener Listener to receive the String response - * @param errorListener Error listener, or null to ignore errors - */ - public StringRequest(String url, Listener listener, ErrorListener errorListener) { - this(Method.GET, url, listener, errorListener); - } - - @Override - protected void deliverResponse(String response) { - mListener.onResponse(response); - } - - @Override - protected Response parseNetworkResponse(NetworkResponse response) { - String parsed; - try { - parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); - } catch (UnsupportedEncodingException e) { - parsed = new String(response.data); - } - return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Volley.java b/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Volley.java deleted file mode 100644 index 9038d507348..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/messenger/volley/toolbox/Volley.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.messenger.volley.toolbox; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.http.AndroidHttpClient; -import android.os.Build; - -import org.telegram.messenger.volley.Network; -import org.telegram.messenger.volley.RequestQueue; - -import java.io.File; - -public class Volley { - - /** Default on-disk cache directory. */ - private static final String DEFAULT_CACHE_DIR = "volley"; - - /** - * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. - * - * @param context A {@link Context} to use for creating the cache dir. - * @param stack An {@link HttpStack} to use for the network, or null for default. - * @return A started {@link RequestQueue} instance. - */ - public static RequestQueue newRequestQueue(Context context, HttpStack stack) { - File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR); - - String userAgent = "volley/0"; - try { - String packageName = context.getPackageName(); - PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - userAgent = packageName + "/" + info.versionCode; - } catch (NameNotFoundException e) { - } - - if (stack == null) { - if (Build.VERSION.SDK_INT >= 9) { - stack = new HurlStack(); - } else { - // Prior to Gingerbread, HttpUrlConnection was unreliable. - // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html - stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent)); - } - } - - Network network = new BasicNetwork(stack); - - RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network); - queue.start(); - - return queue; - } - - /** - * Creates a default instance of the worker pool and calls {@link RequestQueue#start()} on it. - * - * @param context A {@link Context} to use for creating the cache dir. - * @return A started {@link RequestQueue} instance. - */ - public static RequestQueue newRequestQueue(Context context) { - return newRequestQueue(context, null); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index c0e03260972..e703e6d2122 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -17,6 +17,7 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.StatsController; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; @@ -36,6 +37,12 @@ public class ConnectionsManager { public final static int ConnectionTypeUpload = 4; public final static int ConnectionTypePush = 8; public final static int ConnectionTypeDownload2 = ConnectionTypeDownload | (1 << 16); + public final static int ConnectionTypeUpload2 = ConnectionTypeUpload | (1 << 16); + + public final static int FileTypePhoto = 0x01000000; + public final static int FileTypeVideo = 0x02000000; + public final static int FileTypeAudio = 0x03000000; + public final static int FileTypeFile = 0x04000000; public final static int RequestFlagEnableUnauthorized = 1; public final static int RequestFlagFailOnServerErrors = 2; @@ -56,10 +63,11 @@ public class ConnectionsManager { private long lastPauseTime = System.currentTimeMillis(); private boolean appPaused = true; private int lastClassGuid = 1; - private boolean isUpdating = false; + private boolean isUpdating; private int connectionState = native_getConnectionState(); private AtomicInteger lastRequestToken = new AtomicInteger(1); - private PowerManager.WakeLock wakeLock = null; + private PowerManager.WakeLock wakeLock; + private int appResumeCount; private static volatile ConnectionsManager Instance = null; @@ -82,7 +90,7 @@ public ConnectionsManager() { wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lock"); wakeLock.setReferenceCounted(false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -119,7 +127,7 @@ public int sendRequest(final TLObject object, final RequestDelegate onComplete, Utilities.stageQueue.postRunnable(new Runnable() { @Override public void run() { - FileLog.d("tmessages", "send request " + object + " with token = " + requestToken); + FileLog.d("send request " + object + " with token = " + requestToken); try { NativeByteBuffer buffer = new NativeByteBuffer(object.getObjectSize()); object.serializeToStream(buffer); @@ -127,7 +135,7 @@ public void run() { native_sendRequest(buffer.address, new RequestDelegateInternal() { @Override - public void run(int response, int errorCode, String errorText) { + public void run(int response, int errorCode, String errorText, int networkType) { try { TLObject resp = null; TLRPC.TL_error error = null; @@ -139,9 +147,12 @@ public void run(int response, int errorCode, String errorText) { error = new TLRPC.TL_error(); error.code = errorCode; error.text = errorText; - FileLog.e("tmessages", object + " got error " + error.code + " " + error.text); + FileLog.e(object + " got error " + error.code + " " + error.text); + } + if (resp != null) { + resp.networkType = networkType; } - FileLog.d("tmessages", "java received " + resp + " error = " + error); + FileLog.d("java received " + resp + " error = " + error); final TLObject finalResponse = resp; final TLRPC.TL_error finalError = error; Utilities.stageQueue.postRunnable(new Runnable() { @@ -154,12 +165,12 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }, onQuickAck, flags, datacenterId, connetionType, immediate, requestToken); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -199,7 +210,7 @@ public void setUserId(int id) { private void checkConnection() { native_setUseIpv6(useIpv6Address()); - native_setNetworkAvailable(isNetworkOnline()); + native_setNetworkAvailable(isNetworkOnline(), getCurrentNetworkType()); } public void setPushConnectionEnabled(boolean value) { @@ -207,7 +218,7 @@ public void setPushConnectionEnabled(boolean value) { } public void init(int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String configPath, String logPath, int userId, boolean enablePushConnection) { - native_init(version, layer, apiId, deviceModel, systemVersion, appVersion, langCode, configPath, logPath, userId, enablePushConnection); + native_init(version, layer, apiId, deviceModel, systemVersion, appVersion, langCode, configPath, logPath, userId, enablePushConnection, isNetworkOnline(), getCurrentNetworkType()); checkConnection(); BroadcastReceiver networkStateReceiver = new BroadcastReceiver() { @Override @@ -238,9 +249,18 @@ public long getPauseTime() { public void setAppPaused(final boolean value, final boolean byScreenState) { if (!byScreenState) { appPaused = value; - FileLog.d("tmessages", "app paused = " + value); + FileLog.d("app paused = " + value); + if (value) { + appResumeCount--; + } else { + appResumeCount++; + } + FileLog.d("app resume count " + appResumeCount); + if (appResumeCount < 0) { + appResumeCount = 0; + } } - if (value) { + if (appResumeCount == 0) { if (lastPauseTime == 0) { lastPauseTime = System.currentTimeMillis(); } @@ -249,7 +269,7 @@ public void setAppPaused(final boolean value, final boolean byScreenState) { if (appPaused) { return; } - FileLog.e("tmessages", "reset app pause time"); + FileLog.e("reset app pause time"); if (lastPauseTime != 0 && System.currentTimeMillis() - lastPauseTime > 5000) { ContactsController.getInstance().checkContacts(); } @@ -264,12 +284,12 @@ public static void onUnparsedMessageReceived(int address) { buff.reused = true; final TLObject message = TLClassStore.Instance().TLdeserialize(buff, buff.readInt32(true), true); if (message instanceof TLRPC.Updates) { - FileLog.d("tmessages", "java received " + message); + FileLog.d("java received " + message); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (getInstance().wakeLock.isHeld()) { - FileLog.d("tmessages", "release wakelock"); + FileLog.d("release wakelock"); getInstance().wakeLock.release(); } } @@ -282,7 +302,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -326,6 +346,32 @@ public void run() { }); } + public static int getCurrentNetworkType() { + if (isConnectedOrConnectingToWiFi()) { + return StatsController.TYPE_WIFI; + } else if (isRoaming()) { + return StatsController.TYPE_ROAMING; + } else { + return StatsController.TYPE_MOBILE; + } + } + + public static void onBytesSent(int amount, int networkType) { + try { + StatsController.getInstance().incrementSentBytesCount(networkType, StatsController.TYPE_TOTAL, amount); + } catch (Exception e) { + FileLog.e(e); + } + } + + public static void onBytesReceived(int amount, int networkType) { + try { + StatsController.getInstance().incrementReceivedBytesCount(networkType, StatsController.TYPE_TOTAL, amount); + } catch (Exception e) { + FileLog.e(e); + } + } + public static void onUpdateConfig(int address) { try { NativeByteBuffer buff = NativeByteBuffer.wrap(address); @@ -340,7 +386,7 @@ public void run() { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -351,10 +397,10 @@ public void run() { try { if (!getInstance().wakeLock.isHeld()) { getInstance().wakeLock.acquire(10000); - FileLog.d("tmessages", "acquire wakelock"); + FileLog.d("acquire wakelock"); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -364,7 +410,7 @@ public void run() { public static native void native_pauseNetwork(); public static native void native_setUseIpv6(boolean value); public static native void native_updateDcSettings(); - public static native void native_setNetworkAvailable(boolean value); + public static native void native_setNetworkAvailable(boolean value, int networkType); public static native void native_resumeNetwork(boolean partial); public static native long native_getCurrentTimeMillis(); public static native int native_getCurrentTime(); @@ -377,7 +423,7 @@ public void run() { public static native void native_applyDatacenterAddress(int datacenterId, String ipAddress, int port); public static native int native_getConnectionState(); public static native void native_setUserId(int id); - public static native void native_init(int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String configPath, String logPath, int userId, boolean enablePushConnection); + public static native void native_init(int version, int layer, int apiId, String deviceModel, String systemVersion, String appVersion, String langCode, String configPath, String logPath, int userId, boolean enablePushConnection, boolean hasNetwork, int networkType); public static native void native_setJava(boolean useJavaByteBuffers); public static native void native_setPushConnectionEnabled(boolean value); @@ -387,26 +433,40 @@ public int generateClassGuid() { public static boolean isRoaming() { try { - ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = cm.getActiveNetworkInfo(); + ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); if (netInfo != null) { return netInfo.isRoaming(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } + return false; + } + + public static boolean isConnectedOrConnectingToWiFi() { + try { + ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + NetworkInfo.State state = netInfo.getState(); + if (netInfo != null && (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING || state == NetworkInfo.State.SUSPENDED)) { + return true; + } + } catch (Exception e) { + FileLog.e(e); } return false; } public static boolean isConnectedToWiFi() { try { - ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (netInfo != null && netInfo.getState() == NetworkInfo.State.CONNECTED) { return true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -444,24 +504,24 @@ protected static boolean useIpv6Address() { if (!networkInterface.isUp() || networkInterface.isLoopback() || networkInterface.getInterfaceAddresses().isEmpty()) { continue; } - FileLog.e("tmessages", "valid interface: " + networkInterface); + FileLog.e("valid interface: " + networkInterface); List interfaceAddresses = networkInterface.getInterfaceAddresses(); for (int a = 0; a < interfaceAddresses.size(); a++) { InterfaceAddress address = interfaceAddresses.get(a); InetAddress inetAddress = address.getAddress(); if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "address: " + inetAddress.getHostAddress()); + FileLog.e("address: " + inetAddress.getHostAddress()); } if (inetAddress.isLinkLocalAddress() || inetAddress.isLoopbackAddress() || inetAddress.isMulticastAddress()) { continue; } if (BuildVars.DEBUG_VERSION) { - FileLog.e("tmessages", "address is good"); + FileLog.e("address is good"); } } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { @@ -495,7 +555,7 @@ protected static boolean useIpv6Address() { return true; } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; @@ -503,24 +563,24 @@ protected static boolean useIpv6Address() { public static boolean isNetworkOnline() { try { - ConnectivityManager cm = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo netInfo = cm.getActiveNetworkInfo(); + ConnectivityManager connectivityManager = (ConnectivityManager) ApplicationLoader.applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); if (netInfo != null && (netInfo.isConnectedOrConnecting() || netInfo.isAvailable())) { return true; } - netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); + netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE); if (netInfo != null && netInfo.isConnectedOrConnecting()) { return true; } else { - netInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + netInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); if (netInfo != null && netInfo.isConnectedOrConnecting()) { return true; } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return true; } return false; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperation.java b/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperation.java index 6b267997397..a7c32aca15f 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperation.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperation.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperationDelegate.java b/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperationDelegate.java index d5b6b6e3105..628df9f3925 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperationDelegate.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/FileLoadOperationDelegate.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java index b7dc3944960..315f3f35584 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/NativeByteBuffer.java @@ -24,7 +24,7 @@ public static NativeByteBuffer wrap(int address) { NativeByteBuffer result = addressWrapper.get(); if (address != 0) { if (!result.reused) { - FileLog.e("tmessages", "forgot to reuse?"); + FileLog.e("forgot to reuse?"); } result.address = address; result.reused = false; @@ -109,7 +109,7 @@ public void writeInt32(int x) { len += 4; } } catch(Exception e) { - FileLog.e("tmessages", "write int32 error"); + FileLog.e("write int32 error"); } } @@ -121,7 +121,7 @@ public void writeInt64(long x) { len += 8; } } catch(Exception e) { - FileLog.e("tmessages", "write int64 error"); + FileLog.e("write int64 error"); } } @@ -145,7 +145,7 @@ public void writeBytes(byte[] b) { len += b.length; } } catch (Exception e) { - FileLog.e("tmessages", "write raw error"); + FileLog.e("write raw error"); } } @@ -157,7 +157,7 @@ public void writeBytes(byte[] b, int offset, int count) { len += count; } } catch (Exception e) { - FileLog.e("tmessages", "write raw error"); + FileLog.e("write raw error"); } } @@ -173,7 +173,7 @@ public void writeByte(byte b) { len += 1; } } catch (Exception e) { - FileLog.e("tmessages", "write byte error"); + FileLog.e("write byte error"); } } @@ -181,7 +181,7 @@ public void writeString(String s) { try { writeByteArray(s.getBytes("UTF-8")); } catch(Exception e) { - FileLog.e("tmessages", "write string error"); + FileLog.e("write string error"); } } @@ -218,7 +218,7 @@ public void writeByteArray(byte[] b, int offset, int count) { i++; } } catch (Exception e) { - FileLog.e("tmessages", "write byte array error"); + FileLog.e("write byte array error"); } } @@ -255,7 +255,7 @@ public void writeByteArray(byte[] b) { i++; } } catch (Exception e) { - FileLog.e("tmessages", "write byte array error"); + FileLog.e("write byte array error"); } } @@ -263,7 +263,7 @@ public void writeDouble(double d) { try { writeInt64(Double.doubleToRawLongBits(d)); } catch(Exception e) { - FileLog.e("tmessages", "write double error"); + FileLog.e("write double error"); } } @@ -302,7 +302,7 @@ public void writeByteBuffer(NativeByteBuffer b) { i++; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -348,7 +348,7 @@ public int readInt32(boolean exception) { if (exception) { throw new RuntimeException("read int32 error", e); } else { - FileLog.e("tmessages", "read int32 error"); + FileLog.e("read int32 error"); } } return 0; @@ -364,7 +364,7 @@ public boolean readBool(boolean exception) { if (exception) { throw new RuntimeException("Not bool value!"); } else { - FileLog.e("tmessages", "Not bool value!"); + FileLog.e("Not bool value!"); } return false; } @@ -376,7 +376,7 @@ public long readInt64(boolean exception) { if (exception) { throw new RuntimeException("read int64 error", e); } else { - FileLog.e("tmessages", "read int64 error"); + FileLog.e("read int64 error"); } } return 0; @@ -389,7 +389,7 @@ public void readBytes(byte[] b, boolean exception) { if (exception) { throw new RuntimeException("read raw error", e); } else { - FileLog.e("tmessages", "read raw error"); + FileLog.e("read raw error"); } } } @@ -420,7 +420,7 @@ public String readString(boolean exception) { if (exception) { throw new RuntimeException("read string error", e); } else { - FileLog.e("tmessages", "read string error"); + FileLog.e("read string error"); } } return ""; @@ -446,7 +446,7 @@ public byte[] readByteArray(boolean exception) { if (exception) { throw new RuntimeException("read byte array error", e); } else { - FileLog.e("tmessages", "read byte array error"); + FileLog.e("read byte array error"); } } return new byte[0]; @@ -476,7 +476,7 @@ public NativeByteBuffer readByteBuffer(boolean exception) { if (exception) { throw new RuntimeException("read byte array error", e); } else { - FileLog.e("tmessages", "read byte array error"); + FileLog.e("read byte array error"); } } return null; @@ -489,7 +489,7 @@ public double readDouble(boolean exception) { if (exception) { throw new RuntimeException("read double error", e); } else { - FileLog.e("tmessages", "read double error"); + FileLog.e("read double error"); } } return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/RequestDelegateInternal.java b/TMessagesProj/src/main/java/org/telegram/tgnet/RequestDelegateInternal.java index 1f2133f0326..72b61077349 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/RequestDelegateInternal.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/RequestDelegateInternal.java @@ -1,5 +1,5 @@ package org.telegram.tgnet; public interface RequestDelegateInternal { - void run(int response, int errorCode, String errorText); + void run(int response, int errorCode, String errorText, int networkType); } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/SerializedData.java b/TMessagesProj/src/main/java/org/telegram/tgnet/SerializedData.java index 13f1ff2d46f..a8672bbe5f7 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/SerializedData.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/SerializedData.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; @@ -59,7 +59,7 @@ public void cleanup() { inbuf = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (in != null) { @@ -67,7 +67,7 @@ public void cleanup() { in = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (outbuf != null) { @@ -75,7 +75,7 @@ public void cleanup() { outbuf = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (out != null) { @@ -83,7 +83,7 @@ public void cleanup() { out = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -112,7 +112,7 @@ private void writeInt32(int x, DataOutputStream out) { out.write(x >> (i * 8)); } } catch(Exception e) { - FileLog.e("tmessages", "write int32 error"); + FileLog.e("write int32 error"); } } @@ -130,7 +130,7 @@ private void writeInt64(long x, DataOutputStream out) { out.write((int)(x >> (i * 8))); } } catch(Exception e) { - FileLog.e("tmessages", "write int64 error"); + FileLog.e("write int64 error"); } } @@ -154,7 +154,7 @@ public void writeBytes(byte[] b) { len += b.length; } } catch (Exception e) { - FileLog.e("tmessages", "write raw error"); + FileLog.e("write raw error"); } } @@ -166,7 +166,7 @@ public void writeBytes(byte[] b, int offset, int count) { len += count; } } catch (Exception e) { - FileLog.e("tmessages", "write bytes error"); + FileLog.e("write bytes error"); } } @@ -178,7 +178,7 @@ public void writeByte(int i) { len += 1; } } catch (Exception e) { - FileLog.e("tmessages", "write byte error"); + FileLog.e("write byte error"); } } @@ -190,7 +190,7 @@ public void writeByte(byte b) { len += 1; } } catch (Exception e) { - FileLog.e("tmessages", "write byte error"); + FileLog.e("write byte error"); } } @@ -227,7 +227,7 @@ public void writeByteArray(byte[] b) { i++; } } catch (Exception e) { - FileLog.e("tmessages", "write byte array error"); + FileLog.e("write byte array error"); } } @@ -235,7 +235,7 @@ public void writeString(String s) { try { writeByteArray(s.getBytes("UTF-8")); } catch(Exception e) { - FileLog.e("tmessages", "write string error"); + FileLog.e("write string error"); } } @@ -272,7 +272,7 @@ public void writeByteArray(byte[] b, int offset, int count) { i++; } } catch (Exception e) { - FileLog.e("tmessages", "write byte array error"); + FileLog.e("write byte array error"); } } @@ -280,7 +280,7 @@ public void writeDouble(double d) { try { writeInt64(Double.doubleToRawLongBits(d)); } catch(Exception e) { - FileLog.e("tmessages", "write double error"); + FileLog.e("write double error"); } } @@ -310,7 +310,7 @@ public void skip(int count) { try { in.skipBytes(count); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else { @@ -332,7 +332,7 @@ public boolean readBool(boolean exception) { if (exception) { throw new RuntimeException("Not bool value!"); } else { - FileLog.e("tmessages", "Not bool value!"); + FileLog.e("Not bool value!"); } return false; } @@ -345,7 +345,7 @@ public void readBytes(byte[] b, boolean exception) { if (exception) { throw new RuntimeException("read bytes error", e); } else { - FileLog.e("tmessages", "read bytes error"); + FileLog.e("read bytes error"); } } } @@ -380,7 +380,7 @@ public String readString(boolean exception) { if (exception) { throw new RuntimeException("read string error", e); } else { - FileLog.e("tmessages", "read string error"); + FileLog.e("read string error"); } } return null; @@ -410,7 +410,7 @@ public byte[] readByteArray(boolean exception) { if (exception) { throw new RuntimeException("read byte array error", e); } else { - FileLog.e("tmessages", "read byte array error"); + FileLog.e("read byte array error"); } } return null; @@ -423,7 +423,7 @@ public double readDouble(boolean exception) { if (exception) { throw new RuntimeException("read double error", e); } else { - FileLog.e("tmessages", "read double error"); + FileLog.e("read double error"); } } return 0; @@ -441,7 +441,7 @@ public int readInt32(boolean exception) { if (exception) { throw new RuntimeException("read int32 error", e); } else { - FileLog.e("tmessages", "read int32 error"); + FileLog.e("read int32 error"); } } return 0; @@ -459,7 +459,7 @@ public long readInt64(boolean exception) { if (exception) { throw new RuntimeException("read int64 error", e); } else { - FileLog.e("tmessages", "read int64 error"); + FileLog.e("read int64 error"); } } return 0; diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java index 11e79f95e6a..81b56fb3e55 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLClassStore.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; @@ -56,7 +56,7 @@ public TLObject TLdeserialize(NativeByteBuffer stream, int constructor, boolean try { response = (TLObject) objClass.newInstance(); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); return null; } response.readParams(stream, exception); diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLObject.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLObject.java index 1f781b6d3d2..d4e933dac76 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLObject.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLObject.java @@ -3,13 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; public class TLObject { + public int networkType; + public boolean disableFree = false; private static final ThreadLocal sizeCalculator = new ThreadLocal() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index 20224acf0e9..50ce2dc3bc1 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.tgnet; @@ -57,7 +57,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_EDITED = 0x00008000; public static final int MESSAGE_FLAG_MEGAGROUP = 0x80000000; - public static final int LAYER = 57; + public static final int LAYER = 65; public static class DraftMessage extends TLObject { public int flags; @@ -224,6 +224,91 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_payments_paymentReceipt extends TLObject { + public static int constructor = 0x500911e1; + + public int flags; + public int date; + public int bot_id; + public TL_invoice invoice; + public int provider_id; + public TL_paymentRequestedInfo info; + public TL_shippingOption shipping; + public String currency; + public long total_amount; + public String credentials_title; + public ArrayList users = new ArrayList<>(); + + public static TL_payments_paymentReceipt TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_payments_paymentReceipt.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments_paymentReceipt", constructor)); + } else { + return null; + } + } + TL_payments_paymentReceipt result = new TL_payments_paymentReceipt(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + date = stream.readInt32(exception); + bot_id = stream.readInt32(exception); + invoice = TL_invoice.TLdeserialize(stream, stream.readInt32(exception), exception); + provider_id = stream.readInt32(exception); + if ((flags & 1) != 0) { + info = TL_paymentRequestedInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + shipping = TL_shippingOption.TLdeserialize(stream, stream.readInt32(exception), exception); + } + currency = stream.readString(exception); + total_amount = stream.readInt64(exception); + credentials_title = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt32(date); + stream.writeInt32(bot_id); + invoice.serializeToStream(stream); + stream.writeInt32(provider_id); + if ((flags & 1) != 0) { + info.serializeToStream(stream); + } + if ((flags & 2) != 0) { + shipping.serializeToStream(stream); + } + stream.writeString(currency); + stream.writeInt64(total_amount); + stream.writeString(credentials_title); + stream.writeInt32(0x1cb5c415); + int count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + public static class NotifyPeer extends TLObject { public Peer peer; @@ -774,14 +859,183 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_textEmpty extends RichText { + public static int constructor = 0xdc3d824f; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_textUrl extends RichText { + public static int constructor = 0x3c2884c1; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + url = stream.readString(exception); + webpage_id = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + stream.writeString(url); + stream.writeInt64(webpage_id); + } + } + + public static class TL_textStrike extends RichText { + public static int constructor = 0x9bf8bb95; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_textFixed extends RichText { + public static int constructor = 0x6c3f19b9; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_textEmail extends RichText { + public static int constructor = 0xde5a0dd6; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + email = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + stream.writeString(email); + } + } + + public static class TL_textPlain extends RichText { + public static int constructor = 0x744694e0; + + public String text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(text); + } + } + + public static class TL_textConcat extends RichText { + public static int constructor = 0x7e6260d7; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + RichText object = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + texts.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = texts.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + texts.get(a).serializeToStream(stream); + } + } + } + + public static class TL_textBold extends RichText { + public static int constructor = 0x6724abc4; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_textItalic extends RichText { + public static int constructor = 0xd912a59c; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_textUnderline extends RichText { + public static int constructor = 0xc12622c4; + + public RichText text; + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + public static class TL_messages_botCallbackAnswer extends TLObject { - public static int constructor = 0xb10df1fb; + public static int constructor = 0x36585ea4; public int flags; public boolean alert; public boolean has_url; public String message; public String url; + public int cache_time; public static TL_messages_botCallbackAnswer TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_messages_botCallbackAnswer.constructor != constructor) { @@ -806,6 +1060,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { if ((flags & 4) != 0) { url = stream.readString(exception); } + cache_time = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -819,6 +1074,35 @@ public void serializeToStream(AbstractSerializedData stream) { if ((flags & 4) != 0) { stream.writeString(url); } + stream.writeInt32(cache_time); + } + } + + public static class TL_dataJSON extends TLObject { + public static int constructor = 0x7d748d04; + + public String data; + + public static TL_dataJSON TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_dataJSON.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_dataJSON", constructor)); + } else { + return null; + } + } + TL_dataJSON result = new TL_dataJSON(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + data = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(data); } } @@ -1155,85 +1439,135 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class help_AppChangelog extends TLObject { - public String text; - - public static help_AppChangelog TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - help_AppChangelog result = null; - switch(constructor) { - case 0xaf7e0394: - result = new TL_help_appChangelogEmpty(); - break; - case 0x4668e6bd: - result = new TL_help_appChangelog(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in help_AppChangelog", constructor)); - } - if (result != null) { - result.readParams(stream, exception); - } - return result; - } - } - - public static class TL_help_appChangelogEmpty extends help_AppChangelog { - public static int constructor = 0xaf7e0394; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - - public static class TL_help_appChangelog extends help_AppChangelog { - public static int constructor = 0x4668e6bd; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - text = stream.readString(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeString(text); - } - } - - public static class TL_contacts_link extends TLObject { - public static int constructor = 0x3ace484c; + public static class TL_payments_paymentForm extends TLObject { + public static int constructor = 0x3f56aea3; - public ContactLink my_link; - public ContactLink foreign_link; - public User user; + public int flags; + public boolean can_save_credentials; + public boolean password_missing; + public int bot_id; + public TL_invoice invoice; + public int provider_id; + public String url; + public String native_provider; + public TL_dataJSON native_params; + public TL_paymentRequestedInfo saved_info; + public TL_paymentSavedCredentialsCard saved_credentials; + public ArrayList users = new ArrayList<>(); - public static TL_contacts_link TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_contacts_link.constructor != constructor) { + public static TL_payments_paymentForm TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_payments_paymentForm.constructor != constructor) { if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_contacts_link", constructor)); + throw new RuntimeException(String.format("can't parse magic %x in TL_payments_paymentForm", constructor)); } else { return null; } } - TL_contacts_link result = new TL_contacts_link(); + TL_payments_paymentForm result = new TL_payments_paymentForm(); result.readParams(stream, exception); return result; } public void readParams(AbstractSerializedData stream, boolean exception) { - my_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - foreign_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); - user = User.TLdeserialize(stream, stream.readInt32(exception), exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - my_link.serializeToStream(stream); - foreign_link.serializeToStream(stream); - user.serializeToStream(stream); - } - } + flags = stream.readInt32(exception); + can_save_credentials = (flags & 4) != 0; + password_missing = (flags & 8) != 0; + bot_id = stream.readInt32(exception); + invoice = TL_invoice.TLdeserialize(stream, stream.readInt32(exception), exception); + provider_id = stream.readInt32(exception); + url = stream.readString(exception); + if ((flags & 16) != 0) { + native_provider = stream.readString(exception); + } + if ((flags & 16) != 0) { + native_params = TL_dataJSON.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 1) != 0) { + saved_info = TL_paymentRequestedInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + saved_credentials = TL_paymentSavedCredentialsCard.TLdeserialize(stream, stream.readInt32(exception), exception); + } + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = can_save_credentials ? (flags | 4) : (flags &~ 4); + flags = password_missing ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeInt32(bot_id); + invoice.serializeToStream(stream); + stream.writeInt32(provider_id); + stream.writeString(url); + if ((flags & 16) != 0) { + stream.writeString(native_provider); + } + if ((flags & 16) != 0) { + native_params.serializeToStream(stream); + } + if ((flags & 1) != 0) { + saved_info.serializeToStream(stream); + } + if ((flags & 2) != 0) { + saved_credentials.serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + int count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static class TL_contacts_link extends TLObject { + public static int constructor = 0x3ace484c; + + public ContactLink my_link; + public ContactLink foreign_link; + public User user; + + public static TL_contacts_link TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_contacts_link.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_contacts_link", constructor)); + } else { + return null; + } + } + TL_contacts_link result = new TL_contacts_link(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + my_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); + foreign_link = ContactLink.TLdeserialize(stream, stream.readInt32(exception), exception); + user = User.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + my_link.serializeToStream(stream); + foreign_link.serializeToStream(stream); + user.serializeToStream(stream); + } + } public static class EncryptedFile extends TLObject { public long id; @@ -1363,6 +1697,37 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_labeledPrice extends TLObject { + public static int constructor = 0xcb296bf8; + + public String label; + public long amount; + + public static TL_labeledPrice TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_labeledPrice.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_labeledPrice", constructor)); + } else { + return null; + } + } + TL_labeledPrice result = new TL_labeledPrice(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + label = stream.readString(exception); + amount = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(label); + stream.writeInt64(amount); + } + } + public static class TL_messages_affectedMessages extends TLObject { public static int constructor = 0x84d19185; @@ -1509,27 +1874,31 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class updates_Difference extends TLObject { - public int date; - public int seq; public ArrayList new_messages = new ArrayList<>(); public ArrayList new_encrypted_messages = new ArrayList<>(); public ArrayList other_updates = new ArrayList<>(); public ArrayList chats = new ArrayList<>(); public ArrayList users = new ArrayList<>(); - public TL_updates_state intermediate_state; public TL_updates_state state; + public TL_updates_state intermediate_state; + public int pts; + public int date; + public int seq; public static updates_Difference TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { updates_Difference result = null; switch(constructor) { - case 0x5d75a138: - result = new TL_updates_differenceEmpty(); + case 0xf49ca0: + result = new TL_updates_difference(); break; case 0xa8fb1981: result = new TL_updates_differenceSlice(); break; - case 0xf49ca0: - result = new TL_updates_difference(); + case 0x4afe8f6d: + result = new TL_updates_differenceTooLong(); + break; + case 0x5d75a138: + result = new TL_updates_differenceEmpty(); break; } if (result == null && exception) { @@ -1542,24 +1911,8 @@ public static updates_Difference TLdeserialize(AbstractSerializedData stream, in } } - public static class TL_updates_differenceEmpty extends updates_Difference { - public static int constructor = 0x5d75a138; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - date = stream.readInt32(exception); - seq = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(date); - stream.writeInt32(seq); - } - } - - public static class TL_updates_differenceSlice extends updates_Difference { - public static int constructor = 0xa8fb1981; + public static class TL_updates_difference extends updates_Difference { + public static int constructor = 0xf49ca0; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -1638,7 +1991,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } users.add(object); } - intermediate_state = TL_updates_state.TLdeserialize(stream, stream.readInt32(exception), exception); + state = TL_updates_state.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -1673,12 +2026,12 @@ public void serializeToStream(AbstractSerializedData stream) { for (int a = 0; a < count; a++) { users.get(a).serializeToStream(stream); } - intermediate_state.serializeToStream(stream); + state.serializeToStream(stream); } } - public static class TL_updates_difference extends updates_Difference { - public static int constructor = 0xf49ca0; + public static class TL_updates_differenceSlice extends updates_Difference { + public static int constructor = 0xa8fb1981; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -1757,7 +2110,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } users.add(object); } - state = TL_updates_state.TLdeserialize(stream, stream.readInt32(exception), exception); + intermediate_state = TL_updates_state.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -1792,7 +2145,37 @@ public void serializeToStream(AbstractSerializedData stream) { for (int a = 0; a < count; a++) { users.get(a).serializeToStream(stream); } - state.serializeToStream(stream); + intermediate_state.serializeToStream(stream); + } + } + + public static class TL_updates_differenceTooLong extends updates_Difference { + public static int constructor = 0x4afe8f6d; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + pts = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(pts); + } + } + + public static class TL_updates_differenceEmpty extends updates_Difference { + public static int constructor = 0x5d75a138; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + date = stream.readInt32(exception); + seq = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(date); + stream.writeInt32(seq); } } @@ -1807,6 +2190,9 @@ public static PrivacyKey TLdeserialize(AbstractSerializedData stream, int constr case 0x500e6dfa: result = new TL_privacyKeyChatInvite(); break; + case 0x3d662b7b: + result = new TL_privacyKeyPhoneCall(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in PrivacyKey", constructor)); @@ -1836,6 +2222,15 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_privacyKeyPhoneCall extends PrivacyKey { + public static int constructor = 0x3d662b7b; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class GeoPoint extends TLObject { public double _long; public double lat; @@ -2147,9 +2542,6 @@ public static SendMessageAction TLdeserialize(AbstractSerializedData stream, int case 0x628cbc6f: result = new TL_sendMessageChooseContactAction(); break; - case 0x15c2c99a: - result = new TL_sendMessageGameStopAction(); - break; case 0x16bf744e: result = new TL_sendMessageTypingAction(); break; @@ -2296,15 +2688,6 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_sendMessageGameStopAction extends SendMessageAction { - public static int constructor = 0x15c2c99a; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - public static class TL_sendMessageTypingAction extends SendMessageAction { public static int constructor = 0x16bf744e; @@ -2603,6 +2986,58 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class payments_PaymentResult extends TLObject { + public Updates updates; + public String url; + + public static payments_PaymentResult TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + payments_PaymentResult result = null; + switch(constructor) { + case 0x4e5f810d: + result = new TL_payments_paymentResult(); + break; + case 0x6b56b921: + result = new TL_payments_paymentVerficationNeeded(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in payments_PaymentResult", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_payments_paymentResult extends payments_PaymentResult { + public static int constructor = 0x4e5f810d; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + updates = Updates.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + updates.serializeToStream(stream); + } + } + + public static class TL_payments_paymentVerficationNeeded extends payments_PaymentResult { + public static int constructor = 0x6b56b921; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + } + } + public static class TL_inputPhoneContact extends TLObject { public static int constructor = 0xf392b7f4; @@ -2811,6 +3246,47 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messageMediaInvoice extends MessageMedia { + public static int constructor = 0x84551347; + + public TL_webDocument photo; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + shipping_address_requested = (flags & 2) != 0; + test = (flags & 8) != 0; + title = stream.readString(exception); + description = stream.readString(exception); + if ((flags & 1) != 0) { + photo = TL_webDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + receipt_msg_id = stream.readInt32(exception); + } + currency = stream.readString(exception); + total_amount = stream.readInt64(exception); + start_param = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = shipping_address_requested ? (flags | 2) : (flags &~ 2); + flags = test ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeString(title); + stream.writeString(description); + if ((flags & 1) != 0) { + photo.serializeToStream(stream); + } + if ((flags & 4) != 0) { + stream.writeInt32(receipt_msg_id); + } + stream.writeString(currency); + stream.writeInt64(total_amount); + stream.writeString(start_param); + } + } + public static class TL_messageMediaUnsupported extends MessageMedia { public static int constructor = 0x9f84f49e; @@ -4201,6 +4677,9 @@ public static InputPrivacyKey TLdeserialize(AbstractSerializedData stream, int c case 0x4f96cb18: result = new TL_inputPrivacyKeyStatusTimestamp(); break; + case 0xfabadc5f: + result = new TL_inputPrivacyKeyPhoneCall(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in InputPrivacyKey", constructor)); @@ -4230,6 +4709,15 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_inputPrivacyKeyPhoneCall extends InputPrivacyKey { + public static int constructor = 0xfabadc5f; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class photos_Photos extends TLObject { public ArrayList photos = new ArrayList<>(); public ArrayList users = new ArrayList<>(); @@ -4835,6 +5323,181 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class Page extends TLObject { + public ArrayList blocks = new ArrayList<>(); + public ArrayList photos = new ArrayList<>(); + public ArrayList videos = new ArrayList<>(); + + public static Page TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + Page result = null; + switch(constructor) { + case 0x8dee6c44: + result = new TL_pagePart(); + break; + case 0xd7a19d69: + result = new TL_pageFull(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in Page", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_pagePart extends Page { + public static int constructor = 0x8dee6c44; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + PageBlock object = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + blocks.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Photo object = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + photos.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Document object = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + videos.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = blocks.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + blocks.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = photos.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + photos.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = videos.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + videos.get(a).serializeToStream(stream); + } + } + } + + public static class TL_pageFull extends Page { + public static int constructor = 0xd7a19d69; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + PageBlock object = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + blocks.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Photo object = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + photos.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Document object = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + videos.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = blocks.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + blocks.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = photos.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + photos.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = videos.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + videos.get(a).serializeToStream(stream); + } + } + } + public static class TL_topPeerCategoryPeers extends TLObject { public static int constructor = 0xfb834291; @@ -4951,35 +5614,38 @@ public void serializeToStream(AbstractSerializedData stream) { public static class KeyboardButton extends TLObject { public String text; + public String url; public int flags; public boolean same_peer; public String query; public byte[] data; - public String url; public static KeyboardButton TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { KeyboardButton result = null; switch(constructor) { - case 0xa2fa4880: - result = new TL_keyboardButton(); + case 0xb16a6c29: + result = new TL_keyboardButtonRequestPhone(); break; case 0x50f41ccf: result = new TL_keyboardButtonGame(); break; + case 0x258aff05: + result = new TL_keyboardButtonUrl(); + break; case 0x568a748: result = new TL_keyboardButtonSwitchInline(); break; - case 0xb16a6c29: - result = new TL_keyboardButtonRequestPhone(); + case 0xfc796b3f: + result = new TL_keyboardButtonRequestGeoLocation(); + break; + case 0xafd93fbb: + result = new TL_keyboardButtonBuy(); break; case 0x683a5e46: result = new TL_keyboardButtonCallback(); break; - case 0x258aff05: - result = new TL_keyboardButtonUrl(); - break; - case 0xfc796b3f: - result = new TL_keyboardButtonRequestGeoLocation(); + case 0xa2fa4880: + result = new TL_keyboardButton(); break; } if (result == null && exception) { @@ -4992,8 +5658,8 @@ public static KeyboardButton TLdeserialize(AbstractSerializedData stream, int co } } - public static class TL_keyboardButton extends KeyboardButton { - public static int constructor = 0xa2fa4880; + public static class TL_keyboardButtonRequestPhone extends KeyboardButton { + public static int constructor = 0xb16a6c29; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -5006,88 +5672,102 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_keyboardButtonSwitchInline extends KeyboardButton { - public static int constructor = 0x568a748; + public static class TL_keyboardButtonGame extends KeyboardButton { + public static int constructor = 0x50f41ccf; public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - same_peer = (flags & 1) != 0; text = stream.readString(exception); - query = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - flags = same_peer ? (flags | 1) : (flags &~ 1); - stream.writeInt32(flags); stream.writeString(text); - stream.writeString(query); } } - public static class TL_keyboardButtonRequestPhone extends KeyboardButton { - public static int constructor = 0xb16a6c29; + public static class TL_keyboardButtonUrl extends KeyboardButton { + public static int constructor = 0x258aff05; public void readParams(AbstractSerializedData stream, boolean exception) { text = stream.readString(exception); + url = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeString(text); + stream.writeString(url); } } - public static class TL_keyboardButtonGame extends KeyboardButton { - public static int constructor = 0x50f41ccf; + public static class TL_keyboardButtonSwitchInline extends KeyboardButton { + public static int constructor = 0x568a748; public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + same_peer = (flags & 1) != 0; text = stream.readString(exception); + query = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = same_peer ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); stream.writeString(text); + stream.writeString(query); } } - public static class TL_keyboardButtonCallback extends KeyboardButton { - public static int constructor = 0x683a5e46; + public static class TL_keyboardButtonRequestGeoLocation extends KeyboardButton { + public static int constructor = 0xfc796b3f; public void readParams(AbstractSerializedData stream, boolean exception) { text = stream.readString(exception); - data = stream.readByteArray(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeString(text); - stream.writeByteArray(data); } } - public static class TL_keyboardButtonUrl extends KeyboardButton { - public static int constructor = 0x258aff05; + public static class TL_keyboardButtonBuy extends KeyboardButton { + public static int constructor = 0xafd93fbb; public void readParams(AbstractSerializedData stream, boolean exception) { text = stream.readString(exception); - url = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeString(text); - stream.writeString(url); } } - public static class TL_keyboardButtonRequestGeoLocation extends KeyboardButton { - public static int constructor = 0xfc796b3f; + public static class TL_keyboardButtonCallback extends KeyboardButton { + public static int constructor = 0x683a5e46; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = stream.readString(exception); + data = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(text); + stream.writeByteArray(data); + } + } + + public static class TL_keyboardButton extends KeyboardButton { + public static int constructor = 0xa2fa4880; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -5417,6 +6097,7 @@ public static class WebPage extends TLObject { public long id; public String url; public String display_url; + public int hash; public String type; public String site_name; public String title; @@ -5428,27 +6109,34 @@ public static class WebPage extends TLObject { public int embed_height; public int duration; public String author; - public int date; public Document document; + public Page cached_page; + public int date; public static WebPage TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { WebPage result = null; switch(constructor) { - case 0xa31ea0b5: - result = new TL_webPage_old(); - break; + case 0x5f07b4bc: + result = new TL_webPage(); + break; + case 0xa31ea0b5: + result = new TL_webPage_old(); + break; + case 0xeb1477e8: + result = new TL_webPageEmpty(); + break; case 0xd41a5167: result = new TL_webPageUrlPending(); break; - case 0xc586da1c: - result = new TL_webPagePending(); - break; - case 0xeb1477e8: - result = new TL_webPageEmpty(); - break; - case 0xca820ed7: - result = new TL_webPage(); - break; + case 0xc586da1c: + result = new TL_webPagePending(); + break; + case 0x85849473: + result = new TL_webPageNotModified(); + break; + case 0xca820ed7: + result = new TL_webPage_layer58(); + break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in WebPage", constructor)); @@ -5460,52 +6148,8 @@ public static WebPage TLdeserialize(AbstractSerializedData stream, int construct } } - public static class TL_webPagePending extends WebPage { - public static int constructor = 0xc586da1c; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - id = stream.readInt64(exception); - date = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt64(id); - stream.writeInt32(date); - } - } - - public static class TL_webPageEmpty extends WebPage { - public static int constructor = 0xeb1477e8; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - id = stream.readInt64(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt64(id); - } - } - - public static class TL_webPageUrlPending extends WebPage { - public static int constructor = 0xd41a5167; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - url = stream.readString(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeString(url); - } - } - - public static class TL_webPage_old extends WebPage { - public static int constructor = 0xa31ea0b5; + public static class TL_webPage extends WebPage { + public static int constructor = 0x5f07b4bc; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -5513,6 +6157,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { id = stream.readInt64(exception); url = stream.readString(exception); display_url = stream.readString(exception); + hash = stream.readInt32(exception); if ((flags & 1) != 0) { type = stream.readString(exception); } @@ -5521,17 +6166,17 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } if ((flags & 4) != 0) { title = stream.readString(exception); - } - if ((flags & 8) != 0) { + } + if ((flags & 8) != 0) { description = stream.readString(exception); - } - if ((flags & 16) != 0) { - photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 16) != 0) { + photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); } if ((flags & 32) != 0) { embed_url = stream.readString(exception); - } - if ((flags & 32) != 0) { + } + if ((flags & 32) != 0) { embed_type = stream.readString(exception); } if ((flags & 64) != 0) { @@ -5539,25 +6184,32 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } if ((flags & 64) != 0) { embed_height = stream.readInt32(exception); - } - if ((flags & 128) != 0) { + } + if ((flags & 128) != 0) { duration = stream.readInt32(exception); - } - if ((flags & 256) != 0) { + } + if ((flags & 256) != 0) { author = stream.readString(exception); } + if ((flags & 512) != 0) { + document = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 1024) != 0) { + cached_page = Page.TLdeserialize(stream, stream.readInt32(exception), exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(flags); stream.writeInt64(id); - stream.writeString(url); + stream.writeString(url); stream.writeString(display_url); + stream.writeInt32(hash); if ((flags & 1) != 0) { stream.writeString(type); } - if ((flags & 2) != 0) { + if ((flags & 2) != 0) { stream.writeString(site_name); } if ((flags & 4) != 0) { @@ -5565,9 +6217,9 @@ public void serializeToStream(AbstractSerializedData stream) { } if ((flags & 8) != 0) { stream.writeString(description); - } - if ((flags & 16) != 0) { - photo.serializeToStream(stream); + } + if ((flags & 16) != 0) { + photo.serializeToStream(stream); } if ((flags & 32) != 0) { stream.writeString(embed_url); @@ -5586,197 +6238,406 @@ public void serializeToStream(AbstractSerializedData stream) { } if ((flags & 256) != 0) { stream.writeString(author); - } - } - } + } + if ((flags & 512) != 0) { + document.serializeToStream(stream); + } + if ((flags & 1024) != 0) { + cached_page.serializeToStream(stream); + } + } + } - public static class TL_webPage extends WebPage { - public static int constructor = 0xca820ed7; + public static class TL_webPage_old extends TL_webPage { + public static int constructor = 0xa31ea0b5; - public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - id = stream.readInt64(exception); - url = stream.readString(exception); - display_url = stream.readString(exception); - if ((flags & 1) != 0) { + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readInt64(exception); + url = stream.readString(exception); + display_url = stream.readString(exception); + if ((flags & 1) != 0) { type = stream.readString(exception); } - if ((flags & 2) != 0) { - site_name = stream.readString(exception); + if ((flags & 2) != 0) { + site_name = stream.readString(exception); } - if ((flags & 4) != 0) { + if ((flags & 4) != 0) { title = stream.readString(exception); - } - if ((flags & 8) != 0) { - description = stream.readString(exception); - } + } + if ((flags & 8) != 0) { + description = stream.readString(exception); + } if ((flags & 16) != 0) { - photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); - } - if ((flags & 32) != 0) { - embed_url = stream.readString(exception); - } - if ((flags & 32) != 0) { - embed_type = stream.readString(exception); - } - if ((flags & 64) != 0) { + photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32) != 0) { + embed_url = stream.readString(exception); + } + if ((flags & 32) != 0) { + embed_type = stream.readString(exception); + } + if ((flags & 64) != 0) { embed_width = stream.readInt32(exception); } - if ((flags & 64) != 0) { - embed_height = stream.readInt32(exception); - } - if ((flags & 128) != 0) { - duration = stream.readInt32(exception); - } - if ((flags & 256) != 0) { - author = stream.readString(exception); - } - if ((flags & 512) != 0) { - document = Document.TLdeserialize(stream, stream.readInt32(exception), exception); - } - } + if ((flags & 64) != 0) { + embed_height = stream.readInt32(exception); + } + if ((flags & 128) != 0) { + duration = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + author = stream.readString(exception); + } + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(flags); - stream.writeInt64(id); - stream.writeString(url); - stream.writeString(display_url); + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(id); + stream.writeString(url); + stream.writeString(display_url); if ((flags & 1) != 0) { - stream.writeString(type); + stream.writeString(type); } if ((flags & 2) != 0) { - stream.writeString(site_name); - } - if ((flags & 4) != 0) { - stream.writeString(title); - } + stream.writeString(site_name); + } + if ((flags & 4) != 0) { + stream.writeString(title); + } if ((flags & 8) != 0) { - stream.writeString(description); + stream.writeString(description); } if ((flags & 16) != 0) { - photo.serializeToStream(stream); - } + photo.serializeToStream(stream); + } if ((flags & 32) != 0) { stream.writeString(embed_url); - } + } if ((flags & 32) != 0) { - stream.writeString(embed_type); - } + stream.writeString(embed_type); + } if ((flags & 64) != 0) { - stream.writeInt32(embed_width); - } - if ((flags & 64) != 0) { - stream.writeInt32(embed_height); - } - if ((flags & 128) != 0) { - stream.writeInt32(duration); - } - if ((flags & 256) != 0) { - stream.writeString(author); - } - if ((flags & 512) != 0) { - document.serializeToStream(stream); + stream.writeInt32(embed_width); } - } - } - - public static class messages_FeaturedStickers extends TLObject { - public int hash; - public ArrayList sets = new ArrayList<>(); - public ArrayList unread = new ArrayList<>(); - - public static messages_FeaturedStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - messages_FeaturedStickers result = null; - switch(constructor) { - case 0xf89d88e5: - result = new TL_messages_featuredStickers(); - break; - case 0x4ede3cf: - result = new TL_messages_featuredStickersNotModified(); - break; + if ((flags & 64) != 0) { + stream.writeInt32(embed_height); } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in messages_FeaturedStickers", constructor)); + if ((flags & 128) != 0) { + stream.writeInt32(duration); } - if (result != null) { - result.readParams(stream, exception); + if ((flags & 256) != 0) { + stream.writeString(author); } - return result; } } - public static class TL_messages_featuredStickers extends messages_FeaturedStickers { - public static int constructor = 0xf89d88e5; + public static class TL_webPageEmpty extends WebPage { + public static int constructor = 0xeb1477e8; public void readParams(AbstractSerializedData stream, boolean exception) { - hash = stream.readInt32(exception); - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - StickerSetCovered object = StickerSetCovered.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - sets.add(object); - } - magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - unread.add(stream.readInt64(exception)); - } + id = stream.readInt64(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(hash); - stream.writeInt32(0x1cb5c415); - int count = sets.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - sets.get(a).serializeToStream(stream); - } - stream.writeInt32(0x1cb5c415); - count = unread.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stream.writeInt64(unread.get(a)); - } + stream.writeInt64(id); } } - public static class TL_messages_featuredStickersNotModified extends messages_FeaturedStickers { - public static int constructor = 0x4ede3cf; + public static class TL_webPageUrlPending extends WebPage { + public static int constructor = 0xd41a5167; + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + } + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeString(url); } } - public static class TL_auth_passwordRecovery extends TLObject { - public static int constructor = 0x137948a5; + public static class TL_webPagePending extends WebPage { + public static int constructor = 0xc586da1c; - public String email_pattern; - public static TL_auth_passwordRecovery TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_auth_passwordRecovery.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_auth_passwordRecovery", constructor)); + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + date = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt32(date); + } + } + + public static class TL_webPageNotModified extends WebPage { + public static int constructor = 0x85849473; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_webPage_layer58 extends TL_webPage { + public static int constructor = 0xca820ed7; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readInt64(exception); + url = stream.readString(exception); + display_url = stream.readString(exception); + if ((flags & 1) != 0) { + type = stream.readString(exception); + } + if ((flags & 2) != 0) { + site_name = stream.readString(exception); + } + if ((flags & 4) != 0) { + title = stream.readString(exception); + } + if ((flags & 8) != 0) { + description = stream.readString(exception); + } + if ((flags & 16) != 0) { + photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32) != 0) { + embed_url = stream.readString(exception); + } + if ((flags & 32) != 0) { + embed_type = stream.readString(exception); + } + if ((flags & 64) != 0) { + embed_width = stream.readInt32(exception); + } + if ((flags & 64) != 0) { + embed_height = stream.readInt32(exception); + } + if ((flags & 128) != 0) { + duration = stream.readInt32(exception); + } + if ((flags & 256) != 0) { + author = stream.readString(exception); + } + if ((flags & 512) != 0) { + document = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(id); + stream.writeString(url); + stream.writeString(display_url); + if ((flags & 1) != 0) { + stream.writeString(type); + } + if ((flags & 2) != 0) { + stream.writeString(site_name); + } + if ((flags & 4) != 0) { + stream.writeString(title); + } + if ((flags & 8) != 0) { + stream.writeString(description); + } + if ((flags & 16) != 0) { + photo.serializeToStream(stream); + } + if ((flags & 32) != 0) { + stream.writeString(embed_url); + } + if ((flags & 32) != 0) { + stream.writeString(embed_type); + } + if ((flags & 64) != 0) { + stream.writeInt32(embed_width); + } + if ((flags & 64) != 0) { + stream.writeInt32(embed_height); + } + if ((flags & 128) != 0) { + stream.writeInt32(duration); + } + if ((flags & 256) != 0) { + stream.writeString(author); + } + if ((flags & 512) != 0) { + document.serializeToStream(stream); + } + } + } + + public static class messages_FeaturedStickers extends TLObject { + public int hash; + public ArrayList sets = new ArrayList<>(); + public ArrayList unread = new ArrayList<>(); + + public static messages_FeaturedStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_FeaturedStickers result = null; + switch(constructor) { + case 0xf89d88e5: + result = new TL_messages_featuredStickers(); + break; + case 0x4ede3cf: + result = new TL_messages_featuredStickersNotModified(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_FeaturedStickers", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_messages_featuredStickers extends messages_FeaturedStickers { + public static int constructor = 0xf89d88e5; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + hash = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + StickerSetCovered object = StickerSetCovered.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + sets.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + unread.add(stream.readInt64(exception)); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(hash); + stream.writeInt32(0x1cb5c415); + int count = sets.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + sets.get(a).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = unread.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt64(unread.get(a)); + } + } + } + + public static class TL_messages_featuredStickersNotModified extends messages_FeaturedStickers { + public static int constructor = 0x4ede3cf; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class PhoneCallDiscardReason extends TLObject { + + public static PhoneCallDiscardReason TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + PhoneCallDiscardReason result = null; + switch(constructor) { + case 0x57adc690: + result = new TL_phoneCallDiscardReasonHangup(); + break; + case 0xfaf7e8c9: + result = new TL_phoneCallDiscardReasonBusy(); + break; + case 0x85e42301: + result = new TL_phoneCallDiscardReasonMissed(); + break; + case 0xe095c1a0: + result = new TL_phoneCallDiscardReasonDisconnect(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in PhoneCallDiscardReason", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_phoneCallDiscardReasonHangup extends PhoneCallDiscardReason { + public static int constructor = 0x57adc690; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_phoneCallDiscardReasonBusy extends PhoneCallDiscardReason { + public static int constructor = 0xfaf7e8c9; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_phoneCallDiscardReasonMissed extends PhoneCallDiscardReason { + public static int constructor = 0x85e42301; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_phoneCallDiscardReasonDisconnect extends PhoneCallDiscardReason { + public static int constructor = 0xe095c1a0; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_auth_passwordRecovery extends TLObject { + public static int constructor = 0x137948a5; + + public String email_pattern; + + public static TL_auth_passwordRecovery TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_auth_passwordRecovery.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_auth_passwordRecovery", constructor)); } else { return null; } @@ -6045,14 +6906,250 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class User extends TLObject { - public int id; - public String first_name; - public String last_name; - public String username; + public static class PhoneCall extends TLObject { + public long id; public long access_hash; - public String phone; - public UserProfilePhoto photo; + public int date; + public int admin_id; + public int participant_id; + public byte[] g_a_hash; + public TL_phoneCallProtocol protocol; + public byte[] g_a_or_b; + public long key_fingerprint; + public TL_phoneConnection connection; + public ArrayList alternative_connections = new ArrayList<>(); + public int start_date; + public byte[] g_b; + public int flags; + public int receive_date; + public boolean need_rating; + public boolean need_debug; + public PhoneCallDiscardReason reason; + public int duration; + + public static PhoneCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + PhoneCall result = null; + switch(constructor) { + case 0x83761ce4: + result = new TL_phoneCallRequested(); + break; + case 0xffe6ab67: + result = new TL_phoneCall(); + break; + case 0x5366c915: + result = new TL_phoneCallEmpty(); + break; + case 0x6d003d3f: + result = new TL_phoneCallAccepted(); + break; + case 0x1b8f4ad1: + result = new TL_phoneCallWaiting(); + break; + case 0x50ca4de1: + result = new TL_phoneCallDiscarded(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in PhoneCall", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_phoneCallRequested extends PhoneCall { + public static int constructor = 0x83761ce4; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + date = stream.readInt32(exception); + admin_id = stream.readInt32(exception); + participant_id = stream.readInt32(exception); + g_a_hash = stream.readByteArray(exception); + protocol = TL_phoneCallProtocol.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(date); + stream.writeInt32(admin_id); + stream.writeInt32(participant_id); + stream.writeByteArray(g_a_hash); + protocol.serializeToStream(stream); + } + } + + public static class TL_phoneCall extends PhoneCall { + public static int constructor = 0xffe6ab67; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + date = stream.readInt32(exception); + admin_id = stream.readInt32(exception); + participant_id = stream.readInt32(exception); + g_a_or_b = stream.readByteArray(exception); + key_fingerprint = stream.readInt64(exception); + protocol = TL_phoneCallProtocol.TLdeserialize(stream, stream.readInt32(exception), exception); + connection = TL_phoneConnection.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_phoneConnection object = TL_phoneConnection.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + alternative_connections.add(object); + } + start_date = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(date); + stream.writeInt32(admin_id); + stream.writeInt32(participant_id); + stream.writeByteArray(g_a_or_b); + stream.writeInt64(key_fingerprint); + protocol.serializeToStream(stream); + connection.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = alternative_connections.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + alternative_connections.get(a).serializeToStream(stream); + } + stream.writeInt32(start_date); + } + } + + public static class TL_phoneCallEmpty extends PhoneCall { + public static int constructor = 0x5366c915; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + } + } + + public static class TL_phoneCallAccepted extends PhoneCall { + public static int constructor = 0x6d003d3f; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + date = stream.readInt32(exception); + admin_id = stream.readInt32(exception); + participant_id = stream.readInt32(exception); + g_b = stream.readByteArray(exception); + protocol = TL_phoneCallProtocol.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(date); + stream.writeInt32(admin_id); + stream.writeInt32(participant_id); + stream.writeByteArray(g_b); + protocol.serializeToStream(stream); + } + } + + public static class TL_phoneCallWaiting extends PhoneCall { + public static int constructor = 0x1b8f4ad1; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + date = stream.readInt32(exception); + admin_id = stream.readInt32(exception); + participant_id = stream.readInt32(exception); + protocol = TL_phoneCallProtocol.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 1) != 0) { + receive_date = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(id); + stream.writeInt64(access_hash); + stream.writeInt32(date); + stream.writeInt32(admin_id); + stream.writeInt32(participant_id); + protocol.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeInt32(receive_date); + } + } + } + + public static class TL_phoneCallDiscarded extends PhoneCall { + public static int constructor = 0x50ca4de1; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + need_rating = (flags & 4) != 0; + need_debug = (flags & 8) != 0; + id = stream.readInt64(exception); + if ((flags & 1) != 0) { + reason = PhoneCallDiscardReason.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + duration = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = need_rating ? (flags | 4) : (flags &~ 4); + flags = need_debug ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeInt64(id); + if ((flags & 1) != 0) { + reason.serializeToStream(stream); + } + if ((flags & 2) != 0) { + stream.writeInt32(duration); + } + } + } + + public static class User extends TLObject { + public int id; + public String first_name; + public String last_name; + public String username; + public long access_hash; + public String phone; + public UserProfilePhoto photo; public UserStatus status; public boolean inactive; public int flags; @@ -6719,6 +7816,56 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_phone_phoneCall extends TLObject { + public static int constructor = 0xec82e140; + + public PhoneCall phone_call; + public ArrayList users = new ArrayList<>(); + + public static TL_phone_phoneCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_phone_phoneCall.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_phone_phoneCall", constructor)); + } else { + return null; + } + } + TL_phone_phoneCall result = new TL_phone_phoneCall(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + phone_call = PhoneCall.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + User object = User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + phone_call.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + public static class ChannelParticipantsFilter extends TLObject { public static ChannelParticipantsFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -6889,6 +8036,12 @@ public static class MessageAction extends TLObject { public UserProfilePhoto newUserPhoto; public int inviter_id; public int ttl; + public int flags; + public long call_id; + public PhoneCallDiscardReason reason; + public int duration; + public String currency; + public long total_amount; public long game_id; public int score; @@ -6931,18 +8084,18 @@ public static MessageAction TLdeserialize(AbstractSerializedData stream, int con case 0x55555551: result = new TL_messageActionUserUpdatedPhoto(); break; - case 0xc7d53de: - result = new TL_messageActionGeoChatCheckin(); - break; - case 0xf89cf5e8: - result = new TL_messageActionChatJoinedByLink(); - break; case 0x5e3cfc4b: result = new TL_messageActionChatAddUser_old(); break; case 0x55555552: result = new TL_messageActionTTLChange(); break; + case 0xc7d53de: + result = new TL_messageActionGeoChatCheckin(); + break; + case 0xf89cf5e8: + result = new TL_messageActionChatJoinedByLink(); + break; case 0x95d2ac92: result = new TL_messageActionChannelCreate(); break; @@ -6952,9 +8105,15 @@ public static MessageAction TLdeserialize(AbstractSerializedData stream, int con case 0x95e3fbef: result = new TL_messageActionChatDeletePhoto(); break; + case 0x80e11a7f: + result = new TL_messageActionPhoneCall(); + break; case 0xb5a1ce5a: result = new TL_messageActionChatEditTitle(); break; + case 0x40699cd0: + result = new TL_messageActionPaymentSent(); + break; case 0xb6aef7b0: result = new TL_messageActionEmpty(); break; @@ -7164,29 +8323,6 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_messageActionGeoChatCheckin extends MessageAction { - public static int constructor = 0xc7d53de; - - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - } - } - - public static class TL_messageActionChatJoinedByLink extends MessageAction { - public static int constructor = 0xf89cf5e8; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - inviter_id = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(inviter_id); - } - } - public static class TL_messageActionChatAddUser_old extends TL_messageActionChatAddUser { public static int constructor = 0x5e3cfc4b; @@ -7215,6 +8351,29 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messageActionGeoChatCheckin extends MessageAction { + public static int constructor = 0xc7d53de; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_messageActionChatJoinedByLink extends MessageAction { + public static int constructor = 0xf89cf5e8; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + inviter_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(inviter_id); + } + } + public static class TL_messageActionChannelCreate extends MessageAction { public static int constructor = 0x95d2ac92; @@ -7247,6 +8406,34 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messageActionPhoneCall extends MessageAction { + public static int constructor = 0x80e11a7f; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + call_id = stream.readInt64(exception); + if ((flags & 1) != 0) { + reason = PhoneCallDiscardReason.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + duration = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(call_id); + if ((flags & 1) != 0) { + reason.serializeToStream(stream); + } + if ((flags & 2) != 0) { + stream.writeInt32(duration); + } + } + } + public static class TL_messageActionChatEditTitle extends MessageAction { public static int constructor = 0xb5a1ce5a; @@ -7261,6 +8448,22 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messageActionPaymentSent extends MessageAction { + public static int constructor = 0x40699cd0; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + currency = stream.readString(exception); + total_amount = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(currency); + stream.writeInt64(total_amount); + } + } + public static class TL_messageActionEmpty extends MessageAction { public static int constructor = 0xb6aef7b0; @@ -7711,6 +8914,133 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_invoice extends TLObject { + public static int constructor = 0xc30aa358; + + public int flags; + public boolean test; + public boolean name_requested; + public boolean phone_requested; + public boolean email_requested; + public boolean shipping_address_requested; + public boolean flexible; + public String currency; + public ArrayList prices = new ArrayList<>(); + + public static TL_invoice TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_invoice.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_invoice", constructor)); + } else { + return null; + } + } + TL_invoice result = new TL_invoice(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + test = (flags & 1) != 0; + name_requested = (flags & 2) != 0; + phone_requested = (flags & 4) != 0; + email_requested = (flags & 8) != 0; + shipping_address_requested = (flags & 16) != 0; + flexible = (flags & 32) != 0; + currency = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_labeledPrice object = TL_labeledPrice.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + prices.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = test ? (flags | 1) : (flags &~ 1); + flags = name_requested ? (flags | 2) : (flags &~ 2); + flags = phone_requested ? (flags | 4) : (flags &~ 4); + flags = email_requested ? (flags | 8) : (flags &~ 8); + flags = shipping_address_requested ? (flags | 16) : (flags &~ 16); + flags = flexible ? (flags | 32) : (flags &~ 32); + stream.writeInt32(flags); + stream.writeString(currency); + stream.writeInt32(0x1cb5c415); + int count = prices.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + prices.get(a).serializeToStream(stream); + } + } + } + + public static class TL_inputWebDocument extends TLObject { + public static int constructor = 0x9bed434d; + + public String url; + public int size; + public String mime_type; + public ArrayList attributes = new ArrayList<>(); + + public static TL_inputWebDocument TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_inputWebDocument.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_inputWebDocument", constructor)); + } else { + return null; + } + } + TL_inputWebDocument result = new TL_inputWebDocument(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + size = stream.readInt32(exception); + mime_type = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + attributes.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt32(size); + stream.writeString(mime_type); + stream.writeInt32(0x1cb5c415); + int count = attributes.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + attributes.get(a).serializeToStream(stream); + } + } + } + public static class Video extends TLObject { public long id; public long access_hash; @@ -7945,6 +9275,67 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class InputPaymentCredentials extends TLObject { + public int flags; + public boolean save; + public TL_dataJSON data; + public String id; + public byte[] tmp_password; + + public static InputPaymentCredentials TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + InputPaymentCredentials result = null; + switch(constructor) { + case 0x3417d728: + result = new TL_inputPaymentCredentials(); + break; + case 0xc10eb2cf: + result = new TL_inputPaymentCredentialsSaved(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in InputPaymentCredentials", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_inputPaymentCredentials extends InputPaymentCredentials { + public static int constructor = 0x3417d728; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + save = (flags & 1) != 0; + data = TL_dataJSON.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = save ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + data.serializeToStream(stream); + } + } + + public static class TL_inputPaymentCredentialsSaved extends InputPaymentCredentials { + public static int constructor = 0xc10eb2cf; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readString(exception); + tmp_password = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(id); + stream.writeByteArray(tmp_password); + } + } + public static class TL_exportedMessageLink extends TLObject { public static int constructor = 0x1f486803; @@ -8080,46 +9471,160 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class InputDocument extends TLObject { - public long id; - public long access_hash; + public static class TL_payments_validatedRequestedInfo extends TLObject { + public static int constructor = 0xd1451883; - public static InputDocument TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - InputDocument result = null; - switch(constructor) { - case 0x72f0eaae: - result = new TL_inputDocumentEmpty(); - break; - case 0x18798952: - result = new TL_inputDocument(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in InputDocument", constructor)); - } - if (result != null) { - result.readParams(stream, exception); + public int flags; + public String id; + public ArrayList shipping_options = new ArrayList<>(); + + public static TL_payments_validatedRequestedInfo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_payments_validatedRequestedInfo.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments_validatedRequestedInfo", constructor)); + } else { + return null; + } } + TL_payments_validatedRequestedInfo result = new TL_payments_validatedRequestedInfo(); + result.readParams(stream, exception); return result; } - } - - public static class TL_inputDocumentEmpty extends InputDocument { - public static int constructor = 0x72f0eaae; + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + id = stream.readString(exception); + } + if ((flags & 2) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_shippingOption object = TL_shippingOption.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + shipping_options.add(object); + } + } + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeString(id); + } + if ((flags & 2) != 0) { + stream.writeInt32(0x1cb5c415); + int count = shipping_options.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + shipping_options.get(a).serializeToStream(stream); + } + } } } - public static class TL_inputDocument extends InputDocument { - public static int constructor = 0x18798952; + public static class TL_shippingOption extends TLObject { + public static int constructor = 0xb6213cdf; + public String id; + public String title; + public ArrayList prices = new ArrayList<>(); - public void readParams(AbstractSerializedData stream, boolean exception) { - id = stream.readInt64(exception); - access_hash = stream.readInt64(exception); + public static TL_shippingOption TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_shippingOption.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_shippingOption", constructor)); + } else { + return null; + } + } + TL_shippingOption result = new TL_shippingOption(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readString(exception); + title = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_labeledPrice object = TL_labeledPrice.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + prices.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(id); + stream.writeString(title); + stream.writeInt32(0x1cb5c415); + int count = prices.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + prices.get(a).serializeToStream(stream); + } + } + } + + public static class InputDocument extends TLObject { + public long id; + public long access_hash; + + public static InputDocument TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + InputDocument result = null; + switch(constructor) { + case 0x72f0eaae: + result = new TL_inputDocumentEmpty(); + break; + case 0x18798952: + result = new TL_inputDocument(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in InputDocument", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_inputDocumentEmpty extends InputDocument { + public static int constructor = 0x72f0eaae; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_inputDocument extends InputDocument { + public static int constructor = 0x18798952; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -8545,33 +10050,110 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class InputPrivacyRule extends TLObject { - public ArrayList users = new ArrayList<>(); - - public static InputPrivacyRule TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - InputPrivacyRule result = null; + public static class PageBlock extends TLObject { + public String author; + public int published_date; + public RichText text; + public String language; + public int flags; + public boolean full_width; + public boolean allow_scrolling; + public String url; + public String html; + public long poster_photo_id; + public int w; + public int h; + public RichText caption; + public String name; + public boolean autoplay; + public boolean loop; + public long video_id; + public boolean ordered; + public long photo_id; + public long webpage_id; + public long author_photo_id; + public int date; + public ArrayList blocks = new ArrayList<>(); + public PageBlock cover; + public boolean first; //custom + public boolean bottom; //custom + public int level; //custom + + public static PageBlock TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + PageBlock result = null; switch(constructor) { - case 0x90110467: - result = new TL_inputPrivacyValueDisallowUsers(); + case 0xdb20b188: + result = new TL_pageBlockDivider(); break; - case 0xd66b66c9: - result = new TL_inputPrivacyValueDisallowAll(); + case 0xbaafe5e0: + result = new TL_pageBlockAuthorDate(); break; - case 0xba52007: - result = new TL_inputPrivacyValueDisallowContacts(); + case 0xc070d93e: + result = new TL_pageBlockPreformatted(); break; - case 0x184b35ce: - result = new TL_inputPrivacyValueAllowAll(); + case 0xcde200d1: + result = new TL_pageBlockEmbed(); break; - case 0xd09e07b: - result = new TL_inputPrivacyValueAllowContacts(); + case 0xce0d37b0: + result = new TL_pageBlockAnchor(); break; - case 0x131cc67f: - result = new TL_inputPrivacyValueAllowUsers(); + case 0xbfd064ec: + result = new TL_pageBlockHeader(); + break; + case 0xd9d71866: + result = new TL_pageBlockVideo(); + break; + case 0x13567e8a: + result = new TL_pageBlockUnsupported(); + break; + case 0x467a0766: + result = new TL_pageBlockParagraph(); + break; + case 0x3d5b64f2: + result = new TL_pageBlockAuthorDate_layer60(); + break; + case 0x8b31c4f: + result = new TL_pageBlockCollage(); + break; + case 0x48870999: + result = new TL_pageBlockFooter(); + break; + case 0x3a58c7f4: + result = new TL_pageBlockList(); + break; + case 0xd935d8fb: + result = new TL_pageBlockEmbed_layer60(); + break; + case 0xe9c69982: + result = new TL_pageBlockPhoto(); + break; + case 0x8ffa9a1f: + result = new TL_pageBlockSubtitle(); + break; + case 0x263d7c26: + result = new TL_pageBlockBlockquote(); + break; + case 0x292c7be9: + result = new TL_pageBlockEmbedPost(); + break; + case 0x70abc3fd: + result = new TL_pageBlockTitle(); + break; + case 0x39f23300: + result = new TL_pageBlockCover(); + break; + case 0xf12bb6e1: + result = new TL_pageBlockSubheader(); + break; + case 0x130c8963: + result = new TL_pageBlockSlideshow(); + break; + case 0x4f4456d3: + result = new TL_pageBlockPullquote(); break; } if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in InputPrivacyRule", constructor)); + throw new RuntimeException(String.format("can't parse magic %x in PageBlock", constructor)); } if (result != null) { result.readParams(stream, exception); @@ -8580,59 +10162,142 @@ public static InputPrivacyRule TLdeserialize(AbstractSerializedData stream, int } } - public static class TL_inputPrivacyValueDisallowUsers extends InputPrivacyRule { - public static int constructor = 0x90110467; + public static class TL_pageBlockDivider extends PageBlock { + public static int constructor = 0xdb20b188; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_pageBlockAuthorDate extends PageBlock { + public static int constructor = 0xbaafe5e0; + public RichText author; public void readParams(AbstractSerializedData stream, boolean exception) { - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; + author = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + published_date = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + author.serializeToStream(stream); + stream.writeInt32(published_date); + } + } + + public static class TL_pageBlockPreformatted extends PageBlock { + public static int constructor = 0xc070d93e; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + language = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + stream.writeString(language); + } + } + + public static class TL_pageBlockEmbed extends PageBlock { + public static int constructor = 0xcde200d1; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + full_width = (flags & 1) != 0; + allow_scrolling = (flags & 8) != 0; + if ((flags & 2) != 0) { + url = stream.readString(exception); } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - InputUser object = InputUser.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - users.add(object); + if ((flags & 4) != 0) { + html = stream.readString(exception); + } + if ((flags & 16) != 0) { + poster_photo_id = stream.readInt64(exception); } + w = stream.readInt32(exception); + h = stream.readInt32(exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(0x1cb5c415); - int count = users.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - users.get(a).serializeToStream(stream); + flags = full_width ? (flags | 1) : (flags &~ 1); + flags = allow_scrolling ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + if ((flags & 2) != 0) { + stream.writeString(url); + } + if ((flags & 4) != 0) { + stream.writeString(html); + } + if ((flags & 16) != 0) { + stream.writeInt64(poster_photo_id); } + stream.writeInt32(w); + stream.writeInt32(h); + caption.serializeToStream(stream); } } - public static class TL_inputPrivacyValueDisallowAll extends InputPrivacyRule { - public static int constructor = 0xd66b66c9; + public static class TL_pageBlockAnchor extends PageBlock { + public static int constructor = 0xce0d37b0; + + public void readParams(AbstractSerializedData stream, boolean exception) { + name = stream.readString(exception); + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeString(name); } } - public static class TL_inputPrivacyValueDisallowContacts extends InputPrivacyRule { - public static int constructor = 0xba52007; + public static class TL_pageBlockHeader extends PageBlock { + public static int constructor = 0xbfd064ec; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_pageBlockVideo extends PageBlock { + public static int constructor = 0xd9d71866; + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + autoplay = (flags & 1) != 0; + loop = (flags & 2) != 0; + video_id = stream.readInt64(exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = autoplay ? (flags | 1) : (flags &~ 1); + flags = loop ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + stream.writeInt64(video_id); + caption.serializeToStream(stream); } } - public static class TL_inputPrivacyValueAllowAll extends InputPrivacyRule { - public static int constructor = 0x184b35ce; + public static class TL_pageBlockUnsupported extends PageBlock { + public static int constructor = 0x13567e8a; public void serializeToStream(AbstractSerializedData stream) { @@ -8640,18 +10305,24 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputPrivacyValueAllowContacts extends InputPrivacyRule { - public static int constructor = 0xd09e07b; + public static class TL_pageBlockParagraph extends PageBlock { + public static int constructor = 0x467a0766; - public void serializeToStream(AbstractSerializedData stream) { + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + text.serializeToStream(stream); } } - public static class TL_inputPrivacyValueAllowUsers extends InputPrivacyRule { - public static int constructor = 0x131cc67f; + public static class TL_pageBlockCollage extends PageBlock { + public static int constructor = 0x8b31c4f; + public ArrayList items = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { int magic = stream.readInt32(exception); @@ -8663,194 +10334,170 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - InputUser object = InputUser.TLdeserialize(stream, stream.readInt32(exception), exception); + PageBlock object = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } - users.add(object); + items.add(object); } + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(0x1cb5c415); - int count = users.size(); + int count = items.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - users.get(a).serializeToStream(stream); + items.get(a).serializeToStream(stream); } + caption.serializeToStream(stream); } } - public static class TL_maskCoords extends TLObject { - public static int constructor = 0xaed6dbb2; - - public int n; - public double x; - public double y; - public double zoom; + public static class TL_pageBlockFooter extends PageBlock { + public static int constructor = 0x48870999; - public static TL_maskCoords TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_maskCoords.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_maskCoords", constructor)); - } else { - return null; - } - } - TL_maskCoords result = new TL_maskCoords(); - result.readParams(stream, exception); - return result; - } public void readParams(AbstractSerializedData stream, boolean exception) { - n = stream.readInt32(exception); - x = stream.readDouble(exception); - y = stream.readDouble(exception); - zoom = stream.readDouble(exception); + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(n); - stream.writeDouble(x); - stream.writeDouble(y); - stream.writeDouble(zoom); + text.serializeToStream(stream); } } - public static class TL_highScore extends TLObject { - public static int constructor = 0x58fffcd0; + public static class TL_pageBlockList extends PageBlock { + public static int constructor = 0x3a58c7f4; - public int pos; - public int user_id; - public int score; + public ArrayList items = new ArrayList<>(); - public static TL_highScore TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_highScore.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_highScore", constructor)); - } else { - return null; + public void readParams(AbstractSerializedData stream, boolean exception) { + ordered = stream.readBool(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + RichText object = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + items.add(object); } - TL_highScore result = new TL_highScore(); - result.readParams(stream, exception); - return result; - } - - public void readParams(AbstractSerializedData stream, boolean exception) { - pos = stream.readInt32(exception); - user_id = stream.readInt32(exception); - score = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(pos); - stream.writeInt32(user_id); - stream.writeInt32(score); + stream.writeBool(ordered); + stream.writeInt32(0x1cb5c415); + int count = items.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + items.get(a).serializeToStream(stream); + } } } - public static class InputMedia extends TLObject { - public String phone_number; - public String first_name; - public String last_name; - public int flags; - public InputFile file; - public InputFile thumb; - public String mime_type; - public ArrayList attributes = new ArrayList<>(); - public String caption; - public ArrayList stickers = new ArrayList<>(); - public String q; - public InputGeoPoint geo_point; - public String title; - public String address; - public String provider; - public String venue_id; + public static class TL_pageBlockEmbed_layer60 extends TL_pageBlockEmbed { + public static int constructor = 0xd935d8fb; - public static InputMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - InputMedia result = null; - switch(constructor) { - case 0xa6e45987: - result = new TL_inputMediaContact(); - break; - case 0x50d88cae: - result = new TL_inputMediaUploadedThumbDocument(); - break; - case 0x1a77f29c: - result = new TL_inputMediaDocument(); - break; - case 0xd33f43f3: - result = new TL_inputMediaGame(); - break; - case 0x4843b0fd: - result = new TL_inputMediaGifExternal(); - break; - case 0xf9c44144: - result = new TL_inputMediaGeoPoint(); - break; - case 0x7477f92c: - result = new TL_inputMediaDocumentExternal(); - break; - case 0x9664f57f: - result = new TL_inputMediaEmpty(); - break; - case 0x630c9af1: - result = new TL_inputMediaUploadedPhoto(); - break; - case 0x2827a81a: - result = new TL_inputMediaVenue(); - break; - case 0xd070f1e9: - result = new TL_inputMediaUploadedDocument(); - break; - case 0x3b7c62be: - result = new TL_inputMediaPhotoExternal(); - break; - case 0xe9bfb4f3: - result = new TL_inputMediaPhoto(); - break; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + full_width = (flags & 1) != 0; + allow_scrolling = (flags & 8) != 0; + if ((flags & 2) != 0) { + url = stream.readString(exception); } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in InputMedia", constructor)); + if ((flags & 4) != 0) { + html = stream.readString(exception); } - if (result != null) { - result.readParams(stream, exception); + w = stream.readInt32(exception); + h = stream.readInt32(exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = full_width ? (flags | 1) : (flags &~ 1); + flags = allow_scrolling ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + if ((flags & 2) != 0) { + stream.writeString(url); } - return result; + if ((flags & 4) != 0) { + stream.writeString(html); + } + stream.writeInt32(w); + stream.writeInt32(h); + caption.serializeToStream(stream); } } - public static class TL_inputMediaContact extends InputMedia { - public static int constructor = 0xa6e45987; + public static class TL_pageBlockPhoto extends PageBlock { + public static int constructor = 0xe9c69982; public void readParams(AbstractSerializedData stream, boolean exception) { - phone_number = stream.readString(exception); - first_name = stream.readString(exception); - last_name = stream.readString(exception); + photo_id = stream.readInt64(exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(phone_number); - stream.writeString(first_name); - stream.writeString(last_name); + stream.writeInt64(photo_id); + caption.serializeToStream(stream); } } - public static class TL_inputMediaUploadedThumbDocument extends InputMedia { - public static int constructor = 0x50d88cae; + public static class TL_pageBlockSubtitle extends PageBlock { + public static int constructor = 0x8ffa9a1f; public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); - thumb = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); - mime_type = stream.readString(exception); + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + } + } + + public static class TL_pageBlockBlockquote extends PageBlock { + public static int constructor = 0x263d7c26; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + text.serializeToStream(stream); + caption.serializeToStream(stream); + } + } + + public static class TL_pageBlockEmbedPost extends PageBlock { + public static int constructor = 0x292c7be9; + + public String author; + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + webpage_id = stream.readInt64(exception); + author_photo_id = stream.readInt64(exception); + author = stream.readString(exception); + date = stream.readInt32(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -8860,216 +10507,166 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + PageBlock object = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } - attributes.add(object); - } - caption = stream.readString(exception); - if ((flags & 1) != 0) { - magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - stickers.add(object); - } + blocks.add(object); } + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(flags); - file.serializeToStream(stream); - thumb.serializeToStream(stream); - stream.writeString(mime_type); + stream.writeString(url); + stream.writeInt64(webpage_id); + stream.writeInt64(author_photo_id); + stream.writeString(author); + stream.writeInt32(date); stream.writeInt32(0x1cb5c415); - int count = attributes.size(); + int count = blocks.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - attributes.get(a).serializeToStream(stream); - } - stream.writeString(caption); - if ((flags & 1) != 0) { - stream.writeInt32(0x1cb5c415); - count = stickers.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stickers.get(a).serializeToStream(stream); - } + blocks.get(a).serializeToStream(stream); } + caption.serializeToStream(stream); } } - public static class TL_inputMediaDocument extends InputMedia { - public static int constructor = 0x1a77f29c; + public static class TL_pageBlockTitle extends PageBlock { + public static int constructor = 0x70abc3fd; - public InputDocument id; public void readParams(AbstractSerializedData stream, boolean exception) { - id = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); - caption = stream.readString(exception); + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - id.serializeToStream(stream); - stream.writeString(caption); + text.serializeToStream(stream); } } - public static class TL_inputMediaGame extends InputMedia { - public static int constructor = 0xd33f43f3; + public static class TL_pageBlockCover extends PageBlock { + public static int constructor = 0x39f23300; - public InputGame id; public void readParams(AbstractSerializedData stream, boolean exception) { - id = InputGame.TLdeserialize(stream, stream.readInt32(exception), exception); + cover = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - id.serializeToStream(stream); + cover.serializeToStream(stream); } } - public static class TL_inputMediaGifExternal extends InputMedia { - public static int constructor = 0x4843b0fd; + public static class TL_pageBlockSubheader extends PageBlock { + public static int constructor = 0xf12bb6e1; - public String url; public void readParams(AbstractSerializedData stream, boolean exception) { - url = stream.readString(exception); - q = stream.readString(exception); + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(url); - stream.writeString(q); + text.serializeToStream(stream); } } - public static class TL_inputMediaGeoPoint extends InputMedia { - public static int constructor = 0xf9c44144; + public static class TL_pageBlockSlideshow extends PageBlock { + public static int constructor = 0x130c8963; + public ArrayList items = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { - geo_point = InputGeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + PageBlock object = PageBlock.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + items.add(object); + } + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - geo_point.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = items.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + items.get(a).serializeToStream(stream); + } + caption.serializeToStream(stream); } } - public static class TL_inputMediaDocumentExternal extends InputMedia { - public static int constructor = 0x7477f92c; + public static class TL_pageBlockPullquote extends PageBlock { + public static int constructor = 0x4f4456d3; - public InputFile url; public void readParams(AbstractSerializedData stream, boolean exception) { - url = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - url.serializeToStream(stream); + text = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); + caption = RichText.TLdeserialize(stream, stream.readInt32(exception), exception); } - } - - public static class TL_inputMediaEmpty extends InputMedia { - public static int constructor = 0x9664f57f; - public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + text.serializeToStream(stream); + caption.serializeToStream(stream); } } - public static class TL_inputMediaUploadedPhoto extends InputMedia { - public static int constructor = 0x630c9af1; - + public static class InputPrivacyRule extends TLObject { + public ArrayList users = new ArrayList<>(); - public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); - caption = stream.readString(exception); - if ((flags & 1) != 0) { - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - stickers.add(object); - } + public static InputPrivacyRule TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + InputPrivacyRule result = null; + switch(constructor) { + case 0x90110467: + result = new TL_inputPrivacyValueDisallowUsers(); + break; + case 0xd66b66c9: + result = new TL_inputPrivacyValueDisallowAll(); + break; + case 0xba52007: + result = new TL_inputPrivacyValueDisallowContacts(); + break; + case 0x184b35ce: + result = new TL_inputPrivacyValueAllowAll(); + break; + case 0xd09e07b: + result = new TL_inputPrivacyValueAllowContacts(); + break; + case 0x131cc67f: + result = new TL_inputPrivacyValueAllowUsers(); + break; } - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(flags); - file.serializeToStream(stream); - stream.writeString(caption); - if ((flags & 1) != 0) { - stream.writeInt32(0x1cb5c415); - int count = stickers.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stickers.get(a).serializeToStream(stream); - } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in InputPrivacyRule", constructor)); } + if (result != null) { + result.readParams(stream, exception); + } + return result; } } - public static class TL_inputMediaVenue extends InputMedia { - public static int constructor = 0x2827a81a; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - geo_point = InputGeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); - title = stream.readString(exception); - address = stream.readString(exception); - provider = stream.readString(exception); - venue_id = stream.readString(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - geo_point.serializeToStream(stream); - stream.writeString(title); - stream.writeString(address); - stream.writeString(provider); - stream.writeString(venue_id); - } - } - - public static class TL_inputMediaUploadedDocument extends InputMedia { - public static int constructor = 0xd070f1e9; + public static class TL_inputPrivacyValueDisallowUsers extends InputPrivacyRule { + public static int constructor = 0x90110467; public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); - mime_type = stream.readString(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -9079,118 +10676,66 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + InputUser object = InputUser.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } - attributes.add(object); - } - caption = stream.readString(exception); - if ((flags & 1) != 0) { - magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - stickers.add(object); - } + users.add(object); } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(flags); - file.serializeToStream(stream); - stream.writeString(mime_type); stream.writeInt32(0x1cb5c415); - int count = attributes.size(); + int count = users.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - attributes.get(a).serializeToStream(stream); - } - stream.writeString(caption); - if ((flags & 1) != 0) { - stream.writeInt32(0x1cb5c415); - count = stickers.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stickers.get(a).serializeToStream(stream); - } + users.get(a).serializeToStream(stream); } } } - public static class TL_inputMediaPhotoExternal extends InputMedia { - public static int constructor = 0x3b7c62be; - - public String url; + public static class TL_inputPrivacyValueDisallowAll extends InputPrivacyRule { + public static int constructor = 0xd66b66c9; - public void readParams(AbstractSerializedData stream, boolean exception) { - url = stream.readString(exception); - } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(url); } } - public static class TL_inputMediaPhoto extends InputMedia { - public static int constructor = 0xe9bfb4f3; + public static class TL_inputPrivacyValueDisallowContacts extends InputPrivacyRule { + public static int constructor = 0xba52007; - public InputPhoto id; - public void readParams(AbstractSerializedData stream, boolean exception) { - id = InputPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); - caption = stream.readString(exception); + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); } + } + + public static class TL_inputPrivacyValueAllowAll extends InputPrivacyRule { + public static int constructor = 0x184b35ce; + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - id.serializeToStream(stream); - stream.writeString(caption); } } - public static class StickerSetCovered extends TLObject { - public StickerSet set; - public ArrayList covers = new ArrayList<>(); - public Document cover; + public static class TL_inputPrivacyValueAllowContacts extends InputPrivacyRule { + public static int constructor = 0xd09e07b; - public static StickerSetCovered TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - StickerSetCovered result = null; - switch(constructor) { - case 0x3407e51b: - result = new TL_stickerSetMultiCovered(); - break; - case 0x6410a5d2: - result = new TL_stickerSetCovered(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in StickerSetCovered", constructor)); - } - if (result != null) { - result.readParams(stream, exception); - } - return result; + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); } } - public static class TL_stickerSetMultiCovered extends StickerSetCovered { - public static int constructor = 0x3407e51b; + public static class TL_inputPrivacyValueAllowUsers extends InputPrivacyRule { + public static int constructor = 0x131cc67f; public void readParams(AbstractSerializedData stream, boolean exception) { - set = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -9200,63 +10745,600 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - Document object = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + InputUser object = InputUser.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } - covers.add(object); + users.add(object); } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - set.serializeToStream(stream); stream.writeInt32(0x1cb5c415); - int count = covers.size(); + int count = users.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - covers.get(a).serializeToStream(stream); + users.get(a).serializeToStream(stream); } } } - public static class TL_stickerSetCovered extends StickerSetCovered { - public static int constructor = 0x6410a5d2; + public static class TL_maskCoords extends TLObject { + public static int constructor = 0xaed6dbb2; + public int n; + public double x; + public double y; + public double zoom; + + public static TL_maskCoords TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_maskCoords.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_maskCoords", constructor)); + } else { + return null; + } + } + TL_maskCoords result = new TL_maskCoords(); + result.readParams(stream, exception); + return result; + } public void readParams(AbstractSerializedData stream, boolean exception) { - set = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); - cover = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + n = stream.readInt32(exception); + x = stream.readDouble(exception); + y = stream.readDouble(exception); + zoom = stream.readDouble(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - set.serializeToStream(stream); - cover.serializeToStream(stream); + stream.writeInt32(n); + stream.writeDouble(x); + stream.writeDouble(y); + stream.writeDouble(zoom); } } - public static class geochats_Messages extends TLObject { - public int count; - public ArrayList messages = new ArrayList<>(); - public ArrayList chats = new ArrayList<>(); - public ArrayList users = new ArrayList<>(); + public static class TL_highScore extends TLObject { + public static int constructor = 0x58fffcd0; - public static geochats_Messages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - geochats_Messages result = null; - switch(constructor) { - case 0xbc5863e8: - result = new TL_geochats_messagesSlice(); - break; - case 0xd1526db1: - result = new TL_geochats_messages(); - break; - } - if (result == null && exception) { - throw new RuntimeException(String.format("can't parse magic %x in geochats_Messages", constructor)); - } - if (result != null) { - result.readParams(stream, exception); + public int pos; + public int user_id; + public int score; + + public static TL_highScore TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_highScore.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_highScore", constructor)); + } else { + return null; + } + } + TL_highScore result = new TL_highScore(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + pos = stream.readInt32(exception); + user_id = stream.readInt32(exception); + score = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(pos); + stream.writeInt32(user_id); + stream.writeInt32(score); + } + } + + public static class InputMedia extends TLObject { + public String phone_number; + public String first_name; + public String last_name; + public int flags; + public InputFile file; + public InputFile thumb; + public String mime_type; + public ArrayList attributes = new ArrayList<>(); + public String caption; + public ArrayList stickers = new ArrayList<>(); + public String q; + public InputGeoPoint geo_point; + public String title; + public String address; + public String provider; + public String venue_id; + + public static InputMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + InputMedia result = null; + switch(constructor) { + case 0xa6e45987: + result = new TL_inputMediaContact(); + break; + case 0x50d88cae: + result = new TL_inputMediaUploadedThumbDocument(); + break; + case 0x1a77f29c: + result = new TL_inputMediaDocument(); + break; + case 0xd33f43f3: + result = new TL_inputMediaGame(); + break; + case 0x4843b0fd: + result = new TL_inputMediaGifExternal(); + break; + case 0xf9c44144: + result = new TL_inputMediaGeoPoint(); + break; + case 0x7477f92c: + result = new TL_inputMediaDocumentExternal(); + break; + case 0x9664f57f: + result = new TL_inputMediaEmpty(); + break; + case 0x630c9af1: + result = new TL_inputMediaUploadedPhoto(); + break; + case 0x2827a81a: + result = new TL_inputMediaVenue(); + break; + case 0xd070f1e9: + result = new TL_inputMediaUploadedDocument(); + break; + case 0x3b7c62be: + result = new TL_inputMediaPhotoExternal(); + break; + case 0xe9bfb4f3: + result = new TL_inputMediaPhoto(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in InputMedia", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_inputMediaContact extends InputMedia { + public static int constructor = 0xa6e45987; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + phone_number = stream.readString(exception); + first_name = stream.readString(exception); + last_name = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(phone_number); + stream.writeString(first_name); + stream.writeString(last_name); + } + } + + public static class TL_inputMediaUploadedThumbDocument extends InputMedia { + public static int constructor = 0x50d88cae; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); + thumb = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); + mime_type = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + attributes.add(object); + } + caption = stream.readString(exception); + if ((flags & 1) != 0) { + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + stickers.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + file.serializeToStream(stream); + thumb.serializeToStream(stream); + stream.writeString(mime_type); + stream.writeInt32(0x1cb5c415); + int count = attributes.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + attributes.get(a).serializeToStream(stream); + } + stream.writeString(caption); + if ((flags & 1) != 0) { + stream.writeInt32(0x1cb5c415); + count = stickers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stickers.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_inputMediaDocument extends InputMedia { + public static int constructor = 0x1a77f29c; + + public InputDocument id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + caption = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + id.serializeToStream(stream); + stream.writeString(caption); + } + } + + public static class TL_inputMediaGame extends InputMedia { + public static int constructor = 0xd33f43f3; + + public InputGame id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = InputGame.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + id.serializeToStream(stream); + } + } + + public static class TL_inputMediaGifExternal extends InputMedia { + public static int constructor = 0x4843b0fd; + + public String url; + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + q = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeString(q); + } + } + + public static class TL_inputMediaGeoPoint extends InputMedia { + public static int constructor = 0xf9c44144; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo_point = InputGeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo_point.serializeToStream(stream); + } + } + + public static class TL_inputMediaDocumentExternal extends InputMedia { + public static int constructor = 0x7477f92c; + + public InputFile url; + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + url.serializeToStream(stream); + } + } + + public static class TL_inputMediaEmpty extends InputMedia { + public static int constructor = 0x9664f57f; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_inputMediaUploadedPhoto extends InputMedia { + public static int constructor = 0x630c9af1; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); + caption = stream.readString(exception); + if ((flags & 1) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + stickers.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + file.serializeToStream(stream); + stream.writeString(caption); + if ((flags & 1) != 0) { + stream.writeInt32(0x1cb5c415); + int count = stickers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stickers.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_inputMediaVenue extends InputMedia { + public static int constructor = 0x2827a81a; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + geo_point = InputGeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + title = stream.readString(exception); + address = stream.readString(exception); + provider = stream.readString(exception); + venue_id = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + geo_point.serializeToStream(stream); + stream.writeString(title); + stream.writeString(address); + stream.writeString(provider); + stream.writeString(venue_id); + } + } + + public static class TL_inputMediaUploadedDocument extends InputMedia { + public static int constructor = 0xd070f1e9; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + file = InputFile.TLdeserialize(stream, stream.readInt32(exception), exception); + mime_type = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + attributes.add(object); + } + caption = stream.readString(exception); + if ((flags & 1) != 0) { + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + InputDocument object = InputDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + stickers.add(object); + } + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + file.serializeToStream(stream); + stream.writeString(mime_type); + stream.writeInt32(0x1cb5c415); + int count = attributes.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + attributes.get(a).serializeToStream(stream); + } + stream.writeString(caption); + if ((flags & 1) != 0) { + stream.writeInt32(0x1cb5c415); + count = stickers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stickers.get(a).serializeToStream(stream); + } + } + } + } + + public static class TL_inputMediaPhotoExternal extends InputMedia { + public static int constructor = 0x3b7c62be; + + public String url; + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + } + } + + public static class TL_inputMediaPhoto extends InputMedia { + public static int constructor = 0xe9bfb4f3; + + public InputPhoto id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = InputPhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + caption = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + id.serializeToStream(stream); + stream.writeString(caption); + } + } + + public static class StickerSetCovered extends TLObject { + public StickerSet set; + public ArrayList covers = new ArrayList<>(); + public Document cover; + + public static StickerSetCovered TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + StickerSetCovered result = null; + switch(constructor) { + case 0x3407e51b: + result = new TL_stickerSetMultiCovered(); + break; + case 0x6410a5d2: + result = new TL_stickerSetCovered(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in StickerSetCovered", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_stickerSetMultiCovered extends StickerSetCovered { + public static int constructor = 0x3407e51b; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + set = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Document object = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + covers.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + set.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = covers.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + covers.get(a).serializeToStream(stream); + } + } + } + + public static class TL_stickerSetCovered extends StickerSetCovered { + public static int constructor = 0x6410a5d2; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + set = StickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + cover = Document.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + set.serializeToStream(stream); + cover.serializeToStream(stream); + } + } + + public static class geochats_Messages extends TLObject { + public int count; + public ArrayList messages = new ArrayList<>(); + public ArrayList chats = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + + public static geochats_Messages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + geochats_Messages result = null; + switch(constructor) { + case 0xbc5863e8: + result = new TL_geochats_messagesSlice(); + break; + case 0xd1526db1: + result = new TL_geochats_messages(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in geochats_Messages", constructor)); + } + if (result != null) { + result.readParams(stream, exception); } return result; } @@ -9830,130 +11912,160 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class Update extends TLObject { - public int chat_id; - public int user_id; - public long chat_instance; - public int inviter_id; - public int date; - public int version; - public DraftMessage draft; + public ArrayList messages = new ArrayList<>(); public int pts; public int pts_count; + public int chat_id; + public boolean enabled; + public int version; + public int flags; public long query_id; + public int user_id; + public long chat_instance; public byte[] data; public String game_short_name; - public int flags; - public String query; - public GeoPoint geo; - public String offset; - public PeerNotifySettings notify_settings; - public int channel_id; - public SendMessageAction action; - public boolean blocked; - public long auth_key_id; - public String device; - public String location; public int max_id; - public int qts; - public boolean enabled; + public boolean pinned; + public String phone; public long random_id; - public ArrayList dc_options = new ArrayList<>(); - public ChatParticipants participants; - public PrivacyKey key; - public ArrayList rules = new ArrayList<>(); + public int channel_id; + public int qts; public UserStatus status; public int views; + public PeerNotifySettings notify_settings; + public int date; + public String query; + public GeoPoint geo; + public WebPage webpage; + public int inviter_id; + public SendMessageAction action; + public EncryptedChat chat; + public boolean popup; + public int inbox_date; public String type; public MessageMedia media; - public boolean popup; + public ArrayList entities = new ArrayList<>(); public boolean is_admin; - public TL_messages_stickerSet stickerset; - public ContactLink my_link; - public ContactLink foreign_link; + public String offset; + public PrivacyKey key; + public ArrayList rules = new ArrayList<>(); + public DraftMessage draft; public String first_name; public String last_name; public String username; - public ArrayList messages = new ArrayList<>(); - public String phone; - public WebPage webpage; - public EncryptedChat chat; - public ArrayList order = new ArrayList<>(); - public int max_date; - public boolean masks; + public PhoneCall phone_call; + public ContactLink my_link; + public ContactLink foreign_link; public UserProfilePhoto photo; public boolean previous; + public ArrayList dc_options = new ArrayList<>(); + public boolean blocked; + public TL_messages_stickerSet stickerset; + public int max_date; + public boolean masks; + public ChatParticipants participants; public static Update TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { Update result = null; switch(constructor) { - case 0xea4b0e5c: - result = new TL_updateChatParticipantAdd(); + case 0xa20db0e5: + result = new TL_updateDeleteMessages(); break; - case 0xee2bb969: - result = new TL_updateDraftMessage(); + case 0x571d2742: + result = new TL_updateReadFeaturedStickers(); break; - case 0x43ae3dec: - result = new TL_updateStickerSets(); + case 0x6e947941: + result = new TL_updateChatAdmins(); break; - case 0x1b3f4df7: - result = new TL_updateEditChannelMessage(); + case 0xf9d27a5a: + result = new TL_updateInlineBotCallbackQuery(); + break; + case 0x1710f156: + result = new TL_updateEncryptedChatTyping(); break; case 0xe73547e1: result = new TL_updateBotCallbackQuery(); break; - case 0x571d2742: - result = new TL_updateReadFeaturedStickers(); + case 0x62ba04d9: + result = new TL_updateNewChannelMessage(); break; - case 0x9a422c20: - result = new TL_updateRecentStickers(); + case 0x2f2f21bf: + result = new TL_updateReadHistoryOutbox(); break; - case 0x54826690: - result = new TL_updateBotInlineQuery(); + case 0xd711a2cc: + result = new TL_updateDialogPinned(); + break; + case 0x12b9417b: + result = new TL_updateUserPhone(); + break; + case 0x4e90bfd6: + result = new TL_updateMessageID(); + break; + case 0x25d6c9c7: + result = new TL_updateReadChannelOutbox(); + break; + case 0x43ae3dec: + result = new TL_updateStickerSets(); + break; + case 0x1f2b0afd: + result = new TL_updateNewMessage(); + break; + case 0x12bcbd9a: + result = new TL_updateNewEncryptedMessage(); + break; + case 0x1bfbd823: + result = new TL_updateUserStatus(); + break; + case 0x98a12b4b: + result = new TL_updateChannelMessageViews(); + break; + case 0x3354678f: + result = new TL_updatePtsChanged(); break; case 0xbec268ef: result = new TL_updateNotifySettings(); break; - case 0xb6d45656: - result = new TL_updateChannel(); + case 0x2575bbb9: + result = new TL_updateContactRegistered(); break; case 0x6e5f8c22: result = new TL_updateChatParticipantDelete(); break; - case 0x5c486927: - result = new TL_updateUserTyping(); + case 0xe40370a3: + result = new TL_updateEditMessage(); break; - case 0x80ece81a: - result = new TL_updateUserBlocked(); + case 0xe48f964: + result = new TL_updateBotInlineSend(); break; - case 0x8f06529a: - result = new TL_updateNewAuthorization(); + case 0x7f891213: + result = new TL_updateWebPage(); break; - case 0x2575bbb9: - result = new TL_updateContactRegistered(); + case 0xea4b0e5c: + result = new TL_updateChatParticipantAdd(); break; - case 0x4214f37f: - result = new TL_updateReadChannelInbox(); + case 0x9a65ea1f: + result = new TL_updateChatUserTyping(); break; - case 0x12bcbd9a: - result = new TL_updateNewEncryptedMessage(); + case 0xb4a2e88d: + result = new TL_updateEncryption(); break; - case 0x6e947941: - result = new TL_updateChatAdmins(); + case 0xeb0467fb: + result = new TL_updateChannelTooLong(); break; - case 0x9375341e: - result = new TL_updateSavedGifs(); + case 0x5c486927: + result = new TL_updateUserTyping(); break; - case 0x62ba04d9: - result = new TL_updateNewChannelMessage(); + case 0xebe46819: + result = new TL_updateServiceNotification(); break; - case 0x4e90bfd6: - result = new TL_updateMessageID(); + case 0x98592475: + result = new TL_updateChannelPinnedMessage(); break; - case 0x8e5e9873: - result = new TL_updateDcOptions(); + case 0xb6901959: + result = new TL_updateChatParticipantAdmin(); break; - case 0x7761198: - result = new TL_updateChatParticipants(); + case 0x54826690: + result = new TL_updateBotInlineQuery(); break; case 0xee3b272a: result = new TL_updatePrivacy(); @@ -9961,92 +12073,71 @@ public static Update TLdeserialize(AbstractSerializedData stream, int constructo case 0xa229dd06: result = new TL_updateConfig(); break; - case 0xf9d27a5a: - result = new TL_updateInlineBotCallbackQuery(); - break; - case 0x25d6c9c7: - result = new TL_updateReadChannelOutbox(); - break; - case 0x1710f156: - result = new TL_updateEncryptedChatTyping(); - break; - case 0x5a68e3f7: - result = new TL_updateNewGeoChatMessage(); - break; - case 0x1bfbd823: - result = new TL_updateUserStatus(); - break; - case 0x98a12b4b: - result = new TL_updateChannelMessageViews(); - break; - case 0x3354678f: - result = new TL_updatePtsChanged(); + case 0xee2bb969: + result = new TL_updateDraftMessage(); break; - case 0xeb0467fb: - result = new TL_updateChannelTooLong(); + case 0xa7332b73: + result = new TL_updateUserName(); break; - case 0x382dd3e4: - result = new TL_updateServiceNotification(); + case 0xab0f6b1e: + result = new TL_updatePhoneCall(); break; - case 0xb6901959: - result = new TL_updateChatParticipantAdmin(); + case 0xd8caf68d: + result = new TL_updatePinnedDialogs(); break; - case 0x688a30aa: - result = new TL_updateNewStickerSet(); + case 0x9a422c20: + result = new TL_updateRecentStickers(); break; - case 0x9d2e67c5: - result = new TL_updateContactLink(); + case 0x5a68e3f7: + result = new TL_updateNewGeoChatMessage(); break; case 0x9961fd5c: result = new TL_updateReadHistoryInbox(); break; - case 0xa7332b73: - result = new TL_updateUserName(); + case 0x9d2e67c5: + result = new TL_updateContactLink(); break; - case 0xe48f964: - result = new TL_updateBotInlineSend(); + case 0x9375341e: + result = new TL_updateSavedGifs(); break; - case 0xe40370a3: - result = new TL_updateEditMessage(); + case 0xb6d45656: + result = new TL_updateChannel(); break; - case 0x98592475: - result = new TL_updateChannelPinnedMessage(); + case 0x40771900: + result = new TL_updateChannelWebPage(); break; case 0xc37521c9: result = new TL_updateDeleteChannelMessages(); break; - case 0x12b9417b: - result = new TL_updateUserPhone(); + case 0x95313b0c: + result = new TL_updateUserPhoto(); break; - case 0x7f891213: - result = new TL_updateWebPage(); + case 0x8e5e9873: + result = new TL_updateDcOptions(); break; - case 0xb4a2e88d: - result = new TL_updateEncryption(); + case 0x1b3f4df7: + result = new TL_updateEditChannelMessage(); break; - case 0x1f2b0afd: - result = new TL_updateNewMessage(); + case 0x80ece81a: + result = new TL_updateUserBlocked(); break; - case 0xbb2d201: - result = new TL_updateStickerSetsOrder(); + case 0x688a30aa: + result = new TL_updateNewStickerSet(); break; case 0x38fe25b7: result = new TL_updateEncryptedMessagesRead(); break; - case 0x68c13933: - result = new TL_updateReadMessagesContents(); - break; - case 0xa20db0e5: - result = new TL_updateDeleteMessages(); + case 0xbb2d201: + result = new TL_updateStickerSetsOrder(); break; - case 0x9a65ea1f: - result = new TL_updateChatUserTyping(); + case 0x4214f37f: + result = new TL_updateReadChannelInbox(); break; - case 0x95313b0c: - result = new TL_updateUserPhoto(); + case 0x68c13933: + result = new TL_updateReadMessagesContents(); break; - case 0x2f2f21bf: - result = new TL_updateReadHistoryOutbox(); + case 0x7761198: + result = new TL_updateChatParticipants(); break; } if (result == null && exception) { @@ -10055,74 +12146,116 @@ public static Update TLdeserialize(AbstractSerializedData stream, int constructo if (result != null) { result.readParams(stream, exception); } - return result; + return result; + } + } + + public static class TL_updateDeleteMessages extends Update { + public static int constructor = 0xa20db0e5; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + messages.add(stream.readInt32(exception)); + } + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(messages.get(a)); + } + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateChatParticipantAdd extends Update { - public static int constructor = 0xea4b0e5c; - + public static class TL_updateReadFeaturedStickers extends Update { + public static int constructor = 0x571d2742; - public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - user_id = stream.readInt32(exception); - inviter_id = stream.readInt32(exception); - date = stream.readInt32(exception); - version = stream.readInt32(exception); - } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(chat_id); - stream.writeInt32(user_id); - stream.writeInt32(inviter_id); - stream.writeInt32(date); - stream.writeInt32(version); } } - public static class TL_updateDraftMessage extends Update { - public static int constructor = 0xee2bb969; + public static class TL_updateChatAdmins extends Update { + public static int constructor = 0x6e947941; - public Peer peer; public void readParams(AbstractSerializedData stream, boolean exception) { - peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); - draft = DraftMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + chat_id = stream.readInt32(exception); + enabled = stream.readBool(exception); + version = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - peer.serializeToStream(stream); - draft.serializeToStream(stream); + stream.writeInt32(chat_id); + stream.writeBool(enabled); + stream.writeInt32(version); } } - public static class TL_updateStickerSets extends Update { - public static int constructor = 0x43ae3dec; + public static class TL_updateInlineBotCallbackQuery extends Update { + public static int constructor = 0xf9d27a5a; + + public TL_inputBotInlineMessageID msg_id; + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + query_id = stream.readInt64(exception); + user_id = stream.readInt32(exception); + msg_id = TL_inputBotInlineMessageID.TLdeserialize(stream, stream.readInt32(exception), exception); + chat_instance = stream.readInt64(exception); + if ((flags & 1) != 0) { + data = stream.readByteArray(exception); + } + if ((flags & 2) != 0) { + game_short_name = stream.readString(exception); + } + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(query_id); + stream.writeInt32(user_id); + msg_id.serializeToStream(stream); + stream.writeInt64(chat_instance); + if ((flags & 1) != 0) { + stream.writeByteArray(data); + } + if ((flags & 2) != 0) { + stream.writeString(game_short_name); + } } } - public static class TL_updateEditChannelMessage extends Update { - public static int constructor = 0x1b3f4df7; + public static class TL_updateEncryptedChatTyping extends Update { + public static int constructor = 0x1710f156; - public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); + chat_id = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); - stream.writeInt32(pts); - stream.writeInt32(pts_count); + stream.writeInt32(chat_id); } } @@ -10164,270 +12297,433 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_updateReadFeaturedStickers extends Update { - public static int constructor = 0x571d2742; + public static class TL_updateNewChannelMessage extends Update { + public static int constructor = 0x62ba04d9; + public Message message; + + public void readParams(AbstractSerializedData stream, boolean exception) { + message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + message.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateRecentStickers extends Update { - public static int constructor = 0x9a422c20; + public static class TL_updateReadHistoryOutbox extends Update { + public static int constructor = 0x2f2f21bf; + + public Peer peer; + public void readParams(AbstractSerializedData stream, boolean exception) { + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + max_id = stream.readInt32(exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(max_id); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateBotInlineQuery extends Update { - public static int constructor = 0x54826690; + public static class TL_updateDialogPinned extends Update { + public static int constructor = 0xd711a2cc; + public Peer peer; public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); - query_id = stream.readInt64(exception); - user_id = stream.readInt32(exception); - query = stream.readString(exception); - if ((flags & 1) != 0) { - geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); - } - offset = stream.readString(exception); + pinned = (flags & 1) != 0; + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = pinned ? (flags | 1) : (flags &~ 1); stream.writeInt32(flags); - stream.writeInt64(query_id); + peer.serializeToStream(stream); + } + } + + public static class TL_updateUserPhone extends Update { + public static int constructor = 0x12b9417b; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt32(exception); + phone = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); stream.writeInt32(user_id); - stream.writeString(query); - if ((flags & 1) != 0) { - geo.serializeToStream(stream); - } - stream.writeString(offset); + stream.writeString(phone); } } - public static class TL_updateNotifySettings extends Update { - public static int constructor = 0xbec268ef; + public static class TL_updateMessageID extends Update { + public static int constructor = 0x4e90bfd6; - public NotifyPeer peer; + public int id; public void readParams(AbstractSerializedData stream, boolean exception) { - peer = NotifyPeer.TLdeserialize(stream, stream.readInt32(exception), exception); - notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + id = stream.readInt32(exception); + random_id = stream.readInt64(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - peer.serializeToStream(stream); - notify_settings.serializeToStream(stream); + stream.writeInt32(id); + stream.writeInt64(random_id); } } - public static class TL_updateChannel extends Update { - public static int constructor = 0xb6d45656; + public static class TL_updateReadChannelOutbox extends Update { + public static int constructor = 0x25d6c9c7; public void readParams(AbstractSerializedData stream, boolean exception) { channel_id = stream.readInt32(exception); + max_id = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(channel_id); + stream.writeInt32(max_id); } } - public static class TL_updateChatParticipantDelete extends Update { - public static int constructor = 0x6e5f8c22; + public static class TL_updateStickerSets extends Update { + public static int constructor = 0x43ae3dec; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_updateNewMessage extends Update { + public static int constructor = 0x1f2b0afd; + + public Message message; + + public void readParams(AbstractSerializedData stream, boolean exception) { + message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + message.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); + } + } + + public static class TL_updateNewEncryptedMessage extends Update { + public static int constructor = 0x12bcbd9a; + + public EncryptedMessage message; + + public void readParams(AbstractSerializedData stream, boolean exception) { + message = EncryptedMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + qts = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + message.serializeToStream(stream); + stream.writeInt32(qts); + } + } + + public static class TL_updateUserStatus extends Update { + public static int constructor = 0x1bfbd823; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + user_id = stream.readInt32(exception); + status = UserStatus.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(user_id); + status.serializeToStream(stream); + } + } + + public static class TL_updateChannelMessageViews extends Update { + public static int constructor = 0x98a12b4b; + + public int id; + + public void readParams(AbstractSerializedData stream, boolean exception) { + channel_id = stream.readInt32(exception); + id = stream.readInt32(exception); + views = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(channel_id); + stream.writeInt32(id); + stream.writeInt32(views); + } + } + + public static class TL_updatePtsChanged extends Update { + public static int constructor = 0x3354678f; + + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_updateNotifySettings extends Update { + public static int constructor = 0xbec268ef; + public NotifyPeer peer; public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - user_id = stream.readInt32(exception); - version = stream.readInt32(exception); + peer = NotifyPeer.TLdeserialize(stream, stream.readInt32(exception), exception); + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(chat_id); - stream.writeInt32(user_id); - stream.writeInt32(version); + peer.serializeToStream(stream); + notify_settings.serializeToStream(stream); } } - public static class TL_updateUserTyping extends Update { - public static int constructor = 0x5c486927; + public static class TL_updateContactRegistered extends Update { + public static int constructor = 0x2575bbb9; public void readParams(AbstractSerializedData stream, boolean exception) { user_id = stream.readInt32(exception); - action = SendMessageAction.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(user_id); - action.serializeToStream(stream); + stream.writeInt32(date); } } - public static class TL_updateUserBlocked extends Update { - public static int constructor = 0x80ece81a; + public static class TL_updateChatParticipantDelete extends Update { + public static int constructor = 0x6e5f8c22; public void readParams(AbstractSerializedData stream, boolean exception) { + chat_id = stream.readInt32(exception); user_id = stream.readInt32(exception); - blocked = stream.readBool(exception); + version = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(chat_id); stream.writeInt32(user_id); - stream.writeBool(blocked); + stream.writeInt32(version); } } - public static class TL_updateNewAuthorization extends Update { - public static int constructor = 0x8f06529a; + public static class TL_updateEditMessage extends Update { + public static int constructor = 0xe40370a3; + public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - auth_key_id = stream.readInt64(exception); - date = stream.readInt32(exception); - device = stream.readString(exception); - location = stream.readString(exception); + message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt64(auth_key_id); - stream.writeInt32(date); - stream.writeString(device); - stream.writeString(location); + message.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateContactRegistered extends Update { - public static int constructor = 0x2575bbb9; + public static class TL_updateBotInlineSend extends Update { + public static int constructor = 0xe48f964; + public String id; + public TL_inputBotInlineMessageID msg_id; public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); user_id = stream.readInt32(exception); - date = stream.readInt32(exception); + query = stream.readString(exception); + if ((flags & 1) != 0) { + geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); + } + id = stream.readString(exception); + if ((flags & 2) != 0) { + msg_id = TL_inputBotInlineMessageID.TLdeserialize(stream, stream.readInt32(exception), exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); stream.writeInt32(user_id); - stream.writeInt32(date); + stream.writeString(query); + if ((flags & 1) != 0) { + geo.serializeToStream(stream); + } + stream.writeString(id); + if ((flags & 2) != 0) { + msg_id.serializeToStream(stream); + } } } - public static class TL_updateReadChannelInbox extends Update { - public static int constructor = 0x4214f37f; + public static class TL_updateWebPage extends Update { + public static int constructor = 0x7f891213; public void readParams(AbstractSerializedData stream, boolean exception) { - channel_id = stream.readInt32(exception); - max_id = stream.readInt32(exception); + webpage = WebPage.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(channel_id); - stream.writeInt32(max_id); + webpage.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateNewEncryptedMessage extends Update { - public static int constructor = 0x12bcbd9a; + public static class TL_updateChatParticipantAdd extends Update { + public static int constructor = 0xea4b0e5c; - public EncryptedMessage message; public void readParams(AbstractSerializedData stream, boolean exception) { - message = EncryptedMessage.TLdeserialize(stream, stream.readInt32(exception), exception); - qts = stream.readInt32(exception); + chat_id = stream.readInt32(exception); + user_id = stream.readInt32(exception); + inviter_id = stream.readInt32(exception); + date = stream.readInt32(exception); + version = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); - stream.writeInt32(qts); + stream.writeInt32(chat_id); + stream.writeInt32(user_id); + stream.writeInt32(inviter_id); + stream.writeInt32(date); + stream.writeInt32(version); } } - public static class TL_updateChatAdmins extends Update { - public static int constructor = 0x6e947941; + public static class TL_updateChatUserTyping extends Update { + public static int constructor = 0x9a65ea1f; public void readParams(AbstractSerializedData stream, boolean exception) { chat_id = stream.readInt32(exception); - enabled = stream.readBool(exception); - version = stream.readInt32(exception); + user_id = stream.readInt32(exception); + action = SendMessageAction.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(chat_id); - stream.writeBool(enabled); - stream.writeInt32(version); + stream.writeInt32(user_id); + action.serializeToStream(stream); } } - public static class TL_updateSavedGifs extends Update { - public static int constructor = 0x9375341e; + public static class TL_updateEncryption extends Update { + public static int constructor = 0xb4a2e88d; + + public void readParams(AbstractSerializedData stream, boolean exception) { + chat = EncryptedChat.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + chat.serializeToStream(stream); + stream.writeInt32(date); } } - public static class TL_updateNewChannelMessage extends Update { - public static int constructor = 0x62ba04d9; + public static class TL_updateChannelTooLong extends Update { + public static int constructor = 0xeb0467fb; - public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); + flags = stream.readInt32(exception); + channel_id = stream.readInt32(exception); + if ((flags & 1) != 0) { + pts = stream.readInt32(exception); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); - stream.writeInt32(pts); - stream.writeInt32(pts_count); + stream.writeInt32(flags); + stream.writeInt32(channel_id); + if ((flags & 1) != 0) { + stream.writeInt32(pts); + } } } - public static class TL_updateMessageID extends Update { - public static int constructor = 0x4e90bfd6; + public static class TL_updateUserTyping extends Update { + public static int constructor = 0x5c486927; - public int id; public void readParams(AbstractSerializedData stream, boolean exception) { - id = stream.readInt32(exception); - random_id = stream.readInt64(exception); + user_id = stream.readInt32(exception); + action = SendMessageAction.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(id); - stream.writeInt64(random_id); + stream.writeInt32(user_id); + action.serializeToStream(stream); } } - public static class TL_updateDcOptions extends Update { - public static int constructor = 0x8e5e9873; + public static class TL_updateServiceNotification extends Update { + public static int constructor = 0xebe46819; + public String message; public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + popup = (flags & 1) != 0; + if ((flags & 2) != 0) { + inbox_date = stream.readInt32(exception); + } + type = stream.readString(exception); + message = stream.readString(exception); + media = MessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -10437,100 +12733,83 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - TL_dcOption object = TL_dcOption.TLdeserialize(stream, stream.readInt32(exception), exception); + MessageEntity object = MessageEntity.TLdeserialize(stream, stream.readInt32(exception), exception); if (object == null) { return; } - dc_options.add(object); + entities.add(object); } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = popup ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + if ((flags & 2) != 0) { + stream.writeInt32(inbox_date); + } + stream.writeString(type); + stream.writeString(message); + media.serializeToStream(stream); stream.writeInt32(0x1cb5c415); - int count = dc_options.size(); + int count = entities.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - dc_options.get(a).serializeToStream(stream); + entities.get(a).serializeToStream(stream); } } } - public static class TL_updateChatParticipants extends Update { - public static int constructor = 0x7761198; + public static class TL_updateChannelPinnedMessage extends Update { + public static int constructor = 0x98592475; + public int id; public void readParams(AbstractSerializedData stream, boolean exception) { - participants = ChatParticipants.TLdeserialize(stream, stream.readInt32(exception), exception); + channel_id = stream.readInt32(exception); + id = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - participants.serializeToStream(stream); + stream.writeInt32(channel_id); + stream.writeInt32(id); } } - public static class TL_updatePrivacy extends Update { - public static int constructor = 0xee3b272a; + public static class TL_updateChatParticipantAdmin extends Update { + public static int constructor = 0xb6901959; public void readParams(AbstractSerializedData stream, boolean exception) { - key = PrivacyKey.TLdeserialize(stream, stream.readInt32(exception), exception); - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - PrivacyRule object = PrivacyRule.TLdeserialize(stream, stream.readInt32(exception), exception); - if (object == null) { - return; - } - rules.add(object); - } - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - key.serializeToStream(stream); - stream.writeInt32(0x1cb5c415); - int count = rules.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - rules.get(a).serializeToStream(stream); - } + chat_id = stream.readInt32(exception); + user_id = stream.readInt32(exception); + is_admin = stream.readBool(exception); + version = stream.readInt32(exception); } - } - - public static class TL_updateConfig extends Update { - public static int constructor = 0xa229dd06; - public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(chat_id); + stream.writeInt32(user_id); + stream.writeBool(is_admin); + stream.writeInt32(version); } } - public static class TL_updateInlineBotCallbackQuery extends Update { - public static int constructor = 0xf9d27a5a; + public static class TL_updateBotInlineQuery extends Update { + public static int constructor = 0x54826690; - public TL_inputBotInlineMessageID msg_id; public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); query_id = stream.readInt64(exception); user_id = stream.readInt32(exception); - msg_id = TL_inputBotInlineMessageID.TLdeserialize(stream, stream.readInt32(exception), exception); - chat_instance = stream.readInt64(exception); + query = stream.readString(exception); if ((flags & 1) != 0) { - data = stream.readByteArray(exception); - } - if ((flags & 2) != 0) { - game_short_name = stream.readString(exception); + geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); } + offset = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -10538,180 +12817,191 @@ public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(flags); stream.writeInt64(query_id); stream.writeInt32(user_id); - msg_id.serializeToStream(stream); - stream.writeInt64(chat_instance); - if ((flags & 1) != 0) { - stream.writeByteArray(data); - } - if ((flags & 2) != 0) { - stream.writeString(game_short_name); + stream.writeString(query); + if ((flags & 1) != 0) { + geo.serializeToStream(stream); } + stream.writeString(offset); } } - public static class TL_updateReadChannelOutbox extends Update { - public static int constructor = 0x25d6c9c7; + public static class TL_updatePrivacy extends Update { + public static int constructor = 0xee3b272a; public void readParams(AbstractSerializedData stream, boolean exception) { - channel_id = stream.readInt32(exception); - max_id = stream.readInt32(exception); + key = PrivacyKey.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + PrivacyRule object = PrivacyRule.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + rules.add(object); + } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(channel_id); - stream.writeInt32(max_id); + key.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = rules.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + rules.get(a).serializeToStream(stream); + } } } - public static class TL_updateEncryptedChatTyping extends Update { - public static int constructor = 0x1710f156; - + public static class TL_updateConfig extends Update { + public static int constructor = 0xa229dd06; - public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(chat_id); } } - public static class TL_updateNewGeoChatMessage extends Update { - public static int constructor = 0x5a68e3f7; + public static class TL_updateDraftMessage extends Update { + public static int constructor = 0xee2bb969; - public GeoChatMessage message; + public Peer peer; public void readParams(AbstractSerializedData stream, boolean exception) { - message = GeoChatMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + draft = DraftMessage.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); + peer.serializeToStream(stream); + draft.serializeToStream(stream); } } - public static class TL_updateUserStatus extends Update { - public static int constructor = 0x1bfbd823; + public static class TL_updateUserName extends Update { + public static int constructor = 0xa7332b73; public void readParams(AbstractSerializedData stream, boolean exception) { user_id = stream.readInt32(exception); - status = UserStatus.TLdeserialize(stream, stream.readInt32(exception), exception); + first_name = stream.readString(exception); + last_name = stream.readString(exception); + username = stream.readString(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(user_id); - status.serializeToStream(stream); + stream.writeString(first_name); + stream.writeString(last_name); + stream.writeString(username); } } - public static class TL_updateChannelMessageViews extends Update { - public static int constructor = 0x98a12b4b; + public static class TL_updatePhoneCall extends Update { + public static int constructor = 0xab0f6b1e; - public int id; public void readParams(AbstractSerializedData stream, boolean exception) { - channel_id = stream.readInt32(exception); - id = stream.readInt32(exception); - views = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(channel_id); - stream.writeInt32(id); - stream.writeInt32(views); + phone_call = PhoneCall.TLdeserialize(stream, stream.readInt32(exception), exception); } - } - - public static class TL_updatePtsChanged extends Update { - public static int constructor = 0x3354678f; - public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + phone_call.serializeToStream(stream); } } - public static class TL_updateChannelTooLong extends Update { - public static int constructor = 0xeb0467fb; + public static class TL_updatePinnedDialogs extends Update { + public static int constructor = 0xd8caf68d; + public ArrayList order = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); - channel_id = stream.readInt32(exception); if ((flags & 1) != 0) { - pts = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Peer object = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + order.add(object); + } } } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(flags); - stream.writeInt32(channel_id); if ((flags & 1) != 0) { - stream.writeInt32(pts); + stream.writeInt32(0x1cb5c415); + int count = order.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + order.get(a).serializeToStream(stream); + } } } } - public static class TL_updateServiceNotification extends Update { - public static int constructor = 0x382dd3e4; - - public String message; + public static class TL_updateRecentStickers extends Update { + public static int constructor = 0x9a422c20; - public void readParams(AbstractSerializedData stream, boolean exception) { - type = stream.readString(exception); - message = stream.readString(exception); - media = MessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); - popup = stream.readBool(exception); - } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeString(type); - stream.writeString(message); - media.serializeToStream(stream); - stream.writeBool(popup); } } - public static class TL_updateChatParticipantAdmin extends Update { - public static int constructor = 0xb6901959; + public static class TL_updateNewGeoChatMessage extends Update { + public static int constructor = 0x5a68e3f7; + public GeoChatMessage message; public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - user_id = stream.readInt32(exception); - is_admin = stream.readBool(exception); - version = stream.readInt32(exception); + message = GeoChatMessage.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(chat_id); - stream.writeInt32(user_id); - stream.writeBool(is_admin); - stream.writeInt32(version); + message.serializeToStream(stream); } } - public static class TL_updateNewStickerSet extends Update { - public static int constructor = 0x688a30aa; + public static class TL_updateReadHistoryInbox extends Update { + public static int constructor = 0x9961fd5c; + public Peer peer; public void readParams(AbstractSerializedData stream, boolean exception) { - stickerset = TL_messages_stickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); + peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + max_id = stream.readInt32(exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stickerset.serializeToStream(stream); + peer.serializeToStream(stream); + stream.writeInt32(max_id); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } @@ -10733,123 +13023,109 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_updateReadHistoryInbox extends Update { - public static int constructor = 0x9961fd5c; - - public Peer peer; + public static class TL_updateSavedGifs extends Update { + public static int constructor = 0x9375341e; - public void readParams(AbstractSerializedData stream, boolean exception) { - peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); - max_id = stream.readInt32(exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); - } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - peer.serializeToStream(stream); - stream.writeInt32(max_id); - stream.writeInt32(pts); - stream.writeInt32(pts_count); } } - public static class TL_updateUserName extends Update { - public static int constructor = 0xa7332b73; + public static class TL_updateChannel extends Update { + public static int constructor = 0xb6d45656; public void readParams(AbstractSerializedData stream, boolean exception) { - user_id = stream.readInt32(exception); - first_name = stream.readString(exception); - last_name = stream.readString(exception); - username = stream.readString(exception); + channel_id = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(user_id); - stream.writeString(first_name); - stream.writeString(last_name); - stream.writeString(username); + stream.writeInt32(channel_id); } } - public static class TL_updateBotInlineSend extends Update { - public static int constructor = 0xe48f964; + public static class TL_updateChannelWebPage extends Update { + public static int constructor = 0x40771900; - public String id; - public TL_inputBotInlineMessageID msg_id; public void readParams(AbstractSerializedData stream, boolean exception) { - flags = stream.readInt32(exception); - user_id = stream.readInt32(exception); - query = stream.readString(exception); - if ((flags & 1) != 0) { - geo = GeoPoint.TLdeserialize(stream, stream.readInt32(exception), exception); - } - id = stream.readString(exception); - if ((flags & 2) != 0) { - msg_id = TL_inputBotInlineMessageID.TLdeserialize(stream, stream.readInt32(exception), exception); - } + channel_id = stream.readInt32(exception); + webpage = WebPage.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(flags); - stream.writeInt32(user_id); - stream.writeString(query); - if ((flags & 1) != 0) { - geo.serializeToStream(stream); - } - stream.writeString(id); - if ((flags & 2) != 0) { - msg_id.serializeToStream(stream); - } + stream.writeInt32(channel_id); + webpage.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateEditMessage extends Update { - public static int constructor = 0xe40370a3; + public static class TL_updateDeleteChannelMessages extends Update { + public static int constructor = 0xc37521c9; - public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + channel_id = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + messages.add(stream.readInt32(exception)); + } pts = stream.readInt32(exception); pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); + stream.writeInt32(channel_id); + stream.writeInt32(0x1cb5c415); + int count = messages.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(messages.get(a)); + } stream.writeInt32(pts); stream.writeInt32(pts_count); } } - public static class TL_updateChannelPinnedMessage extends Update { - public static int constructor = 0x98592475; + public static class TL_updateUserPhoto extends Update { + public static int constructor = 0x95313b0c; - public int id; public void readParams(AbstractSerializedData stream, boolean exception) { - channel_id = stream.readInt32(exception); - id = stream.readInt32(exception); + user_id = stream.readInt32(exception); + date = stream.readInt32(exception); + photo = UserProfilePhoto.TLdeserialize(stream, stream.readInt32(exception), exception); + previous = stream.readBool(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(channel_id); - stream.writeInt32(id); + stream.writeInt32(user_id); + stream.writeInt32(date); + photo.serializeToStream(stream); + stream.writeBool(previous); } } - public static class TL_updateDeleteChannelMessages extends Update { - public static int constructor = 0xc37521c9; + public static class TL_updateDcOptions extends Update { + public static int constructor = 0x8e5e9873; public void readParams(AbstractSerializedData stream, boolean exception) { - channel_id = stream.readInt32(exception); int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -10859,98 +13135,96 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } int count = stream.readInt32(exception); for (int a = 0; a < count; a++) { - messages.add(stream.readInt32(exception)); + TL_dcOption object = TL_dcOption.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + dc_options.add(object); } - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(channel_id); stream.writeInt32(0x1cb5c415); - int count = messages.size(); + int count = dc_options.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { - stream.writeInt32(messages.get(a)); + dc_options.get(a).serializeToStream(stream); } - stream.writeInt32(pts); - stream.writeInt32(pts_count); } } - public static class TL_updateUserPhone extends Update { - public static int constructor = 0x12b9417b; + public static class TL_updateEditChannelMessage extends Update { + public static int constructor = 0x1b3f4df7; + public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - user_id = stream.readInt32(exception); - phone = stream.readString(exception); + message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); + pts = stream.readInt32(exception); + pts_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(user_id); - stream.writeString(phone); + message.serializeToStream(stream); + stream.writeInt32(pts); + stream.writeInt32(pts_count); } } - public static class TL_updateWebPage extends Update { - public static int constructor = 0x7f891213; + public static class TL_updateUserBlocked extends Update { + public static int constructor = 0x80ece81a; public void readParams(AbstractSerializedData stream, boolean exception) { - webpage = WebPage.TLdeserialize(stream, stream.readInt32(exception), exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); + user_id = stream.readInt32(exception); + blocked = stream.readBool(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - webpage.serializeToStream(stream); - stream.writeInt32(pts); - stream.writeInt32(pts_count); + stream.writeInt32(user_id); + stream.writeBool(blocked); } } - public static class TL_updateEncryption extends Update { - public static int constructor = 0xb4a2e88d; + public static class TL_updateNewStickerSet extends Update { + public static int constructor = 0x688a30aa; public void readParams(AbstractSerializedData stream, boolean exception) { - chat = EncryptedChat.TLdeserialize(stream, stream.readInt32(exception), exception); - date = stream.readInt32(exception); + stickerset = TL_messages_stickerSet.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - chat.serializeToStream(stream); - stream.writeInt32(date); + stickerset.serializeToStream(stream); } } - public static class TL_updateNewMessage extends Update { - public static int constructor = 0x1f2b0afd; + public static class TL_updateEncryptedMessagesRead extends Update { + public static int constructor = 0x38fe25b7; - public Message message; public void readParams(AbstractSerializedData stream, boolean exception) { - message = Message.TLdeserialize(stream, stream.readInt32(exception), exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); + chat_id = stream.readInt32(exception); + max_date = stream.readInt32(exception); + date = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - message.serializeToStream(stream); - stream.writeInt32(pts); - stream.writeInt32(pts_count); + stream.writeInt32(chat_id); + stream.writeInt32(max_date); + stream.writeInt32(date); } } public static class TL_updateStickerSetsOrder extends Update { public static int constructor = 0xbb2d201; + public ArrayList order = new ArrayList<>(); public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); @@ -10981,21 +13255,19 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_updateEncryptedMessagesRead extends Update { - public static int constructor = 0x38fe25b7; + public static class TL_updateReadChannelInbox extends Update { + public static int constructor = 0x4214f37f; public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - max_date = stream.readInt32(exception); - date = stream.readInt32(exception); + channel_id = stream.readInt32(exception); + max_id = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(chat_id); - stream.writeInt32(max_date); - stream.writeInt32(date); + stream.writeInt32(channel_id); + stream.writeInt32(max_id); } } @@ -11032,95 +13304,17 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_updateDeleteMessages extends Update { - public static int constructor = 0xa20db0e5; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - int magic = stream.readInt32(exception); - if (magic != 0x1cb5c415) { - if (exception) { - throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); - } - return; - } - int count = stream.readInt32(exception); - for (int a = 0; a < count; a++) { - messages.add(stream.readInt32(exception)); - } - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(0x1cb5c415); - int count = messages.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stream.writeInt32(messages.get(a)); - } - stream.writeInt32(pts); - stream.writeInt32(pts_count); - } - } - - public static class TL_updateChatUserTyping extends Update { - public static int constructor = 0x9a65ea1f; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - chat_id = stream.readInt32(exception); - user_id = stream.readInt32(exception); - action = SendMessageAction.TLdeserialize(stream, stream.readInt32(exception), exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(chat_id); - stream.writeInt32(user_id); - action.serializeToStream(stream); - } - } - - public static class TL_updateUserPhoto extends Update { - public static int constructor = 0x95313b0c; - - - public void readParams(AbstractSerializedData stream, boolean exception) { - user_id = stream.readInt32(exception); - date = stream.readInt32(exception); - photo = UserProfilePhoto.TLdeserialize(stream, stream.readInt32(exception), exception); - previous = stream.readBool(exception); - } - - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - stream.writeInt32(user_id); - stream.writeInt32(date); - photo.serializeToStream(stream); - stream.writeBool(previous); - } - } - - public static class TL_updateReadHistoryOutbox extends Update { - public static int constructor = 0x2f2f21bf; + public static class TL_updateChatParticipants extends Update { + public static int constructor = 0x7761198; - public Peer peer; public void readParams(AbstractSerializedData stream, boolean exception) { - peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); - max_id = stream.readInt32(exception); - pts = stream.readInt32(exception); - pts_count = stream.readInt32(exception); + participants = ChatParticipants.TLdeserialize(stream, stream.readInt32(exception), exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - peer.serializeToStream(stream); - stream.writeInt32(max_id); - stream.writeInt32(pts); - stream.writeInt32(pts_count); + participants.serializeToStream(stream); } } @@ -12512,9 +14706,10 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_config extends TLObject { - public static int constructor = 0x9a6b2e2a; + public static int constructor = 0xcb601684; public int flags; + public boolean phonecalls_enabled; public int date; public int expires; public boolean test_mode; @@ -12537,6 +14732,12 @@ public static class TL_config extends TLObject { public int rating_e_decay; public int stickers_recent_limit; public int tmp_sessions; + public int pinned_dialogs_count_max; + public int call_receive_timeout_ms; + public int call_ring_timeout_ms; + public int call_connect_timeout_ms; + public int call_packet_timeout_ms; + public String me_url_prefix; public ArrayList disabled_features = new ArrayList<>(); public static TL_config TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -12554,6 +14755,7 @@ public static TL_config TLdeserialize(AbstractSerializedData stream, int constru public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + phonecalls_enabled = (flags & 2) != 0; date = stream.readInt32(exception); expires = stream.readInt32(exception); test_mode = stream.readBool(exception); @@ -12592,6 +14794,12 @@ public void readParams(AbstractSerializedData stream, boolean exception) { if ((flags & 1) != 0) { tmp_sessions = stream.readInt32(exception); } + pinned_dialogs_count_max = stream.readInt32(exception); + call_receive_timeout_ms = stream.readInt32(exception); + call_ring_timeout_ms = stream.readInt32(exception); + call_connect_timeout_ms = stream.readInt32(exception); + call_packet_timeout_ms = stream.readInt32(exception); + me_url_prefix = stream.readString(exception); magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { if (exception) { @@ -12611,6 +14819,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = phonecalls_enabled ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); stream.writeInt32(date); stream.writeInt32(expires); @@ -12641,6 +14850,12 @@ public void serializeToStream(AbstractSerializedData stream) { if ((flags & 1) != 0) { stream.writeInt32(tmp_sessions); } + stream.writeInt32(pinned_dialogs_count_max); + stream.writeInt32(call_receive_timeout_ms); + stream.writeInt32(call_ring_timeout_ms); + stream.writeInt32(call_connect_timeout_ms); + stream.writeInt32(call_packet_timeout_ms); + stream.writeString(me_url_prefix); stream.writeInt32(0x1cb5c415); count = disabled_features.size(); stream.writeInt32(count); @@ -12790,24 +15005,66 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_messages_chats extends TLObject { - public static int constructor = 0x64ff9fd5; + public static class TL_account_tmpPassword extends TLObject { + public static int constructor = 0xdb64fd34; - public ArrayList chats = new ArrayList<>(); + public byte[] tmp_password; + public int valid_until; - public static TL_messages_chats TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_messages_chats.constructor != constructor) { + public static TL_account_tmpPassword TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_account_tmpPassword.constructor != constructor) { if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_messages_chats", constructor)); + throw new RuntimeException(String.format("can't parse magic %x in TL_account_tmpPassword", constructor)); } else { return null; } } - TL_messages_chats result = new TL_messages_chats(); + TL_account_tmpPassword result = new TL_account_tmpPassword(); result.readParams(stream, exception); return result; } + public void readParams(AbstractSerializedData stream, boolean exception) { + tmp_password = stream.readByteArray(exception); + valid_until = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeByteArray(tmp_password); + stream.writeInt32(valid_until); + } + } + + + public static class messages_Chats extends TLObject { + public ArrayList chats = new ArrayList<>(); + public int count; + + public static messages_Chats TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_Chats result = null; + switch(constructor) { + case 0x64ff9fd5: + result = new TL_messages_chats(); + break; + case 0x9cd81144: + result = new TL_messages_chatsSlice(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_Chats", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_messages_chats extends messages_Chats { + public static int constructor = 0x64ff9fd5; + + public void readParams(AbstractSerializedData stream, boolean exception) { int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { @@ -12828,7 +15085,42 @@ public void readParams(AbstractSerializedData stream, boolean exception) { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(0x1cb5c415); + stream.writeInt32(0x1cb5c415); + int count = chats.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + chats.get(a).serializeToStream(stream); + } + } + } + + public static class TL_messages_chatsSlice extends messages_Chats { + public static int constructor = 0x9cd81144; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + Chat object = Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); int count = chats.size(); stream.writeInt32(count); for (int a = 0; a < count; a++) { @@ -12918,7 +15210,7 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_messages_botResults extends TLObject { - public static int constructor = 0x256709a6; + public static int constructor = 0xccd3563d; public int flags; public boolean gallery; @@ -12926,6 +15218,7 @@ public static class TL_messages_botResults extends TLObject { public String next_offset; public TL_inlineBotSwitchPM switch_pm; public ArrayList results = new ArrayList<>(); + public int cache_time; public static TL_messages_botResults TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_messages_botResults.constructor != constructor) { @@ -12965,6 +15258,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { } results.add(object); } + cache_time = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -12984,6 +15278,47 @@ public void serializeToStream(AbstractSerializedData stream) { for (int a = 0; a < count; a++) { results.get(a).serializeToStream(stream); } + stream.writeInt32(cache_time); + } + } + + public static class TL_phoneConnection extends TLObject { + public static int constructor = 0x9d4c17c0; + + public long id; + public String ip; + public String ipv6; + public int port; + public byte[] peer_tag; + + public static TL_phoneConnection TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_phoneConnection.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_phoneConnection", constructor)); + } else { + return null; + } + } + TL_phoneConnection result = new TL_phoneConnection(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + ip = stream.readString(exception); + ipv6 = stream.readString(exception); + port = stream.readInt32(exception); + peer_tag = stream.readByteArray(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeString(ip); + stream.writeString(ipv6); + stream.writeInt32(port); + stream.writeByteArray(peer_tag); } } @@ -13479,6 +15814,99 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_inputPhoneCall extends TLObject { + public static int constructor = 0x1e36fded; + + public long id; + public long access_hash; + + public static TL_inputPhoneCall TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_inputPhoneCall.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_inputPhoneCall", constructor)); + } else { + return null; + } + } + TL_inputPhoneCall result = new TL_inputPhoneCall(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readInt64(exception); + access_hash = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(id); + stream.writeInt64(access_hash); + } + } + + public static class TL_webDocument extends TLObject { + public static int constructor = 0xc61acbd8; + + public String url; + public long access_hash; + public int size; + public String mime_type; + public ArrayList attributes = new ArrayList<>(); + public int dc_id; + + public static TL_webDocument TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_webDocument.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_webDocument", constructor)); + } else { + return null; + } + } + TL_webDocument result = new TL_webDocument(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + access_hash = stream.readInt64(exception); + size = stream.readInt32(exception); + mime_type = stream.readString(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + DocumentAttribute object = DocumentAttribute.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + attributes.add(object); + } + dc_id = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt64(access_hash); + stream.writeInt32(size); + stream.writeString(mime_type); + stream.writeInt32(0x1cb5c415); + int count = attributes.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + attributes.get(a).serializeToStream(stream); + } + stream.writeInt32(dc_id); + } + } + public static class ChannelParticipant extends TLObject { public int user_id; public int date; @@ -14610,6 +17038,80 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_postAddress extends TLObject { + public static int constructor = 0x1e8caaeb; + + public String street_line1; + public String street_line2; + public String city; + public String state; + public String country_iso2; + public String post_code; + + public static TL_postAddress TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_postAddress.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_postAddress", constructor)); + } else { + return null; + } + } + TL_postAddress result = new TL_postAddress(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + street_line1 = stream.readString(exception); + street_line2 = stream.readString(exception); + city = stream.readString(exception); + state = stream.readString(exception); + country_iso2 = stream.readString(exception); + post_code = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(street_line1); + stream.writeString(street_line2); + stream.writeString(city); + stream.writeString(state); + stream.writeString(country_iso2); + stream.writeString(post_code); + } + } + + public static class TL_inputWebFileLocation extends TLObject { + public static int constructor = 0xc239d686; + + public String url; + public long access_hash; + + public static TL_inputWebFileLocation TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_inputWebFileLocation.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_inputWebFileLocation", constructor)); + } else { + return null; + } + } + TL_inputWebFileLocation result = new TL_inputWebFileLocation(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + url = stream.readString(exception); + access_hash = stream.readInt64(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt64(access_hash); + } + } + public static class Chat extends TLObject { public int flags; public boolean creator; @@ -15357,12 +17859,14 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class MessagesFilter extends TLObject { + public int flags; + public boolean missed; public static MessagesFilter TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessagesFilter result = null; switch(constructor) { - case 0x9eddf188: - result = new TL_inputMessagesFilterDocument(); + case 0xffc86587: + result = new TL_inputMessagesFilterGif(); break; case 0x3751b49e: result = new TL_inputMessagesFilterMusic(); @@ -15370,30 +17874,33 @@ public static MessagesFilter TLdeserialize(AbstractSerializedData stream, int co case 0x3a20ecb8: result = new TL_inputMessagesFilterChatPhotos(); break; - case 0x9fc00e65: - result = new TL_inputMessagesFilterVideo(); - break; case 0x9609a51c: result = new TL_inputMessagesFilterPhotos(); break; - case 0xd95e73bb: - result = new TL_inputMessagesFilterPhotoVideoDocuments(); - break; case 0x7ef0dd87: result = new TL_inputMessagesFilterUrl(); break; - case 0xffc86587: - result = new TL_inputMessagesFilterGif(); + case 0x9eddf188: + result = new TL_inputMessagesFilterDocument(); + break; + case 0x56e9f0e4: + result = new TL_inputMessagesFilterPhotoVideo(); + break; + case 0xd95e73bb: + result = new TL_inputMessagesFilterPhotoVideoDocuments(); break; case 0x50f5c392: result = new TL_inputMessagesFilterVoice(); break; + case 0x9fc00e65: + result = new TL_inputMessagesFilterVideo(); + break; + case 0x80c99768: + result = new TL_inputMessagesFilterPhoneCalls(); + break; case 0x57e2f66c: result = new TL_inputMessagesFilterEmpty(); break; - case 0x56e9f0e4: - result = new TL_inputMessagesFilterPhotoVideo(); - break; } if (result == null && exception) { throw new RuntimeException(String.format("can't parse magic %x in MessagesFilter", constructor)); @@ -15405,8 +17912,8 @@ public static MessagesFilter TLdeserialize(AbstractSerializedData stream, int co } } - public static class TL_inputMessagesFilterDocument extends MessagesFilter { - public static int constructor = 0x9eddf188; + public static class TL_inputMessagesFilterGif extends MessagesFilter { + public static int constructor = 0xffc86587; public void serializeToStream(AbstractSerializedData stream) { @@ -15432,8 +17939,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterVideo extends MessagesFilter { - public static int constructor = 0x9fc00e65; + public static class TL_inputMessagesFilterPhotos extends MessagesFilter { + public static int constructor = 0x9609a51c; public void serializeToStream(AbstractSerializedData stream) { @@ -15441,8 +17948,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterPhotos extends MessagesFilter { - public static int constructor = 0x9609a51c; + public static class TL_inputMessagesFilterUrl extends MessagesFilter { + public static int constructor = 0x7ef0dd87; public void serializeToStream(AbstractSerializedData stream) { @@ -15450,8 +17957,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterPhotoVideoDocuments extends MessagesFilter { - public static int constructor = 0xd95e73bb; + public static class TL_inputMessagesFilterDocument extends MessagesFilter { + public static int constructor = 0x9eddf188; public void serializeToStream(AbstractSerializedData stream) { @@ -15459,8 +17966,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterUrl extends MessagesFilter { - public static int constructor = 0x7ef0dd87; + public static class TL_inputMessagesFilterPhotoVideo extends MessagesFilter { + public static int constructor = 0x56e9f0e4; public void serializeToStream(AbstractSerializedData stream) { @@ -15468,8 +17975,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterGif extends MessagesFilter { - public static int constructor = 0xffc86587; + public static class TL_inputMessagesFilterPhotoVideoDocuments extends MessagesFilter { + public static int constructor = 0xd95e73bb; public void serializeToStream(AbstractSerializedData stream) { @@ -15486,8 +17993,8 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterEmpty extends MessagesFilter { - public static int constructor = 0x57e2f66c; + public static class TL_inputMessagesFilterVideo extends MessagesFilter { + public static int constructor = 0x9fc00e65; public void serializeToStream(AbstractSerializedData stream) { @@ -15495,8 +18002,24 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_inputMessagesFilterPhotoVideo extends MessagesFilter { - public static int constructor = 0x56e9f0e4; + public static class TL_inputMessagesFilterPhoneCalls extends MessagesFilter { + public static int constructor = 0x80c99768; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + missed = (flags & 1) != 0; + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = missed ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + } + } + + public static class TL_inputMessagesFilterEmpty extends MessagesFilter { + public static int constructor = 0x57e2f66c; public void serializeToStream(AbstractSerializedData stream) { @@ -16165,16 +18688,19 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_userFull extends TLObject { - public static int constructor = 0x5932fc03; + public static int constructor = 0xf220f3f; public int flags; public boolean blocked; + public boolean phone_calls_available; + public boolean phone_calls_private; public User user; public String about; public TL_contacts_link link; public Photo profile_photo; public PeerNotifySettings notify_settings; public BotInfo bot_info; + public int common_chats_count; public static TL_userFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_userFull.constructor != constructor) { @@ -16192,6 +18718,8 @@ public static TL_userFull TLdeserialize(AbstractSerializedData stream, int const public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); blocked = (flags & 1) != 0; + phone_calls_available = (flags & 16) != 0; + phone_calls_private = (flags & 32) != 0; user = User.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 2) != 0) { about = stream.readString(exception); @@ -16204,11 +18732,14 @@ public void readParams(AbstractSerializedData stream, boolean exception) { if ((flags & 8) != 0) { bot_info = BotInfo.TLdeserialize(stream, stream.readInt32(exception), exception); } + common_chats_count = stream.readInt32(exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = blocked ? (flags | 1) : (flags &~ 1); + flags = phone_calls_available ? (flags | 16) : (flags &~ 16); + flags = phone_calls_private ? (flags | 32) : (flags &~ 32); stream.writeInt32(flags); user.serializeToStream(stream); if ((flags & 2) != 0) { @@ -16222,6 +18753,7 @@ public void serializeToStream(AbstractSerializedData stream) { if ((flags & 8) != 0) { bot_info.serializeToStream(stream); } + stream.writeInt32(common_chats_count); } } @@ -16622,6 +19154,37 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_paymentSavedCredentialsCard extends TLObject { + public static int constructor = 0xcdc27a1f; + + public String id; + public String title; + + public static TL_paymentSavedCredentialsCard TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_paymentSavedCredentialsCard.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_paymentSavedCredentialsCard", constructor)); + } else { + return null; + } + } + TL_paymentSavedCredentialsCard result = new TL_paymentSavedCredentialsCard(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + id = stream.readString(exception); + title = stream.readString(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(id); + stream.writeString(title); + } + } + public static class TL_stickerPack extends TLObject { public static int constructor = 0x12b299d4; @@ -16797,6 +19360,44 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_payments_savedInfo extends TLObject { + public static int constructor = 0xfb8fe43c; + + public int flags; + public boolean has_saved_credentials; + public TL_paymentRequestedInfo saved_info; + + public static TL_payments_savedInfo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_payments_savedInfo.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments_savedInfo", constructor)); + } else { + return null; + } + } + TL_payments_savedInfo result = new TL_payments_savedInfo(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + has_saved_credentials = (flags & 2) != 0; + if ((flags & 1) != 0) { + saved_info = TL_paymentRequestedInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = has_saved_credentials ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + saved_info.serializeToStream(stream); + } + } + } + public static class InputPhoto extends TLObject { public long id; public long access_hash; @@ -17697,6 +20298,62 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_paymentRequestedInfo extends TLObject { + public static int constructor = 0x909c3f94; + + public int flags; + public String name; + public String phone; + public String email; + public TL_postAddress shipping_address; + + public static TL_paymentRequestedInfo TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_paymentRequestedInfo.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_paymentRequestedInfo", constructor)); + } else { + return null; + } + } + TL_paymentRequestedInfo result = new TL_paymentRequestedInfo(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + name = stream.readString(exception); + } + if ((flags & 2) != 0) { + phone = stream.readString(exception); + } + if ((flags & 4) != 0) { + email = stream.readString(exception); + } + if ((flags & 8) != 0) { + shipping_address = TL_postAddress.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeString(name); + } + if ((flags & 2) != 0) { + stream.writeString(phone); + } + if ((flags & 4) != 0) { + stream.writeString(email); + } + if ((flags & 8) != 0) { + shipping_address.serializeToStream(stream); + } + } + } + public static class TL_auth_checkPhone extends TLObject { public static int constructor = 0x6fe51dfb; @@ -18286,8 +20943,10 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_messages_getDialogs extends TLObject { - public static int constructor = 0x6b47f94d; + public static int constructor = 0x191ba9c5; + public int flags; + public boolean exclude_pinned; public int offset_date; public int offset_id; public InputPeer offset_peer; @@ -18299,6 +20958,8 @@ public TLObject deserializeResponse(AbstractSerializedData stream, int construct public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = exclude_pinned ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); stream.writeInt32(offset_date); stream.writeInt32(offset_id); offset_peer.serializeToStream(stream); @@ -18477,16 +21138,49 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_messages_deleteMessages extends TLObject { - public static int constructor = 0xa5f18925; + public static int constructor = 0xe58e95d2; + public int flags; + public boolean revoke; public ArrayList id = new ArrayList<>(); + public static TL_messages_deleteMessages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_messages_deleteMessages.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_messages_deleteMessages", constructor)); + } else { + return null; + } + } + TL_messages_deleteMessages result = new TL_messages_deleteMessages(); + result.readParams(stream, exception); + return result; + } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return TL_messages_affectedMessages.TLdeserialize(stream, constructor, exception); } - public void serializeToStream(AbstractSerializedData stream) { + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + revoke = (flags & 1) != 0; + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + id.add(stream.readInt32(exception)); + } + } + + public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = revoke ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); stream.writeInt32(0x1cb5c415); int count = id.size(); stream.writeInt32(count); @@ -18843,9 +21537,11 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_updates_getDifference extends TLObject { - public static int constructor = 0xa041495; + public static int constructor = 0x25939651; + public int flags; public int pts; + public int pts_total_limit; public int date; public int qts; @@ -18855,32 +21551,40 @@ public TLObject deserializeResponse(AbstractSerializedData stream, int construct public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); stream.writeInt32(pts); + if ((flags & 1) != 0) { + stream.writeInt32(pts_total_limit); + } stream.writeInt32(date); stream.writeInt32(qts); } } - public static class TL_updates_getChannelDifference extends TLObject { - public static int constructor = 0xbb32d7c0; + public static class TL_updates_getChannelDifference extends TLObject { + public static int constructor = 0x3173d78; - public InputChannel channel; - public ChannelMessagesFilter filter; - public int pts; - public int limit; + public int flags; + public boolean force; + public InputChannel channel; + public ChannelMessagesFilter filter; + public int pts; + public int limit; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return updates_ChannelDifference.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return updates_ChannelDifference.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - filter.serializeToStream(stream); + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = force ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + channel.serializeToStream(stream); + filter.serializeToStream(stream); stream.writeInt32(pts); - stream.writeInt32(limit); - } - } + stream.writeInt32(limit); + } + } public static class TL_photos_updateProfilePhoto extends TLObject { public static int constructor = 0xf0bb5152; @@ -19451,6 +22155,21 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messages_reportEncryptedSpam extends TLObject { + public static int constructor = 0x4b0c8c0f; + + public TL_inputEncryptedChat peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + public static class TL_help_getSupport extends TLObject { public static int constructor = 0x9cdf08cd; @@ -19874,6 +22593,23 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_account_getTmpPassword extends TLObject { + public static int constructor = 0x4a82327e; + + public byte[] password_hash; + public int period; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_account_tmpPassword.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeByteArray(password_hash); + stream.writeInt32(period); + } + } + public static class TL_auth_checkPassword extends TLObject { public static int constructor = 0xa63011e; @@ -20303,13 +23039,14 @@ public void serializeToStream(AbstractSerializedData stream) { } public static class TL_messages_setBotCallbackAnswer extends TLObject { - public static int constructor = 0xc927d44b; + public static int constructor = 0xd58f130a; public int flags; public boolean alert; public long query_id; public String message; public String url; + public int cache_time; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return Bool.TLdeserialize(stream, constructor, exception); @@ -20326,6 +23063,7 @@ public void serializeToStream(AbstractSerializedData stream) { if ((flags & 4) != 0) { stream.writeString(url); } + stream.writeInt32(cache_time); } } @@ -20550,6 +23288,7 @@ public static class TL_messages_setGameScore extends TLObject { public int flags; public boolean edit_message; + public boolean force; public InputPeer peer; public int id; public InputUser user_id; @@ -20561,8 +23300,9 @@ public TLObject deserializeResponse(AbstractSerializedData stream, int construct public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - stream.writeInt32(flags); flags = edit_message ? (flags | 1) : (flags &~ 1); + flags = force ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); peer.serializeToStream(stream); stream.writeInt32(id); user_id.serializeToStream(stream); @@ -20575,6 +23315,7 @@ public static class TL_messages_setInlineGameScore extends TLObject { public int flags; public boolean edit_message; + public boolean force; public TL_inputBotInlineMessageID id; public InputUser user_id; public int score; @@ -20586,6 +23327,7 @@ public TLObject deserializeResponse(AbstractSerializedData stream, int construct public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); flags = edit_message ? (flags | 1) : (flags &~ 1); + flags = force ? (flags | 2) : (flags &~ 2); stream.writeInt32(flags); id.serializeToStream(stream); user_id.serializeToStream(stream); @@ -20668,16 +23410,130 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_messages_getCommonChats extends TLObject { + public static int constructor = 0xd0a48c4; + + public InputUser user_id; + public int max_id; + public int limit; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_Chats.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + user_id.serializeToStream(stream); + stream.writeInt32(max_id); + stream.writeInt32(limit); + } + } + + public static class TL_messages_getAllChats extends TLObject { + public static int constructor = 0xeba80ff0; + + public ArrayList except_ids = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_Chats.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(0x1cb5c415); + int count = except_ids.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(except_ids.get(a)); + } + } + } + + public static class TL_messages_getWebPage extends TLObject { + public static int constructor = 0x32ca8f91; + + public String url; + public int hash; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return WebPage.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(url); + stream.writeInt32(hash); + } + } + + public static class TL_messages_toggleDialogPin extends TLObject { + public static int constructor = 0x3289be6a; + + public int flags; + public boolean pinned; + public InputPeer peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = pinned ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + peer.serializeToStream(stream); + } + } + + public static class TL_messages_reorderPinnedDialogs extends TLObject { + public static int constructor = 0x959ff644; + + public int flags; + public boolean force; + public ArrayList order = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = force ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt32(0x1cb5c415); + int count = order.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + order.get(a).serializeToStream(stream); + } + } + } + + public static class TL_messages_getPinnedDialogs extends TLObject { + public static int constructor = 0xe254d64e; + + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_peerDialogs.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + public static class TL_help_getAppChangelog extends TLObject { - public static int constructor = 0xb921197a; + public static int constructor = 0x9010ef6f; + public String prev_app_version; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return help_AppChangelog.TLdeserialize(stream, constructor, exception); + return Updates.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeString(prev_app_version); } } @@ -20694,6 +23550,23 @@ public void serializeToStream(AbstractSerializedData stream) { } } + public static class TL_help_setBotUpdatesStatus extends TLObject { + public static int constructor = 0xec22cfcd; + + public int pending_updates_count; + public String message; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(pending_updates_count); + stream.writeString(message); + } + } + public static class TL_messages_reorderStickerSets extends TLObject { public static int constructor = 0x78337739; @@ -20754,27 +23627,55 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_channels_deleteMessages extends TLObject { - public static int constructor = 0x84c1fd4e; + public static class TL_channels_deleteMessages extends TLObject { + public static int constructor = 0x84c1fd4e; - public InputChannel channel; - public ArrayList id = new ArrayList<>(); + public InputChannel channel; + public ArrayList id = new ArrayList<>(); + + public static TL_channels_deleteMessages TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_channels_deleteMessages.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_channels_deleteMessages", constructor)); + } else { + return null; + } + } + TL_channels_deleteMessages result = new TL_channels_deleteMessages(); + result.readParams(stream, exception); + return result; + } + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_affectedMessages.TLdeserialize(stream, constructor, exception); + } - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_messages_affectedMessages.TLdeserialize(stream, constructor, exception); - } + public void readParams(AbstractSerializedData stream, boolean exception) { + channel = InputChannel.TLdeserialize(stream, stream.readInt32(exception), exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + id.add(stream.readInt32(exception)); + } + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - stream.writeInt32(0x1cb5c415); - int count = id.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - stream.writeInt32(id.get(a)); - } - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = id.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + stream.writeInt32(id.get(a)); + } + } + } public static class TL_channels_deleteUserHistory extends TLObject { public static int constructor = 0xd10dd71b; @@ -21069,170 +23970,486 @@ public void serializeToStream(AbstractSerializedData stream) { } } - public static class TL_channels_inviteToChannel extends TLObject { - public static int constructor = 0x199f3a6c; + public static class TL_channels_inviteToChannel extends TLObject { + public static int constructor = 0x199f3a6c; + + public InputChannel channel; + public ArrayList users = new ArrayList<>(); + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(0x1cb5c415); + int count = users.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + users.get(a).serializeToStream(stream); + } + } + } + + public static class TL_channels_kickFromChannel extends TLObject { + public static int constructor = 0xa672de14; + + public InputChannel channel; + public InputUser user_id; + public boolean kicked; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + user_id.serializeToStream(stream); + stream.writeBool(kicked); + } + } + + public static class TL_channels_exportInvite extends TLObject { + public static int constructor = 0xc7560885; + + public InputChannel channel; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return ExportedChatInvite.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + } + } + + public static class TL_channels_deleteChannel extends TLObject { + public static int constructor = 0xc0111fe3; + + public InputChannel channel; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + } + } + + public static class TL_channels_toggleInvites extends TLObject { + public static int constructor = 0x49609307; + + public InputChannel channel; + public boolean enabled; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeBool(enabled); + } + } + + public static class TL_channels_exportMessageLink extends TLObject { + public static int constructor = 0xc846d22d; + + public InputChannel channel; + public int id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_exportedMessageLink.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeInt32(id); + } + } + + public static class TL_channels_toggleSignatures extends TLObject { + public static int constructor = 0x1f69b606; + + public InputChannel channel; + public boolean enabled; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + channel.serializeToStream(stream); + stream.writeBool(enabled); + } + } + + public static class TL_channels_updatePinnedMessage extends TLObject { + public static int constructor = 0xa72ded52; + + public int flags; + public boolean silent; + public InputChannel channel; + public int id; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = silent ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + channel.serializeToStream(stream); + stream.writeInt32(id); + } + } + + public static class TL_channels_getAdminedPublicChannels extends TLObject { + public static int constructor = 0x8d8d82d7; + + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_messages_chats.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_phone_getCallConfig extends TLObject { + public static int constructor = 0x55451fa9; + + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_dataJSON.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } + } + + public static class TL_phone_requestCall extends TLObject { + public static int constructor = 0x5b95b3d4; + + public InputUser user_id; + public int random_id; + public byte[] g_a_hash; + public TL_phoneCallProtocol protocol; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_phoneCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + user_id.serializeToStream(stream); + stream.writeInt32(random_id); + stream.writeByteArray(g_a_hash); + protocol.serializeToStream(stream); + } + } + + public static class TL_phone_acceptCall extends TLObject { + public static int constructor = 0x3bd2b4a0; + + public TL_inputPhoneCall peer; + public byte[] g_b; + public TL_phoneCallProtocol protocol; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_phoneCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeByteArray(g_b); + protocol.serializeToStream(stream); + } + } + + public static class TL_phone_confirmCall extends TLObject { + public static int constructor = 0x2efe1722; + + public TL_inputPhoneCall peer; + public byte[] g_a; + public long key_fingerprint; + public TL_phoneCallProtocol protocol; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_phone_phoneCall.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeByteArray(g_a); + stream.writeInt64(key_fingerprint); + protocol.serializeToStream(stream); + } + } + + public static class TL_phone_receivedCall extends TLObject { + public static int constructor = 0x17d54f61; + + public TL_inputPhoneCall peer; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + } + } + + public static class TL_phone_discardCall extends TLObject { + public static int constructor = 0x78d413a6; - public InputChannel channel; - public ArrayList users = new ArrayList<>(); + public TL_inputPhoneCall peer; + public int duration; + public PhoneCallDiscardReason reason; + public long connection_id; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - stream.writeInt32(0x1cb5c415); - int count = users.size(); - stream.writeInt32(count); - for (int a = 0; a < count; a++) { - users.get(a).serializeToStream(stream); - } - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(duration); + reason.serializeToStream(stream); + stream.writeInt64(connection_id); + } + } - public static class TL_channels_kickFromChannel extends TLObject { - public static int constructor = 0xa672de14; + public static class TL_phone_setCallRating extends TLObject { + public static int constructor = 0x1c536a34; - public InputChannel channel; - public InputUser user_id; - public boolean kicked; + public TL_inputPhoneCall peer; + public int rating; + public String comment; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Updates.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - user_id.serializeToStream(stream); - stream.writeBool(kicked); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + stream.writeInt32(rating); + stream.writeString(comment); + } + } - public static class TL_channels_exportInvite extends TLObject { - public static int constructor = 0xc7560885; + public static class TL_phone_saveCallDebug extends TLObject { + public static int constructor = 0x277add7e; - public InputChannel channel; + public TL_inputPhoneCall peer; + public TL_dataJSON debug; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return ExportedChatInvite.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return Bool.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + debug.serializeToStream(stream); + } + } - public static class TL_channels_deleteChannel extends TLObject { - public static int constructor = 0xc0111fe3; + public static class TL_payments_getPaymentForm extends TLObject { + public static int constructor = 0x99f09745; - public InputChannel channel; + public int msg_id; - public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); - } + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_payments_paymentForm.TLdeserialize(stream, constructor, exception); + } - public void serializeToStream(AbstractSerializedData stream) { - stream.writeInt32(constructor); - channel.serializeToStream(stream); - } - } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(msg_id); + } + } - public static class TL_channels_toggleInvites extends TLObject { - public static int constructor = 0x49609307; + public static class TL_payments_getPaymentReceipt extends TLObject { + public static int constructor = 0xa092a980; - public InputChannel channel; - public boolean enabled; + public int msg_id; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); + return TL_payments_paymentReceipt.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - channel.serializeToStream(stream); - stream.writeBool(enabled); + stream.writeInt32(msg_id); } } - public static class TL_channels_exportMessageLink extends TLObject { - public static int constructor = 0xc846d22d; + public static class TL_payments_validateRequestedInfo extends TLObject { + public static int constructor = 0x770a8e74; - public InputChannel channel; - public int id; + public int flags; + public boolean save; + public int msg_id; + public TL_paymentRequestedInfo info; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_exportedMessageLink.TLdeserialize(stream, constructor, exception); + return TL_payments_validatedRequestedInfo.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - channel.serializeToStream(stream); - stream.writeInt32(id); + flags = save ? (flags | 1) : (flags &~ 1); + stream.writeInt32(flags); + stream.writeInt32(msg_id); + info.serializeToStream(stream); } } - public static class TL_channels_toggleSignatures extends TLObject { - public static int constructor = 0x1f69b606; + public static class TL_payments_sendPaymentForm extends TLObject { + public static int constructor = 0x2b8879b3; - public InputChannel channel; - public boolean enabled; + public int flags; + public int msg_id; + public String requested_info_id; + public String shipping_option_id; + public InputPaymentCredentials credentials; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); + return payments_PaymentResult.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - channel.serializeToStream(stream); - stream.writeBool(enabled); + stream.writeInt32(flags); + stream.writeInt32(msg_id); + if ((flags & 1) != 0) { + stream.writeString(requested_info_id); + } + if ((flags & 2) != 0) { + stream.writeString(shipping_option_id); + } + credentials.serializeToStream(stream); } } - public static class TL_channels_updatePinnedMessage extends TLObject { - public static int constructor = 0xa72ded52; + public static class TL_payments_getSavedInfo extends TLObject { + public static int constructor = 0x227d824b; - public int flags; - public boolean silent; - public InputChannel channel; - public int id; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return Updates.TLdeserialize(stream, constructor, exception); + return TL_payments_savedInfo.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); - flags = silent ? (flags | 1) : (flags &~ 1); - stream.writeInt32(flags); - channel.serializeToStream(stream); - stream.writeInt32(id); } } - public static class TL_channels_getAdminedPublicChannels extends TLObject { - public static int constructor = 0x8d8d82d7; + public static class TL_payments_clearSavedInfo extends TLObject { + public static int constructor = 0xd83d70c1; + public int flags; + public boolean credentials; + public boolean info; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_messages_chats.TLdeserialize(stream, constructor, exception); + return Bool.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = credentials ? (flags | 1) : (flags &~ 1); + flags = info ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); } } //manually created + //RichText start + public static class RichText extends TLObject { + public String url; + public long webpage_id; + public String email; + public ArrayList texts = new ArrayList<>(); + public RichText parentRichText; + + public static RichText TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + RichText result = null; + switch(constructor) { + case 0xdc3d824f: + result = new TL_textEmpty(); + break; + case 0x3c2884c1: + result = new TL_textUrl(); + break; + case 0x9bf8bb95: + result = new TL_textStrike(); + break; + case 0x6c3f19b9: + result = new TL_textFixed(); + break; + case 0xde5a0dd6: + result = new TL_textEmail(); + break; + case 0x744694e0: + result = new TL_textPlain(); + break; + case 0x7e6260d7: + result = new TL_textConcat(); + break; + case 0x6724abc4: + result = new TL_textBold(); + break; + case 0xd912a59c: + result = new TL_textItalic(); + break; + case 0xc12622c4: + result = new TL_textUnderline(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in RichText", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + //RichText end + //MessageMedia start public static class MessageMedia extends TLObject { public byte[] bytes; public Audio audio_unused; + public int flags; + public boolean shipping_address_requested; public Photo photo; public GeoPoint geo; + public String currency; + public String description; + public int receipt_msg_id; + public long total_amount; + public String start_param; public String title; public String address; public String provider; @@ -21246,6 +24463,7 @@ public static class MessageMedia extends TLObject { public String last_name; public int user_id; public WebPage webpage; + public boolean test; public static MessageMedia TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageMedia result = null; @@ -21259,6 +24477,9 @@ public static MessageMedia TLdeserialize(AbstractSerializedData stream, int cons case 0xc8c45a2a: result = new TL_messageMediaPhoto_old(); break; + case 0x84551347: + result = new TL_messageMediaInvoice(); + break; case 0x9f84f49e: result = new TL_messageMediaUnsupported(); break; @@ -21368,6 +24589,25 @@ public static MessageMedia TLdeserialize(AbstractSerializedData stream, int cons } //MessageMedia end + //PageBlock start + public static class TL_pageBlockAuthorDate_layer60 extends TL_pageBlockAuthorDate { + public static int constructor = 0x3d5b64f2; + + public void readParams(AbstractSerializedData stream, boolean exception) { + String authorString = stream.readString(exception); + author = new TL_textPlain(); + ((TL_textPlain) author).text = authorString; + published_date = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeString(((TL_textPlain) author).text); + stream.writeInt32(published_date); + } + } + //PageBlock end + //EncryptedChat start public static class EncryptedChat extends TLObject { public int id; @@ -22729,6 +25969,7 @@ public static class TL_dialog extends TLObject { public static int constructor = 0x66ffba14; public int flags; + public boolean pinned; public Peer peer; public int top_message; public int read_inbox_max_id; @@ -22739,6 +25980,7 @@ public static class TL_dialog extends TLObject { public DraftMessage draft; public int last_message_date; //custom public long id; //custom + public int pinnedNum; //custom public static TL_dialog TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { if (TL_dialog.constructor != constructor) { @@ -22755,6 +25997,7 @@ public static TL_dialog TLdeserialize(AbstractSerializedData stream, int constru public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); + pinned = (flags & 4) != 0; peer = Peer.TLdeserialize(stream, stream.readInt32(exception), exception); top_message = stream.readInt32(exception); read_inbox_max_id = stream.readInt32(exception); @@ -22771,6 +26014,7 @@ public void readParams(AbstractSerializedData stream, boolean exception) { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + flags = pinned ? (flags | 4) : (flags &~ 4); stream.writeInt32(flags); peer.serializeToStream(stream); stream.writeInt32(top_message); @@ -22877,6 +26121,76 @@ public void freeResources() { } } + public static class TL_upload_getWebFile extends TLObject { + public static int constructor = 0x24e6818d; + + public TL_inputWebFileLocation location; + public int offset; + public int limit; + + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_upload_webFile.TLdeserialize(stream, constructor, exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + location.serializeToStream(stream); + stream.writeInt32(offset); + stream.writeInt32(limit); + } + } + + public static class TL_upload_webFile extends TLObject { + public static int constructor = 0x21e753bc; + + public int size; + public String mime_type; + public storage_FileType file_type; + public int mtime; + public NativeByteBuffer bytes; + + public static TL_upload_webFile TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_upload_webFile.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_upload_webFile", constructor)); + } else { + return null; + } + } + TL_upload_webFile result = new TL_upload_webFile(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + size = stream.readInt32(exception); + mime_type = stream.readString(exception); + file_type = storage_FileType.TLdeserialize(stream, stream.readInt32(exception), exception); + mtime = stream.readInt32(exception); + bytes = stream.readByteBuffer(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(size); + stream.writeString(mime_type); + file_type.serializeToStream(stream); + stream.writeInt32(mtime); + stream.writeByteBuffer(bytes); + } + + @Override + public void freeResources() { + if (disableFree) { + return; + } + if (bytes != null) { + bytes.reuse(); + bytes = null; + } + } + } + public static class TL_upload_file extends TLObject { public static int constructor = 0x96a18d5; @@ -22903,6 +26217,13 @@ public void readParams(AbstractSerializedData stream, boolean exception) { bytes = stream.readByteBuffer(exception); } + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + type.serializeToStream(stream); + stream.writeInt32(mtime); + stream.writeByteBuffer(bytes); + } + @Override public void freeResources() { if (disableFree) { @@ -22915,6 +26236,46 @@ public void freeResources() { } } + public static class TL_phoneCallProtocol extends TLObject { + public static int constructor = 0xa2bb35cb; + + public int flags; + public boolean udp_p2p; + public boolean udp_reflector; + public int min_layer; + public int max_layer; + + public static TL_phoneCallProtocol TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (TL_phoneCallProtocol.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_phoneCallProtocol", constructor)); + } else { + return null; + } + } + TL_phoneCallProtocol result = new TL_phoneCallProtocol(); + result.readParams(stream, exception); + return result; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + udp_p2p = (flags & 1) != 0; + udp_reflector = (flags & 2) != 0; + min_layer = stream.readInt32(exception); + max_layer = stream.readInt32(exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = udp_p2p ? (flags | 1) : (flags &~ 1); + flags = udp_reflector ? (flags | 2) : (flags &~ 2); + stream.writeInt32(flags); + stream.writeInt32(min_layer); + stream.writeInt32(max_layer); + } + } + public static class TL_messages_sendEncryptedFile extends TLObject { public static int constructor = 0x9a901b66; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java index 956efd4f8c0..ecd9c6310fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBar.java @@ -3,18 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Configuration; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; +import android.text.TextUtils; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -22,7 +26,6 @@ import android.widget.ImageView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -58,6 +61,9 @@ public boolean canOpenMenu() { protected boolean isSearchFieldVisible; protected int itemsBackgroundColor; + protected int itemsActionModeBackgroundColor; + protected int itemsColor; + protected int itemsActionModeColor; private boolean isBackOverlayVisible; protected BaseFragment parentFragment; public ActionBarMenuOnItemClick actionBarMenuOnItemClick; @@ -72,7 +78,10 @@ private void createBackButtonImage() { } backButtonImageView = new ImageView(getContext()); backButtonImageView.setScaleType(ImageView.ScaleType.CENTER); - backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); + backButtonImageView.setBackgroundDrawable(Theme.createSelectorDrawable(itemsBackgroundColor)); + if (itemsColor != 0) { + backButtonImageView.setColorFilter(new PorterDuffColorFilter(itemsColor, PorterDuff.Mode.MULTIPLY)); + } backButtonImageView.setPadding(AndroidUtilities.dp(1), 0, 0, 0); addView(backButtonImageView, LayoutHelper.createFrame(54, 54, Gravity.LEFT | Gravity.TOP)); @@ -97,7 +106,10 @@ public void setBackButtonDrawable(Drawable drawable) { backButtonImageView.setVisibility(drawable == null ? GONE : VISIBLE); backButtonImageView.setImageDrawable(drawable); if (drawable instanceof BackDrawable) { - ((BackDrawable) drawable).setRotation(isActionModeShowed() ? 1 : 0, false); + BackDrawable backDrawable = (BackDrawable) drawable; + backDrawable.setRotation(isActionModeShowed() ? 1 : 0, false); + backDrawable.setRotatedColor(itemsActionModeColor); + backDrawable.setColor(itemsColor); } } @@ -115,7 +127,8 @@ private void createSubtitleTextView() { } subtitleTextView = new SimpleTextView(getContext()); subtitleTextView.setGravity(Gravity.LEFT); - subtitleTextView.setTextColor(Theme.ACTION_BAR_SUBTITLE_COLOR); + subtitleTextView.setVisibility(GONE); + subtitleTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); addView(subtitleTextView, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); } @@ -132,7 +145,7 @@ public void setSubtitle(CharSequence value) { createSubtitleTextView(); } if (subtitleTextView != null) { - subtitleTextView.setVisibility(value != null && !isSearchFieldVisible ? VISIBLE : GONE); + subtitleTextView.setVisibility(!TextUtils.isEmpty(value) && !isSearchFieldVisible ? VISIBLE : GONE); subtitleTextView.setText(value); } } @@ -143,7 +156,7 @@ private void createTitleTextView() { } titleTextView = new SimpleTextView(getContext()); titleTextView.setGravity(Gravity.LEFT); - titleTextView.setTextColor(0xffffffff); + titleTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(titleTextView, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); } @@ -159,6 +172,13 @@ public void setTitle(CharSequence value) { } } + public void setTitleColor(int color) { + if (titleTextView == null) { + createTitleTextView(); + } + titleTextView.setTextColor(color); + } + public void setSubtitleColor(int color) { if (subtitleTextView == null) { createSubtitleTextView(); @@ -166,6 +186,18 @@ public void setSubtitleColor(int color) { subtitleTextView.setTextColor(color); } + public void setPopupItemsColor(int color) { + if (menu != null) { + menu.setPopupItemsColor(color); + } + } + + public void setPopupBackgroundColor(int color) { + if (menu != null) { + menu.redrawPopup(color); + } + } + public SimpleTextView getSubtitleTextView() { return subtitleTextView; } @@ -201,12 +233,17 @@ public void setActionBarMenuOnItemClick(ActionBarMenuOnItemClick listener) { actionBarMenuOnItemClick = listener; } + public View getBackButton() { + return backButtonImageView; + } + public ActionBarMenu createActionMode() { if (actionMode != null) { return actionMode; } actionMode = new ActionBarMenu(getContext(), this); - actionMode.setBackgroundColor(0xffffffff); + actionMode.isActionMode = true; + actionMode.setBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefault)); addView(actionMode, indexOfChild(backButtonImageView)); actionMode.setPadding(0, occupyStatusBar ? AndroidUtilities.statusBarHeight : 0, 0, 0); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams)actionMode.getLayoutParams(); @@ -218,7 +255,7 @@ public ActionBarMenu createActionMode() { if (occupyStatusBar && actionModeTop == null) { actionModeTop = new View(getContext()); - actionModeTop.setBackgroundColor(0x99000000); + actionModeTop.setBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefaultTop)); addView(actionModeTop); layoutParams = (FrameLayout.LayoutParams)actionModeTop.getLayoutParams(); layoutParams.height = AndroidUtilities.statusBarHeight; @@ -247,7 +284,7 @@ public void showActionMode() { actionModeAnimation = new AnimatorSet(); actionModeAnimation.playTogether(animators); actionModeAnimation.setDuration(200); - actionModeAnimation.addListener(new AnimatorListenerAdapterProxy() { + actionModeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { actionMode.setVisibility(VISIBLE); @@ -285,7 +322,7 @@ public void onAnimationCancel(Animator animation) { if (drawable instanceof BackDrawable) { ((BackDrawable) drawable).setRotation(1, true); } - backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_MODE_SELECTOR_COLOR)); + backButtonImageView.setBackgroundDrawable(Theme.createSelectorDrawable(itemsActionModeBackgroundColor)); } } @@ -305,7 +342,7 @@ public void hideActionMode() { actionModeAnimation = new AnimatorSet(); actionModeAnimation.playTogether(animators); actionModeAnimation.setDuration(200); - actionModeAnimation.addListener(new AnimatorListenerAdapterProxy() { + actionModeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (actionModeAnimation != null && actionModeAnimation.equals(animation)) { @@ -339,14 +376,14 @@ public void onAnimationCancel(Animator animation) { if (drawable instanceof BackDrawable) { ((BackDrawable) drawable).setRotation(0, true); } - backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); + backButtonImageView.setBackgroundDrawable(Theme.createSelectorDrawable(itemsBackgroundColor)); } } public void showActionModeTop() { if (occupyStatusBar && actionModeTop == null) { actionModeTop = new View(getContext()); - actionModeTop.setBackgroundColor(0x99000000); + actionModeTop.setBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefaultTop)); addView(actionModeTop); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) actionModeTop.getLayoutParams(); layoutParams.height = AndroidUtilities.statusBarHeight; @@ -356,6 +393,24 @@ public void showActionModeTop() { } } + public void setActionModeTopColor(int color) { + if (actionModeTop != null) { + actionModeTop.setBackgroundColor(color); + } + } + + public void setSearchTextColor(int color, boolean placeholder) { + if (menu != null) { + menu.setSearchTextColor(color, placeholder); + } + } + + public void setActionModeColor(int color) { + if (actionMode != null) { + actionMode.setBackgroundColor(color); + } + } + public boolean isActionModeShowed() { return actionMode != null && actionModeVisible; } @@ -574,10 +629,54 @@ public boolean getOccupyStatusBar() { return occupyStatusBar; } - public void setItemsBackgroundColor(int color) { - itemsBackgroundColor = color; - if (backButtonImageView != null) { - backButtonImageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(itemsBackgroundColor)); + public void setItemsBackgroundColor(int color, boolean isActionMode) { + if (isActionMode) { + itemsActionModeBackgroundColor = color; + if (actionModeVisible) { + if (backButtonImageView != null) { + backButtonImageView.setBackgroundDrawable(Theme.createSelectorDrawable(itemsActionModeBackgroundColor)); + } + } + if (actionMode != null) { + actionMode.updateItemsBackgroundColor(); + } + } else { + itemsBackgroundColor = color; + if (backButtonImageView != null) { + backButtonImageView.setBackgroundDrawable(Theme.createSelectorDrawable(itemsBackgroundColor)); + } + if (menu != null) { + menu.updateItemsBackgroundColor(); + } + } + } + + public void setItemsColor(int color, boolean isActionMode) { + if (isActionMode) { + itemsActionModeColor = color; + if (actionMode != null) { + actionMode.updateItemsColor(); + } + if (backButtonImageView != null) { + Drawable drawable = backButtonImageView.getDrawable(); + if (drawable instanceof BackDrawable) { + ((BackDrawable) drawable).setRotatedColor(color); + } + } + } else { + itemsColor = color; + if (backButtonImageView != null) { + if (itemsColor != 0) { + backButtonImageView.setColorFilter(new PorterDuffColorFilter(itemsColor, PorterDuff.Mode.MULTIPLY)); + Drawable drawable = backButtonImageView.getDrawable(); + if (drawable instanceof BackDrawable) { + ((BackDrawable) drawable).setColor(color); + } + } + } + if (menu != null) { + menu.updateItemsColor(); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java index 1822c4e15cb..a1014c75261 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarLayout.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; @@ -22,6 +23,7 @@ import android.os.Handler; import android.view.Gravity; import android.view.KeyEvent; +import android.view.Menu; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -33,7 +35,6 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -159,7 +160,7 @@ public ActionBarLayout(Context context) { if (layerShadowDrawable == null) { layerShadowDrawable = getResources().getDrawable(R.drawable.layer_shadow); - headerShadowDrawable = getResources().getDrawable(R.drawable.header_shadow); + headerShadowDrawable = getResources().getDrawable(R.drawable.header_shadow).mutate(); scrimPaint = new Paint(); } } @@ -193,6 +194,9 @@ public void onConfigurationChanged(android.content.res.Configuration newConfig) if (!fragmentsStack.isEmpty()) { BaseFragment lastFragment = fragmentsStack.get(fragmentsStack.size() - 1); lastFragment.onConfigurationChanged(newConfig); + if (lastFragment.visibleDialog instanceof BottomSheet) { + ((BottomSheet) lastFragment.visibleDialog).onConfigurationChanged(newConfig); + } } } @@ -385,7 +389,7 @@ private void prepareForMoving(MotionEvent ev) { layoutParams.height = LayoutHelper.MATCH_PARENT; fragmentView.setLayoutParams(layoutParams); if (!lastFragment.hasOwnBackground && fragmentView.getBackground() == null) { - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } lastFragment.onResume(); } @@ -466,7 +470,7 @@ public boolean onTouchEvent(MotionEvent ev) { } animatorSet.setDuration(Math.max((int) (200.0f / containerView.getMeasuredWidth() * distToMove), 50)); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { onSlideAnimationEnd(backAnimation); @@ -682,7 +686,7 @@ public boolean presentFragment(final BaseFragment fragment, final boolean remove fragment.onResume(); currentActionBar = fragment.actionBar; if (!fragment.hasOwnBackground && fragmentView.getBackground() == null) { - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } LinearLayoutContainer temp = containerView; @@ -724,7 +728,7 @@ public void run() { currentAnimation.playTogether(animators); currentAnimation.setInterpolator(accelerateDecelerateInterpolator); currentAnimation.setDuration(200); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { onAnimationEndCheck(false); @@ -764,6 +768,7 @@ public void run() { if (waitingForKeyboardCloseRunnable != this) { return; } + waitingForKeyboardCloseRunnable = null; startLayoutAnimation(true, true); } }; @@ -899,7 +904,7 @@ public void closeLastFragment(boolean animated) { previousFragment.onResume(); currentActionBar = previousFragment.actionBar; if (!previousFragment.hasOwnBackground && fragmentView.getBackground() == null) { - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } if (!needAnimation) { @@ -938,6 +943,7 @@ public void run() { if (waitingForKeyboardCloseRunnable != this) { return; } + waitingForKeyboardCloseRunnable = null; startLayoutAnimation(false, true); } }; @@ -986,7 +992,7 @@ public void run() { currentAnimation.playTogether(animators); currentAnimation.setInterpolator(accelerateDecelerateInterpolator); currentAnimation.setDuration(200); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { transitionAnimationStartTime = System.currentTimeMillis(); @@ -1058,7 +1064,7 @@ public void showLastFragment() { previousFragment.onResume(); currentActionBar = previousFragment.actionBar; if (!previousFragment.hasOwnBackground && fragmentView.getBackground() == null) { - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } } @@ -1205,6 +1211,10 @@ public void setTitleOverlayText(String text) { } } + public boolean extendActionMode(Menu menu) { + return !fragmentsStack.isEmpty() && fragmentsStack.get(fragmentsStack.size() - 1).extendActionMode(menu); + } + @Override public boolean hasOverlappingRendering() { return false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java index 0ca40d4e194..07ad493e5e8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenu.java @@ -3,23 +3,25 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.LinearLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.ui.Components.LayoutHelper; public class ActionBarMenu extends LinearLayout { protected ActionBar parentActionBar; + protected boolean isActionMode; public ActionBarMenu(Context context, ActionBar layer) { super(context); @@ -31,30 +33,32 @@ public ActionBarMenu(Context context) { super(context); } - public View addItemResource(int id, int resourceId) { - LayoutInflater li = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = li.inflate(resourceId, null); - view.setTag(id); - addView(view); - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) view.getLayoutParams(); - layoutParams.height = LayoutHelper.MATCH_PARENT; - view.setBackgroundDrawable(Theme.createBarSelectorDrawable(parentActionBar.itemsBackgroundColor)); - view.setLayoutParams(layoutParams); - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - onItemClick((Integer) view.getTag()); + protected void updateItemsBackgroundColor() { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + view.setBackgroundDrawable(Theme.createSelectorDrawable(isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor)); } - }); - return view; + } + } + + protected void updateItemsColor() { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + ((ActionBarMenuItem) view).setIconColor(isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor); + } + } } public ActionBarMenuItem addItem(int id, Drawable drawable) { - return addItem(id, 0, parentActionBar.itemsBackgroundColor, drawable, AndroidUtilities.dp(48)); + return addItem(id, 0, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, drawable, AndroidUtilities.dp(48)); } public ActionBarMenuItem addItem(int id, int icon) { - return addItem(id, icon, parentActionBar.itemsBackgroundColor); + return addItem(id, icon, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor); } public ActionBarMenuItem addItem(int id, int icon, int backgroundColor) { @@ -62,22 +66,18 @@ public ActionBarMenuItem addItem(int id, int icon, int backgroundColor) { } public ActionBarMenuItem addItemWithWidth(int id, int icon, int width) { - return addItem(id, icon, parentActionBar.itemsBackgroundColor, null, width); + return addItem(id, icon, isActionMode ? parentActionBar.itemsActionModeBackgroundColor : parentActionBar.itemsBackgroundColor, null, width); } public ActionBarMenuItem addItem(int id, int icon, int backgroundColor, Drawable drawable, int width) { - ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor); + ActionBarMenuItem menuItem = new ActionBarMenuItem(getContext(), this, backgroundColor, isActionMode ? parentActionBar.itemsActionModeColor : parentActionBar.itemsColor); menuItem.setTag(id); if (drawable != null) { menuItem.iconView.setImageDrawable(drawable); } else { menuItem.iconView.setImageResource(icon); } - addView(menuItem); - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) menuItem.getLayoutParams(); - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.width = width; - menuItem.setLayoutParams(layoutParams); + addView(menuItem, new LinearLayout.LayoutParams(width, ViewGroup.LayoutParams.MATCH_PARENT)); menuItem.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { @@ -106,6 +106,28 @@ public void hideAllPopupMenus() { } } + protected void setPopupItemsColor(int color) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + ActionBarMenuItem item = (ActionBarMenuItem) view; + item.setPopupItemsColor(color); + } + } + } + + protected void redrawPopup(int color) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + ActionBarMenuItem item = (ActionBarMenuItem) view; + item.redrawPopup(color); + } + } + } + public void onItemClick(int id) { if (parentActionBar.actionBarMenuOnItemClick != null) { parentActionBar.actionBarMenuOnItemClick.onItemClick(id); @@ -150,6 +172,24 @@ public void closeSearchField() { } } + public void setSearchTextColor(int color, boolean placeholder) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View view = getChildAt(a); + if (view instanceof ActionBarMenuItem) { + ActionBarMenuItem item = (ActionBarMenuItem) view; + if (item.isSearchField()) { + if (placeholder) { + item.getSearchField().setHintTextColor(color); + } else { + item.getSearchField().setTextColor(color); + } + break; + } + } + } + } + public void openSearchField(boolean toggle, String text) { int count = getChildCount(); for (int a = 0; a < count; a++) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java index b0383079b95..ff12b6f06cc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuItem.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.os.Build; import android.text.Editable; @@ -27,14 +29,17 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.PopupWindow; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.ui.Components.LayoutHelper; import java.lang.reflect.Field; +import java.lang.reflect.Method; public class ActionBarMenuItem extends FrameLayout { @@ -81,17 +86,22 @@ public interface ActionBarMenuItemDelegate { private boolean allowCloseAnimation = true; protected boolean overrideMenuClick; private boolean processedPopupClick; + private boolean layoutInScreen; + private static Method layoutInScreenMethod; - public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor) { + public ActionBarMenuItem(Context context, ActionBarMenu menu, int backgroundColor, int iconColor) { super(context); if (backgroundColor != 0) { - setBackgroundDrawable(Theme.createBarSelectorDrawable(backgroundColor)); + setBackgroundDrawable(Theme.createSelectorDrawable(backgroundColor)); } parentMenu = menu; iconView = new ImageView(context); iconView.setScaleType(ImageView.ScaleType.CENTER); addView(iconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + if (iconColor != 0) { + iconView.setColorFilter(new PorterDuffColorFilter(iconColor, PorterDuff.Mode.MULTIPLY)); + } } @Override @@ -182,11 +192,22 @@ public void setShowFromBottom(boolean value) { } } + public void setIconColor(int color) { + iconView.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + if (clearButton != null) { + clearButton.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + public void setSubMenuOpenSide(int side) { subMenuOpenSide = side; } - public TextView addSubItem(int id, String text, int icon) { + public void setLayoutInScreen(boolean value) { + layoutInScreen = value; + } + + public TextView addSubItem(int id, String text) { if (popupLayout == null) { rect = new Rect(); location = new int[2]; @@ -215,8 +236,8 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { }); } TextView textView = new TextView(getContext()); - textView.setTextColor(0xff212121); - textView.setBackgroundResource(R.drawable.list_selector); + textView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem)); + textView.setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (!LocaleController.isRTL) { textView.setGravity(Gravity.CENTER_VERTICAL); } else { @@ -227,14 +248,6 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { textView.setMinWidth(AndroidUtilities.dp(196)); textView.setTag(id); textView.setText(text); - if (icon != 0) { - textView.setCompoundDrawablePadding(AndroidUtilities.dp(12)); - if (!LocaleController.isRTL) { - textView.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(icon), null, null, null); - } else { - textView.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(icon), null); - } - } popupLayout.setShowedFromBotton(showFromBottom); popupLayout.addView(textView); LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams(); @@ -244,6 +257,7 @@ public void onDispatchKeyEvent(KeyEvent keyEvent) { layoutParams.width = LayoutHelper.MATCH_PARENT; layoutParams.height = AndroidUtilities.dp(48); textView.setLayoutParams(layoutParams); + textView.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { @@ -265,6 +279,26 @@ public void onClick(View view) { return textView; } + protected void redrawPopup(int color) { + if (popupLayout != null) { + popupLayout.backgroundDrawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + popupLayout.invalidate(); + } + } + + public void setPopupItemsColor(int color) { + if (popupLayout == null) { + return; + } + int count = popupLayout.linearLayout.getChildCount(); + for (int a = 0; a < count; a++) { + View child = popupLayout.linearLayout.getChildAt(a); + if (child instanceof TextView) { + ((TextView) child).setTextColor(color); + } + } + } + public boolean hasSubMenu() { return popupLayout != null; } @@ -290,6 +324,17 @@ public void toggleSubMenu() { } popupWindow.setOutsideTouchable(true); popupWindow.setClippingEnabled(true); + if (layoutInScreen) { + try { + if (layoutInScreenMethod == null) { + layoutInScreenMethod = PopupWindow.class.getDeclaredMethod("setLayoutInScreenEnabled", boolean.class); + layoutInScreenMethod.setAccessible(true); + } + layoutInScreenMethod.invoke(popupWindow, true); + } catch (Exception e) { + FileLog.e(e); + } + } popupWindow.setInputMethodMode(ActionBarPopupWindow.INPUT_METHOD_NOT_NEEDED); popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED); popupLayout.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(1000), MeasureSpec.AT_MOST)); @@ -381,19 +426,13 @@ public ActionBarMenuItem setIsSearchField(boolean value) { } if (value && searchContainer == null) { searchContainer = new FrameLayout(getContext()); - parentMenu.addView(searchContainer, 0); - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) searchContainer.getLayoutParams(); - layoutParams.weight = 1; - layoutParams.width = 0; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.leftMargin = AndroidUtilities.dp(6); - searchContainer.setLayoutParams(layoutParams); + parentMenu.addView(searchContainer, 0, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 6, 0, 0, 0)); searchContainer.setVisibility(GONE); searchField = new EditText(getContext()); searchField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - searchField.setHintTextColor(0x88ffffff); - searchField.setTextColor(0xffffffff); + searchField.setHintTextColor(Theme.getColor(Theme.key_actionBarDefaultSearchPlaceholder)); + searchField.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSearch)); searchField.setSingleLine(true); searchField.setBackgroundResource(0); searchField.setPadding(0, 0, 0, 0); @@ -459,16 +498,11 @@ public void afterTextChanged(Editable s) { } searchField.setImeOptions(EditorInfo.IME_FLAG_NO_FULLSCREEN | EditorInfo.IME_ACTION_SEARCH); searchField.setTextIsSelectable(false); - searchContainer.addView(searchField); - FrameLayout.LayoutParams layoutParams2 = (FrameLayout.LayoutParams) searchField.getLayoutParams(); - layoutParams2.width = LayoutHelper.MATCH_PARENT; - layoutParams2.gravity = Gravity.CENTER_VERTICAL; - layoutParams2.height = AndroidUtilities.dp(36); - layoutParams2.rightMargin = AndroidUtilities.dp(48); - searchField.setLayoutParams(layoutParams2); + searchContainer.addView(searchField, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_VERTICAL, 0, 0, 48, 0)); clearButton = new ImageView(getContext()); clearButton.setImageResource(R.drawable.ic_close_white); + clearButton.setColorFilter(new PorterDuffColorFilter(parentMenu.parentActionBar.itemsColor, PorterDuff.Mode.MULTIPLY)); clearButton.setScaleType(ImageView.ScaleType.CENTER); clearButton.setOnClickListener(new OnClickListener() { @Override @@ -478,12 +512,7 @@ public void onClick(View v) { AndroidUtilities.showKeyboard(searchField); } }); - searchContainer.addView(clearButton); - layoutParams2 = (FrameLayout.LayoutParams) clearButton.getLayoutParams(); - layoutParams2.width = AndroidUtilities.dp(48); - layoutParams2.gravity = Gravity.CENTER_VERTICAL | Gravity.RIGHT; - layoutParams2.height = LayoutHelper.MATCH_PARENT; - clearButton.setLayoutParams(layoutParams2); + searchContainer.addView(clearButton, LayoutHelper.createFrame(48, LayoutHelper.MATCH_PARENT, Gravity.CENTER_VERTICAL | Gravity.RIGHT)); } isSearchField = value; return this; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java index 6f354f64041..0d6eb7e59d2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarPopupWindow.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ //Thanks to https://github.com/JakeWharton/ActionBarSherlock/ @@ -15,6 +15,8 @@ import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.KeyEvent; @@ -70,7 +72,7 @@ public interface OnDispatchKeyEventListener { public static class ActionBarPopupWindowLayout extends FrameLayout { private OnDispatchKeyEventListener mOnDispatchKeyEventListener; - protected static Drawable backgroundDrawable; + protected Drawable backgroundDrawable; private float backScaleX = 1; private float backScaleY = 1; private int backAlpha = 255; @@ -80,14 +82,13 @@ public static class ActionBarPopupWindowLayout extends FrameLayout { private HashMap positions = new HashMap<>(); private ScrollView scrollView; - private LinearLayout linearLayout; + protected LinearLayout linearLayout; public ActionBarPopupWindowLayout(Context context) { super(context); - if (backgroundDrawable == null) { - backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed); - } + backgroundDrawable = getResources().getDrawable(R.drawable.popup_fixed).mutate(); + backgroundDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultSubmenuBackground), PorterDuff.Mode.MULTIPLY)); setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); setWillNotDraw(false); @@ -97,7 +98,7 @@ public ActionBarPopupWindowLayout(Context context) { scrollView.setVerticalScrollBarEnabled(false); addView(scrollView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } @@ -216,6 +217,7 @@ public boolean dispatchKeyEvent(KeyEvent event) { protected void onDraw(Canvas canvas) { if (backgroundDrawable != null) { backgroundDrawable.setAlpha(backAlpha); + int height = getMeasuredHeight(); if (showedFromBotton) { backgroundDrawable.setBounds(0, (int) (getMeasuredHeight() * (1.0f - backScaleY)), (int) (getMeasuredWidth() * backScaleX), getMeasuredHeight()); } else { @@ -314,7 +316,7 @@ public void showAsDropDown(View anchor, int xoff, int yoff) { super.showAsDropDown(anchor, xoff, yoff); registerListener(anchor); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java new file mode 100644 index 00000000000..2272a569e9f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java @@ -0,0 +1,762 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.ActionBar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LineProgressView; +import org.telegram.ui.Components.RadialProgressView; + +public class AlertDialog extends Dialog implements Drawable.Callback { + + private View customView; + private TextView titleTextView; + private TextView messageTextView; + private FrameLayout progressViewContainer; + private TextView progressViewTextView; + private ScrollView contentScrollView; + private LinearLayout scrollContainer; + private ViewTreeObserver.OnScrollChangedListener onScrollChangedListener; + private BitmapDrawable shadow[] = new BitmapDrawable[2]; + private boolean shadowVisibility[] = new boolean[2]; + private AnimatorSet shadowAnimation[] = new AnimatorSet[2]; + + private int lastScreenWidth; + + private OnClickListener onClickListener; + private OnDismissListener onDismissListener; + + private CharSequence[] items; + private int[] itemIcons; + private CharSequence title; + private CharSequence message; + private int progressViewStyle; + private int currentProgress; + + private CharSequence positiveButtonText; + private OnClickListener positiveButtonListener; + private CharSequence negativeButtonText; + private OnClickListener negativeButtonListener; + private CharSequence neutralButtonText; + private OnClickListener neutralButtonListener; + private FrameLayout buttonsLayout; + private LineProgressView lineProgressView; + private TextView lineProgressViewPercent; + + private Drawable shadowDrawable; + private Rect backgroundPaddings; + + private class AlertDialogCell extends FrameLayout { + + private TextView textView; + private ImageView imageView; + + public AlertDialogCell(Context context) { + super(context); + + setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 2)); + setPadding(AndroidUtilities.dp(23), 0, AndroidUtilities.dp(23), 0); + + imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); + addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); + + textView = new TextView(context); + textView.setLines(1); + textView.setSingleLine(true); + textView.setGravity(Gravity.CENTER_HORIZONTAL); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + } + + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public void setGravity(int gravity) { + textView.setGravity(gravity); + } + + public void setTextAndIcon(CharSequence text, int icon) { + textView.setText(text); + if (icon != 0) { + imageView.setImageResource(icon); + imageView.setVisibility(VISIBLE); + textView.setPadding(LocaleController.isRTL ? 0 : AndroidUtilities.dp(56), 0, LocaleController.isRTL ? AndroidUtilities.dp(56) : 0, 0); + } else { + imageView.setVisibility(INVISIBLE); + textView.setPadding(0, 0, 0, 0); + } + } + } + + public AlertDialog(Context context, int progressStyle) { + super(context, R.style.TransparentDialog); + + backgroundPaddings = new Rect(); + shadowDrawable = context.getResources().getDrawable(R.drawable.popup_fixed).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); + shadowDrawable.getPadding(backgroundPaddings); + + progressViewStyle = progressStyle; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + LinearLayout containerView = new LinearLayout(getContext()) { + + private boolean inLayout; + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + inLayout = true; + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + int maxContentHeight; + int availableHeight = maxContentHeight = height - getPaddingTop() - getPaddingBottom(); + int availableWidth = width - getPaddingLeft() - getPaddingRight(); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(availableWidth - AndroidUtilities.dp(48), MeasureSpec.EXACTLY); + int childFullWidthMeasureSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY); + LayoutParams layoutParams; + + if (buttonsLayout != null) { + int count = buttonsLayout.getChildCount(); + for (int a = 0; a < count; a++) { + TextView button = (TextView) buttonsLayout.getChildAt(a); + button.setMaxWidth(AndroidUtilities.dp((availableWidth - AndroidUtilities.dp(24)) / 2)); + } + buttonsLayout.measure(childFullWidthMeasureSpec, heightMeasureSpec); + layoutParams = (LayoutParams) buttonsLayout.getLayoutParams(); + availableHeight -= buttonsLayout.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + } + if (titleTextView != null) { + titleTextView.measure(childWidthMeasureSpec, heightMeasureSpec); + layoutParams = (LayoutParams) titleTextView.getLayoutParams(); + availableHeight -= titleTextView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + } + if (progressViewStyle == 0) { + layoutParams = (LayoutParams) contentScrollView.getLayoutParams(); + + if (customView != null) { + layoutParams.topMargin = titleTextView == null && messageTextView.getVisibility() == GONE && items == null ? AndroidUtilities.dp(16) : 0; + layoutParams.bottomMargin = buttonsLayout == null ? AndroidUtilities.dp(8) : 0; + } else if (items != null) { + layoutParams.topMargin = titleTextView == null && messageTextView.getVisibility() == GONE ? AndroidUtilities.dp(8) : 0; + layoutParams.bottomMargin = AndroidUtilities.dp(8); + } else if (messageTextView.getVisibility() == VISIBLE) { + layoutParams.topMargin = titleTextView == null ? AndroidUtilities.dp(19) : 0; + layoutParams.bottomMargin = AndroidUtilities.dp(20); + } + + availableHeight -= layoutParams.bottomMargin + layoutParams.topMargin; + contentScrollView.measure(childFullWidthMeasureSpec, MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST)); + availableHeight -= contentScrollView.getMeasuredHeight(); + } else { + if (progressViewContainer != null) { + progressViewContainer.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST)); + layoutParams = (LayoutParams) progressViewContainer.getLayoutParams(); + availableHeight -= progressViewContainer.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + } else if (messageTextView != null) { + messageTextView.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST)); + if (messageTextView.getVisibility() != GONE) { + layoutParams = (LayoutParams) messageTextView.getLayoutParams(); + availableHeight -= messageTextView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + } + } + if (lineProgressView != null) { + lineProgressView.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(4), MeasureSpec.EXACTLY)); + layoutParams = (LayoutParams) lineProgressView.getLayoutParams(); + availableHeight -= lineProgressView.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + + lineProgressViewPercent.measure(childWidthMeasureSpec, MeasureSpec.makeMeasureSpec(availableHeight, MeasureSpec.AT_MOST)); + layoutParams = (LayoutParams) lineProgressViewPercent.getLayoutParams(); + availableHeight -= lineProgressViewPercent.getMeasuredHeight() + layoutParams.bottomMargin + layoutParams.topMargin; + } + } + + setMeasuredDimension(width, maxContentHeight - availableHeight + getPaddingTop() + getPaddingBottom()); + inLayout = false; + + if (lastScreenWidth != AndroidUtilities.displaySize.x) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + lastScreenWidth = AndroidUtilities.displaySize.x; + final int calculatedWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(56); + int maxWidth; + if (AndroidUtilities.isTablet()) { + if (AndroidUtilities.isSmallTablet()) { + maxWidth = AndroidUtilities.dp(446); + } else { + maxWidth = AndroidUtilities.dp(496); + } + } else { + maxWidth = AndroidUtilities.dp(356); + } + + Window window = getWindow(); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.copyFrom(window.getAttributes()); + params.width = Math.min(maxWidth, calculatedWidth) + backgroundPaddings.left + backgroundPaddings.right; + window.setAttributes(params); + } + }); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (contentScrollView != null) { + if (onScrollChangedListener == null) { + onScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + runShadowAnimation(0, titleTextView != null && contentScrollView.getScrollY() > scrollContainer.getTop()); + runShadowAnimation(1, buttonsLayout != null && contentScrollView.getScrollY() + contentScrollView.getHeight() < scrollContainer.getBottom()); + contentScrollView.invalidate(); + } + }; + contentScrollView.getViewTreeObserver().addOnScrollChangedListener(onScrollChangedListener); + } + onScrollChangedListener.onScrollChanged(); + } + } + + @Override + public void requestLayout() { + if (inLayout) { + return; + } + super.requestLayout(); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } + }; + containerView.setOrientation(LinearLayout.VERTICAL); + containerView.setBackgroundDrawable(shadowDrawable); + containerView.setFitsSystemWindows(Build.VERSION.SDK_INT >= 21); + setContentView(containerView); + + final boolean hasButtons = positiveButtonText != null || negativeButtonText != null || neutralButtonText != null; + + if (title != null) { + titleTextView = new TextView(getContext()); + titleTextView.setText(title); + titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + containerView.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 19, 24, items != null ? 14 : 10)); + } + + if (progressViewStyle == 0) { + shadow[0] = (BitmapDrawable) getContext().getResources().getDrawable(R.drawable.header_shadow).mutate(); + shadow[1] = (BitmapDrawable) getContext().getResources().getDrawable(R.drawable.header_shadow_reverse).mutate(); + shadow[0].setAlpha(0); + shadow[1].setAlpha(0); + shadow[0].setCallback(this); + shadow[1].setCallback(this); + + contentScrollView = new ScrollView(getContext()) { + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (shadow[0].getPaint().getAlpha() != 0) { + shadow[0].setBounds(0, getScrollY(), getMeasuredWidth(), getScrollY() + AndroidUtilities.dp(3)); + shadow[0].draw(canvas); + } + if (shadow[1].getPaint().getAlpha() != 0) { + shadow[1].setBounds(0, getScrollY() + getMeasuredHeight() - AndroidUtilities.dp(3), getMeasuredWidth(), getScrollY() + getMeasuredHeight()); + shadow[1].draw(canvas); + } + return result; + } + }; + contentScrollView.setVerticalScrollBarEnabled(false); + AndroidUtilities.setScrollViewEdgeEffectColor(contentScrollView, Theme.getColor(Theme.key_dialogScrollGlow)); + containerView.addView(contentScrollView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 0)); + + scrollContainer = new LinearLayout(getContext()); + scrollContainer.setOrientation(LinearLayout.VERTICAL); + contentScrollView.addView(scrollContainer, new ScrollView.LayoutParams(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + messageTextView = new TextView(getContext()); + messageTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + messageTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + messageTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + if (progressViewStyle == 1) { + progressViewContainer = new FrameLayout(getContext()); + containerView.addView(progressViewContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.TOP, 23, title == null ? 24 : 0, 23, 24)); + + RadialProgressView progressView = new RadialProgressView(getContext()); + progressView.setProgressColor(Theme.getColor(Theme.key_dialogProgressCircle)); + progressViewContainer.addView(progressView, LayoutHelper.createFrame(44, 44, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP)); + + messageTextView.setLines(1); + messageTextView.setSingleLine(true); + messageTextView.setEllipsize(TextUtils.TruncateAt.END); + progressViewContainer.addView(messageTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, (LocaleController.isRTL ? 0 : 62), 0, (LocaleController.isRTL ? 62 : 0), 0)); + } else if (progressViewStyle == 2) { + containerView.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, title == null ? 19 : 0, 24, 20)); + + lineProgressView = new LineProgressView(getContext()); + lineProgressView.setProgress(currentProgress / 100.0f, false); + lineProgressView.setProgressColor(Theme.getColor(Theme.key_dialogLineProgress)); + lineProgressView.setBackColor(Theme.getColor(Theme.key_dialogLineProgressBackground)); + containerView.addView(lineProgressView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 4, Gravity.LEFT | Gravity.CENTER_VERTICAL, 24, 0, 24, 0)); + + lineProgressViewPercent = new TextView(getContext()); + lineProgressViewPercent.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + lineProgressViewPercent.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + lineProgressViewPercent.setTextColor(Theme.getColor(Theme.key_dialogTextGray2)); + lineProgressViewPercent.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + containerView.addView(lineProgressViewPercent, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 23, 4, 23, 24)); + updateLineProgressTextView(); + } else { + scrollContainer.addView(messageTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 24, 0, 24, customView != null || items != null ? 20 : 0)); + } + if (!TextUtils.isEmpty(message)) { + messageTextView.setText(message); + messageTextView.setVisibility(View.VISIBLE); + } else { + messageTextView.setVisibility(View.GONE); + } + + if (items != null) { + FrameLayout rowLayout = null; + int lastRowLayoutNum = 0; + for (int a = 0; a < items.length; a++) { + if (items[a] == null) { + continue; + } + AlertDialogCell cell = new AlertDialogCell(getContext()); + cell.setTextAndIcon(items[a], itemIcons != null ? itemIcons[a] : 0); + scrollContainer.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + cell.setTag(a); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onClickListener != null) { + onClickListener.onClick(AlertDialog.this, (Integer) v.getTag()); + } + dismiss(); + } + }); + } + } + if (customView != null) { + if (customView.getParent() != null) { + ViewGroup viewGroup = (ViewGroup) customView.getParent(); + viewGroup.removeView(customView); + } + scrollContainer.addView(customView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + if (hasButtons) { + buttonsLayout = new FrameLayout(getContext()) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int count = getChildCount(); + View positiveButton = null; + int width = right - left; + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + if ((Integer) child.getTag() == Dialog.BUTTON_POSITIVE) { + positiveButton = child; + child.layout(width - getPaddingRight() - child.getMeasuredWidth(), getPaddingTop(), width - getPaddingRight() + child.getMeasuredWidth(), getPaddingTop() + child.getMeasuredHeight()); + } else if ((Integer) child.getTag() == Dialog.BUTTON_NEGATIVE) { + int x = width - getPaddingRight() - child.getMeasuredWidth(); + if (positiveButton != null) { + x -= positiveButton.getMeasuredWidth() + AndroidUtilities.dp(8); + } + child.layout(x, getPaddingTop(), x + child.getMeasuredWidth(), getPaddingTop() + child.getMeasuredHeight()); + } else { + child.layout(getPaddingLeft(), getPaddingTop(), getPaddingLeft() + child.getMeasuredWidth(), getPaddingTop() + child.getMeasuredHeight()); + } + } + } + }; + buttonsLayout.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8), AndroidUtilities.dp(8)); + containerView.addView(buttonsLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 52)); + + if (positiveButtonText != null) { + TextView textView = new TextView(getContext()) { + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + setAlpha(enabled ? 1.0f : 0.5f); + } + }; + textView.setMinWidth(AndroidUtilities.dp(64)); + textView.setTag(Dialog.BUTTON_POSITIVE); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogButton)); + textView.setGravity(Gravity.CENTER); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); +// textView.setLines(1); +// textView.setSingleLine(true); //TODO + textView.setText(positiveButtonText.toString().toUpperCase()); + textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable()); + textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + buttonsLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.TOP | Gravity.RIGHT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (positiveButtonListener != null) { + positiveButtonListener.onClick(AlertDialog.this, Dialog.BUTTON_POSITIVE); + } + dismiss(); + } + }); + } + + if (negativeButtonText != null) { + TextView textView = new TextView(getContext()) { + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + setAlpha(enabled ? 1.0f : 0.5f); + } + }; + textView.setMinWidth(AndroidUtilities.dp(64)); + textView.setTag(Dialog.BUTTON_NEGATIVE); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogButton)); + textView.setGravity(Gravity.CENTER); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); +// textView.setLines(1); +// textView.setSingleLine(true); + textView.setText(negativeButtonText.toString().toUpperCase()); + textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable()); + textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + buttonsLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.TOP | Gravity.RIGHT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (negativeButtonListener != null) { + negativeButtonListener.onClick(AlertDialog.this, Dialog.BUTTON_NEGATIVE); + } + cancel(); + } + }); + } + + if (neutralButtonText != null) { + TextView textView = new TextView(getContext()) { + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + setAlpha(enabled ? 1.0f : 0.5f); + } + }; + textView.setMinWidth(AndroidUtilities.dp(64)); + textView.setTag(Dialog.BUTTON_NEUTRAL); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogButton)); + textView.setGravity(Gravity.CENTER); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); +// textView.setLines(1); +// textView.setSingleLine(true); + textView.setText(neutralButtonText.toString().toUpperCase()); + textView.setBackgroundDrawable(Theme.getRoundRectSelectorDrawable()); + textView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + buttonsLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 36, Gravity.TOP | Gravity.LEFT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (neutralButtonListener != null) { + neutralButtonListener.onClick(AlertDialog.this, Dialog.BUTTON_NEGATIVE); + } + dismiss(); + } + }); + } + } + + lastScreenWidth = AndroidUtilities.displaySize.x; + final int calculatedWidth = AndroidUtilities.displaySize.x - AndroidUtilities.dp(56); + int maxWidth; + if (AndroidUtilities.isTablet()) { + if (AndroidUtilities.isSmallTablet()) { + maxWidth = AndroidUtilities.dp(446); + } else { + maxWidth = AndroidUtilities.dp(496); + } + } else { + maxWidth = AndroidUtilities.dp(356); + } + + Window window = getWindow(); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.copyFrom(window.getAttributes()); + params.dimAmount = 0.6f; + params.width = Math.min(maxWidth, calculatedWidth) + backgroundPaddings.left + backgroundPaddings.right; + params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND; + if (customView == null || !canTextInput(customView)) { + params.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + window.setAttributes(params); + } + + private void runShadowAnimation(final int num, final boolean show) { + if (show && !shadowVisibility[num] || !show && shadowVisibility[num]) { + shadowVisibility[num] = show; + if (shadowAnimation[num] != null) { + shadowAnimation[num].cancel(); + } + shadowAnimation[num] = new AnimatorSet(); + if (shadow[num] != null) { + shadowAnimation[num].playTogether(ObjectAnimator.ofInt(shadow[num], "alpha", show ? 255 : 0)); + } + shadowAnimation[num].setDuration(150); + shadowAnimation[num].addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (shadowAnimation[num] != null && shadowAnimation[num].equals(animation)) { + shadowAnimation[num] = null; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (shadowAnimation[num] != null && shadowAnimation[num].equals(animation)) { + shadowAnimation[num] = null; + } + } + }); + try { + shadowAnimation[num].start(); + } catch (Exception e) { + FileLog.e(e); + } + + } + } + + public void setProgressStyle(int style) { + progressViewStyle = style; + } + + public void setProgress(int progress) { + currentProgress = progress; + if (lineProgressView != null) { + lineProgressView.setProgress(progress / 100.0f, true); + updateLineProgressTextView(); + } + } + + private void updateLineProgressTextView() { + lineProgressViewPercent.setText(String.format("%d%%", currentProgress)); + } + + private boolean canTextInput(View v) { + if (v.onCheckIsTextEditor()) { + return true; + } + if (!(v instanceof ViewGroup)) { + return false; + } + ViewGroup vg = (ViewGroup) v; + int i = vg.getChildCount(); + while (i > 0) { + i--; + v = vg.getChildAt(i); + if (canTextInput(v)) { + return true; + } + } + return false; + } + + @Override + public void dismiss() { + super.dismiss(); + } + + @Override + public void setCanceledOnTouchOutside(boolean cancel) { + super.setCanceledOnTouchOutside(cancel); + } + + public void setMessage(CharSequence text) { + message = text; + if (messageTextView != null) { + if (!TextUtils.isEmpty(message)) { + messageTextView.setText(message); + messageTextView.setVisibility(View.VISIBLE); + } else { + messageTextView.setVisibility(View.GONE); + } + } + } + + public void setButton(int type, CharSequence text, final OnClickListener listener) { + switch (type) { + case BUTTON_NEUTRAL: + neutralButtonText = text; + neutralButtonListener = listener; + break; + case BUTTON_NEGATIVE: + negativeButtonText = text; + negativeButtonListener = listener; + break; + case BUTTON_POSITIVE: + positiveButtonText = text; + positiveButtonListener = listener; + break; + } + } + + public View getButton(int type) { + return buttonsLayout.findViewWithTag(type); + } + + @Override + public void invalidateDrawable(Drawable who) { + contentScrollView.invalidate(); + scrollContainer.invalidate(); + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + if (contentScrollView != null) { + contentScrollView.postDelayed(what, when); + } + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + if (contentScrollView != null) { + contentScrollView.removeCallbacks(what); + } + } + + public static class Builder { + + private AlertDialog alertDialog; + + public Builder(Context context) { + alertDialog = new AlertDialog(context, 0); + } + + public Builder(Context context, int progressViewStyle) { + alertDialog = new AlertDialog(context, progressViewStyle); + } + + public Context getContext() { + return alertDialog.getContext(); + } + + public Builder setItems(CharSequence[] items, final OnClickListener onClickListener) { + alertDialog.items = items; + alertDialog.onClickListener = onClickListener; + return this; + } + + public Builder setItems(CharSequence[] items, int[] icons, final OnClickListener onClickListener) { + alertDialog.items = items; + alertDialog.itemIcons = icons; + alertDialog.onClickListener = onClickListener; + return this; + } + + public Builder setView(View view) { + alertDialog.customView = view; + return this; + } + + public Builder setTitle(CharSequence title) { + alertDialog.title = title; + return this; + } + + public Builder setMessage(CharSequence message) { + alertDialog.message = message; + return this; + } + + public Builder setPositiveButton(CharSequence text, final OnClickListener listener) { + alertDialog.positiveButtonText = text; + alertDialog.positiveButtonListener = listener; + return this; + } + + public Builder setNegativeButton(CharSequence text, final OnClickListener listener) { + alertDialog.negativeButtonText = text; + alertDialog.negativeButtonListener = listener; + return this; + } + + public Builder setNeutralButton(CharSequence text, final OnClickListener listener) { + alertDialog.neutralButtonText = text; + alertDialog.neutralButtonListener = listener; + return this; + } + + public AlertDialog create() { + return alertDialog; + } + + public AlertDialog show() { + alertDialog.show(); + return alertDialog; + } + + public Builder setOnDismissListener(OnDismissListener onDismissListener) { + alertDialog.setOnDismissListener(onDismissListener); + return this; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BackDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BackDrawable.java index 4c0ac82b160..4ec13e0e949 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BackDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BackDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; @@ -29,14 +29,27 @@ public class BackDrawable extends Drawable { private int currentAnimationTime; private boolean alwaysClose; private DecelerateInterpolator interpolator = new DecelerateInterpolator(); + private int color = 0xffffffff; + private int rotatedColor = 0xff757575; + private float animationTime = 300.0f; + private boolean rotated = true; public BackDrawable(boolean close) { super(); - paint.setColor(0xffffffff); paint.setStrokeWidth(AndroidUtilities.dp(2)); alwaysClose = close; } + public void setColor(int value) { + color = value; + invalidateSelf(); + } + + public void setRotatedColor(int value) { + rotatedColor = value; + invalidateSelf(); + } + public void setRotation(float rotation, boolean animated) { lastFrameTime = 0; if (currentRotation == 1) { @@ -47,9 +60,9 @@ public void setRotation(float rotation, boolean animated) { lastFrameTime = 0; if (animated) { if (currentRotation < rotation) { - currentAnimationTime = (int) (currentRotation * 300); + currentAnimationTime = (int) (currentRotation * animationTime); } else { - currentAnimationTime = (int) ((1.0f - currentRotation) * 300); + currentAnimationTime = (int) ((1.0f - currentRotation) * animationTime); } lastFrameTime = System.currentTimeMillis(); finalRotation = rotation; @@ -59,6 +72,14 @@ public void setRotation(float rotation, boolean animated) { invalidateSelf(); } + public void setAnimationTime(float value) { + animationTime = value; + } + + public void setRotated(boolean value) { + rotated = value; + } + @Override public void draw(Canvas canvas) { if (currentRotation != finalRotation) { @@ -66,13 +87,13 @@ public void draw(Canvas canvas) { long dt = System.currentTimeMillis() - lastFrameTime; currentAnimationTime += dt; - if (currentAnimationTime >= 300) { + if (currentAnimationTime >= animationTime) { currentRotation = finalRotation; } else { if (currentRotation < finalRotation) { - currentRotation = interpolator.getInterpolation(currentAnimationTime / 300.0f) * finalRotation; + currentRotation = interpolator.getInterpolation(currentAnimationTime / animationTime) * finalRotation; } else { - currentRotation = 1.0f - interpolator.getInterpolation(currentAnimationTime / 300.0f); + currentRotation = 1.0f - interpolator.getInterpolation(currentAnimationTime / animationTime); } } } @@ -80,8 +101,10 @@ public void draw(Canvas canvas) { invalidateSelf(); } - int rD = (int) ((117 - 255) * currentRotation); - int c = Color.rgb(255 + rD, 255 + rD, 255 + rD); + int rD = rotated ? (int) ((Color.red(rotatedColor) - Color.red(color)) * currentRotation) : 0; + int rG = rotated ? (int) ((Color.green(rotatedColor) - Color.green(color)) * currentRotation) : 0; + int rB = rotated ? (int) ((Color.blue(rotatedColor) - Color.blue(color)) * currentRotation) : 0; + int c = Color.rgb(Color.red(color) + rD, Color.green(color) + rG, Color.blue(color) + rB); paint.setColor(c); canvas.save(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java index 4f1b0c00d65..5a34df17589 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BaseFragment.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; @@ -15,6 +15,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.view.Menu; import android.view.View; import android.view.ViewGroup; @@ -66,7 +67,7 @@ protected void clearViews() { try { parent.removeView(fragmentView); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } fragmentView = null; @@ -77,7 +78,7 @@ protected void clearViews() { try { parent.removeView(actionBar); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } actionBar = null; @@ -94,7 +95,7 @@ protected void setParentLayout(ActionBarLayout layout) { try { parent.removeView(fragmentView); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (parentLayout != null && parentLayout.getContext() != fragmentView.getContext()) { @@ -109,7 +110,7 @@ protected void setParentLayout(ActionBarLayout layout) { try { parent.removeView(actionBar); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -126,8 +127,11 @@ protected void setParentLayout(ActionBarLayout layout) { protected ActionBar createActionBar(Context context) { ActionBar actionBar = new ActionBar(context); - actionBar.setBackgroundColor(Theme.ACTION_BAR_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_SELECTOR_COLOR); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefault)); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarDefaultSelector), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), true); + actionBar.setItemsColor(Theme.getColor(Theme.key_actionBarDefaultIcon), false); + actionBar.setItemsColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon), true); return actionBar; } @@ -179,7 +183,7 @@ public void onPause() { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -240,7 +244,7 @@ public void dismissCurrentDialig() { visibleDialog.dismiss(); visibleDialog = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -255,7 +259,7 @@ public void onBeginSlide() { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (actionBar != null) { actionBar.onPause(); @@ -283,10 +287,14 @@ public void onLowMemory() { } public Dialog showDialog(Dialog dialog) { - return showDialog(dialog, false); + return showDialog(dialog, false, null); } - public Dialog showDialog(Dialog dialog, boolean allowInTransition) { + public Dialog showDialog(Dialog dialog, Dialog.OnDismissListener onDismissListener) { + return showDialog(dialog, false, onDismissListener); + } + + public Dialog showDialog(Dialog dialog, boolean allowInTransition, final Dialog.OnDismissListener onDismissListener) { if (dialog == null || parentLayout == null || parentLayout.animationInProgress || parentLayout.startedTracking || !allowInTransition && parentLayout.checkTransitionAnimation()) { return null; } @@ -296,7 +304,7 @@ public Dialog showDialog(Dialog dialog, boolean allowInTransition) { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { visibleDialog = dialog; @@ -304,6 +312,9 @@ public Dialog showDialog(Dialog dialog, boolean allowInTransition) { visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { + if (onDismissListener != null) { + onDismissListener.onDismiss(dialog); + } onDialogDismiss(visibleDialog); visibleDialog = null; } @@ -311,7 +322,7 @@ public void onDismiss(DialogInterface dialog) { visibleDialog.show(); return visibleDialog; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -327,4 +338,12 @@ public Dialog getVisibleDialog() { public void setVisibleDialog(Dialog dialog) { visibleDialog = dialog; } + + public boolean extendActionMode(Menu menu) { + return false; + } + + public ThemeDescription[] getThemeDescriptions() { + return null; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java index 3c3c2afc46a..a0de7c0f995 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -3,19 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; -import android.graphics.Paint; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; @@ -42,7 +45,6 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.LocaleController; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; @@ -62,6 +64,8 @@ public class BottomSheet extends Dialog { private boolean dismissed; private int tag; + private boolean allowDrawContent = true; + private DialogInterface.OnClickListener onClickListener; private CharSequence[] items; @@ -72,14 +76,13 @@ public class BottomSheet extends Dialog { protected ColorDrawable backDrawable = new ColorDrawable(0xff000000); private boolean allowCustomAnimation = true; + private boolean showWithoutAnimation; private int touchSlop; private boolean useFastDismiss; private boolean focusable; - protected Paint ciclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private Drawable shadowDrawable; protected static int backgroundPaddingTop; protected static int backgroundPaddingLeft; @@ -199,7 +202,7 @@ private void checkDismiss(float velX, float velY) { currentAnimation.playTogether(ObjectAnimator.ofFloat(containerView, "translationY", 0)); currentAnimation.setDuration((int) (150 * (translationY / AndroidUtilities.getPixelsInCM(0.8f, false)))); currentAnimation.setInterpolator(new DecelerateInterpolator()); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentAnimation != null && currentAnimation.equals(animation)) { @@ -287,11 +290,13 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { - width -= lastInsets.getSystemWindowInsetRight() + lastInsets.getSystemWindowInsetLeft(); height -= lastInsets.getSystemWindowInsetBottom(); } setMeasuredDimension(width, height); + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + width -= lastInsets.getSystemWindowInsetRight() + lastInsets.getSystemWindowInsetLeft(); + } boolean isPortrait = width < height; if (containerView != null) { @@ -323,12 +328,15 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutCount--; if (containerView != null) { - int t = (bottom - top) - containerView.getMeasuredHeight(); if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { left += lastInsets.getSystemWindowInsetLeft(); - right += lastInsets.getSystemWindowInsetLeft(); + right -= lastInsets.getSystemWindowInsetRight(); } + int t = (bottom - top) - containerView.getMeasuredHeight(); int l = ((right - left) - containerView.getMeasuredWidth()) / 2; + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + l += lastInsets.getSystemWindowInsetLeft(); + } containerView.layout(l, t, l + containerView.getMeasuredWidth(), t + containerView.getMeasuredHeight()); } @@ -380,6 +388,9 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto default: childTop = lp.topMargin; } + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + childLeft += lastInsets.getSystemWindowInsetLeft(); + } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } @@ -410,11 +421,18 @@ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { public boolean hasOverlappingRendering() { return false; } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + onContainerDraw(canvas); + } } public interface BottomSheetDelegateInterface { void onOpenAnimationStart(); void onOpenAnimationEnd(); + boolean canDismiss(); } public static class BottomSheetDelegate implements BottomSheetDelegateInterface { @@ -427,6 +445,11 @@ public void onOpenAnimationStart() { public void onOpenAnimationEnd() { } + + @Override + public boolean canDismiss() { + return true; + } } public static class BottomSheetCell extends FrameLayout { @@ -437,11 +460,12 @@ public static class BottomSheetCell extends FrameLayout { public BottomSheetCell(Context context, int type) { super(context); - setBackgroundResource(R.drawable.list_selector); + setBackgroundDrawable(Theme.getSelectorDrawable(false)); setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); textView = new TextView(context); @@ -450,12 +474,12 @@ public BottomSheetCell(Context context, int type) { textView.setGravity(Gravity.CENTER_HORIZONTAL); textView.setEllipsize(TextUtils.TruncateAt.END); if (type == 0) { - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL)); } else if (type == 1) { textView.setGravity(Gravity.CENTER); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -503,12 +527,23 @@ public BottomSheet(Context context, boolean needFocus) { touchSlop = vc.getScaledTouchSlop(); Rect padding = new Rect(); - shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); shadowDrawable.getPadding(padding); backgroundPaddingLeft = padding.left; backgroundPaddingTop = padding.top; - container = new ContainerView(getContext()); + container = new ContainerView(getContext()) { + @Override + public boolean drawChild(Canvas canvas, View child, long drawingTime) { + try { + return allowDrawContent && super.drawChild(canvas, child, drawingTime); + } catch (Exception e) { + FileLog.e(e); + } + return true; + } + }; container.setBackgroundDrawable(backDrawable); focusable = needFocus; if (Build.VERSION.SDK_INT >= 21) { @@ -525,7 +560,6 @@ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { container.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } - ciclePaint.setColor(0xffffffff); backDrawable.setAlpha(0); } @@ -543,9 +577,15 @@ protected void onCreate(Bundle savedInstanceState) { public boolean hasOverlappingRendering() { return false; } + + @Override + public void setTranslationY(float translationY) { + super.setTranslationY(translationY); + onContainerTranslationYChanged(translationY); + } }; containerView.setBackgroundDrawable(shadowDrawable); - containerView.setPadding(backgroundPaddingLeft, (applyTopPadding ? AndroidUtilities.dp(8) : 0) + backgroundPaddingTop, backgroundPaddingLeft, (applyBottomPadding ? AndroidUtilities.dp(8) : 0)); + containerView.setPadding(backgroundPaddingLeft, (applyTopPadding ? AndroidUtilities.dp(8) : 0) + backgroundPaddingTop - 1, backgroundPaddingLeft, (applyBottomPadding ? AndroidUtilities.dp(8) : 0)); } if (Build.VERSION.SDK_INT >= 21) { containerView.setFitsSystemWindows(true); @@ -566,7 +606,7 @@ public boolean hasOverlappingRendering() { titleView.setLines(1); titleView.setSingleLine(true); titleView.setText(title); - titleView.setTextColor(0xff757575); + titleView.setTextColor(Theme.getColor(Theme.key_dialogTextGray2)); titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); titleView.setEllipsize(TextUtils.TruncateAt.MIDDLE); titleView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), AndroidUtilities.dp(8)); @@ -584,6 +624,9 @@ public boolean onTouch(View v, MotionEvent event) { FrameLayout rowLayout = null; int lastRowLayoutNum = 0; for (int a = 0; a < items.length; a++) { + if (items[a] == null) { + continue; + } BottomSheetCell cell = new BottomSheetCell(getContext(), 0); cell.setTextAndIcon(items[a], itemIcons != null ? itemIcons[a] : 0); containerView.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP, 0, topOffset, 0, 0)); @@ -612,6 +655,10 @@ public void onClick(View v) { window.setAttributes(params); } + public void setShowWithoutAnimation(boolean value) { + showWithoutAnimation = value; + } + @Override public void show() { super.show(); @@ -620,12 +667,16 @@ public void show() { } dismissed = false; cancelSheetAnimation(); - if (containerView.getMeasuredHeight() == 0) { - containerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, View.MeasureSpec.AT_MOST)); + containerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.x + backgroundPaddingLeft * 2, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y, View.MeasureSpec.AT_MOST)); + if (showWithoutAnimation) { + backDrawable.setAlpha(51); + containerView.setTranslationY(0); + return; } backDrawable.setAlpha(0); if (Build.VERSION.SDK_INT >= 18) { layoutCount = 2; + containerView.setTranslationY(containerView.getMeasuredHeight()); AndroidUtilities.runOnUIThread(startAnimationRunnable = new Runnable() { @Override public void run() { @@ -641,6 +692,14 @@ public void run() { } } + public void setAllowDrawContent(boolean value) { + if (allowDrawContent != value) { + allowDrawContent = value; + container.setBackgroundDrawable(allowDrawContent ? backDrawable : null); + container.invalidate(); + } + } + protected boolean canDismissWithSwipe() { return true; } @@ -677,6 +736,10 @@ protected boolean canDismissWithTouchOutside() { return true; } + protected void onContainerTranslationYChanged(float translationY) { + + } + private void cancelSheetAnimation() { if (currentSheetAnimation != null) { currentSheetAnimation.cancel(); @@ -702,7 +765,7 @@ private void startOpenAnimation() { animatorSet.setDuration(200); animatorSet.setStartDelay(20); animatorSet.setInterpolator(new DecelerateInterpolator()); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { @@ -767,7 +830,7 @@ public void dismissWithButtonClick(final int item) { ); animatorSet.setDuration(180); animatorSet.setInterpolator(new AccelerateInterpolator()); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { @@ -781,7 +844,7 @@ public void run() { try { BottomSheet.super.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -801,6 +864,9 @@ public void onAnimationCancel(Animator animation) { @Override public void dismiss() { + if (delegate != null && !delegate.canDismiss()) { + return; + } if (dismissed) { return; } @@ -820,7 +886,7 @@ public void dismiss() { animatorSet.setDuration(180); } animatorSet.setInterpolator(new AccelerateInterpolator()); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentSheetAnimation != null && currentSheetAnimation.equals(animation)) { @@ -831,7 +897,7 @@ public void run() { try { dismissInternal(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -854,7 +920,7 @@ public void dismissInternal() { try { super.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -935,4 +1001,19 @@ public BottomSheet setUseFullWidth(boolean value) { return bottomSheet; } } + + protected int getLeftInset() { + if (lastInsets != null && Build.VERSION.SDK_INT >= 21) { + return lastInsets.getSystemWindowInsetLeft(); + } + return 0; + } + + public void onConfigurationChanged(android.content.res.Configuration newConfig) { + + } + + public void onContainerDraw(Canvas canvas) { + + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java index b514e8f2187..04d812626d0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/DrawerLayoutContainer.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; @@ -31,7 +32,6 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; -import org.telegram.messenger.AnimatorListenerAdapterProxy; public class DrawerLayoutContainer extends FrameLayout { @@ -49,6 +49,8 @@ public class DrawerLayoutContainer extends FrameLayout { private boolean beginTrackingSent; private AnimatorSet currentAnimation; + private int paddingTop; + private Paint scrimPaint = new Paint(); private Object lastInsets; @@ -176,7 +178,7 @@ public void openDrawer(boolean fast) { } else { animatorSet.setDuration(300); } - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { onDrawerAnimationEnd(true); @@ -198,7 +200,7 @@ public void closeDrawer(boolean fast) { } else { animatorSet.setDuration(300); } - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { onDrawerAnimationEnd(false); @@ -333,11 +335,9 @@ public boolean onTouchEvent(MotionEvent ev) { } else { closeDrawer(drawerOpened && Math.abs(velX) >= 3500); } - startedTracking = false; - } else { - maybeStartTracking = false; - startedTracking = false; } + startedTracking = false; + maybeStartTracking = false; if (velocityTracker != null) { velocityTracker.recycle(); velocityTracker = null; @@ -377,12 +377,12 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { try { if (drawerLayout != child) { - child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight()); + child.layout(lp.leftMargin, lp.topMargin + getPaddingTop(), lp.leftMargin + child.getMeasuredWidth(), lp.topMargin + child.getMeasuredHeight() + getPaddingTop()); } else { - child.layout(-child.getMeasuredWidth(), lp.topMargin, 0, lp.topMargin + child.getMeasuredHeight()); + child.layout(-child.getMeasuredWidth(), lp.topMargin + getPaddingTop(), 0, lp.topMargin + child.getMeasuredHeight() + + getPaddingTop()); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } inLayout = false; @@ -393,7 +393,7 @@ public void requestLayout() { if (!inLayout) { /*StackTraceElement[] elements = Thread.currentThread().getStackTrace(); for (int a = 0; a < elements.length; a++) { - FileLog.d("tmessages", "on " + elements[a]); + FileLog.d("on " + elements[a]); }*/ super.requestLayout(); } @@ -406,6 +406,20 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); + if (Build.VERSION.SDK_INT < 21) { + inLayout = true; + if (heightSize == AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) { + if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + setPadding(0, AndroidUtilities.statusBarHeight, 0, 0); + } + heightSize = AndroidUtilities.displaySize.y; + } else { + if (getLayoutParams() instanceof ViewGroup.MarginLayoutParams) { + setPadding(0, 0, 0, 0); + } + } + inLayout = false; + } final boolean applyInsets = lastInsets != null && Build.VERSION.SDK_INT >= 21; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/MenuDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/MenuDrawable.java index 8d68eea921f..6a411f23484 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/MenuDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/MenuDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; @@ -97,7 +97,7 @@ public void setAlpha(int alpha) { @Override public void setColorFilter(ColorFilter cf) { - + paint.setColorFilter(cf); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java index 96584e4ada8..c4a3faf7222 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/SimpleTextView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; @@ -51,6 +51,11 @@ public void setTextColor(int color) { invalidate(); } + public void setLinkTextColor(int color) { + textPaint.linkColor = color; + invalidate(); + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); @@ -118,10 +123,10 @@ private boolean createLayout(int width) { } width -= getPaddingLeft() + getPaddingRight(); CharSequence string = TextUtils.ellipsize(text, textPaint, width, TextUtils.TruncateAt.END); - if (layout != null && TextUtils.equals(layout.getText(), string)) { + /*if (layout != null && TextUtils.equals(layout.getText(), string)) { calcOffset(width); return false; - } + }*/ layout = new StaticLayout(string, 0, string.length(), textPaint, width + AndroidUtilities.dp(8), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); calcOffset(width); } catch (Exception e) { @@ -213,7 +218,11 @@ public void setRightDrawable(Drawable drawable) { } public void setText(CharSequence value) { - if (text == null && value == null || text != null && value != null && text.equals(value)) { + setText(value, false); + } + + public void setText(CharSequence value, boolean force) { + if (text == null && value == null || !force && text != null && value != null && text.equals(value)) { return; } text = value; @@ -259,9 +268,6 @@ protected void onDraw(Canvas canvas) { } if (rightDrawable != null) { int x = textOffsetX + textWidth + drawablePadding; - if (leftDrawable != null) { - x += drawablePadding + leftDrawable.getIntrinsicWidth(); - } int y = (textHeight - rightDrawable.getIntrinsicHeight()) / 2 + rightDrawableTopPadding; rightDrawable.setBounds(x, y, x + rightDrawable.getIntrinsicWidth(), y + rightDrawable.getIntrinsicHeight()); rightDrawable.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java index e76bce904dc..f2f0ecd684d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -3,398 +3,1691 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.ActionBar; +import android.app.Activity; import android.content.Context; +import android.content.SharedPreferences; import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.shapes.OvalShape; +import android.graphics.drawable.shapes.RoundRectShape; import android.os.Build; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.StateSet; +import org.json.JSONArray; +import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.ThemeEditorView; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; public class Theme { - public static final int ACTION_BAR_COLOR = 0xff527da3; + public static class ThemeInfo { + public String name; + public String pathToFile; + public String assetName; + + public JSONObject getSaveJson() { + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("name", name); + jsonObject.put("path", pathToFile); + return jsonObject; + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + public static ThemeInfo createWithJson(JSONObject object) { + if (object == null) { + return null; + } + try { + ThemeInfo themeInfo = new ThemeInfo(); + themeInfo.name = object.getString("name"); + themeInfo.pathToFile = object.getString("path"); + return themeInfo; + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + public static ThemeInfo createWithString(String string) { + if (TextUtils.isEmpty(string)) { + return null; + } + String[] args = string.split("\\|"); + if (args.length != 2) { + return null; + } + ThemeInfo themeInfo = new ThemeInfo(); + themeInfo.name = args[0]; + themeInfo.pathToFile = args[1]; + return themeInfo; + } + } + + private static final Object sync = new Object(); + private static final Object wallpaperSync = new Object(); + public static final int ACTION_BAR_PHOTO_VIEWER_COLOR = 0x7f000000; public static final int ACTION_BAR_MEDIA_PICKER_COLOR = 0xff333333; public static final int ACTION_BAR_VIDEO_EDIT_COLOR = 0xff000000; - public static final int ACTION_BAR_CHANNEL_INTRO_COLOR = 0xffffffff; public static final int ACTION_BAR_PLAYER_COLOR = 0xffffffff; - public static final int ACTION_BAR_TITLE_COLOR = 0xffffffff; - public static final int ACTION_BAR_SUBTITLE_COLOR = 0xffd5e8f7; - public static final int ACTION_BAR_PROFILE_COLOR = 0xff598fba; - public static final int ACTION_BAR_PROFILE_SUBTITLE_COLOR = 0xffd7eafa; - public static final int ACTION_BAR_MAIN_AVATAR_COLOR = 0xff5085b1; - public static final int ACTION_BAR_ACTION_MODE_TEXT_COLOR = 0xff737373; - public static final int ACTION_BAR_SELECTOR_COLOR = 0xff406d94; - - public static final int INPUT_FIELD_SELECTOR_COLOR = 0xffd6d6d6; public static final int ACTION_BAR_PICKER_SELECTOR_COLOR = 0xff3d3d3d; public static final int ACTION_BAR_WHITE_SELECTOR_COLOR = 0x40ffffff; public static final int ACTION_BAR_AUDIO_SELECTOR_COLOR = 0x2f000000; - public static final int ACTION_BAR_CHANNEL_INTRO_SELECTOR_COLOR = 0x2f000000; - public static final int ACTION_BAR_MODE_SELECTOR_COLOR = 0xfff0f0f0; - public static final int ACTION_BAR_BLUE_SELECTOR_COLOR = 0xff4981ad; - public static final int ACTION_BAR_CYAN_SELECTOR_COLOR = 0xff39849d; - public static final int ACTION_BAR_GREEN_SELECTOR_COLOR = 0xff48953d; - public static final int ACTION_BAR_ORANGE_SELECTOR_COLOR = 0xffe67429; - public static final int ACTION_BAR_PINK_SELECTOR_COLOR = 0xffd44e7b; - public static final int ACTION_BAR_RED_SELECTOR_COLOR = 0xffbc4b41; - public static final int ACTION_BAR_VIOLET_SELECTOR_COLOR = 0xff735fbe; - public static final int ACTION_BAR_YELLOW_SELECTOR_COLOR = 0xffef9f09; - - public static final int ATTACH_SHEET_TEXT_COLOR = 0xff757575; - - public static final int DIALOGS_MESSAGE_TEXT_COLOR = 0xff8f8f8f; - public static final int DIALOGS_NAME_TEXT_COLOR = 0xff4d83b3; - public static final int DIALOGS_ATTACH_TEXT_COLOR = 0xff4d83b3; - public static final int DIALOGS_PRINTING_TEXT_COLOR = 0xff4d83b3; - public static final int DIALOGS_DRAFT_TEXT_COLOR = 0xffdd4b39; - - public static final int CHAT_UNREAD_TEXT_COLOR = 0xff5695cc; - public static final int CHAT_ADD_CONTACT_TEXT_COLOR = 0xff4a82b5; - public static final int CHAT_REPORT_SPAM_TEXT_COLOR = 0xffcf5957; - public static final int CHAT_BOTTOM_OVERLAY_TEXT_COLOR = 0xff7f7f7f; - public static final int CHAT_BOTTOM_CHAT_OVERLAY_TEXT_COLOR = 0xff3a8ccf; - public static final int CHAT_GIF_HINT_TEXT_COLOR = 0xffffffff; - public static final int CHAT_EMPTY_VIEW_TEXT_COLOR = 0xffffffff; - public static final int CHAT_SEARCH_COUNT_TEXT_COLOR = 0xff4e9ad4; - - public static final int INAPP_PLAYER_PERFORMER_TEXT_COLOR = 0xff2f3438; - public static final int INAPP_PLAYER_TITLE_TEXT_COLOR = 0xff2f3438; - public static final int INAPP_PLAYER_BACKGROUND_COLOR = 0xffffffff; - - public static final int REPLY_PANEL_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int REPLY_PANEL_MESSAGE_TEXT_COLOR = 0xff222222; - - public static final int ALERT_PANEL_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int ALERT_PANEL_MESSAGE_TEXT_COLOR = 0xff999999; - - public static final int AUTODOWNLOAD_SHEET_SAVE_TEXT_COLOR = 0xff3a8ccf; - - public static final int JOIN_SHEET_NAME_TEXT_COLOR = 0xff212121; - public static final int JOIN_SHEET_COUNT_TEXT_COLOR = 0xff999999; - - public static final int SHARE_SHEET_COPY_TEXT_COLOR = 0xff3a8ccf; - public static final int SHARE_SHEET_SEND_TEXT_COLOR = 0xff3ec1f9; - public static final int SHARE_SHEET_SEND_DISABLED_TEXT_COLOR = 0xffb3b3b3; - public static final int SHARE_SHEET_EDIT_TEXT_COLOR = 0xff212121; - public static final int SHARE_SHEET_EDIT_PLACEHOLDER_TEXT_COLOR = 0xff979797; - public static final int SHARE_SHEET_BADGE_TEXT_COLOR = 0xffffffff; - - public static final int STICKERS_SHEET_TITLE_TEXT_COLOR = 0xff212121; - public static final int STICKERS_SHEET_SEND_TEXT_COLOR = 0xff3a8ccf; - public static final int STICKERS_SHEET_ADD_TEXT_COLOR = 0xff3a8ccf; - public static final int STICKERS_SHEET_CLOSE_TEXT_COLOR = 0xff3a8ccf; - public static final int STICKERS_SHEET_REMOVE_TEXT_COLOR = 0xffcd5a5a; - - public static final int PINNED_PANEL_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int PINNED_PANEL_MESSAGE_TEXT_COLOR = 0xff999999; - - public static final int SECRET_CHAT_INFO_TEXT_COLOR = 0xffffffff; - - public static final int MSG_SELECTED_BACKGROUND_COLOR = 0x6633b5e5; - public static final int MSG_WEB_PREVIEW_DURATION_TEXT_COLOR = 0xffffffff; - public static final int MSG_WEB_PREVIEW_GAME_TEXT_COLOR = 0xffffffff; - public static final int MSG_SECRET_TIME_TEXT_COLOR = 0xffe4e2e0; - public static final int MSG_STICKER_NAME_TEXT_COLOR = 0xffffffff; - public static final int MSG_BOT_BUTTON_TEXT_COLOR = 0xffffffff; - public static final int MSG_BOT_PROGRESS_COLOR = 0xffffffff; - public static final int MSG_IN_FORDWARDED_NAME_TEXT_COLOR = 0xff3886c7; - public static final int MSG_OUT_FORDWARDED_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_VIA_BOT_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int MSG_OUT_VIA_BOT_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_STICKER_VIA_BOT_NAME_TEXT_COLOR = 0xffffffff; - public static final int MSG_IN_REPLY_LINE_COLOR = 0xff70b4e8; - public static final int MSG_OUT_REPLY_LINE_COLOR = 0xff88c97b; - public static final int MSG_STICKER_REPLY_LINE_COLOR = 0xffffffff; - public static final int MSG_IN_REPLY_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int MSG_OUT_REPLY_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_STICKER_REPLY_NAME_TEXT_COLOR = 0xffffffff; - public static final int MSG_IN_REPLY_MESSAGE_TEXT_COLOR = 0xff000000; - public static final int MSG_OUT_REPLY_MESSAGE_TEXT_COLOR = 0xff000000; - public static final int MSG_IN_REPLY_MEDIA_MESSAGE_TEXT_COLOR = 0xffa1aab3; - public static final int MSG_OUT_REPLY_MEDIA_MESSAGE_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR = 0xff89b4c1; - public static final int MSG_OUT_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR = 0xff65b05b; - public static final int MSG_STICKER_REPLY_MESSAGE_TEXT_COLOR = 0xffffffff; - public static final int MSG_IN_WEB_PREVIEW_LINE_COLOR = 0xff70b4e8; - public static final int MSG_OUT_WEB_PREVIEW_LINE_COLOR = 0xff88c97b; - public static final int MSG_IN_SITE_NAME_TEXT_COLOR = 0xff3a8ccf; - public static final int MSG_OUT_SITE_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_CONTACT_NAME_TEXT_COLOR = 0xff4e9ad4; - public static final int MSG_OUT_CONTACT_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_CONTACT_PHONE_TEXT_COLOR = 0xff2f3438; - public static final int MSG_OUT_CONTACT_PHONE_TEXT_COLOR = 0xff354234; - public static final int MSG_MEDIA_PROGRESS_COLOR = 0xffffffff; - public static final int MSG_IN_AUDIO_PROGRESS_COLOR = 0xffffffff; - public static final int MSG_OUT_AUDIO_PROGRESS_COLOR = 0xffefffde; - public static final int MSG_IN_AUDIO_SELECTED_PROGRESS_COLOR = 0xffe2f8ff; - public static final int MSG_OUT_AUDIO_SELECTED_PROGRESS_COLOR = 0xffd4f5bc; - public static final int MSG_MEDIA_TIME_TEXT_COLOR = 0xffffffff; - public static final int MSG_IN_TIME_TEXT_COLOR = 0xffa1aab3; - public static final int MSG_OUT_TIME_TEXT_COLOR = 0xff70b15c; - public static final int MSG_IN_TIME_SELECTED_TEXT_COLOR = 0xff89b4c1; - public static final int MSG_OUT_TIME_SELECTED_TEXT_COLOR = 0xff70b15c; - public static final int MSG_IN_AUDIO_PERFORMER_TEXT_COLOR = 0xff2f3438; - public static final int MSG_OUT_AUDIO_PERFORMER_TEXT_COLOR = 0xff354234; - public static final int MSG_IN_AUDIO_TITLE_TEXT_COLOR = 0xff4e9ad4; - public static final int MSG_OUT_AUDIO_TITLE_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_AUDIO_DURATION_TEXT_COLOR = 0xffa1aab3; - public static final int MSG_OUT_AUDIO_DURATION_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_AUDIO_DURATION_SELECTED_TEXT_COLOR = 0xff89b4c1; - public static final int MSG_OUT_AUDIO_DURATION_SELECTED_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_AUDIO_SEEKBAR_COLOR = 0xffe4eaf0; - public static final int MSG_OUT_AUDIO_SEEKBAR_COLOR = 0xffbbe3ac; - public static final int MSG_IN_AUDIO_SEEKBAR_SELECTED_COLOR = 0xffbcdee8; - public static final int MSG_OUT_AUDIO_SEEKBAR_SELECTED_COLOR = 0xffa9dd96; - public static final int MSG_IN_AUDIO_SEEKBAR_FILL_COLOR = 0xff72b5e8; - public static final int MSG_OUT_AUDIO_SEEKBAR_FILL_COLOR = 0xff78c272; - public static final int MSG_IN_VOICE_SEEKBAR_COLOR = 0xffdee5eb; - public static final int MSG_OUT_VOICE_SEEKBAR_COLOR = 0xffbbe3ac; - public static final int MSG_IN_VOICE_SEEKBAR_SELECTED_COLOR = 0xffbcdee8; - public static final int MSG_OUT_VOICE_SEEKBAR_SELECTED_COLOR = 0xffa9dd96; - public static final int MSG_IN_VOICE_SEEKBAR_FILL_COLOR = 0xff72b5e8; - public static final int MSG_OUT_VOICE_SEEKBAR_FILL_COLOR = 0xff78c272; - public static final int MSG_IN_FILE_PROGRESS_COLOR = 0xffebf0f5; - public static final int MSG_OUT_FILE_PROGRESS_COLOR = 0xffdaf5c3; - public static final int MSG_IN_FILE_PROGRESS_SELECTED_COLOR = 0xffcbeaf6; - public static final int MSG_OUT_FILE_PROGRESS_SELECTED_COLOR = 0xffc5eca7; - public static final int MSG_IN_FILE_NAME_TEXT_COLOR = 0xff4e9ad4; - public static final int MSG_OUT_FILE_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_FILE_INFO_TEXT_COLOR = 0xffa1aab3; - public static final int MSG_OUT_FILE_INFO_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_FILE_INFO_SELECTED_TEXT_COLOR = 0xff89b4c1; - public static final int MSG_OUT_FILE_INFO_SELECTED_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_FILE_BACKGROUND_COLOR = 0xffebf0f5; - public static final int MSG_OUT_FILE_BACKGROUND_COLOR = 0xffdaf5c3; - public static final int MSG_IN_FILE_BACKGROUND_SELECTED_COLOR = 0xffcbeaf6; - public static final int MSG_OUT_FILE_BACKGROUND_SELECTED_COLOR = 0xffc5eca7; - public static final int MSG_IN_VENUE_NAME_TEXT_COLOR = 0xff4e9ad4; - public static final int MSG_OUT_VENUE_NAME_TEXT_COLOR = 0xff55ab4f; - public static final int MSG_IN_VENUE_INFO_TEXT_COLOR = 0xffa1aab3; - public static final int MSG_OUT_VENUE_INFO_TEXT_COLOR = 0xff65b05b; - public static final int MSG_IN_VENUE_INFO_SELECTED_TEXT_COLOR = 0xff89b4c1; - public static final int MSG_OUT_VENUE_INFO_SELECTED_TEXT_COLOR = 0xff65b05b; - public static final int MSG_MEDIA_INFO_TEXT_COLOR = 0xffffffff; - public static final int MSG_TEXT_COLOR = 0xff000000; - public static final int MSG_LINK_TEXT_COLOR = 0xff2678b6; - public static final int MSG_LINK_SELECT_BACKGROUND_COLOR = 0x3362a9e3; - public static final int MSG_TEXT_SELECT_BACKGROUND_COLOR = 0x6662a9e3; - - - public static Drawable backgroundDrawableIn; - public static Drawable backgroundDrawableInSelected; - public static Drawable backgroundDrawableOut; - public static Drawable backgroundDrawableOutSelected; - public static Drawable backgroundMediaDrawableIn; - public static Drawable backgroundMediaDrawableInSelected; - public static Drawable backgroundMediaDrawableOut; - public static Drawable backgroundMediaDrawableOutSelected; - public static Drawable checkDrawable; - public static Drawable halfCheckDrawable; - public static Drawable clockDrawable; - public static Drawable broadcastDrawable; - public static Drawable checkMediaDrawable; - public static Drawable halfCheckMediaDrawable; - public static Drawable clockMediaDrawable; - public static Drawable broadcastMediaDrawable; - public static Drawable errorDrawable; - public static Drawable systemDrawable; - public static Drawable backgroundBluePressed; - public static Drawable timeBackgroundDrawable; - public static Drawable timeStickerBackgroundDrawable; - public static Drawable botLink; - public static Drawable botInline; - public static Drawable[] clockChannelDrawable = new Drawable[2]; - - public static Drawable[] cornerOuter = new Drawable[4]; - public static Drawable[] cornerInner = new Drawable[4]; - - public static Drawable shareDrawable; - public static Drawable shareIconDrawable; - - public static Drawable[] viewsCountDrawable = new Drawable[2]; - public static Drawable viewsOutCountDrawable; - public static Drawable viewsMediaCountDrawable; - - public static Drawable geoInDrawable; - public static Drawable geoOutDrawable; - - public static Drawable inlineDocDrawable; - public static Drawable inlineAudioDrawable; - public static Drawable inlineLocationDrawable; - - public static Drawable[] contactDrawable = new Drawable[2]; - public static Drawable[][] fileStatesDrawable = new Drawable[10][2]; - public static Drawable[][] photoStatesDrawables = new Drawable[13][2]; - public static Drawable[] docMenuDrawable = new Drawable[4]; + public static final int ARTICLE_VIEWER_MEDIA_PROGRESS_COLOR = 0xffffffff; + //public static final int INPUT_FIELD_SELECTOR_COLOR = 0xffd6d6d6; + + private static Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + public static ArrayList themes; + private static ArrayList otherThemes; + private static HashMap themesDict; + private static ThemeInfo currentTheme; + private static ThemeInfo defaultTheme; + private static ThemeInfo previousTheme; public static PorterDuffColorFilter colorFilter; public static PorterDuffColorFilter colorPressedFilter; + private static int selectedColor; + private static boolean isCustomTheme; + private static int serviceMessageColor; + private static int serviceSelectedMessageColor; private static int currentColor; + private static int currentSelectedColor; + private static Drawable wallpaper; + private static Drawable themedWallpaper; + private static int themedWallpaperFileOffset; - public static Drawable attachButtonDrawables[] = new Drawable[8]; + public static Paint dividerPaint; + public static Paint linkSelectionPaint; + public static Paint checkboxSquare_eraserPaint; + public static Paint checkboxSquare_checkPaint; + public static Paint checkboxSquare_backgroundPaint; + public static Paint avatar_backgroundPaint; - private static Paint maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + public static Drawable listSelector; + public static Drawable avatar_broadcastDrawable; + public static Drawable avatar_photoDrawable; - public static void loadRecources(Context context) { - if (backgroundDrawableIn == null) { - backgroundDrawableIn = context.getResources().getDrawable(R.drawable.msg_in); - backgroundDrawableInSelected = context.getResources().getDrawable(R.drawable.msg_in_selected); - backgroundDrawableOut = context.getResources().getDrawable(R.drawable.msg_out); - backgroundDrawableOutSelected = context.getResources().getDrawable(R.drawable.msg_out_selected); - backgroundMediaDrawableIn = context.getResources().getDrawable(R.drawable.msg_in_photo); - backgroundMediaDrawableInSelected = context.getResources().getDrawable(R.drawable.msg_in_photo_selected); - backgroundMediaDrawableOut = context.getResources().getDrawable(R.drawable.msg_out_photo); - backgroundMediaDrawableOutSelected = context.getResources().getDrawable(R.drawable.msg_out_photo_selected); - checkDrawable = context.getResources().getDrawable(R.drawable.msg_check); - halfCheckDrawable = context.getResources().getDrawable(R.drawable.msg_halfcheck); - clockDrawable = context.getResources().getDrawable(R.drawable.msg_clock); - checkMediaDrawable = context.getResources().getDrawable(R.drawable.msg_check_w); - halfCheckMediaDrawable = context.getResources().getDrawable(R.drawable.msg_halfcheck_w); - clockMediaDrawable = context.getResources().getDrawable(R.drawable.msg_clock_photo); - clockChannelDrawable[0] = context.getResources().getDrawable(R.drawable.msg_clock2); - clockChannelDrawable[1] = context.getResources().getDrawable(R.drawable.msg_clock2_s); - errorDrawable = context.getResources().getDrawable(R.drawable.msg_warning); - timeBackgroundDrawable = context.getResources().getDrawable(R.drawable.phototime2_b); - timeStickerBackgroundDrawable = context.getResources().getDrawable(R.drawable.phototime2); - broadcastDrawable = context.getResources().getDrawable(R.drawable.broadcast3); - broadcastMediaDrawable = context.getResources().getDrawable(R.drawable.broadcast4); - systemDrawable = context.getResources().getDrawable(R.drawable.system); - botLink = context.getResources().getDrawable(R.drawable.bot_link); - botInline = context.getResources().getDrawable(R.drawable.bot_lines); - - viewsCountDrawable[0] = context.getResources().getDrawable(R.drawable.post_views); - viewsCountDrawable[1] = context.getResources().getDrawable(R.drawable.post_views_s); - viewsOutCountDrawable = context.getResources().getDrawable(R.drawable.post_viewsg); - viewsMediaCountDrawable = context.getResources().getDrawable(R.drawable.post_views_w); - - fileStatesDrawable[0][0] = context.getResources().getDrawable(R.drawable.play_g); - fileStatesDrawable[0][1] = context.getResources().getDrawable(R.drawable.play_g_s); - fileStatesDrawable[1][0] = context.getResources().getDrawable(R.drawable.pause_g); - fileStatesDrawable[1][1] = context.getResources().getDrawable(R.drawable.pause_g_s); - fileStatesDrawable[2][0] = context.getResources().getDrawable(R.drawable.file_g_load); - fileStatesDrawable[2][1] = context.getResources().getDrawable(R.drawable.file_g_load_s); - fileStatesDrawable[3][0] = context.getResources().getDrawable(R.drawable.file_g); - fileStatesDrawable[3][1] = context.getResources().getDrawable(R.drawable.file_g_s); - fileStatesDrawable[4][0] = context.getResources().getDrawable(R.drawable.file_g_cancel); - fileStatesDrawable[4][1] = context.getResources().getDrawable(R.drawable.file_g_cancel_s); - fileStatesDrawable[5][0] = context.getResources().getDrawable(R.drawable.play_b); - fileStatesDrawable[5][1] = context.getResources().getDrawable(R.drawable.play_b_s); - fileStatesDrawable[6][0] = context.getResources().getDrawable(R.drawable.pause_b); - fileStatesDrawable[6][1] = context.getResources().getDrawable(R.drawable.pause_b_s); - fileStatesDrawable[7][0] = context.getResources().getDrawable(R.drawable.file_b_load); - fileStatesDrawable[7][1] = context.getResources().getDrawable(R.drawable.file_b_load_s); - fileStatesDrawable[8][0] = context.getResources().getDrawable(R.drawable.file_b); - fileStatesDrawable[8][1] = context.getResources().getDrawable(R.drawable.file_b_s); - fileStatesDrawable[9][0] = context.getResources().getDrawable(R.drawable.file_b_cancel); - fileStatesDrawable[9][1] = context.getResources().getDrawable(R.drawable.file_b_cancel_s); - - photoStatesDrawables[0][0] = context.getResources().getDrawable(R.drawable.photoload); - photoStatesDrawables[0][1] = context.getResources().getDrawable(R.drawable.photoload_pressed); - photoStatesDrawables[1][0] = context.getResources().getDrawable(R.drawable.photocancel); - photoStatesDrawables[1][1] = context.getResources().getDrawable(R.drawable.photocancel_pressed); - photoStatesDrawables[2][0] = context.getResources().getDrawable(R.drawable.photogif); - photoStatesDrawables[2][1] = context.getResources().getDrawable(R.drawable.photogif_pressed); - photoStatesDrawables[3][0] = context.getResources().getDrawable(R.drawable.playvideo); - photoStatesDrawables[3][1] = context.getResources().getDrawable(R.drawable.playvideo_pressed); - //photoStatesDrawables[4] = context.getResources().getDrawable(R.drawable.photopause); - photoStatesDrawables[4][0] = photoStatesDrawables[4][1] = context.getResources().getDrawable(R.drawable.burn); - photoStatesDrawables[5][0] = photoStatesDrawables[5][1] = context.getResources().getDrawable(R.drawable.circle); - photoStatesDrawables[6][0] = photoStatesDrawables[6][1] = context.getResources().getDrawable(R.drawable.photocheck); - - photoStatesDrawables[7][0] = context.getResources().getDrawable(R.drawable.photoload_g); - photoStatesDrawables[7][1] = context.getResources().getDrawable(R.drawable.photoload_g_s); - photoStatesDrawables[8][0] = context.getResources().getDrawable(R.drawable.photocancel_g); - photoStatesDrawables[8][1] = context.getResources().getDrawable(R.drawable.photocancel_g_s); - photoStatesDrawables[9][0] = context.getResources().getDrawable(R.drawable.doc_green); - photoStatesDrawables[9][1] = context.getResources().getDrawable(R.drawable.doc_green); - - photoStatesDrawables[10][0] = context.getResources().getDrawable(R.drawable.photoload_b); - photoStatesDrawables[10][1] = context.getResources().getDrawable(R.drawable.photoload_b_s); - photoStatesDrawables[11][0] = context.getResources().getDrawable(R.drawable.photocancel_b); - photoStatesDrawables[11][1] = context.getResources().getDrawable(R.drawable.photocancel_b_s); - photoStatesDrawables[12][0] = context.getResources().getDrawable(R.drawable.doc_blue); - photoStatesDrawables[12][1] = context.getResources().getDrawable(R.drawable.doc_blue_s); - - docMenuDrawable[0] = context.getResources().getDrawable(R.drawable.doc_actions_b); - docMenuDrawable[1] = context.getResources().getDrawable(R.drawable.doc_actions_g); - docMenuDrawable[2] = context.getResources().getDrawable(R.drawable.doc_actions_b_s); - docMenuDrawable[3] = context.getResources().getDrawable(R.drawable.video_actions); - - contactDrawable[0] = context.getResources().getDrawable(R.drawable.contact_blue); - contactDrawable[1] = context.getResources().getDrawable(R.drawable.contact_green); - - shareDrawable = context.getResources().getDrawable(R.drawable.share_round); - shareIconDrawable = context.getResources().getDrawable(R.drawable.share_arrow); - - geoInDrawable = context.getResources().getDrawable(R.drawable.location_b); - geoOutDrawable = context.getResources().getDrawable(R.drawable.location_g); - - cornerOuter[0] = context.getResources().getDrawable(R.drawable.corner_out_tl); - cornerOuter[1] = context.getResources().getDrawable(R.drawable.corner_out_tr); - cornerOuter[2] = context.getResources().getDrawable(R.drawable.corner_out_br); - cornerOuter[3] = context.getResources().getDrawable(R.drawable.corner_out_bl); - - cornerInner[0] = context.getResources().getDrawable(R.drawable.corner_in_tr); - cornerInner[1] = context.getResources().getDrawable(R.drawable.corner_in_tl); - cornerInner[2] = context.getResources().getDrawable(R.drawable.corner_in_br); - cornerInner[3] = context.getResources().getDrawable(R.drawable.corner_in_bl); - - inlineDocDrawable = context.getResources().getDrawable(R.drawable.bot_file); - inlineAudioDrawable = context.getResources().getDrawable(R.drawable.bot_music); - inlineLocationDrawable = context.getResources().getDrawable(R.drawable.bot_location); - } - - int color = ApplicationLoader.getServiceMessageColor(); - if (currentColor != color) { - colorFilter = new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY); - colorPressedFilter = new PorterDuffColorFilter(ApplicationLoader.getServiceSelectedMessageColor(), PorterDuff.Mode.MULTIPLY); - currentColor = color; - for (int a = 0; a < 4; a++) { - cornerOuter[a].setColorFilter(colorFilter); - cornerInner[a].setColorFilter(colorFilter); + public static Paint dialogs_tabletSeletedPaint; + public static Paint dialogs_pinnedPaint; + public static Paint dialogs_countPaint; + public static Paint dialogs_errorPaint; + public static Paint dialogs_countGrayPaint; + public static TextPaint dialogs_namePaint; + public static TextPaint dialogs_nameEncryptedPaint; + public static TextPaint dialogs_messagePaint; + public static TextPaint dialogs_messagePrintingPaint; + public static TextPaint dialogs_timePaint; + public static TextPaint dialogs_countTextPaint; + public static TextPaint dialogs_onlinePaint; + public static TextPaint dialogs_offlinePaint; + public static Drawable dialogs_checkDrawable; + public static Drawable dialogs_halfCheckDrawable; + public static Drawable dialogs_clockDrawable; + public static Drawable dialogs_errorDrawable; + public static Drawable dialogs_lockDrawable; + public static Drawable dialogs_groupDrawable; + public static Drawable dialogs_broadcastDrawable; + public static Drawable dialogs_botDrawable; + public static Drawable dialogs_muteDrawable; + public static Drawable dialogs_verifiedDrawable; + public static Drawable dialogs_verifiedCheckDrawable; + public static Drawable dialogs_pinnedDrawable; + public static Drawable dialogs_errorIconDrawable; + + public static TextPaint profile_aboutTextPaint; + public static Drawable profile_verifiedDrawable; + public static Drawable profile_verifiedCheckDrawable; + + public static Paint chat_docBackPaint; + public static Paint chat_deleteProgressPaint; + public static Paint chat_botProgressPaint; + public static Paint chat_urlPaint; + public static Paint chat_textSearchSelectionPaint; + public static Paint chat_instantViewRectPaint; + public static Paint chat_replyLinePaint; + public static Paint chat_msgErrorPaint; + public static Paint chat_statusPaint; + public static Paint chat_statusRecordPaint; + public static Paint chat_actionBackgroundPaint; + public static Paint chat_composeBackgroundPaint; + public static TextPaint chat_msgTextPaint; + public static TextPaint chat_actionTextPaint; + public static TextPaint chat_msgBotButtonPaint; + public static TextPaint chat_msgGameTextPaint; + public static TextPaint chat_msgTextPaintOneEmoji; + public static TextPaint chat_msgTextPaintTwoEmoji; + public static TextPaint chat_msgTextPaintThreeEmoji; + public static TextPaint chat_infoPaint; + public static TextPaint chat_docNamePaint; + public static TextPaint chat_locationTitlePaint; + public static TextPaint chat_locationAddressPaint; + public static TextPaint chat_durationPaint; + public static TextPaint chat_gamePaint; + public static TextPaint chat_shipmentPaint; + public static TextPaint chat_instantViewPaint; + public static TextPaint chat_audioTimePaint; + public static TextPaint chat_audioTitlePaint; + public static TextPaint chat_audioPerformerPaint; + public static TextPaint chat_botButtonPaint; + public static TextPaint chat_contactNamePaint; + public static TextPaint chat_contactPhonePaint; + public static TextPaint chat_timePaint; + public static TextPaint chat_namePaint; + public static TextPaint chat_forwardNamePaint; + public static TextPaint chat_replyNamePaint; + public static TextPaint chat_replyTextPaint; + public static TextPaint chat_contextResult_titleTextPaint; + public static TextPaint chat_contextResult_descriptionTextPaint; + + public static Drawable chat_composeShadowDrawable; + public static Drawable chat_msgInDrawable; + public static Drawable chat_msgInSelectedDrawable; + public static Drawable chat_msgInShadowDrawable; + public static Drawable chat_msgOutDrawable; + public static Drawable chat_msgOutSelectedDrawable; + public static Drawable chat_msgOutShadowDrawable; + public static Drawable chat_msgInMediaDrawable; + public static Drawable chat_msgInMediaSelectedDrawable; + public static Drawable chat_msgInMediaShadowDrawable; + public static Drawable chat_msgOutMediaDrawable; + public static Drawable chat_msgOutMediaSelectedDrawable; + public static Drawable chat_msgOutMediaShadowDrawable; + public static Drawable chat_msgOutCheckDrawable; + public static Drawable chat_msgOutCheckSelectedDrawable; + public static Drawable chat_msgOutHalfCheckDrawable; + public static Drawable chat_msgOutHalfCheckSelectedDrawable; + public static Drawable chat_msgOutClockDrawable; + public static Drawable chat_msgOutSelectedClockDrawable; + public static Drawable chat_msgInClockDrawable; + public static Drawable chat_msgInSelectedClockDrawable; + public static Drawable chat_msgMediaCheckDrawable; + public static Drawable chat_msgMediaHalfCheckDrawable; + public static Drawable chat_msgMediaClockDrawable; + public static Drawable chat_msgStickerCheckDrawable; + public static Drawable chat_msgStickerHalfCheckDrawable; + public static Drawable chat_msgStickerClockDrawable; + public static Drawable chat_msgStickerViewsDrawable; + public static Drawable chat_msgInViewsDrawable; + public static Drawable chat_msgInViewsSelectedDrawable; + public static Drawable chat_msgOutViewsDrawable; + public static Drawable chat_msgOutViewsSelectedDrawable; + public static Drawable chat_msgMediaViewsDrawable; + public static Drawable chat_msgInMenuDrawable; + public static Drawable chat_msgInMenuSelectedDrawable; + public static Drawable chat_msgOutMenuDrawable; + public static Drawable chat_msgOutMenuSelectedDrawable; + public static Drawable chat_msgMediaMenuDrawable; + public static Drawable chat_msgInInstantDrawable; + public static Drawable chat_msgInInstantSelectedDrawable; + public static Drawable chat_msgOutInstantDrawable; + public static Drawable chat_msgOutInstantSelectedDrawable; + public static Drawable chat_msgErrorDrawable; + public static Drawable chat_muteIconDrawable; + public static Drawable chat_lockIconDrawable; + public static Drawable chat_inlineResultFile; + public static Drawable chat_inlineResultAudio; + public static Drawable chat_inlineResultLocation; + public static Drawable chat_msgOutBroadcastDrawable; + public static Drawable chat_msgMediaBroadcastDrawable; + public static Drawable chat_msgOutLocationDrawable; + public static Drawable chat_msgBroadcastDrawable; + public static Drawable chat_msgBroadcastMediaDrawable; + public static Drawable chat_contextResult_shadowUnderSwitchDrawable; + public static Drawable chat_shareDrawable; + public static Drawable chat_shareIconDrawable; + public static Drawable chat_botLinkDrawalbe; + public static Drawable chat_botInlineDrawable; + public static Drawable chat_systemDrawable; + public static Drawable chat_timeBackgroundDrawable; + public static Drawable chat_timeStickerBackgroundDrawable; + public static Drawable chat_msgInCallDrawable; + public static Drawable chat_msgInCallSelectedDrawable; + public static Drawable chat_msgOutCallDrawable; + public static Drawable chat_msgOutCallSelectedDrawable; + public static Drawable chat_msgCallUpRedDrawable; + public static Drawable chat_msgCallUpGreenDrawable; + public static Drawable chat_msgCallDownRedDrawable; + public static Drawable chat_msgCallDownGreenDrawable; + public static Drawable[] chat_attachButtonDrawables = new Drawable[8]; + public static Drawable[] chat_locationDrawable = new Drawable[2]; + public static Drawable[] chat_contactDrawable = new Drawable[2]; + public static Drawable[] chat_cornerOuter = new Drawable[4]; + public static Drawable[] chat_cornerInner = new Drawable[4]; + public static Drawable[][] chat_fileStatesDrawable = new Drawable[10][2]; + public static Drawable[][] chat_photoStatesDrawables = new Drawable[13][2]; + + public static final String key_dialogBackground = "dialogBackground"; + public static final String key_dialogTextBlack = "dialogTextBlack"; + public static final String key_dialogTextLink = "dialogTextLink"; + public static final String key_dialogLinkSelection = "dialogLinkSelection"; + public static final String key_dialogTextRed = "dialogTextRed"; + public static final String key_dialogTextBlue = "dialogTextBlue"; + public static final String key_dialogTextBlue2 = "dialogTextBlue2"; + public static final String key_dialogTextBlue3 = "dialogTextBlue3"; + public static final String key_dialogTextBlue4 = "dialogTextBlue4"; + public static final String key_dialogTextGray = "dialogTextGray"; + public static final String key_dialogTextGray2 = "dialogTextGray2"; + public static final String key_dialogTextGray3 = "dialogTextGray3"; + public static final String key_dialogTextGray4 = "dialogTextGray4"; + public static final String key_dialogTextHint = "dialogTextHint"; + public static final String key_dialogInputField = "dialogInputField"; + public static final String key_dialogInputFieldActivated = "dialogInputFieldActivated"; + public static final String key_dialogCheckboxSquareBackground = "dialogCheckboxSquareBackground"; + public static final String key_dialogCheckboxSquareCheck = "dialogCheckboxSquareCheck"; + public static final String key_dialogCheckboxSquareUnchecked = "dialogCheckboxSquareUnchecked"; + public static final String key_dialogCheckboxSquareDisabled = "dialogCheckboxSquareDisabled"; + public static final String key_dialogScrollGlow = "dialogScrollGlow"; + public static final String key_dialogRoundCheckBox = "dialogRoundCheckBox"; + public static final String key_dialogRoundCheckBoxCheck = "dialogRoundCheckBoxCheck"; + public static final String key_dialogBadgeBackground = "dialogBadgeBackground"; + public static final String key_dialogBadgeText = "dialogBadgeText"; + public static final String key_dialogRadioBackground = "dialogRadioBackground"; + public static final String key_dialogRadioBackgroundChecked = "dialogRadioBackgroundChecked"; + public static final String key_dialogProgressCircle = "dialogProgressCircle"; + public static final String key_dialogLineProgress = "dialogLineProgress"; + public static final String key_dialogLineProgressBackground = "dialogLineProgressBackground"; + public static final String key_dialogButton = "dialogButton"; + public static final String key_dialogButtonSelector = "dialogButtonSelector"; + public static final String key_dialogIcon = "dialogIcon"; + public static final String key_dialogGrayLine = "dialogGrayLine"; + + public static final String key_windowBackgroundWhite = "windowBackgroundWhite"; + public static final String key_progressCircle = "progressCircle"; + public static final String key_listSelector = "listSelectorSDK21"; + public static final String key_windowBackgroundWhiteInputField = "windowBackgroundWhiteInputField"; + public static final String key_windowBackgroundWhiteInputFieldActivated = "windowBackgroundWhiteInputFieldActivated"; + public static final String key_windowBackgroundWhiteGrayIcon = "windowBackgroundWhiteGrayIcon"; + public static final String key_windowBackgroundWhiteBlueText = "windowBackgroundWhiteBlueText"; + public static final String key_windowBackgroundWhiteBlueText2 = "windowBackgroundWhiteBlueText2"; + public static final String key_windowBackgroundWhiteBlueText3 = "windowBackgroundWhiteBlueText3"; + public static final String key_windowBackgroundWhiteBlueText4 = "windowBackgroundWhiteBlueText4"; + public static final String key_windowBackgroundWhiteBlueText5 = "windowBackgroundWhiteBlueText5"; + public static final String key_windowBackgroundWhiteBlueText6 = "windowBackgroundWhiteBlueText6"; + public static final String key_windowBackgroundWhiteBlueText7 = "windowBackgroundWhiteBlueText7"; + public static final String key_windowBackgroundWhiteGreenText = "windowBackgroundWhiteGreenText"; + public static final String key_windowBackgroundWhiteGreenText2 = "windowBackgroundWhiteGreenText2"; + public static final String key_windowBackgroundWhiteRedText = "windowBackgroundWhiteRedText"; + public static final String key_windowBackgroundWhiteRedText2 = "windowBackgroundWhiteRedText2"; + public static final String key_windowBackgroundWhiteRedText3 = "windowBackgroundWhiteRedText3"; + public static final String key_windowBackgroundWhiteRedText4 = "windowBackgroundWhiteRedText4"; + public static final String key_windowBackgroundWhiteRedText5 = "windowBackgroundWhiteRedText5"; + public static final String key_windowBackgroundWhiteRedText6 = "windowBackgroundWhiteRedText6"; + public static final String key_windowBackgroundWhiteGrayText = "windowBackgroundWhiteGrayText"; + public static final String key_windowBackgroundWhiteGrayText2 = "windowBackgroundWhiteGrayText2"; + public static final String key_windowBackgroundWhiteGrayText3 = "windowBackgroundWhiteGrayText3"; + public static final String key_windowBackgroundWhiteGrayText4 = "windowBackgroundWhiteGrayText4"; + public static final String key_windowBackgroundWhiteGrayText5 = "windowBackgroundWhiteGrayText5"; + public static final String key_windowBackgroundWhiteGrayText6 = "windowBackgroundWhiteGrayText6"; + public static final String key_windowBackgroundWhiteGrayText7 = "windowBackgroundWhiteGrayText7"; + public static final String key_windowBackgroundWhiteGrayText8 = "windowBackgroundWhiteGrayText8"; + public static final String key_windowBackgroundWhiteGrayLine = "windowBackgroundWhiteGrayLine"; + public static final String key_windowBackgroundWhiteBlackText = "windowBackgroundWhiteBlackText"; + public static final String key_windowBackgroundWhiteHintText = "windowBackgroundWhiteHintText"; + public static final String key_windowBackgroundWhiteValueText = "windowBackgroundWhiteValueText"; + public static final String key_windowBackgroundWhiteLinkText = "windowBackgroundWhiteLinkText"; + public static final String key_windowBackgroundWhiteLinkSelection = "windowBackgroundWhiteLinkSelection"; + public static final String key_windowBackgroundWhiteBlueHeader = "windowBackgroundWhiteBlueHeader"; + public static final String key_switchThumb = "switchThumb"; + public static final String key_switchTrack = "switchTrack"; + public static final String key_switchThumbChecked = "switchThumbChecked"; + public static final String key_switchTrackChecked = "switchTrackChecked"; + public static final String key_checkboxSquareBackground = "checkboxSquareBackground"; + public static final String key_checkboxSquareCheck = "checkboxSquareCheck"; + public static final String key_checkboxSquareUnchecked = "checkboxSquareUnchecked"; + public static final String key_checkboxSquareDisabled = "checkboxSquareDisabled"; + public static final String key_windowBackgroundGray = "windowBackgroundGray"; + public static final String key_windowBackgroundGrayShadow = "windowBackgroundGrayShadow"; + public static final String key_emptyListPlaceholder = "emptyListPlaceholder"; + public static final String key_divider = "divider"; + public static final String key_graySection = "graySection"; + public static final String key_radioBackground = "radioBackground"; + public static final String key_radioBackgroundChecked = "radioBackgroundChecked"; + public static final String key_checkbox = "checkbox"; + public static final String key_checkboxCheck = "checkboxCheck"; + public static final String key_fastScrollActive = "fastScrollActive"; + public static final String key_fastScrollInactive = "fastScrollInactive"; + public static final String key_fastScrollText = "fastScrollText"; + + public static final String key_inappPlayerPerformer = "inappPlayerPerformer"; + public static final String key_inappPlayerTitle = "inappPlayerTitle"; + public static final String key_inappPlayerBackground = "inappPlayerBackground"; + public static final String key_inappPlayerPlayPause = "inappPlayerPlayPause"; + public static final String key_inappPlayerClose = "inappPlayerClose"; + + public static final String key_returnToCallBackground = "returnToCallBackground"; + public static final String key_returnToCallText = "returnToCallText"; + + public static final String key_contextProgressInner1 = "contextProgressInner1"; + public static final String key_contextProgressOuter1 = "contextProgressOuter1"; + public static final String key_contextProgressInner2 = "contextProgressInner2"; + public static final String key_contextProgressOuter2 = "contextProgressOuter2"; + public static final String key_contextProgressInner3 = "contextProgressInner3"; + public static final String key_contextProgressOuter3 = "contextProgressOuter3"; + + public static final String key_avatar_text = "avatar_text"; + public static final String key_avatar_backgroundRed = "avatar_backgroundRed"; + public static final String key_avatar_backgroundOrange = "avatar_backgroundOrange"; + public static final String key_avatar_backgroundViolet = "avatar_backgroundViolet"; + public static final String key_avatar_backgroundGreen = "avatar_backgroundGreen"; + public static final String key_avatar_backgroundCyan = "avatar_backgroundCyan"; + public static final String key_avatar_backgroundBlue = "avatar_backgroundBlue"; + public static final String key_avatar_backgroundPink = "avatar_backgroundPink"; + public static final String key_avatar_backgroundGroupCreateSpanBlue = "avatar_backgroundGroupCreateSpanBlue"; + public static final String key_avatar_backgroundInProfileRed = "avatar_backgroundInProfileRed"; + public static final String key_avatar_backgroundInProfileOrange = "avatar_backgroundInProfileOrange"; + public static final String key_avatar_backgroundInProfileViolet = "avatar_backgroundInProfileViolet"; + public static final String key_avatar_backgroundInProfileGreen = "avatar_backgroundInProfileGreen"; + public static final String key_avatar_backgroundInProfileCyan = "avatar_backgroundInProfileCyan"; + public static final String key_avatar_backgroundInProfileBlue = "avatar_backgroundInProfileBlue"; + public static final String key_avatar_backgroundInProfilePink = "avatar_backgroundInProfilePink"; + public static final String key_avatar_backgroundActionBarRed = "avatar_backgroundActionBarRed"; + public static final String key_avatar_backgroundActionBarOrange = "avatar_backgroundActionBarOrange"; + public static final String key_avatar_backgroundActionBarViolet = "avatar_backgroundActionBarViolet"; + public static final String key_avatar_backgroundActionBarGreen = "avatar_backgroundActionBarGreen"; + public static final String key_avatar_backgroundActionBarCyan = "avatar_backgroundActionBarCyan"; + public static final String key_avatar_backgroundActionBarBlue = "avatar_backgroundActionBarBlue"; + public static final String key_avatar_backgroundActionBarPink = "avatar_backgroundActionBarPink"; + public static final String key_avatar_subtitleInProfileRed = "avatar_subtitleInProfileRed"; + public static final String key_avatar_subtitleInProfileOrange = "avatar_subtitleInProfileOrange"; + public static final String key_avatar_subtitleInProfileViolet = "avatar_subtitleInProfileViolet"; + public static final String key_avatar_subtitleInProfileGreen = "avatar_subtitleInProfileGreen"; + public static final String key_avatar_subtitleInProfileCyan = "avatar_subtitleInProfileCyan"; + public static final String key_avatar_subtitleInProfileBlue = "avatar_subtitleInProfileBlue"; + public static final String key_avatar_subtitleInProfilePink = "avatar_subtitleInProfilePink"; + public static final String key_avatar_nameInMessageRed = "avatar_nameInMessageRed"; + public static final String key_avatar_nameInMessageOrange = "avatar_nameInMessageOrange"; + public static final String key_avatar_nameInMessageViolet = "avatar_nameInMessageViolet"; + public static final String key_avatar_nameInMessageGreen = "avatar_nameInMessageGreen"; + public static final String key_avatar_nameInMessageCyan = "avatar_nameInMessageCyan"; + public static final String key_avatar_nameInMessageBlue = "avatar_nameInMessageBlue"; + public static final String key_avatar_nameInMessagePink = "avatar_nameInMessagePink"; + public static final String key_avatar_actionBarSelectorRed = "avatar_actionBarSelectorRed"; + public static final String key_avatar_actionBarSelectorOrange = "avatar_actionBarSelectorOrange"; + public static final String key_avatar_actionBarSelectorViolet = "avatar_actionBarSelectorViolet"; + public static final String key_avatar_actionBarSelectorGreen = "avatar_actionBarSelectorGreen"; + public static final String key_avatar_actionBarSelectorCyan = "avatar_actionBarSelectorCyan"; + public static final String key_avatar_actionBarSelectorBlue = "avatar_actionBarSelectorBlue"; + public static final String key_avatar_actionBarSelectorPink = "avatar_actionBarSelectorPink"; + public static final String key_avatar_actionBarIconRed = "avatar_actionBarIconRed"; + public static final String key_avatar_actionBarIconOrange = "avatar_actionBarIconOrange"; + public static final String key_avatar_actionBarIconViolet = "avatar_actionBarIconViolet"; + public static final String key_avatar_actionBarIconGreen = "avatar_actionBarIconGreen"; + public static final String key_avatar_actionBarIconCyan = "avatar_actionBarIconCyan"; + public static final String key_avatar_actionBarIconBlue = "avatar_actionBarIconBlue"; + public static final String key_avatar_actionBarIconPink = "avatar_actionBarIconPink"; + + public static String[] keys_avatar_background = {key_avatar_backgroundRed, key_avatar_backgroundOrange, key_avatar_backgroundViolet, key_avatar_backgroundGreen, key_avatar_backgroundCyan, key_avatar_backgroundBlue, key_avatar_backgroundPink}; + public static String[] keys_avatar_backgroundInProfile = {key_avatar_backgroundInProfileRed, key_avatar_backgroundInProfileOrange, key_avatar_backgroundInProfileViolet, key_avatar_backgroundInProfileGreen, key_avatar_backgroundInProfileCyan, key_avatar_backgroundInProfileBlue, key_avatar_backgroundInProfilePink}; + public static String[] keys_avatar_backgroundActionBar = {key_avatar_backgroundActionBarRed, key_avatar_backgroundActionBarOrange, key_avatar_backgroundActionBarViolet, key_avatar_backgroundActionBarGreen, key_avatar_backgroundActionBarCyan, key_avatar_backgroundActionBarBlue, key_avatar_backgroundActionBarPink}; + public static String[] keys_avatar_subtitleInProfile = {key_avatar_subtitleInProfileRed, key_avatar_subtitleInProfileOrange, key_avatar_subtitleInProfileViolet, key_avatar_subtitleInProfileGreen, key_avatar_subtitleInProfileCyan, key_avatar_subtitleInProfileBlue, key_avatar_subtitleInProfilePink}; + public static String[] keys_avatar_nameInMessage = {key_avatar_nameInMessageRed, key_avatar_nameInMessageOrange, key_avatar_nameInMessageViolet, key_avatar_nameInMessageGreen, key_avatar_nameInMessageCyan, key_avatar_nameInMessageBlue, key_avatar_nameInMessagePink}; + public static String[] keys_avatar_actionBarSelector = {key_avatar_actionBarSelectorRed, key_avatar_actionBarSelectorOrange, key_avatar_actionBarSelectorViolet, key_avatar_actionBarSelectorGreen, key_avatar_actionBarSelectorCyan, key_avatar_actionBarSelectorBlue, key_avatar_actionBarSelectorPink}; + public static String[] keys_avatar_actionBarIcon = {key_avatar_actionBarIconRed, key_avatar_actionBarIconOrange, key_avatar_actionBarIconViolet, key_avatar_actionBarIconGreen, key_avatar_actionBarIconCyan, key_avatar_actionBarIconBlue, key_avatar_actionBarIconPink}; + + public static final String key_actionBarDefault = "actionBarDefault"; + public static final String key_actionBarDefaultSelector = "actionBarDefaultSelector"; + public static final String key_actionBarWhiteSelector = "actionBarWhiteSelector"; + public static final String key_actionBarDefaultIcon = "actionBarDefaultIcon"; + public static final String key_actionBarActionModeDefault = "actionBarActionModeDefault"; + public static final String key_actionBarActionModeDefaultTop = "actionBarActionModeDefaultTop"; + public static final String key_actionBarActionModeDefaultIcon = "actionBarActionModeDefaultIcon"; + public static final String key_actionBarActionModeDefaultSelector = "actionBarActionModeDefaultSelector"; + public static final String key_actionBarDefaultTitle = "actionBarDefaultTitle"; + public static final String key_actionBarDefaultSubtitle = "actionBarDefaultSubtitle"; + public static final String key_actionBarDefaultSearch = "actionBarDefaultSearch"; + public static final String key_actionBarDefaultSearchPlaceholder = "actionBarDefaultSearchPlaceholder"; + public static final String key_actionBarDefaultSubmenuItem = "actionBarDefaultSubmenuItem"; + public static final String key_actionBarDefaultSubmenuBackground = "actionBarDefaultSubmenuBackground"; + public static final String key_chats_unreadCounter = "chats_unreadCounter"; + public static final String key_chats_unreadCounterMuted = "chats_unreadCounterMuted"; + public static final String key_chats_unreadCounterText = "chats_unreadCounterText"; + public static final String key_chats_name = "chats_name"; + public static final String key_chats_secretName = "chats_secretName"; + public static final String key_chats_secretIcon = "chats_secretIcon"; + public static final String key_chats_nameIcon = "chats_nameIcon"; + public static final String key_chats_pinnedIcon = "chats_pinnedIcon"; + public static final String key_chats_message = "chats_message"; + public static final String key_chats_draft = "chats_draft"; + public static final String key_chats_nameMessage = "chats_nameMessage"; + public static final String key_chats_attachMessage = "chats_attachMessage"; + public static final String key_chats_actionMessage = "chats_actionMessage"; + public static final String key_chats_date = "chats_date"; + public static final String key_chats_pinnedOverlay = "chats_pinnedOverlay"; + public static final String key_chats_tabletSelectedOverlay = "chats_tabletSelectedOverlay"; + public static final String key_chats_sentCheck = "chats_sentCheck"; + public static final String key_chats_sentClock = "chats_sentClock"; + public static final String key_chats_sentError = "chats_sentError"; + public static final String key_chats_sentErrorIcon = "chats_sentErrorIcon"; + public static final String key_chats_verifiedBackground = "chats_verifiedBackground"; + public static final String key_chats_verifiedCheck = "chats_verifiedCheck"; + public static final String key_chats_muteIcon = "chats_muteIcon"; + public static final String key_chats_menuTopShadow = "chats_menuTopShadow"; + public static final String key_chats_menuBackground = "chats_menuBackground"; + public static final String key_chats_menuItemText = "chats_menuItemText"; + public static final String key_chats_menuItemIcon = "chats_menuItemIcon"; + public static final String key_chats_menuName = "chats_menuName"; + public static final String key_chats_menuPhone = "chats_menuPhone"; + public static final String key_chats_menuPhoneCats = "chats_menuPhoneCats"; + public static final String key_chats_menuCloud = "chats_menuCloud"; + public static final String key_chats_menuCloudBackgroundCats = "chats_menuCloudBackgroundCats"; + public static final String key_chats_actionIcon = "chats_actionIcon"; + public static final String key_chats_actionBackground = "chats_actionBackground"; + public static final String key_chats_actionPressedBackground = "chats_actionPressedBackground"; + + public static final String key_chat_inBubble = "chat_inBubble"; + public static final String key_chat_inBubbleSelected = "chat_inBubbleSelected"; + public static final String key_chat_inBubbleShadow = "chat_inBubbleShadow"; + public static final String key_chat_outBubble = "chat_outBubble"; + public static final String key_chat_outBubbleSelected = "chat_outBubbleSelected"; + public static final String key_chat_outBubbleShadow = "chat_outBubbleShadow"; + public static final String key_chat_messageTextIn = "chat_messageTextIn"; + public static final String key_chat_messageTextOut = "chat_messageTextOut"; + public static final String key_chat_messageLinkIn = "chat_messageLinkIn"; + public static final String key_chat_messageLinkOut = "chat_messageLinkOut"; + public static final String key_chat_serviceText = "chat_serviceText"; + public static final String key_chat_serviceLink = "chat_serviceLink"; + public static final String key_chat_serviceIcon = "chat_serviceIcon"; + public static final String key_chat_serviceBackground = "chat_serviceBackground"; + public static final String key_chat_serviceBackgroundSelected = "chat_serviceBackgroundSelected"; + public static final String key_chat_muteIcon = "chat_muteIcon"; + public static final String key_chat_lockIcon = "chat_lockIcon"; + public static final String key_chat_outSentCheck = "chat_outSentCheck"; + public static final String key_chat_outSentCheckSelected = "chat_outSentCheckSelected"; + public static final String key_chat_outSentClock = "chat_outSentClock"; + public static final String key_chat_outSentClockSelected = "chat_outSentClockSelected"; + public static final String key_chat_inSentClock = "chat_inSentClock"; + public static final String key_chat_inSentClockSelected = "chat_inSentClockSelected"; + public static final String key_chat_mediaSentCheck = "chat_mediaSentCheck"; + public static final String key_chat_mediaSentClock = "chat_mediaSentClock"; + public static final String key_chat_outViews = "chat_outViews"; + public static final String key_chat_outViewsSelected = "chat_outViewsSelected"; + public static final String key_chat_inViews = "chat_inViews"; + public static final String key_chat_inViewsSelected = "chat_inViewsSelected"; + public static final String key_chat_mediaViews = "chat_mediaViews"; + public static final String key_chat_outMenu = "chat_outMenu"; + public static final String key_chat_outMenuSelected = "chat_outMenuSelected"; + public static final String key_chat_inMenu = "chat_inMenu"; + public static final String key_chat_inMenuSelected = "chat_inMenuSelected"; + public static final String key_chat_mediaMenu = "chat_mediaMenu"; + public static final String key_chat_outInstant = "chat_outInstant"; + public static final String key_chat_outInstantSelected = "chat_outInstantSelected"; + public static final String key_chat_inInstant = "chat_inInstant"; + public static final String key_chat_inInstantSelected = "chat_inInstantSelected"; + public static final String key_chat_sentError = "chat_sentError"; + public static final String key_chat_sentErrorIcon = "chat_sentErrorIcon"; + public static final String key_chat_selectedBackground = "chat_selectedBackground"; + public static final String key_chat_previewDurationText = "chat_previewDurationText"; + public static final String key_chat_previewGameText = "chat_previewGameText"; + public static final String key_chat_inPreviewInstantText = "chat_inPreviewInstantText"; + public static final String key_chat_outPreviewInstantText = "chat_outPreviewInstantText"; + public static final String key_chat_inPreviewInstantSelectedText = "chat_inPreviewInstantSelectedText"; + public static final String key_chat_outPreviewInstantSelectedText = "chat_outPreviewInstantSelectedText"; + public static final String key_chat_secretTimeText = "chat_secretTimeText"; + public static final String key_chat_stickerNameText = "chat_stickerNameText"; + public static final String key_chat_botButtonText = "chat_botButtonText"; + public static final String key_chat_botProgress = "chat_botProgress"; + public static final String key_chat_inForwardedNameText = "chat_inForwardedNameText"; + public static final String key_chat_outForwardedNameText = "chat_outForwardedNameText"; + public static final String key_chat_inViaBotNameText = "chat_inViaBotNameText"; + public static final String key_chat_outViaBotNameText = "chat_outViaBotNameText"; + public static final String key_chat_stickerViaBotNameText = "chat_stickerViaBotNameText"; + public static final String key_chat_inReplyLine = "chat_inReplyLine"; + public static final String key_chat_outReplyLine = "chat_outReplyLine"; + public static final String key_chat_stickerReplyLine = "chat_stickerReplyLine"; + public static final String key_chat_inReplyNameText = "chat_inReplyNameText"; + public static final String key_chat_outReplyNameText = "chat_outReplyNameText"; + public static final String key_chat_stickerReplyNameText = "chat_stickerReplyNameText"; + public static final String key_chat_inReplyMessageText = "chat_inReplyMessageText"; + public static final String key_chat_outReplyMessageText = "chat_outReplyMessageText"; + public static final String key_chat_inReplyMediaMessageText = "chat_inReplyMediaMessageText"; + public static final String key_chat_outReplyMediaMessageText = "chat_outReplyMediaMessageText"; + public static final String key_chat_inReplyMediaMessageSelectedText = "chat_inReplyMediaMessageSelectedText"; + public static final String key_chat_outReplyMediaMessageSelectedText = "chat_outReplyMediaMessageSelectedText"; + public static final String key_chat_stickerReplyMessageText = "chat_stickerReplyMessageText"; + public static final String key_chat_inPreviewLine = "chat_inPreviewLine"; + public static final String key_chat_outPreviewLine = "chat_outPreviewLine"; + public static final String key_chat_inSiteNameText = "chat_inSiteNameText"; + public static final String key_chat_outSiteNameText = "chat_outSiteNameText"; + public static final String key_chat_inContactNameText = "chat_inContactNameText"; + public static final String key_chat_outContactNameText = "chat_outContactNameText"; + public static final String key_chat_inContactPhoneText = "chat_inContactPhoneText"; + public static final String key_chat_outContactPhoneText = "chat_outContactPhoneText"; + public static final String key_chat_mediaProgress = "chat_mediaProgress"; + public static final String key_chat_inAudioProgress = "chat_inAudioProgress"; + public static final String key_chat_outAudioProgress = "chat_outAudioProgress"; + public static final String key_chat_inAudioSelectedProgress = "chat_inAudioSelectedProgress"; + public static final String key_chat_outAudioSelectedProgress = "chat_outAudioSelectedProgress"; + public static final String key_chat_mediaTimeText = "chat_mediaTimeText"; + public static final String key_chat_inTimeText = "chat_inTimeText"; + public static final String key_chat_outTimeText = "chat_outTimeText"; + public static final String key_chat_inTimeSelectedText = "chat_inTimeSelectedText"; + public static final String key_chat_outTimeSelectedText = "chat_outTimeSelectedText"; + public static final String key_chat_inAudioPerfomerText = "chat_inAudioPerfomerText"; + public static final String key_chat_outAudioPerfomerText = "chat_outAudioPerfomerText"; + public static final String key_chat_inAudioTitleText = "chat_inAudioTitleText"; + public static final String key_chat_outAudioTitleText = "chat_outAudioTitleText"; + public static final String key_chat_inAudioDurationText = "chat_inAudioDurationText"; + public static final String key_chat_outAudioDurationText = "chat_outAudioDurationText"; + public static final String key_chat_inAudioDurationSelectedText = "chat_inAudioDurationSelectedText"; + public static final String key_chat_outAudioDurationSelectedText = "chat_outAudioDurationSelectedText"; + public static final String key_chat_inAudioSeekbar = "chat_inAudioSeekbar"; + public static final String key_chat_outAudioSeekbar = "chat_outAudioSeekbar"; + public static final String key_chat_inAudioSeekbarSelected = "chat_inAudioSeekbarSelected"; + public static final String key_chat_outAudioSeekbarSelected = "chat_outAudioSeekbarSelected"; + public static final String key_chat_inAudioSeekbarFill = "chat_inAudioSeekbarFill"; + public static final String key_chat_outAudioSeekbarFill = "chat_outAudioSeekbarFill"; + public static final String key_chat_inVoiceSeekbar = "chat_inVoiceSeekbar"; + public static final String key_chat_outVoiceSeekbar = "chat_outVoiceSeekbar"; + public static final String key_chat_inVoiceSeekbarSelected = "chat_inVoiceSeekbarSelected"; + public static final String key_chat_outVoiceSeekbarSelected = "chat_outVoiceSeekbarSelected"; + public static final String key_chat_inVoiceSeekbarFill = "chat_inVoiceSeekbarFill"; + public static final String key_chat_outVoiceSeekbarFill = "chat_outVoiceSeekbarFill"; + public static final String key_chat_inFileProgress = "chat_inFileProgress"; + public static final String key_chat_outFileProgress = "chat_outFileProgress"; + public static final String key_chat_inFileProgressSelected = "chat_inFileProgressSelected"; + public static final String key_chat_outFileProgressSelected = "chat_outFileProgressSelected"; + public static final String key_chat_inFileNameText = "chat_inFileNameText"; + public static final String key_chat_outFileNameText = "chat_outFileNameText"; + public static final String key_chat_inFileInfoText = "chat_inFileInfoText"; + public static final String key_chat_outFileInfoText = "chat_outFileInfoText"; + public static final String key_chat_inFileInfoSelectedText = "chat_inFileInfoSelectedText"; + public static final String key_chat_outFileInfoSelectedText = "chat_outFileInfoSelectedText"; + public static final String key_chat_inFileBackground = "chat_inFileBackground"; + public static final String key_chat_outFileBackground = "chat_outFileBackground"; + public static final String key_chat_inFileBackgroundSelected = "chat_inFileBackgroundSelected"; + public static final String key_chat_outFileBackgroundSelected = "chat_outFileBackgroundSelected"; + public static final String key_chat_inVenueNameText = "chat_inVenueNameText"; + public static final String key_chat_outVenueNameText = "chat_outVenueNameText"; + public static final String key_chat_inVenueInfoText = "chat_inVenueInfoText"; + public static final String key_chat_outVenueInfoText = "chat_outVenueInfoText"; + public static final String key_chat_inVenueInfoSelectedText = "chat_inVenueInfoSelectedText"; + public static final String key_chat_outVenueInfoSelectedText = "chat_outVenueInfoSelectedText"; + public static final String key_chat_mediaInfoText = "chat_mediaInfoText"; + public static final String key_chat_linkSelectBackground = "chat_linkSelectBackground"; + public static final String key_chat_textSelectBackground = "chat_textSelectBackground"; + public static final String key_chat_wallpaper = "chat_wallpaper"; + public static final String key_chat_messagePanelBackground = "chat_messagePanelBackground"; + public static final String key_chat_messagePanelShadow = "chat_messagePanelShadow"; + public static final String key_chat_messagePanelText = "chat_messagePanelText"; + public static final String key_chat_messagePanelHint = "chat_messagePanelHint"; + public static final String key_chat_messagePanelIcons = "chat_messagePanelIcons"; + public static final String key_chat_messagePanelSend = "chat_messagePanelSend"; + public static final String key_chat_topPanelBackground = "chat_topPanelBackground"; + public static final String key_chat_topPanelClose = "chat_topPanelClose"; + public static final String key_chat_topPanelLine = "chat_topPanelLine"; + public static final String key_chat_topPanelTitle = "chat_topPanelTitle"; + public static final String key_chat_topPanelMessage = "chat_topPanelMessage"; + public static final String key_chat_reportSpam = "chat_reportSpam"; + public static final String key_chat_addContact = "chat_addContact"; + public static final String key_chat_inLoader = "chat_inLoader"; + public static final String key_chat_inLoaderSelected = "chat_inLoaderSelected"; + public static final String key_chat_outLoader = "chat_outLoader"; + public static final String key_chat_outLoaderSelected = "chat_outLoaderSelected"; + public static final String key_chat_inLoaderPhoto = "chat_inLoaderPhoto"; + public static final String key_chat_inLoaderPhotoSelected = "chat_inLoaderPhotoSelected"; + public static final String key_chat_inLoaderPhotoIcon = "chat_inLoaderPhotoIcon"; + public static final String key_chat_inLoaderPhotoIconSelected = "chat_inLoaderPhotoIconSelected"; + public static final String key_chat_outLoaderPhoto = "chat_outLoaderPhoto"; + public static final String key_chat_outLoaderPhotoSelected = "chat_outLoaderPhotoSelected"; + public static final String key_chat_outLoaderPhotoIcon = "chat_outLoaderPhotoIcon"; + public static final String key_chat_outLoaderPhotoIconSelected = "chat_outLoaderPhotoIconSelected"; + public static final String key_chat_mediaLoaderPhoto = "chat_mediaLoaderPhoto"; + public static final String key_chat_mediaLoaderPhotoSelected = "chat_mediaLoaderPhotoSelected"; + public static final String key_chat_mediaLoaderPhotoIcon = "chat_mediaLoaderPhotoIcon"; + public static final String key_chat_mediaLoaderPhotoIconSelected = "chat_mediaLoaderPhotoIconSelected"; + public static final String key_chat_inLocationBackground = "chat_inLocationBackground"; + public static final String key_chat_inLocationIcon = "chat_inLocationIcon"; + public static final String key_chat_outLocationBackground = "chat_outLocationBackground"; + public static final String key_chat_outLocationIcon = "chat_outLocationIcon"; + public static final String key_chat_inContactBackground = "chat_inContactBackground"; + public static final String key_chat_inContactIcon = "chat_inContactIcon"; + public static final String key_chat_outContactBackground = "chat_outContactBackground"; + public static final String key_chat_outContactIcon = "chat_outContactIcon"; + public static final String key_chat_inFileIcon = "chat_inFileIcon"; + public static final String key_chat_inFileSelectedIcon = "chat_inFileSelectedIcon"; + public static final String key_chat_outFileIcon = "chat_outFileIcon"; + public static final String key_chat_outFileSelectedIcon = "chat_outFileSelectedIcon"; + public static final String key_chat_replyPanelIcons = "chat_replyPanelIcons"; + public static final String key_chat_replyPanelClose = "chat_replyPanelClose"; + public static final String key_chat_replyPanelName = "chat_replyPanelName"; + public static final String key_chat_replyPanelMessage = "chat_replyPanelMessage"; + public static final String key_chat_replyPanelLine = "chat_replyPanelLine"; + public static final String key_chat_searchPanelIcons = "chat_searchPanelIcons"; + public static final String key_chat_searchPanelText = "chat_searchPanelText"; + public static final String key_chat_secretChatStatusText = "chat_secretChatStatusText"; + public static final String key_chat_fieldOverlayText = "chat_fieldOverlayText"; + public static final String key_chat_stickersHintPanel = "chat_stickersHintPanel"; + public static final String key_chat_botSwitchToInlineText = "chat_botSwitchToInlineText"; + public static final String key_chat_unreadMessagesStartArrowIcon = "chat_unreadMessagesStartArrowIcon"; + public static final String key_chat_unreadMessagesStartText = "chat_unreadMessagesStartText"; + public static final String key_chat_unreadMessagesStartBackground = "chat_unreadMessagesStartBackground"; + public static final String key_chat_inlineResultIcon = "chat_inlineResultIcon"; + public static final String key_chat_emojiPanelBackground = "chat_emojiPanelBackground"; + public static final String key_chat_emojiPanelShadowLine = "chat_emojiPanelShadowLine"; + public static final String key_chat_emojiPanelEmptyText = "chat_emojiPanelEmptyText"; + public static final String key_chat_emojiPanelIcon = "chat_emojiPanelIcon"; + public static final String key_chat_emojiPanelIconSelected = "chat_emojiPanelIconSelected"; + public static final String key_chat_emojiPanelStickerPackSelector = "chat_emojiPanelStickerPackSelector"; + public static final String key_chat_emojiPanelIconSelector = "chat_emojiPanelIconSelector"; + public static final String key_chat_emojiPanelBackspace = "chat_emojiPanelBackspace"; + public static final String key_chat_emojiPanelMasksIcon = "chat_emojiPanelMasksIcon"; + public static final String key_chat_emojiPanelMasksIconSelected = "chat_emojiPanelMasksIconSelected"; + public static final String key_chat_emojiPanelTrendingTitle = "chat_emojiPanelTrendingTitle"; + public static final String key_chat_emojiPanelTrendingDescription = "chat_emojiPanelTrendingDescription"; + public static final String key_chat_botKeyboardButtonText = "chat_botKeyboardButtonText"; + public static final String key_chat_botKeyboardButtonBackground = "chat_botKeyboardButtonBackground"; + public static final String key_chat_botKeyboardButtonBackgroundPressed = "chat_botKeyboardButtonBackgroundPressed"; + public static final String key_chat_emojiPanelNewTrending = "chat_emojiPanelNewTrending"; + public static final String key_chat_editDoneIcon = "chat_editDoneIcon"; + public static final String key_chat_messagePanelVoicePressed = "chat_messagePanelVoicePressed"; + public static final String key_chat_messagePanelVoiceBackground = "chat_messagePanelVoiceBackground"; + public static final String key_chat_messagePanelVoiceShadow = "chat_messagePanelVoiceShadow"; + public static final String key_chat_messagePanelVoiceDelete = "chat_messagePanelVoiceDelete"; + public static final String key_chat_messagePanelVoiceDuration = "chat_messagePanelVoiceDuration"; + public static final String key_chat_recordedVoicePlayPause = "chat_recordedVoicePlayPause"; + public static final String key_chat_recordedVoicePlayPausePressed = "chat_recordedVoicePlayPausePressed"; + public static final String key_chat_recordedVoiceProgress = "chat_recordedVoiceProgress"; + public static final String key_chat_recordedVoiceProgressInner = "chat_recordedVoiceProgressInner"; + public static final String key_chat_recordedVoiceDot = "chat_recordedVoiceDot"; + public static final String key_chat_recordedVoiceBackground = "chat_recordedVoiceBackground"; + public static final String key_chat_recordVoiceCancel = "chat_recordVoiceCancel"; + public static final String key_chat_recordTime = "chat_recordTime"; + public static final String key_chat_messagePanelCancelInlineBot = "chat_messagePanelCancelInlineBot"; + public static final String key_chat_gifSaveHintText = "chat_gifSaveHintText"; + public static final String key_chat_gifSaveHintBackground = "chat_gifSaveHintBackground"; + public static final String key_chat_goDownButton = "chat_goDownButton"; + public static final String key_chat_goDownButtonShadow = "chat_goDownButtonShadow"; + public static final String key_chat_goDownButtonIcon = "chat_goDownButtonIcon"; + public static final String key_chat_goDownButtonCounter = "chat_goDownButtonCounter"; + public static final String key_chat_goDownButtonCounterBackground = "chat_goDownButtonCounterBackground"; + public static final String key_chat_secretTimerBackground = "chat_secretTimerBackground"; + public static final String key_chat_secretTimerText = "chat_secretTimerText"; + + public static final String key_profile_creatorIcon = "profile_creatorIcon"; + public static final String key_profile_adminIcon = "profile_adminIcon"; + public static final String key_profile_title = "profile_title"; + public static final String key_profile_actionIcon = "profile_actionIcon"; + public static final String key_profile_actionBackground = "profile_actionBackground"; + public static final String key_profile_actionPressedBackground = "profile_actionPressedBackground"; + public static final String key_profile_verifiedBackground = "profile_verifiedBackground"; + public static final String key_profile_verifiedCheck = "profile_verifiedCheck"; + + public static final String key_sharedMedia_startStopLoadIcon = "sharedMedia_startStopLoadIcon"; + public static final String key_sharedMedia_linkPlaceholder = "sharedMedia_linkPlaceholder"; + public static final String key_sharedMedia_linkPlaceholderText = "sharedMedia_linkPlaceholderText"; + + public static final String key_featuredStickers_addedIcon = "featuredStickers_addedIcon"; + public static final String key_featuredStickers_buttonProgress = "featuredStickers_buttonProgress"; + public static final String key_featuredStickers_addButton = "featuredStickers_addButton"; + public static final String key_featuredStickers_addButtonPressed = "featuredStickers_addButtonPressed"; + public static final String key_featuredStickers_delButton = "featuredStickers_delButton"; + public static final String key_featuredStickers_delButtonPressed = "featuredStickers_delButtonPressed"; + public static final String key_featuredStickers_buttonText = "featuredStickers_buttonText"; + public static final String key_featuredStickers_unread = "featuredStickers_unread"; + + public static final String key_stickers_menu = "stickers_menu"; + public static final String key_stickers_menuSelector = "stickers_menuSelector"; + + public static final String key_changephoneinfo_image = "changephoneinfo_image"; + + public static final String key_groupcreate_hintText = "groupcreate_hintText"; + public static final String key_groupcreate_cursor = "groupcreate_cursor"; + public static final String key_groupcreate_sectionShadow = "groupcreate_sectionShadow"; + public static final String key_groupcreate_sectionText = "groupcreate_sectionText"; + public static final String key_groupcreate_onlineText = "groupcreate_onlineText"; + public static final String key_groupcreate_offlineText = "groupcreate_offlineText"; + public static final String key_groupcreate_checkbox = "groupcreate_checkbox"; + public static final String key_groupcreate_checkboxCheck = "groupcreate_checkboxCheck"; + public static final String key_groupcreate_spanText = "groupcreate_spanText"; + public static final String key_groupcreate_spanBackground = "groupcreate_spanBackground"; + + public static final String key_login_progressInner = "login_progressInner"; + public static final String key_login_progressOuter = "login_progressOuter"; + + public static final String key_musicPicker_checkbox = "musicPicker_checkbox"; + public static final String key_musicPicker_checkboxCheck = "musicPicker_checkboxCheck"; + public static final String key_musicPicker_buttonBackground = "musicPicker_buttonBackground"; + public static final String key_musicPicker_buttonIcon = "musicPicker_buttonIcon"; + + public static final String key_picker_enabledButton = "picker_enabledButton"; + public static final String key_picker_disabledButton = "picker_disabledButton"; + public static final String key_picker_badge = "picker_badge"; + public static final String key_picker_badgeText = "picker_badgeText"; + + public static final String key_location_markerX = "location_markerX"; + public static final String key_location_sendLocationBackground = "location_sendLocationBackground"; + public static final String key_location_sendLocationIcon = "location_sendLocationIcon"; + + public static final String key_files_folderIcon = "files_folderIcon"; + public static final String key_files_folderIconBackground = "files_folderIconBackground"; + public static final String key_files_iconText = "files_iconText"; + + public static final String key_sessions_devicesImage = "sessions_devicesImage"; + + public static final String key_calls_callReceivedGreenIcon = "calls_callReceivedGreenIcon"; + public static final String key_calls_callReceivedRedIcon = "calls_callReceivedRedIcon"; + + public static final String key_calls_ratingStar = "calls_ratingStar"; + public static final String key_calls_ratingStarSelected = "calls_ratingStarSelected"; + + //ununsed + public static final String key_chat_outBroadcast = "chat_outBroadcast"; + public static final String key_chat_mediaBroadcast = "chat_mediaBroadcast"; + + public static final String key_player_actionBar = "player_actionBar"; + public static final String key_player_actionBarSelector = "player_actionBarSelector"; + public static final String key_player_actionBarTitle = "player_actionBarTitle"; + public static final String key_player_actionBarTop = "player_actionBarTop"; + public static final String key_player_actionBarSubtitle = "player_actionBarSubtitle"; + public static final String key_player_actionBarItems = "player_actionBarItems"; + public static final String key_player_seekBarBackground = "player_seekBarBackground"; + public static final String key_player_time = "player_time"; + public static final String key_player_duration = "player_duration"; + public static final String key_player_progressBackground = "player_progressBackground"; + public static final String key_player_progress = "player_progress"; + public static final String key_player_placeholder = "player_placeholder"; + public static final String key_player_button = "player_button"; + public static final String key_player_buttonActive = "player_buttonActive"; + + private static HashMap defaultColors = new HashMap<>(); + private static HashMap currentColors; + + static { + defaultColors.put(key_dialogBackground, 0xffffffff); + defaultColors.put(key_dialogTextBlack, 0xff212121); + defaultColors.put(key_dialogTextLink, 0xff2678b6); + defaultColors.put(key_dialogLinkSelection, 0x3362a9e3); + defaultColors.put(key_dialogTextRed, 0xffcd5a5a); + defaultColors.put(key_dialogTextBlue, 0xff2f8cc9); + defaultColors.put(key_dialogTextBlue2, 0xff3a8ccf); + defaultColors.put(key_dialogTextBlue3, 0xff3ec1f9); + defaultColors.put(key_dialogTextBlue4, 0xff19a7e8); + defaultColors.put(key_dialogTextGray, 0xff348bc1); + defaultColors.put(key_dialogTextGray2, 0xff757575); + defaultColors.put(key_dialogTextGray3, 0xff999999); + defaultColors.put(key_dialogTextGray4, 0xffb3b3b3); + defaultColors.put(key_dialogTextHint, 0xff979797); + defaultColors.put(key_dialogIcon, 0xff8a8a8a); + defaultColors.put(key_dialogGrayLine, 0xffd2d2d2); + defaultColors.put(key_dialogInputField, 0xffdbdbdb); + defaultColors.put(key_dialogInputFieldActivated, 0xff37a9f0); + defaultColors.put(key_dialogCheckboxSquareBackground, 0xff43a0df); + defaultColors.put(key_dialogCheckboxSquareCheck, 0xffffffff); + defaultColors.put(key_dialogCheckboxSquareUnchecked, 0xff737373); + defaultColors.put(key_dialogCheckboxSquareDisabled, 0xffb0b0b0); + defaultColors.put(key_dialogRadioBackground, 0xffb3b3b3); + defaultColors.put(key_dialogRadioBackgroundChecked, 0xff37a9f0); + defaultColors.put(key_dialogProgressCircle, 0xff527da3); + defaultColors.put(key_dialogLineProgress, 0xff527da3); + defaultColors.put(key_dialogLineProgressBackground, 0xffdbdbdb); + defaultColors.put(key_dialogButton, 0xff4991cc); + defaultColors.put(key_dialogButtonSelector, 0x0f000000); + defaultColors.put(key_dialogScrollGlow, 0xfff5f6f7); + defaultColors.put(key_dialogRoundCheckBox, 0xff3ec1f9); + defaultColors.put(key_dialogRoundCheckBoxCheck, 0xffffffff); + defaultColors.put(key_dialogBadgeBackground, 0xff3ec1f9); + defaultColors.put(key_dialogBadgeText, 0xffffffff); + + defaultColors.put(key_windowBackgroundWhite, 0xffffffff); + defaultColors.put(key_progressCircle, 0xff527da3); + defaultColors.put(key_windowBackgroundWhiteGrayIcon, 0xff737373); + defaultColors.put(key_windowBackgroundWhiteBlueText, 0xff3b84c0); + defaultColors.put(key_windowBackgroundWhiteBlueText2, 0xff348bc1); + defaultColors.put(key_windowBackgroundWhiteBlueText3, 0xff2678b6); + defaultColors.put(key_windowBackgroundWhiteBlueText4, 0xff4d83b3); + defaultColors.put(key_windowBackgroundWhiteBlueText5, 0xff4c8eca); + defaultColors.put(key_windowBackgroundWhiteBlueText6, 0xff3a8ccf); + defaultColors.put(key_windowBackgroundWhiteBlueText7, 0xff377aae); + defaultColors.put(key_windowBackgroundWhiteGreenText, 0xff26972c); + defaultColors.put(key_windowBackgroundWhiteGreenText2, 0xff37a919); + defaultColors.put(key_windowBackgroundWhiteRedText, 0xffcd5a5a); + defaultColors.put(key_windowBackgroundWhiteRedText2, 0xffdb5151); + defaultColors.put(key_windowBackgroundWhiteRedText3, 0xffd24949); + defaultColors.put(key_windowBackgroundWhiteRedText4, 0xffcf3030); + defaultColors.put(key_windowBackgroundWhiteRedText5, 0xffed3d39); + defaultColors.put(key_windowBackgroundWhiteRedText6, 0xffff6666); + defaultColors.put(key_windowBackgroundWhiteGrayText, 0xffa8a8a8); + defaultColors.put(key_windowBackgroundWhiteGrayText2, 0xff8a8a8a); + defaultColors.put(key_windowBackgroundWhiteGrayText3, 0xff999999); + defaultColors.put(key_windowBackgroundWhiteGrayText4, 0xff808080); + defaultColors.put(key_windowBackgroundWhiteGrayText5, 0xffa3a3a3); + defaultColors.put(key_windowBackgroundWhiteGrayText6, 0xff757575); + defaultColors.put(key_windowBackgroundWhiteGrayText7, 0xffc6c6c6); + defaultColors.put(key_windowBackgroundWhiteGrayText8, 0xff6d6d72); + defaultColors.put(key_windowBackgroundWhiteGrayLine, 0xffdbdbdb); + defaultColors.put(key_windowBackgroundWhiteBlackText, 0xff212121); + defaultColors.put(key_windowBackgroundWhiteHintText, 0xff979797); + defaultColors.put(key_windowBackgroundWhiteValueText, 0xff2f8cc9); + defaultColors.put(key_windowBackgroundWhiteLinkText, 0xff2678b6); + defaultColors.put(key_windowBackgroundWhiteLinkSelection, 0x3362a9e3); + defaultColors.put(key_windowBackgroundWhiteBlueHeader, 0xff3e90cf); + defaultColors.put(key_windowBackgroundWhiteInputField, 0xffdbdbdb); + defaultColors.put(key_windowBackgroundWhiteInputFieldActivated, 0xff37a9f0); + defaultColors.put(key_switchThumb, 0xffededed); + defaultColors.put(key_switchTrack, 0xffc7c7c7); + defaultColors.put(key_switchThumbChecked, 0xff45abef); + defaultColors.put(key_switchTrackChecked, 0xffa0d6fa); + defaultColors.put(key_checkboxSquareBackground, 0xff43a0df); + defaultColors.put(key_checkboxSquareCheck, 0xffffffff); + defaultColors.put(key_checkboxSquareUnchecked, 0xff737373); + defaultColors.put(key_checkboxSquareDisabled, 0xffb0b0b0); + defaultColors.put(key_listSelector, 0x0f000000); + defaultColors.put(key_radioBackground, 0xffb3b3b3); + defaultColors.put(key_radioBackgroundChecked, 0xff37a9f0); + defaultColors.put(key_windowBackgroundGray, 0xfff0f0f0); + defaultColors.put(key_windowBackgroundGrayShadow, 0xff000000); + defaultColors.put(key_emptyListPlaceholder, 0xff959595); + defaultColors.put(key_divider, 0xffd9d9d9); + defaultColors.put(key_graySection, 0xfff2f2f2); + defaultColors.put(key_contextProgressInner1, 0xffbfdff6); + defaultColors.put(key_contextProgressOuter1, 0xff2b96e2); + defaultColors.put(key_contextProgressInner2, 0xffbfdff6); + defaultColors.put(key_contextProgressOuter2, 0xffffffff); + defaultColors.put(key_contextProgressInner3, 0xffb3b3b3); + defaultColors.put(key_contextProgressOuter3, 0xffffffff); + defaultColors.put(key_fastScrollActive, 0xff52a3db); + defaultColors.put(key_fastScrollInactive, 0xff636363); + defaultColors.put(key_fastScrollText, 0xffffffff); + + defaultColors.put(key_avatar_text, 0xffffffff); + + defaultColors.put(key_avatar_backgroundRed, 0xffe56555); + defaultColors.put(key_avatar_backgroundOrange, 0xfff28c48); + defaultColors.put(key_avatar_backgroundViolet, 0xff8e85ee); + defaultColors.put(key_avatar_backgroundGreen, 0xff76c84d); + defaultColors.put(key_avatar_backgroundCyan, 0xff5fbed5); + defaultColors.put(key_avatar_backgroundBlue, 0xff549cdd); + defaultColors.put(key_avatar_backgroundPink, 0xfff2749a); + defaultColors.put(key_avatar_backgroundGroupCreateSpanBlue, 0xffbfd6ea); + defaultColors.put(key_avatar_backgroundInProfileRed, 0xffd86f65); + defaultColors.put(key_avatar_backgroundInProfileOrange, 0xfff69d61); + defaultColors.put(key_avatar_backgroundInProfileViolet, 0xff8c79d2); + defaultColors.put(key_avatar_backgroundInProfileGreen, 0xff67b35d); + defaultColors.put(key_avatar_backgroundInProfileCyan, 0xff56a2bb); + defaultColors.put(key_avatar_backgroundInProfileBlue, 0xff5085b1); + defaultColors.put(key_avatar_backgroundInProfilePink, 0xfff37fa6); + defaultColors.put(key_avatar_backgroundActionBarRed, 0xffca6056); + defaultColors.put(key_avatar_backgroundActionBarOrange, 0xfff18944); + defaultColors.put(key_avatar_backgroundActionBarViolet, 0xff7d6ac4); + defaultColors.put(key_avatar_backgroundActionBarGreen, 0xff56a14c); + defaultColors.put(key_avatar_backgroundActionBarCyan, 0xff4492ac); + defaultColors.put(key_avatar_backgroundActionBarBlue, 0xff598fba); + defaultColors.put(key_avatar_backgroundActionBarPink, 0xff4c84b6); + defaultColors.put(key_avatar_subtitleInProfileRed, 0xfff9cbc5); + defaultColors.put(key_avatar_subtitleInProfileOrange, 0xfffdddc8); + defaultColors.put(key_avatar_subtitleInProfileViolet, 0xffcdc4ed); + defaultColors.put(key_avatar_subtitleInProfileGreen, 0xffc0edba); + defaultColors.put(key_avatar_subtitleInProfileCyan, 0xffb8e2f0); + defaultColors.put(key_avatar_subtitleInProfileBlue, 0xffd7eafa); + defaultColors.put(key_avatar_subtitleInProfilePink, 0xffb3d7f7); + defaultColors.put(key_avatar_nameInMessageRed, 0xffca5650); + defaultColors.put(key_avatar_nameInMessageOrange, 0xffd87b29); + defaultColors.put(key_avatar_nameInMessageViolet, 0xff4e92cc); + defaultColors.put(key_avatar_nameInMessageGreen, 0xff50b232); + defaultColors.put(key_avatar_nameInMessageCyan, 0xff42b1a8); + defaultColors.put(key_avatar_nameInMessageBlue, 0xff4e92cc); + defaultColors.put(key_avatar_nameInMessagePink, 0xff4e92cc); + defaultColors.put(key_avatar_actionBarSelectorRed, 0xffbc4b41); + defaultColors.put(key_avatar_actionBarSelectorOrange, 0xffe67429); + defaultColors.put(key_avatar_actionBarSelectorViolet, 0xff735fbe); + defaultColors.put(key_avatar_actionBarSelectorGreen, 0xff48953d); + defaultColors.put(key_avatar_actionBarSelectorCyan, 0xff39849d); + defaultColors.put(key_avatar_actionBarSelectorBlue, 0xff4981ad); + defaultColors.put(key_avatar_actionBarSelectorPink, 0xffd44e7b); + defaultColors.put(key_avatar_actionBarIconRed, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconOrange, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconViolet, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconGreen, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconCyan, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconBlue, 0xffffffff); + defaultColors.put(key_avatar_actionBarIconPink, 0xffffffff); + + defaultColors.put(key_actionBarDefault, 0xff527da3); + defaultColors.put(key_actionBarDefaultIcon, 0xffffffff); + defaultColors.put(key_actionBarActionModeDefault, 0xffffffff); + defaultColors.put(key_actionBarActionModeDefaultTop, 0x99000000); + defaultColors.put(key_actionBarActionModeDefaultIcon, 0xff737373); + defaultColors.put(key_actionBarDefaultTitle, 0xffffffff); + defaultColors.put(key_actionBarDefaultSubtitle, 0xffd5e8f7); + defaultColors.put(key_actionBarDefaultSelector, 0xff406d94); + defaultColors.put(key_actionBarWhiteSelector, 0x2f000000); + defaultColors.put(key_actionBarDefaultSearch, 0xffffffff); + defaultColors.put(key_actionBarDefaultSearchPlaceholder, 0x88ffffff); + defaultColors.put(key_actionBarDefaultSubmenuItem, 0xff212121); + defaultColors.put(key_actionBarDefaultSubmenuBackground, 0xffffffff); + defaultColors.put(key_actionBarActionModeDefaultSelector, 0xfff0f0f0); + + defaultColors.put(key_chats_unreadCounter, 0xff4ecc5e); + defaultColors.put(key_chats_unreadCounterMuted, 0xffc7c7c7); + defaultColors.put(key_chats_unreadCounterText, 0xffffffff); + defaultColors.put(key_chats_name, 0xff212121); + defaultColors.put(key_chats_secretName, 0xff00a60e); + defaultColors.put(key_chats_secretIcon, 0xff19b126); + defaultColors.put(key_chats_nameIcon, 0xff242424); + defaultColors.put(key_chats_pinnedIcon, 0xffa8a8a8); + defaultColors.put(key_chats_message, 0xff8f8f8f); + defaultColors.put(key_chats_draft, 0xffdd4b39); + defaultColors.put(key_chats_nameMessage, 0xff4d83b3); + defaultColors.put(key_chats_attachMessage, 0xff4d83b3); + defaultColors.put(key_chats_actionMessage, 0xff4d83b3); + defaultColors.put(key_chats_date, 0xff999999); + defaultColors.put(key_chats_pinnedOverlay, 0x08000000); + defaultColors.put(key_chats_tabletSelectedOverlay, 0x0f000000); + defaultColors.put(key_chats_sentCheck, 0xff46aa36); + defaultColors.put(key_chats_sentClock, 0xff75bd5e); + defaultColors.put(key_chats_sentError, 0xffd55252); + defaultColors.put(key_chats_sentErrorIcon, 0xffffffff); + defaultColors.put(key_chats_verifiedBackground, 0xff33a8e6); + defaultColors.put(key_chats_verifiedCheck, 0xffffffff); + defaultColors.put(key_chats_muteIcon, 0xffa8a8a8); + defaultColors.put(key_chats_menuBackground, 0xffffffff); + defaultColors.put(key_chats_menuItemText, 0xff444444); + defaultColors.put(key_chats_menuItemIcon, 0xff737373); + defaultColors.put(key_chats_menuName, 0xffffffff); + defaultColors.put(key_chats_menuPhone, 0xffffffff); + defaultColors.put(key_chats_menuPhoneCats, 0xffc2e5ff); + defaultColors.put(key_chats_menuCloud, 0xffffffff); + defaultColors.put(key_chats_menuCloudBackgroundCats, 0xff427ba9); + defaultColors.put(key_chats_actionIcon, 0xffffffff); + defaultColors.put(key_chats_actionBackground, 0xff6aa1ce); + defaultColors.put(key_chats_actionPressedBackground, 0xff5792c2); + + defaultColors.put(key_chat_lockIcon, 0xffffffff); + defaultColors.put(key_chat_muteIcon, 0xffb1cce3); + defaultColors.put(key_chat_inBubble, 0xffffffff); + defaultColors.put(key_chat_inBubbleSelected, 0xffe2f8ff); + defaultColors.put(key_chat_inBubbleShadow, 0xff1d3753); + defaultColors.put(key_chat_outBubble, 0xffefffde); + defaultColors.put(key_chat_outBubbleSelected, 0xffd4f5bc); + defaultColors.put(key_chat_outBubbleShadow, 0xff1e750c); + defaultColors.put(key_chat_messageTextIn, 0xff000000); + defaultColors.put(key_chat_messageTextOut, 0xff000000); + defaultColors.put(key_chat_messageLinkIn, 0xff2678b6); + defaultColors.put(key_chat_messageLinkOut, 0xff2678b6); + defaultColors.put(key_chat_serviceText, 0xffffffff); + defaultColors.put(key_chat_serviceLink, 0xffffffff); + defaultColors.put(key_chat_serviceIcon, 0xffffffff); + defaultColors.put(key_chat_outSentCheck, 0xff5db050); + defaultColors.put(key_chat_outSentCheckSelected, 0xff5db050); + defaultColors.put(key_chat_outSentClock, 0xff75bd5e); + defaultColors.put(key_chat_outSentClockSelected, 0xff75bd5e); + defaultColors.put(key_chat_inSentClock, 0xffa1aab3); + defaultColors.put(key_chat_inSentClockSelected, 0xff93bdca); + defaultColors.put(key_chat_mediaSentCheck, 0xffffffff); + defaultColors.put(key_chat_mediaSentClock, 0xffffffff); + defaultColors.put(key_chat_inViews, 0xffa1aab3); + defaultColors.put(key_chat_inViewsSelected, 0xff93bdca); + defaultColors.put(key_chat_outViews, 0xff6eb257); + defaultColors.put(key_chat_outViewsSelected, 0xff6eb257); + defaultColors.put(key_chat_mediaViews, 0xffffffff); + defaultColors.put(key_chat_inMenu, 0xffb6bdc5); + defaultColors.put(key_chat_inMenuSelected, 0xff98c1ce); + defaultColors.put(key_chat_outMenu, 0xff91ce7e); + defaultColors.put(key_chat_outMenuSelected, 0xff91ce7e); + defaultColors.put(key_chat_mediaMenu, 0xffffffff); + defaultColors.put(key_chat_outInstant, 0xff55ab4f); + defaultColors.put(key_chat_outInstantSelected, 0xff489943); + defaultColors.put(key_chat_inInstant, 0xff3a8ccf); + defaultColors.put(key_chat_inInstantSelected, 0xff3079b5); + defaultColors.put(key_chat_sentError, 0xffdb3535); + defaultColors.put(key_chat_sentErrorIcon, 0xffffffff); + defaultColors.put(key_chat_selectedBackground, 0x6633b5e5); + defaultColors.put(key_chat_previewDurationText, 0xffffffff); + defaultColors.put(key_chat_previewGameText, 0xffffffff); + defaultColors.put(key_chat_inPreviewInstantText, 0xff3a8ccf); + defaultColors.put(key_chat_outPreviewInstantText, 0xff55ab4f); + defaultColors.put(key_chat_inPreviewInstantSelectedText, 0xff3079b5); + defaultColors.put(key_chat_outPreviewInstantSelectedText, 0xff489943); + defaultColors.put(key_chat_secretTimeText, 0xffe4e2e0); + defaultColors.put(key_chat_stickerNameText, 0xffffffff); + defaultColors.put(key_chat_botButtonText, 0xffffffff); + defaultColors.put(key_chat_botProgress, 0xffffffff); + defaultColors.put(key_chat_inForwardedNameText, 0xff3886c7); + defaultColors.put(key_chat_outForwardedNameText, 0xff55ab4f); + defaultColors.put(key_chat_inViaBotNameText, 0xff3a8ccf); + defaultColors.put(key_chat_outViaBotNameText, 0xff55ab4f); + defaultColors.put(key_chat_stickerViaBotNameText, 0xffffffff); + defaultColors.put(key_chat_inReplyLine, 0xff70b4e8); + defaultColors.put(key_chat_outReplyLine, 0xff88c97b); + defaultColors.put(key_chat_stickerReplyLine, 0xffffffff); + defaultColors.put(key_chat_inReplyNameText, 0xff3a8ccf); + defaultColors.put(key_chat_outReplyNameText, 0xff55ab4f); + defaultColors.put(key_chat_stickerReplyNameText, 0xffffffff); + defaultColors.put(key_chat_inReplyMessageText, 0xff000000); + defaultColors.put(key_chat_outReplyMessageText, 0xff000000); + defaultColors.put(key_chat_inReplyMediaMessageText, 0xffa1aab3); + defaultColors.put(key_chat_outReplyMediaMessageText, 0xff65b05b); + defaultColors.put(key_chat_inReplyMediaMessageSelectedText, 0xff89b4c1); + defaultColors.put(key_chat_outReplyMediaMessageSelectedText, 0xff65b05b); + defaultColors.put(key_chat_stickerReplyMessageText, 0xffffffff); + defaultColors.put(key_chat_inPreviewLine, 0xff70b4e8); + defaultColors.put(key_chat_outPreviewLine, 0xff88c97b); + defaultColors.put(key_chat_inSiteNameText, 0xff3a8ccf); + defaultColors.put(key_chat_outSiteNameText, 0xff55ab4f); + defaultColors.put(key_chat_inContactNameText, 0xff4e9ad4); + defaultColors.put(key_chat_outContactNameText, 0xff55ab4f); + defaultColors.put(key_chat_inContactPhoneText, 0xff2f3438); + defaultColors.put(key_chat_outContactPhoneText, 0xff354234); + defaultColors.put(key_chat_mediaProgress, 0xffffffff); + defaultColors.put(key_chat_inAudioProgress, 0xffffffff); + defaultColors.put(key_chat_outAudioProgress, 0xffefffde); + defaultColors.put(key_chat_inAudioSelectedProgress, 0xffe2f8ff); + defaultColors.put(key_chat_outAudioSelectedProgress, 0xffd4f5bc); + defaultColors.put(key_chat_mediaTimeText, 0xffffffff); + defaultColors.put(key_chat_inTimeText, 0xffa1aab3); + defaultColors.put(key_chat_outTimeText, 0xff70b15c); + defaultColors.put(key_chat_inTimeSelectedText, 0xff89b4c1); + defaultColors.put(key_chat_outTimeSelectedText, 0xff70b15c); + defaultColors.put(key_chat_inAudioPerfomerText, 0xff2f3438); + defaultColors.put(key_chat_outAudioPerfomerText, 0xff354234); + defaultColors.put(key_chat_inAudioTitleText, 0xff4e9ad4); + defaultColors.put(key_chat_outAudioTitleText, 0xff55ab4f); + defaultColors.put(key_chat_inAudioDurationText, 0xffa1aab3); + defaultColors.put(key_chat_outAudioDurationText, 0xff65b05b); + defaultColors.put(key_chat_inAudioDurationSelectedText, 0xff89b4c1); + defaultColors.put(key_chat_outAudioDurationSelectedText, 0xff65b05b); + defaultColors.put(key_chat_inAudioSeekbar, 0xffe4eaf0); + defaultColors.put(key_chat_outAudioSeekbar, 0xffbbe3ac); + defaultColors.put(key_chat_inAudioSeekbarSelected, 0xffbcdee8); + defaultColors.put(key_chat_outAudioSeekbarSelected, 0xffa9dd96); + defaultColors.put(key_chat_inAudioSeekbarFill, 0xff72b5e8); + defaultColors.put(key_chat_outAudioSeekbarFill, 0xff78c272); + defaultColors.put(key_chat_inVoiceSeekbar, 0xffdee5eb); + defaultColors.put(key_chat_outVoiceSeekbar, 0xffbbe3ac); + defaultColors.put(key_chat_inVoiceSeekbarSelected, 0xffbcdee8); + defaultColors.put(key_chat_outVoiceSeekbarSelected, 0xffa9dd96); + defaultColors.put(key_chat_inVoiceSeekbarFill, 0xff72b5e8); + defaultColors.put(key_chat_outVoiceSeekbarFill, 0xff78c272); + defaultColors.put(key_chat_inFileProgress, 0xffebf0f5); + defaultColors.put(key_chat_outFileProgress, 0xffdaf5c3); + defaultColors.put(key_chat_inFileProgressSelected, 0xffcbeaf6); + defaultColors.put(key_chat_outFileProgressSelected, 0xffc5eca7); + defaultColors.put(key_chat_inFileNameText, 0xff4e9ad4); + defaultColors.put(key_chat_outFileNameText, 0xff55ab4f); + defaultColors.put(key_chat_inFileInfoText, 0xffa1aab3); + defaultColors.put(key_chat_outFileInfoText, 0xff65b05b); + defaultColors.put(key_chat_inFileInfoSelectedText, 0xff89b4c1); + defaultColors.put(key_chat_outFileInfoSelectedText, 0xff65b05b); + defaultColors.put(key_chat_inFileBackground, 0xffebf0f5); + defaultColors.put(key_chat_outFileBackground, 0xffdaf5c3); + defaultColors.put(key_chat_inFileBackgroundSelected, 0xffcbeaf6); + defaultColors.put(key_chat_outFileBackgroundSelected, 0xffc5eca7); + defaultColors.put(key_chat_inVenueNameText, 0xff4e9ad4); + defaultColors.put(key_chat_outVenueNameText, 0xff55ab4f); + defaultColors.put(key_chat_inVenueInfoText, 0xffa1aab3); + defaultColors.put(key_chat_outVenueInfoText, 0xff65b05b); + defaultColors.put(key_chat_inVenueInfoSelectedText, 0xff89b4c1); + defaultColors.put(key_chat_outVenueInfoSelectedText, 0xff65b05b); + defaultColors.put(key_chat_mediaInfoText, 0xffffffff); + defaultColors.put(key_chat_linkSelectBackground, 0x3362a9e3); + defaultColors.put(key_chat_textSelectBackground, 0x6662a9e3); + defaultColors.put(key_chat_emojiPanelBackground, 0xfff5f6f7); + defaultColors.put(key_chat_emojiPanelShadowLine, 0xffe2e5e7); + defaultColors.put(key_chat_emojiPanelEmptyText, 0xff888888); + defaultColors.put(key_chat_emojiPanelIcon, 0xffa8a8a8); + defaultColors.put(key_chat_emojiPanelIconSelected, 0xff2b96e2); + defaultColors.put(key_chat_emojiPanelStickerPackSelector, 0xffe2e5e7); + defaultColors.put(key_chat_emojiPanelIconSelector, 0xff2b96e2); + defaultColors.put(key_chat_emojiPanelBackspace, 0xffa8a8a8); + defaultColors.put(key_chat_emojiPanelMasksIcon, 0xffffffff); + defaultColors.put(key_chat_emojiPanelMasksIconSelected, 0xff62bfe8); + defaultColors.put(key_chat_emojiPanelTrendingTitle, 0xff212121); + defaultColors.put(key_chat_emojiPanelTrendingDescription, 0xff8a8a8a); + defaultColors.put(key_chat_botKeyboardButtonText, 0xff36474f); + defaultColors.put(key_chat_botKeyboardButtonBackground, 0xffe4e7e9); + defaultColors.put(key_chat_botKeyboardButtonBackgroundPressed, 0xffccd1d4); + defaultColors.put(key_chat_unreadMessagesStartArrowIcon, 0xffa2b5c7); + defaultColors.put(key_chat_unreadMessagesStartText, 0xff5695cc); + defaultColors.put(key_chat_unreadMessagesStartBackground, 0xffffffff); + defaultColors.put(key_chat_editDoneIcon, 0xff51bdf3); + defaultColors.put(key_chat_inFileIcon, 0xffa2b5c7); + defaultColors.put(key_chat_inFileSelectedIcon, 0xff87b6c5); + defaultColors.put(key_chat_outFileIcon, 0xff85bf78); + defaultColors.put(key_chat_outFileSelectedIcon, 0xff85bf78); + defaultColors.put(key_chat_inLocationBackground, 0xffebf0f5); + defaultColors.put(key_chat_inLocationIcon, 0xffa2b5c7); + defaultColors.put(key_chat_outLocationBackground, 0xffdaf5c3); + defaultColors.put(key_chat_outLocationIcon, 0xff87bf78); + defaultColors.put(key_chat_inContactBackground, 0xff72b5e8); + defaultColors.put(key_chat_inContactIcon, 0xffffffff); + defaultColors.put(key_chat_outContactBackground, 0xff78c272); + defaultColors.put(key_chat_outContactIcon, 0xffefffde); + defaultColors.put(key_chat_outBroadcast, 0xff46aa36); + defaultColors.put(key_chat_mediaBroadcast, 0xffffffff); + defaultColors.put(key_chat_searchPanelIcons, 0xff5da5dc); + defaultColors.put(key_chat_searchPanelText, 0xff4e9ad4); + defaultColors.put(key_chat_secretChatStatusText, 0xff7f7f7f); + defaultColors.put(key_chat_fieldOverlayText, 0xff3a8ccf); + defaultColors.put(key_chat_stickersHintPanel, 0xffffffff); + defaultColors.put(key_chat_replyPanelIcons, 0xff57a8e6); + defaultColors.put(key_chat_replyPanelClose, 0xffa8a8a8); + defaultColors.put(key_chat_replyPanelName, 0xff3a8ccf); + defaultColors.put(key_chat_replyPanelMessage, 0xff222222); + defaultColors.put(key_chat_replyPanelLine, 0xffe8e8e8); + defaultColors.put(key_chat_messagePanelBackground, 0xffffffff); + defaultColors.put(key_chat_messagePanelText, 0xff000000); + defaultColors.put(key_chat_messagePanelHint, 0xffb2b2b2); + defaultColors.put(key_chat_messagePanelShadow, 0xff000000); + defaultColors.put(key_chat_messagePanelIcons, 0xffa8a8a8); + defaultColors.put(key_chat_recordedVoicePlayPause, 0xffffffff); + defaultColors.put(key_chat_recordedVoicePlayPausePressed, 0xffd9eafb); + defaultColors.put(key_chat_recordedVoiceDot, 0xffda564d); + defaultColors.put(key_chat_recordedVoiceBackground, 0xff559ee3); + defaultColors.put(key_chat_recordedVoiceProgress, 0xffa2cef8); + defaultColors.put(key_chat_recordedVoiceProgressInner, 0xffffffff); + defaultColors.put(key_chat_recordVoiceCancel, 0xff999999); + defaultColors.put(key_chat_messagePanelSend, 0xff62b0eb); + defaultColors.put(key_chat_recordTime, 0xff4d4c4b); + defaultColors.put(key_chat_emojiPanelNewTrending, 0xff4da6ea); + defaultColors.put(key_chat_gifSaveHintText, 0xffffffff); + defaultColors.put(key_chat_gifSaveHintBackground, 0xcc111111); + defaultColors.put(key_chat_goDownButton, 0xffffffff); + defaultColors.put(key_chat_goDownButtonShadow, 0xff000000); + defaultColors.put(key_chat_goDownButtonIcon, 0xffa8a8a8); + defaultColors.put(key_chat_goDownButtonCounter, 0xffffffff); + defaultColors.put(key_chat_goDownButtonCounterBackground, 0xff4da2e8); + defaultColors.put(key_chat_messagePanelCancelInlineBot, 0xffadadad); + defaultColors.put(key_chat_messagePanelVoicePressed, 0xffffffff); + defaultColors.put(key_chat_messagePanelVoiceBackground, 0xff5795cc); + defaultColors.put(key_chat_messagePanelVoiceShadow, 0x0d000000); + defaultColors.put(key_chat_messagePanelVoiceDelete, 0xff737373); + defaultColors.put(key_chat_messagePanelVoiceDuration, 0xffffffff); + defaultColors.put(key_chat_inlineResultIcon, 0xff5795cc); + defaultColors.put(key_chat_topPanelBackground, 0xffffffff); + defaultColors.put(key_chat_topPanelClose, 0xffa8a8a8); + defaultColors.put(key_chat_topPanelLine, 0xff6c9fd2); + defaultColors.put(key_chat_topPanelTitle, 0xff3a8ccf); + defaultColors.put(key_chat_topPanelMessage, 0xff999999); + defaultColors.put(key_chat_reportSpam, 0xffcf5957); + defaultColors.put(key_chat_addContact, 0xff4a82b5); + defaultColors.put(key_chat_inLoader, 0xff72b5e8); + defaultColors.put(key_chat_inLoaderSelected, 0xff65abe0); + defaultColors.put(key_chat_outLoader, 0xff78c272); + defaultColors.put(key_chat_outLoaderSelected, 0xff6ab564); + defaultColors.put(key_chat_inLoaderPhoto, 0xffa2b8c8); + defaultColors.put(key_chat_inLoaderPhotoSelected, 0xffa2b5c7); + defaultColors.put(key_chat_inLoaderPhotoIcon, 0xfffcfcfc); + defaultColors.put(key_chat_inLoaderPhotoIconSelected, 0xffebf0f5); + defaultColors.put(key_chat_outLoaderPhoto, 0xff85bf78); + defaultColors.put(key_chat_outLoaderPhotoSelected, 0xff7db870); + defaultColors.put(key_chat_outLoaderPhotoIcon, 0xffdaf5c3); + defaultColors.put(key_chat_outLoaderPhotoIconSelected, 0xffc0e8a4); + defaultColors.put(key_chat_mediaLoaderPhoto, 0x66000000); + defaultColors.put(key_chat_mediaLoaderPhotoSelected, 0x7f000000); + defaultColors.put(key_chat_mediaLoaderPhotoIcon, 0xffffffff); + defaultColors.put(key_chat_mediaLoaderPhotoIconSelected, 0xffd9d9d9); + defaultColors.put(key_chat_secretTimerBackground, 0xcc3e648e); + defaultColors.put(key_chat_secretTimerText, 0xffffffff); + + defaultColors.put(key_profile_creatorIcon, 0xff4a97d6); + defaultColors.put(key_profile_adminIcon, 0xff858585); + defaultColors.put(key_profile_actionIcon, 0xff737373); + defaultColors.put(key_profile_actionBackground, 0xffffffff); + defaultColors.put(key_profile_actionPressedBackground, 0xfff2f2f2); + defaultColors.put(key_profile_verifiedBackground, 0xffb2d6f8); + defaultColors.put(key_profile_verifiedCheck, 0xff4983b8); + defaultColors.put(key_profile_title, 0xffffffff); + + defaultColors.put(key_player_actionBar, 0xffffffff); + defaultColors.put(key_player_actionBarSelector, 0x2f000000); + defaultColors.put(key_player_actionBarTitle, 0xff212121); + defaultColors.put(key_player_actionBarTop, 0x99000000); + defaultColors.put(key_player_actionBarSubtitle, 0xff8a8a8a); + defaultColors.put(key_player_actionBarItems, 0xff8a8a8a); + defaultColors.put(key_player_seekBarBackground, 0xe5ffffff); + defaultColors.put(key_player_time, 0xff19a7e8); + defaultColors.put(key_player_duration, 0xff8a8a8a); + defaultColors.put(key_player_progressBackground, 0x19000000); + defaultColors.put(key_player_progress, 0xff23afef); + defaultColors.put(key_player_placeholder, 0xffd9d9d9); + defaultColors.put(key_player_button, 0xff8a8a8a); + defaultColors.put(key_player_buttonActive, 0xff23afef); + + defaultColors.put(key_files_folderIcon, 0xff999999); + defaultColors.put(key_files_folderIconBackground, 0xfff0f0f0); + defaultColors.put(key_files_iconText, 0xffffffff); + + defaultColors.put(key_sessions_devicesImage, 0xff969696); + + defaultColors.put(key_location_markerX, 0xff808080); + defaultColors.put(key_location_sendLocationBackground, 0xff6da0d4); + defaultColors.put(key_location_sendLocationIcon, 0xffffffff); + + defaultColors.put(key_calls_callReceivedGreenIcon, 0xff00c853); + defaultColors.put(key_calls_callReceivedRedIcon, 0xffff4848); + + defaultColors.put(key_featuredStickers_addedIcon, 0xff50a8eb); + defaultColors.put(key_featuredStickers_buttonProgress, 0xffffffff); + defaultColors.put(key_featuredStickers_addButton, 0xff50a8eb); + defaultColors.put(key_featuredStickers_addButtonPressed, 0xff439bde); + defaultColors.put(key_featuredStickers_delButton, 0xffd95757); + defaultColors.put(key_featuredStickers_delButtonPressed, 0xffc64949); + defaultColors.put(key_featuredStickers_buttonText, 0xffffffff); + defaultColors.put(key_featuredStickers_unread, 0xff4da6ea); + + defaultColors.put(key_inappPlayerPerformer, 0xff2f3438); + defaultColors.put(key_inappPlayerTitle, 0xff2f3438); + defaultColors.put(key_inappPlayerBackground, 0xffffffff); + defaultColors.put(key_inappPlayerPlayPause, 0xff62b0eb); + defaultColors.put(key_inappPlayerClose, 0xffa8a8a8); + + defaultColors.put(key_returnToCallBackground, 0xff44a1e3); + defaultColors.put(key_returnToCallText, 0xffffffff); + + defaultColors.put(key_sharedMedia_startStopLoadIcon, 0xff36a2ee); + defaultColors.put(key_sharedMedia_linkPlaceholder, 0xfff0f0f0); + defaultColors.put(key_sharedMedia_linkPlaceholderText, 0xffffffff); + defaultColors.put(key_checkbox, 0xff5ec245); + defaultColors.put(key_checkboxCheck, 0xffffffff); + + defaultColors.put(key_stickers_menu, 0xffb6bdc5); + defaultColors.put(key_stickers_menuSelector, 0x2f000000); + + defaultColors.put(key_changephoneinfo_image, 0xffa8a8a8); + + defaultColors.put(key_groupcreate_hintText, 0xffa1aab3); + defaultColors.put(key_groupcreate_cursor, 0xff52a3db); + defaultColors.put(key_groupcreate_sectionShadow, 0xff000000); + defaultColors.put(key_groupcreate_sectionText, 0xff7c8288); + defaultColors.put(key_groupcreate_onlineText, 0xff4092cd); + defaultColors.put(key_groupcreate_offlineText, 0xff838c96); + defaultColors.put(key_groupcreate_checkbox, 0xff5ec245); + defaultColors.put(key_groupcreate_checkboxCheck, 0xffffffff); + defaultColors.put(key_groupcreate_spanText, 0xff212121); + defaultColors.put(key_groupcreate_spanBackground, 0xfff2f2f2); + + defaultColors.put(key_login_progressInner, 0xffe1eaf2); + defaultColors.put(key_login_progressOuter, 0xff62a0d0); + + defaultColors.put(key_musicPicker_checkbox, 0xff29b6f7); + defaultColors.put(key_musicPicker_checkboxCheck, 0xffffffff); + defaultColors.put(key_musicPicker_buttonBackground, 0xff5cafea); + defaultColors.put(key_musicPicker_buttonIcon, 0xffffffff); + defaultColors.put(key_picker_enabledButton, 0xff19a7e8); + defaultColors.put(key_picker_disabledButton, 0xff999999); + defaultColors.put(key_picker_badge, 0xff29b6f7); + defaultColors.put(key_picker_badgeText, 0xffffffff); + + defaultColors.put(key_chat_botSwitchToInlineText, 0xff4391cc); + + defaultColors.put(key_calls_ratingStar, 0x80000000); + defaultColors.put(key_calls_ratingStarSelected, 0xFF4a97d6); + + themes = new ArrayList<>(); + otherThemes = new ArrayList<>(); + themesDict = new HashMap<>(); + currentColors = new HashMap<>(); + + ThemeInfo themeInfo = new ThemeInfo(); + themeInfo.name = LocaleController.getString("Default", R.string.Default); + themes.add(currentTheme = defaultTheme = themeInfo); + themesDict.put("Default", defaultTheme); + + themeInfo = new ThemeInfo(); + themeInfo.name = "Dark"; + themeInfo.assetName = "dark.attheme"; + themes.add(themeInfo); + themesDict.put("Dark", themeInfo); + + themeInfo = new ThemeInfo(); + themeInfo.name = "Blue"; + themeInfo.assetName = "bluebubbles.attheme"; + themes.add(themeInfo); + themesDict.put("Blue", themeInfo); + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("themeconfig", Activity.MODE_PRIVATE); + String themesString = preferences.getString("themes2", null); + if (!TextUtils.isEmpty(themesString)) { + try { + JSONArray jsonArray = new JSONArray(themesString); + for (int a = 0; a < jsonArray.length(); a++) { + themeInfo = ThemeInfo.createWithJson(jsonArray.getJSONObject(a)); + if (themeInfo != null) { + otherThemes.add(themeInfo); + themes.add(themeInfo); + themesDict.put(themeInfo.name, themeInfo); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } else { + themesString = preferences.getString("themes", null); + if (!TextUtils.isEmpty(themesString)) { + String[] themesArr = themesString.split("&"); + for (int a = 0; a < themesArr.length; a++) { + themeInfo = ThemeInfo.createWithString(themesArr[a]); + if (themeInfo != null) { + otherThemes.add(themeInfo); + themes.add(themeInfo); + themesDict.put(themeInfo.name, themeInfo); + } + } + } + saveOtherThemes(); + preferences.edit().remove("themes").commit(); + } + + sortThemes(); + + ThemeInfo applyingTheme = null; + try { + preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + String theme = preferences.getString("theme", null); + if (theme != null) { + applyingTheme = themesDict.get(theme); } - timeStickerBackgroundDrawable.setColorFilter(colorFilter); + } catch (Exception e) { + FileLog.e(e); + } + if (applyingTheme == null) { + applyingTheme = defaultTheme; } + applyTheme(applyingTheme, false, false); } - public static void loadChatResources(Context context) { - if (attachButtonDrawables[0] == null) { - attachButtonDrawables[0] = context.getResources().getDrawable(R.drawable.attach_camera_states); - attachButtonDrawables[1] = context.getResources().getDrawable(R.drawable.attach_gallery_states); - attachButtonDrawables[2] = context.getResources().getDrawable(R.drawable.attach_video_states); - attachButtonDrawables[3] = context.getResources().getDrawable(R.drawable.attach_audio_states); - attachButtonDrawables[4] = context.getResources().getDrawable(R.drawable.attach_file_states); - attachButtonDrawables[5] = context.getResources().getDrawable(R.drawable.attach_contact_states); - attachButtonDrawables[6] = context.getResources().getDrawable(R.drawable.attach_location_states); - attachButtonDrawables[7] = context.getResources().getDrawable(R.drawable.attach_hide_states); + private static Method StateListDrawable_getStateDrawableMethod; + private static Field BitmapDrawable_mColorFilter; + + private static Drawable getStateDrawable(Drawable drawable, int index) { + if (StateListDrawable_getStateDrawableMethod == null) { + try { + StateListDrawable_getStateDrawableMethod = StateListDrawable.class.getDeclaredMethod("getStateDrawable", int.class); + } catch (Throwable ignore) { + + } + } + if (StateListDrawable_getStateDrawableMethod == null) { + return null; + } + try { + return (Drawable) StateListDrawable_getStateDrawableMethod.invoke(drawable, index); + } catch (Exception ignore) { + } + return null; } - public static Drawable createBarSelectorDrawable(int color) { - return createBarSelectorDrawable(color, true); + public static Drawable createEmojiIconSelectorDrawable(Context context, int resource, int defaultColor, int pressedColor) { + Resources resources = context.getResources(); + Drawable defaultDrawable = resources.getDrawable(resource).mutate(); + if (defaultColor != 0) { + defaultDrawable.setColorFilter(new PorterDuffColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY)); + } + Drawable pressedDrawable = resources.getDrawable(resource).mutate(); + if (pressedColor != 0) { + pressedDrawable.setColorFilter(new PorterDuffColorFilter(pressedColor, PorterDuff.Mode.MULTIPLY)); + } + StateListDrawable stateListDrawable = new StateListDrawable() { + @Override + public boolean selectDrawable(int index) { + if (Build.VERSION.SDK_INT < 21) { + Drawable drawable = getStateDrawable(this, index); + ColorFilter colorFilter = null; + if (drawable instanceof BitmapDrawable) { + colorFilter = ((BitmapDrawable) drawable).getPaint().getColorFilter(); + } else if (drawable instanceof NinePatchDrawable) { + colorFilter = ((NinePatchDrawable) drawable).getPaint().getColorFilter(); + } + boolean result = super.selectDrawable(index); + if (colorFilter != null) { + drawable.setColorFilter(colorFilter); + } + return result; + } + return super.selectDrawable(index); + } + }; + stateListDrawable.setEnterFadeDuration(1); + stateListDrawable.setExitFadeDuration(200); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, pressedDrawable); + stateListDrawable.addState(new int[]{}, defaultDrawable); + return stateListDrawable; + } + + public static Drawable createEditTextDrawable(Context context, boolean alert) { + Resources resources = context.getResources(); + Drawable defaultDrawable = resources.getDrawable(R.drawable.search_dark).mutate(); + defaultDrawable.setColorFilter(new PorterDuffColorFilter(getColor(alert ? Theme.key_dialogInputField : Theme.key_windowBackgroundWhiteInputField), PorterDuff.Mode.MULTIPLY)); + Drawable pressedDrawable = resources.getDrawable(R.drawable.search_dark_activated).mutate(); + pressedDrawable.setColorFilter(new PorterDuffColorFilter(getColor(alert ? Theme.key_dialogInputFieldActivated : Theme.key_windowBackgroundWhiteInputFieldActivated), PorterDuff.Mode.MULTIPLY)); + StateListDrawable stateListDrawable = new StateListDrawable() { + @Override + public boolean selectDrawable(int index) { + if (Build.VERSION.SDK_INT < 21) { + Drawable drawable = getStateDrawable(this, index); + ColorFilter colorFilter = null; + if (drawable instanceof BitmapDrawable) { + colorFilter = ((BitmapDrawable) drawable).getPaint().getColorFilter(); + } else if (drawable instanceof NinePatchDrawable) { + colorFilter = ((NinePatchDrawable) drawable).getPaint().getColorFilter(); + } + boolean result = super.selectDrawable(index); + if (colorFilter != null) { + drawable.setColorFilter(colorFilter); + } + return result; + } + return super.selectDrawable(index); + } + }; + stateListDrawable.addState(new int[]{android.R.attr.state_enabled, android.R.attr.state_focused}, pressedDrawable); + stateListDrawable.addState(new int[]{android.R.attr.state_focused}, pressedDrawable); + stateListDrawable.addState(StateSet.WILD_CARD, defaultDrawable); + return stateListDrawable; + } + + public static Drawable createSimpleSelectorDrawable(Context context, int resource, int defaultColor, int pressedColor) { + Resources resources = context.getResources(); + Drawable defaultDrawable = resources.getDrawable(resource).mutate(); + if (defaultColor != 0) { + defaultDrawable.setColorFilter(new PorterDuffColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY)); + } + Drawable pressedDrawable = resources.getDrawable(resource).mutate(); + if (pressedColor != 0) { + pressedDrawable.setColorFilter(new PorterDuffColorFilter(pressedColor, PorterDuff.Mode.MULTIPLY)); + } + StateListDrawable stateListDrawable = new StateListDrawable() { + @Override + public boolean selectDrawable(int index) { + if (Build.VERSION.SDK_INT < 21) { + Drawable drawable = getStateDrawable(this, index); + ColorFilter colorFilter = null; + if (drawable instanceof BitmapDrawable) { + colorFilter = ((BitmapDrawable) drawable).getPaint().getColorFilter(); + } else if (drawable instanceof NinePatchDrawable) { + colorFilter = ((NinePatchDrawable) drawable).getPaint().getColorFilter(); + } + boolean result = super.selectDrawable(index); + if (colorFilter != null) { + drawable.setColorFilter(colorFilter); + } + return result; + } + return super.selectDrawable(index); + } + }; + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, pressedDrawable); + stateListDrawable.addState(StateSet.WILD_CARD, defaultDrawable); + return stateListDrawable; + } + + public static Drawable createCircleDrawable(int size, int color) { + OvalShape ovalShape = new OvalShape(); + ovalShape.resize(size, size); + ShapeDrawable defaultDrawable = new ShapeDrawable(ovalShape); + defaultDrawable.getPaint().setColor(color); + return defaultDrawable; + } + + public static Drawable createCircleDrawableWithIcon(int size, int iconRes) { + OvalShape ovalShape = new OvalShape(); + ovalShape.resize(size, size); + ShapeDrawable defaultDrawable = new ShapeDrawable(ovalShape); + defaultDrawable.getPaint().setColor(0xffffffff); + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(iconRes).mutate(); + CombinedDrawable combinedDrawable = new CombinedDrawable(defaultDrawable, drawable); + combinedDrawable.setCustomSize(size, size); + return combinedDrawable; } - public static Drawable createBarSelectorDrawable(int color, boolean masked) { + public static Drawable createRoundRectDrawableWithIcon(int rad, int iconRes) { + ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null)); + defaultDrawable.getPaint().setColor(0xffffffff); + Drawable drawable = ApplicationLoader.applicationContext.getResources().getDrawable(iconRes).mutate(); + return new CombinedDrawable(defaultDrawable, drawable); + } + + public static void setCombinedDrawableColor(Drawable combinedDrawable, int color, boolean isIcon) { + if (!(combinedDrawable instanceof CombinedDrawable)) { + return; + } + Drawable drawable; + if (isIcon) { + drawable = ((CombinedDrawable) combinedDrawable).getIcon(); + } else { + drawable = ((CombinedDrawable) combinedDrawable).getBackground(); + } + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + + public static Drawable createSimpleSelectorCircleDrawable(int size, int defaultColor, int pressedColor) { + OvalShape ovalShape = new OvalShape(); + ovalShape.resize(size, size); + ShapeDrawable defaultDrawable = new ShapeDrawable(ovalShape); + defaultDrawable.getPaint().setColor(defaultColor); + ShapeDrawable pressedDrawable = new ShapeDrawable(ovalShape); + if (Build.VERSION.SDK_INT >= 21) { + pressedDrawable.getPaint().setColor(0xffffffff); + ColorStateList colorStateList = new ColorStateList( + new int[][]{StateSet.WILD_CARD}, + new int[]{pressedColor} + ); + return new RippleDrawable(colorStateList, defaultDrawable, pressedDrawable); + } else { + pressedDrawable.getPaint().setColor(pressedColor); + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); + stateListDrawable.addState(new int[]{android.R.attr.state_focused}, pressedDrawable); + stateListDrawable.addState(StateSet.WILD_CARD, defaultDrawable); + return stateListDrawable; + } + } + + public static Drawable createRoundRectDrawable(int rad, int defaultColor) { + ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null)); + defaultDrawable.getPaint().setColor(defaultColor); + return defaultDrawable; + } + + public static Drawable createSimpleSelectorRoundRectDrawable(int rad, int defaultColor, int pressedColor) { + ShapeDrawable defaultDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null)); + defaultDrawable.getPaint().setColor(defaultColor); + ShapeDrawable pressedDrawable = new ShapeDrawable(new RoundRectShape(new float[]{rad, rad, rad, rad, rad, rad, rad, rad}, null, null)); + pressedDrawable.getPaint().setColor(pressedColor); + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, pressedDrawable); + stateListDrawable.addState(StateSet.WILD_CARD, defaultDrawable); + return stateListDrawable; + } + + public static Drawable getRoundRectSelectorDrawable() { + if (Build.VERSION.SDK_INT >= 21) { + Drawable maskDrawable = createRoundRectDrawable(AndroidUtilities.dp(3), 0xffffffff); + ColorStateList colorStateList = new ColorStateList( + new int[][]{StateSet.WILD_CARD}, + new int[]{getColor(key_dialogButtonSelector)} + ); + return new RippleDrawable(colorStateList, null, maskDrawable); + } else { + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, createRoundRectDrawable(AndroidUtilities.dp(3), getColor(key_dialogButtonSelector))); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, createRoundRectDrawable(AndroidUtilities.dp(3), getColor(key_dialogButtonSelector))); + stateListDrawable.addState(StateSet.WILD_CARD, new ColorDrawable(0x00000000)); + return stateListDrawable; + } + } + + public static Drawable getSelectorDrawable(boolean whiteBackground) { + if (whiteBackground) { + if (Build.VERSION.SDK_INT >= 21) { + Drawable maskDrawable = new ColorDrawable(0xffffffff); + ColorStateList colorStateList = new ColorStateList( + new int[][]{StateSet.WILD_CARD}, + new int[]{getColor(key_listSelector)} + ); + return new RippleDrawable(colorStateList, new ColorDrawable(getColor(key_windowBackgroundWhite)), maskDrawable); + } else { + int color = getColor(key_listSelector); + StateListDrawable stateListDrawable = new StateListDrawable(); + stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable(color)); + stateListDrawable.addState(new int[]{android.R.attr.state_selected}, new ColorDrawable(color)); + stateListDrawable.addState(StateSet.WILD_CARD, new ColorDrawable(getColor(key_windowBackgroundWhite))); + return stateListDrawable; + } + } else { + return createSelectorDrawable(getColor(key_listSelector), 2); + } + } + + public static Drawable createSelectorDrawable(int color) { + return createSelectorDrawable(color, 1); + } + + public static Drawable createSelectorDrawable(int color, int maskType) { Drawable drawable; if (Build.VERSION.SDK_INT >= 21) { Drawable maskDrawable = null; - if (masked) { + if (maskType == 1) { maskPaint.setColor(0xffffffff); maskDrawable = new Drawable() { @Override @@ -418,20 +1711,1211 @@ public int getOpacity() { return 0; } }; + } else if (maskType == 2) { + maskDrawable = new ColorDrawable(0xffffffff); } ColorStateList colorStateList = new ColorStateList( - new int[][]{new int[]{}}, + new int[][]{StateSet.WILD_CARD}, new int[]{color} ); return new RippleDrawable(colorStateList, null, maskDrawable); } else { StateListDrawable stateListDrawable = new StateListDrawable(); stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, new ColorDrawable(color)); - stateListDrawable.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable(color)); stateListDrawable.addState(new int[]{android.R.attr.state_selected}, new ColorDrawable(color)); - stateListDrawable.addState(new int[]{android.R.attr.state_activated}, new ColorDrawable(color)); - stateListDrawable.addState(new int[]{}, new ColorDrawable(0x00000000)); + stateListDrawable.addState(StateSet.WILD_CARD, new ColorDrawable(0x00000000)); return stateListDrawable; } } + + public static void applyPreviousTheme() { + if (previousTheme == null) { + return; + } + applyTheme(previousTheme, true, false); + previousTheme = null; + } + + private static void sortThemes() { + Collections.sort(themes, new Comparator() { + @Override + public int compare(ThemeInfo o1, ThemeInfo o2) { + if (o1.pathToFile == null && o1.assetName == null) { + return -1; + } else if (o2.pathToFile == null && o2.assetName == null) { + return 1; + } + return o1.name.compareTo(o2.name); + } + }); + } + + public static ThemeInfo applyThemeFile(File file, String themeName, boolean temporary) { + try { + if (themeName.equals("Default") || themeName.equals("Dark") || themeName.equals("Blue")) { + return null; + } + File finalFile = new File(ApplicationLoader.getFilesDirFixed(), themeName); + if (!AndroidUtilities.copyFile(file, finalFile)) { + return null; + } + + boolean newTheme = false; + ThemeInfo themeInfo = themesDict.get(themeName); + if (themeInfo == null) { + newTheme = true; + themeInfo = new ThemeInfo(); + themeInfo.name = themeName; + themeInfo.pathToFile = finalFile.getAbsolutePath(); + } + if (!temporary) { + if (newTheme) { + themes.add(themeInfo); + themesDict.put(themeInfo.name, themeInfo); + otherThemes.add(themeInfo); + sortThemes(); + saveOtherThemes(); + } + } else { + previousTheme = currentTheme; + } + + applyTheme(themeInfo, !temporary, true); + return themeInfo; + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + public static void applyTheme(ThemeInfo themeInfo) { + applyTheme(themeInfo, true, true); + } + + public static void applyTheme(ThemeInfo themeInfo, boolean save, boolean removeWallpaperOverride) { + if (themeInfo == null) { + return; + } + ThemeEditorView editorView = ThemeEditorView.getInstance(); + if (editorView != null) { + editorView.destroy(); + } + try { + if (themeInfo.pathToFile != null || themeInfo.assetName != null) { + if (save) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("theme", themeInfo.name); + if (removeWallpaperOverride) { + editor.remove("overrideThemeWallpaper"); + } + editor.commit(); + } + if (themeInfo.assetName != null) { + currentColors = getThemeFileValues(null, themeInfo.assetName); + } else { + currentColors = getThemeFileValues(new File(themeInfo.pathToFile), null); + } + } else { + if (save) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("theme"); + if (removeWallpaperOverride) { + editor.remove("overrideThemeWallpaper"); + } + editor.commit(); + } + currentColors.clear(); + wallpaper = null; + themedWallpaper = null; + } + currentTheme = themeInfo; + reloadWallpaper(); + applyCommonTheme(); + applyDialogsTheme(); + applyProfileTheme(); + applyChatTheme(false); + } catch (Exception e) { + FileLog.e(e); + } + } + + private static void saveOtherThemes() { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("themeconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + JSONArray array = new JSONArray(); + for (int a = 0; a < otherThemes.size(); a++) { + JSONObject jsonObject = otherThemes.get(a).getSaveJson(); + if (jsonObject != null) { + array.put(jsonObject); + } + } + editor.putString("themes2", array.toString()); + editor.commit(); + } + + public static HashMap getDefaultColors() { + return defaultColors; + } + + public static String getCurrentThemeName() { + String text = currentTheme.name; + if (text.endsWith(".attheme")) { + text = text.substring(0, text.lastIndexOf('.')); + } + return text; + } + + public static ThemeInfo getCurrentTheme() { + return currentTheme != null ? currentTheme : defaultTheme; + } + + public static boolean deleteTheme(ThemeInfo themeInfo) { + if (themeInfo.pathToFile == null) { + return false; + } + boolean currentThemeDeleted = false; + if (currentTheme == themeInfo) { + applyTheme(defaultTheme, true, false); + currentThemeDeleted = true; + } + + otherThemes.remove(themeInfo); + themesDict.remove(themeInfo.name); + themes.remove(themeInfo); + File file = new File(themeInfo.pathToFile); + file.delete(); + saveOtherThemes(); + return currentThemeDeleted; + } + + public static void saveCurrentTheme(String name, boolean finalSave) { + StringBuilder result = new StringBuilder(); + for (HashMap.Entry entry : currentColors.entrySet()) { + result.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); + } + File file = new File(ApplicationLoader.getFilesDirFixed(), name); + FileOutputStream stream = null; + try { + stream = new FileOutputStream(file); + stream.write(result.toString().getBytes()); + if (themedWallpaper instanceof BitmapDrawable) { + Bitmap bitmap = ((BitmapDrawable) themedWallpaper).getBitmap(); + if (bitmap != null) { + stream.write(new byte[]{'W', 'P', 'S', '\n'}); + bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); + stream.write(new byte[]{'\n', 'W', 'P', 'E', '\n'}); + } + if (finalSave) { + wallpaper = themedWallpaper; + calcBackgroundColor(wallpaper, 2); + } + } + ThemeInfo newTheme; + if ((newTheme = themesDict.get(name)) == null) { + newTheme = new ThemeInfo(); + newTheme.pathToFile = file.getAbsolutePath(); + newTheme.name = name; + themes.add(newTheme); + themesDict.put(newTheme.name, newTheme); + otherThemes.add(newTheme); + saveOtherThemes(); + sortThemes(); + } + currentTheme = newTheme; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putString("theme", currentTheme.name); + editor.commit(); + } catch (Exception e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e("tmessage", e); + } + } + } + + public static File getAssetFile(String assetName) { + File file = new File(ApplicationLoader.getFilesDirFixed(), assetName); + if (!file.exists()) { + InputStream in = null; + try { + in = ApplicationLoader.applicationContext.getAssets().open(assetName); + AndroidUtilities.copyFile(in, file); + } catch (Exception e) { + FileLog.e(e); + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception ignore) { + + } + } + } + } + return file; + } + + private static HashMap getThemeFileValues(File file, String assetName) { + FileInputStream stream = null; + HashMap stringMap = new HashMap<>(); + try { + byte[] bytes = new byte[1024]; + int currentPosition = 0; + if (assetName != null) { + file = getAssetFile(assetName); + } + stream = new FileInputStream(file); + int idx; + int read; + boolean finished = false; + themedWallpaperFileOffset = -1; + while ((read = stream.read(bytes)) != -1) { + int previousPosition = currentPosition; + int start = 0; + for (int a = 0; a < read; a++) { + if (bytes[a] == '\n') { + int len = a - start + 1; + String line = new String(bytes, start, len - 1, "UTF-8"); + if (line.startsWith("WPS")) { + themedWallpaperFileOffset = currentPosition + len; + finished = true; + break; + } else { + if ((idx = line.indexOf('=')) != -1) { + String key = line.substring(0, idx); + String param = line.substring(idx + 1); + int value; + if (param.length() > 0 && param.charAt(0) == '#') { + try { + value = Color.parseColor(param); + } catch (Exception ignore) { + value = Utilities.parseInt(param); + } + } else { + value = Utilities.parseInt(param); + } + stringMap.put(key, value); + } + } + start += len; + currentPosition += len; + } + } + if (previousPosition == currentPosition) { + break; + } + stream.getChannel().position(currentPosition); + if (finished) { + break; + } + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + return stringMap; + } + + public static void createCommonResources(Context context) { + if (dividerPaint == null) { + dividerPaint = new Paint(); + dividerPaint.setStrokeWidth(1); + + avatar_backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + checkboxSquare_checkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + checkboxSquare_checkPaint.setStyle(Paint.Style.STROKE); + checkboxSquare_checkPaint.setStrokeWidth(AndroidUtilities.dp(2)); + checkboxSquare_eraserPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + checkboxSquare_eraserPaint.setColor(0); + checkboxSquare_eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + checkboxSquare_backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + linkSelectionPaint = new Paint(); + + Resources resources = context.getResources(); + + avatar_broadcastDrawable = resources.getDrawable(R.drawable.broadcast_w); + avatar_photoDrawable = resources.getDrawable(R.drawable.photo_w); + + applyCommonTheme(); + } + } + + public static void applyCommonTheme() { + if (dividerPaint == null) { + return; + } + dividerPaint.setColor(getColor(key_divider)); + linkSelectionPaint.setColor(getColor(key_windowBackgroundWhiteLinkSelection)); + + setDrawableColorByKey(avatar_broadcastDrawable, key_avatar_text); + setDrawableColorByKey(avatar_photoDrawable, key_avatar_text); + } + + public static void createDialogsResources(Context context) { + createCommonResources(context); + if (dialogs_namePaint == null) { + Resources resources = context.getResources(); + + dialogs_namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + dialogs_nameEncryptedPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_nameEncryptedPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + dialogs_messagePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_messagePrintingPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_countTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_countTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + dialogs_onlinePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + dialogs_offlinePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + + dialogs_tabletSeletedPaint = new Paint(); + dialogs_pinnedPaint = new Paint(); + dialogs_countPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dialogs_countGrayPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dialogs_errorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + dialogs_lockDrawable = resources.getDrawable(R.drawable.list_secret); + dialogs_checkDrawable = resources.getDrawable(R.drawable.list_check); + dialogs_halfCheckDrawable = resources.getDrawable(R.drawable.list_halfcheck); + dialogs_clockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + dialogs_errorDrawable = resources.getDrawable(R.drawable.list_warning_sign); + dialogs_groupDrawable = resources.getDrawable(R.drawable.list_group); + dialogs_broadcastDrawable = resources.getDrawable(R.drawable.list_broadcast); + dialogs_muteDrawable = resources.getDrawable(R.drawable.list_mute).mutate(); + dialogs_verifiedDrawable = resources.getDrawable(R.drawable.verified_area); + dialogs_verifiedCheckDrawable = resources.getDrawable(R.drawable.verified_check); + dialogs_botDrawable = resources.getDrawable(R.drawable.list_bot); + dialogs_pinnedDrawable = resources.getDrawable(R.drawable.list_pin); + + applyDialogsTheme(); + } + + dialogs_namePaint.setTextSize(AndroidUtilities.dp(17)); + dialogs_nameEncryptedPaint.setTextSize(AndroidUtilities.dp(17)); + dialogs_messagePaint.setTextSize(AndroidUtilities.dp(16)); + dialogs_messagePrintingPaint.setTextSize(AndroidUtilities.dp(16)); + dialogs_timePaint.setTextSize(AndroidUtilities.dp(13)); + dialogs_countTextPaint.setTextSize(AndroidUtilities.dp(13)); + dialogs_onlinePaint.setTextSize(AndroidUtilities.dp(16)); + dialogs_offlinePaint.setTextSize(AndroidUtilities.dp(16)); + } + + public static void applyDialogsTheme() { + if (dialogs_namePaint == null) { + return; + } + dialogs_namePaint.setColor(getColor(key_chats_name)); + dialogs_nameEncryptedPaint.setColor(getColor(key_chats_secretName)); + dialogs_messagePaint.setColor(dialogs_messagePaint.linkColor = getColor(key_chats_message)); + dialogs_tabletSeletedPaint.setColor(getColor(key_chats_tabletSelectedOverlay)); + dialogs_pinnedPaint.setColor(getColor(key_chats_pinnedOverlay)); + dialogs_timePaint.setColor(getColor(key_chats_date)); + dialogs_countTextPaint.setColor(getColor(key_chats_unreadCounterText)); + dialogs_messagePrintingPaint.setColor(getColor(key_chats_actionMessage)); + dialogs_countPaint.setColor(getColor(key_chats_unreadCounter)); + dialogs_countGrayPaint.setColor(getColor(key_chats_unreadCounterMuted)); + dialogs_errorPaint.setColor(getColor(key_chats_sentError)); + dialogs_onlinePaint.setColor(getColor(key_windowBackgroundWhiteBlueText3)); + dialogs_offlinePaint.setColor(getColor(key_windowBackgroundWhiteGrayText3)); + + setDrawableColorByKey(dialogs_lockDrawable, key_chats_secretIcon); + setDrawableColorByKey(dialogs_checkDrawable, key_chats_sentCheck); + setDrawableColorByKey(dialogs_halfCheckDrawable, key_chats_sentCheck); + setDrawableColorByKey(dialogs_clockDrawable, key_chats_sentClock); + setDrawableColorByKey(dialogs_errorDrawable, key_chats_sentErrorIcon); + setDrawableColorByKey(dialogs_groupDrawable, key_chats_nameIcon); + setDrawableColorByKey(dialogs_broadcastDrawable, key_chats_nameIcon); + setDrawableColorByKey(dialogs_botDrawable, key_chats_nameIcon); + setDrawableColorByKey(dialogs_pinnedDrawable, key_chats_pinnedIcon); + setDrawableColorByKey(dialogs_muteDrawable, key_chats_muteIcon); + setDrawableColorByKey(dialogs_verifiedDrawable, key_chats_verifiedBackground); + setDrawableColorByKey(dialogs_verifiedCheckDrawable, key_chats_verifiedCheck); + } + + public static void destroyResources() { + for (int a = 0; a < chat_attachButtonDrawables.length; a++) { + if (chat_attachButtonDrawables[a] != null) { + chat_attachButtonDrawables[a].setCallback(null); + } + } + } + + public static void createChatResources(Context context, boolean fontsOnly) { + synchronized (sync) { + if (chat_msgTextPaint == null) { + chat_msgTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgGameTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgTextPaintOneEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgTextPaintTwoEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgTextPaintThreeEmoji = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgBotButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_msgBotButtonPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } + } + + if (!fontsOnly && chat_msgInDrawable == null) { + chat_infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_docNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_docNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_docBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_deleteProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_botProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_botProgressPaint.setStrokeCap(Paint.Cap.ROUND); + chat_botProgressPaint.setStyle(Paint.Style.STROKE); + chat_locationTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_locationTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_urlPaint = new Paint(); + chat_textSearchSelectionPaint = new Paint(); + chat_audioTimePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_audioTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_audioTitlePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_audioPerformerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_botButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_botButtonPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_contactNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_contactNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_contactPhonePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_durationPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_gamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_gamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_shipmentPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_replyNamePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); + chat_instantViewPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_instantViewPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_instantViewRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_instantViewRectPaint.setStyle(Paint.Style.STROKE); + chat_replyLinePaint = new Paint(); + chat_msgErrorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_statusPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_statusRecordPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_statusRecordPaint.setStyle(Paint.Style.STROKE); + chat_statusRecordPaint.setStrokeCap(Paint.Cap.ROUND); + chat_actionTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_actionTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_actionBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + chat_contextResult_titleTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_contextResult_titleTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + chat_contextResult_descriptionTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + chat_composeBackgroundPaint = new Paint(); + + Resources resources = context.getResources(); + + chat_msgInDrawable = resources.getDrawable(R.drawable.msg_in).mutate(); + chat_msgInSelectedDrawable = resources.getDrawable(R.drawable.msg_in).mutate(); + + chat_msgOutDrawable = resources.getDrawable(R.drawable.msg_out).mutate(); + chat_msgOutSelectedDrawable = resources.getDrawable(R.drawable.msg_out).mutate(); + + chat_msgInMediaDrawable = resources.getDrawable(R.drawable.msg_photo).mutate(); + chat_msgInMediaSelectedDrawable = resources.getDrawable(R.drawable.msg_photo).mutate(); + chat_msgOutMediaDrawable = resources.getDrawable(R.drawable.msg_photo).mutate(); + chat_msgOutMediaSelectedDrawable = resources.getDrawable(R.drawable.msg_photo).mutate(); + + chat_msgOutCheckDrawable = resources.getDrawable(R.drawable.msg_check).mutate(); + chat_msgOutCheckSelectedDrawable = resources.getDrawable(R.drawable.msg_check).mutate(); + chat_msgMediaCheckDrawable = resources.getDrawable(R.drawable.msg_check).mutate(); + chat_msgStickerCheckDrawable = resources.getDrawable(R.drawable.msg_check).mutate(); + chat_msgOutHalfCheckDrawable = resources.getDrawable(R.drawable.msg_halfcheck).mutate(); + chat_msgOutHalfCheckSelectedDrawable = resources.getDrawable(R.drawable.msg_halfcheck).mutate(); + chat_msgMediaHalfCheckDrawable = resources.getDrawable(R.drawable.msg_halfcheck).mutate(); + chat_msgStickerHalfCheckDrawable = resources.getDrawable(R.drawable.msg_halfcheck).mutate(); + chat_msgOutClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgOutSelectedClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgInClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgInSelectedClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgMediaClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgStickerClockDrawable = resources.getDrawable(R.drawable.msg_clock).mutate(); + chat_msgInViewsDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgInViewsSelectedDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgOutViewsDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgOutViewsSelectedDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgMediaViewsDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgStickerViewsDrawable = resources.getDrawable(R.drawable.msg_views).mutate(); + chat_msgInMenuDrawable = resources.getDrawable(R.drawable.msg_actions).mutate(); + chat_msgInMenuSelectedDrawable = resources.getDrawable(R.drawable.msg_actions).mutate(); + chat_msgOutMenuDrawable = resources.getDrawable(R.drawable.msg_actions).mutate(); + chat_msgOutMenuSelectedDrawable = resources.getDrawable(R.drawable.msg_actions).mutate(); + chat_msgMediaMenuDrawable = resources.getDrawable(R.drawable.video_actions); + chat_msgInInstantDrawable = resources.getDrawable(R.drawable.msg_instant).mutate(); + chat_msgInInstantSelectedDrawable = resources.getDrawable(R.drawable.msg_instant).mutate(); + chat_msgOutInstantDrawable = resources.getDrawable(R.drawable.msg_instant).mutate(); + chat_msgOutInstantSelectedDrawable = resources.getDrawable(R.drawable.msg_instant).mutate(); + chat_msgErrorDrawable = resources.getDrawable(R.drawable.msg_warning); + chat_muteIconDrawable = resources.getDrawable(R.drawable.list_mute).mutate(); + chat_lockIconDrawable = resources.getDrawable(R.drawable.ic_lock_header); + chat_msgBroadcastDrawable = resources.getDrawable(R.drawable.broadcast3).mutate(); + chat_msgBroadcastMediaDrawable = resources.getDrawable(R.drawable.broadcast3).mutate(); + chat_msgInCallDrawable = resources.getDrawable(R.drawable.ic_call_white_24dp).mutate(); + chat_msgInCallSelectedDrawable = resources.getDrawable(R.drawable.ic_call_white_24dp).mutate(); + chat_msgOutCallDrawable = resources.getDrawable(R.drawable.ic_call_white_24dp).mutate(); + chat_msgOutCallSelectedDrawable = resources.getDrawable(R.drawable.ic_call_white_24dp).mutate(); + chat_msgCallUpRedDrawable = resources.getDrawable(R.drawable.ic_call_made_green_18dp).mutate(); + chat_msgCallUpGreenDrawable = resources.getDrawable(R.drawable.ic_call_made_green_18dp).mutate(); + chat_msgCallDownRedDrawable = resources.getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); + chat_msgCallDownGreenDrawable = resources.getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); + + chat_inlineResultFile = resources.getDrawable(R.drawable.bot_file); + chat_inlineResultAudio = resources.getDrawable(R.drawable.bot_music); + chat_inlineResultLocation = resources.getDrawable(R.drawable.bot_location); + + chat_msgInShadowDrawable = resources.getDrawable(R.drawable.msg_in_shadow); + chat_msgOutShadowDrawable = resources.getDrawable(R.drawable.msg_out_shadow); + chat_msgInMediaShadowDrawable = resources.getDrawable(R.drawable.msg_photo_shadow); + chat_msgOutMediaShadowDrawable = resources.getDrawable(R.drawable.msg_photo_shadow); + + chat_botLinkDrawalbe = resources.getDrawable(R.drawable.bot_link); + chat_botInlineDrawable = resources.getDrawable(R.drawable.bot_lines); + + chat_timeBackgroundDrawable = resources.getDrawable(R.drawable.phototime2_b); + chat_timeStickerBackgroundDrawable = resources.getDrawable(R.drawable.phototime2); + chat_systemDrawable = resources.getDrawable(R.drawable.system); + + chat_contextResult_shadowUnderSwitchDrawable = resources.getDrawable(R.drawable.header_shadow).mutate(); + + chat_attachButtonDrawables[0] = resources.getDrawable(R.drawable.attach_camera_states); + chat_attachButtonDrawables[1] = resources.getDrawable(R.drawable.attach_gallery_states); + chat_attachButtonDrawables[2] = resources.getDrawable(R.drawable.attach_video_states); + chat_attachButtonDrawables[3] = resources.getDrawable(R.drawable.attach_audio_states); + chat_attachButtonDrawables[4] = resources.getDrawable(R.drawable.attach_file_states); + chat_attachButtonDrawables[5] = resources.getDrawable(R.drawable.attach_contact_states); + chat_attachButtonDrawables[6] = resources.getDrawable(R.drawable.attach_location_states); + chat_attachButtonDrawables[7] = resources.getDrawable(R.drawable.attach_hide_states); + + chat_cornerOuter[0] = resources.getDrawable(R.drawable.corner_out_tl); + chat_cornerOuter[1] = resources.getDrawable(R.drawable.corner_out_tr); + chat_cornerOuter[2] = resources.getDrawable(R.drawable.corner_out_br); + chat_cornerOuter[3] = resources.getDrawable(R.drawable.corner_out_bl); + + chat_cornerInner[0] = resources.getDrawable(R.drawable.corner_in_tr); + chat_cornerInner[1] = resources.getDrawable(R.drawable.corner_in_tl); + chat_cornerInner[2] = resources.getDrawable(R.drawable.corner_in_br); + chat_cornerInner[3] = resources.getDrawable(R.drawable.corner_in_bl); + + chat_shareDrawable = resources.getDrawable(R.drawable.share_round); + chat_shareIconDrawable = resources.getDrawable(R.drawable.share_arrow); + + chat_fileStatesDrawable[0][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_play_m); + chat_fileStatesDrawable[0][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_play_m); + chat_fileStatesDrawable[1][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_pause_m); + chat_fileStatesDrawable[1][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_pause_m); + chat_fileStatesDrawable[2][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_load_m); + chat_fileStatesDrawable[2][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_load_m); + chat_fileStatesDrawable[3][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_file_s); + chat_fileStatesDrawable[3][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_file_s); + chat_fileStatesDrawable[4][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_cancel_m); + chat_fileStatesDrawable[4][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_cancel_m); + chat_fileStatesDrawable[5][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_play_m); + chat_fileStatesDrawable[5][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_play_m); + chat_fileStatesDrawable[6][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_pause_m); + chat_fileStatesDrawable[6][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_pause_m); + chat_fileStatesDrawable[7][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_load_m); + chat_fileStatesDrawable[7][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_load_m); + chat_fileStatesDrawable[8][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_file_s); + chat_fileStatesDrawable[8][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_file_s); + chat_fileStatesDrawable[9][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_cancel_m); + chat_fileStatesDrawable[9][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_round_cancel_m); + + chat_photoStatesDrawables[0][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[0][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[1][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[1][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[2][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_gif_m); + chat_photoStatesDrawables[2][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_gif_m); + chat_photoStatesDrawables[3][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_play_m); + chat_photoStatesDrawables[3][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_play_m); + + chat_photoStatesDrawables[4][0] = chat_photoStatesDrawables[4][1] = resources.getDrawable(R.drawable.burn); + chat_photoStatesDrawables[5][0] = chat_photoStatesDrawables[5][1] = resources.getDrawable(R.drawable.circle); + chat_photoStatesDrawables[6][0] = chat_photoStatesDrawables[6][1] = resources.getDrawable(R.drawable.photocheck); + + chat_photoStatesDrawables[7][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[7][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[8][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[8][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[9][0] = resources.getDrawable(R.drawable.doc_big).mutate(); + chat_photoStatesDrawables[9][1] = resources.getDrawable(R.drawable.doc_big).mutate(); + chat_photoStatesDrawables[10][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[10][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_load_m); + chat_photoStatesDrawables[11][0] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[11][1] = createCircleDrawableWithIcon(AndroidUtilities.dp(48), R.drawable.msg_round_cancel_m); + chat_photoStatesDrawables[12][0] = resources.getDrawable(R.drawable.doc_big).mutate(); + chat_photoStatesDrawables[12][1] = resources.getDrawable(R.drawable.doc_big).mutate(); + + chat_contactDrawable[0] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_contact); + chat_contactDrawable[1] = createCircleDrawableWithIcon(AndroidUtilities.dp(44), R.drawable.msg_contact); + + chat_locationDrawable[0] = createRoundRectDrawableWithIcon(AndroidUtilities.dp(2), R.drawable.msg_location); + chat_locationDrawable[1] = createRoundRectDrawableWithIcon(AndroidUtilities.dp(2), R.drawable.msg_location); + + chat_composeShadowDrawable = context.getResources().getDrawable(R.drawable.compose_panel_shadow); + + applyChatTheme(fontsOnly); + } + + chat_msgTextPaintOneEmoji.setTextSize(AndroidUtilities.dp(28)); + chat_msgTextPaintTwoEmoji.setTextSize(AndroidUtilities.dp(24)); + chat_msgTextPaintThreeEmoji.setTextSize(AndroidUtilities.dp(20)); + chat_msgTextPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize)); + chat_msgGameTextPaint.setTextSize(AndroidUtilities.dp(14)); + chat_msgBotButtonPaint.setTextSize(AndroidUtilities.dp(15)); + + if (!fontsOnly && chat_botProgressPaint != null) { + chat_botProgressPaint.setStrokeWidth(AndroidUtilities.dp(2)); + chat_infoPaint.setTextSize(AndroidUtilities.dp(12)); + chat_docNamePaint.setTextSize(AndroidUtilities.dp(15)); + chat_locationTitlePaint.setTextSize(AndroidUtilities.dp(15)); + chat_locationAddressPaint.setTextSize(AndroidUtilities.dp(13)); + chat_audioTimePaint.setTextSize(AndroidUtilities.dp(12)); + chat_audioTitlePaint.setTextSize(AndroidUtilities.dp(16)); + chat_audioPerformerPaint.setTextSize(AndroidUtilities.dp(15)); + chat_botButtonPaint.setTextSize(AndroidUtilities.dp(15)); + chat_contactNamePaint.setTextSize(AndroidUtilities.dp(15)); + chat_contactPhonePaint.setTextSize(AndroidUtilities.dp(13)); + chat_durationPaint.setTextSize(AndroidUtilities.dp(12)); + chat_timePaint.setTextSize(AndroidUtilities.dp(12)); + chat_namePaint.setTextSize(AndroidUtilities.dp(14)); + chat_forwardNamePaint.setTextSize(AndroidUtilities.dp(14)); + chat_replyNamePaint.setTextSize(AndroidUtilities.dp(14)); + chat_replyTextPaint.setTextSize(AndroidUtilities.dp(14)); + chat_gamePaint.setTextSize(AndroidUtilities.dp(13)); + chat_shipmentPaint.setTextSize(AndroidUtilities.dp(13)); + chat_instantViewPaint.setTextSize(AndroidUtilities.dp(13)); + chat_instantViewRectPaint.setStrokeWidth(AndroidUtilities.dp(1)); + chat_statusRecordPaint.setStrokeWidth(AndroidUtilities.dp(2)); + chat_actionTextPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize - 2)); + chat_contextResult_titleTextPaint.setTextSize(AndroidUtilities.dp(15)); + chat_contextResult_descriptionTextPaint.setTextSize(AndroidUtilities.dp(13)); + } + } + + public static void applyChatTheme(boolean fontsOnly) { + if (chat_msgTextPaint == null) { + return; + } + + if (chat_msgInDrawable != null && !fontsOnly) { + chat_gamePaint.setColor(getColor(key_chat_previewGameText)); + chat_durationPaint.setColor(getColor(key_chat_previewDurationText)); + chat_botButtonPaint.setColor(getColor(key_chat_botButtonText)); + chat_urlPaint.setColor(getColor(key_chat_linkSelectBackground)); + chat_botProgressPaint.setColor(getColor(key_chat_botProgress)); + chat_deleteProgressPaint.setColor(getColor(key_chat_secretTimeText)); + chat_textSearchSelectionPaint.setColor(getColor(key_chat_textSelectBackground)); + chat_msgErrorPaint.setColor(getColor(key_chat_sentError)); + chat_statusPaint.setColor(getColor(key_actionBarDefaultSubtitle)); + chat_statusRecordPaint.setColor(getColor(key_actionBarDefaultSubtitle)); + chat_actionTextPaint.setColor(getColor(key_chat_serviceText)); + chat_actionTextPaint.linkColor = getColor(key_chat_serviceLink); + chat_contextResult_titleTextPaint.setColor(getColor(key_windowBackgroundWhiteBlackText)); + chat_composeBackgroundPaint.setColor(getColor(key_chat_messagePanelBackground)); + + setDrawableColorByKey(chat_msgInDrawable, key_chat_inBubble); + setDrawableColorByKey(chat_msgInSelectedDrawable, key_chat_inBubbleSelected); + setDrawableColorByKey(chat_msgInShadowDrawable, key_chat_inBubbleShadow); + setDrawableColorByKey(chat_msgOutDrawable, key_chat_outBubble); + setDrawableColorByKey(chat_msgOutSelectedDrawable, key_chat_outBubbleSelected); + setDrawableColorByKey(chat_msgOutShadowDrawable, key_chat_outBubbleShadow); + setDrawableColorByKey(chat_msgInMediaDrawable, key_chat_inBubble); + setDrawableColorByKey(chat_msgInMediaSelectedDrawable, key_chat_inBubbleSelected); + setDrawableColorByKey(chat_msgInMediaShadowDrawable, key_chat_inBubbleShadow); + setDrawableColorByKey(chat_msgOutMediaDrawable, key_chat_outBubble); + setDrawableColorByKey(chat_msgOutMediaSelectedDrawable, key_chat_outBubbleSelected); + setDrawableColorByKey(chat_msgOutMediaShadowDrawable, key_chat_outBubbleShadow); + setDrawableColorByKey(chat_msgOutCheckDrawable, key_chat_outSentCheck); + setDrawableColorByKey(chat_msgOutCheckSelectedDrawable, key_chat_outSentCheckSelected); + setDrawableColorByKey(chat_msgOutHalfCheckDrawable, key_chat_outSentCheck); + setDrawableColorByKey(chat_msgOutHalfCheckSelectedDrawable, key_chat_outSentCheckSelected); + setDrawableColorByKey(chat_msgOutClockDrawable, key_chat_outSentClock); + setDrawableColorByKey(chat_msgOutSelectedClockDrawable, key_chat_outSentClockSelected); + setDrawableColorByKey(chat_msgInClockDrawable, key_chat_inSentClock); + setDrawableColorByKey(chat_msgInSelectedClockDrawable, key_chat_inSentClockSelected); + setDrawableColorByKey(chat_msgMediaCheckDrawable, key_chat_mediaSentCheck); + setDrawableColorByKey(chat_msgMediaHalfCheckDrawable, key_chat_mediaSentCheck); + setDrawableColorByKey(chat_msgMediaClockDrawable, key_chat_mediaSentClock); + setDrawableColorByKey(chat_msgStickerCheckDrawable, key_chat_serviceText); + setDrawableColorByKey(chat_msgStickerHalfCheckDrawable, key_chat_serviceText); + setDrawableColorByKey(chat_msgStickerClockDrawable, key_chat_serviceText); + setDrawableColorByKey(chat_msgStickerViewsDrawable, key_chat_serviceText); + setDrawableColorByKey(chat_shareIconDrawable, key_chat_serviceIcon); + setDrawableColorByKey(chat_botInlineDrawable, key_chat_serviceIcon); + setDrawableColorByKey(chat_botLinkDrawalbe, key_chat_serviceIcon); + setDrawableColorByKey(chat_msgInViewsDrawable, key_chat_inViews); + setDrawableColorByKey(chat_msgInViewsSelectedDrawable, key_chat_inViewsSelected); + setDrawableColorByKey(chat_msgOutViewsDrawable, key_chat_outViews); + setDrawableColorByKey(chat_msgOutViewsSelectedDrawable, key_chat_outViewsSelected); + setDrawableColorByKey(chat_msgMediaViewsDrawable, key_chat_mediaViews); + setDrawableColorByKey(chat_msgInMenuDrawable, key_chat_inMenu); + setDrawableColorByKey(chat_msgInMenuSelectedDrawable, key_chat_inMenuSelected); + setDrawableColorByKey(chat_msgOutMenuDrawable, key_chat_outMenu); + setDrawableColorByKey(chat_msgOutMenuSelectedDrawable, key_chat_outMenuSelected); + setDrawableColorByKey(chat_msgMediaMenuDrawable, key_chat_mediaMenu); + setDrawableColorByKey(chat_msgOutInstantDrawable, key_chat_outInstant); + setDrawableColorByKey(chat_msgOutInstantSelectedDrawable, key_chat_outInstantSelected); + setDrawableColorByKey(chat_msgInInstantDrawable, key_chat_inInstant); + setDrawableColorByKey(chat_msgInInstantSelectedDrawable, key_chat_inInstantSelected); + setDrawableColorByKey(chat_msgErrorDrawable, key_chat_sentErrorIcon); + setDrawableColorByKey(chat_muteIconDrawable, key_chat_muteIcon); + setDrawableColorByKey(chat_lockIconDrawable, key_chat_lockIcon); + setDrawableColorByKey(chat_msgBroadcastDrawable, key_chat_outBroadcast); + setDrawableColorByKey(chat_msgBroadcastMediaDrawable, key_chat_mediaBroadcast); + setDrawableColorByKey(chat_inlineResultFile, key_chat_inlineResultIcon); + setDrawableColorByKey(chat_inlineResultAudio, key_chat_inlineResultIcon); + setDrawableColorByKey(chat_inlineResultLocation, key_chat_inlineResultIcon); + setDrawableColorByKey(chat_msgInCallDrawable, key_chat_inInstant); + setDrawableColorByKey(chat_msgInCallSelectedDrawable, key_chat_inInstantSelected); + setDrawableColorByKey(chat_msgOutCallDrawable, key_chat_outInstant); + setDrawableColorByKey(chat_msgOutCallSelectedDrawable, key_chat_outInstantSelected); + setDrawableColorByKey(chat_msgCallUpRedDrawable, key_calls_callReceivedRedIcon); + setDrawableColorByKey(chat_msgCallUpGreenDrawable, key_calls_callReceivedGreenIcon); + setDrawableColorByKey(chat_msgCallDownRedDrawable, key_calls_callReceivedRedIcon); + setDrawableColorByKey(chat_msgCallDownGreenDrawable, key_calls_callReceivedGreenIcon); + + for (int a = 0; a < 5; a++) { + setCombinedDrawableColor(chat_fileStatesDrawable[a][0], getColor(key_chat_outLoader), false); + setCombinedDrawableColor(chat_fileStatesDrawable[a][0], getColor(key_chat_outBubble), true); + setCombinedDrawableColor(chat_fileStatesDrawable[a][1], getColor(key_chat_outLoaderSelected), false); + setCombinedDrawableColor(chat_fileStatesDrawable[a][1], getColor(key_chat_outBubbleSelected), true); + setCombinedDrawableColor(chat_fileStatesDrawable[5 + a][0], getColor(key_chat_inLoader), false); + setCombinedDrawableColor(chat_fileStatesDrawable[5 + a][0], getColor(key_chat_inBubble), true); + setCombinedDrawableColor(chat_fileStatesDrawable[5 + a][1], getColor(key_chat_inLoaderSelected), false); + setCombinedDrawableColor(chat_fileStatesDrawable[5 + a][1], getColor(key_chat_inBubbleSelected), true); + } + for (int a = 0; a < 4; a++) { + setCombinedDrawableColor(chat_photoStatesDrawables[a][0], getColor(key_chat_mediaLoaderPhoto), false); + setCombinedDrawableColor(chat_photoStatesDrawables[a][0], getColor(key_chat_mediaLoaderPhotoIcon), true); + setCombinedDrawableColor(chat_photoStatesDrawables[a][1], getColor(key_chat_mediaLoaderPhotoSelected), false); + setCombinedDrawableColor(chat_photoStatesDrawables[a][1], getColor(key_chat_mediaLoaderPhotoIconSelected), true); + } + for (int a = 0; a < 2; a++) { + setCombinedDrawableColor(chat_photoStatesDrawables[7 + a][0], getColor(key_chat_outLoaderPhoto), false); + setCombinedDrawableColor(chat_photoStatesDrawables[7 + a][0], getColor(key_chat_outLoaderPhotoIcon), true); + setCombinedDrawableColor(chat_photoStatesDrawables[7 + a][1], getColor(key_chat_outLoaderPhotoSelected), false); + setCombinedDrawableColor(chat_photoStatesDrawables[7 + a][1], getColor(key_chat_outLoaderPhotoIconSelected), true); + setCombinedDrawableColor(chat_photoStatesDrawables[10 + a][0], getColor(key_chat_inLoaderPhoto), false); + setCombinedDrawableColor(chat_photoStatesDrawables[10 + a][0], getColor(key_chat_inLoaderPhotoIcon), true); + setCombinedDrawableColor(chat_photoStatesDrawables[10 + a][1], getColor(key_chat_inLoaderPhotoSelected), false); + setCombinedDrawableColor(chat_photoStatesDrawables[10 + a][1], getColor(key_chat_inLoaderPhotoIconSelected), true); + } + + setDrawableColorByKey(chat_photoStatesDrawables[9][0], key_chat_outFileIcon); + setDrawableColorByKey(chat_photoStatesDrawables[9][1], key_chat_outFileSelectedIcon); + setDrawableColorByKey(chat_photoStatesDrawables[12][0], key_chat_inFileIcon); + setDrawableColorByKey(chat_photoStatesDrawables[12][1], key_chat_inFileSelectedIcon); + + setCombinedDrawableColor(chat_contactDrawable[0], getColor(key_chat_inContactBackground), false); + setCombinedDrawableColor(chat_contactDrawable[0], getColor(key_chat_inContactIcon), true); + setCombinedDrawableColor(chat_contactDrawable[1], getColor(key_chat_outContactBackground), false); + setCombinedDrawableColor(chat_contactDrawable[1], getColor(key_chat_outContactIcon), true); + + setCombinedDrawableColor(chat_locationDrawable[0], getColor(key_chat_inLocationBackground), false); + setCombinedDrawableColor(chat_locationDrawable[0], getColor(key_chat_inLocationIcon), true); + setCombinedDrawableColor(chat_locationDrawable[1], getColor(key_chat_outLocationBackground), false); + setCombinedDrawableColor(chat_locationDrawable[1], getColor(key_chat_outLocationIcon), true); + + setDrawableColorByKey(chat_composeShadowDrawable, key_chat_messagePanelShadow); + + applyChatServiceMessageColor(); + } + } + + public static void applyChatServiceMessageColor() { + if (chat_actionBackgroundPaint == null) { + return; + } + Integer serviceColor = currentColors.get(key_chat_serviceBackground); + Integer servicePressedColor = currentColors.get(key_chat_serviceBackgroundSelected); + boolean override; + if (serviceColor == null) { + serviceColor = serviceMessageColor; + } + if (servicePressedColor == null) { + servicePressedColor = serviceSelectedMessageColor; + } + if (currentColor != serviceColor) { + chat_actionBackgroundPaint.setColor(serviceColor); + colorFilter = new PorterDuffColorFilter(serviceColor, PorterDuff.Mode.MULTIPLY); + currentColor = serviceColor; + if (chat_timeStickerBackgroundDrawable != null) { + for (int a = 0; a < 4; a++) { + chat_cornerOuter[a].setColorFilter(colorFilter); + chat_cornerInner[a].setColorFilter(colorFilter); + } + chat_timeStickerBackgroundDrawable.setColorFilter(colorFilter); + } + } + if (currentSelectedColor != servicePressedColor) { + currentSelectedColor = servicePressedColor; + colorPressedFilter = new PorterDuffColorFilter(servicePressedColor, PorterDuff.Mode.MULTIPLY); + } + } + + public static void createProfileResources(Context context) { + if (profile_verifiedDrawable == null) { + profile_aboutTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + + Resources resources = context.getResources(); + + profile_verifiedDrawable = resources.getDrawable(R.drawable.verified_area).mutate(); + profile_verifiedCheckDrawable = resources.getDrawable(R.drawable.verified_check).mutate(); + + applyProfileTheme(); + } + + profile_aboutTextPaint.setTextSize(AndroidUtilities.dp(16)); + } + + public static void applyProfileTheme() { + if (profile_verifiedDrawable == null) { + return; + } + + profile_aboutTextPaint.setColor(getColor(key_windowBackgroundWhiteBlackText)); + profile_aboutTextPaint.linkColor = getColor(key_windowBackgroundWhiteLinkText); + + setDrawableColorByKey(profile_verifiedDrawable, key_profile_verifiedBackground); + setDrawableColorByKey(profile_verifiedCheckDrawable, key_profile_verifiedCheck); + } + + public static Drawable getThemedDrawable(Context context, int resId, String key) { + Drawable drawable = context.getResources().getDrawable(resId).mutate(); + drawable.setColorFilter(new PorterDuffColorFilter(getColor(key), PorterDuff.Mode.MULTIPLY)); + return drawable; + } + + public static int getDefaultColor(String key) { + Integer value = defaultColors.get(key); + if (value == null) { + if (key.equals(key_chats_menuTopShadow)) { + return 0; + } + return 0xffff0000; + } + return value; + } + + public static boolean hasThemeKey(String key) { + return currentColors.containsKey(key); + } + + public static Integer getColorOrNull(String key) { + Integer color = currentColors.get(key); + if (color == null) { + color = defaultColors.get(key); + } + return color; + } + + public static int getColor(String key) { + return getColor(key, null); + } + + public static int getColor(String key, boolean[] isDefault) { + Integer color = currentColors.get(key); + if (color == null) { + if (isDefault != null) { + isDefault[0] = true; + } + if (key.equals(key_chat_serviceBackground)) { + return serviceMessageColor; + } else if (key.equals(key_chat_serviceBackgroundSelected)) { + return serviceSelectedMessageColor; + } + return getDefaultColor(key); + } + return color; + } + + public static void setColor(String key, int color, boolean useDefault) { + if (key.equals(key_chat_wallpaper)) { + color = 0xff000000 | color; + } + + if (useDefault) { + currentColors.remove(key); + } else { + currentColors.put(key, color); + } + + if (key.equals(key_chat_serviceBackground) || key.equals(key_chat_serviceBackgroundSelected)) { + applyChatServiceMessageColor(); + } else if (key.equals(key_chat_wallpaper)) { + reloadWallpaper(); + } + } + + public static void setThemeWallpaper(String themeName, Bitmap bitmap, File path) { + currentColors.remove(key_chat_wallpaper); + ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit().remove("overrideThemeWallpaper").commit(); + if (bitmap != null) { + themedWallpaper = new BitmapDrawable(bitmap); + saveCurrentTheme(themeName, false); + calcBackgroundColor(themedWallpaper, 0); + applyChatServiceMessageColor(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didSetNewWallpapper); + } else { + themedWallpaper = null; + wallpaper = null; + saveCurrentTheme(themeName, false); + reloadWallpaper(); + } + } + + public static void setDrawableColor(Drawable drawable, int color) { + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + + public static void setDrawableColorByKey(Drawable drawable, String key) { + drawable.setColorFilter(new PorterDuffColorFilter(getColor(key), PorterDuff.Mode.MULTIPLY)); + } + + public static void setSelectorDrawableColor(Drawable drawable, int color, boolean selected) { + if (drawable instanceof StateListDrawable) { + try { + if (selected) { + Drawable state = getStateDrawable(drawable, 0); + if (state instanceof ShapeDrawable) { + ((ShapeDrawable) state).getPaint().setColor(color); + } else { + state.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + state = getStateDrawable(drawable, 1); + if (state instanceof ShapeDrawable) { + ((ShapeDrawable) state).getPaint().setColor(color); + } else { + state.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else { + Drawable state = getStateDrawable(drawable, 2); + if (state instanceof ShapeDrawable) { + ((ShapeDrawable) state).getPaint().setColor(color); + } else { + state.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } catch (Throwable ignore) { + + } + } else if (Build.VERSION.SDK_INT >= 21 && drawable instanceof RippleDrawable) { + RippleDrawable rippleDrawable = (RippleDrawable) drawable; + if (selected) { + rippleDrawable.setColor(new ColorStateList( + new int[][]{StateSet.WILD_CARD}, + new int[]{color} + )); + } else { + if (rippleDrawable.getNumberOfLayers() > 0) { + Drawable drawable1 = rippleDrawable.getDrawable(0); + if (drawable1 instanceof ShapeDrawable) { + ((ShapeDrawable) drawable1).getPaint().setColor(color); + } else { + drawable1.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } + } + } + + public static boolean hasWallpaperFromTheme() { + return currentColors.containsKey(key_chat_wallpaper) || themedWallpaperFileOffset > 0; + } + + public static boolean isCustomTheme() { + return isCustomTheme; + } + + public static int getSelectedColor() { + return selectedColor; + } + + public static void reloadWallpaper() { + wallpaper = null; + themedWallpaper = null; + loadWallpaper(); + } + + private static void calcBackgroundColor(Drawable drawable, int save) { + if (save != 2) { + int result[] = AndroidUtilities.calcDrawableColor(drawable); + serviceMessageColor = result[0]; + serviceSelectedMessageColor = result[1]; + } + } + + public static int getServiceMessageColor() { + Integer serviceColor = currentColors.get(key_chat_serviceBackground); + return serviceColor == null ? serviceMessageColor : serviceColor; + } + + public static void loadWallpaper() { + if (wallpaper != null) { + return; + } + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + synchronized (wallpaperSync) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + boolean overrideTheme = preferences.getBoolean("overrideThemeWallpaper", false); + if (!overrideTheme) { + Integer backgroundColor = currentColors.get(key_chat_wallpaper); + if (backgroundColor != null) { + wallpaper = new ColorDrawable(backgroundColor); + isCustomTheme = true; + } else if (themedWallpaperFileOffset > 0 && (currentTheme.pathToFile != null || currentTheme.assetName != null)) { + FileInputStream stream = null; + try { + int currentPosition = 0; + File file; + if (currentTheme.assetName != null) { + file = Theme.getAssetFile(currentTheme.assetName); + } else { + file = new File(currentTheme.pathToFile); + } + stream = new FileInputStream(file); + stream.getChannel().position(themedWallpaperFileOffset); + Bitmap bitmap = BitmapFactory.decodeStream(stream); + if (bitmap != null) { + themedWallpaper = wallpaper = new BitmapDrawable(bitmap); + isCustomTheme = true; + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + } + if (wallpaper == null) { + int selectedColor = 0; + try { + preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + int selectedBackground = preferences.getInt("selectedBackground", 1000001); + selectedColor = preferences.getInt("selectedColor", 0); + if (selectedColor == 0) { + if (selectedBackground == 1000001) { + wallpaper = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.background_hd); + isCustomTheme = false; + } else { + File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper.jpg"); + if (toFile.exists()) { + wallpaper = Drawable.createFromPath(toFile.getAbsolutePath()); + isCustomTheme = true; + } else { + wallpaper = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.background_hd); + isCustomTheme = false; + } + } + } + } catch (Throwable throwable) { + //ignore + } + if (wallpaper == null) { + if (selectedColor == 0) { + selectedColor = -2693905; + } + wallpaper = new ColorDrawable(selectedColor); + } + } + calcBackgroundColor(wallpaper, 1); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + applyChatServiceMessageColor(); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didSetNewWallpapper); + } + }); + } + } + }); + } + + public static Drawable getThemedWallpaper(boolean thumb) { + Integer backgroundColor = currentColors.get(key_chat_wallpaper); + if (backgroundColor != null) { + return new ColorDrawable(backgroundColor); + } else if (themedWallpaperFileOffset > 0 && (currentTheme.pathToFile != null || currentTheme.assetName != null)) { + FileInputStream stream = null; + try { + int currentPosition = 0; + File file; + if (currentTheme.assetName != null) { + file = Theme.getAssetFile(currentTheme.assetName); + } else { + file = new File(currentTheme.pathToFile); + } + stream = new FileInputStream(file); + stream.getChannel().position(themedWallpaperFileOffset); + BitmapFactory.Options opts = new BitmapFactory.Options(); + int scaleFactor = 1; + if (thumb) { + opts.inJustDecodeBounds = true; + float photoW = opts.outWidth; + float photoH = opts.outHeight; + int maxWidth = AndroidUtilities.dp(100); + while (photoW > maxWidth || photoH > maxWidth) { + scaleFactor *= 2; + photoW /= 2; + photoH /= 2; + } + } + opts.inJustDecodeBounds = false; + opts.inSampleSize = scaleFactor; + Bitmap bitmap = BitmapFactory.decodeStream(stream, null, opts); + if (bitmap != null) { + return new BitmapDrawable(bitmap); + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + return null; + } + + public static Drawable getCachedWallpaper() { + synchronized (wallpaperSync) { + if (themedWallpaper != null) { + return themedWallpaper; + } else { + return wallpaper; + } + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java new file mode 100644 index 00000000000..c499d4be578 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ThemeDescription.java @@ -0,0 +1,555 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.ActionBar; + +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.RippleDrawable; +import android.graphics.drawable.StateListDrawable; +import android.os.Build; +import android.text.TextPaint; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.ChatBigEmptyView; +import org.telegram.ui.Components.CheckBox; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.EditTextCaption; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.GroupCreateCheckBox; +import org.telegram.ui.Components.GroupCreateSpan; +import org.telegram.ui.Components.LetterDrawable; +import org.telegram.ui.Components.LineProgressView; +import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RadioButton; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.Switch; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; + +public class ThemeDescription { + + public static int FLAG_BACKGROUND = 0x00000001; + public static int FLAG_LINKCOLOR = 0x00000002; + public static int FLAG_TEXTCOLOR = 0x00000004; + public static int FLAG_IMAGECOLOR = 0x00000008; + public static int FLAG_CELLBACKGROUNDCOLOR = 0x00000010; + public static int FLAG_BACKGROUNDFILTER = 0x00000020; + public static int FLAG_AB_ITEMSCOLOR = 0x00000040; + public static int FLAG_AB_TITLECOLOR = 0x00000080; + public static int FLAG_AB_SELECTORCOLOR = 0x00000100; + public static int FLAG_AB_AM_ITEMSCOLOR = 0x00000200; + public static int FLAG_AB_SUBTITLECOLOR = 0x00000400; + public static int FLAG_PROGRESSBAR = 0x00000800; + public static int FLAG_SELECTOR = 0x00001000; + public static int FLAG_CHECKBOX = 0x00002000; + public static int FLAG_CHECKBOXCHECK = 0x00004000; + public static int FLAG_LISTGLOWCOLOR = 0x00008000; + public static int FLAG_DRAWABLESELECTEDSTATE = 0x00010000; + public static int FLAG_USEBACKGROUNDDRAWABLE = 0x00020000; + public static int FLAG_CHECKTAG = 0x00040000; + public static int FLAG_SECTIONS = 0x00080000; + public static int FLAG_AB_AM_BACKGROUND = 0x00100000; + public static int FLAG_AB_AM_TOPBACKGROUND = 0x00200000; + public static int FLAG_AB_AM_SELECTORCOLOR = 0x00400000; + public static int FLAG_HINTTEXTCOLOR = 0x00800000; + public static int FLAG_CURSORCOLOR = 0x01000000; + public static int FLAG_FASTSCROLL = 0x02000000; + public static int FLAG_AB_SEARCHPLACEHOLDER = 0x04000000; + public static int FLAG_AB_SEARCH = 0x08000000; + public static int FLAG_SELECTORWHITE = 0x10000000; + public static int FLAG_SERVICEBACKGROUND = 0x20000000; + public static int FLAG_AB_SUBMENUITEM = 0x40000000; + public static int FLAG_AB_SUBMENUBACKGROUND = 0x80000000; + + private View viewToInvalidate; + private Paint[] paintToUpdate; + private Drawable[] drawablesToUpdate; + private Class[] listClasses; + private String currentKey; + private ThemeDescriptionDelegate delegate; + private int previousColor; + private boolean[] previousIsDefault = new boolean[1]; + private int defaultColor; + private int currentColor; + private int changeFlags; + private String[] listClassesFieldName; + + private HashMap cachedFields; + + public interface ThemeDescriptionDelegate { + void didSetColor(int color); + } + + public ThemeDescription(View view, int flags, Class[] classes, Paint[] paint, Drawable[] drawables, ThemeDescriptionDelegate themeDescriptionDelegate, String key, Object unused) { + currentKey = key; + paintToUpdate = paint; + drawablesToUpdate = drawables; + viewToInvalidate = view; + changeFlags = flags; + listClasses = classes; + delegate = themeDescriptionDelegate; + } + + public ThemeDescription(View view, int flags, Class[] classes, Paint paint, Drawable[] drawables, ThemeDescriptionDelegate themeDescriptionDelegate, String key) { + currentKey = key; + if (paint != null) { + paintToUpdate = new Paint[]{paint}; + } + drawablesToUpdate = drawables; + viewToInvalidate = view; + changeFlags = flags; + listClasses = classes; + delegate = themeDescriptionDelegate; + } + + public ThemeDescription(View view, int flags, Class[] classes, String[] classesFields, Paint[] paint, Drawable[] drawables, ThemeDescriptionDelegate themeDescriptionDelegate, String key) { + currentKey = key; + paintToUpdate = paint; + drawablesToUpdate = drawables; + viewToInvalidate = view; + changeFlags = flags; + listClasses = classes; + listClassesFieldName = classesFields; + delegate = themeDescriptionDelegate; + cachedFields = new HashMap<>(); + } + + public void setColor(int color, boolean useDefault) { + Theme.setColor(currentKey, color, useDefault); + if (paintToUpdate != null) { + for (int a = 0; a < paintToUpdate.length; a++) { + if ((changeFlags & FLAG_LINKCOLOR) != 0 && paintToUpdate[a] instanceof TextPaint) { + ((TextPaint) paintToUpdate[a]).linkColor = color; + } else { + paintToUpdate[a].setColor(color); + } + } + } + if (drawablesToUpdate != null) { + for (int a = 0; a < drawablesToUpdate.length; a++) { + if (drawablesToUpdate[a] == null) { + continue; + } + if (drawablesToUpdate[a] instanceof CombinedDrawable) { + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + ((CombinedDrawable) drawablesToUpdate[a]).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } else { + ((CombinedDrawable) drawablesToUpdate[a]).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (drawablesToUpdate[a] instanceof AvatarDrawable) { + ((AvatarDrawable) drawablesToUpdate[a]).setColor(color); + } else { + drawablesToUpdate[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } + if (viewToInvalidate != null && listClasses == null && listClassesFieldName == null) { + if ((changeFlags & FLAG_CHECKTAG) == 0 || (changeFlags & FLAG_CHECKTAG) != 0 && currentKey.equals(viewToInvalidate.getTag())) { + if ((changeFlags & FLAG_BACKGROUND) != 0) { + viewToInvalidate.setBackgroundColor(color); + } + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + Drawable drawable = viewToInvalidate.getBackground(); + if (drawable instanceof CombinedDrawable) { + if ((changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0) { + drawable = ((CombinedDrawable) drawable).getBackground(); + } else { + drawable = ((CombinedDrawable) drawable).getIcon(); + } + } + if (drawable != null) { + if (drawable instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && drawable instanceof RippleDrawable) { + Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); + } else { + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } + } + } + if (viewToInvalidate instanceof ActionBar) { + if ((changeFlags & FLAG_AB_ITEMSCOLOR) != 0) { + ((ActionBar) viewToInvalidate).setItemsColor(color, false); + } + if ((changeFlags & FLAG_AB_TITLECOLOR) != 0) { + ((ActionBar) viewToInvalidate).setTitleColor(color); + } + if ((changeFlags & FLAG_AB_SELECTORCOLOR) != 0) { + ((ActionBar) viewToInvalidate).setItemsBackgroundColor(color, false); + } + if ((changeFlags & FLAG_AB_AM_SELECTORCOLOR) != 0) { + ((ActionBar) viewToInvalidate).setItemsBackgroundColor(color, true); + } + if ((changeFlags & FLAG_AB_AM_ITEMSCOLOR) != 0) { + ((ActionBar) viewToInvalidate).setItemsColor(color, true); + } + if ((changeFlags & FLAG_AB_SUBTITLECOLOR) != 0) { + ((ActionBar) viewToInvalidate).setSubtitleColor(color); + } + if ((changeFlags & FLAG_AB_AM_BACKGROUND) != 0) { + ((ActionBar) viewToInvalidate).setActionModeColor(color); + } + if ((changeFlags & FLAG_AB_AM_TOPBACKGROUND) != 0) { + ((ActionBar) viewToInvalidate).setActionModeTopColor(color); + } + if ((changeFlags & FLAG_AB_SEARCHPLACEHOLDER) != 0) { + ((ActionBar) viewToInvalidate).setSearchTextColor(color, true); + } + if ((changeFlags & FLAG_AB_SEARCH) != 0) { + ((ActionBar) viewToInvalidate).setSearchTextColor(color, false); + } + if ((changeFlags & FLAG_AB_SUBMENUITEM) != 0) { + ((ActionBar) viewToInvalidate).setPopupItemsColor(color); + } + if ((changeFlags & FLAG_AB_SUBMENUBACKGROUND) != 0) { + ((ActionBar) viewToInvalidate).setPopupBackgroundColor(color); + } + } + if (viewToInvalidate instanceof EmptyTextProgressView) { + if ((changeFlags & FLAG_TEXTCOLOR) != 0) { + ((EmptyTextProgressView) viewToInvalidate).setTextColor(color); + } else if ((changeFlags & FLAG_PROGRESSBAR) != 0) { + ((EmptyTextProgressView) viewToInvalidate).setProgressBarColor(color); + } + } + if (viewToInvalidate instanceof RadialProgressView) { + ((RadialProgressView) viewToInvalidate).setProgressColor(color); + } else if (viewToInvalidate instanceof LineProgressView) { + if ((changeFlags & FLAG_PROGRESSBAR) != 0) { + ((LineProgressView) viewToInvalidate).setProgressColor(color); + } else { + ((LineProgressView) viewToInvalidate).setBackColor(color); + } + } else if (viewToInvalidate instanceof ContextProgressView) { + ((ContextProgressView) viewToInvalidate).updateColors(); + } + if ((changeFlags & FLAG_TEXTCOLOR) != 0) { + if ((changeFlags & FLAG_CHECKTAG) == 0 || viewToInvalidate != null && (changeFlags & FLAG_CHECKTAG) != 0 && currentKey.equals(viewToInvalidate.getTag())) { + if (viewToInvalidate instanceof TextView) { + ((TextView) viewToInvalidate).setTextColor(color); + } else if (viewToInvalidate instanceof NumberTextView) { + ((NumberTextView) viewToInvalidate).setTextColor(color); + } else if (viewToInvalidate instanceof SimpleTextView) { + ((SimpleTextView) viewToInvalidate).setTextColor(color); + } else if (viewToInvalidate instanceof ChatBigEmptyView) { + ((ChatBigEmptyView) viewToInvalidate).setTextColor(color); + } + } + } + if ((changeFlags & FLAG_CURSORCOLOR) != 0) { + if (viewToInvalidate instanceof EditTextBoldCursor) { + ((EditTextBoldCursor) viewToInvalidate).setCursorColor(color); + } + } + if ((changeFlags & FLAG_HINTTEXTCOLOR) != 0) { + if (viewToInvalidate instanceof EditTextBoldCursor) { + ((EditTextBoldCursor) viewToInvalidate).setHintColor(color); + } else if (viewToInvalidate instanceof EditText) { + ((EditText) viewToInvalidate).setHintTextColor(color); + } + } + if (viewToInvalidate != null && (changeFlags & FLAG_SERVICEBACKGROUND) != 0) { + Drawable background = viewToInvalidate.getBackground(); + if (background != null) { + background.setColorFilter(Theme.colorFilter); + } + } + if ((changeFlags & FLAG_IMAGECOLOR) != 0) { + if ((changeFlags & FLAG_CHECKTAG) == 0 || (changeFlags & FLAG_CHECKTAG) != 0 && currentKey.equals(viewToInvalidate.getTag())) { + if (viewToInvalidate instanceof ImageView) { + if ((changeFlags & FLAG_USEBACKGROUNDDRAWABLE) != 0) { + Drawable drawable = ((ImageView) viewToInvalidate).getDrawable(); + if (drawable instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && drawable instanceof RippleDrawable) { + Theme.setSelectorDrawableColor(drawable, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); + } + } else { + ((ImageView) viewToInvalidate).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (viewToInvalidate instanceof BackupImageView) { + //((BackupImageView) viewToInvalidate).setResourceImageColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } + if (viewToInvalidate instanceof ScrollView) { + if ((changeFlags & FLAG_LISTGLOWCOLOR) != 0) { + AndroidUtilities.setScrollViewEdgeEffectColor((ScrollView) viewToInvalidate, color); + } + } + if (viewToInvalidate instanceof RecyclerListView) { + RecyclerListView recyclerListView = (RecyclerListView) viewToInvalidate; + if ((changeFlags & FLAG_SELECTOR) != 0) { + if (currentKey.equals(Theme.key_listSelector)) { + recyclerListView.setListSelectorColor(color); + } + } + if ((changeFlags & FLAG_FASTSCROLL) != 0) { + recyclerListView.updateFastScrollColors(); + } + if ((changeFlags & FLAG_LISTGLOWCOLOR) != 0) { + recyclerListView.setGlowColor(color); + } + if ((changeFlags & FLAG_SECTIONS) != 0) { + ArrayList headers = recyclerListView.getHeaders(); + if (headers != null) { + for (int a = 0; a < headers.size(); a++) { + processViewColor(headers.get(a), color); + } + } + headers = recyclerListView.getHeadersCache(); + if (headers != null) { + for (int a = 0; a < headers.size(); a++) { + processViewColor(headers.get(a), color); + } + } + View header = recyclerListView.getPinnedHeader(); + if (header != null) { + processViewColor(header, color); + } + } + } else if (viewToInvalidate != null) { + if ((changeFlags & FLAG_SELECTOR) != 0) { + viewToInvalidate.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + } else if ((changeFlags & FLAG_SELECTORWHITE) != 0) { + viewToInvalidate.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + } + } + if (listClasses != null) { + if (viewToInvalidate instanceof ViewGroup) { + ViewGroup viewGroup = (ViewGroup) viewToInvalidate; + int count = viewGroup.getChildCount(); + for (int a = 0; a < count; a++) { + processViewColor(viewGroup.getChildAt(a), color); + } + } + processViewColor(viewToInvalidate, color); + } + currentColor = color; + if (delegate != null) { + delegate.didSetColor(color); + } + if (viewToInvalidate != null) { + viewToInvalidate.invalidate(); + } + } + + private void processViewColor(View child, int color) { + for (int b = 0; b < listClasses.length; b++) { + if (listClasses[b].isInstance(child)) { + child.invalidate(); + boolean passedCheck; + if ((changeFlags & FLAG_CHECKTAG) == 0 || (changeFlags & FLAG_CHECKTAG) != 0 && currentKey.equals(child.getTag())) { + passedCheck = true; + child.invalidate(); + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + Drawable drawable = child.getBackground(); + if (drawable != null) { + if ((changeFlags & FLAG_CELLBACKGROUNDCOLOR) != 0) { + if (drawable instanceof CombinedDrawable) { + Drawable back = ((CombinedDrawable) drawable).getBackground(); + if (back instanceof ColorDrawable) { + ((ColorDrawable) back).setColor(color); + } + } + } else { + if (drawable instanceof CombinedDrawable) { + drawable = ((CombinedDrawable) drawable).getIcon(); + } + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } else if ((changeFlags & FLAG_CELLBACKGROUNDCOLOR) != 0) { + child.setBackgroundColor(color); + } else if ((changeFlags & FLAG_TEXTCOLOR) != 0) { + if (child instanceof TextView) { + ((TextView) child).setTextColor(color); + } + } else if ((changeFlags & FLAG_SERVICEBACKGROUND) != 0) { + Drawable background = child.getBackground(); + if (background != null) { + background.setColorFilter(Theme.colorFilter); + } + } + } else { + passedCheck = false; + } + if (listClassesFieldName != null) { + try { + String key = listClasses[b] + "_" + listClassesFieldName[b]; + Field field = cachedFields.get(key); + if (field == null) { + field = listClasses[b].getDeclaredField(listClassesFieldName[b]); + if (field != null) { + field.setAccessible(true); + cachedFields.put(key, field); + } + } + if (field != null) { + Object object = field.get(child); + if (object != null) { + if (!passedCheck && object instanceof View && !currentKey.equals(((View) object).getTag())) { + continue; + } + if (object instanceof View) { + ((View) object).invalidate(); + } + if ((changeFlags & FLAG_USEBACKGROUNDDRAWABLE) != 0 && object instanceof View) { + object = ((View) object).getBackground(); + } + if ((changeFlags & FLAG_BACKGROUND) != 0 && object instanceof View) { + ((View) object).setBackgroundColor(color); + } else if (object instanceof Switch) { + ((Switch) object).checkColorFilters(); + } else if (object instanceof EditTextCaption) { + if ((changeFlags & FLAG_HINTTEXTCOLOR) != 0) { + ((EditTextCaption) object).setHintColor(color); + ((EditTextCaption) object).setHintTextColor(color); + } else { + ((EditTextCaption) object).setTextColor(color); + } + } else if (object instanceof SimpleTextView) { + if ((changeFlags & FLAG_LINKCOLOR) != 0) { + ((SimpleTextView) object).setLinkTextColor(color); + } else { + ((SimpleTextView) object).setTextColor(color); + } + } else if (object instanceof TextView) { + if ((changeFlags & FLAG_IMAGECOLOR) != 0) { + Drawable[] drawables = ((TextView) object).getCompoundDrawables(); + if (drawables != null) { + for (int a = 0; a < drawables.length; a++) { + drawables[a].setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } + } else if ((changeFlags & FLAG_LINKCOLOR) != 0) { + ((TextView) object).getPaint().linkColor = color; + ((TextView) object).invalidate(); + } else { + ((TextView) object).setTextColor(color); + } + } else if (object instanceof ImageView) { + ((ImageView) object).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } else if (object instanceof BackupImageView) { + Drawable drawable = ((BackupImageView) object).getImageReceiver().getStaticThumb(); + if (drawable instanceof CombinedDrawable) { + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + ((CombinedDrawable) drawable).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } else { + ((CombinedDrawable) drawable).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (drawable != null) { + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (object instanceof Drawable) { + if (object instanceof LetterDrawable) { + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + ((LetterDrawable) object).setBackgroundColor(color); + } else { + ((LetterDrawable) object).setColor(color); + } + } else if (object instanceof CombinedDrawable) { + if ((changeFlags & FLAG_BACKGROUNDFILTER) != 0) { + ((CombinedDrawable) object).getBackground().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } else { + ((CombinedDrawable) object).getIcon().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (object instanceof StateListDrawable || Build.VERSION.SDK_INT >= 21 && object instanceof RippleDrawable) { + Theme.setSelectorDrawableColor((Drawable) object, color, (changeFlags & FLAG_DRAWABLESELECTEDSTATE) != 0); + } else { + ((Drawable) object).setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); + } + } else if (object instanceof CheckBox) { + if ((changeFlags & FLAG_CHECKBOX) != 0) { + ((CheckBox) object).setBackgroundColor(color); + } else if ((changeFlags & FLAG_CHECKBOXCHECK) != 0) { + ((CheckBox) object).setCheckColor(color); + } + } else if (object instanceof GroupCreateCheckBox) { + ((GroupCreateCheckBox) object).updateColors(); + } else if (object instanceof Integer) { + field.set(child, color); + } else if (object instanceof RadioButton) { + if ((changeFlags & FLAG_CHECKBOX) != 0) { + ((RadioButton) object).setBackgroundColor(color); + ((RadioButton) object).invalidate(); + } else if ((changeFlags & FLAG_CHECKBOXCHECK) != 0) { + ((RadioButton) object).setCheckedColor(color); + ((RadioButton) object).invalidate(); + } + } else if (object instanceof TextPaint) { + if ((changeFlags & FLAG_LINKCOLOR) != 0) { + ((TextPaint) object).linkColor = color; + } else { + ((TextPaint) object).setColor(color); + } + } else if (object instanceof LineProgressView) { + if ((changeFlags & FLAG_PROGRESSBAR) != 0) { + ((LineProgressView) object).setProgressColor(color); + } else { + ((LineProgressView) object).setBackColor(color); + } + } else if (object instanceof Paint) { + ((Paint) object).setColor(color); + } + } + } + } catch (Throwable e) { + FileLog.e(e); + } + } else if (child instanceof GroupCreateSpan) { + ((GroupCreateSpan) child).updateColors(); + } + } + } + } + + public String getCurrentKey() { + return currentKey; + } + + public void startEditing() { + currentColor = previousColor = Theme.getColor(currentKey, previousIsDefault); + } + + public int getCurrentColor() { + return currentColor; + } + + public int getSetColor() { + return Theme.getColor(currentKey); + } + + public void setDefaultColor() { + setColor(Theme.getDefaultColor(currentKey), true); + } + + public void setPreviousColor() { + setColor(previousColor, previousIsDefault[0]); + } + + public String getTitle() { + return currentKey; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseFragmentAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseFragmentAdapter.java deleted file mode 100644 index ae94e8f3c05..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseFragmentAdapter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 1.3.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import android.database.DataSetObserver; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; - -public class BaseFragmentAdapter extends BaseAdapter { - - @Override - public int getCount() { - return 0; - } - - @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return 0; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - return null; - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - if (observer != null) { - super.unregisterDataSetObserver(observer); - } - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java index ddae75a8b46..6b0f9fdafcd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseLocationAdapter.java @@ -3,55 +3,55 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.location.Location; +import android.os.AsyncTask; import org.json.JSONArray; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.RequestQueue; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.VolleyError; -import org.telegram.messenger.volley.toolbox.JsonObjectRequest; -import org.telegram.messenger.volley.toolbox.Volley; -import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLog; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.Components.RecyclerListView; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; import java.net.URLEncoder; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; -public class BaseLocationAdapter extends BaseFragmentAdapter { +public abstract class BaseLocationAdapter extends RecyclerListView.SelectionAdapter { public interface BaseLocationAdapterDelegate { void didLoadedSearchResult(ArrayList places); } - private RequestQueue requestQueue; protected boolean searching; protected ArrayList places = new ArrayList<>(); protected ArrayList iconUrls = new ArrayList<>(); private Location lastSearchLocation; private BaseLocationAdapterDelegate delegate; private Timer searchTimer; - - public BaseLocationAdapter() { - requestQueue = Volley.newRequestQueue(ApplicationLoader.applicationContext); - } + private AsyncTask currentTask; public void destroy() { - if (requestQueue != null) { - requestQueue.cancelAll("search"); - requestQueue.stop(); + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; } } @@ -69,7 +69,7 @@ public void searchDelayed(final String query, final Location coordinate) { searchTimer.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } searchTimer = new Timer(); searchTimer.schedule(new TimerTask() { @@ -79,7 +79,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -100,124 +100,238 @@ public void searchGooglePlacesWithQuery(final String query, final Location coord lastSearchLocation = coordinate; if (searching) { searching = false; - requestQueue.cancelAll("search"); + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; + } } try { searching = true; - String url = String.format(Locale.US, "https://api.foursquare.com/v2/venues/search/?v=%s&locale=en&limit=25&client_id=%s&client_secret=%s&ll=%s", BuildVars.FOURSQUARE_API_VERSION, BuildVars.FOURSQUARE_API_ID, BuildVars.FOURSQUARE_API_KEY, String.format(Locale.US, "%f,%f", coordinate.getLatitude(), coordinate.getLongitude())); - if (query != null && query.length() > 0) { - url += "&query=" + URLEncoder.encode(query, "UTF-8"); + final String url = String.format(Locale.US, "https://api.foursquare.com/v2/venues/search/?v=%s&locale=en&limit=25&client_id=%s&client_secret=%s&ll=%s%s", BuildVars.FOURSQUARE_API_VERSION, BuildVars.FOURSQUARE_API_ID, BuildVars.FOURSQUARE_API_KEY, String.format(Locale.US, "%f,%f", coordinate.getLatitude(), coordinate.getLongitude()), query != null && query.length() > 0 ? "&query=" + URLEncoder.encode(query, "UTF-8") : ""); + /* + GOOGLE MAPS + JSONArray result = response.getJSONArray("results"); + + for (int a = 0; a < result.length(); a++) { + try { + JSONObject object = result.getJSONObject(a); + JSONObject location = object.getJSONObject("geometry").getJSONObject("location"); + TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); + venue.geo = new TLRPC.TL_geoPoint(); + venue.geo.lat = location.getDouble("lat"); + venue.geo._long = location.getDouble("lng"); + if (object.has("vicinity")) { + venue.address = object.getString("vicinity").trim(); + } else { + venue.address = String.format(Locale.US, "%f,%f", venue.geo.lat, venue.geo._long); + } + if (object.has("name")) { + venue.title = object.getString("name").trim(); + } + venue.venue_id = object.getString("place_id"); + venue.provider = "google"; + places.add(venue); + } catch (Exception e) { + FileLog.e(e); + } } - JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET, url, null, - new Response.Listener() { - @Override - public void onResponse(JSONObject response) { - try { - places.clear(); - iconUrls.clear(); - /* - GOOGLE MAPS - JSONArray result = response.getJSONArray("results"); + */ + currentTask = new AsyncTask() { - for (int a = 0; a < result.length(); a++) { - try { - JSONObject object = result.getJSONObject(a); - JSONObject location = object.getJSONObject("geometry").getJSONObject("location"); - TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); - venue.geo = new TLRPC.TL_geoPoint(); - venue.geo.lat = location.getDouble("lat"); - venue.geo._long = location.getDouble("lng"); - if (object.has("vicinity")) { - venue.address = object.getString("vicinity").trim(); - } else { - venue.address = String.format(Locale.US, "%f,%f", venue.geo.lat, venue.geo._long); - } - if (object.has("name")) { - venue.title = object.getString("name").trim(); - } - venue.venue_id = object.getString("place_id"); - venue.provider = "google"; - places.add(venue); - } catch (Exception e) { - FileLog.e("tmessages", e); - } + private boolean canRetry = true; + + private String downloadUrlContent(String url) { + boolean canRetry = true; + InputStream httpConnectionStream = null; + boolean done = false; + StringBuilder result = null; + URLConnection httpConnection = null; + try { + URL downloadUrl = new URL(url); + httpConnection = downloadUrl.openConnection(); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + httpConnection.setConnectTimeout(5000); + httpConnection.setReadTimeout(5000); + if (httpConnection instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection; + httpURLConnection.setInstanceFollowRedirects(true); + int status = httpURLConnection.getResponseCode(); + if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) { + String newUrl = httpURLConnection.getHeaderField("Location"); + String cookies = httpURLConnection.getHeaderField("Set-Cookie"); + downloadUrl = new URL(newUrl); + httpConnection = downloadUrl.openConnection(); + httpConnection.setRequestProperty("Cookie", cookies); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + } + } + httpConnection.connect(); + httpConnectionStream = httpConnection.getInputStream(); + } catch (Throwable e) { + if (e instanceof SocketTimeoutException) { + if (ConnectionsManager.isNetworkOnline()) { + canRetry = false; + } + } else if (e instanceof UnknownHostException) { + canRetry = false; + } else if (e instanceof SocketException) { + if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) { + canRetry = false; + } + } else if (e instanceof FileNotFoundException) { + canRetry = false; + } + FileLog.e(e); + } + + if (canRetry) { + try { + if (httpConnection != null && httpConnection instanceof HttpURLConnection) { + int code = ((HttpURLConnection) httpConnection).getResponseCode(); + if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { + //canRetry = false; } - */ - JSONArray result = response.getJSONObject("response").getJSONArray("venues"); + } + } catch (Exception e) { + FileLog.e(e); + } - for (int a = 0; a < result.length(); a++) { + if (httpConnectionStream != null) { + try { + byte[] data = new byte[1024 * 32]; + while (true) { + if (isCancelled()) { + break; + } try { - JSONObject object = result.getJSONObject(a); - String iconUrl = null; - if (object.has("categories")) { - JSONArray categories = object.getJSONArray("categories"); - if (categories.length() > 0) { - JSONObject category = categories.getJSONObject(0); - if (category.has("icon")) { - JSONObject icon = category.getJSONObject("icon"); - iconUrl = String.format(Locale.US, "%s64%s", icon.getString("prefix"), icon.getString("suffix")); - } + int read = httpConnectionStream.read(data); + if (read > 0) { + if (result == null) { + result = new StringBuilder(); } - } - iconUrls.add(iconUrl); - - JSONObject location = object.getJSONObject("location"); - TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); - venue.geo = new TLRPC.TL_geoPoint(); - venue.geo.lat = location.getDouble("lat"); - venue.geo._long = location.getDouble("lng"); - if (location.has("address")) { - venue.address = location.getString("address"); - } else if (location.has("city")) { - venue.address = location.getString("city"); - } else if (location.has("state")) { - venue.address = location.getString("state"); - } else if (location.has("country")) { - venue.address = location.getString("country"); + result.append(new String(data, 0, read, "UTF-8")); + } else if (read == -1) { + done = true; + break; } else { - venue.address = String.format(Locale.US, "%f,%f", venue.geo.lat, venue.geo._long); - } - if (object.has("name")) { - venue.title = object.getString("name"); + break; } - venue.venue_id = object.getString("id"); - venue.provider = "foursquare"; - places.add(venue); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + break; } } - } catch (Exception e) { - FileLog.e("tmessages", e); + } catch (Throwable e) { + FileLog.e(e); } - searching = false; - notifyDataSetChanged(); - if (delegate != null) { - delegate.didLoadedSearchResult(places); + } + + try { + if (httpConnectionStream != null) { + httpConnectionStream.close(); } + } catch (Throwable e) { + FileLog.e(e); } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - FileLog.e("tmessages", "Error: " + error.getMessage()); - searching = false; - notifyDataSetChanged(); - if (delegate != null) { - delegate.didLoadedSearchResult(places); + } + return done ? result.toString() : null; + } + + protected JSONObject doInBackground(Void... voids) { + String code = downloadUrlContent(url); + if (isCancelled()) { + return null; + } + try { + return new JSONObject(code); + } catch (Exception e) { + FileLog.e(e); + } + return null; + } + + @Override + protected void onPostExecute(JSONObject response) { + if (response != null) { + try { + places.clear(); + iconUrls.clear(); + + JSONArray result = response.getJSONObject("response").getJSONArray("venues"); + + for (int a = 0; a < result.length(); a++) { + try { + JSONObject object = result.getJSONObject(a); + String iconUrl = null; + if (object.has("categories")) { + JSONArray categories = object.getJSONArray("categories"); + if (categories.length() > 0) { + JSONObject category = categories.getJSONObject(0); + if (category.has("icon")) { + JSONObject icon = category.getJSONObject("icon"); + iconUrl = String.format(Locale.US, "%s64%s", icon.getString("prefix"), icon.getString("suffix")); + } + } + } + iconUrls.add(iconUrl); + + JSONObject location = object.getJSONObject("location"); + TLRPC.TL_messageMediaVenue venue = new TLRPC.TL_messageMediaVenue(); + venue.geo = new TLRPC.TL_geoPoint(); + venue.geo.lat = location.getDouble("lat"); + venue.geo._long = location.getDouble("lng"); + if (location.has("address")) { + venue.address = location.getString("address"); + } else if (location.has("city")) { + venue.address = location.getString("city"); + } else if (location.has("state")) { + venue.address = location.getString("state"); + } else if (location.has("country")) { + venue.address = location.getString("country"); + } else { + venue.address = String.format(Locale.US, "%f,%f", venue.geo.lat, venue.geo._long); + } + if (object.has("name")) { + venue.title = object.getString("name"); + } + venue.venue_id = object.getString("id"); + venue.provider = "foursquare"; + places.add(venue); + } catch (Exception e) { + FileLog.e(e); + } } + } catch (Exception e) { + FileLog.e(e); } - }); - jsonObjReq.setShouldCache(false); - jsonObjReq.setTag("search"); - requestQueue.add(jsonObjReq); + searching = false; + notifyDataSetChanged(); + if (delegate != null) { + delegate.didLoadedSearchResult(places); + } + } else { + searching = false; + notifyDataSetChanged(); + if (delegate != null) { + delegate.didLoadedSearchResult(places); + } + } + } + }; + currentTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); searching = false; if (delegate != null) { delegate.didLoadedSearchResult(places); } } notifyDataSetChanged(); - } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapter.java deleted file mode 100644 index 9324cd9fb30..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapter.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import org.telegram.SQLite.SQLiteCursor; -import org.telegram.SQLite.SQLitePreparedStatement; -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.MessagesStorage; -import org.telegram.messenger.FileLog; -import org.telegram.tgnet.ConnectionsManager; -import org.telegram.tgnet.RequestDelegate; -import org.telegram.tgnet.TLObject; -import org.telegram.tgnet.TLRPC; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class BaseSearchAdapter extends BaseFragmentAdapter { - - protected static class HashtagObject { - String hashtag; - int date; - } - - protected ArrayList globalSearch = new ArrayList<>(); - private int reqId = 0; - private int lastReqId; - protected String lastFoundUsername = null; - - protected ArrayList hashtags; - protected HashMap hashtagsByText; - protected boolean hashtagsLoadedFromDb = false; - - public void queryServerSearch(final String query, final boolean allowChats, final boolean allowBots) { - if (reqId != 0) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - reqId = 0; - } - if (query == null || query.length() < 5) { - globalSearch.clear(); - lastReqId = 0; - notifyDataSetChanged(); - return; - } - TLRPC.TL_contacts_search req = new TLRPC.TL_contacts_search(); - req.q = query; - req.limit = 50; - final int currentReqId = ++lastReqId; - reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(final TLObject response, final TLRPC.TL_error error) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (currentReqId == lastReqId) { - if (error == null) { - TLRPC.TL_contacts_found res = (TLRPC.TL_contacts_found) response; - globalSearch.clear(); - if (allowChats) { - for (int a = 0; a < res.chats.size(); a++) { - globalSearch.add(res.chats.get(a)); - } - } - for (int a = 0; a < res.users.size(); a++) { - if (!allowBots && res.users.get(a).bot) { - continue; - } - globalSearch.add(res.users.get(a)); - } - lastFoundUsername = query; - notifyDataSetChanged(); - } - } - reqId = 0; - } - }); - } - }, ConnectionsManager.RequestFlagFailOnServerErrors); - } - - public void loadRecentHashtags() { - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT id, date FROM hashtag_recent_v2 WHERE 1"); - final ArrayList arrayList = new ArrayList<>(); - final HashMap hashMap = new HashMap<>(); - while (cursor.next()) { - HashtagObject hashtagObject = new HashtagObject(); - hashtagObject.hashtag = cursor.stringValue(0); - hashtagObject.date = cursor.intValue(1); - arrayList.add(hashtagObject); - hashMap.put(hashtagObject.hashtag, hashtagObject); - } - cursor.dispose(); - Collections.sort(arrayList, new Comparator() { - @Override - public int compare(HashtagObject lhs, HashtagObject rhs) { - if (lhs.date < rhs.date) { - return 1; - } else if (lhs.date > rhs.date) { - return -1; - } else { - return 0; - } - } - }); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - setHashtags(arrayList, hashMap); - } - }); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void addHashtagsFromMessage(String message) { - if (message == null) { - return; - } - boolean changed = false; - Pattern pattern = Pattern.compile("(^|\\s)#[\\w@\\.]+"); - Matcher matcher = pattern.matcher(message); - while (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - if (message.charAt(start) != '@' && message.charAt(start) != '#') { - start++; - } - String hashtag = message.substring(start, end); - if (hashtagsByText == null) { - hashtagsByText = new HashMap<>(); - hashtags = new ArrayList<>(); - } - HashtagObject hashtagObject = hashtagsByText.get(hashtag); - if (hashtagObject == null) { - hashtagObject = new HashtagObject(); - hashtagObject.hashtag = hashtag; - hashtagsByText.put(hashtagObject.hashtag, hashtagObject); - } else { - hashtags.remove(hashtagObject); - } - hashtagObject.date = (int) (System.currentTimeMillis() / 1000); - hashtags.add(0, hashtagObject); - changed = true; - } - if (changed) { - putRecentHashtags(hashtags); - } - } - - private void putRecentHashtags(final ArrayList arrayList) { - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - MessagesStorage.getInstance().getDatabase().beginTransaction(); - SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO hashtag_recent_v2 VALUES(?, ?)"); - for (int a = 0; a < arrayList.size(); a++) { - if (a == 100) { - break; - } - HashtagObject hashtagObject = arrayList.get(a); - state.requery(); - state.bindString(1, hashtagObject.hashtag); - state.bindInteger(2, hashtagObject.date); - state.step(); - } - state.dispose(); - MessagesStorage.getInstance().getDatabase().commitTransaction(); - if (arrayList.size() >= 100) { - MessagesStorage.getInstance().getDatabase().beginTransaction(); - for (int a = 100; a < arrayList.size(); a++) { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE id = '" + arrayList.get(a).hashtag + "'").stepThis().dispose(); - } - MessagesStorage.getInstance().getDatabase().commitTransaction(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void clearRecentHashtags() { - hashtags = new ArrayList<>(); - hashtagsByText = new HashMap<>(); - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE 1").stepThis().dispose(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - protected void setHashtags(ArrayList arrayList, HashMap hashMap) { - hashtags = arrayList; - hashtagsByText = hashMap; - hashtagsLoadedFromDb = true; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java deleted file mode 100644 index 9aa9b0fcbaf..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSearchAdapterRecycler.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import org.telegram.SQLite.SQLiteCursor; -import org.telegram.SQLite.SQLitePreparedStatement; -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.MessagesStorage; -import org.telegram.messenger.support.widget.RecyclerView; -import org.telegram.messenger.FileLog; -import org.telegram.tgnet.ConnectionsManager; -import org.telegram.tgnet.RequestDelegate; -import org.telegram.tgnet.TLObject; -import org.telegram.tgnet.TLRPC; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public abstract class BaseSearchAdapterRecycler extends RecyclerView.Adapter { - - protected static class HashtagObject { - String hashtag; - int date; - } - - protected ArrayList globalSearch = new ArrayList<>(); - private int reqId = 0; - private int lastReqId; - protected String lastFoundUsername = null; - - protected ArrayList hashtags; - protected HashMap hashtagsByText; - protected boolean hashtagsLoadedFromDb = false; - - public void queryServerSearch(final String query, final boolean allowChats) { - if (reqId != 0) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - reqId = 0; - } - if (query == null || query.length() < 5) { - globalSearch.clear(); - lastReqId = 0; - notifyDataSetChanged(); - return; - } - TLRPC.TL_contacts_search req = new TLRPC.TL_contacts_search(); - req.q = query; - req.limit = 50; - final int currentReqId = ++lastReqId; - reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(final TLObject response, final TLRPC.TL_error error) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (currentReqId == lastReqId) { - if (error == null) { - TLRPC.TL_contacts_found res = (TLRPC.TL_contacts_found) response; - globalSearch.clear(); - if (allowChats) { - for (int a = 0; a < res.chats.size(); a++) { - globalSearch.add(res.chats.get(a)); - } - } - for (int a = 0; a < res.users.size(); a++) { - globalSearch.add(res.users.get(a)); - } - lastFoundUsername = query; - notifyDataSetChanged(); - } - } - reqId = 0; - } - }); - } - }, ConnectionsManager.RequestFlagFailOnServerErrors); - } - - public void loadRecentHashtags() { - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT id, date FROM hashtag_recent_v2 WHERE 1"); - final ArrayList arrayList = new ArrayList<>(); - final HashMap hashMap = new HashMap<>(); - while (cursor.next()) { - HashtagObject hashtagObject = new HashtagObject(); - hashtagObject.hashtag = cursor.stringValue(0); - hashtagObject.date = cursor.intValue(1); - arrayList.add(hashtagObject); - hashMap.put(hashtagObject.hashtag, hashtagObject); - } - cursor.dispose(); - Collections.sort(arrayList, new Comparator() { - @Override - public int compare(HashtagObject lhs, HashtagObject rhs) { - if (lhs.date < rhs.date) { - return 1; - } else if (lhs.date > rhs.date) { - return -1; - } else { - return 0; - } - } - }); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - setHashtags(arrayList, hashMap); - } - }); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void addHashtagsFromMessage(CharSequence message) { - if (message == null) { - return; - } - boolean changed = false; - Pattern pattern = Pattern.compile("(^|\\s)#[\\w@\\.]+"); - Matcher matcher = pattern.matcher(message); - while (matcher.find()) { - int start = matcher.start(); - int end = matcher.end(); - if (message.charAt(start) != '@' && message.charAt(start) != '#') { - start++; - } - String hashtag = message.subSequence(start, end).toString(); - if (hashtagsByText == null) { - hashtagsByText = new HashMap<>(); - hashtags = new ArrayList<>(); - } - HashtagObject hashtagObject = hashtagsByText.get(hashtag); - if (hashtagObject == null) { - hashtagObject = new HashtagObject(); - hashtagObject.hashtag = hashtag; - hashtagsByText.put(hashtagObject.hashtag, hashtagObject); - } else { - hashtags.remove(hashtagObject); - } - hashtagObject.date = (int) (System.currentTimeMillis() / 1000); - hashtags.add(0, hashtagObject); - changed = true; - } - if (changed) { - putRecentHashtags(hashtags); - } - } - - private void putRecentHashtags(final ArrayList arrayList) { - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - MessagesStorage.getInstance().getDatabase().beginTransaction(); - SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO hashtag_recent_v2 VALUES(?, ?)"); - for (int a = 0; a < arrayList.size(); a++) { - if (a == 100) { - break; - } - HashtagObject hashtagObject = arrayList.get(a); - state.requery(); - state.bindString(1, hashtagObject.hashtag); - state.bindInteger(2, hashtagObject.date); - state.step(); - } - state.dispose(); - MessagesStorage.getInstance().getDatabase().commitTransaction(); - if (arrayList.size() >= 100) { - MessagesStorage.getInstance().getDatabase().beginTransaction(); - for (int a = 100; a < arrayList.size(); a++) { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE id = '" + arrayList.get(a).hashtag + "'").stepThis().dispose(); - } - MessagesStorage.getInstance().getDatabase().commitTransaction(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - public void clearRecentHashtags() { - hashtags = new ArrayList<>(); - hashtagsByText = new HashMap<>(); - MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { - @Override - public void run() { - try { - MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE 1").stepThis().dispose(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - } - - protected void setHashtags(ArrayList arrayList, HashMap hashMap) { - hashtags = arrayList; - hashtagsByText = hashMap; - hashtagsLoadedFromDb = true; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSectionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSectionsAdapter.java deleted file mode 100644 index b09a094023a..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/BaseSectionsAdapter.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import android.util.SparseArray; -import android.view.View; -import android.view.ViewGroup; - -public abstract class BaseSectionsAdapter extends BaseFragmentAdapter { - - private SparseArray sectionPositionCache; - private SparseArray sectionCache; - private SparseArray sectionCountCache; - private int sectionCount; - private int count; - - private void cleanupCache() { - sectionCache = new SparseArray<>(); - sectionPositionCache = new SparseArray<>(); - sectionCountCache = new SparseArray<>(); - count = -1; - sectionCount = -1; - } - - public BaseSectionsAdapter() { - super(); - cleanupCache(); - } - - @Override - public void notifyDataSetChanged() { - cleanupCache(); - super.notifyDataSetChanged(); - } - - @Override - public void notifyDataSetInvalidated() { - cleanupCache(); - super.notifyDataSetInvalidated(); - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - return isRowEnabled(getSectionForPosition(position), getPositionInSectionForPosition(position)); - } - - @Override - public final long getItemId(int position) { - return position; - } - - @Override - public final int getCount() { - if (count >= 0) { - return count; - } - count = 0; - for (int i = 0; i < internalGetSectionCount(); i++) { - count += internalGetCountForSection(i); - } - return count; - } - - @Override - public final Object getItem(int position) { - return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position)); - } - - @Override - public final int getItemViewType(int position) { - return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position)); - } - - @Override - public final View getView(int position, View convertView, ViewGroup parent) { - return getItemView(getSectionForPosition(position), getPositionInSectionForPosition(position), convertView, parent); - } - - private int internalGetCountForSection(int section) { - Integer cachedSectionCount = sectionCountCache.get(section); - if (cachedSectionCount != null) { - return cachedSectionCount; - } - int sectionCount = getCountForSection(section); - sectionCountCache.put(section, sectionCount); - return sectionCount; - } - - private int internalGetSectionCount() { - if (sectionCount >= 0) { - return sectionCount; - } - sectionCount = getSectionCount(); - return sectionCount; - } - - public final int getSectionForPosition(int position) { - Integer cachedSection = sectionCache.get(position); - if (cachedSection != null) { - return cachedSection; - } - int sectionStart = 0; - for (int i = 0; i < internalGetSectionCount(); i++) { - int sectionCount = internalGetCountForSection(i); - int sectionEnd = sectionStart + sectionCount; - if (position >= sectionStart && position < sectionEnd) { - sectionCache.put(position, i); - return i; - } - sectionStart = sectionEnd; - } - return -1; - } - - public int getPositionInSectionForPosition(int position) { - Integer cachedPosition = sectionPositionCache.get(position); - if (cachedPosition != null) { - return cachedPosition; - } - int sectionStart = 0; - for (int i = 0; i < internalGetSectionCount(); i++) { - int sectionCount = internalGetCountForSection(i); - int sectionEnd = sectionStart + sectionCount; - if (position >= sectionStart && position < sectionEnd) { - int positionInSection = position - sectionStart; - sectionPositionCache.put(position, positionInSection); - return positionInSection; - } - sectionStart = sectionEnd; - } - return -1; - } - - public abstract int getSectionCount(); - public abstract int getCountForSection(int section); - public abstract boolean isRowEnabled(int section, int row); - public abstract int getItemViewType(int section, int position); - public abstract Object getItem(int section, int position); - public abstract View getItemView(int section, int position, View convertView, ViewGroup parent); - public abstract View getSectionHeaderView(int section, View convertView, ViewGroup parent); -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java index ade259fc501..9b72f1bb340 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/ContactsAdapter.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; @@ -14,20 +14,22 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.ContactsController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.ui.Cells.DividerCell; -import org.telegram.ui.Cells.GreySectionCell; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.LetterSectionCell; import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.UserCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; -public class ContactsAdapter extends BaseSectionsAdapter { +public class ContactsAdapter extends RecyclerListView.SectionsAdapter { private Context mContext; private int onlyUsers; @@ -53,7 +55,6 @@ public void setIsScrolling(boolean value) { scrolling = value; } - @Override public Object getItem(int section, int position) { HashMap> usersSectionsDict = onlyUsers == 2 ? ContactsController.getInstance().usersMutualSectionsDict : ContactsController.getInstance().usersSectionsDict; ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; @@ -86,7 +87,7 @@ public Object getItem(int section, int position) { } @Override - public boolean isRowEnabled(int section, int row) { + public boolean isEnabled(int section, int row) { HashMap> usersSectionsDict = onlyUsers == 2 ? ContactsController.getInstance().usersMutualSectionsDict : ContactsController.getInstance().usersSectionsDict; ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; @@ -166,99 +167,105 @@ public int getCountForSection(int section) { } @Override - public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { + public View getSectionHeaderView(int section, View view) { HashMap> usersSectionsDict = onlyUsers == 2 ? ContactsController.getInstance().usersMutualSectionsDict : ContactsController.getInstance().usersSectionsDict; ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; - if (convertView == null) { - convertView = new LetterSectionCell(mContext); + if (view == null) { + view = new LetterSectionCell(mContext); } + LetterSectionCell cell = (LetterSectionCell) view; if (onlyUsers != 0 && !isAdmin) { if (section < sortedUsersSectionsArray.size()) { - ((LetterSectionCell) convertView).setLetter(sortedUsersSectionsArray.get(section)); + cell.setLetter(sortedUsersSectionsArray.get(section)); } else { - ((LetterSectionCell) convertView).setLetter(""); + cell.setLetter(""); } } else { if (section == 0) { - ((LetterSectionCell) convertView).setLetter(""); + cell.setLetter(""); } else if (section - 1 < sortedUsersSectionsArray.size()) { - ((LetterSectionCell) convertView).setLetter(sortedUsersSectionsArray.get(section - 1)); + cell.setLetter(sortedUsersSectionsArray.get(section - 1)); } else { - ((LetterSectionCell) convertView).setLetter(""); + cell.setLetter(""); } } - return convertView; + return view; } @Override - public View getItemView(int section, int position, View convertView, ViewGroup parent) { - int type = getItemViewType(section, position); - if (type == 4) { - if (convertView == null) { - convertView = new DividerCell(mContext); - convertView.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 28 : 72), 0, AndroidUtilities.dp(LocaleController.isRTL ? 72 : 28), 0); - } - } else if (type == 3) { - if (convertView == null) { - convertView = new GreySectionCell(mContext); - ((GreySectionCell) convertView).setText(LocaleController.getString("Contacts", R.string.Contacts).toUpperCase()); - } - } else if (type == 2) { - if (convertView == null) { - convertView = new TextCell(mContext); - } - TextCell actionCell = (TextCell) convertView; - if (needPhonebook) { - actionCell.setTextAndIcon(LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite); - } else if (isAdmin) { - actionCell.setTextAndIcon(LocaleController.getString("InviteToGroupByLink", R.string.InviteToGroupByLink), R.drawable.menu_invite); - } else { - if (position == 0) { - actionCell.setTextAndIcon(LocaleController.getString("NewGroup", R.string.NewGroup), R.drawable.menu_newgroup); - } else if (position == 1) { - actionCell.setTextAndIcon(LocaleController.getString("NewSecretChat", R.string.NewSecretChat), R.drawable.menu_secret); - } else if (position == 2) { - actionCell.setTextAndIcon(LocaleController.getString("NewChannel", R.string.NewChannel), R.drawable.menu_broadcast); - } - } - } else if (type == 1) { - if (convertView == null) { - convertView = new TextCell(mContext); - } - ContactsController.Contact contact = ContactsController.getInstance().phoneBookContacts.get(position); - TextCell textCell = (TextCell) convertView; - if (contact.first_name != null && contact.last_name != null) { - textCell.setText(contact.first_name + " " + contact.last_name); - } else if (contact.first_name != null && contact.last_name == null) { - textCell.setText(contact.first_name); - } else { - textCell.setText(contact.last_name); - } - } else if (type == 0) { - if (convertView == null) { - convertView = new UserCell(mContext, 58, 1, false); - ((UserCell) convertView).setStatusColors(0xffa8a8a8, 0xff3b84c0); - } + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new UserCell(mContext, 58, 1, false); + break; + case 1: + view = new TextCell(mContext); + break; + case 2: + view = new GraySectionCell(mContext); + ((GraySectionCell) view).setText(LocaleController.getString("Contacts", R.string.Contacts).toUpperCase()); + break; + case 3: + default: + view = new DividerCell(mContext); + view.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 28 : 72), 0, AndroidUtilities.dp(LocaleController.isRTL ? 72 : 28), 0); + break; + } + return new RecyclerListView.Holder(view); + } - HashMap> usersSectionsDict = onlyUsers == 2 ? ContactsController.getInstance().usersMutualSectionsDict : ContactsController.getInstance().usersSectionsDict; - ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; + @Override + public void onBindViewHolder(int section, int position, RecyclerView.ViewHolder holder) { + switch (holder.getItemViewType()) { + case 0: + UserCell userCell = (UserCell) holder.itemView; + HashMap> usersSectionsDict = onlyUsers == 2 ? ContactsController.getInstance().usersMutualSectionsDict : ContactsController.getInstance().usersSectionsDict; + ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; - ArrayList arr = usersSectionsDict.get(sortedUsersSectionsArray.get(section - (onlyUsers != 0 && !isAdmin ? 0 : 1))); - TLRPC.User user = MessagesController.getInstance().getUser(arr.get(position).user_id); - ((UserCell) convertView).setData(user, null, null, 0); - if (checkedMap != null) { - ((UserCell) convertView).setChecked(checkedMap.containsKey(user.id), !scrolling); - } - if (ignoreUsers != null) { - if (ignoreUsers.containsKey(user.id)) { - convertView.setAlpha(0.5f); + ArrayList arr = usersSectionsDict.get(sortedUsersSectionsArray.get(section - (onlyUsers != 0 && !isAdmin ? 0 : 1))); + TLRPC.User user = MessagesController.getInstance().getUser(arr.get(position).user_id); + userCell.setData(user, null, null, 0); + if (checkedMap != null) { + userCell.setChecked(checkedMap.containsKey(user.id), !scrolling); + } + if (ignoreUsers != null) { + if (ignoreUsers.containsKey(user.id)) { + userCell.setAlpha(0.5f); + } else { + userCell.setAlpha(1.0f); + } + } + break; + case 1: + TextCell textCell = (TextCell) holder.itemView; + if (section == 0) { + if (needPhonebook) { + textCell.setTextAndIcon(LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite); + } else if (isAdmin) { + textCell.setTextAndIcon(LocaleController.getString("InviteToGroupByLink", R.string.InviteToGroupByLink), R.drawable.menu_invite); + } else { + if (position == 0) { + textCell.setTextAndIcon(LocaleController.getString("NewGroup", R.string.NewGroup), R.drawable.menu_newgroup); + } else if (position == 1) { + textCell.setTextAndIcon(LocaleController.getString("NewSecretChat", R.string.NewSecretChat), R.drawable.menu_secret); + } else if (position == 2) { + textCell.setTextAndIcon(LocaleController.getString("NewChannel", R.string.NewChannel), R.drawable.menu_broadcast); + } + } } else { - convertView.setAlpha(1.0f); + ContactsController.Contact contact = ContactsController.getInstance().phoneBookContacts.get(position); + if (contact.first_name != null && contact.last_name != null) { + textCell.setText(contact.first_name + " " + contact.last_name); + } else if (contact.first_name != null && contact.last_name == null) { + textCell.setText(contact.first_name); + } else { + textCell.setText(contact.last_name); + } } - } + break; } - return convertView; } @Override @@ -267,29 +274,35 @@ public int getItemViewType(int section, int position) { ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; if (onlyUsers != 0 && !isAdmin) { ArrayList arr = usersSectionsDict.get(sortedUsersSectionsArray.get(section)); - return position < arr.size() ? 0 : 4; + return position < arr.size() ? 0 : 3; } else { if (section == 0) { - if (needPhonebook || isAdmin) { - if (position == 1) { - return 3; - } - } else { - if (position == 3) { - return 3; - } + if ((needPhonebook || isAdmin) && position == 1 || position == 3) { + return 2; } - return 2; } else if (section - 1 < sortedUsersSectionsArray.size()) { ArrayList arr = usersSectionsDict.get(sortedUsersSectionsArray.get(section - 1)); - return position < arr.size() ? 0 : 4; + return position < arr.size() ? 0 : 3; } } return 1; } @Override - public int getViewTypeCount() { - return 5; + public String getLetter(int position) { + ArrayList sortedUsersSectionsArray = onlyUsers == 2 ? ContactsController.getInstance().sortedUsersMutualSectionsArray : ContactsController.getInstance().sortedUsersSectionsArray; + int section = getSectionForPosition(position); + if (section == -1) { + section = sortedUsersSectionsArray.size() - 1; + } + if (section > 0 && section <= sortedUsersSectionsArray.size()) { + return sortedUsersSectionsArray.get(section - 1); + } + return null; + } + + @Override + public int getPositionForScrollProgress(float progress) { + return (int) (getItemCount() * progress); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountryAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountryAdapter.java deleted file mode 100644 index 4183abe38e2..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountryAdapter.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.ApplicationLoader; -import org.telegram.ui.Cells.DividerCell; -import org.telegram.ui.Cells.LetterSectionCell; -import org.telegram.ui.Cells.TextSettingsCell; - -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; - -public class CountryAdapter extends BaseSectionsAdapter { - - public static class Country { - public String name; - public String code; - public String shortname; - } - - private Context mContext; - private HashMap> countries = new HashMap<>(); - private ArrayList sortedCountries = new ArrayList<>(); - - public CountryAdapter(Context context) { - mContext = context; - - try { - InputStream stream = ApplicationLoader.applicationContext.getResources().getAssets().open("countries.txt"); - BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); - String line; - while ((line = reader.readLine()) != null) { - String[] args = line.split(";"); - Country c = new Country(); - c.name = args[2]; - c.code = args[0]; - c.shortname = args[1]; - String n = c.name.substring(0, 1).toUpperCase(); - ArrayList arr = countries.get(n); - if (arr == null) { - arr = new ArrayList<>(); - countries.put(n, arr); - sortedCountries.add(n); - } - arr.add(c); - } - reader.close(); - stream.close(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - - Collections.sort(sortedCountries, new Comparator() { - @Override - public int compare(String lhs, String rhs) { - return lhs.compareTo(rhs); - } - }); - - for (ArrayList arr : countries.values()) { - Collections.sort(arr, new Comparator() { - @Override - public int compare(Country country, Country country2) { - return country.name.compareTo(country2.name); - } - }); - } - } - - public HashMap> getCountries() { - return countries; - } - - @Override - public Country getItem(int section, int position) { - if (section < 0 || section >= sortedCountries.size()) { - return null; - } - ArrayList arr = countries.get(sortedCountries.get(section)); - if (position < 0 || position >= arr.size()) { - return null; - } - return arr.get(position); - } - - @Override - public boolean isRowEnabled(int section, int row) { - ArrayList arr = countries.get(sortedCountries.get(section)); - return row < arr.size(); - } - - @Override - public int getSectionCount() { - return sortedCountries.size(); - } - - @Override - public int getCountForSection(int section) { - int count = countries.get(sortedCountries.get(section)).size(); - if (section != sortedCountries.size() - 1) { - count++; - } - return count; - } - - @Override - public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new LetterSectionCell(mContext); - ((LetterSectionCell) convertView).setCellHeight(AndroidUtilities.dp(48)); - } - ((LetterSectionCell) convertView).setLetter(sortedCountries.get(section).toUpperCase()); - return convertView; - } - - @Override - public View getItemView(int section, int position, View convertView, ViewGroup parent) { - int type = getItemViewType(section, position); - if (type == 1) { - if (convertView == null) { - convertView = new DividerCell(mContext); - convertView.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 24 : 72), 0, AndroidUtilities.dp(LocaleController.isRTL ? 72 : 24), 0); - } - } else if (type == 0) { - if (convertView == null) { - convertView = new TextSettingsCell(mContext); - convertView.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 16 : 54), 0, AndroidUtilities.dp(LocaleController.isRTL ? 54 : 16), 0); - } - - ArrayList arr = countries.get(sortedCountries.get(section)); - Country c = arr.get(position); - ((TextSettingsCell) convertView).setTextAndValue(c.name, "+" + c.code, false); - } - return convertView; - } - - @Override - public int getItemViewType(int section, int position) { - ArrayList arr = countries.get(sortedCountries.get(section)); - return position < arr.size() ? 0 : 1; - } - - @Override - public int getViewTypeCount() { - return 2; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountrySearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountrySearchAdapter.java deleted file mode 100644 index d3cd6d2b971..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/CountrySearchAdapter.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Adapters; - -import android.content.Context; -import android.view.View; -import android.view.ViewGroup; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.Utilities; -import org.telegram.ui.Adapters.CountryAdapter.Country; -import org.telegram.ui.Cells.TextSettingsCell; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Timer; -import java.util.TimerTask; - -public class CountrySearchAdapter extends BaseFragmentAdapter { - - private Context mContext; - private Timer searchTimer; - private ArrayList searchResult; - private HashMap> countries; - - public CountrySearchAdapter(Context context, HashMap> countries) { - mContext = context; - this.countries = countries; - } - - public void search(final String query) { - if (query == null) { - searchResult = null; - } else { - try { - if (searchTimer != null) { - searchTimer.cancel(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - searchTimer = new Timer(); - searchTimer.schedule(new TimerTask() { - @Override - public void run() { - try { - searchTimer.cancel(); - searchTimer = null; - } catch (Exception e) { - FileLog.e("tmessages", e); - } - processSearch(query); - } - }, 100, 300); - } - } - - private void processSearch(final String query) { - Utilities.searchQueue.postRunnable(new Runnable() { - @Override - public void run() { - - String q = query.trim().toLowerCase(); - if (q.length() == 0) { - updateSearchResults(new ArrayList()); - return; - } - ArrayList resultArray = new ArrayList<>(); - - String n = query.substring(0, 1); - ArrayList arr = countries.get(n.toUpperCase()); - if (arr != null) { - for (Country c : arr) { - if (c.name.toLowerCase().startsWith(query)) { - resultArray.add(c); - } - } - } - - updateSearchResults(resultArray); - } - }); - } - - private void updateSearchResults(final ArrayList arrCounties) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - searchResult = arrCounties; - notifyDataSetChanged(); - } - }); - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { - return true; - } - - @Override - public int getCount() { - if (searchResult == null) { - return 0; - } - return searchResult.size(); - } - - @Override - public Country getItem(int i) { - if (i < 0 || i >= searchResult.size()) { - return null; - } - return searchResult.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new TextSettingsCell(mContext); - } - - Country c = searchResult.get(i); - ((TextSettingsCell) view).setTextAndValue(c.name, "+" + c.code, i != searchResult.size() - 1); - - return view; - } - - @Override - public int getItemViewType(int i) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return searchResult == null || searchResult.size() == 0; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java index 46557a3acea..c456bddf066 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; @@ -18,23 +18,17 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.DialogCell; import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; -public class DialogsAdapter extends RecyclerView.Adapter { +public class DialogsAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private int dialogsType; private long openedDialogId; private int currentCount; - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - public DialogsAdapter(Context context, int type) { mContext = context; dialogsType = type; @@ -89,8 +83,8 @@ public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { } @Override - public long getItemId(int i) { - return i; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() != 1; } @Override @@ -102,7 +96,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewT view = new LoadingCell(mContext); } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java index b123939a53a..b406a5ecb05 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsSearchAdapter.java @@ -3,13 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -33,8 +36,9 @@ import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.DialogCell; -import org.telegram.ui.Cells.GreySectionCell; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.HashtagSearchCell; import org.telegram.ui.Cells.HintDialogCell; import org.telegram.ui.Cells.LoadingCell; @@ -50,7 +54,7 @@ import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; -public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { +public class DialogsSearchAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private Timer searchTimer; @@ -67,17 +71,12 @@ public class DialogsSearchAdapter extends BaseSearchAdapterRecycler { private String lastMessagesSearchString; private int lastSearchId = 0; private int dialogsType; + private SearchAdapterHelper searchAdapterHelper; + private RecyclerListView innerListView; private ArrayList recentSearchObjects = new ArrayList<>(); private HashMap recentSearchObjectsById = new HashMap<>(); - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - private class DialogSearchResult { public TLObject object; public int date; @@ -96,7 +95,7 @@ public interface DialogsSearchAdapterDelegate { void needRemoveHint(int did); } - private class CategoryAdapterRecycler extends RecyclerView.Adapter { + private class CategoryAdapterRecycler extends RecyclerListView.SelectionAdapter { public void setIndex(int value) { notifyDataSetChanged(); @@ -106,7 +105,12 @@ public void setIndex(int value) { public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = new HintDialogCell(mContext); view.setLayoutParams(new RecyclerView.LayoutParams(AndroidUtilities.dp(80), AndroidUtilities.dp(100))); - return new Holder(view); + return new RecyclerListView.Holder(view); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; } @Override @@ -145,6 +149,24 @@ public int getItemCount() { } public DialogsSearchAdapter(Context context, int messagesSearch, int type) { + searchAdapterHelper = new SearchAdapterHelper(); + searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { + @Override + public void onDataSetChanged() { + notifyDataSetChanged(); + } + + @Override + public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { + for (int a = 0; a < arrayList.size(); a++) { + searchResultHashtags.add(arrayList.get(a).hashtag); + } + if (delegate != null) { + delegate.searchStateChanged(false); + } + notifyDataSetChanged(); + } + }); mContext = context; needMessagesSearch = messagesSearch; dialogsType = type; @@ -152,6 +174,10 @@ public DialogsSearchAdapter(Context context, int messagesSearch, int type) { SearchQuery.loadHints(true); } + public RecyclerListView getInnerListView() { + return innerListView; + } + public void setDelegate(DialogsSearchAdapterDelegate delegate) { this.delegate = delegate; } @@ -380,7 +406,7 @@ public void run() { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -412,7 +438,7 @@ public void run() { state.step(); state.dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -428,12 +454,16 @@ public void run() { try { MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM search_recent WHERE 1").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); } + public void addHashtagsFromMessage(CharSequence message) { + searchAdapterHelper.addHashtagsFromMessage(message); + } + private void setRecentSearch(ArrayList arrayList, HashMap hashMap) { recentSearchObjects = arrayList; recentSearchObjectsById = hashMap; @@ -481,7 +511,7 @@ public void run() { int resultCount = 0; HashMap dialogsResult = new HashMap<>(); - SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT did, date FROM dialogs ORDER BY date DESC LIMIT 400"); + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT did, date FROM dialogs ORDER BY date DESC LIMIT 600"); while (cursor.next()) { long id = cursor.longValue(0); DialogSearchResult dialogSearchResult = new DialogSearchResult(); @@ -651,7 +681,8 @@ public void run() { user.status.expires = cursor.intValue(7); } if (found == 1) { - dialogSearchResult.name = AndroidUtilities.replaceTags("" + ContactsController.formatName(user.first_name, user.last_name) + ""); + dialogSearchResult.name = new SpannableStringBuilder(ContactsController.formatName(user.first_name, user.last_name)); + ((SpannableStringBuilder) dialogSearchResult.name).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_secretName)), 0, dialogSearchResult.name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { dialogSearchResult.name = AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q); } @@ -742,7 +773,7 @@ public int compare(DialogSearchResult lhs, DialogSearchResult rhs) { updateSearchResults(resultArray, resultArrayNames, encUsers, searchId); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -777,28 +808,15 @@ public void run() { } public boolean isGlobalSearch(int i) { - return i > searchResult.size() && i <= globalSearch.size() + searchResult.size(); + return i > searchResult.size() && i <= searchAdapterHelper.getGlobalSearch().size() + searchResult.size(); } - @Override public void clearRecentHashtags() { - super.clearRecentHashtags(); + searchAdapterHelper.clearRecentHashtags(); searchResultHashtags.clear(); notifyDataSetChanged(); } - @Override - protected void setHashtags(ArrayList arrayList, HashMap hashMap) { - super.setHashtags(arrayList, hashMap); - for (int a = 0; a < arrayList.size(); a++) { - searchResultHashtags.add(arrayList.get(a).hashtag); - } - if (delegate != null) { - delegate.searchStateChanged(false); - } - notifyDataSetChanged(); - } - public void searchDialogs(final String query) { if (query != null && lastSearchText != null && query.equals(lastSearchText)) { return; @@ -810,40 +828,37 @@ public void searchDialogs(final String query) { searchTimer = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (query == null || query.length() == 0) { - hashtagsLoadedFromDb = false; + searchAdapterHelper.unloadRecentHashtags(); searchResult.clear(); searchResultNames.clear(); searchResultHashtags.clear(); if (needMessagesSearch != 2) { - queryServerSearch(null, true); + searchAdapterHelper.queryServerSearch(null, true, true, true); } searchMessagesInternal(null); notifyDataSetChanged(); } else { if (needMessagesSearch != 2 && (query.startsWith("#") && query.length() == 1)) { messagesSearchEndReached = true; - if (!hashtagsLoadedFromDb) { - loadRecentHashtags(); + if (searchAdapterHelper.loadRecentHashtags()) { + searchResultMessages.clear(); + searchResultHashtags.clear(); + ArrayList hashtags = searchAdapterHelper.getHashtags(); + for (int a = 0; a < hashtags.size(); a++) { + searchResultHashtags.add(hashtags.get(a).hashtag); + } + if (delegate != null) { + delegate.searchStateChanged(false); + } + } else { if (delegate != null) { delegate.searchStateChanged(true); } - notifyDataSetChanged(); - return; - } - searchResultMessages.clear(); - searchResultHashtags.clear(); - - for (int a = 0; a < hashtags.size(); a++) { - searchResultHashtags.add(hashtags.get(a).hashtag); - } - if (delegate != null) { - delegate.searchStateChanged(false); } notifyDataSetChanged(); - return; } else { searchResultHashtags.clear(); notifyDataSetChanged(); @@ -858,14 +873,14 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } searchDialogsInternal(query, searchId); AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (needMessagesSearch != 2) { - queryServerSearch(query, true); + searchAdapterHelper.queryServerSearch(query, true, true, true); } searchMessagesInternal(query); } @@ -884,7 +899,7 @@ public int getItemCount() { return searchResultHashtags.size() + 1; } int count = searchResult.size(); - int globalCount = globalSearch.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); int messagesCount = searchResultMessages.size(); if (globalCount != 0) { count += globalCount + 1; @@ -923,6 +938,7 @@ public Object getItem(int i) { return null; } } + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); int localCount = searchResult.size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; @@ -941,16 +957,21 @@ public long getItemId(int i) { return i; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type != 1 && type != 3; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case 0: view = new ProfileSearchCell(mContext); - view.setBackgroundResource(R.drawable.list_selector); break; case 1: - view = new GreySectionCell(mContext); + view = new GraySectionCell(mContext); break; case 2: view = new DialogCell(mContext); @@ -1002,13 +1023,14 @@ public boolean onItemClick(View view, int position) { } }); view = horizontalListView; + innerListView = horizontalListView; } if (viewType == 5) { view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(100))); } else { view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -1044,6 +1066,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { isRecent = true; cell.useSeparator = position != getItemCount() - 1; } else { + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); int localCount = searchResult.size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; cell.useSeparator = (position != getItemCount() - 1 && position != localCount - 1 && position != localCount + globalCount - 1); @@ -1057,15 +1080,16 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } } } else if (position > searchResult.size() && un != null) { - String foundUserName = lastFoundUsername; + String foundUserName = searchAdapterHelper.getLastFoundUsername(); if (foundUserName.startsWith("@")) { foundUserName = foundUserName.substring(1); } try { - username = AndroidUtilities.replaceTags(String.format("@%s%s", un.substring(0, foundUserName.length()), un.substring(foundUserName.length()))); + username = new SpannableStringBuilder(un); + ((SpannableStringBuilder) username).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), 0, foundUserName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } catch (Exception e) { username = un; - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1073,7 +1097,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { break; } case 1: { - GreySectionCell cell = (GreySectionCell) holder.itemView; + GraySectionCell cell = (GraySectionCell) holder.itemView; if (isRecentSearchDisplayed()) { int offset = (!SearchQuery.hints.isEmpty() ? 2 : 0); if (position < offset) { @@ -1083,7 +1107,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } } else if (!searchResultHashtags.isEmpty()) { cell.setText(LocaleController.getString("Hashtags", R.string.Hashtags).toUpperCase()); - } else if (!globalSearch.isEmpty() && position == searchResult.size()) { + } else if (!searchAdapterHelper.getGlobalSearch().isEmpty() && position == searchResult.size()) { cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); } else { cell.setText(LocaleController.getString("SearchMessages", R.string.SearchMessages)); @@ -1130,6 +1154,7 @@ public int getItemViewType(int i) { if (!searchResultHashtags.isEmpty()) { return i == 0 ? 1 : 4; } + ArrayList globalSearch = searchAdapterHelper.getGlobalSearch(); int localCount = searchResult.size(); int globalCount = globalSearch.isEmpty() ? 0 : globalSearch.size() + 1; int messagesCount = searchResultMessages.isEmpty() ? 0 : searchResultMessages.size() + 1; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java index e695819d54f..b2c064fc6d7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DrawerLayoutAdapter.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; @@ -11,98 +11,82 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.DrawerActionCell; import org.telegram.ui.Cells.DividerCell; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.DrawerProfileCell; +import org.telegram.ui.Components.RecyclerListView; -public class DrawerLayoutAdapter extends BaseAdapter { +import java.util.ArrayList; + +public class DrawerLayoutAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; + private ArrayList items = new ArrayList<>(11); public DrawerLayoutAdapter(Context context) { mContext = context; + Theme.createDialogsResources(context); + resetItems(); } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return !(i == 1 || i == 5); - } - - @Override - public int getCount() { - return UserConfig.isClientActivated() ? 10 : 0; - } - - @Override - public Object getItem(int i) { - return null; + public int getItemCount() { + return items.size(); } @Override - public long getItemId(int i) { - return i; + public void notifyDataSetChanged() { + resetItems(); + super.notifyDataSetChanged(); } @Override - public boolean hasStableIds() { - return true; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == 3; } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - DrawerProfileCell drawerProfileCell; - if (view == null) { - view = drawerProfileCell = new DrawerProfileCell(mContext); - } else { - drawerProfileCell = (DrawerProfileCell) view; - } - drawerProfileCell.setUser(MessagesController.getInstance().getUser(UserConfig.getClientUserId())); - } else if (type == 1) { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new DrawerProfileCell(mContext); + break; + case 1: + default: view = new EmptyCell(mContext, AndroidUtilities.dp(8)); - } - } else if (type == 2) { - if (view == null) { + break; + case 2: view = new DividerCell(mContext); - } - } else if (type == 3) { - if (view == null) { + break; + case 3: view = new DrawerActionCell(mContext); - } - DrawerActionCell actionCell = (DrawerActionCell) view; - if (i == 2) { - actionCell.setTextAndIcon(LocaleController.getString("NewGroup", R.string.NewGroup), R.drawable.menu_newgroup); - } else if (i == 3) { - actionCell.setTextAndIcon(LocaleController.getString("NewSecretChat", R.string.NewSecretChat), R.drawable.menu_secret); - } else if (i == 4) { - actionCell.setTextAndIcon(LocaleController.getString("NewChannel", R.string.NewChannel), R.drawable.menu_broadcast); - } else if (i == 6) { - actionCell.setTextAndIcon(LocaleController.getString("Contacts", R.string.Contacts), R.drawable.menu_contacts); - } else if (i == 7) { - actionCell.setTextAndIcon(LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite); - } else if (i == 8) { - actionCell.setTextAndIcon(LocaleController.getString("Settings", R.string.Settings), R.drawable.menu_settings); - } else if (i == 9) { - actionCell.setTextAndIcon(LocaleController.getString("TelegramFaq", R.string.TelegramFaq), R.drawable.menu_help); - } + break; } + view.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } - return view; + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + ((DrawerProfileCell) holder.itemView).setUser(MessagesController.getInstance().getUser(UserConfig.getClientUserId())); + holder.itemView.setBackgroundColor(Theme.getColor(Theme.key_avatar_backgroundActionBarBlue)); + break; + case 3: + items.get(position).bind((DrawerActionCell) holder.itemView); + break; + } } @Override @@ -117,13 +101,47 @@ public int getItemViewType(int i) { return 3; } - @Override - public int getViewTypeCount() { - return 4; + private void resetItems() { + items.clear(); + if (!UserConfig.isClientActivated()) { + return; + } + items.add(null); // profile + items.add(null); // padding + items.add(new Item(2, LocaleController.getString("NewGroup", R.string.NewGroup), R.drawable.menu_newgroup)); + items.add(new Item(3, LocaleController.getString("NewSecretChat", R.string.NewSecretChat), R.drawable.menu_secret)); + items.add(new Item(4, LocaleController.getString("NewChannel", R.string.NewChannel), R.drawable.menu_broadcast)); + items.add(null); // divider + items.add(new Item(6, LocaleController.getString("Contacts", R.string.Contacts), R.drawable.menu_contacts)); + if (MessagesController.getInstance().callsEnabled) { + items.add(new Item(10, LocaleController.getString("Calls", R.string.Calls), R.drawable.menu_calls)); + } + items.add(new Item(7, LocaleController.getString("InviteFriends", R.string.InviteFriends), R.drawable.menu_invite)); + items.add(new Item(8, LocaleController.getString("Settings", R.string.Settings), R.drawable.menu_settings)); + items.add(new Item(9, LocaleController.getString("TelegramFaq", R.string.TelegramFaq), R.drawable.menu_help)); } - @Override - public boolean isEmpty() { - return !UserConfig.isClientActivated(); + public int getId(int position) { + if (position < 0 || position >= items.size()) { + return -1; + } + Item item = items.get(position); + return item != null ? item.id : -1; + } + + private class Item { + public int icon; + public String text; + public int id; + + public Item(int id, String text, int icon) { + this.icon = icon; + this.id = id; + this.text = text; + } + + public void bind(DrawerActionCell actionCell) { + actionCell.setTextAndIcon(text, icon); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java index a94c1e4308f..102591a6329 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivityAdapter.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; @@ -15,13 +15,15 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.EmptyCell; -import org.telegram.ui.Cells.GreySectionCell; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.LocationCell; import org.telegram.ui.Cells.LocationLoadingCell; import org.telegram.ui.Cells.LocationPoweredCell; import org.telegram.ui.Cells.SendLocationCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.Locale; @@ -67,7 +69,7 @@ private void updateCell() { } @Override - public int getCount() { + public int getItemCount() { if (searching || !searching && places.isEmpty()) { return 4; } @@ -75,48 +77,54 @@ public int getCount() { } @Override - public boolean isEmpty() { - return false; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (i == 0) { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: view = new EmptyCell(mContext); - } - ((EmptyCell) view).setHeight(overScrollHeight); - } else if (i == 1) { - if (view == null) { + break; + case 1: view = new SendLocationCell(mContext); - } - sendLocationCell = (SendLocationCell) view; - updateCell(); - return view; - } else if (i == 2) { - if (view == null) { - view = new GreySectionCell(mContext); - } - ((GreySectionCell) view).setText(LocaleController.getString("NearbyPlaces", R.string.NearbyPlaces)); - } else if (searching || !searching && places.isEmpty()) { - if (view == null) { + break; + case 2: + view = new GraySectionCell(mContext); + break; + case 3: + view = new LocationCell(mContext); + break; + case 4: view = new LocationLoadingCell(mContext); - } - ((LocationLoadingCell) view).setLoading(searching); - } else if (i == places.size() + 3) { - if (view == null) { + break; + case 5: + default: view = new LocationPoweredCell(mContext); - } - } else { - if (view == null) { - view = new LocationCell(mContext); - } - ((LocationCell) view).setLocation(places.get(i - 3), iconUrls.get(i - 3), true); + break; } - return view; + return new RecyclerListView.Holder(view); } @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + ((EmptyCell) holder.itemView).setHeight(overScrollHeight); + break; + case 1: + sendLocationCell = (SendLocationCell) holder.itemView; + updateCell(); + break; + case 2: + ((GraySectionCell) holder.itemView).setText(LocaleController.getString("NearbyPlaces", R.string.NearbyPlaces)); + break; + case 3: + ((LocationCell) holder.itemView).setLocation(places.get(position - 3), iconUrls.get(position - 3), true); + break; + case 4: + ((LocationLoadingCell) holder.itemView).setLoading(searching); + break; + } + } + public TLRPC.TL_messageMediaVenue getItem(int i) { if (i > 2 && i < places.size() + 3) { return places.get(i - 3); @@ -124,11 +132,6 @@ public TLRPC.TL_messageMediaVenue getItem(int i) { return null; } - @Override - public long getItemId(int i) { - return i; - } - @Override public int getItemViewType(int position) { if (position == 0) { @@ -146,22 +149,8 @@ public int getItemViewType(int position) { } @Override - public int getViewTypeCount() { - return 6; - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); return !(position == 2 || position == 0 || position == 3 && (searching || !searching && places.isEmpty()) || position == places.size() + 3); } - - @Override - public boolean hasStableIds() { - return true; - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivitySearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivitySearchAdapter.java index eea4d6f86d4..27a64a30027 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivitySearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/LocationActivitySearchAdapter.java @@ -3,17 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.content.Context; -import android.view.View; import android.view.ViewGroup; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.LocationCell; +import org.telegram.ui.Components.RecyclerListView; public class LocationActivitySearchAdapter extends BaseLocationAdapter { @@ -25,25 +26,20 @@ public LocationActivitySearchAdapter(Context context) { } @Override - public int getCount() { + public int getItemCount() { return places.size(); } @Override - public boolean isEmpty() { - return places.isEmpty(); + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new RecyclerListView.Holder(new LocationCell(mContext)); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new LocationCell(mContext); - } - ((LocationCell) view).setLocation(places.get(i), iconUrls.get(i), i != places.size() - 1); - return view; + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ((LocationCell) holder.itemView).setLocation(places.get(position), iconUrls.get(position), position != places.size() - 1); } - @Override public TLRPC.TL_messageMediaVenue getItem(int i) { if (i >= 0 && i < places.size()) { return places.get(i); @@ -52,32 +48,7 @@ public TLRPC.TL_messageMediaVenue getItem(int i) { } @Override - public long getItemId(int i) { - return i; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 4; - } - - @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public boolean hasStableIds() { + public boolean isEnabled(RecyclerView.ViewHolder holder) { return true; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java index c6c4bcc785d..3a4d9ec65c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.Manifest; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -36,17 +35,19 @@ import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Cells.BotSwitchCell; import org.telegram.ui.Cells.ContextLinkCell; import org.telegram.ui.Cells.MentionCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; -public class MentionsAdapter extends BaseSearchAdapterRecycler { +public class MentionsAdapter extends RecyclerListView.SelectionAdapter { public interface MentionsAdapterDelegate { void needChangePanelVisibility(boolean show); @@ -54,16 +55,10 @@ public interface MentionsAdapterDelegate { void onContextClick(TLRPC.BotInlineResult result); } - public class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - private Context mContext; private long dialog_id; private TLRPC.ChatFull info; + private SearchAdapterHelper searchAdapterHelper; private ArrayList searchResultUsernames; private ArrayList searchResultHashtags; private ArrayList searchResultCommands; @@ -103,7 +98,7 @@ public Holder(View itemView) { public void onLocationAcquired(Location location) { if (foundContextBot != null && foundContextBot.bot_inline_geo) { lastKnownLocation = location; - searchForContextBotResults(foundContextBot, searchingContextQuery, ""); + searchForContextBotResults(true, foundContextBot, searchingContextQuery, ""); } } @@ -124,6 +119,20 @@ public MentionsAdapter(Context context, boolean darkTheme, long did, MentionsAda delegate = mentionsAdapterDelegate; isDarkTheme = darkTheme; dialog_id = did; + searchAdapterHelper = new SearchAdapterHelper(); + searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { + @Override + public void onDataSetChanged() { + notifyDataSetChanged(); + } + + @Override + public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { + if (lastText != null) { + searchUsernameOrHashtag(lastText, lastPosition, messages); + } + } + }); } public void onDestroy() { @@ -179,9 +188,8 @@ public void setBotsCount(int count) { botsCount = count; } - @Override public void clearRecentHashtags() { - super.clearRecentHashtags(); + searchAdapterHelper.clearRecentHashtags(); searchResultHashtags.clear(); notifyDataSetChanged(); if (delegate != null) { @@ -189,14 +197,6 @@ public void clearRecentHashtags() { } } - @Override - protected void setHashtags(ArrayList arrayList, HashMap hashMap) { - super.setHashtags(arrayList, hashMap); - if (lastText != null) { - searchUsernameOrHashtag(lastText, lastPosition, messages); - } - } - public TLRPC.TL_inlineBotSwitchPM getBotContextSwitch() { return searchResultBotContextSwitch; } @@ -213,6 +213,63 @@ public String getContextBotName() { return foundContextBot != null ? foundContextBot.username : ""; } + private void processFoundUser(TLRPC.User user) { + contextUsernameReqid = 0; + locationProvider.stop(); + if (user != null && user.bot && user.bot_inline_placeholder != null) { + foundContextBot = user; + if (foundContextBot.bot_inline_geo) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + boolean allowGeo = preferences.getBoolean("inlinegeo_" + foundContextBot.id, false); + if (!allowGeo && parentFragment != null && parentFragment.getParentActivity() != null) { + final TLRPC.User foundContextBotFinal = foundContextBot; + AlertDialog.Builder builder = new AlertDialog.Builder(parentFragment.getParentActivity()); + builder.setTitle(LocaleController.getString("ShareYouLocationTitle", R.string.ShareYouLocationTitle)); + builder.setMessage(LocaleController.getString("ShareYouLocationInline", R.string.ShareYouLocationInline)); + final boolean buttonClicked[] = new boolean[1]; + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + buttonClicked[0] = true; + if (foundContextBotFinal != null) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putBoolean("inlinegeo_" + foundContextBotFinal.id, true).commit(); + checkLocationPermissionsOrStart(); + } + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + buttonClicked[0] = true; + onLocationUnavailable(); + } + }); + parentFragment.showDialog(builder.create(), new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + if (!buttonClicked[0]) { + onLocationUnavailable(); + } + } + }); + } else { + checkLocationPermissionsOrStart(); + } + } + } else { + foundContextBot = null; + } + if (foundContextBot == null) { + noUserName = true; + } else { + if (delegate != null) { + delegate.onContextSearch(true); + } + searchForContextBotResults(true, foundContextBot, searchingContextQuery, ""); + } + } + private void searchForContextBot(final String username, final String query) { if (foundContextBot != null && foundContextBot.username != null && foundContextBot.username.equals(username) && searchingContextQuery != null && searchingContextQuery.equals(query)) { return; @@ -280,75 +337,40 @@ public void run() { if (noUserName) { return; } - searchForContextBotResults(foundContextBot, query, ""); + searchForContextBotResults(true, foundContextBot, query, ""); } else { - TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); - req.username = searchingContextUsername = username; - contextUsernameReqid = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { - @Override - public void run(final TLObject response, final TLRPC.TL_error error) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - if (searchingContextUsername == null || !searchingContextUsername.equals(username)) { - return; - } - contextUsernameReqid = 0; - foundContextBot = null; - locationProvider.stop(); - if (error == null) { - TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; - if (!res.users.isEmpty()) { - TLRPC.User user = res.users.get(0); - if (user.bot && user.bot_inline_placeholder != null) { + searchingContextUsername = username; + TLRPC.User user = MessagesController.getInstance().getUser(searchingContextUsername); + if (user != null) { + processFoundUser(user); + } else { + TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); + req.username = searchingContextUsername; + contextUsernameReqid = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (searchingContextUsername == null || !searchingContextUsername.equals(username)) { + return; + } + TLRPC.User user = null; + if (error == null) { + TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; + if (!res.users.isEmpty()) { + user = res.users.get(0); MessagesController.getInstance().putUser(user, false); MessagesStorage.getInstance().putUsersAndChats(res.users, null, true, true); - foundContextBot = user; - if (foundContextBot.bot_inline_geo) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - boolean allowGeo = preferences.getBoolean("inlinegeo_" + foundContextBot.id, false); - if (!allowGeo && parentFragment != null && parentFragment.getParentActivity() != null) { - final TLRPC.User foundContextBotFinal = foundContextBot; - AlertDialog.Builder builder = new AlertDialog.Builder(parentFragment.getParentActivity()); - builder.setTitle(LocaleController.getString("ShareYouLocationTitle", R.string.ShareYouLocationTitle)); - builder.setMessage(LocaleController.getString("ShareYouLocationInline", R.string.ShareYouLocationInline)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (foundContextBotFinal != null) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putBoolean("inlinegeo_" + foundContextBotFinal.id, true).commit(); - checkLocationPermissionsOrStart(); - } - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - onLocationUnavailable(); - } - }); - parentFragment.showDialog(builder.create()); - } else { - checkLocationPermissionsOrStart(); - } - } + } } + processFoundUser(user); } - if (foundContextBot == null) { - noUserName = true; - } else { - if (delegate != null) { - delegate.onContextSearch(true); - } - searchForContextBotResults(foundContextBot, searchingContextQuery, ""); - } - } - }); - - } - }); + }); + } + }); + } } } }; @@ -360,7 +382,7 @@ private void onLocationUnavailable() { lastKnownLocation = new Location("network"); lastKnownLocation.setLatitude(-1000); lastKnownLocation.setLongitude(-1000); - searchForContextBotResults(foundContextBot, searchingContextQuery, ""); + searchForContextBotResults(true, foundContextBot, searchingContextQuery, ""); } } @@ -390,10 +412,10 @@ public void searchForContextBotForNextOffset() { if (contextQueryReqid != 0 || nextQueryOffset == null || nextQueryOffset.length() == 0 || foundContextBot == null || searchingContextQuery == null) { return; } - searchForContextBotResults(foundContextBot, searchingContextQuery, nextQueryOffset); + searchForContextBotResults(true, foundContextBot, searchingContextQuery, nextQueryOffset); } - private void searchForContextBotResults(TLRPC.User user, final String query, final String offset) { + private void searchForContextBotResults(final boolean cache, final TLRPC.User user, final String query, final String offset) { if (contextQueryReqid != 0) { ConnectionsManager.getInstance().cancelRequest(contextQueryReqid, true); contextQueryReqid = 0; @@ -405,24 +427,8 @@ private void searchForContextBotResults(TLRPC.User user, final String query, fin if (user.bot_inline_geo && lastKnownLocation == null) { return; } - TLRPC.TL_messages_getInlineBotResults req = new TLRPC.TL_messages_getInlineBotResults(); - req.bot = MessagesController.getInputUser(user); - req.query = query; - req.offset = offset; - if (user.bot_inline_geo && lastKnownLocation != null && lastKnownLocation.getLatitude() != -1000) { - req.flags |= 1; - req.geo_point = new TLRPC.TL_inputGeoPoint(); - req.geo_point.lat = lastKnownLocation.getLatitude(); - req.geo_point._long = lastKnownLocation.getLongitude(); - } - int lower_id = (int) dialog_id; - int high_id = (int) (dialog_id >> 32); - if (lower_id != 0) { - req.peer = MessagesController.getInputPeer(lower_id); - } else { - req.peer = new TLRPC.TL_inputPeerEmpty(); - } - contextQueryReqid = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + final String key = dialog_id + "_" + query + "_" + offset + "_" + dialog_id + "_" + user.id + "_" + (user.bot_inline_geo && lastKnownLocation != null && lastKnownLocation.getLatitude() != -1000 ? lastKnownLocation.getLatitude() + lastKnownLocation.getLongitude() : ""); + RequestDelegate requestDelegate = new RequestDelegate() { @Override public void run(final TLObject response, final TLRPC.TL_error error) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -435,8 +441,14 @@ public void run() { delegate.onContextSearch(false); } contextQueryReqid = 0; - if (error == null) { + if (cache && response == null) { + searchForContextBotResults(false, user, query, offset); + } + if (response != null) { TLRPC.TL_messages_botResults res = (TLRPC.TL_messages_botResults) response; + if (!cache && res.cache_time != 0) { + MessagesStorage.getInstance().saveBotCache(key, res); + } nextQueryOffset = res.next_offset; if (searchResultBotContextById == null) { searchResultBotContextById = new HashMap<>(); @@ -479,7 +491,30 @@ public void run() { } }); } - }, ConnectionsManager.RequestFlagFailOnServerErrors); + }; + + if (cache) { + MessagesStorage.getInstance().getBotCache(key, requestDelegate); + } else { + TLRPC.TL_messages_getInlineBotResults req = new TLRPC.TL_messages_getInlineBotResults(); + req.bot = MessagesController.getInputUser(user); + req.query = query; + req.offset = offset; + if (user.bot_inline_geo && lastKnownLocation != null && lastKnownLocation.getLatitude() != -1000) { + req.flags |= 1; + req.geo_point = new TLRPC.TL_inputGeoPoint(); + req.geo_point.lat = lastKnownLocation.getLatitude(); + req.geo_point._long = lastKnownLocation.getLongitude(); + } + int lower_id = (int) dialog_id; + int high_id = (int) (dialog_id >> 32); + if (lower_id != 0) { + req.peer = MessagesController.getInputPeer(lower_id); + } else { + req.peer = new TLRPC.TL_inputPeerEmpty(); + } + contextQueryReqid = ConnectionsManager.getInstance().sendRequest(req, requestDelegate, ConnectionsManager.RequestFlagFailOnServerErrors); + } } public void searchUsernameOrHashtag(String text, int position, ArrayList messageObjects) { @@ -556,19 +591,19 @@ public void searchUsernameOrHashtag(String text, int position, ArrayList newResult = new ArrayList<>(); String hashtagString = result.toString().toLowerCase(); + ArrayList hashtags = searchAdapterHelper.getHashtags(); for (int a = 0; a < hashtags.size(); a++) { - HashtagObject hashtagObject = hashtags.get(a); + SearchAdapterHelper.HashtagObject hashtagObject = hashtags.get(a); if (hashtagObject != null && hashtagObject.hashtag != null && hashtagObject.hashtag.startsWith(hashtagString)) { newResult.add(hashtagObject.hashtag); } @@ -749,6 +785,10 @@ public int getItemViewType(int position) { } } + public void addHashtagsFromMessage(CharSequence message) { + searchAdapterHelper.addHashtagsFromMessage(message); + } + public int getItemPosition(int i) { if (searchResultBotContext != null && searchResultBotContextSwitch != null) { i--; @@ -811,6 +851,11 @@ public boolean isMediaLayout() { return contextMedia; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view; @@ -828,7 +873,7 @@ public void didPressedImage(ContextLinkCell cell) { view = new MentionCell(mContext); ((MentionCell) view).setIsDarkTheme(isDarkTheme); } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java index 11a5651b2b1..75a6d6e35e8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapter.java @@ -3,18 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; import android.view.View; import android.view.ViewGroup; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.ContactsController; @@ -22,21 +26,24 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; -import org.telegram.ui.Cells.GreySectionCell; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.ProfileSearchCell; import org.telegram.ui.Cells.UserCell; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; import java.util.Timer; import java.util.TimerTask; -public class SearchAdapter extends BaseSearchAdapter { +public class SearchAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private HashMap ignoreUsers; private ArrayList searchResult = new ArrayList<>(); private ArrayList searchResultNames = new ArrayList<>(); + private SearchAdapterHelper searchAdapterHelper; private HashMap checkedMap; private Timer searchTimer; private boolean allowUsernameSearch; @@ -52,6 +59,18 @@ public SearchAdapter(Context context, HashMap arg1, boolean allowUsernameSearch = usernameSearch; allowChats = chats; allowBots = bots; + searchAdapterHelper = new SearchAdapterHelper(); + searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { + @Override + public void onDataSetChanged() { + notifyDataSetChanged(); + } + + @Override + public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { + + } + }); } public void setCheckedMap(HashMap map) { @@ -68,13 +87,13 @@ public void searchDialogs(final String query) { searchTimer.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (query == null) { searchResult.clear(); searchResultNames.clear(); if (allowUsernameSearch) { - queryServerSearch(null, allowChats, allowBots); + searchAdapterHelper.queryServerSearch(null, allowChats, allowBots, true); } notifyDataSetChanged(); } else { @@ -86,7 +105,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } processSearch(query); } @@ -99,7 +118,7 @@ private void processSearch(final String query) { @Override public void run() { if (allowUsernameSearch) { - queryServerSearch(query, allowChats, allowBots); + searchAdapterHelper.queryServerSearch(query, allowChats, allowBots, true); } final ArrayList contactsCopy = new ArrayList<>(); contactsCopy.addAll(ContactsController.getInstance().contacts); @@ -176,19 +195,14 @@ public void run() { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() != searchResult.size(); } @Override - public boolean isEnabled(int i) { - return i != searchResult.size(); - } - - @Override - public int getCount() { + public int getItemCount() { int count = searchResult.size(); - int globalCount = globalSearch.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); if (globalCount != 0) { count += globalCount + 1; } @@ -197,7 +211,7 @@ public int getCount() { public boolean isGlobalSearch(int i) { int localCount = searchResult.size(); - int globalCount = globalSearch.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); if (i >= 0 && i < localCount) { return false; } else if (i > localCount && i <= globalCount + localCount) { @@ -206,37 +220,22 @@ public boolean isGlobalSearch(int i) { return false; } - @Override public TLObject getItem(int i) { int localCount = searchResult.size(); - int globalCount = globalSearch.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); if (i >= 0 && i < localCount) { return searchResult.get(i); } else if (i > localCount && i <= globalCount + localCount) { - return globalSearch.get(i - localCount - 1); + return searchAdapterHelper.getGlobalSearch().get(i - localCount - 1); } return null; } @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (i == searchResult.size()) { - if (view == null) { - view = new GreySectionCell(mContext); - ((GreySectionCell) view).setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); - } - } else { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: if (useUserCell) { view = new UserCell(mContext, 1, 1, false); if (checkedMap != null) { @@ -245,9 +244,20 @@ public View getView(int i, View view, ViewGroup viewGroup) { } else { view = new ProfileSearchCell(mContext); } - } + break; + case 1: + default: + view = new GraySectionCell(mContext); + ((GraySectionCell) view).setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); + break; + } + return new RecyclerListView.Holder(view); + } - TLObject object = getItem(i); + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + TLObject object = getItem(position); if (object != null) { int id = 0; String un = null; @@ -261,46 +271,48 @@ public View getView(int i, View view, ViewGroup viewGroup) { CharSequence username = null; CharSequence name = null; - if (i < searchResult.size()) { - name = searchResultNames.get(i); + if (position < searchResult.size()) { + name = searchResultNames.get(position); if (name != null && un != null && un.length() > 0) { if (name.toString().startsWith("@" + un)) { username = name; name = null; } } - } else if (i > searchResult.size() && un != null) { - String foundUserName = lastFoundUsername; + } else if (position > searchResult.size() && un != null) { + String foundUserName = searchAdapterHelper.getLastFoundUsername(); if (foundUserName.startsWith("@")) { foundUserName = foundUserName.substring(1); } try { - username = AndroidUtilities.replaceTags(String.format("@%s%s", un.substring(0, foundUserName.length()), un.substring(foundUserName.length()))); + username = new SpannableStringBuilder(un); + ((SpannableStringBuilder) username).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), 0, foundUserName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } catch (Exception e) { username = un; - FileLog.e("tmessages", e); + FileLog.e(e); } } if (useUserCell) { - ((UserCell) view).setData(object, name, username, 0); + UserCell userCell = (UserCell) holder.itemView; + userCell.setData(object, name, username, 0); if (checkedMap != null) { - ((UserCell) view).setChecked(checkedMap.containsKey(id), false); + userCell.setChecked(checkedMap.containsKey(id), false); } } else { - ((ProfileSearchCell) view).setData(object, null, name, username, false); - ((ProfileSearchCell) view).useSeparator = (i != getCount() - 1 && i != searchResult.size() - 1); - if (ignoreUsers != null) { + ProfileSearchCell profileSearchCell = (ProfileSearchCell) holder.itemView; + profileSearchCell.setData(object, null, name, username, false); + profileSearchCell.useSeparator = (position != getItemCount() - 1 && position != searchResult.size() - 1); + /*if (ignoreUsers != null) { if (ignoreUsers.containsKey(id)) { - ((ProfileSearchCell) view).drawAlpha = 0.5f; + profileSearchCell.drawAlpha = 0.5f; } else { - ((ProfileSearchCell) view).drawAlpha = 1.0f; + profileSearchCell.drawAlpha = 1.0f; } - } + }*/ } } } - return view; } @Override @@ -310,14 +322,4 @@ public int getItemViewType(int i) { } return 0; } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public boolean isEmpty() { - return searchResult.isEmpty() && globalSearch.isEmpty(); - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java new file mode 100644 index 00000000000..b72fa683c08 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/SearchAdapterHelper.java @@ -0,0 +1,253 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Adapters; + +import org.telegram.SQLite.SQLiteCursor; +import org.telegram.SQLite.SQLitePreparedStatement; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.MessagesStorage; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SearchAdapterHelper { + + public static class HashtagObject { + String hashtag; + int date; + } + + public interface SearchAdapterHelperDelegate { + void onDataSetChanged(); + void onSetHashtags(ArrayList arrayList, HashMap hashMap); + } + + private SearchAdapterHelperDelegate delegate; + + private int reqId = 0; + private int lastReqId; + private String lastFoundUsername = null; + private ArrayList globalSearch = new ArrayList<>(); + + private ArrayList hashtags; + private HashMap hashtagsByText; + private boolean hashtagsLoadedFromDb = false; + + public void queryServerSearch(final String query, final boolean allowChats, final boolean allowBots, final boolean allowSelf) { + if (reqId != 0) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + reqId = 0; + } + if (query == null || query.length() < 5) { + globalSearch.clear(); + lastReqId = 0; + delegate.onDataSetChanged(); + return; + } + TLRPC.TL_contacts_search req = new TLRPC.TL_contacts_search(); + req.q = query; + req.limit = 50; + final int currentReqId = ++lastReqId; + reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (currentReqId == lastReqId) { + if (error == null) { + TLRPC.TL_contacts_found res = (TLRPC.TL_contacts_found) response; + globalSearch.clear(); + if (allowChats) { + for (int a = 0; a < res.chats.size(); a++) { + globalSearch.add(res.chats.get(a)); + } + } + for (int a = 0; a < res.users.size(); a++) { + TLRPC.User user = res.users.get(a); + if (!allowBots && user.bot || !allowSelf && user.self) { + continue; + } + globalSearch.add(res.users.get(a)); + } + lastFoundUsername = query; + delegate.onDataSetChanged(); + } + } + reqId = 0; + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + public void unloadRecentHashtags() { + hashtagsLoadedFromDb = false; + } + + public boolean loadRecentHashtags() { + if (hashtagsLoadedFromDb) { + return true; + } + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + SQLiteCursor cursor = MessagesStorage.getInstance().getDatabase().queryFinalized("SELECT id, date FROM hashtag_recent_v2 WHERE 1"); + final ArrayList arrayList = new ArrayList<>(); + final HashMap hashMap = new HashMap<>(); + while (cursor.next()) { + HashtagObject hashtagObject = new HashtagObject(); + hashtagObject.hashtag = cursor.stringValue(0); + hashtagObject.date = cursor.intValue(1); + arrayList.add(hashtagObject); + hashMap.put(hashtagObject.hashtag, hashtagObject); + } + cursor.dispose(); + Collections.sort(arrayList, new Comparator() { + @Override + public int compare(HashtagObject lhs, HashtagObject rhs) { + if (lhs.date < rhs.date) { + return 1; + } else if (lhs.date > rhs.date) { + return -1; + } else { + return 0; + } + } + }); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + setHashtags(arrayList, hashMap); + } + }); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + return false; + } + + public void setDelegate(SearchAdapterHelperDelegate searchAdapterHelperDelegate) { + delegate = searchAdapterHelperDelegate; + } + + public void addHashtagsFromMessage(CharSequence message) { + if (message == null) { + return; + } + boolean changed = false; + Pattern pattern = Pattern.compile("(^|\\s)#[\\w@\\.]+"); + Matcher matcher = pattern.matcher(message); + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + if (message.charAt(start) != '@' && message.charAt(start) != '#') { + start++; + } + String hashtag = message.subSequence(start, end).toString(); + if (hashtagsByText == null) { + hashtagsByText = new HashMap<>(); + hashtags = new ArrayList<>(); + } + HashtagObject hashtagObject = hashtagsByText.get(hashtag); + if (hashtagObject == null) { + hashtagObject = new HashtagObject(); + hashtagObject.hashtag = hashtag; + hashtagsByText.put(hashtagObject.hashtag, hashtagObject); + } else { + hashtags.remove(hashtagObject); + } + hashtagObject.date = (int) (System.currentTimeMillis() / 1000); + hashtags.add(0, hashtagObject); + changed = true; + } + if (changed) { + putRecentHashtags(hashtags); + } + } + + private void putRecentHashtags(final ArrayList arrayList) { + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + MessagesStorage.getInstance().getDatabase().beginTransaction(); + SQLitePreparedStatement state = MessagesStorage.getInstance().getDatabase().executeFast("REPLACE INTO hashtag_recent_v2 VALUES(?, ?)"); + for (int a = 0; a < arrayList.size(); a++) { + if (a == 100) { + break; + } + HashtagObject hashtagObject = arrayList.get(a); + state.requery(); + state.bindString(1, hashtagObject.hashtag); + state.bindInteger(2, hashtagObject.date); + state.step(); + } + state.dispose(); + MessagesStorage.getInstance().getDatabase().commitTransaction(); + if (arrayList.size() >= 100) { + MessagesStorage.getInstance().getDatabase().beginTransaction(); + for (int a = 100; a < arrayList.size(); a++) { + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE id = '" + arrayList.get(a).hashtag + "'").stepThis().dispose(); + } + MessagesStorage.getInstance().getDatabase().commitTransaction(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public ArrayList getGlobalSearch() { + return globalSearch; + } + + public ArrayList getHashtags() { + return hashtags; + } + + public String getLastFoundUsername() { + return lastFoundUsername; + } + + public void clearRecentHashtags() { + hashtags = new ArrayList<>(); + hashtagsByText = new HashMap<>(); + MessagesStorage.getInstance().getStorageQueue().postRunnable(new Runnable() { + @Override + public void run() { + try { + MessagesStorage.getInstance().getDatabase().executeFast("DELETE FROM hashtag_recent_v2 WHERE 1").stepThis().dispose(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + public void setHashtags(ArrayList arrayList, HashMap hashMap) { + hashtags = arrayList; + hashtagsByText = hashMap; + hashtagsLoadedFromDb = true; + delegate.onSetHashtags(arrayList, hashMap); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java index 97400194d29..4319c116395 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/StickersAdapter.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Adapters; import android.content.Context; import android.text.TextUtils; -import android.view.View; import android.view.ViewGroup; import org.telegram.messenger.NotificationCenter; @@ -19,6 +18,7 @@ import org.telegram.messenger.FileLoader; import org.telegram.tgnet.TLRPC; import org.telegram.ui.Cells.StickerCell; +import org.telegram.ui.Components.RecyclerListView; import java.io.File; import java.util.ArrayList; @@ -26,7 +26,7 @@ import java.util.Comparator; import java.util.HashMap; -public class StickersAdapter extends RecyclerView.Adapter implements NotificationCenter.NotificationCenterDelegate { +public class StickersAdapter extends RecyclerListView.SelectionAdapter implements NotificationCenter.NotificationCenterDelegate { private Context mContext; private ArrayList stickers; @@ -39,13 +39,6 @@ public interface StickersAdapterDelegate { void needChangePanelVisibility(boolean show); } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - public StickersAdapter(Context context, StickersAdapterDelegate delegate) { mContext = context; this.delegate = delegate; @@ -175,14 +168,14 @@ public TLRPC.Document getItem(int i) { } @Override - public long getItemId(int i) { - return i; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { StickerCell view = new StickerCell(mContext); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java index 557ccd780e5..45686fb2a5c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArchivedStickersActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -27,6 +27,8 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.ArchivedStickerSetCell; import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; @@ -42,6 +44,7 @@ public class ArchivedStickersActivity extends BaseFragment implements Notificati private ListAdapter listAdapter; private EmptyTextProgressView emptyView; private LinearLayoutManager layoutManager; + private RecyclerListView listView; private ArrayList sets = new ArrayList<>(); private boolean firstLoaded; @@ -99,7 +102,7 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); emptyView = new EmptyTextProgressView(context); if (currentType == StickersQuery.TYPE_IMAGE) { @@ -114,7 +117,7 @@ public void onItemClick(int id) { emptyView.showTextView(); } - RecyclerListView listView = new RecyclerListView(context); + listView = new RecyclerListView(context); listView.setFocusable(true); listView.setEmptyView(emptyView); listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); @@ -250,15 +253,9 @@ public void didReceivedNotification(int id, Object... args) { } } - private class ListAdapter extends RecyclerListView.Adapter { - private Context mContext; - - private class Holder extends RecyclerView.ViewHolder { + private class ListAdapter extends RecyclerListView.SelectionAdapter { - public Holder(View itemView) { - super(itemView); - } - } + private Context mContext; public ListAdapter(Context context) { mContext = context; @@ -275,38 +272,47 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ArchivedStickerSetCell cell = (ArchivedStickerSetCell) holder.itemView; cell.setTag(position); TLRPC.StickerSetCovered stickerSet = sets.get(position); - cell.setStickersSet(stickerSet, position != sets.size() - 1, false); + cell.setStickersSet(stickerSet, position != sets.size() - 1); cell.setChecked(StickersQuery.isStickerPackInstalled(stickerSet.set.id)); } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == 0; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case 0: view = new ArchivedStickerSetCell(mContext, true); - view.setBackgroundResource(R.drawable.list_selector_white); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); ((ArchivedStickerSetCell) view).setOnCheckClick(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { ArchivedStickerSetCell cell = (ArchivedStickerSetCell) buttonView.getParent(); - TLRPC.StickerSetCovered stickerSet = sets.get((Integer) cell.getTag()); + int num = (Integer) cell.getTag(); + if (num >= sets.size()) { + return; + } + TLRPC.StickerSetCovered stickerSet = sets.get(num); StickersQuery.removeStickersSet(getParentActivity(), stickerSet.set, !isChecked ? 1 : 2, ArchivedStickersActivity.this, false); } }); break; case 1: view = new LoadingCell(mContext); - view.setBackgroundResource(R.drawable.greydivider_bottom); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); break; case 2: view = new TextInfoPrivacyCell(mContext); - view.setBackgroundResource(R.drawable.greydivider_bottom); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -321,4 +327,34 @@ public int getItemViewType(int i) { return 0; } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{ArchivedStickerSetCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{LoadingCell.class, TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{ArchivedStickerSetCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked) + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java new file mode 100644 index 00000000000..39b83ec53ee --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ArticleViewer.java @@ -0,0 +1,6728 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.database.DataSetObserver; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.SurfaceTexture; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.MetricAffectingSpan; +import android.util.TypedValue; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.SoundEffectConstants; +import android.view.TextureView; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.webkit.CookieManager; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.Emoji; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.browser.Browser; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; +import org.telegram.messenger.support.widget.GridLayoutManager; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.ClippingImageView; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LinkPath; +import org.telegram.ui.Components.RadialProgress; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.Scroller; +import org.telegram.ui.Components.SeekBar; +import org.telegram.ui.Components.ShareAlert; +import org.telegram.ui.Components.TextPaintSpan; +import org.telegram.ui.Components.TextPaintUrlSpan; +import org.telegram.ui.Components.VideoPlayer; +import org.telegram.ui.Components.WebPlayerView; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; + +@TargetApi(16) +public class ArticleViewer implements NotificationCenter.NotificationCenterDelegate, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { + + private Activity parentActivity; + private ArrayList createdWebViews = new ArrayList<>(); + + private View customView; + private FrameLayout fullscreenVideoContainer; + private TextureView fullscreenTextureView; + private AspectRatioFrameLayout fullscreenAspectRatioView; + private WebChromeClient.CustomViewCallback customViewCallback; + + private Object lastInsets; + + private boolean isVisible; + private boolean collapsed; + private boolean attachedToWindow; + + private int animationInProgress; + private Runnable animationEndRunnable; + private long transitionAnimationStartTime; + + private TLRPC.WebPage currentPage; + private ArrayList pagesStack = new ArrayList<>(); + + private WindowManager.LayoutParams windowLayoutParams; + private WindowView windowView; + private View barBackground; + private FrameLayout containerView; + private View photoContainerBackground; + private FrameLayoutDrawer photoContainerView; + private WebpageAdapter adapter; + private FrameLayout headerView; + private ImageView backButton; + private ImageView shareButton; + private FrameLayout shareContainer; + private ContextProgressView progressView; + private BackDrawable backDrawable; + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + private Dialog visibleDialog; + private Paint backgroundPaint; + private Drawable layerShadowDrawable; + private Paint scrimPaint; + private AnimatorSet progressViewAnimation; + + private WebPlayerView currentPlayingVideo; + private WebPlayerView fullscreenedVideo; + + private Drawable slideDotDrawable; + private Drawable slideDotBigDrawable; + + private int openUrlReqId; + private int previewsReqId; + + private int currentHeaderHeight; + + private boolean checkingForLongPress = false; + private CheckForLongPress pendingCheckForLongPress = null; + private int pressCount = 0; + private CheckForTap pendingCheckForTap = null; + + private TextPaintUrlSpan pressedLink; + private StaticLayout pressedLinkOwnerLayout; + private View pressedLinkOwnerView; + private LinkPath urlPath = new LinkPath(); + + public ArrayList blocks = new ArrayList<>(); + public ArrayList photoBlocks = new ArrayList<>(); + public HashMap anchors = new HashMap<>(); + + @SuppressLint("StaticFieldLeak") + private static volatile ArticleViewer Instance = null; + + public static ArticleViewer getInstance() { + ArticleViewer localInstance = Instance; + if (localInstance == null) { + synchronized (ArticleViewer.class) { + localInstance = Instance; + if (localInstance == null) { + Instance = localInstance = new ArticleViewer(); + } + } + } + return localInstance; + } + + private class FrameLayoutDrawer extends FrameLayout { + + public FrameLayoutDrawer(Context context) { + super(context); + } + + /*@Override + public boolean onInterceptTouchEvent(MotionEvent event) { + boolean result = super.onInterceptTouchEvent(event); + if (!result) { + processTouchEvent(event); + result = true; + } + return result; + }*/ + + @Override + public boolean onTouchEvent(MotionEvent event) { + processTouchEvent(event); + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + drawContent(canvas); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + return child != aspectRatioFrameLayout && super.drawChild(canvas, child, drawingTime); + } + } + + private final class CheckForTap implements Runnable { + public void run() { + if (pendingCheckForLongPress == null) { + pendingCheckForLongPress = new CheckForLongPress(); + } + pendingCheckForLongPress.currentPressCount = ++pressCount; + if (windowView != null) { + windowView.postDelayed(pendingCheckForLongPress, ViewConfiguration.getLongPressTimeout() - ViewConfiguration.getTapTimeout()); + } + } + } + + private class WindowView extends FrameLayout { + + private Runnable attachRunnable; + private boolean selfLayout; + private int startedTrackingPointerId; + private boolean maybeStartTracking; + private boolean startedTracking; + private int startedTrackingX; + private int startedTrackingY; + private VelocityTracker tracker; + private boolean closeAnimationInProgress; + private float innerTranslationX; + private float alpha; + + public WindowView(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); + int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + setMeasuredDimension(widthSize, heightSize); + WindowInsets insets = (WindowInsets) lastInsets; + if (AndroidUtilities.incorrectDisplaySizeFix) { + if (heightSize > AndroidUtilities.displaySize.y) { + heightSize = AndroidUtilities.displaySize.y; + } + heightSize += AndroidUtilities.statusBarHeight; + } + heightSize -= insets.getSystemWindowInsetBottom(); + widthSize -= insets.getSystemWindowInsetRight() + insets.getSystemWindowInsetLeft(); + if (insets.getSystemWindowInsetRight() != 0) { + barBackground.measure(View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetRight(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + } else if (insets.getSystemWindowInsetLeft() != 0) { + barBackground.measure(View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetLeft(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + } else { + barBackground.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(insets.getSystemWindowInsetBottom(), View.MeasureSpec.EXACTLY)); + } + } else { + setMeasuredDimension(widthSize, heightSize); + } + containerView.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + photoContainerView.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + photoContainerBackground.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + fullscreenVideoContainer.measure(View.MeasureSpec.makeMeasureSpec(widthSize, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(heightSize, View.MeasureSpec.EXACTLY)); + ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); + animatingImageView.measure(View.MeasureSpec.makeMeasureSpec(layoutParams.width, View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(layoutParams.height, View.MeasureSpec.AT_MOST)); + } + + @SuppressWarnings("DrawAllocation") + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (selfLayout) { + return; + } + int x; + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + WindowInsets insets = (WindowInsets) lastInsets; + x = insets.getSystemWindowInsetLeft(); + + if (insets.getSystemWindowInsetRight() != 0) { + barBackground.layout(right - left - insets.getSystemWindowInsetRight(), 0, right - left, bottom - top); + } else if (insets.getSystemWindowInsetLeft() != 0) { + barBackground.layout(0, 0, insets.getSystemWindowInsetLeft(), bottom - top); + } else { + barBackground.layout(0, bottom - top - insets.getStableInsetBottom(), right - left, bottom - top); + } + } else { + x = 0; + } + containerView.layout(x, 0, x + containerView.getMeasuredWidth(), containerView.getMeasuredHeight()); + photoContainerView.layout(x, 0, x + photoContainerView.getMeasuredWidth(), photoContainerView.getMeasuredHeight()); + photoContainerBackground.layout(x, 0, x + photoContainerBackground.getMeasuredWidth(), photoContainerBackground.getMeasuredHeight()); + fullscreenVideoContainer.layout(x, 0, x + fullscreenVideoContainer.getMeasuredWidth(), fullscreenVideoContainer.getMeasuredHeight()); + animatingImageView.layout(0, 0, animatingImageView.getMeasuredWidth(), animatingImageView.getMeasuredHeight()); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attachedToWindow = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attachedToWindow = false; + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + handleTouchEvent(null); + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !collapsed && (handleTouchEvent(ev) || super.onInterceptTouchEvent(ev)); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return !collapsed && (handleTouchEvent(event) || super.onTouchEvent(event)); + } + + public void setInnerTranslationX(float value) { + innerTranslationX = value; + if (parentActivity instanceof LaunchActivity) { + ((LaunchActivity) parentActivity).drawerLayoutContainer.setAllowDrawContent(!isVisible || alpha != 1.0f || innerTranslationX != 0); + } + invalidate(); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + int width = getMeasuredWidth(); + int translationX = (int) innerTranslationX; + + final int restoreCount = canvas.save(); + canvas.clipRect(translationX, 0, width, getHeight()); + final boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restoreToCount(restoreCount); + + if (translationX != 0 && child == containerView) { + float opacity = Math.min(0.8f, (width - translationX) / (float) width); + if (opacity < 0) { + opacity = 0; + } + scrimPaint.setColor((int) (((0x99000000 & 0xff000000) >>> 24) * opacity) << 24); + canvas.drawRect(0, 0, translationX, getHeight(), scrimPaint); + + final float alpha = Math.max(0, Math.min((width - translationX) / (float) AndroidUtilities.dp(20), 1.0f)); + layerShadowDrawable.setBounds(translationX - layerShadowDrawable.getIntrinsicWidth(), child.getTop(), translationX, child.getBottom()); + layerShadowDrawable.setAlpha((int) (0xff * alpha)); + layerShadowDrawable.draw(canvas); + } + + return result; + } + + public float getInnerTranslationX() { + return innerTranslationX; + } + + private void prepareForMoving(MotionEvent ev) { + maybeStartTracking = false; + startedTracking = true; + startedTrackingX = (int) ev.getX(); + } + + public boolean handleTouchEvent(MotionEvent event) { + if (!isPhotoVisible && !closeAnimationInProgress && fullscreenVideoContainer.getVisibility() != VISIBLE) { + if (event != null && event.getAction() == MotionEvent.ACTION_DOWN && !startedTracking && !maybeStartTracking) { + startedTrackingPointerId = event.getPointerId(0); + maybeStartTracking = true; + startedTrackingX = (int) event.getX(); + startedTrackingY = (int) event.getY(); + if (tracker != null) { + tracker.clear(); + } + } else if (event != null && event.getAction() == MotionEvent.ACTION_MOVE && event.getPointerId(0) == startedTrackingPointerId) { + if (tracker == null) { + tracker = VelocityTracker.obtain(); + } + int dx = Math.max(0, (int) (event.getX() - startedTrackingX)); + int dy = Math.abs((int) event.getY() - startedTrackingY); + tracker.addMovement(event); + if (maybeStartTracking && !startedTracking && dx >= AndroidUtilities.getPixelsInCM(0.4f, true) && Math.abs(dx) / 3 > dy) { + prepareForMoving(event); + } else if (startedTracking) { + containerView.setTranslationX(dx); + setInnerTranslationX(dx); + } + } else if (event != null && event.getPointerId(0) == startedTrackingPointerId && (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_POINTER_UP)) { + if (tracker == null) { + tracker = VelocityTracker.obtain(); + } + tracker.computeCurrentVelocity(1000); + if (!startedTracking) { + float velX = tracker.getXVelocity(); + float velY = tracker.getYVelocity(); + if (velX >= 3500 && velX > Math.abs(velY)) { + prepareForMoving(event); + } + } + if (startedTracking) { + float x = containerView.getX(); + AnimatorSet animatorSet = new AnimatorSet(); + float velX = tracker.getXVelocity(); + float velY = tracker.getYVelocity(); + final boolean backAnimation = x < containerView.getMeasuredWidth() / 3.0f && (velX < 3500 || velX < velY); + float distToMove; + if (!backAnimation) { + distToMove = containerView.getMeasuredWidth() - x; + animatorSet.playTogether( + ObjectAnimator.ofFloat(containerView, "translationX", containerView.getMeasuredWidth()), + ObjectAnimator.ofFloat(this, "innerTranslationX", (float) containerView.getMeasuredWidth()) + ); + } else { + distToMove = x; + animatorSet.playTogether( + ObjectAnimator.ofFloat(containerView, "translationX", 0), + ObjectAnimator.ofFloat(this, "innerTranslationX", 0.0f) + ); + } + + animatorSet.setDuration(Math.max((int) (200.0f / containerView.getMeasuredWidth() * distToMove), 50)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (!backAnimation) { + saveCurrentPagePosition(); + onClosed(); + } + startedTracking = false; + closeAnimationInProgress = false; + } + }); + animatorSet.start(); + closeAnimationInProgress = true; + } else { + maybeStartTracking = false; + startedTracking = false; + } + if (tracker != null) { + tracker.recycle(); + tracker = null; + } + } else if (event == null) { + maybeStartTracking = false; + startedTracking = false; + if (tracker != null) { + tracker.recycle(); + tracker = null; + } + } + return startedTracking; + } + return false; + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawRect(innerTranslationX, 0, getMeasuredWidth(), getMeasuredHeight(), backgroundPaint); + } + + @Override + public void setAlpha(float value) { + backgroundPaint.setAlpha((int) (255 * value)); + alpha = value; + if (parentActivity instanceof LaunchActivity) { + ((LaunchActivity) parentActivity).drawerLayoutContainer.setAllowDrawContent(!isVisible || alpha != 1.0f || innerTranslationX != 0); + } + invalidate(); + } + + @Override + public float getAlpha() { + return alpha; + } + } + + class CheckForLongPress implements Runnable { + public int currentPressCount; + + public void run() { + if (checkingForLongPress && windowView != null) { + checkingForLongPress = false; + windowView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + if (pressedLink != null) { + final String urlFinal = pressedLink.getUrl(); + BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); + builder.setTitle(urlFinal); + builder.setItems(new CharSequence[]{LocaleController.getString("Open", R.string.Open), LocaleController.getString("Copy", R.string.Copy)}, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, final int which) { + if (parentActivity == null) { + return; + } + if (which == 0) { + Browser.openUrl(parentActivity, urlFinal); + } else if (which == 1) { + String url = urlFinal; + if (url.startsWith("mailto:")) { + url = url.substring(7); + } else if (url.startsWith("tel:")) { + url = url.substring(4); + } + AndroidUtilities.addToClipboard(url); + } + } + }); + showDialog(builder.create()); + hideActionBar(); + pressedLink = null; + pressedLinkOwnerLayout = null; + pressedLinkOwnerView.invalidate(); + } + } + } + } + + private void setRichTextParents(TLRPC.RichText parentRichText, TLRPC.RichText richText) { + if (richText == null) { + return; + } + richText.parentRichText = parentRichText; + if (richText instanceof TLRPC.TL_textFixed) { + setRichTextParents(richText, ((TLRPC.TL_textFixed) richText).text); + } else if (richText instanceof TLRPC.TL_textItalic) { + setRichTextParents(richText, ((TLRPC.TL_textItalic) richText).text); + } else if (richText instanceof TLRPC.TL_textBold) { + setRichTextParents(richText, ((TLRPC.TL_textBold) richText).text); + } else if (richText instanceof TLRPC.TL_textUnderline) { + setRichTextParents(richText, ((TLRPC.TL_textUnderline) richText).text); + } else if (richText instanceof TLRPC.TL_textStrike) { + setRichTextParents(parentRichText, ((TLRPC.TL_textStrike) richText).text); + } else if (richText instanceof TLRPC.TL_textEmail) { + setRichTextParents(richText, ((TLRPC.TL_textEmail) richText).text); + } else if (richText instanceof TLRPC.TL_textUrl) { + setRichTextParents(richText, ((TLRPC.TL_textUrl) richText).text); + } else if (richText instanceof TLRPC.TL_textConcat) { + int count = richText.texts.size(); + for (int a = 0; a < count; a++) { + setRichTextParents(richText, richText.texts.get(a)); + } + } + } + + private void updateInterfaceForCurrentPage(boolean back) { + if (currentPage == null || currentPage.cached_page == null) { + return; + } + blocks.clear(); + photoBlocks.clear(); + int numBlocks = 0; + for (int a = 0; a < currentPage.cached_page.blocks.size(); a++) { + TLRPC.PageBlock block = currentPage.cached_page.blocks.get(a); + if (block instanceof TLRPC.TL_pageBlockUnsupported) { + continue; + } else if (block instanceof TLRPC.TL_pageBlockAnchor) { + anchors.put(block.name, blocks.size()); + continue; + } + setRichTextParents(null, block.text); + setRichTextParents(null, block.caption); + if (block instanceof TLRPC.TL_pageBlockAuthorDate) { + setRichTextParents(null, ((TLRPC.TL_pageBlockAuthorDate) block).author); + } else if (block instanceof TLRPC.TL_pageBlockCollage) { + TLRPC.TL_pageBlockCollage innerBlock = (TLRPC.TL_pageBlockCollage) block; + for (int i = 0; i < innerBlock.items.size(); i++) { + setRichTextParents(null, innerBlock.items.get(i).text); + setRichTextParents(null, innerBlock.items.get(i).caption); + } + } else if (block instanceof TLRPC.TL_pageBlockList) { + TLRPC.TL_pageBlockList innerBlock = (TLRPC.TL_pageBlockList) block; + for (int i = 0; i < innerBlock.items.size(); i++) { + setRichTextParents(null, innerBlock.items.get(i)); + } + } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { + TLRPC.TL_pageBlockSlideshow innerBlock = (TLRPC.TL_pageBlockSlideshow) block; + for (int i = 0; i < innerBlock.items.size(); i++) { + setRichTextParents(null, innerBlock.items.get(i).text); + setRichTextParents(null, innerBlock.items.get(i).caption); + } + } + if (a == 0) { + block.first = true; + } + addAllMediaFromBlock(block); + blocks.add(block); + if (block instanceof TLRPC.TL_pageBlockEmbedPost) { + if (!block.blocks.isEmpty()) { + block.level = -1; + for (int b = 0; b < block.blocks.size(); b++) { + TLRPC.PageBlock innerBlock = block.blocks.get(b); + if (innerBlock instanceof TLRPC.TL_pageBlockUnsupported) { + continue; + } else if (innerBlock instanceof TLRPC.TL_pageBlockAnchor) { + anchors.put(innerBlock.name, blocks.size()); + continue; + } + innerBlock.level = 1; + if (b == block.blocks.size() - 1) { + innerBlock.bottom = true; + } + blocks.add(innerBlock); + addAllMediaFromBlock(innerBlock); + } + } + if (!(block.caption instanceof TLRPC.TL_textEmpty)) { + TLRPC.TL_pageBlockParagraph caption = new TLRPC.TL_pageBlockParagraph(); + caption.caption = block.caption; + blocks.add(caption); + } + } + } + + adapter.notifyDataSetChanged(); + + if (pagesStack.size() == 1 || back) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("articles", Activity.MODE_PRIVATE); + String key = "article" + currentPage.id; + int position = preferences.getInt(key, -1); + int offset; + if (preferences.getBoolean(key + "r", true) == AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) { + offset = preferences.getInt(key + "o", 0) - listView.getPaddingTop(); + } else { + offset = AndroidUtilities.dp(10); + } + if (position != -1) { + layoutManager.scrollToPositionWithOffset(position, offset); + } + } else { + layoutManager.scrollToPositionWithOffset(0, 0); + } + } + + private void addPageToStack(TLRPC.WebPage webPage, String anchor) { + saveCurrentPagePosition(); + currentPage = webPage; + pagesStack.add(webPage); + updateInterfaceForCurrentPage(false); + + if (anchor != null) { + Integer row = anchors.get(anchor); + if (row != null) { + layoutManager.scrollToPositionWithOffset(row, 0); + } + } + } + + private boolean removeLastPageFromStack() { + if (pagesStack.size() < 2) { + return false; + } + pagesStack.remove(pagesStack.size() - 1); + currentPage = pagesStack.get(pagesStack.size() - 1); + updateInterfaceForCurrentPage(true); + return true; + } + + protected void startCheckLongPress() { + if (checkingForLongPress) { + return; + } + checkingForLongPress = true; + if (pendingCheckForTap == null) { + pendingCheckForTap = new CheckForTap(); + } + windowView.postDelayed(pendingCheckForTap, ViewConfiguration.getTapTimeout()); + } + + protected void cancelCheckLongPress() { + checkingForLongPress = false; + if (pendingCheckForLongPress != null) { + windowView.removeCallbacks(pendingCheckForLongPress); + } + if (pendingCheckForTap != null) { + windowView.removeCallbacks(pendingCheckForTap); + } + } + + private static final int TEXT_FLAG_REGULAR = 0; + private static final int TEXT_FLAG_MEDIUM = 1; + private static final int TEXT_FLAG_ITALIC = 2; + private static final int TEXT_FLAG_MONO = 4; + private static final int TEXT_FLAG_URL = 8; + private static final int TEXT_FLAG_UNDERLINE = 16; + private static final int TEXT_FLAG_STRIKE = 32; + + private static TextPaint errorTextPaint; + private static HashMap captionTextPaints = new HashMap<>(); + private static HashMap titleTextPaints = new HashMap<>(); + private static HashMap headerTextPaints = new HashMap<>(); + private static HashMap subtitleTextPaints = new HashMap<>(); + private static HashMap subheaderTextPaints = new HashMap<>(); + private static HashMap authorTextPaints = new HashMap<>(); + private static HashMap footerTextPaints = new HashMap<>(); + private static HashMap paragraphTextPaints = new HashMap<>(); + private static HashMap listTextPaints = new HashMap<>(); + private static HashMap preformattedTextPaints = new HashMap<>(); + private static HashMap quoteTextPaints = new HashMap<>(); + private static HashMap subquoteTextPaints = new HashMap<>(); + private static HashMap embedTextPaints = new HashMap<>(); + private static HashMap slideshowTextPaints = new HashMap<>(); + private static HashMap embedPostTextPaints = new HashMap<>(); + private static HashMap embedPostCaptionTextPaints = new HashMap<>(); + private static HashMap videoTextPaints = new HashMap<>(); + + private static TextPaint embedPostAuthorPaint; + private static TextPaint embedPostDatePaint; + + private static Paint preformattedBackgroundPaint; + private static Paint quoteLinePaint; + private static Paint dividerPaint; + private static Paint urlPaint; + + private int getTextFlags(TLRPC.RichText richText) { + if (richText instanceof TLRPC.TL_textFixed) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_MONO; + } else if (richText instanceof TLRPC.TL_textItalic) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_ITALIC; + } else if (richText instanceof TLRPC.TL_textBold) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_MEDIUM; + } else if (richText instanceof TLRPC.TL_textUnderline) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_UNDERLINE; + } else if (richText instanceof TLRPC.TL_textStrike) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_STRIKE; + } else if (richText instanceof TLRPC.TL_textEmail) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_URL; + } else if (richText instanceof TLRPC.TL_textUrl) { + return getTextFlags(richText.parentRichText) | TEXT_FLAG_URL; + } else if (richText != null) { + return getTextFlags(richText.parentRichText); + } + return TEXT_FLAG_REGULAR; + } + + private CharSequence getText(TLRPC.RichText parentRichText, TLRPC.RichText richText, TLRPC.PageBlock parentBlock) { + if (richText instanceof TLRPC.TL_textFixed) { + return getText(parentRichText, ((TLRPC.TL_textFixed) richText).text, parentBlock); + } else if (richText instanceof TLRPC.TL_textItalic) { + return getText(parentRichText, ((TLRPC.TL_textItalic) richText).text, parentBlock); + } else if (richText instanceof TLRPC.TL_textBold) { + return getText(parentRichText, ((TLRPC.TL_textBold) richText).text, parentBlock); + } else if (richText instanceof TLRPC.TL_textUnderline) { + return getText(parentRichText, ((TLRPC.TL_textUnderline) richText).text, parentBlock); + } else if (richText instanceof TLRPC.TL_textStrike) { + return getText(parentRichText, ((TLRPC.TL_textStrike) richText).text, parentBlock); + } else if (richText instanceof TLRPC.TL_textEmail) { + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getText(parentRichText, ((TLRPC.TL_textEmail) richText).text, parentBlock)); + MetricAffectingSpan innerSpans[] = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), MetricAffectingSpan.class); + spannableStringBuilder.setSpan(new TextPaintUrlSpan(innerSpans == null || innerSpans.length == 0 ? getTextPaint(parentRichText, richText, parentBlock) : null, getUrl(richText)), 0, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return spannableStringBuilder; + } else if (richText instanceof TLRPC.TL_textUrl) { + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getText(parentRichText, ((TLRPC.TL_textUrl) richText).text, parentBlock)); + MetricAffectingSpan innerSpans[] = spannableStringBuilder.getSpans(0, spannableStringBuilder.length(), MetricAffectingSpan.class); + spannableStringBuilder.setSpan(new TextPaintUrlSpan(innerSpans == null || innerSpans.length == 0 ? getTextPaint(parentRichText, richText, parentBlock) : null, getUrl(richText)), 0, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + return spannableStringBuilder; + } else if (richText instanceof TLRPC.TL_textPlain) { + return ((TLRPC.TL_textPlain) richText).text; + } else if (richText instanceof TLRPC.TL_textEmpty) { + return ""; + } else if (richText instanceof TLRPC.TL_textConcat) { + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(); + int count = richText.texts.size(); + for (int a = 0; a < count; a++) { + TLRPC.RichText innerRichText = richText.texts.get(a); + CharSequence innerText = getText(parentRichText, innerRichText, parentBlock); + int flags = getTextFlags(innerRichText); + int startLength = spannableStringBuilder.length(); + spannableStringBuilder.append(innerText); + if (flags != 0 && !(innerText instanceof SpannableStringBuilder)) { + if ((flags & TEXT_FLAG_URL) != 0) { + String url = getUrl(innerRichText); + if (url == null) { + url = getUrl(parentRichText); + } + spannableStringBuilder.setSpan(new TextPaintUrlSpan(getTextPaint(parentRichText, innerRichText, parentBlock), url), startLength, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else { + spannableStringBuilder.setSpan(new TextPaintSpan(getTextPaint(parentRichText, innerRichText, parentBlock)), startLength, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + return spannableStringBuilder; + } + return "not supported " + richText; + } + + private String getUrl(TLRPC.RichText richText) { + if (richText instanceof TLRPC.TL_textFixed) { + return getUrl(((TLRPC.TL_textFixed) richText).text); + } else if (richText instanceof TLRPC.TL_textItalic) { + return getUrl(((TLRPC.TL_textItalic) richText).text); + } else if (richText instanceof TLRPC.TL_textBold) { + return getUrl(((TLRPC.TL_textBold) richText).text); + } else if (richText instanceof TLRPC.TL_textUnderline) { + return getUrl(((TLRPC.TL_textUnderline) richText).text); + } else if (richText instanceof TLRPC.TL_textStrike) { + return getUrl(((TLRPC.TL_textStrike) richText).text); + } else if (richText instanceof TLRPC.TL_textEmail) { + return ((TLRPC.TL_textEmail) richText).email; + } else if (richText instanceof TLRPC.TL_textUrl) { + return ((TLRPC.TL_textUrl) richText).url; + } + return null; + } + + private TextPaint getTextPaint(TLRPC.RichText parentRichText, TLRPC.RichText richText, TLRPC.PageBlock parentBlock) { + int flags = getTextFlags(richText); + HashMap currentMap = null; + int textSize = AndroidUtilities.dp(14); + int textColor = 0xffff0000; + + if (parentBlock instanceof TLRPC.TL_pageBlockPhoto) { + currentMap = captionTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else if (parentBlock instanceof TLRPC.TL_pageBlockTitle) { + currentMap = titleTextPaints; + textSize = AndroidUtilities.dp(24); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockAuthorDate) { + currentMap = authorTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else if (parentBlock instanceof TLRPC.TL_pageBlockFooter) { + currentMap = footerTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else if (parentBlock instanceof TLRPC.TL_pageBlockSubtitle) { + currentMap = subtitleTextPaints; + textSize = AndroidUtilities.dp(21); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockHeader) { + currentMap = headerTextPaints; + textSize = AndroidUtilities.dp(21); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockSubheader) { + currentMap = subheaderTextPaints; + textSize = AndroidUtilities.dp(18); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockBlockquote || parentBlock instanceof TLRPC.TL_pageBlockPullquote) { + if (parentBlock.text == parentRichText) { + currentMap = quoteTextPaints; + textSize = AndroidUtilities.dp(15); + textColor = 0xff000000; + } else if (parentBlock.caption == parentRichText) { + currentMap = subquoteTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } + } else if (parentBlock instanceof TLRPC.TL_pageBlockPreformatted) { + currentMap = preformattedTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockParagraph) { + if (parentBlock.caption == parentRichText) { + currentMap = embedPostCaptionTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else { + currentMap = paragraphTextPaints; + textSize = AndroidUtilities.dp(16); + textColor = 0xff000000; + } + } else if (parentBlock instanceof TLRPC.TL_pageBlockList) { + currentMap = listTextPaints; + textSize = AndroidUtilities.dp(15); + textColor = 0xff000000; + } else if (parentBlock instanceof TLRPC.TL_pageBlockEmbed) { + currentMap = embedTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else if (parentBlock instanceof TLRPC.TL_pageBlockSlideshow) { + currentMap = slideshowTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff838c96; + } else if (parentBlock instanceof TLRPC.TL_pageBlockEmbedPost) { + if (richText != null) { + currentMap = embedPostTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff000000; + } + } else if (parentBlock instanceof TLRPC.TL_pageBlockVideo) { + currentMap = videoTextPaints; + textSize = AndroidUtilities.dp(14); + textColor = 0xff000000; + } + if (currentMap == null) { + if (errorTextPaint == null) { + errorTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + errorTextPaint.setColor(0xffff0000); + } + errorTextPaint.setTextSize(AndroidUtilities.dp(14)); + return errorTextPaint; + } + TextPaint paint = currentMap.get(flags); + if (paint == null) { + paint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + if ((flags & TEXT_FLAG_MONO) != 0) { + paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmono.ttf")); + } else { + if (parentBlock instanceof TLRPC.TL_pageBlockTitle || parentBlock instanceof TLRPC.TL_pageBlockHeader || parentBlock instanceof TLRPC.TL_pageBlockSubtitle || parentBlock instanceof TLRPC.TL_pageBlockSubheader) { + if ((flags & TEXT_FLAG_MEDIUM) != 0 && (flags & TEXT_FLAG_ITALIC) != 0) { + paint.setTypeface(Typeface.create("serif", Typeface.BOLD_ITALIC)); + } else if ((flags & TEXT_FLAG_MEDIUM) != 0) { + paint.setTypeface(Typeface.create("serif", Typeface.BOLD)); + } else if ((flags & TEXT_FLAG_ITALIC) != 0) { + paint.setTypeface(Typeface.create("serif", Typeface.ITALIC)); + } else { + paint.setTypeface(Typeface.create("serif", Typeface.NORMAL)); + } + } else { + if ((flags & TEXT_FLAG_MEDIUM) != 0 && (flags & TEXT_FLAG_ITALIC) != 0) { + paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmediumitalic.ttf")); + } else if ((flags & TEXT_FLAG_MEDIUM) != 0) { + paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } else if ((flags & TEXT_FLAG_ITALIC) != 0) { + paint.setTypeface(AndroidUtilities.getTypeface("fonts/ritalic.ttf")); + } + } + } + if ((flags & TEXT_FLAG_STRIKE) != 0) { + paint.setFlags(paint.getFlags() | TextPaint.STRIKE_THRU_TEXT_FLAG); + } + if ((flags & TEXT_FLAG_UNDERLINE) != 0) { + paint.setFlags(paint.getFlags() | TextPaint.UNDERLINE_TEXT_FLAG); + } + if ((flags & TEXT_FLAG_URL) != 0) { + textColor = 0xff4d83b3; + } + paint.setColor(textColor); + currentMap.put(flags, paint); + } + paint.setTextSize(textSize); + return paint; + } + + private StaticLayout createLayoutForText(CharSequence plainText, TLRPC.RichText richText, int width, TLRPC.PageBlock parentBlock) { + if (plainText == null && (richText == null || richText instanceof TLRPC.TL_textEmpty)) { + return null; + } + + if (quoteLinePaint == null) { + quoteLinePaint = new Paint(); + quoteLinePaint.setColor(0xff000000); + + preformattedBackgroundPaint = new Paint(); + preformattedBackgroundPaint.setColor(0xfff5f8fc); + + urlPaint = new Paint(); + urlPaint.setColor(0x3362a9e3); + } + + CharSequence text; + if (plainText != null) { + text = plainText; + } else { + text = getText(richText, richText, parentBlock); + } + if (TextUtils.isEmpty(text)) { + return null; + } + TextPaint paint; + if (parentBlock instanceof TLRPC.TL_pageBlockEmbedPost && richText == null) { + if (parentBlock.author == plainText) { + if (embedPostAuthorPaint == null) { + embedPostAuthorPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + embedPostAuthorPaint.setColor(0xff000000); + } + embedPostAuthorPaint.setTextSize(AndroidUtilities.dp(15)); + paint = embedPostAuthorPaint; + } else { + if (embedPostDatePaint == null) { + embedPostDatePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + embedPostDatePaint.setColor(0xff8f97a0); + } + embedPostDatePaint.setTextSize(AndroidUtilities.dp(14)); + paint = embedPostDatePaint; + } + } else { + paint = getTextPaint(richText, richText, parentBlock); + } + if (parentBlock instanceof TLRPC.TL_pageBlockPullquote) { + return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + } else { + return new StaticLayout(text, paint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(4), false); + } + } + + private void drawLayoutLink(Canvas canvas, StaticLayout layout) { + if (canvas == null || pressedLink == null || pressedLinkOwnerLayout != layout) { + return; + } + if (pressedLink != null) { + canvas.drawPath(urlPath, urlPaint); + } + } + + private boolean checkLayoutForLinks(MotionEvent event, View parentView, StaticLayout layout, int layoutX, int layoutY) { + if (parentView == null || layout == null) { + return false; + } + CharSequence text = layout.getText(); + if (!(text instanceof Spannable)) { + return false; + } + int x = (int) event.getX(); + int y = (int) event.getY(); + boolean removeLink = false; + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (x >= layoutX && x <= layoutX + layout.getWidth() && y >= layoutY && y <= layoutY + layout.getHeight()) { + try { + int checkX = x - layoutX; + int checkY = y - layoutY; + final int line = layout.getLineForVertical(checkY); + final int off = layout.getOffsetForHorizontal(line, checkX); + final float left = layout.getLineLeft(line); + if (left <= checkX && left + layout.getLineWidth(line) >= checkX) { + Spannable buffer = (Spannable) layout.getText(); + TextPaintUrlSpan[] link = buffer.getSpans(off, off, TextPaintUrlSpan.class); + if (link != null && link.length > 0) { + pressedLink = link[0]; + int pressedStart = buffer.getSpanStart(pressedLink); + int pressedEnd = buffer.getSpanEnd(pressedLink); + for (int a = 1; a < link.length; a++) { + TextPaintUrlSpan span = link[a]; + int start = buffer.getSpanStart(span); + int end = buffer.getSpanEnd(span); + if (pressedStart > start || end > pressedEnd) { + pressedLink = span; + pressedStart = start; + pressedEnd = end; + } + } + pressedLinkOwnerLayout = layout; + pressedLinkOwnerView = parentView; + try { + urlPath.setCurrentLayout(layout, pressedStart, 0); + layout.getSelectionPath(pressedStart, pressedEnd, urlPath); + parentView.invalidate(); + } catch (Exception e) { + FileLog.e(e); + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (pressedLink != null) { + removeLink = true; + String url = pressedLink.getUrl(); + if (url != null) { + int index; + boolean isAnchor = false; + final String anchor; + if ((index = url.lastIndexOf('#')) != -1) { + anchor = url.substring(index + 1); + if (url.toLowerCase().contains(currentPage.url.toLowerCase())) { + Integer row = anchors.get(anchor); + if (row != null) { + layoutManager.scrollToPositionWithOffset(row, 0); + isAnchor = true; + } + } + } else { + anchor = null; + } + if (!isAnchor) { + if (openUrlReqId == 0) { + showProgressView(true); + final TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); + req.url = pressedLink.getUrl(); + req.hash = 0; + openUrlReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (openUrlReqId == 0) { + return; + } + openUrlReqId = 0; + showProgressView(false); + if (isVisible) { + if (response instanceof TLRPC.TL_webPage && ((TLRPC.TL_webPage) response).cached_page instanceof TLRPC.TL_pageFull) { + addPageToStack((TLRPC.TL_webPage) response, anchor); + } else { + Browser.openUrl(parentActivity, req.url); + } + } + } + }); + } + }); + } + } + } + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + removeLink = true; + } + if (removeLink && pressedLink != null) { + pressedLink = null; + pressedLinkOwnerLayout = null; + pressedLinkOwnerView = null; + parentView.invalidate(); + } + if (pressedLink != null && event.getAction() == MotionEvent.ACTION_DOWN) { + startCheckLongPress(); + } + if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) { + cancelCheckLongPress(); + } + return pressedLink != null; + } + + private TLRPC.Photo getPhotoWithId(long id) { + if (currentPage == null || currentPage.cached_page == null) { + return null; + } + if (currentPage.photo != null && currentPage.photo.id == id) { + return currentPage.photo; + } + for (int a = 0; a < currentPage.cached_page.photos.size(); a++) { + TLRPC.Photo photo = currentPage.cached_page.photos.get(a); + if (photo.id == id) { + return photo; + } + } + return null; + } + + private TLRPC.Document getDocumentWithId(long id) { + if (currentPage == null || currentPage.cached_page == null) { + return null; + } + if (currentPage.document != null && currentPage.document.id == id) { + return currentPage.document; + } + for (int a = 0; a < currentPage.cached_page.videos.size(); a++) { + TLRPC.Document document = currentPage.cached_page.videos.get(a); + if (document.id == id) { + return document; + } + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.FileDidFailedLoad) { + String location = (String) args[0]; + for (int a = 0; a < 3; a++) { + if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { + radialProgressViews[a].setProgress(1.0f, true); + checkProgress(a, true); + break; + } + } + } else if (id == NotificationCenter.FileDidLoaded) { + String location = (String) args[0]; + for (int a = 0; a < 3; a++) { + if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { + radialProgressViews[a].setProgress(1.0f, true); + checkProgress(a, true); + if (a == 0 && isMediaVideo(currentIndex)) { + onActionClick(false); + } + break; + } + } + } else if (id == NotificationCenter.FileLoadProgressChanged) { + String location = (String) args[0]; + for (int a = 0; a < 3; a++) { + if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { + Float progress = (Float) args[1]; + radialProgressViews[a].setProgress(progress, true); + } + } + } else if (id == NotificationCenter.emojiDidLoaded) { + if (captionTextView != null) { + captionTextView.invalidate(); + } + } + } + + public void setParentActivity(Activity activity) { + if (parentActivity == activity) { + return; + } + parentActivity = activity; + + backgroundPaint = new Paint(); + backgroundPaint.setColor(0xffffffff); + + layerShadowDrawable = activity.getResources().getDrawable(R.drawable.layer_shadow); + slideDotDrawable = activity.getResources().getDrawable(R.drawable.slide_dot_small); + slideDotBigDrawable = activity.getResources().getDrawable(R.drawable.slide_dot_big); + scrimPaint = new Paint(); + + windowView = new WindowView(activity); + windowView.setWillNotDraw(false); + windowView.setClipChildren(true); + windowView.setFocusable(false); + + containerView = new FrameLayout(activity); + windowView.addView(containerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + containerView.setFitsSystemWindows(true); + if (Build.VERSION.SDK_INT >= 21) { + containerView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { + @SuppressLint("NewApi") + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + WindowInsets oldInsets = (WindowInsets) lastInsets; + lastInsets = insets; + if (oldInsets == null || !oldInsets.toString().equals(insets.toString())) { + windowView.requestLayout(); + } + return insets.consumeSystemWindowInsets(); + } + }); + } + containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN); + + photoContainerBackground = new View(activity); + photoContainerBackground.setVisibility(View.INVISIBLE); + photoContainerBackground.setBackgroundDrawable(photoBackgroundDrawable); + windowView.addView(photoContainerBackground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + + animatingImageView = new ClippingImageView(activity); + animatingImageView.setAnimationValues(animationValues); + animatingImageView.setVisibility(View.GONE); + windowView.addView(animatingImageView, LayoutHelper.createFrame(40, 40)); + + photoContainerView = new FrameLayoutDrawer(activity) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int y = bottom - top - captionTextView.getMeasuredHeight(); + if (bottomLayout.getVisibility() == VISIBLE) { + y -= bottomLayout.getMeasuredHeight(); + } + captionTextView.layout(0, y, captionTextView.getMeasuredWidth(), y + captionTextView.getMeasuredHeight()); + } + }; + photoContainerView.setVisibility(View.INVISIBLE); + photoContainerView.setWillNotDraw(false); + windowView.addView(photoContainerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + + fullscreenVideoContainer = new FrameLayout(activity); + fullscreenVideoContainer.setBackgroundColor(0xff000000); + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + windowView.addView(fullscreenVideoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + fullscreenAspectRatioView = new AspectRatioFrameLayout(activity); + fullscreenAspectRatioView.setVisibility(View.GONE); + fullscreenVideoContainer.addView(fullscreenAspectRatioView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + fullscreenTextureView = new TextureView(activity); + + if (Build.VERSION.SDK_INT >= 21) { + barBackground = new View(activity); + barBackground.setBackgroundColor(0xff000000); + windowView.addView(barBackground); + } + + listView = new RecyclerListView(activity); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(parentActivity, LinearLayoutManager.VERTICAL, false)); + listView.setAdapter(adapter = new WebpageAdapter(parentActivity)); + listView.setClipToPadding(false); + listView.setPadding(0, AndroidUtilities.dp(56), 0, 0); + listView.setTopGlowOffset(AndroidUtilities.dp(56)); + listView.setGlowColor(0xfff5f6f7); + containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { + @Override + public boolean onItemClick(View view, int position) { + return false; + } + }); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == blocks.size() && currentPage != null) { + if (previewsReqId != 0) { + return; + } + TLRPC.User user = MessagesController.getInstance().getUser("previews"); + if (user != null) { + openPreviewsChat(user, currentPage.id); + } else { + final long pageId = currentPage.id; + showProgressView(true); + TLRPC.TL_contacts_resolveUsername req = new TLRPC.TL_contacts_resolveUsername(); + req.username = "previews"; + previewsReqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (previewsReqId == 0) { + return; + } + previewsReqId = 0; + showProgressView(false); + if (response != null) { + TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; + MessagesController.getInstance().putUsers(res.users, false); + MessagesStorage.getInstance().putUsersAndChats(res.users, res.chats, false, true); + if (!res.users.isEmpty()) { + openPreviewsChat(res.users.get(0), pageId); + } + } + } + }); + } + }); + } + } + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (listView.getChildCount() == 0) { + return; + } + checkScroll(dy); + } + }); + headerView = new FrameLayout(activity); + headerView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + headerView.setBackgroundColor(0xff000000); + containerView.addView(headerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 56)); + + backButton = new ImageView(activity); + backButton.setScaleType(ImageView.ScaleType.CENTER); + backDrawable = new BackDrawable(false); + backDrawable.setAnimationTime(200.0f); + backDrawable.setColor(0xffb3b3b3); + backDrawable.setRotated(false); + backButton.setImageDrawable(backDrawable); + backButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + headerView.addView(backButton, LayoutHelper.createFrame(54, 56)); + backButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + /*if (collapsed) { + uncollapse(); + } else { + collapse(); + }*/ + close(true, true); + } + }); + + shareContainer = new FrameLayout(activity); + headerView.addView(shareContainer, LayoutHelper.createFrame(48, 56, Gravity.TOP | Gravity.RIGHT)); + shareContainer.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (currentPage == null || parentActivity == null) { + return; + } + showDialog(new ShareAlert(parentActivity, null, currentPage.url, false, currentPage.url, true)); + hideActionBar(); + } + }); + + shareButton = new ImageView(activity); + shareButton.setScaleType(ImageView.ScaleType.CENTER); + shareButton.setImageResource(R.drawable.ic_share_article); + shareButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + shareContainer.addView(shareButton, LayoutHelper.createFrame(48, 56)); + + progressView = new ContextProgressView(activity, 2); + progressView.setVisibility(View.GONE); + shareContainer.addView(progressView, LayoutHelper.createFrame(48, 56)); + + windowLayoutParams = new WindowManager.LayoutParams(); + windowLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT; + windowLayoutParams.format = PixelFormat.TRANSLUCENT; + windowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT; + windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; + windowLayoutParams.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + if (Build.VERSION.SDK_INT >= 21) { + windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } else { + windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + } + + if (progressDrawables == null) { + progressDrawables = new Drawable[4]; + progressDrawables[0] = parentActivity.getResources().getDrawable(R.drawable.circle_big); + progressDrawables[1] = parentActivity.getResources().getDrawable(R.drawable.cancel_big); + progressDrawables[2] = parentActivity.getResources().getDrawable(R.drawable.load_big); + progressDrawables[3] = parentActivity.getResources().getDrawable(R.drawable.play_big); + } + + scroller = new Scroller(activity); + + blackPaint.setColor(0xff000000); + + actionBar = new ActionBar(activity); + actionBar.setBackgroundColor(Theme.ACTION_BAR_PHOTO_VIEWER_COLOR); + actionBar.setOccupyStatusBar(false); + actionBar.setTitleColor(0xffffffff); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, false); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); + photoContainerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + closePhoto(true); + } else if (id == gallery_menu_save) { + if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + parentActivity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 4); + return; + } + File f = getMediaFile(currentIndex); + if (f != null && f.exists()) { + MediaController.saveFile(f.toString(), parentActivity, isMediaVideo(currentIndex) ? 1 : 0, null, null); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.getString("PleaseDownload", R.string.PleaseDownload)); + showDialog(builder.create()); + } + } else if (id == gallery_menu_share) { + onSharePressed(); + } else if (id == gallery_menu_openin) { + try { + AndroidUtilities.openForView(getMedia(currentIndex), parentActivity); + closePhoto(false); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + @Override + public boolean canOpenMenu() { + File f = getMediaFile(currentIndex); + return f != null && f.exists(); + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + + menu.addItem(gallery_menu_share, R.drawable.share); + menuItem = menu.addItem(0, R.drawable.ic_ab_other); + menuItem.setLayoutInScreen(true); + menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); + //menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile), 0); + menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); + + bottomLayout = new FrameLayout(parentActivity); + bottomLayout.setBackgroundColor(0x7f000000); + photoContainerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); + + captionTextViewOld = new TextView(activity); + captionTextViewOld.setMaxLines(10); + captionTextViewOld.setBackgroundColor(0x7f000000); + captionTextViewOld.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); + captionTextViewOld.setLinkTextColor(0xffffffff); + captionTextViewOld.setTextColor(0xffffffff); + captionTextViewOld.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + captionTextViewOld.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + captionTextViewOld.setVisibility(View.INVISIBLE); + photoContainerView.addView(captionTextViewOld, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); + + captionTextView = captionTextViewNew = new TextView(activity); + captionTextViewNew.setMaxLines(10); + captionTextViewNew.setBackgroundColor(0x7f000000); + captionTextViewNew.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); + captionTextViewNew.setLinkTextColor(0xffffffff); + captionTextViewNew.setTextColor(0xffffffff); + captionTextViewNew.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + captionTextViewNew.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + captionTextViewNew.setVisibility(View.INVISIBLE); + photoContainerView.addView(captionTextViewNew, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); + + radialProgressViews[0] = new RadialProgressView(activity, photoContainerView); + radialProgressViews[0].setBackgroundState(0, false); + radialProgressViews[1] = new RadialProgressView(activity, photoContainerView); + radialProgressViews[1].setBackgroundState(0, false); + radialProgressViews[2] = new RadialProgressView(activity, photoContainerView); + radialProgressViews[2].setBackgroundState(0, false); + + videoPlayerSeekbar = new SeekBar(activity); + videoPlayerSeekbar.setColors(0x66ffffff, 0xffffffff, 0xffffffff); + videoPlayerSeekbar.setDelegate(new SeekBar.SeekBarDelegate() { + @Override + public void onSeekBarDrag(float progress) { + if (videoPlayer != null) { + videoPlayer.seekTo((int) (progress * videoPlayer.getDuration())); + } + } + }); + + videoPlayerControlFrameLayout = new FrameLayout(activity) { + @Override + public boolean onTouchEvent(MotionEvent event) { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (videoPlayerSeekbar.onTouch(event.getAction(), event.getX() - AndroidUtilities.dp(48), event.getY())) { + getParent().requestDisallowInterceptTouchEvent(true); + invalidate(); + return true; + } + return super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + long duration; + if (videoPlayer != null) { + duration = videoPlayer.getDuration(); + if (duration == C.TIME_UNSET) { + duration = 0; + } + } else { + duration = 0; + } + duration /= 1000; + int size = (int) Math.ceil(videoPlayerTime.getPaint().measureText(String.format("%02d:%02d / %02d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); + videoPlayerSeekbar.setSize(getMeasuredWidth() - AndroidUtilities.dp(48 + 16) - size, getMeasuredHeight()); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + float progress = 0; + if (videoPlayer != null) { + progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); + } + videoPlayerSeekbar.setProgress(progress); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(48), 0); + videoPlayerSeekbar.draw(canvas); + canvas.restore(); + } + }; + videoPlayerControlFrameLayout.setWillNotDraw(false); + bottomLayout.addView(videoPlayerControlFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + + videoPlayButton = new ImageView(activity); + videoPlayButton.setScaleType(ImageView.ScaleType.CENTER); + videoPlayerControlFrameLayout.addView(videoPlayButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); + videoPlayButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (videoPlayer != null) { + if (isPlaying) { + videoPlayer.pause(); + } else { + videoPlayer.play(); + } + } + } + }); + + videoPlayerTime = new TextView(activity); + videoPlayerTime.setTextColor(0xffffffff); + videoPlayerTime.setGravity(Gravity.CENTER_VERTICAL); + videoPlayerTime.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + videoPlayerControlFrameLayout.addView(videoPlayerTime, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 8, 0)); + + gestureDetector = new GestureDetector(activity, this); + gestureDetector.setOnDoubleTapListener(this); + + ImageReceiver.ImageReceiverDelegate imageReceiverDelegate = new ImageReceiver.ImageReceiverDelegate() { + @Override + public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { + if (imageReceiver == centerImage && set && scaleToFill()) { + if (!wasLayout) { + dontResetZoomOnFirstLayout = true; + } else { + setScaleToFill(); + } + } + } + }; + + centerImage.setParentView(photoContainerView); + centerImage.setCrossfadeAlpha((byte) 2); + centerImage.setInvalidateAll(true); + centerImage.setDelegate(imageReceiverDelegate); + leftImage.setParentView(photoContainerView); + leftImage.setCrossfadeAlpha((byte) 2); + leftImage.setInvalidateAll(true); + leftImage.setDelegate(imageReceiverDelegate); + rightImage.setParentView(photoContainerView); + rightImage.setCrossfadeAlpha((byte) 2); + rightImage.setInvalidateAll(true); + rightImage.setDelegate(imageReceiverDelegate); + } + + private void checkScroll(int dy) { + int maxHeight = AndroidUtilities.dp(56); + int minHeight = Math.max(AndroidUtilities.statusBarHeight, AndroidUtilities.dp(24)); + float heightDiff = maxHeight - minHeight; + int newHeight = currentHeaderHeight - dy; + if (newHeight < minHeight) { + newHeight = minHeight; + } else if (newHeight > maxHeight) { + newHeight = maxHeight; + } + currentHeaderHeight = newHeight; + float scale = 0.8f + (currentHeaderHeight - minHeight) / heightDiff * 0.2f; + int scaledHeight = (int) (maxHeight * scale); + backButton.setScaleX(scale); + backButton.setScaleY(scale); + backButton.setTranslationY((maxHeight - currentHeaderHeight) / 2); + shareContainer.setScaleX(scale); + shareContainer.setScaleY(scale); + shareContainer.setTranslationY((maxHeight - currentHeaderHeight) / 2); + headerView.setTranslationY(currentHeaderHeight - maxHeight); + listView.setTopGlowOffset(currentHeaderHeight); + } + + private void openPreviewsChat(TLRPC.User user, long wid) { + if (user == null || parentActivity == null) { + return; + } + Bundle args = new Bundle(); + args.putInt("user_id", user.id); + args.putString("botUser", "webpage" + wid); + ((LaunchActivity) parentActivity).presentFragment(new ChatActivity(args), false, true); + close(false, true); + } + + private void addAllMediaFromBlock(TLRPC.PageBlock block) { + if (block instanceof TLRPC.TL_pageBlockPhoto || block instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { + photoBlocks.add(block); + } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { + TLRPC.TL_pageBlockSlideshow slideshow = (TLRPC.TL_pageBlockSlideshow) block; + int count = slideshow.items.size(); + for (int a = 0; a < count; a++) { + TLRPC.PageBlock innerBlock = slideshow.items.get(a); + if (innerBlock instanceof TLRPC.TL_pageBlockPhoto || innerBlock instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { + photoBlocks.add(innerBlock); + } + } + } else if (block instanceof TLRPC.TL_pageBlockCollage) { + TLRPC.TL_pageBlockCollage collage = (TLRPC.TL_pageBlockCollage) block; + int count = collage.items.size(); + for (int a = 0; a < count; a++) { + TLRPC.PageBlock innerBlock = collage.items.get(a); + if (innerBlock instanceof TLRPC.TL_pageBlockPhoto || innerBlock instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block)) { + photoBlocks.add(innerBlock); + } + } + } else if (block instanceof TLRPC.TL_pageBlockCover && (block.cover instanceof TLRPC.TL_pageBlockPhoto || block.cover instanceof TLRPC.TL_pageBlockVideo && isVideoBlock(block.cover))) { + photoBlocks.add(block.cover); + } + } + + public boolean open(MessageObject messageObject) { + return open(messageObject, true); + } + + private boolean open(final MessageObject messageObject, boolean first) { + if (parentActivity == null || isVisible && !collapsed || messageObject == null) { + return false; + } + + if (first) { + TLRPC.TL_messages_getWebPage req = new TLRPC.TL_messages_getWebPage(); + req.url = messageObject.messageOwner.media.webpage.url; + if (messageObject.messageOwner.media.webpage.cached_page instanceof TLRPC.TL_pagePart) { + req.hash = 0; + } else { + req.hash = messageObject.messageOwner.media.webpage.hash; + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_webPage) { + final TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) response; + if (webPage.cached_page == null) { + return; + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (!pagesStack.isEmpty() && pagesStack.get(0) == messageObject.messageOwner.media.webpage && webPage.cached_page != null) { + messageObject.messageOwner.media.webpage = webPage; + pagesStack.set(0, webPage); + if (pagesStack.size() == 1) { + currentPage = webPage; + ApplicationLoader.applicationContext.getSharedPreferences("articles", Activity.MODE_PRIVATE).edit().remove("article" + currentPage.id).commit(); + updateInterfaceForCurrentPage(false); + } + } + } + }); + HashMap webpages = new HashMap<>(); + webpages.put(webPage.id, webPage); + MessagesStorage.getInstance().putWebPages(webpages); + } + } + }); + } + + pagesStack.clear(); + collapsed = false; + backDrawable.setRotation(0, false); + containerView.setTranslationX(0); + containerView.setTranslationY(0); + listView.setTranslationY(0); + listView.setAlpha(1.0f); + windowView.setInnerTranslationX(0); + + actionBar.setVisibility(View.GONE); + bottomLayout.setVisibility(View.GONE); + captionTextViewNew.setVisibility(View.GONE); + captionTextViewOld.setVisibility(View.GONE); + shareContainer.setAlpha(0.0f); + backButton.setAlpha(0.0f); + layoutManager.scrollToPositionWithOffset(0, 0); + checkScroll(-AndroidUtilities.dp(56)); + + TLRPC.WebPage webPage = messageObject.messageOwner.media.webpage; + String webPageUrl = webPage.url.toLowerCase(); + int index; + String anchor = null; + for (int a = 0; a < messageObject.messageOwner.entities.size(); a++) { + TLRPC.MessageEntity entity = messageObject.messageOwner.entities.get(a); + if (entity instanceof TLRPC.TL_messageEntityUrl) { + try { + String url = messageObject.messageOwner.message.substring(entity.offset, entity.offset + entity.length).toLowerCase(); + if (url.contains(webPageUrl) || webPageUrl.contains(url)) { + if ((index = url.lastIndexOf('#')) != -1) { + anchor = url.substring(index + 1); + } + break; + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + addPageToStack(webPage, anchor); + + lastInsets = null; + if (!isVisible) { + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + if (attachedToWindow) { + try { + wm.removeView(windowView); + } catch (Exception e) { + //ignore + } + } + try { + if (Build.VERSION.SDK_INT >= 21) { + windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | + WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | + WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + } + windowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; + windowView.setFocusable(false); + containerView.setFocusable(false); + wm.addView(windowView, windowLayoutParams); + } catch (Exception e) { + FileLog.e(e); + return false; + } + } else { + windowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.updateViewLayout(windowView, windowLayoutParams); + } + isVisible = true; + animationInProgress = 1; + windowView.setAlpha(0); + containerView.setAlpha(0); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(windowView, "alpha", 0, 1.0f), + ObjectAnimator.ofFloat(containerView, "alpha", 0.0f, 1.0f), + ObjectAnimator.ofFloat(windowView, "translationX", AndroidUtilities.dp(56), 0) + ); + + animationEndRunnable = new Runnable() { + @Override + public void run() { + if (containerView == null || windowView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + animationInProgress = 0; + } + }; + + animatorSet.setDuration(150); + animatorSet.setInterpolator(interpolator); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().setAnimationInProgress(false); + if (animationEndRunnable != null) { + animationEndRunnable.run(); + animationEndRunnable = null; + } + } + }); + } + }); + transitionAnimationStartTime = System.currentTimeMillis(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded}); + NotificationCenter.getInstance().setAnimationInProgress(true); + animatorSet.start(); + } + }); + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + showActionBar(200); + return true; + } + + private void hideActionBar() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(backButton, "alpha", 0.0f), + ObjectAnimator.ofFloat(shareContainer, "alpha", 0.0f)); + animatorSet.setDuration(250); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.start(); + } + + private void showActionBar(int delay) { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(backButton, "alpha", 1.0f), + ObjectAnimator.ofFloat(shareContainer, "alpha", 1.0f)); + animatorSet.setDuration(150); + animatorSet.setStartDelay(delay); + animatorSet.start(); + } + + private void showProgressView(final boolean show) { + if (progressViewAnimation != null) { + progressViewAnimation.cancel(); + } + progressViewAnimation = new AnimatorSet(); + if (show) { + progressView.setVisibility(View.VISIBLE); + shareContainer.setEnabled(false); + progressViewAnimation.playTogether( + ObjectAnimator.ofFloat(shareButton, "scaleX", 0.1f), + ObjectAnimator.ofFloat(shareButton, "scaleY", 0.1f), + ObjectAnimator.ofFloat(shareButton, "alpha", 0.0f), + ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); + } else { + shareButton.setVisibility(View.VISIBLE); + shareContainer.setEnabled(true); + progressViewAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), + ObjectAnimator.ofFloat(shareButton, "scaleX", 1.0f), + ObjectAnimator.ofFloat(shareButton, "scaleY", 1.0f), + ObjectAnimator.ofFloat(shareButton, "alpha", 1.0f)); + + } + progressViewAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (progressViewAnimation != null && progressViewAnimation.equals(animation)) { + if (!show) { + progressView.setVisibility(View.INVISIBLE); + } else { + shareButton.setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (progressViewAnimation != null && progressViewAnimation.equals(animation)) { + progressViewAnimation = null; + } + } + }); + progressViewAnimation.setDuration(150); + progressViewAnimation.start(); + } + + public void collapse() { + if (parentActivity == null || !isVisible || checkAnimation()) { + return; + } + if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { + if (customView != null) { + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + customViewCallback.onCustomViewHidden(); + fullscreenVideoContainer.removeView(customView); + customView = null; + } else if (fullscreenedVideo != null) { + fullscreenedVideo.exitFullscreen(); + } + } + if (isPhotoVisible) { + closePhoto(false); + } + try { + if (visibleDialog != null) { + visibleDialog.dismiss(); + visibleDialog = null; + } + } catch (Exception e) { + FileLog.e(e); + } + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(containerView, "translationX", containerView.getMeasuredWidth() - AndroidUtilities.dp(56)), + ObjectAnimator.ofFloat(containerView, "translationY", ActionBar.getCurrentActionBarHeight() + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)), + ObjectAnimator.ofFloat(windowView, "alpha", 0.0f), + ObjectAnimator.ofFloat(listView, "alpha", 0.0f), + ObjectAnimator.ofFloat(listView, "translationY", -AndroidUtilities.dp(56)), + ObjectAnimator.ofFloat(headerView, "translationY", 0), + + ObjectAnimator.ofFloat(backButton, "scaleX", 1.0f), + ObjectAnimator.ofFloat(backButton, "scaleY", 1.0f), + ObjectAnimator.ofFloat(backButton, "translationY", 0), + ObjectAnimator.ofFloat(shareContainer, "scaleX", 1.0f), + ObjectAnimator.ofFloat(shareContainer, "translationY", 0), + ObjectAnimator.ofFloat(shareContainer, "scaleY", 1.0f) + ); + collapsed = true; + animationInProgress = 2; + animationEndRunnable = new Runnable() { + @Override + public void run() { + if (containerView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + animationInProgress = 0; + + //windowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.updateViewLayout(windowView, windowLayoutParams); + + //onClosed(); + //containerView.setScaleX(1.0f); + //containerView.setScaleY(1.0f); + } + }; + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(250); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animationEndRunnable != null) { + animationEndRunnable.run(); + animationEndRunnable = null; + } + } + }); + transitionAnimationStartTime = System.currentTimeMillis(); + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + backDrawable.setRotation(1, true); + animatorSet.start(); + } + + public void uncollapse() { + if (parentActivity == null || !isVisible || checkAnimation()) { + return; + } + + /*windowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.updateViewLayout(windowView, windowLayoutParams);*/ + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(containerView, "translationX", 0), + ObjectAnimator.ofFloat(containerView, "translationY", 0), + ObjectAnimator.ofFloat(windowView, "alpha", 1.0f), + ObjectAnimator.ofFloat(listView, "alpha", 1.0f), + ObjectAnimator.ofFloat(listView, "translationY", 0), + ObjectAnimator.ofFloat(headerView, "translationY", 0), + + ObjectAnimator.ofFloat(backButton, "scaleX", 1.0f), + ObjectAnimator.ofFloat(backButton, "scaleY", 1.0f), + ObjectAnimator.ofFloat(backButton, "translationY", 0), + ObjectAnimator.ofFloat(shareContainer, "scaleX", 1.0f), + ObjectAnimator.ofFloat(shareContainer, "translationY", 0), + ObjectAnimator.ofFloat(shareContainer, "scaleY", 1.0f) + ); + collapsed = false; + animationInProgress = 2; + animationEndRunnable = new Runnable() { + @Override + public void run() { + if (containerView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + animationInProgress = 0; + //onClosed(); + } + }; + animatorSet.setDuration(250); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animationEndRunnable != null) { + animationEndRunnable.run(); + animationEndRunnable = null; + } + } + }); + transitionAnimationStartTime = System.currentTimeMillis(); + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + backDrawable.setRotation(0, true); + animatorSet.start(); + } + + private void saveCurrentPagePosition() { + if (currentPage == null) { + return; + } + int position = layoutManager.findFirstVisibleItemPosition(); + if (position != RecyclerView.NO_POSITION) { + int offset; + View view = layoutManager.findViewByPosition(position); + if (view != null) { + offset = view.getTop(); + } else { + offset = 0; + } + SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("articles", Activity.MODE_PRIVATE).edit(); + String key = "article" + currentPage.id; + editor.putInt(key, position).putInt(key + "o", offset).putBoolean(key + "r", AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y).commit(); + } + } + + public void close(boolean byBackPress, boolean force) { + if (parentActivity == null || !isVisible || checkAnimation()) { + return; + } + if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { + if (customView != null) { + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + customViewCallback.onCustomViewHidden(); + fullscreenVideoContainer.removeView(customView); + customView = null; + } else if (fullscreenedVideo != null) { + fullscreenedVideo.exitFullscreen(); + } + if (!force) { + return; + } + } + if (isPhotoVisible) { + closePhoto(!force); + if (!force) { + return; + } + } + if (openUrlReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(openUrlReqId, true); + openUrlReqId = 0; + showProgressView(false); + } + if (previewsReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(previewsReqId, true); + previewsReqId = 0; + showProgressView(false); + } + saveCurrentPagePosition(); + if (byBackPress && !force) { + if (removeLastPageFromStack()) { + return; + } + } + + try { + if (visibleDialog != null) { + visibleDialog.dismiss(); + visibleDialog = null; + } + } catch (Exception e) { + FileLog.e(e); + } + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(windowView, "alpha", 0), + ObjectAnimator.ofFloat(containerView, "alpha", 0.0f), + ObjectAnimator.ofFloat(windowView, "translationX", 0, AndroidUtilities.dp(56)) + ); + animationInProgress = 2; + animationEndRunnable = new Runnable() { + @Override + public void run() { + if (containerView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + animationInProgress = 0; + onClosed(); + } + }; + animatorSet.setDuration(150); + animatorSet.setInterpolator(interpolator); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animationEndRunnable != null) { + animationEndRunnable.run(); + animationEndRunnable = null; + } + } + }); + transitionAnimationStartTime = System.currentTimeMillis(); + if (Build.VERSION.SDK_INT >= 18) { + containerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + animatorSet.start(); + } + + private void onClosed() { + isVisible = false; + currentPage = null; + blocks.clear(); + photoBlocks.clear(); + adapter.notifyDataSetChanged(); + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + for (int a = 0; a < createdWebViews.size(); a++) { + BlockEmbedCell cell = createdWebViews.get(a); + cell.destroyWebView(false); + } + containerView.post(new Runnable() { + @Override + public void run() { + try { + if (windowView.getParent() != null) { + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(windowView); + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } + + private boolean checkAnimation() { + if (animationInProgress != 0) { + if (Math.abs(transitionAnimationStartTime - System.currentTimeMillis()) >= 500) { + if (animationEndRunnable != null) { + animationEndRunnable.run(); + animationEndRunnable = null; + } + animationInProgress = 0; + } + } + return animationInProgress != 0; + } + + public void destroyArticleViewer() { + if (parentActivity == null || windowView == null) { + return; + } + releasePlayer(); + try { + if (windowView.getParent() != null) { + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.removeViewImmediate(windowView); + } + windowView = null; + } catch (Exception e) { + FileLog.e(e); + } + for (int a = 0; a < createdWebViews.size(); a++) { + BlockEmbedCell cell = createdWebViews.get(a); + cell.destroyWebView(true); + } + createdWebViews.clear(); + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + Instance = null; + } + + public boolean isVisible() { + return isVisible; + } + + public void showDialog(Dialog dialog) { + if (parentActivity == null) { + return; + } + try { + if (visibleDialog != null) { + visibleDialog.dismiss(); + visibleDialog = null; + } + } catch (Exception e) { + FileLog.e(e); + } + try { + visibleDialog = dialog; + visibleDialog.setCanceledOnTouchOutside(true); + visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + showActionBar(120); + visibleDialog = null; + } + }); + dialog.show(); + } catch (Exception e) { + FileLog.e(e); + } + } + + private class WebpageAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + + public WebpageAdapter(Context ctx) { + context = ctx; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: { + view = new BlockParagraphCell(context); + break; + } + case 1: { + view = new BlockHeaderCell(context); + break; + } + case 2: { + view = new BlockDividerCell(context); + break; + } + case 3: { + view = new BlockEmbedCell(context); + break; + } + case 4: { + view = new BlockSubtitleCell(context); + break; + } + case 5: { + view = new BlockVideoCell(context, 0); + break; + } + case 6: { + view = new BlockPullquoteCell(context); + break; + } + case 7: { + view = new BlockBlockquoteCell(context); + break; + } + case 8: { + view = new BlockSlideshowCell(context); + break; + } + case 9: { + view = new BlockPhotoCell(context, 0); + break; + } + case 10: { + view = new BlockAuthorDateCell(context); + break; + } + case 11: { + view = new BlockTitleCell(context); + break; + } + case 12: { + view = new BlockListCell(context); + break; + } + case 13: { + view = new BlockFooterCell(context); + break; + } + case 14: { + view = new BlockPreformattedCell(context); + break; + } + case 15: { + view = new BlockSubheaderCell(context); + break; + } + case 16: { + view = new BlockEmbedPostCell(context); + break; + } + case 17:{ + view = new BlockCollageCell(context); + break; + } + case 90: + default: { + FrameLayout frameLayout = new FrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY)); + } + }; + TextView textView = new TextView(context); + frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 34, Gravity.LEFT | Gravity.TOP, 0, 10, 0, 0)); + textView.setTextColor(0xff78828d); + textView.setBackgroundColor(0xffedeff0); + textView.setText(LocaleController.getString("PreviewFeedback", R.string.PreviewFeedback)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + textView.setGravity(Gravity.CENTER); + view = frameLayout; + break; + } + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (position < blocks.size()) { + TLRPC.PageBlock block = blocks.get(position); + TLRPC.PageBlock originalBlock = block; + if (block instanceof TLRPC.TL_pageBlockCover) { + block = block.cover; + } + switch (holder.getItemViewType()) { + case 0: { + BlockParagraphCell cell = (BlockParagraphCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockParagraph) block); + break; + } + case 1: { + BlockHeaderCell cell = (BlockHeaderCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockHeader) block); + break; + } + case 2: { + BlockDividerCell cell = (BlockDividerCell) holder.itemView; + break; + } + case 3: { + BlockEmbedCell cell = (BlockEmbedCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockEmbed) block); + break; + } + case 4: { + BlockSubtitleCell cell = (BlockSubtitleCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockSubtitle) block); + break; + } + case 5: { + BlockVideoCell cell = (BlockVideoCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockVideo) block, position == 0, position == blocks.size() - 1); + cell.setParentBlock(originalBlock); + break; + } + case 6: { + BlockPullquoteCell cell = (BlockPullquoteCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockPullquote) block); + break; + } + case 7: { + BlockBlockquoteCell cell = (BlockBlockquoteCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockBlockquote) block); + break; + } + case 8: { + BlockSlideshowCell cell = (BlockSlideshowCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockSlideshow) block); + break; + } + case 9: { + BlockPhotoCell cell = (BlockPhotoCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockPhoto) block, position == 0, position == blocks.size() - 1); + cell.setParentBlock(originalBlock); + break; + } + case 10: { + BlockAuthorDateCell cell = (BlockAuthorDateCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockAuthorDate) block); + break; + } + case 11: { + BlockTitleCell cell = (BlockTitleCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockTitle) block); + break; + } + case 12: { + BlockListCell cell = (BlockListCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockList) block); + break; + } + case 13: { + BlockFooterCell cell = (BlockFooterCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockFooter) block); + break; + } + case 14: { + BlockPreformattedCell cell = (BlockPreformattedCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockPreformatted) block); + break; + } + case 15: { + BlockSubheaderCell cell = (BlockSubheaderCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockSubheader) block); + break; + } + case 16: { + BlockEmbedPostCell cell = (BlockEmbedPostCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockEmbedPost) block); + break; + } + case 17: { + BlockCollageCell cell = (BlockCollageCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockCollage) block); + break; + } + } + } + } + + private int getTypeForBlock(TLRPC.PageBlock block) { + if (block instanceof TLRPC.TL_pageBlockParagraph) { + return 0; + } else if (block instanceof TLRPC.TL_pageBlockHeader) { + return 1; + } else if (block instanceof TLRPC.TL_pageBlockDivider) { + return 2; + } else if (block instanceof TLRPC.TL_pageBlockEmbed) { + return 3; + } else if (block instanceof TLRPC.TL_pageBlockSubtitle) { + return 4; + } else if (block instanceof TLRPC.TL_pageBlockVideo) { + return 5; + } else if (block instanceof TLRPC.TL_pageBlockPullquote) { + return 6; + } else if (block instanceof TLRPC.TL_pageBlockBlockquote) { + return 7; + } else if (block instanceof TLRPC.TL_pageBlockSlideshow) { + return 8; + } else if (block instanceof TLRPC.TL_pageBlockPhoto) { + return 9; + } else if (block instanceof TLRPC.TL_pageBlockAuthorDate) { + return 10; + } else if (block instanceof TLRPC.TL_pageBlockTitle) { + return 11; + } else if (block instanceof TLRPC.TL_pageBlockList) { + return 12; + } else if (block instanceof TLRPC.TL_pageBlockFooter) { + return 13; + } else if (block instanceof TLRPC.TL_pageBlockPreformatted) { + return 14; + } else if (block instanceof TLRPC.TL_pageBlockSubheader) { + return 15; + } else if (block instanceof TLRPC.TL_pageBlockEmbedPost) { + return 16; + } else if (block instanceof TLRPC.TL_pageBlockCollage) { + return 17; + } else if (block instanceof TLRPC.TL_pageBlockCover) { + return getTypeForBlock(block.cover); + } + return 0; + } + + @Override + public int getItemViewType(int position) { + if (position == blocks.size()) { + return 90; + } + return getTypeForBlock(blocks.get(position)); + } + + @Override + public int getItemCount() { + return currentPage != null && currentPage.cached_page != null ? blocks.size() + 1 : 0; + } + } + + private class BlockVideoCell extends View implements MediaController.FileDownloadProgressListener { + + private StaticLayout textLayout; + private ImageReceiver imageView; + private RadialProgress radialProgress; + private int lastCreatedWidth; + private int currentType; + private boolean isFirst; + private boolean isLast; + private int textX; + private int textY; + + private int buttonX; + private int buttonY; + private boolean photoPressed; + private int buttonState; + private int buttonPressed; + private boolean cancelLoading; + + private int TAG; + + private TLRPC.TL_pageBlockVideo currentBlock; + private TLRPC.PageBlock parentBlock; + private TLRPC.Document currentDocument; + private boolean isGif; + + public BlockVideoCell(Context context, int type) { + super(context); + + imageView = new ImageReceiver(this); + currentType = type; + radialProgress = new RadialProgress(this); + radialProgress.setProgressColor(Theme.ARTICLE_VIEWER_MEDIA_PROGRESS_COLOR); + TAG = MediaController.getInstance().generateObserverTag(); + } + + public void setBlock(TLRPC.TL_pageBlockVideo block, boolean first, boolean last) { + currentBlock = block; + parentBlock = null; + cancelLoading = false; + currentDocument = getDocumentWithId(currentBlock.video_id); + isGif = MessageObject.isGifDocument(currentDocument)/* && currentBlock.autoplay*/; + lastCreatedWidth = 0; + isFirst = first; + isLast = last; + updateButtonState(false); + requestLayout(); + } + + public void setParentBlock(TLRPC.PageBlock block) { + parentBlock = block; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + + if (event.getAction() == MotionEvent.ACTION_DOWN && imageView.isInsideImage(x, y)) { + if (buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48) || buttonState == 0) { + buttonPressed = 1; + invalidate(); + } else { + photoPressed = true; + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (photoPressed) { + photoPressed = false; + openPhoto(currentBlock); + } else if (buttonPressed == 1) { + buttonPressed = 0; + playSoundEffect(SoundEffectConstants.CLICK); + didPressedButton(false); + radialProgress.swapBackground(getDrawableForCurrentState()); + invalidate(); + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + photoPressed = false; + } + return photoPressed || buttonPressed != 0 || checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + if (currentType == 1) { + width = listView.getWidth(); + height = ((View) getParent()).getMeasuredHeight(); + } else if (currentType == 2) { + height = width; + } + + if (currentBlock != null) { + int photoWidth = width; + int photoX; + int textWidth; + if (currentType == 0 && currentBlock.level > 0) { + textX = photoX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); + photoWidth -= photoX + AndroidUtilities.dp(18); + textWidth = photoWidth; + } else { + photoX = 0; + textX = AndroidUtilities.dp(18); + textWidth = width - AndroidUtilities.dp(36); + } + if (currentDocument != null) { + TLRPC.PhotoSize thumb = currentDocument.thumb; + if (currentType == 0) { + float scale; + scale = photoWidth / (float) thumb.w; + height = (int) (scale * thumb.h); + if (parentBlock instanceof TLRPC.TL_pageBlockCover) { + height = Math.min(height, photoWidth); + } else { + int maxHeight = (int) ((Math.max(listView.getMeasuredWidth(), listView.getMeasuredHeight()) - AndroidUtilities.dp(56)) * 0.9f); + if (height > maxHeight) { + height = maxHeight; + scale = height / (float) thumb.h; + photoWidth = (int) (scale * thumb.w); + photoX += (width - photoX - photoWidth) / 2; + } + } + } + imageView.setImageCoords(photoX, (isFirst || currentType == 1 || currentType == 2 || currentBlock.level > 0) ? 0 : AndroidUtilities.dp(8), photoWidth, height); + if (isGif) { + String filter = String.format(Locale.US, "%d_%d", photoWidth, height); + imageView.setImage(currentDocument, filter, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, currentDocument.size, null, true); + } else { + imageView.setImage(null, null, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, 0, null, true); + } + + int size = AndroidUtilities.dp(48); + buttonX = (int) (imageView.getImageX() + (imageView.getImageWidth() - size) / 2.0f); + buttonY = (int) (imageView.getImageY() + (imageView.getImageHeight() - size) / 2.0f); + radialProgress.setProgressRect(buttonX, buttonY, buttonX + size, buttonY + size); + } + + if (currentType == 0 && lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + if (!isFirst && currentType == 0 && currentBlock.level <= 0) { + height += AndroidUtilities.dp(8); + } + if (currentType != 2) { + height += AndroidUtilities.dp(8); + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + imageView.draw(canvas); + if (imageView.getVisible()) { + radialProgress.draw(canvas); + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY = imageView.getImageY() + imageView.getImageHeight() + AndroidUtilities.dp(8)); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + + private Drawable getDrawableForCurrentState() { + radialProgress.setAlphaForPrevious(true); + if (buttonState >= 0 && buttonState < 4) { + return Theme.chat_photoStatesDrawables[buttonState][buttonPressed]; + } + return null; + } + + public void updateButtonState(boolean animated) { + String fileName = FileLoader.getAttachFileName(currentDocument); + File path = FileLoader.getPathToAttach(currentDocument, true); + boolean fileExists = path.exists(); + if (TextUtils.isEmpty(fileName)) { + radialProgress.setBackground(null, false, false); + return; + } + if (!fileExists) { + MediaController.getInstance().addLoadingFileObserver(fileName, null, this); + float setProgress = 0; + boolean progressVisible = false; + if (!FileLoader.getInstance().isLoadingFile(fileName)) { + if (!cancelLoading && isGif) { + progressVisible = true; + buttonState = 1; + } else { + buttonState = 0; + } + } else { + progressVisible = true; + buttonState = 1; + Float progress = ImageLoader.getInstance().getFileProgress(fileName); + setProgress = progress != null ? progress : 0; + } + radialProgress.setBackground(getDrawableForCurrentState(), progressVisible, animated); + radialProgress.setProgress(setProgress, false); + invalidate(); + } else { + MediaController.getInstance().removeLoadingFileObserver(this); + if (!isGif) { + buttonState = 3; + } else { + buttonState = -1; + } + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + invalidate(); + } + } + + private void didPressedButton(boolean animated) { + if (buttonState == 0) { + cancelLoading = false; + radialProgress.setProgress(0, false); + if (isGif) { + imageView.setImage(currentDocument, null, currentDocument.thumb != null ? currentDocument.thumb.location : null, "80_80_b", currentDocument.size, null, true); + } else { + FileLoader.getInstance().loadFile(currentDocument, true, true); + } + buttonState = 1; + radialProgress.setBackground(getDrawableForCurrentState(), true, animated); + invalidate(); + } else if (buttonState == 1) { + cancelLoading = true; + if (isGif) { + imageView.cancelLoadImage(); + } else { + FileLoader.getInstance().cancelLoadFile(currentDocument); + } + buttonState = 0; + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + invalidate(); + } else if (buttonState == 2) { + imageView.setAllowStartAnimation(true); + imageView.startAnimation(); + buttonState = -1; + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + } else if (buttonState == 3) { + openPhoto(currentBlock); + } + } + + @Override + public void onFailedDownload(String fileName) { + updateButtonState(false); + } + + @Override + public void onSuccessDownload(String fileName) { + radialProgress.setProgress(1, true); + if (isGif) { + buttonState = 2; + didPressedButton(true); + } else { + updateButtonState(true); + } + } + + @Override + public void onProgressUpload(String fileName, float progress, boolean isEncrypted) { + + } + + @Override + public void onProgressDownload(String fileName, float progress) { + radialProgress.setProgress(progress, true); + if (buttonState != 1) { + updateButtonState(false); + } + } + + @Override + public int getObserverTag() { + return TAG; + } + } + + private class BlockEmbedPostCell extends View { + + private ImageReceiver avatarImageView; + private AvatarDrawable avatarDrawable; + private StaticLayout dateLayout; + private StaticLayout nameLayout; + private StaticLayout textLayout; + private boolean avatarVisible; + private int nameX; + private int dateX; + + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18 + 14); + private int textY = AndroidUtilities.dp(40 + 8 + 8); + + private int captionX = AndroidUtilities.dp(18); + private int captionY; + + private int lineHeight; + + private TLRPC.TL_pageBlockEmbedPost currentBlock; + + public BlockEmbedPostCell(Context context) { + super(context); + avatarImageView = new ImageReceiver(this); + avatarImageView.setRoundRadius(AndroidUtilities.dp(20)); + avatarImageView.setImageCoords(AndroidUtilities.dp(18 + 14), AndroidUtilities.dp(8), AndroidUtilities.dp(40), AndroidUtilities.dp(40)); + + avatarDrawable = new AvatarDrawable(); + } + + public void setBlock(TLRPC.TL_pageBlockEmbedPost block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + if (avatarVisible = (currentBlock.author_photo_id != 0)) { + TLRPC.Photo photo = getPhotoWithId(currentBlock.author_photo_id); + if (avatarVisible = (photo != null)) { + avatarDrawable.setInfo(0, currentBlock.author, null, false); + TLRPC.PhotoSize image = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.dp(40), true); + avatarImageView.setImage(image.location, String.format(Locale.US, "%d_%d", 40, 40), avatarDrawable, 0, null, true); + } + } + nameLayout = createLayoutForText(currentBlock.author, null, width - AndroidUtilities.dp(36 + 14 + (avatarVisible ? 40 + 14 : 0)), currentBlock); + if (currentBlock.date != 0) { + dateLayout = createLayoutForText(LocaleController.getInstance().chatFullDate.format((long) currentBlock.date * 1000), null, width - AndroidUtilities.dp(36 + 14 + (avatarVisible ? 40 + 14 : 0)), currentBlock); + } else { + dateLayout = null; + } + height = AndroidUtilities.dp(40 + 8 + 8); + if (currentBlock.text != null) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36 + 14), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + } + lineHeight = height; + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (avatarVisible) { + avatarImageView.draw(canvas); + } + if (nameLayout != null) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(dateLayout != null ? 10 : 19)); + nameLayout.draw(canvas); + canvas.restore(); + } + if (dateLayout != null) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(18 + 14 + (avatarVisible ? 40 + 14 : 0)), AndroidUtilities.dp(29)); + dateLayout.draw(canvas); + canvas.restore(); + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), lineHeight - (currentBlock.level != 0 ? 0 : AndroidUtilities.dp(6)), quoteLinePaint); + } + } + + private class BlockParagraphCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX; + private int textY; + + private TLRPC.TL_pageBlockParagraph currentBlock; + + public BlockParagraphCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockParagraph block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (currentBlock.level == 0) { + if (currentBlock.caption != null) { + textY = AndroidUtilities.dp(4); + } else { + textY = AndroidUtilities.dp(8); + } + textX = AndroidUtilities.dp(18); + } else { + textY = 0; + textX = AndroidUtilities.dp(18 + 14 * currentBlock.level); + } + if (lastCreatedWidth != width) { + if (currentBlock.text != null) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(18) - textX, currentBlock); + } else if (currentBlock.caption != null) { + textLayout = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(18) - textX, currentBlock); + } + if (textLayout != null) { + height = textLayout.getHeight(); + if (currentBlock.level > 0) { + height += AndroidUtilities.dp(8); + } else { + height += AndroidUtilities.dp(8 + 8); + } + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + } + + private class BlockEmbedCell extends FrameLayout { + + private TouchyWebView webView; + private WebPlayerView videoView; + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX; + private int textY; + private int listX; + + private TLRPC.TL_pageBlockEmbed currentBlock; + + public class TouchyWebView extends WebView { + + public TouchyWebView(Context context) { + super(context); + setFocusable(false); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (currentBlock.allow_scrolling) { + requestDisallowInterceptTouchEvent(true); + } else { + windowView.requestDisallowInterceptTouchEvent(true); + } + return super.onTouchEvent(event); + } + } + + @SuppressLint("SetJavaScriptEnabled") + public BlockEmbedCell(final Context context) { + super(context); + setWillNotDraw(false); + + videoView = new WebPlayerView(context, false, false, new WebPlayerView.WebPlayerViewDelegate() { + @Override + public void onInitFailed() { + webView.setVisibility(VISIBLE); + videoView.setVisibility(INVISIBLE); + videoView.loadVideo(null, null, null, false); + HashMap args = new HashMap<>(); + args.put("Referer", "http://youtube.com"); + webView.loadUrl(currentBlock.url, args); + } + + @Override + public void onVideoSizeChanged(float aspectRatio, int rotation) { + fullscreenAspectRatioView.setAspectRatio(aspectRatio, rotation); + } + + @Override + public void onInlineSurfaceTextureReady() { + + } + + @Override + public TextureView onSwitchToFullscreen(View controlsView, boolean fullscreen, float aspectRatio, int rotation, boolean byButton) { + if (fullscreen) { + fullscreenAspectRatioView.addView(fullscreenTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + fullscreenAspectRatioView.setVisibility(View.VISIBLE); + fullscreenAspectRatioView.setAspectRatio(aspectRatio, rotation); + fullscreenedVideo = videoView; + fullscreenVideoContainer.addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + fullscreenVideoContainer.setVisibility(VISIBLE); + } else { + fullscreenAspectRatioView.removeView(fullscreenTextureView); + fullscreenedVideo = null; + fullscreenAspectRatioView.setVisibility(View.GONE); + fullscreenVideoContainer.setVisibility(INVISIBLE); + } + return fullscreenTextureView; + } + + @Override + public void prepareToSwitchInlineMode(boolean inline, Runnable switchInlineModeRunnable, float aspectRatio, boolean animated) { + + } + + @Override + public TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated) { + return null; + } + + @Override + public void onSharePressed() { + if (parentActivity == null) { + return; + } + showDialog(new ShareAlert(parentActivity, null, currentBlock.url, false, currentBlock.url, true)); + } + + @Override + public void onPlayStateChanged(WebPlayerView playerView, boolean playing) { + if (playing) { + if (currentPlayingVideo != null && currentPlayingVideo != playerView) { + currentPlayingVideo.pause(); + } + currentPlayingVideo = playerView; + try { + parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } else { + if (currentPlayingVideo == playerView) { + currentPlayingVideo = null; + } + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + @Override + public boolean checkInlinePermissons() { + return false; + } + + @Override + public ViewGroup getTextureViewContainer() { + return null; + } + }); + addView(videoView); + createdWebViews.add(this); + + webView = new TouchyWebView(context); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setDomStorageEnabled(true); + + webView.getSettings().setAllowContentAccess(true); + if (Build.VERSION.SDK_INT >= 17) { + webView.getSettings().setMediaPlaybackRequiresUserGesture(false); + } + + if (Build.VERSION.SDK_INT >= 21) { + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptThirdPartyCookies(webView, true); + } + + webView.setWebChromeClient(new WebChromeClient() { + + @Override + public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { + onShowCustomView(view, callback); + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + if (customView != null) { + callback.onCustomViewHidden(); + return; + } + customView = view; + fullscreenVideoContainer.setVisibility(VISIBLE); + fullscreenVideoContainer.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + customViewCallback = callback; + } + + @Override + public void onHideCustomView() { + super.onHideCustomView(); + if (customView == null) { + return; + } + fullscreenVideoContainer.setVisibility(INVISIBLE); + fullscreenVideoContainer.removeView(customView); + if (customViewCallback != null && !customViewCallback.getClass().getName().contains(".chromium.")) { + customViewCallback.onCustomViewHidden(); + } + customView = null; + } + }); + + webView.setWebViewClient(new WebViewClient() { + @Override + public void onLoadResource(WebView view, String url) { + super.onLoadResource(view, url); + } + + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + //progressBar.setVisibility(INVISIBLE); + } + + /*@TargetApi(Build.VERSION_CODES.LOLLIPOP) TODO check + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + Browser.openUrl(parentActivity, request.getUrl()); + return true; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Browser.openUrl(parentActivity, url); + return true; + }*/ + }); + addView(webView); + } + + public void destroyWebView(boolean completely) { + try { + webView.stopLoading(); + webView.loadUrl("about:blank"); + if (completely) { + webView.destroy(); + } + currentBlock = null; + } catch (Exception e) { + FileLog.e(e); + } + videoView.destroy(); + } + + public void setBlock(TLRPC.TL_pageBlockEmbed block) { + /*if (currentBlock == block) { + return; + }*/ + TLRPC.TL_pageBlockEmbed previousBlock = currentBlock; + currentBlock = block; + lastCreatedWidth = 0; + if (previousBlock != currentBlock) { + try { + webView.loadUrl("about:blank"); + } catch (Exception e) { + FileLog.e(e); + } + + try { + if (currentBlock.html != null) { + webView.loadData(currentBlock.html, "text/html", "UTF-8"); + } else { + TLRPC.Photo thumb = currentBlock.poster_photo_id != 0 ? getPhotoWithId(currentBlock.poster_photo_id) : null; + boolean handled = videoView.loadVideo(block.url, thumb, null, currentBlock.autoplay); + if (handled) { + webView.setVisibility(INVISIBLE); + videoView.setVisibility(VISIBLE); + } else { + webView.setVisibility(VISIBLE); + videoView.setVisibility(INVISIBLE); + videoView.loadVideo(null, null, null, false); + HashMap args = new HashMap<>(); + args.put("Referer", "http://youtube.com"); + webView.loadUrl(currentBlock.url, args); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + requestLayout(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (!isVisible) { + currentBlock = null; + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height; + + if (currentBlock != null) { + int listWidth = width; + int textWidth; + if (currentBlock.level > 0) { + textX = listX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); + listWidth -= listX + AndroidUtilities.dp(18); + textWidth = listWidth; + } else { + listX = 0; + textX = AndroidUtilities.dp(18); + textWidth = width - AndroidUtilities.dp(36); + } + float scale; + if (currentBlock.w == 0) { + scale = 1; + } else { + scale = width / (float) currentBlock.w; + } + height = (int) (currentBlock.w == 0 ? AndroidUtilities.dp(currentBlock.h) * scale : currentBlock.h * scale); + webView.measure(MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + if (videoView.getParent() == this) { + videoView.measure(MeasureSpec.makeMeasureSpec(listWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height + AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); + } + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); + if (textLayout != null) { + textY = AndroidUtilities.dp(8) + height; + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + height += AndroidUtilities.dp(5); + + if (currentBlock.level > 0 && !currentBlock.bottom) { + height += AndroidUtilities.dp(8); + } else if (currentBlock.level == 0 && textLayout != null) { + height += AndroidUtilities.dp(8); + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + webView.layout(listX, 0, listX + webView.getMeasuredWidth(), webView.getMeasuredHeight()); + if (videoView.getParent() == this) { + videoView.layout(listX, 0, listX + videoView.getMeasuredWidth(), videoView.getMeasuredHeight()); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + } + + private class BlockCollageCell extends FrameLayout { + + private RecyclerListView innerListView; + private GridLayoutManager gridLayoutManager; + private RecyclerView.Adapter adapter; + private StaticLayout textLayout; + private int listX; + private int textX; + private int textY; + + private boolean inLayout; + + private TLRPC.TL_pageBlockCollage currentBlock; + private int lastCreatedWidth; + + public BlockCollageCell(Context context) { + super(context); + + innerListView = new RecyclerListView(context) { + @Override + public void requestLayout() { + if (inLayout) { + return; + } + super.requestLayout(); + } + }; + innerListView.setGlowColor(0xfff5f6f7); + innerListView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + outRect.top = outRect.left = 0; + outRect.bottom = outRect.right = AndroidUtilities.dp(2); + } + }); + innerListView.setLayoutManager(gridLayoutManager = new GridLayoutManager(context, 3)); + innerListView.setAdapter(adapter = new RecyclerView.Adapter() { + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: { + view = new BlockPhotoCell(getContext(), 2); + break; + } + case 1: + default: { + view = new BlockVideoCell(getContext(), 2); + break; + } + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + BlockPhotoCell cell = (BlockPhotoCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockPhoto) currentBlock.items.get(position), true, true); + break; + } + case 1: + default: { + BlockVideoCell cell = (BlockVideoCell) holder.itemView; + cell.setBlock((TLRPC.TL_pageBlockVideo) currentBlock.items.get(position), true, true); + break; + } + } + } + + @Override + public int getItemCount() { + if (currentBlock == null) { + return 0; + } + return currentBlock.items.size(); + } + + @Override + public int getItemViewType(int position) { + TLRPC.PageBlock block = currentBlock.items.get(position); + if (block instanceof TLRPC.TL_pageBlockPhoto) { + return 0; + } else { + return 1; + } + } + }); + addView(innerListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + setWillNotDraw(false); + } + + public void setBlock(TLRPC.TL_pageBlockCollage block) { + currentBlock = block; + lastCreatedWidth = 0; + adapter.notifyDataSetChanged(); + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + inLayout = true; + int width = MeasureSpec.getSize(widthMeasureSpec); + int height; + + if (currentBlock != null) { + int listWidth = width; + int textWidth; + if (currentBlock.level > 0) { + textX = listX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); + listWidth -= listX + AndroidUtilities.dp(18); + textWidth = listWidth; + } else { + listX = 0; + textX = AndroidUtilities.dp(18); + textWidth = width - AndroidUtilities.dp(36); + } + + int countPerRow = listWidth / AndroidUtilities.dp(100); + int rowCount = (int) Math.ceil(currentBlock.items.size() / (float) countPerRow); + int itemSize = listWidth / countPerRow; + gridLayoutManager.setSpanCount(countPerRow); + innerListView.measure(MeasureSpec.makeMeasureSpec(itemSize * countPerRow + AndroidUtilities.dp(2), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(itemSize * rowCount, MeasureSpec.EXACTLY)); + height = rowCount * itemSize - AndroidUtilities.dp(2); + + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); + if (textLayout != null) { + textY = height + AndroidUtilities.dp(8); + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + + if (currentBlock.level > 0 && !currentBlock.bottom) { + height += AndroidUtilities.dp(8); + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + inLayout = false; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + innerListView.layout(listX, 0, listX + innerListView.getMeasuredWidth(), innerListView.getMeasuredHeight()); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + } + + private class BlockSlideshowCell extends FrameLayout { + + private ViewPager innerListView; + private PagerAdapter adapter; + private View dotsContainer; + + private TLRPC.TL_pageBlockSlideshow currentBlock; + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY; + + public BlockSlideshowCell(Context context) { + super(context); + + if (dotsPaint == null) { + dotsPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dotsPaint.setColor(0xffffffff); + } + + innerListView = new ViewPager(context) { + @Override + public boolean onTouchEvent(MotionEvent ev) { + return super.onTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + windowView.requestDisallowInterceptTouchEvent(true); + return super.onInterceptTouchEvent(ev); + } + }; + innerListView.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + dotsContainer.invalidate(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + innerListView.setAdapter(adapter = new PagerAdapter() { + + class ObjectContainer { + private TLRPC.PageBlock block; + private View view; + } + + @Override + public int getCount() { + if (currentBlock == null) { + return 0; + } + return currentBlock.items.size(); + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((ObjectContainer) object).view == view; + } + + @Override + public int getItemPosition(Object object) { + ObjectContainer objectContainer = (ObjectContainer) object; + if (currentBlock.items.contains(objectContainer.block)) { + return POSITION_UNCHANGED; + } + return POSITION_NONE; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + View view; + TLRPC.PageBlock block = currentBlock.items.get(position); + if (block instanceof TLRPC.TL_pageBlockPhoto) { + view = new BlockPhotoCell(getContext(), 1); + ((BlockPhotoCell) view).setBlock((TLRPC.TL_pageBlockPhoto) block, true, true); + } else { + view = new BlockVideoCell(getContext(), 1); + ((BlockVideoCell) view).setBlock((TLRPC.TL_pageBlockVideo) block, true, true); + } + container.addView(view); + ObjectContainer objectContainer = new ObjectContainer(); + objectContainer.view = view; + objectContainer.block = block; + return objectContainer; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView(((ObjectContainer) object).view); + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + if (observer != null) { + super.unregisterDataSetObserver(observer); + } + } + }); + AndroidUtilities.setViewPagerEdgeEffectColor(innerListView, 0xfff5f6f7); + addView(innerListView); + + dotsContainer = new View(context) { + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + int selected = innerListView.getCurrentItem(); + for (int a = 0; a < currentBlock.items.size(); a++) { + int cx = AndroidUtilities.dp(4) + AndroidUtilities.dp(13) * a; + Drawable drawable = selected == a ? slideDotBigDrawable : slideDotDrawable; + drawable.setBounds(cx - AndroidUtilities.dp(5), 0, cx + AndroidUtilities.dp(5), AndroidUtilities.dp(10)); + drawable.draw(canvas); + } + } + }; + addView(dotsContainer); + + setWillNotDraw(false); + } + + public void setBlock(TLRPC.TL_pageBlockSlideshow block) { + currentBlock = block; + lastCreatedWidth = 0; + innerListView.setCurrentItem(0, false); + adapter.notifyDataSetChanged(); + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height; + + if (currentBlock != null) { + height = AndroidUtilities.dp(310); + innerListView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + int count = currentBlock.items.size(); + dotsContainer.measure(MeasureSpec.makeMeasureSpec(count * AndroidUtilities.dp(7) + (count - 1) * AndroidUtilities.dp(6) + AndroidUtilities.dp(4), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); + if (lastCreatedWidth != width) { + textY = height + AndroidUtilities.dp(16); + textLayout = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + height += AndroidUtilities.dp(16); + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + innerListView.layout(0, AndroidUtilities.dp(8), innerListView.getMeasuredWidth(), AndroidUtilities.dp(8) + innerListView.getMeasuredHeight()); + int y = innerListView.getBottom() - AndroidUtilities.dp(7 + 16); + int x = (right - left - dotsContainer.getMeasuredWidth()) / 2; + dotsContainer.layout(x, y, x + dotsContainer.getMeasuredWidth(), y + dotsContainer.getMeasuredHeight()); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockListCell extends View { + + private ArrayList textLayouts = new ArrayList<>(); + private ArrayList textNumLayouts = new ArrayList<>(); + private ArrayList textYLayouts = new ArrayList<>(); + private int lastCreatedWidth; + + private TLRPC.TL_pageBlockList currentBlock; + + public BlockListCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockList block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int count = textLayouts.size(); + int textX = AndroidUtilities.dp(36); + for (int a = 0; a < count; a++) { + StaticLayout textLayout = textLayouts.get(a); + if (checkLayoutForLinks(event, this, textLayout, textX, textYLayouts.get(a))) { + return true; + } + } + return super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayouts.clear(); + textYLayouts.clear(); + textNumLayouts.clear(); + int count = currentBlock.items.size(); + for (int a = 0; a < count; a++) { + TLRPC.RichText item = currentBlock.items.get(a); + height += AndroidUtilities.dp(8); + StaticLayout textLayout = createLayoutForText(null, item, width - AndroidUtilities.dp(36 + 18), currentBlock); + textYLayouts.add(height); + textLayouts.add(textLayout); + if (textLayout != null) { + height += textLayout.getHeight(); + } + String num; + if (currentBlock.ordered) { + num = String.format(Locale.US, "%d.", a + 1); + } else { + num = "•"; + } + textLayout = createLayoutForText(num, item, width - AndroidUtilities.dp(36 + 18), currentBlock); + textNumLayouts.add(textLayout); + } + height += AndroidUtilities.dp(8); + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + int count = textLayouts.size(); + for (int a = 0; a < count; a++) { + StaticLayout textLayout = textLayouts.get(a); + StaticLayout textLayout2 = textNumLayouts.get(a); + canvas.save(); + canvas.translate(AndroidUtilities.dp(18), textYLayouts.get(a)); + if (textLayout2 != null) { + textLayout2.draw(canvas); + } + canvas.translate(AndroidUtilities.dp(18), 0); + drawLayoutLink(canvas, textLayout); + if (textLayout != null) { + textLayout.draw(canvas); + } + canvas.restore(); + } + } + } + + private class BlockHeaderCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockHeader currentBlock; + + public BlockHeaderCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockHeader block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockDividerCell extends View { + + private RectF rect = new RectF(); + + public BlockDividerCell(Context context) { + super(context); + if (dividerPaint == null) { + dividerPaint = new Paint(); + dividerPaint.setColor(0xffcdd1d5); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(2 + 16)); + } + + @Override + protected void onDraw(Canvas canvas) { + int width = getMeasuredWidth() / 3; + rect.set(width, AndroidUtilities.dp(8), width * 2, AndroidUtilities.dp(10)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), dividerPaint); + } + } + + private class BlockSubtitleCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockSubtitle currentBlock; + + public BlockSubtitleCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockSubtitle block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockPullquoteCell extends View { + + private StaticLayout textLayout; + private StaticLayout textLayout2; + private int textY2; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockPullquote currentBlock; + + public BlockPullquoteCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockPullquote block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || checkLayoutForLinks(event, this, textLayout2, textX, textY2) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + textLayout2 = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout2 != null) { + textY2 = height + AndroidUtilities.dp(2); + height += AndroidUtilities.dp(8) + textLayout2.getHeight(); + } + if (height != 0) { + height += AndroidUtilities.dp(8); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (textLayout2 != null) { + canvas.save(); + canvas.translate(textX, textY2); + drawLayoutLink(canvas, textLayout2); + textLayout2.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockBlockquoteCell extends View { + + private StaticLayout textLayout; + private StaticLayout textLayout2; + private int textY2; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18 + 14); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockBlockquote currentBlock; + + public BlockBlockquoteCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockBlockquote block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || checkLayoutForLinks(event, this, textLayout2, textX, textY2) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36 + 14), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + textLayout2 = createLayoutForText(null, currentBlock.caption, width - AndroidUtilities.dp(36 + 14), currentBlock); + if (textLayout2 != null) { + textY2 = height + AndroidUtilities.dp(2); + height += AndroidUtilities.dp(8) + textLayout2.getHeight(); + } + if (height != 0) { + height += AndroidUtilities.dp(8); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (textLayout2 != null) { + canvas.save(); + canvas.translate(textX, textY2); + drawLayoutLink(canvas, textLayout2); + textLayout2.draw(canvas); + canvas.restore(); + } + canvas.drawRect(AndroidUtilities.dp(18), AndroidUtilities.dp(6), AndroidUtilities.dp(20), getMeasuredHeight() - AndroidUtilities.dp(6), quoteLinePaint); + } + } + + private class BlockPhotoCell extends View { + + private StaticLayout textLayout; + private ImageReceiver imageView; + private int lastCreatedWidth; + private int currentType; + private boolean isFirst; + private boolean isLast; + private int textX; + private int textY; + private boolean photoPressed; + + private TLRPC.TL_pageBlockPhoto currentBlock; + private TLRPC.PageBlock parentBlock; + + public BlockPhotoCell(Context context, int type) { + super(context); + + imageView = new ImageReceiver(this); + currentType = type; + //imageView.setAspectFit(currentType == 1); + } + + public void setBlock(TLRPC.TL_pageBlockPhoto block, boolean first, boolean last) { + parentBlock = null; + currentBlock = block; + lastCreatedWidth = 0; + isFirst = first; + isLast = last; + requestLayout(); + } + + public void setParentBlock(TLRPC.PageBlock block) { + parentBlock = block; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + float y = event.getY(); + if (event.getAction() == MotionEvent.ACTION_DOWN && imageView.isInsideImage(x, y)) { + photoPressed = true; + } else if (event.getAction() == MotionEvent.ACTION_UP && photoPressed) { + photoPressed = false; + openPhoto(currentBlock); + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + photoPressed = false; + } + return photoPressed || checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + if (currentType == 1) { + width = listView.getWidth(); + height = ((View) getParent()).getMeasuredHeight(); + } else if (currentType == 2) { + height = width; + } + if (currentBlock != null) { + TLRPC.Photo photo = getPhotoWithId(currentBlock.photo_id); + int photoWidth = width; + int photoX; + int textWidth; + if (currentType == 0 && currentBlock.level > 0) { + textX = photoX = AndroidUtilities.dp(14 * currentBlock.level) + AndroidUtilities.dp(18); + photoWidth -= photoX + AndroidUtilities.dp(18); + textWidth = photoWidth; + } else { + photoX = 0; + textX = AndroidUtilities.dp(18); + textWidth = width - AndroidUtilities.dp(36); + } + if (photo != null) { + TLRPC.PhotoSize image = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); + TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 80, true); + if (image == thumb) { + thumb = null; + } + if (currentType == 0) { + float scale; + scale = photoWidth / (float) image.w; + height = (int) (scale * image.h); + if (parentBlock instanceof TLRPC.TL_pageBlockCover) { + height = Math.min(height, photoWidth); + } else { + int maxHeight = (int) ((Math.max(listView.getMeasuredWidth(), listView.getMeasuredHeight()) - AndroidUtilities.dp(56)) * 0.9f); + if (height > maxHeight) { + height = maxHeight; + scale = height / (float) image.h; + photoWidth = (int) (scale * image.w); + photoX += (width - photoX - photoWidth) / 2; + } + } + } + imageView.setImageCoords(photoX, (isFirst || currentType == 1 || currentType == 2 || currentBlock.level > 0) ? 0 : AndroidUtilities.dp(8), photoWidth, height); + String filter; + if (currentType == 0) { + filter = null; + } else { + filter = String.format(Locale.US, "%d_%d", photoWidth, height); + } + imageView.setImage(image.location, filter, thumb != null ? thumb.location : null, thumb != null ? "80_80_b" : null, image.size, null, true); + } + + if (currentType == 0 && lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.caption, textWidth, currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + if (!isFirst && currentType == 0 && currentBlock.level <= 0) { + height += AndroidUtilities.dp(8); + } + if (currentType != 2) { + height += AndroidUtilities.dp(8); + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + imageView.draw(canvas); + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY = imageView.getImageY() + imageView.getImageHeight() + AndroidUtilities.dp(8)); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + } + + private class BlockAuthorDateCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockAuthorDate currentBlock; + + public BlockAuthorDateCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockAuthorDate block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + CharSequence text; + CharSequence author = getText(currentBlock.author, currentBlock.author, currentBlock); //TODO support styles + Spannable spannableAuthor; + MetricAffectingSpan spans[]; + if (author instanceof Spannable) { + spannableAuthor = (Spannable) author; + spans = spannableAuthor.getSpans(0, author.length(), MetricAffectingSpan.class); + } else { + spannableAuthor = null; + spans = null; + } + if (currentBlock.published_date != 0 && !TextUtils.isEmpty(author)) { + text = LocaleController.formatString("ArticleDateByAuthor", R.string.ArticleDateByAuthor, LocaleController.getInstance().chatFullDate.format((long) currentBlock.published_date * 1000), author); + } else if (!TextUtils.isEmpty(author)) { + text = LocaleController.formatString("ArticleByAuthor", R.string.ArticleByAuthor, author); + } else { + text = LocaleController.getInstance().chatFullDate.format((long) currentBlock.published_date * 1000); + } + try { + if (spans != null && spans.length > 0) { + int idx = TextUtils.indexOf(text, author); + if (idx != -1) { + Spannable spannable = Spannable.Factory.getInstance().newSpannable(author); + text = spannable; + for (int a = 0; a < spans.length; a++) { + spannable.setSpan(spans[a], idx + spannableAuthor.getSpanStart(spans[a]), idx + spannableAuthor.getSpanEnd(spans[a]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + textLayout = createLayoutForText(text, null, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockTitleCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + + private TLRPC.TL_pageBlockTitle currentBlock; + private int textX = AndroidUtilities.dp(18); + private int textY; + + public BlockTitleCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockTitle block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); + } + if (currentBlock.first) { + height += AndroidUtilities.dp(8); + textY = AndroidUtilities.dp(16); + } else { + textY = AndroidUtilities.dp(8); + } + + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockFooterCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockFooter currentBlock; + + public BlockFooterCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockFooter block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (currentBlock.level == 0) { + textY = AndroidUtilities.dp(8); + textX = AndroidUtilities.dp(18); + } else { + textY = 0; + textX = AndroidUtilities.dp(18 + 14 * currentBlock.level); + } + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(18) - textX, currentBlock); + if (textLayout != null) { + height = textLayout.getHeight(); + if (currentBlock.level > 0) { + height += AndroidUtilities.dp(8); + } else { + height += AndroidUtilities.dp(8 + 8); + } + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + if (currentBlock.level > 0) { + canvas.drawRect(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(20), getMeasuredHeight() - (currentBlock.bottom ? AndroidUtilities.dp(6) : 0), quoteLinePaint); + } + } + } + + private class BlockPreformattedCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + + private TLRPC.TL_pageBlockPreformatted currentBlock; + + public BlockPreformattedCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockPreformatted block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(24), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8 + 8 + 8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + canvas.drawRect(0, AndroidUtilities.dp(8), getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(8), preformattedBackgroundPaint); + if (textLayout != null) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(12), AndroidUtilities.dp(16)); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + private class BlockSubheaderCell extends View { + + private StaticLayout textLayout; + private int lastCreatedWidth; + private int textX = AndroidUtilities.dp(18); + private int textY = AndroidUtilities.dp(8); + + private TLRPC.TL_pageBlockSubheader currentBlock; + + public BlockSubheaderCell(Context context) { + super(context); + } + + public void setBlock(TLRPC.TL_pageBlockSubheader block) { + currentBlock = block; + lastCreatedWidth = 0; + requestLayout(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return checkLayoutForLinks(event, this, textLayout, textX, textY) || super.onTouchEvent(event); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = 0; + + if (currentBlock != null) { + if (lastCreatedWidth != width) { + textLayout = createLayoutForText(null, currentBlock.text, width - AndroidUtilities.dp(36), currentBlock); + if (textLayout != null) { + height += AndroidUtilities.dp(8 + 8) + textLayout.getHeight(); + } + //lastCreatedWidth = width; + } + } else { + height = 1; + } + + setMeasuredDimension(width, height); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentBlock == null) { + return; + } + if (textLayout != null) { + canvas.save(); + canvas.translate(textX, textY); + drawLayoutLink(canvas, textLayout); + textLayout.draw(canvas); + canvas.restore(); + } + } + } + + + //------------ photo viewer + + private int coords[] = new int[2]; + + private boolean isPhotoVisible; + + private ActionBar actionBar; + private boolean isActionBarVisible = true; + + private static Drawable[] progressDrawables; + + private ClippingImageView animatingImageView; + private FrameLayout bottomLayout; + + private ActionBarMenuItem menuItem; + private PhotoBackgroundDrawable photoBackgroundDrawable = new PhotoBackgroundDrawable(0xff000000); + private Paint blackPaint = new Paint(); + + private RadialProgressView radialProgressViews[] = new RadialProgressView[3]; + private AnimatorSet currentActionBarAnimation; + + private TextView captionTextView; + private TextView captionTextViewOld; + private TextView captionTextViewNew; + + private AnimatedFileDrawable currentAnimation; + + private AspectRatioFrameLayout aspectRatioFrameLayout; + private TextureView videoTextureView; + private VideoPlayer videoPlayer; + private FrameLayout videoPlayerControlFrameLayout; + private ImageView videoPlayButton; + private TextView videoPlayerTime; + private SeekBar videoPlayerSeekbar; + private boolean textureUploaded; + private boolean videoCrossfadeStarted; + private float videoCrossfadeAlpha; + private long videoCrossfadeAlphaLastTime; + private boolean isPlaying; + private Runnable updateProgressRunnable = new Runnable() { + @Override + public void run() { + if (videoPlayer != null && videoPlayerSeekbar != null) { + if (!videoPlayerSeekbar.isDragging()) { + float progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); + videoPlayerSeekbar.setProgress(progress); + videoPlayerControlFrameLayout.invalidate(); + updateVideoPlayerTime(); + } + } + if (isPlaying) { + AndroidUtilities.runOnUIThread(updateProgressRunnable, 100); + } + } + }; + + private float animationValues[][] = new float[2][8]; + + private int photoAnimationInProgress; + private long photoTransitionAnimationStartTime; + private Runnable photoAnimationEndRunnable; + private PlaceProviderObject showAfterAnimation; + private PlaceProviderObject hideAfterAnimation; + private boolean disableShowCheck; + + private ImageReceiver leftImage = new ImageReceiver(); + private ImageReceiver centerImage = new ImageReceiver(); + private ImageReceiver rightImage = new ImageReceiver(); + private int currentIndex; + private TLRPC.PageBlock currentMedia; + private String currentFileNames[] = new String[3]; + private PlaceProviderObject currentPlaceObject; + private Bitmap currentThumb; + + private boolean wasLayout; + private boolean dontResetZoomOnFirstLayout; + + private boolean draggingDown; + private float dragY; + private float translationX; + private float translationY; + private float scale = 1; + private float animateToX; + private float animateToY; + private float animateToScale; + private float animationValue; + private int currentRotation; + private long animationStartTime; + private AnimatorSet imageMoveAnimation; + private GestureDetector gestureDetector; + private DecelerateInterpolator interpolator = new DecelerateInterpolator(1.5f); + private float pinchStartDistance; + private float pinchStartScale = 1; + private float pinchCenterX; + private float pinchCenterY; + private float pinchStartX; + private float pinchStartY; + private float moveStartX; + private float moveStartY; + private float minX; + private float maxX; + private float minY; + private float maxY; + private boolean canZoom = true; + private boolean changingPage; + private boolean zooming; + private boolean moving; + private boolean doubleTap; + private boolean invalidCoords; + private boolean canDragDown = true; + private boolean zoomAnimation; + private boolean discardTap; + private int switchImageAfterAnimation; + private VelocityTracker velocityTracker; + private Scroller scroller; + + private ArrayList imagesArr = new ArrayList<>(); + + private final static int gallery_menu_save = 1; + private final static int gallery_menu_share = 2; + private final static int gallery_menu_openin = 3; + + private static DecelerateInterpolator decelerateInterpolator; + private static Paint progressPaint; + private static Paint dotsPaint; + + private class PhotoBackgroundDrawable extends ColorDrawable { + + private Runnable drawRunnable; + + public PhotoBackgroundDrawable(int color) { + super(color); + } + + @Override + public void setAlpha(int alpha) { + if (parentActivity instanceof LaunchActivity) { + ((LaunchActivity) parentActivity).drawerLayoutContainer.setAllowDrawContent(!isPhotoVisible || alpha != 255); + } + super.setAlpha(alpha); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (getAlpha() != 0) { + if (drawRunnable != null) { + drawRunnable.run(); + drawRunnable = null; + } + } + } + } + + private class RadialProgressView { + + private long lastUpdateTime = 0; + private float radOffset = 0; + private float currentProgress = 0; + private float animationProgressStart = 0; + private long currentProgressTime = 0; + private float animatedProgressValue = 0; + private RectF progressRect = new RectF(); + private int backgroundState = -1; + private View parent = null; + private int size = AndroidUtilities.dp(64); + private int previousBackgroundState = -2; + private float animatedAlphaValue = 1.0f; + private float alpha = 1.0f; + private float scale = 1.0f; + + public RadialProgressView(Context context, View parentView) { + if (decelerateInterpolator == null) { + decelerateInterpolator = new DecelerateInterpolator(1.5f); + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + progressPaint.setStrokeWidth(AndroidUtilities.dp(3)); + progressPaint.setColor(0xffffffff); + } + parent = parentView; + } + + private void updateAnimation() { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + + if (animatedProgressValue != 1) { + radOffset += 360 * dt / 3000.0f; + float progressDiff = currentProgress - animationProgressStart; + if (progressDiff > 0) { + currentProgressTime += dt; + if (currentProgressTime >= 300) { + animatedProgressValue = currentProgress; + animationProgressStart = currentProgress; + currentProgressTime = 0; + } else { + animatedProgressValue = animationProgressStart + progressDiff * decelerateInterpolator.getInterpolation(currentProgressTime / 300.0f); + } + } + parent.invalidate(); + } + if (animatedProgressValue >= 1 && previousBackgroundState != -2) { + animatedAlphaValue -= dt / 200.0f; + if (animatedAlphaValue <= 0) { + animatedAlphaValue = 0.0f; + previousBackgroundState = -2; + } + parent.invalidate(); + } + } + + public void setProgress(float value, boolean animated) { + if (!animated) { + animatedProgressValue = value; + animationProgressStart = value; + } else { + animationProgressStart = animatedProgressValue; + } + currentProgress = value; + currentProgressTime = 0; + } + + public void setBackgroundState(int state, boolean animated) { + lastUpdateTime = System.currentTimeMillis(); + if (animated && backgroundState != state) { + previousBackgroundState = backgroundState; + animatedAlphaValue = 1.0f; + } else { + previousBackgroundState = -2; + } + backgroundState = state; + parent.invalidate(); + } + + public void setAlpha(float value) { + alpha = value; + } + + public void setScale(float value) { + scale = value; + } + + public void onDraw(Canvas canvas) { + int sizeScaled = (int) (size * scale); + int x = (getContainerViewWidth() - sizeScaled) / 2; + int y = (getContainerViewHeight() - sizeScaled) / 2; + + if (previousBackgroundState >= 0 && previousBackgroundState < 4) { + Drawable drawable = progressDrawables[previousBackgroundState]; + if (drawable != null) { + drawable.setAlpha((int) (255 * animatedAlphaValue * alpha)); + drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); + drawable.draw(canvas); + } + } + + if (backgroundState >= 0 && backgroundState < 4) { + Drawable drawable = progressDrawables[backgroundState]; + if (drawable != null) { + if (previousBackgroundState != -2) { + drawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue) * alpha)); + } else { + drawable.setAlpha((int) (255 * alpha)); + } + drawable.setBounds(x, y, x + sizeScaled, y + sizeScaled); + drawable.draw(canvas); + } + } + + if (backgroundState == 0 || backgroundState == 1 || previousBackgroundState == 0 || previousBackgroundState == 1) { + int diff = AndroidUtilities.dp(4); + if (previousBackgroundState != -2) { + progressPaint.setAlpha((int) (255 * animatedAlphaValue * alpha)); + } else { + progressPaint.setAlpha((int) (255 * alpha)); + } + progressRect.set(x + diff, y + diff, x + sizeScaled - diff, y + sizeScaled - diff); + canvas.drawArc(progressRect, -90 + radOffset, Math.max(4, 360 * animatedProgressValue), false, progressPaint); + updateAnimation(); + } + } + } + + public static class PlaceProviderObject { + public ImageReceiver imageReceiver; + public int viewX; + public int viewY; + public View parentView; + public Bitmap thumb; + public int index; + public int size; + public int radius; + public int clipBottomAddition; + public int clipTopAddition; + public float scale = 1.0f; + } + + private void onSharePressed() { + if (parentActivity == null || currentMedia == null) { + return; + } + try { + File f = getMediaFile(currentIndex); + if (f != null && f.exists()) { + Intent intent = new Intent(Intent.ACTION_SEND); + if (isMediaVideo(currentIndex)) { + intent.setType("video/mp4"); + } else { + intent.setType("image/jpeg"); + } + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); + parentActivity.startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); + } else { + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.getString("PleaseDownload", R.string.PleaseDownload)); + showDialog(builder.create()); + } + } catch (Exception e) { + FileLog.e(e); + } + } + + private void setScaleToFill() { + float bitmapWidth = centerImage.getBitmapWidth(); + float containerWidth = getContainerViewWidth(); + float bitmapHeight = centerImage.getBitmapHeight(); + float containerHeight = getContainerViewHeight(); + float scaleFit = Math.min(containerHeight / bitmapHeight, containerWidth / bitmapWidth); + float width = (int) (bitmapWidth * scaleFit); + float height = (int) (bitmapHeight * scaleFit); + scale = Math.max(containerWidth / width, containerHeight / height); + updateMinMax(scale); + } + + private void updateVideoPlayerTime() { + String newText; + if (videoPlayer == null) { + newText = "00:00 / 00:00"; + } else { + long current = videoPlayer.getCurrentPosition() / 1000; + long total = videoPlayer.getDuration(); + total /= 1000; + if (total != C.TIME_UNSET && current != C.TIME_UNSET) { + newText = String.format("%02d:%02d / %02d:%02d", current / 60, current % 60, total / 60, total % 60); + } else { + newText = "00:00 / 00:00"; + } + } + if (!TextUtils.equals(videoPlayerTime.getText(), newText)) { + videoPlayerTime.setText(newText); + } + } + + @SuppressLint("NewApi") + private void preparePlayer(File file, boolean playWhenReady) { + if (parentActivity == null) { + return; + } + releasePlayer(); + if (videoTextureView == null) { + aspectRatioFrameLayout = new AspectRatioFrameLayout(parentActivity); + aspectRatioFrameLayout.setVisibility(View.INVISIBLE); + photoContainerView.addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + videoTextureView = new TextureView(parentActivity); + videoTextureView.setOpaque(false); + aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + } + textureUploaded = false; + videoCrossfadeStarted = false; + videoTextureView.setAlpha(videoCrossfadeAlpha = 0.0f); + videoPlayButton.setImageResource(R.drawable.inline_video_play); + if (videoPlayer == null) { + videoPlayer = new VideoPlayer(); + videoPlayer.setTextureView(videoTextureView); + videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { + @Override + public void onStateChanged(boolean playWhenReady, int playbackState) { + if (videoPlayer == null) { + return; + } + if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE) { + try { + parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } else { + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } + if (playbackState == ExoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { + aspectRatioFrameLayout.setVisibility(View.VISIBLE); + } + if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { + if (!isPlaying) { + isPlaying = true; + videoPlayButton.setImageResource(R.drawable.inline_video_pause); + AndroidUtilities.runOnUIThread(updateProgressRunnable); + } + } else if (isPlaying) { + isPlaying = false; + videoPlayButton.setImageResource(R.drawable.inline_video_play); + AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); + if (playbackState == ExoPlayer.STATE_ENDED) { + if (!videoPlayerSeekbar.isDragging()) { + videoPlayerSeekbar.setProgress(0.0f); + videoPlayerControlFrameLayout.invalidate(); + videoPlayer.seekTo(0); + videoPlayer.pause(); + } + } + } + updateVideoPlayerTime(); + } + + @Override + public void onError(Exception e) { + FileLog.e(e); + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + if (aspectRatioFrameLayout != null) { + if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { + int temp = width; + width = height; + height = temp; + } + aspectRatioFrameLayout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height, unappliedRotationDegrees); + } + } + + @Override + public void onRenderedFirstFrame() { + if (!textureUploaded) { + textureUploaded = true; + containerView.invalidate(); + } + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + + } + }); + long duration; + if (videoPlayer != null) { + duration = videoPlayer.getDuration(); + if (duration == C.TIME_UNSET) { + duration = 0; + } + } else { + duration = 0; + } + duration /= 1000; + int size = (int) Math.ceil(videoPlayerTime.getPaint().measureText(String.format("%02d:%02d / %02d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); + } + videoPlayer.preparePlayer(Uri.fromFile(file), "other"); + bottomLayout.setVisibility(View.VISIBLE); + videoPlayer.setPlayWhenReady(playWhenReady); + } + + private void releasePlayer() { + if (videoPlayer != null) { + videoPlayer.releasePlayer(); + videoPlayer = null; + } + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + if (aspectRatioFrameLayout != null) { + photoContainerView.removeView(aspectRatioFrameLayout); + aspectRatioFrameLayout = null; + } + if (videoTextureView != null) { + videoTextureView = null; + } + if (isPlaying) { + isPlaying = false; + videoPlayButton.setImageResource(R.drawable.inline_video_play); + AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); + } + bottomLayout.setVisibility(View.GONE); + } + + private void toggleActionBar(boolean show, final boolean animated) { + if (show) { + actionBar.setVisibility(View.VISIBLE); + if (videoPlayer != null) { + bottomLayout.setVisibility(View.VISIBLE); + } + if (captionTextView.getTag() != null) { + captionTextView.setVisibility(View.VISIBLE); + } + } + isActionBarVisible = show; + actionBar.setEnabled(show); + bottomLayout.setEnabled(show); + + if (animated) { + ArrayList arrayList = new ArrayList<>(); + arrayList.add(ObjectAnimator.ofFloat(actionBar, "alpha", show ? 1.0f : 0.0f)); + arrayList.add(ObjectAnimator.ofFloat(bottomLayout, "alpha", show ? 1.0f : 0.0f)); + if (captionTextView.getTag() != null) { + arrayList.add(ObjectAnimator.ofFloat(captionTextView, "alpha", show ? 1.0f : 0.0f)); + } + currentActionBarAnimation = new AnimatorSet(); + currentActionBarAnimation.playTogether(arrayList); + if (!show) { + currentActionBarAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (currentActionBarAnimation != null && currentActionBarAnimation.equals(animation)) { + actionBar.setVisibility(View.GONE); + if (videoPlayer != null) { + bottomLayout.setVisibility(View.GONE); + } + if (captionTextView.getTag() != null) { + captionTextView.setVisibility(View.INVISIBLE); + } + currentActionBarAnimation = null; + } + } + }); + } + + currentActionBarAnimation.setDuration(200); + currentActionBarAnimation.start(); + } else { + actionBar.setAlpha(show ? 1.0f : 0.0f); + bottomLayout.setAlpha(show ? 1.0f : 0.0f); + if (captionTextView.getTag() != null) { + captionTextView.setAlpha(show ? 1.0f : 0.0f); + } + if (!show) { + actionBar.setVisibility(View.GONE); + if (videoPlayer != null) { + bottomLayout.setVisibility(View.GONE); + } + if (captionTextView.getTag() != null) { + captionTextView.setVisibility(View.INVISIBLE); + } + } + } + } + + private String getFileName(int index) { + TLObject media = getMedia(index); + if (media instanceof TLRPC.Photo) { + media = FileLoader.getClosestPhotoSizeWithSize(((TLRPC.Photo) media).sizes, AndroidUtilities.getPhotoSize()); + } + return FileLoader.getAttachFileName(media); + } + + private TLObject getMedia(int index) { + if (imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) { + return null; + } + TLRPC.PageBlock block = imagesArr.get(index); + if (block.photo_id != 0) { + return getPhotoWithId(block.photo_id); + } else if (block.video_id != 0) { + return getDocumentWithId(block.video_id); + } + return null; + } + + private File getMediaFile(int index) { + if (imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) { + return null; + } + TLRPC.PageBlock block = imagesArr.get(index); + if (block.photo_id != 0) { + TLRPC.Photo photo = getPhotoWithId(block.photo_id); + if (photo != null) { + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); + if (sizeFull != null) { + return FileLoader.getPathToAttach(sizeFull, true); + } + } + } else if (block.video_id != 0) { + TLRPC.Document document = getDocumentWithId(block.video_id); + if (document != null) { + return FileLoader.getPathToAttach(document, true); + } + } + return null; + } + + private boolean isVideoBlock(TLRPC.PageBlock block) { + if (block != null && block.video_id != 0) { + TLRPC.Document document = getDocumentWithId(block.video_id); + if (document != null) { + return MessageObject.isVideoDocument(document); + } + } + return false; + } + + private boolean isMediaVideo(int index) { + return !(imagesArr.isEmpty() || index >= imagesArr.size() || index < 0) && isVideoBlock(imagesArr.get(index)); + } + + private TLRPC.FileLocation getFileLocation(int index, int size[]) { + if (index < 0 || index >= imagesArr.size()) { + return null; + } + TLObject media = getMedia(index); + if (media instanceof TLRPC.Photo) { + TLRPC.Photo photo = (TLRPC.Photo) media; + TLRPC.PhotoSize sizeFull = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, AndroidUtilities.getPhotoSize()); + if (sizeFull != null) { + size[0] = sizeFull.size; + if (size[0] == 0) { + size[0] = -1; + } + return sizeFull.location; + } else { + size[0] = -1; + } + } else if (media instanceof TLRPC.Document) { + TLRPC.Document document = (TLRPC.Document) media; + if (document.thumb != null) { + size[0] = document.thumb.size; + if (size[0] == 0) { + size[0] = -1; + } + return document.thumb.location; + } + } + return null; + } + + private void onPhotoShow(int index, final PlaceProviderObject object) { + currentIndex = -1; + currentFileNames[0] = null; + currentFileNames[1] = null; + currentFileNames[2] = null; + currentThumb = object != null ? object.thumb : null; + menuItem.setVisibility(View.VISIBLE); + menuItem.hideSubItem(gallery_menu_openin); + actionBar.setTranslationY(0); + captionTextView.setTag(null); + captionTextView.setVisibility(View.INVISIBLE); + + for (int a = 0; a < 3; a++) { + if (radialProgressViews[a] != null) { + radialProgressViews[a].setBackgroundState(-1, false); + } + } + + setImageIndex(index, true); + + if (currentMedia != null && isMediaVideo(currentIndex)) { + onActionClick(false); + } + } + + private void setImages() { + if (photoAnimationInProgress == 0) { + setIndexToImage(centerImage, currentIndex); + setIndexToImage(rightImage, currentIndex + 1); + setIndexToImage(leftImage, currentIndex - 1); + } + } + + private void setImageIndex(int index, boolean init) { + if (currentIndex == index) { + return; + } + if (!init) { + currentThumb = null; + } + currentFileNames[0] = getFileName(index); + currentFileNames[1] = getFileName(index + 1); + currentFileNames[2] = getFileName(index - 1); + + int prevIndex = currentIndex; + currentIndex = index; + boolean isVideo = false; + boolean sameImage = false; + + if (!imagesArr.isEmpty()) { + if (currentIndex < 0 || currentIndex >= imagesArr.size()) { + closePhoto(false); + return; + } + TLRPC.PageBlock newMedia = imagesArr.get(currentIndex); + sameImage = currentMedia != null && currentMedia == newMedia; + currentMedia = newMedia; + isVideo = isMediaVideo(currentIndex); + if (isVideo) { + menuItem.showSubItem(gallery_menu_openin); + } + setCurrentCaption(getText(currentMedia.caption, currentMedia.caption, currentMedia)); + if (currentAnimation != null) { + menuItem.setVisibility(View.GONE); + menuItem.hideSubItem(gallery_menu_save); + actionBar.setTitle(LocaleController.getString("AttachGif", R.string.AttachGif)); + } else { + menuItem.setVisibility(View.VISIBLE); + if (imagesArr.size() == 1) { + if (isVideo) { + actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); + } else { + actionBar.setTitle(LocaleController.getString("AttachPhoto", R.string.AttachPhoto)); + } + } else { + actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, currentIndex + 1, imagesArr.size())); + } + menuItem.showSubItem(gallery_menu_save); + } + } + + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof BlockSlideshowCell) { + BlockSlideshowCell cell = (BlockSlideshowCell) child; + int idx = cell.currentBlock.items.indexOf(currentMedia); + if (idx != -1) { + cell.innerListView.setCurrentItem(idx, false); + break; + } + } + } + + if (currentPlaceObject != null) { + if (photoAnimationInProgress == 0) { + currentPlaceObject.imageReceiver.setVisible(true, true); + } else { + showAfterAnimation = currentPlaceObject; + } + } + currentPlaceObject = getPlaceForPhoto(currentMedia); + if (currentPlaceObject != null) { + if (photoAnimationInProgress == 0) { + currentPlaceObject.imageReceiver.setVisible(false, true); + } else { + hideAfterAnimation = currentPlaceObject; + } + } + + if (!sameImage) { + draggingDown = false; + translationX = 0; + translationY = 0; + scale = 1; + animateToX = 0; + animateToY = 0; + animateToScale = 1; + animationStartTime = 0; + imageMoveAnimation = null; + if (aspectRatioFrameLayout != null) { + aspectRatioFrameLayout.setVisibility(View.INVISIBLE); + } + releasePlayer(); + + pinchStartDistance = 0; + pinchStartScale = 1; + pinchCenterX = 0; + pinchCenterY = 0; + pinchStartX = 0; + pinchStartY = 0; + moveStartX = 0; + moveStartY = 0; + zooming = false; + moving = false; + doubleTap = false; + invalidCoords = false; + canDragDown = true; + changingPage = false; + switchImageAfterAnimation = 0; + canZoom = (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); + updateMinMax(scale); + } + + if (prevIndex == -1) { + setImages(); + + for (int a = 0; a < 3; a++) { + checkProgress(a, false); + } + } else { + checkProgress(0, false); + if (prevIndex > currentIndex) { + ImageReceiver temp = rightImage; + rightImage = centerImage; + centerImage = leftImage; + leftImage = temp; + + RadialProgressView tempProgress = radialProgressViews[0]; + radialProgressViews[0] = radialProgressViews[2]; + radialProgressViews[2] = tempProgress; + setIndexToImage(leftImage, currentIndex - 1); + + checkProgress(1, false); + checkProgress(2, false); + } else if (prevIndex < currentIndex) { + ImageReceiver temp = leftImage; + leftImage = centerImage; + centerImage = rightImage; + rightImage = temp; + + RadialProgressView tempProgress = radialProgressViews[0]; + radialProgressViews[0] = radialProgressViews[1]; + radialProgressViews[1] = tempProgress; + setIndexToImage(rightImage, currentIndex + 1); + + checkProgress(1, false); + checkProgress(2, false); + } + } + } + + private void setCurrentCaption(final CharSequence caption) { + if (!TextUtils.isEmpty(caption)) { + captionTextView = captionTextViewOld; + captionTextViewOld = captionTextViewNew; + captionTextViewNew = captionTextView; + Theme.createChatResources(null, true); + CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), captionTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + captionTextView.setTag(str); + captionTextView.setText(str); + captionTextView.setTextColor(0xffffffff); + captionTextView.setAlpha(actionBar.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + captionTextViewOld.setTag(null); + captionTextViewOld.setVisibility(View.INVISIBLE); + captionTextViewNew.setVisibility(actionBar.getVisibility() == View.VISIBLE ? View.VISIBLE : View.INVISIBLE); + } + }); + } else { + captionTextView.setTextColor(0xffffffff); + captionTextView.setTag(null); + captionTextView.setVisibility(View.INVISIBLE); + } + } + + private void checkProgress(int a, boolean animated) { + if (currentFileNames[a] != null) { + int index = currentIndex; + if (a == 1) { + index += 1; + } else if (a == 2) { + index -= 1; + } + File f = getMediaFile(index); + boolean isVideo = isMediaVideo(index); + if (f != null && f.exists()) { + if (isVideo) { + radialProgressViews[a].setBackgroundState(3, animated); + } else { + radialProgressViews[a].setBackgroundState(-1, animated); + } + } else { + if (isVideo) { + if (!FileLoader.getInstance().isLoadingFile(currentFileNames[a])) { + radialProgressViews[a].setBackgroundState(2, false); + } else { + radialProgressViews[a].setBackgroundState(1, false); + } + } else { + radialProgressViews[a].setBackgroundState(0, animated); + } + Float progress = ImageLoader.getInstance().getFileProgress(currentFileNames[a]); + if (progress == null) { + progress = 0.0f; + } + radialProgressViews[a].setProgress(progress, false); + } + if (a == 0) { + canZoom = (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); + } + } else { + radialProgressViews[a].setBackgroundState(-1, animated); + } + } + + private void setIndexToImage(ImageReceiver imageReceiver, int index) { + imageReceiver.setOrientation(0, false); + + int size[] = new int[1]; + TLRPC.FileLocation fileLocation = getFileLocation(index, size); + + if (fileLocation != null) { + TLObject media = getMedia(index); + if (media instanceof TLRPC.Photo) { + TLRPC.Photo photo = (TLRPC.Photo) media; + Bitmap placeHolder = null; + if (currentThumb != null && imageReceiver == centerImage) { + placeHolder = currentThumb; + } + if (size[0] == 0) { + size[0] = -1; + } + TLRPC.PhotoSize thumbLocation = FileLoader.getClosestPhotoSizeWithSize(photo.sizes, 80); + imageReceiver.setImage(fileLocation, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, thumbLocation != null ? thumbLocation.location : null, "b", size[0], null, true); + } else if (isMediaVideo(index)) { + if (!(fileLocation instanceof TLRPC.TL_fileLocationUnavailable)) { + Bitmap placeHolder = null; + if (currentThumb != null && imageReceiver == centerImage) { + placeHolder = currentThumb; + } + imageReceiver.setImage(null, null, null, placeHolder != null ? new BitmapDrawable(null, placeHolder) : null, fileLocation, "b", 0, null, true); + } else { + imageReceiver.setImageBitmap(parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); + } + } else if (currentAnimation != null) { + imageReceiver.setImageBitmap(currentAnimation); + currentAnimation.setSecondParentView(photoContainerView); + } else { + //TODO gif + } + } else { + if (size[0] == 0) { + imageReceiver.setImageBitmap((Bitmap) null); + } else { + imageReceiver.setImageBitmap(parentActivity.getResources().getDrawable(R.drawable.photoview_placeholder)); + } + } + } + + public boolean isShowingImage(TLRPC.PageBlock object) { + return isPhotoVisible && !disableShowCheck && object != null && currentMedia == object; + } + + private boolean checkPhotoAnimation() { + if (photoAnimationInProgress != 0) { + if (Math.abs(photoTransitionAnimationStartTime - System.currentTimeMillis()) >= 500) { + if (photoAnimationEndRunnable != null) { + photoAnimationEndRunnable.run(); + photoAnimationEndRunnable = null; + } + photoAnimationInProgress = 0; + } + } + return photoAnimationInProgress != 0; + } + + public boolean openPhoto(TLRPC.PageBlock block) { + if (parentActivity == null || isPhotoVisible || checkPhotoAnimation() || block == null) { + return false; + } + + final PlaceProviderObject object = getPlaceForPhoto(block); + if (object == null) { + return false; + } + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidFailedLoad); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileLoadProgressChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); + + if (velocityTracker == null) { + velocityTracker = VelocityTracker.obtain(); + } + + isPhotoVisible = true; + toggleActionBar(true, false); + actionBar.setAlpha(0.0f); + bottomLayout.setAlpha(0.0f); + captionTextView.setAlpha(0.0f); + photoBackgroundDrawable.setAlpha(0); + disableShowCheck = true; + photoAnimationInProgress = 1; + if (block != null) { + currentAnimation = object.imageReceiver.getAnimation(); + } + int index = photoBlocks.indexOf(block); + + imagesArr.clear(); + if (!(block instanceof TLRPC.TL_pageBlockVideo) || isVideoBlock(block)) { + imagesArr.addAll(photoBlocks); + } else { + imagesArr.add(block); + index = 0; + } + + onPhotoShow(index, object); + + final Rect drawRegion = object.imageReceiver.getDrawRegion(); + int orientation = object.imageReceiver.getOrientation(); + int animatedOrientation = object.imageReceiver.getAnimatedOrientation(); + if (animatedOrientation != 0) { + orientation = animatedOrientation; + } + + animatingImageView.setVisibility(View.VISIBLE); + animatingImageView.setRadius(object.radius); + animatingImageView.setOrientation(orientation); + animatingImageView.setNeedRadius(object.radius != 0); + animatingImageView.setImageBitmap(object.thumb); + + animatingImageView.setAlpha(1.0f); + animatingImageView.setPivotX(0.0f); + animatingImageView.setPivotY(0.0f); + animatingImageView.setScaleX(object.scale); + animatingImageView.setScaleY(object.scale); + animatingImageView.setTranslationX(object.viewX + drawRegion.left * object.scale); + animatingImageView.setTranslationY(object.viewY + drawRegion.top * object.scale); + final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); + layoutParams.width = (drawRegion.right - drawRegion.left); + layoutParams.height = (drawRegion.bottom - drawRegion.top); + animatingImageView.setLayoutParams(layoutParams); + + float scaleX = (float) AndroidUtilities.displaySize.x / layoutParams.width; + float scaleY = (float) (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) / layoutParams.height; + float scale = scaleX > scaleY ? scaleY : scaleX; + float width = layoutParams.width * scale; + float height = layoutParams.height * scale; + float xPos = (AndroidUtilities.displaySize.x - width) / 2.0f; + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + xPos += ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); + } + float yPos = ((AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) - height) / 2.0f; + int clipHorizontal = Math.abs(drawRegion.left - object.imageReceiver.getImageX()); + int clipVertical = Math.abs(drawRegion.top - object.imageReceiver.getImageY()); + + int coords2[] = new int[2]; + object.parentView.getLocationInWindow(coords2); + int clipTop = coords2[1] - (object.viewY + drawRegion.top) + object.clipTopAddition; + if (clipTop < 0) { + clipTop = 0; + } + int clipBottom = (object.viewY + drawRegion.top + layoutParams.height) - (coords2[1] + object.parentView.getHeight()) + object.clipBottomAddition; + if (clipBottom < 0) { + clipBottom = 0; + } + clipTop = Math.max(clipTop, clipVertical); + clipBottom = Math.max(clipBottom, clipVertical); + + animationValues[0][0] = animatingImageView.getScaleX(); + animationValues[0][1] = animatingImageView.getScaleY(); + animationValues[0][2] = animatingImageView.getTranslationX(); + animationValues[0][3] = animatingImageView.getTranslationY(); + animationValues[0][4] = clipHorizontal * object.scale; + animationValues[0][5] = clipTop * object.scale; + animationValues[0][6] = clipBottom * object.scale; + animationValues[0][7] = animatingImageView.getRadius(); + + animationValues[1][0] = scale; + animationValues[1][1] = scale; + animationValues[1][2] = xPos; + animationValues[1][3] = yPos; + animationValues[1][4] = 0; + animationValues[1][5] = 0; + animationValues[1][6] = 0; + animationValues[1][7] = 0; + + photoContainerView.setVisibility(View.VISIBLE); + photoContainerBackground.setVisibility(View.VISIBLE); + animatingImageView.setAnimationProgress(0); + + final AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(animatingImageView, "animationProgress", 0.0f, 1.0f), + ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0, 255), + ObjectAnimator.ofFloat(actionBar, "alpha", 0, 1.0f), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 0, 1.0f), + ObjectAnimator.ofFloat(captionTextView, "alpha", 0, 1.0f) + ); + + photoAnimationEndRunnable = new Runnable() { + @Override + public void run() { + if (photoContainerView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + photoAnimationInProgress = 0; + photoTransitionAnimationStartTime = 0; + setImages(); + photoContainerView.invalidate(); + animatingImageView.setVisibility(View.GONE); + if (showAfterAnimation != null) { + showAfterAnimation.imageReceiver.setVisible(true, true); + } + if (hideAfterAnimation != null) { + hideAfterAnimation.imageReceiver.setVisible(false, true); + } + } + }; + + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().setAnimationInProgress(false); + if (photoAnimationEndRunnable != null) { + photoAnimationEndRunnable.run(); + photoAnimationEndRunnable = null; + } + } + }); + } + }); + photoTransitionAnimationStartTime = System.currentTimeMillis(); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + NotificationCenter.getInstance().setAllowedNotificationsDutingAnimation(new int[]{NotificationCenter.dialogsNeedReload, NotificationCenter.closeChats, NotificationCenter.mediaCountDidLoaded, NotificationCenter.mediaDidLoaded, NotificationCenter.dialogPhotosLoaded}); + NotificationCenter.getInstance().setAnimationInProgress(true); + animatorSet.start(); + } + }); + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + photoBackgroundDrawable.drawRunnable = new Runnable() { + @Override + public void run() { + disableShowCheck = false; + object.imageReceiver.setVisible(false, true); + } + }; + return true; + } + + public void closePhoto(boolean animated) { + if (parentActivity == null || !isPhotoVisible || checkPhotoAnimation()) { + return; + } + + releasePlayer(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidFailedLoad); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileLoadProgressChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); + + isActionBarVisible = false; + + if (velocityTracker != null) { + velocityTracker.recycle(); + velocityTracker = null; + } + + final PlaceProviderObject object = getPlaceForPhoto(currentMedia); + + if (animated) { + photoAnimationInProgress = 1; + animatingImageView.setVisibility(View.VISIBLE); + photoContainerView.invalidate(); + + AnimatorSet animatorSet = new AnimatorSet(); + + final ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); + Rect drawRegion = null; + int orientation = centerImage.getOrientation(); + int animatedOrientation = 0; + if (object != null && object.imageReceiver != null) { + animatedOrientation = object.imageReceiver.getAnimatedOrientation(); + } + if (animatedOrientation != 0) { + orientation = animatedOrientation; + } + animatingImageView.setOrientation(orientation); + if (object != null) { + animatingImageView.setNeedRadius(object.radius != 0); + drawRegion = object.imageReceiver.getDrawRegion(); + layoutParams.width = drawRegion.right - drawRegion.left; + layoutParams.height = drawRegion.bottom - drawRegion.top; + animatingImageView.setImageBitmap(object.thumb); + } else { + animatingImageView.setNeedRadius(false); + layoutParams.width = centerImage.getImageWidth(); + layoutParams.height = centerImage.getImageHeight(); + animatingImageView.setImageBitmap(centerImage.getBitmap()); + } + animatingImageView.setLayoutParams(layoutParams); + + float scaleX = (float) AndroidUtilities.displaySize.x / layoutParams.width; + float scaleY = (float) (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight) / layoutParams.height; + float scale2 = scaleX > scaleY ? scaleY : scaleX; + float width = layoutParams.width * scale * scale2; + float height = layoutParams.height * scale * scale2; + float xPos = (AndroidUtilities.displaySize.x - width) / 2.0f; + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + xPos += ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); + } + float yPos = (AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight - height) / 2.0f; + animatingImageView.setTranslationX(xPos + translationX); + animatingImageView.setTranslationY(yPos + translationY); + animatingImageView.setScaleX(scale * scale2); + animatingImageView.setScaleY(scale * scale2); + + if (object != null) { + object.imageReceiver.setVisible(false, true); + int clipHorizontal = Math.abs(drawRegion.left - object.imageReceiver.getImageX()); + int clipVertical = Math.abs(drawRegion.top - object.imageReceiver.getImageY()); + + int coords2[] = new int[2]; + object.parentView.getLocationInWindow(coords2); + int clipTop = coords2[1] - (object.viewY + drawRegion.top) + object.clipTopAddition; + if (clipTop < 0) { + clipTop = 0; + } + int clipBottom = (object.viewY + drawRegion.top + (drawRegion.bottom - drawRegion.top)) - (coords2[1] + object.parentView.getHeight()) + object.clipBottomAddition; + if (clipBottom < 0) { + clipBottom = 0; + } + + clipTop = Math.max(clipTop, clipVertical); + clipBottom = Math.max(clipBottom, clipVertical); + + animationValues[0][0] = animatingImageView.getScaleX(); + animationValues[0][1] = animatingImageView.getScaleY(); + animationValues[0][2] = animatingImageView.getTranslationX(); + animationValues[0][3] = animatingImageView.getTranslationY(); + animationValues[0][4] = 0; + animationValues[0][5] = 0; + animationValues[0][6] = 0; + animationValues[0][7] = 0; + + animationValues[1][0] = object.scale; + animationValues[1][1] = object.scale; + animationValues[1][2] = object.viewX + drawRegion.left * object.scale; + animationValues[1][3] = object.viewY + drawRegion.top * object.scale; + animationValues[1][4] = clipHorizontal * object.scale; + animationValues[1][5] = clipTop * object.scale; + animationValues[1][6] = clipBottom * object.scale; + animationValues[1][7] = object.radius; + + animatorSet.playTogether( + ObjectAnimator.ofFloat(animatingImageView, "animationProgress", 0.0f, 1.0f), + ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), + ObjectAnimator.ofFloat(actionBar, "alpha", 0), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), + ObjectAnimator.ofFloat(captionTextView, "alpha", 0) + ); + } else { + int h = AndroidUtilities.displaySize.y + AndroidUtilities.statusBarHeight; + animatorSet.playTogether( + ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), + ObjectAnimator.ofFloat(animatingImageView, "alpha", 0.0f), + ObjectAnimator.ofFloat(animatingImageView, "translationY", translationY >= 0 ? h : -h), + ObjectAnimator.ofFloat(actionBar, "alpha", 0), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), + ObjectAnimator.ofFloat(captionTextView, "alpha", 0) + ); + } + + photoAnimationEndRunnable = new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + photoContainerView.setVisibility(View.INVISIBLE); + photoContainerBackground.setVisibility(View.INVISIBLE); + photoAnimationInProgress = 0; + onPhotoClosed(object); + } + }; + + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (photoAnimationEndRunnable != null) { + photoAnimationEndRunnable.run(); + photoAnimationEndRunnable = null; + } + } + }); + } + }); + photoTransitionAnimationStartTime = System.currentTimeMillis(); + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + animatorSet.start(); + } else { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(photoContainerView, "scaleX", 0.9f), + ObjectAnimator.ofFloat(photoContainerView, "scaleY", 0.9f), + ObjectAnimator.ofInt(photoBackgroundDrawable, "alpha", 0), + ObjectAnimator.ofFloat(actionBar, "alpha", 0), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 0), + ObjectAnimator.ofFloat(captionTextView, "alpha", 0) + ); + photoAnimationInProgress = 2; + photoAnimationEndRunnable = new Runnable() { + @Override + public void run() { + if (photoContainerView == null) { + return; + } + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_NONE, null); + } + photoContainerView.setVisibility(View.INVISIBLE); + photoContainerBackground.setVisibility(View.INVISIBLE); + photoAnimationInProgress = 0; + onPhotoClosed(object); + photoContainerView.setScaleX(1.0f); + photoContainerView.setScaleY(1.0f); + } + }; + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (photoAnimationEndRunnable != null) { + photoAnimationEndRunnable.run(); + photoAnimationEndRunnable = null; + } + } + }); + photoTransitionAnimationStartTime = System.currentTimeMillis(); + if (Build.VERSION.SDK_INT >= 18) { + photoContainerView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + animatorSet.start(); + } + if (currentAnimation != null) { + currentAnimation.setSecondParentView(null); + currentAnimation = null; + centerImage.setImageBitmap((Drawable) null); + } + } + + private void onPhotoClosed(PlaceProviderObject object) { + isPhotoVisible = false; + disableShowCheck = true; + currentMedia = null; + currentThumb = null; + if (currentAnimation != null) { + currentAnimation.setSecondParentView(null); + currentAnimation = null; + } + for (int a = 0; a < 3; a++) { + if (radialProgressViews[a] != null) { + radialProgressViews[a].setBackgroundState(-1, false); + } + } + centerImage.setImageBitmap((Bitmap) null); + leftImage.setImageBitmap((Bitmap) null); + rightImage.setImageBitmap((Bitmap) null); + photoContainerView.post(new Runnable() { + @Override + public void run() { + animatingImageView.setImageBitmap(null); + } + }); + disableShowCheck = false; + if (object != null) { + object.imageReceiver.setVisible(true, true); + } + } + + public void onPause() { + if (currentAnimation != null) { + closePhoto(false); + } + } + + private void updateMinMax(float scale) { + int maxW = (int) (centerImage.getImageWidth() * scale - getContainerViewWidth()) / 2; + int maxH = (int) (centerImage.getImageHeight() * scale - getContainerViewHeight()) / 2; + if (maxW > 0) { + minX = -maxW; + maxX = maxW; + } else { + minX = maxX = 0; + } + if (maxH > 0) { + minY = -maxH; + maxY = maxH; + } else { + minY = maxY = 0; + } + } + + private int getContainerViewWidth() { + return photoContainerView.getWidth(); + } + + private int getContainerViewHeight() { + return photoContainerView.getHeight(); + } + + private boolean processTouchEvent(MotionEvent ev) { + if (photoAnimationInProgress != 0 || animationStartTime != 0) { + return false; + } + + if (ev.getPointerCount() == 1 && gestureDetector.onTouchEvent(ev) && doubleTap) { + doubleTap = false; + moving = false; + zooming = false; + checkMinMax(false); + return true; + } + + if (ev.getActionMasked() == MotionEvent.ACTION_DOWN || ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { + discardTap = false; + if (!scroller.isFinished()) { + scroller.abortAnimation(); + } + if (!draggingDown && !changingPage) { + if (canZoom && ev.getPointerCount() == 2) { + pinchStartDistance = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)); + pinchStartScale = scale; + pinchCenterX = (ev.getX(0) + ev.getX(1)) / 2.0f; + pinchCenterY = (ev.getY(0) + ev.getY(1)) / 2.0f; + pinchStartX = translationX; + pinchStartY = translationY; + zooming = true; + moving = false; + if (velocityTracker != null) { + velocityTracker.clear(); + } + } else if (ev.getPointerCount() == 1) { + moveStartX = ev.getX(); + dragY = moveStartY = ev.getY(); + draggingDown = false; + canDragDown = true; + if (velocityTracker != null) { + velocityTracker.clear(); + } + } + } + } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { + if (canZoom && ev.getPointerCount() == 2 && !draggingDown && zooming && !changingPage) { + discardTap = true; + scale = (float) Math.hypot(ev.getX(1) - ev.getX(0), ev.getY(1) - ev.getY(0)) / pinchStartDistance * pinchStartScale; + translationX = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (scale / pinchStartScale); + translationY = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (scale / pinchStartScale); + updateMinMax(scale); + photoContainerView.invalidate(); + } else if (ev.getPointerCount() == 1) { + if (velocityTracker != null) { + velocityTracker.addMovement(ev); + } + float dx = Math.abs(ev.getX() - moveStartX); + float dy = Math.abs(ev.getY() - dragY); + if (dx > AndroidUtilities.dp(3) || dy > AndroidUtilities.dp(3)) { + discardTap = true; + } + if (canDragDown && !draggingDown && scale == 1 && dy >= AndroidUtilities.dp(30) && dy / 2 > dx) { + draggingDown = true; + moving = false; + dragY = ev.getY(); + if (isActionBarVisible) { + toggleActionBar(false, true); + } + return true; + } else if (draggingDown) { + translationY = ev.getY() - dragY; + photoContainerView.invalidate(); + } else if (!invalidCoords && animationStartTime == 0) { + float moveDx = moveStartX - ev.getX(); + float moveDy = moveStartY - ev.getY(); + if (moving || scale == 1 && Math.abs(moveDy) + AndroidUtilities.dp(12) < Math.abs(moveDx) || scale != 1) { + if (!moving) { + moveDx = 0; + moveDy = 0; + moving = true; + canDragDown = false; + } + + moveStartX = ev.getX(); + moveStartY = ev.getY(); + updateMinMax(scale); + if (translationX < minX && (!rightImage.hasImage()) || translationX > maxX && !leftImage.hasImage()) { + moveDx /= 3.0f; + } + if (maxY == 0 && minY == 0) { + if (translationY - moveDy < minY) { + translationY = minY; + moveDy = 0; + } else if (translationY - moveDy > maxY) { + translationY = maxY; + moveDy = 0; + } + } else { + if (translationY < minY || translationY > maxY) { + moveDy /= 3.0f; + } + } + + translationX -= moveDx; + if (scale != 1) { + translationY -= moveDy; + } + + photoContainerView.invalidate(); + } + } else { + invalidCoords = false; + moveStartX = ev.getX(); + moveStartY = ev.getY(); + } + } + } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked() == MotionEvent.ACTION_UP || ev.getActionMasked() == MotionEvent.ACTION_POINTER_UP) { + if (zooming) { + invalidCoords = true; + if (scale < 1.0f) { + updateMinMax(1.0f); + animateTo(1.0f, 0, 0, true); + } else if (scale > 3.0f) { + float atx = (pinchCenterX - getContainerViewWidth() / 2) - ((pinchCenterX - getContainerViewWidth() / 2) - pinchStartX) * (3.0f / pinchStartScale); + float aty = (pinchCenterY - getContainerViewHeight() / 2) - ((pinchCenterY - getContainerViewHeight() / 2) - pinchStartY) * (3.0f / pinchStartScale); + updateMinMax(3.0f); + if (atx < minX) { + atx = minX; + } else if (atx > maxX) { + atx = maxX; + } + if (aty < minY) { + aty = minY; + } else if (aty > maxY) { + aty = maxY; + } + animateTo(3.0f, atx, aty, true); + } else { + checkMinMax(true); + } + zooming = false; + } else if (draggingDown) { + if (Math.abs(dragY - ev.getY()) > getContainerViewHeight() / 6.0f) { + closePhoto(true); + } else { + animateTo(1, 0, 0, false); + } + draggingDown = false; + } else if (moving) { + float moveToX = translationX; + float moveToY = translationY; + updateMinMax(scale); + moving = false; + canDragDown = true; + float velocity = 0; + if (velocityTracker != null && scale == 1) { + velocityTracker.computeCurrentVelocity(1000); + velocity = velocityTracker.getXVelocity(); + } + + if ((translationX < minX - getContainerViewWidth() / 3 || velocity < -AndroidUtilities.dp(650)) && rightImage.hasImage()) { + goToNext(); + return true; + } + if ((translationX > maxX + getContainerViewWidth() / 3 || velocity > AndroidUtilities.dp(650)) && leftImage.hasImage()) { + goToPrev(); + return true; + } + + if (translationX < minX) { + moveToX = minX; + } else if (translationX > maxX) { + moveToX = maxX; + } + if (translationY < minY) { + moveToY = minY; + } else if (translationY > maxY) { + moveToY = maxY; + } + animateTo(scale, moveToX, moveToY, false); + } + } + return false; + } + + private void checkMinMax(boolean zoom) { + float moveToX = translationX; + float moveToY = translationY; + updateMinMax(scale); + if (translationX < minX) { + moveToX = minX; + } else if (translationX > maxX) { + moveToX = maxX; + } + if (translationY < minY) { + moveToY = minY; + } else if (translationY > maxY) { + moveToY = maxY; + } + animateTo(scale, moveToX, moveToY, zoom); + } + + private void goToNext() { + float extra = 0; + if (scale != 1) { + extra = (getContainerViewWidth() - centerImage.getImageWidth()) / 2 * scale; + } + switchImageAfterAnimation = 1; + animateTo(scale, minX - getContainerViewWidth() - extra - AndroidUtilities.dp(30) / 2, translationY, false); + } + + private void goToPrev() { + float extra = 0; + if (scale != 1) { + extra = (getContainerViewWidth() - centerImage.getImageWidth()) / 2 * scale; + } + switchImageAfterAnimation = 2; + animateTo(scale, maxX + getContainerViewWidth() + extra + AndroidUtilities.dp(30) / 2, translationY, false); + } + + private void animateTo(float newScale, float newTx, float newTy, boolean isZoom) { + animateTo(newScale, newTx, newTy, isZoom, 250); + } + + private void animateTo(float newScale, float newTx, float newTy, boolean isZoom, int duration) { + if (scale == newScale && translationX == newTx && translationY == newTy) { + return; + } + zoomAnimation = isZoom; + animateToScale = newScale; + animateToX = newTx; + animateToY = newTy; + animationStartTime = System.currentTimeMillis(); + imageMoveAnimation = new AnimatorSet(); + imageMoveAnimation.playTogether( + ObjectAnimator.ofFloat(this, "animationValue", 0, 1) + ); + imageMoveAnimation.setInterpolator(interpolator); + imageMoveAnimation.setDuration(duration); + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + imageMoveAnimation = null; + photoContainerView.invalidate(); + } + }); + imageMoveAnimation.start(); + } + + public void setAnimationValue(float value) { + animationValue = value; + photoContainerView.invalidate(); + } + + public float getAnimationValue() { + return animationValue; + } + + private void drawContent(Canvas canvas) { + if (photoAnimationInProgress == 1 || !isPhotoVisible && photoAnimationInProgress != 2) { + return; + } + + float currentTranslationY; + float currentTranslationX; + float currentScale; + float aty = -1; + + if (imageMoveAnimation != null) { + if (!scroller.isFinished()) { + scroller.abortAnimation(); + } + + float ts = scale + (animateToScale - scale) * animationValue; + float tx = translationX + (animateToX - translationX) * animationValue; + float ty = translationY + (animateToY - translationY) * animationValue; + + if (animateToScale == 1 && scale == 1 && translationX == 0) { + aty = ty; + } + currentScale = ts; + currentTranslationY = ty; + currentTranslationX = tx; + photoContainerView.invalidate(); + } else { + if (animationStartTime != 0) { + translationX = animateToX; + translationY = animateToY; + scale = animateToScale; + animationStartTime = 0; + updateMinMax(scale); + zoomAnimation = false; + } + if (!scroller.isFinished()) { + if (scroller.computeScrollOffset()) { + if (scroller.getStartX() < maxX && scroller.getStartX() > minX) { + translationX = scroller.getCurrX(); + } + if (scroller.getStartY() < maxY && scroller.getStartY() > minY) { + translationY = scroller.getCurrY(); + } + photoContainerView.invalidate(); + } + } + if (switchImageAfterAnimation != 0) { + if (switchImageAfterAnimation == 1) { + setImageIndex(currentIndex + 1, false); + } else if (switchImageAfterAnimation == 2) { + setImageIndex(currentIndex - 1, false); + } + switchImageAfterAnimation = 0; + } + currentScale = scale; + currentTranslationY = translationY; + currentTranslationX = translationX; + if (!moving) { + aty = translationY; + } + } + + if (scale == 1 && aty != -1 && !zoomAnimation) { + float maxValue = getContainerViewHeight() / 4.0f; + photoBackgroundDrawable.setAlpha((int) Math.max(127, 255 * (1.0f - (Math.min(Math.abs(aty), maxValue) / maxValue)))); + } else { + photoBackgroundDrawable.setAlpha(255); + } + + ImageReceiver sideImage = null; + + if (scale >= 1.0f && !zoomAnimation && !zooming) { + if (currentTranslationX > maxX + AndroidUtilities.dp(5)) { + sideImage = leftImage; + } else if (currentTranslationX < minX - AndroidUtilities.dp(5)) { + sideImage = rightImage; + } + } + changingPage = sideImage != null; + + if (sideImage == rightImage) { + float tranlateX = currentTranslationX; + float scaleDiff = 0; + float alpha = 1; + if (!zoomAnimation && tranlateX < minX) { + alpha = Math.min(1.0f, (minX - tranlateX) / canvas.getWidth()); + scaleDiff = (1.0f - alpha) * 0.3f; + tranlateX = -canvas.getWidth() - AndroidUtilities.dp(30) / 2; + } + + if (sideImage.hasBitmapImage()) { + canvas.save(); + canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); + canvas.translate(canvas.getWidth() + AndroidUtilities.dp(30) / 2 + tranlateX, 0); + canvas.scale(1.0f - scaleDiff, 1.0f - scaleDiff); + int bitmapWidth = sideImage.getBitmapWidth(); + int bitmapHeight = sideImage.getBitmapHeight(); + + float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; + float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; + float scale = scaleX > scaleY ? scaleY : scaleX; + int width = (int) (bitmapWidth * scale); + int height = (int) (bitmapHeight * scale); + + sideImage.setAlpha(alpha); + sideImage.setImageCoords(-width / 2, -height / 2, width, height); + sideImage.draw(canvas); + canvas.restore(); + } + + canvas.save(); + canvas.translate(tranlateX, currentTranslationY / currentScale); + canvas.translate((canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); + radialProgressViews[1].setScale(1.0f - scaleDiff); + radialProgressViews[1].setAlpha(alpha); + radialProgressViews[1].onDraw(canvas); + canvas.restore(); + } + + float translateX = currentTranslationX; + float scaleDiff = 0; + float alpha = 1; + if (!zoomAnimation && translateX > maxX) { + alpha = Math.min(1.0f, (translateX - maxX) / canvas.getWidth()); + scaleDiff = alpha * 0.3f; + alpha = 1.0f - alpha; + translateX = maxX; + } + boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; + if (centerImage.hasBitmapImage()) { + canvas.save(); + canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); + canvas.translate(translateX, currentTranslationY); + canvas.scale(currentScale - scaleDiff, currentScale - scaleDiff); + + int bitmapWidth = centerImage.getBitmapWidth(); + int bitmapHeight = centerImage.getBitmapHeight(); + if (drawTextureView && textureUploaded) { + float scale1 = bitmapWidth / (float) bitmapHeight; + float scale2 = videoTextureView.getMeasuredWidth() / (float) videoTextureView.getMeasuredHeight(); + if (Math.abs(scale1 - scale2) > 0.01f) { + bitmapWidth = videoTextureView.getMeasuredWidth(); + bitmapHeight = videoTextureView.getMeasuredHeight(); + } + } + + float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; + float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; + float scale = scaleX > scaleY ? scaleY : scaleX; + int width = (int) (bitmapWidth * scale); + int height = (int) (bitmapHeight * scale); + + if (!drawTextureView || !textureUploaded || !videoCrossfadeStarted || videoCrossfadeAlpha != 1.0f) { + centerImage.setAlpha(alpha); + centerImage.setImageCoords(-width / 2, -height / 2, width, height); + centerImage.draw(canvas); + } + if (drawTextureView) { + if (!videoCrossfadeStarted && textureUploaded) { + videoCrossfadeStarted = true; + videoCrossfadeAlpha = 0.0f; + videoCrossfadeAlphaLastTime = System.currentTimeMillis(); + } + canvas.translate(-width / 2, -height / 2); + videoTextureView.setAlpha(alpha * videoCrossfadeAlpha); + aspectRatioFrameLayout.draw(canvas); + if (videoCrossfadeStarted && videoCrossfadeAlpha < 1.0f) { + long newUpdateTime = System.currentTimeMillis(); + long dt = newUpdateTime - videoCrossfadeAlphaLastTime; + videoCrossfadeAlphaLastTime = newUpdateTime; + videoCrossfadeAlpha += dt / 300.0f; + photoContainerView.invalidate(); + if (videoCrossfadeAlpha > 1.0f) { + videoCrossfadeAlpha = 1.0f; + } + } + } + canvas.restore(); + } + if (!drawTextureView && bottomLayout.getVisibility() != View.VISIBLE) { + canvas.save(); + canvas.translate(translateX, currentTranslationY / currentScale); + radialProgressViews[0].setScale(1.0f - scaleDiff); + radialProgressViews[0].setAlpha(alpha); + radialProgressViews[0].onDraw(canvas); + canvas.restore(); + } + + if (sideImage == leftImage) { + if (sideImage.hasBitmapImage()) { + canvas.save(); + canvas.translate(getContainerViewWidth() / 2, getContainerViewHeight() / 2); + canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2 + currentTranslationX, 0); + int bitmapWidth = sideImage.getBitmapWidth(); + int bitmapHeight = sideImage.getBitmapHeight(); + + float scaleX = (float) getContainerViewWidth() / (float) bitmapWidth; + float scaleY = (float) getContainerViewHeight() / (float) bitmapHeight; + float scale = scaleX > scaleY ? scaleY : scaleX; + int width = (int) (bitmapWidth * scale); + int height = (int) (bitmapHeight * scale); + + sideImage.setAlpha(1.0f); + sideImage.setImageCoords(-width / 2, -height / 2, width, height); + sideImage.draw(canvas); + canvas.restore(); + } + + canvas.save(); + canvas.translate(currentTranslationX, currentTranslationY / currentScale); + canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); + radialProgressViews[2].setScale(1.0f); + radialProgressViews[2].setAlpha(1.0f); + radialProgressViews[2].onDraw(canvas); + canvas.restore(); + } + } + + private void onActionClick(boolean download) { + TLObject media = getMedia(currentIndex); + if (!(media instanceof TLRPC.Document) || currentFileNames[0] == null) { + return; + } + TLRPC.Document document = (TLRPC.Document) media; + File file = null; + if (currentMedia != null) { + file = getMediaFile(currentIndex); + if (file != null && !file.exists()) { + file = null; + } + } + if (file == null) { + if (download) { + if (!FileLoader.getInstance().isLoadingFile(currentFileNames[0])) { + FileLoader.getInstance().loadFile(document, true, true); + } else { + FileLoader.getInstance().cancelLoadFile(document); + } + } + } else { + preparePlayer(file, true); + } + } + + @Override + public boolean onDown(MotionEvent e) { + return false; + } + + @Override + public void onShowPress(MotionEvent e) { + + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + return false; + } + + @Override + public void onLongPress(MotionEvent e) { + + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (scale != 1) { + scroller.abortAnimation(); + scroller.fling(Math.round(translationX), Math.round(translationY), Math.round(velocityX), Math.round(velocityY), (int) minX, (int) maxX, (int) minY, (int) maxY); + photoContainerView.postInvalidate(); + } + return false; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (discardTap) { + return false; + } + boolean drawTextureView = aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; + if (radialProgressViews[0] != null && photoContainerView != null && !drawTextureView) { + int state = radialProgressViews[0].backgroundState; + if (state > 0 && state <= 3) { + float x = e.getX(); + float y = e.getY(); + if (x >= (getContainerViewWidth() - AndroidUtilities.dp(100)) / 2.0f && x <= (getContainerViewWidth() + AndroidUtilities.dp(100)) / 2.0f && + y >= (getContainerViewHeight() - AndroidUtilities.dp(100)) / 2.0f && y <= (getContainerViewHeight() + AndroidUtilities.dp(100)) / 2.0f) { + onActionClick(true); + checkProgress(0, true); + return true; + } + } + } + toggleActionBar(!isActionBarVisible, true); + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (!canZoom || scale == 1.0f && (translationY != 0 || translationX != 0)) { + return false; + } + if (animationStartTime != 0 || photoAnimationInProgress != 0) { + return false; + } + if (scale == 1.0f) { + float atx = (e.getX() - getContainerViewWidth() / 2) - ((e.getX() - getContainerViewWidth() / 2) - translationX) * (3.0f / scale); + float aty = (e.getY() - getContainerViewHeight() / 2) - ((e.getY() - getContainerViewHeight() / 2) - translationY) * (3.0f / scale); + updateMinMax(3.0f); + if (atx < minX) { + atx = minX; + } else if (atx > maxX) { + atx = maxX; + } + if (aty < minY) { + aty = minY; + } else if (aty > maxY) { + aty = maxY; + } + animateTo(3.0f, atx, aty, true); + } else { + animateTo(1.0f, 0, 0, true); + } + doubleTap = true; + return true; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent e) { + return false; + } + + private ImageReceiver getImageReceiverFromListView(ViewGroup listView, TLRPC.PageBlock pageBlock, int[] coords) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof BlockPhotoCell) { + BlockPhotoCell cell = (BlockPhotoCell) view; + if (cell.currentBlock == pageBlock) { + view.getLocationInWindow(coords); + return cell.imageView; + } + } else if (view instanceof BlockVideoCell) { + BlockVideoCell cell = (BlockVideoCell) view; + if (cell.currentBlock == pageBlock) { + view.getLocationInWindow(coords); + return cell.imageView; + } + } else if (view instanceof BlockCollageCell) { + ImageReceiver imageReceiver = getImageReceiverFromListView(((BlockCollageCell) view).innerListView, pageBlock, coords); + if (imageReceiver != null) { + return imageReceiver; + } + } else if (view instanceof BlockSlideshowCell) { + ImageReceiver imageReceiver = getImageReceiverFromListView(((BlockSlideshowCell) view).innerListView, pageBlock, coords); + if (imageReceiver != null) { + return imageReceiver; + } + } + } + return null; + } + + private PlaceProviderObject getPlaceForPhoto(TLRPC.PageBlock pageBlock) { + ImageReceiver imageReceiver = getImageReceiverFromListView(listView, pageBlock, coords); + if (imageReceiver == null) { + return null; + } + PlaceProviderObject object = new PlaceProviderObject(); + object.viewX = coords[0]; + object.viewY = coords[1]; + object.parentView = listView; + object.imageReceiver = imageReceiver; + object.thumb = imageReceiver.getBitmap(); + object.radius = imageReceiver.getRoundRadius(); + object.clipTopAddition = currentHeaderHeight; + return object; + } + + private boolean scaleToFill() { + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java index 2d8b76f4e20..69a82bfb4da 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/AudioPlayerActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -11,6 +11,8 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; @@ -31,6 +33,7 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.LineProgressView; @@ -41,8 +44,6 @@ public class AudioPlayerActivity extends BaseFragment implements NotificationCen private MessageObject lastMessageObject; private ImageView placeholder; private ImageView playButton; - private ImageView nextButton; - private ImageView prevButton; private ImageView shuffleButton; private LineProgressView progressView; private ImageView repeatButton; @@ -50,6 +51,10 @@ public class AudioPlayerActivity extends BaseFragment implements NotificationCen private TextView durationTextView; private TextView timeTextView; private SeekBarView seekBarView; + private FrameLayout seekBarContainer; + private FrameLayout bottomView; + private ImageView prevButton; + private ImageView nextButton; private int TAG; @@ -69,10 +74,10 @@ public SeekBarView(Context context) { super(context); setWillNotDraw(false); innerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); - innerPaint1.setColor(0x19000000); + innerPaint1.setColor(Theme.getColor(Theme.key_player_progressBackground)); outerPaint1 = new Paint(Paint.ANTI_ALIAS_FLAG); - outerPaint1.setColor(0xff23afef); + outerPaint1.setColor(Theme.getColor(Theme.key_player_progress)); thumbWidth = AndroidUtilities.dp(24); thumbHeight = AndroidUtilities.dp(24); @@ -94,7 +99,7 @@ boolean onTouch(MotionEvent ev) { int additionWidth = (getMeasuredHeight() - thumbWidth) / 2; if (thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbWidth + additionWidth && ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) { pressed = true; - thumbDX = (int)(ev.getX() - thumbX); + thumbDX = (int) (ev.getX() - thumbX); invalidate(); return true; } @@ -109,7 +114,7 @@ boolean onTouch(MotionEvent ev) { } } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { if (pressed) { - thumbX = (int)(ev.getX() - thumbDX); + thumbX = (int) (ev.getX() - thumbDX); if (thumbX < 0) { thumbX = 0; } else if (thumbX > getMeasuredWidth() - thumbWidth) { @@ -123,7 +128,7 @@ boolean onTouch(MotionEvent ev) { } public void setProgress(float progress) { - int newThumbX = (int)Math.ceil((getMeasuredWidth() - thumbWidth) * progress); + int newThumbX = (int) Math.ceil((getMeasuredWidth() - thumbWidth) * progress); if (thumbX != newThumbX) { thumbX = newThumbX; if (thumbX < 0) { @@ -171,7 +176,7 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { FrameLayout frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); frameLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -180,11 +185,16 @@ public boolean onTouch(View v, MotionEvent event) { }); fragmentView = frameLayout; - actionBar.setBackgroundColor(Theme.ACTION_BAR_PLAYER_COLOR); - actionBar.setBackButtonImage(R.drawable.pl_back); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_player_actionBar)); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setItemsColor(Theme.getColor(Theme.key_player_actionBarItems), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_player_actionBarSelector), false); + actionBar.setTitleColor(Theme.getColor(Theme.key_player_actionBarTitle)); + actionBar.setSubtitleColor(Theme.getColor(Theme.key_player_actionBarSubtitle)); + if (!AndroidUtilities.isTablet()) { actionBar.showActionModeTop(); + actionBar.setActionModeTopColor(Theme.getColor(Theme.key_player_actionBarTop)); } actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override @@ -202,20 +212,20 @@ public void onItemClick(int id) { shadow.setBackgroundResource(R.drawable.header_shadow_reverse); frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 96)); - FrameLayout seekBarContainer = new FrameLayout(context); - seekBarContainer.setBackgroundColor(0xe5ffffff); + seekBarContainer = new FrameLayout(context); + seekBarContainer.setBackgroundColor(Theme.getColor(Theme.key_player_seekBarBackground)); frameLayout.addView(seekBarContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 66)); timeTextView = new TextView(context); timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - timeTextView.setTextColor(0xff19a7e8); + timeTextView.setTextColor(Theme.getColor(Theme.key_player_time)); timeTextView.setGravity(Gravity.CENTER); timeTextView.setText("0:00"); seekBarContainer.addView(timeTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); durationTextView = new TextView(context); durationTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - durationTextView.setTextColor(0xff8a8a8a); + durationTextView.setTextColor(Theme.getColor(Theme.key_player_duration)); durationTextView.setGravity(Gravity.CENTER); durationTextView.setText("3:00"); seekBarContainer.addView(durationTextView, LayoutHelper.createFrame(44, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); @@ -225,11 +235,11 @@ public void onItemClick(int id) { progressView = new LineProgressView(context); progressView.setVisibility(View.INVISIBLE); - progressView.setBackgroundColor(0x19000000); - progressView.setProgressColor(0xff23afef); + progressView.setBackgroundColor(Theme.getColor(Theme.key_player_progressBackground)); + progressView.setProgressColor(Theme.getColor(Theme.key_player_progress)); seekBarContainer.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 2, Gravity.CENTER_VERTICAL | Gravity.LEFT, 44, 0, 44, 0)); - FrameLayout bottomView = new FrameLayout(context) { + bottomView = new FrameLayout(context) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int dist = ((right - left) - AndroidUtilities.dp(30 + 48 * 5)) / 4; @@ -240,7 +250,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto } } }; - bottomView.setBackgroundColor(0xffffffff); + bottomView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); frameLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 66, Gravity.BOTTOM | Gravity.LEFT)); buttons[0] = repeatButton = new ImageView(context); @@ -256,7 +266,7 @@ public void onClick(View v) { buttons[1] = prevButton = new ImageView(context); prevButton.setScaleType(ImageView.ScaleType.CENTER); - prevButton.setImageResource(R.drawable.player_prev_states); + prevButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_previous, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); bottomView.addView(prevButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); prevButton.setOnClickListener(new View.OnClickListener() { @Override @@ -267,7 +277,7 @@ public void onClick(View v) { buttons[2] = playButton = new ImageView(context); playButton.setScaleType(ImageView.ScaleType.CENTER); - playButton.setImageResource(R.drawable.player_play_states); + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); bottomView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); playButton.setOnClickListener(new View.OnClickListener() { @Override @@ -285,7 +295,7 @@ public void onClick(View v) { buttons[3] = nextButton = new ImageView(context); nextButton.setScaleType(ImageView.ScaleType.CENTER); - nextButton.setImageResource(R.drawable.player_next_states); + nextButton.setImageDrawable(Theme.createSimpleSelectorDrawable(context, R.drawable.pl_next, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); bottomView.addView(nextButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); nextButton.setOnClickListener(new View.OnClickListener() { @Override @@ -295,6 +305,7 @@ public void onClick(View v) { }); buttons[4] = shuffleButton = new ImageView(context); + shuffleButton.setImageResource(R.drawable.pl_shuffle); shuffleButton.setScaleType(ImageView.ScaleType.CENTER); bottomView.addView(shuffleButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP)); shuffleButton.setOnClickListener(new View.OnClickListener() { @@ -355,9 +366,11 @@ private void onSeekBarDrag(float progress) { private void updateShuffleButton() { if (MediaController.getInstance().isShuffleMusic()) { - shuffleButton.setImageResource(R.drawable.pl_shuffle_active); + shuffleButton.setTag(Theme.key_player_buttonActive); + shuffleButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); } else { - shuffleButton.setImageResource(R.drawable.pl_shuffle); + shuffleButton.setTag(Theme.key_player_button); + shuffleButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); } } @@ -365,10 +378,16 @@ private void updateRepeatButton() { int mode = MediaController.getInstance().getRepeatMode(); if (mode == 0) { repeatButton.setImageResource(R.drawable.pl_repeat); + repeatButton.setTag(Theme.key_player_button); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_button), PorterDuff.Mode.MULTIPLY)); } else if (mode == 1) { - repeatButton.setImageResource(R.drawable.pl_repeat_active); + repeatButton.setImageResource(R.drawable.pl_repeat); + repeatButton.setTag(Theme.key_player_buttonActive); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); } else if (mode == 2) { - repeatButton.setImageResource(R.drawable.pl_repeat1_active); + repeatButton.setImageResource(R.drawable.pl_repeat1); + repeatButton.setTag(Theme.key_player_buttonActive); + repeatButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_buttonActive), PorterDuff.Mode.MULTIPLY)); } } @@ -389,7 +408,7 @@ private void checkIfMusicDownloaded(MessageObject messageObject) { File cacheFile = null; if (messageObject.messageOwner.attachPath != null && messageObject.messageOwner.attachPath.length() > 0) { cacheFile = new File(messageObject.messageOwner.attachPath); - if(!cacheFile.exists()) { + if (!cacheFile.exists()) { cacheFile = null; } } @@ -428,25 +447,27 @@ private void updateTitle(boolean shutdown) { updateProgress(messageObject); if (MediaController.getInstance().isAudioPaused()) { - playButton.setImageResource(R.drawable.player_play_states); + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_play, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); } else { - playButton.setImageResource(R.drawable.player_pause_states); + playButton.setImageDrawable(Theme.createSimpleSelectorDrawable(playButton.getContext(), R.drawable.pl_pause, Theme.getColor(Theme.key_player_button), Theme.getColor(Theme.key_player_buttonActive))); } if (actionBar != null) { actionBar.setTitle(messageObject.getMusicTitle()); - actionBar.getTitleTextView().setTextColor(0xff212121); actionBar.setSubtitle(messageObject.getMusicAuthor()); - actionBar.getSubtitleTextView().setTextColor(0xff8a8a8a); } AudioInfo audioInfo = MediaController.getInstance().getAudioInfo(); if (audioInfo != null && audioInfo.getCover() != null) { placeholder.setImageBitmap(audioInfo.getCover()); placeholder.setPadding(0, 0, 0, 0); placeholder.setScaleType(ImageView.ScaleType.CENTER_CROP); + placeholder.setTag(null); + placeholder.setColorFilter(null); } else { placeholder.setImageResource(R.drawable.nocover); placeholder.setPadding(0, 0, 0, AndroidUtilities.dp(30)); placeholder.setScaleType(ImageView.ScaleType.CENTER); + placeholder.setTag(Theme.key_player_placeholder); + placeholder.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_player_placeholder), PorterDuff.Mode.MULTIPLY)); } if (durationTextView != null) { @@ -465,4 +486,45 @@ private void updateTitle(boolean shutdown) { } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + new ThemeDescription(bottomView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_actionBar), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_player_actionBarItems), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_player_actionBarTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBTITLECOLOR, null, null, null, null, Theme.key_player_actionBarSubtitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_player_actionBarSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_TOPBACKGROUND, null, null, null, null, Theme.key_player_actionBarTop), + + new ThemeDescription(seekBarContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_seekBarBackground), + + new ThemeDescription(timeTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_player_time), + new ThemeDescription(durationTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_player_time), + + new ThemeDescription(progressView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_player_progressBackground), + new ThemeDescription(progressView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_player_progress), + + new ThemeDescription(seekBarView, 0, null, seekBarView.innerPaint1, null, null, Theme.key_player_progressBackground), + new ThemeDescription(seekBarView, 0, null, seekBarView.outerPaint1, null, null, Theme.key_player_progress), + + new ThemeDescription(repeatButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_buttonActive), + new ThemeDescription(repeatButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_button), + + new ThemeDescription(shuffleButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_buttonActive), + new ThemeDescription(shuffleButton, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_button), + + new ThemeDescription(placeholder, ThemeDescription.FLAG_CHECKTAG | ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_player_placeholder), + + new ThemeDescription(prevButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), + new ThemeDescription(prevButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), + new ThemeDescription(playButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), + new ThemeDescription(playButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), + new ThemeDescription(nextButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, null, null, null, null, Theme.key_player_button), + new ThemeDescription(nextButton, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_player_buttonActive), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/AudioSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/AudioSelectActivity.java index 92ff709ab9a..97fb9a4908f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/AudioSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/AudioSelectActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -14,9 +14,7 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; @@ -27,16 +25,20 @@ import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.AudioCell; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PickerBottomLayout; +import org.telegram.ui.Components.RecyclerListView; import java.io.File; import java.util.ArrayList; @@ -47,6 +49,8 @@ public class AudioSelectActivity extends BaseFragment implements NotificationCen private ListAdapter listViewAdapter; private EmptyTextProgressView progressView; private PickerBottomLayout bottomLayout; + private RecyclerListView listView; + private View shadow; private boolean loadingAudio; @@ -101,18 +105,17 @@ public void onItemClick(int id) { progressView.setText(LocaleController.getString("NoAudio", R.string.NoAudio)); frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - ListView listView = new ListView(context); + listView = new RecyclerListView(context); listView.setEmptyView(progressView); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(listViewAdapter = new ListAdapter(context)); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 48)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { AudioCell audioCell = (AudioCell) view; MediaController.AudioEntry audioEntry = audioCell.getAudioEntry(); if (selectedAudios.containsKey(audioEntry.id)) { @@ -253,7 +256,7 @@ public void run() { id--; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (cursor != null) { cursor.close(); @@ -271,7 +274,8 @@ public void run() { }); } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -279,21 +283,10 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { - return true; - } - - @Override - public int getCount() { + public int getItemCount() { return audioEntries.size(); } - @Override public Object getItem(int i) { return audioEntries.get(i); } @@ -304,40 +297,66 @@ public long getItemId(int i) { } @Override - public boolean hasStableIds() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (view == null) { - view = new AudioCell(mContext); - ((AudioCell) view).setDelegate(new AudioCell.AudioCellDelegate() { - @Override - public void startedPlayingAudio(MessageObject messageObject) { - playingAudio = messageObject; - } - }); - } - MediaController.AudioEntry audioEntry = audioEntries.get(i); - ((AudioCell) view).setAudio(audioEntries.get(i), i != audioEntries.size() - 1, selectedAudios.containsKey(audioEntry.id)); - return view; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + AudioCell view = new AudioCell(mContext); + view.setDelegate(new AudioCell.AudioCellDelegate() { + @Override + public void startedPlayingAudio(MessageObject messageObject) { + playingAudio = messageObject; + } + }); + return new RecyclerListView.Holder(view); } @Override - public int getItemViewType(int i) { - return 0; + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + MediaController.AudioEntry audioEntry = audioEntries.get(position); + ((AudioCell) holder.itemView).setAudio(audioEntries.get(position), position != audioEntries.size() - 1, selectedAudios.containsKey(audioEntry.id)); } @Override - public int getViewTypeCount() { - return 1; + public int getItemViewType(int i) { + return 0; } + } - @Override - public boolean isEmpty() { - return audioEntries.isEmpty(); - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(progressView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(progressView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{AudioCell.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{AudioCell.class}, new String[]{"genreTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{AudioCell.class}, new String[]{"authorTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{AudioCell.class}, new String[]{"timeTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{AudioCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_musicPicker_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{AudioCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_musicPicker_checkboxCheck), + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{AudioCell.class}, new String[]{"playButton"}, null, null, null, Theme.key_musicPicker_buttonIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{AudioCell.class}, new String[]{"playButton"}, null, null, null, Theme.key_musicPicker_buttonBackground), + + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{PickerBottomLayout.class}, new String[]{"cancelButton"}, null, null, null, Theme.key_picker_enabledButton), + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{PickerBottomLayout.class}, new String[]{"doneButtonTextView"}, null, null, null, Theme.key_picker_enabledButton), + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{PickerBottomLayout.class}, new String[]{"doneButtonTextView"}, null, null, null, Theme.key_picker_disabledButton), + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{PickerBottomLayout.class}, new String[]{"doneButtonBadgeTextView"}, null, null, null, Theme.key_picker_badgeText), + new ThemeDescription(bottomLayout, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{PickerBottomLayout.class}, new String[]{"doneButtonBadgeTextView"}, null, null, null, Theme.key_picker_badge), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java index fb41fc92f80..1c26f2b7e27 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/BlockedUsersActivity.java @@ -3,49 +3,46 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.os.Build; +import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextInfoCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; public class BlockedUsersActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, ContactsActivity.ContactsActivityDelegate { - private ListView listView; + private RecyclerListView listView; private ListAdapter listViewAdapter; - private FrameLayout progressView; - private TextView emptyTextView; + private EmptyTextProgressView emptyView; private int selectedUserId; - private final static int block_user = 1; @Override @@ -92,53 +89,37 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setVisibility(View.INVISIBLE); - emptyTextView.setText(LocaleController.getString("NoBlocked", R.string.NoBlocked)); - frameLayout.addView(emptyTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - emptyTextView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - progressView = new FrameLayout(context); - frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - - ProgressBar progressBar = new ProgressBar(context); - progressView.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoBlocked", R.string.NoBlocked)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView = new ListView(context); - listView.setEmptyView(emptyTextView); + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); listView.setAdapter(listViewAdapter = new ListAdapter(context)); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - if (i < MessagesController.getInstance().blockedUsers.size()) { - Bundle args = new Bundle(); - args.putInt("user_id", MessagesController.getInstance().blockedUsers.get(i)); - presentFragment(new ProfileActivity(args)); + public void onItemClick(View view, int position) { + if (position >= MessagesController.getInstance().blockedUsers.size()) { + return; } + Bundle args = new Bundle(); + args.putInt("user_id", MessagesController.getInstance().blockedUsers.get(position)); + presentFragment(new ProfileActivity(args)); } }); - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { - if (i < 0 || i >= MessagesController.getInstance().blockedUsers.size() || getParentActivity() == null) { + public boolean onItemClick(View view, int position) { + if (position >= MessagesController.getInstance().blockedUsers.size() || getParentActivity() == null) { return true; } - selectedUserId = MessagesController.getInstance().blockedUsers.get(i); + selectedUserId = MessagesController.getInstance().blockedUsers.get(position); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); CharSequence[] items = new CharSequence[]{LocaleController.getString("Unblock", R.string.Unblock)}; @@ -157,12 +138,9 @@ public void onClick(DialogInterface dialogInterface, int i) { }); if (MessagesController.getInstance().loadingBlockedUsers) { - progressView.setVisibility(View.VISIBLE); - emptyTextView.setVisibility(View.GONE); - listView.setEmptyView(null); + emptyView.showProgress(); } else { - progressView.setVisibility(View.GONE); - listView.setEmptyView(emptyTextView); + emptyView.showTextView(); } return fragmentView; } @@ -170,17 +148,12 @@ public void onClick(DialogInterface dialogInterface, int i) { @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; + int mask = (Integer) args[0]; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0) { updateVisibleRows(mask); } } else if (id == NotificationCenter.blockedUsersDidLoaded) { - if (progressView != null) { - progressView.setVisibility(View.GONE); - } - if (listView != null && listView.getEmptyView() == null) { - listView.setEmptyView(emptyTextView); - } + emptyView.showTextView(); if (listViewAdapter != null) { listViewAdapter.notifyDataSetChanged(); } @@ -216,7 +189,8 @@ public void didSelectContact(final TLRPC.User user, String param) { MessagesController.getInstance().blockUser(user.id); } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -224,17 +198,7 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return i != MessagesController.getInstance().blockedUsers.size(); - } - - @Override - public int getCount() { + public int getItemCount() { if (MessagesController.getInstance().blockedUsers.isEmpty()) { return 0; } @@ -242,28 +206,30 @@ public int getCount() { } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == 0; } @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new UserCell(mContext, 1, 0, false); + break; + case 1: + default: + view = new TextInfoCell(mContext); + ((TextInfoCell) view).setText(LocaleController.getString("UnblockText", R.string.UnblockText)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new UserCell(mContext, 1, 0, false); - } - TLRPC.User user = MessagesController.getInstance().getUser(MessagesController.getInstance().blockedUsers.get(i)); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + TLRPC.User user = MessagesController.getInstance().getUser(MessagesController.getInstance().blockedUsers.get(position)); if (user != null) { String number; if (user.bot) { @@ -273,33 +239,61 @@ public View getView(int i, View view, ViewGroup viewGroup) { } else { number = LocaleController.getString("NumberUnknown", R.string.NumberUnknown); } - ((UserCell) view).setData(user, null, number, 0); - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoCell(mContext); - ((TextInfoCell) view).setText(LocaleController.getString("UnblockText", R.string.UnblockText)); + ((UserCell) holder.itemView).setData(user, null, number, 0); } } - return view; } @Override public int getItemViewType(int i) { - if(i == MessagesController.getInstance().blockedUsers.size()) { + if (i == MessagesController.getInstance().blockedUsers.size()) { return 1; } return 0; } + } - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public boolean isEmpty() { - return MessagesController.getInstance().blockedUsers.isEmpty(); - } + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{TextInfoCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText5), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java index 754ed18872a..5122dcf1e36 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CacheControlActivity.java @@ -3,26 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; import android.app.AlarmManager; -import android.app.AlertDialog; import android.app.PendingIntent; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ListView; import org.telegram.SQLite.SQLiteCursor; import org.telegram.SQLite.SQLiteDatabase; @@ -38,16 +34,21 @@ import org.telegram.messenger.R; import org.telegram.messenger.Utilities; import org.telegram.messenger.query.BotQuery; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.NativeByteBuffer; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.io.File; import java.util.ArrayList; @@ -55,6 +56,7 @@ public class CacheControlActivity extends BaseFragment { private ListAdapter listAdapter; + private RecyclerListView listView; private int databaseRow; private int databaseInfoRow; @@ -139,28 +141,6 @@ public void onFragmentDestroy() { canceled = true; } - /*private long getDirectorySize2(File dir) { - long size = 0; - if (dir.isDirectory()) { - File[] array = dir.listFiles(); - if (array != null) { - for (int a = 0; a < array.length; a++) { - File file = array[a]; - if (file.isDirectory()) { - size += getDirectorySize2(file); - } else { - size += file.length(); - FileLog.e("tmessages", "" + file + " size = " + file.length()); - } - } - } - } else if (dir.isFile()) { - FileLog.e("tmessages", "" + dir + " size = " + dir.length()); - size += dir.length(); - } - return size; - }*/ - private long getDirectorySize(File dir, int documentsMusicType) { if (dir == null || canceled) { return 0; @@ -193,7 +173,7 @@ private long getDirectorySize(File dir, int documentsMusicType) { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (dir.isFile()) { size += dir.length(); @@ -202,7 +182,7 @@ private long getDirectorySize(File dir, int documentsMusicType) { } private void cleanupFolders() { - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -260,7 +240,7 @@ public void run() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (type == FileLoader.MEDIA_DIR_CACHE) { @@ -295,7 +275,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -307,7 +287,7 @@ public void run() { public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - actionBar.setTitle(LocaleController.getString("CacheSettings", R.string.CacheSettings)); + actionBar.setTitle(LocaleController.getString("StorageUsage", R.string.StorageUsage)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { @@ -321,22 +301,20 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(final AdapterView adapterView, View view, final int i, long l) { + public void onItemClick(View view, int position) { if (getParentActivity() == null) { return; } - if (i == keepMediaRow) { + if (position == keepMediaRow) { BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); builder.setItems(new CharSequence[]{LocaleController.formatPluralString("Weeks", 1), LocaleController.formatPluralString("Months", 1), LocaleController.getString("KeepMediaForever", R.string.KeepMediaForever)}, new DialogInterface.OnClickListener() { @Override @@ -356,7 +334,7 @@ public void onClick(DialogInterface dialog, final int which) { } }); showDialog(builder.create()); - } else if (i == databaseRow) { + } else if (position == databaseRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -364,7 +342,7 @@ public void onClick(DialogInterface dialog, final int which) { builder.setPositiveButton(LocaleController.getString("CacheClear", R.string.CacheClear), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -421,7 +399,7 @@ public void run() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cursor2.dispose(); @@ -443,7 +421,7 @@ public void run() { database.commitTransaction(); database.executeFast("VACUUM").stepThis().dispose(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -451,7 +429,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (listAdapter != null) { File file = new File(ApplicationLoader.getFilesDirFixed(), "cache4.db"); @@ -466,7 +444,7 @@ public void run() { } }); showDialog(builder.create()); - } else if (i == cacheRow) { + } else if (position == cacheRow) { if (totalSize <= 0 || getParentActivity() == null) { return; } @@ -499,11 +477,12 @@ public void run() { } if (size > 0) { clear[a] = true; - CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity()); + CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity(), true); checkBoxCell.setTag(a); - checkBoxCell.setBackgroundResource(R.drawable.list_selector); + checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); checkBoxCell.setText(name, AndroidUtilities.formatFileSize(size), true, true); + checkBoxCell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); checkBoxCell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -518,9 +497,9 @@ public void onClick(View v) { } } BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); - cell.setBackgroundResource(R.drawable.list_selector); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setTextAndIcon(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache).toUpperCase(), 0); - cell.setTextColor(0xffcd5a5a); + cell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText)); cell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -529,7 +508,7 @@ public void onClick(View v) { visibleDialog.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cleanupFolders(); } @@ -552,7 +531,8 @@ public void onResume() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -560,101 +540,103 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == databaseRow || position == cacheRow && totalSize > 0 || position == keepMediaRow; } @Override - public boolean isEnabled(int i) { - return i == databaseRow || i == cacheRow && totalSize > 0 || i == keepMediaRow; - } - - @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + default: + view = new TextInfoPrivacyCell(mContext); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == databaseRow) { - textCell.setTextAndValue(LocaleController.getString("LocalDatabase", R.string.LocalDatabase), AndroidUtilities.formatFileSize(databaseSize), false); - } else if (i == cacheRow) { - if (calculating) { - textCell.setTextAndValue(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), LocaleController.getString("CalculatingSize", R.string.CalculatingSize), false); - } else { - textCell.setTextAndValue(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), totalSize == 0 ? LocaleController.getString("CacheEmpty", R.string.CacheEmpty) : AndroidUtilities.formatFileSize(totalSize), false); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == databaseRow) { + textCell.setTextAndValue(LocaleController.getString("LocalDatabase", R.string.LocalDatabase), AndroidUtilities.formatFileSize(databaseSize), false); + } else if (position == cacheRow) { + if (calculating) { + textCell.setTextAndValue(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), LocaleController.getString("CalculatingSize", R.string.CalculatingSize), false); + } else { + textCell.setTextAndValue(LocaleController.getString("ClearMediaCache", R.string.ClearMediaCache), totalSize == 0 ? LocaleController.getString("CacheEmpty", R.string.CacheEmpty) : AndroidUtilities.formatFileSize(totalSize), false); + } + } else if (position == keepMediaRow) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + int keepMedia = preferences.getInt("keep_media", 2); + String value; + if (keepMedia == 0) { + value = LocaleController.formatPluralString("Weeks", 1); + } else if (keepMedia == 1) { + value = LocaleController.formatPluralString("Months", 1); + } else { + value = LocaleController.getString("KeepMediaForever", R.string.KeepMediaForever); + } + textCell.setTextAndValue(LocaleController.getString("KeepMedia", R.string.KeepMedia), value, false); } - } else if (i == keepMediaRow) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - int keepMedia = preferences.getInt("keep_media", 2); - String value; - if (keepMedia == 0) { - value = LocaleController.formatPluralString("Weeks", 1); - } else if (keepMedia == 1) { - value = LocaleController.formatPluralString("Months", 1); - } else { - value = LocaleController.getString("KeepMediaForever", R.string.KeepMediaForever); + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == databaseInfoRow) { + privacyCell.setText(LocaleController.getString("LocalDatabaseInfo", R.string.LocalDatabaseInfo)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == cacheInfoRow) { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == keepMediaInfoRow) { + privacyCell.setText(AndroidUtilities.replaceTags(LocaleController.getString("KeepMediaInfo", R.string.KeepMediaInfo))); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); } - textCell.setTextAndValue(LocaleController.getString("KeepMedia", R.string.KeepMedia), value, false); - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == databaseInfoRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("LocalDatabaseInfo", R.string.LocalDatabaseInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (i == cacheInfoRow) { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == keepMediaInfoRow) { - ((TextInfoPrivacyCell) view).setText(AndroidUtilities.replaceTags(LocaleController.getString("KeepMediaInfo", R.string.KeepMediaInfo))); - view.setBackgroundResource(R.drawable.greydivider); - } + break; } - return view; } @Override public int getItemViewType(int i) { - if (i == databaseRow || i == cacheRow || i == keepMediaRow) { - return 0; - } else if (i == databaseInfoRow || i == cacheInfoRow || i == keepMediaInfoRow) { + if (i == databaseInfoRow || i == cacheInfoRow || i == keepMediaInfoRow) { return 1; } return 0; } + } - @Override - public int getViewTypeCount() { - return 2; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java new file mode 100644 index 00000000000..f5c49056bb7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/CallLogActivity.java @@ -0,0 +1,649 @@ +package org.telegram.ui; + +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.graphics.Outline; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.style.ImageSpan; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Cells.LocationCell; +import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.voip.VoIPHelper; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class CallLogActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private ListAdapter listViewAdapter; + private EmptyTextProgressView emptyView; + private LinearLayoutManager layoutManager; + private RecyclerListView listView; + private ImageView floatingButton; + + private ArrayList calls = new ArrayList<>(); + private boolean loading; + private boolean firstLoaded; + private boolean endReached; + + private int prevPosition; + private int prevTop; + private boolean scrollUpdated; + private boolean floatingHidden; + private final AccelerateDecelerateInterpolator floatingInterpolator = new AccelerateDecelerateInterpolator(); + + private Drawable greenDrawable; + private Drawable greenDrawable2; + private Drawable redDrawable; + private ImageSpan iconOut, iconIn, iconMissed; + private TLRPC.User lastCallUser; + + private static final int TYPE_OUT = 0; + private static final int TYPE_IN = 1; + private static final int TYPE_MISSED = 2; + + @Override + @SuppressWarnings("unchecked") + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.didReceivedNewMessages && firstLoaded) { + ArrayList arr = (ArrayList) args[1]; + for (MessageObject msg : arr) { + if (msg.messageOwner.action != null && msg.messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { + int userID = msg.messageOwner.from_id == UserConfig.getClientUserId() ? msg.messageOwner.to_id.user_id : msg.messageOwner.from_id; + int callType = msg.messageOwner.from_id == UserConfig.getClientUserId() ? TYPE_OUT : TYPE_IN; + TLRPC.PhoneCallDiscardReason reason = msg.messageOwner.action.reason; + if (callType == TYPE_IN && (reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed || reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy)) { + callType = TYPE_MISSED; + } + if (calls.size() > 0) { + CallLogRow topRow = calls.get(0); + if (topRow.user.id == userID && topRow.type == callType) { + topRow.calls.add(0, msg.messageOwner); + listViewAdapter.notifyItemChanged(0); + continue; + } + } + CallLogRow row = new CallLogRow(); + row.calls = new ArrayList<>(); + row.calls.add(msg.messageOwner); + row.user = MessagesController.getInstance().getUser(userID); + row.type = callType; + calls.add(0, row); + listViewAdapter.notifyItemInserted(0); + } + } + } else if (id == NotificationCenter.messagesDeleted && firstLoaded) { + boolean didChange = false; + ArrayList ids = (ArrayList) args[0]; + Iterator itrtr = calls.iterator(); + while (itrtr.hasNext()) { + CallLogRow row = itrtr.next(); + Iterator msgs = row.calls.iterator(); + while (msgs.hasNext()) { + TLRPC.Message msg = msgs.next(); + if (ids.contains(msg.id)) { + didChange = true; + msgs.remove(); + } + } + if (row.calls.size() == 0) + itrtr.remove(); + } + if (didChange && listViewAdapter != null) + listViewAdapter.notifyDataSetChanged(); + } + } + + private class CustomCell extends FrameLayout { + + public CustomCell(Context context) { + super(context); + } + } + + private View.OnClickListener callBtnClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + CallLogRow row = (CallLogRow) v.getTag(); + VoIPHelper.startCall(lastCallUser = row.user, getParentActivity(), null); + } + }; + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + getCalls(0, 50); + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didReceivedNewMessages); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.messagesDeleted); + + return true; + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceivedNewMessages); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesDeleted); + } + + @Override + public View createView(Context context) { + greenDrawable = getParentActivity().getResources().getDrawable(R.drawable.ic_call_made_green_18dp).mutate(); + greenDrawable.setBounds(0, 0, greenDrawable.getIntrinsicWidth(), greenDrawable.getIntrinsicHeight()); + greenDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_calls_callReceivedGreenIcon), PorterDuff.Mode.MULTIPLY)); + iconOut = new ImageSpan(greenDrawable, ImageSpan.ALIGN_BOTTOM); + greenDrawable2 = getParentActivity().getResources().getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); + greenDrawable2.setBounds(0, 0, greenDrawable2.getIntrinsicWidth(), greenDrawable2.getIntrinsicHeight()); + greenDrawable2.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_calls_callReceivedGreenIcon), PorterDuff.Mode.MULTIPLY)); + iconIn = new ImageSpan(greenDrawable2, ImageSpan.ALIGN_BOTTOM); + redDrawable = getParentActivity().getResources().getDrawable(R.drawable.ic_call_received_green_18dp).mutate(); + redDrawable.setBounds(0, 0, redDrawable.getIntrinsicWidth(), redDrawable.getIntrinsicHeight()); + redDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_calls_callReceivedRedIcon), PorterDuff.Mode.MULTIPLY)); + iconMissed = new ImageSpan(redDrawable, ImageSpan.ALIGN_BOTTOM); + + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("Calls", R.string.Calls)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoCallLog", R.string.NoCallLog)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setAdapter(listViewAdapter = new ListAdapter(context)); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position < 0 || position >= calls.size()) { + return; + } + CallLogRow row = calls.get(position); + Bundle args = new Bundle(); + args.putInt("user_id", row.user.id); + args.putInt("message_id", row.calls.get(0).id); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + presentFragment(new ChatActivity(args), true); + } + }); + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { + @Override + public boolean onItemClick(View view, final int position) { + if (position < 0 || position >= calls.size()) { + return false; + } + final CallLogRow row = calls.get(position); + new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString("Calls", R.string.Calls)) + .setItems(new String[]{ + LocaleController.getString("Delete", R.string.Delete) + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + confirmAndDelete(row); + } + }) + .show(); + return true; + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(layoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + if (visibleItemCount > 0) { + int totalItemCount = listViewAdapter.getItemCount(); + if (!endReached && !loading && !calls.isEmpty() && firstVisibleItem + visibleItemCount >= totalItemCount - 5) { + CallLogRow row = calls.get(calls.size() - 1); + getCalls(row.calls.get(row.calls.size() - 1).id, 100); + } + } + + if (floatingButton.getVisibility() != View.GONE) { + final View topChild = recyclerView.getChildAt(0); + int firstViewTop = 0; + if (topChild != null) { + firstViewTop = topChild.getTop(); + } + boolean goingDown; + boolean changed = true; + if (prevPosition == firstVisibleItem) { + final int topDelta = prevTop - firstViewTop; + goingDown = firstViewTop < prevTop; + changed = Math.abs(topDelta) > 1; + } else { + goingDown = firstVisibleItem > prevPosition; + } + if (changed && scrollUpdated) { + hideFloatingButton(goingDown); + } + prevPosition = firstVisibleItem; + prevTop = firstViewTop; + scrollUpdated = true; + } + } + }); + + if (loading) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } + + + floatingButton = new ImageView(context); + floatingButton.setVisibility(View.VISIBLE); + floatingButton.setScaleType(ImageView.ScaleType.CENTER); + + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + floatingButton.setBackgroundDrawable(drawable); + floatingButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); + floatingButton.setImageResource(R.drawable.ic_call_white_24dp); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + floatingButton.setStateListAnimator(animator); + floatingButton.setOutlineProvider(new ViewOutlineProvider() { + @SuppressLint("NewApi") + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } + frameLayout.addView(floatingButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + floatingButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putBoolean("destroyAfterSelect", true); + args.putBoolean("returnAsResult", true); + args.putBoolean("onlyUsers", true); + ContactsActivity contactsFragment = new ContactsActivity(args); + contactsFragment.setDelegate(new ContactsActivity.ContactsActivityDelegate() { + @Override + public void didSelectContact(TLRPC.User user, String param) { + VoIPHelper.startCall(user, getParentActivity(), null); + } + }); + presentFragment(contactsFragment); + } + }); + + return fragmentView; + } + + private void hideFloatingButton(boolean hide) { + if (floatingHidden == hide) { + return; + } + floatingHidden = hide; + ObjectAnimator animator = ObjectAnimator.ofFloat(floatingButton, "translationY", floatingHidden ? AndroidUtilities.dp(100) : 0).setDuration(300); + animator.setInterpolator(floatingInterpolator); + floatingButton.setClickable(!hide); + animator.start(); + } + + private void getCalls(int max_id, final int count) { + if (loading) { + return; + } + loading = true; + if (emptyView != null && !firstLoaded) { + emptyView.showProgress(); + } + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + TLRPC.TL_messages_search req = new TLRPC.TL_messages_search(); + req.limit = count; + req.peer = new TLRPC.TL_inputPeerEmpty(); + req.filter = new TLRPC.TL_inputMessagesFilterPhoneCalls(); + req.q = ""; + req.max_id = max_id; + int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + SparseArray users = new SparseArray<>(); + TLRPC.messages_Messages msgs = (TLRPC.messages_Messages) response; + endReached = msgs.messages.isEmpty(); + for (int a = 0; a < msgs.users.size(); a++) { + TLRPC.User user = msgs.users.get(a); + users.put(user.id, user); + } + CallLogRow currentRow = calls.size() > 0 ? calls.get(calls.size() - 1) : null; + for (int a = 0; a < msgs.messages.size(); a++) { + TLRPC.Message msg = msgs.messages.get(a); + if (msg.action == null) { + continue; + } + int callType = msg.from_id == UserConfig.getClientUserId() ? TYPE_OUT : TYPE_IN; + TLRPC.PhoneCallDiscardReason reason = msg.action.reason; + if (callType == TYPE_IN && (reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed || reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy)) { + callType = TYPE_MISSED; + } + int userID = msg.from_id == UserConfig.getClientUserId() ? msg.to_id.user_id : msg.from_id; + if (currentRow == null || currentRow.user.id != userID || currentRow.type != callType) { + if (currentRow != null && !calls.contains(currentRow)) { + calls.add(currentRow); + } + CallLogRow row = new CallLogRow(); + row.calls = new ArrayList<>(); + row.user = users.get(userID); + row.type = callType; + currentRow = row; + } + currentRow.calls.add(msg); + } + if (currentRow != null && currentRow.calls.size() > 0 && !calls.contains(currentRow)) { + calls.add(currentRow); + } + } else { + endReached = true; + } + loading = false; + firstLoaded = true; + if (emptyView != null) { + emptyView.showTextView(); + } + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); + } + + private void confirmAndDelete(final CallLogRow row) { + if(getParentActivity()==null) + return; + new AlertDialog.Builder(getParentActivity()) + .setTitle(LocaleController.getString("AppName", R.string.AppName)) + .setMessage(LocaleController.getString("ConfirmDeleteCallLog", R.string.ConfirmDeleteCallLog)) + .setPositiveButton(LocaleController.getString("Delete", R.string.Delete), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ArrayList ids = new ArrayList<>(); + for (TLRPC.Message msg : row.calls) { + ids.add(msg.id); + } + MessagesController.getInstance().deleteMessages(ids, null, null, 0, false); + } + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null) + .show() + .setCanceledOnTouchOutside(true); + } + + @Override + public void onResume() { + super.onResume(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 101) { + if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){ + VoIPHelper.startCall(lastCallUser, getParentActivity(), null); + }else{ + VoIPHelper.permissionDenied(getParentActivity(), null); + } + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() != calls.size(); + } + + @Override + public int getItemCount() { + int count = calls.size(); + if (!calls.isEmpty()) { + if (!endReached) { + count++; + } + } + return count; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + CustomCell frameLayout = new CustomCell(mContext); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + ProfileSearchCell cell = new ProfileSearchCell(mContext); + cell.setPaddingRight(AndroidUtilities.dp(32)); + frameLayout.addView(cell); + + ImageView imageView = new ImageView(mContext); + imageView.setImageResource(R.drawable.profile_phone); + imageView.setAlpha(214); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); + imageView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setOnClickListener(callBtnClickListener); + frameLayout.addView(imageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 8, 0, 8, 0)); + + view = frameLayout; + view.setTag(new ViewItem(imageView, cell)); + break; + case 1: + view = new LoadingCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + default: + view = new TextInfoPrivacyCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + ViewItem viewItem = (ViewItem) holder.itemView.getTag(); + ProfileSearchCell cell = viewItem.cell; + CallLogRow row = calls.get(position); + TLRPC.Message last = row.calls.get(0); + SpannableString subtitle; + String ldir = LocaleController.isRTL ? "\u202b" : ""; + if (row.calls.size() == 1) { + subtitle = new SpannableString(ldir + " " + LocaleController.formatDateCallLog(last.date)); + } else { + subtitle = new SpannableString(String.format(ldir + " (%d) %s", row.calls.size(), LocaleController.formatDateCallLog(last.date))); + } + switch (row.type) { + case TYPE_OUT: + subtitle.setSpan(iconOut, ldir.length(), ldir.length() + 1, 0); + break; + case TYPE_IN: + subtitle.setSpan(iconIn, ldir.length(), ldir.length() + 1, 0); + break; + case TYPE_MISSED: + subtitle.setSpan(iconMissed, ldir.length(), ldir.length() + 1, 0); + break; + } + cell.setData(row.user, null, null, subtitle, false); + cell.useSeparator = position != calls.size() - 1 || !endReached; + viewItem.button.setTag(row); + } + } + + @Override + public int getItemViewType(int i) { + if (i < calls.size()) { + return 0; + } else if (!endReached && i == calls.size()) { + return 1; + } + return 2; + } + } + + private class ViewItem { + public ProfileSearchCell cell; + public ImageView button; + + public ViewItem(ImageView button, ProfileSearchCell cell) { + this.button = button; + this.cell = cell; + } + } + + private class CallLogRow { + public TLRPC.User user; + public List calls; + public int type; + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof ProfileSearchCell) { + ((ProfileSearchCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{LocationCell.class, CustomCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(floatingButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), + new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), + new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), + + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedCheckDrawable}, null, Theme.key_chats_verifiedCheck), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedDrawable}, null, Theme.key_chats_verifiedBackground), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, 0, new Class[]{View.class}, null, new Drawable[]{greenDrawable, greenDrawable2, Theme.chat_msgCallUpRedDrawable, Theme.chat_msgCallDownRedDrawable}, null, Theme.key_calls_callReceivedGreenIcon), + new ThemeDescription(listView, 0, new Class[]{View.class}, null, new Drawable[]{redDrawable, Theme.chat_msgCallUpGreenDrawable, Theme.chat_msgCallDownGreenDrawable}, null, Theme.key_calls_callReceivedRedIcon), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java index cc5ed7f77c6..aabec948505 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CancelAccountDeletionActivity.java @@ -3,17 +3,17 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.app.AlertDialog; +import android.annotation.SuppressLint; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; @@ -25,6 +25,7 @@ import android.text.Editable; import android.text.InputFilter; import android.text.InputType; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.TypedValue; import android.view.Gravity; @@ -35,13 +36,11 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; @@ -53,8 +52,13 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.SlideView; import java.util.ArrayList; @@ -66,7 +70,7 @@ public class CancelAccountDeletionActivity extends BaseFragment { private int currentViewNum = 0; private SlideView[] views = new SlideView[5]; - private ProgressDialog progressDialog; + private AlertDialog progressDialog; private Dialog permissionsDialog; private ArrayList permissionsItems = new ArrayList<>(); private boolean checkPermissions = false; //true; @@ -77,6 +81,31 @@ public class CancelAccountDeletionActivity extends BaseFragment { private final static int done_button = 1; + private class ProgressView extends View { + + private Paint paint = new Paint(); + private Paint paint2 = new Paint(); + private float progress; + + public ProgressView(Context context) { + super(context); + paint.setColor(Theme.getColor(Theme.key_login_progressInner)); + paint2.setColor(Theme.getColor(Theme.key_login_progressOuter)); + } + + public void setProgress(float value) { + progress = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int start = (int) (getMeasuredWidth() * progress); + canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); + canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); + } + } + public CancelAccountDeletionActivity(Bundle args) { super(args); hash = args.getString("hash"); @@ -95,7 +124,7 @@ public void onFragmentDestroy() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -187,24 +216,11 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { } } - public Dialog needShowAlert(final String text) { - if (text == null || getParentActivity() == null) { - return null; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(text); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - Dialog dialog = builder.create(); - showDialog(dialog); - return dialog; - } - public void needShowProgress() { if (getParentActivity() == null || getParentActivity().isFinishing() || progressDialog != null) { return; } - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -218,7 +234,7 @@ public void needHideProgress() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -236,7 +252,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { final SlideView newView = views[page]; currentViewNum = page; - newView.setParams(params); + newView.setParams(params, false); actionBar.setTitle(newView.getHeaderName()); newView.onShow(); newView.setX(back ? -AndroidUtilities.displaySize.x : AndroidUtilities.displaySize.x); @@ -247,7 +263,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { animatorSet.playTogether( ObjectAnimator.ofFloat(outView, "translationX", back ? AndroidUtilities.displaySize.x : -AndroidUtilities.displaySize.x), ObjectAnimator.ofFloat(newView, "translationX", 0)); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { newView.setVisibility(View.VISIBLE); @@ -299,6 +315,7 @@ private void fillNextCodeParams(Bundle params, TLRPC.TL_auth_sentCode res) { public class PhoneView extends SlideView { private boolean nextPressed = false; + private RadialProgressView progressBar; public PhoneView(Context context) { super(context); @@ -308,7 +325,7 @@ public PhoneView(Context context) { FrameLayout frameLayout = new FrameLayout(context); addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 200)); - ProgressBar progressBar = new ProgressBar(context); + progressBar = new RadialProgressView(context); frameLayout.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } @@ -354,16 +371,23 @@ public void onNextPressed() { }*/ } - TLRPC.TL_account_sendConfirmPhoneCode req = new TLRPC.TL_account_sendConfirmPhoneCode(); + final TLRPC.TL_account_sendConfirmPhoneCode req = new TLRPC.TL_account_sendConfirmPhoneCode(); req.allow_flashcall = false;//simcardAvailable && allowCall; req.hash = hash; if (req.allow_flashcall) { try { - String number = tm.getLine1Number(); - req.current_number = number != null && number.length() != 0 && (phone.contains(number) || number.contains(phone)); + @SuppressLint("HardwareIds") String number = tm.getLine1Number(); + if (!TextUtils.isEmpty(number)) { + req.current_number = phone.contains(number) || number.contains(phone); + if (!req.current_number) { + req.allow_flashcall = false; + } + } else { + req.current_number = false; + } } catch (Exception e) { req.allow_flashcall = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -380,15 +404,7 @@ public void run() { if (error == null) { fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { - if (error.code == 400) { - errorDialog = needShowAlert(LocaleController.getString("CancelLinkExpired", R.string.CancelLinkExpired)); - } else if (error.text != null) { - if (error.text.startsWith("FLOOD_WAIT")) { - errorDialog = needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - errorDialog = needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); - } - } + errorDialog = AlertsCreator.processError(error, CancelAccountDeletionActivity.this, req); } } }); @@ -410,31 +426,6 @@ public void onShow() { public class LoginActivitySmsView extends SlideView implements NotificationCenter.NotificationCenterDelegate { - private class ProgressView extends View { - - private Paint paint = new Paint(); - private Paint paint2 = new Paint(); - private float progress; - - public ProgressView(Context context) { - super(context); - paint.setColor(0xffe1eaf2); - paint2.setColor(0xff62a0d0); - } - - public void setProgress(float value) { - progress = value; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - int start = (int) (getMeasuredWidth() * progress); - canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); - canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); - } - } - private String phone; private String phoneHash; private EditText codeField; @@ -469,7 +460,7 @@ public LoginActivitySmsView(Context context, final int type) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -492,15 +483,16 @@ public LoginActivitySmsView(Context context, final int type) { } codeField = new EditText(context); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); AndroidUtilities.clearCursorDrawable(codeField); - codeField.setHintTextColor(0xff979797); + codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setInputType(InputType.TYPE_CLASS_PHONE); codeField.setMaxLines(1); codeField.setPadding(0, 0, 0, 0); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); addView(codeField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.CENTER_HORIZONTAL, 0, 20, 0, 0)); codeField.addTextChangedListener(new TextWatcher() { @Override @@ -541,7 +533,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { timeText = new TextView(context); timeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - timeText.setTextColor(0xff757575); + timeText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); timeText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); timeText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(timeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 30, 0, 0)); @@ -555,7 +547,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { problemText.setText(LocaleController.getString("DidNotGetTheCode", R.string.DidNotGetTheCode)); problemText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); problemText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - problemText.setTextColor(0xff4d83b3); + problemText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); problemText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); problemText.setPadding(0, AndroidUtilities.dp(2), 0, AndroidUtilities.dp(12)); addView(problemText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 20, 0, 0)); @@ -579,7 +571,7 @@ public void onClick(View v) { mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + phone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError); getContext().startActivity(Intent.createChooser(mailer, "Send email...")); } catch (Exception e) { - needShowAlert(LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); + AlertsCreator.showSimpleAlert(CancelAccountDeletionActivity.this, LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); } } } @@ -593,7 +585,7 @@ private void resendCode() { nextPressed = true; needShowProgress(); - TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + final TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); req.phone_number = phone; req.phone_code_hash = phoneHash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -606,15 +598,7 @@ public void run() { if (error == null) { fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { - if (error.text != null) { - if (error.text.contains("PHONE_CODE_EXPIRED")) { - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else if (error.code != -1000) { - needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); - } - } + AlertsCreator.processError(error, CancelAccountDeletionActivity.this, req); } needHideProgress(); } @@ -629,7 +613,7 @@ public String getHeaderName() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -751,7 +735,7 @@ private void destroyCodeTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -839,7 +823,7 @@ private void destroyTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -871,7 +855,7 @@ public void run() { needHideProgress(); nextPressed = false; if (error == null) { - errorDialog = needShowAlert(LocaleController.formatString("CancelLinkSuccess", R.string.CancelLinkSuccess, PhoneFormat.getInstance().format("+" + phone))); + errorDialog = AlertsCreator.showSimpleAlert(CancelAccountDeletionActivity.this, LocaleController.formatString("CancelLinkSuccess", R.string.CancelLinkSuccess, PhoneFormat.getInstance().format("+" + phone))); } else { lastError = error.text; if (currentType == 3 && (nextType == 4 || nextType == 2) || currentType == 2 && (nextType == 4 || nextType == 3)) { @@ -886,15 +870,7 @@ public void run() { } waitingForEvent = true; if (currentType != 3) { - if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - needShowAlert(error.text); - } + AlertsCreator.processError(error, CancelAccountDeletionActivity.this, req); } } } @@ -939,11 +915,8 @@ public void didReceivedNotification(int id, final Object... args) { onNextPressed(); } else if (id == NotificationCenter.didReceiveCall) { String num = "" + args[0]; - if (!pattern.equals("*")) { - String patternNumbers = pattern.replace("*", ""); - if (!num.contains(patternNumbers)) { - return; - } + if (!AndroidUtilities.checkPhonePattern(pattern, num)) { + return; } ignoreOnTextChange = true; codeField.setText(num); @@ -952,4 +925,65 @@ public void didReceivedNotification(int id, final Object... args) { } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + PhoneView phoneView = (PhoneView) views[0]; + LoginActivitySmsView smsView1 = (LoginActivitySmsView) views[1]; + LoginActivitySmsView smsView2 = (LoginActivitySmsView) views[2]; + LoginActivitySmsView smsView3 = (LoginActivitySmsView) views[3]; + LoginActivitySmsView smsView4 = (LoginActivitySmsView) views[4]; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(phoneView.progressBar, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(smsView1.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView1.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView2.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView2.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView3.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView3.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView4.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView4.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java index f4aa9af9b4a..d182083f9b1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AboutLinkCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -11,13 +11,12 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; -import android.os.Build; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.StaticLayout; -import android.text.TextPaint; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.Gravity; @@ -39,8 +38,6 @@ public class AboutLinkCell extends FrameLayout { private StaticLayout textLayout; - private TextPaint textPaint; - private Paint urlPaint; private String oldText; private int textX; private int textY; @@ -60,20 +57,17 @@ public interface AboutLinkCellDelegate { public AboutLinkCell(Context context) { super(context); - textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaint.setTextSize(AndroidUtilities.dp(16)); - textPaint.setColor(0xff000000); - textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - - urlPaint = new Paint(); - urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); - imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 16, 5, LocaleController.isRTL ? 16 : 0, 0)); setWillNotDraw(false); } + public ImageView getImageView() { + return imageView; + } + public void setDelegate(AboutLinkCellDelegate botHelpCellDelegate) { delegate = botHelpCellDelegate; } @@ -95,8 +89,8 @@ public void setTextAndIcon(String text, int resId) { } oldText = text; stringBuilder = new SpannableStringBuilder(oldText); - MessageObject.addLinks(stringBuilder, false); - Emoji.replaceEmoji(stringBuilder, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + MessageObject.addLinks(false, stringBuilder, false); + Emoji.replaceEmoji(stringBuilder, Theme.profile_aboutTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); requestLayout(); if (resId == 0) { imageView.setImageDrawable(null); @@ -110,12 +104,6 @@ public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(x, y); - } - } - boolean result = false; if (textLayout != null) { if (event.getAction() == MotionEvent.ACTION_DOWN || pressedLink != null && event.getAction() == MotionEvent.ACTION_UP) { @@ -140,7 +128,7 @@ public boolean onTouchEvent(MotionEvent event) { urlPath.setCurrentLayout(textLayout, start, 0); textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { resetPressedLink(); @@ -150,7 +138,7 @@ public boolean onTouchEvent(MotionEvent event) { } } catch (Exception e) { resetPressedLink(); - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (pressedLink != null) { try { @@ -169,7 +157,7 @@ public boolean onTouchEvent(MotionEvent event) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } resetPressedLink(); result = true; @@ -184,7 +172,7 @@ public boolean onTouchEvent(MotionEvent event) { @SuppressLint("DrawAllocation") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - textLayout = new StaticLayout(stringBuilder, textPaint, MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(71 + 16), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + textLayout = new StaticLayout(stringBuilder, Theme.profile_aboutTextPaint, MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(71 + 16), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(textLayout.getHeight() + AndroidUtilities.dp(16), MeasureSpec.EXACTLY)); } @@ -193,14 +181,14 @@ protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(textX = AndroidUtilities.dp(LocaleController.isRTL ? 16 : 71), textY = AndroidUtilities.dp(8)); if (pressedLink != null) { - canvas.drawPath(urlPath, urlPaint); + canvas.drawPath(urlPath, Theme.linkSelectionPaint); } try { if (textLayout != null) { textLayout.draw(canvas); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } canvas.restore(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java index 5597743d277..6c2c4019c34 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AdminedChannelCell.java @@ -3,15 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.text.SpannableStringBuilder; import android.text.Spanned; -import android.text.style.ForegroundColorSpan; import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; @@ -19,12 +20,15 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.URLSpanNoUnderline; public class AdminedChannelCell extends FrameLayout { @@ -32,14 +36,13 @@ public class AdminedChannelCell extends FrameLayout { private SimpleTextView nameTextView; private SimpleTextView statusTextView; private AvatarDrawable avatarDrawable; + private ImageView deleteButton; private TLRPC.Chat currentChannel; private boolean isLast; public AdminedChannelCell(Context context, View.OnClickListener onClickListener) { super(context); - setBackgroundResource(R.drawable.list_selector_white); - avatarDrawable = new AvatarDrawable(); avatarImageView = new BackupImageView(context); @@ -47,21 +50,23 @@ public AdminedChannelCell(Context context, View.OnClickListener onClickListener) addView(avatarImageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 12, LocaleController.isRTL ? 12 : 0, 0)); nameTextView = new SimpleTextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(17); nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 62 : 73, 15.5f, LocaleController.isRTL ? 73 : 62, 0)); statusTextView = new SimpleTextView(context); statusTextView.setTextSize(14); - statusTextView.setTextColor(0xffa8a8a8); + statusTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + statusTextView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); statusTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); addView(statusTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 62 : 73, 38.5f, LocaleController.isRTL ? 73 : 62, 0)); - ImageView deleteButton = new ImageView(context); + deleteButton = new ImageView(context); deleteButton.setScaleType(ImageView.ScaleType.CENTER); - deleteButton.setImageResource(R.drawable.delete_reply); + deleteButton.setImageResource(R.drawable.msg_panel_clear); deleteButton.setOnClickListener(onClickListener); + deleteButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText), PorterDuff.Mode.MULTIPLY)); addView(deleteButton, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 7 : 0, 12, LocaleController.isRTL ? 0 : 7, 0)); } @@ -70,17 +75,22 @@ public void setChannel(TLRPC.Chat channel, boolean last) { if (channel.photo != null) { photo = channel.photo.photo_small; } - final String url = "telegram.me/"; + final String url = MessagesController.getInstance().linkPrefix + "/"; currentChannel = channel; avatarDrawable.setInfo(channel); nameTextView.setText(channel.title); SpannableStringBuilder stringBuilder = new SpannableStringBuilder(url + channel.username); - stringBuilder.setSpan(new ForegroundColorSpan(0xff3b84c0), url.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new URLSpanNoUnderline(""), url.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); statusTextView.setText(stringBuilder); avatarImageView.setImage(photo, "50_50", avatarDrawable); isLast = last; } + public void update() { + avatarDrawable.setInfo(currentChannel); + avatarImageView.invalidate(); + } + public TLRPC.Chat getCurrentChannel() { return currentChannel; } @@ -94,4 +104,16 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { public boolean hasOverlappingRendering() { return false; } + + public SimpleTextView getNameTextView() { + return nameTextView; + } + + public SimpleTextView getStatusTextView() { + return statusTextView; + } + + public ImageView getDeleteButton() { + return deleteButton; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java index 317c94b05bd..6eb7f551f2d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ArchivedStickerSetCell.java @@ -3,18 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -27,6 +23,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.Switch; @@ -42,18 +39,11 @@ public class ArchivedStickerSetCell extends FrameLayout { private Rect rect = new Rect(); private CompoundButton.OnCheckedChangeListener onCheckedChangeListener; - private static Paint paint; - public ArchivedStickerSetCell(Context context, boolean needCheckBox) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -63,7 +53,7 @@ public ArchivedStickerSetCell(Context context, boolean needCheckBox) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 40 : 71, 10, LocaleController.isRTL ? 71 : 40, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -84,57 +74,29 @@ public ArchivedStickerSetCell(Context context, boolean needCheckBox) { } } + public TextView getTextView() { + return textView; + } + + public TextView getValueTextView() { + return valueTextView; + } + + public Switch getCheckBox() { + return checkBox; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } - public void setStickersSet(TLRPC.StickerSetCovered set, boolean divider, boolean unread) { + public void setStickersSet(TLRPC.StickerSetCovered set, boolean divider) { needDivider = divider; stickersSet = set; setWillNotDraw(!needDivider); textView.setText(stickersSet.set.title); - if (unread) { - Drawable drawable = new Drawable() { - - Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - - @Override - public void draw(Canvas canvas) { - paint.setColor(0xff44a8ea); - canvas.drawCircle(AndroidUtilities.dp(4), AndroidUtilities.dp(5), AndroidUtilities.dp(3), paint); - } - - @Override - public void setAlpha(int alpha) { - - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - - } - - @Override - public int getOpacity() { - return 0; - } - - @Override - public int getIntrinsicWidth() { - return AndroidUtilities.dp(12); - } - - @Override - public int getIntrinsicHeight() { - return AndroidUtilities.dp(8); - } - }; - textView.setCompoundDrawablesWithIntrinsicBounds(LocaleController.isRTL ? null : drawable, null, LocaleController.isRTL ? drawable : null, null); - } else { - textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); - } valueTextView.setText(LocaleController.formatPluralString("Stickers", set.set.count)); if (set.cover != null && set.cover.thumb != null && set.cover.thumb.location != null) { @@ -177,18 +139,13 @@ public boolean onTouchEvent(MotionEvent event) { return checkBox.onTouchEvent(event); } } - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } return super.onTouchEvent(event); } @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioCell.java index 5b44689da6e..5c2fd9e6f3c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/AudioCell.java @@ -3,14 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -24,7 +26,9 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.CheckBox; +import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -40,7 +44,6 @@ public class AudioCell extends FrameLayout { private MediaController.AudioEntry audioEntry; private boolean needDivider; - private static Paint paint; private AudioCellDelegate delegate; @@ -51,14 +54,7 @@ public interface AudioCellDelegate { public AudioCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - playButton = new ImageView(context); - playButton.setScaleType(ImageView.ScaleType.CENTER); addView(playButton, LayoutHelper.createFrame(46, 46, ((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP), LocaleController.isRTL ? 0 : 13, 13, LocaleController.isRTL ? 13 : 0, 0)); playButton.setOnClickListener(new OnClickListener() { @Override @@ -66,12 +62,12 @@ public void onClick(View v) { if (audioEntry != null) { if (MediaController.getInstance().isPlayingAudio(audioEntry.messageObject) && !MediaController.getInstance().isAudioPaused()) { MediaController.getInstance().pauseAudio(audioEntry.messageObject); - playButton.setImageResource(R.drawable.audiosend_play); + setPlayDrawable(false); } else { ArrayList arrayList = new ArrayList<>(); arrayList.add(audioEntry.messageObject); if (MediaController.getInstance().setPlaylist(arrayList, audioEntry.messageObject)) { - playButton.setImageResource(R.drawable.audiosend_pause); + setPlayDrawable(true); if (delegate != null) { delegate.startedPlayingAudio(audioEntry.messageObject); } @@ -82,7 +78,7 @@ public void onClick(View v) { }); titleTextView = new TextView(context); - titleTextView.setTextColor(0xff212121); + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); titleTextView.setLines(1); @@ -93,7 +89,7 @@ public void onClick(View v) { addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 7, LocaleController.isRTL ? 72 : 50, 0)); genreTextView = new TextView(context); - genreTextView.setTextColor(0xff8a8a8a); + genreTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); genreTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); genreTextView.setLines(1); genreTextView.setMaxLines(1); @@ -103,7 +99,7 @@ public void onClick(View v) { addView(genreTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 28, LocaleController.isRTL ? 72 : 50, 0)); authorTextView = new TextView(context); - authorTextView.setTextColor(0xff8a8a8a); + authorTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); authorTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); authorTextView.setLines(1); authorTextView.setMaxLines(1); @@ -113,7 +109,7 @@ public void onClick(View v) { addView(authorTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 50 : 72, 44, LocaleController.isRTL ? 72 : 50, 0)); timeTextView = new TextView(context); - timeTextView.setTextColor(0xff999999); + timeTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); timeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); timeTextView.setLines(1); timeTextView.setMaxLines(1); @@ -124,13 +120,46 @@ public void onClick(View v) { checkBox = new CheckBox(context, R.drawable.round_check2); checkBox.setVisibility(VISIBLE); - checkBox.setColor(0xff29b6f7); + checkBox.setColor(Theme.getColor(Theme.key_musicPicker_checkbox), Theme.getColor(Theme.key_musicPicker_checkboxCheck)); addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 18 : 0, 39, LocaleController.isRTL ? 0 : 18, 0)); } + private void setPlayDrawable(boolean play) { + Drawable circle = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(46), Theme.getColor(Theme.key_musicPicker_buttonBackground), Theme.getColor(Theme.key_musicPicker_buttonBackground)); + Drawable drawable = getResources().getDrawable(play ? R.drawable.audiosend_pause : R.drawable.audiosend_play); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_musicPicker_buttonIcon), PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); + combinedDrawable.setCustomSize(AndroidUtilities.dp(46), AndroidUtilities.dp(46)); + playButton.setBackgroundDrawable(combinedDrawable); + } + + public ImageView getPlayButton() { + return playButton; + } + + public TextView getTitleTextView() { + return titleTextView; + } + + public TextView getGenreTextView() { + return genreTextView; + } + + public TextView getTimeTextView() { + return timeTextView; + } + + public TextView getAuthorTextView() { + return authorTextView; + } + + public CheckBox getCheckBox() { + return checkBox; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } public void setAudio(MediaController.AudioEntry entry, boolean divider, boolean checked) { @@ -140,7 +169,7 @@ public void setAudio(MediaController.AudioEntry entry, boolean divider, boolean genreTextView.setText(audioEntry.genre); authorTextView.setText(audioEntry.author); timeTextView.setText(String.format("%d:%02d", audioEntry.duration / 60, audioEntry.duration % 60)); - playButton.setImageResource(MediaController.getInstance().isPlayingAudio(audioEntry.messageObject) && !MediaController.getInstance().isAudioPaused() ? R.drawable.audiosend_pause : R.drawable.audiosend_play); + setPlayDrawable(MediaController.getInstance().isPlayingAudio(audioEntry.messageObject) && !MediaController.getInstance().isAudioPaused()); needDivider = divider; setWillNotDraw(!divider); @@ -163,7 +192,7 @@ public MediaController.AudioEntry getAudioEntry() { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth(), getHeight() - 1, paint); + canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java index 71d87bdbeaa..2066ac4d53c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BaseCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java index e2f0292fc12..d0a90cd88e3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotHelpCell.java @@ -3,20 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.Layout; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.StaticLayout; -import android.text.TextPaint; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.MotionEvent; @@ -37,8 +35,6 @@ public class BotHelpCell extends View { private StaticLayout textLayout; - private TextPaint textPaint; - private Paint urlPaint; private String oldText; private int width; @@ -57,14 +53,6 @@ public interface BotHelpCellDelegate { public BotHelpCell(Context context) { super(context); - - textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaint.setTextSize(AndroidUtilities.dp(16)); - textPaint.setColor(0xff000000); - textPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - - urlPaint = new Paint(); - urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); } public void setDelegate(BotHelpCellDelegate botHelpCellDelegate) { @@ -88,27 +76,37 @@ public void setText(String text) { } oldText = text; setVisibility(VISIBLE); + int maxWidth; if (AndroidUtilities.isTablet()) { - width = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); + maxWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); } else { - width = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); + maxWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); } + String lines[] = text.split("\n"); SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); String help = LocaleController.getString("BotInfoTitle", R.string.BotInfoTitle); stringBuilder.append(help); stringBuilder.append("\n\n"); - stringBuilder.append(text); - MessageObject.addLinks(stringBuilder); + for (int a = 0; a < lines.length; a++) { + stringBuilder.append(lines[a].trim()); + if (a != lines.length - 1) { + stringBuilder.append("\n"); + } + } + MessageObject.addLinks(false, stringBuilder); stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), 0, help.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - Emoji.replaceEmoji(stringBuilder, textPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + Emoji.replaceEmoji(stringBuilder, Theme.chat_msgTextPaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); try { - textLayout = new StaticLayout(stringBuilder, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + textLayout = new StaticLayout(stringBuilder, Theme.chat_msgTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); width = 0; height = textLayout.getHeight() + AndroidUtilities.dp(4 + 18); int count = textLayout.getLineCount(); for (int a = 0; a < count; a++) { width = (int) Math.ceil(Math.max(width, textLayout.getLineWidth(a) + textLayout.getLineLeft(a))); } + if (width > maxWidth) { + width = maxWidth; + } } catch (Exception e) { FileLog.e("tmessage", e); } @@ -144,7 +142,7 @@ public boolean onTouchEvent(MotionEvent event) { urlPath.setCurrentLayout(textLayout, start, 0); textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), urlPath); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { resetPressedLink(); @@ -154,7 +152,7 @@ public boolean onTouchEvent(MotionEvent event) { } } catch (Exception e) { resetPressedLink(); - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (pressedLink != null) { try { @@ -173,7 +171,7 @@ public boolean onTouchEvent(MotionEvent event) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } resetPressedLink(); result = true; @@ -194,12 +192,16 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onDraw(Canvas canvas) { int x = (canvas.getWidth() - width) / 2; int y = AndroidUtilities.dp(4); - Theme.backgroundMediaDrawableIn.setBounds(x, y, width + x, height + y); - Theme.backgroundMediaDrawableIn.draw(canvas); + Theme.chat_msgInMediaShadowDrawable.setBounds(x, y, width + x, height + y); + Theme.chat_msgInMediaShadowDrawable.draw(canvas); + Theme.chat_msgInMediaDrawable.setBounds(x, y, width + x, height + y); + Theme.chat_msgInMediaDrawable.draw(canvas); + Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_msgTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkIn); canvas.save(); canvas.translate(textX = AndroidUtilities.dp(2 + 9) + x, textY = AndroidUtilities.dp(2 + 9) + y); if (pressedLink != null) { - canvas.drawPath(urlPath, urlPaint); + canvas.drawPath(urlPath, Theme.chat_urlPaint); } if (textLayout != null) { textLayout.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotSwitchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotSwitchCell.java index dbe52d88820..7954b317097 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotSwitchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/BotSwitchCell.java @@ -3,23 +3,21 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class BotSwitchCell extends FrameLayout { @@ -28,11 +26,10 @@ public class BotSwitchCell extends FrameLayout { public BotSwitchCell(Context context) { super(context); - setBackgroundResource(R.drawable.list_selector); textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - textView.setTextColor(0xff4391cc); + textView.setTextColor(Theme.getColor(Theme.key_chat_botSwitchToInlineText)); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setSingleLine(true); textView.setEllipsize(TextUtils.TruncateAt.END); @@ -41,16 +38,6 @@ public BotSwitchCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 14, 0, 14, 0)); } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(36), MeasureSpec.EXACTLY)); @@ -59,4 +46,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { public void setText(String text) { textView.setText(text); } + + public TextView getTextView() { + return textView; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java index 1ffa035a9eb..0851136a3fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatActionCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -11,20 +11,18 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.Layout; import android.text.Spannable; import android.text.StaticLayout; -import android.text.TextPaint; +import android.text.TextUtils; import android.text.style.URLSpan; import android.view.MotionEvent; import android.view.SoundEffectConstants; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; -import org.telegram.messenger.MessagesController; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; import org.telegram.tgnet.TLRPC; @@ -43,9 +41,6 @@ public interface ChatActionCellDelegate { void didPressedReplyMessage(ChatActionCell cell, int id); } - private static TextPaint textPaint; - private static Paint backPaint; - private URLSpan pressedLink; private ImageReceiver imageReceiver; @@ -62,31 +57,45 @@ public interface ChatActionCellDelegate { private boolean hasReplyMessage; private MessageObject currentMessageObject; + private int customDate; + private CharSequence customText; private ChatActionCellDelegate delegate; public ChatActionCell(Context context) { super(context); - if (textPaint == null) { - textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - textPaint.setColor(0xffffffff); - textPaint.linkColor = 0xffffffff; - textPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - backPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - } - backPaint.setColor(ApplicationLoader.getServiceMessageColor()); - imageReceiver = new ImageReceiver(this); imageReceiver.setRoundRadius(AndroidUtilities.dp(32)); avatarDrawable = new AvatarDrawable(); - textPaint.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize - 2)); } public void setDelegate(ChatActionCellDelegate delegate) { this.delegate = delegate; } + public void setCustomDate(int date) { + if (customDate == date) { + return; + } + CharSequence newText = LocaleController.formatDateChat(date); + if (customText != null && TextUtils.equals(newText, customText)) { + return; + } + previousWidth = 0; + customDate = date; + customText = newText; + if (getMeasuredWidth() != 0) { + createLayout(customText, getMeasuredWidth()); + invalidate(); + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + requestLayout(); + } + }); + } + public void setMessageObject(MessageObject messageObject) { if (currentMessageObject == messageObject && (hasReplyMessage || messageObject.replyMessageObject == null)) { return; @@ -121,7 +130,7 @@ public void setMessageObject(MessageObject messageObject) { } imageReceiver.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); } else { - imageReceiver.setImageBitmap((Bitmap)null); + imageReceiver.setImageBitmap((Bitmap) null); } requestLayout(); } @@ -143,6 +152,9 @@ protected void onLongPress() { @Override public boolean onTouchEvent(MotionEvent event) { + if (currentMessageObject == null) { + return super.onTouchEvent(event); + } float x = event.getX(); float y = event.getY(); @@ -246,48 +258,60 @@ public boolean onTouchEvent(MotionEvent event) { return result; } + private void createLayout(CharSequence text, int width) { + int maxWidth = width - AndroidUtilities.dp(30); + textLayout = new StaticLayout(text, Theme.chat_actionTextPaint, maxWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + textHeight = 0; + textWidth = 0; + try { + int linesCount = textLayout.getLineCount(); + for (int a = 0; a < linesCount; a++) { + float lineWidth; + try { + lineWidth = textLayout.getLineWidth(a); + if (lineWidth > maxWidth) { + lineWidth = maxWidth; + } + textHeight = (int)Math.max(textHeight, Math.ceil(textLayout.getLineBottom(a))); + } catch (Exception e) { + FileLog.e(e); + return; + } + textWidth = (int)Math.max(textWidth, Math.ceil(lineWidth)); + } + } catch (Exception e) { + FileLog.e(e); + } + textX = (width - textWidth) / 2; + textY = AndroidUtilities.dp(7); + textXLeft = (width - textLayout.getWidth()) / 2; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (currentMessageObject == null) { + if (currentMessageObject == null && customText == null) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), textHeight + AndroidUtilities.dp(14)); return; } int width = Math.max(AndroidUtilities.dp(30), MeasureSpec.getSize(widthMeasureSpec)); if (width != previousWidth) { - previousWidth = width; - int maxWidth = width - AndroidUtilities.dp(30); - textLayout = new StaticLayout(currentMessageObject.messageText, textPaint, maxWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); - textHeight = 0; - textWidth = 0; - try { - int linesCount = textLayout.getLineCount(); - for (int a = 0; a < linesCount; a++) { - float lineWidth; - try { - lineWidth = textLayout.getLineWidth(a); - if (lineWidth > maxWidth) { - lineWidth = maxWidth; - } - textHeight = (int)Math.max(textHeight, Math.ceil(textLayout.getLineBottom(a))); - } catch (Exception e) { - FileLog.e("tmessages", e); - return; - } - textWidth = (int)Math.max(textWidth, Math.ceil(lineWidth)); - } - } catch (Exception e) { - FileLog.e("tmessages", e); + CharSequence text; + if (currentMessageObject != null) { + text = currentMessageObject.messageText; + } else { + text = customText; } - - textX = (width - textWidth) / 2; - textY = AndroidUtilities.dp(7); - textXLeft = (width - textLayout.getWidth()) / 2; - - if (currentMessageObject.type == 11) { + previousWidth = width; + createLayout(text, width); + if (currentMessageObject != null && currentMessageObject.type == 11) { imageReceiver.setImageCoords((width - AndroidUtilities.dp(64)) / 2, textHeight + AndroidUtilities.dp(15), AndroidUtilities.dp(64), AndroidUtilities.dp(64)); } } - setMeasuredDimension(width, textHeight + AndroidUtilities.dp(14 + (currentMessageObject.type == 11 ? 70 : 0))); + setMeasuredDimension(width, textHeight + AndroidUtilities.dp(14 + (currentMessageObject != null && currentMessageObject.type == 11 ? 70 : 0))); + } + + public int getCustomDate() { + return customDate; } private int findMaxWidthAroundLine(int line) { @@ -314,11 +338,7 @@ private int findMaxWidthAroundLine(int line) { @Override protected void onDraw(Canvas canvas) { - if (currentMessageObject == null) { - return; - } - - if (currentMessageObject.type == 11) { + if (currentMessageObject != null && currentMessageObject.type == 11) { imageReceiver.draw(canvas); } @@ -348,7 +368,8 @@ protected void onDraw(Canvas canvas) { if (drawBottomCorners) { height += AndroidUtilities.dp(3); } - canvas.drawRect(x, y, x + width, y + height, backPaint); + + canvas.drawRect(x, y, x + width, y + height, Theme.chat_actionBackgroundPaint); if (!drawBottomCorners && a + 1 < count) { int nextLineWidth = findMaxWidthAroundLine(a + 1) + AndroidUtilities.dp(6); @@ -357,20 +378,20 @@ protected void onDraw(Canvas canvas) { drawBottomCorners = true; additionalHeight = AndroidUtilities.dp(3); - canvas.drawRect(x, y + height, nextX, y + height + AndroidUtilities.dp(3), backPaint); - canvas.drawRect(nextX + nextLineWidth, y + height, x + width, y + height + AndroidUtilities.dp(3), backPaint); + canvas.drawRect(x, y + height, nextX, y + height + AndroidUtilities.dp(3), Theme.chat_actionBackgroundPaint); + canvas.drawRect(nextX + nextLineWidth, y + height, x + width, y + height + AndroidUtilities.dp(3), Theme.chat_actionBackgroundPaint); } else if (width + corner * 2 < nextLineWidth) { additionalHeight = AndroidUtilities.dp(3); dy = y + height - AndroidUtilities.dp(9); dx = x - corner * 2; - Theme.cornerInner[2].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerInner[2].draw(canvas); + Theme.chat_cornerInner[2].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerInner[2].draw(canvas); dx = x + width + corner; - Theme.cornerInner[3].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerInner[3].draw(canvas); + Theme.chat_cornerInner[3].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerInner[3].draw(canvas); } else { additionalHeight = AndroidUtilities.dp(6); } @@ -383,8 +404,8 @@ protected void onDraw(Canvas canvas) { y -= AndroidUtilities.dp(3); height += AndroidUtilities.dp(3); - canvas.drawRect(x, y, prevX, y + AndroidUtilities.dp(3), backPaint); - canvas.drawRect(prevX + prevLineWidth, y, x + width, y + AndroidUtilities.dp(3), backPaint); + canvas.drawRect(x, y, prevX, y + AndroidUtilities.dp(3), Theme.chat_actionBackgroundPaint); + canvas.drawRect(prevX + prevLineWidth, y, x + width, y + AndroidUtilities.dp(3), Theme.chat_actionBackgroundPaint); } else if (width + corner * 2 < prevLineWidth) { y -= AndroidUtilities.dp(3); height += AndroidUtilities.dp(3); @@ -392,41 +413,41 @@ protected void onDraw(Canvas canvas) { dy = y + corner; dx = x - corner * 2; - Theme.cornerInner[0].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerInner[0].draw(canvas); + Theme.chat_cornerInner[0].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerInner[0].draw(canvas); dx = x + width + corner; - Theme.cornerInner[1].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerInner[1].draw(canvas); + Theme.chat_cornerInner[1].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerInner[1].draw(canvas); } else { y -= AndroidUtilities.dp(6); height += AndroidUtilities.dp(6); } } - canvas.drawRect(x - corner, y + corner, x, y + height + additionalHeight - corner, backPaint); - canvas.drawRect(x + width, y + corner, x + width + corner, y + height + additionalHeight - corner, backPaint); + canvas.drawRect(x - corner, y + corner, x, y + height + additionalHeight - corner, Theme.chat_actionBackgroundPaint); + canvas.drawRect(x + width, y + corner, x + width + corner, y + height + additionalHeight - corner, Theme.chat_actionBackgroundPaint); if (drawTopCorners) { dx = x - corner; - Theme.cornerOuter[0].setBounds(dx, y, dx + corner, y + corner); - Theme.cornerOuter[0].draw(canvas); + Theme.chat_cornerOuter[0].setBounds(dx, y, dx + corner, y + corner); + Theme.chat_cornerOuter[0].draw(canvas); dx = x + width; - Theme.cornerOuter[1].setBounds(dx, y, dx + corner, y + corner); - Theme.cornerOuter[1].draw(canvas); + Theme.chat_cornerOuter[1].setBounds(dx, y, dx + corner, y + corner); + Theme.chat_cornerOuter[1].draw(canvas); } if (drawBottomCorners) { dy = y + height + additionalHeight - corner; dx = x + width; - Theme.cornerOuter[2].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerOuter[2].draw(canvas); + Theme.chat_cornerOuter[2].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerOuter[2].draw(canvas); dx = x - corner; - Theme.cornerOuter[3].setBounds(dx, dy, dx + corner, dy + corner); - Theme.cornerOuter[3].draw(canvas); + Theme.chat_cornerOuter[3].setBounds(dx, dy, dx + corner, dy + corner); + Theme.chat_cornerOuter[3].draw(canvas); } y += height; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatLoadingCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatLoadingCell.java index a0f48780e92..422809ae900 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatLoadingCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatLoadingCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -11,16 +11,17 @@ import android.content.Context; import android.view.Gravity; import android.widget.FrameLayout; -import android.widget.ProgressBar; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.RadialProgressView; public class ChatLoadingCell extends FrameLayout { private FrameLayout frameLayout; + private RadialProgressView progressBar; public ChatLoadingCell(Context context) { super(context); @@ -30,14 +31,9 @@ public ChatLoadingCell(Context context) { frameLayout.getBackground().setColorFilter(Theme.colorFilter); addView(frameLayout, LayoutHelper.createFrame(36, 36, Gravity.CENTER)); - ProgressBar progressBar = new ProgressBar(context); - try { - progressBar.setIndeterminateDrawable(getResources().getDrawable(R.drawable.loading_animation)); - } catch (Exception e) { - //don't promt - } - progressBar.setIndeterminate(true); - AndroidUtilities.setProgressBarAnimationDuration(progressBar, 1500); + progressBar = new RadialProgressView(context); + progressBar.setSize(AndroidUtilities.dp(28)); + progressBar.setProgressColor(Theme.getColor(Theme.key_chat_serviceText)); frameLayout.addView(progressBar, LayoutHelper.createFrame(32, 32, Gravity.CENTER)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java index 56a9e8c8a34..56a496317be 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -25,10 +25,12 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; +import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.view.MotionEvent; import android.view.SoundEffectConstants; +import android.view.View; import android.view.ViewStructure; import org.telegram.PhoneFormat.PhoneFormat; @@ -59,6 +61,7 @@ import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; +import org.telegram.ui.Components.URLSpanMono; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.PhotoViewer; @@ -67,8 +70,6 @@ import java.util.HashMap; import java.util.Locale; -import static org.telegram.messenger.AndroidUtilities.*; - public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate, ImageReceiver.ImageReceiverDelegate, MediaController.FileDownloadProgressListener { public interface ChatMessageCellDelegate { @@ -78,12 +79,13 @@ public interface ChatMessageCellDelegate { void didPressedCancelSendButton(ChatMessageCell cell); void didLongPressed(ChatMessageCell cell); void didPressedReplyMessage(ChatMessageCell cell, int id); - void didPressedUrl(MessageObject messageObject, ClickableSpan url, boolean longPress); + void didPressedUrl(MessageObject messageObject, CharacterStyle url, boolean longPress); void needOpenWebView(String url, String title, String description, String originalUrl, int w, int h); void didPressedImage(ChatMessageCell cell); void didPressedShare(ChatMessageCell cell); void didPressedOther(ChatMessageCell cell); void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button); + void didPressedInstantButton(ChatMessageCell cell); boolean needPlayAudio(MessageObject messageObject); boolean canPerformActions(); } @@ -108,6 +110,9 @@ private class BotButton { private long lastUpdateTime; } + private boolean pinnedTop; + private boolean pinnedBottom; + private int textX; private int textY; private int totalHeight; @@ -123,6 +128,7 @@ private class BotButton { private int firstVisibleBlockNum; private int totalVisibleBlocksCount; private boolean needNewVisiblePart; + private boolean fullyDraw; private RadialProgress radialProgress; private ImageReceiver photoImage; @@ -137,6 +143,7 @@ private class BotButton { private boolean drawPhotoImage; private boolean hasLinkPreview; private boolean hasGamePreview; + private boolean hasInvoicePreview; private int linkPreviewHeight; private int mediaOffsetY; private int descriptionY; @@ -149,6 +156,11 @@ private class BotButton { private StaticLayout descriptionLayout; private StaticLayout videoInfoLayout; private StaticLayout authorLayout; + private StaticLayout instantViewLayout; + private boolean drawInstantView; + private int instantTextX; + private int instantWidth; + private boolean instantPressed; private StaticLayout docTitleLayout; private int docTitleOffsetX; @@ -180,19 +192,7 @@ private class BotButton { private String currentPhotoFilterThumb; private boolean cancelLoading; - private static TextPaint infoPaint; - private static TextPaint docNamePaint; - private static Paint docBackPaint; - private static Paint deleteProgressPaint; - private static Paint botProgressPaint; - private static TextPaint locationTitlePaint; - private static TextPaint locationAddressPaint; - private static Paint urlPaint; - private static Paint urlSelectionPaint; - private static TextPaint durationPaint; - private static TextPaint gamePaint; - - private ClickableSpan pressedLink; + private CharacterStyle pressedLink; private int pressedLinkType; private boolean linkPreviewPressed; private boolean gamePreviewPressed; @@ -211,13 +211,6 @@ private class BotButton { private int timeWidthAudio; private int timeAudioX; - private static TextPaint audioTimePaint; - private static TextPaint audioTitlePaint; - private static TextPaint audioPerformerPaint; - private static TextPaint botButtonPaint; - private static TextPaint contactNamePaint; - private static TextPaint contactPhonePaint; - private StaticLayout songLayout; private int songX; @@ -226,6 +219,8 @@ private class BotButton { private ArrayList botButtons = new ArrayList<>(); private HashMap botButtonsByData = new HashMap<>(); + private HashMap botButtonsByPosition = new HashMap<>(); + private String botButtonsLayout; private int widthForButtons; private int pressedBotButton; @@ -250,13 +245,6 @@ private class BotButton { private int viaNameWidth; private int availableTimeWidth; - private static TextPaint timePaint; - private static TextPaint namePaint; - private static TextPaint forwardNamePaint; - private static TextPaint replyNamePaint; - private static TextPaint replyTextPaint; - private static Paint replyLinePaint; - private int backgroundWidth = 100; private int layoutWidth; @@ -307,6 +295,7 @@ private class BotButton { private int timeX; private String currentTimeString; private boolean drawTime = true; + private boolean forceNotDrawTime; private StaticLayout viewsLayout; private int viewsTextWidth; @@ -332,93 +321,9 @@ private class BotButton { public ChatMessageCell(Context context) { super(context); - if (infoPaint == null) { - infoPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - docNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - docNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - docBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - deleteProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - deleteProgressPaint.setColor(Theme.MSG_SECRET_TIME_TEXT_COLOR); - - botProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - botProgressPaint.setColor(Theme.MSG_BOT_PROGRESS_COLOR); - botProgressPaint.setStrokeCap(Paint.Cap.ROUND); - botProgressPaint.setStyle(Paint.Style.STROKE); - - locationTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - locationTitlePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - locationAddressPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - urlPaint = new Paint(); - urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); - - urlSelectionPaint = new Paint(); - urlSelectionPaint.setColor(Theme.MSG_TEXT_SELECT_BACKGROUND_COLOR); - - audioTimePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - - audioTitlePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - audioTitlePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - audioPerformerPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - botButtonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - botButtonPaint.setColor(Theme.MSG_BOT_BUTTON_TEXT_COLOR); - botButtonPaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - contactNamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - contactNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - contactPhonePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - durationPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - durationPaint.setColor(Theme.MSG_WEB_PREVIEW_DURATION_TEXT_COLOR); - gamePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - gamePaint.setColor(Theme.MSG_WEB_PREVIEW_GAME_TEXT_COLOR); - gamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - - namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - namePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - forwardNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - - replyNamePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - replyNamePaint.setTypeface(getTypeface("fonts/rmedium.ttf")); - - replyTextPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - replyTextPaint.linkColor = Theme.MSG_LINK_TEXT_COLOR; - - replyLinePaint = new Paint(); - } - - botProgressPaint.setStrokeWidth(dp(2)); - infoPaint.setTextSize(dp(12)); - docNamePaint.setTextSize(dp(15)); - locationTitlePaint.setTextSize(dp(15)); - locationAddressPaint.setTextSize(dp(13)); - audioTimePaint.setTextSize(dp(12)); - audioTitlePaint.setTextSize(dp(16)); - audioPerformerPaint.setTextSize(dp(15)); - botButtonPaint.setTextSize(dp(15)); - contactNamePaint.setTextSize(dp(15)); - contactPhonePaint.setTextSize(dp(13)); - durationPaint.setTextSize(dp(12)); - timePaint.setTextSize(dp(12)); - namePaint.setTextSize(dp(14)); - forwardNamePaint.setTextSize(dp(14)); - replyNamePaint.setTextSize(dp(14)); - replyTextPaint.setTextSize(dp(14)); - gamePaint.setTextSize(dp(13)); - - avatarImage = new ImageReceiver(this); - avatarImage.setRoundRadius(dp(21)); + avatarImage = new ImageReceiver(); + avatarImage.setRoundRadius(AndroidUtilities.dp(21)); avatarDrawable = new AvatarDrawable(); replyImageReceiver = new ImageReceiver(this); TAG = MediaController.getInstance().generateObserverTag(); @@ -432,8 +337,6 @@ public ChatMessageCell(Context context) { seekBarWaveform = new SeekBarWaveform(context); seekBarWaveform.setDelegate(this); seekBarWaveform.setParentView(this); - - radialProgress = new RadialProgress(this); } private void resetPressedLink(int type) { @@ -496,15 +399,20 @@ private boolean checkTextBlockMotionEvent(MotionEvent event) { } try { MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(blockNum); - x -= textX - (int) Math.ceil(block.textXOffset); + x -= textX - (block.isRtl() ? currentMessageObject.textXOffset : 0); y -= block.textYOffset; final int line = block.textLayout.getLineForVertical(y); - final int off = block.textLayout.getOffsetForHorizontal(line, x) + block.charactersOffset; + final int off = block.textLayout.getOffsetForHorizontal(line, x); final float left = block.textLayout.getLineLeft(line); if (left <= x && left + block.textLayout.getLineWidth(line) >= x) { Spannable buffer = (Spannable) currentMessageObject.messageText; - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + CharacterStyle[] link = buffer.getSpans(off, off, ClickableSpan.class); + boolean isMono = false; + if (link == null || link.length == 0) { + link = buffer.getSpans(off, off, URLSpanMono.class); + isMono = true; + } boolean ignore = false; if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { ignore = true; @@ -517,46 +425,45 @@ private boolean checkTextBlockMotionEvent(MotionEvent event) { resetUrlPaths(false); try { LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink) - block.charactersOffset; + int start = buffer.getSpanStart(pressedLink); int end = buffer.getSpanEnd(pressedLink); - int length = block.textLayout.getText().length(); path.setCurrentLayout(block.textLayout, start, 0); - block.textLayout.getSelectionPath(start, end - block.charactersOffset, path); - if (end >= block.charactersOffset + length) { + block.textLayout.getSelectionPath(start, end, path); + if (end >= block.charactersEnd) { for (int a = blockNum + 1; a < currentMessageObject.textLayoutBlocks.size(); a++) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); - length = nextBlock.textLayout.getText().length(); - ClickableSpan[] nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, ClickableSpan.class); + CharacterStyle[] nextLink = buffer.getSpans(nextBlock.charactersOffset, nextBlock.charactersOffset, isMono ? URLSpanMono.class : ClickableSpan.class); if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) { break; } path = obtainNewUrlPath(false); - path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.height); - nextBlock.textLayout.getSelectionPath(0, end - nextBlock.charactersOffset, path); - if (end < block.charactersOffset + length - 1) { + path.setCurrentLayout(nextBlock.textLayout, 0, nextBlock.textYOffset - block.textYOffset); + nextBlock.textLayout.getSelectionPath(0, end, path); + if (end < nextBlock.charactersEnd - 1) { break; } } } - if (start < 0) { + if (start <= block.charactersOffset) { + int offsetY = 0; for (int a = blockNum - 1; a >= 0; a--) { MessageObject.TextLayoutBlock nextBlock = currentMessageObject.textLayoutBlocks.get(a); - length = nextBlock.textLayout.getText().length(); - ClickableSpan[] nextLink = buffer.getSpans(nextBlock.charactersOffset + length - 1, nextBlock.charactersOffset + length - 1, ClickableSpan.class); + CharacterStyle[] nextLink = buffer.getSpans(nextBlock.charactersEnd - 1, nextBlock.charactersEnd - 1, isMono ? URLSpanMono.class : ClickableSpan.class); if (nextLink == null || nextLink.length == 0 || nextLink[0] != pressedLink) { break; } path = obtainNewUrlPath(false); - start = buffer.getSpanStart(pressedLink) - nextBlock.charactersOffset; - path.setCurrentLayout(nextBlock.textLayout, start, -nextBlock.height); - nextBlock.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink) - nextBlock.charactersOffset, path); - if (start >= 0) { + start = buffer.getSpanStart(pressedLink); + offsetY -= nextBlock.height; + path.setCurrentLayout(nextBlock.textLayout, start, offsetY); + nextBlock.textLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); + if (start > nextBlock.charactersOffset) { break; } } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } invalidate(); return true; @@ -570,7 +477,7 @@ private boolean checkTextBlockMotionEvent(MotionEvent event) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { resetPressedLink(1); @@ -612,14 +519,14 @@ private boolean checkCaptionMotionEvent(MotionEvent event) { path.setCurrentLayout(captionLayout, start, 0); captionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } invalidate(); return true; } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (pressedLinkType == 3) { delegate.didPressedUrl(currentMessageObject, pressedLink, false); @@ -646,7 +553,7 @@ private boolean checkGameMotionEvent(MotionEvent event) { return true; } else if (descriptionLayout != null && y >= descriptionY) { try { - x -= textX + dp(10) + descriptionX; + x -= textX + AndroidUtilities.dp(10) + descriptionX; y -= descriptionY; final int line = descriptionLayout.getLineForVertical(y); final int off = descriptionLayout.getOffsetForHorizontal(line, x); @@ -670,14 +577,14 @@ private boolean checkGameMotionEvent(MotionEvent event) { path.setCurrentLayout(descriptionLayout, start, 0); descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } invalidate(); return true; } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else if (event.getAction() == MotionEvent.ACTION_UP) { @@ -685,8 +592,8 @@ private boolean checkGameMotionEvent(MotionEvent event) { if (pressedLink != null) { if (pressedLink instanceof URLSpan) { Browser.openUrl(getContext(), ((URLSpan) pressedLink).getURL()); - } else { - pressedLink.onClick(this); + } else if (pressedLink instanceof ClickableSpan) { + ((ClickableSpan) pressedLink).onClick(this); } resetPressedLink(2); } else { @@ -717,13 +624,55 @@ private boolean checkLinkPreviewMotionEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); - if (x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + dp(8)) { + if (x >= textX && x <= textX + backgroundWidth && y >= textY + currentMessageObject.textHeight && y <= textY + currentMessageObject.textHeight + linkPreviewHeight + AndroidUtilities.dp(8)) { if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && drawPhotoImage && photoImage.isInsideImage(x, y)) { - if (drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + dp(48) && y >= buttonY && y <= buttonY + dp(48)) { + if (descriptionLayout != null && y >= descriptionY) { + try { + int checkX = x - (textX + AndroidUtilities.dp(10) + descriptionX); + int checkY = y - descriptionY; + if (checkY <= descriptionLayout.getHeight()) { + final int line = descriptionLayout.getLineForVertical(checkY); + final int off = descriptionLayout.getOffsetForHorizontal(line, checkX); + + final float left = descriptionLayout.getLineLeft(line); + if (left <= checkX && left + descriptionLayout.getLineWidth(line) >= checkX) { + Spannable buffer = (Spannable) currentMessageObject.linkDescription; + ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); + boolean ignore = false; + if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { + ignore = true; + } + if (!ignore) { + pressedLink = link[0]; + linkBlockNum = -10; + pressedLinkType = 2; + resetUrlPaths(false); + try { + LinkPath path = obtainNewUrlPath(false); + int start = buffer.getSpanStart(pressedLink); + path.setCurrentLayout(descriptionLayout, start, 0); + descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); + } catch (Exception e) { + FileLog.e(e); + } + invalidate(); + return true; + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + if (pressedLink == null) { + if (drawPhotoImage && drawImageButton && buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { buttonPressed = 1; return true; - } else { + } else if (drawInstantView) { + instantPressed = true; + invalidate(); + return true; + } else if (documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && drawPhotoImage && photoImage.isInsideImage(x, y)) { linkPreviewPressed = true; TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF && buttonState == -1 && MediaController.getInstance().canAutoplayGifs() && (photoImage.getAnimation() == null || !TextUtils.isEmpty(webPage.embed_url))) { @@ -732,56 +681,26 @@ private boolean checkLinkPreviewMotionEvent(MotionEvent event) { } return true; } - } else if (descriptionLayout != null && y >= descriptionY) { - try { - x -= textX + dp(10) + descriptionX; - y -= descriptionY; - final int line = descriptionLayout.getLineForVertical(y); - final int off = descriptionLayout.getOffsetForHorizontal(line, x); - - final float left = descriptionLayout.getLineLeft(line); - if (left <= x && left + descriptionLayout.getLineWidth(line) >= x) { - Spannable buffer = (Spannable) currentMessageObject.linkDescription; - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - boolean ignore = false; - if (link.length == 0 || link.length != 0 && link[0] instanceof URLSpanBotCommand && !URLSpanBotCommand.enabled) { - ignore = true; - } - if (!ignore) { - pressedLink = link[0]; - linkBlockNum = -10; - pressedLinkType = 2; - resetUrlPaths(false); - try { - LinkPath path = obtainNewUrlPath(false); - int start = buffer.getSpanStart(pressedLink); - path.setCurrentLayout(descriptionLayout, start, 0); - descriptionLayout.getSelectionPath(start, buffer.getSpanEnd(pressedLink), path); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - invalidate(); - return true; - } - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } } } else if (event.getAction() == MotionEvent.ACTION_UP) { - if (pressedLinkType == 2 || buttonPressed != 0 || linkPreviewPressed) { + if (instantPressed) { + if (delegate != null) { + delegate.didPressedInstantButton(this); + } + playSoundEffect(SoundEffectConstants.CLICK); + instantPressed = false; + invalidate(); + } else if (pressedLinkType == 2 || buttonPressed != 0 || linkPreviewPressed) { if (buttonPressed != 0) { - if (event.getAction() == MotionEvent.ACTION_UP) { - buttonPressed = 0; - playSoundEffect(SoundEffectConstants.CLICK); - didPressedButton(false); - invalidate(); - } + buttonPressed = 0; + playSoundEffect(SoundEffectConstants.CLICK); + didPressedButton(false); + invalidate(); } else if (pressedLink != null) { if (pressedLink instanceof URLSpan) { Browser.openUrl(getContext(), ((URLSpan) pressedLink).getURL()); - } else { - pressedLink.onClick(this); + } else if (pressedLink instanceof ClickableSpan) { + ((ClickableSpan) pressedLink).onClick(this); } resetPressedLink(2); } else { @@ -805,7 +724,7 @@ private boolean checkLinkPreviewMotionEvent(MotionEvent event) { } else { TLRPC.WebPage webPage = currentMessageObject.messageOwner.media.webpage; if (webPage != null && Build.VERSION.SDK_INT >= 16 && !TextUtils.isEmpty(webPage.embed_url)) { - delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.description, webPage.url, webPage.embed_width, webPage.embed_height); + delegate.needOpenWebView(webPage.embed_url, webPage.site_name, webPage.title, webPage.url, webPage.embed_width, webPage.embed_height); } else if (buttonState == -1) { delegate.didPressedImage(this); playSoundEffect(SoundEffectConstants.CLICK); @@ -825,7 +744,11 @@ private boolean checkLinkPreviewMotionEvent(MotionEvent event) { } private boolean checkOtherButtonMotionEvent(MotionEvent event) { - if (documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && currentMessageObject.type != 12 && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && currentMessageObject.type != 8 || hasGamePreview) { + boolean allow = currentMessageObject.type == 16; + if (!allow) { + allow = !(documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT && currentMessageObject.type != 12 && documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && documentAttachType != DOCUMENT_ATTACH_TYPE_VIDEO && documentAttachType != DOCUMENT_ATTACH_TYPE_GIF && currentMessageObject.type != 8 || hasGamePreview || hasInvoicePreview); + } + if (!allow) { return false; } @@ -834,9 +757,18 @@ private boolean checkOtherButtonMotionEvent(MotionEvent event) { boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (x >= otherX - dp(20) && x <= otherX + dp(20) && y >= otherY - dp(4) && y <= otherY + dp(30)) { - otherPressed = true; - result = true; + if (currentMessageObject.type == 16) { + if (x >= otherX && x <= otherX + AndroidUtilities.dp(30 + 205) && y >= otherY - AndroidUtilities.dp(14) && y <= otherY + AndroidUtilities.dp(50)) { + otherPressed = true; + result = true; + invalidate(); + } + } else { + if (x >= otherX - AndroidUtilities.dp(20) && x <= otherX + AndroidUtilities.dp(20) && y >= otherY - AndroidUtilities.dp(4) && y <= otherY + AndroidUtilities.dp(30)) { + otherPressed = true; + result = true; + invalidate(); + } } } else { if (event.getAction() == MotionEvent.ACTION_UP) { @@ -844,6 +776,7 @@ private boolean checkOtherButtonMotionEvent(MotionEvent event) { otherPressed = false; playSoundEffect(SoundEffectConstants.CLICK); delegate.didPressedOther(this); + invalidate(); } } } @@ -860,13 +793,13 @@ private boolean checkPhotoImageMotionEvent(MotionEvent event) { boolean result = false; if (event.getAction() == MotionEvent.ACTION_DOWN) { - if (buttonState != -1 && x >= buttonX && x <= buttonX + dp(48) && y >= buttonY && y <= buttonY + dp(48)) { + if (buttonState != -1 && x >= buttonX && x <= buttonX + AndroidUtilities.dp(48) && y >= buttonY && y <= buttonY + AndroidUtilities.dp(48)) { buttonPressed = 1; invalidate(); result = true; } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { - if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { + if (x >= photoImage.getImageX() && x <= photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(50) && y >= photoImage.getImageY() && y <= photoImage.getImageY() + photoImage.getImageHeight()) { imagePressed = true; result = true; } @@ -928,7 +861,7 @@ private boolean checkAudioMotionEvent(MotionEvent event) { int y = (int) event.getY(); boolean result; if (useSeekBarWaweform) { - result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - dp(13), event.getY() - seekBarY); + result = seekBarWaveform.onTouch(event.getAction(), event.getX() - seekBarX - AndroidUtilities.dp(13), event.getY() - seekBarY); } else { result = seekBar.onTouch(event.getAction(), event.getX() - seekBarX, event.getY() - seekBarY); } @@ -941,10 +874,10 @@ private boolean checkAudioMotionEvent(MotionEvent event) { disallowLongPress = true; invalidate(); } else { - int side = dp(36); + int side = AndroidUtilities.dp(36); boolean area; if (buttonState == 0 || buttonState == 1 || buttonState == 2) { - area = x >= buttonX - dp(12) && x <= buttonX - dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; + area = x >= buttonX - AndroidUtilities.dp(12) && x <= buttonX - AndroidUtilities.dp(12) + backgroundWidth && y >= namesOffset + mediaOffsetY && y <= layoutHeight; } else { area = x >= buttonX && x <= buttonX + side && y >= buttonY && y <= buttonY + side; } @@ -988,13 +921,13 @@ private boolean checkBotButtonMotionEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { int addX; if (currentMessageObject.isOutOwner()) { - addX = getMeasuredWidth() - widthForButtons - dp(10); + addX = getMeasuredWidth() - widthForButtons - AndroidUtilities.dp(10); } else { - addX = backgroundDrawableLeft + dp(mediaBackground ? 1 : 7); + addX = backgroundDrawableLeft + AndroidUtilities.dp(mediaBackground ? 1 : 7); } for (int a = 0; a < botButtons.size(); a++) { BotButton button = botButtons.get(a); - int y2 = button.y + layoutHeight - dp(2); + int y2 = button.y + layoutHeight - AndroidUtilities.dp(2); if (x >= button.x + addX && x <= button.x + addX + button.width && y >= y2 && y <= y2 + button.height) { pressedBotButton = a; invalidate(); @@ -1052,6 +985,7 @@ public boolean onTouchEvent(MotionEvent event) { linkPreviewPressed = false; otherPressed = false; imagePressed = false; + instantPressed = false; result = false; resetPressedLink(-1); } @@ -1067,23 +1001,23 @@ public boolean onTouchEvent(MotionEvent event) { float y = event.getY(); if (event.getAction() == MotionEvent.ACTION_DOWN) { if (delegate == null || delegate.canPerformActions()) { - if (isAvatarVisible && avatarImage.isInsideImage(x, y)) { + if (isAvatarVisible && avatarImage.isInsideImage(x, y + getTop())) { avatarPressed = true; result = true; - } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32)) { - if (viaWidth != 0 && x >= forwardNameX + viaNameWidth + dp(4)) { + } else if (drawForwardedName && forwardedNameLayout[0] != null && x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32)) { + if (viaWidth != 0 && x >= forwardNameX + viaNameWidth + AndroidUtilities.dp(4)) { forwardBotPressed = true; } else { forwardNamePressed = true; } result = true; - } else if (drawNameLayout && nameLayout != null && viaWidth != 0 && x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - dp(4) && y <= nameY + dp(20)) { + } else if (drawNameLayout && nameLayout != null && viaWidth != 0 && x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - AndroidUtilities.dp(4) && y <= nameY + AndroidUtilities.dp(20)) { forwardBotPressed = true; result = true; - } else if (currentMessageObject.isReply() && x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + dp(35)) { + } else if (currentMessageObject.isReply() && x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35)) { replyPressed = true; result = true; - } else if (drawShareButton && x >= shareStartX && x <= shareStartX + dp(40) && y >= shareStartY && y <= shareStartY + dp(32)) { + } else if (drawShareButton && x >= shareStartX && x <= shareStartX + AndroidUtilities.dp(40) && y >= shareStartY && y <= shareStartY + AndroidUtilities.dp(32)) { sharePressed = true; result = true; invalidate(); @@ -1110,7 +1044,7 @@ public boolean onTouchEvent(MotionEvent event) { } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { avatarPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (isAvatarVisible && !avatarImage.isInsideImage(x, y)) { + if (isAvatarVisible && !avatarImage.isInsideImage(x, y + getTop())) { avatarPressed = false; } } @@ -1128,7 +1062,7 @@ public boolean onTouchEvent(MotionEvent event) { } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { forwardNamePressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32))) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { forwardNamePressed = false; } } @@ -1143,11 +1077,11 @@ public boolean onTouchEvent(MotionEvent event) { forwardBotPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (drawForwardedName && forwardedNameLayout[0] != null) { - if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + dp(32))) { + if (!(x >= forwardNameX && x <= forwardNameX + forwardedNameWidth && y >= forwardNameY && y <= forwardNameY + AndroidUtilities.dp(32))) { forwardBotPressed = false; } } else { - if (!(x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - dp(4) && y <= nameY + dp(20))) { + if (!(x >= nameX + viaNameWidth && x <= nameX + viaNameWidth + viaWidth && y >= nameY - AndroidUtilities.dp(4) && y <= nameY + AndroidUtilities.dp(20))) { forwardBotPressed = false; } } @@ -1162,7 +1096,7 @@ public boolean onTouchEvent(MotionEvent event) { } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { replyPressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + dp(35))) { + if (!(x >= replyStartX && x <= replyStartX + Math.max(replyNameWidth, replyTextWidth) && y >= replyStartY && y <= replyStartY + AndroidUtilities.dp(35))) { replyPressed = false; } } @@ -1176,7 +1110,7 @@ public boolean onTouchEvent(MotionEvent event) { } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { sharePressed = false; } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (!(x >= shareStartX && x <= shareStartX + dp(40) && y >= shareStartY && y <= shareStartY + dp(32))) { + if (!(x >= shareStartX && x <= shareStartX + AndroidUtilities.dp(40) && y >= shareStartY && y <= shareStartY + AndroidUtilities.dp(32))) { sharePressed = false; } } @@ -1218,8 +1152,8 @@ public void updateAudioProgress() { String timeString = String.format("%02d:%02d", duration / 60, duration % 60); if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { lastTimeString = timeString; - timeWidthAudio = (int) Math.ceil(audioTimePaint.measureText(timeString)); - durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + timeWidthAudio = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(timeString)); + durationLayout = new StaticLayout(timeString, Theme.chat_audioTimePaint, timeWidthAudio, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } else { int currentProgress = 0; @@ -1236,8 +1170,8 @@ public void updateAudioProgress() { String timeString = String.format("%d:%02d / %d:%02d", currentProgress / 60, currentProgress % 60, duration / 60, duration % 60); if (lastTimeString == null || lastTimeString != null && !lastTimeString.equals(timeString)) { lastTimeString = timeString; - int timeWidth = (int) Math.ceil(audioTimePaint.measureText(timeString)); - durationLayout = new StaticLayout(timeString, audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int timeWidth = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(timeString)); + durationLayout = new StaticLayout(timeString, Theme.chat_audioTimePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } invalidate(); @@ -1254,6 +1188,10 @@ public void downloadAudioIfNeed() { } } + public void setFullyDraw(boolean draw) { + fullyDraw = draw; + } + public void setVisiblePart(int position, int height) { if (currentMessageObject == null || currentMessageObject.textLayoutBlocks == null) { return; @@ -1323,7 +1261,7 @@ public static StaticLayout generateStaticLayout(CharSequence text, TextPaint pai break; } } - return StaticLayoutEx.createStaticLayout(stringBuilder, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, maxWidth, maxLines); + return StaticLayoutEx.createStaticLayout(stringBuilder, paint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, maxWidth, maxLines); } private void didClickedImage() { @@ -1339,7 +1277,9 @@ private void didClickedImage() { } else if (currentMessageObject.type == 8) { if (buttonState == -1) { if (MediaController.getInstance().canAutoplayGifs()) { - delegate.didPressedImage(this); + if (!currentMessageObject.isVideoVoice()) { //TODO + delegate.didPressedImage(this); + } } else { buttonState = 2; currentMessageObject.audioProgress = 1; @@ -1372,6 +1312,10 @@ private void didClickedImage() { } } } + } else if (hasInvoicePreview) { + if (buttonState == -1) { + delegate.didPressedImage(this); + } } } @@ -1383,9 +1327,9 @@ private void updateSecretTimeText(MessageObject messageObject) { if (str == null) { return; } - infoWidth = (int) Math.ceil(infoPaint.measureText(str)); - CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); - infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + infoWidth = (int) Math.ceil(Theme.chat_infoPaint.measureText(str)); + CharSequence str2 = TextUtils.ellipsize(str, Theme.chat_infoPaint, infoWidth, TextUtils.TruncateAt.END); + infoLayout = new StaticLayout(str2, Theme.chat_infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); invalidate(); } @@ -1399,7 +1343,7 @@ private boolean isPhotoDataChanged(MessageObject object) { } double lat = object.messageOwner.media.geo.lat; double lon = object.messageOwner.media.geo._long; - String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); + String url = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); if (!url.equals(currentUrl)) { return true; } @@ -1508,6 +1452,7 @@ protected void onDetachedFromWindow() { protected void onAttachedToWindow() { super.onAttachedToWindow(); avatarImage.onAttachedToWindow(); + avatarImage.setParentView((View) getParent()); replyImageReceiver.onAttachedToWindow(); if (drawPhotoImage) { if (photoImage.onAttachedToWindow()) { @@ -1520,7 +1465,9 @@ protected void onAttachedToWindow() { @Override protected void onLongPress() { - if (pressedLink instanceof URLSpanNoUnderline) { + if (pressedLink instanceof URLSpanMono) { + delegate.didPressedUrl(currentMessageObject, pressedLink, true); + } else if (pressedLink instanceof URLSpanNoUnderline) { URLSpanNoUnderline url = (URLSpanNoUnderline) pressedLink; if (url.getURL().startsWith("/")) { delegate.didPressedUrl(currentMessageObject, pressedLink, true); @@ -1536,6 +1483,10 @@ protected void onLongPress() { pressedBotButton = -1; invalidate(); } + if (instantPressed) { + instantPressed = false; + invalidate(); + } if (delegate != null) { delegate.didLongPressed(this); } @@ -1624,40 +1575,27 @@ private int createDocumentLayout(int maxWidth, MessageObject messageObject) { break; } } - availableTimeWidth = maxWidth - dp(76 + 18) - (int) Math.ceil(audioTimePaint.measureText("00:00")); + availableTimeWidth = maxWidth - AndroidUtilities.dp(76 + 18) - (int) Math.ceil(Theme.chat_audioTimePaint.measureText("00:00")); measureTime(messageObject); - int minSize = dp(40 + 14 + 20 + 90 + 10) + timeWidth; + int minSize = AndroidUtilities.dp(40 + 14 + 20 + 90 + 10) + timeWidth; if (!hasLinkPreview) { - backgroundWidth = Math.min(maxWidth, minSize + duration * dp(10)); - } - - if (messageObject.isOutOwner()) { - seekBarWaveform.setColors(Theme.MSG_OUT_VOICE_SEEKBAR_COLOR, Theme.MSG_OUT_VOICE_SEEKBAR_FILL_COLOR, Theme.MSG_OUT_VOICE_SEEKBAR_SELECTED_COLOR); - seekBar.setColors(Theme.MSG_OUT_AUDIO_SEEKBAR_COLOR, Theme.MSG_OUT_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_OUT_AUDIO_SEEKBAR_SELECTED_COLOR); - } else { - seekBarWaveform.setColors(Theme.MSG_IN_VOICE_SEEKBAR_COLOR, Theme.MSG_IN_VOICE_SEEKBAR_FILL_COLOR, Theme.MSG_IN_VOICE_SEEKBAR_SELECTED_COLOR); - seekBar.setColors(Theme.MSG_IN_AUDIO_SEEKBAR_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_SELECTED_COLOR); + backgroundWidth = Math.min(maxWidth, minSize + duration * AndroidUtilities.dp(10)); } seekBarWaveform.setMessageObject(messageObject); return 0; } else if (MessageObject.isMusicDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_MUSIC; - if (messageObject.isOutOwner()) { - seekBar.setColors(Theme.MSG_OUT_AUDIO_SEEKBAR_COLOR, Theme.MSG_OUT_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_OUT_AUDIO_SEEKBAR_SELECTED_COLOR); - } else { - seekBar.setColors(Theme.MSG_IN_AUDIO_SEEKBAR_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_SELECTED_COLOR); - } - maxWidth = maxWidth - dp(86); + maxWidth = maxWidth - AndroidUtilities.dp(86); - CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace('\n', ' '), audioTitlePaint, maxWidth - dp(12), TextUtils.TruncateAt.END); - songLayout = new StaticLayout(stringFinal, audioTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + CharSequence stringFinal = TextUtils.ellipsize(messageObject.getMusicTitle().replace('\n', ' '), Theme.chat_audioTitlePaint, maxWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + songLayout = new StaticLayout(stringFinal, Theme.chat_audioTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (songLayout.getLineCount() > 0) { songX = -(int) Math.ceil(songLayout.getLineLeft(0)); } - stringFinal = TextUtils.ellipsize(messageObject.getMusicAuthor().replace('\n', ' '), audioPerformerPaint, maxWidth, TextUtils.TruncateAt.END); - performerLayout = new StaticLayout(stringFinal, audioPerformerPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + stringFinal = TextUtils.ellipsize(messageObject.getMusicAuthor().replace('\n', ' '), Theme.chat_audioPerformerPaint, maxWidth, TextUtils.TruncateAt.END); + performerLayout = new StaticLayout(stringFinal, Theme.chat_audioPerformerPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (performerLayout.getLineCount() > 0) { performerX = -(int) Math.ceil(performerLayout.getLineLeft(0)); } @@ -1670,8 +1608,8 @@ private int createDocumentLayout(int maxWidth, MessageObject messageObject) { break; } } - int durationWidth = (int) Math.ceil(audioTimePaint.measureText(String.format("%d:%02d / %d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); - availableTimeWidth = backgroundWidth - dp(76 + 18) - durationWidth; + int durationWidth = (int) Math.ceil(Theme.chat_audioTimePaint.measureText(String.format("%d:%02d / %d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(76 + 18) - durationWidth; return durationWidth; } else if (MessageObject.isVideoDocument(documentAttach)) { documentAttachType = DOCUMENT_ATTACH_TYPE_VIDEO; @@ -1685,22 +1623,22 @@ private int createDocumentLayout(int maxWidth, MessageObject messageObject) { } int minutes = duration / 60; int seconds = duration - minutes * 60; - String str = String.format("%d:%02d, %s", minutes, seconds, formatFileSize(documentAttach.size)); - infoWidth = (int) Math.ceil(infoPaint.measureText(str)); - infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + String str = String.format("%d:%02d, %s", minutes, seconds, AndroidUtilities.formatFileSize(documentAttach.size)); + infoWidth = (int) Math.ceil(Theme.chat_infoPaint.measureText(str)); + infoLayout = new StaticLayout(str, Theme.chat_infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); return 0; } else { drawPhotoImage = documentAttach.mime_type != null && documentAttach.mime_type.toLowerCase().startsWith("image/") || documentAttach.thumb instanceof TLRPC.TL_photoSize && !(documentAttach.thumb.location instanceof TLRPC.TL_fileLocationUnavailable); if (!drawPhotoImage) { - maxWidth += dp(30); + maxWidth += AndroidUtilities.dp(30); } documentAttachType = DOCUMENT_ATTACH_TYPE_DOCUMENT; String name = FileLoader.getDocumentFileName(documentAttach); if (name == null || name.length() == 0) { name = LocaleController.getString("AttachDocument", R.string.AttachDocument); } - docTitleLayout = StaticLayoutEx.createStaticLayout(name, docNamePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, drawPhotoImage ? 2 : 1); + docTitleLayout = StaticLayoutEx.createStaticLayout(name, Theme.chat_docNamePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.MIDDLE, maxWidth, drawPhotoImage ? 2 : 1); docTitleOffsetX = Integer.MIN_VALUE; int width; if (docTitleLayout != null && docTitleLayout.getLineCount() > 0) { @@ -1715,20 +1653,20 @@ private int createDocumentLayout(int maxWidth, MessageObject messageObject) { docTitleOffsetX = 0; } - String str = formatFileSize(documentAttach.size) + " " + FileLoader.getDocumentExtension(documentAttach); - infoWidth = Math.min(maxWidth - AndroidUtilities.dp(30), (int) Math.ceil(infoPaint.measureText(str))); - CharSequence str2 = TextUtils.ellipsize(str, infoPaint, infoWidth, TextUtils.TruncateAt.END); + String str = AndroidUtilities.formatFileSize(documentAttach.size) + " " + FileLoader.getDocumentExtension(documentAttach); + infoWidth = Math.min(maxWidth - AndroidUtilities.dp(30), (int) Math.ceil(Theme.chat_infoPaint.measureText(str))); + CharSequence str2 = TextUtils.ellipsize(str, Theme.chat_infoPaint, infoWidth, TextUtils.TruncateAt.END); try { if (infoWidth < 0) { - infoWidth = dp(10); + infoWidth = AndroidUtilities.dp(10); } - infoLayout = new StaticLayout(str2, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + infoLayout = new StaticLayout(str2, Theme.chat_infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (drawPhotoImage) { - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); @@ -1744,16 +1682,16 @@ private int createDocumentLayout(int maxWidth, MessageObject messageObject) { } private void calcBackgroundWidth(int maxWidth, int timeMore, int maxChildWidth) { - if (hasLinkPreview || hasGamePreview || maxWidth - currentMessageObject.lastLineWidth < timeMore) { - totalHeight += dp(14); - backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + dp(31); - backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); + if (hasLinkPreview || hasGamePreview || hasInvoicePreview || maxWidth - currentMessageObject.lastLineWidth < timeMore || currentMessageObject.hasRtl) { + totalHeight += AndroidUtilities.dp(14); + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth) + AndroidUtilities.dp(31); + backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(31)); } else { int diff = maxChildWidth - currentMessageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { - backgroundWidth = maxChildWidth + timeMore - diff + dp(31); + backgroundWidth = maxChildWidth + timeMore - diff + AndroidUtilities.dp(31); } else { - backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + dp(31); + backgroundWidth = Math.max(maxChildWidth, currentMessageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(31); } } } @@ -1800,7 +1738,7 @@ public void setHighlightedText(String text) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } invalidate(); break; @@ -1808,14 +1746,16 @@ public void setHighlightedText(String text) { } } - public void setMessageObject(MessageObject messageObject) { + public void setMessageObject(MessageObject messageObject, boolean bottomNear, boolean topNear) { if (messageObject.checkLayout()) { currentMessageObject = null; } boolean messageIdChanged = currentMessageObject == null || currentMessageObject.getId() != messageObject.getId(); boolean messageChanged = currentMessageObject != messageObject || messageObject.forceUpdate; boolean dataChanged = currentMessageObject == messageObject && (isUserDataChanged() || photoNotSet); - if (messageChanged || dataChanged || isPhotoDataChanged(messageObject)) { + if (messageChanged || dataChanged || isPhotoDataChanged(messageObject) || pinnedBottom != bottomNear || pinnedTop != topNear) { + pinnedBottom = bottomNear; + pinnedTop = topNear; currentMessageObject = messageObject; lastSendState = messageObject.messageOwner.send_state; lastDeleteDate = messageObject.messageOwner.destroyTime; @@ -1842,6 +1782,8 @@ public void setMessageObject(MessageObject messageObject) { drawPhotoImage = false; hasLinkPreview = false; hasGamePreview = false; + hasInvoicePreview = false; + instantPressed = false; linkPreviewPressed = false; buttonPressed = 0; pressedBotButton = -1; @@ -1868,13 +1810,14 @@ public void setMessageObject(MessageObject messageObject) { drawBackground = true; drawName = false; useSeekBarWaweform = false; + drawInstantView = false; drawForwardedName = false; mediaBackground = false; availableTimeWidth = 0; photoImage.setNeedsQualityThumb(false); photoImage.setShouldGenerateQualityThumb(false); photoImage.setParentMessageObject(null); - photoImage.setRoundRadius(dp(3)); + photoImage.setRoundRadius(AndroidUtilities.dp(3)); if (messageChanged) { firstVisibleBlockNum = 0; @@ -1886,50 +1829,55 @@ public void setMessageObject(MessageObject messageObject) { drawForwardedName = true; int maxWidth; - if (isTablet()) { + if (AndroidUtilities.isTablet()) { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = getMinTabletSide() - dp(122); + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); drawName = true; } else { drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); - maxWidth = getMinTabletSide() - dp(80); + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); } } else { if (isChat && !messageObject.isOutOwner() && messageObject.isFromUser()) { - maxWidth = Math.min(displaySize.x, displaySize.y) - dp(122); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); drawName = true; } else { - maxWidth = Math.min(displaySize.x, displaySize.y) - dp(80); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); drawName = messageObject.messageOwner.to_id.channel_id != 0 && !messageObject.isOutOwner(); } } availableTimeWidth = maxWidth; measureTime(messageObject); - int timeMore = timeWidth + dp(6); + int timeMore = timeWidth + AndroidUtilities.dp(6); if (messageObject.isOutOwner()) { - timeMore += dp(20.5f); + timeMore += AndroidUtilities.dp(20.5f); } hasGamePreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame && messageObject.messageOwner.media.game instanceof TLRPC.TL_game; + hasInvoicePreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice; hasLinkPreview = messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage instanceof TLRPC.TL_webPage; + drawInstantView = Build.VERSION.SDK_INT >= 16 && hasLinkPreview && messageObject.messageOwner.media.webpage.cached_page != null; backgroundWidth = maxWidth; - if (hasLinkPreview || hasGamePreview || maxWidth - messageObject.lastLineWidth < timeMore) { - backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth) + dp(31); - backgroundWidth = Math.max(backgroundWidth, timeWidth + dp(31)); + if (hasLinkPreview || hasGamePreview || hasInvoicePreview || maxWidth - messageObject.lastLineWidth < timeMore) { + backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth) + AndroidUtilities.dp(31); + backgroundWidth = Math.max(backgroundWidth, timeWidth + AndroidUtilities.dp(31)); } else { int diff = backgroundWidth - messageObject.lastLineWidth; if (diff >= 0 && diff <= timeMore) { - backgroundWidth = backgroundWidth + timeMore - diff + dp(31); + backgroundWidth = backgroundWidth + timeMore - diff + AndroidUtilities.dp(31); } else { - backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth + timeMore) + dp(31); + backgroundWidth = Math.max(backgroundWidth, messageObject.lastLineWidth + timeMore) + AndroidUtilities.dp(31); } } - availableTimeWidth = backgroundWidth - dp(31); + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31); setMessageObjectInternal(messageObject); - backgroundWidth = messageObject.textWidth + (hasGamePreview ? AndroidUtilities.dp(10) : 0); - totalHeight = messageObject.textHeight + dp(19.5f) + namesOffset; + backgroundWidth = messageObject.textWidth + ((hasGamePreview || hasInvoicePreview) ? AndroidUtilities.dp(10) : 0); + totalHeight = messageObject.textHeight + AndroidUtilities.dp(19.5f) + namesOffset; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); + } int maxChildWidth = Math.max(backgroundWidth, nameWidth); maxChildWidth = Math.max(maxChildWidth, forwardedNameWidth); @@ -1937,23 +1885,23 @@ public void setMessageObject(MessageObject messageObject) { maxChildWidth = Math.max(maxChildWidth, replyTextWidth); int maxWebWidth = 0; - if (hasLinkPreview || hasGamePreview) { + if (hasLinkPreview || hasGamePreview || hasInvoicePreview) { int linkPreviewMaxWidth; - if (isTablet()) { + if (AndroidUtilities.isTablet()) { if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOut()) { - linkPreviewMaxWidth = getMinTabletSide() - dp(122); + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(122); } else { - linkPreviewMaxWidth = getMinTabletSide() - dp(80); + linkPreviewMaxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(80); } } else { if (messageObject.isFromUser() && (currentMessageObject.messageOwner.to_id.channel_id != 0 || currentMessageObject.messageOwner.to_id.chat_id != 0) && !currentMessageObject.isOutOwner()) { - linkPreviewMaxWidth = Math.min(displaySize.x, displaySize.y) - dp(122); + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(122); } else { - linkPreviewMaxWidth = Math.min(displaySize.x, displaySize.y) - dp(80); + linkPreviewMaxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(80); } } if (drawShareButton) { - linkPreviewMaxWidth -= dp(20); + linkPreviewMaxWidth -= AndroidUtilities.dp(20); } String site_name; String title; @@ -1961,28 +1909,43 @@ public void setMessageObject(MessageObject messageObject) { String description; TLRPC.Photo photo; TLRPC.Document document; + TLRPC.TL_webDocument webDocument; int duration; boolean smallImage; String type; - if (messageObject.messageOwner.media.webpage != null) { + if (hasLinkPreview) { TLRPC.TL_webPage webPage = (TLRPC.TL_webPage) messageObject.messageOwner.media.webpage; site_name = webPage.site_name; title = webPage.title; author = webPage.author; description = webPage.description; photo = webPage.photo; + webDocument = null; document = webPage.document; type = webPage.type; duration = webPage.duration; if (site_name != null && photo != null && site_name.toLowerCase().equals("instagram")) { - linkPreviewMaxWidth = Math.max(displaySize.y / 3, currentMessageObject.textWidth); + linkPreviewMaxWidth = Math.max(AndroidUtilities.displaySize.y / 3, currentMessageObject.textWidth); } - smallImage = type != null && (type.equals("app") || type.equals("profile") || type.equals("article")); - isSmallImage = description != null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")) && currentMessageObject.photoThumbs != null; + smallImage = !drawInstantView && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")); + isSmallImage = !drawInstantView && description != null && type != null && (type.equals("app") || type.equals("profile") || type.equals("article")) && currentMessageObject.photoThumbs != null; + } else if (hasInvoicePreview) { + site_name = messageObject.messageOwner.media.title; + title = null; + description = null; + photo = null; + author = null; + document = null; + webDocument = ((TLRPC.TL_messageMediaInvoice) messageObject.messageOwner.media).photo; + duration = 0; + type = "invoice"; + isSmallImage = false; + smallImage = false; } else { TLRPC.TL_game game = messageObject.messageOwner.media.game; site_name = game.title; title = null; + webDocument = null; description = TextUtils.isEmpty(messageObject.messageText) ? game.description : null; photo = game.photo; author = null; @@ -1993,7 +1956,7 @@ public void setMessageObject(MessageObject messageObject) { smallImage = false; } - int additinalWidth = dp(10); + int additinalWidth = hasInvoicePreview ? 0 : AndroidUtilities.dp(10); int restLinesCount = 3; int additionalHeight = 0; linkPreviewMaxWidth -= additinalWidth; @@ -2004,8 +1967,8 @@ public void setMessageObject(MessageObject messageObject) { if (site_name != null) { try { - int width = (int) Math.ceil(replyNamePaint.measureText(site_name)); - siteNameLayout = new StaticLayout(site_name, replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int width = (int) Math.ceil(Theme.chat_replyNamePaint.measureText(site_name)); + siteNameLayout = new StaticLayout(site_name, Theme.chat_replyNamePaint, Math.min(width, linkPreviewMaxWidth), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); int height = siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); linkPreviewHeight += height; totalHeight += height; @@ -2014,7 +1977,7 @@ public void setMessageObject(MessageObject messageObject) { maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2023,15 +1986,15 @@ public void setMessageObject(MessageObject messageObject) { try { titleX = Integer.MAX_VALUE; if (linkPreviewHeight != 0) { - linkPreviewHeight += dp(2); - totalHeight += dp(2); + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); } int restLines = 0; if (!isSmallImage || description == null) { - titleLayout = StaticLayoutEx.createStaticLayout(title, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); + titleLayout = StaticLayoutEx.createStaticLayout(title, Theme.chat_replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 4); } else { restLines = restLinesCount; - titleLayout = generateStaticLayout(title, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 4); + titleLayout = generateStaticLayout(title, Theme.chat_replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 4); restLinesCount -= titleLayout.getLineCount(); } int height = titleLayout.getLineBottom(titleLayout.getLineCount() - 1); @@ -2055,27 +2018,27 @@ public void setMessageObject(MessageObject messageObject) { width = (int) Math.ceil(titleLayout.getLineWidth(a)); } if (a < restLines || lineLeft != 0 && isSmallImage) { - width += dp(48 + 4); + width += AndroidUtilities.dp(48 + 4); } maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } boolean authorIsRTL = false; - if (author != null) { + if (author != null && title == null) { try { if (linkPreviewHeight != 0) { - linkPreviewHeight += dp(2); - totalHeight += dp(2); + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); } if (restLinesCount == 3 && (!isSmallImage || description == null)) { - authorLayout = new StaticLayout(author, replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + authorLayout = new StaticLayout(author, Theme.chat_replyNamePaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } else { - authorLayout = generateStaticLayout(author, replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 1); + authorLayout = generateStaticLayout(author, Theme.chat_replyNamePaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 1); restLinesCount -= authorLayout.getLineCount(); } int height = authorLayout.getLineBottom(authorLayout.getLineCount() - 1); @@ -2093,7 +2056,7 @@ public void setMessageObject(MessageObject messageObject) { maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); maxWebWidth = Math.max(maxWebWidth, width + additinalWidth); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2102,15 +2065,15 @@ public void setMessageObject(MessageObject messageObject) { descriptionX = 0; currentMessageObject.generateLinkDescription(); if (linkPreviewHeight != 0) { - linkPreviewHeight += dp(2); - totalHeight += dp(2); + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); } int restLines = 0; if (restLinesCount == 3 && !isSmallImage) { - descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); + descriptionLayout = StaticLayoutEx.createStaticLayout(messageObject.linkDescription, Theme.chat_replyTextPaint, linkPreviewMaxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, AndroidUtilities.dp(1), false, TextUtils.TruncateAt.END, linkPreviewMaxWidth, 6); } else { restLines = restLinesCount; - descriptionLayout = generateStaticLayout(messageObject.linkDescription, replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - dp(48 + 4), restLinesCount, 6); + descriptionLayout = generateStaticLayout(messageObject.linkDescription, Theme.chat_replyTextPaint, linkPreviewMaxWidth, linkPreviewMaxWidth - AndroidUtilities.dp(48 + 4), restLinesCount, 6); } int height = descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); linkPreviewHeight += height; @@ -2129,6 +2092,7 @@ public void setMessageObject(MessageObject messageObject) { } } + int textWidth = descriptionLayout.getWidth(); for (int a = 0; a < descriptionLayout.getLineCount(); a++) { int lineLeft = (int) Math.ceil(descriptionLayout.getLineLeft(a)); if (lineLeft == 0 && descriptionX != 0) { @@ -2137,12 +2101,16 @@ public void setMessageObject(MessageObject messageObject) { int width; if (lineLeft != 0) { - width = descriptionLayout.getWidth() - lineLeft; + width = textWidth - lineLeft; } else { - width = hasRTL ? descriptionLayout.getWidth() : (int) Math.ceil(descriptionLayout.getLineWidth(a)); + if (hasRTL) { + width = textWidth; + } else { + width = Math.min((int) Math.ceil(descriptionLayout.getLineWidth(a)), textWidth); + } } if (a < restLines || restLines != 0 && lineLeft != 0 && isSmallImage) { - width += dp(48 + 4); + width += AndroidUtilities.dp(48 + 4); } if (maxWebWidth < width + additinalWidth) { if (titleIsRTL) { @@ -2156,7 +2124,7 @@ public void setMessageObject(MessageObject messageObject) { maxChildWidth = Math.max(maxChildWidth, width + additinalWidth); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2164,7 +2132,7 @@ public void setMessageObject(MessageObject messageObject) { smallImage = false; isSmallImage = false; } - int maxPhotoWidth = smallImage ? dp(48) : linkPreviewMaxWidth; + int maxPhotoWidth = smallImage ? AndroidUtilities.dp(48) : linkPreviewMaxWidth; if (document != null) { if (MessageObject.isGifDocument(document)){ @@ -2183,7 +2151,7 @@ public void setMessageObject(MessageObject messageObject) { } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = dp(150); + currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); } } documentAttachType = DOCUMENT_ATTACH_TYPE_GIF; @@ -2199,7 +2167,7 @@ public void setMessageObject(MessageObject messageObject) { } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = dp(150); + currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); } } createDocumentLayout(0, messageObject); @@ -2215,7 +2183,7 @@ public void setMessageObject(MessageObject messageObject) { } } if (currentPhotoObject.w == 0 || currentPhotoObject.h == 0) { - currentPhotoObject.w = currentPhotoObject.h = dp(150); + currentPhotoObject.w = currentPhotoObject.h = AndroidUtilities.dp(150); } } documentAttach = document; @@ -2223,76 +2191,85 @@ public void setMessageObject(MessageObject messageObject) { } else { calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); if (!MessageObject.isStickerDocument(document)) { - if (backgroundWidth < maxWidth + dp(20)) { - backgroundWidth = maxWidth + dp(20); + if (backgroundWidth < maxWidth + AndroidUtilities.dp(20)) { + backgroundWidth = maxWidth + AndroidUtilities.dp(20); } if (MessageObject.isVoiceDocument(document)) { - createDocumentLayout(backgroundWidth - dp(10), messageObject); - mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; - totalHeight += dp(30 + 14); - linkPreviewHeight += dp(44); + createDocumentLayout(backgroundWidth - AndroidUtilities.dp(10), messageObject); + mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; + totalHeight += AndroidUtilities.dp(30 + 14); + linkPreviewHeight += AndroidUtilities.dp(44); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } else if (MessageObject.isMusicDocument(document)) { - int durationWidth = createDocumentLayout(backgroundWidth - dp(10), messageObject); - mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; - totalHeight += dp(42 + 14); - linkPreviewHeight += dp(56); + int durationWidth = createDocumentLayout(backgroundWidth - AndroidUtilities.dp(10), messageObject); + mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; + totalHeight += AndroidUtilities.dp(42 + 14); + linkPreviewHeight += AndroidUtilities.dp(56); - maxWidth = maxWidth - dp(86); - maxChildWidth = Math.max(maxChildWidth, durationWidth + additinalWidth + dp(86 + 8)); + maxWidth = maxWidth - AndroidUtilities.dp(86); + maxChildWidth = Math.max(maxChildWidth, durationWidth + additinalWidth + AndroidUtilities.dp(86 + 8)); if (songLayout != null && songLayout.getLineCount() > 0) { - maxChildWidth = (int) Math.max(maxChildWidth, songLayout.getLineWidth(0) + additinalWidth + dp(86)); + maxChildWidth = (int) Math.max(maxChildWidth, songLayout.getLineWidth(0) + additinalWidth + AndroidUtilities.dp(86)); } if (performerLayout != null && performerLayout.getLineCount() > 0) { - maxChildWidth = (int) Math.max(maxChildWidth, performerLayout.getLineWidth(0) + additinalWidth + dp(86)); + maxChildWidth = (int) Math.max(maxChildWidth, performerLayout.getLineWidth(0) + additinalWidth + AndroidUtilities.dp(86)); } calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } else { - createDocumentLayout(backgroundWidth - dp(86 + 24 + 58), messageObject); + createDocumentLayout(backgroundWidth - AndroidUtilities.dp(86 + 24 + 58), messageObject); drawImageButton = true; if (drawPhotoImage) { - totalHeight += dp(86 + 14); - linkPreviewHeight += dp(86); - photoImage.setImageCoords(0, totalHeight + namesOffset, dp(86), dp(86)); + totalHeight += AndroidUtilities.dp(86 + 14); + linkPreviewHeight += AndroidUtilities.dp(86); + photoImage.setImageCoords(0, totalHeight + namesOffset, AndroidUtilities.dp(86), AndroidUtilities.dp(86)); } else { - mediaOffsetY = currentMessageObject.textHeight + dp(8) + linkPreviewHeight; - photoImage.setImageCoords(0, totalHeight + namesOffset - dp(14), dp(56), dp(56)); - totalHeight += dp(50 + 14); - linkPreviewHeight += dp(50); + mediaOffsetY = currentMessageObject.textHeight + AndroidUtilities.dp(8) + linkPreviewHeight; + photoImage.setImageCoords(0, totalHeight + namesOffset - AndroidUtilities.dp(14), AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + totalHeight += AndroidUtilities.dp(50 + 14); + linkPreviewHeight += AndroidUtilities.dp(50); } } } } } else if (photo != null) { drawImageButton = type != null && type.equals("photo"); - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? getPhotoSize() : maxPhotoWidth, !drawImageButton); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, drawImageButton ? AndroidUtilities.getPhotoSize() : maxPhotoWidth, !drawImageButton); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); if (currentPhotoObjectThumb == currentPhotoObject) { currentPhotoObjectThumb = null; } + } else if (webDocument != null) { + if (!webDocument.mime_type.startsWith("image/")) { + webDocument = null; + } + drawImageButton = false; } if (documentAttachType != DOCUMENT_ATTACH_TYPE_MUSIC && documentAttachType != DOCUMENT_ATTACH_TYPE_AUDIO && documentAttachType != DOCUMENT_ATTACH_TYPE_DOCUMENT) { - if (currentPhotoObject != null) { + if (currentPhotoObject != null || webDocument != null) { drawImageButton = type != null && (type.equals("photo") || type.equals("document") && documentAttachType != DOCUMENT_ATTACH_TYPE_STICKER || type.equals("gif") || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO); if (linkPreviewHeight != 0) { - linkPreviewHeight += dp(2); - totalHeight += dp(2); + linkPreviewHeight += AndroidUtilities.dp(2); + totalHeight += AndroidUtilities.dp(2); } if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { - if (isTablet()) { - maxPhotoWidth = (int) (getMinTabletSide() * 0.5f); + if (AndroidUtilities.isTablet()) { + maxPhotoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); } else { - maxPhotoWidth = (int) (displaySize.x * 0.5f); + maxPhotoWidth = (int) (AndroidUtilities.displaySize.x * 0.5f); } } - maxChildWidth = Math.max(maxChildWidth, maxPhotoWidth + additinalWidth); - currentPhotoObject.size = -1; - if (currentPhotoObjectThumb != null) { - currentPhotoObjectThumb.size = -1; + maxChildWidth = Math.max(maxChildWidth, maxPhotoWidth - (hasInvoicePreview ? AndroidUtilities.dp(12) : 0) + additinalWidth); + if (currentPhotoObject != null) { + currentPhotoObject.size = -1; + if (currentPhotoObjectThumb != null) { + currentPhotoObjectThumb.size = -1; + } + } else { + webDocument.size = -1; } int width; @@ -2300,33 +2277,33 @@ public void setMessageObject(MessageObject messageObject) { if (smallImage) { width = height = maxPhotoWidth; } else { - if (hasGamePreview) { + if (hasGamePreview || hasInvoicePreview) { width = 640; height = 360; - float scale = width / (float) (maxPhotoWidth - dp(2)); + float scale = width / (float) (maxPhotoWidth - AndroidUtilities.dp(2)); width /= scale; height /= scale; } else { width = currentPhotoObject.w; height = currentPhotoObject.h; - float scale = width / (float) (maxPhotoWidth - dp(2)); + float scale = width / (float) (maxPhotoWidth - AndroidUtilities.dp(2)); width /= scale; height /= scale; if (site_name == null || site_name != null && !site_name.toLowerCase().equals("instagram") && documentAttachType == 0) { - if (height > displaySize.y / 3) { - height = displaySize.y / 3; + if (height > AndroidUtilities.displaySize.y / 3) { + height = AndroidUtilities.displaySize.y / 3; } } } } if (isSmallImage) { - if (dp(50) + additionalHeight > linkPreviewHeight) { - totalHeight += dp(50) + additionalHeight - linkPreviewHeight + dp(8); - linkPreviewHeight = dp(50) + additionalHeight; + if (AndroidUtilities.dp(50) + additionalHeight > linkPreviewHeight) { + totalHeight += AndroidUtilities.dp(50) + additionalHeight - linkPreviewHeight + AndroidUtilities.dp(8); + linkPreviewHeight = AndroidUtilities.dp(50) + additionalHeight; } - linkPreviewHeight -= dp(8); + linkPreviewHeight -= AndroidUtilities.dp(8); } else { - totalHeight += height + dp(12); + totalHeight += height + AndroidUtilities.dp(12); linkPreviewHeight += height; } @@ -2335,32 +2312,36 @@ public void setMessageObject(MessageObject messageObject) { currentPhotoFilter = String.format(Locale.US, "%d_%d", width, height); currentPhotoFilterThumb = String.format(Locale.US, "%d_%d_b", width, height); - if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { - photoImage.setImage(documentAttach, null, currentPhotoFilter, null, currentPhotoObject != null ? currentPhotoObject.location : null, "b1", documentAttach.size, "webp", true); - } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { - photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); - } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF) { - boolean photoExist = messageObject.mediaExists; - String fileName = FileLoader.getAttachFileName(document); - if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) || FileLoader.getInstance().isLoadingFile(fileName)) { - photoNotSet = false; - photoImage.setImage(document, null, currentPhotoObject.location, currentPhotoFilter, document.size, null, false); - } else { - photoNotSet = true; - photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); - } + if (webDocument != null) { + photoImage.setImage(webDocument, null, currentPhotoFilter, null, null, "b1", webDocument.size, null, true); } else { - boolean photoExist = messageObject.mediaExists; - String fileName = FileLoader.getAttachFileName(currentPhotoObject); - if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - photoNotSet = false; - photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); + if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { + photoImage.setImage(documentAttach, null, currentPhotoFilter, null, currentPhotoObject != null ? currentPhotoObject.location : null, "b1", documentAttach.size, "webp", true); + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { + photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF) { + boolean photoExist = messageObject.mediaExists; + String fileName = FileLoader.getAttachFileName(document); + if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_GIF) || FileLoader.getInstance().isLoadingFile(fileName)) { + photoNotSet = false; + photoImage.setImage(document, null, currentPhotoObject.location, currentPhotoFilter, document.size, null, false); + } else { + photoNotSet = true; + photoImage.setImage(null, null, currentPhotoObject.location, currentPhotoFilter, 0, null, false); + } } else { - photoNotSet = true; - if (currentPhotoObjectThumb != null) { - photoImage.setImage(null, null, currentPhotoObjectThumb.location, String.format(Locale.US, "%d_%d_b", width, height), 0, null, false); + boolean photoExist = messageObject.mediaExists; + String fileName = FileLoader.getAttachFileName(currentPhotoObject); + if (hasGamePreview || photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + photoNotSet = false; + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilterThumb, 0, null, false); } else { - photoImage.setImageBitmap((Drawable) null); + photoNotSet = true; + if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, String.format(Locale.US, "%d_%d_b", width, height), 0, null, false); + } else { + photoImage.setImageBitmap((Drawable) null); + } } } } @@ -2370,46 +2351,132 @@ public void setMessageObject(MessageObject messageObject) { int minutes = duration / 60; int seconds = duration - minutes * 60; String str = String.format("%d:%02d", minutes, seconds); - durationWidth = (int) Math.ceil(durationPaint.measureText(str)); - videoInfoLayout = new StaticLayout(str, durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + durationWidth = (int) Math.ceil(Theme.chat_durationPaint.measureText(str)); + videoInfoLayout = new StaticLayout(str, Theme.chat_durationPaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } else if (hasGamePreview) { String str = LocaleController.getString("AttachGame", R.string.AttachGame).toUpperCase(); - durationWidth = (int) Math.ceil(gamePaint.measureText(str)); - videoInfoLayout = new StaticLayout(str, gamePaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + durationWidth = (int) Math.ceil(Theme.chat_gamePaint.measureText(str)); + videoInfoLayout = new StaticLayout(str, Theme.chat_gamePaint, durationWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } } else { photoImage.setImageBitmap((Drawable) null); - linkPreviewHeight -= dp(6); - totalHeight += dp(4); + linkPreviewHeight -= AndroidUtilities.dp(6); + totalHeight += AndroidUtilities.dp(4); + } + if (hasInvoicePreview) { + CharSequence str; + if ((messageObject.messageOwner.media.flags & 4) != 0) { + str = LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt).toUpperCase(); + } else { + if (messageObject.messageOwner.media.test) { + str = LocaleController.getString("PaymentTestInvoice", R.string.PaymentTestInvoice).toUpperCase(); + } else { + str = LocaleController.getString("PaymentInvoice", R.string.PaymentInvoice).toUpperCase(); + } + } + String price = LocaleController.getInstance().formatCurrencyString(messageObject.messageOwner.media.total_amount, messageObject.messageOwner.media.currency); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(price + " " + str); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), 0, price.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + durationWidth = (int) Math.ceil(Theme.chat_shipmentPaint.measureText(stringBuilder, 0, stringBuilder.length())); + videoInfoLayout = new StaticLayout(stringBuilder, Theme.chat_shipmentPaint, durationWidth + AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (!drawPhotoImage) { + totalHeight += AndroidUtilities.dp(6); + if (durationWidth + timeWidth + AndroidUtilities.dp(6) > maxWidth) { + maxChildWidth = Math.max(durationWidth, maxChildWidth); + totalHeight += AndroidUtilities.dp(12); + } else { + maxChildWidth = Math.max(durationWidth + timeWidth + AndroidUtilities.dp(6), maxChildWidth); + } + } } if (hasGamePreview && messageObject.textHeight != 0) { linkPreviewHeight += messageObject.textHeight + AndroidUtilities.dp(6); - totalHeight += dp(4); + totalHeight += AndroidUtilities.dp(4); } calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } + if (drawInstantView) { + instantWidth = AndroidUtilities.dp(12 + 9 + 12); + String str = LocaleController.getString("InstantView", R.string.InstantView); + int mWidth = backgroundWidth - AndroidUtilities.dp(10 + 24 + 10 + 31); + instantViewLayout = new StaticLayout(TextUtils.ellipsize(str, Theme.chat_instantViewPaint, mWidth, TextUtils.TruncateAt.END), Theme.chat_instantViewPaint, mWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (instantViewLayout != null && instantViewLayout.getLineCount() > 0) { + instantTextX = (int) -instantViewLayout.getLineLeft(0); + instantWidth += instantViewLayout.getLineWidth(0) + instantTextX; + } + linkPreviewHeight += AndroidUtilities.dp(40); + totalHeight += AndroidUtilities.dp(40); + } } else { photoImage.setImageBitmap((Drawable) null); calcBackgroundWidth(maxWidth, timeMore, maxChildWidth); } + } else if (messageObject.type == 16) { + drawName = false; + drawForwardedName = false; + drawPhotoImage = false; + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + } else { + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); + } + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31); + + int maxWidth = getMaxNameWidth() - AndroidUtilities.dp(50); + if (maxWidth < 0) { + maxWidth = AndroidUtilities.dp(10); + } + + String text; + String time = LocaleController.getInstance().formatterDay.format((long) (messageObject.messageOwner.date) * 1000); + TLRPC.TL_messageActionPhoneCall call = (TLRPC.TL_messageActionPhoneCall) messageObject.messageOwner.action; + boolean isMissed = call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed; + if (messageObject.isOutOwner()) { + if (isMissed) { + text = LocaleController.getString("CallMessageOutgoingMissed", R.string.CallMessageOutgoingMissed); + } else { + text = LocaleController.getString("CallMessageOutgoing", R.string.CallMessageOutgoing); + } + } else { + if (isMissed) { + text = LocaleController.getString("CallMessageIncomingMissed", R.string.CallMessageIncomingMissed); + } else if(call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { + text = LocaleController.getString("CallMessageIncomingDeclined", R.string.CallMessageIncomingDeclined); + } else { + text = LocaleController.getString("CallMessageIncoming", R.string.CallMessageIncoming); + } + } + if (call.duration > 0) { + time += ", " + LocaleController.formatCallDuration(call.duration); + } + + titleLayout = new StaticLayout(TextUtils.ellipsize(text, Theme.chat_audioTitlePaint, maxWidth, TextUtils.TruncateAt.END), Theme.chat_audioTitlePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + docTitleLayout = new StaticLayout(TextUtils.ellipsize(time, Theme.chat_contactPhonePaint, maxWidth, TextUtils.TruncateAt.END), Theme.chat_contactPhonePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + + setMessageObjectInternal(messageObject); + + totalHeight = AndroidUtilities.dp(65) + namesOffset; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); + } } else if (messageObject.type == 12) { drawName = false; drawForwardedName = true; drawPhotoImage = true; - photoImage.setRoundRadius(dp(22)); - if (isTablet()) { - backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + photoImage.setRoundRadius(AndroidUtilities.dp(22)); + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { - backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } - availableTimeWidth = backgroundWidth - dp(31); + availableTimeWidth = backgroundWidth - AndroidUtilities.dp(31); int uid = messageObject.messageOwner.media.user_id; TLRPC.User user = MessagesController.getInstance().getUser(uid); - int maxWidth = getMaxNameWidth() - dp(110); + int maxWidth = getMaxNameWidth() - AndroidUtilities.dp(110); if (maxWidth < 0) { - maxWidth = dp(10); + maxWidth = AndroidUtilities.dp(10); } TLRPC.FileLocation currentPhoto = null; @@ -2419,7 +2486,7 @@ public void setMessageObject(MessageObject messageObject) { } contactAvatarDrawable.setInfo(user); } - photoImage.setImage(currentPhoto, "50_50", user != null ? contactAvatarDrawable : Theme.contactDrawable[messageObject.isOutOwner() ? 1 : 0], null, false); + photoImage.setImage(currentPhoto, "50_50", user != null ? contactAvatarDrawable : Theme.chat_contactDrawable[messageObject.isOutOwner() ? 1 : 0], null, false); String phone = messageObject.messageOwner.media.phone_number; if (phone != null && phone.length() != 0) { @@ -2432,18 +2499,21 @@ public void setMessageObject(MessageObject messageObject) { if (currentNameString.length() == 0) { currentNameString = phone; } - titleLayout = new StaticLayout(TextUtils.ellipsize(currentNameString, contactNamePaint, maxWidth, TextUtils.TruncateAt.END), contactNamePaint, maxWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - docTitleLayout = new StaticLayout(TextUtils.ellipsize(phone.replace('\n', ' '), contactPhonePaint, maxWidth, TextUtils.TruncateAt.END), contactPhonePaint, maxWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + titleLayout = new StaticLayout(TextUtils.ellipsize(currentNameString, Theme.chat_contactNamePaint, maxWidth, TextUtils.TruncateAt.END), Theme.chat_contactNamePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + docTitleLayout = new StaticLayout(TextUtils.ellipsize(phone.replace('\n', ' '), Theme.chat_contactPhonePaint, maxWidth, TextUtils.TruncateAt.END), Theme.chat_contactPhonePaint, maxWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); setMessageObjectInternal(messageObject); if (drawForwardedName && messageObject.isForwarded()) { - namesOffset += dp(5); + namesOffset += AndroidUtilities.dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { - namesOffset += dp(7); + namesOffset += AndroidUtilities.dp(7); } - totalHeight = dp(70) + namesOffset; + totalHeight = AndroidUtilities.dp(70) + namesOffset; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); + } if (docTitleLayout.getLineCount() > 0) { int timeLeft = backgroundWidth - AndroidUtilities.dp(40 + 18 + 44 + 8) - (int) Math.ceil(docTitleLayout.getLineWidth(0)); if (timeLeft < timeWidth) { @@ -2452,28 +2522,34 @@ public void setMessageObject(MessageObject messageObject) { } } else if (messageObject.type == 2) { drawForwardedName = true; - if (isTablet()) { - backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { - backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } createDocumentLayout(backgroundWidth, messageObject); setMessageObjectInternal(messageObject); - totalHeight = dp(70) + namesOffset; + totalHeight = AndroidUtilities.dp(70) + namesOffset; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); + } } else if (messageObject.type == 14) { - if (isTablet()) { - backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { - backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } createDocumentLayout(backgroundWidth, messageObject); setMessageObjectInternal(messageObject); - totalHeight = dp(82) + namesOffset; + totalHeight = AndroidUtilities.dp(82) + namesOffset; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); + } } else { drawForwardedName = messageObject.messageOwner.fwd_from != null && messageObject.type != 13; mediaBackground = messageObject.type != 9; @@ -2492,25 +2568,25 @@ public void setMessageObject(MessageObject messageObject) { photoImage.setForcePreview(messageObject.isSecretPhoto()); if (messageObject.type == 9) { - if (isTablet()) { - backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { - backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } if (checkNeedDrawShareButton(messageObject)) { - backgroundWidth -= dp(20); + backgroundWidth -= AndroidUtilities.dp(20); } - int maxWidth = backgroundWidth - dp(86 + 52); + int maxWidth = backgroundWidth - AndroidUtilities.dp(86 + 52); createDocumentLayout(maxWidth, messageObject); if (!TextUtils.isEmpty(messageObject.caption)) { maxWidth += AndroidUtilities.dp(86); } if (drawPhotoImage) { - photoWidth = dp(86); - photoHeight = dp(86); + photoWidth = AndroidUtilities.dp(86); + photoHeight = AndroidUtilities.dp(86); } else { - photoWidth = dp(56); - photoHeight = dp(56); + photoWidth = AndroidUtilities.dp(56); + photoHeight = AndroidUtilities.dp(56); maxWidth += AndroidUtilities.dp(TextUtils.isEmpty(messageObject.caption) ? 51 : 21); } availableTimeWidth = maxWidth; @@ -2528,37 +2604,37 @@ public void setMessageObject(MessageObject messageObject) { double lon = messageObject.messageOwner.media.geo._long; if (messageObject.messageOwner.media.title != null && messageObject.messageOwner.media.title.length() > 0) { - if (isTablet()) { - backgroundWidth = Math.min(getMinTabletSide() - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + if (AndroidUtilities.isTablet()) { + backgroundWidth = Math.min(AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } else { - backgroundWidth = Math.min(displaySize.x - dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), dp(270)); + backgroundWidth = Math.min(AndroidUtilities.displaySize.x - AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 102 : 50), AndroidUtilities.dp(270)); } if (checkNeedDrawShareButton(messageObject)) { - backgroundWidth -= dp(20); + backgroundWidth -= AndroidUtilities.dp(20); } - int maxWidth = backgroundWidth - dp(86 + 37); + int maxWidth = backgroundWidth - AndroidUtilities.dp(86 + 37); - docTitleLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, 2); + docTitleLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.title, Theme.chat_locationTitlePaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, 2); int lineCount = docTitleLayout.getLineCount(); if (messageObject.messageOwner.media.address != null && messageObject.messageOwner.media.address.length() > 0) { - infoLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.address, locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, Math.min(3, 3 - lineCount)); + infoLayout = StaticLayoutEx.createStaticLayout(messageObject.messageOwner.media.address, Theme.chat_locationAddressPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false, TextUtils.TruncateAt.END, maxWidth, Math.min(3, 3 - lineCount)); } else { infoLayout = null; } mediaBackground = false; availableTimeWidth = maxWidth; - photoWidth = dp(86); - photoHeight = dp(86); - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); + photoWidth = AndroidUtilities.dp(86); + photoHeight = AndroidUtilities.dp(86); + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=72x72&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); } else { - availableTimeWidth = dp(200 - 14); - photoWidth = dp(200); - photoHeight = dp(100); - backgroundWidth = photoWidth + dp(12); - currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(density)), lat, lon); + availableTimeWidth = AndroidUtilities.dp(200 - 14); + photoWidth = AndroidUtilities.dp(200); + photoHeight = AndroidUtilities.dp(100); + backgroundWidth = photoWidth + AndroidUtilities.dp(12); + currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=15&size=200x100&maptype=roadmap&scale=%d&markers=color:red|size:mid|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); } - photoImage.setImage(currentUrl, null, messageObject.isOutOwner() ? Theme.geoOutDrawable : Theme.geoInDrawable, null, 0); + photoImage.setImage(currentUrl, null, Theme.chat_locationDrawable[messageObject.isOutOwner() ? 1 : 0], null, 0); } else if (messageObject.type == 13) { //webp drawBackground = false; for (int a = 0; a < messageObject.messageOwner.media.document.attributes.size(); a++) { @@ -2571,14 +2647,14 @@ public void setMessageObject(MessageObject messageObject) { } float maxHeight; float maxWidth; - if (isTablet()) { - maxHeight = maxWidth = getMinTabletSide() * 0.4f; + if (AndroidUtilities.isTablet()) { + maxHeight = maxWidth = AndroidUtilities.getMinTabletSide() * 0.4f; } else { - maxHeight = maxWidth = Math.min(displaySize.x, displaySize.y) * 0.5f; + maxHeight = maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f; } if (photoWidth == 0) { photoHeight = (int) maxHeight; - photoWidth = photoHeight + dp(100); + photoWidth = photoHeight + AndroidUtilities.dp(100); } photoHeight *= maxWidth / photoWidth; photoWidth = (int) maxWidth; @@ -2587,8 +2663,8 @@ public void setMessageObject(MessageObject messageObject) { photoHeight = (int) maxHeight; } documentAttachType = DOCUMENT_ATTACH_TYPE_STICKER; - availableTimeWidth = photoWidth - dp(14); - backgroundWidth = photoWidth + dp(12); + availableTimeWidth = photoWidth - AndroidUtilities.dp(14); + backgroundWidth = photoWidth + AndroidUtilities.dp(12); currentPhotoObjectThumb = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, 80); if (messageObject.attachPathExists) { photoImage.setImage(null, messageObject.messageOwner.attachPath, @@ -2607,22 +2683,22 @@ public void setMessageObject(MessageObject messageObject) { } } else { int maxPhotoWidth; - if (isTablet()) { - maxPhotoWidth = photoWidth = (int) (getMinTabletSide() * 0.7f); + if (AndroidUtilities.isTablet()) { + maxPhotoWidth = photoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); } else { - maxPhotoWidth = photoWidth = (int) (Math.min(displaySize.x, displaySize.y) * 0.7f); + maxPhotoWidth = photoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); } - photoHeight = photoWidth + dp(100); + photoHeight = photoWidth + AndroidUtilities.dp(100); if (checkNeedDrawShareButton(messageObject)) { - maxPhotoWidth -= dp(20); - photoWidth -= dp(20); + maxPhotoWidth -= AndroidUtilities.dp(20); + photoWidth -= AndroidUtilities.dp(20); } - if (photoWidth > getPhotoSize()) { - photoWidth = getPhotoSize(); + if (photoWidth > AndroidUtilities.getPhotoSize()) { + photoWidth = AndroidUtilities.getPhotoSize(); } - if (photoHeight > getPhotoSize()) { - photoHeight = getPhotoSize(); + if (photoHeight > AndroidUtilities.getPhotoSize()) { + photoHeight = AndroidUtilities.getPhotoSize(); } if (messageObject.type == 1) { //photo @@ -2634,9 +2710,9 @@ public void setMessageObject(MessageObject messageObject) { photoImage.setShouldGenerateQualityThumb(true); photoImage.setParentMessageObject(messageObject); } else if (messageObject.type == 8) { //gif - String str = formatFileSize(messageObject.messageOwner.media.document.size); - infoWidth = (int) Math.ceil(infoPaint.measureText(str)); - infoLayout = new StaticLayout(str, infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + String str = AndroidUtilities.formatFileSize(messageObject.messageOwner.media.document.size); + infoWidth = (int) Math.ceil(Theme.chat_infoPaint.measureText(str)); + infoLayout = new StaticLayout(str, Theme.chat_infoPaint, infoWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); photoImage.setNeedsQualityThumb(true); photoImage.setShouldGenerateQualityThumb(true); @@ -2647,7 +2723,7 @@ public void setMessageObject(MessageObject messageObject) { mediaBackground = false; } - currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, getPhotoSize()); + currentPhotoObject = FileLoader.getClosestPhotoSizeWithSize(messageObject.photoThumbs, AndroidUtilities.getPhotoSize()); int w = 0; int h = 0; @@ -2661,18 +2737,18 @@ public void setMessageObject(MessageObject messageObject) { w = (int) (currentPhotoObject.w / scale); h = (int) (currentPhotoObject.h / scale); if (w == 0) { - w = dp(150); + w = AndroidUtilities.dp(150); } if (h == 0) { - h = dp(150); + h = AndroidUtilities.dp(150); } if (h > photoHeight) { float scale2 = h; h = photoHeight; scale2 /= h; w = (int) (w / scale2); - } else if (h < dp(120)) { - h = dp(120); + } else if (h < AndroidUtilities.dp(120)) { + h = AndroidUtilities.dp(120); float hScale = (float) currentPhotoObject.h / h; if (currentPhotoObject.w / hScale < photoWidth) { w = (int) (currentPhotoObject.w / hScale); @@ -2692,8 +2768,8 @@ public void setMessageObject(MessageObject messageObject) { h = photoHeight; scale2 /= h; w = (int) (w / scale2); - } else if (h < dp(120)) { - h = dp(120); + } else if (h < AndroidUtilities.dp(120)) { + h = AndroidUtilities.dp(120); float hScale = (float) attribute.h / h; if (attribute.w / hScale < photoWidth) { w = (int) (attribute.w / hScale); @@ -2706,52 +2782,57 @@ public void setMessageObject(MessageObject messageObject) { if (w == 0 || h == 0) { - w = h = dp(150); + w = h = AndroidUtilities.dp(150); } if (messageObject.type == 3) { - if (w < infoWidth + dp(16 + 24)) { - w = infoWidth + dp(16 + 24); + if (w < infoWidth + AndroidUtilities.dp(16 + 24)) { + w = infoWidth + AndroidUtilities.dp(16 + 24); } } - availableTimeWidth = maxPhotoWidth - dp(14); + availableTimeWidth = maxPhotoWidth - AndroidUtilities.dp(14); measureTime(messageObject); - int timeWidthTotal = timeWidth + dp(14 + (messageObject.isOutOwner() ? 20 : 0)); + int timeWidthTotal = timeWidth + AndroidUtilities.dp(14 + (messageObject.isOutOwner() ? 20 : 0)); if (w < timeWidthTotal) { w = timeWidthTotal; } if (messageObject.isSecretPhoto()) { - if (isTablet()) { - w = h = (int) (getMinTabletSide() * 0.5f); + if (AndroidUtilities.isTablet()) { + w = h = (int) (AndroidUtilities.getMinTabletSide() * 0.5f); } else { - w = h = (int) (Math.min(displaySize.x, displaySize.y) * 0.5f); + w = h = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.5f); } } + if (messageObject.isVideoVoice()) { + w = h = Math.min(w, h); + drawBackground = false; + photoImage.setRoundRadius(w / 2); + } photoWidth = w; photoHeight = h; - backgroundWidth = w + dp(12); + backgroundWidth = w + AndroidUtilities.dp(12); if (!mediaBackground) { - backgroundWidth += dp(9); + backgroundWidth += AndroidUtilities.dp(9); } if (messageObject.caption != null) { try { - captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), photoWidth - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + captionLayout = new StaticLayout(messageObject.caption, Theme.chat_msgTextPaint, photoWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (captionLayout.getLineCount() > 0) { captionHeight = captionLayout.getHeight(); - additionHeight += captionHeight + dp(9); + additionHeight += captionHeight + AndroidUtilities.dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); - if (photoWidth - dp(8) - lastLineWidth < timeWidthTotal) { - additionHeight += dp(14); + if (photoWidth - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { + additionHeight += AndroidUtilities.dp(14); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / density), (int) (h / density)); + currentPhotoFilter = String.format(Locale.US, "%d_%d", (int) (w / AndroidUtilities.density), (int) (h / AndroidUtilities.density)); if (messageObject.photoThumbs != null && messageObject.photoThumbs.size() > 1 || messageObject.type == 3 || messageObject.type == 8) { if (messageObject.isSecretPhoto()) { currentPhotoFilter += "_b2"; @@ -2769,26 +2850,30 @@ public void setMessageObject(MessageObject messageObject) { } if (messageObject.type == 1) { - if (currentPhotoObject != null) { - boolean photoExist = true; - String fileName = FileLoader.getAttachFileName(currentPhotoObject); - if (messageObject.mediaExists) { - MediaController.getInstance().removeLoadingFileObserver(this); - } else { - photoExist = false; - } - if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { - photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); - } else { - photoNotSet = true; - if (currentPhotoObjectThumb != null) { - photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); + if (messageObject.useCustomPhoto) { + photoImage.setImageBitmap(getResources().getDrawable(R.drawable.theme_preview_image)); + } else { + if (currentPhotoObject != null) { + boolean photoExist = true; + String fileName = FileLoader.getAttachFileName(currentPhotoObject); + if (messageObject.mediaExists) { + MediaController.getInstance().removeLoadingFileObserver(this); } else { - photoImage.setImageBitmap((Drawable) null); + photoExist = false; + } + if (photoExist || MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_PHOTO) || FileLoader.getInstance().isLoadingFile(fileName)) { + photoImage.setImage(currentPhotoObject.location, currentPhotoFilter, currentPhotoObjectThumb != null ? currentPhotoObjectThumb.location : null, currentPhotoFilter, noSize ? 0 : currentPhotoObject.size, null, false); + } else { + photoNotSet = true; + if (currentPhotoObjectThumb != null) { + photoImage.setImage(null, null, currentPhotoObjectThumb.location, currentPhotoFilter, 0, null, false); + } else { + photoImage.setImageBitmap((Drawable) null); + } } + } else { + photoImage.setImageBitmap((BitmapDrawable) null); } - } else { - photoImage.setImageBitmap((BitmapDrawable) null); } } else if (messageObject.type == 8) { String fileName = FileLoader.getAttachFileName(messageObject.messageOwner.media.document); @@ -2816,69 +2901,90 @@ public void setMessageObject(MessageObject messageObject) { setMessageObjectInternal(messageObject); if (drawForwardedName) { - namesOffset += dp(5); + namesOffset += AndroidUtilities.dp(5); } else if (drawNameLayout && messageObject.messageOwner.reply_to_msg_id == 0) { - namesOffset += dp(7); + namesOffset += AndroidUtilities.dp(7); + } + totalHeight = photoHeight + AndroidUtilities.dp(14) + namesOffset + additionHeight; + if (pinnedTop) { + namesOffset -= AndroidUtilities.dp(1); } + photoImage.setImageCoords(0, AndroidUtilities.dp(7) + namesOffset, photoWidth, photoHeight); invalidate(); - - photoImage.setImageCoords(0, dp(7) + namesOffset, photoWidth, photoHeight); - totalHeight = photoHeight + dp(14) + namesOffset + additionHeight; } if (captionLayout == null && messageObject.caption != null && messageObject.type != 13) { try { int width = backgroundWidth - AndroidUtilities.dp(31); - captionLayout = new StaticLayout(messageObject.caption, MessageObject.getTextPaint(), width - dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + captionLayout = new StaticLayout(messageObject.caption, Theme.chat_msgTextPaint, width - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (captionLayout.getLineCount() > 0) { - int timeWidthTotal = timeWidth + (messageObject.isOutOwner() ? dp(20) : 0); + int timeWidthTotal = timeWidth + (messageObject.isOutOwner() ? AndroidUtilities.dp(20) : 0); captionHeight = captionLayout.getHeight(); - totalHeight += captionHeight + dp(9); + totalHeight += captionHeight + AndroidUtilities.dp(9); float lastLineWidth = captionLayout.getLineWidth(captionLayout.getLineCount() - 1) + captionLayout.getLineLeft(captionLayout.getLineCount() - 1); - if (width - dp(8) - lastLineWidth < timeWidthTotal) { - totalHeight += dp(14); - captionHeight += dp(14); + if (width - AndroidUtilities.dp(8) - lastLineWidth < timeWidthTotal) { + totalHeight += AndroidUtilities.dp(14); + captionHeight += AndroidUtilities.dp(14); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } botButtons.clear(); if (messageIdChanged) { botButtonsByData.clear(); + botButtonsByPosition.clear(); + botButtonsLayout = null; } if (messageObject.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) { int rows = messageObject.messageOwner.reply_markup.rows.size(); - substractBackgroundHeight = keyboardHeight = dp(44 + 4) * rows + dp(1); + substractBackgroundHeight = keyboardHeight = AndroidUtilities.dp(44 + 4) * rows + AndroidUtilities.dp(1); widthForButtons = backgroundWidth; boolean fullWidth = false; if (messageObject.wantedBotKeyboardWidth > widthForButtons) { - int maxButtonWidth = -dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 62 : 10); - if (isTablet()) { - maxButtonWidth += getMinTabletSide(); + int maxButtonWidth = -AndroidUtilities.dp(isChat && messageObject.isFromUser() && !messageObject.isOutOwner() ? 62 : 10); + if (AndroidUtilities.isTablet()) { + maxButtonWidth += AndroidUtilities.getMinTabletSide(); } else { - maxButtonWidth += Math.min(displaySize.x, displaySize.y); + maxButtonWidth += Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); } widthForButtons = Math.max(backgroundWidth, Math.min(messageObject.wantedBotKeyboardWidth, maxButtonWidth)); fullWidth = true; } int maxButtonsWidth = 0; + HashMap oldByData = new HashMap<>(botButtonsByData); + HashMap oldByPosition; + if (messageObject.botButtonsLayout != null && botButtonsLayout != null && botButtonsLayout.equals(messageObject.botButtonsLayout.toString())) { + oldByPosition = new HashMap<>(botButtonsByPosition); + } else { + if (messageObject.botButtonsLayout != null) { + botButtonsLayout = messageObject.botButtonsLayout.toString(); + } + oldByPosition = null; + } + botButtonsByData.clear(); for (int a = 0; a < rows; a++) { TLRPC.TL_keyboardButtonRow row = messageObject.messageOwner.reply_markup.rows.get(a); int buttonsCount = row.buttons.size(); if (buttonsCount == 0) { continue; } - int buttonWidth = (widthForButtons - (dp(5) * (buttonsCount - 1)) - dp(!fullWidth && mediaBackground ? 0 : 9) - dp(2)) / buttonsCount; + int buttonWidth = (widthForButtons - (AndroidUtilities.dp(5) * (buttonsCount - 1)) - AndroidUtilities.dp(!fullWidth && mediaBackground ? 0 : 9) - AndroidUtilities.dp(2)) / buttonsCount; for (int b = 0; b < row.buttons.size(); b++) { BotButton botButton = new BotButton(); botButton.button = row.buttons.get(b); String key = Utilities.bytesToHex(botButton.button.data); - BotButton oldButton = botButtonsByData.get(key); + String position = a + "" + b; + BotButton oldButton; + if (oldByPosition != null) { + oldButton = oldByPosition.get(position); + } else { + oldButton = oldByData.get(key); + } if (oldButton != null) { botButton.progressAlpha = oldButton.progressAlpha; botButton.angle = oldButton.angle; @@ -2887,13 +2993,19 @@ public void setMessageObject(MessageObject messageObject) { botButton.lastUpdateTime = System.currentTimeMillis(); } botButtonsByData.put(key, botButton); - botButton.x = b * (buttonWidth + dp(5)); - botButton.y = a * dp(44 + 4) + dp(5); + botButtonsByPosition.put(position, botButton); + botButton.x = b * (buttonWidth + AndroidUtilities.dp(5)); + botButton.y = a * AndroidUtilities.dp(44 + 4) + AndroidUtilities.dp(5); botButton.width = buttonWidth; - botButton.height = dp(44); - CharSequence buttonText = Emoji.replaceEmoji(botButton.button.text, botButtonPaint.getFontMetricsInt(), dp(15), false); - buttonText = TextUtils.ellipsize(buttonText, botButtonPaint, buttonWidth - dp(10), TextUtils.TruncateAt.END); - botButton.title = new StaticLayout(buttonText, botButtonPaint, buttonWidth - dp(10), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + botButton.height = AndroidUtilities.dp(44); + CharSequence buttonText; + if (botButton.button instanceof TLRPC.TL_keyboardButtonBuy && (messageObject.messageOwner.media.flags & 4) != 0) { + buttonText = LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt); + } else { + buttonText = Emoji.replaceEmoji(botButton.button.text, Theme.chat_botButtonPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false); + buttonText = TextUtils.ellipsize(buttonText, Theme.chat_botButtonPaint, buttonWidth - AndroidUtilities.dp(10), TextUtils.TruncateAt.END); + } + botButton.title = new StaticLayout(buttonText, Theme.chat_botButtonPaint, buttonWidth - AndroidUtilities.dp(10), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); botButtons.add(botButton); if (b == row.buttons.size() - 1) { maxButtonsWidth = Math.max(maxButtonsWidth, botButton.x + botButton.width); @@ -2905,6 +3017,11 @@ public void setMessageObject(MessageObject messageObject) { substractBackgroundHeight = 0; keyboardHeight = 0; } + if (pinnedBottom && pinnedTop) { + totalHeight -= AndroidUtilities.dp(2); + } else if (pinnedBottom) { + totalHeight -= AndroidUtilities.dp(1); + } } updateWaveform(); updateButtonState(dataChanged); @@ -2924,7 +3041,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { inLayout = true; MessageObject messageObject = currentMessageObject; currentMessageObject = null; - setMessageObject(messageObject); + setMessageObject(messageObject, pinnedBottom, pinnedTop); inLayout = false; } setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), totalHeight + keyboardHeight); @@ -2942,177 +3059,186 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto layoutWidth = getMeasuredWidth(); layoutHeight = getMeasuredHeight() - substractBackgroundHeight; if (timeTextWidth < 0) { - timeTextWidth = dp(10); + timeTextWidth = AndroidUtilities.dp(10); } - timeLayout = new StaticLayout(currentTimeString, timePaint, timeTextWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + timeLayout = new StaticLayout(currentTimeString, Theme.chat_timePaint, timeTextWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (!mediaBackground) { if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); + timeX = backgroundWidth - AndroidUtilities.dp(9) - timeWidth + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(48) : 0); } else { - timeX = layoutWidth - timeWidth - dp(38.5f); + timeX = layoutWidth - timeWidth - AndroidUtilities.dp(38.5f); } } else { if (!currentMessageObject.isOutOwner()) { - timeX = backgroundWidth - dp(4) - timeWidth + (isChat && currentMessageObject.isFromUser() ? dp(48) : 0); + timeX = backgroundWidth - AndroidUtilities.dp(4) - timeWidth + (isChat && currentMessageObject.isFromUser() ? AndroidUtilities.dp(48) : 0); } else { - timeX = layoutWidth - timeWidth - dp(42.0f); + timeX = layoutWidth - timeWidth - AndroidUtilities.dp(42.0f); } } if ((currentMessageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { - viewsLayout = new StaticLayout(currentViewsString, timePaint, viewsTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + viewsLayout = new StaticLayout(currentViewsString, Theme.chat_timePaint, viewsTextWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } else { viewsLayout = null; } if (isAvatarVisible) { - avatarImage.setImageCoords(dp(6), layoutHeight - dp(44), dp(42), dp(42)); + avatarImage.setImageCoords(AndroidUtilities.dp(6), avatarImage.getImageY(), AndroidUtilities.dp(42), AndroidUtilities.dp(42)); } wasLayout = true; } if (currentMessageObject.type == 0) { - textY = dp(10) + namesOffset; + textY = AndroidUtilities.dp(10) + namesOffset; } if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { if (currentMessageObject.isOutOwner()) { - seekBarX = layoutWidth - backgroundWidth + dp(57); - buttonX = layoutWidth - backgroundWidth + dp(14); - timeAudioX = layoutWidth - backgroundWidth + dp(67); + seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(57); + buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); + timeAudioX = layoutWidth - backgroundWidth + AndroidUtilities.dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { - seekBarX = dp(114); - buttonX = dp(71); - timeAudioX = dp(124); + seekBarX = AndroidUtilities.dp(114); + buttonX = AndroidUtilities.dp(71); + timeAudioX = AndroidUtilities.dp(124); } else { - seekBarX = dp(66); - buttonX = dp(23); - timeAudioX = dp(76); + seekBarX = AndroidUtilities.dp(66); + buttonX = AndroidUtilities.dp(23); + timeAudioX = AndroidUtilities.dp(76); } } if (hasLinkPreview) { - seekBarX += dp(10); - buttonX += dp(10); - timeAudioX += dp(10); + seekBarX += AndroidUtilities.dp(10); + buttonX += AndroidUtilities.dp(10); + timeAudioX += AndroidUtilities.dp(10); } - seekBarWaveform.setSize(backgroundWidth - dp(92 + (hasLinkPreview ? 10 : 0)), dp(30)); - seekBar.setSize(backgroundWidth - dp(72 + (hasLinkPreview ? 10 : 0)), dp(30)); - seekBarY = dp(13) + namesOffset + mediaOffsetY; - buttonY = dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); + seekBarWaveform.setSize(backgroundWidth - AndroidUtilities.dp(92 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + seekBar.setSize(backgroundWidth - AndroidUtilities.dp(72 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + seekBarY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; + buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { - seekBarX = layoutWidth - backgroundWidth + dp(56); - buttonX = layoutWidth - backgroundWidth + dp(14); - timeAudioX = layoutWidth - backgroundWidth + dp(67); + seekBarX = layoutWidth - backgroundWidth + AndroidUtilities.dp(56); + buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); + timeAudioX = layoutWidth - backgroundWidth + AndroidUtilities.dp(67); } else { if (isChat && currentMessageObject.isFromUser()) { - seekBarX = dp(113); - buttonX = dp(71); - timeAudioX = dp(124); + seekBarX = AndroidUtilities.dp(113); + buttonX = AndroidUtilities.dp(71); + timeAudioX = AndroidUtilities.dp(124); } else { - seekBarX = dp(65); - buttonX = dp(23); - timeAudioX = dp(76); + seekBarX = AndroidUtilities.dp(65); + buttonX = AndroidUtilities.dp(23); + timeAudioX = AndroidUtilities.dp(76); } } if (hasLinkPreview) { - seekBarX += dp(10); - buttonX += dp(10); - timeAudioX += dp(10); + seekBarX += AndroidUtilities.dp(10); + buttonX += AndroidUtilities.dp(10); + timeAudioX += AndroidUtilities.dp(10); } - seekBar.setSize(backgroundWidth - dp(65 + (hasLinkPreview ? 10 : 0)), dp(30)); - seekBarY = dp(29) + namesOffset + mediaOffsetY; - buttonY = dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); + seekBar.setSize(backgroundWidth - AndroidUtilities.dp(65 + (hasLinkPreview ? 10 : 0)), AndroidUtilities.dp(30)); + seekBarY = AndroidUtilities.dp(29) + namesOffset + mediaOffsetY; + buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); updateAudioProgress(); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT && !drawPhotoImage) { if (currentMessageObject.isOutOwner()) { - buttonX = layoutWidth - backgroundWidth + dp(14); + buttonX = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); } else { if (isChat && currentMessageObject.isFromUser()) { - buttonX = dp(71); + buttonX = AndroidUtilities.dp(71); } else { - buttonX = dp(23); + buttonX = AndroidUtilities.dp(23); } } if (hasLinkPreview) { - buttonX += dp(10); + buttonX += AndroidUtilities.dp(10); } - buttonY = dp(13) + namesOffset + mediaOffsetY; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(44), buttonY + dp(44)); - photoImage.setImageCoords(buttonX - dp(10), buttonY - dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); + buttonY = AndroidUtilities.dp(13) + namesOffset + mediaOffsetY; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(44), buttonY + AndroidUtilities.dp(44)); + photoImage.setImageCoords(buttonX - AndroidUtilities.dp(10), buttonY - AndroidUtilities.dp(10), photoImage.getImageWidth(), photoImage.getImageHeight()); } else if (currentMessageObject.type == 12) { int x; if (currentMessageObject.isOutOwner()) { - x = layoutWidth - backgroundWidth + dp(14); + x = layoutWidth - backgroundWidth + AndroidUtilities.dp(14); } else { if (isChat && currentMessageObject.isFromUser()) { - x = dp(72); + x = AndroidUtilities.dp(72); } else { - x = dp(23); + x = AndroidUtilities.dp(23); } } - photoImage.setImageCoords(x, dp(13) + namesOffset, dp(44), dp(44)); + photoImage.setImageCoords(x, AndroidUtilities.dp(13) + namesOffset, AndroidUtilities.dp(44), AndroidUtilities.dp(44)); } else { int x; if (currentMessageObject.isOutOwner()) { if (mediaBackground) { - x = layoutWidth - backgroundWidth - dp(3); + x = layoutWidth - backgroundWidth - AndroidUtilities.dp(3); } else { - x = layoutWidth - backgroundWidth + dp(6); + x = layoutWidth - backgroundWidth + AndroidUtilities.dp(6); } } else { if (isChat && currentMessageObject.isFromUser()) { - x = dp(63); + x = AndroidUtilities.dp(63); } else { - x = dp(15); + x = AndroidUtilities.dp(15); } } photoImage.setImageCoords(x, photoImage.getImageY(), photoImage.getImageWidth(), photoImage.getImageHeight()); - buttonX = (int) (x + (photoImage.getImageWidth() - dp(48)) / 2.0f); - buttonY = (int) (dp(7) + (photoImage.getImageHeight() - dp(48)) / 2.0f) + namesOffset; - radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(48), buttonY + dp(48)); - deleteProgressRect.set(buttonX + dp(3), buttonY + dp(3), buttonX + dp(45), buttonY + dp(45)); + buttonX = (int) (x + (photoImage.getImageWidth() - AndroidUtilities.dp(48)) / 2.0f); + buttonY = (int) (AndroidUtilities.dp(7) + (photoImage.getImageHeight() - AndroidUtilities.dp(48)) / 2.0f) + namesOffset; + radialProgress.setProgressRect(buttonX, buttonY, buttonX + AndroidUtilities.dp(48), buttonY + AndroidUtilities.dp(48)); + deleteProgressRect.set(buttonX + AndroidUtilities.dp(3), buttonY + AndroidUtilities.dp(3), buttonX + AndroidUtilities.dp(45), buttonY + AndroidUtilities.dp(45)); } } private void drawContent(Canvas canvas) { - if (needNewVisiblePart && currentMessageObject.type == 0) { getLocalVisibleRect(scrollRect); setVisiblePart(scrollRect.top, scrollRect.bottom - scrollRect.top); needNewVisiblePart = false; } + forceNotDrawTime = false; photoImage.setPressed(isDrawSelectedBackground()); photoImage.setVisible(!PhotoViewer.getInstance().isShowingImage(currentMessageObject), false); radialProgress.setHideCurrentDrawable(false); - radialProgress.setProgressColor(Theme.MSG_MEDIA_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(Theme.key_chat_mediaProgress)); boolean imageDrawn = false; if (currentMessageObject.type == 0) { if (currentMessageObject.isOutOwner()) { - textX = currentBackgroundDrawable.getBounds().left + dp(11); + textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); } else { - textX = currentBackgroundDrawable.getBounds().left + dp(17); + textX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17); } if (hasGamePreview) { - textX += dp(11); - textY = dp(14) + namesOffset; + textX += AndroidUtilities.dp(11); + textY = AndroidUtilities.dp(14) + namesOffset; + if (siteNameLayout != null) { + textY += siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); + } + } else if (hasInvoicePreview) { + textY = AndroidUtilities.dp(14) + namesOffset; if (siteNameLayout != null) { textY += siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); } } else { - textY = dp(10) + namesOffset; + textY = AndroidUtilities.dp(10) + namesOffset; } if (currentMessageObject.textLayoutBlocks != null && !currentMessageObject.textLayoutBlocks.isEmpty()) { + if (fullyDraw) { + firstVisibleBlockNum = 0; + lastVisibleBlockNum = currentMessageObject.textLayoutBlocks.size(); + } if (firstVisibleBlockNum >= 0) { for (int a = firstVisibleBlockNum; a <= lastVisibleBlockNum; a++) { if (a >= currentMessageObject.textLayoutBlocks.size()) { @@ -3120,65 +3246,90 @@ private void drawContent(Canvas canvas) { } MessageObject.TextLayoutBlock block = currentMessageObject.textLayoutBlocks.get(a); canvas.save(); - canvas.translate(textX - (int) Math.ceil(block.textXOffset), textY + block.textYOffset); + canvas.translate(textX - (block.isRtl() ? (int) Math.ceil(currentMessageObject.textXOffset) : 0), textY + block.textYOffset); if (pressedLink != null && a == linkBlockNum) { for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), urlPaint); + canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint); } } if (a == linkSelectionBlockNum && !urlPathSelection.isEmpty()) { for (int b = 0; b < urlPathSelection.size(); b++) { - canvas.drawPath(urlPathSelection.get(b), urlSelectionPaint); + canvas.drawPath(urlPathSelection.get(b), Theme.chat_textSearchSelectionPaint); } } try { block.textLayout.draw(canvas); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } canvas.restore(); } } } - if (hasLinkPreview || hasGamePreview) { + if (hasLinkPreview || hasGamePreview || hasInvoicePreview) { int startY; int linkX; if (hasGamePreview) { - startY = dp(14) + namesOffset; - linkX = textX - dp(10); + startY = AndroidUtilities.dp(14) + namesOffset; + linkX = textX - AndroidUtilities.dp(10); + } else if (hasInvoicePreview) { + startY = AndroidUtilities.dp(14) + namesOffset; + linkX = textX + AndroidUtilities.dp(1); } else { - startY = textY + currentMessageObject.textHeight + dp(8); - linkX = textX + dp(1); + startY = textY + currentMessageObject.textHeight + AndroidUtilities.dp(8); + linkX = textX + AndroidUtilities.dp(1); } int linkPreviewY = startY; int smallImageStartY = 0; - replyLinePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_WEB_PREVIEW_LINE_COLOR : Theme.MSG_IN_WEB_PREVIEW_LINE_COLOR); - canvas.drawRect(linkX, linkPreviewY - dp(3), linkX + dp(2), linkPreviewY + linkPreviewHeight + dp(3), replyLinePaint); + if (!hasInvoicePreview) { + Theme.chat_replyLinePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outPreviewLine : Theme.key_chat_inPreviewLine)); + canvas.drawRect(linkX, linkPreviewY - AndroidUtilities.dp(3), linkX + AndroidUtilities.dp(2), linkPreviewY + linkPreviewHeight + AndroidUtilities.dp(3), Theme.chat_replyLinePaint); + } if (siteNameLayout != null) { - replyNamePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_SITE_NAME_TEXT_COLOR : Theme.MSG_IN_SITE_NAME_TEXT_COLOR); + Theme.chat_replyNamePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outSiteNameText : Theme.key_chat_inSiteNameText)); canvas.save(); - canvas.translate(linkX + dp(10), linkPreviewY - dp(3)); + canvas.translate(linkX + (hasInvoicePreview ? 0 : AndroidUtilities.dp(10)), linkPreviewY - AndroidUtilities.dp(3)); siteNameLayout.draw(canvas); canvas.restore(); linkPreviewY += siteNameLayout.getLineBottom(siteNameLayout.getLineCount() - 1); } - if (hasGamePreview && currentMessageObject.textHeight != 0) { - startY += currentMessageObject.textHeight + dp(4); - linkPreviewY += currentMessageObject.textHeight + dp(4); + if ((hasGamePreview || hasInvoicePreview) && currentMessageObject.textHeight != 0) { + startY += currentMessageObject.textHeight + AndroidUtilities.dp(4); + linkPreviewY += currentMessageObject.textHeight + AndroidUtilities.dp(4); } - replyNamePaint.setColor(Theme.MSG_TEXT_COLOR); - replyTextPaint.setColor(Theme.MSG_TEXT_COLOR); + if (drawPhotoImage && drawInstantView) { + if (linkPreviewY != startY) { + linkPreviewY += AndroidUtilities.dp(2); + } + photoImage.setImageCoords(linkX + AndroidUtilities.dp(10), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); + if (drawImageButton) { + int size = AndroidUtilities.dp(48); + buttonX = (int) (photoImage.getImageX() + (photoImage.getImageWidth() - size) / 2.0f); + buttonY = (int) (photoImage.getImageY() + (photoImage.getImageHeight() - size) / 2.0f); + radialProgress.setProgressRect(buttonX, buttonY, buttonX + size, buttonY + size); + } + imageDrawn = photoImage.draw(canvas); + linkPreviewY += photoImage.getImageHeight() + AndroidUtilities.dp(6); + } + + if (currentMessageObject.isOutOwner()) { + Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + } else { + Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + } if (titleLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += dp(2); + linkPreviewY += AndroidUtilities.dp(2); } - smallImageStartY = linkPreviewY - dp(1); + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); canvas.save(); - canvas.translate(linkX + dp(10) + titleX, linkPreviewY - dp(3)); + canvas.translate(linkX + AndroidUtilities.dp(10) + titleX, linkPreviewY - AndroidUtilities.dp(3)); titleLayout.draw(canvas); canvas.restore(); linkPreviewY += titleLayout.getLineBottom(titleLayout.getLineCount() - 1); @@ -3186,13 +3337,13 @@ private void drawContent(Canvas canvas) { if (authorLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += dp(2); + linkPreviewY += AndroidUtilities.dp(2); } if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - dp(1); + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); } canvas.save(); - canvas.translate(linkX + dp(10) + authorX, linkPreviewY - dp(3)); + canvas.translate(linkX + AndroidUtilities.dp(10) + authorX, linkPreviewY - AndroidUtilities.dp(3)); authorLayout.draw(canvas); canvas.restore(); linkPreviewY += authorLayout.getLineBottom(authorLayout.getLineCount() - 1); @@ -3200,17 +3351,17 @@ private void drawContent(Canvas canvas) { if (descriptionLayout != null) { if (linkPreviewY != startY) { - linkPreviewY += dp(2); + linkPreviewY += AndroidUtilities.dp(2); } if (smallImageStartY == 0) { - smallImageStartY = linkPreviewY - dp(1); + smallImageStartY = linkPreviewY - AndroidUtilities.dp(1); } - descriptionY = linkPreviewY - dp(3); + descriptionY = linkPreviewY - AndroidUtilities.dp(3); canvas.save(); - canvas.translate(linkX + dp(10) + descriptionX, descriptionY); + canvas.translate(linkX + (hasInvoicePreview ? 0 : AndroidUtilities.dp(10)) + descriptionX, descriptionY); if (pressedLink != null && linkBlockNum == -10) { for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), urlPaint); + canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint); } } descriptionLayout.draw(canvas); @@ -3218,42 +3369,84 @@ private void drawContent(Canvas canvas) { linkPreviewY += descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1); } - if (drawPhotoImage) { + if (drawPhotoImage && !drawInstantView) { if (linkPreviewY != startY) { - linkPreviewY += dp(2); + linkPreviewY += AndroidUtilities.dp(2); } if (isSmallImage) { - photoImage.setImageCoords(linkX + backgroundWidth - dp(81), smallImageStartY, photoImage.getImageWidth(), photoImage.getImageHeight()); + photoImage.setImageCoords(linkX + backgroundWidth - AndroidUtilities.dp(81), smallImageStartY, photoImage.getImageWidth(), photoImage.getImageHeight()); } else { - photoImage.setImageCoords(linkX + dp(10), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); + photoImage.setImageCoords(linkX + (hasInvoicePreview ? -AndroidUtilities.dp(6.3f) : AndroidUtilities.dp(10)), linkPreviewY, photoImage.getImageWidth(), photoImage.getImageHeight()); if (drawImageButton) { - int size = dp(48); + int size = AndroidUtilities.dp(48); buttonX = (int) (photoImage.getImageX() + (photoImage.getImageWidth() - size) / 2.0f); buttonY = (int) (photoImage.getImageY() + (photoImage.getImageHeight() - size) / 2.0f); - radialProgress.setProgressRect(buttonX, buttonY, buttonX + dp(48), buttonY + dp(48)); + radialProgress.setProgressRect(buttonX, buttonY, buttonX + size, buttonY + size); } } imageDrawn = photoImage.draw(canvas); + } + if (videoInfoLayout != null && (!drawPhotoImage || photoImage.getVisible())) { + int x; + int y; + if (hasGamePreview || hasInvoicePreview) { + if (drawPhotoImage) { + x = photoImage.getImageX() + AndroidUtilities.dp(8.5f); + y = photoImage.getImageY() + AndroidUtilities.dp(6); + Theme.chat_timeBackgroundDrawable.setBounds(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + durationWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(16.5f)); + Theme.chat_timeBackgroundDrawable.draw(canvas); + } else { + x = linkX; + y = linkPreviewY; + } + } else { + x = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(8) - durationWidth; + y = photoImage.getImageY() + photoImage.getImageHeight() - AndroidUtilities.dp(19); + Theme.chat_timeBackgroundDrawable.setBounds(x - AndroidUtilities.dp(4), y - AndroidUtilities.dp(1.5f), x + durationWidth + AndroidUtilities.dp(4), y + AndroidUtilities.dp(14.5f)); + Theme.chat_timeBackgroundDrawable.draw(canvas); + } - if (videoInfoLayout != null) { - int x; - int y; - if (hasGamePreview) { - x = photoImage.getImageX() + dp(8.5f); - y = photoImage.getImageY() + dp(6); - Theme.timeBackgroundDrawable.setBounds(x - dp(4), y - dp(1.5f), x + durationWidth + dp(4), y + dp(16.5f)); - Theme.timeBackgroundDrawable.draw(canvas); + canvas.save(); + canvas.translate(x, y); + if (hasInvoicePreview) { + if (drawPhotoImage) { + Theme.chat_shipmentPaint.setColor(Theme.getColor(Theme.key_chat_previewGameText)); } else { - x = photoImage.getImageX() + photoImage.getImageWidth() - dp(8) - durationWidth; - y = photoImage.getImageY() + photoImage.getImageHeight() - dp(19); - Theme.timeBackgroundDrawable.setBounds(x - dp(4), y - dp(1.5f), x + durationWidth + dp(4), y + dp(14.5f)); - Theme.timeBackgroundDrawable.draw(canvas); + if (currentMessageObject.isOutOwner()) { + Theme.chat_shipmentPaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + } else { + Theme.chat_shipmentPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + } } + } + videoInfoLayout.draw(canvas); + canvas.restore(); + } + if (drawInstantView) { + Drawable instantDrawable; + int instantX = linkX + AndroidUtilities.dp(10); + int instantY = linkPreviewY + AndroidUtilities.dp(4); + Paint backPaint = Theme.chat_instantViewRectPaint; + if (currentMessageObject.isOutOwner()) { + instantDrawable = instantPressed ? Theme.chat_msgOutInstantSelectedDrawable : Theme.chat_msgOutInstantDrawable; + Theme.chat_instantViewPaint.setColor(Theme.getColor(instantPressed ? Theme.key_chat_outPreviewInstantSelectedText : Theme.key_chat_outPreviewInstantText)); + backPaint.setColor(Theme.getColor(instantPressed ? Theme.key_chat_outPreviewInstantSelectedText : Theme.key_chat_outPreviewInstantText)); + } else { + instantDrawable = instantPressed ? Theme.chat_msgInInstantSelectedDrawable : Theme.chat_msgInInstantDrawable; + Theme.chat_instantViewPaint.setColor(Theme.getColor(instantPressed ? Theme.key_chat_inPreviewInstantSelectedText : Theme.key_chat_inPreviewInstantText)); + backPaint.setColor(Theme.getColor(instantPressed ? Theme.key_chat_inPreviewInstantSelectedText : Theme.key_chat_inPreviewInstantText)); + } + + rect.set(instantX, instantY, instantX + instantWidth, instantY + AndroidUtilities.dp(30)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(3), AndroidUtilities.dp(3), backPaint); + setDrawableBounds(instantDrawable, instantX + AndroidUtilities.dp(9), instantY + AndroidUtilities.dp(9), AndroidUtilities.dp(9), AndroidUtilities.dp(13)); + instantDrawable.draw(canvas); + if (instantViewLayout != null) { canvas.save(); - canvas.translate(x, y); - videoInfoLayout.draw(canvas); + canvas.translate(instantX + instantTextX + AndroidUtilities.dp(24), instantY + AndroidUtilities.dp(8)); + instantViewLayout.draw(canvas); canvas.restore(); } } @@ -3273,42 +3466,42 @@ private void drawContent(Canvas canvas) { drawable = 5; } } - setDrawableBounds(Theme.photoStatesDrawables[drawable][buttonPressed], buttonX, buttonY); - Theme.photoStatesDrawables[drawable][buttonPressed].setAlpha((int) (255 * (1.0f - radialProgress.getAlpha()))); - Theme.photoStatesDrawables[drawable][buttonPressed].draw(canvas); + setDrawableBounds(Theme.chat_photoStatesDrawables[drawable][buttonPressed], buttonX, buttonY); + Theme.chat_photoStatesDrawables[drawable][buttonPressed].setAlpha((int) (255 * (1.0f - radialProgress.getAlpha()))); + Theme.chat_photoStatesDrawables[drawable][buttonPressed].draw(canvas); if (!currentMessageObject.isOutOwner() && currentMessageObject.messageOwner.destroyTime != 0) { long msTime = System.currentTimeMillis() + ConnectionsManager.getInstance().getTimeDifference() * 1000; float progress = Math.max(0, (long) currentMessageObject.messageOwner.destroyTime * 1000 - msTime) / (currentMessageObject.messageOwner.ttl * 1000.0f); - canvas.drawArc(deleteProgressRect, -90, -360 * progress, true, deleteProgressPaint); + canvas.drawArc(deleteProgressRect, -90, -360 * progress, true, Theme.chat_deleteProgressPaint); if (progress != 0) { - int offset = dp(2); + int offset = AndroidUtilities.dp(2); invalidate((int) deleteProgressRect.left - offset, (int) deleteProgressRect.top - offset, (int) deleteProgressRect.right + offset * 2, (int) deleteProgressRect.bottom + offset * 2); } updateSecretTimeText(currentMessageObject); } } - if (documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || currentMessageObject.type == 8) { + if ((documentAttachType == DOCUMENT_ATTACH_TYPE_GIF || currentMessageObject.type == 8) && !currentMessageObject.isVideoVoice()) { //TODO if (photoImage.getVisible() && !hasGamePreview) { - setDrawableBounds(Theme.docMenuDrawable[3], otherX = photoImage.getImageX() + photoImage.getImageWidth() - dp(14), otherY = photoImage.getImageY() + dp(8.1f)); - Theme.docMenuDrawable[3].draw(canvas); + setDrawableBounds(Theme.chat_msgMediaMenuDrawable, otherX = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(14), otherY = photoImage.getImageY() + AndroidUtilities.dp(8.1f)); + Theme.chat_msgMediaMenuDrawable.draw(canvas); } } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { if (currentMessageObject.isOutOwner()) { - audioTitlePaint.setColor(Theme.MSG_OUT_AUDIO_TITLE_TEXT_COLOR); - audioPerformerPaint.setColor(Theme.MSG_OUT_AUDIO_PERFORMER_TEXT_COLOR); - audioTimePaint.setColor(Theme.MSG_OUT_AUDIO_DURATION_TEXT_COLOR); - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_OUT_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_OUT_AUDIO_PROGRESS_COLOR); + Theme.chat_audioTitlePaint.setColor(Theme.getColor(Theme.key_chat_outAudioTitleText)); + Theme.chat_audioPerformerPaint.setColor(Theme.getColor(Theme.key_chat_outAudioPerfomerText)); + Theme.chat_audioTimePaint.setColor(Theme.getColor(Theme.key_chat_outAudioDurationText)); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_outAudioSelectedProgress : Theme.key_chat_outAudioProgress)); } else { - audioTitlePaint.setColor(Theme.MSG_IN_AUDIO_TITLE_TEXT_COLOR); - audioPerformerPaint.setColor(Theme.MSG_IN_AUDIO_PERFORMER_TEXT_COLOR); - audioTimePaint.setColor(Theme.MSG_IN_AUDIO_DURATION_TEXT_COLOR); - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_IN_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_IN_AUDIO_PROGRESS_COLOR); + Theme.chat_audioTitlePaint.setColor(Theme.getColor(Theme.key_chat_inAudioTitleText)); + Theme.chat_audioPerformerPaint.setColor(Theme.getColor(Theme.key_chat_inAudioPerfomerText)); + Theme.chat_audioTimePaint.setColor(Theme.getColor(Theme.key_chat_inAudioDurationText)); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); } radialProgress.draw(canvas); canvas.save(); - canvas.translate(timeAudioX + songX, dp(13) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX + songX, AndroidUtilities.dp(13) + namesOffset + mediaOffsetY); songLayout.draw(canvas); canvas.restore(); @@ -3317,37 +3510,37 @@ private void drawContent(Canvas canvas) { canvas.translate(seekBarX, seekBarY); seekBar.draw(canvas); } else { - canvas.translate(timeAudioX + performerX, dp(35) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX + performerX, AndroidUtilities.dp(35) + namesOffset + mediaOffsetY); performerLayout.draw(canvas); } canvas.restore(); canvas.save(); - canvas.translate(timeAudioX, dp(57) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX, AndroidUtilities.dp(57) + namesOffset + mediaOffsetY); durationLayout.draw(canvas); canvas.restore(); Drawable menuDrawable; if (currentMessageObject.isOutOwner()) { - menuDrawable = Theme.docMenuDrawable[1]; + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutMenuSelectedDrawable : Theme.chat_msgOutMenuDrawable; } else { - menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgInMenuSelectedDrawable : Theme.chat_msgInMenuDrawable; } - setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - dp(5)); + setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - AndroidUtilities.dp(5)); menuDrawable.draw(canvas); } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { if (currentMessageObject.isOutOwner()) { - audioTimePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_AUDIO_DURATION_SELECTED_TEXT_COLOR : Theme.MSG_OUT_AUDIO_DURATION_TEXT_COLOR); - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_OUT_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_OUT_AUDIO_PROGRESS_COLOR); + Theme.chat_audioTimePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outAudioDurationSelectedText : Theme.key_chat_outAudioDurationText)); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_outAudioSelectedProgress : Theme.key_chat_outAudioProgress)); } else { - audioTimePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_AUDIO_DURATION_SELECTED_TEXT_COLOR : Theme.MSG_IN_AUDIO_DURATION_TEXT_COLOR); - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_IN_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_IN_AUDIO_PROGRESS_COLOR); + Theme.chat_audioTimePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inAudioDurationSelectedText : Theme.key_chat_inAudioDurationText)); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); } radialProgress.draw(canvas); canvas.save(); if (useSeekBarWaweform) { - canvas.translate(seekBarX + dp(13), seekBarY); + canvas.translate(seekBarX + AndroidUtilities.dp(13), seekBarY); seekBarWaveform.draw(canvas); } else { canvas.translate(seekBarX, seekBarY); @@ -3356,30 +3549,30 @@ private void drawContent(Canvas canvas) { canvas.restore(); canvas.save(); - canvas.translate(timeAudioX, dp(44) + namesOffset + mediaOffsetY); + canvas.translate(timeAudioX, AndroidUtilities.dp(44) + namesOffset + mediaOffsetY); durationLayout.draw(canvas); canvas.restore(); if (currentMessageObject.type != 0 && currentMessageObject.messageOwner.to_id.channel_id == 0 && currentMessageObject.isContentUnread()) { - docBackPaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_VOICE_SEEKBAR_FILL_COLOR : Theme.MSG_IN_VOICE_SEEKBAR_FILL_COLOR); - canvas.drawCircle(timeAudioX + timeWidthAudio + dp(6), dp(51) + namesOffset + mediaOffsetY, dp(3), docBackPaint); + Theme.chat_docBackPaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outVoiceSeekbarFill : Theme.key_chat_inVoiceSeekbarFill)); + canvas.drawCircle(timeAudioX + timeWidthAudio + AndroidUtilities.dp(6), AndroidUtilities.dp(51) + namesOffset + mediaOffsetY, AndroidUtilities.dp(3), Theme.chat_docBackPaint); } } if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { if (photoImage.getVisible()) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO) { - setDrawableBounds(Theme.docMenuDrawable[3], otherX = photoImage.getImageX() + photoImage.getImageWidth() - dp(14), otherY = photoImage.getImageY() + dp(8.1f)); - Theme.docMenuDrawable[3].draw(canvas); + setDrawableBounds(Theme.chat_msgMediaMenuDrawable, otherX = photoImage.getImageX() + photoImage.getImageWidth() - AndroidUtilities.dp(14), otherY = photoImage.getImageY() + AndroidUtilities.dp(8.1f)); + Theme.chat_msgMediaMenuDrawable.draw(canvas); } if (infoLayout != null && (buttonState == 1 || buttonState == 0 || buttonState == 3 || currentMessageObject.isSecretPhoto())) { - infoPaint.setColor(Theme.MSG_MEDIA_INFO_TEXT_COLOR); - setDrawableBounds(Theme.timeBackgroundDrawable, photoImage.getImageX() + dp(4), photoImage.getImageY() + dp(4), infoWidth + dp(8), dp(16.5f)); - Theme.timeBackgroundDrawable.draw(canvas); + Theme.chat_infoPaint.setColor(Theme.getColor(Theme.key_chat_mediaInfoText)); + setDrawableBounds(Theme.chat_timeBackgroundDrawable, photoImage.getImageX() + AndroidUtilities.dp(4), photoImage.getImageY() + AndroidUtilities.dp(4), infoWidth + AndroidUtilities.dp(8), AndroidUtilities.dp(16.5f)); + Theme.chat_timeBackgroundDrawable.draw(canvas); canvas.save(); - canvas.translate(photoImage.getImageX() + dp(8), photoImage.getImageY() + dp(5.5f)); + canvas.translate(photoImage.getImageX() + AndroidUtilities.dp(8), photoImage.getImageY() + AndroidUtilities.dp(5.5f)); infoLayout.draw(canvas); canvas.restore(); } @@ -3388,48 +3581,99 @@ private void drawContent(Canvas canvas) { if (currentMessageObject.type == 4) { if (docTitleLayout != null) { if (currentMessageObject.isOutOwner()) { - locationTitlePaint.setColor(Theme.MSG_OUT_VENUE_NAME_TEXT_COLOR); - locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_OUT_VENUE_INFO_TEXT_COLOR); + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_outVenueNameText)); + Theme.chat_locationAddressPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outVenueInfoSelectedText : Theme.key_chat_outVenueInfoText)); } else { - locationTitlePaint.setColor(Theme.MSG_IN_VENUE_NAME_TEXT_COLOR); - locationAddressPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_VENUE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_IN_VENUE_INFO_TEXT_COLOR); + Theme.chat_locationTitlePaint.setColor(Theme.getColor(Theme.key_chat_inVenueNameText)); + Theme.chat_locationAddressPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inVenueInfoSelectedText : Theme.key_chat_inVenueInfoText)); } canvas.save(); - canvas.translate(docTitleOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + dp(10), photoImage.getImageY() + dp(8)); + canvas.translate(docTitleOffsetX + photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + AndroidUtilities.dp(8)); docTitleLayout.draw(canvas); canvas.restore(); if (infoLayout != null) { canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(10), photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + dp(13)); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10), photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + AndroidUtilities.dp(13)); infoLayout.draw(canvas); canvas.restore(); } } + } else if (currentMessageObject.type == 16) { + if (currentMessageObject.isOutOwner()) { + Theme.chat_audioTitlePaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + Theme.chat_contactPhonePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_outTimeText)); + } else { + Theme.chat_audioTitlePaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_contactPhonePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inTimeSelectedText : Theme.key_chat_inTimeText)); + } + forceNotDrawTime = true; + int x; + if (currentMessageObject.isOutOwner()) { + x = layoutWidth - backgroundWidth + AndroidUtilities.dp(16); + } else { + if (isChat && currentMessageObject.isFromUser()) { + x = AndroidUtilities.dp(74); + } else { + x = AndroidUtilities.dp(25); + } + } + otherX = x; + if (titleLayout != null) { + canvas.save(); + canvas.translate(x, AndroidUtilities.dp(12) + namesOffset); + titleLayout.draw(canvas); + canvas.restore(); + } + if (docTitleLayout != null) { + canvas.save(); + canvas.translate(x + AndroidUtilities.dp(19), AndroidUtilities.dp(37) + namesOffset); + docTitleLayout.draw(canvas); + canvas.restore(); + } + Drawable icon; + Drawable phone; + if (currentMessageObject.isOutOwner()) { + icon = Theme.chat_msgCallUpGreenDrawable; + phone = isDrawSelectedBackground() || otherPressed ? Theme.chat_msgOutCallSelectedDrawable : Theme.chat_msgOutCallDrawable; + } else { + TLRPC.PhoneCallDiscardReason reason = currentMessageObject.messageOwner.action.reason; + if (reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed || reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) { + icon = Theme.chat_msgCallDownRedDrawable; + } else { + icon = Theme.chat_msgCallDownGreenDrawable; + } + phone = isDrawSelectedBackground() || otherPressed ? Theme.chat_msgInCallSelectedDrawable : Theme.chat_msgInCallDrawable; + } + setDrawableBounds(icon, x - AndroidUtilities.dp(3), AndroidUtilities.dp(36) + namesOffset); + icon.draw(canvas); + + setDrawableBounds(phone, x + AndroidUtilities.dp(205), otherY = AndroidUtilities.dp(22)); + phone.draw(canvas); } else if (currentMessageObject.type == 12) { - contactNamePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_NAME_TEXT_COLOR : Theme.MSG_IN_CONTACT_NAME_TEXT_COLOR); - contactPhonePaint.setColor(currentMessageObject.isOutOwner() ? Theme.MSG_OUT_CONTACT_PHONE_TEXT_COLOR : Theme.MSG_IN_CONTACT_PHONE_TEXT_COLOR); + Theme.chat_contactNamePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outContactNameText : Theme.key_chat_inContactNameText)); + Theme.chat_contactPhonePaint.setColor(Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outContactPhoneText : Theme.key_chat_inContactPhoneText)); if (titleLayout != null) { canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(9), dp(16) + namesOffset); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(9), AndroidUtilities.dp(16) + namesOffset); titleLayout.draw(canvas); canvas.restore(); } if (docTitleLayout != null) { canvas.save(); - canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + dp(9), dp(39) + namesOffset); + canvas.translate(photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(9), AndroidUtilities.dp(39) + namesOffset); docTitleLayout.draw(canvas); canvas.restore(); } Drawable menuDrawable; if (currentMessageObject.isOutOwner()) { - menuDrawable = Theme.docMenuDrawable[1]; + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutMenuSelectedDrawable : Theme.chat_msgOutMenuDrawable; } else { - menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgInMenuSelectedDrawable : Theme.chat_msgInMenuDrawable; } - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(48), otherY = photoImage.getImageY() - dp(5)); + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(48), otherY = photoImage.getImageY() - AndroidUtilities.dp(5)); menuDrawable.draw(canvas); } } @@ -3437,19 +3681,19 @@ private void drawContent(Canvas canvas) { if (captionLayout != null) { canvas.save(); if (currentMessageObject.type == 1 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 8) { - canvas.translate(captionX = photoImage.getImageX() + dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + dp(6)); + canvas.translate(captionX = photoImage.getImageX() + AndroidUtilities.dp(5), captionY = photoImage.getImageY() + photoImage.getImageHeight() + AndroidUtilities.dp(6)); } else { - canvas.translate(captionX = backgroundDrawableLeft + dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - dp(10)); + canvas.translate(captionX = backgroundDrawableLeft + AndroidUtilities.dp(currentMessageObject.isOutOwner() ? 11 : 17), captionY = totalHeight - captionHeight - AndroidUtilities.dp(pinnedTop ? 9 : 10)); } if (pressedLink != null) { for (int b = 0; b < urlPath.size(); b++) { - canvas.drawPath(urlPath.get(b), urlPaint); + canvas.drawPath(urlPath.get(b), Theme.chat_urlPaint); } } try { captionLayout.draw(canvas); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } canvas.restore(); } @@ -3457,15 +3701,15 @@ private void drawContent(Canvas canvas) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { Drawable menuDrawable; if (currentMessageObject.isOutOwner()) { - docNamePaint.setColor(Theme.MSG_OUT_FILE_NAME_TEXT_COLOR); - infoPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_FILE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_OUT_FILE_INFO_TEXT_COLOR); - docBackPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_FILE_BACKGROUND_SELECTED_COLOR : Theme.MSG_OUT_FILE_BACKGROUND_COLOR); - menuDrawable = Theme.docMenuDrawable[1]; + Theme.chat_docNamePaint.setColor(Theme.getColor(Theme.key_chat_outFileNameText)); + Theme.chat_infoPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outFileInfoSelectedText : Theme.key_chat_outFileInfoText)); + Theme.chat_docBackPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outFileBackgroundSelected : Theme.key_chat_outFileBackground)); + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutMenuSelectedDrawable : Theme.chat_msgOutMenuDrawable; } else { - docNamePaint.setColor(Theme.MSG_IN_FILE_NAME_TEXT_COLOR); - infoPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_FILE_INFO_SELECTED_TEXT_COLOR : Theme.MSG_IN_FILE_INFO_TEXT_COLOR); - docBackPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_FILE_BACKGROUND_SELECTED_COLOR : Theme.MSG_IN_FILE_BACKGROUND_COLOR); - menuDrawable = Theme.docMenuDrawable[isDrawSelectedBackground() ? 2 : 0]; + Theme.chat_docNamePaint.setColor(Theme.getColor(Theme.key_chat_inFileNameText)); + Theme.chat_infoPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inFileInfoSelectedText : Theme.key_chat_inFileInfoText)); + Theme.chat_docBackPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inFileBackgroundSelected : Theme.key_chat_inFileBackground)); + menuDrawable = isDrawSelectedBackground() ? Theme.chat_msgInMenuSelectedDrawable : Theme.chat_msgInMenuDrawable; } int x; @@ -3473,14 +3717,14 @@ private void drawContent(Canvas canvas) { int subtitleY; if (drawPhotoImage) { if (currentMessageObject.type == 0) { - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(56), otherY = photoImage.getImageY() + dp(1)); + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(56), otherY = photoImage.getImageY() + AndroidUtilities.dp(1)); } else { - setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - dp(40), otherY = photoImage.getImageY() + dp(1)); + setDrawableBounds(menuDrawable, otherX = photoImage.getImageX() + backgroundWidth - AndroidUtilities.dp(40), otherY = photoImage.getImageY() + AndroidUtilities.dp(1)); } - x = photoImage.getImageX() + photoImage.getImageWidth() + dp(10); - titleY = photoImage.getImageY() + dp(8); - subtitleY = photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + dp(13); + x = photoImage.getImageX() + photoImage.getImageWidth() + AndroidUtilities.dp(10); + titleY = photoImage.getImageY() + AndroidUtilities.dp(8); + subtitleY = photoImage.getImageY() + docTitleLayout.getLineBottom(docTitleLayout.getLineCount() - 1) + AndroidUtilities.dp(13); if (buttonState >= 0 && buttonState < 4) { if (!imageDrawn) { int image = buttonState; @@ -3489,35 +3733,35 @@ private void drawContent(Canvas canvas) { } else if (buttonState == 1) { image = currentMessageObject.isOutOwner() ? 8 : 11; } - radialProgress.swapBackground(Theme.photoStatesDrawables[image][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]); + radialProgress.swapBackground(Theme.chat_photoStatesDrawables[image][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]); } else { - radialProgress.swapBackground(Theme.photoStatesDrawables[buttonState][buttonPressed]); + radialProgress.swapBackground(Theme.chat_photoStatesDrawables[buttonState][buttonPressed]); } } if (!imageDrawn) { rect.set(photoImage.getImageX(), photoImage.getImageY(), photoImage.getImageX() + photoImage.getImageWidth(), photoImage.getImageY() + photoImage.getImageHeight()); - canvas.drawRoundRect(rect, dp(3), dp(3), docBackPaint); + canvas.drawRoundRect(rect, AndroidUtilities.dp(3), AndroidUtilities.dp(3), Theme.chat_docBackPaint); if (currentMessageObject.isOutOwner()) { - radialProgress.setProgressColor(isDrawSelectedBackground() ? Theme.MSG_OUT_FILE_PROGRESS_SELECTED_COLOR : Theme.MSG_OUT_FILE_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outFileProgressSelected : Theme.key_chat_outFileProgress)); } else { - radialProgress.setProgressColor(isDrawSelectedBackground() ? Theme.MSG_IN_FILE_PROGRESS_SELECTED_COLOR : Theme.MSG_IN_FILE_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inFileProgressSelected : Theme.key_chat_inFileProgress)); } } else { if (buttonState == -1) { radialProgress.setHideCurrentDrawable(true); } - radialProgress.setProgressColor(Theme.MSG_MEDIA_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(Theme.key_chat_mediaProgress)); } } else { - setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - dp(5)); - x = buttonX + dp(53); - titleY = buttonY + dp(4); - subtitleY = buttonY + dp(27); + setDrawableBounds(menuDrawable, otherX = buttonX + backgroundWidth - AndroidUtilities.dp(currentMessageObject.type == 0 ? 58 : 48), otherY = buttonY - AndroidUtilities.dp(5)); + x = buttonX + AndroidUtilities.dp(53); + titleY = buttonY + AndroidUtilities.dp(4); + subtitleY = buttonY + AndroidUtilities.dp(27); if (currentMessageObject.isOutOwner()) { - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_OUT_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_OUT_AUDIO_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_outAudioSelectedProgress : Theme.key_chat_outAudioProgress)); } else { - radialProgress.setProgressColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.MSG_IN_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_IN_AUDIO_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(isDrawSelectedBackground() || buttonPressed != 0 ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); } } menuDrawable.draw(canvas); @@ -3530,7 +3774,7 @@ private void drawContent(Canvas canvas) { canvas.restore(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -3541,7 +3785,7 @@ private void drawContent(Canvas canvas) { canvas.restore(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (drawImageButton && photoImage.getVisible()) { @@ -3551,37 +3795,37 @@ private void drawContent(Canvas canvas) { if (!botButtons.isEmpty()) { int addX; if (currentMessageObject.isOutOwner()) { - addX = getMeasuredWidth() - widthForButtons - dp(10); + addX = getMeasuredWidth() - widthForButtons - AndroidUtilities.dp(10); } else { - addX = backgroundDrawableLeft + dp(mediaBackground ? 1 : 7); + addX = backgroundDrawableLeft + AndroidUtilities.dp(mediaBackground ? 1 : 7); } for (int a = 0; a < botButtons.size(); a++) { BotButton button = botButtons.get(a); - int y = button.y + layoutHeight - dp(2); - Theme.systemDrawable.setColorFilter(a == pressedBotButton ? Theme.colorPressedFilter : Theme.colorFilter); - Theme.systemDrawable.setBounds(button.x + addX, y, button.x + addX + button.width, y + button.height); - Theme.systemDrawable.draw(canvas); + int y = button.y + layoutHeight - AndroidUtilities.dp(2); + Theme.chat_systemDrawable.setColorFilter(a == pressedBotButton ? Theme.colorPressedFilter : Theme.colorFilter); + Theme.chat_systemDrawable.setBounds(button.x + addX, y, button.x + addX + button.width, y + button.height); + Theme.chat_systemDrawable.draw(canvas); canvas.save(); - canvas.translate(button.x + addX + dp(5), y + (dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2); + canvas.translate(button.x + addX + AndroidUtilities.dp(5), y + (AndroidUtilities.dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2); button.title.draw(canvas); canvas.restore(); if (button.button instanceof TLRPC.TL_keyboardButtonUrl) { - int x = button.x + button.width - dp(3) - Theme.botLink.getIntrinsicWidth() + addX; - setDrawableBounds(Theme.botLink, x, y + dp(3)); - Theme.botLink.draw(canvas); + int x = button.x + button.width - AndroidUtilities.dp(3) - Theme.chat_botLinkDrawalbe.getIntrinsicWidth() + addX; + setDrawableBounds(Theme.chat_botLinkDrawalbe, x, y + AndroidUtilities.dp(3)); + Theme.chat_botLinkDrawalbe.draw(canvas); } else if (button.button instanceof TLRPC.TL_keyboardButtonSwitchInline) { - int x = button.x + button.width - dp(3) - Theme.botInline.getIntrinsicWidth() + addX; - setDrawableBounds(Theme.botInline, x, y + dp(3)); - Theme.botInline.draw(canvas); - } else if (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation || button.button instanceof TLRPC.TL_keyboardButtonGame) { - boolean drawProgress = (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonGame) && SendMessagesHelper.getInstance().isSendingCallback(currentMessageObject, button.button) || + int x = button.x + button.width - AndroidUtilities.dp(3) - Theme.chat_botInlineDrawable.getIntrinsicWidth() + addX; + setDrawableBounds(Theme.chat_botInlineDrawable, x, y + AndroidUtilities.dp(3)); + Theme.chat_botInlineDrawable.draw(canvas); + } else if (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy) { + boolean drawProgress = (button.button instanceof TLRPC.TL_keyboardButtonCallback || button.button instanceof TLRPC.TL_keyboardButtonGame || button.button instanceof TLRPC.TL_keyboardButtonBuy) && SendMessagesHelper.getInstance().isSendingCallback(currentMessageObject, button.button) || button.button instanceof TLRPC.TL_keyboardButtonRequestGeoLocation && SendMessagesHelper.getInstance().isSendingCurrentLocation(currentMessageObject, button.button); if (drawProgress || !drawProgress && button.progressAlpha != 0) { - botProgressPaint.setAlpha(Math.min(255, (int) (button.progressAlpha * 255))); - int x = button.x + button.width - dp(9 + 3) + addX; - rect.set(x, y + dp(4), x + dp(8), y + dp(8 + 4)); - canvas.drawArc(rect, button.angle, 220, false, botProgressPaint); - invalidate((int) rect.left - dp(2), (int) rect.top - dp(2), (int) rect.right + dp(2), (int) rect.bottom + dp(2)); + Theme.chat_botProgressPaint.setAlpha(Math.min(255, (int) (button.progressAlpha * 255))); + int x = button.x + button.width - AndroidUtilities.dp(9 + 3) + addX; + rect.set(x, y + AndroidUtilities.dp(4), x + AndroidUtilities.dp(8), y + AndroidUtilities.dp(8 + 4)); + canvas.drawArc(rect, button.angle, 220, false, Theme.chat_botProgressPaint); + invalidate((int) rect.left - AndroidUtilities.dp(2), (int) rect.top - AndroidUtilities.dp(2), (int) rect.right + AndroidUtilities.dp(2), (int) rect.bottom + AndroidUtilities.dp(2)); long newTime = System.currentTimeMillis(); if (Math.abs(button.lastUpdateTime - System.currentTimeMillis()) < 1000) { long delta = (newTime - button.lastUpdateTime); @@ -3617,16 +3861,16 @@ private Drawable getDrawableForCurrentState() { return null; } radialProgress.setAlphaForPrevious(false); - return Theme.fileStatesDrawable[currentMessageObject.isOutOwner() ? buttonState : buttonState + 5][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]; + return Theme.chat_fileStatesDrawable[currentMessageObject.isOutOwner() ? buttonState : buttonState + 5][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]; } else { if (documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT && !drawPhotoImage) { radialProgress.setAlphaForPrevious(false); if (buttonState == -1) { - return Theme.fileStatesDrawable[currentMessageObject.isOutOwner() ? 3 : 8][isDrawSelectedBackground() ? 1 : 0]; + return Theme.chat_fileStatesDrawable[currentMessageObject.isOutOwner() ? 3 : 8][isDrawSelectedBackground() ? 1 : 0]; } else if (buttonState == 0) { - return Theme.fileStatesDrawable[currentMessageObject.isOutOwner() ? 2 : 7][isDrawSelectedBackground() ? 1 : 0]; + return Theme.chat_fileStatesDrawable[currentMessageObject.isOutOwner() ? 2 : 7][isDrawSelectedBackground() ? 1 : 0]; } else if (buttonState == 1) { - return Theme.fileStatesDrawable[currentMessageObject.isOutOwner() ? 4 : 9][isDrawSelectedBackground() ? 1 : 0]; + return Theme.chat_fileStatesDrawable[currentMessageObject.isOutOwner() ? 4 : 9][isDrawSelectedBackground() ? 1 : 0]; } } else { radialProgress.setAlphaForPrevious(true); @@ -3638,12 +3882,12 @@ private Drawable getDrawableForCurrentState() { } else if (buttonState == 1) { image = currentMessageObject.isOutOwner() ? 8 : 11; } - return Theme.photoStatesDrawables[image][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]; + return Theme.chat_photoStatesDrawables[image][isDrawSelectedBackground() || buttonPressed != 0 ? 1 : 0]; } else { - return Theme.photoStatesDrawables[buttonState][buttonPressed]; + return Theme.chat_photoStatesDrawables[buttonState][buttonPressed]; } } else if (buttonState == -1 && documentAttachType == DOCUMENT_ATTACH_TYPE_DOCUMENT) { - return Theme.photoStatesDrawables[currentMessageObject.isOutOwner() ? 9 : 12][isDrawSelectedBackground() ? 1 : 0]; + return Theme.chat_photoStatesDrawables[currentMessageObject.isOutOwner() ? 9 : 12][isDrawSelectedBackground() ? 1 : 0]; } } } @@ -3653,22 +3897,22 @@ private Drawable getDrawableForCurrentState() { private int getMaxNameWidth() { if (documentAttachType == DOCUMENT_ATTACH_TYPE_STICKER) { int maxWidth; - if (isTablet()) { + if (AndroidUtilities.isTablet()) { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { - maxWidth = getMinTabletSide() - dp(42); + maxWidth = AndroidUtilities.getMinTabletSide() - AndroidUtilities.dp(42); } else { - maxWidth = getMinTabletSide(); + maxWidth = AndroidUtilities.getMinTabletSide(); } } else { if (isChat && !currentMessageObject.isOutOwner() && currentMessageObject.isFromUser()) { - maxWidth = Math.min(displaySize.x, displaySize.y) - dp(42); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(42); } else { - maxWidth = Math.min(displaySize.x, displaySize.y); + maxWidth = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y); } } - return maxWidth - backgroundWidth - dp(57); + return maxWidth - backgroundWidth - AndroidUtilities.dp(57); } - return backgroundWidth - dp(mediaBackground ? 22 : 31); + return backgroundWidth - AndroidUtilities.dp(mediaBackground ? 22 : 31); } public void updateButtonState(boolean animated) { @@ -3681,6 +3925,11 @@ public void updateButtonState(boolean animated) { fileName = FileLoader.getAttachFileName(currentPhotoObject); fileExists = currentMessageObject.mediaExists; } else if (currentMessageObject.type == 8 || documentAttachType == DOCUMENT_ATTACH_TYPE_VIDEO || currentMessageObject.type == 9 || documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + if (currentMessageObject.useCustomPhoto) { + buttonState = 1; + radialProgress.setBackground(getDrawableForCurrentState(), false, animated); + return; + } if (currentMessageObject.attachPathExists) { fileName = currentMessageObject.messageOwner.attachPath; fileExists = true; @@ -3841,7 +4090,7 @@ public void updateButtonState(boolean animated) { } radialProgress.setBackground(getDrawableForCurrentState(), false, animated); if (photoNotSet) { - setMessageObject(currentMessageObject); + setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); } invalidate(); } @@ -3959,7 +4208,7 @@ public void onSuccessDownload(String fileName) { } else if (!photoNotSet) { updateButtonState(true); } else { - setMessageObject(currentMessageObject); + setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); } } else { if (!photoNotSet || currentMessageObject.type == 8 && currentMessageObject.audioProgress != 1) { @@ -3972,7 +4221,7 @@ public void onSuccessDownload(String fileName) { } } if (photoNotSet) { - setMessageObject(currentMessageObject); + setMessageObject(currentMessageObject, pinnedBottom, pinnedTop); } } } @@ -4046,21 +4295,21 @@ private void measureTime(MessageObject messageObject) { } else { currentTimeString = timeString; } - timeTextWidth = timeWidth = (int) Math.ceil(timePaint.measureText(currentTimeString)); + timeTextWidth = timeWidth = (int) Math.ceil(Theme.chat_timePaint.measureText(currentTimeString)); if ((messageObject.messageOwner.flags & TLRPC.MESSAGE_FLAG_HAS_VIEWS) != 0) { currentViewsString = String.format("%s", LocaleController.formatShortNumber(Math.max(1, messageObject.messageOwner.views), null)); - viewsTextWidth = (int) Math.ceil(timePaint.measureText(currentViewsString)); - timeWidth += viewsTextWidth + Theme.viewsCountDrawable[0].getIntrinsicWidth() + dp(10); + viewsTextWidth = (int) Math.ceil(Theme.chat_timePaint.measureText(currentViewsString)); + timeWidth += viewsTextWidth + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(10); } if (hasSign) { if (availableTimeWidth == 0) { - availableTimeWidth = dp(1000); + availableTimeWidth = AndroidUtilities.dp(1000); } CharSequence name = ContactsController.formatName(signUser.first_name, signUser.last_name).replace('\n', ' '); int widthForSign = availableTimeWidth - timeWidth; - int width = (int) Math.ceil(timePaint.measureText(name, 0, name.length())); + int width = (int) Math.ceil(Theme.chat_timePaint.measureText(name, 0, name.length())); if (width > widthForSign) { - name = TextUtils.ellipsize(name, timePaint, widthForSign, TextUtils.TruncateAt.END); + name = TextUtils.ellipsize(name, Theme.chat_timePaint, widthForSign, TextUtils.TruncateAt.END); width = widthForSign; } currentTimeString = name + currentTimeString; @@ -4087,7 +4336,7 @@ private boolean checkNeedDrawShareButton(MessageObject messageObject) { return true; } if (!messageObject.isOut()) { - if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { return true; } if (messageObject.isMegagroup()) { @@ -4156,14 +4405,14 @@ private void setMessageObjectInternal(MessageObject messageObject) { TLRPC.User botUser = MessagesController.getInstance().getUser(messageObject.messageOwner.via_bot_id); if (botUser != null && botUser.username != null && botUser.username.length() > 0) { viaUsername = "@" + botUser.username; - viaString = replaceTags(String.format(" via %s", viaUsername)); - viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); + viaString = AndroidUtilities.replaceTags(String.format(" via %s", viaUsername)); + viaWidth = (int) Math.ceil(Theme.chat_replyNamePaint.measureText(viaString, 0, viaString.length())); currentViaBotUser = botUser; } } else if (messageObject.messageOwner.via_bot_name != null && messageObject.messageOwner.via_bot_name.length() > 0) { viaUsername = "@" + messageObject.messageOwner.via_bot_name; - viaString = replaceTags(String.format(" via %s", viaUsername)); - viaWidth = (int) Math.ceil(replyNamePaint.measureText(viaString, 0, viaString.length())); + viaString = AndroidUtilities.replaceTags(String.format(" via %s", viaUsername)); + viaWidth = (int) Math.ceil(Theme.chat_replyNamePaint.measureText(viaString, 0, viaString.length())); } boolean authorName = drawName && isChat && !currentMessageObject.isOutOwner(); @@ -4172,7 +4421,7 @@ private void setMessageObjectInternal(MessageObject messageObject) { drawNameLayout = true; nameWidth = getMaxNameWidth(); if (nameWidth < 0) { - nameWidth = dp(100); + nameWidth = AndroidUtilities.dp(100); } if (authorName) { @@ -4186,44 +4435,44 @@ private void setMessageObjectInternal(MessageObject messageObject) { } else { currentNameString = ""; } - CharSequence nameStringFinal = TextUtils.ellipsize(currentNameString.replace('\n', ' '), namePaint, nameWidth - (viaBot ? viaWidth : 0), TextUtils.TruncateAt.END); + CharSequence nameStringFinal = TextUtils.ellipsize(currentNameString.replace('\n', ' '), Theme.chat_namePaint, nameWidth - (viaBot ? viaWidth : 0), TextUtils.TruncateAt.END); if (viaBot) { - viaNameWidth = (int) Math.ceil(namePaint.measureText(nameStringFinal, 0, nameStringFinal.length())); + viaNameWidth = (int) Math.ceil(Theme.chat_namePaint.measureText(nameStringFinal, 0, nameStringFinal.length())); if (viaNameWidth != 0) { - viaNameWidth += dp(4); + viaNameWidth += AndroidUtilities.dp(4); } int color; if (currentMessageObject.type == 13) { - color = Theme.MSG_STICKER_VIA_BOT_NAME_TEXT_COLOR; + color = Theme.getColor(Theme.key_chat_stickerViaBotNameText); } else { - color = currentMessageObject.isOutOwner() ? Theme.MSG_OUT_VIA_BOT_NAME_TEXT_COLOR : Theme.MSG_IN_VIA_BOT_NAME_TEXT_COLOR; + color = Theme.getColor(currentMessageObject.isOutOwner() ? Theme.key_chat_outViaBotNameText : Theme.key_chat_inViaBotNameText); } if (currentNameString.length() > 0) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("%s via %s", nameStringFinal, viaUsername)); stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), nameStringFinal.length() + 1, nameStringFinal.length() + 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 5, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), nameStringFinal.length() + 5, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); nameStringFinal = stringBuilder; } else { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(String.format("via %s", viaUsername)); stringBuilder.setSpan(new TypefaceSpan(Typeface.DEFAULT, 0, color), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - stringBuilder.setSpan(new TypefaceSpan(getTypeface("fonts/rmedium.ttf"), 0, color), 4, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, color), 4, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); nameStringFinal = stringBuilder; } - nameStringFinal = TextUtils.ellipsize(nameStringFinal, namePaint, nameWidth, TextUtils.TruncateAt.END); + nameStringFinal = TextUtils.ellipsize(nameStringFinal, Theme.chat_namePaint, nameWidth, TextUtils.TruncateAt.END); } try { - nameLayout = new StaticLayout(nameStringFinal, namePaint, nameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + nameLayout = new StaticLayout(nameStringFinal, Theme.chat_namePaint, nameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (nameLayout != null && nameLayout.getLineCount() > 0) { nameWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); if (messageObject.type != 13) { - namesOffset += dp(19); + namesOffset += AndroidUtilities.dp(19); } nameOffsetX = nameLayout.getLineLeft(0); } else { nameWidth = 0; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentNameString.length() == 0) { currentNameString = null; @@ -4260,43 +4509,48 @@ private void setMessageObjectInternal(MessageObject messageObject) { } forwardedNameWidth = getMaxNameWidth(); - int fromWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " ")); - CharSequence name = TextUtils.ellipsize(currentForwardNameString.replace('\n', ' '), replyNamePaint, forwardedNameWidth - fromWidth - viaWidth, TextUtils.TruncateAt.END); + String fromString = LocaleController.getString("From", R.string.From); + int fromWidth = (int) Math.ceil(Theme.chat_forwardNamePaint.measureText(fromString + " ")); + CharSequence name = TextUtils.ellipsize(currentForwardNameString.replace('\n', ' '), Theme.chat_replyNamePaint, forwardedNameWidth - fromWidth - viaWidth, TextUtils.TruncateAt.END); CharSequence lastLine; + SpannableStringBuilder stringBuilder; if (viaString != null) { - viaNameWidth = (int) Math.ceil(forwardNamePaint.measureText(LocaleController.getString("From", R.string.From) + " " + name)); - lastLine = replaceTags(String.format("%s %s via %s", LocaleController.getString("From", R.string.From), name, viaUsername)); + stringBuilder = new SpannableStringBuilder(String.format("%s %s via %s", fromString, name, viaUsername)); + viaNameWidth = (int) Math.ceil(Theme.chat_forwardNamePaint.measureText(fromString + " " + name)); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), stringBuilder.length() - viaUsername.length() - 1, stringBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - lastLine = replaceTags(String.format("%s %s", LocaleController.getString("From", R.string.From), name)); + stringBuilder = new SpannableStringBuilder(String.format("%s %s", fromString, name)); } - lastLine = TextUtils.ellipsize(lastLine, forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), fromString.length() + 1, fromString.length() + 1 + name.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + lastLine = stringBuilder; + lastLine = TextUtils.ellipsize(lastLine, Theme.chat_forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); try { - forwardedNameLayout[1] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - lastLine = TextUtils.ellipsize(replaceTags(LocaleController.getString("ForwardedMessage", R.string.ForwardedMessage)), forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); - forwardedNameLayout[0] = new StaticLayout(lastLine, forwardNamePaint, forwardedNameWidth + dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + forwardedNameLayout[1] = new StaticLayout(lastLine, Theme.chat_forwardNamePaint, forwardedNameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + lastLine = TextUtils.ellipsize(AndroidUtilities.replaceTags(LocaleController.getString("ForwardedMessage", R.string.ForwardedMessage)), Theme.chat_forwardNamePaint, forwardedNameWidth, TextUtils.TruncateAt.END); + forwardedNameLayout[0] = new StaticLayout(lastLine, Theme.chat_forwardNamePaint, forwardedNameWidth + AndroidUtilities.dp(2), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); forwardedNameWidth = Math.max((int) Math.ceil(forwardedNameLayout[0].getLineWidth(0)), (int) Math.ceil(forwardedNameLayout[1].getLineWidth(0))); forwardNameOffsetX[0] = forwardedNameLayout[0].getLineLeft(0); forwardNameOffsetX[1] = forwardedNameLayout[1].getLineLeft(0); - namesOffset += dp(36); + namesOffset += AndroidUtilities.dp(36); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } if (messageObject.isReply()) { - namesOffset += dp(42); + namesOffset += AndroidUtilities.dp(42); if (messageObject.type != 0) { if (messageObject.type == 13) { - namesOffset -= dp(42); + namesOffset -= AndroidUtilities.dp(42); } else { - namesOffset += dp(5); + namesOffset += AndroidUtilities.dp(5); } } int maxWidth = getMaxNameWidth(); if (messageObject.type != 13) { - maxWidth -= dp(10); + maxWidth -= AndroidUtilities.dp(10); } CharSequence stringFinalName = null; @@ -4306,78 +4560,89 @@ private void setMessageObjectInternal(MessageObject messageObject) { if (photoSize == null) { photoSize = FileLoader.getClosestPhotoSizeWithSize(messageObject.replyMessageObject.photoThumbs, 80); } - if (photoSize == null || messageObject.replyMessageObject.type == 13 || messageObject.type == 13 && !isTablet() || messageObject.replyMessageObject.isSecretMedia()) { + if (photoSize == null || messageObject.replyMessageObject.type == 13 || messageObject.type == 13 && !AndroidUtilities.isTablet() || messageObject.replyMessageObject.isSecretMedia()) { replyImageReceiver.setImageBitmap((Drawable) null); needReplyImage = false; } else { currentReplyPhoto = photoSize.location; replyImageReceiver.setImage(photoSize.location, "50_50", null, null, true); needReplyImage = true; - maxWidth -= dp(44); + maxWidth -= AndroidUtilities.dp(44); } String name = null; - if (messageObject.replyMessageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(messageObject.replyMessageObject.messageOwner.from_id); - if (user != null) { - name = UserObject.getUserName(user); - } - } else if (messageObject.replyMessageObject.messageOwner.from_id < 0) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(-messageObject.replyMessageObject.messageOwner.from_id); - if (chat != null) { - name = chat.title; - } + if (messageObject.customReplyName != null) { + name = messageObject.customReplyName; } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.replyMessageObject.messageOwner.to_id.channel_id); - if (chat != null) { - name = chat.title; + if (messageObject.replyMessageObject.isFromUser()) { + TLRPC.User user = MessagesController.getInstance().getUser(messageObject.replyMessageObject.messageOwner.from_id); + if (user != null) { + name = UserObject.getUserName(user); + } + } else if (messageObject.replyMessageObject.messageOwner.from_id < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-messageObject.replyMessageObject.messageOwner.from_id); + if (chat != null) { + name = chat.title; + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(messageObject.replyMessageObject.messageOwner.to_id.channel_id); + if (chat != null) { + name = chat.title; + } } } if (name != null) { - stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), replyNamePaint, maxWidth, TextUtils.TruncateAt.END); + stringFinalName = TextUtils.ellipsize(name.replace('\n', ' '), Theme.chat_replyNamePaint, maxWidth, TextUtils.TruncateAt.END); } if (messageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.messageOwner.media.game.title, replyTextPaint.getFontMetricsInt(), dp(14), false); - stringFinalText = TextUtils.ellipsize(stringFinalText, replyTextPaint, maxWidth, TextUtils.TruncateAt.END); + stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.messageOwner.media.game.title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); + stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); + } else if (messageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + stringFinalText = Emoji.replaceEmoji(messageObject.replyMessageObject.messageOwner.media.title, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); + stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); } else if (messageObject.replyMessageObject.messageText != null && messageObject.replyMessageObject.messageText.length() > 0) { String mess = messageObject.replyMessageObject.messageText.toString(); if (mess.length() > 150) { mess = mess.substring(0, 150); } mess = mess.replace('\n', ' '); - stringFinalText = Emoji.replaceEmoji(mess, replyTextPaint.getFontMetricsInt(), dp(14), false); - stringFinalText = TextUtils.ellipsize(stringFinalText, replyTextPaint, maxWidth, TextUtils.TruncateAt.END); + stringFinalText = Emoji.replaceEmoji(mess, Theme.chat_replyTextPaint.getFontMetricsInt(), AndroidUtilities.dp(14), false); + stringFinalText = TextUtils.ellipsize(stringFinalText, Theme.chat_replyTextPaint, maxWidth, TextUtils.TruncateAt.END); } } if (stringFinalName == null) { stringFinalName = LocaleController.getString("Loading", R.string.Loading); } try { - replyNameLayout = new StaticLayout(stringFinalName, replyNamePaint, maxWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + replyNameLayout = new StaticLayout(stringFinalName, Theme.chat_replyNamePaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (replyNameLayout.getLineCount() > 0) { - replyNameWidth = (int)Math.ceil(replyNameLayout.getLineWidth(0)) + dp(12 + (needReplyImage ? 44 : 0)); + replyNameWidth = (int)Math.ceil(replyNameLayout.getLineWidth(0)) + AndroidUtilities.dp(12 + (needReplyImage ? 44 : 0)); replyNameOffset = replyNameLayout.getLineLeft(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (stringFinalText != null) { - replyTextLayout = new StaticLayout(stringFinalText, replyTextPaint, maxWidth + dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + replyTextLayout = new StaticLayout(stringFinalText, Theme.chat_replyTextPaint, maxWidth + AndroidUtilities.dp(6), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (replyTextLayout.getLineCount() > 0) { - replyTextWidth = (int) Math.ceil(replyTextLayout.getLineWidth(0)) + dp(12 + (needReplyImage ? 44 : 0)); + replyTextWidth = (int) Math.ceil(replyTextLayout.getLineWidth(0)) + AndroidUtilities.dp(12 + (needReplyImage ? 44 : 0)); replyTextOffset = replyTextLayout.getLineLeft(0); } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } requestLayout(); } + public ImageReceiver getAvatarImage() { + return isAvatarVisible ? avatarImage : null; + } + @Override protected void onDraw(Canvas canvas) { if (currentMessageObject == null) { @@ -4389,103 +4654,173 @@ protected void onDraw(Canvas canvas) { return; } - if (isAvatarVisible) { - avatarImage.draw(canvas); + if (currentMessageObject.isOutOwner()) { + Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + Theme.chat_msgTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkOut); + Theme.chat_msgGameTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + Theme.chat_msgGameTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkOut); + Theme.chat_replyTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkOut); + } else { + Theme.chat_msgTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_msgTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkIn); + Theme.chat_msgGameTextPaint.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + Theme.chat_msgGameTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkIn); + Theme.chat_replyTextPaint.linkColor = Theme.getColor(Theme.key_chat_messageLinkIn); + } + + if (documentAttach != null) { + if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO) { + if (currentMessageObject.isOutOwner()) { + seekBarWaveform.setColors(Theme.getColor(Theme.key_chat_outVoiceSeekbar), Theme.getColor(Theme.key_chat_outVoiceSeekbarFill), Theme.getColor(Theme.key_chat_outVoiceSeekbarSelected)); + seekBar.setColors(Theme.getColor(Theme.key_chat_outAudioSeekbar), Theme.getColor(Theme.key_chat_outAudioSeekbarFill), Theme.getColor(Theme.key_chat_outAudioSeekbarSelected)); + } else { + seekBarWaveform.setColors(Theme.getColor(Theme.key_chat_inVoiceSeekbar), Theme.getColor(Theme.key_chat_inVoiceSeekbarFill), Theme.getColor(Theme.key_chat_inVoiceSeekbarSelected)); + seekBar.setColors(Theme.getColor(Theme.key_chat_inAudioSeekbar), Theme.getColor(Theme.key_chat_inAudioSeekbarFill), Theme.getColor(Theme.key_chat_inAudioSeekbarSelected)); + } + } else if (documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { + documentAttachType = DOCUMENT_ATTACH_TYPE_MUSIC; + if (currentMessageObject.isOutOwner()) { + seekBar.setColors(Theme.getColor(Theme.key_chat_outAudioSeekbar), Theme.getColor(Theme.key_chat_outAudioSeekbarFill), Theme.getColor(Theme.key_chat_outAudioSeekbarSelected)); + } else { + seekBar.setColors(Theme.getColor(Theme.key_chat_inAudioSeekbar), Theme.getColor(Theme.key_chat_inAudioSeekbarFill), Theme.getColor(Theme.key_chat_inAudioSeekbarSelected)); + } + } } if (mediaBackground) { - timePaint.setColor(Theme.MSG_MEDIA_TIME_TEXT_COLOR); + if (currentMessageObject.type == 13) { + Theme.chat_timePaint.setColor(Theme.getColor(Theme.key_chat_serviceText)); + } else { + Theme.chat_timePaint.setColor(Theme.getColor(Theme.key_chat_mediaTimeText)); + } } else { if (currentMessageObject.isOutOwner()) { - timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_TIME_SELECTED_TEXT_COLOR : Theme.MSG_OUT_TIME_TEXT_COLOR); + Theme.chat_timePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outTimeSelectedText : Theme.key_chat_outTimeText)); } else { - timePaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_TIME_SELECTED_TEXT_COLOR : Theme.MSG_IN_TIME_TEXT_COLOR); + Theme.chat_timePaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inTimeSelectedText : Theme.key_chat_inTimeText)); } } + Drawable currentBackgroundShadowDrawable; if (currentMessageObject.isOutOwner()) { if (isDrawSelectedBackground()) { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableOutSelected; + if (!mediaBackground && !pinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgOutSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutShadowDrawable; } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableOutSelected; + currentBackgroundDrawable = Theme.chat_msgOutMediaSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutMediaShadowDrawable; } } else { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableOut; + if (!mediaBackground && !pinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgOutDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutShadowDrawable; } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableOut; + currentBackgroundDrawable = Theme.chat_msgOutMediaDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgOutMediaShadowDrawable; } } - setDrawableBounds(currentBackgroundDrawable, backgroundDrawableLeft = layoutWidth - backgroundWidth - (!mediaBackground ? 0 : dp(9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + backgroundDrawableLeft = layoutWidth - backgroundWidth - (!mediaBackground ? 0 : AndroidUtilities.dp(9)); + int backgroundRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); + int backgroundLeft = backgroundDrawableLeft; + if (!mediaBackground && pinnedBottom) { + backgroundRight -= AndroidUtilities.dp(6); + } + int offsetBottom; + if (pinnedBottom && pinnedTop) { + offsetBottom = 0; + } else if (pinnedBottom) { + offsetBottom = AndroidUtilities.dp(1); + } else { + offsetBottom = AndroidUtilities.dp(2); + } + setDrawableBounds(currentBackgroundDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); + setDrawableBounds(currentBackgroundShadowDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); } else { if (isDrawSelectedBackground()) { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableInSelected; + if (!mediaBackground && !pinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgInSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInShadowDrawable; } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableInSelected; + currentBackgroundDrawable = Theme.chat_msgInMediaSelectedDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInMediaShadowDrawable; } } else { - if (!mediaBackground) { - currentBackgroundDrawable = Theme.backgroundDrawableIn; + if (!mediaBackground && !pinnedBottom) { + currentBackgroundDrawable = Theme.chat_msgInDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInShadowDrawable; } else { - currentBackgroundDrawable = Theme.backgroundMediaDrawableIn; - } - } - if (isChat && currentMessageObject.isFromUser()) { - setDrawableBounds(currentBackgroundDrawable, backgroundDrawableLeft = dp(48 + (!mediaBackground ? 3 : 9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + currentBackgroundDrawable = Theme.chat_msgInMediaDrawable; + currentBackgroundShadowDrawable = Theme.chat_msgInMediaShadowDrawable; + } + } + backgroundDrawableLeft = AndroidUtilities.dp((isChat && currentMessageObject.isFromUser() ? 48 : 0) + (!mediaBackground ? 3 : 9)); + int backgroundRight = backgroundWidth - (mediaBackground ? 0 : AndroidUtilities.dp(3)); + int backgroundLeft = backgroundDrawableLeft; + if (!mediaBackground && pinnedBottom) { + backgroundRight -= AndroidUtilities.dp(6); + backgroundLeft += AndroidUtilities.dp(6); + } + int offsetBottom; + if (pinnedBottom && pinnedTop) { + offsetBottom = 0; + } else if (pinnedBottom) { + offsetBottom = AndroidUtilities.dp(1); } else { - setDrawableBounds(currentBackgroundDrawable, backgroundDrawableLeft = (!mediaBackground ? dp(3) : dp(9)), dp(1), backgroundWidth - (mediaBackground ? 0 : dp(3)), layoutHeight - dp(2)); + offsetBottom = AndroidUtilities.dp(2); } + setDrawableBounds(currentBackgroundDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); + setDrawableBounds(currentBackgroundShadowDrawable, backgroundLeft, pinnedTop || pinnedTop && pinnedBottom ? 0 : AndroidUtilities.dp(1), backgroundRight, layoutHeight - offsetBottom); } if (drawBackground && currentBackgroundDrawable != null) { currentBackgroundDrawable.draw(canvas); + currentBackgroundShadowDrawable.draw(canvas); } drawContent(canvas); if (drawShareButton) { - Theme.shareDrawable.setColorFilter(sharePressed ? Theme.colorPressedFilter : Theme.colorFilter); + Theme.chat_shareDrawable.setColorFilter(sharePressed ? Theme.colorPressedFilter : Theme.colorFilter); if (currentMessageObject.isOutOwner()) { - shareStartX = currentBackgroundDrawable.getBounds().left - dp(8) - Theme.shareDrawable.getIntrinsicWidth(); + shareStartX = currentBackgroundDrawable.getBounds().left - AndroidUtilities.dp(8) - Theme.chat_shareDrawable.getIntrinsicWidth(); } else { - shareStartX = currentBackgroundDrawable.getBounds().right + dp(8); + shareStartX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(8); } - setDrawableBounds(Theme.shareDrawable, shareStartX, shareStartY = layoutHeight - dp(41)); - Theme.shareDrawable.draw(canvas); - setDrawableBounds(Theme.shareIconDrawable, shareStartX + dp(9), shareStartY + dp(9)); - Theme.shareIconDrawable.draw(canvas); + setDrawableBounds(Theme.chat_shareDrawable, shareStartX, shareStartY = layoutHeight - AndroidUtilities.dp(41)); + Theme.chat_shareDrawable.draw(canvas); + setDrawableBounds(Theme.chat_shareIconDrawable, shareStartX + AndroidUtilities.dp(9), shareStartY + AndroidUtilities.dp(9)); + Theme.chat_shareIconDrawable.draw(canvas); } if (drawNameLayout && nameLayout != null) { canvas.save(); if (currentMessageObject.type == 13) { - namePaint.setColor(Theme.MSG_STICKER_NAME_TEXT_COLOR); + Theme.chat_namePaint.setColor(Theme.getColor(Theme.key_chat_stickerNameText)); int backWidth; if (currentMessageObject.isOutOwner()) { - nameX = dp(28); + nameX = AndroidUtilities.dp(28); } else { - nameX = currentBackgroundDrawable.getBounds().right + dp(22); + nameX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(22); } - nameY = layoutHeight - dp(38); - Theme.systemDrawable.setColorFilter(Theme.colorFilter); - Theme.systemDrawable.setBounds((int) nameX - dp(12), (int) nameY - dp(5), (int) nameX + dp(12) + nameWidth, (int) nameY + dp(22)); - Theme.systemDrawable.draw(canvas); + nameY = layoutHeight - AndroidUtilities.dp(38); + Theme.chat_systemDrawable.setColorFilter(Theme.colorFilter); + Theme.chat_systemDrawable.setBounds((int) nameX - AndroidUtilities.dp(12), (int) nameY - AndroidUtilities.dp(5), (int) nameX + AndroidUtilities.dp(12) + nameWidth, (int) nameY + AndroidUtilities.dp(22)); + Theme.chat_systemDrawable.draw(canvas); } else { if (mediaBackground || currentMessageObject.isOutOwner()) { - nameX = currentBackgroundDrawable.getBounds().left + dp(11) - nameOffsetX; + nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11) - nameOffsetX; } else { - nameX = currentBackgroundDrawable.getBounds().left + dp(17) - nameOffsetX; + nameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17) - nameOffsetX; } if (currentUser != null) { - namePaint.setColor(AvatarDrawable.getNameColorForId(currentUser.id)); + Theme.chat_namePaint.setColor(AvatarDrawable.getNameColorForId(currentUser.id)); } else if (currentChat != null) { - namePaint.setColor(AvatarDrawable.getNameColorForId(currentChat.id)); + Theme.chat_namePaint.setColor(AvatarDrawable.getNameColorForId(currentChat.id)); } else { - namePaint.setColor(AvatarDrawable.getNameColorForId(0)); + Theme.chat_namePaint.setColor(AvatarDrawable.getNameColorForId(0)); } - nameY = dp(10); + nameY = AndroidUtilities.dp(pinnedTop ? 9 : 10); } canvas.translate(nameX, nameY); nameLayout.draw(canvas); @@ -4493,21 +4828,21 @@ protected void onDraw(Canvas canvas) { } if (drawForwardedName && forwardedNameLayout[0] != null && forwardedNameLayout[1] != null) { - forwardNameY = dp(10 + (drawNameLayout ? 19 : 0)); + forwardNameY = AndroidUtilities.dp(10 + (drawNameLayout ? 19 : 0)); if (currentMessageObject.isOutOwner()) { - forwardNamePaint.setColor(Theme.MSG_OUT_FORDWARDED_NAME_TEXT_COLOR); - forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); + Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_outForwardedNameText)); + forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); } else { - forwardNamePaint.setColor(Theme.MSG_IN_FORDWARDED_NAME_TEXT_COLOR); + Theme.chat_forwardNamePaint.setColor(Theme.getColor(Theme.key_chat_inForwardedNameText)); if (mediaBackground) { - forwardNameX = currentBackgroundDrawable.getBounds().left + dp(11); + forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(11); } else { - forwardNameX = currentBackgroundDrawable.getBounds().left + dp(17); + forwardNameX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 11 : 17); } } for (int a = 0; a < 2; a++) { canvas.save(); - canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + dp(16) * a); + canvas.translate(forwardNameX - forwardNameOffsetX[a], forwardNameY + AndroidUtilities.dp(16) * a); forwardedNameLayout[a].draw(canvas); canvas.restore(); } @@ -4515,77 +4850,80 @@ protected void onDraw(Canvas canvas) { if (currentMessageObject.isReply()) { if (currentMessageObject.type == 13) { - replyLinePaint.setColor(Theme.MSG_STICKER_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_STICKER_REPLY_NAME_TEXT_COLOR); - replyTextPaint.setColor(Theme.MSG_STICKER_REPLY_MESSAGE_TEXT_COLOR); + Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyLine)); + Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyNameText)); + Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_stickerReplyMessageText)); if (currentMessageObject.isOutOwner()) { - replyStartX = dp(23); + replyStartX = AndroidUtilities.dp(23); } else { - replyStartX = currentBackgroundDrawable.getBounds().right + dp(17); + replyStartX = currentBackgroundDrawable.getBounds().right + AndroidUtilities.dp(17); } - replyStartY = layoutHeight - dp(58); + replyStartY = layoutHeight - AndroidUtilities.dp(58); if (nameLayout != null) { - replyStartY -= dp(25 + 6); + replyStartY -= AndroidUtilities.dp(25 + 6); } - int backWidth = Math.max(replyNameWidth, replyTextWidth) + dp(14 + (needReplyImage ? 44 : 0)); - Theme.systemDrawable.setColorFilter(Theme.colorFilter); - Theme.systemDrawable.setBounds(replyStartX - dp(7), replyStartY - dp(6), replyStartX - dp(7) + backWidth, replyStartY + dp(41)); - Theme.systemDrawable.draw(canvas); + int backWidth = Math.max(replyNameWidth, replyTextWidth) + AndroidUtilities.dp(14 + (needReplyImage ? 44 : 0)); + Theme.chat_systemDrawable.setColorFilter(Theme.colorFilter); + Theme.chat_systemDrawable.setBounds(replyStartX - AndroidUtilities.dp(7), replyStartY - AndroidUtilities.dp(6), replyStartX - AndroidUtilities.dp(7) + backWidth, replyStartY + AndroidUtilities.dp(41)); + Theme.chat_systemDrawable.draw(canvas); } else { if (currentMessageObject.isOutOwner()) { - replyLinePaint.setColor(Theme.MSG_OUT_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_OUT_REPLY_NAME_TEXT_COLOR); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame)) { - replyTextPaint.setColor(Theme.MSG_OUT_REPLY_MESSAGE_TEXT_COLOR); + Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_outReplyLine)); + Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_outReplyNameText)); + if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { + Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_outReplyMessageText)); } else { - replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_OUT_REPLY_MEDIA_MESSAGE_TEXT_COLOR); + Theme.chat_replyTextPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_outReplyMediaMessageSelectedText : Theme.key_chat_outReplyMediaMessageText)); } - replyStartX = currentBackgroundDrawable.getBounds().left + dp(12); + replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); } else { - replyLinePaint.setColor(Theme.MSG_IN_REPLY_LINE_COLOR); - replyNamePaint.setColor(Theme.MSG_IN_REPLY_NAME_TEXT_COLOR); - if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame)) { - replyTextPaint.setColor(Theme.MSG_IN_REPLY_MESSAGE_TEXT_COLOR); + Theme.chat_replyLinePaint.setColor(Theme.getColor(Theme.key_chat_inReplyLine)); + Theme.chat_replyNamePaint.setColor(Theme.getColor(Theme.key_chat_inReplyNameText)); + if (currentMessageObject.replyMessageObject != null && currentMessageObject.replyMessageObject.type == 0 && !(currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaGame || currentMessageObject.replyMessageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice)) { + Theme.chat_replyTextPaint.setColor(Theme.getColor(Theme.key_chat_inReplyMessageText)); } else { - replyTextPaint.setColor(isDrawSelectedBackground() ? Theme.MSG_IN_REPLY_MEDIA_MESSAGE_SELETED_TEXT_COLOR : Theme.MSG_IN_REPLY_MEDIA_MESSAGE_TEXT_COLOR); + Theme.chat_replyTextPaint.setColor(Theme.getColor(isDrawSelectedBackground() ? Theme.key_chat_inReplyMediaMessageSelectedText : Theme.key_chat_inReplyMediaMessageText)); } if (mediaBackground) { - replyStartX = currentBackgroundDrawable.getBounds().left + dp(12); + replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(12); } else { - replyStartX = currentBackgroundDrawable.getBounds().left + dp(18); + replyStartX = currentBackgroundDrawable.getBounds().left + AndroidUtilities.dp(!mediaBackground && pinnedBottom ? 12 : 18); } } - replyStartY = dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); + replyStartY = AndroidUtilities.dp(12 + (drawForwardedName && forwardedNameLayout[0] != null ? 36 : 0) + (drawNameLayout && nameLayout != null ? 20 : 0)); } - canvas.drawRect(replyStartX, replyStartY, replyStartX + dp(2), replyStartY + dp(35), replyLinePaint); + canvas.drawRect(replyStartX, replyStartY, replyStartX + AndroidUtilities.dp(2), replyStartY + AndroidUtilities.dp(35), Theme.chat_replyLinePaint); if (needReplyImage) { - replyImageReceiver.setImageCoords(replyStartX + dp(10), replyStartY, dp(35), dp(35)); + replyImageReceiver.setImageCoords(replyStartX + AndroidUtilities.dp(10), replyStartY, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); replyImageReceiver.draw(canvas); } if (replyNameLayout != null) { canvas.save(); - canvas.translate(replyStartX - replyNameOffset + dp(10 + (needReplyImage ? 44 : 0)), replyStartY); + canvas.translate(replyStartX - replyNameOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY); replyNameLayout.draw(canvas); canvas.restore(); } if (replyTextLayout != null) { canvas.save(); - canvas.translate(replyStartX - replyTextOffset + dp(10 + (needReplyImage ? 44 : 0)), replyStartY + dp(19)); + canvas.translate(replyStartX - replyTextOffset + AndroidUtilities.dp(10 + (needReplyImage ? 44 : 0)), replyStartY + AndroidUtilities.dp(19)); replyTextLayout.draw(canvas); canvas.restore(); } } - if (drawTime || !mediaBackground) { + if ((drawTime || !mediaBackground) && !forceNotDrawTime) { + if (pinnedBottom) { + canvas.translate(0, AndroidUtilities.dp(2)); + } if (mediaBackground) { Drawable drawable; if (currentMessageObject.type == 13) { - drawable = Theme.timeStickerBackgroundDrawable; + drawable = Theme.chat_timeStickerBackgroundDrawable; } else { - drawable = Theme.timeBackgroundDrawable; + drawable = Theme.chat_timeBackgroundDrawable; } - setDrawableBounds(drawable, timeX - dp(4), layoutHeight - dp(27), timeWidth + dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), dp(17)); + setDrawableBounds(drawable, timeX - AndroidUtilities.dp(4), layoutHeight - AndroidUtilities.dp(27), timeWidth + AndroidUtilities.dp(8 + (currentMessageObject.isOutOwner() ? 20 : 0)), AndroidUtilities.dp(17)); drawable.draw(canvas); int additionalX = 0; @@ -4594,22 +4932,31 @@ protected void onDraw(Canvas canvas) { if (currentMessageObject.isSending()) { if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.clockMediaDrawable, timeX + dp(11), layoutHeight - dp(13.0f) - Theme.clockMediaDrawable.getIntrinsicHeight()); - Theme.clockMediaDrawable.draw(canvas); + setDrawableBounds(Theme.chat_msgMediaClockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(13.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaClockDrawable.draw(canvas); } } else if (currentMessageObject.isSendError()) { if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.errorDrawable, timeX + dp(11), layoutHeight - dp(12.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); + int x = timeX + AndroidUtilities.dp(11); + int y = layoutHeight - AndroidUtilities.dp(26.5f); + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); } } else { - Drawable countDrawable = Theme.viewsMediaCountDrawable; - setDrawableBounds(countDrawable, timeX, layoutHeight - dp(9.5f) - timeLayout.getHeight()); - countDrawable.draw(canvas); + Drawable viewsDrawable; + if (currentMessageObject.type == 13) { + viewsDrawable = Theme.chat_msgStickerViewsDrawable; + } else { + viewsDrawable = Theme.chat_msgMediaViewsDrawable; + } + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(9.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); if (viewsLayout != null) { canvas.save(); - canvas.translate(timeX + countDrawable.getIntrinsicWidth() + dp(3), layoutHeight - dp(11.3f) - timeLayout.getHeight()); + canvas.translate(timeX + viewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(11.3f) - timeLayout.getHeight()); viewsLayout.draw(canvas); canvas.restore(); } @@ -4617,7 +4964,7 @@ protected void onDraw(Canvas canvas) { } canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - dp(11.3f) - timeLayout.getHeight()); + canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(11.3f) - timeLayout.getHeight()); timeLayout.draw(canvas); canvas.restore(); } else { @@ -4627,27 +4974,33 @@ protected void onDraw(Canvas canvas) { if (currentMessageObject.isSending()) { if (!currentMessageObject.isOutOwner()) { - Drawable clockDrawable = Theme.clockChannelDrawable[isDrawSelectedBackground() ? 1 : 0]; - setDrawableBounds(clockDrawable, timeX + dp(11), layoutHeight - dp(8.5f) - clockDrawable.getIntrinsicHeight()); + Drawable clockDrawable = isDrawSelectedBackground() ? Theme.chat_msgInSelectedClockDrawable : Theme.chat_msgInClockDrawable; + setDrawableBounds(clockDrawable, timeX + AndroidUtilities.dp(11), layoutHeight - AndroidUtilities.dp(8.5f) - clockDrawable.getIntrinsicHeight()); clockDrawable.draw(canvas); } } else if (currentMessageObject.isSendError()) { if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.errorDrawable, timeX + dp(11), layoutHeight - dp(6.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); + int x = timeX + AndroidUtilities.dp(11); + int y = layoutHeight - AndroidUtilities.dp(20.5f); + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); } } else { if (!currentMessageObject.isOutOwner()) { - setDrawableBounds(Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0], timeX, layoutHeight - dp(4.5f) - timeLayout.getHeight()); - Theme.viewsCountDrawable[isDrawSelectedBackground() ? 1 : 0].draw(canvas); + Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgInViewsSelectedDrawable : Theme.chat_msgInViewsDrawable; + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); } else { - setDrawableBounds(Theme.viewsOutCountDrawable, timeX, layoutHeight - dp(4.5f) - timeLayout.getHeight()); - Theme.viewsOutCountDrawable.draw(canvas); + Drawable viewsDrawable = isDrawSelectedBackground() ? Theme.chat_msgOutViewsSelectedDrawable : Theme.chat_msgOutViewsDrawable; + setDrawableBounds(viewsDrawable, timeX, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight()); + viewsDrawable.draw(canvas); } if (viewsLayout != null) { canvas.save(); - canvas.translate(timeX + Theme.viewsOutCountDrawable.getIntrinsicWidth() + dp(3), layoutHeight - dp(6.5f) - timeLayout.getHeight()); + canvas.translate(timeX + Theme.chat_msgInViewsDrawable.getIntrinsicWidth() + AndroidUtilities.dp(3), layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); viewsLayout.draw(canvas); canvas.restore(); } @@ -4655,10 +5008,9 @@ protected void onDraw(Canvas canvas) { } canvas.save(); - canvas.translate(timeX + additionalX, layoutHeight - dp(6.5f) - timeLayout.getHeight()); + canvas.translate(timeX + additionalX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight()); timeLayout.draw(canvas); canvas.restore(); - //canvas.drawRect(timeX, layoutHeight - AndroidUtilities.dp(6.5f) - timeLayout.getHeight(), timeX + availableTimeWidth, layoutHeight - AndroidUtilities.dp(4.5f) - timeLayout.getHeight(), timePaint); } if (currentMessageObject.isOutOwner()) { @@ -4692,59 +5044,87 @@ protected void onDraw(Canvas canvas) { if (drawClock) { if (!mediaBackground) { - setDrawableBounds(Theme.clockDrawable, layoutWidth - dp(18.5f) - Theme.clockDrawable.getIntrinsicWidth(), layoutHeight - dp(8.5f) - Theme.clockDrawable.getIntrinsicHeight()); - Theme.clockDrawable.draw(canvas); + setDrawableBounds(Theme.chat_msgOutClockDrawable, layoutWidth - AndroidUtilities.dp(18.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.5f) - Theme.chat_msgOutClockDrawable.getIntrinsicHeight()); + Theme.chat_msgOutClockDrawable.draw(canvas); } else { - setDrawableBounds(Theme.clockMediaDrawable, layoutWidth - dp(22.0f) - Theme.clockMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.clockMediaDrawable.getIntrinsicHeight()); - Theme.clockMediaDrawable.draw(canvas); + if (currentMessageObject.type == 13) { + setDrawableBounds(Theme.chat_msgStickerClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgStickerClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgStickerClockDrawable.getIntrinsicHeight()); + Theme.chat_msgStickerClockDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_msgMediaClockDrawable, layoutWidth - AndroidUtilities.dp(22.0f) - Theme.chat_msgMediaClockDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgMediaClockDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaClockDrawable.draw(canvas); + } } } + if (isBroadcast) { if (drawCheck1 || drawCheck2) { if (!mediaBackground) { - setDrawableBounds(Theme.broadcastDrawable, layoutWidth - dp(20.5f) - Theme.broadcastDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.broadcastDrawable.getIntrinsicHeight()); - Theme.broadcastDrawable.draw(canvas); + setDrawableBounds(Theme.chat_msgBroadcastDrawable, layoutWidth - AndroidUtilities.dp(20.5f) - Theme.chat_msgBroadcastDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - Theme.chat_msgBroadcastDrawable.getIntrinsicHeight()); + Theme.chat_msgBroadcastDrawable.draw(canvas); } else { - setDrawableBounds(Theme.broadcastMediaDrawable, layoutWidth - dp(24.0f) - Theme.broadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(13.0f) - Theme.broadcastMediaDrawable.getIntrinsicHeight()); - Theme.broadcastMediaDrawable.draw(canvas); + setDrawableBounds(Theme.chat_msgBroadcastMediaDrawable, layoutWidth - AndroidUtilities.dp(24.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(13.0f) - Theme.chat_msgBroadcastMediaDrawable.getIntrinsicHeight()); + Theme.chat_msgBroadcastMediaDrawable.draw(canvas); } } } else { if (drawCheck2) { if (!mediaBackground) { + Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutCheckSelectedDrawable : Theme.chat_msgOutCheckDrawable; if (drawCheck1) { - setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(22.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(22.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); } else { - setDrawableBounds(Theme.checkDrawable, layoutWidth - dp(18.5f) - Theme.checkDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.checkDrawable.getIntrinsicHeight()); + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18.5f) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); } - Theme.checkDrawable.draw(canvas); + drawable.draw(canvas); } else { - if (drawCheck1) { - setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(26.3f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); + if (currentMessageObject.type == 13) { + if (drawCheck1) { + setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.chat_msgStickerCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgStickerCheckDrawable.getIntrinsicHeight()); + } + Theme.chat_msgStickerCheckDrawable.draw(canvas); } else { - setDrawableBounds(Theme.checkMediaDrawable, layoutWidth - dp(21.5f) - Theme.checkMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.checkMediaDrawable.getIntrinsicHeight()); + if (drawCheck1) { + setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(26.3f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); + } else { + setDrawableBounds(Theme.chat_msgMediaCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgMediaCheckDrawable.getIntrinsicHeight()); + } + Theme.chat_msgMediaCheckDrawable.draw(canvas); } - Theme.checkMediaDrawable.draw(canvas); } } if (drawCheck1) { if (!mediaBackground) { - setDrawableBounds(Theme.halfCheckDrawable, layoutWidth - dp(18) - Theme.halfCheckDrawable.getIntrinsicWidth(), layoutHeight - dp(8.0f) - Theme.halfCheckDrawable.getIntrinsicHeight()); - Theme.halfCheckDrawable.draw(canvas); + Drawable drawable = isDrawSelectedBackground() ? Theme.chat_msgOutHalfCheckSelectedDrawable : Theme.chat_msgOutHalfCheckDrawable; + setDrawableBounds(drawable, layoutWidth - AndroidUtilities.dp(18) - drawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(8.0f) - drawable.getIntrinsicHeight()); + drawable.draw(canvas); } else { - setDrawableBounds(Theme.halfCheckMediaDrawable, layoutWidth - dp(21.5f) - Theme.halfCheckMediaDrawable.getIntrinsicWidth(), layoutHeight - dp(12.5f) - Theme.halfCheckMediaDrawable.getIntrinsicHeight()); - Theme.halfCheckMediaDrawable.draw(canvas); + if (currentMessageObject.type == 13) { + setDrawableBounds(Theme.chat_msgStickerHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgStickerHalfCheckDrawable.getIntrinsicHeight()); + Theme.chat_msgStickerHalfCheckDrawable.draw(canvas); + } else { + setDrawableBounds(Theme.chat_msgMediaHalfCheckDrawable, layoutWidth - AndroidUtilities.dp(21.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicWidth(), layoutHeight - AndroidUtilities.dp(12.5f) - Theme.chat_msgMediaHalfCheckDrawable.getIntrinsicHeight()); + Theme.chat_msgMediaHalfCheckDrawable.draw(canvas); + } } } } if (drawError) { + int x; + int y; if (!mediaBackground) { - setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(18) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(7) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); + x = layoutWidth - AndroidUtilities.dp(32); + y = layoutHeight - AndroidUtilities.dp(21); } else { - setDrawableBounds(Theme.errorDrawable, layoutWidth - dp(20.5f) - Theme.errorDrawable.getIntrinsicWidth(), layoutHeight - dp(11.5f) - Theme.errorDrawable.getIntrinsicHeight()); - Theme.errorDrawable.draw(canvas); + x = layoutWidth - AndroidUtilities.dp(34.5f); + y = layoutHeight - AndroidUtilities.dp(25.5f); } + rect.set(x, y, x + AndroidUtilities.dp(14), y + AndroidUtilities.dp(14)); + canvas.drawRoundRect(rect, AndroidUtilities.dp(1), AndroidUtilities.dp(1), Theme.chat_msgErrorPaint); + setDrawableBounds(Theme.chat_msgErrorDrawable, x + AndroidUtilities.dp(6), y + AndroidUtilities.dp(2)); + Theme.chat_msgErrorDrawable.draw(canvas); } } } @@ -4758,4 +5138,16 @@ public int getObserverTag() { public MessageObject getMessageObject() { return currentMessageObject; } + + public boolean isPinnedBottom() { + return pinnedBottom; + } + + public boolean isPinnedTop() { + return pinnedTop; + } + + public int getLayoutHeight() { + return layoutHeight; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatUnreadCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatUnreadCell.java index b69baddc4d3..18d4bd925d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatUnreadCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatUnreadCell.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -23,23 +25,27 @@ public class ChatUnreadCell extends FrameLayout { private TextView textView; + private ImageView imageView; + private FrameLayout backgroundLayout; public ChatUnreadCell(Context context) { super(context); - FrameLayout frameLayout = new FrameLayout(context); - frameLayout.setBackgroundResource(R.drawable.newmsg_divider); - addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 27, Gravity.LEFT | Gravity.TOP, 0, 7, 0, 0)); + backgroundLayout = new FrameLayout(context); + backgroundLayout.setBackgroundResource(R.drawable.newmsg_divider); + backgroundLayout.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_unreadMessagesStartBackground), PorterDuff.Mode.MULTIPLY)); + addView(backgroundLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 27, Gravity.LEFT | Gravity.TOP, 0, 7, 0, 0)); - ImageView imageView = new ImageView(context); + imageView = new ImageView(context); imageView.setImageResource(R.drawable.ic_ab_new); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_unreadMessagesStartArrowIcon), PorterDuff.Mode.MULTIPLY)); imageView.setPadding(0, AndroidUtilities.dp(2), 0, 0); - frameLayout.addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); + backgroundLayout.addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); textView = new TextView(context); textView.setPadding(0, 0, 0, AndroidUtilities.dp(1)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(Theme.CHAT_UNREAD_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_chat_unreadMessagesStartText)); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } @@ -48,6 +54,18 @@ public void setText(String text) { textView.setText(text); } + public ImageView getImageView() { + return imageView; + } + + public TextView getTextView() { + return textView; + } + + public FrameLayout getBackgroundLayout() { + return backgroundLayout; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(40), MeasureSpec.EXACTLY)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java index f32a6553ecc..aa45016ff29 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/CheckBoxCell.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -19,6 +18,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.CheckBoxSquare; import org.telegram.ui.Components.LayoutHelper; @@ -27,20 +27,13 @@ public class CheckBoxCell extends FrameLayout { private TextView textView; private TextView valueTextView; private CheckBoxSquare checkBox; - private static Paint paint; private boolean needDivider; - public CheckBoxCell(Context context) { + public CheckBoxCell(Context context, boolean alert) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(alert ? Theme.key_dialogTextBlack : Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -50,7 +43,7 @@ public CheckBoxCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 17 : 46), 0, (LocaleController.isRTL ? 46 : 17), 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff2f8cc9); + valueTextView.setTextColor(Theme.getColor(alert ? Theme.key_dialogTextBlue : Theme.key_windowBackgroundWhiteValueText)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -59,7 +52,7 @@ public CheckBoxCell(Context context) { valueTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL); addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, 17, 0, 17, 0)); - checkBox = new CheckBoxSquare(context); + checkBox = new CheckBoxSquare(context, alert); addView(checkBox, LayoutHelper.createFrame(18, 18, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 0 : 17), 15, (LocaleController.isRTL ? 17 : 0), 0)); } @@ -94,10 +87,22 @@ public boolean isChecked() { return checkBox.isChecked(); } + public TextView getTextView() { + return textView; + } + + public TextView getValueTextView() { + return valueTextView; + } + + public CheckBoxSquare getCheckBox() { + return checkBox; + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java index 042fd89f593..42b9a47e0ce 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ContextLinkCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -11,12 +11,9 @@ import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.drawable.Drawable; -import android.os.Build; import android.text.Layout; import android.text.StaticLayout; -import android.text.TextPaint; import android.text.TextUtils; import android.view.MotionEvent; import android.view.SoundEffectConstants; @@ -32,7 +29,6 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; -import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLRPC; @@ -83,11 +79,6 @@ public interface ContextLinkCellDelegate { private boolean mediaWebpage; private MessageObject currentMessageObject; - private static TextPaint titleTextPaint; - private static TextPaint descriptionTextPaint; - private static Paint paint; - private static Drawable shadowDrawable; - private int TAG; private int buttonState; private RadialProgress radialProgress; @@ -103,21 +94,6 @@ public interface ContextLinkCellDelegate { public ContextLinkCell(Context context) { super(context); - if (titleTextPaint == null) { - titleTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - titleTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - titleTextPaint.setColor(0xff212121); - - descriptionTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - - titleTextPaint.setTextSize(AndroidUtilities.dp(15)); - descriptionTextPaint.setTextSize(AndroidUtilities.dp(13)); - linkImageView = new ImageReceiver(this); letterDrawable = new LetterDrawable(); radialProgress = new RadialProgress(this); @@ -156,33 +132,33 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!mediaWebpage && inlineResult != null) { if (inlineResult.title != null) { try { - int width = (int) Math.ceil(titleTextPaint.measureText(inlineResult.title)); - CharSequence titleFinal = TextUtils.ellipsize(Emoji.replaceEmoji(inlineResult.title.replace('\n', ' '), titleTextPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false), titleTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.END); - titleLayout = new StaticLayout(titleFinal, titleTextPaint, maxWidth + AndroidUtilities.dp(4), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int width = (int) Math.ceil(Theme.chat_contextResult_titleTextPaint.measureText(inlineResult.title)); + CharSequence titleFinal = TextUtils.ellipsize(Emoji.replaceEmoji(inlineResult.title.replace('\n', ' '), Theme.chat_contextResult_titleTextPaint.getFontMetricsInt(), AndroidUtilities.dp(15), false), Theme.chat_contextResult_titleTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.END); + titleLayout = new StaticLayout(titleFinal, Theme.chat_contextResult_titleTextPaint, maxWidth + AndroidUtilities.dp(4), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } letterDrawable.setTitle(inlineResult.title); } if (inlineResult.description != null) { try { - descriptionLayout = ChatMessageCell.generateStaticLayout(Emoji.replaceEmoji(inlineResult.description, descriptionTextPaint.getFontMetricsInt(), AndroidUtilities.dp(13), false), descriptionTextPaint, maxWidth, maxWidth, 0, 3); + descriptionLayout = ChatMessageCell.generateStaticLayout(Emoji.replaceEmoji(inlineResult.description, Theme.chat_contextResult_descriptionTextPaint.getFontMetricsInt(), AndroidUtilities.dp(13), false), Theme.chat_contextResult_descriptionTextPaint, maxWidth, maxWidth, 0, 3); if (descriptionLayout.getLineCount() > 0) { linkY = descriptionY + descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1) + AndroidUtilities.dp(1); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (inlineResult.url != null) { try { - int width = (int) Math.ceil(descriptionTextPaint.measureText(inlineResult.url)); - CharSequence linkFinal = TextUtils.ellipsize(inlineResult.url.replace('\n', ' '), descriptionTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.MIDDLE); - linkLayout = new StaticLayout(linkFinal, descriptionTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + int width = (int) Math.ceil(Theme.chat_contextResult_descriptionTextPaint.measureText(inlineResult.url)); + CharSequence linkFinal = TextUtils.ellipsize(inlineResult.url.replace('\n', ' '), Theme.chat_contextResult_descriptionTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.MIDDLE); + linkLayout = new StaticLayout(linkFinal, Theme.chat_contextResult_descriptionTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -297,7 +273,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } if (mediaWebpage) { - setBackgroundDrawable(null); width = viewWidth; int height = MeasureSpec.getSize(heightMeasureSpec); if (height == 0) { @@ -309,7 +284,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { radialProgress.setProgressRect(x, y, x + AndroidUtilities.dp(24), y + AndroidUtilities.dp(24)); linkImageView.setImageCoords(0, 0, width, height); } else { - setBackgroundResource(R.drawable.list_selector); int height = 0; if (titleLayout != null && titleLayout.getLineCount() != 0) { height += titleLayout.getLineBottom(titleLayout.getLineCount() - 1); @@ -405,9 +379,6 @@ private void setAttachType() { public void setLink(TLRPC.BotInlineResult contextResult, boolean media, boolean divider, boolean shadow) { needDivider = divider; needShadow = shadow; - if (needShadow && shadowDrawable == null) { - shadowDrawable = getContext().getResources().getDrawable(R.drawable.header_shadow); - } inlineResult = contextResult; if (inlineResult != null && inlineResult.document != null) { documentAttach = inlineResult.document; @@ -478,12 +449,6 @@ public MessageObject getMessageObject() { @Override public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - if (mediaWebpage || delegate == null || inlineResult == null) { return super.onTouchEvent(event); } @@ -597,7 +562,7 @@ protected void onDraw(Canvas canvas) { } if (descriptionLayout != null) { - descriptionTextPaint.setColor(0xff8a8a8a); + Theme.chat_contextResult_descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), descriptionY); descriptionLayout.draw(canvas); @@ -605,7 +570,7 @@ protected void onDraw(Canvas canvas) { } if (linkLayout != null) { - descriptionTextPaint.setColor(Theme.MSG_LINK_TEXT_COLOR); + Theme.chat_contextResult_descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), linkY); linkLayout.draw(canvas); @@ -614,44 +579,44 @@ protected void onDraw(Canvas canvas) { if (!mediaWebpage) { if (documentAttachType == DOCUMENT_ATTACH_TYPE_AUDIO || documentAttachType == DOCUMENT_ATTACH_TYPE_MUSIC) { - radialProgress.setProgressColor(buttonPressed ? Theme.MSG_IN_AUDIO_SELECTED_PROGRESS_COLOR : Theme.MSG_IN_AUDIO_PROGRESS_COLOR); + radialProgress.setProgressColor(Theme.getColor(buttonPressed ? Theme.key_chat_inAudioSelectedProgress : Theme.key_chat_inAudioProgress)); radialProgress.draw(canvas); } else if (inlineResult != null && inlineResult.type.equals("file")) { - int w = Theme.inlineDocDrawable.getIntrinsicWidth(); - int h = Theme.inlineDocDrawable.getIntrinsicHeight(); + int w = Theme.chat_inlineResultFile.getIntrinsicWidth(); + int h = Theme.chat_inlineResultFile.getIntrinsicHeight(); int x = linkImageView.getImageX() + (AndroidUtilities.dp(52) - w) / 2; int y = linkImageView.getImageY() + (AndroidUtilities.dp(52) - h) / 2; canvas.drawRect(linkImageView.getImageX(), linkImageView.getImageY(), linkImageView.getImageX() + AndroidUtilities.dp(52), linkImageView.getImageY() + AndroidUtilities.dp(52), LetterDrawable.paint); - Theme.inlineDocDrawable.setBounds(x, y, x + w, y + h); - Theme.inlineDocDrawable.draw(canvas); + Theme.chat_inlineResultFile.setBounds(x, y, x + w, y + h); + Theme.chat_inlineResultFile.draw(canvas); } else if (inlineResult != null && (inlineResult.type.equals("audio") || inlineResult.type.equals("voice"))) { - int w = Theme.inlineAudioDrawable.getIntrinsicWidth(); - int h = Theme.inlineAudioDrawable.getIntrinsicHeight(); + int w = Theme.chat_inlineResultAudio.getIntrinsicWidth(); + int h = Theme.chat_inlineResultAudio.getIntrinsicHeight(); int x = linkImageView.getImageX() + (AndroidUtilities.dp(52) - w) / 2; int y = linkImageView.getImageY() + (AndroidUtilities.dp(52) - h) / 2; canvas.drawRect(linkImageView.getImageX(), linkImageView.getImageY(), linkImageView.getImageX() + AndroidUtilities.dp(52), linkImageView.getImageY() + AndroidUtilities.dp(52), LetterDrawable.paint); - Theme.inlineAudioDrawable.setBounds(x, y, x + w, y + h); - Theme.inlineAudioDrawable.draw(canvas); + Theme.chat_inlineResultAudio.setBounds(x, y, x + w, y + h); + Theme.chat_inlineResultAudio.draw(canvas); } else if (inlineResult != null && (inlineResult.type.equals("venue") || inlineResult.type.equals("geo"))) { - int w = Theme.inlineLocationDrawable.getIntrinsicWidth(); - int h = Theme.inlineLocationDrawable.getIntrinsicHeight(); + int w = Theme.chat_inlineResultLocation.getIntrinsicWidth(); + int h = Theme.chat_inlineResultLocation.getIntrinsicHeight(); int x = linkImageView.getImageX() + (AndroidUtilities.dp(52) - w) / 2; int y = linkImageView.getImageY() + (AndroidUtilities.dp(52) - h) / 2; canvas.drawRect(linkImageView.getImageX(), linkImageView.getImageY(), linkImageView.getImageX() + AndroidUtilities.dp(52), linkImageView.getImageY() + AndroidUtilities.dp(52), LetterDrawable.paint); - Theme.inlineLocationDrawable.setBounds(x, y, x + w, y + h); - Theme.inlineLocationDrawable.draw(canvas); + Theme.chat_inlineResultLocation.setBounds(x, y, x + w, y + h); + Theme.chat_inlineResultLocation.draw(canvas); } else { letterDrawable.draw(canvas); } } else { if (inlineResult != null && (inlineResult.send_message instanceof TLRPC.TL_botInlineMessageMediaGeo || inlineResult.send_message instanceof TLRPC.TL_botInlineMessageMediaVenue)) { - int w = Theme.inlineLocationDrawable.getIntrinsicWidth(); - int h = Theme.inlineLocationDrawable.getIntrinsicHeight(); + int w = Theme.chat_inlineResultLocation.getIntrinsicWidth(); + int h = Theme.chat_inlineResultLocation.getIntrinsicHeight(); int x = linkImageView.getImageX() + (linkImageView.getImageWidth() - w) / 2; int y = linkImageView.getImageY() + (linkImageView.getImageHeight() - h) / 2; canvas.drawRect(linkImageView.getImageX(), linkImageView.getImageY(), linkImageView.getImageX() + linkImageView.getImageWidth(), linkImageView.getImageY() + linkImageView.getImageHeight(), LetterDrawable.paint); - Theme.inlineLocationDrawable.setBounds(x, y, x + w, y + h); - Theme.inlineLocationDrawable.draw(canvas); + Theme.chat_inlineResultLocation.setBounds(x, y, x + w, y + h); + Theme.chat_inlineResultLocation.draw(canvas); } } if (drawLinkImageView) { @@ -678,20 +643,19 @@ protected void onDraw(Canvas canvas) { canvas.restore(); } if (mediaWebpage && (documentAttachType == DOCUMENT_ATTACH_TYPE_PHOTO || documentAttachType == DOCUMENT_ATTACH_TYPE_GIF)) { - radialProgress.setProgressColor(0xffffffff); radialProgress.draw(canvas); } if (needDivider && !mediaWebpage) { if (LocaleController.isRTL) { - canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, paint); + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, Theme.dividerPaint); } else { - canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, paint); + canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); } } - if (needShadow && shadowDrawable != null) { - shadowDrawable.setBounds(0, 0, getMeasuredWidth(), AndroidUtilities.dp(3)); - shadowDrawable.draw(canvas); + if (needShadow) { + Theme.chat_contextResult_shadowUnderSwitchDrawable.setBounds(0, 0, getMeasuredWidth(), AndroidUtilities.dp(3)); + Theme.chat_contextResult_shadowUnderSwitchDrawable.draw(canvas); } } @@ -701,9 +665,9 @@ private Drawable getDrawableForCurrentState() { return null; } radialProgress.setAlphaForPrevious(false); - return Theme.fileStatesDrawable[buttonState + 5][buttonPressed ? 1 : 0]; + return Theme.chat_fileStatesDrawable[buttonState + 5][buttonPressed ? 1 : 0]; } - return buttonState == 1 ? Theme.photoStatesDrawables[5][0] : null; + return buttonState == 1 ? Theme.chat_photoStatesDrawables[5][0] : null; } public void updateButtonState(boolean animated) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java index 30876f8e6fb..075b714016d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -3,16 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Build; +import android.graphics.RectF; import android.text.Layout; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -20,7 +18,6 @@ import android.text.TextPaint; import android.text.TextUtils; import android.text.style.ForegroundColorSpan; -import android.view.MotionEvent; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; @@ -44,29 +41,21 @@ public class DialogCell extends BaseCell { - private static TextPaint namePaint; - private static TextPaint nameEncryptedPaint; - private static TextPaint messagePaint; - private static TextPaint messagePrintingPaint; - private static TextPaint timePaint; - private static TextPaint countPaint; - - private static Drawable checkDrawable; - private static Drawable halfCheckDrawable; - private static Drawable clockDrawable; - private static Drawable errorDrawable; - private static Drawable lockDrawable; - private static Drawable countDrawable; - private static Drawable countDrawableGrey; - private static Drawable groupDrawable; - private static Drawable broadcastDrawable; - private static Drawable botDrawable; - private static Drawable muteDrawable; - private static Drawable verifiedDrawable; - - private static Paint linePaint; - private static Paint backPaint; + public static class CustomDialog { + public String name; + public String message; + public int id; + public int unread_count; + public boolean pinned; + public boolean muted; + public int type; + public int date; + public boolean verified; + public boolean isMedia; + public boolean sent; + } + private CustomDialog customDialog; private long currentDialogId; private int currentEditDate; private boolean isDialogCell; @@ -79,8 +68,8 @@ public class DialogCell extends BaseCell { private int index; private int dialogsType; - private ImageReceiver avatarImage; - private AvatarDrawable avatarDrawable; + private ImageReceiver avatarImage = new ImageReceiver(this); + private AvatarDrawable avatarDrawable = new AvatarDrawable(); private TLRPC.User user = null; private TLRPC.Chat chat = null; @@ -119,6 +108,10 @@ public class DialogCell extends BaseCell { private int errorTop = AndroidUtilities.dp(39); private int errorLeft; + private boolean drawPin; + private int pinTop = AndroidUtilities.dp(39); + private int pinLeft; + private boolean drawCount; private int countTop = AndroidUtilities.dp(39); private int countLeft; @@ -131,65 +124,13 @@ public class DialogCell extends BaseCell { private boolean isSelected; + private RectF rect = new RectF(); + public DialogCell(Context context) { super(context); - if (namePaint == null) { - namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - - namePaint.setColor(0xff212121); - namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - nameEncryptedPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - nameEncryptedPaint.setColor(0xff00a60e); - nameEncryptedPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - messagePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - messagePaint.setColor(Theme.DIALOGS_MESSAGE_TEXT_COLOR); - messagePaint.linkColor = Theme.DIALOGS_MESSAGE_TEXT_COLOR; - - linePaint = new Paint(); - linePaint.setColor(0xffdcdcdc); - - backPaint = new Paint(); - backPaint.setColor(0x0f000000); - - messagePrintingPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - messagePrintingPaint.setColor(Theme.DIALOGS_PRINTING_TEXT_COLOR); - - timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - timePaint.setColor(0xff999999); - - countPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - countPaint.setColor(0xffffffff); - countPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - lockDrawable = getResources().getDrawable(R.drawable.list_secret); - checkDrawable = getResources().getDrawable(R.drawable.dialogs_check); - halfCheckDrawable = getResources().getDrawable(R.drawable.dialogs_halfcheck); - clockDrawable = getResources().getDrawable(R.drawable.msg_clock); - errorDrawable = getResources().getDrawable(R.drawable.dialogs_warning); - countDrawable = getResources().getDrawable(R.drawable.dialogs_badge); - countDrawableGrey = getResources().getDrawable(R.drawable.dialogs_badge2); - groupDrawable = getResources().getDrawable(R.drawable.list_group); - broadcastDrawable = getResources().getDrawable(R.drawable.list_broadcast); - muteDrawable = getResources().getDrawable(R.drawable.mute_grey); - verifiedDrawable = getResources().getDrawable(R.drawable.check_list); - botDrawable = getResources().getDrawable(R.drawable.bot_list); - } - - namePaint.setTextSize(AndroidUtilities.dp(17)); - nameEncryptedPaint.setTextSize(AndroidUtilities.dp(17)); - messagePaint.setTextSize(AndroidUtilities.dp(16)); - messagePrintingPaint.setTextSize(AndroidUtilities.dp(16)); - timePaint.setTextSize(AndroidUtilities.dp(13)); - countPaint.setTextSize(AndroidUtilities.dp(13)); - - setBackgroundResource(R.drawable.list_selector); - - avatarImage = new ImageReceiver(this); + Theme.createDialogsResources(context); avatarImage.setRoundRadius(AndroidUtilities.dp(26)); - avatarDrawable = new AvatarDrawable(); } public void setDialog(TLRPC.TL_dialog dialog, int i, int type) { @@ -200,6 +141,11 @@ public void setDialog(TLRPC.TL_dialog dialog, int i, int type) { update(0); } + public void setDialog(CustomDialog dialog) { + customDialog = dialog; + update(0); + } + public void setDialog(long dialog_id, MessageObject messageObject, int date) { currentDialogId = dialog_id; message = messageObject; @@ -237,7 +183,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (currentDialogId == 0) { + if (currentDialogId == 0 && customDialog == null) { super.onLayout(changed, left, top, right, bottom); return; } @@ -246,16 +192,6 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto } } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - public void buildLayout() { String nameString = ""; String timeString = ""; @@ -265,8 +201,8 @@ public void buildLayout() { if (isDialogCell) { printingString = MessagesController.getInstance().printingStrings.get(currentDialogId); } - TextPaint currentNamePaint = namePaint; - TextPaint currentMessagePaint = messagePaint; + TextPaint currentNamePaint = Theme.dialogs_namePaint; + TextPaint currentMessagePaint = Theme.dialogs_messagePaint; boolean checkMessage = true; drawNameGroup = false; @@ -275,273 +211,361 @@ public void buildLayout() { drawNameBot = false; drawVerified = false; - if (encryptedChat != null) { - drawNameLock = true; - nameLockTop = AndroidUtilities.dp(16.5f); - if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + lockDrawable.getIntrinsicWidth(); - } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - lockDrawable.getIntrinsicWidth(); - nameLeft = AndroidUtilities.dp(14); - } - } else { - if (chat != null) { - if (chat.id < 0 || ChatObject.isChannel(chat) && !chat.megagroup) { - drawNameBroadcast = true; - nameLockTop = AndroidUtilities.dp(16.5f); + if (customDialog != null) { + if (customDialog.type == 2) { + drawNameLock = true; + nameLockTop = AndroidUtilities.dp(16.5f); + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLeft = AndroidUtilities.dp(14); + } + } else { + drawVerified = customDialog.verified; + if (customDialog.type == 1) { drawNameGroup = true; nameLockTop = AndroidUtilities.dp(17.5f); + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + nameLeft = AndroidUtilities.dp(14); + } + } else { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } } - drawVerified = chat.verified; + } - if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? groupDrawable.getIntrinsicWidth() : broadcastDrawable.getIntrinsicWidth()); + if (customDialog.type == 1) { + String name = LocaleController.getString("FromYou", R.string.FromYou); + checkMessage = false; + SpannableStringBuilder stringBuilder; + if (customDialog.isMedia) { + currentMessagePaint = Theme.dialogs_messagePrintingPaint; + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_attachMessage)), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - (drawNameGroup ? groupDrawable.getIntrinsicWidth() : broadcastDrawable.getIntrinsicWidth()); - nameLeft = AndroidUtilities.dp(14); + String mess = customDialog.message; + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + } + if (stringBuilder.length() > 0) { + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_nameMessage)), 0, name.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + messageString = Emoji.replaceEmoji(stringBuilder, Theme.dialogs_messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + } else { + messageString = customDialog.message; + if (customDialog.isMedia) { + currentMessagePaint = Theme.dialogs_messagePrintingPaint; } + } + + timeString = LocaleController.stringForMessageListDate(customDialog.date); + + if (customDialog.unread_count != 0) { + drawCount = true; + countString = String.format("%d", customDialog.unread_count); + } else { + drawCount = false; + } + + if (customDialog.sent) { + drawCheck1 = true; + drawCheck2 = true; + drawClock = false; + drawError = false; } else { + drawCheck1 = false; + drawCheck2 = false; + drawClock = false; + drawError = false; + } + nameString = customDialog.name; + if (customDialog.type == 2) { + currentNamePaint = Theme.dialogs_nameEncryptedPaint; + } + } else { + if (encryptedChat != null) { + drawNameLock = true; + nameLockTop = AndroidUtilities.dp(16.5f); if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(14); } - if (user != null) { - if (user.bot) { - drawNameBot = true; + } else { + if (chat != null) { + if (chat.id < 0 || ChatObject.isChannel(chat) && !chat.megagroup) { + drawNameBroadcast = true; nameLockTop = AndroidUtilities.dp(16.5f); - if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + botDrawable.getIntrinsicWidth(); - } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - botDrawable.getIntrinsicWidth(); - nameLeft = AndroidUtilities.dp(14); + } else { + drawNameGroup = true; + nameLockTop = AndroidUtilities.dp(17.5f); + } + drawVerified = chat.verified; + + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); + nameLeft = AndroidUtilities.dp(14); + } + } else { + if (!LocaleController.isRTL) { + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + } else { + nameLeft = AndroidUtilities.dp(14); + } + if (user != null) { + if (user.bot) { + drawNameBot = true; + nameLockTop = AndroidUtilities.dp(16.5f); + if (!LocaleController.isRTL) { + nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_botDrawable.getIntrinsicWidth(); + } else { + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline) - Theme.dialogs_botDrawable.getIntrinsicWidth(); + nameLeft = AndroidUtilities.dp(14); + } } + drawVerified = user.verified; } - drawVerified = user.verified; } } - } - int lastDate = lastMessageDate; - if (lastMessageDate == 0 && message != null) { - lastDate = message.messageOwner.date; - } + int lastDate = lastMessageDate; + if (lastMessageDate == 0 && message != null) { + lastDate = message.messageOwner.date; + } - if (isDialogCell) { - draftMessage = DraftQuery.getDraft(currentDialogId); - if (draftMessage != null && (TextUtils.isEmpty(draftMessage.message) && draftMessage.reply_to_msg_id == 0 || lastDate > draftMessage.date && unreadCount != 0) || - ChatObject.isChannel(chat) && !chat.megagroup && !chat.creator && !chat.editor || - chat != null && (chat.left || chat.kicked)) { + if (isDialogCell) { + draftMessage = DraftQuery.getDraft(currentDialogId); + if (draftMessage != null && (TextUtils.isEmpty(draftMessage.message) && draftMessage.reply_to_msg_id == 0 || lastDate > draftMessage.date && unreadCount != 0) || + ChatObject.isChannel(chat) && !chat.megagroup && !chat.creator && !chat.editor || + chat != null && (chat.left || chat.kicked)) { + draftMessage = null; + } + } else { draftMessage = null; } - } else { - draftMessage = null; - } - if (printingString != null) { - lastPrintString = messageString = printingString; - currentMessagePaint = messagePrintingPaint; - } else { - lastPrintString = null; - - if (draftMessage != null) { - checkMessage = false; - if (TextUtils.isEmpty(draftMessage.message)) { - String draftString = LocaleController.getString("Draft", R.string.Draft); - SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(draftString); - stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_DRAFT_TEXT_COLOR), 0, draftString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - messageString = stringBuilder; - } else { - String mess = draftMessage.message; - if (mess.length() > 150) { - mess = mess.substring(0, 150); - } - String draftString = LocaleController.getString("Draft", R.string.Draft); - SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", draftString, mess.replace('\n', ' '))); - stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_DRAFT_TEXT_COLOR), 0, draftString.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - messageString = Emoji.replaceEmoji(stringBuilder, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - } + if (printingString != null) { + lastPrintString = messageString = printingString; + currentMessagePaint = Theme.dialogs_messagePrintingPaint; } else { - if (message == null) { - if (encryptedChat != null) { - currentMessagePaint = messagePrintingPaint; - if (encryptedChat instanceof TLRPC.TL_encryptedChatRequested) { - messageString = LocaleController.getString("EncryptionProcessing", R.string.EncryptionProcessing); - } else if (encryptedChat instanceof TLRPC.TL_encryptedChatWaiting) { - if (user != null && user.first_name != null) { - messageString = LocaleController.formatString("AwaitingEncryption", R.string.AwaitingEncryption, user.first_name); - } else { - messageString = LocaleController.formatString("AwaitingEncryption", R.string.AwaitingEncryption, ""); - } - } else if (encryptedChat instanceof TLRPC.TL_encryptedChatDiscarded) { - messageString = LocaleController.getString("EncryptionRejected", R.string.EncryptionRejected); - } else if (encryptedChat instanceof TLRPC.TL_encryptedChat) { - if (encryptedChat.admin_id == UserConfig.getClientUserId()) { + lastPrintString = null; + + if (draftMessage != null) { + checkMessage = false; + if (TextUtils.isEmpty(draftMessage.message)) { + String draftString = LocaleController.getString("Draft", R.string.Draft); + SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(draftString); + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_draft)), 0, draftString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + messageString = stringBuilder; + } else { + String mess = draftMessage.message; + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + String draftString = LocaleController.getString("Draft", R.string.Draft); + SpannableStringBuilder stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", draftString, mess.replace('\n', ' '))); + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_draft)), 0, draftString.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + messageString = Emoji.replaceEmoji(stringBuilder, Theme.dialogs_messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + } + } else { + if (message == null) { + if (encryptedChat != null) { + currentMessagePaint = Theme.dialogs_messagePrintingPaint; + if (encryptedChat instanceof TLRPC.TL_encryptedChatRequested) { + messageString = LocaleController.getString("EncryptionProcessing", R.string.EncryptionProcessing); + } else if (encryptedChat instanceof TLRPC.TL_encryptedChatWaiting) { if (user != null && user.first_name != null) { - messageString = LocaleController.formatString("EncryptedChatStartedOutgoing", R.string.EncryptedChatStartedOutgoing, user.first_name); + messageString = LocaleController.formatString("AwaitingEncryption", R.string.AwaitingEncryption, user.first_name); } else { - messageString = LocaleController.formatString("EncryptedChatStartedOutgoing", R.string.EncryptedChatStartedOutgoing, ""); + messageString = LocaleController.formatString("AwaitingEncryption", R.string.AwaitingEncryption, ""); + } + } else if (encryptedChat instanceof TLRPC.TL_encryptedChatDiscarded) { + messageString = LocaleController.getString("EncryptionRejected", R.string.EncryptionRejected); + } else if (encryptedChat instanceof TLRPC.TL_encryptedChat) { + if (encryptedChat.admin_id == UserConfig.getClientUserId()) { + if (user != null && user.first_name != null) { + messageString = LocaleController.formatString("EncryptedChatStartedOutgoing", R.string.EncryptedChatStartedOutgoing, user.first_name); + } else { + messageString = LocaleController.formatString("EncryptedChatStartedOutgoing", R.string.EncryptedChatStartedOutgoing, ""); + } + } else { + messageString = LocaleController.getString("EncryptedChatStartedIncoming", R.string.EncryptedChatStartedIncoming); } - } else { - messageString = LocaleController.getString("EncryptedChatStartedIncoming", R.string.EncryptedChatStartedIncoming); } } - } - } else { - TLRPC.User fromUser = null; - TLRPC.Chat fromChat = null; - if (message.isFromUser()) { - fromUser = MessagesController.getInstance().getUser(message.messageOwner.from_id); } else { - fromChat = MessagesController.getInstance().getChat(message.messageOwner.to_id.channel_id); - } - if (message.messageOwner instanceof TLRPC.TL_messageService) { - messageString = message.messageText; - currentMessagePaint = messagePrintingPaint; - } else { - if (chat != null && chat.id > 0 && fromChat == null) { - String name; - if (message.isOutOwner()) { - name = LocaleController.getString("FromYou", R.string.FromYou); - } else if (fromUser != null) { - name = UserObject.getFirstName(fromUser).replace("\n", ""); - } else if (fromChat != null) { - name = fromChat.title.replace("\n", ""); - } else { - name = "DELETED"; - } - checkMessage = false; - SpannableStringBuilder stringBuilder; - if (message.caption != null) { - String mess = message.caption.toString(); - if (mess.length() > 150) { - mess = mess.substring(0, 150); + TLRPC.User fromUser = null; + TLRPC.Chat fromChat = null; + if (message.isFromUser()) { + fromUser = MessagesController.getInstance().getUser(message.messageOwner.from_id); + } else { + fromChat = MessagesController.getInstance().getChat(message.messageOwner.to_id.channel_id); + } + if (message.messageOwner instanceof TLRPC.TL_messageService) { + messageString = message.messageText; + currentMessagePaint = Theme.dialogs_messagePrintingPaint; + } else { + if (chat != null && chat.id > 0 && fromChat == null) { + String name; + if (message.isOutOwner()) { + name = LocaleController.getString("FromYou", R.string.FromYou); + } else if (fromUser != null) { + name = UserObject.getFirstName(fromUser).replace("\n", ""); + } else if (fromChat != null) { + name = fromChat.title.replace("\n", ""); + } else { + name = "DELETED"; } - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); - } else if (message.messageOwner.media != null && !message.isMediaEmpty()) { - currentMessagePaint = messagePrintingPaint; - if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, "\uD83C\uDFAE " + message.messageOwner.media.game.title)); + checkMessage = false; + SpannableStringBuilder stringBuilder; + if (message.caption != null) { + String mess = message.caption.toString(); + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); + } else if (message.messageOwner.media != null && !message.isMediaEmpty()) { + currentMessagePaint = Theme.dialogs_messagePrintingPaint; + if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFAE %s", name, message.messageOwner.media.game.title)); + } else if (message.type == 14) { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: \uD83C\uDFA7 %s - %s", name, message.getMusicAuthor(), message.getMusicTitle())); + } else { + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + } + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_attachMessage)), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (message.messageOwner.message != null) { + String mess = message.messageOwner.message; + if (mess.length() > 150) { + mess = mess.substring(0, 150); + } + stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); } else { - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, message.messageText)); + stringBuilder = SpannableStringBuilder.valueOf(""); } - stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_ATTACH_TEXT_COLOR), name.length() + 2, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (message.messageOwner.message != null) { - String mess = message.messageOwner.message; - if (mess.length() > 150) { - mess = mess.substring(0, 150); + if (stringBuilder.length() > 0) { + stringBuilder.setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_chats_nameMessage)), 0, name.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - stringBuilder = SpannableStringBuilder.valueOf(String.format("%s: %s", name, mess.replace('\n', ' '))); - } else { - stringBuilder = SpannableStringBuilder.valueOf(""); - } - if (stringBuilder.length() > 0) { - stringBuilder.setSpan(new ForegroundColorSpan(Theme.DIALOGS_NAME_TEXT_COLOR), 0, name.length() + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - messageString = Emoji.replaceEmoji(stringBuilder, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); - } else { - if (message.caption != null) { - messageString = message.caption; + messageString = Emoji.replaceEmoji(stringBuilder, Theme.dialogs_messagePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); } else { - if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { - messageString = "\uD83C\uDFAE " + message.messageOwner.media.game.title; + if (message.caption != null) { + messageString = message.caption; } else { - messageString = message.messageText; - } - if (message.messageOwner.media != null && !message.isMediaEmpty()) { - currentMessagePaint = messagePrintingPaint; + if (message.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { + messageString = "\uD83C\uDFAE " + message.messageOwner.media.game.title; + } else if (message.type == 14) { + messageString = String.format("\uD83C\uDFA7 %s - %s", message.getMusicAuthor(), message.getMusicTitle()); + } else { + messageString = message.messageText; + } + if (message.messageOwner.media != null && !message.isMediaEmpty()) { + currentMessagePaint = Theme.dialogs_messagePrintingPaint; + } } } } } } } - } - if (draftMessage != null) { - timeString = LocaleController.stringForMessageListDate(draftMessage.date); - } else if (lastMessageDate != 0) { - timeString = LocaleController.stringForMessageListDate(lastMessageDate); - } else if (message != null) { - timeString = LocaleController.stringForMessageListDate(message.messageOwner.date); - } + if (draftMessage != null) { + timeString = LocaleController.stringForMessageListDate(draftMessage.date); + } else if (lastMessageDate != 0) { + timeString = LocaleController.stringForMessageListDate(lastMessageDate); + } else if (message != null) { + timeString = LocaleController.stringForMessageListDate(message.messageOwner.date); + } - if (message == null) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = false; - drawCount = false; - drawError = false; - } else { - if (unreadCount != 0) { - drawCount = true; - countString = String.format("%d", unreadCount); - } else { + if (message == null) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = false; drawCount = false; - } + drawError = false; + } else { + if (unreadCount != 0) { + drawCount = true; + countString = String.format("%d", unreadCount); + } else { + drawCount = false; + } - if (message.isOut() && draftMessage == null) { - if (message.isSending()) { - drawCheck1 = false; - drawCheck2 = false; - drawClock = true; - drawError = false; - } else if (message.isSendError()) { + if (message.isOut() && draftMessage == null) { + if (message.isSending()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = true; + drawError = false; + } else if (message.isSendError()) { + drawCheck1 = false; + drawCheck2 = false; + drawClock = false; + drawError = true; + drawCount = false; + } else if (message.isSent()) { + drawCheck1 = !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; + drawCheck2 = true; + drawClock = false; + drawError = false; + } + } else { drawCheck1 = false; drawCheck2 = false; drawClock = false; - drawError = true; - drawCount = false; - } else if (message.isSent()) { - drawCheck1 = !message.isUnread() || ChatObject.isChannel(chat) && !chat.megagroup; - drawCheck2 = true; - drawClock = false; drawError = false; } - } else { - drawCheck1 = false; - drawCheck2 = false; - drawClock = false; - drawError = false; } - } - - int timeWidth = (int) Math.ceil(timePaint.measureText(timeString)); - timeLayout = new StaticLayout(timeString, timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (!LocaleController.isRTL) { - timeLeft = getMeasuredWidth() - AndroidUtilities.dp(15) - timeWidth; - } else { - timeLeft = AndroidUtilities.dp(15); - } - if (chat != null) { - nameString = chat.title; - } else if (user != null) { - if (user.id == UserConfig.getClientUserId()) { - nameString = LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName); - } else if (user.id / 1000 != 777 && user.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(user.id) == null) { - if (ContactsController.getInstance().contactsDict.size() == 0 && (!ContactsController.getInstance().contactsLoaded || ContactsController.getInstance().isLoadingContacts())) { - nameString = UserObject.getUserName(user); - } else { - if (user.phone != null && user.phone.length() != 0) { - nameString = PhoneFormat.getInstance().format("+" + user.phone); - } else { + if (chat != null) { + nameString = chat.title; + } else if (user != null) { + if (user.id == UserConfig.getClientUserId()) { + nameString = LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName); + } else if (user.id / 1000 != 777 && user.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(user.id) == null) { + if (ContactsController.getInstance().contactsDict.size() == 0 && (!ContactsController.getInstance().contactsLoaded || ContactsController.getInstance().isLoadingContacts())) { nameString = UserObject.getUserName(user); + } else { + if (user.phone != null && user.phone.length() != 0) { + nameString = PhoneFormat.getInstance().format("+" + user.phone); + } else { + nameString = UserObject.getUserName(user); + } } + } else { + nameString = UserObject.getUserName(user); + } + if (encryptedChat != null) { + currentNamePaint = Theme.dialogs_nameEncryptedPaint; } - } else { - nameString = UserObject.getUserName(user); } - if (encryptedChat != null) { - currentNamePaint = nameEncryptedPaint; + if (nameString.length() == 0) { + nameString = LocaleController.getString("HiddenName", R.string.HiddenName); } } - if (nameString.length() == 0) { - nameString = LocaleController.getString("HiddenName", R.string.HiddenName); + + int timeWidth = (int) Math.ceil(Theme.dialogs_timePaint.measureText(timeString)); + timeLayout = new StaticLayout(timeString, Theme.dialogs_timePaint, timeWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (!LocaleController.isRTL) { + timeLeft = getMeasuredWidth() - AndroidUtilities.dp(15) - timeWidth; + } else { + timeLeft = AndroidUtilities.dp(15); } int nameWidth; @@ -553,16 +577,16 @@ public void buildLayout() { nameLeft += timeWidth; } if (drawNameLock) { - nameWidth -= AndroidUtilities.dp(4) + lockDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else if (drawNameGroup) { - nameWidth -= AndroidUtilities.dp(4) + groupDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_groupDrawable.getIntrinsicWidth(); } else if (drawNameBroadcast) { - nameWidth -= AndroidUtilities.dp(4) + broadcastDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_broadcastDrawable.getIntrinsicWidth(); } else if (drawNameBot) { - nameWidth -= AndroidUtilities.dp(4) + botDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(4) + Theme.dialogs_botDrawable.getIntrinsicWidth(); } if (drawClock) { - int w = clockDrawable.getIntrinsicWidth() + AndroidUtilities.dp(5); + int w = Theme.dialogs_clockDrawable.getIntrinsicWidth() + AndroidUtilities.dp(5); nameWidth -= w; if (!LocaleController.isRTL) { checkDrawLeft = timeLeft - w; @@ -571,17 +595,17 @@ public void buildLayout() { nameLeft += w; } } else if (drawCheck2) { - int w = checkDrawable.getIntrinsicWidth() + AndroidUtilities.dp(5); + int w = Theme.dialogs_checkDrawable.getIntrinsicWidth() + AndroidUtilities.dp(5); nameWidth -= w; if (drawCheck1) { - nameWidth -= halfCheckDrawable.getIntrinsicWidth() - AndroidUtilities.dp(8); + nameWidth -= Theme.dialogs_halfCheckDrawable.getIntrinsicWidth() - AndroidUtilities.dp(8); if (!LocaleController.isRTL) { halfCheckDrawLeft = timeLeft - w; checkDrawLeft = halfCheckDrawLeft - AndroidUtilities.dp(5.5f); } else { checkDrawLeft = timeLeft + timeWidth + AndroidUtilities.dp(5); halfCheckDrawLeft = checkDrawLeft + AndroidUtilities.dp(5.5f); - nameLeft += w + halfCheckDrawable.getIntrinsicWidth() - AndroidUtilities.dp(8); + nameLeft += w + Theme.dialogs_halfCheckDrawable.getIntrinsicWidth() - AndroidUtilities.dp(8); } } else { if (!LocaleController.isRTL) { @@ -594,13 +618,13 @@ public void buildLayout() { } if (dialogMuted && !drawVerified) { - int w = AndroidUtilities.dp(6) + muteDrawable.getIntrinsicWidth(); + int w = AndroidUtilities.dp(6) + Theme.dialogs_muteDrawable.getIntrinsicWidth(); nameWidth -= w; if (LocaleController.isRTL) { nameLeft += w; } } else if (drawVerified) { - int w = AndroidUtilities.dp(6) + verifiedDrawable.getIntrinsicWidth(); + int w = AndroidUtilities.dp(6) + Theme.dialogs_verifiedDrawable.getIntrinsicWidth(); nameWidth -= w; if (LocaleController.isRTL) { nameLeft += w; @@ -612,7 +636,7 @@ public void buildLayout() { CharSequence nameStringFinal = TextUtils.ellipsize(nameString.replace('\n', ' '), currentNamePaint, nameWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); nameLayout = new StaticLayout(nameStringFinal, currentNamePaint, nameWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } int messageWidth = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 16); @@ -626,17 +650,17 @@ public void buildLayout() { } avatarImage.setImageCoords(avatarLeft, avatarTop, AndroidUtilities.dp(52), AndroidUtilities.dp(52)); if (drawError) { - int w = errorDrawable.getIntrinsicWidth() + AndroidUtilities.dp(8); + int w = AndroidUtilities.dp(23 + 8); messageWidth -= w; if (!LocaleController.isRTL) { - errorLeft = getMeasuredWidth() - errorDrawable.getIntrinsicWidth() - AndroidUtilities.dp(11); + errorLeft = getMeasuredWidth() - AndroidUtilities.dp(23 + 11); } else { errorLeft = AndroidUtilities.dp(11); messageLeft += w; } } else if (countString != null) { - countWidth = Math.max(AndroidUtilities.dp(12), (int)Math.ceil(countPaint.measureText(countString))); - countLayout = new StaticLayout(countString, countPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + countWidth = Math.max(AndroidUtilities.dp(12), (int)Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); + countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); int w = countWidth + AndroidUtilities.dp(18); messageWidth -= w; if (!LocaleController.isRTL) { @@ -647,6 +671,16 @@ public void buildLayout() { } drawCount = true; } else { + if (drawPin) { + int w = Theme.dialogs_pinnedDrawable.getIntrinsicWidth() + AndroidUtilities.dp(8); + messageWidth -= w; + if (!LocaleController.isRTL) { + pinLeft = getMeasuredWidth() - Theme.dialogs_pinnedDrawable.getIntrinsicWidth() - AndroidUtilities.dp(14); + } else { + pinLeft = AndroidUtilities.dp(14); + messageLeft += w; + } + } drawCount = false; } @@ -659,14 +693,14 @@ public void buildLayout() { mess = mess.substring(0, 150); } mess = mess.replace('\n', ' '); - messageString = Emoji.replaceEmoji(mess, messagePaint.getFontMetricsInt(), AndroidUtilities.dp(17), false); + messageString = Emoji.replaceEmoji(mess, Theme.dialogs_messagePaint.getFontMetricsInt(), AndroidUtilities.dp(17), false); } messageWidth = Math.max(AndroidUtilities.dp(12), messageWidth); CharSequence messageStringFinal = TextUtils.ellipsize(messageString, currentMessagePaint, messageWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); try { messageLayout = new StaticLayout(messageStringFinal, currentMessagePaint, messageWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } double widthpx; @@ -676,9 +710,9 @@ public void buildLayout() { left = nameLayout.getLineLeft(0); widthpx = Math.ceil(nameLayout.getLineWidth(0)); if (dialogMuted && !drawVerified) { - nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - muteDrawable.getIntrinsicWidth()); + nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - Theme.dialogs_muteDrawable.getIntrinsicWidth()); } else if (drawVerified) { - nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - verifiedDrawable.getIntrinsicWidth()); + nameMuteLeft = (int) (nameLeft + (nameWidth - widthpx) - AndroidUtilities.dp(6) - Theme.dialogs_verifiedDrawable.getIntrinsicWidth()); } if (left == 0) { if (widthpx < nameWidth) { @@ -748,7 +782,7 @@ public void checkCurrentDialogIndex() { newMessageObject != null && newMessageObject.messageOwner.edit_date != currentEditDate || unreadCount != dialog.unread_count || message != newMessageObject || - message == null && newMessageObject != null || newDraftMessage != draftMessage) { + message == null && newMessageObject != null || newDraftMessage != draftMessage || drawPin != dialog.pinned) { currentDialogId = dialog.id; update(0); } @@ -756,118 +790,130 @@ public void checkCurrentDialogIndex() { } public void update(int mask) { - if (isDialogCell) { - TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(currentDialogId); - if (dialog != null && mask == 0) { - message = MessagesController.getInstance().dialogMessage.get(dialog.id); - lastUnreadState = message != null && message.isUnread(); - unreadCount = dialog.unread_count; - currentEditDate = message != null ? message.messageOwner.edit_date : 0; - lastMessageDate = dialog.last_message_date; - if (message != null) { - lastSendState = message.messageOwner.send_state; + if (customDialog != null) { + lastMessageDate = customDialog.date; + lastUnreadState = customDialog.unread_count != 0; + unreadCount = customDialog.unread_count; + drawPin = customDialog.pinned; + dialogMuted = customDialog.muted; + avatarDrawable.setInfo(customDialog.id, customDialog.name, null, false); + avatarImage.setImage(null, "50_50", avatarDrawable, null, false); + } else { + if (isDialogCell) { + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(currentDialogId); + if (dialog != null && mask == 0) { + message = MessagesController.getInstance().dialogMessage.get(dialog.id); + lastUnreadState = message != null && message.isUnread(); + unreadCount = dialog.unread_count; + currentEditDate = message != null ? message.messageOwner.edit_date : 0; + lastMessageDate = dialog.last_message_date; + drawPin = dialog.pinned; + if (message != null) { + lastSendState = message.messageOwner.send_state; + } } + } else { + drawPin = false; } - } - if (mask != 0) { - boolean continueUpdate = false; - if (isDialogCell) { - if ((mask & MessagesController.UPDATE_MASK_USER_PRINT) != 0) { - CharSequence printString = MessagesController.getInstance().printingStrings.get(currentDialogId); - if (lastPrintString != null && printString == null || lastPrintString == null && printString != null || lastPrintString != null && printString != null && !lastPrintString.equals(printString)) { - continueUpdate = true; + if (mask != 0) { + boolean continueUpdate = false; + if (isDialogCell) { + if ((mask & MessagesController.UPDATE_MASK_USER_PRINT) != 0) { + CharSequence printString = MessagesController.getInstance().printingStrings.get(currentDialogId); + if (lastPrintString != null && printString == null || lastPrintString == null && printString != null || lastPrintString != null && printString != null && !lastPrintString.equals(printString)) { + continueUpdate = true; + } } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_AVATAR) != 0) { - if (chat == null) { - continueUpdate = true; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_AVATAR) != 0) { + if (chat == null) { + continueUpdate = true; + } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_NAME) != 0) { - if (chat == null) { - continueUpdate = true; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_NAME) != 0) { + if (chat == null) { + continueUpdate = true; + } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_CHAT_AVATAR) != 0) { - if (user == null) { - continueUpdate = true; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_CHAT_AVATAR) != 0) { + if (user == null) { + continueUpdate = true; + } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_CHAT_NAME) != 0) { - if (user == null) { - continueUpdate = true; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_CHAT_NAME) != 0) { + if (user == null) { + continueUpdate = true; + } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE) != 0) { - if (message != null && lastUnreadState != message.isUnread()) { - lastUnreadState = message.isUnread(); - continueUpdate = true; - } else if (isDialogCell) { - TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(currentDialogId); - if (dialog != null && unreadCount != dialog.unread_count) { - unreadCount = dialog.unread_count; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_READ_DIALOG_MESSAGE) != 0) { + if (message != null && lastUnreadState != message.isUnread()) { + lastUnreadState = message.isUnread(); continueUpdate = true; + } else if (isDialogCell) { + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(currentDialogId); + if (dialog != null && unreadCount != dialog.unread_count) { + unreadCount = dialog.unread_count; + continueUpdate = true; + } } } - } - if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_SEND_STATE) != 0) { - if (message != null && lastSendState != message.messageOwner.send_state) { - lastSendState = message.messageOwner.send_state; - continueUpdate = true; + if (!continueUpdate && (mask & MessagesController.UPDATE_MASK_SEND_STATE) != 0) { + if (message != null && lastSendState != message.messageOwner.send_state) { + lastSendState = message.messageOwner.send_state; + continueUpdate = true; + } } - } - if (!continueUpdate) { - return; + if (!continueUpdate) { + return; + } } - } - dialogMuted = isDialogCell && MessagesController.getInstance().isDialogMuted(currentDialogId); - user = null; - chat = null; - encryptedChat = null; + dialogMuted = isDialogCell && MessagesController.getInstance().isDialogMuted(currentDialogId); + user = null; + chat = null; + encryptedChat = null; - int lower_id = (int)currentDialogId; - int high_id = (int)(currentDialogId >> 32); - if (lower_id != 0) { - if (high_id == 1) { - chat = MessagesController.getInstance().getChat(lower_id); - } else { - if (lower_id < 0) { - chat = MessagesController.getInstance().getChat(-lower_id); - if (!isDialogCell && chat != null && chat.migrated_to != null) { - TLRPC.Chat chat2 = MessagesController.getInstance().getChat(chat.migrated_to.channel_id); - if (chat2 != null) { - chat = chat2; + int lower_id = (int) currentDialogId; + int high_id = (int) (currentDialogId >> 32); + if (lower_id != 0) { + if (high_id == 1) { + chat = MessagesController.getInstance().getChat(lower_id); + } else { + if (lower_id < 0) { + chat = MessagesController.getInstance().getChat(-lower_id); + if (!isDialogCell && chat != null && chat.migrated_to != null) { + TLRPC.Chat chat2 = MessagesController.getInstance().getChat(chat.migrated_to.channel_id); + if (chat2 != null) { + chat = chat2; + } } + } else { + user = MessagesController.getInstance().getUser(lower_id); } - } else { - user = MessagesController.getInstance().getUser(lower_id); + } + } else { + encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); + if (encryptedChat != null) { + user = MessagesController.getInstance().getUser(encryptedChat.user_id); } } - } else { - encryptedChat = MessagesController.getInstance().getEncryptedChat(high_id); - if (encryptedChat != null) { - user = MessagesController.getInstance().getUser(encryptedChat.user_id); - } - } - TLRPC.FileLocation photo = null; - if (user != null) { - if (user.photo != null) { - photo = user.photo.photo_small; - } - avatarDrawable.setInfo(user); - } else if (chat != null) { - if (chat.photo != null) { - photo = chat.photo.photo_small; + TLRPC.FileLocation photo = null; + if (user != null) { + if (user.photo != null) { + photo = user.photo.photo_small; + } + avatarDrawable.setInfo(user); + } else if (chat != null) { + if (chat.photo != null) { + photo = chat.photo.photo_small; + } + avatarDrawable.setInfo(chat); } - avatarDrawable.setInfo(chat); + avatarImage.setImage(photo, "50_50", avatarDrawable, null, false); } - avatarImage.setImage(photo, "50_50", avatarDrawable, null, false); - if (getMeasuredWidth() != 0 || getMeasuredHeight() != 0) { buildLayout(); } else { @@ -879,26 +925,29 @@ public void update(int mask) { @Override protected void onDraw(Canvas canvas) { - if (currentDialogId == 0) { + if (currentDialogId == 0 && customDialog == null) { return; } if (isSelected) { - canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), backPaint); + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_tabletSeletedPaint); + } + if (drawPin) { + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), Theme.dialogs_pinnedPaint); } if (drawNameLock) { - setDrawableBounds(lockDrawable, nameLockLeft, nameLockTop); - lockDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_lockDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_lockDrawable.draw(canvas); } else if (drawNameGroup) { - setDrawableBounds(groupDrawable, nameLockLeft, nameLockTop); - groupDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_groupDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_groupDrawable.draw(canvas); } else if (drawNameBroadcast) { - setDrawableBounds(broadcastDrawable, nameLockLeft, nameLockTop); - broadcastDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_broadcastDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_broadcastDrawable.draw(canvas); } else if (drawNameBot) { - setDrawableBounds(botDrawable, nameLockLeft, nameLockTop); - botDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_botDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_botDrawable.draw(canvas); } if (nameLayout != null) { @@ -919,58 +968,61 @@ protected void onDraw(Canvas canvas) { try { messageLayout.draw(canvas); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } canvas.restore(); } if (drawClock) { - setDrawableBounds(clockDrawable, checkDrawLeft, checkDrawTop); - clockDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_clockDrawable, checkDrawLeft, checkDrawTop); + Theme.dialogs_clockDrawable.draw(canvas); } else if (drawCheck2) { if (drawCheck1) { - setDrawableBounds(halfCheckDrawable, halfCheckDrawLeft, checkDrawTop); - halfCheckDrawable.draw(canvas); - setDrawableBounds(checkDrawable, checkDrawLeft, checkDrawTop); - checkDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_halfCheckDrawable, halfCheckDrawLeft, checkDrawTop); + Theme.dialogs_halfCheckDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_checkDrawable, checkDrawLeft, checkDrawTop); + Theme.dialogs_checkDrawable.draw(canvas); } else { - setDrawableBounds(checkDrawable, checkDrawLeft, checkDrawTop); - checkDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_checkDrawable, checkDrawLeft, checkDrawTop); + Theme.dialogs_checkDrawable.draw(canvas); } } if (dialogMuted && !drawVerified) { - setDrawableBounds(muteDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); - muteDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_muteDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + Theme.dialogs_muteDrawable.draw(canvas); } else if (drawVerified) { - setDrawableBounds(verifiedDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); - verifiedDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_verifiedDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + setDrawableBounds(Theme.dialogs_verifiedCheckDrawable, nameMuteLeft, AndroidUtilities.dp(16.5f)); + Theme.dialogs_verifiedDrawable.draw(canvas); + Theme.dialogs_verifiedCheckDrawable.draw(canvas); } if (drawError) { - setDrawableBounds(errorDrawable, errorLeft, errorTop); - errorDrawable.draw(canvas); + rect.set(errorLeft, errorTop, errorLeft + AndroidUtilities.dp(23), errorTop + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, Theme.dialogs_errorPaint); + setDrawableBounds(Theme.dialogs_errorDrawable, errorLeft + AndroidUtilities.dp(5.5f), errorTop + AndroidUtilities.dp(5)); + Theme.dialogs_errorDrawable.draw(canvas); } else if (drawCount) { - if (dialogMuted) { - setDrawableBounds(countDrawableGrey, countLeft - AndroidUtilities.dp(5.5f), countTop, countWidth + AndroidUtilities.dp(11), countDrawable.getIntrinsicHeight()); - countDrawableGrey.draw(canvas); - } else { - setDrawableBounds(countDrawable, countLeft - AndroidUtilities.dp(5.5f), countTop, countWidth + AndroidUtilities.dp(11), countDrawable.getIntrinsicHeight()); - countDrawable.draw(canvas); - } + int x = countLeft - AndroidUtilities.dp(5.5f); + rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, dialogMuted ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); canvas.save(); canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); if (countLayout != null) { countLayout.draw(canvas); } canvas.restore(); + } else if (drawPin) { + setDrawableBounds(Theme.dialogs_pinnedDrawable, pinLeft, pinTop); + Theme.dialogs_pinnedDrawable.draw(canvas); } if (useSeparator) { if (LocaleController.isRTL) { - canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, linePaint); + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, Theme.dividerPaint); } else { - canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, linePaint); + canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DividerCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DividerCell.java index bdea51926fc..f78e2260682 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DividerCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DividerCell.java @@ -3,28 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; -public class DividerCell extends BaseCell { - - private static Paint paint; +public class DividerCell extends View { public DividerCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } } @Override @@ -34,6 +28,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onDraw(Canvas canvas) { - canvas.drawLine(getPaddingLeft(), AndroidUtilities.dp(8), getWidth() - getPaddingRight(), AndroidUtilities.dp(8), paint); + canvas.drawLine(getPaddingLeft(), AndroidUtilities.dp(8), getWidth() - getPaddingRight(), AndroidUtilities.dp(8), Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerActionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerActionCell.java index db6f37cca2d..fc004b1d25f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerActionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerActionCell.java @@ -3,12 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -16,6 +19,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class DrawerActionCell extends FrameLayout { @@ -26,7 +30,7 @@ public DrawerActionCell(Context context) { super(context); textView = new TextView(context); - textView.setTextColor(0xff444444); + textView.setTextColor(Theme.getColor(Theme.key_chats_menuItemText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setLines(1); @@ -39,15 +43,25 @@ public DrawerActionCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + textView.setTextColor(Theme.getColor(Theme.key_chats_menuItemText)); } public void setTextAndIcon(String text, int resId) { try { textView.setText(text); - textView.setCompoundDrawablesWithIntrinsicBounds(resId, 0, 0, 0); + Drawable drawable = getResources().getDrawable(resId); + if (drawable != null) { + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_menuItemIcon), PorterDuff.Mode.MULTIPLY)); + } + textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java index 1c8681b03d6..13c0682c2ad 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DrawerProfileCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -30,7 +30,6 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.UserObject; -import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; @@ -49,25 +48,28 @@ public class DrawerProfileCell extends FrameLayout { private Rect srcRect = new Rect(); private Rect destRect = new Rect(); private Paint paint = new Paint(); - private int currentColor; + private Integer currentColor; + private Drawable cloudDrawable; + private int lastCloudColor; private class CloudView extends View { - private Drawable cloudDrawable; private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); public CloudView(Context context) { super(context); - - cloudDrawable = getResources().getDrawable(R.drawable.cloud); } @Override protected void onDraw(Canvas canvas) { - if (ApplicationLoader.isCustomTheme() && ApplicationLoader.getCachedWallpaper() != null) { - paint.setColor(ApplicationLoader.getServiceMessageColor()); + if (Theme.isCustomTheme() && Theme.getCachedWallpaper() != null) { + paint.setColor(Theme.getServiceMessageColor()); } else { - paint.setColor(0xff427ba9); + paint.setColor(Theme.getColor(Theme.key_chats_menuCloudBackgroundCats)); + } + int newColor = Theme.getColor(Theme.key_chats_menuCloud); + if (lastCloudColor != newColor) { + cloudDrawable.setColorFilter(new PorterDuffColorFilter(lastCloudColor = Theme.getColor(Theme.key_chats_menuCloud), PorterDuff.Mode.MULTIPLY)); } canvas.drawCircle(getMeasuredWidth() / 2.0f, getMeasuredHeight() / 2.0f, AndroidUtilities.dp(34) / 2.0f, paint); int l = (getMeasuredWidth() - AndroidUtilities.dp(33)) / 2; @@ -79,7 +81,9 @@ protected void onDraw(Canvas canvas) { public DrawerProfileCell(Context context) { super(context); - setBackgroundColor(Theme.ACTION_BAR_PROFILE_COLOR); + + cloudDrawable = context.getResources().getDrawable(R.drawable.cloud); + cloudDrawable.setColorFilter(new PorterDuffColorFilter(lastCloudColor = Theme.getColor(Theme.key_chats_menuCloud), PorterDuff.Mode.MULTIPLY)); shadowView = new ImageView(context); shadowView.setVisibility(INVISIBLE); @@ -92,7 +96,6 @@ public DrawerProfileCell(Context context) { addView(avatarImageView, LayoutHelper.createFrame(64, 64, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 0, 67)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xffffffff); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setLines(1); @@ -103,7 +106,6 @@ public DrawerProfileCell(Context context) { addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 16, 0, 76, 28)); phoneTextView = new TextView(context); - phoneTextView.setTextColor(0xffc2e5ff); phoneTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); phoneTextView.setLines(1); phoneTextView.setMaxLines(1); @@ -118,28 +120,33 @@ public DrawerProfileCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (Build.VERSION.SDK_INT >= 21) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(148) + AndroidUtilities.statusBarHeight, MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(148) + AndroidUtilities.statusBarHeight, MeasureSpec.EXACTLY)); } else { try { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(148), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(148), MeasureSpec.EXACTLY)); } catch (Exception e) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(148)); - FileLog.e("tmessages", e); + FileLog.e(e); } } } @Override protected void onDraw(Canvas canvas) { - Drawable backgroundDrawable = ApplicationLoader.getCachedWallpaper(); - int color = ApplicationLoader.getServiceMessageColor(); - if (currentColor != color) { + Drawable backgroundDrawable = Theme.getCachedWallpaper(); + int color; + if (Theme.hasThemeKey(Theme.key_chats_menuTopShadow)) { + color = Theme.getColor(Theme.key_chats_menuTopShadow); + } else { + color = Theme.getServiceMessageColor() | 0xff000000; + } + if (currentColor == null || currentColor != color) { currentColor = color; - shadowView.getDrawable().setColorFilter(new PorterDuffColorFilter(color | 0xff000000, PorterDuff.Mode.MULTIPLY)); + shadowView.getDrawable().setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); } - - if (ApplicationLoader.isCustomTheme() && backgroundDrawable != null) { - phoneTextView.setTextColor(0xffffffff); + nameTextView.setTextColor(Theme.getColor(Theme.key_chats_menuName)); + if (Theme.isCustomTheme() && backgroundDrawable != null) { + phoneTextView.setTextColor(Theme.getColor(Theme.key_chats_menuPhone)); shadowView.setVisibility(VISIBLE); if (backgroundDrawable instanceof ColorDrawable) { backgroundDrawable.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); @@ -155,11 +162,15 @@ protected void onDraw(Canvas canvas) { int y = (bitmap.getHeight() - height) / 2; srcRect.set(x, y, x + width, y + height); destRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); - canvas.drawBitmap(bitmap, srcRect, destRect, paint); + try { + canvas.drawBitmap(bitmap, srcRect, destRect, paint); + } catch (Throwable e) { + FileLog.e(e); + } } } else { shadowView.setVisibility(INVISIBLE); - phoneTextView.setTextColor(0xffc2e5ff); + phoneTextView.setTextColor(Theme.getColor(Theme.key_chats_menuPhoneCats)); super.onDraw(canvas); } } @@ -175,7 +186,7 @@ public void setUser(TLRPC.User user) { nameTextView.setText(UserObject.getUserName(user)); phoneTextView.setText(PhoneFormat.getInstance().format("+" + user.phone)); AvatarDrawable avatarDrawable = new AvatarDrawable(user); - avatarDrawable.setColor(Theme.ACTION_BAR_MAIN_AVATAR_COLOR); + avatarDrawable.setColor(Theme.getColor(Theme.key_avatar_backgroundInProfileBlue)); avatarImageView.setImage(photo, "50_50", avatarDrawable); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/EmptyCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/EmptyCell.java index 522043d9119..755930431e9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/EmptyCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/EmptyCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -31,6 +31,6 @@ public void setHeight(int height) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(cellHeight, MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(cellHeight, MeasureSpec.EXACTLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java index 5c4e709afeb..fa8f124b8bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetCell.java @@ -3,39 +3,39 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.Switch; public class FeaturedStickerSetCell extends FrameLayout { @@ -45,7 +45,6 @@ public class FeaturedStickerSetCell extends FrameLayout { private TextView addButton; private ImageView checkImage; private boolean needDivider; - private Switch checkBox; private TLRPC.StickerSetCovered stickersSet; private Rect rect = new Rect(); private AnimatorSet currentAnimation; @@ -57,28 +56,20 @@ public class FeaturedStickerSetCell extends FrameLayout { private float progressAlpha; private RectF progressRect = new RectF(); private long lastUpdateTime; - private static Paint botProgressPaint; + private Paint progressPaint; private int angle; - private static Paint paint; - public FeaturedStickerSetCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - } - if (botProgressPaint == null) { - botProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - botProgressPaint.setColor(0xffffffff); - botProgressPaint.setStrokeCap(Paint.Cap.ROUND); - botProgressPaint.setStyle(Paint.Style.STROKE); - } - botProgressPaint.setStrokeWidth(AndroidUtilities.dp(2)); + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setColor(Theme.getColor(Theme.key_featuredStickers_buttonProgress)); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeWidth(AndroidUtilities.dp(2)); textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -88,7 +79,7 @@ public FeaturedStickerSetCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 100 : 71, 10, LocaleController.isRTL ? 71 : 100, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -106,10 +97,10 @@ public FeaturedStickerSetCell(Context context) { protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (drawProgress || !drawProgress && progressAlpha != 0) { - botProgressPaint.setAlpha(Math.min(255, (int) (progressAlpha * 255))); + progressPaint.setAlpha(Math.min(255, (int) (progressAlpha * 255))); int x = getMeasuredWidth() - AndroidUtilities.dp(11); progressRect.set(x, AndroidUtilities.dp(3), x + AndroidUtilities.dp(8), AndroidUtilities.dp(8 + 3)); - canvas.drawArc(progressRect, angle, 220, false, botProgressPaint); + canvas.drawArc(progressRect, angle, 220, false, progressPaint); invalidate((int) progressRect.left - AndroidUtilities.dp(2), (int) progressRect.top - AndroidUtilities.dp(2), (int) progressRect.right + AndroidUtilities.dp(2), (int) progressRect.bottom + AndroidUtilities.dp(2)); long newTime = System.currentTimeMillis(); if (Math.abs(lastUpdateTime - System.currentTimeMillis()) < 1000) { @@ -140,14 +131,15 @@ protected void onDraw(Canvas canvas) { }; addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); addButton.setGravity(Gravity.CENTER); - addButton.setTextColor(0xffffffff); + addButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - addButton.setBackgroundResource(R.drawable.add_states); + addButton.setBackgroundDrawable(Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed))); addButton.setText(LocaleController.getString("Add", R.string.Add).toUpperCase()); addView(addButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT), LocaleController.isRTL ? 14 : 0, 18, LocaleController.isRTL ? 0 : 14, 0)); checkImage = new ImageView(context); + checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.MULTIPLY)); checkImage.setImageResource(R.drawable.sticker_added); addView(checkImage, LayoutHelper.createFrame(19, 14)); } @@ -246,7 +238,7 @@ public int getIntrinsicHeight() { ObjectAnimator.ofFloat(checkImage, "alpha", 0.0f, 1.0f), ObjectAnimator.ofFloat(checkImage, "scaleX", 0.01f, 1.0f), ObjectAnimator.ofFloat(checkImage, "scaleY", 0.01f, 1.0f)); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { if (currentAnimation != null && currentAnimation.equals(animator)) { @@ -275,7 +267,7 @@ public void onAnimationCancel(Animator animator) { ObjectAnimator.ofFloat(addButton, "alpha", 0.0f, 1.0f), ObjectAnimator.ofFloat(addButton, "scaleX", 0.01f, 1.0f), ObjectAnimator.ofFloat(addButton, "scaleY", 0.01f, 1.0f)); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { if (currentAnimation != null && currentAnimation.equals(animator)) { @@ -330,20 +322,10 @@ public boolean isInstalled() { return isInstalled; } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java index b7cb57c32ce..f646022e1a3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/FeaturedStickerSetInfoCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -25,6 +25,7 @@ import org.telegram.messenger.R; import org.telegram.messenger.query.StickersQuery; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class FeaturedStickerSetInfoCell extends FrameLayout { @@ -33,12 +34,14 @@ public class FeaturedStickerSetInfoCell extends FrameLayout { private TextView infoTextView; private TextView addButton; private TLRPC.StickerSetCovered set; + private Drawable addDrawable; + private Drawable delDrawable; private boolean drawProgress; private float progressAlpha; private RectF rect = new RectF(); private long lastUpdateTime; - private static Paint botProgressPaint; + private Paint botProgressPaint; private int angle; private boolean isInstalled; private boolean hasOnClick; @@ -49,7 +52,7 @@ public class FeaturedStickerSetInfoCell extends FrameLayout { @Override public void draw(Canvas canvas) { - paint.setColor(0xff4da6ea); + paint.setColor(Theme.getColor(Theme.key_featuredStickers_unread)); canvas.drawCircle(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(4), paint); } @@ -82,16 +85,17 @@ public int getIntrinsicHeight() { public FeaturedStickerSetInfoCell(Context context, int left) { super(context); - if (botProgressPaint == null) { - botProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - botProgressPaint.setColor(0xffffffff); - botProgressPaint.setStrokeCap(Paint.Cap.ROUND); - botProgressPaint.setStyle(Paint.Style.STROKE); - } + delDrawable = Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_delButton), Theme.getColor(Theme.key_featuredStickers_delButtonPressed)); + addDrawable = Theme.createSimpleSelectorRoundRectDrawable(AndroidUtilities.dp(4), Theme.getColor(Theme.key_featuredStickers_addButton), Theme.getColor(Theme.key_featuredStickers_addButtonPressed)); + + botProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + botProgressPaint.setColor(Theme.getColor(Theme.key_featuredStickers_buttonProgress)); + botProgressPaint.setStrokeCap(Paint.Cap.ROUND); + botProgressPaint.setStyle(Paint.Style.STROKE); botProgressPaint.setStrokeWidth(AndroidUtilities.dp(2)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff333333); + nameTextView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelTrendingTitle)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setEllipsize(TextUtils.TruncateAt.END); @@ -99,7 +103,7 @@ public FeaturedStickerSetInfoCell(Context context, int left) { addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, left, 8, 100, 0)); infoTextView = new TextView(context); - infoTextView.setTextColor(0xff8a8a8a); + infoTextView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelTrendingDescription)); infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); infoTextView.setEllipsize(TextUtils.TruncateAt.END); infoTextView.setSingleLine(true); @@ -144,7 +148,7 @@ protected void onDraw(Canvas canvas) { }; addButton.setPadding(AndroidUtilities.dp(17), 0, AndroidUtilities.dp(17), 0); addButton.setGravity(Gravity.CENTER); - addButton.setTextColor(0xffffffff); + addButton.setTextColor(Theme.getColor(Theme.key_featuredStickers_buttonText)); addButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(addButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 28, Gravity.TOP | Gravity.RIGHT, 0, 16, 14, 0)); @@ -152,7 +156,7 @@ protected void onDraw(Canvas canvas) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); } public void setAddOnClickListener(OnClickListener onClickListener) { @@ -172,10 +176,10 @@ public void setStickerSet(TLRPC.StickerSetCovered stickerSet, boolean unread) { if (hasOnClick) { addButton.setVisibility(VISIBLE); if (isInstalled = StickersQuery.isStickerPackInstalled(stickerSet.set.id)) { - addButton.setBackgroundResource(R.drawable.del_states); + addButton.setBackgroundDrawable(delDrawable); addButton.setText(LocaleController.getString("StickersRemove", R.string.StickersRemove).toUpperCase()); } else { - addButton.setBackgroundResource(R.drawable.add_states); + addButton.setBackgroundDrawable(addDrawable); addButton.setText(LocaleController.getString("Add", R.string.Add).toUpperCase()); } } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GraySectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GraySectionCell.java new file mode 100644 index 00000000000..517cfa1ec18 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GraySectionCell.java @@ -0,0 +1,47 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class GraySectionCell extends FrameLayout { + + private TextView textView; + + public GraySectionCell(Context context) { + super(context); + + setBackgroundColor(Theme.getColor(Theme.key_graySection)); + + textView = new TextView(getContext()); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 16, 0, 16, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); + } + + public void setText(String text) { + textView.setText(text); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java deleted file mode 100644 index 0915c3f94ad..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GreySectionCell.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Cells; - -import android.content.Context; -import android.util.TypedValue; -import android.view.Gravity; -import android.widget.FrameLayout; -import android.widget.TextView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.ui.Components.LayoutHelper; - -public class GreySectionCell extends FrameLayout { - private TextView textView; - - public GreySectionCell(Context context) { - super(context); - - setBackgroundColor(0xfff2f2f2); - - textView = new TextView(getContext()); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setTextColor(0xff8a8a8a); - textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 16, 0, 16, 0)); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); - } - - public void setText(String text) { - textView.setText(text); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateSectionCell.java new file mode 100644 index 00000000000..9c7afc462e0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateSectionCell.java @@ -0,0 +1,61 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class GroupCreateSectionCell extends FrameLayout { + + private Drawable drawable; + private TextView textView; + + public GroupCreateSectionCell(Context context) { + super(context); + setBackgroundColor(Theme.getColor(Theme.key_graySection)); + + drawable = getResources().getDrawable(R.drawable.shadowdown); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_groupcreate_sectionShadow), PorterDuff.Mode.MULTIPLY)); + + textView = new TextView(getContext()); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setTextColor(Theme.getColor(Theme.key_groupcreate_sectionText)); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 16, 0, 16, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(40), MeasureSpec.EXACTLY)); + } + + @Override + protected void onDraw(Canvas canvas) { + drawable.setBounds(0, getMeasuredHeight() - AndroidUtilities.dp(3), getMeasuredWidth(), getMeasuredHeight()); + drawable.draw(canvas); + } + + public void setText(String text) { + textView.setText(text); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java new file mode 100644 index 00000000000..286a0b140ec --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/GroupCreateUserCell.java @@ -0,0 +1,173 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.view.Gravity; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.GroupCreateCheckBox; +import org.telegram.ui.Components.LayoutHelper; + +public class GroupCreateUserCell extends FrameLayout { + + private BackupImageView avatarImageView; + private SimpleTextView nameTextView; + private SimpleTextView statusTextView; + private GroupCreateCheckBox checkBox; + private AvatarDrawable avatarDrawable; + private TLRPC.User currentUser; + private CharSequence currentName; + private CharSequence currentStatus; + + private String lastName; + private int lastStatus; + private TLRPC.FileLocation lastAvatar; + + public GroupCreateUserCell(Context context, boolean needCheck) { + super(context); + avatarDrawable = new AvatarDrawable(); + + avatarImageView = new BackupImageView(context); + avatarImageView.setRoundRadius(AndroidUtilities.dp(24)); + addView(avatarImageView, LayoutHelper.createFrame(50, 50, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 11, 11, LocaleController.isRTL ? 11 : 0, 0)); + + nameTextView = new SimpleTextView(context); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + nameTextView.setTextSize(17); + nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : 72, 14, LocaleController.isRTL ? 72 : 28, 0)); + + statusTextView = new SimpleTextView(context); + statusTextView.setTextSize(16); + statusTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(statusTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 : 72, 39, LocaleController.isRTL ? 72 : 28, 0)); + + if (needCheck) { + checkBox = new GroupCreateCheckBox(context); + checkBox.setVisibility(VISIBLE); + addView(checkBox, LayoutHelper.createFrame(24, 24, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 41, 41, LocaleController.isRTL ? 41 : 0, 0)); + } + } + + public void setUser(TLRPC.User user, CharSequence name, CharSequence status) { + currentUser = user; + currentStatus = status; + currentName = name; + update(0); + } + + public void setChecked(boolean checked, boolean animated) { + checkBox.setChecked(checked, animated); + } + + public TLRPC.User getUser() { + return currentUser; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(72), MeasureSpec.EXACTLY)); + } + + public void recycle() { + avatarImageView.getImageReceiver().cancelLoadImage(); + } + + public void update(int mask) { + if (currentUser == null) { + return; + } + TLRPC.FileLocation photo = null; + String newName = null; + if (currentUser.photo != null) { + photo = currentUser.photo.photo_small; + } + + if (mask != 0) { + boolean continueUpdate = false; + if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0) { + if (lastAvatar != null && photo == null || lastAvatar == null && photo != null && lastAvatar != null && photo != null && (lastAvatar.volume_id != photo.volume_id || lastAvatar.local_id != photo.local_id)) { + continueUpdate = true; + } + } + if (currentUser != null && currentStatus == null && !continueUpdate && (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { + int newStatus = 0; + if (currentUser.status != null) { + newStatus = currentUser.status.expires; + } + if (newStatus != lastStatus) { + continueUpdate = true; + } + } + if (!continueUpdate && currentName == null && lastName != null && (mask & MessagesController.UPDATE_MASK_NAME) != 0) { + newName = UserObject.getUserName(currentUser); + if (!newName.equals(lastName)) { + continueUpdate = true; + } + } + if (!continueUpdate) { + return; + } + } + + avatarDrawable.setInfo(currentUser); + lastStatus = currentUser.status != null ? currentUser.status.expires : 0; + + if (currentName != null) { + lastName = null; + nameTextView.setText(currentName, true); + } else { + lastName = newName == null ? UserObject.getUserName(currentUser) : newName; + nameTextView.setText(lastName); + } + + if (currentStatus != null) { + statusTextView.setText(currentStatus, true); + statusTextView.setTag(Theme.key_groupcreate_offlineText); + statusTextView.setTextColor(Theme.getColor(Theme.key_groupcreate_offlineText)); + } else { + if (currentUser.bot) { + statusTextView.setTag(Theme.key_groupcreate_offlineText); + statusTextView.setTextColor(Theme.getColor(Theme.key_groupcreate_offlineText)); + statusTextView.setText(LocaleController.getString("Bot", R.string.Bot)); + } else { + if (currentUser.id == UserConfig.getClientUserId() || currentUser.status != null && currentUser.status.expires > ConnectionsManager.getInstance().getCurrentTime() || MessagesController.getInstance().onlinePrivacy.containsKey(currentUser.id)) { + statusTextView.setTag(Theme.key_groupcreate_offlineText); + statusTextView.setTextColor(Theme.getColor(Theme.key_groupcreate_onlineText)); + statusTextView.setText(LocaleController.getString("Online", R.string.Online)); + } else { + statusTextView.setTag(Theme.key_groupcreate_offlineText); + statusTextView.setTextColor(Theme.getColor(Theme.key_groupcreate_offlineText)); + statusTextView.setText(LocaleController.formatUserStatus(currentUser)); + } + } + } + + avatarImageView.setImage(photo, "50_50", avatarDrawable); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HashtagSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HashtagSearchCell.java index 5800a02b127..330da8fec24 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HashtagSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HashtagSearchCell.java @@ -3,50 +3,30 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; -import android.os.Build; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; public class HashtagSearchCell extends TextView { private boolean needDivider; - private static Paint paint; public HashtagSearchCell(Context context) { super(context); setGravity(Gravity.CENTER_VERTICAL); setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); - setTextColor(0xff000000); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffdcdcdc); - } - - setBackgroundResource(R.drawable.list_selector); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); + setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); } public void setNeedDivider(boolean value) { @@ -62,7 +42,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (needDivider) { - canvas.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1, paint); + canvas.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HeaderCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HeaderCell.java index 2fa0b5dd11a..ec80a20c46c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HeaderCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HeaderCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -16,6 +16,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class HeaderCell extends FrameLayout { @@ -28,14 +29,18 @@ public HeaderCell(Context context) { textView = new TextView(getContext()); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setTextColor(0xff3e90cf); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueHeader)); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 15, 17, 0)); } + public TextView getTextView() { + return textView; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(38), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(38), MeasureSpec.EXACTLY)); } public void setText(String text) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java index 19a618f82bf..38ea6984d71 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/HintDialogCell.java @@ -3,22 +3,19 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; +import android.graphics.RectF; import android.text.Layout; import android.text.StaticLayout; -import android.text.TextPaint; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; @@ -26,8 +23,8 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; import org.telegram.messenger.MessagesController; -import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -37,10 +34,7 @@ public class HintDialogCell extends FrameLayout { private BackupImageView imageView; private TextView nameTextView; private AvatarDrawable avatarDrawable = new AvatarDrawable(); - - private static Drawable countDrawable; - private static Drawable countDrawableGrey; - private static TextPaint countPaint; + private RectF rect = new RectF(); private int lastUnreadCount; private int countWidth; @@ -50,45 +44,24 @@ public class HintDialogCell extends FrameLayout { public HintDialogCell(Context context) { super(context); - setBackgroundResource(R.drawable.list_selector); imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(27)); addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); nameTextView.setMaxLines(2); nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); nameTextView.setLines(2); nameTextView.setEllipsize(TextUtils.TruncateAt.END); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 6, 64, 6, 0)); - - if (countDrawable == null) { - countDrawable = getResources().getDrawable(R.drawable.dialogs_badge); - countDrawableGrey = getResources().getDrawable(R.drawable.dialogs_badge2); - - countPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - countPaint.setColor(0xffffffff); - countPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - } - countPaint.setTextSize(AndroidUtilities.dp(13)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); } public void checkUnreadCounter(int mask) { @@ -100,8 +73,8 @@ public void checkUnreadCounter(int mask) { if (lastUnreadCount != dialog.unread_count) { lastUnreadCount = dialog.unread_count; String countString = String.format("%d", dialog.unread_count); - countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(countPaint.measureText(countString))); - countLayout = new StaticLayout(countString, countPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); + countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); if (mask != 0) { invalidate(); } @@ -115,6 +88,18 @@ public void checkUnreadCounter(int mask) { } } + public void update() { + int uid = (int) dialog_id; + TLRPC.FileLocation photo = null; + if (uid > 0) { + TLRPC.User user = MessagesController.getInstance().getUser(uid); + avatarDrawable.setInfo(user); + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-uid); + avatarDrawable.setInfo(chat); + } + } + public void setDialog(int uid, boolean counter, CharSequence name) { dialog_id = uid; TLRPC.FileLocation photo = null; @@ -156,23 +141,16 @@ public void setDialog(int uid, boolean counter, CharSequence name) { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean result = super.drawChild(canvas, child, drawingTime); - if (child == imageView) { - if (countLayout != null) { - int top = AndroidUtilities.dp(6); - int left = AndroidUtilities.dp(54); - int x = left - AndroidUtilities.dp(5.5f); - if (MessagesController.getInstance().isDialogMuted(dialog_id)) { - countDrawableGrey.setBounds(x, top, x + countWidth + AndroidUtilities.dp(11), top + countDrawableGrey.getIntrinsicHeight()); - countDrawableGrey.draw(canvas); - } else { - countDrawable.setBounds(x, top, x + countWidth + AndroidUtilities.dp(11), top + countDrawable.getIntrinsicHeight()); - countDrawable.draw(canvas); - } - canvas.save(); - canvas.translate(left, top + AndroidUtilities.dp(4)); - countLayout.draw(canvas); - canvas.restore(); - } + if (child == imageView && countLayout != null) { + int top = AndroidUtilities.dp(6); + int left = AndroidUtilities.dp(54); + int x = left - AndroidUtilities.dp(5.5f); + rect.set(x, top, x + countWidth + AndroidUtilities.dp(11), top + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, MessagesController.getInstance().isDialogMuted(dialog_id) ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); + canvas.save(); + canvas.translate(left, top + AndroidUtilities.dp(4)); + countLayout.draw(canvas); + canvas.restore(); } return result; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/JoinSheetUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/JoinSheetUserCell.java index c88c315e68b..bc07e5d4659 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/JoinSheetUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/JoinSheetUserCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -19,6 +19,7 @@ import org.telegram.messenger.ContactsController; import org.telegram.messenger.LocaleController; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -38,7 +39,7 @@ public JoinSheetUserCell(Context context) { addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); nameTextView.setMaxLines(1); nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); @@ -65,7 +66,6 @@ public void setUser(TLRPC.User user) { public void setCount(int count) { nameTextView.setText(""); - //String shortNumber = LocaleController.formatShortNumber(info.participants_count, result); avatarDrawable.setInfo(0, null, null, false, "+" + LocaleController.formatShortNumber(count, result)); imageView.setImage((TLRPC.FileLocation) null, "50_50", avatarDrawable); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LetterSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LetterSectionCell.java index e911108f859..95ce0b67241 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LetterSectionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LetterSectionCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -16,6 +16,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class LetterSectionCell extends FrameLayout { @@ -29,7 +30,7 @@ public LetterSectionCell(Context context) { textView = new TextView(getContext()); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 22); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setTextColor(0xff808080); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); textView.setGravity(Gravity.CENTER); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LoadingCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LoadingCell.java index 161f21c0f4c..2dd47316c5c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LoadingCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LoadingCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -11,17 +11,19 @@ import android.content.Context; import android.view.Gravity; import android.widget.FrameLayout; -import android.widget.ProgressBar; import org.telegram.messenger.AndroidUtilities; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; public class LoadingCell extends FrameLayout { + private RadialProgressView progressBar; + public LoadingCell(Context context) { super(context); - ProgressBar progressBar = new ProgressBar(context); + progressBar = new RadialProgressView(context); addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationCell.java index 53e5f686d37..984d2a457f2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationCell.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.text.TextUtils; @@ -23,6 +22,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -32,21 +32,14 @@ public class LocationCell extends FrameLayout { private TextView addressTextView; private BackupImageView imageView; private boolean needDivider; - private static Paint paint; public LocationCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - imageView = new BackupImageView(context); imageView.setBackgroundResource(R.drawable.round_grey); imageView.setSize(AndroidUtilities.dp(30), AndroidUtilities.dp(30)); - imageView.getImageReceiver().setColorFilter(new PorterDuffColorFilter(0xff999999, PorterDuff.Mode.MULTIPLY)); + imageView.getImageReceiver().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3), PorterDuff.Mode.MULTIPLY)); addView(imageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 8, LocaleController.isRTL ? 17 : 0, 0)); nameTextView = new TextView(context); @@ -54,7 +47,7 @@ public LocationCell(Context context) { nameTextView.setMaxLines(1); nameTextView.setEllipsize(TextUtils.TruncateAt.END); nameTextView.setSingleLine(true); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), (LocaleController.isRTL ? 16 : 72), 5, (LocaleController.isRTL ? 72 : 16), 0)); @@ -64,14 +57,14 @@ public LocationCell(Context context) { addressTextView.setMaxLines(1); addressTextView.setEllipsize(TextUtils.TruncateAt.END); addressTextView.setSingleLine(true); - addressTextView.setTextColor(0xff999999); + addressTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); addressTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(addressTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), (LocaleController.isRTL ? 16 : 72), 30, (LocaleController.isRTL ? 72 : 16), 0)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } public void setLocation(TLRPC.TL_messageMediaVenue location, String icon, boolean divider) { @@ -85,7 +78,7 @@ public void setLocation(TLRPC.TL_messageMediaVenue location, String icon, boolea @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth(), getHeight() - 1, paint); + canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationLoadingCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationLoadingCell.java index 3d12155e8e8..e044f748939 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationLoadingCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationLoadingCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -12,27 +12,28 @@ import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; -import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; public class LocationLoadingCell extends FrameLayout { - private ProgressBar progressBar; + private RadialProgressView progressBar; private TextView textView; public LocationLoadingCell(Context context) { super(context); - progressBar = new ProgressBar(context); + progressBar = new RadialProgressView(context); addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); textView = new TextView(context); - textView.setTextColor(0xff999999); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setText(LocaleController.getString("NoResult", R.string.NoResult)); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); @@ -40,7 +41,7 @@ public LocationLoadingCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec((int) (AndroidUtilities.dp(56) * 2.5f), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (AndroidUtilities.dp(56) * 2.5f), MeasureSpec.EXACTLY)); } public void setLoading(boolean value) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationPoweredCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationPoweredCell.java index b813baa746d..bfbb9133d48 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationPoweredCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/LocationPoweredCell.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -18,36 +20,42 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class LocationPoweredCell extends FrameLayout { + private TextView textView; + private TextView textView2; + private ImageView imageView; + public LocationPoweredCell(Context context) { super(context); LinearLayout linearLayout = new LinearLayout(context); addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); - TextView textView = new TextView(context); + textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView.setTextColor(0xff999999); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); textView.setText("Powered by"); linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - ImageView imageView = new ImageView(context); + imageView = new ImageView(context); imageView.setImageResource(R.drawable.foursquare); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3), PorterDuff.Mode.MULTIPLY)); imageView.setPadding(0, AndroidUtilities.dp(2), 0, 0); linearLayout.addView(imageView, LayoutHelper.createLinear(35, LayoutHelper.WRAP_CONTENT)); - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView.setTextColor(0xff999999); - textView.setText("Foursquare"); - linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + textView2 = new TextView(context); + textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); + textView2.setText("Foursquare"); + linearLayout.addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56), MeasureSpec.EXACTLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java index a15dcce7fae..e24451e4c5f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/MentionCell.java @@ -3,25 +3,23 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Emoji; -import org.telegram.messenger.R; import org.telegram.messenger.UserObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -38,17 +36,15 @@ public MentionCell(Context context) { setOrientation(HORIZONTAL); - setBackgroundResource(R.drawable.list_selector); - avatarDrawable = new AvatarDrawable(); - avatarDrawable.setSmallStyle(true); + avatarDrawable.setTextSize(AndroidUtilities.dp(12)); imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(14)); addView(imageView, LayoutHelper.createLinear(28, 28, 12, 4, 0, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff000000); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); nameTextView.setSingleLine(true); nameTextView.setGravity(Gravity.LEFT); @@ -56,7 +52,7 @@ public MentionCell(Context context) { addView(nameTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 12, 0, 0, 0)); usernameTextView = new TextView(context); - usernameTextView.setTextColor(0xff999999); + usernameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); usernameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); usernameTextView.setSingleLine(true); usernameTextView.setGravity(Gravity.LEFT); @@ -69,16 +65,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(36), MeasureSpec.EXACTLY)); } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - public void setUser(TLRPC.User user) { if (user == null) { nameTextView.setText(""); @@ -130,8 +116,8 @@ public void setIsDarkTheme(boolean isDarkTheme) { nameTextView.setTextColor(0xffffffff); usernameTextView.setTextColor(0xff999999); } else { - nameTextView.setTextColor(0xff000000); - usernameTextView.setTextColor(0xff999999); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + usernameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PaymentInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PaymentInfoCell.java new file mode 100644 index 00000000000..000330bc15f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PaymentInfoCell.java @@ -0,0 +1,111 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.LayoutHelper; + +import java.util.Locale; + +public class PaymentInfoCell extends FrameLayout { + + private TextView nameTextView; + private TextView detailTextView; + private TextView detailExTextView; + private BackupImageView imageView; + + public PaymentInfoCell(Context context) { + super(context); + + imageView = new BackupImageView(context); + addView(imageView, LayoutHelper.createFrame(100, 100, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 10, 10, 10, 0)); + + nameTextView = new TextView(context); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + nameTextView.setLines(1); + nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + nameTextView.setMaxLines(1); + nameTextView.setSingleLine(true); + nameTextView.setEllipsize(TextUtils.TruncateAt.END); + nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 9, LocaleController.isRTL ? 123 : 10, 0)); + + detailTextView = new TextView(context); + detailTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + detailTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + detailTextView.setMaxLines(3); + detailTextView.setEllipsize(TextUtils.TruncateAt.END); + detailTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(detailTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 33, LocaleController.isRTL ? 123 : 10, 0)); + + detailExTextView = new TextView(context); + detailExTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + detailExTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + detailExTextView.setLines(1); + detailExTextView.setMaxLines(1); + detailExTextView.setSingleLine(true); + detailExTextView.setEllipsize(TextUtils.TruncateAt.END); + detailExTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); + addView(detailExTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 90, LocaleController.isRTL ? 123 : 10, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(120), MeasureSpec.EXACTLY)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + top = detailTextView.getBottom() + AndroidUtilities.dp(3); + detailExTextView.layout(detailExTextView.getLeft(), top, detailExTextView.getRight(), top + detailExTextView.getMeasuredHeight()); + } + + public void setInvoice(TLRPC.TL_messageMediaInvoice invoice, String botname) { + nameTextView.setText(invoice.title); + detailTextView.setText(invoice.description); + detailExTextView.setText(botname); + + int maxPhotoWidth; + if (AndroidUtilities.isTablet()) { + maxPhotoWidth = (int) (AndroidUtilities.getMinTabletSide() * 0.7f); + } else { + maxPhotoWidth = (int) (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) * 0.7f); + } + int width = 640; + int height = 360; + float scale = width / (float) (maxPhotoWidth - AndroidUtilities.dp(2)); + width /= scale; + height /= scale; + if (invoice.photo != null && invoice.photo.mime_type.startsWith("image/")) { + nameTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 9, LocaleController.isRTL ? 123 : 10, 0)); + detailTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 33, LocaleController.isRTL ? 123 : 10, 0)); + detailExTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 10 : 123, 90, LocaleController.isRTL ? 123 : 10, 0)); + imageView.setVisibility(VISIBLE); + imageView.getImageReceiver().setImage(invoice.photo, null, String.format(Locale.US, "%d_%d", width, height), null, null, null, -1, null, true); + } else { + nameTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 9, 17, 0)); + detailTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 33, 17, 0)); + detailExTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 90, 17, 0)); + imageView.setVisibility(GONE); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java index d5abe62cc88..93913c63da5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachCameraCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java index ac98606ee31..bfd36b86612 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoAttachPhotoCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -52,7 +52,7 @@ public PhotoAttachPhotoCell(Context context) { checkBox.setSize(30); checkBox.setCheckOffset(AndroidUtilities.dp(1)); checkBox.setDrawBackground(true); - checkBox.setColor(0xff3ccaef); + checkBox.setColor(0xff3ccaef, 0xffffffff); addView(checkBox, LayoutHelper.createFrame(30, 30, Gravity.LEFT | Gravity.TOP, 46, 4, 0, 0)); checkBox.setVisibility(VISIBLE); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java index d4f4c065ad9..551806160b4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoEditToolCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -25,12 +25,16 @@ public class PhotoEditToolCell extends FrameLayout { private TextView nameTextView; private TextView valueTextView; - public PhotoEditToolCell(Context context) { + private int width; + + public PhotoEditToolCell(Context context, int w) { super(context); + width = w; + iconImage = new ImageView(context); iconImage.setScaleType(ImageView.ScaleType.CENTER); - addView(iconImage, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 12)); + addView(iconImage, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.CENTER, 0, 0, 7, 12)); nameTextView = new TextView(context); nameTextView.setGravity(Gravity.CENTER); @@ -40,18 +44,19 @@ public PhotoEditToolCell(Context context) { nameTextView.setMaxLines(1); nameTextView.setSingleLine(true); nameTextView.setEllipsize(TextUtils.TruncateAt.END); - addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 4, 0, 4, 0)); + addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER | Gravity.BOTTOM, 0, 0, 7, 0)); valueTextView = new TextView(context); valueTextView.setTextColor(0xff6cc3ff); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 11); valueTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 57, 3, 0, 0)); + valueTextView.setSingleLine(true); + addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 50, 3, 0, 0)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(86), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(60), MeasureSpec.EXACTLY)); } public void setIconAndTextAndValue(int resId, String text, float value) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerAlbumsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerAlbumsCell.java index a559cdc5c23..4995022d5a1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerAlbumsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerAlbumsCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -22,6 +22,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.MediaController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -73,7 +74,7 @@ public AlbumView(Context context) { linearLayout.addView(countTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, 4, 0, 4, 0)); selector = new View(context); - selector.setBackgroundResource(R.drawable.list_selector); + selector.setBackgroundDrawable(Theme.getSelectorDrawable(false)); addView(selector, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java index 01744fa9b52..eba5f6953da 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerPhotoCell.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; @@ -16,7 +17,6 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.R; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox; @@ -43,7 +43,7 @@ public PhotoPickerPhotoCell(Context context) { checkBox.setSize(30); checkBox.setCheckOffset(AndroidUtilities.dp(1)); checkBox.setDrawBackground(true); - checkBox.setColor(0xff3ccaef); + checkBox.setColor(0xff3ccaef, 0xffffffff); addView(checkBox, LayoutHelper.createFrame(30, 30, Gravity.RIGHT | Gravity.TOP, 0, 4, 4, 0)); } @@ -66,7 +66,7 @@ public void setChecked(final boolean checked, final boolean animated) { animator.playTogether(ObjectAnimator.ofFloat(photoImage, "scaleX", checked ? 0.85f : 1.0f), ObjectAnimator.ofFloat(photoImage, "scaleY", checked ? 0.85f : 1.0f)); animator.setDuration(200); - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animator != null && animator.equals(animation)) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerSearchCell.java index 3b1b58b37b6..6e3500d1fc3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/PhotoPickerSearchCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -23,6 +23,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class PhotoPickerSearchCell extends LinearLayout { @@ -44,7 +45,7 @@ public SearchButton(Context context) { setBackgroundColor(0xff1a1a1a); selector = new View(context); - selector.setBackgroundResource(R.drawable.list_selector); + selector.setBackgroundDrawable(Theme.getSelectorDrawable(false)); addView(selector, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); imageView = new ImageView(context); @@ -145,6 +146,6 @@ public void setDelegate(PhotoPickerSearchCellDelegate delegate) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(52), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(52), MeasureSpec.EXACTLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java index d0b7496b352..4c1e0f7009d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -3,21 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Build; +import android.graphics.RectF; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; -import android.view.MotionEvent; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; @@ -36,20 +33,6 @@ public class ProfileSearchCell extends BaseCell { - private static TextPaint namePaint; - private static TextPaint nameEncryptedPaint; - private static TextPaint onlinePaint; - private static TextPaint offlinePaint; - private static TextPaint countPaint; - private static Drawable lockDrawable; - private static Drawable botDrawable; - private static Drawable broadcastDrawable; - private static Drawable groupDrawable; - private static Drawable countDrawable; - private static Drawable countDrawableGrey; - private static Drawable checkDrawable; - private static Paint linePaint; - private CharSequence currentName; private ImageReceiver avatarImage; private AvatarDrawable avatarDrawable; @@ -65,7 +48,6 @@ public class ProfileSearchCell extends BaseCell { private TLRPC.FileLocation lastAvatar = null; public boolean useSeparator = false; - public float drawAlpha = 1; private int nameLeft; private int nameTop; @@ -83,67 +65,23 @@ public class ProfileSearchCell extends BaseCell { private int countLeft; private int countWidth; private StaticLayout countLayout; + private int paddingRight; private boolean drawCheck; private int onlineLeft; private StaticLayout onlineLayout; + private RectF rect = new RectF(); + public ProfileSearchCell(Context context) { super(context); - if (namePaint == null) { - namePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - namePaint.setColor(0xff212121); - namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - nameEncryptedPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - nameEncryptedPaint.setColor(0xff00a60e); - nameEncryptedPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - onlinePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - onlinePaint.setColor(Theme.MSG_LINK_TEXT_COLOR); - - offlinePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - offlinePaint.setColor(0xff999999); - - linePaint = new Paint(); - linePaint.setColor(0xffdcdcdc); - - countPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - countPaint.setColor(0xffffffff); - countPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - - broadcastDrawable = getResources().getDrawable(R.drawable.list_broadcast); - lockDrawable = getResources().getDrawable(R.drawable.list_secret); - groupDrawable = getResources().getDrawable(R.drawable.list_group); - countDrawable = getResources().getDrawable(R.drawable.dialogs_badge); - countDrawableGrey = getResources().getDrawable(R.drawable.dialogs_badge2); - checkDrawable = getResources().getDrawable(R.drawable.check_list); - botDrawable = getResources().getDrawable(R.drawable.bot_list); - } - - namePaint.setTextSize(AndroidUtilities.dp(17)); - nameEncryptedPaint.setTextSize(AndroidUtilities.dp(17)); - onlinePaint.setTextSize(AndroidUtilities.dp(16)); - offlinePaint.setTextSize(AndroidUtilities.dp(16)); - countPaint.setTextSize(AndroidUtilities.dp(13)); - avatarImage = new ImageReceiver(this); avatarImage.setRoundRadius(AndroidUtilities.dp(26)); avatarDrawable = new AvatarDrawable(); } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - public void setData(TLObject object, TLRPC.EncryptedChat ec, CharSequence n, CharSequence s, boolean needCount) { currentName = n; if (object instanceof TLRPC.User) { @@ -159,6 +97,10 @@ public void setData(TLObject object, TLRPC.EncryptedChat ec, CharSequence n, Cha update(0); } + public void setPaddingRight(int padding) { + paddingRight = padding; + } + @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); @@ -202,9 +144,9 @@ public void buildLayout() { dialog_id = ((long) encryptedChat.id) << 32; if (!LocaleController.isRTL) { nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + lockDrawable.getIntrinsicWidth(); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - lockDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(11); } nameLockTop = AndroidUtilities.dp(16.5f); @@ -227,9 +169,9 @@ public void buildLayout() { drawCheck = chat.verified; if (!LocaleController.isRTL) { nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? groupDrawable.getIntrinsicWidth() : broadcastDrawable.getIntrinsicWidth()); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - (drawNameGroup ? groupDrawable.getIntrinsicWidth() : broadcastDrawable.getIntrinsicWidth()); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - (drawNameGroup ? Theme.dialogs_groupDrawable.getIntrinsicWidth() : Theme.dialogs_broadcastDrawable.getIntrinsicWidth()); nameLeft = AndroidUtilities.dp(11); } } else { @@ -243,9 +185,9 @@ public void buildLayout() { drawNameBot = true; if (!LocaleController.isRTL) { nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + botDrawable.getIntrinsicWidth(); + nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_botDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - botDrawable.getIntrinsicWidth(); + nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - Theme.dialogs_botDrawable.getIntrinsicWidth(); nameLeft = AndroidUtilities.dp(11); } nameLockTop = AndroidUtilities.dp(16.5f); @@ -275,9 +217,9 @@ public void buildLayout() { } } if (encryptedChat != null) { - currentNamePaint = nameEncryptedPaint; + currentNamePaint = Theme.dialogs_nameEncryptedPaint; } else { - currentNamePaint = namePaint; + currentNamePaint = Theme.dialogs_namePaint; } int onlineWidth; @@ -288,22 +230,25 @@ public void buildLayout() { onlineWidth = nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(AndroidUtilities.leftBaseline); } if (drawNameLock) { - nameWidth -= AndroidUtilities.dp(6) + lockDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else if (drawNameBroadcast) { - nameWidth -= AndroidUtilities.dp(6) + broadcastDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_broadcastDrawable.getIntrinsicWidth(); } else if (drawNameGroup) { - nameWidth -= AndroidUtilities.dp(6) + groupDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_groupDrawable.getIntrinsicWidth(); } else if (drawNameBot) { - nameWidth -= AndroidUtilities.dp(6) + botDrawable.getIntrinsicWidth(); + nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_botDrawable.getIntrinsicWidth(); } + nameWidth -= paddingRight; + onlineWidth -= paddingRight; + if (drawCount) { TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); if (dialog != null && dialog.unread_count != 0) { lastUnreadCount = dialog.unread_count; String countString = String.format("%d", dialog.unread_count); - countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(countPaint.measureText(countString))); - countLayout = new StaticLayout(countString, countPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); + countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); int w = countWidth + AndroidUtilities.dp(18); nameWidth -= w; if (!LocaleController.isRTL) { @@ -332,7 +277,7 @@ public void buildLayout() { } CharSequence onlineString = ""; - TextPaint currentOnlinePaint = offlinePaint; + TextPaint currentOnlinePaint = Theme.dialogs_offlinePaint; if (subLabel != null) { onlineString = subLabel; @@ -342,7 +287,7 @@ public void buildLayout() { } else { onlineString = LocaleController.formatUserStatus(user); if (user != null && (user.id == UserConfig.getClientUserId() || user.status != null && user.status.expires > ConnectionsManager.getInstance().getCurrentTime())) { - currentOnlinePaint = onlinePaint; + currentOnlinePaint = Theme.dialogs_onlinePaint; onlineString = LocaleController.getString("Online", R.string.Online); } } @@ -409,6 +354,11 @@ public void buildLayout() { } } } + + if (LocaleController.isRTL) { + nameLeft += paddingRight; + onlineLeft += paddingRight; + } } public void update(int mask) { @@ -497,28 +447,24 @@ protected void onDraw(Canvas canvas) { if (useSeparator) { if (LocaleController.isRTL) { - canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, linePaint); + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, Theme.dividerPaint); } else { - canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, linePaint); + canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); } } - if (drawAlpha != 1) { - canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(), (int)(255 * drawAlpha), Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); - } - if (drawNameLock) { - setDrawableBounds(lockDrawable, nameLockLeft, nameLockTop); - lockDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_lockDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_lockDrawable.draw(canvas); } else if (drawNameGroup) { - setDrawableBounds(groupDrawable, nameLockLeft, nameLockTop); - groupDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_groupDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_groupDrawable.draw(canvas); } else if (drawNameBroadcast) { - setDrawableBounds(broadcastDrawable, nameLockLeft, nameLockTop); - broadcastDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_broadcastDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_broadcastDrawable.draw(canvas); } else if (drawNameBot) { - setDrawableBounds(botDrawable, nameLockLeft, nameLockTop); - botDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_botDrawable, nameLockLeft, nameLockTop); + Theme.dialogs_botDrawable.draw(canvas); } if (nameLayout != null) { @@ -527,12 +473,16 @@ protected void onDraw(Canvas canvas) { nameLayout.draw(canvas); canvas.restore(); if (drawCheck) { + int x; if (LocaleController.isRTL) { - setDrawableBounds(checkDrawable, nameLeft - AndroidUtilities.dp(4) - checkDrawable.getIntrinsicWidth(), nameLockTop); + x = nameLeft - AndroidUtilities.dp(4) - Theme.dialogs_checkDrawable.getIntrinsicWidth(); } else { - setDrawableBounds(checkDrawable, nameLeft + (int) nameLayout.getLineWidth(0) + AndroidUtilities.dp(4), nameLockTop); + x = nameLeft + (int) nameLayout.getLineWidth(0) + AndroidUtilities.dp(4); } - checkDrawable.draw(canvas); + setDrawableBounds(Theme.dialogs_verifiedDrawable, x, nameLockTop); + setDrawableBounds(Theme.dialogs_verifiedCheckDrawable, x, nameLockTop); + Theme.dialogs_verifiedDrawable.draw(canvas); + Theme.dialogs_verifiedCheckDrawable.draw(canvas); } } @@ -544,13 +494,9 @@ protected void onDraw(Canvas canvas) { } if (countLayout != null) { - if (MessagesController.getInstance().isDialogMuted(dialog_id)) { - setDrawableBounds(countDrawableGrey, countLeft - AndroidUtilities.dp(5.5f), countTop, countWidth + AndroidUtilities.dp(11), countDrawableGrey.getIntrinsicHeight()); - countDrawableGrey.draw(canvas); - } else { - setDrawableBounds(countDrawable, countLeft - AndroidUtilities.dp(5.5f), countTop, countWidth + AndroidUtilities.dp(11), countDrawable.getIntrinsicHeight()); - countDrawable.draw(canvas); - } + int x = countLeft - AndroidUtilities.dp(5.5f); + rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); + canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, MessagesController.getInstance().isDialogMuted(dialog_id) ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); canvas.save(); canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); countLayout.draw(canvas); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioButtonCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioButtonCell.java index 3d3525923a6..51860f5d31c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioButtonCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioButtonCell.java @@ -3,14 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -19,6 +17,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RadioButton; @@ -27,25 +26,17 @@ public class RadioButtonCell extends FrameLayout { private TextView textView; private TextView valueTextView; private RadioButton radioButton; - private static Paint paint; - private boolean needDivider; public RadioButtonCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - radioButton = new RadioButton(context); radioButton.setSize(AndroidUtilities.dp(20)); - radioButton.setColor(0xffb3b3b3, 0xff37a9f0); + radioButton.setColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_radioBackgroundChecked)); addView(radioButton, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 0 : 18), 10, (LocaleController.isRTL ? 18 : 0), 0)); textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -54,7 +45,7 @@ public RadioButtonCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 17 : 51), 10, (LocaleController.isRTL ? 51 : 17), 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); valueTextView.setLines(0); @@ -66,25 +57,16 @@ public RadioButtonCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); } - public void setTextAndValue(String text, String value, boolean checked, boolean divider) { + public void setTextAndValue(String text, String value, boolean checked) { textView.setText(text); valueTextView.setText(value); - needDivider = divider; radioButton.setChecked(checked, false); - setWillNotDraw(!divider); } public void setChecked(boolean checked, boolean animated) { radioButton.setChecked(checked, animated); } - - @Override - protected void onDraw(Canvas canvas) { - if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); - } - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java index e71b5b78c0d..29e5cff3f87 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioCell.java @@ -3,14 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -19,27 +20,23 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RadioButton; +import java.util.ArrayList; + public class RadioCell extends FrameLayout { private TextView textView; private RadioButton radioButton; - private static Paint paint; private boolean needDivider; public RadioCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -50,7 +47,7 @@ public RadioCell(Context context) { radioButton = new RadioButton(context); radioButton.setSize(AndroidUtilities.dp(20)); - radioButton.setColor(0xffb3b3b3, 0xff37a9f0); + radioButton.setColor(Theme.getColor(Theme.key_radioBackground), Theme.getColor(Theme.key_radioBackgroundChecked)); addView(radioButton, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, (LocaleController.isRTL ? 18 : 0), 13, (LocaleController.isRTL ? 0 : 18), 0)); } @@ -74,14 +71,28 @@ public void setText(String text, boolean checked, boolean divider) { setWillNotDraw(!divider); } + public boolean isChecked() { + return radioButton.isChecked(); + } + public void setChecked(boolean checked, boolean animated) { radioButton.setChecked(checked, animated); } + public void setEnabled(boolean value, ArrayList animators) { + if (animators != null) { + animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f)); + animators.add(ObjectAnimator.ofFloat(radioButton, "alpha", value ? 1.0f : 0.5f)); + } else { + textView.setAlpha(value ? 1.0f : 0.5f); + radioButton.setAlpha(value ? 1.0f : 0.5f); + } + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioColorCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioColorCell.java new file mode 100644 index 00000000000..238f9edb500 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/RadioColorCell.java @@ -0,0 +1,63 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadioButton; + +public class RadioColorCell extends FrameLayout { + + private TextView textView; + private RadioButton radioButton; + + public RadioColorCell(Context context) { + super(context); + + radioButton = new RadioButton(context); + radioButton.setSize(AndroidUtilities.dp(20)); + radioButton.setColor(Theme.getColor(Theme.key_dialogRadioBackground), Theme.getColor(Theme.key_dialogRadioBackgroundChecked)); + addView(radioButton, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 0 : 18), 13, (LocaleController.isRTL ? 18 : 0), 0)); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 17 : 51), 12, (LocaleController.isRTL ? 51 : 17), 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + } + + public void setCheckColor(int color1, int color2) { + radioButton.setColor(color1, color2); + } + + public void setTextAndValue(String text, boolean checked) { + textView.setText(text); + radioButton.setChecked(checked, false); + } + + public void setChecked(boolean checked, boolean animated) { + radioButton.setChecked(checked, animated); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java index e7d11b6fff0..98cc2af1ea6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SendLocationCell.java @@ -3,12 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; @@ -16,6 +19,8 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.ActionBar.SimpleTextView; @@ -23,31 +28,42 @@ public class SendLocationCell extends FrameLayout { private SimpleTextView accurateTextView; private SimpleTextView titleTextView; + private ImageView imageView; public SendLocationCell(Context context) { super(context); - ImageView imageView = new ImageView(context); + imageView = new ImageView(context); imageView.setImageResource(R.drawable.pin); - addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 13, LocaleController.isRTL ? 17 : 0, 0)); + Drawable circle = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(40), Theme.getColor(Theme.key_location_sendLocationBackground), Theme.getColor(Theme.key_location_sendLocationBackground)); + Drawable drawable = getResources().getDrawable(R.drawable.pin); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_sendLocationIcon), PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(circle, drawable); + combinedDrawable.setCustomSize(AndroidUtilities.dp(40), AndroidUtilities.dp(40)); + imageView.setBackgroundDrawable(combinedDrawable); + addView(imageView, LayoutHelper.createFrame(40, 40, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 17, 13, LocaleController.isRTL ? 17 : 0, 0)); titleTextView = new SimpleTextView(context); titleTextView.setTextSize(16); - titleTextView.setTextColor(0xff377aae); + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText7)); titleTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 16 : 73, 12, LocaleController.isRTL ? 73 : 16, 0)); accurateTextView = new SimpleTextView(context); accurateTextView.setTextSize(14); - accurateTextView.setTextColor(0xff999999); + accurateTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); accurateTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(accurateTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 16 : 73, 37, LocaleController.isRTL ? 73 : 16, 0)); } + private ImageView getImageView() { + return imageView; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(66), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(66), MeasureSpec.EXACTLY)); } public void setText(String title, String text) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java index ad04766fe00..324f544c857 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SessionCell.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -22,6 +21,7 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; import java.util.Locale; @@ -33,24 +33,17 @@ public class SessionCell extends FrameLayout { private TextView detailTextView; private TextView detailExTextView; boolean needDivider; - private static Paint paint; public SessionCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.HORIZONTAL); linearLayout.setWeightSum(1); addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 30, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 11, 11, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); nameTextView.setLines(1); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -72,7 +65,7 @@ public SessionCell(Context context) { } detailTextView = new TextView(context); - detailTextView.setTextColor(0xff212121); + detailTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); detailTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); detailTextView.setLines(1); detailTextView.setMaxLines(1); @@ -82,7 +75,7 @@ public SessionCell(Context context) { addView(detailTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 36, 17, 0)); detailExTextView = new TextView(context); - detailExTextView.setTextColor(0xff999999); + detailExTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); detailExTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); detailExTextView.setLines(1); detailExTextView.setMaxLines(1); @@ -94,7 +87,7 @@ public SessionCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(90) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(90) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } public void setSession(TLRPC.TL_authorization session, boolean divider) { @@ -102,11 +95,13 @@ public void setSession(TLRPC.TL_authorization session, boolean divider) { nameTextView.setText(String.format(Locale.US, "%s %s", session.app_name, session.app_version)); if ((session.flags & 1) != 0) { + setTag(Theme.key_windowBackgroundWhiteValueText); onlineTextView.setText(LocaleController.getString("Online", R.string.Online)); - onlineTextView.setTextColor(0xff2f8cc9); + onlineTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); } else { + setTag(Theme.key_windowBackgroundWhiteGrayText3); onlineTextView.setText(LocaleController.stringForMessageListDate(session.date_active)); - onlineTextView.setTextColor(0xff999999); + onlineTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); } StringBuilder stringBuilder = new StringBuilder(); @@ -157,7 +152,7 @@ public void setSession(TLRPC.TL_authorization session, boolean divider) { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowBottomSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowBottomSectionCell.java deleted file mode 100644 index 008f4bd35f1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowBottomSectionCell.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Cells; - -import android.content.Context; -import android.view.View; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.R; - -public class ShadowBottomSectionCell extends View { - - public ShadowBottomSectionCell(Context context) { - super(context); - setBackgroundResource(R.drawable.greydivider_bottom); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(6), MeasureSpec.EXACTLY)); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java index c7d9a88ab9a..316007d1b2b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShadowSectionCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -13,6 +13,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; public class ShadowSectionCell extends View { @@ -20,7 +21,7 @@ public class ShadowSectionCell extends View { public ShadowSectionCell(Context context) { super(context); - setBackgroundResource(R.drawable.greydivider); + setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); } public void setSize(int value) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java index 1d5b16d0170..715a0b2d740 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ShareDialogCell.java @@ -3,17 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; -import android.os.Build; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.widget.FrameLayout; import android.widget.TextView; @@ -22,6 +20,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox; @@ -36,14 +35,13 @@ public class ShareDialogCell extends FrameLayout { public ShareDialogCell(Context context) { super(context); - setBackgroundResource(R.drawable.list_selector); imageView = new BackupImageView(context); imageView.setRoundRadius(AndroidUtilities.dp(27)); addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); nameTextView.setMaxLines(2); nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); @@ -55,7 +53,7 @@ public ShareDialogCell(Context context) { checkBox.setSize(24); checkBox.setCheckOffset(AndroidUtilities.dp(1)); checkBox.setVisibility(VISIBLE); - checkBox.setColor(0xff3ec1f9); + checkBox.setColor(Theme.getColor(Theme.key_dialogRoundCheckBox), Theme.getColor(Theme.key_dialogRoundCheckBoxCheck)); addView(checkBox, LayoutHelper.createFrame(24, 24, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 17, 39, 0, 0)); } @@ -64,16 +62,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - public void setDialog(int uid, boolean checked, CharSequence name) { TLRPC.FileLocation photo = null; if (uid > 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java index 63475c9b328..e8c52efb51f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedDocumentCell.java @@ -3,14 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.TypedValue; @@ -28,6 +29,7 @@ import org.telegram.messenger.FileLoader; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox; import org.telegram.ui.Components.LayoutHelper; @@ -38,7 +40,7 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.FileDownloadProgressListener { - private ImageView placeholderImabeView; + private ImageView placeholderImageView; private BackupImageView thumbImageView; private TextView nameTextView; private TextView extTextView; @@ -49,8 +51,6 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F private boolean needDivider; - private static Paint paint; - private int TAG; private MessageObject message; @@ -67,19 +67,13 @@ public class SharedDocumentCell extends FrameLayout implements MediaController.F public SharedDocumentCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - TAG = MediaController.getInstance().generateObserverTag(); - placeholderImabeView = new ImageView(context); - addView(placeholderImabeView, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); + placeholderImageView = new ImageView(context); + addView(placeholderImageView, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 12, 8, LocaleController.isRTL ? 12 : 0, 0)); extTextView = new TextView(context); - extTextView.setTextColor(0xffffffff); + extTextView.setTextColor(Theme.getColor(Theme.key_files_iconText)); extTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); extTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); extTextView.setLines(1); @@ -95,12 +89,12 @@ public SharedDocumentCell(Context context) { @Override public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { extTextView.setVisibility(set ? INVISIBLE : VISIBLE); - placeholderImabeView.setVisibility(set ? INVISIBLE : VISIBLE); + placeholderImageView.setVisibility(set ? INVISIBLE : VISIBLE); } }); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setLines(1); @@ -112,10 +106,11 @@ public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) statusImageView = new ImageView(context); statusImageView.setVisibility(INVISIBLE); + statusImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_sharedMedia_startStopLoadIcon), PorterDuff.Mode.MULTIPLY)); addView(statusImageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 8 : 72, 35, LocaleController.isRTL ? 72 : 8, 0)); dateTextView = new TextView(context); - dateTextView.setTextColor(0xff999999); + dateTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); dateTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); dateTextView.setLines(1); dateTextView.setMaxLines(1); @@ -125,10 +120,12 @@ public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) addView(dateTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 8 : 72, 30, LocaleController.isRTL ? 72 : 8, 0)); progressView = new LineProgressView(context); + progressView.setProgressColor(Theme.getColor(Theme.key_sharedMedia_startStopLoadIcon)); addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 2, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 72, 54, LocaleController.isRTL ? 72 : 0, 0)); checkBox = new CheckBox(context, R.drawable.round_check2); checkBox.setVisibility(INVISIBLE); + checkBox.setColor(Theme.getColor(Theme.key_checkbox), Theme.getColor(Theme.key_checkboxCheck)); addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 34, 30, LocaleController.isRTL ? 34 : 0, 0)); } @@ -168,19 +165,23 @@ public void setTextAndValueAndTypeAndThumb(String text, String value, String typ extTextView.setVisibility(INVISIBLE); } if (resId == 0) { - placeholderImabeView.setImageResource(getThumbForNameOrMime(text, type)); - placeholderImabeView.setVisibility(VISIBLE); + placeholderImageView.setImageResource(getThumbForNameOrMime(text, type)); + placeholderImageView.setVisibility(VISIBLE); } else { - placeholderImabeView.setVisibility(INVISIBLE); + placeholderImageView.setVisibility(INVISIBLE); } if (thumb != null || resId != 0) { if (thumb != null) { thumbImageView.setImage(thumb, "40_40", null); - } else { - thumbImageView.setImageResource(resId); + } else { + Drawable drawable = Theme.createCircleDrawableWithIcon(AndroidUtilities.dp(40), resId); + Theme.setCombinedDrawableColor(drawable, Theme.getColor(Theme.key_files_folderIconBackground), false); + Theme.setCombinedDrawableColor(drawable, Theme.getColor(Theme.key_files_folderIcon), true); + thumbImageView.setImageDrawable(drawable); } thumbImageView.setVisibility(VISIBLE); } else { + thumbImageView.setImageBitmap(null); thumbImageView.setVisibility(INVISIBLE); } } @@ -228,9 +229,9 @@ public void setDocument(MessageObject messageObject, boolean divider) { name = fileName; } nameTextView.setText(name); - placeholderImabeView.setVisibility(VISIBLE); + placeholderImageView.setVisibility(VISIBLE); extTextView.setVisibility(VISIBLE); - placeholderImabeView.setImageResource(getThumbForNameOrMime(fileName, messageObject.getDocument().mime_type)); + placeholderImageView.setImageResource(getThumbForNameOrMime(fileName, messageObject.getDocument().mime_type)); extTextView.setText((idx = fileName.lastIndexOf('.')) == -1 ? "" : fileName.substring(idx + 1).toLowerCase()); if (messageObject.getDocument().thumb instanceof TLRPC.TL_photoSizeEmpty || messageObject.getDocument().thumb == null) { thumbImageView.setVisibility(INVISIBLE); @@ -245,7 +246,7 @@ public void setDocument(MessageObject messageObject, boolean divider) { nameTextView.setText(""); extTextView.setText(""); dateTextView.setText(""); - placeholderImabeView.setVisibility(VISIBLE); + placeholderImageView.setVisibility(VISIBLE); extTextView.setVisibility(VISIBLE); thumbImageView.setVisibility(INVISIBLE); thumbImageView.setImageBitmap(null); @@ -315,13 +316,13 @@ public boolean isLoading() { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(56) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(AndroidUtilities.dp(72), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java index df5cd33b382..b5f6d61aebe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedLinkCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -52,7 +52,6 @@ public interface SharedLinkCellDelegate { private boolean linkPreviewPressed; private LinkPath urlPath = new LinkPath(); - private static Paint urlPaint; private int pressedLink; private ImageReceiver linkImageView; @@ -79,27 +78,17 @@ public interface SharedLinkCellDelegate { private MessageObject message; - private static TextPaint titleTextPaint; - private static TextPaint descriptionTextPaint; - private static Paint paint; + private TextPaint titleTextPaint; + private TextPaint descriptionTextPaint; public SharedLinkCell(Context context) { super(context); - if (titleTextPaint == null) { - titleTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - titleTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - titleTextPaint.setColor(0xff212121); + titleTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + titleTextPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); - descriptionTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - - urlPaint = new Paint(); - urlPaint.setColor(Theme.MSG_LINK_SELECT_BACKGROUND_COLOR); - } + descriptionTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); titleTextPaint.setTextSize(AndroidUtilities.dp(16)); descriptionTextPaint.setTextSize(AndroidUtilities.dp(16)); @@ -110,6 +99,7 @@ public SharedLinkCell(Context context) { checkBox = new CheckBox(context, R.drawable.round_check2); checkBox.setVisibility(INVISIBLE); + checkBox.setColor(Theme.getColor(Theme.key_checkbox), Theme.getColor(Theme.key_checkboxCheck)); addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 44, 44, LocaleController.isRTL ? 44 : 0, 0)); } @@ -206,7 +196,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -220,7 +210,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { CharSequence titleFinal = TextUtils.ellipsize(title.replace('\n', ' '), titleTextPaint, Math.min(width, maxWidth), TextUtils.TruncateAt.END); titleLayout = new StaticLayout(titleFinal, titleTextPaint, maxWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } letterDrawable.setTitle(title); } @@ -232,7 +222,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { description2Y = descriptionY + descriptionLayout.getLineBottom(descriptionLayout.getLineCount() - 1) + AndroidUtilities.dp(1); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -244,7 +234,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { description2Y += AndroidUtilities.dp(10); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -261,7 +251,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } linkLayout.add(layout); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -382,7 +372,7 @@ public boolean onTouchEvent(MotionEvent event) { urlPath.setCurrentLayout(layout, 0, 0); layout.getSelectionPath(0, layout.getText().length(), urlPath); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } result = true; } else if (linkPreviewPressed) { @@ -394,7 +384,7 @@ public boolean onTouchEvent(MotionEvent event) { Browser.openUrl(getContext(), links.get(pressedLink)); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } resetPressedLink(); result = true; @@ -446,7 +436,7 @@ protected void onDraw(Canvas canvas) { } if (descriptionLayout != null) { - descriptionTextPaint.setColor(0xff212121); + descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), descriptionY); descriptionLayout.draw(canvas); @@ -454,7 +444,7 @@ protected void onDraw(Canvas canvas) { } if (descriptionLayout2 != null) { - descriptionTextPaint.setColor(0xff212121); + descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), description2Y); descriptionLayout2.draw(canvas); @@ -462,7 +452,7 @@ protected void onDraw(Canvas canvas) { } if (!linkLayout.isEmpty()) { - descriptionTextPaint.setColor(Theme.MSG_LINK_TEXT_COLOR); + descriptionTextPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); int offset = 0; for (int a = 0; a < linkLayout.size(); a++) { StaticLayout layout = linkLayout.get(a); @@ -470,7 +460,7 @@ protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(AndroidUtilities.dp(LocaleController.isRTL ? 8 : AndroidUtilities.leftBaseline), linkY + offset); if (pressedLink == a) { - canvas.drawPath(urlPath, urlPaint); + canvas.drawPath(urlPath, Theme.linkSelectionPaint); } layout.draw(canvas); canvas.restore(); @@ -486,9 +476,9 @@ protected void onDraw(Canvas canvas) { if (needDivider) { if (LocaleController.isRTL) { - canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, paint); + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, Theme.dividerPaint); } else { - canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, paint); + canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedMediaSectionCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedMediaSectionCell.java index 3e2c8dd117b..49926b8e2f1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedMediaSectionCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedMediaSectionCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -16,6 +16,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class SharedMediaSectionCell extends FrameLayout { @@ -28,14 +29,14 @@ public SharedMediaSectionCell(Context context) { textView = new TextView(getContext()); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 13, 0, 13, 0)); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(40), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(40), MeasureSpec.EXACTLY)); } public void setText(String text) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell.java index 9ac990c886a..877f1b70102 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SharedPhotoVideoCell.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; @@ -23,12 +24,12 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.MessageObject; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLoader; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox; import org.telegram.ui.Components.LayoutHelper; @@ -87,11 +88,12 @@ public PhotoVideoView(Context context) { videoInfoContainer.addView(videoTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 4, 0, 0, 1)); selector = new View(context); - selector.setBackgroundResource(R.drawable.list_selector); + selector.setBackgroundDrawable(Theme.getSelectorDrawable(false)); addView(selector, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); checkBox = new CheckBox(context, R.drawable.round_check2); checkBox.setVisibility(INVISIBLE); + checkBox.setColor(Theme.getColor(Theme.key_checkbox), Theme.getColor(Theme.key_checkboxCheck)); addView(checkBox, LayoutHelper.createFrame(22, 22, Gravity.RIGHT | Gravity.TOP, 0, 2, 2, 0)); } @@ -120,7 +122,7 @@ public void setChecked(final boolean checked, boolean animated) { animator.playTogether(ObjectAnimator.ofFloat(container, "scaleX", checked ? 0.85f : 1.0f), ObjectAnimator.ofFloat(container, "scaleY", checked ? 0.85f : 1.0f)); animator.setDuration(200); - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animator != null && animator.equals(animation)) { @@ -189,6 +191,12 @@ public boolean onLongClick(View v) { } } + public void updateCheckboxColor() { + for (int a = 0; a < 6; a++) { + photoVideoViews[a].checkBox.setColor(Theme.getColor(Theme.key_checkbox), Theme.getColor(Theme.key_checkboxCheck)); + } + } + public void setDelegate(SharedPhotoVideoCellDelegate delegate) { this.delegate = delegate; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java index fa44fa0b69a..8ee143c383b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerCell.java @@ -3,13 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.view.Gravity; import android.view.View; import android.view.animation.AccelerateInterpolator; @@ -18,6 +21,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.LayoutHelper; @@ -71,8 +75,10 @@ public void setSticker(TLRPC.Document document, int side) { setBackgroundResource(R.drawable.stickers_back_all); setPadding(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(3), 0); } - if (getBackground() != null) { - getBackground().setAlpha(230); + Drawable background = getBackground(); + if (background != null) { + background.setAlpha(230); + background.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_stickersHintPanel), PorterDuff.Mode.MULTIPLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java index 408b3b59dff..fda9097decf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerEmojiCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -34,7 +34,8 @@ public class StickerEmojiCell extends FrameLayout { private long lastUpdateTime; private boolean scaled; private float scale; - private long time = 0; + private long time; + private boolean recent; private static AccelerateInterpolator interpolator = new AccelerateInterpolator(0.5f); public StickerEmojiCell(Context context) { @@ -53,6 +54,14 @@ public TLRPC.Document getSticker() { return sticker; } + public boolean isRecent() { + return recent; + } + + public void setRecent(boolean value) { + recent = value; + } + public void setSticker(TLRPC.Document document, boolean showEmoji) { if (document != null) { sticker = document; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java index ee22d9d1d2e..553a0093638 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/StickerSetCell.java @@ -3,14 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.os.Build; import android.text.TextUtils; @@ -41,18 +42,11 @@ public class StickerSetCell extends FrameLayout { private TLRPC.TL_messages_stickerSet stickersSet; private Rect rect = new Rect(); - private static Paint paint; - public StickerSetCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -62,7 +56,7 @@ public StickerSetCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 40 : 71, 10, LocaleController.isRTL ? 71 : 40, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -76,8 +70,9 @@ public StickerSetCell(Context context) { optionsButton = new ImageView(context); optionsButton.setFocusable(false); - optionsButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR)); - optionsButton.setImageResource(R.drawable.doc_actions_b); + optionsButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_stickers_menuSelector))); + optionsButton.setImageResource(R.drawable.msg_actions); + optionsButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_stickers_menu), PorterDuff.Mode.MULTIPLY)); optionsButton.setScaleType(ImageView.ScaleType.CENTER); addView(optionsButton, LayoutHelper.createFrame(40, 40, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP)); } @@ -128,9 +123,6 @@ public boolean onTouchEvent(MotionEvent event) { if (rect.contains((int) event.getX(), (int) event.getY())) { return true; } - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } } return super.onTouchEvent(event); } @@ -138,7 +130,7 @@ public boolean onTouchEvent(MotionEvent event) { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(0, getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java index 8a25f0efda5..da43db30e38 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextBlockCell.java @@ -3,39 +3,32 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.TextView; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class TextBlockCell extends FrameLayout { private TextView textView; - private static Paint paint; private boolean needDivider; public TextBlockCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 10, 17, 10)); @@ -54,7 +47,7 @@ public void setText(String text, boolean divider) { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java index cbc363e6842..cf2e314e3ee 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCell.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.view.Gravity; import android.widget.FrameLayout; @@ -17,6 +19,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.Theme; public class TextCell extends FrameLayout { @@ -29,19 +32,20 @@ public TextCell(Context context) { super(context); textView = new SimpleTextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(16); textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(textView); valueTextView = new SimpleTextView(context); - valueTextView.setTextColor(0xff2f8cc9); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); valueTextView.setTextSize(16); valueTextView.setGravity(LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT); addView(valueTextView); imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); addView(imageView); valueImageView = new ImageView(context); @@ -49,6 +53,18 @@ public TextCell(Context context) { addView(valueImageView); } + public SimpleTextView getTextView() { + return textView; + } + + public SimpleTextView getValueTextView() { + return valueTextView; + } + + public ImageView getValueImageView() { + return valueImageView; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java new file mode 100644 index 00000000000..b51671c6820 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckBoxCell.java @@ -0,0 +1,80 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CheckBoxSquare; +import org.telegram.ui.Components.LayoutHelper; + +public class TextCheckBoxCell extends FrameLayout { + + private TextView textView; + private CheckBoxSquare checkBox; + private boolean needDivider; + + public TextCheckBoxCell(Context context) { + super(context); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + textView.setEllipsize(TextUtils.TruncateAt.END); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 64 : 17, 0, LocaleController.isRTL ? 17 : 64, 0)); + + checkBox = new CheckBoxSquare(context, false); + checkBox.setDuplicateParentStateEnabled(false); + checkBox.setFocusable(false); + checkBox.setFocusableInTouchMode(false); + checkBox.setClickable(false); + addView(checkBox, LayoutHelper.createFrame(18, 18, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 19, 0, 19, 0)); + } + + @Override + public void invalidate() { + super.invalidate(); + checkBox.invalidate(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + } + + public void setTextAndCheck(String text, boolean checked, boolean divider) { + textView.setText(text); + checkBox.setChecked(checked, false); + needDivider = divider; + setWillNotDraw(!divider); + } + + public void setChecked(boolean checked) { + checkBox.setChecked(checked, true); + } + + @Override + protected void onDraw(Canvas canvas) { + if (needDivider) { + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java index 7456eb30d2c..1f200ec1f26 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextCheckCell.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -19,6 +18,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.Switch; @@ -27,21 +27,14 @@ public class TextCheckCell extends FrameLayout { private TextView textView; private TextView valueTextView; private Switch checkBox; - private static Paint paint; private boolean needDivider; private boolean isMultiline; public TextCheckCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -51,7 +44,7 @@ public TextCheckCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 64 : 17, 0, LocaleController.isRTL ? 17 : 64, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); valueTextView.setLines(1); @@ -72,9 +65,9 @@ public TextCheckCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (isMultiline) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); } else { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(valueTextView.getVisibility() == VISIBLE ? 64 : 48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(valueTextView.getVisibility() == VISIBLE ? 64 : 48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } } @@ -118,6 +111,18 @@ public void setTextAndValueAndCheck(String text, String value, boolean checked, setWillNotDraw(!divider); } + @Override + public void setEnabled(boolean value) { + super.setEnabled(value); + if (value) { + textView.setAlpha(1.0f); + valueTextView.setAlpha(1.0f); + } else { + textView.setAlpha(0.5f); + valueTextView.setAlpha(0.5f); + } + } + public void setChecked(boolean checked) { checkBox.setChecked(checked); } @@ -125,7 +130,7 @@ public void setChecked(boolean checked) { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java index 33ce9ee9d3f..8ff8f504c63 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorCell.java @@ -3,17 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -21,31 +20,32 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; +import java.util.ArrayList; + public class TextColorCell extends FrameLayout { private TextView textView; private boolean needDivider; private int currentColor; + private float alpha = 1.0f; + + private static Paint colorPaint; - private Drawable colorDrawable; - private static Paint paint; + public final static int colors[] = new int[] {0xfff04444, 0xffff8e01, 0xffffce1f, 0xff79d660, 0xff40edf6, 0xff46beff, 0xffd274f9, 0xffff4f96, 0xffbbbbbb}; + public final static int colorsToSave[] = new int[] {0xffff0000, 0xffff8e01, 0xffffce1f, 0xff00ff00, 0xff40edf6, 0xff0000ff, 0xffd274f9, 0xffff4f96, 0xffffffff}; public TextColorCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); + if (colorPaint == null) { + colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); } - colorDrawable = getResources().getDrawable(R.drawable.switch_to_on2); - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -54,35 +54,49 @@ public TextColorCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 0, 17, 0)); } + @Override + public void setAlpha(float value) { + alpha = value; + invalidate(); + } + + @Override + public float getAlpha() { + return alpha; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } public void setTextAndColor(String text, int color, boolean divider) { textView.setText(text); needDivider = divider; currentColor = color; - colorDrawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.MULTIPLY)); setWillNotDraw(!needDivider && currentColor == 0); invalidate(); } + public void setEnabled(boolean value, ArrayList animators) { + if (animators != null) { + animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f)); + animators.add(ObjectAnimator.ofFloat(this, "alpha", value ? 1.0f : 0.5f)); + } else { + textView.setAlpha(value ? 1.0f : 0.5f); + setAlpha(value ? 1.0f : 0.5f); + } + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } - if (currentColor != 0 && colorDrawable != null) { - int x; - int y = (getMeasuredHeight() - colorDrawable.getMinimumHeight()) / 2; - if (!LocaleController.isRTL) { - x = getMeasuredWidth() - colorDrawable.getIntrinsicWidth() - AndroidUtilities.dp(14.5f); - } else { - x = AndroidUtilities.dp(14.5f); - } - colorDrawable.setBounds(x, y, x + colorDrawable.getIntrinsicWidth(), y + colorDrawable.getIntrinsicHeight()); - colorDrawable.draw(canvas); + if (currentColor != 0) { + colorPaint.setColor(currentColor); + colorPaint.setAlpha((int) (255 * alpha)); + canvas.drawCircle(LocaleController.isRTL ? AndroidUtilities.dp(29) : getMeasuredWidth() - AndroidUtilities.dp(29), getMeasuredHeight() / 2, AndroidUtilities.dp(10), colorPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java new file mode 100644 index 00000000000..829ae2386da --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextColorThemeCell.java @@ -0,0 +1,81 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.Components.LayoutHelper; + +public class TextColorThemeCell extends FrameLayout { + + private TextView textView; + private boolean needDivider; + private int currentColor; + private float alpha = 1.0f; + + private static Paint colorPaint; + + public TextColorThemeCell(Context context) { + super(context); + + if (colorPaint == null) { + colorPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + } + + textView = new TextView(context); + textView.setTextColor(0xff212121); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + textView.setPadding(0, 0, 0, AndroidUtilities.dp(3)); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, (LocaleController.isRTL ? 17 : 17 + 36), 0, (LocaleController.isRTL ? 17 + 36 : 17), 0)); + } + + @Override + public void setAlpha(float value) { + alpha = value; + invalidate(); + } + + @Override + public float getAlpha() { + return alpha; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + } + + public void setTextAndColor(String text, int color) { + textView.setText(text); + currentColor = color; + setWillNotDraw(!needDivider && currentColor == 0); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (currentColor != 0) { + colorPaint.setColor(currentColor); + colorPaint.setAlpha((int) (255 * alpha)); + canvas.drawCircle(!LocaleController.isRTL ? AndroidUtilities.dp(28) : getMeasuredWidth() - AndroidUtilities.dp(28 + 20), getMeasuredHeight() / 2, AndroidUtilities.dp(10), colorPaint); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java index 494014ccabe..81578574e36 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailCell.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -18,6 +20,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class TextDetailCell extends FrameLayout { @@ -30,7 +33,7 @@ public TextDetailCell(Context context) { super(context); textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -39,7 +42,7 @@ public TextDetailCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, LocaleController.isRTL ? 16 : 71, 10, LocaleController.isRTL ? 71 : 16, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -49,6 +52,7 @@ public TextDetailCell(Context context) { imageView = new ImageView(context); imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 0 : 16, 0, LocaleController.isRTL ? 16 : 0, 0)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java index f1a8bd8006b..7a8481e883f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextDetailSettingsCell.java @@ -3,14 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.widget.FrameLayout; @@ -18,36 +18,31 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class TextDetailSettingsCell extends FrameLayout { private TextView textView; private TextView valueTextView; - private static Paint paint; private boolean needDivider; private boolean multiline; public TextDetailSettingsCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 10, 17, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff8a8a8a); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); valueTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); valueTextView.setLines(1); @@ -60,9 +55,9 @@ public TextDetailSettingsCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (!multiline) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(64) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); } else { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); } } @@ -91,7 +86,7 @@ public void setTextAndValue(String text, String value, boolean divider) { @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoCell.java index 12ca36a5df3..21062bdc4cf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -15,6 +15,7 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; public class TextInfoCell extends FrameLayout { @@ -25,7 +26,7 @@ public TextInfoCell(Context context) { super(context); textView = new TextView(context); - textView.setTextColor(0xffa3a3a3); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText5)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); textView.setGravity(Gravity.CENTER); textView.setPadding(0, AndroidUtilities.dp(19), 0, AndroidUtilities.dp(19)); @@ -34,7 +35,7 @@ public TextInfoCell(Context context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); } public void setText(String text) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoPrivacyCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoPrivacyCell.java index b008bb767bc..c46474c556c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoPrivacyCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextInfoPrivacyCell.java @@ -3,11 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.content.Context; import android.text.method.LinkMovementMethod; import android.util.TypedValue; @@ -20,6 +22,8 @@ import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.ActionBar.Theme; +import java.util.ArrayList; + public class TextInfoPrivacyCell extends FrameLayout { private TextView textView; @@ -28,8 +32,8 @@ public TextInfoPrivacyCell(Context context) { super(context); textView = new TextView(context); - textView.setTextColor(0xff808080); - textView.setLinkTextColor(Theme.MSG_LINK_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + textView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); textView.setPadding(0, AndroidUtilities.dp(10), 0, AndroidUtilities.dp(17)); @@ -43,10 +47,27 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } public void setText(CharSequence text) { + if (text == null) { + textView.setPadding(0, AndroidUtilities.dp(2), 0, 0); + } else { + textView.setPadding(0, AndroidUtilities.dp(10), 0, AndroidUtilities.dp(17)); + } textView.setText(text); } public void setTextColor(int color) { textView.setTextColor(color); } + + public TextView getTextView() { + return textView; + } + + public void setEnabled(boolean value, ArrayList animators) { + if (animators != null) { + animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f)); + } else { + textView.setAlpha(value ? 1.0f : 0.5f); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextPriceCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextPriceCell.java new file mode 100644 index 00000000000..fc1a181701f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextPriceCell.java @@ -0,0 +1,116 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class TextPriceCell extends FrameLayout { + + private TextView textView; + private TextView valueTextView; + private String dotstring; + private int dotLength; + + public TextPriceCell(Context context) { + super(context); + + dotstring = LocaleController.isRTL ? " ." : ". "; + setWillNotDraw(false); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 0, 17, 0)); + + valueTextView = new TextView(context); + valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + valueTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + valueTextView.setLines(1); + valueTextView.setMaxLines(1); + valueTextView.setSingleLine(true); + valueTextView.setEllipsize(TextUtils.TruncateAt.END); + valueTextView.setGravity((LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL); + addView(valueTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, 17, 0, 17, 0)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(40)); + + int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - AndroidUtilities.dp(34); + int width = availableWidth / 2; + + valueTextView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + width = availableWidth - valueTextView.getMeasuredWidth() - AndroidUtilities.dp(8); + + textView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + dotLength = (int) Math.ceil(textView.getPaint().measureText(dotstring)); + } + + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public void setTextValueColor(int color) { + valueTextView.setTextColor(color); + } + + public void setTextAndValue(String text, String value, boolean bold) { + textView.setText(text); + if (value != null) { + valueTextView.setText(value); + valueTextView.setVisibility(VISIBLE); + } else { + valueTextView.setVisibility(INVISIBLE); + } + if (bold) { + setTag(Theme.key_windowBackgroundWhiteBlackText); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + valueTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + } else { + setTag(Theme.key_windowBackgroundWhiteGrayText2); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + textView.setTypeface(Typeface.DEFAULT); + valueTextView.setTypeface(Typeface.DEFAULT); + } + requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + /*if (LocaleController.isRTL) { + + } else { + int start = textView.getMeasuredWidth() + AndroidUtilities.dp(17 + 4); + int end = getMeasuredWidth() - valueTextView.getMeasuredWidth() - AndroidUtilities.dp(17 + 4); + for (int a = start; a < end; a+= dotLength) { + canvas.drawText(dotstring, a, AndroidUtilities.dp(30), textView.getPaint()); + } + }*/ + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java index 7aa88dc2942..02ed5995ac1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/TextSettingsCell.java @@ -3,14 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; +import android.animation.Animator; +import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.support.annotation.ColorInt; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -20,27 +24,23 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.LayoutHelper; +import java.util.ArrayList; + public class TextSettingsCell extends FrameLayout { private TextView textView; private TextView valueTextView; private ImageView valueImageView; - private static Paint paint; private boolean needDivider; public TextSettingsCell(Context context) { super(context); - if (paint == null) { - paint = new Paint(); - paint.setColor(0xffd9d9d9); - paint.setStrokeWidth(1); - } - textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLines(1); textView.setMaxLines(1); @@ -50,7 +50,7 @@ public TextSettingsCell(Context context) { addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 17, 0, 17, 0)); valueTextView = new TextView(context); - valueTextView.setTextColor(0xff2f8cc9); + valueTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); valueTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); valueTextView.setLines(1); valueTextView.setMaxLines(1); @@ -62,6 +62,7 @@ public TextSettingsCell(Context context) { valueImageView = new ImageView(context); valueImageView.setScaleType(ImageView.ScaleType.CENTER); valueImageView.setVisibility(INVISIBLE); + valueImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayIcon), PorterDuff.Mode.MULTIPLY)); addView(valueImageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 17, 0, 17, 0)); } @@ -83,6 +84,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { textView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); } + public TextView getTextView() { + return textView; + } + + public TextView getValueTextView() { + return valueTextView; + } + public void setTextColor(int color) { textView.setTextColor(color); } @@ -126,10 +135,31 @@ public void setTextAndIcon(String text, int resId, boolean divider) { setWillNotDraw(!divider); } + public void setEnabled(boolean value, ArrayList animators) { + setEnabled(value); + if (animators != null) { + animators.add(ObjectAnimator.ofFloat(textView, "alpha", value ? 1.0f : 0.5f)); + if (valueTextView.getVisibility() == VISIBLE) { + animators.add(ObjectAnimator.ofFloat(valueTextView, "alpha", value ? 1.0f : 0.5f)); + } + if (valueImageView.getVisibility() == VISIBLE) { + animators.add(ObjectAnimator.ofFloat(valueImageView, "alpha", value ? 1.0f : 0.5f)); + } + } else { + textView.setAlpha(value ? 1.0f : 0.5f); + if (valueTextView.getVisibility() == VISIBLE) { + valueTextView.setAlpha(value ? 1.0f : 0.5f); + } + if (valueImageView.getVisibility() == VISIBLE) { + valueImageView.setAlpha(value ? 1.0f : 0.5f); + } + } + } + @Override protected void onDraw(Canvas canvas) { if (needDivider) { - canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, paint); + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java new file mode 100644 index 00000000000..294ef537891 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ThemeCell.java @@ -0,0 +1,194 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Cells; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +import java.io.File; +import java.io.FileInputStream; + +public class ThemeCell extends FrameLayout { + + private TextView textView; + private ImageView checkImage; + private ImageView optionsButton; + private boolean needDivider; + private Paint paint; + private Theme.ThemeInfo currentThemeInfo; + private static byte[] bytes = new byte[1024]; + + public ThemeCell(Context context) { + super(context); + + setWillNotDraw(false); + + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setPadding(0, 0, 0, AndroidUtilities.dp(1)); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 53 + 48 : 60, 0, LocaleController.isRTL ? 60 : 53 + 48, 0)); + + checkImage = new ImageView(context); + checkImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_featuredStickers_addedIcon), PorterDuff.Mode.MULTIPLY)); + checkImage.setImageResource(R.drawable.sticker_added); + addView(checkImage, LayoutHelper.createFrame(19, 14, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, 17 + 38, 0, 17 + 38, 0)); + + optionsButton = new ImageView(context); + optionsButton.setFocusable(false); + optionsButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_stickers_menuSelector))); + optionsButton.setImageResource(R.drawable.ic_ab_other); + optionsButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_stickers_menu), PorterDuff.Mode.MULTIPLY)); + optionsButton.setScaleType(ImageView.ScaleType.CENTER); + addView(optionsButton, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48) + (needDivider ? 1 : 0), MeasureSpec.EXACTLY)); + } + + public void setOnOptionsClick(OnClickListener listener) { + optionsButton.setOnClickListener(listener); + } + + public TextView getTextView() { + return textView; + } + + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public Theme.ThemeInfo getCurrentThemeInfo() { + return currentThemeInfo; + } + + public void setTheme(Theme.ThemeInfo themeInfo, boolean divider) { + currentThemeInfo = themeInfo; + String text = themeInfo.name; + if (text.endsWith(".attheme")) { + text = text.substring(0, text.lastIndexOf('.')); + } + textView.setText(text); + needDivider = divider; + checkImage.setVisibility(themeInfo == Theme.getCurrentTheme() ? VISIBLE : INVISIBLE); + + boolean finished = false; + if (themeInfo.pathToFile != null || themeInfo.assetName != null) { + FileInputStream stream = null; + try { + int currentPosition = 0; + File file; + if (themeInfo.assetName != null) { + file = Theme.getAssetFile(themeInfo.assetName); + } else { + file = new File(themeInfo.pathToFile); + } + stream = new FileInputStream(file); + int idx; + int read; + int linesRead = 0; + while ((read = stream.read(bytes)) != -1) { + int previousPosition = currentPosition; + int start = 0; + for (int a = 0; a < read; a++) { + if (bytes[a] == '\n') { + linesRead++; + int len = a - start + 1; + String line = new String(bytes, start, len - 1, "UTF-8"); + if (line.startsWith("WPS")) { + break; + } else { + if ((idx = line.indexOf('=')) != -1) { + String key = line.substring(0, idx); + if (key.equals(Theme.key_actionBarDefault)) { + String param = line.substring(idx + 1); + int value; + if (param.length() > 0 && param.charAt(0) == '#') { + try { + value = Color.parseColor(param); + } catch (Exception ignore) { + value = Utilities.parseInt(param); + } + } else { + value = Utilities.parseInt(param); + } + finished = true; + paint.setColor(value); + break; + } + } + } + start += len; + currentPosition += len; + } + } + if (previousPosition == currentPosition || linesRead >= 500) { + break; + } + stream.getChannel().position(currentPosition); + if (finished) { + break; + } + } + } catch (Throwable e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + if (!finished) { + paint.setColor(Theme.getDefaultColor(Theme.key_actionBarDefault)); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (needDivider) { + canvas.drawLine(getPaddingLeft(), getHeight() - 1, getWidth() - getPaddingRight(), getHeight() - 1, Theme.dividerPaint); + } + int x = AndroidUtilities.dp(16 + 11); + if (LocaleController.isRTL) { + x = getWidth() - x; + } + canvas.drawCircle(x, AndroidUtilities.dp(13 + 11), AndroidUtilities.dp(11), paint); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java index 1da73b4a0fd..8a887650395 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/UserCell.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.view.Gravity; import android.widget.FrameLayout; import android.widget.ImageView; @@ -22,6 +24,7 @@ import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.CheckBox; @@ -50,12 +53,15 @@ public class UserCell extends FrameLayout { private int lastStatus; private TLRPC.FileLocation lastAvatar; - private int statusColor = 0xffa8a8a8; - private int statusOnlineColor = 0xff3b84c0; + private int statusColor; + private int statusOnlineColor; public UserCell(Context context, int padding, int checkbox, boolean admin) { super(context); + statusColor = Theme.getColor(Theme.key_windowBackgroundWhiteGrayText); + statusOnlineColor = Theme.getColor(Theme.key_windowBackgroundWhiteBlueText); + avatarDrawable = new AvatarDrawable(); avatarImageView = new BackupImageView(context); @@ -63,7 +69,7 @@ public UserCell(Context context, int padding, int checkbox, boolean admin) { addView(avatarImageView, LayoutHelper.createFrame(48, 48, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 7 + padding, 8, LocaleController.isRTL ? 7 + padding : 0, 0)); nameTextView = new SimpleTextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(17); nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 20, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 28 + (checkbox == 2 ? 18 : 0) : (68 + padding), 11.5f, LocaleController.isRTL ? (68 + padding) : 28 + (checkbox == 2 ? 18 : 0), 0)); @@ -79,16 +85,18 @@ public UserCell(Context context, int padding, int checkbox, boolean admin) { addView(imageView, LayoutHelper.createFrame(LayoutParams.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 0 : 16, 0, LocaleController.isRTL ? 16 : 0, 0)); if (checkbox == 2) { - checkBoxBig = new CheckBoxSquare(context); + checkBoxBig = new CheckBoxSquare(context, false); addView(checkBoxBig, LayoutHelper.createFrame(18, 18, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 19 : 0, 0, LocaleController.isRTL ? 0 : 19, 0)); } else if (checkbox == 1) { checkBox = new CheckBox(context, R.drawable.round_check2); checkBox.setVisibility(INVISIBLE); + checkBox.setColor(Theme.getColor(Theme.key_checkbox), Theme.getColor(Theme.key_checkboxCheck)); addView(checkBox, LayoutHelper.createFrame(22, 22, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 37 + padding, 38, LocaleController.isRTL ? 37 + padding : 0, 0)); } if (admin) { adminImage = new ImageView(context); + adminImage.setImageResource(R.drawable.admin_star); addView(adminImage, LayoutHelper.createFrame(16, 16, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, LocaleController.isRTL ? 24 : 0, 13.5f, LocaleController.isRTL ? 0 : 24, 0)); } } @@ -100,9 +108,11 @@ public void setIsAdmin(int value) { adminImage.setVisibility(value != 0 ? VISIBLE : GONE); nameTextView.setPadding(LocaleController.isRTL && value != 0 ? AndroidUtilities.dp(16) : 0, 0, !LocaleController.isRTL && value != 0 ? AndroidUtilities.dp(16) : 0, 0); if (value == 1) { - adminImage.setImageResource(R.drawable.admin_star); + setTag(Theme.key_profile_creatorIcon); + adminImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_profile_creatorIcon), PorterDuff.Mode.MULTIPLY)); } else if (value == 2) { - adminImage.setImageResource(R.drawable.admin_star2); + setTag(Theme.key_profile_adminIcon); + adminImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_profile_adminIcon), PorterDuff.Mode.MULTIPLY)); } } @@ -153,6 +163,14 @@ public void setStatusColors(int color, int onlineColor) { statusOnlineColor = onlineColor; } + @Override + public void invalidate() { + super.invalidate(); + if (checkBoxBig != null) { + checkBoxBig.invalidate(); + } + } + public void update(int mask) { if (currentObject == null) { return; @@ -232,7 +250,7 @@ public void update(int mask) { } else if (currentUser != null) { if (currentUser.bot) { statusTextView.setTextColor(statusColor); - if (currentUser.bot_chat_history || adminImage != null && adminImage.getVisibility() == VISIBLE) { //TODO fix + if (currentUser.bot_chat_history || adminImage != null && adminImage.getVisibility() == VISIBLE) { statusTextView.setText(LocaleController.getString("BotStatusRead", R.string.BotStatusRead)); } else { statusTextView.setText(LocaleController.getString("BotStatusCantRead", R.string.BotStatusCantRead)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/WallpaperCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/WallpaperCell.java index a37de6fe24f..cb4490e0893 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/WallpaperCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/WallpaperCell.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Cells; @@ -48,12 +48,19 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(102), MeasureSpec.EXACTLY)); } - public void setWallpaper(TLRPC.WallPaper wallpaper, int selectedBackground) { + public void setWallpaper(TLRPC.WallPaper wallpaper, int selectedBackground, Drawable themedWallpaper, boolean themed) { if (wallpaper == null) { imageView.setVisibility(INVISIBLE); imageView2.setVisibility(VISIBLE); - selectionView.setVisibility(selectedBackground == -1 ? View.VISIBLE : INVISIBLE); - imageView2.setBackgroundColor(selectedBackground == -1 || selectedBackground == 1000001 ? 0x5a475866 : 0x5a000000); + if (themed) { + selectionView.setVisibility(selectedBackground == -2 ? View.VISIBLE : INVISIBLE); + imageView2.setImageDrawable(themedWallpaper); + imageView2.setScaleType(ImageView.ScaleType.CENTER_CROP); + } else { + selectionView.setVisibility(selectedBackground == -1 ? View.VISIBLE : INVISIBLE); + imageView2.setBackgroundColor(selectedBackground == -1 || selectedBackground == 1000001 ? 0x5a475866 : 0x5a000000); + imageView2.setScaleType(ImageView.ScaleType.CENTER); + } } else { imageView.setVisibility(VISIBLE); imageView2.setVisibility(INVISIBLE); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java index cff4907c07a..9bf2e33fbbe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeChatNameActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -33,6 +33,8 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.LayoutHelper; public class ChangeChatNameActivity extends BaseFragment { @@ -93,8 +95,9 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField = new EditText(context); firstNameField.setText(currentChat.title); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setMaxLines(3); firstNameField.setPadding(0, 0, 0, 0); firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); @@ -154,4 +157,21 @@ public void run() { private void saveName() { MessagesController.getInstance().changeChatTitle(chat_id, firstNameField.getText().toString()); } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java index 3af498a7fe0..5ebf2fa017c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeNameActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -37,6 +37,8 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.LayoutHelper; public class ChangeNameActivity extends BaseFragment { @@ -88,8 +90,9 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField = new EditText(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setMaxLines(1); firstNameField.setLines(1); firstNameField.setSingleLine(true); @@ -113,8 +116,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { lastNameField = new EditText(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - lastNameField.setHintTextColor(0xff979797); - lastNameField.setTextColor(0xff212121); + lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); lastNameField.setMaxLines(1); lastNameField.setLines(1); lastNameField.setSingleLine(true); @@ -199,4 +203,25 @@ public void run() { }, 100); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java index e30445e506d..bba00fce5e4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneActivity.java @@ -3,19 +3,19 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.Manifest; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -62,8 +62,11 @@ import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.messenger.AnimatorListenerAdapterProxy; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; @@ -82,7 +85,7 @@ public class ChangePhoneActivity extends BaseFragment { private int currentViewNum = 0; private SlideView[] views = new SlideView[5]; - private ProgressDialog progressDialog; + private AlertDialog progressDialog; private Dialog permissionsDialog; private ArrayList permissionsItems = new ArrayList<>(); private boolean checkPermissions = true; @@ -90,6 +93,31 @@ public class ChangePhoneActivity extends BaseFragment { private final static int done_button = 1; + private class ProgressView extends View { + + private Paint paint = new Paint(); + private Paint paint2 = new Paint(); + private float progress; + + public ProgressView(Context context) { + super(context); + paint.setColor(Theme.getColor(Theme.key_login_progressInner)); + paint2.setColor(Theme.getColor(Theme.key_login_progressOuter)); + } + + public void setProgress(float value) { + progress = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int start = (int) (getMeasuredWidth() * progress); + canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); + canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); + } + } + @Override public void onFragmentDestroy() { super.onFragmentDestroy(); @@ -102,7 +130,7 @@ public void onFragmentDestroy() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -197,22 +225,11 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { } } - public void needShowAlert(final String text) { - if (text == null || getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(text); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); - } - public void needShowProgress() { if (getParentActivity() == null || getParentActivity().isFinishing() || progressDialog != null) { return; } - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -226,7 +243,7 @@ public void needHideProgress() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -244,7 +261,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { final SlideView newView = views[page]; currentViewNum = page; - newView.setParams(params); + newView.setParams(params, false); actionBar.setTitle(newView.getHeaderName()); newView.onShow(); newView.setX(back ? -AndroidUtilities.displaySize.x : AndroidUtilities.displaySize.x); @@ -255,7 +272,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { animatorSet.playTogether( ObjectAnimator.ofFloat(outView, "translationX", back ? AndroidUtilities.displaySize.x : -AndroidUtilities.displaySize.x), ObjectAnimator.ofFloat(newView, "translationX", 0)); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { newView.setVisibility(View.VISIBLE); @@ -309,6 +326,9 @@ public class PhoneView extends SlideView implements AdapterView.OnItemSelectedLi private EditText codeField; private HintEditText phoneField; private TextView countryButton; + private View view; + private TextView textView; + private TextView textView2; private int countryState = 0; @@ -330,7 +350,7 @@ public PhoneView(Context context) { countryButton = new TextView(context); countryButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); countryButton.setPadding(AndroidUtilities.dp(12), AndroidUtilities.dp(10), AndroidUtilities.dp(12), 0); - countryButton.setTextColor(0xff212121); + countryButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); countryButton.setMaxLines(1); countryButton.setSingleLine(true); countryButton.setEllipsize(TextUtils.TruncateAt.END); @@ -340,10 +360,10 @@ public PhoneView(Context context) { countryButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - CountrySelectActivity fragment = new CountrySelectActivity(); + CountrySelectActivity fragment = new CountrySelectActivity(true); fragment.setCountrySelectActivityDelegate(new CountrySelectActivity.CountrySelectActivityDelegate() { @Override - public void didSelectCountry(String name) { + public void didSelectCountry(String name, String shortName) { selectCountry(name); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -359,25 +379,26 @@ public void run() { } }); - View view = new View(context); + view = new View(context); view.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); - view.setBackgroundColor(0xffdbdbdb); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayLine)); addView(view, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 4, -17.5f, 4, 0)); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(HORIZONTAL); addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 20, 0, 0)); - TextView textView = new TextView(context); + textView = new TextView(context); textView.setText("+"); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); codeField = new EditText(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); AndroidUtilities.clearCursorDrawable(codeField); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setMaxLines(1); @@ -477,8 +498,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { phoneField = new HintEditText(context); phoneField.setInputType(InputType.TYPE_CLASS_PHONE); - phoneField.setTextColor(0xff212121); - phoneField.setHintTextColor(0xff979797); + phoneField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); AndroidUtilities.clearCursorDrawable(phoneField); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -571,13 +593,13 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } }); - textView = new TextView(context); - textView.setText(LocaleController.getString("ChangePhoneHelp", R.string.ChangePhoneHelp)); - textView.setTextColor(0xff757575); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - textView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 28, 0, 10)); + textView2 = new TextView(context); + textView2.setText(LocaleController.getString("ChangePhoneHelp", R.string.ChangePhoneHelp)); + textView2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); + textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView2.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + textView2.setLineSpacing(AndroidUtilities.dp(2), 1.0f); + addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 28, 0, 10)); HashMap languageMap = new HashMap<>(); try { @@ -595,7 +617,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } reader.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Collections.sort(countriesArray, new Comparator() { @@ -613,7 +635,7 @@ public int compare(String lhs, String rhs) { country = telephonyManager.getSimCountryIso().toUpperCase(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (country != null) { @@ -716,27 +738,34 @@ public void onNextPressed() { } if (countryState == 1) { - needShowAlert(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); + AlertsCreator.showSimpleAlert(ChangePhoneActivity.this, LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); return; } else if (countryState == 2 && !BuildVars.DEBUG_VERSION) { - needShowAlert(LocaleController.getString("WrongCountry", R.string.WrongCountry)); + AlertsCreator.showSimpleAlert(ChangePhoneActivity.this, LocaleController.getString("WrongCountry", R.string.WrongCountry)); return; } if (codeField.length() == 0) { - needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + AlertsCreator.showSimpleAlert(ChangePhoneActivity.this, LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); return; } - TLRPC.TL_account_sendChangePhoneCode req = new TLRPC.TL_account_sendChangePhoneCode(); + final TLRPC.TL_account_sendChangePhoneCode req = new TLRPC.TL_account_sendChangePhoneCode(); String phone = PhoneFormat.stripExceptNumbers("" + codeField.getText() + phoneField.getText()); req.phone_number = phone; req.allow_flashcall = simcardAvailable && allowCall; if (req.allow_flashcall) { try { - String number = tm.getLine1Number(); - req.current_number = number != null && number.length() != 0 && (phone.contains(number) || number.contains(phone)); + @SuppressLint("HardwareIds") String number = tm.getLine1Number(); + if (!TextUtils.isEmpty(number)) { + req.current_number = phone.contains(number) || number.contains(phone); + if (!req.current_number) { + req.allow_flashcall = false; + } + } else { + req.current_number = false; + } } catch (Exception e) { req.allow_flashcall = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -745,7 +774,7 @@ public void onNextPressed() { try { params.putString("ephone", "+" + PhoneFormat.stripExceptNumbers(codeField.getText().toString()) + " " + PhoneFormat.stripExceptNumbers(phoneField.getText().toString())); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); params.putString("ephone", "+" + phone); } params.putString("phoneFormated", phone); @@ -761,21 +790,7 @@ public void run() { if (error == null) { fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { - if (error.text != null) { - if (error.text.contains("PHONE_NUMBER_INVALID")) { - needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); - } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else if (error.text.startsWith("PHONE_NUMBER_OCCUPIED")) { - needShowAlert(LocaleController.formatString("ChangePhoneNumberOccupied", R.string.ChangePhoneNumberOccupied, params.getString("phone"))); - } else { - needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); - } - } + AlertsCreator.processError(error, ChangePhoneActivity.this, req, params.getString("phone")); } needHideProgress(); } @@ -807,31 +822,6 @@ public String getHeaderName() { public class LoginActivitySmsView extends SlideView implements NotificationCenter.NotificationCenterDelegate { - private class ProgressView extends View { - - private Paint paint = new Paint(); - private Paint paint2 = new Paint(); - private float progress; - - public ProgressView(Context context) { - super(context); - paint.setColor(0xffe1eaf2); - paint2.setColor(0xff62a0d0); - } - - public void setProgress(float value) { - progress = value; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - int start = (int) (getMeasuredWidth() * progress); - canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); - canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); - } - } - private String phone; private String phoneHash; private String requestPhone; @@ -842,6 +832,7 @@ protected void onDraw(Canvas canvas) { private TextView problemText; private Bundle currentParams; private ProgressView progressView; + private TextView wrongNumber; private Timer timeTimer; private Timer codeTimer; @@ -868,7 +859,7 @@ public LoginActivitySmsView(Context context, final int type) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -891,10 +882,11 @@ public LoginActivitySmsView(Context context, final int type) { } codeField = new EditText(context); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); AndroidUtilities.clearCursorDrawable(codeField); - codeField.setHintTextColor(0xff979797); + codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setInputType(InputType.TYPE_CLASS_PHONE); @@ -940,7 +932,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { timeText = new TextView(context); timeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - timeText.setTextColor(0xff757575); + timeText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); timeText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); timeText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(timeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 30, 0, 0)); @@ -954,7 +946,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { problemText.setText(LocaleController.getString("DidNotGetTheCode", R.string.DidNotGetTheCode)); problemText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); problemText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - problemText.setTextColor(0xff4d83b3); + problemText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); problemText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); problemText.setPadding(0, AndroidUtilities.dp(2), 0, AndroidUtilities.dp(12)); addView(problemText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 20, 0, 0)); @@ -978,7 +970,7 @@ public void onClick(View v) { mailer.putExtra(Intent.EXTRA_TEXT, "Phone: " + requestPhone + "\nApp version: " + version + "\nOS version: SDK " + Build.VERSION.SDK_INT + "\nDevice Name: " + Build.MANUFACTURER + Build.MODEL + "\nLocale: " + Locale.getDefault() + "\nError: " + lastError); getContext().startActivity(Intent.createChooser(mailer, "Send email...")); } catch (Exception e) { - needShowAlert(LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); + AlertsCreator.showSimpleAlert(ChangePhoneActivity.this, LocaleController.getString("NoMailInstalled", R.string.NoMailInstalled)); } } } @@ -988,9 +980,9 @@ public void onClick(View v) { linearLayout.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); - TextView wrongNumber = new TextView(context); + wrongNumber = new TextView(context); wrongNumber.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_HORIZONTAL); - wrongNumber.setTextColor(0xff4d83b3); + wrongNumber.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); wrongNumber.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); wrongNumber.setLineSpacing(AndroidUtilities.dp(2), 1.0f); wrongNumber.setPadding(0, AndroidUtilities.dp(24), 0, 0); @@ -1023,7 +1015,7 @@ private void resendCode() { nextPressed = true; needShowProgress(); - TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); + final TLRPC.TL_auth_resendCode req = new TLRPC.TL_auth_resendCode(); req.phone_number = requestPhone; req.phone_code_hash = phoneHash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -1036,20 +1028,10 @@ public void run() { if (error == null) { fillNextCodeParams(params, (TLRPC.TL_auth_sentCode) response); } else { - if (error.text != null) { - if (error.text.contains("PHONE_NUMBER_INVALID")) { - needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); - } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - onBackPressed(); - setPage(0, true, null, true); - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else if (error.code != -1000) { - needShowAlert(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); - } + AlertsCreator.processError(error, ChangePhoneActivity.this, req); + if (error.text.contains("PHONE_CODE_EXPIRED")) { + onBackPressed(); + setPage(0, true, null, true); } } needHideProgress(); @@ -1065,7 +1047,7 @@ public String getHeaderName() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -1189,7 +1171,7 @@ private void destroyCodeTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1277,7 +1259,7 @@ private void destroyTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1335,17 +1317,7 @@ public void run() { } waitingForEvent = true; if (currentType != 3) { - if (error.text.contains("PHONE_NUMBER_INVALID")) { - needShowAlert(LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); - } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { - needShowAlert(LocaleController.getString("InvalidCode", R.string.InvalidCode)); - } else if (error.text.contains("PHONE_CODE_EXPIRED")) { - needShowAlert(LocaleController.getString("CodeExpired", R.string.CodeExpired)); - } else if (error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - needShowAlert(error.text); - } + AlertsCreator.processError(error, ChangePhoneActivity.this, req); } } } @@ -1405,11 +1377,8 @@ public void didReceivedNotification(int id, final Object... args) { onNextPressed(); } else if (id == NotificationCenter.didReceiveCall) { String num = "" + args[0]; - if (!pattern.equals("*")) { - String patternNumbers = pattern.replace("*", ""); - if (!num.contains(patternNumbers)) { - return; - } + if (!AndroidUtilities.checkPhonePattern(pattern, num)) { + return; } ignoreOnTextChange = true; codeField.setText(num); @@ -1418,4 +1387,79 @@ public void didReceivedNotification(int id, final Object... args) { } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + PhoneView phoneView = (PhoneView) views[0]; + LoginActivitySmsView smsView1 = (LoginActivitySmsView) views[1]; + LoginActivitySmsView smsView2 = (LoginActivitySmsView) views[2]; + LoginActivitySmsView smsView3 = (LoginActivitySmsView) views[3]; + LoginActivitySmsView smsView4 = (LoginActivitySmsView) views[4]; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(phoneView.countryButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.view, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhiteGrayLine), + new ThemeDescription(phoneView.textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(phoneView.textView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + + new ThemeDescription(smsView1.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView1.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView1.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView2.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView2.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView2.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView3.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView3.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView3.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView4.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView4.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView4.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java index bb14930ad39..f12eb60a18a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangePhoneHelpActivity.java @@ -3,14 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; @@ -29,11 +30,18 @@ import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.LayoutHelper; public class ChangePhoneHelpActivity extends BaseFragment { + private TextView textView1; + private TextView textView2; + private ImageView imageView; + @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -84,54 +92,34 @@ public boolean onTouch(View v, MotionEvent event) { layoutParams.height = ScrollView.LayoutParams.WRAP_CONTENT; linearLayout.setLayoutParams(layoutParams); - ImageView imageView = new ImageView(context); + imageView = new ImageView(context); imageView.setImageResource(R.drawable.phone_change); - linearLayout.addView(imageView); - LinearLayout.LayoutParams layoutParams2 = (LinearLayout.LayoutParams) imageView.getLayoutParams(); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - layoutParams2.gravity = Gravity.CENTER_HORIZONTAL; - imageView.setLayoutParams(layoutParams2); - - TextView textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView.setGravity(Gravity.CENTER_HORIZONTAL); - textView.setTextColor(0xff212121); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_changephoneinfo_image), PorterDuff.Mode.MULTIPLY)); + linearLayout.addView(imageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); + + textView1 = new TextView(context); + textView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView1.setGravity(Gravity.CENTER_HORIZONTAL); + textView1.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); try { - textView.setText(AndroidUtilities.replaceTags(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp))); + textView1.setText(AndroidUtilities.replaceTags(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp))); } catch (Exception e) { - FileLog.e("tmessages", e); - textView.setText(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp)); + FileLog.e(e); + textView1.setText(LocaleController.getString("PhoneNumberHelp", R.string.PhoneNumberHelp)); } - linearLayout.addView(textView); - layoutParams2 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - layoutParams2.gravity = Gravity.CENTER_HORIZONTAL; - layoutParams2.leftMargin = AndroidUtilities.dp(20); - layoutParams2.rightMargin = AndroidUtilities.dp(20); - layoutParams2.topMargin = AndroidUtilities.dp(56); - textView.setLayoutParams(layoutParams2); - - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - textView.setGravity(Gravity.CENTER_HORIZONTAL); - textView.setTextColor(0xff4d83b3); - textView.setText(LocaleController.getString("PhoneNumberChange", R.string.PhoneNumberChange)); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setPadding(0, AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10)); - linearLayout.addView(textView); - layoutParams2 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - layoutParams2.gravity = Gravity.CENTER_HORIZONTAL; - layoutParams2.leftMargin = AndroidUtilities.dp(20); - layoutParams2.rightMargin = AndroidUtilities.dp(20); - layoutParams2.topMargin = AndroidUtilities.dp(46); - textView.setLayoutParams(layoutParams2); - - textView.setOnClickListener(new View.OnClickListener() { + linearLayout.addView(textView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 20, 56, 20, 0)); + + textView2 = new TextView(context); + textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + textView2.setGravity(Gravity.CENTER_HORIZONTAL); + textView2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); + textView2.setText(LocaleController.getString("PhoneNumberChange", R.string.PhoneNumberChange)); + textView2.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView2.setPadding(0, AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10)); + linearLayout.addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 20, 46, 20, 0)); + + textView2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (getParentActivity() == null) { @@ -153,4 +141,20 @@ public void onClick(DialogInterface dialogInterface, int i) { return fragmentView; } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(textView1, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(textView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(imageView, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_changephoneinfo_image), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java index ccdf81dfcc2..22d767d2976 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChangeUsernameActivity.java @@ -3,20 +3,26 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.text.Editable; import android.text.InputType; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; @@ -26,6 +32,7 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; @@ -42,7 +49,11 @@ import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; import java.util.ArrayList; @@ -52,13 +63,60 @@ public class ChangeUsernameActivity extends BaseFragment { private EditText firstNameField; private View doneButton; private TextView checkTextView; - private int checkReqId = 0; - private String lastCheckName = null; - private Runnable checkRunnable = null; - private boolean lastNameAvailable = false; + private TextView helpTextView; + + private int checkReqId; + private String lastCheckName; + private Runnable checkRunnable; + private boolean lastNameAvailable; + private boolean ignoreCheck; + private CharSequence infoText; private final static int done_button = 1; + public class LinkSpan extends ClickableSpan { + + private String url; + + public LinkSpan(String value) { + url = value; + } + + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + } + + @Override + public void onClick(View widget) { + try { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", url); + clipboard.setPrimaryClip(clip); + Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + private static class LinkMovementMethodMy extends LinkMovementMethod { + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + try { + boolean result = super.onTouchEvent(widget, buffer, event); + if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + Selection.removeSelection(buffer); + } + return result; + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + } + @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -84,7 +142,8 @@ public void onItemClick(int id) { } fragmentView = new LinearLayout(context); - ((LinearLayout) fragmentView).setOrientation(LinearLayout.VERTICAL); + LinearLayout linearLayout = (LinearLayout) fragmentView; + linearLayout.setOrientation(LinearLayout.VERTICAL); fragmentView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -94,8 +153,9 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField = new EditText(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setMaxLines(1); firstNameField.setLines(1); firstNameField.setPadding(0, 0, 0, 0); @@ -115,26 +175,6 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { return false; } }); - - ((LinearLayout) fragmentView).addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); - - if (user != null && user.username != null && user.username.length() > 0) { - firstNameField.setText(user.username); - firstNameField.setSelection(firstNameField.length()); - } - - checkTextView = new TextView(context); - checkTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - checkTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - ((LinearLayout) fragmentView).addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 12, 24, 0)); - - TextView helpTextView = new TextView(context); - helpTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - helpTextView.setTextColor(0xff6d6d72); - helpTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - helpTextView.setText(AndroidUtilities.replaceTags(LocaleController.getString("UsernameHelp", R.string.UsernameHelp))); - ((LinearLayout) fragmentView).addView(helpTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 10, 24, 0)); - firstNameField.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { @@ -143,17 +183,53 @@ public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + if (ignoreCheck) { + return; + } checkUserName(firstNameField.getText().toString(), false); } @Override public void afterTextChanged(Editable editable) { - + if (firstNameField.length() > 0) { + String url = "https://" + MessagesController.getInstance().linkPrefix + "/" + firstNameField.getText(); + String text = LocaleController.formatString("UsernameHelpLink", R.string.UsernameHelpLink, url); + int index = text.indexOf(url); + SpannableStringBuilder textSpan = new SpannableStringBuilder(text); + textSpan.setSpan(new LinkSpan(url), index, index + url.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + helpTextView.setText(TextUtils.concat(infoText, "\n\n", textSpan)); + } else { + helpTextView.setText(infoText); + } } }); + linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); + + checkTextView = new TextView(context); + checkTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + checkTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + linearLayout.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 12, 24, 0)); + + helpTextView = new TextView(context); + helpTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + helpTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); + helpTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + helpTextView.setText(infoText = AndroidUtilities.replaceTags(LocaleController.getString("UsernameHelp", R.string.UsernameHelp))); + helpTextView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); + helpTextView.setHighlightColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkSelection)); + helpTextView.setMovementMethod(new LinkMovementMethodMy()); + linearLayout.addView(helpTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 10, 24, 0)); + checkTextView.setVisibility(View.GONE); + if (user != null && user.username != null && user.username.length() > 0) { + ignoreCheck = true; + firstNameField.setText(user.username); + firstNameField.setSelection(firstNameField.length()); + ignoreCheck = false; + } + return fragmentView; } @@ -168,30 +244,6 @@ public void onResume() { } } - private void showErrorAlert(String error) { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - switch (error) { - case "USERNAME_INVALID": - builder.setMessage(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); - break; - case "USERNAME_OCCUPIED": - builder.setMessage(LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); - break; - case "USERNAMES_UNAVAILABLE": - builder.setMessage(LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); - break; - default: - builder.setMessage(LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); - break; - } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); - } - private boolean checkUserName(final String name, boolean alert) { if (name != null && name.length() > 0) { checkTextView.setVisibility(View.VISIBLE); @@ -213,26 +265,29 @@ private boolean checkUserName(final String name, boolean alert) { if (name != null) { if (name.startsWith("_") || name.endsWith("_")) { checkTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } for (int a = 0; a < name.length(); a++) { char ch = name.charAt(a); if (a == 0 && ch >= '0' && ch <= '9') { if (alert) { - showErrorAlert(LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); + AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); } else { checkTextView.setText(LocaleController.getString("UsernameInvalidStartNumber", R.string.UsernameInvalidStartNumber)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { if (alert) { - showErrorAlert(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); + AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); } else { checkTextView.setText(LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } @@ -240,19 +295,21 @@ private boolean checkUserName(final String name, boolean alert) { } if (name == null || name.length() < 5) { if (alert) { - showErrorAlert(LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); + AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); } else { checkTextView.setText(LocaleController.getString("UsernameInvalidShort", R.string.UsernameInvalidShort)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } if (name.length() > 32) { if (alert) { - showErrorAlert(LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); + AlertsCreator.showSimpleAlert(this, LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); } else { checkTextView.setText(LocaleController.getString("UsernameInvalidLong", R.string.UsernameInvalidLong)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } @@ -264,12 +321,14 @@ private boolean checkUserName(final String name, boolean alert) { } if (name.equals(currentName)) { checkTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, name)); - checkTextView.setTextColor(0xff26972c); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); return true; } checkTextView.setText(LocaleController.getString("UsernameChecking", R.string.UsernameChecking)); - checkTextView.setTextColor(0xff6d6d72); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGrayText8); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); lastCheckName = name; checkRunnable = new Runnable() { @Override @@ -286,11 +345,13 @@ public void run() { if (lastCheckName != null && lastCheckName.equals(name)) { if (error == null && response instanceof TLRPC.TL_boolTrue) { checkTextView.setText(LocaleController.formatString("UsernameAvailable", R.string.UsernameAvailable, name)); - checkTextView.setTextColor(0xff26972c); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); lastNameAvailable = true; } else { checkTextView.setText(LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); lastNameAvailable = false; } } @@ -323,12 +384,12 @@ private void saveName() { return; } - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); - TLRPC.TL_account_updateUsername req = new TLRPC.TL_account_updateUsername(); + final TLRPC.TL_account_updateUsername req = new TLRPC.TL_account_updateUsername(); req.username = newName; NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); @@ -343,7 +404,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } ArrayList users = new ArrayList<>(); users.add(user); @@ -360,9 +421,9 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - showErrorAlert(error.text); + AlertsCreator.processError(error, ChangeUsernameActivity.this, req); } }); } @@ -377,7 +438,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -391,4 +452,27 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { AndroidUtilities.showKeyboard(firstNameField); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(helpTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8), + + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGreenText), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java index 0f99caa5d63..fb47619ba7e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelCreateActivity.java @@ -3,49 +3,34 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Vibrator; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.SpannableStringBuilder; import android.text.TextWatcher; -import android.text.style.ImageSpan; import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; -import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatObject; @@ -54,16 +39,16 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.messenger.UserObject; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.ContactsAdapter; -import org.telegram.ui.Adapters.SearchAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.AdminedChannelCell; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.LoadingCell; @@ -71,22 +56,18 @@ import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextBlockCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; -import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; -import org.telegram.ui.Components.ChipSpan; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.LetterSectionsListView; import java.util.ArrayList; -import java.util.HashMap; public class ChannelCreateActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, AvatarUpdater.AvatarUpdaterDelegate { private View doneButton; private EditText nameTextView; - private ProgressDialog progressDialog; + private AlertDialog progressDialog; private ShadowSectionCell sectionCell; private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; @@ -94,14 +75,18 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC private EditText descriptionTextView; private TLRPC.FileLocation avatar; private String nameToSet; + private LinearLayout linearLayout2; + private EditText editText; private LinearLayout linearLayout; + private LinearLayout adminnedChannelsLayout; private LinearLayout linkContainer; private LinearLayout publicContainer; private TextBlockCell privateContainer; private RadioButtonCell radioButtonCell1; private RadioButtonCell radioButtonCell2; private TextInfoPrivacyCell typeInfoCell; + private TextView helpTextView; private TextView checkTextView; private HeaderCell headerCell; private int checkReqId; @@ -117,18 +102,6 @@ public class ChannelCreateActivity extends BaseFragment implements NotificationC private ArrayList adminedChannelCells = new ArrayList<>(); private LoadingCell loadingAdminedCell; - private ContactsAdapter listViewAdapter; - private TextView emptyTextView; - private LetterSectionsListView listView; - private SearchAdapter searchListViewAdapter; - private boolean searchWas; - private boolean searching; - private HashMap selectedContacts = new HashMap<>(); - private ArrayList allSpans = new ArrayList<>(); - private int beforeChangeIndex; - private boolean ignoreChange; - private CharSequence changeString; - private int currentStep; private int chatId; private boolean canCreatePublic = true; @@ -175,12 +148,9 @@ public void run() { @SuppressWarnings("unchecked") @Override public boolean onFragmentCreate() { - NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatDidCreated); NotificationCenter.getInstance().addObserver(this, NotificationCenter.chatDidFailCreate); - if (currentStep == 2) { - NotificationCenter.getInstance().addObserver(this, NotificationCenter.contactsDidLoaded); - } else if (currentStep == 1) { + if (currentStep == 1) { generateLink(); } if (avatarUpdater != null) { @@ -193,12 +163,8 @@ public boolean onFragmentCreate() { @Override public void onFragmentDestroy() { super.onFragmentDestroy(); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateInterfaces); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatDidCreated); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatDidFailCreate); - if (currentStep == 2) { - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.contactsDidLoaded); - } if (avatarUpdater != null) { avatarUpdater.clear(); } @@ -213,9 +179,6 @@ public void onResume() { @Override public View createView(Context context) { - searching = false; - searchWas = false; - actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); @@ -240,7 +203,7 @@ public void onItemClick(int id) { donePressed = true; if (avatarUpdater.uploadingAvatar != null) { createAfterUpload = true; - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -253,7 +216,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -261,7 +224,7 @@ public void onClick(DialogInterface dialog, int which) { return; } final int reqId = MessagesController.getInstance().createChat(nameTextView.getText().toString(), new ArrayList(), descriptionTextView.getText().toString(), ChatObject.CHAT_TYPE_CHANNEL, ChannelCreateActivity.this); - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -273,7 +236,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -302,21 +265,9 @@ public void onClick(DialogInterface dialog, int which) { } Bundle args = new Bundle(); args.putInt("step", 2); - args.putInt("chat_id", chatId); - presentFragment(new ChannelCreateActivity(args), true); - } else { - ArrayList result = new ArrayList<>(); - for (Integer uid : selectedContacts.keySet()) { - TLRPC.InputUser user = MessagesController.getInputUser(MessagesController.getInstance().getUser(uid)); - if (user != null) { - result.add(user); - } - } - MessagesController.getInstance().addUsersToChannel(chatId, result, null); - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); - Bundle args2 = new Bundle(); - args2.putInt("chat_id", chatId); - presentFragment(new ChatActivity(args2), true); + args.putInt("chatId", chatId); + args.putInt("chatType", ChatObject.CHAT_TYPE_CHANNEL); + presentFragment(new GroupCreateActivity(args), true); } } } @@ -325,27 +276,17 @@ public void onClick(DialogInterface dialog, int which) { ActionBarMenu menu = actionBar.createMenu(); doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); - if (currentStep != 2) { - fragmentView = new ScrollView(context); - ScrollView scrollView = (ScrollView) fragmentView; - scrollView.setFillViewport(true); - linearLayout = new LinearLayout(context); - scrollView.addView(linearLayout, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - } else { - fragmentView = new LinearLayout(context); - fragmentView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - linearLayout = (LinearLayout) fragmentView; - } + fragmentView = new ScrollView(context); + ScrollView scrollView = (ScrollView) fragmentView; + scrollView.setFillViewport(true); + linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.VERTICAL); + scrollView.addView(linearLayout, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); if (currentStep == 0) { actionBar.setTitle(LocaleController.getString("NewChannel", R.string.NewChannel)); - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setTag(Theme.key_windowBackgroundWhite); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); FrameLayout frameLayout = new FrameLayout(context); linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); @@ -398,15 +339,16 @@ public void onClick(DialogInterface dialogInterface, int i) { nameTextView.setMaxLines(4); nameTextView.setGravity(Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setHintTextColor(0xff979797); + nameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); nameTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + nameTextView.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(100); nameTextView.setFilters(inputFilters); nameTextView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); AndroidUtilities.clearCursorDrawable(nameTextView); - nameTextView.setTextColor(0xff212121); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -428,8 +370,9 @@ public void afterTextChanged(Editable s) { descriptionTextView = new EditText(context); descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - descriptionTextView.setHintTextColor(0xff979797); - descriptionTextView.setTextColor(0xff212121); + descriptionTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + descriptionTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + descriptionTextView.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); descriptionTextView.setPadding(0, 0, 0, AndroidUtilities.dp(6)); descriptionTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); descriptionTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); @@ -467,24 +410,25 @@ public void afterTextChanged(Editable editable) { } }); - TextView helpTextView = new TextView(context); + helpTextView = new TextView(context); helpTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - helpTextView.setTextColor(0xff6d6d72); + helpTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); helpTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); helpTextView.setText(LocaleController.getString("DescriptionInfo", R.string.DescriptionInfo)); linearLayout.addView(helpTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 24, 10, 24, 20)); } else if (currentStep == 1) { actionBar.setTitle(LocaleController.getString("ChannelSettings", R.string.ChannelSettings)); - fragmentView.setBackgroundColor(0xfff0f0f0); + fragmentView.setTag(Theme.key_windowBackgroundGray); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - LinearLayout linearLayout2 = new LinearLayout(context); + linearLayout2 = new LinearLayout(context); linearLayout2.setOrientation(LinearLayout.VERTICAL); - linearLayout2.setBackgroundColor(0xffffffff); + linearLayout2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell1 = new RadioButtonCell(context); - radioButtonCell1.setBackgroundResource(R.drawable.list_selector); - radioButtonCell1.setTextAndValue(LocaleController.getString("ChannelPublic", R.string.ChannelPublic), LocaleController.getString("ChannelPublicInfo", R.string.ChannelPublicInfo), !isPrivate, false); + radioButtonCell1.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell1.setTextAndValue(LocaleController.getString("ChannelPublic", R.string.ChannelPublic), LocaleController.getString("ChannelPublicInfo", R.string.ChannelPublicInfo), !isPrivate); linearLayout2.addView(radioButtonCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell1.setOnClickListener(new View.OnClickListener() { @Override @@ -498,8 +442,8 @@ public void onClick(View v) { }); radioButtonCell2 = new RadioButtonCell(context); - radioButtonCell2.setBackgroundResource(R.drawable.list_selector); - radioButtonCell2.setTextAndValue(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate), LocaleController.getString("ChannelPrivateInfo", R.string.ChannelPrivateInfo), isPrivate, false); + radioButtonCell2.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + radioButtonCell2.setTextAndValue(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate), LocaleController.getString("ChannelPrivateInfo", R.string.ChannelPrivateInfo), isPrivate); linearLayout2.addView(radioButtonCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell2.setOnClickListener(new View.OnClickListener() { @Override @@ -517,7 +461,7 @@ public void onClick(View v) { linkContainer = new LinearLayout(context); linkContainer.setOrientation(LinearLayout.VERTICAL); - linkContainer.setBackgroundColor(0xffffffff); + linkContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linkContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); headerCell = new HeaderCell(context); @@ -527,11 +471,11 @@ public void onClick(View v) { publicContainer.setOrientation(LinearLayout.HORIZONTAL); linkContainer.addView(publicContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 17, 7, 17, 0)); - EditText editText = new EditText(context); - editText.setText("telegram.me/"); + editText = new EditText(context); + editText.setText(MessagesController.getInstance().linkPrefix + "/"); editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - editText.setHintTextColor(0xff979797); - editText.setTextColor(0xff212121); + editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); editText.setMaxLines(1); editText.setLines(1); editText.setEnabled(false); @@ -544,8 +488,8 @@ public void onClick(View v) { nameTextView = new EditText(context); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - nameTextView.setHintTextColor(0xff979797); - nameTextView.setTextColor(0xff212121); + nameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setMaxLines(1); nameTextView.setLines(1); nameTextView.setBackgroundDrawable(null); @@ -574,7 +518,7 @@ public void afterTextChanged(Editable editable) { }); privateContainer = new TextBlockCell(context); - privateContainer.setBackgroundResource(R.drawable.list_selector); + privateContainer.setBackgroundDrawable(Theme.getSelectorDrawable(false)); linkContainer.addView(privateContainer); privateContainer.setOnClickListener(new View.OnClickListener() { @Override @@ -588,7 +532,7 @@ public void onClick(View v) { clipboard.setPrimaryClip(clip); Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -600,250 +544,22 @@ public void onClick(View v) { linkContainer.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 17, 3, 17, 7)); typeInfoCell = new TextInfoPrivacyCell(context); - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout.addView(typeInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); loadingAdminedCell = new LoadingCell(context); linearLayout.addView(loadingAdminedCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminnedChannelsLayout = new LinearLayout(context); + adminnedChannelsLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + adminnedChannelsLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.addView(adminnedChannelsLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminedInfoCell = new TextInfoPrivacyCell(context); - adminedInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + adminedInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout.addView(adminedInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); updatePrivatePublic(); - } else if (currentStep == 2) { - actionBar.setTitle(LocaleController.getString("ChannelAddMembers", R.string.ChannelAddMembers)); - actionBar.setSubtitle(LocaleController.formatPluralString("Members", selectedContacts.size())); - - searchListViewAdapter = new SearchAdapter(context, null, false, false, false, false); - searchListViewAdapter.setCheckedMap(selectedContacts); - searchListViewAdapter.setUseUserCell(true); - listViewAdapter = new ContactsAdapter(context, 1, false, null, false); - listViewAdapter.setCheckedMap(selectedContacts); - - FrameLayout frameLayout = new FrameLayout(context); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - nameTextView = new EditText(context); - nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setHintTextColor(0xff979797); - nameTextView.setTextColor(0xff212121); - nameTextView.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - nameTextView.setMinimumHeight(AndroidUtilities.dp(54)); - nameTextView.setSingleLine(false); - nameTextView.setLines(2); - nameTextView.setMaxLines(2); - nameTextView.setVerticalScrollBarEnabled(true); - nameTextView.setHorizontalScrollBarEnabled(false); - nameTextView.setPadding(0, 0, 0, 0); - nameTextView.setHint(LocaleController.getString("AddMutual", R.string.AddMutual)); - nameTextView.setTextIsSelectable(false); - nameTextView.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); - AndroidUtilities.clearCursorDrawable(nameTextView); - frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 10, 0, 10, 0)); - - nameTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { - if (!ignoreChange) { - beforeChangeIndex = nameTextView.getSelectionStart(); - changeString = new SpannableString(charSequence); - } - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void afterTextChanged(Editable editable) { - if (!ignoreChange) { - boolean search = false; - int afterChangeIndex = nameTextView.getSelectionEnd(); - if (editable.toString().length() < changeString.toString().length()) { - String deletedString = ""; - try { - deletedString = changeString.toString().substring(afterChangeIndex, beforeChangeIndex); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - if (deletedString.length() > 0) { - if (searching && searchWas) { - search = true; - } - Spannable span = nameTextView.getText(); - for (int a = 0; a < allSpans.size(); a++) { - ChipSpan sp = allSpans.get(a); - if (span.getSpanStart(sp) == -1) { - allSpans.remove(sp); - selectedContacts.remove(sp.uid); - } - } - actionBar.setSubtitle(LocaleController.formatPluralString("Members", selectedContacts.size())); - listView.invalidateViews(); - } else { - search = true; - } - } else { - search = true; - } - if (search) { - String text = nameTextView.getText().toString().replace("<", ""); - if (text.length() != 0) { - searching = true; - searchWas = true; - if (listView != null) { - listView.setAdapter(searchListViewAdapter); - searchListViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(false); - listView.setFastScrollEnabled(false); - listView.setVerticalScrollBarEnabled(true); - } - if (emptyTextView != null) { - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - } - searchListViewAdapter.searchDialogs(text); - } else { - searchListViewAdapter.searchDialogs(null); - searching = false; - searchWas = false; - listView.setAdapter(listViewAdapter); - listViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); - listView.setVerticalScrollBarEnabled(false); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - } - } - } - } - }); - - LinearLayout emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.INVISIBLE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - linearLayout.addView(emptyTextLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - emptyTextLayout.addView(emptyTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); - - FrameLayout frameLayout2 = new FrameLayout(context); - emptyTextLayout.addView(frameLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); - - listView = new LetterSectionsListView(context); - listView.setEmptyView(emptyTextLayout); - listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setFastScrollEnabled(true); - listView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); - listView.setAdapter(listViewAdapter); - listView.setFastScrollAlwaysVisible(true); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); - linearLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - TLRPC.User user; - if (searching && searchWas) { - user = (TLRPC.User) searchListViewAdapter.getItem(i); - } else { - int section = listViewAdapter.getSectionForPosition(i); - int row = listViewAdapter.getPositionInSectionForPosition(i); - if (row < 0 || section < 0) { - return; - } - user = (TLRPC.User) listViewAdapter.getItem(section, row); - } - if (user == null) { - return; - } - - boolean check = true; - if (selectedContacts.containsKey(user.id)) { - check = false; - try { - ChipSpan span = selectedContacts.get(user.id); - selectedContacts.remove(user.id); - SpannableStringBuilder text = new SpannableStringBuilder(nameTextView.getText()); - text.delete(text.getSpanStart(span), text.getSpanEnd(span)); - allSpans.remove(span); - ignoreChange = true; - nameTextView.setText(text); - nameTextView.setSelection(text.length()); - ignoreChange = false; - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else { - ignoreChange = true; - ChipSpan span = createAndPutChipForUser(user); - if (span != null) { - span.uid = user.id; - } - ignoreChange = false; - if (span == null) { - return; - } - } - actionBar.setSubtitle(LocaleController.formatPluralString("Members", selectedContacts.size())); - if (searching || searchWas) { - ignoreChange = true; - SpannableStringBuilder ssb = new SpannableStringBuilder(""); - for (ImageSpan sp : allSpans) { - ssb.append("<<"); - ssb.setSpan(sp, ssb.length() - 2, ssb.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - nameTextView.setText(ssb); - nameTextView.setSelection(ssb.length()); - ignoreChange = false; - - searchListViewAdapter.searchDialogs(null); - searching = false; - searchWas = false; - listView.setAdapter(listViewAdapter); - listViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); - listView.setVerticalScrollBarEnabled(false); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - } else { - if (view instanceof UserCell) { - ((UserCell) view).setChecked(check, true); - } - } - } - }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL) { - AndroidUtilities.hideKeyboard(nameTextView); - } - if (listViewAdapter != null) { - listViewAdapter.setIsScrolling(i != SCROLL_STATE_IDLE); - } - } - - @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (absListView.isFastScrollEnabled()) { - AndroidUtilities.clearDrawableAnimation(absListView); - } - } - }); } return fragmentView; @@ -879,32 +595,28 @@ private void updatePrivatePublic() { } if (!isPrivate && !canCreatePublic) { typeInfoCell.setText(LocaleController.getString("ChangePublicLimitReached", R.string.ChangePublicLimitReached)); - typeInfoCell.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); linkContainer.setVisibility(View.GONE); sectionCell.setVisibility(View.GONE); if (loadingAdminedChannels) { loadingAdminedCell.setVisibility(View.VISIBLE); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.GONE); - } - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + adminnedChannelsLayout.setVisibility(View.GONE); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); adminedInfoCell.setVisibility(View.GONE); } else { - typeInfoCell.setBackgroundResource(R.drawable.greydivider); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); loadingAdminedCell.setVisibility(View.GONE); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.VISIBLE); - } + adminnedChannelsLayout.setVisibility(View.VISIBLE); adminedInfoCell.setVisibility(View.VISIBLE); } } else { - typeInfoCell.setTextColor(0xff808080); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteGrayText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); sectionCell.setVisibility(View.VISIBLE); adminedInfoCell.setVisibility(View.GONE); - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.GONE); - } + adminnedChannelsLayout.setVisibility(View.GONE); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linkContainer.setVisibility(View.VISIBLE); loadingAdminedCell.setVisibility(View.GONE); typeInfoCell.setText(isPrivate ? LocaleController.getString("ChannelPrivateLinkHelp", R.string.ChannelPrivateLinkHelp) : LocaleController.getString("ChannelUsernameHelp", R.string.ChannelUsernameHelp)); @@ -936,7 +648,7 @@ public void run() { progressDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } doneButton.performClick(); } @@ -991,17 +703,12 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { @Override public void didReceivedNotification(int id, final Object... args) { - if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; - if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { - updateVisibleRows(mask); - } - } else if (id == NotificationCenter.chatDidFailCreate) { + if (id == NotificationCenter.chatDidFailCreate) { if (progressDialog != null) { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } donePressed = false; @@ -1010,7 +717,7 @@ public void didReceivedNotification(int id, final Object... args) { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } int chat_id = (Integer) args[0]; @@ -1022,10 +729,6 @@ public void didReceivedNotification(int id, final Object... args) { MessagesController.getInstance().changeChatAvatar(chat_id, uploadedAvatar); } presentFragment(new ChannelCreateActivity(bundle), true); - } else if (id == NotificationCenter.contactsDidLoaded) { - if (listViewAdapter != null) { - listViewAdapter.notifyDataSetChanged(); - } } } @@ -1062,9 +765,9 @@ public void onClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (channel.megagroup) { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, "telegram.me/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance().linkPrefix + "/" + channel.username, channel.title))); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, "telegram.me/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance().linkPrefix + "/" + channel.username, channel.title))); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("RevokeButton", R.string.RevokeButton), new DialogInterface.OnClickListener() { @@ -1097,7 +800,7 @@ public void run() { }); adminedChannelCell.setChannel(res.chats.get(a), a == res.chats.size() - 1); adminedChannelCells.add(adminedChannelCell); - linearLayout.addView(adminedChannelCell, linearLayout.getChildCount() - 1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 72)); + adminnedChannelsLayout.addView(adminedChannelCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 72)); } updatePrivatePublic(); } @@ -1125,36 +828,42 @@ private boolean checkUserName(final String name) { if (name != null) { if (name.startsWith("_") || name.endsWith("_")) { checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } for (int a = 0; a < name.length(); a++) { char ch = name.charAt(a); if (a == 0 && ch >= '0' && ch <= '9') { checkTextView.setText(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } } } if (name == null || name.length() < 5) { checkTextView.setText(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } if (name.length() > 32) { checkTextView.setText(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } checkTextView.setText(LocaleController.getString("LinkChecking", R.string.LinkChecking)); - checkTextView.setTextColor(0xff6d6d72); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGrayText8); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); lastCheckName = name; checkRunnable = new Runnable() { @Override @@ -1172,7 +881,8 @@ public void run() { if (lastCheckName != null && lastCheckName.equals(name)) { if (error == null && response instanceof TLRPC.TL_boolTrue) { checkTextView.setText(LocaleController.formatString("LinkAvailable", R.string.LinkAvailable, name)); - checkTextView.setTextColor(0xff26972c); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); lastNameAvailable = true; } else { if (error != null && error.text.equals("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { @@ -1181,7 +891,8 @@ public void run() { } else { checkTextView.setText(LocaleController.getString("LinkInUse", R.string.LinkInUse)); } - checkTextView.setTextColor(0xffcf3030); + checkTextView.setTag(Theme.key_windowBackgroundWhiteRedText4); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); lastNameAvailable = false; } } @@ -1219,59 +930,88 @@ private void showErrorAlert(String error) { showDialog(builder.create()); } - private void updateVisibleRows(int mask) { - if (listView == null) { - return; - } - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View child = listView.getChildAt(a); - if (child instanceof UserCell) { - ((UserCell) child).update(mask); + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + if (adminnedChannelsLayout != null) { + int count = adminnedChannelsLayout.getChildCount(); + for (int a = 0; a < count; a++) { + View child = adminnedChannelsLayout.getChildAt(a); + if (child instanceof AdminedChannelCell) { + ((AdminedChannelCell) child).update(); + } + } + } + if (avatarImage != null) { + avatarDrawable.setInfo(5, nameTextView.length() > 0 ? nameTextView.getText().toString() : null, null, false); + avatarImage.invalidate(); + } } - } - } + }; - private ChipSpan createAndPutChipForUser(TLRPC.User user) { - try { - LayoutInflater lf = (LayoutInflater) ApplicationLoader.applicationContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - View textView = lf.inflate(R.layout.group_create_bubble, null); - TextView text = (TextView)textView.findViewById(R.id.bubble_text_view); - String name = UserObject.getUserName(user); - if (name.length() == 0 && user.phone != null && user.phone.length() != 0) { - name = PhoneFormat.getInstance().format("+" + user.phone); - } - text.setText(name + ", "); - - int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(spec, spec); - textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight()); - Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(b); - canvas.translate(-textView.getScrollX(), -textView.getScrollY()); - textView.draw(canvas); - textView.setDrawingCacheEnabled(true); - Bitmap cacheBmp = textView.getDrawingCache(); - Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true); - textView.destroyDrawingCache(); - - final BitmapDrawable bmpDrawable = new BitmapDrawable(b); - bmpDrawable.setBounds(0, 0, b.getWidth(), b.getHeight()); - - SpannableStringBuilder ssb = new SpannableStringBuilder(""); - ChipSpan span = new ChipSpan(bmpDrawable, ImageSpan.ALIGN_BASELINE); - allSpans.add(span); - selectedContacts.put(user.id, span); - for (ImageSpan sp : allSpans) { - ssb.append("<<"); - ssb.setSpan(sp, ssb.length() - 2, ssb.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - nameTextView.setText(ssb); - nameTextView.setSelection(ssb.length()); - return span; - } catch (Exception e) { - FileLog.e("tmessages", e); - } - return null; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(helpTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8), + + new ThemeDescription(linearLayout2, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(linkContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(sectionCell, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(headerCell, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGreenText), + + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText4), + + new ThemeDescription(adminedInfoCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(privateContainer, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(privateContainer, 0, new Class[]{TextBlockCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(loadingAdminedCell, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"deleteButton"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java index 47d402c4aa5..04866b6848b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditActivity.java @@ -3,16 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Vibrator; import android.text.Editable; @@ -42,7 +41,10 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; @@ -62,9 +64,22 @@ public class ChannelEditActivity extends BaseFragment implements AvatarUpdater.A private BackupImageView avatarImage; private AvatarDrawable avatarDrawable; private AvatarUpdater avatarUpdater; - private ProgressDialog progressDialog; + private AlertDialog progressDialog; private TextSettingsCell typeCell; private TextSettingsCell adminCell; + private LinearLayout linearLayout2; + private LinearLayout linearLayout3; + private View lineView; + private View lineView2; + private FrameLayout container1; + private FrameLayout container2; + private FrameLayout container3; + private ShadowSectionCell sectionCell; + private ShadowSectionCell sectionCell2; + private TextCheckCell textCheckCell; + private TextInfoPrivacyCell infoCell; + private TextSettingsCell textCell; + private TextInfoPrivacyCell infoCell2; private TLRPC.FileLocation avatar; private TLRPC.Chat currentChat; @@ -101,7 +116,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentChat != null) { MessagesController.getInstance().putChat(currentChat, true); @@ -113,7 +128,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (info == null) { return false; @@ -171,7 +186,7 @@ public void onItemClick(int id) { if (avatarUpdater.uploadingAvatar != null) { createAfterUpload = true; - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -184,7 +199,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -217,7 +232,7 @@ public void onClick(DialogInterface dialog, int which) { LinearLayout linearLayout; fragmentView = new ScrollView(context); - fragmentView.setBackgroundColor(0xfff0f0f0); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); ScrollView scrollView = (ScrollView) fragmentView; scrollView.setFillViewport(true); linearLayout = new LinearLayout(context); @@ -227,9 +242,9 @@ public void onClick(DialogInterface dialog, int which) { actionBar.setTitle(LocaleController.getString("ChannelEdit", R.string.ChannelEdit)); - LinearLayout linearLayout2 = new LinearLayout(context); + linearLayout2 = new LinearLayout(context); linearLayout2.setOrientation(LinearLayout.VERTICAL); - linearLayout2.setBackgroundColor(0xffffffff); + linearLayout2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); FrameLayout frameLayout = new FrameLayout(context); @@ -283,7 +298,8 @@ public void onClick(DialogInterface dialogInterface, int i) { nameTextView.setMaxLines(4); nameTextView.setGravity(Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setHintTextColor(0xff979797); + nameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + nameTextView.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); nameTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); nameTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); nameTextView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); @@ -291,7 +307,7 @@ public void onClick(DialogInterface dialogInterface, int i) { inputFilters[0] = new InputFilter.LengthFilter(100); nameTextView.setFilters(inputFilters); AndroidUtilities.clearCursorDrawable(nameTextView); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -311,19 +327,19 @@ public void afterTextChanged(Editable s) { } }); - View lineView = new View(context); - lineView.setBackgroundColor(0xffcfcfcf); + lineView = new View(context); + lineView.setBackgroundColor(Theme.getColor(Theme.key_divider)); linearLayout.addView(lineView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - linearLayout2 = new LinearLayout(context); - linearLayout2.setOrientation(LinearLayout.VERTICAL); - linearLayout2.setBackgroundColor(0xffffffff); - linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + linearLayout3 = new LinearLayout(context); + linearLayout3.setOrientation(LinearLayout.VERTICAL); + linearLayout3.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(linearLayout3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); descriptionTextView = new EditText(context); descriptionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - descriptionTextView.setHintTextColor(0xff979797); - descriptionTextView.setTextColor(0xff212121); + descriptionTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + descriptionTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); descriptionTextView.setPadding(0, 0, 0, AndroidUtilities.dp(6)); descriptionTextView.setBackgroundDrawable(null); descriptionTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); @@ -334,7 +350,7 @@ public void afterTextChanged(Editable s) { descriptionTextView.setFilters(inputFilters); descriptionTextView.setHint(LocaleController.getString("DescriptionOptionalPlaceholder", R.string.DescriptionOptionalPlaceholder)); AndroidUtilities.clearCursorDrawable(descriptionTextView); - linearLayout2.addView(descriptionTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 17, 12, 17, 6)); + linearLayout3.addView(descriptionTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 17, 12, 17, 6)); descriptionTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -362,83 +378,81 @@ public void afterTextChanged(Editable editable) { } }); - ShadowSectionCell sectionCell = new ShadowSectionCell(context); + sectionCell = new ShadowSectionCell(context); sectionCell.setSize(20); linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - if (currentChat.megagroup || !currentChat.megagroup) { - frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - typeCell = new TextSettingsCell(context); - updateTypeCell(); - typeCell.setBackgroundResource(R.drawable.list_selector); - frameLayout.addView(typeCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - lineView = new View(context); - lineView.setBackgroundColor(0xffcfcfcf); - linearLayout.addView(lineView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); - - frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - if (!currentChat.megagroup) { - TextCheckCell textCheckCell = new TextCheckCell(context); - textCheckCell.setBackgroundResource(R.drawable.list_selector); - textCheckCell.setTextAndCheck(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), signMessages, false); - frameLayout.addView(textCheckCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - textCheckCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - signMessages = !signMessages; - ((TextCheckCell) v).setChecked(signMessages); - } - }); - - TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context); - infoCell.setBackgroundResource(R.drawable.greydivider); - infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - } else { - adminCell = new TextSettingsCell(context); - updateAdminCell(); - adminCell.setBackgroundResource(R.drawable.list_selector); - frameLayout.addView(adminCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - adminCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Bundle args = new Bundle(); - args.putInt("chat_id", chatId); - args.putInt("type", 1); - presentFragment(new ChannelUsersActivity(args)); - } - }); + container1 = new FrameLayout(context); + container1.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(container1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + typeCell = new TextSettingsCell(context); + updateTypeCell(); + typeCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + container1.addView(typeCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + lineView2 = new View(context); + lineView2.setBackgroundColor(Theme.getColor(Theme.key_divider)); + linearLayout.addView(lineView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1)); + + container2 = new FrameLayout(context); + container2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(container2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + if (!currentChat.megagroup) { + textCheckCell = new TextCheckCell(context); + textCheckCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + textCheckCell.setTextAndCheck(LocaleController.getString("ChannelSignMessages", R.string.ChannelSignMessages), signMessages, false); + container2.addView(textCheckCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + textCheckCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + signMessages = !signMessages; + ((TextCheckCell) v).setChecked(signMessages); + } + }); - sectionCell = new ShadowSectionCell(context); - sectionCell.setSize(20); - linearLayout.addView(sectionCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - if (!currentChat.creator) { - sectionCell.setBackgroundResource(R.drawable.greydivider_bottom); + infoCell = new TextInfoPrivacyCell(context); + infoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + infoCell.setText(LocaleController.getString("ChannelSignMessagesInfo", R.string.ChannelSignMessagesInfo)); + linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else { + adminCell = new TextSettingsCell(context); + updateAdminCell(); + adminCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + container2.addView(adminCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Bundle args = new Bundle(); + args.putInt("chat_id", chatId); + args.putInt("type", 1); + presentFragment(new ChannelUsersActivity(args)); } + }); + + sectionCell2 = new ShadowSectionCell(context); + sectionCell2.setSize(20); + linearLayout.addView(sectionCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + if (!currentChat.creator) { + sectionCell2.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } } if (currentChat.creator) { - frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + container3 = new FrameLayout(context); + container3.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(container3, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - TextSettingsCell textCell = new TextSettingsCell(context); - textCell.setTextColor(0xffed3d39); - textCell.setBackgroundResource(R.drawable.list_selector); + textCell = new TextSettingsCell(context); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText5)); + textCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (currentChat.megagroup) { textCell.setText(LocaleController.getString("DeleteMega", R.string.DeleteMega), false); } else { textCell.setText(LocaleController.getString("ChannelDelete", R.string.ChannelDelete), false); } - frameLayout.addView(textCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + container3.addView(textCell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); textCell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -467,14 +481,14 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - TextInfoPrivacyCell infoCell = new TextInfoPrivacyCell(context); - infoCell.setBackgroundResource(R.drawable.greydivider_bottom); + infoCell2 = new TextInfoPrivacyCell(context); + infoCell2.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); if (currentChat.megagroup) { - infoCell.setText(LocaleController.getString("MegaDeleteInfo", R.string.MegaDeleteInfo)); + infoCell2.setText(LocaleController.getString("MegaDeleteInfo", R.string.MegaDeleteInfo)); } else { - infoCell.setText(LocaleController.getString("ChannelDeleteInfo", R.string.ChannelDeleteInfo)); + infoCell2.setText(LocaleController.getString("ChannelDeleteInfo", R.string.ChannelDeleteInfo)); } - linearLayout.addView(infoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + linearLayout.addView(infoCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } nameTextView.setText(currentChat.title); @@ -527,7 +541,7 @@ public void run() { progressDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } doneButton.performClick(); } @@ -583,12 +597,16 @@ public void onClick(View v) { presentFragment(fragment); } }); - typeCell.setTextColor(0xff212121); - typeCell.setTextValueColor(0xff2f8cc9); + typeCell.getTextView().setTag(Theme.key_windowBackgroundWhiteBlackText); + typeCell.getValueTextView().setTag(Theme.key_windowBackgroundWhiteValueText); + typeCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + typeCell.setTextValueColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); } else { typeCell.setOnClickListener(null); - typeCell.setTextColor(0xffa8a8a8); - typeCell.setTextValueColor(0xffa8a8a8); + typeCell.getTextView().setTag(Theme.key_windowBackgroundWhiteGrayText); + typeCell.getValueTextView().setTag(Theme.key_windowBackgroundWhiteGrayText); + typeCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); + typeCell.setTextValueColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText)); } } @@ -602,4 +620,74 @@ private void updateAdminCell() { adminCell.setText(LocaleController.getString("ChannelAdministrators", R.string.ChannelAdministrators), false); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + if (avatarImage != null) { + avatarDrawable.setInfo(5, nameTextView.length() > 0 ? nameTextView.getText().toString() : null, null, false); + avatarImage.invalidate(); + } + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(descriptionTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + + new ThemeDescription(linearLayout2, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(linearLayout3, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(container1, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(container2, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(container3, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + + new ThemeDescription(lineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_divider), + new ThemeDescription(lineView2, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_divider), + + new ThemeDescription(sectionCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(typeCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(typeCell, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(typeCell, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + new ThemeDescription(typeCell, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(typeCell, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + + new ThemeDescription(textCheckCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(textCheckCell, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(infoCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(infoCell, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(adminCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(adminCell, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(adminCell, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(sectionCell2, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(textCell, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(textCell, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText5), + + new ThemeDescription(infoCell2, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(infoCell2, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java index b3fcaa05d62..2d5d221e90a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelEditTypeActivity.java @@ -3,14 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Vibrator; import android.text.Editable; @@ -41,7 +41,10 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.AdminedChannelCell; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.LoadingCell; @@ -57,6 +60,8 @@ public class ChannelEditTypeActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private LinearLayout linearLayout; + private LinearLayout linearLayout2; + private LinearLayout adminnedChannelsLayout; private LinearLayout linkContainer; private LinearLayout publicContainer; private TextBlockCell privateContainer; @@ -64,6 +69,7 @@ public class ChannelEditTypeActivity extends BaseFragment implements Notificatio private RadioButtonCell radioButtonCell2; private ShadowSectionCell sectionCell; private TextInfoPrivacyCell typeInfoCell; + private EditText editText; private TextView checkTextView; private HeaderCell headerCell; private EditText nameTextView; @@ -109,7 +115,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentChat != null) { MessagesController.getInstance().putChat(currentChat, true); @@ -195,7 +201,7 @@ public void onItemClick(int id) { menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); fragmentView = new ScrollView(context); - fragmentView.setBackgroundColor(0xfff0f0f0); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); ScrollView scrollView = (ScrollView) fragmentView; scrollView.setFillViewport(true); linearLayout = new LinearLayout(context); @@ -209,17 +215,17 @@ public void onItemClick(int id) { actionBar.setTitle(LocaleController.getString("ChannelType", R.string.ChannelType)); } - LinearLayout linearLayout2 = new LinearLayout(context); + linearLayout2 = new LinearLayout(context); linearLayout2.setOrientation(LinearLayout.VERTICAL); - linearLayout2.setBackgroundColor(0xffffffff); + linearLayout2.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell1 = new RadioButtonCell(context); - radioButtonCell1.setBackgroundResource(R.drawable.list_selector); + radioButtonCell1.setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (currentChat.megagroup) { - radioButtonCell1.setTextAndValue(LocaleController.getString("MegaPublic", R.string.MegaPublic), LocaleController.getString("MegaPublicInfo", R.string.MegaPublicInfo), !isPrivate, false); + radioButtonCell1.setTextAndValue(LocaleController.getString("MegaPublic", R.string.MegaPublic), LocaleController.getString("MegaPublicInfo", R.string.MegaPublicInfo), !isPrivate); } else { - radioButtonCell1.setTextAndValue(LocaleController.getString("ChannelPublic", R.string.ChannelPublic), LocaleController.getString("ChannelPublicInfo", R.string.ChannelPublicInfo), !isPrivate, false); + radioButtonCell1.setTextAndValue(LocaleController.getString("ChannelPublic", R.string.ChannelPublic), LocaleController.getString("ChannelPublicInfo", R.string.ChannelPublicInfo), !isPrivate); } linearLayout2.addView(radioButtonCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell1.setOnClickListener(new View.OnClickListener() { @@ -234,11 +240,11 @@ public void onClick(View v) { }); radioButtonCell2 = new RadioButtonCell(context); - radioButtonCell2.setBackgroundResource(R.drawable.list_selector); + radioButtonCell2.setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (currentChat.megagroup) { - radioButtonCell2.setTextAndValue(LocaleController.getString("MegaPrivate", R.string.MegaPrivate), LocaleController.getString("MegaPrivateInfo", R.string.MegaPrivateInfo), isPrivate, false); + radioButtonCell2.setTextAndValue(LocaleController.getString("MegaPrivate", R.string.MegaPrivate), LocaleController.getString("MegaPrivateInfo", R.string.MegaPrivateInfo), isPrivate); } else { - radioButtonCell2.setTextAndValue(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate), LocaleController.getString("ChannelPrivateInfo", R.string.ChannelPrivateInfo), isPrivate, false); + radioButtonCell2.setTextAndValue(LocaleController.getString("ChannelPrivate", R.string.ChannelPrivate), LocaleController.getString("ChannelPrivateInfo", R.string.ChannelPrivateInfo), isPrivate); } linearLayout2.addView(radioButtonCell2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); radioButtonCell2.setOnClickListener(new View.OnClickListener() { @@ -257,7 +263,7 @@ public void onClick(View v) { linkContainer = new LinearLayout(context); linkContainer.setOrientation(LinearLayout.VERTICAL); - linkContainer.setBackgroundColor(0xffffffff); + linkContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); linearLayout.addView(linkContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); headerCell = new HeaderCell(context); @@ -267,11 +273,11 @@ public void onClick(View v) { publicContainer.setOrientation(LinearLayout.HORIZONTAL); linkContainer.addView(publicContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 17, 7, 17, 0)); - EditText editText = new EditText(context); - editText.setText("telegram.me/"); + editText = new EditText(context); + editText.setText(MessagesController.getInstance().linkPrefix + "/"); editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - editText.setHintTextColor(0xff979797); - editText.setTextColor(0xff212121); + editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); editText.setMaxLines(1); editText.setLines(1); editText.setEnabled(false); @@ -287,8 +293,8 @@ public void onClick(View v) { if (!isPrivate) { nameTextView.setText(currentChat.username); } - nameTextView.setHintTextColor(0xff979797); - nameTextView.setTextColor(0xff212121); + nameTextView.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setMaxLines(1); nameTextView.setLines(1); nameTextView.setBackgroundDrawable(null); @@ -317,7 +323,7 @@ public void afterTextChanged(Editable editable) { }); privateContainer = new TextBlockCell(context); - privateContainer.setBackgroundResource(R.drawable.list_selector); + privateContainer.setBackgroundDrawable(Theme.getSelectorDrawable(false)); linkContainer.addView(privateContainer); privateContainer.setOnClickListener(new View.OnClickListener() { @Override @@ -331,7 +337,7 @@ public void onClick(View v) { clipboard.setPrimaryClip(clip); Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -343,14 +349,19 @@ public void onClick(View v) { linkContainer.addView(checkTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 17, 3, 17, 7)); typeInfoCell = new TextInfoPrivacyCell(context); - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout.addView(typeInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); loadingAdminedCell = new LoadingCell(context); linearLayout.addView(loadingAdminedCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminnedChannelsLayout = new LinearLayout(context); + adminnedChannelsLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + adminnedChannelsLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.addView(adminnedChannelsLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + adminedInfoCell = new TextInfoPrivacyCell(context); - adminedInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + adminedInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); linearLayout.addView(adminedInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); updatePrivatePublic(); @@ -412,9 +423,9 @@ public void onClick(View view) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (channel.megagroup) { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, "telegram.me/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlert", R.string.RevokeLinkAlert, MessagesController.getInstance().linkPrefix + "/" + channel.username, channel.title))); } else { - builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, "telegram.me/" + channel.username, channel.title))); + builder.setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("RevokeLinkAlertChannel", R.string.RevokeLinkAlertChannel, MessagesController.getInstance().linkPrefix + "/" + channel.username, channel.title))); } builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); builder.setPositiveButton(LocaleController.getString("RevokeButton", R.string.RevokeButton), new DialogInterface.OnClickListener() { @@ -463,32 +474,28 @@ private void updatePrivatePublic() { } if (!isPrivate && !canCreatePublic) { typeInfoCell.setText(LocaleController.getString("ChangePublicLimitReached", R.string.ChangePublicLimitReached)); - typeInfoCell.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); linkContainer.setVisibility(View.GONE); sectionCell.setVisibility(View.GONE); if (loadingAdminedChannels) { loadingAdminedCell.setVisibility(View.VISIBLE); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.GONE); - } - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); + adminnedChannelsLayout.setVisibility(View.GONE); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); adminedInfoCell.setVisibility(View.GONE); } else { - typeInfoCell.setBackgroundResource(R.drawable.greydivider); + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); loadingAdminedCell.setVisibility(View.GONE); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.VISIBLE); - } + adminnedChannelsLayout.setVisibility(View.VISIBLE); adminedInfoCell.setVisibility(View.VISIBLE); } } else { - typeInfoCell.setTextColor(0xff808080); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteGrayText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); sectionCell.setVisibility(View.VISIBLE); adminedInfoCell.setVisibility(View.GONE); - typeInfoCell.setBackgroundResource(R.drawable.greydivider_bottom); - for (int a = 0; a < adminedChannelCells.size(); a++) { - adminedChannelCells.get(a).setVisibility(View.GONE); - } + typeInfoCell.setBackgroundDrawable(Theme.getThemedDrawable(typeInfoCell.getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + adminnedChannelsLayout.setVisibility(View.GONE); linkContainer.setVisibility(View.VISIBLE); loadingAdminedCell.setVisibility(View.GONE); if (currentChat.megagroup) { @@ -528,7 +535,8 @@ private boolean checkUserName(final String name) { if (name != null) { if (name.startsWith("_") || name.endsWith("_")) { checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } for (int a = 0; a < name.length(); a++) { @@ -536,16 +544,19 @@ private boolean checkUserName(final String name) { if (a == 0 && ch >= '0' && ch <= '9') { if (currentChat.megagroup) { checkTextView.setText(LocaleController.getString("LinkInvalidStartNumberMega", R.string.LinkInvalidStartNumberMega)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } else { checkTextView.setText(LocaleController.getString("LinkInvalidStartNumber", R.string.LinkInvalidStartNumber)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } if (!(ch >= '0' && ch <= '9' || ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch == '_')) { checkTextView.setText(LocaleController.getString("LinkInvalid", R.string.LinkInvalid)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } } @@ -553,22 +564,26 @@ private boolean checkUserName(final String name) { if (name == null || name.length() < 5) { if (currentChat.megagroup) { checkTextView.setText(LocaleController.getString("LinkInvalidShortMega", R.string.LinkInvalidShortMega)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } else { checkTextView.setText(LocaleController.getString("LinkInvalidShort", R.string.LinkInvalidShort)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); } return false; } if (name.length() > 32) { checkTextView.setText(LocaleController.getString("LinkInvalidLong", R.string.LinkInvalidLong)); - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); return false; } checkTextView.setText(LocaleController.getString("LinkChecking", R.string.LinkChecking)); - checkTextView.setTextColor(0xff6d6d72); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGrayText8); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText8)); lastCheckName = name; checkRunnable = new Runnable() { @Override @@ -586,7 +601,8 @@ public void run() { if (lastCheckName != null && lastCheckName.equals(name)) { if (error == null && response instanceof TLRPC.TL_boolTrue) { checkTextView.setText(LocaleController.formatString("LinkAvailable", R.string.LinkAvailable, name)); - checkTextView.setTextColor(0xff26972c); + checkTextView.setTag(Theme.key_windowBackgroundWhiteGreenText); + checkTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText)); lastNameAvailable = true; } else { if (error != null && error.text.equals("CHANNELS_ADMIN_PUBLIC_TOO_MUCH")) { @@ -595,7 +611,8 @@ public void run() { } else { checkTextView.setText(LocaleController.getString("LinkInUse", R.string.LinkInUse)); } - checkTextView.setTextColor(0xffcf3030); + typeInfoCell.setTag(Theme.key_windowBackgroundWhiteRedText4); + typeInfoCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); lastNameAvailable = false; } } @@ -632,4 +649,77 @@ public void run() { } }); } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + if (adminnedChannelsLayout != null) { + int count = adminnedChannelsLayout.getChildCount(); + for (int a = 0; a < count; a++) { + View child = adminnedChannelsLayout.getChildAt(a); + if (child instanceof AdminedChannelCell) { + ((AdminedChannelCell) child).update(); + } + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + + new ThemeDescription(linearLayout2, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(linkContainer, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(sectionCell, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(headerCell, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText4), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText8), + new ThemeDescription(checkTextView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGreenText), + + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(typeInfoCell, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText4), + + new ThemeDescription(adminedInfoCell, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(privateContainer, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(privateContainer, 0, new Class[]{TextBlockCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(loadingAdminedCell, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell1, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioButtonCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(radioButtonCell2, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{RadioButtonCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + new ThemeDescription(adminnedChannelsLayout, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{AdminedChannelCell.class}, new String[]{"deleteButton"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java index 1ad5b77b88f..3b697fd8b55 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelIntroActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -24,6 +24,7 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; public class ChannelIntroActivity extends BaseFragment { @@ -34,9 +35,10 @@ public class ChannelIntroActivity extends BaseFragment { @Override public View createView(Context context) { - actionBar.setBackgroundColor(Theme.ACTION_BAR_CHANNEL_INTRO_COLOR); - actionBar.setBackButtonImage(R.drawable.pl_back); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_CHANNEL_INTRO_SELECTOR_COLOR); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2), false); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarWhiteSelector), false); actionBar.setCastShadows(false); if (!AndroidUtilities.isTablet()) { actionBar.showActionModeTop(); @@ -101,7 +103,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } } }; - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); ViewGroup viewGroup = (ViewGroup) fragmentView; viewGroup.setOnTouchListener(new View.OnTouchListener() { @Override @@ -116,21 +118,21 @@ public boolean onTouch(View v, MotionEvent event) { viewGroup.addView(imageView); whatIsChannelText = new TextView(context); - whatIsChannelText.setTextColor(0xff212121); + whatIsChannelText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); whatIsChannelText.setGravity(Gravity.CENTER_HORIZONTAL); whatIsChannelText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 24); whatIsChannelText.setText(LocaleController.getString("ChannelAlertTitle", R.string.ChannelAlertTitle)); viewGroup.addView(whatIsChannelText); descriptionText = new TextView(context); - descriptionText.setTextColor(0xff787878); + descriptionText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); descriptionText.setGravity(Gravity.CENTER_HORIZONTAL); descriptionText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); descriptionText.setText(LocaleController.getString("ChannelAlertText", R.string.ChannelAlertText)); viewGroup.addView(descriptionText); createChannelText = new TextView(context); - createChannelText.setTextColor(0xff4c8eca); + createChannelText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText5)); createChannelText.setGravity(Gravity.CENTER); createChannelText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); createChannelText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -147,4 +149,19 @@ public void onClick(View v) { return fragmentView; } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarWhiteSelector), + + new ThemeDescription(whatIsChannelText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(descriptionText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(createChannelText, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText5), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java index 958a1e7adde..32bf10aa7fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelUsersActivity.java @@ -3,20 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; @@ -27,14 +25,17 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.RadioCell; import org.telegram.ui.Cells.ShadowSectionCell; @@ -45,6 +46,7 @@ import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +56,7 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe private ListAdapter listViewAdapter; private EmptyTextProgressView emptyView; + private RecyclerListView listView; private ArrayList participants = new ArrayList<>(); private int chatId; @@ -61,6 +64,7 @@ public class ChannelUsersActivity extends BaseFragment implements NotificationCe private boolean loadingUsers; private boolean firstLoaded; private boolean isAdmin; + private boolean isModerator; private boolean isPublic; private boolean isMegagroup; private int participantsStartRow; @@ -74,6 +78,8 @@ public ChannelUsersActivity(Bundle args) { if (chat.creator) { isAdmin = true; isPublic = (chat.flags & TLRPC.CHAT_FLAG_IS_PUBLIC) != 0; + } else if (chat.editor) { + isModerator = true; } isMegagroup = chat.megagroup; } @@ -120,10 +126,8 @@ public void onItemClick(int id) { } }); - ActionBarMenu menu = actionBar.createMenu(); - fragmentView = new FrameLayout(context); - fragmentView.setBackgroundColor(0xfff0f0f0); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); FrameLayout frameLayout = (FrameLayout) fragmentView; emptyView = new EmptyTextProgressView(context); @@ -136,27 +140,24 @@ public void onItemClick(int id) { } frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - final ListView listView = new ListView(context); + listView = new RecyclerListView(context); listView.setEmptyView(emptyView); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setDrawSelectorOnTop(true); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(listViewAdapter = new ListAdapter(context)); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { if (type == 2) { if (isAdmin) { - if (i == 0) { + if (position == 0) { Bundle args = new Bundle(); args.putBoolean("onlyUsers", true); args.putBoolean("destroyAfterSelect", true); args.putBoolean("returnAsResult", true); args.putBoolean("needForwardCount", false); - args.putBoolean("allowUsernameSearch", false); args.putString("selectAlertString", LocaleController.getString("ChannelAddTo", R.string.ChannelAddTo)); ContactsActivity fragment = new ContactsActivity(args); fragment.setDelegate(new ContactsActivity.ContactsActivityDelegate() { @@ -166,23 +167,23 @@ public void didSelectContact(TLRPC.User user, String param) { } }); presentFragment(fragment); - } else if (!isPublic && i == 1) { + } else if (!isPublic && position == 1) { presentFragment(new GroupInviteActivity(chatId)); } } } else if (type == 1) { if (isAdmin) { - if (isMegagroup && (i == 1 || i == 2)) { + if (isMegagroup && (position == 1 || position == 2)) { TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId); if (chat == null) { return; } boolean changed = false; - if (i == 1 && !chat.democracy) { + if (position == 1 && !chat.democracy) { chat.democracy = true; changed = true; - } else if (i == 2 && chat.democracy) { + } else if (position == 2 && chat.democracy) { chat.democracy = false; changed = true; } @@ -199,13 +200,13 @@ public void didSelectContact(TLRPC.User user, String param) { } return; } - if (i == participantsStartRow + participants.size()) { + if (position == participantsStartRow + participants.size()) { Bundle args = new Bundle(); args.putBoolean("onlyUsers", true); args.putBoolean("destroyAfterSelect", true); args.putBoolean("returnAsResult", true); args.putBoolean("needForwardCount", false); - args.putBoolean("allowUsernameSearch", true); + args.putBoolean("addingToChannel", !isMegagroup); /*if (isMegagroup) { args.putBoolean("allowBots", false); }*/ @@ -223,8 +224,8 @@ public void didSelectContact(TLRPC.User user, String param) { } } TLRPC.ChannelParticipant participant = null; - if (i >= participantsStartRow && i < participants.size() + participantsStartRow) { - participant = participants.get(i - participantsStartRow); + if (position >= participantsStartRow && position < participants.size() + participantsStartRow) { + participant = participants.get(position - participantsStartRow); } if (participant != null) { Bundle args = new Bundle(); @@ -234,18 +235,21 @@ public void didSelectContact(TLRPC.User user, String param) { } }); - if (isAdmin || isMegagroup && type == 0) { - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + if (isAdmin || isModerator && type == 2 || isMegagroup && type == 0) { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { + public boolean onItemClick(View view, int position) { if (getParentActivity() == null) { return false; } TLRPC.ChannelParticipant participant = null; - if (i >= participantsStartRow && i < participants.size() + participantsStartRow) { - participant = participants.get(i - participantsStartRow); + if (position >= participantsStartRow && position < participants.size() + participantsStartRow) { + participant = participants.get(position - participantsStartRow); } if (participant != null) { + if (participant.user_id == UserConfig.getClientUserId()) { + return false; + } final TLRPC.ChannelParticipant finalParticipant = participant; AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); CharSequence[] items = null; @@ -314,7 +318,7 @@ public void setUserChannelRole(TLRPC.User user, TLRPC.ChannelParticipantRole rol if (user == null || role == null) { return; } - TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); + final TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); req.channel = MessagesController.getInputChannel(chatId); req.user_id = MessagesController.getInputUser(user); req.role = role; @@ -333,7 +337,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - AlertsCreator.showAddUserAlert(error.text, ChannelUsersActivity.this, !isMegagroup); + AlertsCreator.processError(error, ChannelUsersActivity.this, req, !isMegagroup); } }); } @@ -459,7 +463,7 @@ public int compare(TLRPC.ChannelParticipant lhs, TLRPC.ChannelParticipant rhs) { }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } loadingUsers = false; @@ -485,7 +489,8 @@ public void onResume() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -493,42 +498,38 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int postion = holder.getAdapterPosition(); if (type == 2) { if (isAdmin) { if (!isPublic) { - if (i == 0 || i == 1) { + if (postion == 0 || postion == 1) { return true; - } else if (i == 2) { + } else if (postion == 2) { return false; } } else { - if (i == 0) { + if (postion == 0) { return true; - } else if (i == 1) { + } else if (postion == 1) { return false; } } } } else if (type == 1) { - if (i == participantsStartRow + participants.size()) { + if (postion == participantsStartRow + participants.size()) { return isAdmin; - } else if (i == participantsStartRow + participants.size() + 1) { + } else if (postion == participantsStartRow + participants.size() + 1) { return false; - } else if (isMegagroup && isAdmin && i < 4) { - return i == 1 || i == 2; + } else if (isMegagroup && isAdmin && postion < 4) { + return postion == 1 || postion == 2; } } - return i != participants.size() + participantsStartRow && participants.get(i - participantsStartRow).user_id != UserConfig.getClientUserId(); + return postion != participants.size() + participantsStartRow && participants.get(postion - participantsStartRow).user_id != UserConfig.getClientUserId(); } @Override - public int getCount() { + public int getItemCount() { if (participants.isEmpty() && type == 0 || loadingUsers && !firstLoaded) { return 0; } else if (type == 1) { @@ -538,128 +539,126 @@ public int getCount() { } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new UserCell(mContext, 1, 0, false); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + break; + case 2: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new ShadowSectionCell(mContext); + break; + case 4: + view = new TextCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 5: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 6: + default: + view = new RadioCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int viewType = getItemViewType(i); - if (viewType == 0) { - if (view == null) { - view = new UserCell(mContext, 1, 0, false); - view.setBackgroundColor(0xffffffff); - } - UserCell userCell = (UserCell) view; - TLRPC.ChannelParticipant participant = participants.get(i - participantsStartRow); - TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); - if (user != null) { + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + UserCell userCell = (UserCell) holder.itemView; + TLRPC.ChannelParticipant participant = participants.get(position - participantsStartRow); + TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); + if (user != null) { + if (type == 0) { + userCell.setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); + } else if (type == 1) { + String role = null; + if (participant instanceof TLRPC.TL_channelParticipantCreator || participant instanceof TLRPC.TL_channelParticipantSelf) { + role = LocaleController.getString("ChannelCreator", R.string.ChannelCreator); + } else if (participant instanceof TLRPC.TL_channelParticipantModerator) { + role = LocaleController.getString("ChannelModerator", R.string.ChannelModerator); + } else if (participant instanceof TLRPC.TL_channelParticipantEditor) { + role = LocaleController.getString("ChannelEditor", R.string.ChannelEditor); + } + userCell.setData(user, null, role, 0); + } else if (type == 2) { + userCell.setData(user, null, null, 0); + } + } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; if (type == 0) { - userCell.setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); + privacyCell.setText(String.format("%1$s\n\n%2$s", LocaleController.getString("NoBlockedGroup", R.string.NoBlockedGroup), LocaleController.getString("UnblockText", R.string.UnblockText))); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } else if (type == 1) { - String role = null; - if (participant instanceof TLRPC.TL_channelParticipantCreator || participant instanceof TLRPC.TL_channelParticipantSelf) { - role = LocaleController.getString("ChannelCreator", R.string.ChannelCreator); - } else if (participant instanceof TLRPC.TL_channelParticipantModerator) { - role = LocaleController.getString("ChannelModerator", R.string.ChannelModerator); - } else if (participant instanceof TLRPC.TL_channelParticipantEditor) { - role = LocaleController.getString("ChannelEditor", R.string.ChannelEditor); + if (isAdmin) { + if (isMegagroup) { + privacyCell.setText(LocaleController.getString("MegaAdminsInfo", R.string.MegaAdminsInfo)); + } else { + privacyCell.setText(LocaleController.getString("ChannelAdminsInfo", R.string.ChannelAdminsInfo)); + } + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } - userCell.setData(user, null, role, 0); } else if (type == 2) { - userCell.setData(user, null, null, 0); - } - } - } else if (viewType == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (type == 0) { - ((TextInfoPrivacyCell) view).setText(String.format("%1$s\n\n%2$s", LocaleController.getString("NoBlockedGroup", R.string.NoBlockedGroup), LocaleController.getString("UnblockText", R.string.UnblockText))); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (type == 1) { - if (isAdmin) { - if (isMegagroup) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("MegaAdminsInfo", R.string.MegaAdminsInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); + if ((!isPublic && position == 2 || position == 1) && isAdmin) { + if (isMegagroup) { + privacyCell.setText(""); + } else { + privacyCell.setText(LocaleController.getString("ChannelMembersInfo", R.string.ChannelMembersInfo)); + } + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("ChannelAdminsInfo", R.string.ChannelAdminsInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } - } else { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); } - } else if (type == 2) { - if ((!isPublic && i == 2 || i == 1) && isAdmin) { - if (isMegagroup) { - ((TextInfoPrivacyCell) view).setText(""); - } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("ChannelMembersInfo", R.string.ChannelMembersInfo)); + break; + case 2: + TextSettingsCell actionCell = (TextSettingsCell) holder.itemView; + if (type == 2) { + if (position == 0) { + actionCell.setText(LocaleController.getString("AddMember", R.string.AddMember), true); + } else if (position == 1) { + actionCell.setText(LocaleController.getString("ChannelInviteViaLink", R.string.ChannelInviteViaLink), false); } - view.setBackgroundResource(R.drawable.greydivider); - } else { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); + } else if (type == 1) { + actionCell.setTextAndIcon(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin), R.drawable.managers, false); } - } - } else if (viewType == 2) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell actionCell = (TextSettingsCell) view; - if (type == 2) { - if (i == 0) { - actionCell.setText(LocaleController.getString("AddMember", R.string.AddMember), true); - } else if (i == 1) { - actionCell.setText(LocaleController.getString("ChannelInviteViaLink", R.string.ChannelInviteViaLink), false); + break; + case 4: + ((TextCell) holder.itemView).setTextAndIcon(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin), R.drawable.managers); + break; + case 5: + ((HeaderCell) holder.itemView).setText(LocaleController.getString("WhoCanAddMembers", R.string.WhoCanAddMembers)); + break; + case 6: + RadioCell radioCell = (RadioCell) holder.itemView; + TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId); + if (position == 1) { + radioCell.setTag(0); + radioCell.setText(LocaleController.getString("WhoCanAddMembersAllMembers", R.string.WhoCanAddMembersAllMembers), chat != null && chat.democracy, true); + } else if (position == 2) { + radioCell.setTag(1); + radioCell.setText(LocaleController.getString("WhoCanAddMembersAdmins", R.string.WhoCanAddMembersAdmins), chat != null && !chat.democracy, false); } - } else if (type == 1) { - actionCell.setTextAndIcon(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin), R.drawable.managers, false); - } - } else if (viewType == 3) { - if (view == null) { - view = new ShadowSectionCell(mContext); - } - } else if (viewType == 4) { - if (view == null) { - view = new TextCell(mContext); - view.setBackgroundColor(0xffffffff); - } - ((TextCell) view).setTextAndIcon(LocaleController.getString("ChannelAddAdmin", R.string.ChannelAddAdmin), R.drawable.managers); - } else if (viewType == 5) { - if (view == null) { - view = new HeaderCell(mContext); - view.setBackgroundColor(0xffffffff); - } - ((HeaderCell) view).setText(LocaleController.getString("WhoCanAddMembers", R.string.WhoCanAddMembers)); - } else if (viewType == 6) { - if (view == null) { - view = new RadioCell(mContext); - view.setBackgroundColor(0xffffffff); - } - RadioCell radioCell = (RadioCell) view; - TLRPC.Chat chat = MessagesController.getInstance().getChat(chatId); - if (i == 1) { - radioCell.setTag(0); - radioCell.setText(LocaleController.getString("WhoCanAddMembersAllMembers", R.string.WhoCanAddMembersAllMembers), chat != null && chat.democracy, true); - } else if (i == 2) { - radioCell.setTag(1); - radioCell.setText(LocaleController.getString("WhoCanAddMembersAdmins", R.string.WhoCanAddMembersAdmins), chat != null && !chat.democracy, false); - } + break; } - return view; } @Override @@ -703,15 +702,66 @@ public int getItemViewType(int i) { } return 0; } + } - @Override - public int getViewTypeCount() { - return 7; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } + } + } + }; - @Override - public boolean isEmpty() { - return getCount() == 0 || participants.isEmpty() && loadingUsers; - } + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{UserCell.class, TextSettingsCell.class, TextCell.class, RadioCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueImageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 3281fc20a35..0c0a273f6da 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -3,17 +3,19 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.Manifest; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; +import android.app.DatePickerDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; @@ -24,6 +26,9 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.media.ExifInterface; @@ -38,29 +43,32 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextUtils; +import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TypedValue; import android.view.Gravity; +import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.DatePicker; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.BuildConfig; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; import org.telegram.messenger.LocaleController; @@ -96,12 +104,14 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBarLayout; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Adapters.MentionsAdapter; import org.telegram.ui.Adapters.StickersAdapter; -import org.telegram.messenger.AnimatorListenerAdapterProxy; +import org.telegram.ui.Cells.BotSwitchCell; import org.telegram.ui.Cells.ChatActionCell; import org.telegram.ui.Cells.ChatLoadingCell; import org.telegram.ui.ActionBar.ActionBar; @@ -111,6 +121,8 @@ import org.telegram.ui.Cells.ChatUnreadCell; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.ContextLinkCell; +import org.telegram.ui.Cells.MentionCell; +import org.telegram.ui.Cells.StickerCell; import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; @@ -120,26 +132,33 @@ import org.telegram.ui.Components.ChatAttachAlert; import org.telegram.ui.Components.ChatAvatarContainer; import org.telegram.ui.Components.ChatBigEmptyView; -import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.EmbedBottomSheet; +import org.telegram.ui.Components.EmojiView; import org.telegram.ui.Components.ExtendedGridLayoutManager; -import org.telegram.ui.Components.PlayerView; +import org.telegram.ui.Components.FragmentContextView; +import org.telegram.ui.Components.InstantCameraView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.ShareAlert; import org.telegram.ui.Components.Size; import org.telegram.ui.Components.SizeNotifierFrameLayout; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.TypefaceSpan; import org.telegram.ui.Components.URLSpanBotCommand; +import org.telegram.ui.Components.URLSpanMono; import org.telegram.ui.Components.URLSpanNoUnderline; import org.telegram.ui.Components.URLSpanReplacement; import org.telegram.ui.Components.URLSpanUserMention; -import org.telegram.ui.Components.WebFrameLayout; +import org.telegram.ui.Components.voip.VoIPHelper; import java.io.File; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.concurrent.Semaphore; @@ -158,16 +177,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private Dialog closeChatDialog; private FrameLayout progressView; + private View progressView2; private FrameLayout bottomOverlay; protected ChatActivityEnterView chatActivityEnterView; private View timeItem2; - private ActionBarMenuItem menuItem; private ActionBarMenuItem attachItem; private ActionBarMenuItem headerItem; private ActionBarMenuItem searchItem; - private ActionBarMenuItem editDoneItem; - private ContextProgressView editDoneItemProgress; - private AnimatorSet editDoneItemAnimation; + private RadialProgressView progressBar; private TextView addContactItem; private RecyclerListView chatListView; private LinearLayoutManager chatLayoutManager; @@ -178,12 +195,14 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private ChatBigEmptyView bigEmptyView; private ArrayList actionModeViews = new ArrayList<>(); private ChatAvatarContainer avatarContainer; + private View actionBarOverlay; private TextView bottomOverlayText; private NumberTextView selectedMessagesCountTextView; private FrameLayout actionModeTitleContainer; private SimpleTextView actionModeTextView; private SimpleTextView actionModeSubTextView; private RecyclerListView stickersListView; + private ImageView stickersPanelArrow; private RecyclerListView.OnItemClickListener stickersOnItemClickListener; private RecyclerListView.OnItemClickListener mentionsOnItemClickListener; private StickersAdapter stickersAdapter; @@ -196,6 +215,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private SimpleTextView replyNameTextView; private SimpleTextView replyObjectTextView; private ImageView replyIconImageView; + private ImageView replyCloseImageView; private MentionsAdapter mentionsAdapter; private FrameLayout mentionContainer; private RecyclerListView mentionListView; @@ -208,13 +228,19 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TextView addToContactsButton; private TextView reportSpamButton; private FrameLayout reportSpamContainer; - private PlayerView playerView; + private ImageView closeReportSpam; + private FragmentContextView fragmentContextView; + private View replyLineView; + private TextView emptyView; + private ImageView pagedownButtonImage; private TextView gifHintTextView; private View emojiButtonRed; private FrameLayout pinnedMessageView; + private View pinnedLineView; private AnimatorSet pinnedMessageViewAnimator; private BackupImageView pinnedMessageImageView; private SimpleTextView pinnedMessageNameTextView; + private ImageView closePinned; private SimpleTextView pinnedMessageTextView; private FrameLayout alertView; private Runnable hideAlertViewRunnable; @@ -222,9 +248,23 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private TextView alertTextView; private AnimatorSet alertViewAnimator; private FrameLayout searchContainer; + private ImageView searchCalendarButton; private ImageView searchUpButton; private ImageView searchDownButton; private SimpleTextView searchCountText; + private ChatActionCell floatingDateView; + private InstantCameraView instantCameraView; + private boolean currentFloatingDateOnScreen; + private boolean currentFloatingTopIsNotMessage; + private AnimatorSet floatingDateAnimation; + private boolean scrollingFloatingDate; + + private ArrayList animatingMessageObjects = new ArrayList<>(); + + private int scrollToPositionOnRecreate = -1; + private int scrollToOffsetOnRecreate = 0; + + private int topViewWasVisible; private boolean mentionListViewIgnoreLayout; private int mentionListViewScrollOffsetY; @@ -242,6 +282,8 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private boolean waitingForReplyMessageLoad; + private boolean ignoreAttachOnPause; + private boolean allowStickersPanel; private boolean allowContextBotPanel; private boolean allowContextBotPanelSecond = true; @@ -298,9 +340,12 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private long mergeDialogId; private int startLoadFromMessageId; + private int startLoadFromMessageOffset = Integer.MAX_VALUE; private boolean needSelectFromMessageId; private int returnToMessageId; private int returnToLoadIndex; + private int createUnreadMessageAfterId; + private boolean loadingFromOldPosition; private boolean first = true; private int unread_to_load; @@ -331,6 +376,25 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not private float startX = 0; private float startY = 0; + private Runnable readRunnable = new Runnable() { + @Override + public void run() { + if (readWhenResume && !messages.isEmpty()) { + for (int a = 0; a < messages.size(); a++) { + MessageObject messageObject = messages.get(a); + if (!messageObject.isUnread() && !messageObject.isOut()) { + break; + } + if (!messageObject.isOut()) { + messageObject.setIsRead(); + } + } + readWhenResume = false; + MessagesController.getInstance().markDialogAsRead(dialog_id, messages.get(0).getId(), readWithMid, readWithDate, true, false); + } + } + }; + private ArrayList botContextResults; private PhotoViewer.PhotoViewerProvider botContextProvider = new PhotoViewer.PhotoViewerProvider() { @@ -399,7 +463,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (index < 0 || index >= botContextResults.size()) { return; } @@ -437,11 +501,11 @@ public boolean allowCaption() { private final static int share_contact = 17; private final static int mute = 18; private final static int reply = 19; - private final static int edit_done = 20; private final static int report = 21; private final static int bot_help = 30; private final static int bot_settings = 31; + private final static int call = 32; private final static int attach_photo = 0; private final static int attach_gallery = 1; @@ -506,7 +570,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentChat != null) { MessagesController.getInstance().putChat(currentChat, true); @@ -537,7 +601,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentUser != null) { MessagesController.getInstance().putUser(currentUser, true); @@ -564,7 +628,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentEncryptedChat != null) { MessagesController.getInstance().putEncryptedChat(currentEncryptedChat, true); @@ -585,7 +649,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentUser != null) { MessagesController.getInstance().putUser(currentUser, true); @@ -639,6 +703,8 @@ public void run() { NotificationCenter.getInstance().addObserver(this, NotificationCenter.didLoadedPinnedMessage); NotificationCenter.getInstance().addObserver(this, NotificationCenter.peerSettingsDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.newDraftReceived); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.userInfoDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetNewWallpapper); super.onFragmentCreate(); @@ -647,20 +713,32 @@ public void run() { } loading = true; - MessagesController.getInstance().loadPeerSettings(dialog_id, currentUser, currentChat); + MessagesController.getInstance().loadPeerSettings(currentUser, currentChat); MessagesController.getInstance().setLastCreatedDialogId(dialog_id, true); - if (startLoadFromMessageId != 0) { + + if (startLoadFromMessageId == 0) { + SharedPreferences sharedPreferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int messageId = sharedPreferences.getInt("diditem" + dialog_id, 0); + if (messageId != 0) { + loadingFromOldPosition = true; + startLoadFromMessageOffset = sharedPreferences.getInt("diditemo" + dialog_id, 0); + startLoadFromMessageId = messageId; + } + } else { needSelectFromMessageId = true; + } + + if (startLoadFromMessageId != 0) { waitingForLoad.add(lastLoadIndex); if (migrated_to != 0) { mergeDialogId = migrated_to; - MessagesController.getInstance().loadMessages(mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(mergeDialogId, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { - MessagesController.getInstance().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, loadingFromOldPosition ? 50 : (AndroidUtilities.isTablet() ? 30 : 20), startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } else { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } if (currentChat != null) { @@ -673,7 +751,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -714,6 +792,9 @@ public void onFragmentDestroy() { if (mentionsAdapter != null) { mentionsAdapter.onDestroy(); } + if (chatAttachAlert != null) { + chatAttachAlert.dismissInternal(); + } MessagesController.getInstance().setLastCreatedDialogId(dialog_id, false); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); @@ -721,7 +802,7 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceivedNewMessages); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesRead); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messagesDeleted); + NotificationCenter.getInstance().removeObserver (this, NotificationCenter.messagesDeleted); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageReceivedByServer); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageReceivedByAck); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.messageSendError); @@ -753,6 +834,8 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didLoadedPinnedMessage); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.peerSettingsDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.newDraftReceived); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.userInfoDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetNewWallpapper); if (AndroidUtilities.isTablet()) { NotificationCenter.getInstance().postNotificationName(NotificationCenter.openedChatChanged, dialog_id, true); @@ -760,11 +843,11 @@ public void onFragmentDestroy() { if (currentEncryptedChat != null) { MediaController.getInstance().stopMediaObserver(); try { - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23 && (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture)) { getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (currentUser != null) { @@ -802,13 +885,12 @@ public View createView(Context context) { cantDeleteMessagesCount = 0; hasOwnBackground = true; - if (chatAttachAlert != null){ + if (chatAttachAlert != null) { chatAttachAlert.onDestroy(); chatAttachAlert = null; } - Theme.loadRecources(context); - Theme.loadChatResources(context); + Theme.createChatResources(context, false); actionBar.setAddToContainer(false); actionBar.setBackButtonDrawable(new BackDrawable(false)); @@ -863,10 +945,6 @@ public void onItemClick(final int id) { actionBar.hideActionMode(); updatePinnedMessageView(true); updateVisibleRows(); - } else if (id == edit_done) { - if (chatActivityEnterView != null && (chatActivityEnterView.isEditingCaption() || chatActivityEnterView.hasText())) { - chatActivityEnterView.doneEditingMessage(); - } } else if (id == delete) { if (getParentActivity() == null) { return; @@ -875,7 +953,6 @@ public void onItemClick(final int id) { } else if (id == forward) { Bundle args = new Bundle(); args.putBoolean("onlySelect", true); - args.putInt("dialogsType", 1); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(ChatActivity.this); presentFragment(fragment); @@ -883,7 +960,7 @@ public void onItemClick(final int id) { if (getParentActivity() == null) { return; } - showDialog(AndroidUtilities.buildTTLAlert(getParentActivity(), currentEncryptedChat).create()); + showDialog(AlertsCreator.createTTLAlert(getParentActivity(), currentEncryptedChat).create()); } else if (id == clear_history || id == delete_chat) { if (getParentActivity() == null) { return; @@ -948,30 +1025,23 @@ public void onClick(DialogInterface dialogInterface, int i) { selectedMessagesCanCopyIds[a].clear(); } if (messageObject != null && (messageObject.messageOwner.id > 0 || messageObject.messageOwner.id < 0 && currentEncryptedChat != null)) { - showReplyPanel(true, messageObject, null, null, false, true); + showReplyPanel(true, messageObject, null, null, false); } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); updatePinnedMessageView(true); updateVisibleRows(); } else if (id == chat_menu_attach) { - if (getParentActivity() == null) { - return; - } - - createChatAttachView(); - chatAttachAlert.loadGalleryPhotos(); - if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) { - chatActivityEnterView.closeKeyboard(); - } - chatAttachAlert.init(); - showDialog(chatAttachAlert); + openAttachMenu(); } else if (id == bot_help) { SendMessagesHelper.getInstance().sendMessage("/help", dialog_id, null, null, false, null, null, null); } else if (id == bot_settings) { SendMessagesHelper.getInstance().sendMessage("/settings", dialog_id, null, null, false, null, null, null); } else if (id == search) { openSearchWithText(null); + } else if(id == call) { + if(currentUser!=null) + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); } } }); @@ -979,6 +1049,11 @@ public void onClick(DialogInterface dialogInterface, int i) { avatarContainer = new ChatAvatarContainer(context, this, currentEncryptedChat != null); actionBar.addView(avatarContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 56, 0, 40, 0)); + actionBarOverlay = new View(context); + actionBarOverlay.setBackgroundColor(0x7f000000); + actionBarOverlay.setVisibility(View.GONE); + actionBar.addView(actionBarOverlay, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + if (currentChat != null) { if (!ChatObject.isChannel(currentChat)) { int count = currentChat.participants_count; @@ -1046,32 +1121,41 @@ public void onSearchPressed(EditText editText) { } headerItem = menu.addItem(0, R.drawable.ic_ab_other); + if (currentUser != null && MessagesController.getInstance().callsEnabled) { + headerItem.addSubItem(call, LocaleController.getString("Call", R.string.Call)); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(currentUser.id); + if (userFull != null && userFull.phone_calls_available) { + headerItem.showSubItem(call); + } else { + headerItem.hideSubItem(call); + } + } if (searchItem != null) { - headerItem.addSubItem(search, LocaleController.getString("Search", R.string.Search), 0); + headerItem.addSubItem(search, LocaleController.getString("Search", R.string.Search)); } if (ChatObject.isChannel(currentChat) && !currentChat.creator && (!currentChat.megagroup || currentChat.username != null && currentChat.username.length() > 0)) { - headerItem.addSubItem(report, LocaleController.getString("ReportChat", R.string.ReportChat), 0); + headerItem.addSubItem(report, LocaleController.getString("ReportChat", R.string.ReportChat)); } if (currentUser != null) { - addContactItem = headerItem.addSubItem(share_contact, "", 0); + addContactItem = headerItem.addSubItem(share_contact, ""); } if (currentEncryptedChat != null) { - timeItem2 = headerItem.addSubItem(chat_enc_timer, LocaleController.getString("SetTimer", R.string.SetTimer), 0); + timeItem2 = headerItem.addSubItem(chat_enc_timer, LocaleController.getString("SetTimer", R.string.SetTimer)); } if (!ChatObject.isChannel(currentChat)) { - headerItem.addSubItem(clear_history, LocaleController.getString("ClearHistory", R.string.ClearHistory), 0); + headerItem.addSubItem(clear_history, LocaleController.getString("ClearHistory", R.string.ClearHistory)); if (currentChat != null && !isBroadcast) { - headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), 0); + headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit)); } else { - headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteChatUser", R.string.DeleteChatUser), 0); + headerItem.addSubItem(delete_chat, LocaleController.getString("DeleteChatUser", R.string.DeleteChatUser)); } } if (currentUser == null || !currentUser.self) { - muteItem = headerItem.addSubItem(mute, null, 0); + muteItem = headerItem.addSubItem(mute, null); } if (currentUser != null && currentEncryptedChat == null && currentUser.bot) { - headerItem.addSubItem(bot_settings, LocaleController.getString("BotSettings", R.string.BotSettings), 0); - headerItem.addSubItem(bot_help, LocaleController.getString("BotHelp", R.string.BotHelp), 0); + headerItem.addSubItem(bot_settings, LocaleController.getString("BotSettings", R.string.BotSettings)); + headerItem.addSubItem(bot_help, LocaleController.getString("BotHelp", R.string.BotHelp)); updateBotButtons(); } @@ -1082,8 +1166,6 @@ public void onSearchPressed(EditText editText) { attachItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_other).setOverrideMenuClick(true).setAllowCloseAnimation(false); attachItem.setVisibility(View.GONE); - menuItem = menu.addItem(chat_menu_attach, R.drawable.ic_ab_attach).setAllowCloseAnimation(false); - menuItem.setBackgroundDrawable(null); actionModeViews.clear(); @@ -1092,7 +1174,7 @@ public void onSearchPressed(EditText editText) { selectedMessagesCountTextView = new NumberTextView(actionMode.getContext()); selectedMessagesCountTextView.setTextSize(18); selectedMessagesCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - selectedMessagesCountTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); + selectedMessagesCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); actionMode.addView(selectedMessagesCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 65, 0, 0, 0)); selectedMessagesCountTextView.setOnTouchListener(new View.OnTouchListener() { @Override @@ -1149,31 +1231,26 @@ public boolean onTouch(View v, MotionEvent event) { actionModeTextView = new SimpleTextView(context); actionModeTextView.setTextSize(18); actionModeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - actionModeTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); + actionModeTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); actionModeTextView.setText(LocaleController.getString("Edit", R.string.Edit)); actionModeTitleContainer.addView(actionModeTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); actionModeSubTextView = new SimpleTextView(context); actionModeSubTextView.setGravity(Gravity.LEFT); - actionModeSubTextView.setTextColor(Theme.ACTION_BAR_ACTION_MODE_TEXT_COLOR); + actionModeSubTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); actionModeTitleContainer.addView(actionModeSubTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); if (currentEncryptedChat == null) { if (!isBroadcast) { - actionModeViews.add(actionMode.addItem(reply, R.drawable.ic_ab_reply, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - } - actionModeViews.add(actionMode.addItem(copy, R.drawable.ic_ab_fwd_copy, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(actionMode.addItem(forward, R.drawable.ic_ab_fwd_forward, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(actionMode.addItem(delete, R.drawable.ic_ab_fwd_delete, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(editDoneItem = actionMode.addItem(edit_done, R.drawable.check_blue, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - editDoneItem.setVisibility(View.GONE); - editDoneItemProgress = new ContextProgressView(context, 0); - editDoneItem.addView(editDoneItemProgress, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - editDoneItemProgress.setVisibility(View.INVISIBLE); + actionModeViews.add(actionMode.addItemWithWidth(reply, R.drawable.ic_ab_reply, AndroidUtilities.dp(54))); + } + actionModeViews.add(actionMode.addItemWithWidth(copy, R.drawable.ic_ab_copy, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(forward, R.drawable.ic_ab_forward, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(delete, R.drawable.ic_ab_delete, AndroidUtilities.dp(54))); } else { - actionModeViews.add(actionMode.addItem(reply, R.drawable.ic_ab_reply, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(actionMode.addItem(copy, R.drawable.ic_ab_fwd_copy, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); - actionModeViews.add(actionMode.addItem(delete, R.drawable.ic_ab_fwd_delete, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(reply, R.drawable.ic_ab_reply, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(copy, R.drawable.ic_ab_copy, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(delete, R.drawable.ic_ab_delete, AndroidUtilities.dp(54))); } actionMode.getItem(copy).setVisibility(selectedMessagesCanCopyIds[0].size() + selectedMessagesCanCopyIds[1].size() != 0 ? View.VISIBLE : View.GONE); actionMode.getItem(delete).setVisibility(cantDeleteMessagesCount == 0 ? View.VISIBLE : View.GONE); @@ -1186,12 +1263,17 @@ public boolean onTouch(View v, MotionEvent event) { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { boolean result = super.drawChild(canvas, child, drawingTime); - if (child == actionBar) { - parentLayout.drawHeaderShadow(canvas, actionBar.getMeasuredHeight()); + if (child == actionBar && parentLayout != null) { + parentLayout.drawHeaderShadow(canvas, actionBar.getVisibility() == VISIBLE ? actionBar.getMeasuredHeight() : 0); } return result; } + @Override + protected boolean isActionBarVisible() { + return actionBar.getVisibility() == VISIBLE; + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); @@ -1202,7 +1284,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { measureChildWithMargins(actionBar, widthMeasureSpec, 0, heightMeasureSpec, 0); int actionBarHeight = actionBar.getMeasuredHeight(); - heightSize -= actionBarHeight; + if (actionBar.getVisibility() == VISIBLE) { + heightSize -= actionBarHeight; + } int keyboardSize = getKeyboardHeight(); @@ -1243,7 +1327,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int height; mentionListViewIgnoreLayout = true; - if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout()) { int size = mentionGridLayoutManager.getRowsCount(widthSize); int maxHeight = size * 102; @@ -1324,7 +1407,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { switch (verticalGravity) { case Gravity.TOP: childTop = lp.topMargin + getPaddingTop(); - if (child != actionBar) { + if (child != actionBar && actionBar.getVisibility() == VISIBLE) { childTop += actionBar.getMeasuredHeight(); } break; @@ -1343,7 +1426,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } else if (child == pagedownButton) { childTop -= chatActivityEnterView.getMeasuredHeight(); } else if (child == emptyViewContainer) { - childTop -= inputFieldHeight / 2 - actionBar.getMeasuredHeight() / 2; + childTop -= inputFieldHeight / 2 - (actionBar.getVisibility() == VISIBLE ? actionBar.getMeasuredHeight() / 2 : 0); } else if (chatActivityEnterView.isPopupView(child)) { if (AndroidUtilities.isInMultiwindow) { childTop = chatActivityEnterView.getTop() - child.getMeasuredHeight() + AndroidUtilities.dp(1); @@ -1369,7 +1452,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { SizeNotifierFrameLayout contentView = (SizeNotifierFrameLayout) fragmentView; - contentView.setBackgroundImage(ApplicationLoader.getCachedWallpaper()); + contentView.setBackgroundImage(Theme.getCachedWallpaper()); emptyViewContainer = new FrameLayout(context); emptyViewContainer.setVisibility(View.INVISIBLE); @@ -1386,15 +1469,15 @@ public boolean onTouch(View v, MotionEvent event) { bigEmptyView = new ChatBigEmptyView(context, false); emptyViewContainer.addView(bigEmptyView, new FrameLayout.LayoutParams(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); } else { - TextView emptyView = new TextView(context); - if (currentUser != null && currentUser.id != 777000 && currentUser.id != 429000 && (currentUser.id / 1000 == 333 || currentUser.id % 1000 == 0)) { + emptyView = new TextView(context); + if (currentUser != null && currentUser.id != 777000 && currentUser.id != 429000 && currentUser.id != 4244000 && (currentUser.id / 1000 == 333 || currentUser.id % 1000 == 0)) { emptyView.setText(LocaleController.getString("GotAQuestion", R.string.GotAQuestion)); } else { emptyView.setText(LocaleController.getString("NoMessages", R.string.NoMessages)); } emptyView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); emptyView.setGravity(Gravity.CENTER); - emptyView.setTextColor(Theme.CHAT_EMPTY_VIEW_TEXT_COLOR); + emptyView.setTextColor(Theme.getColor(Theme.key_chat_serviceText)); emptyView.setBackgroundResource(R.drawable.system); emptyView.getBackground().setColorFilter(Theme.colorFilter); emptyView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -1438,6 +1521,56 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } } } + + @Override + public boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child instanceof ChatMessageCell) { + ChatMessageCell chatMessageCell = (ChatMessageCell) child; + ImageReceiver imageReceiver = chatMessageCell.getAvatarImage(); + if (imageReceiver != null) { + int top = child.getTop(); + if (chatMessageCell.isPinnedBottom()) { + ViewHolder holder = chatListView.getChildViewHolder(child); + if (holder != null) { + holder = chatListView.findViewHolderForAdapterPosition(holder.getAdapterPosition() + 1); + if (holder != null) { + imageReceiver.setImageY(-AndroidUtilities.dp(1000)); + imageReceiver.draw(canvas); + return result; + } + } + } + if (chatMessageCell.isPinnedTop()) { + ViewHolder holder = chatListView.getChildViewHolder(child); + if (holder != null) { + while (true) { + holder = chatListView.findViewHolderForAdapterPosition(holder.getAdapterPosition() - 1); + if (holder != null) { + top = holder.itemView.getTop(); + if (!(holder.itemView instanceof ChatMessageCell) || !((ChatMessageCell) holder.itemView).isPinnedTop()) { + break; + } + } else { + break; + } + } + } + } + int y = child.getTop() + chatMessageCell.getLayoutHeight(); + int maxY = chatListView.getHeight() - chatListView.getPaddingBottom(); + if (y > maxY) { + y = maxY; + } + if (y - AndroidUtilities.dp(48) < top) { + y = top + AndroidUtilities.dp(48); + } + imageReceiver.setImageY(y - AndroidUtilities.dp(44)); + imageReceiver.draw(canvas); + } + } + return result; + } }; chatListView.setTag(1); chatListView.setVerticalScrollBarEnabled(true); @@ -1465,14 +1598,41 @@ public boolean supportsPredictiveItemAnimations() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - if (newState == RecyclerView.SCROLL_STATE_DRAGGING && highlightMessageId != Integer.MAX_VALUE) { - highlightMessageId = Integer.MAX_VALUE; - updateVisibleRows(); + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + scrollingFloatingDate = true; + } else if (newState == RecyclerView.SCROLL_STATE_IDLE) { + scrollingFloatingDate = false; + hideFloatingDateView(true); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + chatListView.invalidate(); + if (dy != 0 && scrollingFloatingDate && !currentFloatingTopIsNotMessage) { + if (highlightMessageId != Integer.MAX_VALUE) { + highlightMessageId = Integer.MAX_VALUE; + updateVisibleRows(); + } + if (floatingDateView.getTag() == null) { + if (floatingDateAnimation != null) { + floatingDateAnimation.cancel(); + } + floatingDateView.setTag(1); + floatingDateAnimation = new AnimatorSet(); + floatingDateAnimation.setDuration(150); + floatingDateAnimation.playTogether(ObjectAnimator.ofFloat(floatingDateView, "alpha", 1.0f)); + floatingDateAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(floatingDateAnimation)) { + floatingDateAnimation = null; + } + } + }); + floatingDateAnimation.start(); + } + } checkScrollForLoad(true); int firstVisibleItem = chatLayoutManager.findFirstVisibleItemPosition(); int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(chatLayoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; @@ -1521,7 +1681,7 @@ public void run() { try { Toast.makeText(v.getContext(), LocaleController.getString("PhotoTip", R.string.PhotoTip), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (SecretPhotoViewer.getInstance().isVisible()) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -1614,32 +1774,53 @@ public void run() { return false; } }); + if (scrollToPositionOnRecreate != -1) { + chatLayoutManager.scrollToPositionWithOffset(scrollToPositionOnRecreate, scrollToOffsetOnRecreate); + scrollToPositionOnRecreate = -1; + } progressView = new FrameLayout(context); progressView.setVisibility(View.INVISIBLE); contentView.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - View view = new View(context); - view.setBackgroundResource(R.drawable.system_loader); - view.getBackground().setColorFilter(Theme.colorFilter); - progressView.addView(view, LayoutHelper.createFrame(36, 36, Gravity.CENTER)); + progressView2 = new View(context); + progressView2.setBackgroundResource(R.drawable.system_loader); + progressView2.getBackground().setColorFilter(Theme.colorFilter); + progressView.addView(progressView2, LayoutHelper.createFrame(36, 36, Gravity.CENTER)); - ProgressBar progressBar = new ProgressBar(context); - try { - progressBar.setIndeterminateDrawable(context.getResources().getDrawable(R.drawable.loading_animation)); - } catch (Exception e) { - //don't promt - } - progressBar.setIndeterminate(true); - AndroidUtilities.setProgressBarAnimationDuration(progressBar, 1500); + progressBar = new RadialProgressView(context); + progressBar.setSize(AndroidUtilities.dp(28)); + progressBar.setProgressColor(Theme.getColor(Theme.key_chat_serviceText)); progressView.addView(progressBar, LayoutHelper.createFrame(32, 32, Gravity.CENTER)); + floatingDateView = new ChatActionCell(context); + floatingDateView.setAlpha(0.0f); + contentView.addView(floatingDateView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 4, 0, 0)); + floatingDateView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (floatingDateView.getAlpha() == 0) { + return; + } + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis((long) floatingDateView.getCustomDate() * 1000); + int year = calendar.get(Calendar.YEAR); + int monthOfYear = calendar.get(Calendar.MONTH); + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + + calendar.clear(); + calendar.set(year, monthOfYear, dayOfMonth); + jumpToDate((int) (calendar.getTime().getTime() / 1000)); + } + }); + if (ChatObject.isChannel(currentChat)) { pinnedMessageView = new FrameLayout(context); pinnedMessageView.setTag(1); pinnedMessageView.setTranslationY(-AndroidUtilities.dp(50)); pinnedMessageView.setVisibility(View.GONE); pinnedMessageView.setBackgroundResource(R.drawable.blockpanel); + pinnedMessageView.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelBackground), PorterDuff.Mode.MULTIPLY)); contentView.addView(pinnedMessageView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); pinnedMessageView.setOnClickListener(new View.OnClickListener() { @Override @@ -1648,26 +1829,27 @@ public void onClick(View v) { } }); - View lineView = new View(context); - lineView.setBackgroundColor(0xff6c9fd2); - pinnedMessageView.addView(lineView, LayoutHelper.createFrame(2, 32, Gravity.LEFT | Gravity.TOP, 8, 8, 0, 0)); + pinnedLineView = new View(context); + pinnedLineView.setBackgroundColor(Theme.getColor(Theme.key_chat_topPanelLine)); + pinnedMessageView.addView(pinnedLineView, LayoutHelper.createFrame(2, 32, Gravity.LEFT | Gravity.TOP, 8, 8, 0, 0)); pinnedMessageImageView = new BackupImageView(context); pinnedMessageView.addView(pinnedMessageImageView, LayoutHelper.createFrame(32, 32, Gravity.TOP | Gravity.LEFT, 17, 8, 0, 0)); pinnedMessageNameTextView = new SimpleTextView(context); pinnedMessageNameTextView.setTextSize(14); - pinnedMessageNameTextView.setTextColor(Theme.PINNED_PANEL_NAME_TEXT_COLOR); + pinnedMessageNameTextView.setTextColor(Theme.getColor(Theme.key_chat_topPanelTitle)); pinnedMessageNameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); pinnedMessageView.addView(pinnedMessageNameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, AndroidUtilities.dp(18), Gravity.TOP | Gravity.LEFT, 18, 7.3f, 52, 0)); pinnedMessageTextView = new SimpleTextView(context); pinnedMessageTextView.setTextSize(14); - pinnedMessageTextView.setTextColor(Theme.PINNED_PANEL_MESSAGE_TEXT_COLOR); + pinnedMessageTextView.setTextColor(Theme.getColor(Theme.key_chat_topPanelMessage)); pinnedMessageView.addView(pinnedMessageTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, AndroidUtilities.dp(18), Gravity.TOP | Gravity.LEFT, 18, 25.3f, 52, 0)); - ImageView closePinned = new ImageView(context); + closePinned = new ImageView(context); closePinned.setImageResource(R.drawable.miniplayer_close); + closePinned.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelClose), PorterDuff.Mode.MULTIPLY)); closePinned.setScaleType(ImageView.ScaleType.CENTER); pinnedMessageView.addView(closePinned, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); closePinned.setOnClickListener(new View.OnClickListener() { @@ -1702,10 +1884,11 @@ public void onClick(DialogInterface dialogInterface, int i) { reportSpamView.setTranslationY(-AndroidUtilities.dp(50)); reportSpamView.setVisibility(View.GONE); reportSpamView.setBackgroundResource(R.drawable.blockpanel); + reportSpamView.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelBackground), PorterDuff.Mode.MULTIPLY)); contentView.addView(reportSpamView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); addToContactsButton = new TextView(context); - addToContactsButton.setTextColor(Theme.CHAT_ADD_CONTACT_TEXT_COLOR); + addToContactsButton.setTextColor(Theme.getColor(Theme.key_chat_addContact)); addToContactsButton.setVisibility(View.GONE); addToContactsButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addToContactsButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -1729,7 +1912,7 @@ public void onClick(View v) { reportSpamView.addView(reportSpamContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 1.0f, Gravity.LEFT | Gravity.TOP, 0, 0, 0, AndroidUtilities.dp(1))); reportSpamButton = new TextView(context); - reportSpamButton.setTextColor(Theme.CHAT_REPORT_SPAM_TEXT_COLOR); + reportSpamButton.setTextColor(Theme.getColor(Theme.key_chat_reportSpam)); reportSpamButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); reportSpamButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); reportSpamButton.setSingleLine(true); @@ -1763,7 +1946,7 @@ public void onClick(DialogInterface dialogInterface, int i) { if (currentUser != null) { MessagesController.getInstance().blockUser(currentUser.id); } - MessagesController.getInstance().reportSpam(dialog_id, currentUser, currentChat); + MessagesController.getInstance().reportSpam(dialog_id, currentUser, currentChat, currentEncryptedChat); updateSpamView(); if (currentChat != null) { if (ChatObject.isNotInChat(currentChat)) { @@ -1782,8 +1965,9 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - ImageView closeReportSpam = new ImageView(context); + closeReportSpam = new ImageView(context); closeReportSpam.setImageResource(R.drawable.miniplayer_close); + closeReportSpam.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelClose), PorterDuff.Mode.MULTIPLY)); closeReportSpam.setScaleType(ImageView.ScaleType.CENTER); reportSpamContainer.addView(closeReportSpam, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); closeReportSpam.setOnClickListener(new View.OnClickListener() { @@ -1799,11 +1983,12 @@ public void onClick(View v) { alertView.setTranslationY(-AndroidUtilities.dp(50)); alertView.setVisibility(View.GONE); alertView.setBackgroundResource(R.drawable.blockpanel); + alertView.getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_topPanelBackground), PorterDuff.Mode.MULTIPLY)); contentView.addView(alertView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 50, Gravity.TOP | Gravity.LEFT)); alertNameTextView = new TextView(context); alertNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - alertNameTextView.setTextColor(Theme.ALERT_PANEL_NAME_TEXT_COLOR); + alertNameTextView.setTextColor(Theme.getColor(Theme.key_chat_topPanelTitle)); alertNameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); alertNameTextView.setSingleLine(true); alertNameTextView.setEllipsize(TextUtils.TruncateAt.END); @@ -1812,33 +1997,47 @@ public void onClick(View v) { alertTextView = new TextView(context); alertTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - alertTextView.setTextColor(Theme.ALERT_PANEL_MESSAGE_TEXT_COLOR); + alertTextView.setTextColor(Theme.getColor(Theme.key_chat_topPanelMessage)); alertTextView.setSingleLine(true); alertTextView.setEllipsize(TextUtils.TruncateAt.END); alertTextView.setMaxLines(1); alertView.addView(alertTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 8, 23, 8, 0)); + pagedownButton = new FrameLayout(context); + pagedownButton.setVisibility(View.INVISIBLE); + contentView.addView(pagedownButton, LayoutHelper.createFrame(46, 59, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 7, 5)); + pagedownButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (createUnreadMessageAfterId != 0) { + scrollToMessageId(createUnreadMessageAfterId, 0, false, returnToLoadIndex); + } else if (returnToMessageId > 0) { + scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex); + } else { + scrollToLastMessage(true); + } + } + }); + if (!isBroadcast) { mentionContainer = new FrameLayout(context) { - private Drawable background; - @Override public void onDraw(Canvas canvas) { if (mentionListView.getChildCount() <= 0) { return; } + int top; if (mentionsAdapter.isBotContext() && mentionsAdapter.isMediaLayout() && mentionsAdapter.getBotContextSwitch() == null) { - background.setBounds(0, mentionListViewScrollOffsetY - AndroidUtilities.dp(4), getMeasuredWidth(), getMeasuredHeight()); + top = mentionListViewScrollOffsetY - AndroidUtilities.dp(4); } else { - background.setBounds(0, mentionListViewScrollOffsetY - AndroidUtilities.dp(2), getMeasuredWidth(), getMeasuredHeight()); + top = mentionListViewScrollOffsetY - AndroidUtilities.dp(2); } - background.draw(canvas); - } - @Override - public void setBackgroundResource(int resid) { - background = getContext().getResources().getDrawable(resid); + int bottom = top + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, top, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); } @Override @@ -1849,7 +2048,6 @@ public void requestLayout() { super.requestLayout(); } }; - mentionContainer.setBackgroundResource(R.drawable.compose_panel); mentionContainer.setVisibility(View.GONE); mentionContainer.setWillNotDraw(false); contentView.addView(mentionContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 110, Gravity.LEFT | Gravity.BOTTOM)); @@ -1864,7 +2062,7 @@ public boolean onInterceptTouchEvent(MotionEvent event) { if (!mentionListViewIsScrolling && mentionListViewScrollOffsetY != 0 && event.getY() < mentionListViewScrollOffsetY) { return false; } - boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, mentionListView, 0); + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, mentionListView, 0, null); return super.onInterceptTouchEvent(event) || result; } @@ -1918,7 +2116,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { mentionListView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - return StickerPreviewViewer.getInstance().onTouch(event, mentionListView, 0, mentionsOnItemClickListener); + return StickerPreviewViewer.getInstance().onTouch(event, mentionListView, 0, mentionsOnItemClickListener, null); } }); mentionListView.setTag(2); @@ -2010,7 +2208,7 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle mentionListView.setLayoutAnimation(null); mentionListView.setClipToPadding(false); mentionListView.setLayoutManager(mentionLayoutManager); - mentionListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER); + mentionListView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); mentionContainer.addView(mentionListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); mentionListView.setAdapter(mentionsAdapter = new MentionsAdapter(context, false, dialog_id, new MentionsAdapter.MentionsAdapterDelegate() { @@ -2054,7 +2252,7 @@ public void needChangePanelVisibility(boolean show) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionContainer, "alpha", 0.0f, 1.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -2089,7 +2287,7 @@ public void onAnimationCancel(Animator animation) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionContainer, "alpha", 0.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -2129,10 +2327,7 @@ public void onContextClick(TLRPC.BotInlineResult result) { return; } if (result.type.equals("video") || result.type.equals("web_player_video")) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setCustomView(new WebFrameLayout(getParentActivity(), builder.create(), result.title != null ? result.title : "", result.description, result.content_url, result.content_url, result.w, result.h)); - builder.setUseFullWidth(true); - showDialog(builder.create()); + EmbedBottomSheet.show(getParentActivity(), result.title != null ? result.title : "", result.description, result.content_url, result.content_url, result.w, result.h); } else { Browser.openUrl(getParentActivity(), result.content_url); } @@ -2163,7 +2358,7 @@ public void onItemClick(View view, int position) { name = user.last_name; } Spannable spannable = new SpannableString(name + " "); - spannable.setSpan(new URLSpanUserMention("" + user.id), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + spannable.setSpan(new URLSpanUserMention("" + user.id, true), 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); chatActivityEnterView.replaceWithText(start, len, spannable); } } @@ -2253,38 +2448,39 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { }); } - pagedownButton = new FrameLayout(context); - pagedownButton.setVisibility(View.INVISIBLE); - contentView.addView(pagedownButton, LayoutHelper.createFrame(46, 59, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 7, 5)); - pagedownButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (returnToMessageId > 0) { - scrollToMessageId(returnToMessageId, 0, true, returnToLoadIndex); - } else { - scrollToLastMessage(true); - } - } - }); - - ImageView pagedownButtonImage = new ImageView(context); + pagedownButtonImage = new ImageView(context); pagedownButtonImage.setImageResource(R.drawable.pagedown); + pagedownButtonImage.setScaleType(ImageView.ScaleType.CENTER); + pagedownButtonImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_goDownButtonIcon), PorterDuff.Mode.MULTIPLY)); + pagedownButtonImage.setPadding(0, AndroidUtilities.dp(2), 0, 0); + Drawable drawable = Theme.createCircleDrawable(AndroidUtilities.dp(42), Theme.getColor(Theme.key_chat_goDownButton)); + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.pagedown_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_goDownButtonShadow), PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(42), AndroidUtilities.dp(42)); + drawable = combinedDrawable; + pagedownButtonImage.setBackgroundDrawable(drawable); + pagedownButton.addView(pagedownButtonImage, LayoutHelper.createFrame(46, 46, Gravity.LEFT | Gravity.BOTTOM)); pagedownButtonCounter = new TextView(context); pagedownButtonCounter.setVisibility(View.INVISIBLE); pagedownButtonCounter.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); pagedownButtonCounter.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - pagedownButtonCounter.setTextColor(0xffffffff); + pagedownButtonCounter.setTextColor(Theme.getColor(Theme.key_chat_goDownButtonCounter)); pagedownButtonCounter.setGravity(Gravity.CENTER); - pagedownButtonCounter.setBackgroundResource(R.drawable.chat_badge); + pagedownButtonCounter.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(11.5f), Theme.getColor(Theme.key_chat_goDownButtonCounterBackground))); pagedownButtonCounter.setMinWidth(AndroidUtilities.dp(23)); pagedownButtonCounter.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); pagedownButton.addView(pagedownButtonCounter, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 23, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + if (BuildVars.DEBUG_PRIVATE_VERSION) { + instantCameraView = new InstantCameraView(context, this, actionBarOverlay); + contentView.addView(instantCameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 48)); + } + chatActivityEnterView = new ChatActivityEnterView(getParentActivity(), contentView, this, true); chatActivityEnterView.setDialogId(dialog_id); - chatActivityEnterView.addToAttachLayout(menuItem); chatActivityEnterView.setId(id_chat_compose_panel); chatActivityEnterView.setBotsCount(botsCount, hasBotsCommands); chatActivityEnterView.setAllowStickersAndGifs(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 23, currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); @@ -2293,7 +2489,7 @@ public void onClick(View view) { @Override public void onMessageSend(CharSequence message) { moveScrollToLastMessage(); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); if (mentionsAdapter != null) { mentionsAdapter.addHashtagsFromMessage(message); } @@ -2363,9 +2559,7 @@ public void onAttachButtonShow() { @Override public void onMessageEditEnd(boolean loading) { - if (loading) { - showEditDoneProgress(true, true); - } else { + if (!loading) { mentionsAdapter.setNeedBotContext(currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46); chatListView.setOnItemLongClickListener(onItemLongClickListener); chatListView.setOnItemClickListener(onItemClickListener); @@ -2418,6 +2612,24 @@ public void onStickersTab(boolean opened) { allowContextBotPanelSecond = !opened; checkContextBotPanel(); } + + @Override + public void didPressedAttachButton() { + openAttachMenu(); + } + + @Override + public void needStartRecordVideo(int state) { + if (instantCameraView != null) { + if (state == 0) { + instantCameraView.showCamera(); + } else if (state == 1) { + instantCameraView.send(); + } else if (state == 2) { + instantCameraView.cancel(); + } + } + } }); FrameLayout replyLayout = new FrameLayout(context) { @@ -2471,37 +2683,39 @@ public void setVisibility(int visibility) { replyLayout.setClickable(true); chatActivityEnterView.addTopView(replyLayout, 48); - View lineView = new View(context); - lineView.setBackgroundColor(0xffe8e8e8); - replyLayout.addView(lineView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, Gravity.BOTTOM | Gravity.LEFT)); + replyLineView = new View(context); + replyLineView.setBackgroundColor(Theme.getColor(Theme.key_chat_replyPanelLine)); + replyLayout.addView(replyLineView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 1, Gravity.BOTTOM | Gravity.LEFT)); replyIconImageView = new ImageView(context); + replyIconImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_replyPanelIcons), PorterDuff.Mode.MULTIPLY)); replyIconImageView.setScaleType(ImageView.ScaleType.CENTER); replyLayout.addView(replyIconImageView, LayoutHelper.createFrame(52, 46, Gravity.TOP | Gravity.LEFT)); - ImageView imageView = new ImageView(context); - imageView.setImageResource(R.drawable.delete_reply); - imageView.setScaleType(ImageView.ScaleType.CENTER); - replyLayout.addView(imageView, LayoutHelper.createFrame(52, 46, Gravity.RIGHT | Gravity.TOP, 0, 0.5f, 0, 0)); - imageView.setOnClickListener(new View.OnClickListener() { + replyCloseImageView = new ImageView(context); + replyCloseImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_replyPanelClose), PorterDuff.Mode.MULTIPLY)); + replyCloseImageView.setImageResource(R.drawable.msg_panel_clear); + replyCloseImageView.setScaleType(ImageView.ScaleType.CENTER); + replyLayout.addView(replyCloseImageView, LayoutHelper.createFrame(52, 46, Gravity.RIGHT | Gravity.TOP, 0, 0.5f, 0, 0)); + replyCloseImageView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (forwardingMessages != null) { forwardingMessages.clear(); } - showReplyPanel(false, null, null, foundWebPage, true, true); + showReplyPanel(false, null, null, foundWebPage, true); } }); replyNameTextView = new SimpleTextView(context); replyNameTextView.setTextSize(14); - replyNameTextView.setTextColor(Theme.REPLY_PANEL_NAME_TEXT_COLOR); + replyNameTextView.setTextColor(Theme.getColor(Theme.key_chat_replyPanelName)); replyNameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); replyLayout.addView(replyNameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.TOP | Gravity.LEFT, 52, 6, 52, 0)); replyObjectTextView = new SimpleTextView(context); replyObjectTextView.setTextSize(14); - replyObjectTextView.setTextColor(Theme.REPLY_PANEL_MESSAGE_TEXT_COLOR); + replyObjectTextView.setTextColor(Theme.getColor(Theme.key_chat_replyPanelMessage)); replyLayout.addView(replyObjectTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 18, Gravity.TOP | Gravity.LEFT, 52, 24, 52, 0)); replyImageView = new BackupImageView(context); @@ -2514,7 +2728,7 @@ public void onClick(View v) { stickersListView = new RecyclerListView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersListView, 0); + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersListView, 0, null); return super.onInterceptTouchEvent(event) || result; } }; @@ -2522,7 +2736,7 @@ public boolean onInterceptTouchEvent(MotionEvent event) { stickersListView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - return StickerPreviewViewer.getInstance().onTouch(event, stickersListView, 0, stickersOnItemClickListener); + return StickerPreviewViewer.getInstance().onTouch(event, stickersListView, 0, stickersOnItemClickListener, null); } }); stickersListView.setDisallowInterceptTouchEvents(true); @@ -2534,23 +2748,32 @@ public boolean onTouch(View v, MotionEvent event) { stickersPanel.addView(stickersListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 78)); initStickers(); - imageView = new ImageView(context); - imageView.setImageResource(R.drawable.stickers_back_arrow); - stickersPanel.addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 53, 0, 0, 0)); + stickersPanelArrow = new ImageView(context); + stickersPanelArrow.setImageResource(R.drawable.stickers_back_arrow); + stickersPanelArrow.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_stickersHintPanel), PorterDuff.Mode.MULTIPLY)); + stickersPanel.addView(stickersPanelArrow, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 53, 0, 0, 0)); - searchContainer = new FrameLayout(context); - searchContainer.setBackgroundResource(R.drawable.compose_panel); + searchContainer = new FrameLayout(context) { + @Override + public void onDraw(Canvas canvas) { + int bottom = Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, 0, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); + } + }; + searchContainer.setWillNotDraw(false); searchContainer.setVisibility(View.INVISIBLE); searchContainer.setFocusable(true); searchContainer.setFocusableInTouchMode(true); searchContainer.setClickable(true); - searchContainer.setBackgroundResource(R.drawable.compose_panel); searchContainer.setPadding(0, AndroidUtilities.dp(3), 0, 0); contentView.addView(searchContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.BOTTOM)); searchUpButton = new ImageView(context); searchUpButton.setScaleType(ImageView.ScaleType.CENTER); searchUpButton.setImageResource(R.drawable.search_up); + searchUpButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); searchContainer.addView(searchUpButton, LayoutHelper.createFrame(48, 48)); searchUpButton.setOnClickListener(new View.OnClickListener() { @Override @@ -2562,6 +2785,7 @@ public void onClick(View view) { searchDownButton = new ImageView(context); searchDownButton.setScaleType(ImageView.ScaleType.CENTER); searchDownButton.setImageResource(R.drawable.search_down); + searchDownButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); searchContainer.addView(searchDownButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.TOP, 48, 0, 0, 0)); searchDownButton.setOnClickListener(new View.OnClickListener() { @Override @@ -2570,28 +2794,105 @@ public void onClick(View view) { } }); + searchCalendarButton = new ImageView(context); + searchCalendarButton.setScaleType(ImageView.ScaleType.CENTER); + searchCalendarButton.setImageResource(R.drawable.search_calendar); + searchCalendarButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_searchPanelIcons), PorterDuff.Mode.MULTIPLY)); + searchContainer.addView(searchCalendarButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); + searchCalendarButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (getParentActivity() == null) { + return; + } + AndroidUtilities.hideKeyboard(searchItem.getSearchField()); + Calendar calendar = Calendar.getInstance(); + int year = calendar.get(Calendar.YEAR); + int monthOfYear = calendar.get(Calendar.MONTH); + int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH); + try { + DatePickerDialog dialog = new DatePickerDialog(getParentActivity(), new DatePickerDialog.OnDateSetListener() { + @Override + public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) { + Calendar calendar = Calendar.getInstance(); + calendar.clear(); + calendar.set(year, month, dayOfMonth); + int date = (int) (calendar.getTime().getTime() / 1000); + clearChatData(); + waitingForLoad.add(lastLoadIndex); + MessagesController.getInstance().loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + } + }, year, monthOfYear, dayOfMonth); + final DatePicker datePicker = dialog.getDatePicker(); + datePicker.setMinDate(1375315200000L); + datePicker.setMaxDate(System.currentTimeMillis()); + dialog.setButton(DialogInterface.BUTTON_POSITIVE, LocaleController.getString("JumpToDate", R.string.JumpToDate), dialog); + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }); + if (Build.VERSION.SDK_INT >= 21) { + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + int count = datePicker.getChildCount(); + for (int a = 0; a < count; a++) { + View child = datePicker.getChildAt(a); + ViewGroup.LayoutParams layoutParams = child.getLayoutParams(); + layoutParams.width = LayoutHelper.MATCH_PARENT; + child.setLayoutParams(layoutParams); + } + FileLog.e(""); + } + }); + } + showDialog(dialog); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + searchCountText = new SimpleTextView(context); - searchCountText.setTextColor(Theme.CHAT_SEARCH_COUNT_TEXT_COLOR); + searchCountText.setTextColor(Theme.getColor(Theme.key_chat_searchPanelText)); searchCountText.setTextSize(15); searchCountText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); searchContainer.addView(searchCountText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 108, 0, 0, 0)); - bottomOverlay = new FrameLayout(context); + bottomOverlay = new FrameLayout(context) { + @Override + public void onDraw(Canvas canvas) { + int bottom = Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, 0, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); + } + }; + bottomOverlay.setWillNotDraw(false); bottomOverlay.setVisibility(View.INVISIBLE); bottomOverlay.setFocusable(true); bottomOverlay.setFocusableInTouchMode(true); bottomOverlay.setClickable(true); - bottomOverlay.setBackgroundResource(R.drawable.compose_panel); bottomOverlay.setPadding(0, AndroidUtilities.dp(3), 0, 0); contentView.addView(bottomOverlay, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.BOTTOM)); bottomOverlayText = new TextView(context); bottomOverlayText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - bottomOverlayText.setTextColor(Theme.CHAT_BOTTOM_OVERLAY_TEXT_COLOR); + bottomOverlayText.setTextColor(Theme.getColor(Theme.key_chat_secretChatStatusText)); bottomOverlay.addView(bottomOverlayText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); - bottomOverlayChat = new FrameLayout(context); - bottomOverlayChat.setBackgroundResource(R.drawable.compose_panel); + bottomOverlayChat = new FrameLayout(context) { + @Override + public void onDraw(Canvas canvas) { + int bottom = Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, 0, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); + } + }; + bottomOverlayChat.setWillNotDraw(false); bottomOverlayChat.setPadding(0, AndroidUtilities.dp(3), 0, 0); bottomOverlayChat.setVisibility(View.INVISIBLE); contentView.addView(bottomOverlayChat, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 51, Gravity.BOTTOM)); @@ -2660,7 +2961,7 @@ public void onClick(DialogInterface dialogInterface, int i) { bottomOverlayChatText = new TextView(context); bottomOverlayChatText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); bottomOverlayChatText.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - bottomOverlayChatText.setTextColor(Theme.CHAT_BOTTOM_CHAT_OVERLAY_TEXT_COLOR); + bottomOverlayChatText.setTextColor(Theme.getColor(Theme.key_chat_fieldOverlayText)); bottomOverlayChat.addView(bottomOverlayChatText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); chatAdapter.updateRows(); @@ -2675,7 +2976,7 @@ public void onClick(DialogInterface dialogInterface, int i) { chatActivityEnterView.setButtons(userBlocked ? null : botButtons); if (!AndroidUtilities.isTablet() || AndroidUtilities.isSmallTablet()) { - contentView.addView(playerView = new PlayerView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + contentView.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); } updateContactStatus(); @@ -2685,11 +2986,11 @@ public void onClick(DialogInterface dialogInterface, int i) { updatePinnedMessageView(true); try { - if (currentEncryptedChat != null && Build.VERSION.SDK_INT >= 23) { + if (currentEncryptedChat != null && Build.VERSION.SDK_INT >= 23 && (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture)) { getParentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fixLayoutInternal(); @@ -2707,7 +3008,7 @@ private void sendBotInlineResult(TLRPC.BotInlineResult result) { params.put("bot_name", mentionsAdapter.getContextBotName()); SendMessagesHelper.prepareSendingBotContextResult(result, params, dialog_id, replyingMessageObject); chatActivityEnterView.setFieldText(""); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); SearchQuery.increaseInlineRaiting(uid); } @@ -2718,7 +3019,7 @@ private void mentionListViewUpdateLayout() { return; } View child = mentionListView.getChildAt(mentionListView.getChildCount() - 1); - MentionsAdapter.Holder holder = (MentionsAdapter.Holder) mentionListView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); if (holder != null) { mentionListViewLastViewPosition = holder.getAdapterPosition(); mentionListViewLastViewTop = child.getTop(); @@ -2727,7 +3028,7 @@ private void mentionListViewUpdateLayout() { } child = mentionListView.getChildAt(0); - holder = (MentionsAdapter.Holder) mentionListView.findContainingViewHolder(child); + holder = (RecyclerListView.Holder) mentionListView.findContainingViewHolder(child); int newOffset = child.getTop() > 0 && holder != null && holder.getAdapterPosition() == 0 ? child.getTop() : 0; if (mentionListViewScrollOffsetY != newOffset) { mentionListView.setTopGlowOffset(mentionListViewScrollOffsetY = newOffset); @@ -2750,7 +3051,30 @@ private void checkBotCommands() { } } } else if (info instanceof TLRPC.TL_channelFull) { - URLSpanBotCommand.enabled = !info.bot_info.isEmpty(); + URLSpanBotCommand.enabled = !info.bot_info.isEmpty() && currentChat != null && currentChat.megagroup; + } + } + + private void jumpToDate(int date) { + if (messages.isEmpty()) { + return; + } + MessageObject firstMessage = messages.get(0); + MessageObject lastMessage = messages.get(messages.size() - 1); + if (firstMessage.messageOwner.date >= date && lastMessage.messageOwner.date <= date) { + for (int a = messages.size() - 1; a >= 0; a--) { + MessageObject message = messages.get(a); + if (message.messageOwner.date >= date && message.getId() != 0) { + scrollToMessageId(message.getId(), 0, false, message.getDialogId() == mergeDialogId ? 1 : 0); + break; + } + } + } else if ((int) dialog_id != 0) { + clearChatData(); + waitingForLoad.add(lastLoadIndex); + MessagesController.getInstance().loadMessages(dialog_id, 30, 0, date, true, 0, classGuid, 4, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + floatingDateView.setAlpha(0.0f); + floatingDateView.setTag(null); } } @@ -2813,8 +3137,8 @@ public void didPressedButton(int button) { photoEntry.caption = null; photoEntry.stickers.clear(); } - SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks); - showReplyPanel(false, null, null, null, false, true); + SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks, null); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } return; @@ -2826,7 +3150,7 @@ public void didPressedButton(int button) { @Override public View getRevealView() { - return menuItem; + return chatActivityEnterView.getAttachButton(); } @Override @@ -2837,6 +3161,11 @@ public void didSelectBot(TLRPC.User user) { chatActivityEnterView.setFieldText("@" + user.username + " "); chatActivityEnterView.openKeyboard(); } + + @Override + public void onCameraOpened() { + chatActivityEnterView.closeKeyboard(); + } }); } } @@ -2899,7 +3228,7 @@ public void needChangePanelVisibility(final boolean show) { ObjectAnimator.ofFloat(stickersPanel, "alpha", show ? 0.0f : 1.0f, show ? 1.0f : 0.0f) ); runningAnimation.setDuration(150); - runningAnimation.addListener(new AnimatorListenerAdapterProxy() { + runningAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (runningAnimation != null && runningAnimation.equals(animation)) { @@ -2934,7 +3263,7 @@ public void onItemClick(View view, int position) { TLRPC.Document document = stickersAdapter.getItem(position); if (document instanceof TLRPC.TL_document) { SendMessagesHelper.getInstance().sendSticker(document, dialog_id, replyingMessageObject); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); chatActivityEnterView.addStickerToRecent(document); } chatActivityEnterView.setFieldText(""); @@ -2959,7 +3288,7 @@ public void shareMyContact(final MessageObject messageObject) { public void onClick(DialogInterface dialogInterface, int i) { SendMessagesHelper.getInstance().sendMessage(UserConfig.getCurrentUser(), dialog_id, messageObject, null, null); moveScrollToLastMessage(); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -2993,8 +3322,8 @@ private void showGifHint() { frameLayout.addView(emojiButtonRed, index + 1, LayoutHelper.createFrame(10, 10, Gravity.BOTTOM | Gravity.LEFT, 30, 0, 0, 27)); gifHintTextView = new TextView(getParentActivity()); - gifHintTextView.setBackgroundResource(R.drawable.tooltip); - gifHintTextView.setTextColor(Theme.CHAT_GIF_HINT_TEXT_COLOR); + gifHintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); + gifHintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); gifHintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); gifHintTextView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); gifHintTextView.setText(LocaleController.getString("TapHereGifs", R.string.TapHereGifs)); @@ -3006,7 +3335,7 @@ private void showGifHint() { ObjectAnimator.ofFloat(gifHintTextView, "alpha", 0.0f, 1.0f), ObjectAnimator.ofFloat(emojiButtonRed, "alpha", 0.0f, 1.0f) ); - AnimatorSet.addListener(new AnimatorListenerAdapterProxy() { + AnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -3019,7 +3348,7 @@ public void run() { AnimatorSet.playTogether( ObjectAnimator.ofFloat(gifHintTextView, "alpha", 0.0f) ); - AnimatorSet.addListener(new AnimatorListenerAdapterProxy() { + AnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (gifHintTextView != null) { @@ -3037,6 +3366,19 @@ public void onAnimationEnd(Animator animation) { AnimatorSet.start(); } + private void openAttachMenu() { + if (getParentActivity() == null) { + return; + } + createChatAttachView(); + chatAttachAlert.loadGalleryPhotos(); + if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) { + chatActivityEnterView.closeKeyboard(); + } + chatAttachAlert.init(); + showDialog(chatAttachAlert); + } + private void checkContextBotPanel() { if (allowStickersPanel && mentionsAdapter != null && mentionsAdapter.isBotContext()) { if (!allowContextBotPanel && !allowContextBotPanelSecond) { @@ -3050,7 +3392,7 @@ private void checkContextBotPanel() { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionContainer, "alpha", 0.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -3080,7 +3422,7 @@ public void onAnimationCancel(Animator animation) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionContainer, "alpha", 0.0f, 1.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -3102,6 +3444,37 @@ public void onAnimationCancel(Animator animation) { } } + private void hideFloatingDateView(boolean animated) { + if (floatingDateView.getTag() != null && !currentFloatingDateOnScreen && (!scrollingFloatingDate || currentFloatingTopIsNotMessage)) { + floatingDateView.setTag(null); + if (animated) { + floatingDateAnimation = new AnimatorSet(); + floatingDateAnimation.setDuration(150); + floatingDateAnimation.playTogether(ObjectAnimator.ofFloat(floatingDateView, "alpha", 0.0f)); + floatingDateAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(floatingDateAnimation)) { + floatingDateAnimation = null; + } + } + }); + floatingDateAnimation.setStartDelay(500); + floatingDateAnimation.start(); + } else { + if (floatingDateAnimation != null) { + floatingDateAnimation.cancel(); + floatingDateAnimation = null; + } + floatingDateView.setAlpha(0.0f); + } + } + } + + protected void setIgnoreAttachOnPause(boolean value) { + ignoreAttachOnPause = value; + } + private void checkScrollForLoad(boolean scroll) { if (chatLayoutManager == null || paused) { return; @@ -3121,24 +3494,24 @@ private void checkScrollForLoad(boolean scroll) { loading = true; waitingForLoad.add(lastLoadIndex); if (messagesByDays.size() != 0) { - MessagesController.getInstance().loadMessages(dialog_id, 50, maxMessageId[0], !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, 50, maxMessageId[0], 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { - MessagesController.getInstance().loadMessages(dialog_id, 50, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, 50, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } else if (mergeDialogId != 0 && !endReached[1]) { loading = true; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(mergeDialogId, 50, maxMessageId[1], !cacheEndReached[1], minDate[1], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(mergeDialogId, 50, maxMessageId[1], 0, !cacheEndReached[1], minDate[1], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } if (!loadingForward && firstVisibleItem + visibleItemCount >= totalItemCount - 10) { if (mergeDialogId != 0 && !forwardEndReached[1]) { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(mergeDialogId, 50, minMessageId[1], true, maxDate[1], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(mergeDialogId, 50, minMessageId[1], 0, true, maxDate[1], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loadingForward = true; } else if (!forwardEndReached[0]) { waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(dialog_id, 50, minMessageId[0], true, maxDate[0], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, 50, minMessageId[0], 0, true, maxDate[0], classGuid, 1, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loadingForward = true; } } @@ -3194,7 +3567,7 @@ private void processSelectedAttach(int which) { } startActivityForResult(takePictureIntent, 0); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (which == attach_gallery) { if (Build.VERSION.SDK_INT >= 23 && getParentActivity().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { @@ -3205,9 +3578,9 @@ private void processSelectedAttach(int which) { fragment.setDelegate(new PhotoAlbumPickerActivity.PhotoAlbumPickerActivityDelegate() { @Override public void didSelectPhotos(ArrayList photos, ArrayList captions, ArrayList> masks, ArrayList webPhotos) { - SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks); + SendMessagesHelper.prepareSendingPhotos(photos, null, dialog_id, replyingMessageObject, captions, masks, null); SendMessagesHelper.prepareSendingPhotosSearch(webPhotos, dialog_id, replyingMessageObject); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } @@ -3226,20 +3599,19 @@ public void startPhotoSelectActivity() { startActivityForResult(chooserIntent, 1); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @Override - public boolean didSelectVideo(String path) { - if (Build.VERSION.SDK_INT >= 16) { - return !openVideoEditor(path, true, true); + public void didSelectVideo(String path, VideoEditedInfo info, long estimatedSize, long estimatedDuration, String caption) { + if (info != null) { + SendMessagesHelper.prepareSendingVideo(path, estimatedSize, estimatedDuration, info.resultWidth, info.resultHeight, info, dialog_id, replyingMessageObject, caption); } else { SendMessagesHelper.prepareSendingVideo(path, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null); - showReplyPanel(false, null, null, null, false, true); - DraftQuery.cleanDraft(dialog_id, true); - return true; } + showReplyPanel(false, null, null, null, false); + DraftQuery.cleanDraft(dialog_id, true); } }); presentFragment(fragment); @@ -3264,7 +3636,7 @@ public boolean didSelectVideo(String path) { } startActivityForResult(takeVideoIntent, 2); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (which == attach_location) { if (!AndroidUtilities.isGoogleMapsInstalled(ChatActivity.this)) { @@ -3276,7 +3648,7 @@ public boolean didSelectVideo(String path) { public void didSelectLocation(TLRPC.MessageMedia location) { SendMessagesHelper.getInstance().sendMessage(location, dialog_id, replyingMessageObject, null, null); moveScrollToLastMessage(); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); if (paused) { scrollToTopOnResume = true; @@ -3294,8 +3666,8 @@ public void didSelectLocation(TLRPC.MessageMedia location) { @Override public void didSelectFiles(DocumentSelectActivity activity, ArrayList files) { activity.finishFragment(); - SendMessagesHelper.prepareSendingDocuments(files, files, null, null, dialog_id, replyingMessageObject); - showReplyPanel(false, null, null, null, false, true); + SendMessagesHelper.prepareSendingDocuments(files, files, null, null, dialog_id, replyingMessageObject, null); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } @@ -3306,7 +3678,7 @@ public void startDocumentSelectActivity() { photoPickerIntent.setType("*/*"); startActivityForResult(photoPickerIntent, 21); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -3321,7 +3693,7 @@ public void startDocumentSelectActivity() { @Override public void didSelectAudio(ArrayList audios) { SendMessagesHelper.prepareSendingAudioDocuments(audios, dialog_id, replyingMessageObject); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } }); @@ -3338,7 +3710,7 @@ public void didSelectAudio(ArrayList audios) { intent.setType(ContactsContract.CommonDataKinds.Phone.CONTENT_TYPE); startActivityForResult(intent, 31); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -3372,7 +3744,7 @@ private void searchLinks(final CharSequence charSequence, final boolean force) { } } pendingLinkSearchString = null; - showReplyPanel(false, null, null, foundWebPage, false, true); + showReplyPanel(false, null, null, foundWebPage, false); } Utilities.searchQueue.postRunnable(new Runnable() { @Override @@ -3413,7 +3785,7 @@ public void run() { @Override public void run() { if (foundWebPage != null) { - showReplyPanel(false, null, null, foundWebPage, false, true); + showReplyPanel(false, null, null, foundWebPage, false); foundWebPage = null; } } @@ -3422,14 +3794,14 @@ public void run() { } textToCheck = TextUtils.join(" ", urls); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); String text = charSequence.toString().toLowerCase(); if (charSequence.length() < 13 || !text.contains("http://") && !text.contains("https://")) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { if (foundWebPage != null) { - showReplyPanel(false, null, null, foundWebPage, false, true); + showReplyPanel(false, null, null, foundWebPage, false); foundWebPage = null; } } @@ -3488,16 +3860,16 @@ public void run() { if (currentEncryptedChat != null && foundWebPage instanceof TLRPC.TL_webPagePending) { foundWebPage.url = req.message; } - showReplyPanel(true, null, null, foundWebPage, false, true); + showReplyPanel(true, null, null, foundWebPage, false); } else { if (foundWebPage != null) { - showReplyPanel(false, null, null, foundWebPage, false, true); + showReplyPanel(false, null, null, foundWebPage, false); foundWebPage = null; } } } else { if (foundWebPage != null) { - showReplyPanel(false, null, null, foundWebPage, false, true); + showReplyPanel(false, null, null, foundWebPage, false); foundWebPage = null; } } @@ -3524,7 +3896,7 @@ private void forwardMessages(ArrayList arrayList, boolean fromMyN } } - public void showReplyPanel(boolean show, MessageObject messageObjectToReply, ArrayList messageObjectsToForward, TLRPC.WebPage webPage, boolean cancel, boolean animated) { + public void showReplyPanel(boolean show, MessageObject messageObjectToReply, ArrayList messageObjectsToForward, TLRPC.WebPage webPage, boolean cancel) { if (chatActivityEnterView == null) { return; } @@ -3564,7 +3936,7 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr } name = chat.title; } - replyIconImageView.setImageResource(R.drawable.reply); + replyIconImageView.setImageResource(R.drawable.msg_panel_reply); replyNameTextView.setText(name); if (messageObjectToReply.messageOwner.media instanceof TLRPC.TL_messageMediaGame) { @@ -3587,9 +3959,9 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr if (foundWebPage != null) { return; } - chatActivityEnterView.setForceShowSendButton(true, animated); + chatActivityEnterView.setForceShowSendButton(true, false); ArrayList uids = new ArrayList<>(); - replyIconImageView.setImageResource(R.drawable.forward_blue); + replyIconImageView.setImageResource(R.drawable.msg_panel_forward); MessageObject object = messageObjectsToForward.get(0); if (object.isFromUser()) { uids.add(object.messageOwner.from_id); @@ -3707,7 +4079,7 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr } } } else { - replyIconImageView.setImageResource(R.drawable.link); + replyIconImageView.setImageResource(R.drawable.msg_panel_link); if (webPage instanceof TLRPC.TL_webPagePending) { replyNameTextView.setText(LocaleController.getString("GettingLinkInfo", R.string.GettingLinkInfo)); replyObjectTextView.setText(pendingLinkSearchString); @@ -3719,10 +4091,10 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr } else { replyNameTextView.setText(LocaleController.getString("LinkPreview", R.string.LinkPreview)); } - if (webPage.description != null) { - replyObjectTextView.setText(webPage.description); - } else if (webPage.title != null && webPage.site_name != null) { + if (webPage.title != null) { replyObjectTextView.setText(webPage.title); + } else if (webPage.description != null) { + replyObjectTextView.setText(webPage.description); } else if (webPage.author != null) { replyObjectTextView.setText(webPage.author); } else { @@ -3753,7 +4125,7 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr } replyNameTextView.setLayoutParams(layoutParams1); replyObjectTextView.setLayoutParams(layoutParams2); - chatActivityEnterView.showTopView(animated, openKeyboard); + chatActivityEnterView.showTopView(false, openKeyboard); } else { if (replyingMessageObject == null && forwardingMessages == null && foundWebPage == null) { return; @@ -3766,15 +4138,15 @@ public void showReplyPanel(boolean show, MessageObject messageObjectToReply, Arr foundWebPage = null; chatActivityEnterView.setWebPage(null, !cancel); if (webPage != null && (replyingMessageObject != null || forwardingMessages != null)) { - showReplyPanel(true, replyingMessageObject, forwardingMessages, null, false, true); + showReplyPanel(true, replyingMessageObject, forwardingMessages, null, false); return; } } if (forwardingMessages != null) { forwardMessages(forwardingMessages, false); } - chatActivityEnterView.setForceShowSendButton(false, animated); - chatActivityEnterView.hideTopView(animated); + chatActivityEnterView.setForceShowSendButton(false, false); + chatActivityEnterView.hideTopView(false); chatActivityEnterView.setReplyingMessageObject(null); replyingMessageObject = null; forwardingMessages = null; @@ -3826,6 +4198,7 @@ private void clearChatData() { waitingForReplyMessageLoad = false; startLoadFromMessageId = 0; last_message_id = 0; + createUnreadMessageAfterId = 0; needSelectFromMessageId = false; chatAdapter.notifyDataSetChanged(); } @@ -3842,7 +4215,7 @@ private void scrollToLastMessage(boolean pagedown) { } else { clearChatData(); waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(dialog_id, 30, 0, true, 0, classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, 30, 0, 0, true, 0, classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } } @@ -3853,6 +4226,11 @@ private void updateMessagesVisisblePart() { int count = chatListView.getChildCount(); int additionalTop = chatActivityEnterView.isTopViewVisible() ? AndroidUtilities.dp(48) : 0; int height = chatListView.getMeasuredHeight(); + int minPositionHolder = Integer.MAX_VALUE; + int minPositionDateHolder = Integer.MAX_VALUE; + View minDateChild = null; + View minChild = null; + View minMessageChild = null; for (int a = 0; a < count; a++) { View view = chatListView.getChildAt(a); if (view instanceof ChatMessageCell) { @@ -3866,6 +4244,69 @@ private void updateMessagesVisisblePart() { } messageCell.setVisiblePart(viewTop, viewBottom - viewTop); } + if (view.getBottom() <= chatListView.getPaddingTop()) { + continue; + } + int position = view.getBottom(); + if (position < minPositionHolder) { + minPositionHolder = position; + if (view instanceof ChatMessageCell || view instanceof ChatActionCell) { + minMessageChild = view; + } + minChild = view; + } + if (view instanceof ChatActionCell && ((ChatActionCell) view).getMessageObject().isDateObject) { + if (view.getAlpha() != 1.0f) { + view.setAlpha(1.0f); + } + if (position < minPositionDateHolder) { + minPositionDateHolder = position; + minDateChild = view; + } + } + } + if (minMessageChild != null) { + MessageObject messageObject; + if (minMessageChild instanceof ChatMessageCell) { + messageObject = ((ChatMessageCell) minMessageChild).getMessageObject(); + } else { + messageObject = ((ChatActionCell) minMessageChild).getMessageObject(); + } + floatingDateView.setCustomDate(messageObject.messageOwner.date); + } + currentFloatingDateOnScreen = false; + currentFloatingTopIsNotMessage = !(minChild instanceof ChatMessageCell || minChild instanceof ChatActionCell); + if (minDateChild != null) { + if (minDateChild.getTop() > chatListView.getPaddingTop() || currentFloatingTopIsNotMessage) { + if (minDateChild.getAlpha() != 1.0f) { + minDateChild.setAlpha(1.0f); + } + hideFloatingDateView(!currentFloatingTopIsNotMessage); + } else { + if (minDateChild.getAlpha() != 0.0f) { + minDateChild.setAlpha(0.0f); + } + if (floatingDateAnimation != null) { + floatingDateAnimation.cancel(); + floatingDateAnimation = null; + } + if (floatingDateView.getTag() == null) { + floatingDateView.setTag(1); + } + if (floatingDateView.getAlpha() != 1.0f) { + floatingDateView.setAlpha(1.0f); + } + currentFloatingDateOnScreen = true; + } + int offset = minDateChild.getBottom() - chatListView.getPaddingTop(); + if (offset > floatingDateView.getMeasuredHeight() && offset < floatingDateView.getMeasuredHeight() * 2) { + floatingDateView.setTranslationY(-floatingDateView.getMeasuredHeight() * 2 + offset); + } else { + floatingDateView.setTranslationY(0); + } + } else { + hideFloatingDateView(true); + floatingDateView.setTranslationY(0); } } @@ -3919,7 +4360,7 @@ private void scrollToMessageId(int id, int fromMessageId, boolean select, int lo if (messages.get(messages.size() - 1) == object) { chatLayoutManager.scrollToPositionWithOffset(0, -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); } else { - chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(object) - 1, -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(object) - 1, -AndroidUtilities.dp(7) + yOffset); } updateVisibleRows(); boolean found = false; @@ -3970,7 +4411,7 @@ private void scrollToMessageId(int id, int fromMessageId, boolean select, int lo scrollToMessagePosition = -10000; startLoadFromMessageId = id; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(loadIndex == 0 ? dialog_id : mergeDialogId, AndroidUtilities.isTablet() ? 30 : 20, startLoadFromMessageId, 0, true, 0, classGuid, 3, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); //emptyViewContainer.setVisibility(View.INVISIBLE); } returnToMessageId = fromMessageId; @@ -4012,7 +4453,7 @@ private void showPagedownButton(boolean show, boolean animated) { } if (animated) { pagedownButtonAnimation = ObjectAnimator.ofFloat(pagedownButton, "translationY", AndroidUtilities.dp(100)).setDuration(200); - pagedownButtonAnimation.addListener(new AnimatorListenerAdapterProxy() { + pagedownButtonAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { pagedownButtonCounter.setVisibility(View.INVISIBLE); @@ -4073,10 +4514,40 @@ public void onRequestPermissionsResultFragment(int requestCode, String[] permiss } if (requestCode == 17 && chatAttachAlert != null) { chatAttachAlert.checkCamera(false); + } else if (requestCode == 21) { + if (getParentActivity() == null) { + return; + } + if (grantResults != null && grantResults.length != 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("PermissionNoAudioVideo", R.string.PermissionNoAudioVideo)); + builder.setNegativeButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), new DialogInterface.OnClickListener() { + @TargetApi(Build.VERSION_CODES.GINGERBREAD) + @Override + public void onClick(DialogInterface dialog, int which) { + try { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); + getParentActivity().startActivity(intent); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.show(); + } } else if (requestCode == 19 && grantResults != null && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { processSelectedAttach(attach_photo); } else if (requestCode == 20 && grantResults != null && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { processSelectedAttach(attach_video); + } else if (requestCode == 101 && currentUser != null) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + } else { + VoIPHelper.permissionDenied(getParentActivity(), null); + } } } @@ -4084,9 +4555,6 @@ private void checkActionBarMenu() { if (currentEncryptedChat != null && !(currentEncryptedChat instanceof TLRPC.TL_encryptedChat) || currentChat != null && ChatObject.isNotInChat(currentChat) || currentUser != null && UserObject.isDeleted(currentUser)) { - if (menuItem != null) { - menuItem.setVisibility(View.GONE); - } if (timeItem2 != null) { timeItem2.setVisibility(View.GONE); } @@ -4094,9 +4562,6 @@ private void checkActionBarMenu() { avatarContainer.hideTimeItem(); } } else { - if (menuItem != null) { - menuItem.setVisibility(View.VISIBLE); - } if (timeItem2 != null) { timeItem2.setVisibility(View.VISIBLE); } @@ -4129,7 +4594,7 @@ private int getMessageType(MessageObject messageObject) { } else { if (messageObject.type == 6) { return -1; - } else if (messageObject.type == 10 || messageObject.type == 11) { + } else if (messageObject.type == 10 || messageObject.type == 11 || messageObject.type == 16) { if (messageObject.getId() == 0) { return -1; } @@ -4166,7 +4631,9 @@ private int getMessageType(MessageObject messageObject) { if (messageObject.getDocument() != null) { String mime = messageObject.getDocument().mime_type; if (mime != null) { - if (mime.endsWith("/xml")) { + if (messageObject.getDocumentName().endsWith("attheme")) { + return 10; + } else if (mime.endsWith("/xml")) { return 5; } else if (mime.endsWith("/png") || mime.endsWith("/jpg") || mime.endsWith("/jpeg")) { return 6; @@ -4257,6 +4724,9 @@ private void addToSelectedMessages(MessageObject messageObject) { cantDeleteMessagesCount--; } } else { + if (selectedMessagesIds[0].size() + selectedMessagesIds[1].size() >= 100) { + return; + } selectedMessagesIds[index].put(messageObject.getId(), messageObject); if (messageObject.type == 0 || messageObject.caption != null) { selectedMessagesCanCopyIds[index].put(messageObject.getId(), messageObject); @@ -4310,7 +4780,7 @@ private void addToSelectedMessages(MessageObject messageObject) { ); } replyButtonAnimation.setDuration(100); - replyButtonAnimation.addListener(new AnimatorListenerAdapterProxy() { + replyButtonAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (replyButtonAnimation != null && replyButtonAnimation.equals(animation)) { @@ -4371,7 +4841,7 @@ private void updateTitle() { } else if (currentUser != null) { if (currentUser.self) { avatarContainer.setTitle(LocaleController.getString("ChatYourSelfName", R.string.ChatYourSelfName)); - } else if (currentUser.id / 1000 != 777 && currentUser.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(currentUser.id) == null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts())) { + } else if (currentUser.id != 4244000 && currentUser.id / 1000 != 777 && currentUser.id / 1000 != 333 && ContactsController.getInstance().contactsDict.get(currentUser.id) == null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts())) { if (currentUser.phone != null && currentUser.phone.length() != 0) { avatarContainer.setTitle(PhoneFormat.getInstance().format("+" + currentUser.phone)); } else { @@ -4421,10 +4891,10 @@ private void updateTitleIcons() { if (avatarContainer == null) { return; } - int rightIcon = MessagesController.getInstance().isDialogMuted(dialog_id) ? R.drawable.mute_fixed : 0; - avatarContainer.setTitleIcons(currentEncryptedChat != null ? R.drawable.ic_lock_header : 0, rightIcon); + Drawable rightIcon = MessagesController.getInstance().isDialogMuted(dialog_id) ? Theme.chat_muteIconDrawable : null; + avatarContainer.setTitleIcons(currentEncryptedChat != null ? Theme.chat_lockIconDrawable : null, rightIcon); if (muteItem != null) { - if (rightIcon != 0) { + if (rightIcon != null) { muteItem.setText(LocaleController.getString("UnmuteNotifications", R.string.UnmuteNotifications)); } else { muteItem.setText(LocaleController.getString("MuteNotifications", R.string.MuteNotifications)); @@ -4469,14 +4939,14 @@ public void didFinishEditVideo(String videoPath, long startTime, long endTime, i videoEditedInfo.resultHeight = resultHeight; videoEditedInfo.originalPath = videoPath; SendMessagesHelper.prepareSendingVideo(videoPath, estimatedSize, estimatedDuration, resultWidth, resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, caption); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } }); if (parentLayout == null || !fragment.onFragmentCreate()) { SendMessagesHelper.prepareSendingVideo(videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); return false; } @@ -4516,14 +4986,14 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } arrayList.add(new MediaController.PhotoEntry(0, 0, 0, currentPicturePath, orientation, false)); PhotoViewer.getInstance().openPhotoForSelect(arrayList, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { @Override - public void sendButtonPressed(int index) { - sendMedia((MediaController.PhotoEntry) arrayList.get(0), false); + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { + sendMedia((MediaController.PhotoEntry) arrayList.get(0), null); } }, this); AndroidUtilities.addMediaToGallery(currentPicturePath); @@ -4539,7 +5009,7 @@ public void sendButtonPressed(int index) { try { videoPath = AndroidUtilities.getPath(uri); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (videoPath == null) { showAttachmentError(); @@ -4554,13 +5024,13 @@ public void sendButtonPressed(int index) { SendMessagesHelper.prepareSendingVideo(videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null); } } else { - SendMessagesHelper.prepareSendingPhoto(null, uri, dialog_id, replyingMessageObject, null, null); + SendMessagesHelper.prepareSendingPhoto(null, uri, dialog_id, replyingMessageObject, null, null, null); } - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } else if (requestCode == 2) { String videoPath = null; - FileLog.d("tmessages", "pic path " + currentPicturePath); + FileLog.d("pic path " + currentPicturePath); if (data != null && currentPicturePath != null) { if (new File(currentPicturePath).exists()) { data = null; @@ -4569,9 +5039,9 @@ public void sendButtonPressed(int index) { if (data != null) { Uri uri = data.getData(); if (uri != null) { - FileLog.d("tmessages", "video record uri " + uri.toString()); + FileLog.d("video record uri " + uri.toString()); videoPath = AndroidUtilities.getPath(uri); - FileLog.d("tmessages", "resolved path = " + videoPath); + FileLog.d("resolved path = " + videoPath); if (!(new File(videoPath).exists())) { videoPath = currentPicturePath; } @@ -4596,7 +5066,7 @@ public void sendButtonPressed(int index) { } } else { SendMessagesHelper.prepareSendingVideo(videoPath, 0, 0, 0, 0, null, dialog_id, replyingMessageObject, null); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } } else if (requestCode == 21) { @@ -4617,7 +5087,7 @@ public void sendButtonPressed(int index) { uri = Uri.parse(secondExtraction); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } String tempPath = AndroidUtilities.getPath(uri); @@ -4630,8 +5100,8 @@ public void sendButtonPressed(int index) { showAttachmentError(); return; } - SendMessagesHelper.prepareSendingDocument(tempPath, originalPath, null, null, dialog_id, replyingMessageObject); - showReplyPanel(false, null, null, null, false, true); + SendMessagesHelper.prepareSendingDocument(tempPath, originalPath, null, null, dialog_id, replyingMessageObject, null); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } else if (requestCode == 31) { if (data == null || data.getData() == null) { @@ -4655,7 +5125,7 @@ public void sendButtonPressed(int index) { SendMessagesHelper.getInstance().sendMessage(user, dialog_id, replyingMessageObject, null, null); } if (sent) { - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } } @@ -4665,7 +5135,7 @@ public void sendButtonPressed(int index) { c.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -4689,6 +5159,7 @@ private void removeUnreadPlane() { forwardEndReached[0] = forwardEndReached[1] = true; first_unread_id = 0; last_message_id = 0; + createUnreadMessageAfterId = 0; unread_to_load = 0; removeMessageObject(unreadMessageObject); unreadMessageObject = null; @@ -4719,10 +5190,19 @@ public void didReceivedNotification(int id, final Object... args) { if (waitingForReplyMessageLoad) { boolean found = false; for (int a = 0; a < messArr.size(); a++) { - if (messArr.get(a).getId() == startLoadFromMessageId) { + MessageObject obj = messArr.get(a); + if (obj.getId() == startLoadFromMessageId) { found = true; break; } + if (a + 1 < messArr.size()) { + MessageObject obj2 = messArr.get(a + 1); + if (obj.getId() >= startLoadFromMessageId && obj2.getId() < startLoadFromMessageId) { + startLoadFromMessageId = obj.getId(); + found = true; + break; + } + } } if (!found) { startLoadFromMessageId = 0; @@ -4730,7 +5210,9 @@ public void didReceivedNotification(int id, final Object... args) { } int startLoadFrom = startLoadFromMessageId; boolean needSelect = needSelectFromMessageId; + int unreadAfterId = createUnreadMessageAfterId; clearChatData(); + createUnreadMessageAfterId = unreadAfterId; startLoadFromMessageId = startLoadFrom; needSelectFromMessageId = needSelect; } @@ -4743,12 +5225,35 @@ public void didReceivedNotification(int id, final Object... args) { int fnid = (Integer) args[4]; int last_unread_date = (Integer) args[7]; int load_type = (Integer) args[8]; + int loaded_max_id = (Integer) args[12]; + if (load_type == 4) { + startLoadFromMessageId = loaded_max_id; + + for (int a = messArr.size() - 1; a > 0; a--) { + MessageObject obj = messArr.get(a); + if (obj.type < 0 && obj.getId() == startLoadFromMessageId) { + startLoadFromMessageId = messArr.get(a - 1).getId(); + break; + } + } + } boolean wasUnread = false; if (fnid != 0) { - first_unread_id = fnid; last_message_id = (Integer) args[5]; - unread_to_load = (Integer) args[6]; - } else if (startLoadFromMessageId != 0 && load_type == 3) { + if (load_type == 3) { + if (loadingFromOldPosition) { + unread_to_load = (Integer) args[6]; + if (unread_to_load != 0) { + createUnreadMessageAfterId = fnid; + } + loadingFromOldPosition = false; + } + first_unread_id = 0; + } else { + first_unread_id = fnid; + unread_to_load = (Integer) args[6]; + } + } else if (startLoadFromMessageId != 0 && (load_type == 3 || load_type == 4)) { last_message_id = (Integer) args[5]; } int newRowsCount = 0; @@ -4854,6 +5359,7 @@ public void run() { MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.type = 10; dateObj.contentType = 1; + dateObj.isDateObject = true; if (load_type == 1) { messages.add(0, dateObj); } else { @@ -4877,6 +5383,26 @@ public void run() { forwardEndReached[loadIndex] = true; } + MessageObject prevObj; + if (currentEncryptedChat == null) { + if (createUnreadMessageAfterId != 0 && load_type != 1 && a + 1 < messArr.size()) { + prevObj = messArr.get(a + 1); + if (obj.isOut() || prevObj.getId() >= createUnreadMessageAfterId) { + prevObj = null; + } + } else { + prevObj = null; + } + } else { + if (createUnreadMessageAfterId != 0 && load_type != 1 && a - 1 >= 0) { + prevObj = messArr.get(a - 1); + if (obj.isOut() || prevObj.getId() >= createUnreadMessageAfterId) { + prevObj = null; + } + } else { + prevObj = null; + } + } if (load_type == 2 && obj.getId() == first_unread_id) { if (approximateHeightSum > AndroidUtilities.displaySize.y / 2 || !forwardEndReached[0]) { TLRPC.Message dateMsg = new TLRPC.Message(); @@ -4891,7 +5417,7 @@ public void run() { scrollToMessagePosition = -10000; newRowsCount++; } - } else if (load_type == 3 && obj.getId() == startLoadFromMessageId) { + } else if ((load_type == 3 || load_type == 4) && obj.getId() == startLoadFromMessageId) { if (needSelectFromMessageId) { highlightMessageId = obj.getId(); } else { @@ -4903,20 +5429,32 @@ public void run() { scrollToMessagePosition = -9000; } } - } - if (load_type == 0 && newRowsCount == 0) { - loadsCount--; - } - + if (load_type != 2 && unreadMessageObject == null && createUnreadMessageAfterId != 0 && + (currentEncryptedChat == null && !obj.isOut() && obj.getId() >= createUnreadMessageAfterId || currentEncryptedChat != null && !obj.isOut() && obj.getId() <= createUnreadMessageAfterId) && + (load_type == 1 || prevObj != null)) { + TLRPC.Message dateMsg = new TLRPC.Message(); + dateMsg.message = ""; + dateMsg.id = 0; + MessageObject dateObj = new MessageObject(dateMsg, null, false); + dateObj.type = 6; + dateObj.contentType = 2; + if (load_type == 1) { + messages.add(1, dateObj); + } else { + messages.add(messages.size() - 1, dateObj); + } + unreadMessageObject = dateObj; + newRowsCount++; + } + } + if (load_type == 0 && newRowsCount == 0) { + loadsCount--; + } + if (forwardEndReached[loadIndex] && loadIndex != 1) { first_unread_id = 0; last_message_id = 0; - } - - if (loadsCount <= 2) { - if (!isCache) { - updateSpamView(); - } + createUnreadMessageAfterId = 0; } if (load_type == 1) { @@ -4925,6 +5463,7 @@ public void run() { if (loadIndex != 1) { first_unread_id = 0; last_message_id = 0; + createUnreadMessageAfterId = 0; chatAdapter.notifyItemRemoved(chatAdapter.getItemCount() - 1); newRowsCount--; } @@ -4939,14 +5478,16 @@ public void run() { View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); } - chatAdapter.notifyItemRangeInserted(chatAdapter.getItemCount() - 1, newRowsCount); + int insertStart = chatAdapter.getItemCount() - 1; + chatAdapter.notifyItemChanged(insertStart); + chatAdapter.notifyItemRangeInserted(insertStart, newRowsCount); if (firstVisPos != RecyclerView.NO_POSITION) { chatLayoutManager.scrollToPositionWithOffset(firstVisPos, top); } } loadingForward = false; } else { - if (messArr.size() < count && load_type != 3) { + if (messArr.size() < count && load_type != 3 && load_type != 4) { if (isCache) { if (currentEncryptedChat != null || isBroadcast) { endReached[loadIndex] = true; @@ -4954,8 +5495,8 @@ public void run() { if (load_type != 2) { cacheEndReached[loadIndex] = true; } - } else if (load_type != 2) { - endReached[loadIndex] = true;// =TODO if < 7 from unread + } else if (load_type != 2 || messArr.size() == 0 && messages.isEmpty()) { + endReached[loadIndex] = true;//TODO if < 7 from unread } } loading = false; @@ -4966,7 +5507,10 @@ public void run() { chatAdapter.notifyDataSetChanged(); if (scrollToMessage != null) { int yOffset; - if (scrollToMessagePosition == -9000) { + if (startLoadFromMessageOffset != Integer.MAX_VALUE) { + yOffset = chatListView.getHeight() - startLoadFromMessageOffset + AndroidUtilities.dp(4); + startLoadFromMessageOffset = Integer.MAX_VALUE; + } else if (scrollToMessagePosition == -9000) { yOffset = Math.max(0, (chatListView.getHeight() - scrollToMessage.getApproximateHeight()) / 2); } else if (scrollToMessagePosition == -10000) { yOffset = 0; @@ -4977,12 +5521,16 @@ public void run() { if (messages.get(messages.size() - 1) == scrollToMessage || messages.get(messages.size() - 2) == scrollToMessage) { chatLayoutManager.scrollToPositionWithOffset((chatAdapter.isBot ? 1 : 0), -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); } else { - chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(scrollToMessage) - 1, -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(chatAdapter.messagesStartRow + messages.size() - messages.indexOf(scrollToMessage) - 1, -AndroidUtilities.dp(7) + yOffset - (scrollToMessage == unreadMessageObject ? chatListView.getPaddingTop() : 0)); } } chatListView.invalidate(); if (scrollToMessagePosition == -10000 || scrollToMessagePosition == -9000) { showPagedownButton(true, true); + if (load_type == 3 && unread_to_load != 0) { + pagedownButtonCounter.setVisibility(View.VISIBLE); + pagedownButtonCounter.setText(String.format("%d", newUnreadMessageCount = unread_to_load)); + } } scrollToMessagePosition = -10000; scrollToMessage = null; @@ -5000,7 +5548,9 @@ public void run() { View firstVisView = chatLayoutManager.findViewByPosition(firstVisPos); int top = ((firstVisView == null) ? 0 : firstVisView.getTop()) - chatListView.getPaddingTop(); if (newRowsCount - (end ? 1 : 0) > 0) { - chatAdapter.notifyItemRangeInserted((chatAdapter.isBot ? 2 : 1) + (end ? 0 : 1), newRowsCount - (end ? 1 : 0)); + int insertStart = (chatAdapter.isBot ? 2 : 1) + (end ? 0 : 1); + chatAdapter.notifyItemChanged(insertStart); + chatAdapter.notifyItemRangeInserted(insertStart, newRowsCount - (end ? 1 : 0)); } if (firstVisPos != -1) { chatLayoutManager.scrollToPositionWithOffset(firstVisPos + newRowsCount - (end ? 1 : 0), top); @@ -5174,6 +5724,8 @@ obj.messageOwner.action.encryptedAction instanceof TLRPC.TL_decryptedMessageActi messageObject.generatePinMessageText(null, null); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { messageObject.generateGameMessageText(null); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { + messageObject.generatePaymentSentMessageText(null); } } } else if (inlineReturn != 0) { @@ -5257,10 +5809,15 @@ public void run() { unread_to_load++; currentMarkAsRead = true; } + newUnreadMessageCount++; if (obj.type == 10 || obj.type == 11) { updateChat = true; } } + if (newUnreadMessageCount != 0 && pagedownButtonCounter != null) { + pagedownButtonCounter.setVisibility(View.VISIBLE); + pagedownButtonCounter.setText(String.format("%d", newUnreadMessageCount)); + } if (currentMarkAsRead) { if (paused) { @@ -5286,6 +5843,9 @@ public void run() { if (a == 0) { if (obj.messageOwner.id < 0) { placeToPaste = 0; + if (BuildVars.DEBUG_PRIVATE_VERSION && obj.isVideoVoice()) { + animatingMessageObjects.add(obj); //TODO + } } else { if (!messages.isEmpty()) { int size = messages.size(); @@ -5389,6 +5949,7 @@ public void run() { MessageObject dateObj = new MessageObject(dateMsg, null, false); dateObj.type = 10; dateObj.contentType = 1; + dateObj.isDateObject = true; messages.add(placeToPaste, dateObj); addedCount++; } @@ -5453,7 +6014,9 @@ public void run() { chatAdapter.updateRowWithMessageObject(unreadMessageObject); } if (addedCount != 0) { - chatAdapter.notifyItemRangeInserted(chatAdapter.getItemCount() - placeToPaste, addedCount); + int insertStart = chatAdapter.getItemCount() - placeToPaste; + chatAdapter.notifyItemChanged(insertStart - 1); + chatAdapter.notifyItemRangeInserted(insertStart, addedCount); } } else { scrollToTopOnResume = true; @@ -5562,6 +6125,9 @@ public void run() { } break; } + if (inbox.size() != 0) { + removeUnreadPlane(); + } if (updated) { updateVisibleRows(); } @@ -5627,7 +6193,7 @@ public void run() { maxDate[0] = maxDate[1] = Integer.MIN_VALUE; minDate[0] = minDate[1] = 0; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(dialog_id, 30, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, 30, 0, 0, !cacheEndReached[0], minDate[0], classGuid, 0, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); loading = true; } else { if (botButtons != null) { @@ -5680,7 +6246,7 @@ public void run() { obj.messageOwner.params != null && obj.messageOwner.params.containsKey("query_id") || newMsgObj.media != null && obj.messageOwner.media != null && !newMsgObj.media.getClass().equals(obj.messageOwner.media.getClass()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } obj.messageOwner = newMsgObj; obj.generateThumbs(true); @@ -5785,7 +6351,7 @@ public void run() { hasBotsCommands = false; botInfo.clear(); botsCount = 0; - URLSpanBotCommand.enabled = !info.bot_info.isEmpty(); + URLSpanBotCommand.enabled = !info.bot_info.isEmpty() && currentChat != null && currentChat.megagroup; botsCount = info.bot_info.size(); for (int a = 0; a < info.bot_info.size(); a++) { TLRPC.BotInfo bot = info.bot_info.get(a); @@ -5844,6 +6410,9 @@ public void run() { } } else if (id == NotificationCenter.contactsDidLoaded) { updateContactStatus(); + if (currentEncryptedChat != null) { + updateSpamView(); + } if (avatarContainer != null) { avatarContainer.updateSubtitle(); } @@ -5973,7 +6542,7 @@ public void run() { startLoadFromMessageId = 0; needSelectFromMessageId = false; waitingForLoad.add(lastLoadIndex); - MessagesController.getInstance().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); + MessagesController.getInstance().loadMessages(dialog_id, AndroidUtilities.isTablet() ? 30 : 20, 0, 0, true, 0, classGuid, 2, 0, ChatObject.isChannel(currentChat), lastLoadIndex++); } else { if (progressView != null) { progressView.setVisibility(View.INVISIBLE); @@ -6037,6 +6606,17 @@ public void run() { } } } + count = mentionListView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = mentionListView.getChildAt(a); + if (view instanceof ContextLinkCell) { + ContextLinkCell cell = (ContextLinkCell) view; + MessageObject messageObject1 = cell.getMessageObject(); + if (messageObject1 != null && (messageObject1.isVoice() || messageObject1.isMusic())) { + cell.updateButtonState(false); + } + } + } } } else if (id == NotificationCenter.updateMessageMedia) { MessageObject messageObject = (MessageObject) args[0]; @@ -6072,6 +6652,8 @@ public void run() { messageObject.replyMessageObject = old.replyMessageObject; if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { messageObject.generateGameMessageText(null); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { + messageObject.generatePaymentSentMessageText(null); } } messageObject.messageOwner.attachPath = old.messageOwner.attachPath; @@ -6164,7 +6746,7 @@ public void run() { HashMap hashMap = (HashMap) args[0]; for (TLRPC.WebPage webPage : hashMap.values()) { if (webPage.id == foundWebPage.id) { - showReplyPanel(!(webPage instanceof TLRPC.TL_webPageEmpty), null, null, webPage, false, true); + showReplyPanel(!(webPage instanceof TLRPC.TL_webPageEmpty), null, null, webPage, false); break; } } @@ -6215,12 +6797,12 @@ public void run() { if (preferences.getInt("answered_" + dialog_id, 0) != botButtons.getId() && (replyingMessageObject == null || chatActivityEnterView.getFieldText() == null)) { botReplyButtons = botButtons; chatActivityEnterView.setButtons(botButtons); - showReplyPanel(true, botButtons, null, null, false, true); + showReplyPanel(true, botButtons, null, null, false); } } else { if (replyingMessageObject != null && botReplyButtons == replyingMessageObject) { botReplyButtons = null; - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); } chatActivityEnterView.setButtons(botButtons); } @@ -6230,7 +6812,7 @@ public void run() { if (chatActivityEnterView != null) { if (replyingMessageObject != null && botReplyButtons == replyingMessageObject) { botReplyButtons = null; - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); } chatActivityEnterView.setButtons(botButtons); } @@ -6275,6 +6857,30 @@ public void run() { if (did == dialog_id) { applyDraftMaybe(true); } + } else if (id == NotificationCenter.userInfoDidLoaded) { + Integer uid = (Integer) args[0]; + if (currentUser != null && currentUser.id == uid) { + TLRPC.TL_userFull userFull = (TLRPC.TL_userFull) args[1]; + if (headerItem != null) { + if (userFull.phone_calls_available) { + headerItem.showSubItem(call); + } else { + headerItem.hideSubItem(call); + } + } + } + } else if (id == NotificationCenter.didSetNewWallpapper) { + if (fragmentView != null) { + ((SizeNotifierFrameLayout) fragmentView).setBackgroundImage(Theme.getCachedWallpaper()); + progressView2.getBackground().setColorFilter(Theme.colorFilter); + if (emptyView != null) { + emptyView.getBackground().setColorFilter(Theme.colorFilter); + } + if (bigEmptyView != null) { + bigEmptyView.getBackground().setColorFilter(Theme.colorFilter); + } + chatListView.invalidateViews(); + } } } @@ -6374,6 +6980,24 @@ protected void onDialogDismiss(Dialog dialog) { } } + @Override + public boolean extendActionMode(Menu menu) { + if (chatActivityEnterView.getSelectionLength() == 0 || menu.findItem(android.R.id.copy) == null) { + return true; + } + if (Build.VERSION.SDK_INT >= 23) { + menu.removeItem(android.R.id.shareText); + } + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(LocaleController.getString("Bold", R.string.Bold)); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menu.add(R.id.menu_groupbolditalic, R.id.menu_bold, 6, stringBuilder); + stringBuilder = new SpannableStringBuilder(LocaleController.getString("Italic", R.string.Italic)); + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + menu.add(R.id.menu_groupbolditalic, R.id.menu_italic, 7, stringBuilder); + menu.add(R.id.menu_groupbolditalic, R.id.menu_regular, 8, LocaleController.getString("Regular", R.string.Regular)); + return true; + } + private void updateBottomOverlay() { if (bottomOverlayChatText == null) { return; @@ -6404,7 +7028,7 @@ private void updateBottomOverlay() { if (chatActivityEnterView != null) { if (replyingMessageObject != null && botReplyButtons == replyingMessageObject) { botReplyButtons = null; - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); } chatActivityEnterView.setButtons(botButtons, false); } @@ -6425,6 +7049,12 @@ private void updateBottomOverlay() { bottomOverlayChat.setVisibility(View.INVISIBLE); chatActivityEnterView.setFieldFocused(false); chatActivityEnterView.setVisibility(View.INVISIBLE); + if (chatActivityEnterView.isTopViewVisible()) { + topViewWasVisible = 1; + chatActivityEnterView.hideTopView(false); + } else { + topViewWasVisible = 2; + } } else { searchContainer.setVisibility(View.INVISIBLE); if (currentChat != null && (ChatObject.isNotInChat(currentChat) || !ChatObject.canWriteToChat(currentChat)) || @@ -6449,6 +7079,10 @@ private void updateBottomOverlay() { muteItem.setVisibility(View.VISIBLE); } } + if (topViewWasVisible == 1) { + chatActivityEnterView.showTopView(false, false); + topViewWasVisible = 0; + } } checkRaiseSensors(); } @@ -6469,7 +7103,7 @@ public void showAlert(String name, String message) { alertViewAnimator = new AnimatorSet(); alertViewAnimator.playTogether(ObjectAnimator.ofFloat(alertView, "translationY", 0)); alertViewAnimator.setDuration(200); - alertViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + alertViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (alertViewAnimator != null && alertViewAnimator.equals(animation)) { @@ -6506,7 +7140,7 @@ public void run() { alertViewAnimator = new AnimatorSet(); alertViewAnimator.playTogether(ObjectAnimator.ofFloat(alertView, "translationY", -AndroidUtilities.dp(50))); alertViewAnimator.setDuration(200); - alertViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + alertViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (alertViewAnimator != null && alertViewAnimator.equals(animation)) { @@ -6539,7 +7173,7 @@ private void hidePinnedMessageView(boolean animated) { pinnedMessageViewAnimator = new AnimatorSet(); pinnedMessageViewAnimator.playTogether(ObjectAnimator.ofFloat(pinnedMessageView, "translationY", -AndroidUtilities.dp(50))); pinnedMessageViewAnimator.setDuration(200); - pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { @@ -6591,7 +7225,7 @@ private void updatePinnedMessageView(boolean animated) { pinnedMessageViewAnimator = new AnimatorSet(); pinnedMessageViewAnimator.playTogether(ObjectAnimator.ofFloat(pinnedMessageView, "translationY", 0)); pinnedMessageViewAnimator.setDuration(200); - pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + pinnedMessageViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (pinnedMessageViewAnimator != null && pinnedMessageViewAnimator.equals(animation)) { @@ -6662,20 +7296,14 @@ private void updateSpamView() { return; } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - boolean show = preferences.getInt("spam3_" + dialog_id, 0) == 2; - if (show) { - if (messages.isEmpty()) { + boolean show; + if (currentEncryptedChat != null) { + show = !(currentEncryptedChat.admin_id == UserConfig.getClientUserId() || ContactsController.getInstance().isLoadingContacts()) && ContactsController.getInstance().contactsDict.get(currentUser.id) == null; + if (show && preferences.getInt("spam3_" + dialog_id, 0) == 1) { show = false; - } else { - int count = messages.size() - 1; - for (int a = count; a >= Math.max(count - 50, 0); a--) { - MessageObject messageObject = messages.get(a); - if (messageObject.isOut()) { - show = false; - break; - } - } } + } else { + show = preferences.getInt("spam3_" + dialog_id, 0) == 2; } if (!show) { if (reportSpamView.getTag() == null) { @@ -6687,7 +7315,7 @@ private void updateSpamView() { reportSpamViewAnimator = new AnimatorSet(); reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(reportSpamView, "translationY", -AndroidUtilities.dp(50))); reportSpamViewAnimator.setDuration(200); - reportSpamViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { @@ -6715,7 +7343,7 @@ public void onAnimationCancel(Animator animation) { reportSpamViewAnimator = new AnimatorSet(); reportSpamViewAnimator.playTogether(ObjectAnimator.ofFloat(reportSpamView, "translationY", 0)); reportSpamViewAnimator.setDuration(200); - reportSpamViewAnimator.addListener(new AnimatorListenerAdapterProxy() { + reportSpamViewAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (reportSpamViewAnimator != null && reportSpamViewAnimator.equals(animation)) { @@ -6748,7 +7376,7 @@ private void updateContactStatus() { currentUser = user; } if (currentEncryptedChat != null && !(currentEncryptedChat instanceof TLRPC.TL_encryptedChat) - || currentUser.id / 1000 == 333 || currentUser.id / 1000 == 777 + || currentUser.id == 4244000 || currentUser.id / 1000 == 333 || currentUser.id / 1000 == 777 || UserObject.isDeleted(currentUser) || ContactsController.getInstance().isLoadingContacts() || (currentUser.phone != null && currentUser.phone.length() != 0 && ContactsController.getInstance().contactsDict.get(currentUser.id) != null && (ContactsController.getInstance().contactsDict.size() != 0 || !ContactsController.getInstance().isLoadingContacts()))) { @@ -6784,10 +7412,16 @@ public void run() { } if (chatListView.getPaddingTop() != AndroidUtilities.dp(52) && (pinnedMessageView != null && pinnedMessageView.getTag() == null || reportSpamView != null && reportSpamView.getTag() == null)) { chatListView.setPadding(0, AndroidUtilities.dp(52), 0, AndroidUtilities.dp(3)); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) floatingDateView.getLayoutParams(); + layoutParams.topMargin = AndroidUtilities.dp(52); + floatingDateView.setLayoutParams(layoutParams); chatListView.setTopGlowOffset(AndroidUtilities.dp(48)); top -= AndroidUtilities.dp(48); } else if (chatListView.getPaddingTop() != AndroidUtilities.dp(4) && (pinnedMessageView == null || pinnedMessageView.getTag() != null) && (reportSpamView == null || reportSpamView.getTag() != null)) { chatListView.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(3)); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) floatingDateView.getLayoutParams(); + layoutParams.topMargin = AndroidUtilities.dp(4); + floatingDateView.setLayoutParams(layoutParams); chatListView.setTopGlowOffset(0); top += AndroidUtilities.dp(48); } else { @@ -6797,7 +7431,7 @@ public void run() { chatLayoutManager.scrollToPositionWithOffset(firstVisPos, top); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -6829,6 +7463,9 @@ public void onResume() { AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); MediaController.getInstance().startRaiseToEarSensors(this); checkRaiseSensors(); + if (chatAttachAlert != null) { + chatAttachAlert.onResume(); + } checkActionBarMenu(); if (replyImageLocation != null && replyImageView != null) { @@ -6850,7 +7487,7 @@ public void onResume() { } else { yOffset = scrollToMessagePosition; } - chatLayoutManager.scrollToPositionWithOffset(messages.size() - messages.indexOf(scrollToMessage), -chatListView.getPaddingTop() - AndroidUtilities.dp(7) + yOffset); + chatLayoutManager.scrollToPositionWithOffset(messages.size() - messages.indexOf(scrollToMessage), -AndroidUtilities.dp(7) + yOffset - (scrollToMessage == unreadMessageObject ? chatListView.getPaddingTop() : 0)); } } else { moveScrollToLastMessage(); @@ -6860,18 +7497,7 @@ public void onResume() { scrollToMessage = null; } paused = false; - if (readWhenResume && !messages.isEmpty()) { - for (MessageObject messageObject : messages) { - if (!messageObject.isUnread() && !messageObject.isOut()) { - break; - } - if (!messageObject.isOut()) { - messageObject.setIsRead(); - } - } - readWhenResume = false; - MessagesController.getInstance().markDialogAsRead(dialog_id, messages.get(0).getId(), readWithMid, readWithDate, true, false); - } + AndroidUtilities.runOnUIThread(readRunnable, 500); checkScrollForLoad(false); if (wasPaused) { wasPaused = false; @@ -6914,12 +7540,14 @@ public void run() { @Override public void onPause() { super.onPause(); + AndroidUtilities.cancelRunOnUIThread(readRunnable); MediaController.getInstance().stopRaiseToEarSensors(this); - if (menuItem != null) { - menuItem.closeSubMenu(); - } if (chatAttachAlert != null) { - chatAttachAlert.onPause(); + if (!ignoreAttachOnPause){ + chatAttachAlert.onPause(); + } else { + ignoreAttachOnPause = false; + } } paused = true; wasPaused = true; @@ -6943,6 +7571,34 @@ public void onPause() { MessagesController.getInstance().cancelTyping(0, dialog_id); + SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE).edit(); + int messageId = 0; + int offset = 0; + if (chatLayoutManager != null) { + int position = chatLayoutManager.findLastVisibleItemPosition(); + if (position < chatLayoutManager.getItemCount() - 1) { + RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(position); + if (holder != null) { + if (holder.itemView instanceof ChatMessageCell) { + messageId = ((ChatMessageCell) holder.itemView).getMessageObject().getId(); + } else if (holder.itemView instanceof ChatActionCell) { + messageId = ((ChatActionCell) holder.itemView).getMessageObject().getId(); + } + if (messageId != 0) { + offset = holder.itemView.getMeasuredHeight() - (holder.itemView.getBottom() - chatListView.getMeasuredHeight()); + } + } + } + } + if (messageId != 0) { + editor.putInt("diditem" + dialog_id, messageId); + editor.putInt("diditemo" + dialog_id, offset); + } else { + editor.remove("diditem" + dialog_id); + editor.remove("diditemo" + dialog_id); + } + editor.commit(); + if (currentEncryptedChat != null) { chatLeaveTime = System.currentTimeMillis(); updateInformationForScreenshotDetector(); @@ -6975,7 +7631,7 @@ private void applyDraftMaybe(boolean canClear) { if (entity.offset + addToOffset + entity.length < stringBuilder.length() && stringBuilder.charAt(entity.offset + addToOffset + entity.length) == ' ') { entity.length++; } - stringBuilder.setSpan(new URLSpanUserMention("" + user_id), entity.offset + addToOffset, entity.offset + addToOffset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + stringBuilder.setSpan(new URLSpanUserMention("" + user_id, true), entity.offset + addToOffset, entity.offset + addToOffset + entity.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else if (entity instanceof TLRPC.TL_messageEntityCode) { stringBuilder.insert(entity.offset + entity.length + addToOffset, "`"); stringBuilder.insert(entity.offset + addToOffset, "`"); @@ -6984,6 +7640,10 @@ private void applyDraftMaybe(boolean canClear) { stringBuilder.insert(entity.offset + entity.length + addToOffset, "```"); stringBuilder.insert(entity.offset + addToOffset, "```"); addToOffset += 6; + } else if (entity instanceof TLRPC.TL_messageEntityBold) { + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityItalic) { + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } message = stringBuilder; @@ -7006,11 +7666,11 @@ public void run() { } } else if (canClear && draftMessage == null) { chatActivityEnterView.setFieldText(""); - showReplyPanel(false, null, null, null, false, true); + showReplyPanel(false, null, null, null, false); } if (replyingMessageObject == null && draftReplyMessage != null) { replyingMessageObject = new MessageObject(draftReplyMessage, MessagesController.getInstance().getUsers(), false); - showReplyPanel(true, replyingMessageObject, null, null, false, false); + showReplyPanel(true, replyingMessageObject, null, null, false); } } @@ -7046,14 +7706,14 @@ private boolean fixLayoutInternal() { if (AndroidUtilities.isTablet()) { if (AndroidUtilities.isSmallTablet() && ApplicationLoader.applicationContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { actionBar.setBackButtonDrawable(new BackDrawable(false)); - if (playerView != null && playerView.getParent() == null) { - ((ViewGroup) fragmentView).addView(playerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + if (fragmentContextView != null && fragmentContextView.getParent() == null) { + ((ViewGroup) fragmentView).addView(fragmentContextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); } } else { actionBar.setBackButtonDrawable(new BackDrawable(parentLayout == null || parentLayout.fragmentsStack.isEmpty() || parentLayout.fragmentsStack.get(0) == ChatActivity.this || parentLayout.fragmentsStack.size() == 1)); - if (playerView != null && playerView.getParent() != null) { + if (fragmentContextView != null && fragmentContextView.getParent() != null) { fragmentView.setPadding(0, 0, 0, 0); - ((ViewGroup) fragmentView).removeView(playerView); + ((ViewGroup) fragmentView).removeView(fragmentContextView); } } return false; @@ -7078,6 +7738,9 @@ public boolean onPreDraw() { @Override public void onConfigurationChanged(android.content.res.Configuration newConfig) { fixLayout(); + if (visibleDialog instanceof DatePickerDialog) { + visibleDialog.dismiss(); + } } private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) { @@ -7086,12 +7749,16 @@ private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) builder.setTitle(LocaleController.getString("Message", R.string.Message)); final boolean[] checks = new boolean[3]; + final boolean[] deleteForAll = new boolean[1]; TLRPC.User user = null; if (currentChat != null && currentChat.megagroup) { + boolean hasOutgoing = false; + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); if (finalSelectedObject != null) { if (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) { user = MessagesController.getInstance().getUser(finalSelectedObject.messageOwner.from_id); } + hasOutgoing = finalSelectedObject.getDialogId() == mergeDialogId && (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && finalSelectedObject.isOut() && (currentDate - finalSelectedObject.messageOwner.date) <= 2 * 24 * 60 * 60; } else { int from_id = -1; for (int a = 1; a >= 0; a--) { @@ -7110,18 +7777,41 @@ private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) break; } } + boolean exit = false; + for (int a = 1; a >= 0; a--) { + for (HashMap.Entry entry : selectedMessagesIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (a == 1) { + if (msg.isOut() && msg.messageOwner.action == null) { + if ((currentDate - msg.messageOwner.date) <= 2 * 24 * 60 * 60) { + hasOutgoing = true; + } + } else { + hasOutgoing = false; + exit = true; + break; + } + } else if (a == 0) { + if (!msg.isOut()) { + hasOutgoing = false; + exit = true; + break; + } + } + } + if (exit) { + break; + } + } if (from_id != -1) { user = MessagesController.getInstance().getUser(from_id); } } if (user != null && user.id != UserConfig.getClientUserId()) { FrameLayout frameLayout = new FrameLayout(getParentActivity()); - if (Build.VERSION.SDK_INT >= 21) { - frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); - } for (int a = 0; a < 3; a++) { - CheckBoxCell cell = new CheckBoxCell(getParentActivity()); - cell.setBackgroundResource(R.drawable.list_selector); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setTag(a); if (a == 0) { cell.setText(LocaleController.getString("DeleteBanUser", R.string.DeleteBanUser), "", false, false); @@ -7130,8 +7820,8 @@ private void createDeleteMessagesAlert(final MessageObject finalSelectedObject) } else if (a == 2) { cell.setText(LocaleController.formatString("DeleteAllFrom", R.string.DeleteAllFrom, ContactsController.formatName(user.first_name, user.last_name)), "", false, false); } - cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); - frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 48 * a, 8, 0)); + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 48 * a, 0, 0)); cell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -7143,9 +7833,81 @@ public void onClick(View v) { }); } builder.setView(frameLayout); + } else if (hasOutgoing) { + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (currentChat != null) { + cell.setText(LocaleController.getString("DeleteForAll", R.string.DeleteForAll), "", false, false); + } else { + cell.setText(LocaleController.formatString("DeleteForUser", R.string.DeleteForUser, UserObject.getFirstName(currentUser)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + deleteForAll[0] = !deleteForAll[0]; + cell.setChecked(deleteForAll[0], true); + } + }); + builder.setView(frameLayout); } else { user = null; } + } else if (!ChatObject.isChannel(currentChat) && currentEncryptedChat == null) { + boolean hasOutgoing = false; + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + if (currentUser != null && currentUser.id != UserConfig.getClientUserId() || currentChat != null) { + if (finalSelectedObject != null) { + hasOutgoing = (finalSelectedObject.messageOwner.action == null || finalSelectedObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && finalSelectedObject.isOut() && (currentDate - finalSelectedObject.messageOwner.date) <= 2 * 24 * 60 * 60; + } else { + boolean exit = false; + for (int a = 1; a >= 0; a--) { + int channelId = 0; + for (HashMap.Entry entry : selectedMessagesIds[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (msg.messageOwner.action != null) { + continue; + } + if (msg.isOut()) { + if ((currentDate - msg.messageOwner.date) <= 2 * 24 * 60 * 60) { + hasOutgoing = true; + } + } else { + hasOutgoing = false; + exit = true; + break; + } + } + if (exit) { + break; + } + } + } + } + if (hasOutgoing) { + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (currentChat != null) { + cell.setText(LocaleController.getString("DeleteForAll", R.string.DeleteForAll), "", false, false); + } else { + cell.setText(LocaleController.formatString("DeleteForUser", R.string.DeleteForUser, UserObject.getFirstName(currentUser)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + deleteForAll[0] = !deleteForAll[0]; + cell.setChecked(deleteForAll[0], true); + } + }); + builder.setView(frameLayout); + } } final TLRPC.User userFinal = user; builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @@ -7160,7 +7922,7 @@ public void onClick(DialogInterface dialogInterface, int i) { random_ids = new ArrayList<>(); random_ids.add(finalSelectedObject.messageOwner.random_id); } - MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, finalSelectedObject.messageOwner.to_id.channel_id); + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, finalSelectedObject.messageOwner.to_id.channel_id, deleteForAll[0]); } else { for (int a = 1; a >= 0; a--) { ids = new ArrayList<>(selectedMessagesIds[a].keySet()); @@ -7181,7 +7943,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } } } - MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId); + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId, deleteForAll[0]); } actionBar.hideActionMode(); updatePinnedMessageView(true); @@ -7227,9 +7989,11 @@ private void createMenu(View v, boolean single) { return; } final int type = getMessageType(message); - if (single && message.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { - scrollToMessageId(message.messageOwner.reply_to_msg_id, 0, true, 0); - return; + if (single) { + if (message.messageOwner.action instanceof TLRPC.TL_messageActionPinMessage) { + scrollToMessageId(message.messageOwner.reply_to_msg_id, message.messageOwner.id, true, 0); + return; + } } selectedObject = null; @@ -7288,6 +8052,11 @@ private void createMenu(View v, boolean single) { options.add(1); } } else { + if (message.messageOwner.action != null && message.messageOwner.action instanceof TLRPC.TL_messageActionPhoneCall) { + TLRPC.TL_messageActionPhoneCall call = (TLRPC.TL_messageActionPhoneCall) message.messageOwner.action; + items.add((call.reason instanceof TLRPC.TL_phoneCallDiscardReasonMissed || call.reason instanceof TLRPC.TL_phoneCallDiscardReasonBusy) && !message.isOutOwner() ? LocaleController.getString("CallBack", R.string.CallBack) : LocaleController.getString("CallAgain", R.string.CallAgain)); + options.add(18); + } if (single && selectedObject.getId() > 0 && allowChatActions) { items.add(LocaleController.getString("Reply", R.string.Reply)); options.add(8); @@ -7346,6 +8115,15 @@ private void createMenu(View v, boolean single) { } else if (type == 5) { items.add(LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile)); options.add(5); + items.add(LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads)); + options.add(10); + items.add(LocaleController.getString("ShareFile", R.string.ShareFile)); + options.add(6); + } else if (type == 10) { + items.add(LocaleController.getString("ApplyThemeFile", R.string.ApplyThemeFile)); + options.add(5); + items.add(LocaleController.getString("SaveToDownloads", R.string.SaveToDownloads)); + options.add(10); items.add(LocaleController.getString("ShareFile", R.string.ShareFile)); options.add(6); } else if (type == 6) { @@ -7424,6 +8202,9 @@ private void createMenu(View v, boolean single) { } else if (type == 5) { items.add(LocaleController.getString("ApplyLocalizationFile", R.string.ApplyLocalizationFile)); options.add(5); + } else if (type == 10) { + items.add(LocaleController.getString("ApplyThemeFile", R.string.ApplyThemeFile)); + options.add(5); } else if (type == 7) { items.add(LocaleController.getString("AddToStickers", R.string.AddToStickers)); options.add(9); @@ -7462,9 +8243,6 @@ public void onClick(DialogInterface dialogInterface, int i) { if (item != null) { item.setVisibility(View.VISIBLE); } - if (editDoneItem != null) { - editDoneItem.setVisibility(View.GONE); - } actionBar.showActionMode(); updatePinnedMessageView(true); @@ -7485,80 +8263,6 @@ public void onClick(DialogInterface dialogInterface, int i) { updateVisibleRows(); } - private void showEditDoneProgress(final boolean show, boolean animated) { - if (editDoneItemAnimation != null) { - editDoneItemAnimation.cancel(); - } - if (!animated) { - if (show) { - editDoneItem.getImageView().setScaleX(0.1f); - editDoneItem.getImageView().setScaleY(0.1f); - editDoneItem.getImageView().setAlpha(0.0f); - editDoneItemProgress.setScaleX(1.0f); - editDoneItemProgress.setScaleY(1.0f); - editDoneItemProgress.setAlpha(1.0f); - editDoneItem.getImageView().setVisibility(View.INVISIBLE); - editDoneItemProgress.setVisibility(View.VISIBLE); - editDoneItem.setEnabled(false); - } else { - editDoneItemProgress.setScaleX(0.1f); - editDoneItemProgress.setScaleY(0.1f); - editDoneItemProgress.setAlpha(0.0f); - editDoneItem.getImageView().setScaleX(1.0f); - editDoneItem.getImageView().setScaleY(1.0f); - editDoneItem.getImageView().setAlpha(1.0f); - editDoneItem.getImageView().setVisibility(View.VISIBLE); - editDoneItemProgress.setVisibility(View.INVISIBLE); - editDoneItem.setEnabled(true); - } - } else { - editDoneItemAnimation = new AnimatorSet(); - if (show) { - editDoneItemProgress.setVisibility(View.VISIBLE); - editDoneItem.setEnabled(false); - editDoneItemAnimation.playTogether( - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleX", 0.1f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleY", 0.1f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "alpha", 0.0f), - ObjectAnimator.ofFloat(editDoneItemProgress, "scaleX", 1.0f), - ObjectAnimator.ofFloat(editDoneItemProgress, "scaleY", 1.0f), - ObjectAnimator.ofFloat(editDoneItemProgress, "alpha", 1.0f)); - } else { - editDoneItem.getImageView().setVisibility(View.VISIBLE); - editDoneItem.setEnabled(true); - editDoneItemAnimation.playTogether( - ObjectAnimator.ofFloat(editDoneItemProgress, "scaleX", 0.1f), - ObjectAnimator.ofFloat(editDoneItemProgress, "scaleY", 0.1f), - ObjectAnimator.ofFloat(editDoneItemProgress, "alpha", 0.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleX", 1.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "scaleY", 1.0f), - ObjectAnimator.ofFloat(editDoneItem.getImageView(), "alpha", 1.0f)); - - } - editDoneItemAnimation.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Animator animation) { - if (editDoneItemAnimation != null && editDoneItemAnimation.equals(animation)) { - if (!show) { - editDoneItemProgress.setVisibility(View.INVISIBLE); - } else { - editDoneItem.getImageView().setVisibility(View.INVISIBLE); - } - } - } - - @Override - public void onAnimationCancel(Animator animation) { - if (editDoneItemAnimation != null && editDoneItemAnimation.equals(animation)) { - editDoneItemAnimation = null; - } - } - }); - editDoneItemAnimation.setDuration(150); - editDoneItemAnimation.start(); - } - } - private String getMessageContent(MessageObject messageObject, int previousUid, boolean name) { String str = ""; if (name) { @@ -7609,7 +8313,6 @@ private void processSelectedOption(int option) { forwaringMessage = selectedObject; Bundle args = new Bundle(); args.putBoolean("onlySelect", true); - args.putInt("dialogsType", 1); DialogsActivity fragment = new DialogsActivity(args); fragment.setDelegate(this); presentFragment(fragment); @@ -7655,18 +8358,50 @@ private void processSelectedOption(int option) { } } if (locFile != null) { - if (LocaleController.getInstance().applyLanguageFile(locFile)) { - presentFragment(new LanguageSelectActivity()); + if (locFile.getName().endsWith("attheme")) { + if (chatLayoutManager != null) { + int lastPosition = chatLayoutManager.findLastVisibleItemPosition(); + if (lastPosition < chatLayoutManager.getItemCount() - 1) { + scrollToPositionOnRecreate = chatLayoutManager.findFirstVisibleItemPosition(); + RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(scrollToPositionOnRecreate); + if (holder != null) { + scrollToOffsetOnRecreate = holder.itemView.getTop(); + } else { + scrollToPositionOnRecreate = -1; + } + } else { + scrollToPositionOnRecreate = -1; + } + } + Theme.ThemeInfo themeInfo = Theme.applyThemeFile(locFile, selectedObject.getDocumentName(), true); + if (themeInfo != null) { + presentFragment(new ThemePreviewActivity(locFile, themeInfo)); + } else { + scrollToPositionOnRecreate = -1; + if (getParentActivity() == null) { + selectedObject = null; + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("IncorrectTheme", R.string.IncorrectTheme)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + } } else { - if (getParentActivity() == null) { - selectedObject = null; - return; + if (LocaleController.getInstance().applyLanguageFile(locFile)) { + presentFragment(new LanguageSelectActivity()); + } else { + if (getParentActivity() == null) { + selectedObject = null; + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("IncorrectLocalization", R.string.IncorrectLocalization)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("IncorrectLocalization", R.string.IncorrectLocalization)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); } } break; @@ -7708,7 +8443,7 @@ private void processSelectedOption(int option) { break; } case 8: { - showReplyPanel(true, selectedObject, null, null, false, true); + showReplyPanel(true, selectedObject, null, null, false); break; } case 9: { @@ -7722,7 +8457,7 @@ private void processSelectedOption(int option) { return; } String fileName = FileLoader.getDocumentFileName(selectedObject.getDocument()); - if (fileName == null || fileName.length() == 0) { + if (TextUtils.isEmpty(fileName)) { fileName = selectedObject.getFileName(); } String path = selectedObject.messageOwner.attachPath; @@ -7774,12 +8509,7 @@ private void processSelectedOption(int option) { actionMode.getItem(copy).setVisibility(View.GONE); actionMode.getItem(forward).setVisibility(View.GONE); actionMode.getItem(delete).setVisibility(View.GONE); - if (editDoneItemAnimation != null) { - editDoneItemAnimation.cancel(); - editDoneItemAnimation = null; - } - editDoneItem.setVisibility(View.VISIBLE); - showEditDoneProgress(true, false); + actionBar.showActionMode(); updatePinnedMessageView(true); updateVisibleRows(); @@ -7805,7 +8535,9 @@ public void run() { chatActivityEnterView.setEditingMessageObject(null, false); } } else { - showEditDoneProgress(false, true); + if (chatActivityEnterView != null) { + chatActivityEnterView.showEditDoneProgress(false, true); + } } } }); @@ -7820,11 +8552,8 @@ public void run() { final boolean[] checks = new boolean[]{true}; FrameLayout frameLayout = new FrameLayout(getParentActivity()); - if (Build.VERSION.SDK_INT >= 21) { - frameLayout.setPadding(0, AndroidUtilities.dp(8), 0, 0); - } - CheckBoxCell cell = new CheckBoxCell(getParentActivity()); - cell.setBackgroundResource(R.drawable.list_selector); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setText(LocaleController.getString("PinNotify", R.string.PinNotify), "", true, false); cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(8) : 0, 0, LocaleController.isRTL ? 0 : AndroidUtilities.dp(8), 0); frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 8, 0, 8, 0)); @@ -7880,10 +8609,15 @@ public void onClick(DialogInterface dialogInterface, int i) { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } break; } + case 18: { + if(currentUser!=null) + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + break; + } } selectedObject = null; } @@ -7916,26 +8650,28 @@ public void didSelectDialog(DialogsActivity activity, long did, boolean param) { if (did != dialog_id) { int lower_part = (int) did; + int high_part = (int) (did >> 32); + Bundle args = new Bundle(); + args.putBoolean("scrollToTopOnResume", scrollToTopOnResume); if (lower_part != 0) { - Bundle args = new Bundle(); - args.putBoolean("scrollToTopOnResume", scrollToTopOnResume); if (lower_part > 0) { args.putInt("user_id", lower_part); } else if (lower_part < 0) { args.putInt("chat_id", -lower_part); } + } else { + args.putInt("enc_id", high_part); + } + if (lower_part != 0) { if (!MessagesController.checkCanOpenChat(args, activity)) { return; } - - ChatActivity chatActivity = new ChatActivity(args); - if (presentFragment(chatActivity, true)) { - chatActivity.showReplyPanel(true, null, fmessages, null, false, false); - if (!AndroidUtilities.isTablet()) { - removeSelfFromStack(); - } - } else { - activity.finishFragment(); + } + ChatActivity chatActivity = new ChatActivity(args); + if (presentFragment(chatActivity, true)) { + chatActivity.showReplyPanel(true, null, fmessages, null, false); + if (!AndroidUtilities.isTablet()) { + removeSelfFromStack(); } } else { activity.finishFragment(); @@ -7943,7 +8679,7 @@ public void didSelectDialog(DialogsActivity activity, long did, boolean param) { } else { activity.finishFragment(); moveScrollToLastMessage(); - showReplyPanel(true, null, fmessages, null, false, AndroidUtilities.isTablet()); + showReplyPanel(true, null, fmessages, null, false); if (AndroidUtilities.isTablet()) { actionBar.hideActionMode(); updatePinnedMessageView(true); @@ -7989,17 +8725,17 @@ private void updateVisibleRows() { if (actionBar.isActionModeShowed()) { MessageObject messageObject = cell.getMessageObject(); if (messageObject == editingMessageObject || selectedMessagesIds[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId())) { - view.setBackgroundColor(Theme.MSG_SELECTED_BACKGROUND_COLOR); + view.setBackgroundColor(Theme.getColor(Theme.key_chat_selectedBackground)); selected = true; } else { - view.setBackgroundColor(0); + view.setBackgroundDrawable(null); } disableSelection = true; } else { - view.setBackgroundColor(0); + view.setBackgroundDrawable(null); } - cell.setMessageObject(cell.getMessageObject()); + cell.setMessageObject(cell.getMessageObject(), cell.isPinnedBottom(), cell.isPinnedTop()); cell.setCheckPressed(!disableSelection, disableSelection && selected); cell.setHighlighted(highlightMessageId != Integer.MAX_VALUE && cell.getMessageObject() != null && cell.getMessageObject().getId() == highlightMessageId); if (searchContainer != null && searchContainer.getVisibility() == View.VISIBLE && MessagesSearchQuery.getLastSearchQuery() != null) { @@ -8047,7 +8783,7 @@ public void run() { } }, 1000); } else { - editDoneItem.setVisibility(View.GONE); + chatActivityEnterView.onEditTimeExpired(); actionModeSubTextView.setText(LocaleController.formatString("TimeToEditExpired", R.string.TimeToEditExpired)); } } @@ -8219,7 +8955,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { } @Override @@ -8227,26 +8963,19 @@ public int getSelectedCount() { return 0; } - public void sendMedia(MediaController.PhotoEntry photoEntry, boolean mutedVideo) { + public void sendMedia(MediaController.PhotoEntry photoEntry, VideoEditedInfo videoEditedInfo) { if (photoEntry.isVideo) { - VideoEditedInfo videoEditedInfo = null; - long size = 0; - if (mutedVideo) { - videoEditedInfo = new VideoEditedInfo(); - videoEditedInfo.bitrate = -1; - videoEditedInfo.originalPath = photoEntry.path; - videoEditedInfo.startTime = videoEditedInfo.endTime = -1; - size = new File(photoEntry.path).length(); - } - SendMessagesHelper.prepareSendingVideo(photoEntry.path, size, 0, 0, 0, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption != null ? photoEntry.caption.toString() : null); + SendMessagesHelper.prepareSendingVideo(photoEntry.path, videoEditedInfo.estimatedSize, videoEditedInfo.estimatedDuration, videoEditedInfo.resultWidth, videoEditedInfo.resultHeight, videoEditedInfo, dialog_id, replyingMessageObject, photoEntry.caption != null ? photoEntry.caption.toString() : null); + showReplyPanel(false, null, null, null, false); + DraftQuery.cleanDraft(dialog_id, true); } else { if (photoEntry.imagePath != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.stickers); - showReplyPanel(false, null, null, null, false, true); + SendMessagesHelper.prepareSendingPhoto(photoEntry.imagePath, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.stickers, null); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } else if (photoEntry.path != null) { - SendMessagesHelper.prepareSendingPhoto(photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.stickers); - showReplyPanel(false, null, null, null, false, true); + SendMessagesHelper.prepareSendingPhoto(photoEntry.path, null, dialog_id, replyingMessageObject, photoEntry.caption, photoEntry.stickers, null); + showReplyPanel(false, null, null, null, false); DraftQuery.cleanDraft(dialog_id, true); } } @@ -8358,13 +9087,6 @@ public void updateRows() { } } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - @Override public int getItemCount() { return rowCount; @@ -8395,7 +9117,7 @@ public void didPressedShare(ChatMessageCell cell) { if (chatActivityEnterView != null) { chatActivityEnterView.closeKeyboard(); } - showDialog(new ShareAlert(mContext, cell.getMessageObject(), null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null)); + showDialog(new ShareAlert(mContext, cell.getMessageObject(), null, ChatObject.isChannel(currentChat) && !currentChat.megagroup && currentChat.username != null && currentChat.username.length() > 0, null, false)); } @Override @@ -8430,7 +9152,12 @@ public void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int p @Override public void didPressedOther(ChatMessageCell cell) { - createMenu(cell, true); + if (cell.getMessageObject().type == 16) { + if(currentUser!=null) + VoIPHelper.startCall(currentUser, getParentActivity(), MessagesController.getInstance().getUserFull(currentUser.id)); + } else { + createMenu(cell, true); + } } @Override @@ -8452,7 +9179,8 @@ public void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user) { public void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button) { if (getParentActivity() == null || bottomOverlayChat.getVisibility() == View.VISIBLE && !(button instanceof TLRPC.TL_keyboardButtonSwitchInline) && !(button instanceof TLRPC.TL_keyboardButtonCallback) && - !(button instanceof TLRPC.TL_keyboardButtonGame) && !(button instanceof TLRPC.TL_keyboardButtonUrl)) { + !(button instanceof TLRPC.TL_keyboardButtonGame) && !(button instanceof TLRPC.TL_keyboardButtonUrl) && + !(button instanceof TLRPC.TL_keyboardButtonBuy)) { return; } chatActivityEnterView.didPressedBotButton(button, cell.getMessageObject(), cell.getMessageObject()); @@ -8477,11 +9205,14 @@ public boolean canPerformActions() { } @Override - public void didPressedUrl(MessageObject messageObject, final ClickableSpan url, boolean longPress) { + public void didPressedUrl(MessageObject messageObject, final CharacterStyle url, boolean longPress) { if (url == null) { return; } - if (url instanceof URLSpanUserMention) { + if (url instanceof URLSpanMono) { + ((URLSpanMono) url).copyToClipboard(); + Toast.makeText(getParentActivity(), LocaleController.getString("TextCopied", R.string.TextCopied), Toast.LENGTH_SHORT).show(); + } else if (url instanceof URLSpanUserMention) { TLRPC.User user = MessagesController.getInstance().getUser(Utilities.parseInt(((URLSpanUserMention) url).getURL())); if (user != null) { MessagesController.openChatOrProfileWith(user, null, ChatActivity.this, 0, false); @@ -8526,13 +9257,21 @@ public void onClick(DialogInterface dialog, final int which) { }); showDialog(builder.create()); } else { - if (((URLSpan) url).getURL().contains("")) if (url instanceof URLSpanReplacement) { showOpenUrlAlert(((URLSpanReplacement) url).getURL(), true); } else if (url instanceof URLSpan) { + if (messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage && messageObject.messageOwner.media.webpage != null && messageObject.messageOwner.media.webpage.cached_page != null) { + String lowerUrl = urlFinal.toLowerCase(); + String lowerUrl2 = messageObject.messageOwner.media.webpage.url.toLowerCase(); + if (lowerUrl.contains("telegra.ph") && (lowerUrl.contains(lowerUrl2) || lowerUrl2.contains(lowerUrl))) { + ArticleViewer.getInstance().setParentActivity(getParentActivity()); + ArticleViewer.getInstance().open(messageObject); + return; + } + } Browser.openUrl(getParentActivity(), urlFinal, inlineReturn == 0); - } else { - url.onClick(fragmentView); + } else if (url instanceof ClickableSpan) { + ((ClickableSpan) url).onClick(fragmentView); } } } @@ -8540,10 +9279,7 @@ public void onClick(DialogInterface dialog, final int which) { @Override public void needOpenWebView(String url, String title, String description, String originalUrl, int w, int h) { - BottomSheet.Builder builder = new BottomSheet.Builder(mContext); - builder.setCustomView(new WebFrameLayout(mContext, builder.create(), title, description, originalUrl, url, w, h)); - builder.setUseFullWidth(true); - showDialog(builder.create()); + EmbedBottomSheet.show(mContext, title, description, originalUrl, url, w, h); } @Override @@ -8575,6 +9311,9 @@ public void didPressedImage(ChatMessageCell cell) { if (message.type == 13) { showDialog(new StickersAlert(getParentActivity(), ChatActivity.this, message.getInputStickerSet(), null, bottomOverlayChat.getVisibility() != View.VISIBLE ? chatActivityEnterView : null)); } else if (Build.VERSION.SDK_INT >= 16 && message.isVideo() || message.type == 1 || message.type == 0 && !message.isWebpageDocument() || message.isGif()) { + if (message.isVideo()) { + sendSecretMessageRead(message); + } PhotoViewer.getInstance().setParentActivity(getParentActivity()); if (PhotoViewer.getInstance().openPhoto(message, message.type != 0 ? dialog_id : 0, message.type != 0 ? mergeDialogId : 0, ChatActivity.this)) { PhotoViewer.getInstance().setParentChatActivity(ChatActivity.this); @@ -8608,6 +9347,42 @@ public void didPressedImage(ChatMessageCell cell) { fragment.setMessageObject(message); presentFragment(fragment); } else if (message.type == 9 || message.type == 0) { + if (message.getDocumentName().endsWith("attheme")) { + File locFile = null; + if (message.messageOwner.attachPath != null && message.messageOwner.attachPath.length() != 0) { + File f = new File(message.messageOwner.attachPath); + if (f.exists()) { + locFile = f; + } + } + if (locFile == null) { + File f = FileLoader.getPathToMessage(message.messageOwner); + if (f.exists()) { + locFile = f; + } + } + if (chatLayoutManager != null) { + int lastPosition = chatLayoutManager.findLastVisibleItemPosition(); + if (lastPosition < chatLayoutManager.getItemCount() - 1) { + scrollToPositionOnRecreate = chatLayoutManager.findFirstVisibleItemPosition(); + RecyclerListView.Holder holder = (RecyclerListView.Holder) chatListView.findViewHolderForAdapterPosition(scrollToPositionOnRecreate); + if (holder != null) { + scrollToOffsetOnRecreate = holder.itemView.getTop(); + } else { + scrollToPositionOnRecreate = -1; + } + } else { + scrollToPositionOnRecreate = -1; + } + } + Theme.ThemeInfo themeInfo = Theme.applyThemeFile(locFile, message.getDocumentName(), true); + if (themeInfo != null) { + presentFragment(new ThemePreviewActivity(locFile, themeInfo)); + return; + } else { + scrollToPositionOnRecreate = -1; + } + } try { AndroidUtilities.openForView(message, getParentActivity()); } catch (Exception e) { @@ -8615,6 +9390,15 @@ public void didPressedImage(ChatMessageCell cell) { } } } + + @Override + public void didPressedInstantButton(ChatMessageCell cell) { + MessageObject messageObject = cell.getMessageObject(); + if (messageObject.messageOwner.media != null && messageObject.messageOwner.media.webpage != null && messageObject.messageOwner.media.webpage.cached_page != null) { + ArticleViewer.getInstance().setParentActivity(getParentActivity()); + ArticleViewer.getInstance().open(messageObject); + } + } }); if (currentEncryptedChat == null) { chatMessageCell.setAllowAssistant(true); @@ -8669,7 +9453,8 @@ public void didPressedReplyMessage(ChatActionCell cell, int id) { public void didPressedBotButton(MessageObject messageObject, TLRPC.KeyboardButton button) { if (getParentActivity() == null || bottomOverlayChat.getVisibility() == View.VISIBLE && !(button instanceof TLRPC.TL_keyboardButtonSwitchInline) && !(button instanceof TLRPC.TL_keyboardButtonCallback) && - !(button instanceof TLRPC.TL_keyboardButtonGame) && !(button instanceof TLRPC.TL_keyboardButtonUrl)) { + !(button instanceof TLRPC.TL_keyboardButtonGame) && !(button instanceof TLRPC.TL_keyboardButtonUrl) && + !(button instanceof TLRPC.TL_keyboardButtonBuy)) { return; } chatActivityEnterView.didPressedBotButton(button, messageObject, messageObject); @@ -8697,7 +9482,7 @@ public void didPressUrl(String url) { view = new ChatLoadingCell(mContext); } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -8712,26 +9497,26 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { MessageObject message = messages.get(messages.size() - (position - messagesStartRow) - 1); View view = holder.itemView; - boolean selected = false; - boolean disableSelection = false; - if (actionBar.isActionModeShowed()) { - MessageObject messageObject = chatActivityEnterView != null ? chatActivityEnterView.getEditingMessageObject() : null; - if (messageObject == message || selectedMessagesIds[message.getDialogId() == dialog_id ? 0 : 1].containsKey(message.getId())) { - view.setBackgroundColor(Theme.MSG_SELECTED_BACKGROUND_COLOR); - selected = true; - } else { - view.setBackgroundColor(0); - } - disableSelection = true; - } else { - view.setBackgroundColor(0); - } - if (view instanceof ChatMessageCell) { - ChatMessageCell messageCell = (ChatMessageCell) view; + final ChatMessageCell messageCell = (ChatMessageCell) view; messageCell.isChat = currentChat != null; - messageCell.setMessageObject(message); - messageCell.setCheckPressed(!disableSelection, disableSelection && selected); + int nextType = getItemViewType(position + 1); + int prevType = getItemViewType(position - 1); + boolean pinnedBotton; + boolean pinnedTop; + if (!(message.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && nextType == holder.getItemViewType()) { + MessageObject nextMessage = messages.get(messages.size() - (position + 1 - messagesStartRow) - 1); + pinnedBotton = nextMessage.isOutOwner() == message.isOutOwner() && (currentChat != null && nextMessage.messageOwner.from_id == message.messageOwner.from_id || currentChat == null) && Math.abs(nextMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + } else { + pinnedBotton = false; + } + if (prevType == holder.getItemViewType()) { + MessageObject prevMessage = messages.get(messages.size() - (position - messagesStartRow)); + pinnedTop = !(prevMessage.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && prevMessage.isOutOwner() == message.isOutOwner() && (currentChat != null && prevMessage.messageOwner.from_id == message.messageOwner.from_id || currentChat == null) && Math.abs(prevMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + } else { + pinnedTop = false; + } + messageCell.setMessageObject(message, pinnedBotton, pinnedTop); if (view instanceof ChatMessageCell && MediaController.getInstance().canDownloadMedia(MediaController.AUTODOWNLOAD_MASK_AUDIO)) { ((ChatMessageCell) view).downloadAudioIfNeed(); } @@ -8741,12 +9526,72 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else { messageCell.setHighlightedText(null); } + int index; + if (BuildVars.DEBUG_PRIVATE_VERSION && (index = animatingMessageObjects.indexOf(message)) != -1) { + animatingMessageObjects.remove(index); + final ChatMessageCell animatingView = messageCell; + messageCell.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + animatingView.getViewTreeObserver().removeOnPreDrawListener(this); //TODO + ImageReceiver imageReceiver = animatingView.getPhotoImage(); + int w = imageReceiver.getImageWidth(); + org.telegram.ui.Components.Rect rect = instantCameraView.getCameraRect(); + float scale = w / rect.width; + int position[] = new int[2]; + messageCell.setAlpha(0.0f); + messageCell.getLocationOnScreen(position); + position[0] += imageReceiver.getImageX(); + position[1] += imageReceiver.getImageY(); + final View cameraContainer = instantCameraView.getCameraContainer(); + cameraContainer.setPivotX(0.0f); + cameraContainer.setPivotY(0.0f); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(actionBarOverlay, "alpha", 0.0f), + ObjectAnimator.ofFloat(instantCameraView, "alpha", 0.0f), + ObjectAnimator.ofFloat(cameraContainer, "scaleX", scale), + ObjectAnimator.ofFloat(cameraContainer, "scaleY", scale), + ObjectAnimator.ofFloat(cameraContainer, "translationX", position[0] - rect.x), + ObjectAnimator.ofFloat(cameraContainer, "translationY", position[1] - rect.y)); + animatorSet.setDuration(180); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + //messageCell.setAlpha(1.0f); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(cameraContainer, "alpha", 0.0f), + ObjectAnimator.ofFloat(messageCell, "alpha", 1.0f) + ); + animatorSet.setDuration(100); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + instantCameraView.hideCamera(true); + instantCameraView.setVisibility(View.GONE); + } + }); + animatorSet.start(); + } + }); + animatorSet.start(); + return true; + } + }); + } } else if (view instanceof ChatActionCell) { ChatActionCell actionCell = (ChatActionCell) view; actionCell.setMessageObject(message); + actionCell.setAlpha(1.0f); } else if (view instanceof ChatUnreadCell) { ChatUnreadCell unreadCell = (ChatUnreadCell) view; unreadCell.setText(LocaleController.formatPluralString("NewMessages", unread_to_load)); + if (createUnreadMessageAfterId != 0) { + createUnreadMessageAfterId = 0; + } } } } @@ -8765,6 +9610,24 @@ public int getItemViewType(int position) { public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { if (holder.itemView instanceof ChatMessageCell) { final ChatMessageCell messageCell = (ChatMessageCell) holder.itemView; + MessageObject message = messageCell.getMessageObject(); + + boolean selected = false; + boolean disableSelection = false; + if (actionBar.isActionModeShowed()) { + MessageObject messageObject = chatActivityEnterView != null ? chatActivityEnterView.getEditingMessageObject() : null; + if (messageObject == message || selectedMessagesIds[message.getDialogId() == dialog_id ? 0 : 1].containsKey(message.getId())) { + messageCell.setBackgroundColor(Theme.getColor(Theme.key_chat_selectedBackground)); + selected = true; + } else { + messageCell.setBackgroundDrawable(null); + } + disableSelection = true; + } else { + messageCell.setBackgroundDrawable(null); + } + messageCell.setCheckPressed(!disableSelection, disableSelection && selected); + messageCell.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { @@ -8801,7 +9664,7 @@ public void notifyDataSetChanged() { try { super.notifyDataSetChanged(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8811,7 +9674,7 @@ public void notifyItemChanged(int position) { try { super.notifyItemChanged(position); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8821,7 +9684,7 @@ public void notifyItemRangeChanged(int positionStart, int itemCount) { try { super.notifyItemRangeChanged(positionStart, itemCount); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8831,7 +9694,7 @@ public void notifyItemInserted(int position) { try { super.notifyItemInserted(position); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8841,7 +9704,7 @@ public void notifyItemMoved(int fromPosition, int toPosition) { try { super.notifyItemMoved(fromPosition, toPosition); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8851,7 +9714,7 @@ public void notifyItemRangeInserted(int positionStart, int itemCount) { try { super.notifyItemRangeInserted(positionStart, itemCount); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8861,7 +9724,7 @@ public void notifyItemRemoved(int position) { try { super.notifyItemRemoved(position); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -8871,8 +9734,372 @@ public void notifyItemRangeRemoved(int positionStart, int itemCount) { try { super.notifyItemRangeRemoved(positionStart, itemCount); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate selectedBackgroundDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + updateVisibleRows(); + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, 0, null, null, null, null, Theme.key_chat_wallpaper), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(chatListView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(chatListView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(avatarContainer.getTitleTextView(), ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(avatarContainer.getSubtitleTextView(), ThemeDescription.FLAG_TEXTCOLOR, null, new Paint[]{Theme.chat_statusPaint, Theme.chat_statusRecordPaint}, null, null, Theme.key_actionBarDefaultSubtitle, null), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_BACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_TOPBACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefaultTop), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultSelector), + new ThemeDescription(selectedMessagesCountTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + new ThemeDescription(actionModeTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + new ThemeDescription(actionModeSubTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + + new ThemeDescription(avatarContainer.getTitleTextView(), 0, null, null, new Drawable[]{Theme.chat_muteIconDrawable}, null, Theme.key_chat_muteIcon), + new ThemeDescription(avatarContainer.getTitleTextView(), 0, null, null, new Drawable[]{Theme.chat_lockIconDrawable}, null, Theme.key_chat_lockIcon), + + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundRed), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundOrange), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundViolet), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundGreen), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundCyan), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundBlue), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_backgroundPink), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageRed), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageOrange), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageViolet), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageGreen), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageCyan), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessageBlue), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_avatar_nameInMessagePink), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInDrawable, Theme.chat_msgInMediaDrawable}, null, Theme.key_chat_inBubble), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInSelectedDrawable, Theme.chat_msgInMediaSelectedDrawable}, null, Theme.key_chat_inBubbleSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInShadowDrawable, Theme.chat_msgInMediaShadowDrawable}, null, Theme.key_chat_inBubbleShadow), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutDrawable, Theme.chat_msgOutMediaDrawable}, null, Theme.key_chat_outBubble), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutSelectedDrawable, Theme.chat_msgOutMediaSelectedDrawable}, null, Theme.key_chat_outBubbleSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutShadowDrawable, Theme.chat_msgOutMediaShadowDrawable}, null, Theme.key_chat_outBubbleShadow), + new ThemeDescription(chatListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceText), + new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatActionCell.class}, Theme.chat_actionTextPaint, null, null, Theme.key_chat_serviceLink), + + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_shareIconDrawable, Theme.chat_botInlineDrawable, Theme.chat_botLinkDrawalbe}, null, Theme.key_chat_serviceIcon), + + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class, ChatActionCell.class}, null, null, null, Theme.key_chat_serviceBackgroundSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_messageTextIn), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_messageTextOut), + new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_messageLinkIn, null), + new ThemeDescription(chatListView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_messageLinkOut, null), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutCheckDrawable, Theme.chat_msgOutHalfCheckDrawable}, null, Theme.key_chat_outSentCheck), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutCheckSelectedDrawable, Theme.chat_msgOutHalfCheckSelectedDrawable}, null, Theme.key_chat_outSentCheckSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutClockDrawable}, null, Theme.key_chat_outSentClock), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutSelectedClockDrawable}, null, Theme.key_chat_outSentClockSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInClockDrawable}, null, Theme.key_chat_inSentClock), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInSelectedClockDrawable}, null, Theme.key_chat_inSentClockSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgMediaCheckDrawable, Theme.chat_msgMediaHalfCheckDrawable}, null, Theme.key_chat_mediaSentCheck), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgStickerHalfCheckDrawable, Theme.chat_msgStickerCheckDrawable, Theme.chat_msgStickerClockDrawable, Theme.chat_msgStickerViewsDrawable}, null, Theme.key_chat_serviceText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgMediaClockDrawable}, null, Theme.key_chat_mediaSentClock), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutViewsDrawable}, null, Theme.key_chat_outViews), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutViewsSelectedDrawable}, null, Theme.key_chat_outViewsSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInViewsDrawable}, null, Theme.key_chat_inViews), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInViewsSelectedDrawable}, null, Theme.key_chat_inViewsSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgMediaViewsDrawable}, null, Theme.key_chat_mediaViews), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutMenuDrawable}, null, Theme.key_chat_outMenu), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutMenuSelectedDrawable}, null, Theme.key_chat_outMenuSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInMenuDrawable}, null, Theme.key_chat_inMenu), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInMenuSelectedDrawable}, null, Theme.key_chat_inMenuSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgMediaMenuDrawable}, null, Theme.key_chat_mediaMenu), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutInstantDrawable, Theme.chat_msgOutCallDrawable}, null, Theme.key_chat_outInstant), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgOutInstantSelectedDrawable, Theme.chat_msgOutCallSelectedDrawable}, null, Theme.key_chat_outInstantSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInInstantDrawable, Theme.chat_msgInCallDrawable}, null, Theme.key_chat_inInstant), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgInInstantSelectedDrawable, Theme.chat_msgInCallSelectedDrawable}, null, Theme.key_chat_inInstantSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgCallUpRedDrawable, Theme.chat_msgCallDownRedDrawable}, null, Theme.key_calls_callReceivedRedIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgCallUpGreenDrawable, Theme.chat_msgCallDownGreenDrawable}, null, Theme.key_calls_callReceivedGreenIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_msgErrorPaint, null, null, Theme.key_chat_sentError), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_msgErrorDrawable}, null, Theme.key_chat_sentErrorIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, selectedBackgroundDelegate, Theme.key_chat_selectedBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_durationPaint, null, null, Theme.key_chat_previewDurationText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_gamePaint, null, null, Theme.key_chat_previewGameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inPreviewInstantText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outPreviewInstantText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inPreviewInstantSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outPreviewInstantSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_deleteProgressPaint, null, null, Theme.key_chat_secretTimeText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_stickerNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_botButtonPaint, null, null, Theme.key_chat_botButtonText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_botProgressPaint, null, null, Theme.key_chat_botProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inForwardedNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outForwardedNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inViaBotNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outViaBotNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_stickerViaBotNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inReplyLine), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outReplyLine), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_stickerReplyLine), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inReplyNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outReplyNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_stickerReplyNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inReplyMessageText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outReplyMessageText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inReplyMediaMessageText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outReplyMediaMessageText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inReplyMediaMessageSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outReplyMediaMessageSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_stickerReplyMessageText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inPreviewLine), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outPreviewLine), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inSiteNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outSiteNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inContactNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outContactNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inContactPhoneText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outContactPhoneText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_mediaProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioSelectedProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioSelectedProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_mediaTimeText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inTimeText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outTimeText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inTimeSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outTimeSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioPerfomerText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioPerfomerText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioTitleText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioTitleText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioDurationText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioDurationText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioDurationSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioDurationSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioSeekbar), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioSeekbar), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioSeekbarSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioSeekbarSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inAudioSeekbarFill), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outAudioSeekbarFill), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVoiceSeekbar), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVoiceSeekbar), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVoiceSeekbarSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVoiceSeekbarSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVoiceSeekbarFill), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVoiceSeekbarFill), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileProgress), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileProgressSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileProgressSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileInfoText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileInfoText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileInfoSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileInfoSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inFileBackgroundSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outFileBackgroundSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVenueNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVenueNameText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVenueInfoText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVenueInfoText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_inVenueInfoSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_outVenueInfoSelectedText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, null, null, Theme.key_chat_mediaInfoText), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_urlPaint, null, null, Theme.key_chat_linkSelectBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, Theme.chat_textSearchSelectionPaint, null, null, Theme.key_chat_textSelectBackground), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[0][0], Theme.chat_fileStatesDrawable[1][0], Theme.chat_fileStatesDrawable[2][0], Theme.chat_fileStatesDrawable[3][0], Theme.chat_fileStatesDrawable[4][0]}, null, Theme.key_chat_outLoader), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[0][0], Theme.chat_fileStatesDrawable[1][0], Theme.chat_fileStatesDrawable[2][0], Theme.chat_fileStatesDrawable[3][0], Theme.chat_fileStatesDrawable[4][0]}, null, Theme.key_chat_outBubble), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[0][1], Theme.chat_fileStatesDrawable[1][1], Theme.chat_fileStatesDrawable[2][1], Theme.chat_fileStatesDrawable[3][1], Theme.chat_fileStatesDrawable[4][1]}, null, Theme.key_chat_outLoaderSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[0][1], Theme.chat_fileStatesDrawable[1][1], Theme.chat_fileStatesDrawable[2][1], Theme.chat_fileStatesDrawable[3][1], Theme.chat_fileStatesDrawable[4][1]}, null, Theme.key_chat_outBubbleSelected), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[5][0], Theme.chat_fileStatesDrawable[6][0], Theme.chat_fileStatesDrawable[7][0], Theme.chat_fileStatesDrawable[8][0], Theme.chat_fileStatesDrawable[9][0]}, null, Theme.key_chat_inLoader), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[5][0], Theme.chat_fileStatesDrawable[6][0], Theme.chat_fileStatesDrawable[7][0], Theme.chat_fileStatesDrawable[8][0], Theme.chat_fileStatesDrawable[9][0]}, null, Theme.key_chat_inBubble), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[5][1], Theme.chat_fileStatesDrawable[6][1], Theme.chat_fileStatesDrawable[7][1], Theme.chat_fileStatesDrawable[8][1], Theme.chat_fileStatesDrawable[9][1]}, null, Theme.key_chat_inLoaderSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_fileStatesDrawable[5][1], Theme.chat_fileStatesDrawable[6][1], Theme.chat_fileStatesDrawable[7][1], Theme.chat_fileStatesDrawable[8][1], Theme.chat_fileStatesDrawable[9][1]}, null, Theme.key_chat_inBubbleSelected), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[0][0], Theme.chat_photoStatesDrawables[1][0], Theme.chat_photoStatesDrawables[2][0], Theme.chat_photoStatesDrawables[3][0]}, null, Theme.key_chat_mediaLoaderPhoto), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[0][0], Theme.chat_photoStatesDrawables[1][0], Theme.chat_photoStatesDrawables[2][0], Theme.chat_photoStatesDrawables[3][0]}, null, Theme.key_chat_mediaLoaderPhotoIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[0][1], Theme.chat_photoStatesDrawables[1][1], Theme.chat_photoStatesDrawables[2][1], Theme.chat_photoStatesDrawables[3][1]}, null, Theme.key_chat_mediaLoaderPhotoSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[0][1], Theme.chat_photoStatesDrawables[1][1], Theme.chat_photoStatesDrawables[2][1], Theme.chat_photoStatesDrawables[3][1]}, null, Theme.key_chat_mediaLoaderPhotoIconSelected), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[7][0], Theme.chat_photoStatesDrawables[8][0]}, null, Theme.key_chat_outLoaderPhoto), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[7][0], Theme.chat_photoStatesDrawables[8][0]}, null, Theme.key_chat_outLoaderPhotoIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[7][1], Theme.chat_photoStatesDrawables[8][1]}, null, Theme.key_chat_outLoaderPhotoSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[7][1], Theme.chat_photoStatesDrawables[8][1]}, null, Theme.key_chat_outLoaderPhotoIconSelected), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[10][0], Theme.chat_photoStatesDrawables[11][0]}, null, Theme.key_chat_inLoaderPhoto), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[10][0], Theme.chat_photoStatesDrawables[11][0]}, null, Theme.key_chat_inLoaderPhotoIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[10][1], Theme.chat_photoStatesDrawables[11][1]}, null, Theme.key_chat_inLoaderPhotoSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[10][1], Theme.chat_photoStatesDrawables[11][1]}, null, Theme.key_chat_inLoaderPhotoIconSelected), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[9][0]}, null, Theme.key_chat_outFileIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[9][1]}, null, Theme.key_chat_outFileSelectedIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[12][0]}, null, Theme.key_chat_inFileIcon), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_photoStatesDrawables[12][1]}, null, Theme.key_chat_inFileSelectedIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_contactDrawable[0]}, null, Theme.key_chat_inContactBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_contactDrawable[0]}, null, Theme.key_chat_inContactIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_contactDrawable[1]}, null, Theme.key_chat_outContactBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_contactDrawable[1]}, null, Theme.key_chat_outContactIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_locationDrawable[0]}, null, Theme.key_chat_inLocationBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_locationDrawable[0]}, null, Theme.key_chat_inLocationIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_locationDrawable[1]}, null, Theme.key_chat_outLocationBackground), + new ThemeDescription(chatListView, 0, new Class[]{ChatMessageCell.class}, null, new Drawable[]{Theme.chat_locationDrawable[1]}, null, Theme.key_chat_outLocationIcon), + + new ThemeDescription(mentionContainer, 0, null, Theme.chat_composeBackgroundPaint, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(mentionContainer, 0, null, null, new Drawable[]{Theme.chat_composeShadowDrawable}, null, Theme.key_chat_messagePanelShadow), + new ThemeDescription(searchContainer, 0, null, Theme.chat_composeBackgroundPaint, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(searchContainer, 0, null, null, new Drawable[]{Theme.chat_composeShadowDrawable}, null, Theme.key_chat_messagePanelShadow), + new ThemeDescription(bottomOverlay, 0, null, Theme.chat_composeBackgroundPaint, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(bottomOverlay, 0, null, null, new Drawable[]{Theme.chat_composeShadowDrawable}, null, Theme.key_chat_messagePanelShadow), + new ThemeDescription(bottomOverlayChat, 0, null, Theme.chat_composeBackgroundPaint, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(bottomOverlayChat, 0, null, null, new Drawable[]{Theme.chat_composeShadowDrawable}, null, Theme.key_chat_messagePanelShadow), + + new ThemeDescription(chatActivityEnterView, 0, null, Theme.chat_composeBackgroundPaint, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(chatActivityEnterView, 0, null, null, new Drawable[]{Theme.chat_composeShadowDrawable}, null, Theme.key_chat_messagePanelShadow), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_BACKGROUND, new Class[]{ChatActivityEnterView.class}, new String[]{"audioVideoButtonContainer"}, null, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"messageEditText"}, null, null, null, Theme.key_chat_messagePanelText), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_HINTTEXTCOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"messageEditText"}, null, null, null, Theme.key_chat_messagePanelHint), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"sendButton"}, null, null, null, Theme.key_chat_messagePanelSend), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"emojiButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"botButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"notifyButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"attachButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"audioSendButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"videoSendButton"}, null, null, null, Theme.key_chat_messagePanelIcons), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"doneButtonImage"}, null, null, null, Theme.key_chat_editDoneIcon), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_BACKGROUND, new Class[]{ChatActivityEnterView.class}, new String[]{"recordedAudioPanel"}, null, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"micDrawable"}, null, null, null, Theme.key_chat_messagePanelVoicePressed), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"cameraDrawable"}, null, null, null, Theme.key_chat_messagePanelVoicePressed), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"recordDeleteImageView"}, null, null, null, Theme.key_chat_messagePanelVoiceDelete), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ChatActivityEnterView.class}, new String[]{"recordedAudioBackground"}, null, null, null, Theme.key_chat_recordedVoiceBackground), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"recordTimeText"}, null, null, null, Theme.key_chat_recordTime), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_BACKGROUND, new Class[]{ChatActivityEnterView.class}, new String[]{"recordTimeContainer"}, null, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"recordCancelText"}, null, null, null, Theme.key_chat_recordVoiceCancel), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_BACKGROUND, new Class[]{ChatActivityEnterView.class}, new String[]{"recordPanel"}, null, null, null, Theme.key_chat_messagePanelBackground), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"recordedAudioTimeTextView"}, null, null, null, Theme.key_chat_messagePanelVoiceDuration), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"recordCancelImage"}, null, null, null, Theme.key_chat_recordVoiceCancel), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"doneButtonProgress"}, null, null, null, Theme.key_contextProgressInner1), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"doneButtonProgress"}, null, null, null, Theme.key_contextProgressOuter1), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{ChatActivityEnterView.class}, new String[]{"cancelBotButton"}, null, null, null, Theme.key_chat_messagePanelCancelInlineBot), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"redDotPaint"}, null, null, null, Theme.key_chat_recordedVoiceDot), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"paint"}, null, null, null, Theme.key_chat_messagePanelVoiceBackground), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"paintRecord"}, null, null, null, Theme.key_chat_messagePanelVoiceShadow), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"seekBarWaveform"}, null, null, null, Theme.key_chat_recordedVoiceProgress), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"seekBarWaveform"}, null, null, null, Theme.key_chat_recordedVoiceProgressInner), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"playDrawable"}, null, null, null, Theme.key_chat_recordedVoicePlayPause), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"pauseDrawable"}, null, null, null, Theme.key_chat_recordedVoicePlayPause), + new ThemeDescription(chatActivityEnterView, 0, new Class[]{ChatActivityEnterView.class}, new String[]{"dotPaint"}, null, null, null, Theme.key_chat_emojiPanelNewTrending), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{ChatActivityEnterView.class}, new String[]{"playDrawable"}, null, null, null, Theme.key_chat_recordedVoicePlayPausePressed), + new ThemeDescription(chatActivityEnterView, ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{ChatActivityEnterView.class}, new String[]{"pauseDrawable"}, null, null, null, Theme.key_chat_recordedVoicePlayPausePressed), + + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelBackground), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelShadowLine), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelEmptyText), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelIcon), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelIconSelected), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelStickerPackSelector), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelIconSelector), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelBackspace), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelTrendingTitle), + new ThemeDescription(chatActivityEnterView.getEmojiView(), 0, new Class[]{EmojiView.class}, new String[]{""}, null, null, null, Theme.key_chat_emojiPanelTrendingDescription), + + new ThemeDescription(null, 0, null, null, null, null, Theme.key_chat_botKeyboardButtonText), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_chat_botKeyboardButtonBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_chat_botKeyboardButtonBackgroundPressed), + + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerBackground), + new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"playButton"}, null, null, null, Theme.key_inappPlayerPlayPause), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_inappPlayerTitle), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerPerformer), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"closeButton"}, null, null, null, Theme.key_inappPlayerClose), + + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_returnToCallBackground), + new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_returnToCallText), + + new ThemeDescription(pinnedLineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_chat_topPanelLine), + new ThemeDescription(pinnedMessageNameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_topPanelTitle), + new ThemeDescription(pinnedMessageTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_topPanelMessage), + new ThemeDescription(alertNameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_topPanelTitle), + new ThemeDescription(alertTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_topPanelMessage), + new ThemeDescription(closePinned, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_topPanelClose), + new ThemeDescription(closeReportSpam, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_topPanelClose), + new ThemeDescription(reportSpamView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), + new ThemeDescription(alertView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), + new ThemeDescription(pinnedMessageView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_topPanelBackground), + new ThemeDescription(addToContactsButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_addContact), + new ThemeDescription(reportSpamButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_reportSpam), + + new ThemeDescription(replyLineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_chat_replyPanelLine), + new ThemeDescription(replyNameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_replyPanelName), + new ThemeDescription(replyObjectTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_replyPanelMessage), + new ThemeDescription(replyIconImageView, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_replyPanelIcons), + new ThemeDescription(replyCloseImageView, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_replyPanelClose), + + new ThemeDescription(searchUpButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), + new ThemeDescription(searchDownButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), + new ThemeDescription(searchCalendarButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_searchPanelIcons), + new ThemeDescription(searchCountText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_searchPanelText), + + new ThemeDescription(bottomOverlayText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_secretChatStatusText), + new ThemeDescription(bottomOverlayChatText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_fieldOverlayText), + + new ThemeDescription(bigEmptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_serviceText), + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_serviceText), + + new ThemeDescription(progressBar, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_chat_serviceText), + + new ThemeDescription(stickersPanelArrow, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_stickersHintPanel), + new ThemeDescription(stickersListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{StickerCell.class}, null, null, null, Theme.key_chat_stickersHintPanel), + + new ThemeDescription(chatListView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{ChatUnreadCell.class}, new String[]{"backgroundLayout"}, null, null, null, Theme.key_chat_unreadMessagesStartBackground), + new ThemeDescription(chatListView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{ChatUnreadCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_chat_unreadMessagesStartArrowIcon), + new ThemeDescription(chatListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{ChatUnreadCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_unreadMessagesStartText), + + new ThemeDescription(progressView2, ThemeDescription.FLAG_SERVICEBACKGROUND, null, null, null, null, Theme.key_chat_serviceBackground), + new ThemeDescription(emptyView, ThemeDescription.FLAG_SERVICEBACKGROUND, null, null, null, null, Theme.key_chat_serviceBackground), + new ThemeDescription(bigEmptyView, ThemeDescription.FLAG_SERVICEBACKGROUND, null, null, null, null, Theme.key_chat_serviceBackground), + + new ThemeDescription(chatListView, ThemeDescription.FLAG_SERVICEBACKGROUND, new Class[]{ChatLoadingCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_serviceBackground), + new ThemeDescription(chatListView, ThemeDescription.FLAG_PROGRESSBAR, new Class[]{ChatLoadingCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_serviceText), + + new ThemeDescription(mentionListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{BotSwitchCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chat_botSwitchToInlineText), + + new ThemeDescription(mentionListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(mentionListView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{MentionCell.class}, new String[]{"usernameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, new Drawable[]{Theme.chat_inlineResultFile, Theme.chat_inlineResultAudio, Theme.chat_inlineResultLocation}, null, Theme.key_chat_inlineResultIcon), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioProgress), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_chat_inAudioSelectedProgress), + new ThemeDescription(mentionListView, 0, new Class[]{ContextLinkCell.class}, null, null, null, Theme.key_divider), + + new ThemeDescription(gifHintTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_gifSaveHintBackground), + new ThemeDescription(gifHintTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_gifSaveHintText), + + new ThemeDescription(pagedownButtonCounter, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_goDownButtonCounterBackground), + new ThemeDescription(pagedownButtonCounter, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_goDownButtonCounter), + new ThemeDescription(pagedownButtonImage, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_goDownButton), + new ThemeDescription(pagedownButtonImage, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chat_goDownButtonShadow), + new ThemeDescription(pagedownButtonImage, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chat_goDownButtonIcon), + + new ThemeDescription(avatarContainer.getTimeItem(), 0, null, null, null, null, Theme.key_chat_secretTimerBackground), + new ThemeDescription(avatarContainer.getTimeItem(), 0, null, null, null, null, Theme.key_chat_secretTimerText), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java new file mode 100644 index 00000000000..f2df4738a8a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/CommonGroupsActivity.java @@ -0,0 +1,302 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; + +public class CommonGroupsActivity extends BaseFragment { + + private ListAdapter listViewAdapter; + private EmptyTextProgressView emptyView; + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + + private ArrayList chats = new ArrayList<>(); + private int userId; + private boolean loading; + private boolean firstLoaded; + private boolean endReached; + + public CommonGroupsActivity(int uid) { + super(); + userId = uid; + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + getChats(0, 50); + return true; + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + actionBar.setTitle(LocaleController.getString("GroupsInCommonTitle", R.string.GroupsInCommonTitle)); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoGroupsInCommon", R.string.NoGroupsInCommon)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setAdapter(listViewAdapter = new ListAdapter(context)); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position < 0 || position >= chats.size()) { + return; + } + TLRPC.Chat chat = chats.get(position); + Bundle args = new Bundle(); + args.putInt("chat_id", chat.id); + if (!MessagesController.checkCanOpenChat(args, CommonGroupsActivity.this)) { + return; + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + presentFragment(new ChatActivity(args), true); + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(layoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + if (visibleItemCount > 0) { + int totalItemCount = listViewAdapter.getItemCount(); + if (!endReached && !loading && !chats.isEmpty() && firstVisibleItem + visibleItemCount >= totalItemCount - 5) { + getChats(chats.get(chats.size() - 1).id, 100); + } + } + } + }); + + if (loading) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } + return fragmentView; + } + + private void getChats(int max_id, final int count) { + if (loading) { + return; + } + loading = true; + if (emptyView != null && !firstLoaded) { + emptyView.showProgress(); + } + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + TLRPC.TL_messages_getCommonChats req = new TLRPC.TL_messages_getCommonChats(); + req.user_id = MessagesController.getInputUser(userId); + if (req.user_id instanceof TLRPC.TL_inputUserEmpty) { + return; + } + req.limit = count; + req.max_id = max_id; + int reqId = ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + TLRPC.messages_Chats res = (TLRPC.messages_Chats) response; + MessagesController.getInstance().putChats(res.chats, false); + endReached = res.chats.isEmpty() || res.chats.size() != count; + chats.addAll(res.chats); + } else { + endReached = true; + } + loading = false; + firstLoaded = true; + if (emptyView != null) { + emptyView.showTextView(); + } + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + }); + } + }); + ConnectionsManager.getInstance().bindRequestToGuid(reqId, classGuid); + } + + @Override + public void onResume() { + super.onResume(); + if (listViewAdapter != null) { + listViewAdapter.notifyDataSetChanged(); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() != chats.size(); + } + + @Override + public int getItemCount() { + int count = chats.size(); + if (!chats.isEmpty()) { + count++; + if (!endReached) { + count++; + } + } + return count; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new ProfileSearchCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new LoadingCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + default: + view = new TextInfoPrivacyCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + ProfileSearchCell cell = (ProfileSearchCell) holder.itemView; + TLRPC.Chat chat = chats.get(position); + cell.setData(chat, null, null, null, false); + cell.useSeparator = position != chats.size() - 1 || !endReached; + } + } + + @Override + public int getItemViewType(int i) { + if (i < chats.size()) { + return 0; + } else if (!endReached && i == chats.size()) { + return 1; + } + return 2; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof ProfileSearchCell) { + ((ProfileSearchCell) child).update(0); + } + } + } + }; + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{LoadingCell.class, ProfileSearchCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java index 93ffa940cef..36e40de57d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -3,36 +3,229 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.Toast; +import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.NotificationsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; +import org.telegram.messenger.SecretChatHelper; import org.telegram.messenger.Utilities; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.Cells.RadioColorCell; +import org.telegram.ui.Cells.TextColorCell; import org.telegram.ui.ReportOtherActivity; public class AlertsCreator { + public static Dialog processError(TLRPC.TL_error error, BaseFragment fragment, TLObject request, Object... args) { + if (error.code == 406 || error.text == null) { + return null; + } + if (request instanceof TLRPC.TL_channels_joinChannel || + request instanceof TLRPC.TL_channels_editAdmin || + request instanceof TLRPC.TL_channels_inviteToChannel || + request instanceof TLRPC.TL_messages_addChatUser || + request instanceof TLRPC.TL_messages_startBot) { + if (fragment != null) { + AlertsCreator.showAddUserAlert(error.text, fragment, (Boolean) args[0]); + } else { + if (error.text.equals("PEER_FLOOD")) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 1); + } + } + } else if (request instanceof TLRPC.TL_messages_createChat) { + if (error.text.startsWith("FLOOD_WAIT")) { + AlertsCreator.showFloodWaitAlert(error.text, fragment); + } else { + AlertsCreator.showAddUserAlert(error.text, fragment, false); + } + } else if (request instanceof TLRPC.TL_channels_createChannel) { + if (error.text.startsWith("FLOOD_WAIT")) { + AlertsCreator.showFloodWaitAlert(error.text, fragment); + } + } else if (request instanceof TLRPC.TL_messages_editMessage) { + if (!error.text.equals("MESSAGE_NOT_MODIFIED")) { + showSimpleAlert(fragment, LocaleController.getString("EditMessageError", R.string.EditMessageError)); + } + } else if (request instanceof TLRPC.TL_messages_sendMessage || + request instanceof TLRPC.TL_messages_sendMedia || + request instanceof TLRPC.TL_geochats_sendMessage || + request instanceof TLRPC.TL_messages_sendBroadcast || + request instanceof TLRPC.TL_messages_sendInlineBotResult || + request instanceof TLRPC.TL_geochats_sendMedia || + request instanceof TLRPC.TL_messages_forwardMessages) { + if (error.text.equals("PEER_FLOOD")) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.needShowAlert, 0); + } + } else if (request instanceof TLRPC.TL_messages_importChatInvite) { + if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else if (error.text.equals("USERS_TOO_MUCH")) { + showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorFull", R.string.JoinToGroupErrorFull)); + } else { + showSimpleAlert(fragment, LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); + } + } else if (request instanceof TLRPC.TL_messages_getAttachedStickers) { + if (fragment != null && fragment.getParentActivity() != null) { + Toast.makeText(fragment.getParentActivity(), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text, Toast.LENGTH_SHORT).show(); + } + } else if (request instanceof TLRPC.TL_account_confirmPhone) { + if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + showSimpleAlert(fragment, LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + showSimpleAlert(fragment, error.text); + } + } else if (request instanceof TLRPC.TL_auth_resendCode) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + showSimpleAlert(fragment, LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else if (error.code != -1000) { + showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); + } + } else if (request instanceof TLRPC.TL_account_sendConfirmPhoneCode) { + if (error.code == 400) { + return showSimpleAlert(fragment, LocaleController.getString("CancelLinkExpired", R.string.CancelLinkExpired)); + } else if (error.text != null) { + if (error.text.startsWith("FLOOD_WAIT")) { + return showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + return showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); + } + } + } else if (request instanceof TLRPC.TL_account_changePhone) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + showSimpleAlert(fragment, LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + showSimpleAlert(fragment, error.text); + } + } else if (request instanceof TLRPC.TL_account_sendChangePhoneCode) { + if (error.text.contains("PHONE_NUMBER_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidPhoneNumber", R.string.InvalidPhoneNumber)); + } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { + showSimpleAlert(fragment, LocaleController.getString("InvalidCode", R.string.InvalidCode)); + } else if (error.text.contains("PHONE_CODE_EXPIRED")) { + showSimpleAlert(fragment, LocaleController.getString("CodeExpired", R.string.CodeExpired)); + } else if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else if (error.text.startsWith("PHONE_NUMBER_OCCUPIED")) { + showSimpleAlert(fragment, LocaleController.formatString("ChangePhoneNumberOccupied", R.string.ChangePhoneNumberOccupied, (String) args[0])); + } else { + showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); + } + } else if (request instanceof TLRPC.TL_updateUserName) { + switch (error.text) { + case "USERNAME_INVALID": + showSimpleAlert(fragment, LocaleController.getString("UsernameInvalid", R.string.UsernameInvalid)); + break; + case "USERNAME_OCCUPIED": + showSimpleAlert(fragment, LocaleController.getString("UsernameInUse", R.string.UsernameInUse)); + break; + case "USERNAMES_UNAVAILABLE": + showSimpleAlert(fragment, LocaleController.getString("FeatureUnavailable", R.string.FeatureUnavailable)); + break; + default: + showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred)); + break; + } + } else if (request instanceof TLRPC.TL_contacts_importContacts) { + if (error == null || error.text.startsWith("FLOOD_WAIT")) { + showSimpleAlert(fragment, LocaleController.getString("FloodWait", R.string.FloodWait)); + } else { + showSimpleAlert(fragment, LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); + } + } else if (request instanceof TLRPC.TL_account_getPassword || request instanceof TLRPC.TL_account_getTmpPassword) { + if (error.text.startsWith("FLOOD_WAIT")) { + showSimpleToast(fragment, getFloodWaitString(error.text)); + } else { + showSimpleToast(fragment, error.text); + } + } else if (request instanceof TLRPC.TL_payments_sendPaymentForm) { + switch (error.text) { + case "BOT_PRECHECKOUT_FAILED": + showSimpleToast(fragment, LocaleController.getString("PaymentPrecheckoutFailed", R.string.PaymentPrecheckoutFailed)); + break; + case "PAYMENT_FAILED": + showSimpleToast(fragment, LocaleController.getString("PaymentFailed", R.string.PaymentFailed)); + break; + default: + showSimpleToast(fragment, error.text); + break; + } + } else if (request instanceof TLRPC.TL_payments_validateRequestedInfo) { + switch (error.text) { + case "SHIPPING_NOT_AVAILABLE": + showSimpleToast(fragment, LocaleController.getString("PaymentNoShippingMethod", R.string.PaymentNoShippingMethod)); + break; + default: + showSimpleToast(fragment, error.text); + break; + } + } + + return null; + } + + public static Toast showSimpleToast(BaseFragment baseFragment, final String text) { + if (text == null || baseFragment == null || baseFragment.getParentActivity() == null) { + return null; + } + Toast toast = Toast.makeText(baseFragment.getParentActivity(), text, Toast.LENGTH_LONG); + toast.show(); + return toast; + } + + public static Dialog showSimpleAlert(BaseFragment baseFragment, final String text) { + if (text == null || baseFragment == null || baseFragment.getParentActivity() == null) { + return null; + } + AlertDialog.Builder builder = new AlertDialog.Builder(baseFragment.getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(text); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + Dialog dialog = builder.create(); + baseFragment.showDialog(dialog); + return dialog; + } + public static Dialog createMuteAlert(Context context, final long dialog_id) { if (context == null) { return null; @@ -129,6 +322,17 @@ public void run(TLObject response, TLRPC.TL_error error) { return builder.create(); } + private static String getFloodWaitString(String error) { + int time = Utilities.parseInt(error); + String timeString; + if (time < 60) { + timeString = LocaleController.formatPluralString("Seconds", time); + } else { + timeString = LocaleController.formatPluralString("Minutes", time / 60); + } + return LocaleController.formatString("FloodWaitTime", R.string.FloodWaitTime, timeString); + } + public static void showFloodWaitAlert(String error, final BaseFragment fragment) { if (error == null || !error.startsWith("FLOOD_WAIT") || fragment == null || fragment.getParentActivity() == null) { return; @@ -145,7 +349,7 @@ public static void showFloodWaitAlert(String error, final BaseFragment fragment) builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setMessage(LocaleController.formatString("FloodWaitTime", R.string.FloodWaitTime, timeString)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - fragment.showDialog(builder.create(), true); + fragment.showDialog(builder.create(), true, null); } public static void showAddUserAlert(String error, final BaseFragment fragment, boolean isChannel) { @@ -214,11 +418,485 @@ public void onClick(DialogInterface dialogInterface, int i) { case "USER_RESTRICTED": builder.setMessage(LocaleController.getString("UserRestricted", R.string.UserRestricted)); break; + case "YOU_BLOCKED_USER": + builder.setMessage(LocaleController.getString("YouBlockedUser", R.string.YouBlockedUser)); + break; default: builder.setMessage(error); break; } builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - fragment.showDialog(builder.create(), true); + fragment.showDialog(builder.create(), true, null); + } + + public static Dialog createColorSelectDialog(Activity parentActivity, final long dialog_id, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { + int currentColor; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (globalGroup) { + currentColor = preferences.getInt("GroupLed", 0xff0000ff); + } else if (globalAll) { + currentColor = preferences.getInt("MessagesLed", 0xff0000ff); + } else { + if (preferences.contains("color_" + dialog_id)) { + currentColor = preferences.getInt("color_" + dialog_id, 0xff0000ff); + } else { + if ((int) dialog_id < 0) { + currentColor = preferences.getInt("GroupLed", 0xff0000ff); + } else { + currentColor = preferences.getInt("MessagesLed", 0xff0000ff); + } + } + } + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + String descriptions[] = new String[] {LocaleController.getString("ColorRed", R.string.ColorRed), + LocaleController.getString("ColorOrange", R.string.ColorOrange), + LocaleController.getString("ColorYellow", R.string.ColorYellow), + LocaleController.getString("ColorGreen", R.string.ColorGreen), + LocaleController.getString("ColorCyan", R.string.ColorCyan), + LocaleController.getString("ColorBlue", R.string.ColorBlue), + LocaleController.getString("ColorViolet", R.string.ColorViolet), + LocaleController.getString("ColorPink", R.string.ColorPink), + LocaleController.getString("ColorWhite", R.string.ColorWhite)}; + final int selectedColor[] = new int[] {currentColor}; + for (int a = 0; a < 9; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(TextColorCell.colors[a], TextColorCell.colors[a]); + cell.setTextAndValue(descriptions[a], currentColor == TextColorCell.colorsToSave[a]); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int count = linearLayout.getChildCount(); + for (int a = 0; a < count; a++) { + RadioColorCell cell = (RadioColorCell) linearLayout.getChildAt(a); + cell.setChecked(cell == v, true); + } + selectedColor[0] = TextColorCell.colorsToSave[(Integer) v.getTag()]; + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("LedColor", R.string.LedColor)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("Set", R.string.Set), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int which) { + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (globalAll) { + editor.putInt("MessagesLed", selectedColor[0]); + } else if (globalGroup) { + editor.putInt("GroupLed", selectedColor[0]); + } else { + editor.putInt("color_" + dialog_id, selectedColor[0]); + } + editor.commit(); + if (onSelect != null) { + onSelect.run(); + } + } + }); + builder.setNeutralButton(LocaleController.getString("LedDisabled", R.string.LedDisabled), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (globalAll) { + editor.putInt("MessagesLed", 0); + } else if (globalGroup) { + editor.putInt("GroupLed", 0); + } else { + editor.putInt("color_" + dialog_id, 0); + } + editor.commit(); + if (onSelect != null) { + onSelect.run(); + } + } + }); + if (!globalAll && !globalGroup) { + builder.setNegativeButton(LocaleController.getString("Default", R.string.Default), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.remove("color_" + dialog_id); + editor.commit(); + if (onSelect != null) { + onSelect.run(); + } + } + }); + } + return builder.create(); + } + + public static Dialog createVibrationSelectDialog(Activity parentActivity, final BaseFragment parentFragment, final long dialog_id, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { + String prefix; + if (dialog_id != 0) { + prefix = "vibrate_"; + } else { + prefix = globalGroup ? "vibrate_group" : "vibrate_messages"; + } + return createVibrationSelectDialog(parentActivity, parentFragment, dialog_id, prefix, onSelect); + } + + public static Dialog createVibrationSelectDialog(Activity parentActivity, final BaseFragment parentFragment, final long dialog_id, final String prefKeyPrefix, final Runnable onSelect) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + final int selected[] = new int[1]; + String descriptions[]; + if (dialog_id != 0) { + selected[0] = preferences.getInt(prefKeyPrefix + dialog_id, 0); + if (selected[0] == 3) { + selected[0] = 2; + } else if (selected[0] == 2) { + selected[0] = 3; + } + descriptions = new String[]{ + LocaleController.getString("VibrationDefault", R.string.VibrationDefault), + LocaleController.getString("Short", R.string.Short), + LocaleController.getString("Long", R.string.Long), + LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled) + }; + } else { + selected[0] = preferences.getInt(prefKeyPrefix, 0); + if (selected[0] == 0) { + selected[0] = 1; + } else if (selected[0] == 1) { + selected[0] = 2; + } else if (selected[0] == 2) { + selected[0] = 0; + } + descriptions = new String[]{ + LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), + LocaleController.getString("VibrationDefault", R.string.VibrationDefault), + LocaleController.getString("Short", R.string.Short), + LocaleController.getString("Long", R.string.Long), + LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent) + }; + } + + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + for (int a = 0; a < descriptions.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setTextAndValue(descriptions[a], selected[0] == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selected[0] = (Integer) v.getTag(); + + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (dialog_id != 0) { + if (selected[0] == 0) { + editor.putInt(prefKeyPrefix + dialog_id, 0); + } else if (selected[0] == 1) { + editor.putInt(prefKeyPrefix + dialog_id, 1); + } else if (selected[0] == 2) { + editor.putInt(prefKeyPrefix + dialog_id, 3); + } else if (selected[0] == 3) { + editor.putInt(prefKeyPrefix + dialog_id, 2); + } + } else { + if (selected[0] == 0) { + editor.putInt(prefKeyPrefix, 2); + } else if (selected[0] == 1) { + editor.putInt(prefKeyPrefix, 0); + } else if (selected[0] == 2) { + editor.putInt(prefKeyPrefix, 1); + } else if (selected[0] == 3) { + editor.putInt(prefKeyPrefix, 3); + } else if (selected[0] == 4) { + editor.putInt(prefKeyPrefix, 4); + } + } + editor.commit(); + if (parentFragment != null) { + parentFragment.dismissCurrentDialig(); + } + if (onSelect != null) { + onSelect.run(); + } + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("Vibrate", R.string.Vibrate)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); + return builder.create(); + } + + public static Dialog createPrioritySelectDialog(Activity parentActivity, final BaseFragment parentFragment, final long dialog_id, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + final int selected[] = new int[1]; + String descriptions[]; + if (dialog_id != 0) { + selected[0] = preferences.getInt("priority_" + dialog_id, 3); + if (selected[0] == 3) { + selected[0] = 0; + } else { + selected[0]++; + } + descriptions = new String[]{ + LocaleController.getString("NotificationsPrioritySettings", R.string.NotificationsPrioritySettings), + LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), + LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), + LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax) + }; + } else { + if (globalAll) { + selected[0] = preferences.getInt("priority_messages", 1); + } else if (globalGroup) { + selected[0] = preferences.getInt("priority_group", 1); + } + descriptions = new String[]{ + LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), + LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), + LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax) + }; + } + + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + for (int a = 0; a < descriptions.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setTextAndValue(descriptions[a], selected[0] == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selected[0] = (Integer) v.getTag(); + + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + if (dialog_id != 0) { + if (selected[0] == 0) { + selected[0] = 3; + } else { + selected[0]--; + } + editor.putInt("priority_" + dialog_id, selected[0]); + } else { + editor.putInt(globalGroup ? "priority_group" : "priority_messages", selected[0]); + } + editor.commit(); + if (parentFragment != null) { + parentFragment.dismissCurrentDialig(); + } + if (onSelect != null) { + onSelect.run(); + } + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); + return builder.create(); + } + + public static Dialog createPopupSelectDialog(Activity parentActivity, final BaseFragment parentFragment, final boolean globalGroup, final boolean globalAll, final Runnable onSelect) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + final int selected[] = new int[1]; + if (globalAll) { + selected[0] = preferences.getInt("popupAll", 0); + } else if (globalGroup) { + selected[0] = preferences.getInt("popupGroup", 0); + } + String descriptions[] = new String[]{ + LocaleController.getString("NoPopup", R.string.NoPopup), + LocaleController.getString("OnlyWhenScreenOn", R.string.OnlyWhenScreenOn), + LocaleController.getString("OnlyWhenScreenOff", R.string.OnlyWhenScreenOff), + LocaleController.getString("AlwaysShowPopup", R.string.AlwaysShowPopup) + }; + + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + for (int a = 0; a < descriptions.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setTag(a); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setTextAndValue(descriptions[a], selected[0] == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + selected[0] = (Integer) v.getTag(); + + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(globalGroup ? "popupGroup" : "popupAll", selected[0]); + editor.commit(); + if (parentFragment != null) { + parentFragment.dismissCurrentDialig(); + } + if (onSelect != null) { + onSelect.run(); + } + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(LocaleController.getString("PopupNotification", R.string.PopupNotification)); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); + return builder.create(); + } + + public static Dialog createSingleChoiceDialog(Activity parentActivity, final BaseFragment parentFragment, final String[] options, final String title, final int selected, final DialogInterface.OnClickListener listener) { + final LinearLayout linearLayout = new LinearLayout(parentActivity); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + for (int a = 0; a < options.length; a++) { + RadioColorCell cell = new RadioColorCell(parentActivity); + cell.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); + cell.setTag(a); + cell.setCheckColor(0xffb3b3b3, 0xff37a9f0); + cell.setTextAndValue(options[a], selected == a); + linearLayout.addView(cell); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int sel = (Integer) v.getTag(); + + if (parentFragment != null) { + parentFragment.dismissCurrentDialig(); + } + listener.onClick(null, sel); + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + builder.setTitle(title); + builder.setView(linearLayout); + builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); + return builder.create(); + } + + public static AlertDialog.Builder createTTLAlert(final Context context, final TLRPC.EncryptedChat encryptedChat) { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(LocaleController.getString("MessageLifetime", R.string.MessageLifetime)); + final NumberPicker numberPicker = new NumberPicker(context); + numberPicker.setMinValue(0); + numberPicker.setMaxValue(20); + if (encryptedChat.ttl > 0 && encryptedChat.ttl < 16) { + numberPicker.setValue(encryptedChat.ttl); + } else if (encryptedChat.ttl == 30) { + numberPicker.setValue(16); + } else if (encryptedChat.ttl == 60) { + numberPicker.setValue(17); + } else if (encryptedChat.ttl == 60 * 60) { + numberPicker.setValue(18); + } else if (encryptedChat.ttl == 60 * 60 * 24) { + numberPicker.setValue(19); + } else if (encryptedChat.ttl == 60 * 60 * 24 * 7) { + numberPicker.setValue(20); + } else if (encryptedChat.ttl == 0) { + numberPicker.setValue(0); + } + numberPicker.setFormatter(new NumberPicker.Formatter() { + @Override + public String format(int value) { + if (value == 0) { + return LocaleController.getString("ShortMessageLifetimeForever", R.string.ShortMessageLifetimeForever); + } else if (value >= 1 && value < 16) { + return LocaleController.formatTTLString(value); + } else if (value == 16) { + return LocaleController.formatTTLString(30); + } else if (value == 17) { + return LocaleController.formatTTLString(60); + } else if (value == 18) { + return LocaleController.formatTTLString(60 * 60); + } else if (value == 19) { + return LocaleController.formatTTLString(60 * 60 * 24); + } else if (value == 20) { + return LocaleController.formatTTLString(60 * 60 * 24 * 7); + } + return ""; + } + }); + builder.setView(numberPicker); + builder.setNegativeButton(LocaleController.getString("Done", R.string.Done), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int oldValue = encryptedChat.ttl; + which = numberPicker.getValue(); + if (which >= 0 && which < 16) { + encryptedChat.ttl = which; + } else if (which == 16) { + encryptedChat.ttl = 30; + } else if (which == 17) { + encryptedChat.ttl = 60; + } else if (which == 18) { + encryptedChat.ttl = 60 * 60; + } else if (which == 19) { + encryptedChat.ttl = 60 * 60 * 24; + } else if (which == 20) { + encryptedChat.ttl = 60 * 60 * 24 * 7; + } + if (oldValue != encryptedChat.ttl) { + SecretChatHelper.getInstance().sendTTLMessage(encryptedChat, null); + MessagesStorage.getInstance().updateEncryptedChatTTL(encryptedChat); + } + } + }); + return builder; + } +// +// public static AlertDialog createExpireDateAlert(final Context context, final boolean month, final int[] result, final Runnable callback) { +// AlertDialog.Builder builder = new AlertDialog.Builder(context); +// builder.setTitle(month ? LocaleController.getString("PaymentCardExpireDateMonth", R.string.PaymentCardExpireDateMonth) : LocaleController.getString("PaymentCardExpireDateYear", R.string.PaymentCardExpireDateYear)); +// final NumberPicker numberPicker = new NumberPicker(context); +// final int currentYear; +// if (month) { +// numberPicker.setMinValue(1); +// numberPicker.setMaxValue(12); +// currentYear = 0; +// } else { +// Calendar rightNow = Calendar.getInstance(); +// currentYear = rightNow.get(Calendar.YEAR); +// numberPicker.setMinValue(0); +// numberPicker.setMaxValue(30); +// } +// numberPicker.setFormatter(new NumberPicker.Formatter() { +// @Override +// public String format(int value) { +// if (month) { +// return String.format(Locale.US, "%02d", value); +// } else { +// return String.format(Locale.US, "%02d", value + currentYear); +// } +// } +// }); +// builder.setView(numberPicker); +// builder.setNegativeButton(LocaleController.getString("Done", R.string.Done), new DialogInterface.OnClickListener() { +// @Override +// public void onClick(DialogInterface dialog, int which) { +// result[0] = month ? numberPicker.getValue() : ((numberPicker.getValue() + currentYear) % 100); +// callback.run(); +// } +// }); +// return builder.create(); +// } + + public interface PaymentAlertDelegate { + void didPressedNewCard(); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java index 9a49f438579..848bd9538c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -29,6 +29,7 @@ import java.io.File; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public class AnimatedFileDrawable extends BitmapDrawable implements Animatable { @@ -49,6 +50,10 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable { private File path; private boolean recycleWithSecond; + private long lastFrameDecodeTime; + + private RectF actualDrawRect = new RectF(); + private BitmapShader renderingShader; private BitmapShader nextRenderingShader; private BitmapShader backgroundShader; @@ -90,6 +95,10 @@ public void run() { nativePtr = 0; } if (nativePtr == 0) { + if (renderingBitmap != null) { + renderingBitmap.recycle(); + renderingBitmap = null; + } if (backgroundBitmap != null) { backgroundBitmap.recycle(); backgroundBitmap = null; @@ -111,6 +120,7 @@ public void run() { } else if (parentView != null) { parentView.invalidate(); } + scheduleNextGetFrame(); } }; @@ -127,17 +137,19 @@ public void run() { try { backgroundBitmap = Bitmap.createBitmap(metaData[0], metaData[1], Bitmap.Config.ARGB_8888); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (backgroundShader == null && backgroundBitmap != null && roundRadius != 0) { backgroundShader = new BitmapShader(backgroundBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); } } if (backgroundBitmap != null) { + lastFrameDecodeTime = System.currentTimeMillis(); getVideoFrame(nativePtr, backgroundBitmap, metaData); + } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } AndroidUtilities.runOnUIThread(uiRunnable); @@ -163,10 +175,6 @@ public AnimatedFileDrawable(File file, boolean createDecoder) { } } - protected void postToDecodeQueue(Runnable runnable) { - executor.execute(runnable); - } - public void setParentView(View view) { parentView = view; } @@ -190,6 +198,10 @@ public void recycle() { destroyDecoder(nativePtr); nativePtr = 0; } + if (renderingBitmap != null) { + renderingBitmap.recycle(); + renderingBitmap = null; + } if (nextRenderingBitmap != null) { nextRenderingBitmap.recycle(); nextRenderingBitmap = null; @@ -197,10 +209,6 @@ public void recycle() { } else { destroyWhenDone = true; } - if (renderingBitmap != null) { - renderingBitmap.recycle(); - renderingBitmap = null; - } } protected static void runOnUiThread(Runnable task) { @@ -231,17 +239,19 @@ public void start() { return; } isRunning = true; - if (renderingBitmap == null) { - scheduleNextGetFrame(); - } + scheduleNextGetFrame(); runOnUiThread(mStartTask); } private void scheduleNextGetFrame() { - if (loadFrameTask != null || nativePtr == 0 && decoderCreated || destroyWhenDone) { + if (loadFrameTask != null || nativePtr == 0 && decoderCreated || destroyWhenDone || !isRunning) { return; } - postToDecodeQueue(loadFrameTask = loadFrameRunnable); + long ms = 0; + if (lastFrameDecodeTime != 0) { + ms = Math.min(invalidateAfter, Math.max(0, invalidateAfter - (System.currentTimeMillis() - lastFrameDecodeTime))); + } + executor.schedule(loadFrameTask = loadFrameRunnable, ms, TimeUnit.MILLISECONDS); } @Override @@ -275,17 +285,17 @@ public void draw(Canvas canvas) { if (nativePtr == 0 && decoderCreated || destroyWhenDone) { return; } + long now = System.currentTimeMillis(); if (isRunning) { if (renderingBitmap == null && nextRenderingBitmap == null) { scheduleNextGetFrame(); - } else if (Math.abs(System.currentTimeMillis() - lastFrameTime) >= invalidateAfter) { + } else if (Math.abs(now - lastFrameTime) >= invalidateAfter) { if (nextRenderingBitmap != null) { - scheduleNextGetFrame(); renderingBitmap = nextRenderingBitmap; renderingShader = nextRenderingShader; nextRenderingBitmap = null; nextRenderingShader = null; - lastFrameTime = System.currentTimeMillis(); + lastFrameTime = now; } } } @@ -331,7 +341,7 @@ public void draw(Canvas canvas) { } renderingShader.setLocalMatrix(shaderMatrix); - canvas.drawRoundRect(roundRect, roundRadius, roundRadius, getPaint()); + canvas.drawRoundRect(actualDrawRect, roundRadius, roundRadius, getPaint()); } else { canvas.translate(dstRect.left, dstRect.top); if (metaData[2] == 90) { @@ -348,7 +358,9 @@ public void draw(Canvas canvas) { canvas.drawBitmap(renderingBitmap, 0, 0, getPaint()); } if (isRunning) { - uiHandler.postDelayed(mInvalidateTask, invalidateAfter); + long timeToNextFrame = Math.max(1, invalidateAfter - (now - lastFrameTime) - 17); + uiHandler.removeCallbacks(mInvalidateTask); + uiHandler.postDelayed(mInvalidateTask, Math.min(timeToNextFrame, invalidateAfter)); } } } @@ -372,6 +384,10 @@ public Bitmap getAnimatedBitmap() { return null; } + public void setActualDrawRect(int x, int y, int width, int height) { + actualDrawRect.set(x, y, x + width, y + height); + } + public void setRoundRadius(int value) { roundRadius = value; getPaint().setFlags(Paint.ANTI_ALIAS_FLAG); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java index 02616e01992..6a9b6477605 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -21,27 +21,12 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; -import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.ApplicationLoader; import org.telegram.ui.ActionBar.Theme; public class AvatarDrawable extends Drawable { - private static Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private static TextPaint namePaint; - private static TextPaint namePaintSmall; - private static int[] arrColors = {0xffe56555, 0xfff28c48, 0xff8e85ee, 0xff76c84d, 0xff5fbed5, 0xff549cdd, 0xff8e85ee, 0xfff2749a}; - private static int[] arrColorsProfiles = {0xffd86f65, 0xfff69d61, 0xff8c79d2, 0xff67b35d, 0xff56a2bb, Theme.ACTION_BAR_MAIN_AVATAR_COLOR, 0xff8c79d2, 0xfff37fa6}; - private static int[] arrColorsProfilesBack = {0xffca6056, 0xfff18944, 0xff7d6ac4, 0xff56a14c, 0xff4492ac, Theme.ACTION_BAR_PROFILE_COLOR, 0xff7d6ac4, 0xff4c84b6}; - private static int[] arrColorsProfilesText = {0xfff9cbc5, 0xfffdddc8, 0xffcdc4ed, 0xffc0edba, 0xffb8e2f0, Theme.ACTION_BAR_PROFILE_SUBTITLE_COLOR, 0xffcdc4ed, 0xffb3d7f7}; - private static int[] arrColorsNames = {0xffca5650, 0xffd87b29, 0xff4e92cc, 0xff50b232, 0xff42b1a8, 0xff4e92cc, 0xff4e92cc, 0xff4e92cc}; - private static int[] arrColorsButtons = {Theme.ACTION_BAR_RED_SELECTOR_COLOR, Theme.ACTION_BAR_ORANGE_SELECTOR_COLOR, Theme.ACTION_BAR_VIOLET_SELECTOR_COLOR, - Theme.ACTION_BAR_GREEN_SELECTOR_COLOR, Theme.ACTION_BAR_CYAN_SELECTOR_COLOR, Theme.ACTION_BAR_BLUE_SELECTOR_COLOR, Theme.ACTION_BAR_VIOLET_SELECTOR_COLOR, Theme.ACTION_BAR_BLUE_SELECTOR_COLOR}; - - private static Drawable broadcastDrawable; - private static Drawable photoDrawable; - + private TextPaint namePaint; private int color; private StaticLayout textLayout; private float textWidth; @@ -50,24 +35,14 @@ public class AvatarDrawable extends Drawable { private boolean isProfile; private boolean drawBrodcast; private boolean drawPhoto; - private boolean smallStyle; private StringBuilder stringBuilder = new StringBuilder(5); public AvatarDrawable() { super(); - if (namePaint == null) { - namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - namePaint.setColor(0xffffffff); - - namePaintSmall = new TextPaint(Paint.ANTI_ALIAS_FLAG); - namePaintSmall.setColor(0xffffffff); - - broadcastDrawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.broadcast_w); - } - - namePaint.setTextSize(AndroidUtilities.dp(20)); - namePaintSmall.setTextSize(AndroidUtilities.dp(14)); + namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + namePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + namePaint.setTextSize(AndroidUtilities.dp(18)); } public AvatarDrawable(TLRPC.User user) { @@ -98,59 +73,39 @@ public void setProfile(boolean value) { isProfile = value; } - public void setSmallStyle(boolean value) { - smallStyle = value; - } - public static int getColorIndex(int id) { if (id >= 0 && id < 8) { return id; } - /*try { - String str; - if (id >= 0) { - str = String.format(Locale.US, "%d%d", id, UserConfig.getClientUserId()); - } else { - str = String.format(Locale.US, "%d", id); - } - if (str.length() > 15) { - str = str.substring(0, 15); - } - java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); - byte[] digest = md.digest(str.getBytes()); - int b = digest[Math.abs(id % 16)]; - if (b < 0) { - b += 256; - } - return Math.abs(b) % arrColors.length; - } catch (Exception e) { - FileLog.e("tmessages", e); - }*/ - return Math.abs(id % arrColors.length); + return Math.abs(id % Theme.keys_avatar_background.length); } public static int getColorForId(int id) { - return arrColors[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_background[getColorIndex(id)]); } public static int getButtonColorForId(int id) { - return arrColorsButtons[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_actionBarSelector[getColorIndex(id)]); + } + + public static int getIconColorForId(int id) { + return Theme.getColor(Theme.keys_avatar_actionBarIcon[getColorIndex(id)]); } public static int getProfileColorForId(int id) { - return arrColorsProfiles[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_backgroundInProfile[getColorIndex(id)]); } public static int getProfileTextColorForId(int id) { - return arrColorsProfilesText[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_subtitleInProfile[getColorIndex(id)]); } public static int getProfileBackColorForId(int id) { - return arrColorsProfilesBack[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_backgroundActionBar[getColorIndex(id)]); } public static int getNameColorForId(int id) { - return arrColorsNames[getColorIndex(id)]; + return Theme.getColor(Theme.keys_avatar_nameInMessage[getColorIndex(id)]); } public void setInfo(TLRPC.User user) { @@ -169,15 +124,23 @@ public void setColor(int value) { color = value; } + public void setTextSize(int size) { + namePaint.setTextSize(size); + } + public void setInfo(int id, String firstName, String lastName, boolean isBroadcast) { setInfo(id, firstName, lastName, isBroadcast, null); } + public int getColor() { + return color; + } + public void setInfo(int id, String firstName, String lastName, boolean isBroadcast, String custom) { if (isProfile) { - color = arrColorsProfiles[getColorIndex(id)]; + color = getProfileColorForId(id); } else { - color = arrColors[getColorIndex(id)]; + color = getColorForId(id); } drawBrodcast = isBroadcast; @@ -192,20 +155,20 @@ public void setInfo(int id, String firstName, String lastName, boolean isBroadca stringBuilder.append(custom); } else { if (firstName != null && firstName.length() > 0) { - stringBuilder.append(firstName.substring(0, 1)); + stringBuilder.appendCodePoint(firstName.codePointAt(0)); } if (lastName != null && lastName.length() > 0) { - String lastch = null; + Integer lastch = null; for (int a = lastName.length() - 1; a >= 0; a--) { if (lastch != null && lastName.charAt(a) == ' ') { break; } - lastch = lastName.substring(a, a + 1); + lastch = lastName.codePointAt(a); } if (Build.VERSION.SDK_INT >= 16) { stringBuilder.append("\u200C"); } - stringBuilder.append(lastch); + stringBuilder.appendCodePoint(lastch); } else if (firstName != null && firstName.length() > 0) { for (int a = firstName.length() - 1; a >= 0; a--) { if (firstName.charAt(a) == ' ') { @@ -213,7 +176,7 @@ public void setInfo(int id, String firstName, String lastName, boolean isBroadca if (Build.VERSION.SDK_INT >= 16) { stringBuilder.append("\u200C"); } - stringBuilder.append(firstName.substring(a + 1, a + 2)); + stringBuilder.appendCodePoint(firstName.codePointAt(a + 1)); break; } } @@ -224,14 +187,14 @@ public void setInfo(int id, String firstName, String lastName, boolean isBroadca if (stringBuilder.length() > 0) { String text = stringBuilder.toString().toUpperCase(); try { - textLayout = new StaticLayout(text, (smallStyle ? namePaintSmall : namePaint), AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + textLayout = new StaticLayout(text, namePaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (textLayout.getLineCount() > 0) { textLeft = textLayout.getLineLeft(0); textWidth = textLayout.getLineWidth(0); textHeight = textLayout.getLineBottom(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { textLayout = null; @@ -239,9 +202,6 @@ public void setInfo(int id, String firstName, String lastName, boolean isBroadca } public void setDrawPhoto(boolean value) { - if (value && photoDrawable == null) { - photoDrawable = ApplicationLoader.applicationContext.getResources().getDrawable(R.drawable.photo_w); - } drawPhoto = value; } @@ -252,25 +212,26 @@ public void draw(Canvas canvas) { return; } int size = bounds.width(); - paint.setColor(color); + namePaint.setColor(Theme.getColor(Theme.key_avatar_text)); + Theme.avatar_backgroundPaint.setColor(color); canvas.save(); canvas.translate(bounds.left, bounds.top); - canvas.drawCircle(size / 2, size / 2, size / 2, paint); + canvas.drawCircle(size / 2, size / 2, size / 2, Theme.avatar_backgroundPaint); - if (drawBrodcast && broadcastDrawable != null) { - int x = (size - broadcastDrawable.getIntrinsicWidth()) / 2; - int y = (size - broadcastDrawable.getIntrinsicHeight()) / 2; - broadcastDrawable.setBounds(x, y, x + broadcastDrawable.getIntrinsicWidth(), y + broadcastDrawable.getIntrinsicHeight()); - broadcastDrawable.draw(canvas); + if (drawBrodcast && Theme.avatar_broadcastDrawable != null) { + int x = (size - Theme.avatar_broadcastDrawable.getIntrinsicWidth()) / 2; + int y = (size - Theme.avatar_broadcastDrawable.getIntrinsicHeight()) / 2; + Theme.avatar_broadcastDrawable.setBounds(x, y, x + Theme.avatar_broadcastDrawable.getIntrinsicWidth(), y + Theme.avatar_broadcastDrawable.getIntrinsicHeight()); + Theme.avatar_broadcastDrawable.draw(canvas); } else { if (textLayout != null) { canvas.translate((size - textWidth) / 2 - textLeft, (size - textHeight) / 2); textLayout.draw(canvas); - } else if (drawPhoto && photoDrawable != null) { - int x = (size - photoDrawable.getIntrinsicWidth()) / 2; - int y = (size - photoDrawable.getIntrinsicHeight()) / 2; - photoDrawable.setBounds(x, y, x + photoDrawable.getIntrinsicWidth(), y + photoDrawable.getIntrinsicHeight()); - photoDrawable.draw(canvas); + } else if (drawPhoto && Theme.avatar_photoDrawable != null) { + int x = (size - Theme.avatar_photoDrawable.getIntrinsicWidth()) / 2; + int y = (size - Theme.avatar_photoDrawable.getIntrinsicHeight()) / 2; + Theme.avatar_photoDrawable.setBounds(x, y, x + Theme.avatar_photoDrawable.getIntrinsicWidth(), y + Theme.avatar_photoDrawable.getIntrinsicHeight()); + Theme.avatar_photoDrawable.draw(canvas); } } canvas.restore(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java index 35a08d396b0..2028c804a80 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AvatarUpdater.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -24,6 +24,8 @@ import org.telegram.messenger.BuildConfig; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.MediaController; +import org.telegram.messenger.VideoEditedInfo; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; @@ -79,7 +81,7 @@ public void openCamera() { } parentFragment.startActivityForResult(takePictureIntent, 13); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -107,13 +109,13 @@ public void startPhotoSelectActivity() { photoPickerIntent.setType("image/*"); parentFragment.startActivityForResult(photoPickerIntent, 14); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @Override - public boolean didSelectVideo(String path) { - return true; + public void didSelectVideo(String path, VideoEditedInfo info, long estimatedSize, long estimatedDuration, String caption) { + } }); parentFragment.presentFragment(fragment); @@ -121,7 +123,7 @@ public boolean didSelectVideo(String path) { private void startCrop(String path, Uri uri) { try { - LaunchActivity activity = (LaunchActivity)parentFragment.getParentActivity(); + LaunchActivity activity = (LaunchActivity) parentFragment.getParentActivity(); if (activity == null) { return; } @@ -135,7 +137,7 @@ private void startCrop(String path, Uri uri) { photoCropActivity.setDelegate(this); activity.presentFragment(photoCropActivity); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); Bitmap bitmap = ImageLoader.loadBitmap(path, uri, 800, 800, true); processBitmap(bitmap); } @@ -149,7 +151,7 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { try { ExifInterface ei = new ExifInterface(currentPicturePath); int exif = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - switch(exif) { + switch (exif) { case ExifInterface.ORIENTATION_ROTATE_90: orientation = 90; break; @@ -161,13 +163,13 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } final ArrayList arrayList = new ArrayList<>(); arrayList.add(new MediaController.PhotoEntry(0, 0, 0, currentPicturePath, orientation, false)); PhotoViewer.getInstance().openPhotoForSelect(arrayList, 0, 1, new PhotoViewer.EmptyPhotoViewerProvider() { @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { String path = null; MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) arrayList.get(0); if (photoEntry.imagePath != null) { @@ -212,7 +214,7 @@ private void processBitmap(Bitmap bitmap) { uploadingAvatar = FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE) + "/" + bigPhoto.location.volume_id + "_" + bigPhoto.location.local_id + ".jpg"; NotificationCenter.getInstance().addObserver(AvatarUpdater.this, NotificationCenter.FileDidUpload); NotificationCenter.getInstance().addObserver(AvatarUpdater.this, NotificationCenter.FileDidFailUpload); - FileLoader.getInstance().uploadFile(uploadingAvatar, false, true); + FileLoader.getInstance().uploadFile(uploadingAvatar, false, true, ConnectionsManager.FileTypePhoto); } } } @@ -225,12 +227,12 @@ public void didFinishEdit(Bitmap bitmap) { @Override public void didReceivedNotification(int id, final Object... args) { if (id == NotificationCenter.FileDidUpload) { - String location = (String)args[0]; + String location = (String) args[0]; if (uploadingAvatar != null && location.equals(uploadingAvatar)) { NotificationCenter.getInstance().removeObserver(AvatarUpdater.this, NotificationCenter.FileDidUpload); NotificationCenter.getInstance().removeObserver(AvatarUpdater.this, NotificationCenter.FileDidFailUpload); if (delegate != null) { - delegate.didUploadedPhoto((TLRPC.InputFile)args[1], smallPhoto, bigPhoto); + delegate.didUploadedPhoto((TLRPC.InputFile) args[1], smallPhoto, bigPhoto); } uploadingAvatar = null; if (clearAfterUpdate) { @@ -239,7 +241,7 @@ public void didReceivedNotification(int id, final Object... args) { } } } else if (id == NotificationCenter.FileDidFailUpload) { - String location = (String)args[0]; + String location = (String) args[0]; if (uploadingAvatar != null && location.equals(uploadingAvatar)) { NotificationCenter.getInstance().removeObserver(AvatarUpdater.this, NotificationCenter.FileDidUpload); NotificationCenter.getInstance().removeObserver(AvatarUpdater.this, NotificationCenter.FileDidFailUpload); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java index 8664e8b1e18..a0e5017a566 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BackupImageView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -89,7 +89,9 @@ public void setImageBitmap(Bitmap bitmap) { } public void setImageResource(int resId) { - imageReceiver.setImageBitmap(getResources().getDrawable(resId)); + Drawable drawable = getResources().getDrawable(resId); + imageReceiver.setImageBitmap(drawable); + invalidate(); } public void setImageDrawable(Drawable drawable) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BetterRatingView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BetterRatingView.java new file mode 100644 index 00000000000..53c688aca02 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BetterRatingView.java @@ -0,0 +1,74 @@ +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.view.MotionEvent; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; + +/** + * Created by grishka on 10.02.17. + */ + +public class BetterRatingView extends View{ + private Bitmap filledStar, hollowStar; + private Paint paint=new Paint(); + private int numStars=5; + private int selectedRating=0; + private OnRatingChangeListener listener; + + public BetterRatingView(Context context){ + super(context); + filledStar=BitmapFactory.decodeResource(getResources(), R.drawable.ic_rating_star_filled).extractAlpha(); + hollowStar=BitmapFactory.decodeResource(getResources(), R.drawable.ic_rating_star).extractAlpha(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ + setMeasuredDimension(numStars*AndroidUtilities.dp(32)+(numStars-1)*AndroidUtilities.dp(16), AndroidUtilities.dp(32)); + } + + @Override + protected void onDraw(Canvas canvas){ + for(int i=0;ioffset && event.getX() 0) { - CharSequence text = getText(); - if (text.length() > 1 && text.charAt(0) == '@') { - int index = TextUtils.indexOf(text, ' '); - if (index != -1) { - TextPaint paint = getPaint(); - CharSequence str = text.subSequence(0, index + 1); - int size = (int) Math.ceil(paint.measureText(text, 0, index + 1)); - int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - userNameLength = str.length(); - CharSequence captionFinal = TextUtils.ellipsize(caption, paint, width - size, TextUtils.TruncateAt.END); - xOffset = size; - try { - captionLayout = new StaticLayout(captionFinal, getPaint(), width - size, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - if (captionLayout.getLineCount() > 0) { - xOffset += -captionLayout.getLineLeft(0); - } - yOffset = (getMeasuredHeight() - captionLayout.getLineBottom(0)) / 2 + AndroidUtilities.dp(0.5f); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - } - } - - @Override - protected void onDraw(Canvas canvas) { - try { - super.onDraw(canvas); - if (captionLayout != null && userNameLength == length()) { - Paint paint = getPaint(); - int oldColor = getPaint().getColor(); - paint.setColor(0xffb2b2b2); - canvas.save(); - canvas.translate(xOffset, yOffset); - captionLayout.draw(canvas); - canvas.restore(); - paint.setColor(oldColor); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - - try { - if (editorField != null && mCursorDrawable != null && mCursorDrawable[0] != null) { - long mShowCursor = editorField.getLong(editor); - boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500; - if (showCursor) { - canvas.save(); - canvas.translate(0, getPaddingTop()); - mCursorDrawable[0].draw(canvas); - canvas.restore(); - } - } - } catch (Throwable e) { - //ignore - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isPopupShowing() && event.getAction() == MotionEvent.ACTION_DOWN) { - showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2, 0); - openKeyboardInternal(); - } - try { - return super.onTouchEvent(event); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - return false; - } - } - private EditTextCaption messageEditText; private ImageView sendButton; private ImageView cancelBotButton; private ImageView emojiButton; private EmojiView emojiView; private TextView recordTimeText; + private FrameLayout audioVideoButtonContainer; + private AnimatorSet audioVideoButtonAnimation; private ImageView audioSendButton; + private ImageView videoSendButton; private FrameLayout recordPanel; private FrameLayout recordedAudioPanel; + private ImageView recordDeleteImageView; private SeekBarWaveformView recordedAudioSeekBar; + private View recordedAudioBackground; private ImageView recordedAudioPlayButton; private TextView recordedAudioTimeTextView; private LinearLayout slideText; + private ImageView recordCancelImage; + private TextView recordCancelText; + private LinearLayout recordTimeContainer; private RecordDot recordDot; private SizeNotifierFrameLayout sizeNotifierLayout; - private LinearLayout attachButton; + private LinearLayout attachLayout; + private ImageView attachButton; private ImageView botButton; private LinearLayout textFieldContainer; private FrameLayout sendButtonContainer; + private FrameLayout doneButtonContainer; + private ImageView doneButtonImage; + private AnimatorSet doneButtonAnimation; + private ContextProgressView doneButtonProgress; private View topView; private PopupWindow botKeyboardPopup; private BotKeyboardView botKeyboardView; private ImageView notifyButton; private RecordCircle recordCircle; private CloseProgressDrawable2 progressDrawable; - private Drawable backgroundDrawable; - private Drawable dotDrawable; + private Paint dotPaint; + private Drawable playDrawable; + private Drawable pauseDrawable; private MessageObject editingMessageObject; private int editingMessageReqId; private boolean editingCaption; + private boolean hasRecordVideo = BuildVars.DEBUG_PRIVATE_VERSION; + private int currentPopupContentType = -1; private boolean silent; @@ -331,12 +219,12 @@ public boolean onTouchEvent(MotionEvent event) { private int botCount; private boolean hasBotCommands; - private PowerManager.WakeLock mWakeLock; + private PowerManager.WakeLock wakeLock; private AnimatorSet runningAnimation; private AnimatorSet runningAnimation2; private AnimatorSet runningAnimationAudio; private int runningAnimationType; - private int audioInterfaceState; + private int recordInterfaceState; private int keyboardHeight; private int keyboardHeightLand; @@ -347,7 +235,7 @@ public boolean onTouchEvent(MotionEvent event) { private String lastTimeString; private float startedDraggingX = -1; private float distCanMove = AndroidUtilities.dp(80); - private boolean recordingAudio; + private boolean recordingAudioVideo; private boolean forceShowSendButton; private boolean allowStickers; private boolean allowGifs; @@ -391,17 +279,75 @@ public void run() { } }; + private Paint redDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private boolean recordAudioVideoRunnableStarted; + private Runnable recordAudioVideoRunnable = new Runnable() { + @Override + public void run() { + if (delegate == null || parentActivity == null) { + return; + } + recordAudioVideoRunnableStarted = false; + if (videoSendButton != null && videoSendButton.getTag() != null) { + if (Build.VERSION.SDK_INT >= 23) { + boolean hasAudio = parentActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED; + boolean hasVideo = parentActivity.checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; + if (!hasAudio || !hasVideo) { + String[] permissions = new String[!hasAudio && !hasVideo ? 2 : 1]; + if (!hasAudio && !hasVideo) { + permissions[0] = Manifest.permission.RECORD_AUDIO; + permissions[1] = Manifest.permission.CAMERA; + } else if (!hasAudio) { + permissions[0] = Manifest.permission.RECORD_AUDIO; + } else { + permissions[0] = Manifest.permission.CAMERA; + } + parentActivity.requestPermissions(permissions, 3); + return; + } + } + delegate.needStartRecordVideo(0); + } else { + if (parentFragment != null) { + if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + parentActivity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 3); + return; + } + + String action; + TLRPC.Chat currentChat; + if ((int) dialog_id < 0) { + currentChat = MessagesController.getInstance().getChat(-(int) dialog_id); + if (currentChat != null && currentChat.participants_count > MessagesController.getInstance().groupBigSize) { + action = "bigchat_upload_audio"; + } else { + action = "chat_upload_audio"; + } + } else { + action = "pm_upload_audio"; + } + if (!MessagesController.isFeatureEnabled(action, parentFragment)) { + return; + } + } + startedDraggingX = -1; + MediaController.getInstance().startRecording(dialog_id, replyingMessageObject); + updateRecordIntefrace(); + audioVideoButtonContainer.getParent().requestDisallowInterceptTouchEvent(true); + } + } + }; + private class RecordDot extends View { - private Drawable dotDrawable; private float alpha; private long lastUpdateTime; private boolean isIncr; public RecordDot(Context context) { super(context); - - dotDrawable = getResources().getDrawable(R.drawable.rec); + redDotPaint.setColor(Theme.getColor(Theme.key_chat_recordedVoiceDot)); } public void resetAlpha() { @@ -413,8 +359,7 @@ public void resetAlpha() { @Override protected void onDraw(Canvas canvas) { - dotDrawable.setBounds(0, 0, AndroidUtilities.dp(11), AndroidUtilities.dp(11)); - dotDrawable.setAlpha((int) (255 * alpha)); + redDotPaint.setAlpha((int) (255 * alpha)); long dt = (System.currentTimeMillis() - lastUpdateTime); if (!isIncr) { alpha -= dt / 400.0f; @@ -430,16 +375,17 @@ protected void onDraw(Canvas canvas) { } } lastUpdateTime = System.currentTimeMillis(); - dotDrawable.draw(canvas); + canvas.drawCircle(AndroidUtilities.dp(5), AndroidUtilities.dp(5), AndroidUtilities.dp(5), redDotPaint); invalidate(); } } - private class RecordCircle extends View { + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint paintRecord = new Paint(Paint.ANTI_ALIAS_FLAG); + private Drawable micDrawable; + private Drawable cameraDrawable; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private Paint paintRecord = new Paint(Paint.ANTI_ALIAS_FLAG); - private Drawable micDrawable; + private class RecordCircle extends View { private float scale; private float amplitude; private float animateToAmplitude; @@ -448,9 +394,13 @@ private class RecordCircle extends View { public RecordCircle(Context context) { super(context); - paint.setColor(0xff5795cc); - paintRecord.setColor(0x0d000000); - micDrawable = getResources().getDrawable(R.drawable.mic_pressed); + paint.setColor(Theme.getColor(Theme.key_chat_messagePanelVoiceBackground)); + paintRecord.setColor(Theme.getColor(Theme.key_chat_messagePanelVoiceShadow)); + micDrawable = getResources().getDrawable(R.drawable.mic).mutate(); + micDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelVoicePressed), PorterDuff.Mode.MULTIPLY)); + + cameraDrawable = getResources().getDrawable(R.drawable.ic_msg_panel_video).mutate(); + cameraDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelVoicePressed), PorterDuff.Mode.MULTIPLY)); } public void setAmplitude(double value) { @@ -503,16 +453,18 @@ protected void onDraw(Canvas canvas) { canvas.drawCircle(getMeasuredWidth() / 2.0f, getMeasuredHeight() / 2.0f, (AndroidUtilities.dp(42) + AndroidUtilities.dp(20) * amplitude) * scale, paintRecord); } canvas.drawCircle(getMeasuredWidth() / 2.0f, getMeasuredHeight() / 2.0f, AndroidUtilities.dp(42) * sc, paint); - micDrawable.setBounds(cx - micDrawable.getIntrinsicWidth() / 2, cy - micDrawable.getIntrinsicHeight() / 2, cx + micDrawable.getIntrinsicWidth() / 2, cy + micDrawable.getIntrinsicHeight() / 2); - micDrawable.setAlpha((int) (255 * alpha)); - micDrawable.draw(canvas); + Drawable drawable = videoSendButton != null && videoSendButton.getTag() != null ? cameraDrawable : micDrawable; + drawable.setBounds(cx - drawable.getIntrinsicWidth() / 2, cy - drawable.getIntrinsicHeight() / 2, cx + drawable.getIntrinsicWidth() / 2, cy + drawable.getIntrinsicHeight() / 2); + drawable.setAlpha((int) (255 * alpha)); + drawable.draw(canvas); } } - public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, ChatActivity fragment, boolean isChat) { + public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, ChatActivity fragment, final boolean isChat) { super(context); - backgroundDrawable = context.getResources().getDrawable(R.drawable.compose_panel); - dotDrawable = context.getResources().getDrawable(R.drawable.bluecircle); + + dotPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dotPaint.setColor(Theme.getColor(Theme.key_chat_emojiPanelNewTrending)); setFocusable(true); setFocusableInTouchMode(true); setWillNotDraw(false); @@ -536,7 +488,6 @@ public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, C sendByEnter = preferences.getBoolean("send_by_enter", false); textFieldContainer = new LinearLayout(context); - //textFieldContainer.setBackgroundColor(0xffffffff); textFieldContainer.setOrientation(LinearLayout.HORIZONTAL); addView(textFieldContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 2, 0, 0)); @@ -547,19 +498,19 @@ public ChatActivityEnterView(Activity context, SizeNotifierFrameLayout parent, C @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - if ((emojiView == null || emojiView.getVisibility() != VISIBLE) && !StickersQuery.getUnreadStickerSets().isEmpty() && dotDrawable != null) { - int x = canvas.getWidth() / 2 + AndroidUtilities.dp(4); - int y = canvas.getHeight() / 2 - AndroidUtilities.dp(13); - dotDrawable.setBounds(x, y, x + dotDrawable.getIntrinsicWidth(), y + dotDrawable.getIntrinsicHeight()); - dotDrawable.draw(canvas); + if (attachLayout != null && (emojiView == null || emojiView.getVisibility() != VISIBLE) && !StickersQuery.getUnreadStickerSets().isEmpty() && dotPaint != null) { + int x = canvas.getWidth() / 2 + AndroidUtilities.dp(4 + 5); + int y = canvas.getHeight() / 2 - AndroidUtilities.dp(13 - 5); + canvas.drawCircle(x, y, AndroidUtilities.dp(5), dotPaint); } } }; + emojiButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); emojiButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); emojiButton.setPadding(0, AndroidUtilities.dp(1), 0, 0); - if (Build.VERSION.SDK_INT >= 21) { - emojiButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); - } +// if (Build.VERSION.SDK_INT >= 21) { +// emojiButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); +// } setEmojiButtonImage(); frameLayout.addView(emojiButton, LayoutHelper.createFrame(48, 48, Gravity.BOTTOM | Gravity.LEFT, 3, 0, 0, 0)); emojiButton.setOnClickListener(new OnClickListener() { @@ -567,7 +518,7 @@ protected void onDraw(Canvas canvas) { public void onClick(View view) { if (!isPopupShowing() || currentPopupContentType != 0) { showPopup(1, 0); - emojiView.onOpen(messageEditText.length() > 0); + emojiView.onOpen(messageEditText.length() > 0 && !messageEditText.getText().toString().startsWith("@gif")); } else { openKeyboardInternal(); removeGifFromInputField(); @@ -575,7 +526,51 @@ public void onClick(View view) { } }); - messageEditText = new EditTextCaption(context); + messageEditText = new EditTextCaption(context) { + @Override + public InputConnection onCreateInputConnection(EditorInfo editorInfo) { + final InputConnection ic = super.onCreateInputConnection(editorInfo); + EditorInfoCompat.setContentMimeTypes(editorInfo, new String[]{"image/gif", "image/*", "image/jpg", "image/png"}); + + final InputConnectionCompat.OnCommitContentListener callback = new InputConnectionCompat.OnCommitContentListener() { + @Override + public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts) { + if (BuildCompat.isAtLeastNMR1() && (flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) { + try { + inputContentInfo.requestPermission(); + } catch (Exception e) { + return false; + } + } + ClipDescription description = inputContentInfo.getDescription(); + if (description.hasMimeType("image/gif")) { + SendMessagesHelper.prepareSendingDocument(null, null, inputContentInfo.getContentUri(), "image/gif", dialog_id, replyingMessageObject, inputContentInfo); + } else { + SendMessagesHelper.prepareSendingPhoto(null, inputContentInfo.getContentUri(), dialog_id, replyingMessageObject, null, null, inputContentInfo); + } + if (delegate != null) { + delegate.onMessageSend(null); + } + return true; + } + }; + return InputConnectionCompat.createWrapper(ic, editorInfo, callback); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isPopupShowing() && event.getAction() == MotionEvent.ACTION_DOWN) { + showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2, 0); + openKeyboardInternal(); + } + try { + return super.onTouchEvent(event); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + }; updateFieldHint(); messageEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); messageEditText.setInputType(messageEditText.getInputType() | EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); @@ -585,8 +580,9 @@ public void onClick(View view) { messageEditText.setGravity(Gravity.BOTTOM); messageEditText.setPadding(0, AndroidUtilities.dp(11), 0, AndroidUtilities.dp(12)); messageEditText.setBackgroundDrawable(null); - messageEditText.setTextColor(0xff000000); - messageEditText.setHintTextColor(0xffb2b2b2); + messageEditText.setTextColor(Theme.getColor(Theme.key_chat_messagePanelText)); + messageEditText.setHintColor(Theme.getColor(Theme.key_chat_messagePanelHint)); + messageEditText.setHintTextColor(Theme.getColor(Theme.key_chat_messagePanelHint)); frameLayout.addView(messageEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 52, 0, isChat ? 50 : 2, 0)); messageEditText.setOnKeyListener(new OnKeyListener() { @@ -695,29 +691,23 @@ public void afterTextChanged(Editable editable) { } } }); - try { - Field mCursorDrawableRes = TextView.class.getDeclaredField("mCursorDrawableRes"); - mCursorDrawableRes.setAccessible(true); - mCursorDrawableRes.set(messageEditText, R.drawable.field_carret); - } catch (Exception e) { - //nothing to do - } if (isChat) { - attachButton = new LinearLayout(context); - attachButton.setOrientation(LinearLayout.HORIZONTAL); - attachButton.setEnabled(false); - attachButton.setPivotX(AndroidUtilities.dp(48)); - frameLayout.addView(attachButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 48, Gravity.BOTTOM | Gravity.RIGHT)); + attachLayout = new LinearLayout(context); + attachLayout.setOrientation(LinearLayout.HORIZONTAL); + attachLayout.setEnabled(false); + attachLayout.setPivotX(AndroidUtilities.dp(48)); + frameLayout.addView(attachLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 48, Gravity.BOTTOM | Gravity.RIGHT)); botButton = new ImageView(context); + botButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); botButton.setImageResource(R.drawable.bot_keyboard2); botButton.setScaleType(ImageView.ScaleType.CENTER); botButton.setVisibility(GONE); - if (Build.VERSION.SDK_INT >= 21) { - botButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); - } - attachButton.addView(botButton, LayoutHelper.createLinear(48, 48)); +// if (Build.VERSION.SDK_INT >= 21) { +// botButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); +// } + attachLayout.addView(botButton, LayoutHelper.createLinear(48, 48)); botButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -735,6 +725,7 @@ public void onClick(View v) { } } else if (hasBotCommands) { setFieldText("/"); + messageEditText.requestFocus(); openKeyboard(); } } @@ -742,12 +733,13 @@ public void onClick(View v) { notifyButton = new ImageView(context); notifyButton.setImageResource(silent ? R.drawable.notify_members_off : R.drawable.notify_members_on); + notifyButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); notifyButton.setScaleType(ImageView.ScaleType.CENTER); notifyButton.setVisibility(canWriteToChannel ? VISIBLE : GONE); - if (Build.VERSION.SDK_INT >= 21) { - notifyButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); - } - attachButton.addView(notifyButton, LayoutHelper.createLinear(48, 48)); +// if (Build.VERSION.SDK_INT >= 21) { +// notifyButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); +// } + attachLayout.addView(notifyButton, LayoutHelper.createLinear(48, 48)); notifyButton.setOnClickListener(new OnClickListener() { private Toast visibleToast; @@ -763,7 +755,7 @@ public void onClick(View v) { visibleToast.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (silent) { visibleToast = Toast.makeText(parentActivity, LocaleController.getString("ChannelNotifyMembersInfoOff", R.string.ChannelNotifyMembersInfoOff), Toast.LENGTH_SHORT); @@ -774,21 +766,37 @@ public void onClick(View v) { updateFieldHint(); } }); + + attachButton = new ImageView(context); + attachButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); + attachButton.setImageResource(R.drawable.ic_ab_attach); + attachButton.setScaleType(ImageView.ScaleType.CENTER); +// if (Build.VERSION.SDK_INT >= 21) { +// attachButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); +// } + attachLayout.addView(attachButton, LayoutHelper.createLinear(48, 48)); + attachButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + delegate.didPressedAttachButton(); + } + }); } recordedAudioPanel = new FrameLayout(context); recordedAudioPanel.setVisibility(audioToSend == null ? GONE : VISIBLE); - recordedAudioPanel.setBackgroundColor(0xffffffff); + recordedAudioPanel.setBackgroundColor(Theme.getColor(Theme.key_chat_messagePanelBackground)); recordedAudioPanel.setFocusable(true); recordedAudioPanel.setFocusableInTouchMode(true); recordedAudioPanel.setClickable(true); frameLayout.addView(recordedAudioPanel, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM)); - ImageView imageView = new ImageView(context); - imageView.setScaleType(ImageView.ScaleType.CENTER); - imageView.setImageResource(R.drawable.ic_ab_fwd_delete); - recordedAudioPanel.addView(imageView, LayoutHelper.createFrame(48, 48)); - imageView.setOnClickListener(new OnClickListener() { + recordDeleteImageView = new ImageView(context); + recordDeleteImageView.setScaleType(ImageView.ScaleType.CENTER); + recordDeleteImageView.setImageResource(R.drawable.ic_ab_delete); + recordDeleteImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelVoiceDelete), PorterDuff.Mode.MULTIPLY)); + recordedAudioPanel.addView(recordDeleteImageView, LayoutHelper.createFrame(48, 48)); + recordDeleteImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { MessageObject playing = MediaController.getInstance().getPlayingMessageObject(); @@ -803,15 +811,18 @@ public void onClick(View v) { } }); - View view = new View(context); - view.setBackgroundResource(R.drawable.recorded); - recordedAudioPanel.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.CENTER_VERTICAL | Gravity.LEFT, 48, 0, 0, 0)); + recordedAudioBackground = new View(context); + recordedAudioBackground.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(16), Theme.getColor(Theme.key_chat_recordedVoiceBackground))); + recordedAudioPanel.addView(recordedAudioBackground, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.CENTER_VERTICAL | Gravity.LEFT, 48, 0, 0, 0)); recordedAudioSeekBar = new SeekBarWaveformView(context); recordedAudioPanel.addView(recordedAudioSeekBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.CENTER_VERTICAL | Gravity.LEFT, 48 + 44, 0, 52, 0)); + playDrawable = Theme.createSimpleSelectorDrawable(context, R.drawable.s_play, Theme.getColor(Theme.key_chat_recordedVoicePlayPause), Theme.getColor(Theme.key_chat_recordedVoicePlayPausePressed)); + pauseDrawable = Theme.createSimpleSelectorDrawable(context, R.drawable.s_pause, Theme.getColor(Theme.key_chat_recordedVoicePlayPause), Theme.getColor(Theme.key_chat_recordedVoicePlayPausePressed)); + recordedAudioPlayButton = new ImageView(context); - recordedAudioPlayButton.setImageResource(R.drawable.s_player_play_states); + recordedAudioPlayButton.setImageDrawable(playDrawable); recordedAudioPlayButton.setScaleType(ImageView.ScaleType.CENTER); recordedAudioPanel.addView(recordedAudioPlayButton, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.BOTTOM, 48, 0, 0, 0)); recordedAudioPlayButton.setOnClickListener(new OnClickListener() { @@ -822,108 +833,98 @@ public void onClick(View v) { } if (MediaController.getInstance().isPlayingAudio(audioToSendMessageObject) && !MediaController.getInstance().isAudioPaused()) { MediaController.getInstance().pauseAudio(audioToSendMessageObject); - recordedAudioPlayButton.setImageResource(R.drawable.s_player_play_states); + recordedAudioPlayButton.setImageDrawable(playDrawable); } else { - recordedAudioPlayButton.setImageResource(R.drawable.s_player_pause_states); + recordedAudioPlayButton.setImageDrawable(pauseDrawable); MediaController.getInstance().playAudio(audioToSendMessageObject); } } }); recordedAudioTimeTextView = new TextView(context); - recordedAudioTimeTextView.setTextColor(0xffffffff); + recordedAudioTimeTextView.setTextColor(Theme.getColor(Theme.key_chat_messagePanelVoiceDuration)); recordedAudioTimeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - recordedAudioTimeTextView.setText("0:13"); recordedAudioPanel.addView(recordedAudioTimeTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 13, 0)); recordPanel = new FrameLayout(context); recordPanel.setVisibility(GONE); - recordPanel.setBackgroundColor(0xffffffff); + recordPanel.setBackgroundColor(Theme.getColor(Theme.key_chat_messagePanelBackground)); frameLayout.addView(recordPanel, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM)); slideText = new LinearLayout(context); slideText.setOrientation(LinearLayout.HORIZONTAL); recordPanel.addView(slideText, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 30, 0, 0, 0)); - imageView = new ImageView(context); - imageView.setImageResource(R.drawable.slidearrow); - slideText.addView(imageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 0, 1, 0, 0)); + recordCancelImage = new ImageView(context); + recordCancelImage.setImageResource(R.drawable.slidearrow); + recordCancelImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_recordVoiceCancel), PorterDuff.Mode.MULTIPLY)); + slideText.addView(recordCancelImage, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 0, 1, 0, 0)); - TextView textView = new TextView(context); - textView.setText(LocaleController.getString("SlideToCancel", R.string.SlideToCancel)); - textView.setTextColor(0xff999999); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - slideText.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); + recordCancelText = new TextView(context); + recordCancelText.setText(LocaleController.getString("SlideToCancel", R.string.SlideToCancel)); + recordCancelText.setTextColor(Theme.getColor(Theme.key_chat_recordVoiceCancel)); + recordCancelText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + slideText.addView(recordCancelText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - linearLayout.setPadding(AndroidUtilities.dp(13), 0, 0, 0); - linearLayout.setBackgroundColor(0xffffffff); - recordPanel.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); + recordTimeContainer = new LinearLayout(context); + recordTimeContainer.setOrientation(LinearLayout.HORIZONTAL); + recordTimeContainer.setPadding(AndroidUtilities.dp(13), 0, 0, 0); + recordTimeContainer.setBackgroundColor(Theme.getColor(Theme.key_chat_messagePanelBackground)); + recordPanel.addView(recordTimeContainer, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); recordDot = new RecordDot(context); - linearLayout.addView(recordDot, LayoutHelper.createLinear(11, 11, Gravity.CENTER_VERTICAL, 0, 1, 0, 0)); + recordTimeContainer.addView(recordDot, LayoutHelper.createLinear(11, 11, Gravity.CENTER_VERTICAL, 0, 1, 0, 0)); recordTimeText = new TextView(context); recordTimeText.setText("00:00"); - recordTimeText.setTextColor(0xff4d4c4b); + recordTimeText.setTextColor(Theme.getColor(Theme.key_chat_recordTime)); recordTimeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - linearLayout.addView(recordTimeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); + recordTimeContainer.addView(recordTimeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); sendButtonContainer = new FrameLayout(context); textFieldContainer.addView(sendButtonContainer, LayoutHelper.createLinear(48, 48, Gravity.BOTTOM)); - audioSendButton = new ImageView(context); - audioSendButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - audioSendButton.setImageResource(R.drawable.mic); - audioSendButton.setBackgroundColor(0xffffffff); - audioSendButton.setSoundEffectsEnabled(false); - audioSendButton.setPadding(0, 0, AndroidUtilities.dp(4), 0); - sendButtonContainer.addView(audioSendButton, LayoutHelper.createFrame(48, 48)); - audioSendButton.setOnTouchListener(new OnTouchListener() { + audioVideoButtonContainer = new FrameLayout(context); + audioVideoButtonContainer.setBackgroundColor(Theme.getColor(Theme.key_chat_messagePanelBackground)); + audioVideoButtonContainer.setSoundEffectsEnabled(false); + sendButtonContainer.addView(audioVideoButtonContainer, LayoutHelper.createFrame(48, 48)); + audioVideoButtonContainer.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - if (parentFragment != null) { - if (Build.VERSION.SDK_INT >= 23 && parentActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - parentActivity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 3); - return false; - } - - String action; - TLRPC.Chat currentChat; - if ((int) dialog_id < 0) { - currentChat = MessagesController.getInstance().getChat(-(int) dialog_id); - if (currentChat != null && currentChat.participants_count > MessagesController.getInstance().groupBigSize) { - action = "bigchat_upload_audio"; - } else { - action = "chat_upload_audio"; - } + if (hasRecordVideo) { + recordAudioVideoRunnableStarted = true; + AndroidUtilities.runOnUIThread(recordAudioVideoRunnable, 150); + } else { + recordAudioVideoRunnable.run(); + } + } else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) { + if (recordAudioVideoRunnableStarted) { + AndroidUtilities.cancelRunOnUIThread(recordAudioVideoRunnable); + setRecordVideoButtonVisible(videoSendButton.getTag() == null, true); + } else { + startedDraggingX = -1; + if (hasRecordVideo && videoSendButton.getTag() != null) { + delegate.needStartRecordVideo(1); } else { - action = "pm_upload_audio"; - } - if (!MessagesController.isFeatureEnabled(action, parentFragment)) { - return false; + MediaController.getInstance().stopRecording(1); } + recordingAudioVideo = false; + updateRecordIntefrace(); } - startedDraggingX = -1; - MediaController.getInstance().startRecording(dialog_id, replyingMessageObject); - updateAudioRecordIntefrace(); - audioSendButton.getParent().requestDisallowInterceptTouchEvent(true); - } else if (motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) { - startedDraggingX = -1; - MediaController.getInstance().stopRecording(1); - recordingAudio = false; - updateAudioRecordIntefrace(); - } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && recordingAudio) { + } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && recordingAudioVideo) { float x = motionEvent.getX(); if (x < -distCanMove) { - MediaController.getInstance().stopRecording(0); - recordingAudio = false; - updateAudioRecordIntefrace(); + if (hasRecordVideo && videoSendButton.getTag() != null) { + delegate.needStartRecordVideo(2); + } else { + MediaController.getInstance().stopRecording(0); + } + recordingAudioVideo = false; + updateRecordIntefrace(); } - x = x + audioSendButton.getX(); + x = x + audioVideoButtonContainer.getX(); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) slideText.getLayoutParams(); if (startedDraggingX != -1) { float dist = (x - startedDraggingX); @@ -962,6 +963,22 @@ public boolean onTouch(View view, MotionEvent motionEvent) { } }); + audioSendButton = new ImageView(context); + audioSendButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + audioSendButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); + audioSendButton.setImageResource(R.drawable.mic); + audioSendButton.setPadding(0, 0, AndroidUtilities.dp(4), 0); + audioVideoButtonContainer.addView(audioSendButton, LayoutHelper.createFrame(48, 48)); + + if (hasRecordVideo) { + videoSendButton = new ImageView(context); + videoSendButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + videoSendButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.MULTIPLY)); + videoSendButton.setImageResource(R.drawable.ic_msg_panel_video); + videoSendButton.setPadding(0, 0, AndroidUtilities.dp(4), 0); + audioVideoButtonContainer.addView(videoSendButton, LayoutHelper.createFrame(48, 48)); + } + recordCircle = new RecordCircle(context); recordCircle.setVisibility(GONE); sizeNotifierLayout.addView(recordCircle, LayoutHelper.createFrame(124, 124, Gravity.BOTTOM | Gravity.RIGHT, 0, 0, -36, -38)); @@ -969,8 +986,8 @@ public boolean onTouch(View view, MotionEvent motionEvent) { cancelBotButton = new ImageView(context); cancelBotButton.setVisibility(INVISIBLE); cancelBotButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - //cancelBotButton.setImageResource(R.drawable.delete_reply); cancelBotButton.setImageDrawable(progressDrawable = new CloseProgressDrawable2()); + progressDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelCancelInlineBot), PorterDuff.Mode.MULTIPLY)); cancelBotButton.setSoundEffectsEnabled(false); cancelBotButton.setScaleX(0.1f); cancelBotButton.setScaleY(0.1f); @@ -992,6 +1009,7 @@ public void onClick(View view) { sendButton = new ImageView(context); sendButton.setVisibility(INVISIBLE); sendButton.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + sendButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_messagePanelSend), PorterDuff.Mode.MULTIPLY)); sendButton.setImageResource(R.drawable.ic_send); sendButton.setSoundEffectsEnabled(false); sendButton.setScaleX(0.1f); @@ -1005,10 +1023,34 @@ public void onClick(View view) { } }); + doneButtonContainer = new FrameLayout(context); + doneButtonContainer.setVisibility(GONE); + textFieldContainer.addView(doneButtonContainer, LayoutHelper.createLinear(48, 48, Gravity.BOTTOM)); +// if (Build.VERSION.SDK_INT >= 21) { +// doneButtonContainer.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); +// } + doneButtonContainer.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + doneEditingMessage(); + } + }); + + doneButtonImage = new ImageView(context); + doneButtonImage.setScaleType(ImageView.ScaleType.CENTER); + doneButtonImage.setImageResource(R.drawable.edit_done); + doneButtonImage.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_editDoneIcon), PorterDuff.Mode.MULTIPLY)); + doneButtonContainer.addView(doneButtonImage, LayoutHelper.createFrame(48, 48)); + + doneButtonProgress = new ContextProgressView(context, 0); + doneButtonProgress.setVisibility(View.INVISIBLE); + doneButtonContainer.addView(doneButtonProgress, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + SharedPreferences sharedPreferences = ApplicationLoader.applicationContext.getSharedPreferences("emoji", Context.MODE_PRIVATE); keyboardHeight = sharedPreferences.getInt("kbd_height", AndroidUtilities.dp(200)); keyboardHeightLand = sharedPreferences.getInt("kbd_height_land3", AndroidUtilities.dp(200)); + setRecordVideoButtonVisible(false, false); checkSendButton(false); } @@ -1028,8 +1070,10 @@ protected boolean drawChild(Canvas canvas, View child, long drawingTime) { @Override protected void onDraw(Canvas canvas) { int top = topView != null && topView.getVisibility() == VISIBLE ? (int) topView.getTranslationY() : 0; - backgroundDrawable.setBounds(0, top, getMeasuredWidth(), getMeasuredHeight()); - backgroundDrawable.draw(canvas); + int bottom = top + Theme.chat_composeShadowDrawable.getIntrinsicHeight(); + Theme.chat_composeShadowDrawable.setBounds(0, top, getMeasuredWidth(), bottom); + Theme.chat_composeShadowDrawable.draw(canvas); + canvas.drawRect(0, bottom, getMeasuredWidth(), getMeasuredHeight(), Theme.chat_composeBackgroundPaint); } @Override @@ -1037,6 +1081,45 @@ public boolean hasOverlappingRendering() { return false; } + private void setRecordVideoButtonVisible(boolean visible, boolean animated) { + if (!hasRecordVideo) { + return; + } + videoSendButton.setTag(visible ? 1 : null); + if (audioVideoButtonAnimation != null) { + audioVideoButtonAnimation.cancel(); + audioVideoButtonAnimation = null; + } + if (animated) { + audioVideoButtonAnimation = new AnimatorSet(); + audioVideoButtonAnimation.playTogether( + ObjectAnimator.ofFloat(videoSendButton, "scaleX", visible ? 1.0f : 0.1f), + ObjectAnimator.ofFloat(videoSendButton, "scaleY", visible ? 1.0f : 0.1f), + ObjectAnimator.ofFloat(videoSendButton, "alpha", visible ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(audioSendButton, "scaleX", visible ? 0.1f : 1.0f), + ObjectAnimator.ofFloat(audioSendButton, "scaleY", visible ? 0.1f : 1.0f), + ObjectAnimator.ofFloat(audioSendButton, "alpha", visible ? 0.0f : 1.0f)); + audioVideoButtonAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(audioVideoButtonAnimation)) { + audioVideoButtonAnimation = null; + } + } + }); + audioVideoButtonAnimation.setInterpolator(new DecelerateInterpolator()); + audioVideoButtonAnimation.setDuration(150); + audioVideoButtonAnimation.start(); + } else { + videoSendButton.setScaleX(visible ? 1.0f : 0.1f); + videoSendButton.setScaleY(visible ? 1.0f : 0.1f); + videoSendButton.setAlpha(visible ? 1.0f : 0.0f); + audioSendButton.setScaleX(visible ? 0.1f : 1.0f); + audioSendButton.setScaleY(visible ? 0.1f : 1.0f); + audioSendButton.setAlpha(visible ? 0.0f : 1.0f); + } + } + public void showContextProgress(boolean show) { if (progressDrawable == null) { return; @@ -1107,7 +1190,7 @@ public void showTopView(boolean animated, final boolean openKeyboard) { if (keyboardVisible || isPopupShowing()) { currentTopViewAnimation = new AnimatorSet(); currentTopViewAnimation.playTogether(ObjectAnimator.ofFloat(topView, "translationY", 0)); - currentTopViewAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentTopViewAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentTopViewAnimation != null && currentTopViewAnimation.equals(animation)) { @@ -1135,10 +1218,91 @@ public void onAnimationCancel(Animator animation) { } } else { topView.setTranslationY(0); + if (recordedAudioPanel.getVisibility() != VISIBLE && (!forceShowSendButton || openKeyboard)) { + openKeyboard(); + } } } } + public void onEditTimeExpired() { + doneButtonContainer.setVisibility(View.GONE); + } + + public void showEditDoneProgress(final boolean show, boolean animated) { + if (doneButtonAnimation != null) { + doneButtonAnimation.cancel(); + } + if (!animated) { + if (show) { + doneButtonImage.setScaleX(0.1f); + doneButtonImage.setScaleY(0.1f); + doneButtonImage.setAlpha(0.0f); + doneButtonProgress.setScaleX(1.0f); + doneButtonProgress.setScaleY(1.0f); + doneButtonProgress.setAlpha(1.0f); + doneButtonImage.setVisibility(View.INVISIBLE); + doneButtonProgress.setVisibility(View.VISIBLE); + doneButtonContainer.setEnabled(false); + } else { + doneButtonProgress.setScaleX(0.1f); + doneButtonProgress.setScaleY(0.1f); + doneButtonProgress.setAlpha(0.0f); + doneButtonImage.setScaleX(1.0f); + doneButtonImage.setScaleY(1.0f); + doneButtonImage.setAlpha(1.0f); + doneButtonImage.setVisibility(View.VISIBLE); + doneButtonProgress.setVisibility(View.INVISIBLE); + doneButtonContainer.setEnabled(true); + } + } else { + doneButtonAnimation = new AnimatorSet(); + if (show) { + doneButtonProgress.setVisibility(View.VISIBLE); + doneButtonContainer.setEnabled(false); + doneButtonAnimation.playTogether( + ObjectAnimator.ofFloat(doneButtonImage, "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneButtonImage, "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneButtonImage, "alpha", 0.0f), + ObjectAnimator.ofFloat(doneButtonProgress, "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneButtonProgress, "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneButtonProgress, "alpha", 1.0f)); + } else { + doneButtonImage.setVisibility(View.VISIBLE); + doneButtonContainer.setEnabled(true); + doneButtonAnimation.playTogether( + ObjectAnimator.ofFloat(doneButtonProgress, "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneButtonProgress, "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneButtonProgress, "alpha", 0.0f), + ObjectAnimator.ofFloat(doneButtonImage, "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneButtonImage, "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneButtonImage, "alpha", 1.0f)); + + } + doneButtonAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (doneButtonAnimation != null && doneButtonAnimation.equals(animation)) { + if (!show) { + doneButtonProgress.setVisibility(View.INVISIBLE); + } else { + doneButtonImage.setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (doneButtonAnimation != null && doneButtonAnimation.equals(animation)) { + doneButtonAnimation = null; + } + } + }); + doneButtonAnimation.setDuration(150); + doneButtonAnimation.start(); + } + } + public void hideTopView(final boolean animated) { if (topView == null || !topViewShowed) { return; @@ -1154,7 +1318,7 @@ public void hideTopView(final boolean animated) { if (animated) { currentTopViewAnimation = new AnimatorSet(); currentTopViewAnimation.playTogether(ObjectAnimator.ofFloat(topView, "translationY", topView.getLayoutParams().height)); - currentTopViewAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentTopViewAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentTopViewAnimation != null && currentTopViewAnimation.equals(animation)) { @@ -1175,6 +1339,7 @@ public void onAnimationCancel(Animator animation) { currentTopViewAnimation.start(); } else { topView.setVisibility(GONE); + resizeForTopView(false); topView.setTranslationY(topView.getLayoutParams().height); } } @@ -1236,12 +1401,12 @@ public void onDestroy() { if (emojiView != null) { emojiView.onDestroy(); } - if (mWakeLock != null) { + if (wakeLock != null) { try { - mWakeLock.release(); - mWakeLock = null; + wakeLock.release(); + wakeLock = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (sizeNotifierLayout != null) { @@ -1277,10 +1442,10 @@ public void setDialogId(long id) { if (notifyButton != null) { notifyButton.setVisibility(canWriteToChannel ? VISIBLE : GONE); notifyButton.setImageResource(silent ? R.drawable.notify_members_off : R.drawable.notify_members_on); - attachButton.setPivotX(AndroidUtilities.dp((botButton == null || botButton.getVisibility() == GONE) && (notifyButton == null || notifyButton.getVisibility() == GONE) ? 48 : 96)); + attachLayout.setPivotX(AndroidUtilities.dp((botButton == null || botButton.getVisibility() == GONE) && (notifyButton == null || notifyButton.getVisibility() == GONE) ? 48 : 96)); } - if (attachButton != null) { - updateFieldRight(attachButton.getVisibility() == VISIBLE ? 1 : 0); + if (attachLayout != null) { + updateFieldRight(attachLayout.getVisibility() == VISIBLE ? 1 : 0); } } updateFieldHint(); @@ -1341,7 +1506,7 @@ private void hideRecordedAudioPanel() { ObjectAnimator.ofFloat(recordedAudioPanel, "alpha", 0.0f) ); AnimatorSet.setDuration(200); - AnimatorSet.addListener(new AnimatorListenerAdapterProxy() { + AnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { recordedAudioPanel.setVisibility(GONE); @@ -1399,6 +1564,7 @@ private void sendMessage() { public void doneEditingMessage() { if (editingMessageObject != null) { delegate.onMessageEditEnd(true); + showEditDoneProgress(true, true); CharSequence[] message = new CharSequence[] {messageEditText.getText()}; ArrayList entities = MessagesQuery.getEntities(message); editingMessageReqId = SendMessagesHelper.getInstance().editMessage(editingMessageObject, message[0].toString(), messageWebPageSearch, parentFragment, entities, new Runnable() { @@ -1434,11 +1600,12 @@ private void checkSendButton(boolean animated) { } CharSequence message = AndroidUtilities.getTrimmedString(messageEditText.getText()); if (message.length() > 0 || forceShowSendButton || audioToSend != null) { - boolean showBotButton = messageEditText.caption != null && sendButton.getVisibility() == VISIBLE; - boolean showSendButton = messageEditText.caption == null && cancelBotButton.getVisibility() == VISIBLE; - if (audioSendButton.getVisibility() == VISIBLE || showBotButton || showSendButton) { + final String caption = messageEditText.getCaption(); + boolean showBotButton = caption != null && sendButton.getVisibility() == VISIBLE; + boolean showSendButton = caption == null && cancelBotButton.getVisibility() == VISIBLE; + if (audioVideoButtonContainer.getVisibility() == VISIBLE || showBotButton || showSendButton) { if (animated) { - if (runningAnimationType == 1 && messageEditText.caption == null || runningAnimationType == 3 && messageEditText.caption != null) { + if (runningAnimationType == 1 && messageEditText.getCaption() == null || runningAnimationType == 3 && caption != null) { return; } if (runningAnimation != null) { @@ -1450,18 +1617,18 @@ private void checkSendButton(boolean animated) { runningAnimation2 = null; } - if (attachButton != null) { + if (attachLayout != null) { runningAnimation2 = new AnimatorSet(); runningAnimation2.playTogether( - ObjectAnimator.ofFloat(attachButton, "alpha", 0.0f), - ObjectAnimator.ofFloat(attachButton, "scaleX", 0.0f) + ObjectAnimator.ofFloat(attachLayout, "alpha", 0.0f), + ObjectAnimator.ofFloat(attachLayout, "scaleX", 0.0f) ); runningAnimation2.setDuration(100); - runningAnimation2.addListener(new AnimatorListenerAdapterProxy() { + runningAnimation2.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (runningAnimation2 != null && runningAnimation2.equals(animation)) { - attachButton.setVisibility(GONE); + attachLayout.setVisibility(GONE); } } @@ -1482,10 +1649,10 @@ public void onAnimationCancel(Animator animation) { runningAnimation = new AnimatorSet(); ArrayList animators = new ArrayList<>(); - if (audioSendButton.getVisibility() == VISIBLE) { - animators.add(ObjectAnimator.ofFloat(audioSendButton, "scaleX", 0.1f)); - animators.add(ObjectAnimator.ofFloat(audioSendButton, "scaleY", 0.1f)); - animators.add(ObjectAnimator.ofFloat(audioSendButton, "alpha", 0.0f)); + if (audioVideoButtonContainer.getVisibility() == VISIBLE) { + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "scaleX", 0.1f)); + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "scaleY", 0.1f)); + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "alpha", 0.0f)); } if (showBotButton) { animators.add(ObjectAnimator.ofFloat(sendButton, "scaleX", 0.1f)); @@ -1496,7 +1663,7 @@ public void onAnimationCancel(Animator animation) { animators.add(ObjectAnimator.ofFloat(cancelBotButton, "scaleY", 0.1f)); animators.add(ObjectAnimator.ofFloat(cancelBotButton, "alpha", 0.0f)); } - if (messageEditText.caption != null) { + if (caption != null) { runningAnimationType = 3; animators.add(ObjectAnimator.ofFloat(cancelBotButton, "scaleX", 1.0f)); animators.add(ObjectAnimator.ofFloat(cancelBotButton, "scaleY", 1.0f)); @@ -1512,18 +1679,18 @@ public void onAnimationCancel(Animator animation) { runningAnimation.playTogether(animators); runningAnimation.setDuration(150); - runningAnimation.addListener(new AnimatorListenerAdapterProxy() { + runningAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (runningAnimation != null && runningAnimation.equals(animation)) { - if (messageEditText.caption != null) { + if (caption != null) { cancelBotButton.setVisibility(VISIBLE); sendButton.setVisibility(GONE); } else { sendButton.setVisibility(VISIBLE); cancelBotButton.setVisibility(GONE); } - audioSendButton.setVisibility(GONE); + audioVideoButtonContainer.setVisibility(GONE); runningAnimation = null; runningAnimationType = 0; } @@ -1538,10 +1705,10 @@ public void onAnimationCancel(Animator animation) { }); runningAnimation.start(); } else { - audioSendButton.setScaleX(0.1f); - audioSendButton.setScaleY(0.1f); - audioSendButton.setAlpha(0.0f); - if (messageEditText.caption != null) { + audioVideoButtonContainer.setScaleX(0.1f); + audioVideoButtonContainer.setScaleY(0.1f); + audioVideoButtonContainer.setAlpha(0.0f); + if (caption != null) { sendButton.setScaleX(0.1f); sendButton.setScaleY(0.1f); sendButton.setAlpha(0.0f); @@ -1560,9 +1727,9 @@ public void onAnimationCancel(Animator animation) { sendButton.setVisibility(VISIBLE); cancelBotButton.setVisibility(GONE); } - audioSendButton.setVisibility(GONE); - if (attachButton != null) { - attachButton.setVisibility(GONE); + audioVideoButtonContainer.setVisibility(GONE); + if (attachLayout != null) { + attachLayout.setVisibility(GONE); if (delegate != null && getVisibility() == VISIBLE) { delegate.onAttachButtonHidden(); } @@ -1585,12 +1752,12 @@ public void onAnimationCancel(Animator animation) { runningAnimation2 = null; } - if (attachButton != null) { - attachButton.setVisibility(VISIBLE); + if (attachLayout != null) { + attachLayout.setVisibility(VISIBLE); runningAnimation2 = new AnimatorSet(); runningAnimation2.playTogether( - ObjectAnimator.ofFloat(attachButton, "alpha", 1.0f), - ObjectAnimator.ofFloat(attachButton, "scaleX", 1.0f) + ObjectAnimator.ofFloat(attachLayout, "alpha", 1.0f), + ObjectAnimator.ofFloat(attachLayout, "scaleX", 1.0f) ); runningAnimation2.setDuration(100); runningAnimation2.start(); @@ -1600,14 +1767,14 @@ public void onAnimationCancel(Animator animation) { } } - audioSendButton.setVisibility(VISIBLE); + audioVideoButtonContainer.setVisibility(VISIBLE); runningAnimation = new AnimatorSet(); runningAnimationType = 2; ArrayList animators = new ArrayList<>(); - animators.add(ObjectAnimator.ofFloat(audioSendButton, "scaleX", 1.0f)); - animators.add(ObjectAnimator.ofFloat(audioSendButton, "scaleY", 1.0f)); - animators.add(ObjectAnimator.ofFloat(audioSendButton, "alpha", 1.0f)); + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "scaleX", 1.0f)); + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "scaleY", 1.0f)); + animators.add(ObjectAnimator.ofFloat(audioVideoButtonContainer, "alpha", 1.0f)); if (cancelBotButton.getVisibility() == VISIBLE) { animators.add(ObjectAnimator.ofFloat(cancelBotButton, "scaleX", 0.1f)); animators.add(ObjectAnimator.ofFloat(cancelBotButton, "scaleY", 0.1f)); @@ -1620,13 +1787,13 @@ public void onAnimationCancel(Animator animation) { runningAnimation.playTogether(animators); runningAnimation.setDuration(150); - runningAnimation.addListener(new AnimatorListenerAdapterProxy() { + runningAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (runningAnimation != null && runningAnimation.equals(animation)) { sendButton.setVisibility(GONE); cancelBotButton.setVisibility(GONE); - audioSendButton.setVisibility(VISIBLE); + audioVideoButtonContainer.setVisibility(VISIBLE); runningAnimation = null; runningAnimationType = 0; } @@ -1647,17 +1814,17 @@ public void onAnimationCancel(Animator animation) { cancelBotButton.setScaleX(0.1f); cancelBotButton.setScaleY(0.1f); cancelBotButton.setAlpha(0.0f); - audioSendButton.setScaleX(1.0f); - audioSendButton.setScaleY(1.0f); - audioSendButton.setAlpha(1.0f); + audioVideoButtonContainer.setScaleX(1.0f); + audioVideoButtonContainer.setScaleY(1.0f); + audioVideoButtonContainer.setAlpha(1.0f); cancelBotButton.setVisibility(GONE); sendButton.setVisibility(GONE); - audioSendButton.setVisibility(VISIBLE); - if (attachButton != null) { + audioVideoButtonContainer.setVisibility(VISIBLE); + if (attachLayout != null) { if (getVisibility() == VISIBLE) { delegate.onAttachButtonShow(); } - attachButton.setVisibility(VISIBLE); + attachLayout.setVisibility(VISIBLE); updateFieldRight(1); } } @@ -1689,20 +1856,20 @@ private void updateFieldRight(int attachVisible) { messageEditText.setLayoutParams(layoutParams); } - private void updateAudioRecordIntefrace() { - if (recordingAudio) { - if (audioInterfaceState == 1) { + private void updateRecordIntefrace() { + if (recordingAudioVideo) { + if (recordInterfaceState == 1) { return; } - audioInterfaceState = 1; + recordInterfaceState = 1; try { - if (mWakeLock == null) { + if (wakeLock == null) { PowerManager pm = (PowerManager) ApplicationLoader.applicationContext.getSystemService(Context.POWER_SERVICE); - mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "audio record lock"); - mWakeLock.acquire(); + wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "audio record lock"); + wakeLock.acquire(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.lockOrientation(parentActivity); @@ -1725,9 +1892,9 @@ private void updateAudioRecordIntefrace() { runningAnimationAudio = new AnimatorSet(); runningAnimationAudio.playTogether(ObjectAnimator.ofFloat(recordPanel, "translationX", 0), ObjectAnimator.ofFloat(recordCircle, "scale", 1), - ObjectAnimator.ofFloat(audioSendButton, "alpha", 0)); + ObjectAnimator.ofFloat(audioVideoButtonContainer, "alpha", 0)); runningAnimationAudio.setDuration(300); - runningAnimationAudio.addListener(new AnimatorListenerAdapterProxy() { + runningAnimationAudio.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { if (runningAnimationAudio != null && runningAnimationAudio.equals(animator)) { @@ -1739,19 +1906,19 @@ public void onAnimationEnd(Animator animator) { runningAnimationAudio.setInterpolator(new DecelerateInterpolator()); runningAnimationAudio.start(); } else { - if (mWakeLock != null) { + if (wakeLock != null) { try { - mWakeLock.release(); - mWakeLock = null; + wakeLock.release(); + wakeLock = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } AndroidUtilities.unlockOrientation(parentActivity); - if (audioInterfaceState == 0) { + if (recordInterfaceState == 0) { return; } - audioInterfaceState = 0; + recordInterfaceState = 0; if (runningAnimationAudio != null) { runningAnimationAudio.cancel(); @@ -1759,9 +1926,9 @@ public void onAnimationEnd(Animator animator) { runningAnimationAudio = new AnimatorSet(); runningAnimationAudio.playTogether(ObjectAnimator.ofFloat(recordPanel, "translationX", AndroidUtilities.displaySize.x), ObjectAnimator.ofFloat(recordCircle, "scale", 0.0f), - ObjectAnimator.ofFloat(audioSendButton, "alpha", 1.0f)); + ObjectAnimator.ofFloat(audioVideoButtonContainer, "alpha", 1.0f)); runningAnimationAudio.setDuration(300); - runningAnimationAudio.addListener(new AnimatorListenerAdapterProxy() { + runningAnimationAudio.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { if (runningAnimationAudio != null && runningAnimationAudio.equals(animator)) { @@ -1827,6 +1994,13 @@ public void setEditingMessageObject(MessageObject messageObject, boolean caption editingMessageObject = messageObject; editingCaption = caption; if (editingMessageObject != null) { + if (doneButtonAnimation != null) { + doneButtonAnimation.cancel(); + doneButtonAnimation = null; + } + doneButtonContainer.setVisibility(View.VISIBLE); + showEditDoneProgress(true, false); + InputFilter[] inputFilters = new InputFilter[1]; if (caption) { inputFilters[0] = new InputFilter.LengthFilter(200); @@ -1838,27 +2012,44 @@ public void setEditingMessageObject(MessageObject messageObject, boolean caption } else { inputFilters[0] = new InputFilter.LengthFilter(4096); if (editingMessageObject.messageText != null) { - CharSequence[] message = new CharSequence[] {editingMessageObject.messageText}; ArrayList entities = editingMessageObject.messageOwner.entities;//MessagesQuery.getEntities(message); - SpannableStringBuilder stringBuilder = new SpannableStringBuilder(message[0]); - int addToOffset = 0; + MessagesQuery.sortEntities(entities); + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(editingMessageObject.messageText); + Object spansToRemove[] = stringBuilder.getSpans(0, stringBuilder.length(), Object.class); + if (spansToRemove != null && spansToRemove.length > 0) { + for (int a = 0; a < spansToRemove.length; a++) { + stringBuilder.removeSpan(spansToRemove[a]); + } + } if (entities != null) { - for (int a = 0; a < entities.size(); a++) { - TLRPC.MessageEntity entity = entities.get(a); - if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { - if (entity.offset + entity.length + addToOffset < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length + addToOffset) == ' ') { - entity.length++; + int addToOffset = 0; + try { + for (int a = 0; a < entities.size(); a++) { + TLRPC.MessageEntity entity = entities.get(a); + if (entity.offset + entity.length + addToOffset > stringBuilder.length()) { + continue; + } + if (entity instanceof TLRPC.TL_inputMessageEntityMentionName) { + if (entity.offset + entity.length + addToOffset < stringBuilder.length() && stringBuilder.charAt(entity.offset + entity.length + addToOffset) == ' ') { + entity.length++; + } + stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id, true), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityCode) { + stringBuilder.insert(entity.offset + entity.length + addToOffset, "`"); + stringBuilder.insert(entity.offset + addToOffset, "`"); + addToOffset += 2; + } else if (entity instanceof TLRPC.TL_messageEntityPre) { + stringBuilder.insert(entity.offset + entity.length + addToOffset, "```"); + stringBuilder.insert(entity.offset + addToOffset, "```"); + addToOffset += 6; + } else if (entity instanceof TLRPC.TL_messageEntityBold) { + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } else if (entity instanceof TLRPC.TL_messageEntityItalic) { + stringBuilder.setSpan(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf")), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - stringBuilder.setSpan(new URLSpanUserMention("" + ((TLRPC.TL_inputMessageEntityMentionName) entity).user_id.user_id), entity.offset + addToOffset, entity.offset + entity.length + addToOffset, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (entity instanceof TLRPC.TL_messageEntityCode) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "`"); - stringBuilder.insert(entity.offset + addToOffset, "`"); - addToOffset += 2; - } else if (entity instanceof TLRPC.TL_messageEntityPre) { - stringBuilder.insert(entity.offset + entity.length + addToOffset, "```"); - stringBuilder.insert(entity.offset + addToOffset, "```"); - addToOffset += 6; } + } catch (Exception e) { + FileLog.e(e); } } setFieldText(Emoji.replaceEmoji(stringBuilder, messageEditText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false)); @@ -1873,26 +2064,27 @@ public void setEditingMessageObject(MessageObject messageObject, boolean caption messageEditText.setLayoutParams(layoutParams); sendButton.setVisibility(GONE); cancelBotButton.setVisibility(GONE); - audioSendButton.setVisibility(GONE); - attachButton.setVisibility(GONE); + audioVideoButtonContainer.setVisibility(GONE); + attachLayout.setVisibility(GONE); sendButtonContainer.setVisibility(GONE); } else { + doneButtonContainer.setVisibility(View.GONE); messageEditText.setFilters(new InputFilter[0]); delegate.onMessageEditEnd(false); - audioSendButton.setVisibility(VISIBLE); - attachButton.setVisibility(VISIBLE); + audioVideoButtonContainer.setVisibility(VISIBLE); + attachLayout.setVisibility(VISIBLE); sendButtonContainer.setVisibility(VISIBLE); - attachButton.setScaleX(1.0f); - attachButton.setAlpha(1.0f); + attachLayout.setScaleX(1.0f); + attachLayout.setAlpha(1.0f); sendButton.setScaleX(0.1f); sendButton.setScaleY(0.1f); sendButton.setAlpha(0.0f); cancelBotButton.setScaleX(0.1f); cancelBotButton.setScaleY(0.1f); cancelBotButton.setAlpha(0.0f); - audioSendButton.setScaleX(1.0f); - audioSendButton.setScaleY(1.0f); - audioSendButton.setAlpha(1.0f); + audioVideoButtonContainer.setScaleX(1.0f); + audioVideoButtonContainer.setScaleY(1.0f); + audioVideoButtonContainer.setAlpha(1.0f); sendButton.setVisibility(GONE); cancelBotButton.setVisibility(GONE); messageEditText.setText(""); @@ -1904,6 +2096,26 @@ public void setEditingMessageObject(MessageObject messageObject, boolean caption updateFieldHint(); } + public ImageView getAttachButton() { + return attachButton; + } + + public ImageView getBotButton() { + return botButton; + } + + public ImageView getEmojiButton() { + return emojiButton; + } + + public ImageView getSendButton() { + return sendButton; + } + + public EmojiView getEmojiView() { + return emojiView; + } + public void setFieldText(CharSequence text) { if (messageEditText == null) { return; @@ -1931,6 +2143,18 @@ public int getCursorPosition() { return messageEditText.getSelectionStart(); } + public int getSelectionLength() { + if (messageEditText == null) { + return 0; + } + try { + return messageEditText.getSelectionEnd() - messageEditText.getSelectionStart(); + } catch (Exception e) { + FileLog.e(e); + } + return 0; + } + public void replaceWithText(int start, int len, CharSequence text) { try { SpannableStringBuilder builder = new SpannableStringBuilder(messageEditText.getText()); @@ -1938,7 +2162,7 @@ public void replaceWithText(int start, int len, CharSequence text) { messageEditText.setText(builder); messageEditText.setSelection(start + text.length()); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1947,7 +2171,7 @@ public void setFieldFocused() { try { messageEditText.requestFocus(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1965,7 +2189,7 @@ public void run() { try { messageEditText.requestFocus(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1989,20 +2213,6 @@ public CharSequence getFieldText() { return null; } - public void addToAttachLayout(View view) { - if (attachButton == null) { - return; - } - if (view.getParent() != null) { - ViewGroup viewGroup = (ViewGroup) view.getParent(); - viewGroup.removeView(view); - } - if (Build.VERSION.SDK_INT >= 21) { - view.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); - } - attachButton.addView(view, LayoutHelper.createLinear(48, 48)); - } - private void updateBotButton() { if (botButton == null) { return; @@ -2024,7 +2234,7 @@ private void updateBotButton() { botButton.setVisibility(GONE); } updateFieldRight(2); - attachButton.setPivotX(AndroidUtilities.dp((botButton == null || botButton.getVisibility() == GONE) && (notifyButton == null || notifyButton.getVisibility() == GONE) ? 48 : 96)); + attachLayout.setPivotX(AndroidUtilities.dp((botButton == null || botButton.getVisibility() == GONE) && (notifyButton == null || notifyButton.getVisibility() == GONE) ? 48 : 96)); } public void setBotsCount(int count, boolean hasCommands) { @@ -2126,8 +2336,8 @@ public void onClick(DialogInterface dialogInterface, int i) { }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); parentFragment.showDialog(builder.create()); - } else if (button instanceof TLRPC.TL_keyboardButtonCallback || button instanceof TLRPC.TL_keyboardButtonGame) { - SendMessagesHelper.getInstance().sendCallback(messageObject, button, parentFragment); + } else if (button instanceof TLRPC.TL_keyboardButtonCallback || button instanceof TLRPC.TL_keyboardButtonGame || button instanceof TLRPC.TL_keyboardButtonBuy) { + SendMessagesHelper.getInstance().sendCallback(true, messageObject, button, parentFragment); } else if (button instanceof TLRPC.TL_keyboardButtonSwitchInline) { if (parentFragment.processSwitchButton((TLRPC.TL_keyboardButtonSwitchInline) button)) { return; @@ -2228,7 +2438,7 @@ public void onEmojiSelected(String symbol) { int j = i + localCharSequence.length(); messageEditText.setSelection(j, j); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { innerTextChange = 0; } @@ -2299,14 +2509,16 @@ public void onClick(DialogInterface dialogInterface, int i) { } @Override - public void onShowStickerSet(TLRPC.StickerSetCovered stickerSet) { + public void onShowStickerSet(TLRPC.StickerSet stickerSet, TLRPC.InputStickerSet inputStickerSet) { if (parentFragment == null || parentActivity == null) { return; } - TLRPC.TL_inputStickerSetID inputStickerSetID = new TLRPC.TL_inputStickerSetID(); - inputStickerSetID.access_hash = stickerSet.set.access_hash; - inputStickerSetID.id = stickerSet.set.id; - parentFragment.showDialog(new StickersAlert(parentActivity, parentFragment, inputStickerSetID, null, ChatActivityEnterView.this)); + if (stickerSet != null) { + inputStickerSet = new TLRPC.TL_inputStickerSetID(); + inputStickerSet.access_hash = stickerSet.access_hash; + inputStickerSet.id = stickerSet.id; + } + parentFragment.showDialog(new StickersAlert(parentActivity, parentFragment, inputStickerSet, null, ChatActivityEnterView.this)); } @Override @@ -2495,10 +2707,10 @@ public boolean isKeyboardVisible() { } public void addRecentGif(TLRPC.Document searchImage) { - if (emojiView == null) { - return; + StickersQuery.addRecentGif(searchImage, (int) (System.currentTimeMillis() / 1000)); + if (emojiView != null) { + emojiView.addRecentGif(searchImage); } - emojiView.addRecentGif(searchImage); } @Override @@ -2599,15 +2811,15 @@ public void didReceivedNotification(int id, Object... args) { AndroidUtilities.hideKeyboard(messageEditText); } } else if (id == NotificationCenter.recordStartError || id == NotificationCenter.recordStopped) { - if (recordingAudio) { + if (recordingAudioVideo) { MessagesController.getInstance().sendTyping(dialog_id, 2, 0); - recordingAudio = false; - updateAudioRecordIntefrace(); + recordingAudioVideo = false; + updateRecordIntefrace(); } } else if (id == NotificationCenter.recordStarted) { - if (!recordingAudio) { - recordingAudio = true; - updateAudioRecordIntefrace(); + if (!recordingAudioVideo) { + recordingAudioVideo = true; + updateRecordIntefrace(); } } else if (id == NotificationCenter.audioDidSent) { audioToSend = (TLRPC.TL_document) args[0]; @@ -2667,7 +2879,7 @@ public void didReceivedNotification(int id, Object... args) { } } else if (id == NotificationCenter.audioDidReset) { if (audioToSendMessageObject != null && !MediaController.getInstance().isPlayingAudio(audioToSendMessageObject)) { - recordedAudioPlayButton.setImageResource(R.drawable.s_player_play_states); + recordedAudioPlayButton.setImageDrawable(playDrawable); recordedAudioSeekBar.setProgress(0); } } else if (id == NotificationCenter.audioProgressDidChanged) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java index 5617d2b84ba..599ec7a1214 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -16,13 +16,13 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; @@ -30,6 +30,7 @@ import android.hardware.Camera; import android.media.ExifInterface; import android.os.Build; +import android.provider.Settings; import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; @@ -47,7 +48,6 @@ import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.MessagesController; import org.telegram.messenger.ContactsController; @@ -56,12 +56,14 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MessageObject; import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.camera.*; import org.telegram.messenger.query.SearchQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.R; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.PhotoAttachCameraCell; @@ -80,6 +82,7 @@ public interface ChatAttachViewDelegate { void didPressedButton(int button); View getRevealView(); void didSelectBot(TLRPC.User user); + void onCameraOpened(); } private class InnerAnimator { @@ -95,7 +98,7 @@ private class InnerAnimator { private RecyclerListView attachPhotoRecyclerView; private View lineView; private EmptyTextProgressView progressView; - private ArrayList viewsCache = new ArrayList<>(8); + private ArrayList viewsCache = new ArrayList<>(8); private RecyclerListView listView; private LinearLayoutManager layoutManager; private Drawable shadowDrawable; @@ -103,6 +106,8 @@ private class InnerAnimator { private ListAdapter adapter; private TextView hintTextView; private ArrayList innerAnimators = new ArrayList<>(); + private boolean requestingPermissions; + private Paint ciclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private CameraView cameraView; private FrameLayout cameraIcon; @@ -110,6 +115,7 @@ private class InnerAnimator { private ImageView[] flashModeButton = new ImageView[2]; private boolean flashAnimationInProgress; private int[] cameraViewLocation = new int[2]; + private int[] viewPosition = new int[2]; private int cameraViewOffsetX; private int cameraViewOffsetY; private boolean cameraOpened; @@ -125,11 +131,13 @@ private class InnerAnimator { private ImageView switchCameraButton; private File cameraFile; private boolean takingPhoto; + private boolean mediaCaptured; private ArrayList cameraPhoto; private float lastY; private boolean pressed; private boolean maybeStartDraging; + private boolean dragging; private AnimatorSet currentHintAnimation; private boolean hintShowed; @@ -152,6 +160,8 @@ private class InnerAnimator { private int revealY; private boolean revealAnimationInProgress; + private boolean paused; + private class AttachButton extends FrameLayout { private TextView textView; @@ -169,7 +179,7 @@ public AttachButton(Context context) { textView.setSingleLine(true); textView.setGravity(Gravity.CENTER_HORIZONTAL); textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setTextColor(Theme.ATTACH_SHEET_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextGray2)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 64, 0, 0)); } @@ -237,7 +247,7 @@ public AttachBotButton(Context context) { addView(imageView, LayoutHelper.createFrame(54, 54, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 7, 0, 0)); nameTextView = new TextView(context); - nameTextView.setTextColor(Theme.ATTACH_SHEET_TEXT_COLOR); + nameTextView.setTextColor(Theme.getColor(Theme.key_dialogTextGray2)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); nameTextView.setMaxLines(2); nameTextView.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL); @@ -345,6 +355,7 @@ protected void cancelCheckLongPress() { public ChatAttachAlert(Context context, final ChatActivity parentFragment) { super(context, false); baseFragment = parentFragment; + ciclePaint.setColor(Theme.getColor(Theme.key_dialogBackground)); setDelegate(this); setUseRevealAnimation(true); checkCamera(false); @@ -354,7 +365,7 @@ public ChatAttachAlert(Context context, final ChatActivity parentFragment) { NotificationCenter.getInstance().addObserver(this, NotificationCenter.albumsDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.reloadInlineHints); NotificationCenter.getInstance().addObserver(this, NotificationCenter.cameraInitied); - shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); containerView = listView = new RecyclerListView(context) { @@ -486,7 +497,7 @@ public void setTranslationY(float translationY) { listView.setAdapter(adapter = new ListAdapter(context)); listView.setVerticalScrollBarEnabled(false); listView.setEnabled(true); - listView.setGlowColor(0xfff5f6f7); + listView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); listView.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { @@ -577,6 +588,7 @@ public void onItemClick(View view, int position) { return; } PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); + PhotoViewer.getInstance().setParentAlert(ChatAttachAlert.this); PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, 0, ChatAttachAlert.this, baseFragment); AndroidUtilities.hideKeyboard(baseFragment.getFragmentView().findFocus()); } else { @@ -608,7 +620,7 @@ public boolean hasOverlappingRendering() { return false; } }; - lineView.setBackgroundColor(0xffd2d2d2); + lineView.setBackgroundColor(Theme.getColor(Theme.key_dialogGrayLine)); attachView.addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.TOP | Gravity.LEFT)); CharSequence[] items = new CharSequence[]{ LocaleController.getString("ChatCamera", R.string.ChatCamera), @@ -622,7 +634,7 @@ public boolean hasOverlappingRendering() { }; for (int a = 0; a < 8; a++) { AttachButton attachButton = new AttachButton(context); - attachButton.setTextAndIcon(items[a], Theme.attachButtonDrawables[a]); + attachButton.setTextAndIcon(items[a], Theme.chat_attachButtonDrawables[a]); attachView.addView(attachButton, LayoutHelper.createFrame(85, 90, Gravity.LEFT | Gravity.TOP)); attachButton.setTag(a); views[a] = attachButton; @@ -639,8 +651,8 @@ public void onClick(View v) { } hintTextView = new TextView(context); - hintTextView.setBackgroundResource(R.drawable.tooltip); - hintTextView.setTextColor(Theme.CHAT_GIF_HINT_TEXT_COLOR); + hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); + hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); hintTextView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); hintTextView.setText(LocaleController.getString("AttachBotsHelp", R.string.AttachBotsHelp)); @@ -703,14 +715,15 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto cameraPanel.addView(shutterButton, LayoutHelper.createFrame(84, 84, Gravity.CENTER)); shutterButton.setDelegate(new ShutterButton.ShutterButtonDelegate() { @Override - public void shutterLongPressed() { - if (takingPhoto || baseFragment == null || baseFragment.getParentActivity() == null) { - return; + public boolean shutterLongPressed() { + if (mediaCaptured || takingPhoto || baseFragment == null || baseFragment.getParentActivity() == null || cameraView == null) { + return false; } if (Build.VERSION.SDK_INT >= 23) { if (baseFragment.getParentActivity().checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestingPermissions = true; baseFragment.getParentActivity().requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 21); - return; + return false; } } for (int a = 0; a < 2; a++) { @@ -740,6 +753,7 @@ public void onFinishVideoRecording(final Bitmap thumb) { return; } PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); + PhotoViewer.getInstance().setParentAlert(ChatAttachAlert.this); cameraPhoto = new ArrayList<>(); cameraPhoto.add(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), 0, true)); PhotoViewer.getInstance().openPhotoForSelect(cameraPhoto, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { @@ -766,27 +780,39 @@ public void run() { } return true; } - @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (cameraFile == null) { return; } AndroidUtilities.addMediaToGallery(cameraFile.getAbsolutePath()); - baseFragment.sendMedia((MediaController.PhotoEntry) cameraPhoto.get(0), PhotoViewer.getInstance().isMuteVideo()); + baseFragment.sendMedia((MediaController.PhotoEntry) cameraPhoto.get(0), videoEditedInfo); closeCamera(false); dismiss(); cameraFile = null; } + + @Override + public void willHidePhotoViewer() { + mediaCaptured = false; + } }, baseFragment); } - }); - AndroidUtilities.runOnUIThread(videoRecordRunnable, 1000); + }, new Runnable() { + @Override + public void run() { + AndroidUtilities.runOnUIThread(videoRecordRunnable, 1000); + } + }, false); shutterButton.setState(ShutterButton.State.RECORDING, true); + return true; } @Override public void shutterCancel() { + if (mediaCaptured) { + return; + } cameraFile.delete(); resetRecordState(); CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), true); @@ -794,9 +820,10 @@ public void shutterCancel() { @Override public void shutterReleased() { - if (takingPhoto) { + if (takingPhoto || cameraView == null || mediaCaptured) { return; } + mediaCaptured = true; if (shutterButton.getState() == ShutterButton.State.RECORDING) { resetRecordState(); CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), false); @@ -804,6 +831,7 @@ public void shutterReleased() { return; } cameraFile = AndroidUtilities.generatePicturePath(); + final boolean sameTakePictureOrientation = cameraView.getCameraSession().isSameTakePictureOrientation(); takingPhoto = CameraController.getInstance().takePicture(cameraFile, cameraView.getCameraSession(), new Runnable() { @Override public void run() { @@ -812,6 +840,7 @@ public void run() { return; } PhotoViewer.getInstance().setParentActivity(baseFragment.getParentActivity()); + PhotoViewer.getInstance().setParentAlert(ChatAttachAlert.this); cameraPhoto = new ArrayList<>(); int orientation = 0; try { @@ -829,7 +858,7 @@ public void run() { break; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } cameraPhoto.add(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), orientation, false)); PhotoViewer.getInstance().openPhotoForSelect(cameraPhoto, 0, 2, new PhotoViewer.EmptyPhotoViewerProvider() { @@ -853,12 +882,12 @@ public void run() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (cameraFile == null) { return; } AndroidUtilities.addMediaToGallery(cameraFile.getAbsolutePath()); - baseFragment.sendMedia((MediaController.PhotoEntry) cameraPhoto.get(0), false); + baseFragment.sendMedia((MediaController.PhotoEntry) cameraPhoto.get(0), null); closeCamera(false); dismiss(); cameraFile = null; @@ -866,7 +895,13 @@ public void sendButtonPressed(int index) { @Override public boolean scaleToFill() { - return true; + int locked = Settings.System.getInt(baseFragment.getParentActivity().getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0); + return sameTakePictureOrientation || locked == 1; + } + + @Override + public void willHidePhotoViewer() { + mediaCaptured = false; } }, baseFragment); } @@ -886,7 +921,7 @@ public void onClick(View v) { cameraInitied = false; cameraView.switchCamera(); ObjectAnimator animator = ObjectAnimator.ofFloat(switchCameraButton, "scaleX", 0.0f).setDuration(100); - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { switchCameraButton.setImageResource(cameraView.isFrontface() ? R.drawable.camera_revert1 : R.drawable.camera_revert2); @@ -925,7 +960,7 @@ public void onClick(final View currentImage) { ObjectAnimator.ofFloat(currentImage, "alpha", 1.0f, 0.0f), ObjectAnimator.ofFloat(nextImage, "alpha", 0.0f, 1.0f)); animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { flashAnimationInProgress = false; @@ -953,36 +988,49 @@ private boolean processTouchEvent(MotionEvent event) { if (maybeStartDraging) { if (Math.abs(dy) > AndroidUtilities.getPixelsInCM(0.4f, false)) { maybeStartDraging = false; + dragging = true; } - } else { - cameraView.setTranslationY(cameraView.getTranslationY() + dy); - lastY = newY; - if (cameraPanel.getTag() == null) { - cameraPanel.setTag(1); - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - ObjectAnimator.ofFloat(cameraPanel, "alpha", 0.0f), - ObjectAnimator.ofFloat(flashModeButton[0], "alpha", 0.0f), - ObjectAnimator.ofFloat(flashModeButton[1], "alpha", 0.0f)); - animatorSet.setDuration(200); - animatorSet.start(); + } else if (dragging) { + if (cameraView != null) { + cameraView.setTranslationY(cameraView.getTranslationY() + dy); + lastY = newY; + if (cameraPanel.getTag() == null) { + cameraPanel.setTag(1); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(cameraPanel, "alpha", 0.0f), + ObjectAnimator.ofFloat(flashModeButton[0], "alpha", 0.0f), + ObjectAnimator.ofFloat(flashModeButton[1], "alpha", 0.0f)); + animatorSet.setDuration(200); + animatorSet.start(); + } } } } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || event.getActionMasked() == MotionEvent.ACTION_UP || event.getActionMasked() == MotionEvent.ACTION_POINTER_UP) { pressed = false; - if (Math.abs(cameraView.getTranslationY()) > cameraView.getMeasuredHeight() / 6.0f) { - closeCamera(true); + if (dragging) { + dragging = false; + if (cameraView != null) { + if (Math.abs(cameraView.getTranslationY()) > cameraView.getMeasuredHeight() / 6.0f) { + closeCamera(true); + } else { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(cameraView, "translationY", 0.0f), + ObjectAnimator.ofFloat(cameraPanel, "alpha", 1.0f), + ObjectAnimator.ofFloat(flashModeButton[0], "alpha", 1.0f), + ObjectAnimator.ofFloat(flashModeButton[1], "alpha", 1.0f)); + animatorSet.setDuration(250); + animatorSet.setInterpolator(interpolator); + animatorSet.start(); + cameraPanel.setTag(null); + } + } } else { - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether( - ObjectAnimator.ofFloat(cameraView, "translationY", 0.0f), - ObjectAnimator.ofFloat(cameraPanel, "alpha", 1.0f), - ObjectAnimator.ofFloat(flashModeButton[0], "alpha", 1.0f), - ObjectAnimator.ofFloat(flashModeButton[1], "alpha", 1.0f)); - animatorSet.setDuration(250); - animatorSet.setInterpolator(interpolator); - animatorSet.start(); - cameraPanel.setTag(null); + cameraView.getLocationOnScreen(viewPosition); + float viewX = event.getRawX() - viewPosition[0]; + float viewY = event.getRawY() - viewPosition[1]; + cameraView.focusToPoint((int) viewX, (int) viewY); } } } @@ -1079,7 +1127,7 @@ private void hideHint() { ObjectAnimator.ofFloat(hintTextView, "alpha", 0.0f) ); currentHintAnimation.setInterpolator(decelerateInterpolator); - currentHintAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentHintAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentHintAnimation == null || !currentHintAnimation.equals(animation)) { @@ -1103,12 +1151,33 @@ public void onAnimationCancel(Animator animation) { } public void onPause() { - if (!cameraOpened || shutterButton.getState() != ShutterButton.State.RECORDING) { + if (shutterButton == null) { return; } - resetRecordState(); - CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), false); - shutterButton.setState(ShutterButton.State.DEFAULT, true); + if (!requestingPermissions) { + if (cameraView != null && shutterButton.getState() == ShutterButton.State.RECORDING) { + resetRecordState(); + CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), false); + shutterButton.setState(ShutterButton.State.DEFAULT, true); + } + if (cameraOpened) { + closeCamera(false); + } + hideCamera(true); + } else { + if (cameraView != null && shutterButton.getState() == ShutterButton.State.RECORDING) { + shutterButton.setState(ShutterButton.State.DEFAULT, true); + } + requestingPermissions = false; + } + paused = true; + } + + public void onResume() { + paused = false; + if (isShowing() && !isDismissed()) { + checkCamera(false); + } } @TargetApi(16) @@ -1134,10 +1203,13 @@ private void openCamera() { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(animators); animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { cameraAnimationInProgress = false; + if (cameraOpened) { + delegate.onCameraOpened(); + } } }); animatorSet.start(); @@ -1173,13 +1245,15 @@ public void closeCamera(boolean animated) { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(animators); animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { cameraAnimationInProgress = false; - cameraPanel.setVisibility(View.GONE); cameraOpened = false; - if (Build.VERSION.SDK_INT >= 21) { + if (cameraPanel != null) { + cameraPanel.setVisibility(View.GONE); + } + if (Build.VERSION.SDK_INT >= 21 && cameraView != null) { cameraView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } } @@ -1269,12 +1343,13 @@ private void checkCameraViewPosition() { } } child.getLocationInWindow(cameraViewLocation); - float listViewX = listView.getX() + backgroundPaddingLeft; + cameraViewLocation[0] -= getLeftInset(); + float listViewX = listView.getX() + backgroundPaddingLeft - getLeftInset(); if (cameraViewLocation[0] < listViewX) { cameraViewOffsetX = (int) (listViewX - cameraViewLocation[0]); if (cameraViewOffsetX >= AndroidUtilities.dp(80)) { cameraViewOffsetX = 0; - cameraViewLocation[0] = AndroidUtilities.dp(-100); + cameraViewLocation[0] = AndroidUtilities.dp(-150); cameraViewLocation[1] = 0; } else { cameraViewLocation[0] += cameraViewOffsetX; @@ -1286,7 +1361,7 @@ private void checkCameraViewPosition() { cameraViewOffsetY = AndroidUtilities.statusBarHeight - cameraViewLocation[1]; if (cameraViewOffsetY >= AndroidUtilities.dp(80)) { cameraViewOffsetY = 0; - cameraViewLocation[0] = AndroidUtilities.dp(-100); + cameraViewLocation[0] = AndroidUtilities.dp(-150); cameraViewLocation[1] = 0; } else { cameraViewLocation[1] += cameraViewOffsetY; @@ -1300,7 +1375,7 @@ private void checkCameraViewPosition() { } cameraViewOffsetX = 0; cameraViewOffsetY = 0; - cameraViewLocation[0] = AndroidUtilities.dp(-100); + cameraViewLocation[0] = AndroidUtilities.dp(-150); cameraViewLocation[1] = 0; applyCameraViewPosition(); } @@ -1357,10 +1432,18 @@ public void run() { @TargetApi(16) public void showCamera() { + if (paused) { + return; + } if (cameraView == null) { - cameraView = new CameraView(baseFragment.getParentActivity()); + cameraView = new CameraView(baseFragment.getParentActivity(), false); container.addView(cameraView, 1, LayoutHelper.createFrame(80, 80)); cameraView.setDelegate(new CameraView.CameraViewDelegate() { + @Override + public void onCameraCreated(Camera camera) { + + } + @Override public void onCameraInit() { int count = attachPhotoRecyclerView.getChildCount(); @@ -1411,7 +1494,7 @@ public void hideCamera(boolean async) { if (!deviceHasGoodCamera || cameraView == null) { return; } - cameraView.destroy(async); + cameraView.destroy(async, null); container.removeView(cameraView); container.removeView(cameraIcon); cameraView = null; @@ -1442,7 +1525,7 @@ private void showHint() { ObjectAnimator.ofFloat(hintTextView, "alpha", 0.0f, 1.0f) ); currentHintAnimation.setInterpolator(decelerateInterpolator); - currentHintAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentHintAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentHintAnimation == null || !currentHintAnimation.equals(animation)) { @@ -1497,7 +1580,7 @@ private void updateLayout() { return; } View child = listView.getChildAt(0); - Holder holder = (Holder) listView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); int top = child.getTop(); int newOffset = 0; if (top >= 0 && holder != null && holder.getAdapterPosition() == 0) { @@ -1607,6 +1690,7 @@ public PhotoViewer.PlaceProviderObject getPlaceForPhoto(MessageObject messageObj if (cell != null) { int coords[] = new int[2]; cell.getImageView().getLocationInWindow(coords); + coords[0] -= getLeftInset(); PhotoViewer.PlaceProviderObject object = new PhotoViewer.PlaceProviderObject(); object.viewX = coords[0]; object.viewY = coords[1]; @@ -1716,7 +1800,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (photoAttachAdapter.getSelectedPhotos().isEmpty()) { if (index < 0 || index >= MediaController.allPhotosAlbumEntry.photos.size()) { return; @@ -1766,7 +1850,7 @@ public void checkCamera(boolean request) { if (old != deviceHasGoodCamera && photoAttachAdapter != null) { photoAttachAdapter.notifyDataSetChanged(); } - if (isShowing() && deviceHasGoodCamera && baseFragment != null && !revealAnimationInProgress) { + if (isShowing() && deviceHasGoodCamera && baseFragment != null && backDrawable.getAlpha() != 0 && !revealAnimationInProgress && !cameraOpened) { showCamera(); } } @@ -1781,14 +1865,18 @@ public void onOpenAnimationStart() { } - private class Holder extends RecyclerView.ViewHolder { + @Override + public boolean canDismiss() { + return true; + } - public Holder(View itemView) { - super(itemView); - } + @Override + public void setAllowDrawContent(boolean value) { + super.setAllowDrawContent(value); + checkCameraViewPosition(); } - private class ListAdapter extends RecyclerView.Adapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; @@ -1825,7 +1913,7 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto frameLayout.setLayoutParams(new RecyclerView.LayoutParams(LayoutHelper.MATCH_PARENT, AndroidUtilities.dp(100))); break; } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -1847,6 +1935,11 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public int getItemCount() { return 1 + (!SearchQuery.inlineBots.isEmpty() ? 1 + (int) Math.ceil(SearchQuery.inlineBots.size() / 4.0f) : 0); @@ -1865,7 +1958,7 @@ public int getItemViewType(int position) { } } - private class PhotoAttachAdapter extends RecyclerView.Adapter { + private class PhotoAttachAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private HashMap selectedPhotos = new HashMap<>(); @@ -1889,7 +1982,7 @@ public void clearSelectedPhotos() { } } - public Holder createHolder() { + public RecyclerListView.Holder createHolder() { PhotoAttachPhotoCell cell = new PhotoAttachPhotoCell(mContext); cell.setDelegate(new PhotoAttachPhotoCell.PhotoAttachPhotoCellDelegate() { @Override @@ -1909,7 +2002,7 @@ public void onCheckClick(PhotoAttachPhotoCell v) { updatePhotosButton(); } }); - return new Holder(cell); + return new RecyclerListView.Holder(cell); } public HashMap getSelectedPhotos() { @@ -1937,12 +2030,17 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - Holder holder; + RecyclerListView.Holder holder; switch (viewType) { case 1: - holder = new Holder(new PhotoAttachCameraCell(mContext)); + holder = new RecyclerListView.Holder(new PhotoAttachCameraCell(mContext)); break; default: if (!viewsCache.isEmpty()) { @@ -2053,7 +2151,7 @@ private void startRevealAnimation(final boolean open) { try { animators.add(ViewAnimationUtils.createCircularReveal(containerView, finalRevealX, revealY, open ? 0 : finalRevealRadius, open ? finalRevealRadius : 0)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } animatorSet.setDuration(320); } else { @@ -2086,7 +2184,7 @@ public void onAnimationEnd(Animator animation) { try { dismissInternal(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java index f7b8ca00fe1..f74388e7483 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAvatarContainer.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; @@ -42,7 +43,8 @@ public class ChatAvatarContainer extends FrameLayout { private ChatActivity parentFragment; private TypingDotsDrawable typingDotsDrawable; private RecordStatusDrawable recordStatusDrawable; - private SendingFileExDrawable sendingFileDrawable; + private SendingFileDrawable sendingFileDrawable; + private PlayingGameDrawable playingGameDrawable; private AvatarDrawable avatarDrawable = new AvatarDrawable(); private int onlineCount = -1; @@ -56,16 +58,15 @@ public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean n addView(avatarImageView); titleTextView = new SimpleTextView(context); - titleTextView.setTextColor(Theme.ACTION_BAR_TITLE_COLOR); + titleTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); titleTextView.setTextSize(18); titleTextView.setGravity(Gravity.LEFT); titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); titleTextView.setLeftDrawableTopPadding(-AndroidUtilities.dp(1.3f)); - titleTextView.setRightDrawableTopPadding(-AndroidUtilities.dp(1.3f)); addView(titleTextView); subtitleTextView = new SimpleTextView(context); - subtitleTextView.setTextColor(Theme.ACTION_BAR_SUBTITLE_COLOR); + subtitleTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); subtitleTextView.setTextSize(14); subtitleTextView.setGravity(Gravity.LEFT); addView(subtitleTextView); @@ -79,7 +80,7 @@ public ChatAvatarContainer(Context context, ChatActivity chatActivity, boolean n timeItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - parentFragment.showDialog(AndroidUtilities.buildTTLAlert(getContext(), parentFragment.getCurrentEncryptedChat()).create()); + parentFragment.showDialog(AlertsCreator.createTTLAlert(getContext(), parentFragment.getCurrentEncryptedChat()).create()); } }); } @@ -114,8 +115,10 @@ public void onClick(View v) { typingDotsDrawable.setIsChat(chat != null); recordStatusDrawable = new RecordStatusDrawable(); recordStatusDrawable.setIsChat(chat != null); - sendingFileDrawable = new SendingFileExDrawable(); + sendingFileDrawable = new SendingFileDrawable(); sendingFileDrawable.setIsChat(chat != null); + playingGameDrawable = new PlayingGameDrawable(); + playingGameDrawable.setIsChat(chat != null); } @Override @@ -169,10 +172,27 @@ public void setTitleIcons(int leftIcon, int rightIcon) { titleTextView.setRightDrawable(rightIcon); } + public void setTitleIcons(Drawable leftIcon, Drawable rightIcon) { + titleTextView.setLeftDrawable(leftIcon); + titleTextView.setRightDrawable(rightIcon); + } + public void setTitle(CharSequence value) { titleTextView.setText(value); } + public ImageView getTimeItem() { + return timeItem; + } + + public SimpleTextView getTitleTextView() { + return titleTextView; + } + + public SimpleTextView getSubtitleTextView() { + return subtitleTextView; + } + private void setTypingAnimation(boolean start) { if (start) { try { @@ -182,25 +202,35 @@ private void setTypingAnimation(boolean start) { typingDotsDrawable.start(); recordStatusDrawable.stop(); sendingFileDrawable.stop(); + playingGameDrawable.stop(); } else if (type == 1) { subtitleTextView.setLeftDrawable(recordStatusDrawable); recordStatusDrawable.start(); typingDotsDrawable.stop(); sendingFileDrawable.stop(); + playingGameDrawable.stop(); } else if (type == 2) { subtitleTextView.setLeftDrawable(sendingFileDrawable); sendingFileDrawable.start(); typingDotsDrawable.stop(); recordStatusDrawable.stop(); + playingGameDrawable.stop(); + } else if (type == 3) { + subtitleTextView.setLeftDrawable(playingGameDrawable); + playingGameDrawable.start(); + typingDotsDrawable.stop(); + recordStatusDrawable.stop(); + sendingFileDrawable.stop(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { subtitleTextView.setLeftDrawable(null); typingDotsDrawable.stop(); recordStatusDrawable.stop(); sendingFileDrawable.stop(); + playingGameDrawable.stop(); } } @@ -258,7 +288,10 @@ public void updateSubtitle() { } } } else if (user != null) { - user = MessagesController.getInstance().getUser(user.id); + TLRPC.User newUser = MessagesController.getInstance().getUser(user.id); + if (newUser != null) { + user = newUser; + } String newStatus; if (user.id == UserConfig.getClientUserId()) { newStatus = LocaleController.getString("ChatYourSelf", R.string.ChatYourSelf); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatBigEmptyView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatBigEmptyView.java index 6a88b191e20..80258cc8d11 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatBigEmptyView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatBigEmptyView.java @@ -3,12 +3,14 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.util.TypedValue; import android.view.Gravity; import android.widget.ImageView; @@ -20,9 +22,13 @@ import org.telegram.messenger.R; import org.telegram.ui.ActionBar.Theme; +import java.util.ArrayList; + public class ChatBigEmptyView extends LinearLayout { private TextView secretViewStatusTextView; + private ArrayList textViews = new ArrayList<>(); + private ArrayList imageViews = new ArrayList<>(); public ChatBigEmptyView(Context context, boolean secretChat) { super(context); @@ -35,9 +41,10 @@ public ChatBigEmptyView(Context context, boolean secretChat) { if (secretChat) { secretViewStatusTextView = new TextView(context); secretViewStatusTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - secretViewStatusTextView.setTextColor(Theme.SECRET_CHAT_INFO_TEXT_COLOR); + secretViewStatusTextView.setTextColor(Theme.getColor(Theme.key_chat_serviceText)); secretViewStatusTextView.setGravity(Gravity.CENTER_HORIZONTAL); secretViewStatusTextView.setMaxWidth(AndroidUtilities.dp(210)); + textViews.add(secretViewStatusTextView); addView(secretViewStatusTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); } else { ImageView imageView = new ImageView(context); @@ -55,7 +62,8 @@ public ChatBigEmptyView(Context context, boolean secretChat) { textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setGravity(Gravity.CENTER_HORIZONTAL); } - textView.setTextColor(Theme.SECRET_CHAT_INFO_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_chat_serviceText)); + textViews.add(textView); textView.setMaxWidth(AndroidUtilities.dp(260)); addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (secretChat ? (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) : Gravity.CENTER_HORIZONTAL) | Gravity.TOP, 0, 8, 0, secretChat ? 0 : 8)); @@ -65,11 +73,14 @@ public ChatBigEmptyView(Context context, boolean secretChat) { addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 8, 0, 0)); ImageView imageView = new ImageView(context); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_serviceText), PorterDuff.Mode.MULTIPLY)); imageView.setImageResource(secretChat ? R.drawable.ic_lock_white : R.drawable.list_circle); + imageViews.add(imageView); textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - textView.setTextColor(Theme.SECRET_CHAT_INFO_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_chat_serviceText)); + textViews.add(textView); textView.setGravity(Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); textView.setMaxWidth(AndroidUtilities.dp(260)); @@ -122,6 +133,15 @@ public ChatBigEmptyView(Context context, boolean secretChat) { } } + public void setTextColor(int color) { + for (int a = 0; a < textViews.size(); a++) { + textViews.get(a).setTextColor(color); + } + for (int a = 0; a < imageViews.size(); a++) { + imageViews.get(a).setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_serviceText), PorterDuff.Mode.MULTIPLY)); + } + } + public void setSecretText(String text) { secretViewStatusTextView.setText(text); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java index 7b68bc2e1b8..2d1ea0eaf3b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBox.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -14,6 +14,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; import android.graphics.drawable.Drawable; import android.view.View; @@ -45,7 +46,7 @@ public class CheckBox extends View { private int size = 22; private int checkOffset; - private int color = 0xff5ec245; + private int color; //default 0xff5ec245 private final static float progressBounceDiff = 0.2f; @@ -67,7 +68,7 @@ public CheckBox(Context context, int resId) { backgroundPaint.setStrokeWidth(AndroidUtilities.dp(2)); } - checkDrawable = context.getResources().getDrawable(resId); + checkDrawable = context.getResources().getDrawable(resId).mutate(); } @Override @@ -105,8 +106,20 @@ public float getProgress() { return progress; } - public void setColor(int value) { - color = value; + public void setColor(int backgroundColor, int checkColor) { + color = backgroundColor; + checkDrawable.setColorFilter(new PorterDuffColorFilter(checkColor, PorterDuff.Mode.MULTIPLY)); + invalidate(); + } + + public void setBackgroundColor(int backgroundColor) { + color = backgroundColor; + invalidate(); + } + + public void setCheckColor(int checkColor) { + checkDrawable.setColorFilter(new PorterDuffColorFilter(checkColor, PorterDuff.Mode.MULTIPLY)); + invalidate(); } private void cancelCheckAnimator() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxSquare.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxSquare.java index ab89f7ab0d3..e187cae2900 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxSquare.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CheckBoxSquare.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -13,20 +13,16 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; import android.graphics.RectF; +import android.support.annotation.Keep; import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; public class CheckBoxSquare extends View { - private static Paint eraser; - private static Paint checkPaint; - private static Paint backgroundPaint; - private static RectF rectF; + private RectF rectF; private Bitmap drawBitmap; private Canvas drawCanvas; @@ -37,37 +33,18 @@ public class CheckBoxSquare extends View { private boolean attachedToWindow; private boolean isChecked; private boolean isDisabled; - - private int color = 0xff43a0df; + private boolean isAlert; private final static float progressBounceDiff = 0.2f; - public CheckBoxSquare(Context context) { + public CheckBoxSquare(Context context, boolean alert) { super(context); - if (checkPaint == null) { - checkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - checkPaint.setColor(0xffffffff); - checkPaint.setStyle(Paint.Style.STROKE); - checkPaint.setStrokeWidth(AndroidUtilities.dp(2)); - eraser = new Paint(Paint.ANTI_ALIAS_FLAG); - eraser.setColor(0); - eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); - backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - rectF = new RectF(); - } - + rectF = new RectF(); drawBitmap = Bitmap.createBitmap(AndroidUtilities.dp(18), AndroidUtilities.dp(18), Bitmap.Config.ARGB_4444); drawCanvas = new Canvas(drawBitmap); } - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - if (visibility == VISIBLE && drawBitmap == null) { - - } - } - + @Keep public void setProgress(float value) { if (progress == value) { return; @@ -80,10 +57,6 @@ public float getProgress() { return progress; } - public void setColor(int value) { - color = value; - } - private void cancelCheckAnimator() { if (checkAnimator != null) { checkAnimator.cancel(); @@ -143,40 +116,43 @@ protected void onDraw(Canvas canvas) { float checkProgress; float bounceProgress; + int uncheckedColor = Theme.getColor(isAlert ? Theme.key_dialogCheckboxSquareUnchecked : Theme.key_checkboxSquareUnchecked); + int color = Theme.getColor(isAlert ? Theme.key_dialogCheckboxSquareBackground : Theme.key_checkboxSquareBackground); if (progress <= 0.5f) { bounceProgress = checkProgress = progress / 0.5f; - int rD = (int) ((Color.red(color) - 0x73) * checkProgress); - int gD = (int) ((Color.green(color) - 0x73) * checkProgress); - int bD = (int) ((Color.blue(color) - 0x73) * checkProgress); - int c = Color.rgb(0x73 + rD, 0x73 + gD, 0x73 + bD); - backgroundPaint.setColor(c); + int rD = (int) ((Color.red(color) - Color.red(uncheckedColor)) * checkProgress); + int gD = (int) ((Color.green(color) - Color.green(uncheckedColor)) * checkProgress); + int bD = (int) ((Color.blue(color) - Color.blue(uncheckedColor)) * checkProgress); + int c = Color.rgb(Color.red(uncheckedColor) + rD, Color.green(uncheckedColor) + gD, Color.blue(uncheckedColor) + bD); + Theme.checkboxSquare_backgroundPaint.setColor(c); } else { bounceProgress = 2.0f - progress / 0.5f; checkProgress = 1.0f; - backgroundPaint.setColor(color); + Theme.checkboxSquare_backgroundPaint.setColor(color); } if (isDisabled) { - backgroundPaint.setColor(0xffb0b0b0); + Theme.checkboxSquare_backgroundPaint.setColor(Theme.getColor(isAlert ? Theme.key_dialogCheckboxSquareDisabled : Theme.key_checkboxSquareDisabled)); } float bounce = AndroidUtilities.dp(1) * bounceProgress; rectF.set(bounce, bounce, AndroidUtilities.dp(18) - bounce, AndroidUtilities.dp(18) - bounce); drawBitmap.eraseColor(0); - drawCanvas.drawRoundRect(rectF, AndroidUtilities.dp(2), AndroidUtilities.dp(2), backgroundPaint); + drawCanvas.drawRoundRect(rectF, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.checkboxSquare_backgroundPaint); if (checkProgress != 1) { float rad = Math.min(AndroidUtilities.dp(7), AndroidUtilities.dp(7) * checkProgress + bounce); rectF.set(AndroidUtilities.dp(2) + rad, AndroidUtilities.dp(2) + rad, AndroidUtilities.dp(16) - rad, AndroidUtilities.dp(16) - rad); - drawCanvas.drawRect(rectF, eraser); + drawCanvas.drawRect(rectF, Theme.checkboxSquare_eraserPaint); } if (progress > 0.5f) { + Theme.checkboxSquare_checkPaint.setColor(Theme.getColor(isAlert ? Theme.key_dialogCheckboxSquareCheck : Theme.key_checkboxSquareCheck)); int endX = (int) (AndroidUtilities.dp(7.5f) - AndroidUtilities.dp(5) * (1.0f - bounceProgress)); int endY = (int) (AndroidUtilities.dpf2(13.5f) - AndroidUtilities.dp(5) * (1.0f - bounceProgress)); - drawCanvas.drawLine(AndroidUtilities.dp(7.5f), (int) AndroidUtilities.dpf2(13.5f), endX, endY, checkPaint); + drawCanvas.drawLine(AndroidUtilities.dp(7.5f), (int) AndroidUtilities.dpf2(13.5f), endX, endY, Theme.checkboxSquare_checkPaint); endX = (int) (AndroidUtilities.dpf2(6.5f) + AndroidUtilities.dp(9) * (1.0f - bounceProgress)); endY = (int) (AndroidUtilities.dpf2(13.5f) - AndroidUtilities.dp(9) * (1.0f - bounceProgress)); - drawCanvas.drawLine((int) AndroidUtilities.dpf2(6.5f), (int) AndroidUtilities.dpf2(13.5f), endX, endY, checkPaint); + drawCanvas.drawLine((int) AndroidUtilities.dpf2(6.5f), (int) AndroidUtilities.dpf2(13.5f), endX, endY, Theme.checkboxSquare_checkPaint); } canvas.drawBitmap(drawBitmap, 0, 0, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChipSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChipSpan.java deleted file mode 100644 index 6ef95a40440..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChipSpan.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.text.style.ImageSpan; - -import org.telegram.messenger.AndroidUtilities; - -public class ChipSpan extends ImageSpan { - - public int uid; - - public ChipSpan(Drawable d, int verticalAlignment) { - super(d, verticalAlignment); - } - - @Override - public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { - if (fm == null) { - fm = new Paint.FontMetricsInt(); - } - - int sz = super.getSize(paint, text, start, end, fm); - int offset = AndroidUtilities.dp(6); - int w = (fm.bottom - fm.top) / 2; - fm.top = -w - offset; - fm.bottom = w - offset; - fm.ascent = -w - offset; - fm.leading = 0; - fm.descent = w - offset; - return sz; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ClippingImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ClippingImageView.java index 0cbe153c92c..368b868397d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ClippingImageView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ClippingImageView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -159,7 +159,7 @@ public void onDraw(Canvas canvas) { try { canvas.drawBitmap(bmp, matrix, paint); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } canvas.restore(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable.java index 7ddd3586303..5e1d59a5077 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable2.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable2.java index 7d93f1253fa..975115f4f64 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CloseProgressDrawable2.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -29,7 +29,7 @@ public class CloseProgressDrawable2 extends Drawable { public CloseProgressDrawable2() { super(); - paint.setColor(0xffadadad); + paint.setColor(0xffffffff); paint.setStrokeWidth(AndroidUtilities.dp(2)); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStyle(Paint.Style.STROKE); @@ -45,6 +45,10 @@ public void stopAnimation() { animating = false; } + public void setColor(int value) { + paint.setColor(value); + } + @Override public void draw(Canvas canvas) { long newTime = System.currentTimeMillis(); @@ -123,7 +127,7 @@ public void setAlpha(int alpha) { @Override public void setColorFilter(ColorFilter cf) { - + paint.setColorFilter(cf); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ColorPickerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ColorPickerView.java deleted file mode 100644 index 4c51e354ce8..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ColorPickerView.java +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright 2012 Lars Werkman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.telegram.ui.Components; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Shader; -import android.graphics.SweepGradient; -import android.os.Bundle; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -import org.telegram.messenger.AndroidUtilities; - -public class ColorPickerView extends View { - - private static final String STATE_PARENT = "parent"; - private static final String STATE_ANGLE = "angle"; - private static final String STATE_OLD_COLOR = "color"; - private static final String STATE_SHOW_OLD_COLOR = "showColor"; - - private static final int[] COLORS = new int[] { 0xFFFF0000, 0xFFFF00FF, 0xFF0000FF, 0xFF00FFFF, 0xFF00FF00, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFF0000 }; - - private Paint mColorWheelPaint; - private Paint mPointerHaloPaint; - private Paint mPointerColor; - private int mColorWheelThickness; - private int mColorWheelRadius; - private int mPreferredColorWheelRadius; - private int mColorCenterRadius; - private int mPreferredColorCenterRadius; - private int mColorCenterHaloRadius; - private int mPreferredColorCenterHaloRadius; - private int mColorPointerRadius; - private int mColorPointerHaloRadius; - private RectF mColorWheelRectangle = new RectF(); - private RectF mCenterRectangle = new RectF(); - private boolean mUserIsMovingPointer = false; - private int mCenterOldColor; - private boolean mShowCenterOldColor; - private int mCenterNewColor; - private float mTranslationOffset; - private float mSlopX; - private float mSlopY; - private float mAngle; - private Paint mCenterOldPaint; - private Paint mCenterNewPaint; - private Paint mCenterHaloPaint; - private float[] mHSV = new float[3]; - - private OnColorChangedListener onColorChangedListener; - private OnColorSelectedListener onColorSelectedListener; - - private int oldChangedListenerColor; - private int oldSelectedListenerColor; - - public ColorPickerView(Context context) { - super(context); - init(null, 0); - } - - public ColorPickerView(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs, 0); - } - - public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(attrs, defStyle); - } - - public interface OnColorChangedListener { - void onColorChanged(int color); - } - - public interface OnColorSelectedListener { - void onColorSelected(int color); - } - - public void setOnColorChangedListener(OnColorChangedListener listener) { - this.onColorChangedListener = listener; - } - - public void setOnColorSelectedListener(OnColorSelectedListener listener) { - this.onColorSelectedListener = listener; - } - - private void init(AttributeSet attrs, int defStyle) { - mColorWheelThickness = AndroidUtilities.dp(8); - mColorWheelRadius = AndroidUtilities.dp(124); - mPreferredColorWheelRadius = mColorWheelRadius; - mColorCenterRadius = AndroidUtilities.dp(54); - mPreferredColorCenterRadius = mColorCenterRadius; - mColorCenterHaloRadius = AndroidUtilities.dp(60); - mPreferredColorCenterHaloRadius = mColorCenterHaloRadius; - mColorPointerRadius = AndroidUtilities.dp(14); - mColorPointerHaloRadius = AndroidUtilities.dp(18); - - mAngle = (float) (-Math.PI / 2); - - Shader s = new SweepGradient(0, 0, COLORS, null); - - mColorWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mColorWheelPaint.setShader(s); - mColorWheelPaint.setStyle(Paint.Style.STROKE); - mColorWheelPaint.setStrokeWidth(mColorWheelThickness); - - mPointerHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPointerHaloPaint.setColor(Color.BLACK); - mPointerHaloPaint.setAlpha(0x50); - - mPointerColor = new Paint(Paint.ANTI_ALIAS_FLAG); - mPointerColor.setColor(calculateColor(mAngle)); - - mCenterNewPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mCenterNewPaint.setColor(calculateColor(mAngle)); - mCenterNewPaint.setStyle(Paint.Style.FILL); - - mCenterOldPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mCenterOldPaint.setColor(calculateColor(mAngle)); - mCenterOldPaint.setStyle(Paint.Style.FILL); - - mCenterHaloPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mCenterHaloPaint.setColor(Color.BLACK); - mCenterHaloPaint.setAlpha(0x00); - - mCenterNewColor = calculateColor(mAngle); - mCenterOldColor = calculateColor(mAngle); - mShowCenterOldColor = true; - } - - @Override - protected void onDraw(Canvas canvas) { - canvas.translate(mTranslationOffset, mTranslationOffset); - canvas.drawOval(mColorWheelRectangle, mColorWheelPaint); - - float[] pointerPosition = calculatePointerPosition(mAngle); - - canvas.drawCircle(pointerPosition[0], pointerPosition[1], mColorPointerHaloRadius, mPointerHaloPaint); - canvas.drawCircle(pointerPosition[0], pointerPosition[1], mColorPointerRadius, mPointerColor); - canvas.drawCircle(0, 0, mColorCenterHaloRadius, mCenterHaloPaint); - - if (mShowCenterOldColor) { - canvas.drawArc(mCenterRectangle, 90, 180, true, mCenterOldPaint); - canvas.drawArc(mCenterRectangle, 270, 180, true, mCenterNewPaint); - } else { - canvas.drawArc(mCenterRectangle, 0, 360, true, mCenterNewPaint); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int intrinsicSize = 2 * (mPreferredColorWheelRadius + mColorPointerHaloRadius); - - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - int width; - int height; - - if (widthMode == MeasureSpec.EXACTLY) { - width = widthSize; - } else if (widthMode == MeasureSpec.AT_MOST) { - width = Math.min(intrinsicSize, widthSize); - } else { - width = intrinsicSize; - } - - if (heightMode == MeasureSpec.EXACTLY) { - height = heightSize; - } else if (heightMode == MeasureSpec.AT_MOST) { - height = Math.min(intrinsicSize, heightSize); - } else { - height = intrinsicSize; - } - - int min = Math.min(width, height); - setMeasuredDimension(min, min); - mTranslationOffset = min * 0.5f; - - mColorWheelRadius = min / 2 - mColorWheelThickness - mColorPointerHaloRadius; - mColorWheelRectangle.set(-mColorWheelRadius, -mColorWheelRadius, mColorWheelRadius, mColorWheelRadius); - - mColorCenterRadius = (int) ((float) mPreferredColorCenterRadius * ((float) mColorWheelRadius / (float) mPreferredColorWheelRadius)); - mColorCenterHaloRadius = (int) ((float) mPreferredColorCenterHaloRadius * ((float) mColorWheelRadius / (float) mPreferredColorWheelRadius)); - mCenterRectangle.set(-mColorCenterRadius, -mColorCenterRadius, mColorCenterRadius, mColorCenterRadius); - } - - private int ave(int s, int d, float p) { - return s + java.lang.Math.round(p * (d - s)); - } - - private int calculateColor(float angle) { - float unit = (float) (angle / (2 * Math.PI)); - if (unit < 0) { - unit += 1; - } - - if (unit <= 0) { - return COLORS[0]; - } - if (unit >= 1) { - return COLORS[COLORS.length - 1]; - } - - float p = unit * (COLORS.length - 1); - int i = (int) p; - p -= i; - - int c0 = COLORS[i]; - int c1 = COLORS[i + 1]; - int a = ave(Color.alpha(c0), Color.alpha(c1), p); - int r = ave(Color.red(c0), Color.red(c1), p); - int g = ave(Color.green(c0), Color.green(c1), p); - int b = ave(Color.blue(c0), Color.blue(c1), p); - - return Color.argb(a, r, g, b); - } - - public int getColor() { - return mCenterNewColor; - } - - public void setColor(int color) { - mAngle = colorToAngle(color); - mPointerColor.setColor(calculateColor(mAngle)); - mCenterNewPaint.setColor(calculateColor(mAngle)); - - invalidate(); - } - - private float colorToAngle(int color) { - float[] colors = new float[3]; - Color.colorToHSV(color, colors); - - return (float) Math.toRadians(-colors[0]); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - getParent().requestDisallowInterceptTouchEvent(true); - - float x = event.getX() - mTranslationOffset; - float y = event.getY() - mTranslationOffset; - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - float[] pointerPosition = calculatePointerPosition(mAngle); - if (x >= (pointerPosition[0] - mColorPointerHaloRadius) && x <= (pointerPosition[0] + mColorPointerHaloRadius) && y >= (pointerPosition[1] - mColorPointerHaloRadius) && y <= (pointerPosition[1] + mColorPointerHaloRadius)) { - mSlopX = x - pointerPosition[0]; - mSlopY = y - pointerPosition[1]; - mUserIsMovingPointer = true; - invalidate(); - } else if (x >= -mColorCenterRadius && x <= mColorCenterRadius && y >= -mColorCenterRadius && y <= mColorCenterRadius && mShowCenterOldColor) { - mCenterHaloPaint.setAlpha(0x50); - setColor(getOldCenterColor()); - invalidate(); - } else { - getParent().requestDisallowInterceptTouchEvent(false); - return false; - } - break; - case MotionEvent.ACTION_MOVE: - if (mUserIsMovingPointer) { - mAngle = (float) java.lang.Math.atan2(y - mSlopY, x - mSlopX); - mPointerColor.setColor(calculateColor(mAngle)); - setNewCenterColor(mCenterNewColor = calculateColor(mAngle)); - invalidate(); - } else { - getParent().requestDisallowInterceptTouchEvent(false); - return false; - } - break; - case MotionEvent.ACTION_UP: - mUserIsMovingPointer = false; - mCenterHaloPaint.setAlpha(0x00); - - if (onColorSelectedListener != null && mCenterNewColor != oldSelectedListenerColor) { - onColorSelectedListener.onColorSelected(mCenterNewColor); - oldSelectedListenerColor = mCenterNewColor; - } - - invalidate(); - break; - case MotionEvent.ACTION_CANCEL: - if (onColorSelectedListener != null && mCenterNewColor != oldSelectedListenerColor) { - onColorSelectedListener.onColorSelected(mCenterNewColor); - oldSelectedListenerColor = mCenterNewColor; - } - break; - } - return true; - } - - private float[] calculatePointerPosition(float angle) { - float x = (float) (mColorWheelRadius * Math.cos(angle)); - float y = (float) (mColorWheelRadius * Math.sin(angle)); - - return new float[] { x, y }; - } - - public void setNewCenterColor(int color) { - mCenterNewColor = color; - mCenterNewPaint.setColor(color); - if (mCenterOldColor == 0) { - mCenterOldColor = color; - mCenterOldPaint.setColor(color); - } - if (onColorChangedListener != null && color != oldChangedListenerColor ) { - onColorChangedListener.onColorChanged(color); - oldChangedListenerColor = color; - } - invalidate(); - } - - public void setOldCenterColor(int color) { - mCenterOldColor = color; - mCenterOldPaint.setColor(color); - invalidate(); - } - - public int getOldCenterColor() { - return mCenterOldColor; - } - - public void setShowOldCenterColor(boolean show) { - mShowCenterOldColor = show; - invalidate(); - } - - public boolean getShowOldCenterColor() { - return mShowCenterOldColor; - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - - Bundle state = new Bundle(); - state.putParcelable(STATE_PARENT, superState); - state.putFloat(STATE_ANGLE, mAngle); - state.putInt(STATE_OLD_COLOR, mCenterOldColor); - state.putBoolean(STATE_SHOW_OLD_COLOR, mShowCenterOldColor); - - return state; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - Bundle savedState = (Bundle) state; - - Parcelable superState = savedState.getParcelable(STATE_PARENT); - super.onRestoreInstanceState(superState); - - mAngle = savedState.getFloat(STATE_ANGLE); - setOldCenterColor(savedState.getInt(STATE_OLD_COLOR)); - mShowCenterOldColor = savedState.getBoolean(STATE_SHOW_OLD_COLOR); - int currentColor = calculateColor(mAngle); - mPointerColor.setColor(currentColor); - setNewCenterColor(currentColor); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java new file mode 100644 index 00000000000..def3a056e83 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CombinedDrawable.java @@ -0,0 +1,167 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; + +public class CombinedDrawable extends Drawable implements Drawable.Callback { + + private Drawable background; + private Drawable icon; + private int left; + private int top; + private int iconWidth; + private int iconHeight; + private int backWidth; + private int backHeight; + private boolean fullSize; + + public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable, int leftOffset, int topOffset) { + background = backgroundDrawable; + icon = iconDrawable; + left = leftOffset; + top = topOffset; + iconDrawable.setCallback(this); + } + + public void setIconSize(int width, int height) { + iconWidth = width; + iconHeight = height; + } + + public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable) { + background = backgroundDrawable; + icon = iconDrawable; + iconDrawable.setCallback(this); + } + + public void setCustomSize(int width, int height) { + backWidth = width; + backHeight = height; + } + + public Drawable getIcon() { + return icon; + } + + public Drawable getBackground() { + return background; + } + + public void setFullsize(boolean value) { + fullSize = value; + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + icon.setColorFilter(colorFilter); + } + + @Override + public boolean isStateful() { + return icon.isStateful(); + } + + @Override + public boolean setState(int[] stateSet) { + icon.setState(stateSet); + return true; + } + + @Override + public int[] getState() { + return icon.getState(); + } + + @Override + protected boolean onStateChange(int[] state) { + return true; + } + + @Override + public void jumpToCurrentState() { + icon.jumpToCurrentState(); + } + + @Override + public ConstantState getConstantState() { + return icon.getConstantState(); + } + + @Override + public void draw(Canvas canvas) { + background.setBounds(getBounds()); + background.draw(canvas); + int x; + int y; + if (fullSize) { + icon.setBounds(getBounds()); + } else { + if (iconWidth != 0) { + x = getBounds().centerX() - iconWidth / 2 + left; + y = getBounds().centerY() - iconHeight / 2 + top; + icon.setBounds(x, y, x + iconWidth, y + iconHeight); + } else { + x = getBounds().centerX() - icon.getIntrinsicWidth() / 2 + left; + y = getBounds().centerY() - icon.getIntrinsicHeight() / 2 + top; + icon.setBounds(x, y, x + icon.getIntrinsicWidth(), y + icon.getIntrinsicHeight()); + } + } + icon.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + icon.setAlpha(alpha); + background.setAlpha(alpha); + } + + @Override + public int getIntrinsicWidth() { + return backWidth != 0 ? backWidth : background.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return backHeight != 0 ? backHeight : background.getIntrinsicHeight(); + } + + @Override + public int getMinimumWidth() { + return backWidth != 0 ? backWidth : background.getMinimumWidth(); + } + + @Override + public int getMinimumHeight() { + return backHeight != 0 ? backHeight : background.getMinimumHeight(); + } + + @Override + public int getOpacity() { + return icon.getOpacity(); + } + + @Override + public void invalidateDrawable(@NonNull Drawable who) { + invalidateSelf(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + scheduleSelf(what, when); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + unscheduleSelf(what); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ContextProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ContextProgressView.java index 82b32d05045..2f82d144df7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ContextProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ContextProgressView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -15,6 +15,7 @@ import android.view.View; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; public class ContextProgressView extends View { @@ -23,6 +24,7 @@ public class ContextProgressView extends View { private RectF cicleRect = new RectF(); private int radOffset = 0; private long lastUpdateTime; + private int currentColorType; public ContextProgressView(Context context, int colorType) { super(context); @@ -31,14 +33,22 @@ public ContextProgressView(Context context, int colorType) { outerPaint.setStyle(Paint.Style.STROKE); outerPaint.setStrokeWidth(AndroidUtilities.dp(2)); outerPaint.setStrokeCap(Paint.Cap.ROUND); + currentColorType = colorType; + updateColors(); + } - if (colorType == 0) { - innerPaint.setColor(0xffbfdff6); - outerPaint.setColor(0xff2b96e2); - } else { - innerPaint.setColor(0xffbfdff6); - outerPaint.setColor(0xffffffff); + public void updateColors() { + if (currentColorType == 0) { + innerPaint.setColor(Theme.getColor(Theme.key_contextProgressInner1)); + outerPaint.setColor(Theme.getColor(Theme.key_contextProgressOuter1)); + } else if (currentColorType == 1) { + innerPaint.setColor(Theme.getColor(Theme.key_contextProgressInner2)); + outerPaint.setColor(Theme.getColor(Theme.key_contextProgressOuter2)); + } else if (currentColorType == 2) { + innerPaint.setColor(Theme.getColor(Theme.key_contextProgressInner3)); + outerPaint.setColor(Theme.getColor(Theme.key_contextProgressOuter3)); } + invalidate(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CorrectlyMeasuringTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CorrectlyMeasuringTextView.java new file mode 100644 index 00000000000..f9f196f8de3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CorrectlyMeasuringTextView.java @@ -0,0 +1,35 @@ +package org.telegram.ui.Components; + +import android.content.Context; +import android.text.Layout; +import android.util.AttributeSet; +import android.widget.TextView; + +public class CorrectlyMeasuringTextView extends TextView{ + + public CorrectlyMeasuringTextView(Context context) { + super(context); + } + + public CorrectlyMeasuringTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CorrectlyMeasuringTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void onMeasure(int wms, int hms) { + super.onMeasure(wms, hms); + try { + Layout l = getLayout(); + if (l.getLineCount() <= 1) return; + int maxw = 0; + for (int i = l.getLineCount() - 1; i >= 0; --i) { + maxw = Math.max(maxw, Math.round(l.getPaint().measureText(getText(), l.getLineStart(i), l.getLineEnd(i)))); + } + super.onMeasure(Math.min(maxw + getPaddingLeft() + getPaddingRight(), getMeasuredWidth()) | MeasureSpec.EXACTLY, getMeasuredHeight() | MeasureSpec.EXACTLY); + } catch (Exception x) { + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java new file mode 100644 index 00000000000..7172dc6e7c6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropAreaView.java @@ -0,0 +1,698 @@ +package org.telegram.ui.Components.Crop; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.os.Build; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Interpolator; + +import org.telegram.messenger.AndroidUtilities; + +public class CropAreaView extends View { + + private enum Control { + NONE, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, TOP, LEFT, BOTTOM, RIGHT + } + + interface AreaViewListener { + void onAreaChangeBegan(); + void onAreaChange(); + void onAreaChangeEnded(); + } + + private RectF topLeftCorner = new RectF(); + private RectF topRightCorner = new RectF(); + private RectF bottomLeftCorner = new RectF(); + private RectF bottomRightCorner = new RectF(); + private RectF topEdge = new RectF(); + private RectF leftEdge = new RectF(); + private RectF bottomEdge = new RectF(); + private RectF rightEdge = new RectF(); + + private float lockAspectRatio; + + private Control activeControl; + private RectF actualRect = new RectF(); + private RectF tempRect = new RectF(); + private int previousX; + private int previousY; + + private float bottomPadding; + private boolean dimVisibile; + private boolean frameVisible; + + Paint dimPaint; + Paint shadowPaint; + Paint linePaint; + Paint handlePaint; + Paint framePaint; + + AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); + + private float sidePadding; + private float minWidth; + + enum GridType { + NONE, MINOR, MAJOR + } + private GridType previousGridType; + private GridType gridType; + private float gridProgress; + private Animator gridAnimator; + + private AreaViewListener listener; + + private boolean isDragging; + + private Animator animator; + + public CropAreaView(Context context) { + super(context); + + frameVisible = true; + dimVisibile = true; + + sidePadding = AndroidUtilities.dp(16); + minWidth = AndroidUtilities.dp(32); + + gridType = GridType.NONE; + + dimPaint = new Paint(); + dimPaint.setColor(0xcc000000); + + shadowPaint = new Paint(); + shadowPaint.setStyle(Paint.Style.FILL); + shadowPaint.setColor(0x1a000000); + shadowPaint.setStrokeWidth(AndroidUtilities.dp(2)); + + linePaint = new Paint(); + linePaint.setStyle(Paint.Style.FILL); + linePaint.setColor(0xffffffff); + linePaint.setStrokeWidth(AndroidUtilities.dp(1)); + + handlePaint = new Paint(); + handlePaint.setStyle(Paint.Style.FILL); + handlePaint.setColor(Color.WHITE); + + framePaint = new Paint(); + framePaint.setStyle(Paint.Style.FILL); + framePaint.setColor(0xb2ffffff); + } + + public boolean isDragging() { + return isDragging; + } + + public void setDimVisibility(boolean visible) { + dimVisibile = visible; + } + + public void setFrameVisibility(boolean visible) { + frameVisible = visible; + } + + public void setBottomPadding(float value) { + bottomPadding = value; + } + + public Interpolator getInterpolator() { + return interpolator; + } + + public void setListener(AreaViewListener l) { + listener = l; + } + + public void setBitmap(Bitmap bitmap, boolean sideward, boolean freeform) { + if (bitmap == null || bitmap.isRecycled()) { + return; + } + + float aspectRatio; + if (sideward) { + aspectRatio = ((float)bitmap.getHeight()) / ((float)bitmap.getWidth()); + } else { + aspectRatio = ((float)bitmap.getWidth()) / ((float)bitmap.getHeight()); + } + + if (!freeform) { + aspectRatio = 1.0f; + lockAspectRatio = 1.0f; + } + + setActualRect(aspectRatio); + } + + public void setActualRect(float aspectRatio) { + calculateRect(actualRect, aspectRatio); + updateTouchAreas(); + invalidate(); + } + + public void setActualRect(RectF rect) { + actualRect.set(rect); + updateTouchAreas(); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int lineThickness = AndroidUtilities.dp(2); + int handleSize = AndroidUtilities.dp(16); + int handleThickness = AndroidUtilities.dp(3); + + int originX = (int)actualRect.left - lineThickness; + int originY = (int)actualRect.top - lineThickness; + int width = (int)(actualRect.right - actualRect.left) + lineThickness * 2; + int height = (int)(actualRect.bottom - actualRect.top) + lineThickness * 2; + + if (dimVisibile) { + canvas.drawRect(0, 0, getWidth(), originY + lineThickness, dimPaint); + canvas.drawRect(0, originY + lineThickness, originX + lineThickness, originY + height - lineThickness, dimPaint); + canvas.drawRect(originX + width - lineThickness, originY + lineThickness, getWidth(), originY + height - lineThickness, dimPaint); + canvas.drawRect(0, originY + height - lineThickness, getWidth(), getHeight(), dimPaint); + } + + if (!frameVisible) { + return; + } + + int inset = handleThickness - lineThickness; + int gridWidth = width - handleThickness * 2; + int gridHeight = height - handleThickness * 2; + + GridType type = gridType; + if (type == GridType.NONE && gridProgress > 0) + type = previousGridType; + + shadowPaint.setAlpha((int)(gridProgress * 26)); + linePaint.setAlpha((int)(gridProgress * 178)); + + for (int i = 0; i < 3; i++) { + if (type == GridType.MINOR) { + for (int j = 1; j < 4; j++) { + if (i == 2 && j == 3) + continue; + + canvas.drawLine(originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness + gridHeight, shadowPaint); + canvas.drawLine(originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 / 3 * j + gridWidth / 3 * i, originY + handleThickness + gridHeight, linePaint); + + canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, shadowPaint); + canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 / 3 * j + gridHeight / 3 * i, linePaint); + } + } + else if (type == GridType.MAJOR) { + if (i > 0) { + canvas.drawLine(originX + handleThickness + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 * i, originY + handleThickness + gridHeight, shadowPaint); + canvas.drawLine(originX + handleThickness + gridWidth / 3 * i, originY + handleThickness, originX + handleThickness + gridWidth / 3 * i, originY + handleThickness + gridHeight, linePaint); + + canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 * i, shadowPaint); + canvas.drawLine(originX + handleThickness, originY + handleThickness + gridHeight / 3 * i, originX + handleThickness + gridWidth, originY + handleThickness + gridHeight / 3 * i, linePaint); + } + } + } + + canvas.drawRect(originX + inset, originY + inset, originX + width - inset, originY + inset + lineThickness, framePaint); + canvas.drawRect(originX + inset, originY + inset, originX + inset + lineThickness, originY + height - inset, framePaint); + canvas.drawRect(originX + inset, originY + height - inset - lineThickness, originX + width - inset, originY + height - inset, framePaint); + canvas.drawRect(originX + width - inset - lineThickness, originY + inset, originX + width - inset, originY + height - inset, framePaint); + + canvas.drawRect(originX, originY, originX + handleSize, originY + handleThickness, handlePaint); + canvas.drawRect(originX, originY, originX + handleThickness, originY + handleSize, handlePaint); + + canvas.drawRect(originX + width - handleSize, originY, originX + width, originY + handleThickness, handlePaint); + canvas.drawRect(originX + width - handleThickness, originY, originX + width, originY + handleSize, handlePaint); + + canvas.drawRect(originX, originY + height - handleThickness, originX + handleSize, originY + height, handlePaint); + canvas.drawRect(originX, originY + height - handleSize, originX + handleThickness, originY + height, handlePaint); + + canvas.drawRect(originX + width - handleSize, originY + height - handleThickness, originX + width, originY + height, handlePaint); + canvas.drawRect(originX + width - handleThickness, originY + height - handleSize, originX + width, originY + height, handlePaint); + } + + private void updateTouchAreas() { + int touchPadding = AndroidUtilities.dp(16); + + topLeftCorner.set(actualRect.left - touchPadding, actualRect.top - touchPadding, actualRect.left + touchPadding, actualRect.top + touchPadding); + topRightCorner.set(actualRect.right - touchPadding, actualRect.top - touchPadding, actualRect.right + touchPadding, actualRect.top + touchPadding); + bottomLeftCorner.set(actualRect.left - touchPadding, actualRect.bottom - touchPadding, actualRect.left + touchPadding, actualRect.bottom + touchPadding); + bottomRightCorner.set(actualRect.right - touchPadding, actualRect.bottom - touchPadding, actualRect.right + touchPadding, actualRect.bottom + touchPadding); + + topEdge.set(actualRect.left + touchPadding, actualRect.top - touchPadding, actualRect.right - touchPadding, actualRect.top + touchPadding); + leftEdge.set(actualRect.left - touchPadding, actualRect.top + touchPadding, actualRect.left + touchPadding, actualRect.bottom - touchPadding); + rightEdge.set(actualRect.right - touchPadding, actualRect.top + touchPadding, actualRect.right + touchPadding, actualRect.bottom - touchPadding); + bottomEdge.set(actualRect.left + touchPadding, actualRect.bottom - touchPadding, actualRect.right - touchPadding, actualRect.bottom + touchPadding); + } + + public float getLockAspectRatio() { + return lockAspectRatio; + } + + public void setLockedAspectRatio(float aspectRatio) { + lockAspectRatio = aspectRatio; + } + + public void setGridType(GridType type, boolean animated) { + if (gridAnimator != null) { + if (!animated || gridType != type) { + gridAnimator.cancel(); + gridAnimator = null; + } + } + + if (gridType == type) + return; + + previousGridType = gridType; + gridType = type; + + final float targetProgress = type == GridType.NONE ? 0.0f : 1.0f; + if (!animated) { + gridProgress = targetProgress; + invalidate(); + } else { + gridAnimator = ObjectAnimator.ofFloat(this, "gridProgress", gridProgress, targetProgress); + gridAnimator.setDuration(200); + gridAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + gridAnimator = null; + } + }); + if (type == GridType.NONE) + gridAnimator.setStartDelay(200); + gridAnimator.start(); + } + } + + @SuppressWarnings("unused") + private void setGridProgress(float value) { + gridProgress = value; + invalidate(); + } + + @SuppressWarnings("unused") + private float getGridProgress() { + return gridProgress; + } + + public float getAspectRatio() { + return (actualRect.right - actualRect.left) / (actualRect.bottom - actualRect.top); + } + + public void fill(final RectF targetRect, Animator scaleAnimator, boolean animated) { + if (animated) { + if (animator != null) { + animator.cancel(); + animator = null; + } + + AnimatorSet set = new AnimatorSet(); + animator = set; + set.setDuration(300); + + Animator animators[] = new Animator[5]; + animators[0] = ObjectAnimator.ofFloat(this, "cropLeft", targetRect.left); + animators[0].setInterpolator(interpolator); + animators[1] = ObjectAnimator.ofFloat(this, "cropTop", targetRect.top); + animators[1].setInterpolator(interpolator); + animators[2] = ObjectAnimator.ofFloat(this, "cropRight", targetRect.right); + animators[2].setInterpolator(interpolator); + animators[3] = ObjectAnimator.ofFloat(this, "cropBottom", targetRect.bottom); + animators[3].setInterpolator(interpolator); + animators[4] = scaleAnimator; + animators[4].setInterpolator(interpolator); + + set.playTogether(animators); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + setActualRect(targetRect); + animator = null; + } + }); + set.start(); + } else { + setActualRect(targetRect); + } + } + + public void resetAnimator() { + if (animator != null) { + animator.cancel(); + animator = null; + } + } + + @SuppressWarnings("unused") + private void setCropLeft(float value) { + actualRect.left = value; + invalidate(); + } + + @SuppressWarnings("unused") + public float getCropLeft() { + return actualRect.left; + } + + @SuppressWarnings("unused") + private void setCropTop(float value) { + actualRect.top = value; + invalidate(); + } + + @SuppressWarnings("unused") + public float getCropTop() { + return actualRect.top; + } + + @SuppressWarnings("unused") + private void setCropRight(float value) { + actualRect.right = value; + invalidate(); + } + + public float getCropRight() { + return actualRect.right; + } + + @SuppressWarnings("unused") + private void setCropBottom(float value) { + actualRect.bottom = value; + invalidate(); + } + + public float getCropBottom() { + return actualRect.bottom; + } + + public float getCropCenterX() { + return actualRect.left + ((actualRect.right - actualRect.left) / 2.0f); + } + + public float getCropCenterY() { + return actualRect.top + ((actualRect.bottom - actualRect.top) / 2.0f); + } + + public float getCropWidth() { + return actualRect.right - actualRect.left; + } + + public float getCropHeight() { + return actualRect.bottom - actualRect.top; + } + + public RectF getTargetRectToFill() { + RectF rect = new RectF(); + calculateRect(rect, getAspectRatio()); + return rect; + } + + public void calculateRect(RectF rect, float cropAspectRatio) { + float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + float left, top, right, bottom; + float measuredHeight = (float)getMeasuredHeight() - bottomPadding - statusBarHeight; + float aspectRatio = (float)getMeasuredWidth() / measuredHeight; + float minSide = Math.min(getMeasuredWidth(), measuredHeight) - 2 * sidePadding; + float width = getMeasuredWidth() - 2 * sidePadding; + float height = measuredHeight - 2 * sidePadding; + float centerX = getMeasuredWidth() / 2.0f; + float centerY = statusBarHeight + measuredHeight / 2.0f; + + if (Math.abs(1.0f - cropAspectRatio) < 0.0001) { + left = centerX - (minSide / 2.0f); + top = centerY - (minSide / 2.0f); + right = centerX + (minSide / 2.0f); + bottom = centerY + (minSide / 2.0f); + } else if (cropAspectRatio > aspectRatio) { + left = centerX - (width / 2.0f); + top = centerY - ((width / cropAspectRatio) / 2.0f); + right = centerX + (width / 2.0f); + bottom = centerY + ((width / cropAspectRatio) / 2.0f); + } else { + left = centerX - ((height * cropAspectRatio) / 2.0f); + top = centerY - (height / 2.0f); + right = centerX + ((height * cropAspectRatio) / 2.0f); + bottom = centerY + (height / 2.0f); + } + rect.set(left, top, right, bottom); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int x = (int)(event.getX() - ((ViewGroup)getParent()).getX()); + int y = (int)(event.getY() - ((ViewGroup)getParent()).getY()); + + float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + + int action = event.getActionMasked(); + + if (action == MotionEvent.ACTION_DOWN) { + if (this.topLeftCorner.contains(x, y)) { + activeControl = Control.TOP_LEFT; + } else if (this.topRightCorner.contains(x, y)) { + activeControl = Control.TOP_RIGHT; + } else if (this.bottomLeftCorner.contains(x, y)) { + activeControl = Control.BOTTOM_LEFT; + } else if (this.bottomRightCorner.contains(x, y)) { + activeControl = Control.BOTTOM_RIGHT; + } else if (this.leftEdge.contains(x, y)) { + activeControl = Control.LEFT; + } else if (this.topEdge.contains(x, y)) { + activeControl = Control.TOP; + } else if (this.rightEdge.contains(x, y)) { + activeControl = Control.RIGHT; + } else if (this.bottomEdge.contains(x, y)) { + activeControl = Control.BOTTOM; + } else { + activeControl = Control.NONE; + return false; + } + previousX = x; + previousY = y; + setGridType(GridType.MAJOR, false); + + isDragging = true; + + if (listener != null) + listener.onAreaChangeBegan(); + + return true; + } + else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + isDragging = false; + + if (activeControl == Control.NONE) + return false; + + activeControl = Control.NONE; + + if (listener != null) + listener.onAreaChangeEnded(); + + return true; + } + else if (action == MotionEvent.ACTION_MOVE) { + if (activeControl == Control.NONE) + return false; + + tempRect.set(actualRect); + + float translationX = x - previousX; + float translationY = y - previousY; + previousX = x; + previousY = y; + + switch (activeControl) { + case TOP_LEFT: + tempRect.left += translationX; + tempRect.top += translationY; + + if (lockAspectRatio > 0) { + float w = tempRect.width(); + float h = tempRect.height(); + + if (Math.abs(translationX) > Math.abs(translationY)) { + constrainRectByWidth(tempRect, lockAspectRatio); + } else { + constrainRectByHeight(tempRect, lockAspectRatio); + } + + tempRect.left -= tempRect.width() - w; + tempRect.top -= tempRect.width() - h; + } + break; + + case TOP_RIGHT: + tempRect.right += translationX; + tempRect.top += translationY; + + if (lockAspectRatio > 0) { + float h = tempRect.height(); + + if (Math.abs(translationX) > Math.abs(translationY)) { + constrainRectByWidth(tempRect, lockAspectRatio); + } else { + constrainRectByHeight(tempRect, lockAspectRatio); + } + + tempRect.top -= tempRect.width() - h; + } + break; + + case BOTTOM_LEFT: + tempRect.left += translationX; + tempRect.bottom += translationY; + + if (lockAspectRatio > 0) { + float w = tempRect.width(); + + if (Math.abs(translationX) > Math.abs(translationY)) { + constrainRectByWidth(tempRect, lockAspectRatio); + } else { + constrainRectByHeight(tempRect, lockAspectRatio); + } + + tempRect.left -= tempRect.width() - w; + } + break; + + case BOTTOM_RIGHT: + tempRect.right += translationX; + tempRect.bottom += translationY; + + if (lockAspectRatio > 0) { + if (Math.abs(translationX) > Math.abs(translationY)) { + constrainRectByWidth(tempRect, lockAspectRatio); + } else { + constrainRectByHeight(tempRect, lockAspectRatio); + } + } + break; + + case TOP: + tempRect.top += translationY; + + if (lockAspectRatio > 0) { + constrainRectByHeight(tempRect, lockAspectRatio); + } + break; + + case LEFT: + tempRect.left += translationX; + + if (lockAspectRatio > 0) { + constrainRectByWidth(tempRect, lockAspectRatio); + } + break; + + case RIGHT: + tempRect.right += translationX; + + if (lockAspectRatio > 0) { + constrainRectByWidth(tempRect, lockAspectRatio); + } + break; + + case BOTTOM: + tempRect.bottom += translationY; + + if (lockAspectRatio > 0) { + constrainRectByHeight(tempRect, lockAspectRatio); + } + break; + + default: + break; + } + + if (tempRect.left < sidePadding) { + if (lockAspectRatio > 0) { + tempRect.bottom = tempRect.top + (tempRect.right - sidePadding) / lockAspectRatio; + } + tempRect.left = sidePadding; + } else if (tempRect.right > getWidth() - sidePadding) { + tempRect.right = getWidth() - sidePadding; + if (lockAspectRatio > 0) { + tempRect.bottom = tempRect.top + tempRect.width() / lockAspectRatio; + } + } + + float topPadding = statusBarHeight + sidePadding; + float finalBottomPadidng = bottomPadding + sidePadding; + if (tempRect.top < topPadding) { + if (lockAspectRatio > 0) { + tempRect.right = tempRect.left + (tempRect.bottom - topPadding) * lockAspectRatio; + } + tempRect.top = topPadding; + } else if (tempRect.bottom > getHeight() - finalBottomPadidng) { + tempRect.bottom = getHeight() - finalBottomPadidng; + if (lockAspectRatio > 0) { + tempRect.right = tempRect.left + tempRect.height() * lockAspectRatio; + } + } + + if (tempRect.width() < minWidth) { + tempRect.right = tempRect.left + minWidth; + } + if (tempRect.height() < minWidth) { + tempRect.bottom = tempRect.top + minWidth; + } + + if (lockAspectRatio > 0) { + if (lockAspectRatio < 1) { + if (tempRect.width() <= minWidth) { + tempRect.right = tempRect.left + minWidth; + tempRect.bottom = tempRect.top + tempRect.width() / lockAspectRatio; + } + } else { + if (tempRect.height() <= minWidth) { + tempRect.bottom = tempRect.top + minWidth; + tempRect.right = tempRect.left + tempRect.height() * lockAspectRatio; + } + } + } + + setActualRect(tempRect); + + if (listener != null) { + listener.onAreaChange(); + } + + return true; + } + + return false; + } + + private void constrainRectByWidth(RectF rect, float aspectRatio) { + float w = rect.width(); + float h = w / aspectRatio; + + rect.right = rect.left + w; + rect.bottom = rect.top + h; + } + + private void constrainRectByHeight(RectF rect, float aspectRatio) { + float h = rect.height(); + float w = h * aspectRatio; + + rect.right = rect.left + w; + rect.bottom = rect.top + h; + } + + public void getCropRect(RectF rect) { + rect.set(actualRect); + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropGestureDetector.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropGestureDetector.java new file mode 100644 index 00000000000..351246c8a85 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropGestureDetector.java @@ -0,0 +1,220 @@ +package org.telegram.ui.Components.Crop; + +import android.content.Context; +import android.support.v4.view.MotionEventCompat; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import org.telegram.messenger.AndroidUtilities; + +public class CropGestureDetector { + private ScaleGestureDetector mDetector; + private CropGestureListener mListener; + float mLastTouchX; + float mLastTouchY; + final float mTouchSlop; + final float mMinimumVelocity; + private VelocityTracker mVelocityTracker; + private boolean mIsDragging; + + private static final int INVALID_POINTER_ID = -1; + private int mActivePointerId; + private int mActivePointerIndex; + + private boolean started; + + public interface CropGestureListener { + void onDrag(float dx, float dy); + void onFling(float startX, float startY, float velocityX, float velocityY); + void onScale(float scaleFactor, float focusX, float focusY); + } + + public CropGestureDetector(Context context) { + final ViewConfiguration configuration = ViewConfiguration + .get(context); + mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); + mTouchSlop = AndroidUtilities.dp(1); + + this.mActivePointerId = INVALID_POINTER_ID; + this.mActivePointerIndex = 0; + + ScaleGestureDetector.OnScaleGestureListener mScaleListener = new ScaleGestureDetector.OnScaleGestureListener() { + + @Override + public boolean onScale(ScaleGestureDetector detector) { + float scaleFactor = detector.getScaleFactor(); + + if (Float.isNaN(scaleFactor) || Float.isInfinite(scaleFactor)) + return false; + + mListener.onScale(scaleFactor, + detector.getFocusX(), detector.getFocusY()); + return true; + } + + @Override + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + @Override + public void onScaleEnd(ScaleGestureDetector detector) { + // NO-OP + } + }; + mDetector = new ScaleGestureDetector(context, mScaleListener); + } + + float getActiveX(MotionEvent ev) { + try { + return ev.getX(this.mActivePointerIndex); + } catch (Exception e) { + return ev.getX(); + } + } + + float getActiveY(MotionEvent ev) { + try { + return ev.getY(this.mActivePointerIndex); + } catch (Exception e) { + return ev.getY(); + } + } + + public void setOnGestureListener(CropGestureListener listener) { + this.mListener = listener; + } + + public boolean isScaling() { + return mDetector.isInProgress(); + } + + public boolean isDragging() { + return mIsDragging; + } + + + public boolean onTouchEvent(MotionEvent ev) { + mDetector.onTouchEvent(ev); + + int i = 0; + switch (ev.getAction() & MotionEventCompat.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + this.mActivePointerId = ev.getPointerId(0); + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + this.mActivePointerId = INVALID_POINTER_ID; + break; + case MotionEvent.ACTION_POINTER_UP: + int pointerIndex = (MotionEventCompat.ACTION_POINTER_INDEX_MASK & ev.getAction()) >> 8; + if (ev.getPointerId(pointerIndex) == this.mActivePointerId) { + int newPointerIndex; + if (pointerIndex == 0) { + newPointerIndex = 1; + } else { + newPointerIndex = 0; + } + this.mActivePointerId = ev.getPointerId(newPointerIndex); + this.mLastTouchX = ev.getX(newPointerIndex); + this.mLastTouchY = ev.getY(newPointerIndex); + break; + } + break; + } + if (this.mActivePointerId != INVALID_POINTER_ID) { + i = this.mActivePointerId; + } + this.mActivePointerIndex = ev.findPointerIndex(i); + + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: { + if (!started) { + mVelocityTracker = VelocityTracker.obtain(); + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } + + mLastTouchX = getActiveX(ev); + mLastTouchY = getActiveY(ev); + mIsDragging = false; + + started = true; + return true; + } + + final float x = getActiveX(ev); + final float y = getActiveY(ev); + final float dx = x - mLastTouchX, dy = y - mLastTouchY; + + if (!mIsDragging) { + // Use Pythagoras to see if drag length is larger than + // touch slop + mIsDragging = (float) Math.sqrt((dx * dx) + (dy * dy)) >= mTouchSlop; + } + + if (mIsDragging) { + mListener.onDrag(dx, dy); + mLastTouchX = x; + mLastTouchY = y; + + if (null != mVelocityTracker) { + mVelocityTracker.addMovement(ev); + } + } + break; + } + + case MotionEvent.ACTION_CANCEL: { + // Recycle Velocity Tracker + if (null != mVelocityTracker) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + started = false; + mIsDragging = false; + break; + } + + case MotionEvent.ACTION_UP: { + if (mIsDragging) { + if (null != mVelocityTracker) { + mLastTouchX = getActiveX(ev); + mLastTouchY = getActiveY(ev); + + // Compute velocity within the last 1000ms + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000); + + final float vX = mVelocityTracker.getXVelocity(), vY = mVelocityTracker + .getYVelocity(); + + // If the velocity is greater than minVelocity, call + // listener + if (Math.max(Math.abs(vX), Math.abs(vY)) >= mMinimumVelocity) { + mListener.onFling(mLastTouchX, mLastTouchY, -vX, + -vY); + } + } + + mIsDragging = false; + } + + // Recycle Velocity Tracker + if (null != mVelocityTracker) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + + started = false; + break; + } + } + + return true; + } + +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropRotationWheel.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropRotationWheel.java new file mode 100644 index 00000000000..f0a56371e42 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropRotationWheel.java @@ -0,0 +1,221 @@ +package org.telegram.ui.Components.Crop; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.LayoutHelper; + +public class CropRotationWheel extends FrameLayout { + + public interface RotationWheelListener { + void onStart(); + void onChange(float angle); + void onEnd(float angle); + + void aspectRatioPressed(); + void rotate90Pressed(); + } + + private static final int MAX_ANGLE = 45; + private static final int DELTA_ANGLE = 5; + + private Paint whitePaint; + private Paint bluePaint; + + private ImageView aspectRatioButton; + private ImageView rotation90Button; + private TextView degreesLabel; + + protected float rotation; + private RectF tempRect; + private float prevX; + + private RotationWheelListener rotationListener; + + public CropRotationWheel(Context context) { + super(context); + + tempRect = new RectF(0, 0, 0, 0); + + whitePaint = new Paint(); + whitePaint.setStyle(Paint.Style.FILL); + whitePaint.setColor(Color.WHITE); + whitePaint.setAlpha(255); + whitePaint.setAntiAlias(true); + + bluePaint = new Paint(); + bluePaint.setStyle(Paint.Style.FILL); + bluePaint.setColor(0xff51bdf3); + bluePaint.setAlpha(255); + bluePaint.setAntiAlias(true); + + aspectRatioButton = new ImageView(context); + aspectRatioButton.setImageResource(R.drawable.tool_cropfix); + aspectRatioButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + aspectRatioButton.setScaleType(ImageView.ScaleType.CENTER); + aspectRatioButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (rotationListener != null) + rotationListener.aspectRatioPressed(); + } + }); + addView(aspectRatioButton, LayoutHelper.createFrame(70, 64, Gravity.LEFT | Gravity.CENTER_VERTICAL)); + + rotation90Button = new ImageView(context); + rotation90Button.setImageResource(R.drawable.tool_rotate); + rotation90Button.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + rotation90Button.setScaleType(ImageView.ScaleType.CENTER); + rotation90Button.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (rotationListener != null) + rotationListener.rotate90Pressed(); + } + }); + addView(rotation90Button, LayoutHelper.createFrame(70, 64, Gravity.RIGHT | Gravity.CENTER_VERTICAL)); + + degreesLabel = new TextView(context); + degreesLabel.setTextColor(Color.WHITE); + addView(degreesLabel, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); + + setWillNotDraw(false); + + setRotation(0.0f, false); + } + + public void setFreeform(boolean freeform) { + aspectRatioButton.setVisibility(freeform ? VISIBLE : GONE); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + super.onMeasure(MeasureSpec.makeMeasureSpec(Math.min(width, AndroidUtilities.dp(400)), MeasureSpec.EXACTLY), heightMeasureSpec); + } + + public void reset() { + setRotation(0.0f, false); + } + + public void setListener(RotationWheelListener listener) { + rotationListener = listener; + } + + public void setRotation(float rotation, boolean animated) { + this.rotation = rotation; + float value = this.rotation; + if (Math.abs(value) < 0.1 - 0.001) + value = Math.abs(value); + degreesLabel.setText(String.format("%.1fº", value)); + + invalidate(); + } + + public void setAspectLock(boolean enabled) { + aspectRatioButton.setImageResource(enabled ? R.drawable.tool_cropfix_active : R.drawable.tool_cropfix); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + int action = ev.getActionMasked(); + float x = ev.getX(); + + if (action == MotionEvent.ACTION_DOWN) { + prevX = x; + + if (rotationListener != null) + rotationListener.onStart(); + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (rotationListener != null) + rotationListener.onEnd(this.rotation); + } else if (action == MotionEvent.ACTION_MOVE) { + float delta = prevX - x; + + float newAngle = this.rotation + (float)(delta / AndroidUtilities.density / Math.PI / 1.65f); + newAngle = Math.max(-MAX_ANGLE, Math.min(MAX_ANGLE, newAngle)); + + if (Math.abs(newAngle - this.rotation) > 0.001) { + if (Math.abs(newAngle) < 0.05) + newAngle = 0; + + setRotation(newAngle, false); + + if (rotationListener != null) + rotationListener.onChange(this.rotation); + + prevX = x; + } + } + + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int width = getWidth(); + int height = getHeight(); + + float angle = -rotation * 2; + float delta = angle % DELTA_ANGLE; + int segments = (int)Math.floor(angle / DELTA_ANGLE); + + for (int i = 0; i < 16; i++) { + Paint paint = whitePaint; + int a = i; + if (a < segments || (a == 0 && delta < 0)) + paint = bluePaint; + + drawLine(canvas, a, delta, width, height, (a == segments || a == 0 && segments == - 1), paint); + + if (i != 0) { + a = -i; + paint = a > segments ? bluePaint : whitePaint; + drawLine(canvas, a, delta, width, height, a == segments + 1, paint); + } + } + + bluePaint.setAlpha(255); + + tempRect.left = (width - AndroidUtilities.dp(2.5f)) / 2; + tempRect.top = (height - AndroidUtilities.dp(22)) / 2; + tempRect.right = (width + AndroidUtilities.dp(2.5f)) / 2; + tempRect.bottom = (height + AndroidUtilities.dp(22)) / 2; + canvas.drawRoundRect(tempRect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), bluePaint); + } + + protected void drawLine(Canvas canvas, int i, float delta, int width, int height, boolean center, Paint paint) { + int radius = (int)(width / 2.0f - AndroidUtilities.dp(70)); + + float angle = 90 - (i * DELTA_ANGLE + delta); + int val = (int)(radius * Math.cos(Math.toRadians(angle))); + int x = width / 2 + val; + + float f = Math.abs(val) / (float)radius; + int alpha = Math.min(255, Math.max(0, (int)((1.0f - f * f) * 255))); + + if (center) + paint = bluePaint; + + paint.setAlpha(alpha); + + int w = center ? 4 : 2; + int h = center ? AndroidUtilities.dp(16) : AndroidUtilities.dp(12); + + canvas.drawRect(x - w / 2, (height - h) / 2, x + w / 2, (height + h) / 2, paint); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropState.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropState.java new file mode 100644 index 00000000000..45be6d10d01 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropState.java @@ -0,0 +1,99 @@ +package org.telegram.ui.Components.Crop; + +import android.graphics.Bitmap; +import android.graphics.Matrix; + +public class CropState { + private float width; + private float height; + + private float x; + private float y; + private float scale; + private float minimumScale; + private float rotation; + private Matrix matrix; + + private float[] values; + + public CropState(Bitmap bitmap) { + width = bitmap.getWidth(); + height = bitmap.getHeight(); + + x = 0.0f; + y = 0.0f; + scale = 1.0f; + rotation = 0.0f; + matrix = new Matrix(); + + values = new float[9]; + } + + private void updateValues() { + matrix.getValues(values); + } + + public float getWidth() { + return width; + } + + public float getHeight() { + return height; + } + + public void translate(float x, float y) { + this.x += x; + this.y += y; + matrix.postTranslate(x, y); + } + + public float getX() { + updateValues(); + return values[matrix.MTRANS_X]; + } + + public float getY() { + updateValues(); + return values[matrix.MTRANS_Y]; + } + + public void scale(float s, float pivotX, float pivotY) { + scale *= s; + matrix.postScale(s, s, pivotX, pivotY); + } + + public float getScale() { + return scale; + } + + public void rotate(float angle, float pivotX, float pivotY) { + rotation += angle; + matrix.postRotate(angle, pivotX, pivotY); + } + + public float getRotation() { + return rotation; + } + + public void reset(CropAreaView areaView) { + matrix.reset(); + + x = 0.0f; + y = 0.0f; + rotation = 0.0f; + minimumScale = areaView.getCropWidth() / width; + scale = minimumScale; + + matrix.postScale(scale, scale); + } + + public void getConcatMatrix(Matrix toMatrix) { + toMatrix.postConcat(matrix); + } + + public Matrix getMatrix() { + Matrix m = new Matrix(); + m.set(matrix); + return m; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java new file mode 100644 index 00000000000..26fe5c2deae --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Crop/CropView.java @@ -0,0 +1,840 @@ +package org.telegram.ui.Components.Crop; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RectF; +import android.os.Build; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.AlertDialog; + +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + +public class CropView extends FrameLayout implements CropAreaView.AreaViewListener, CropGestureDetector.CropGestureListener { + private static final float EPSILON = 0.00001f; + private static final int RESULT_SIDE = 1280; + private static final float MAX_SCALE = 30.0f; + + private View backView; + + private CropAreaView areaView; + private ImageView imageView; + private Matrix presentationMatrix; + + private RectF previousAreaRect; + private RectF initialAreaRect; + private float rotationStartScale; + + private CropRectangle tempRect; + private Matrix tempMatrix; + + private Bitmap bitmap; + private boolean freeform; + private float bottomPadding; + + private boolean animating; + private CropGestureDetector detector; + + private boolean hasAspectRatioDialog; + + private class CropState { + private float width; + private float height; + + private float x; + private float y; + private float scale; + private float minimumScale; + private float baseRotation; + private float orientation; + private float rotation; + private Matrix matrix; + + private CropState(Bitmap bitmap, int bRotation) { + width = bitmap.getWidth(); + height = bitmap.getHeight(); + + x = 0.0f; + y = 0.0f; + scale = 1.0f; + baseRotation = bRotation; + rotation = 0.0f; + matrix = new Matrix(); + } + + private boolean hasChanges() { + return Math.abs(x) > EPSILON || Math.abs(y) > EPSILON || Math.abs(scale - minimumScale) > EPSILON + || Math.abs(rotation) > EPSILON || Math.abs(orientation) > EPSILON; + } + + private float getWidth() { + return width; + } + + private float getHeight() { + return height; + } + + private float getOrientedWidth() { + return (orientation + baseRotation) % 180 != 0 ? height : width; + } + + private float getOrientedHeight() { + return (orientation + baseRotation) % 180 != 0 ? width : height; + } + + private void translate(float x, float y) { + this.x += x; + this.y += y; + matrix.postTranslate(x, y); + } + + private float getX() { + return x; + } + + private float getY() { + return y; + } + + private void scale(float s, float pivotX, float pivotY) { + scale *= s; + matrix.postScale(s, s, pivotX, pivotY); + } + + private float getScale() { + return scale; + } + + private float getMinimumScale() { + return minimumScale; + } + + private void rotate(float angle, float pivotX, float pivotY) { + rotation += angle; + matrix.postRotate(angle, pivotX, pivotY); + } + + private float getRotation() { + return rotation; + } + + private float getOrientation() { + return orientation + baseRotation; + } + + private float getBaseRotation() { + return baseRotation; + } + + private void reset(CropAreaView areaView, float orient, boolean freeform) { + matrix.reset(); + + x = 0.0f; + y = 0.0f; + rotation = 0.0f; + orientation = orient; + float w = (orientation + baseRotation) % 180 != 0 ? height : width; + float h = (orientation + baseRotation) % 180 != 0 ? width : height; + if (freeform) { + minimumScale = areaView.getCropWidth() / w; + } else { + float wScale = areaView.getCropWidth() / w; + float hScale = areaView.getCropHeight() / h; + minimumScale = Math.max(wScale, hScale); + } + scale = minimumScale; + + matrix.postScale(scale, scale); + } + + private void getConcatMatrix(Matrix toMatrix) { + toMatrix.postConcat(matrix); + } + + private Matrix getMatrix() { + Matrix m = new Matrix(); + m.set(matrix); + return m; + } + } + + private CropState state; + + public interface CropViewListener { + void onChange(boolean reset); + + void onAspectLock(boolean enabled); + } + + private CropViewListener listener; + + public CropView(Context context) { + super(context); + + previousAreaRect = new RectF(); + initialAreaRect = new RectF(); + presentationMatrix = new Matrix(); + tempRect = new CropRectangle(); + tempMatrix = new Matrix(); + animating = false; + + backView = new View(context); + backView.setBackgroundColor(0xff000000); + backView.setVisibility(INVISIBLE); + addView(backView); + + imageView = new ImageView(context); + imageView.setDrawingCacheEnabled(true); + imageView.setScaleType(ImageView.ScaleType.MATRIX); + addView(imageView); + + detector = new CropGestureDetector(context); + detector.setOnGestureListener(this); + + areaView = new CropAreaView(context); + areaView.setListener(this); + addView(areaView); + } + + public boolean isReady() { + return !detector.isScaling() && !detector.isDragging() && !areaView.isDragging(); + } + + public void setListener(CropViewListener l) { + listener = l; + } + + public void setBottomPadding(float value) { + bottomPadding = value; + areaView.setBottomPadding(value); + } + + public void setBitmap(Bitmap b, int rotation, boolean fform) { + bitmap = b; + freeform = fform; + state = new CropState(bitmap, rotation); + + backView.setVisibility(INVISIBLE); + imageView.setVisibility(INVISIBLE); + if (fform) + areaView.setDimVisibility(false); + imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + reset(); + imageView.getViewTreeObserver().removeOnPreDrawListener(this); + return false; + } + }); + imageView.setImageBitmap(bitmap); + } + + public void willShow() { + areaView.setFrameVisibility(true); + areaView.setDimVisibility(true); + areaView.invalidate(); + } + + public void show() { + backView.setVisibility(VISIBLE); + imageView.setVisibility(VISIBLE); + areaView.setDimVisibility(true); + areaView.setFrameVisibility(true); + areaView.invalidate(); + } + + public void hide() { + backView.setVisibility(INVISIBLE); + imageView.setVisibility(INVISIBLE); + areaView.setDimVisibility(false); + areaView.setFrameVisibility(false); + areaView.invalidate(); + } + + public void reset() { + areaView.resetAnimator(); + + areaView.setBitmap(bitmap, state.getBaseRotation() % 180 != 0, freeform); + areaView.setLockedAspectRatio(freeform ? 0.0f : 1.0f); + state.reset(areaView, 0, freeform); + areaView.getCropRect(initialAreaRect); + updateMatrix(); + + resetRotationStartScale(); + + if (listener != null) { + listener.onChange(true); + listener.onAspectLock(false); + } + } + + public void updateMatrix() { + presentationMatrix.reset(); + presentationMatrix.postTranslate(-state.getWidth() / 2, -state.getHeight() / 2); + presentationMatrix.postRotate(state.getOrientation()); + state.getConcatMatrix(presentationMatrix); + presentationMatrix.postTranslate(areaView.getCropCenterX(), areaView.getCropCenterY()); + + imageView.setImageMatrix(presentationMatrix); + } + + private void fillAreaView(RectF targetRect, boolean allowZoomOut) { + final float[] currentScale = new float[]{1.0f}; + float scale = Math.max(targetRect.width() / areaView.getCropWidth(), + targetRect.height() / areaView.getCropHeight()); + + float newScale = state.getScale() * scale; + boolean ensureFit = false; + if (newScale > MAX_SCALE) { + scale = MAX_SCALE / state.getScale(); + ensureFit = true; + } + float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + + final float x = (targetRect.centerX() - imageView.getWidth() / 2) / areaView.getCropWidth() * state.getOrientedWidth(); + final float y = (targetRect.centerY() - (imageView.getHeight() - bottomPadding + statusBarHeight) / 2) / areaView.getCropHeight() * state.getOrientedHeight(); + final float targetScale = scale; + + final boolean animEnsureFit = ensureFit; + + ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + float deltaScale = (1.0f + ((targetScale - 1.0f) * value)) / currentScale[0]; + currentScale[0] *= deltaScale; + state.scale(deltaScale, x, y); + updateMatrix(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animEnsureFit) + fitContentInBounds(false, false, true); + } + }); + areaView.fill(targetRect, animator, true); + initialAreaRect.set(targetRect); + } + + private float fitScale(RectF contentRect, float scale, float ratio) { + float scaledW = contentRect.width() * ratio; + float scaledH = contentRect.height() * ratio; + + float scaledX = (contentRect.width() - scaledW) / 2.0f; + float scaledY = (contentRect.height() - scaledH) / 2.0f; + + contentRect.set(contentRect.left + scaledX, contentRect.top + scaledY, + contentRect.left + scaledX + scaledW, contentRect.top + scaledY + scaledH); + + return scale * ratio; + } + + private void fitTranslation(RectF contentRect, RectF boundsRect, PointF translation, float radians) { + float frameLeft = boundsRect.left; + float frameTop = boundsRect.top; + float frameRight = boundsRect.right; + float frameBottom = boundsRect.bottom; + + if (contentRect.left > frameLeft) { + frameRight += contentRect.left - frameLeft; + frameLeft = contentRect.left; + } + if (contentRect.top > frameTop) { + frameBottom += contentRect.top - frameTop; + frameTop = contentRect.top; + } + if (contentRect.right < frameRight) { + frameLeft += contentRect.right - frameRight; + } + if (contentRect.bottom < frameBottom) { + frameTop += contentRect.bottom - frameBottom; + } + + float deltaX = boundsRect.centerX() - (frameLeft + boundsRect.width() / 2.0f); + float deltaY = boundsRect.centerY() - (frameTop + boundsRect.height() / 2.0f); + + float xCompX = (float) (Math.sin(Math.PI / 2 - radians) * deltaX); + float xCompY = (float) (Math.cos(Math.PI / 2 - radians) * deltaX); + + float yCompX = (float) (Math.cos(Math.PI / 2 + radians) * deltaY); + float yCompY = (float) (Math.sin(Math.PI / 2 + radians) * deltaY); + + translation.set(translation.x + xCompX + yCompX, translation.y + xCompY + yCompY); + } + + public RectF calculateBoundingBox(float w, float h, float rotation) { + RectF result = new RectF(0, 0, w, h); + Matrix m = new Matrix(); + m.postRotate(rotation, w / 2.0f, h / 2.0f); + m.mapRect(result); + return result; + } + + public float scaleWidthToMaxSize(RectF sizeRect, RectF maxSizeRect) { + float w = maxSizeRect.width(); + float h = (float) Math.floor(w * sizeRect.height() / sizeRect.width()); + if (h > maxSizeRect.height()) { + h = maxSizeRect.height(); + w = (float) Math.floor(h * sizeRect.width() / sizeRect.height()); + } + return w; + } + + private class CropRectangle { + float[] coords = new float[8]; + + CropRectangle() { + } + + void setRect(RectF rect) { + coords[0] = rect.left; + coords[1] = rect.top; + coords[2] = rect.right; + coords[3] = rect.top; + coords[4] = rect.right; + coords[5] = rect.bottom; + coords[6] = rect.left; + coords[7] = rect.bottom; + } + + void applyMatrix(Matrix m) { + m.mapPoints(coords); + } + + void getRect(RectF rect) { + rect.set(coords[0], coords[1], coords[2], coords[7]); + } + } + + private void fitContentInBounds(boolean allowScale, boolean maximize, boolean animated) { + fitContentInBounds(allowScale, maximize, animated, false); + } + + private void fitContentInBounds(final boolean allowScale, final boolean maximize, final boolean animated, final boolean fast) { + float boundsW = areaView.getCropWidth(); + float boundsH = areaView.getCropHeight(); + float contentW = state.getOrientedWidth(); + float contentH = state.getOrientedHeight(); + float rotation = state.getRotation(); + float radians = (float) Math.toRadians(rotation); + + RectF boundsRect = calculateBoundingBox(boundsW, boundsH, rotation); + RectF contentRect = new RectF(0.0f, 0.0f, contentW, contentH); + + float initialX = (boundsW - contentW) / 2.0f; + float initialY = (boundsH - contentH) / 2.0f; + + float scale = state.getScale(); + + tempRect.setRect(contentRect); + + Matrix matrix = state.getMatrix(); + matrix.preTranslate(initialX / scale, initialY / scale); + + tempMatrix.reset(); + tempMatrix.setTranslate(contentRect.centerX(), contentRect.centerY()); + tempMatrix.setConcat(tempMatrix, matrix); + tempMatrix.preTranslate(-contentRect.centerX(), -contentRect.centerY()); + tempRect.applyMatrix(tempMatrix); + + tempMatrix.reset(); + tempMatrix.preRotate(-rotation, contentW / 2.0f, contentH / 2.0f); + tempRect.applyMatrix(tempMatrix); + tempRect.getRect(contentRect); + + PointF targetTranslation = new PointF(state.getX(), state.getY()); + float targetScale = scale; + + if (!contentRect.contains(boundsRect)) { + if (allowScale && (boundsRect.width() > contentRect.width() || boundsRect.height() > contentRect.height())) { + float ratio = boundsRect.width() / scaleWidthToMaxSize(boundsRect, contentRect); + targetScale = fitScale(contentRect, scale, ratio); + } + + fitTranslation(contentRect, boundsRect, targetTranslation, radians); + } else if (maximize && rotationStartScale > 0) { + float ratio = boundsRect.width() / scaleWidthToMaxSize(boundsRect, contentRect); + float newScale = state.getScale() * ratio; + if (newScale < rotationStartScale) + ratio = 1.0f; + targetScale = fitScale(contentRect, scale, ratio); + + fitTranslation(contentRect, boundsRect, targetTranslation, radians); + } + + float dx = targetTranslation.x - state.getX(); + float dy = targetTranslation.y - state.getY(); + + if (animated) { + final float animScale = targetScale / scale; + final float animDX = dx; + final float animDY = dy; + + if (Math.abs(animScale - 1.0f) < EPSILON + && Math.abs(animDX) < EPSILON && Math.abs(animDY) < EPSILON) { + return; + } + + animating = true; + + final float[] currentValues = new float[]{1.0f, 0.0f, 0.0f}; + ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + + float deltaX = animDX * value - currentValues[1]; + currentValues[1] += deltaX; + float deltaY = animDY * value - currentValues[2]; + currentValues[2] += deltaY; + state.translate(deltaX * currentValues[0], deltaY * currentValues[0]); + + float deltaScale = (1.0f + ((animScale - 1.0f) * value)) / currentValues[0]; + currentValues[0] *= deltaScale; + state.scale(deltaScale, 0, 0); + + updateMatrix(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animating = false; + + if (!fast) + fitContentInBounds(allowScale, maximize, animated, true); + } + }); + animator.setInterpolator(areaView.getInterpolator()); + animator.setDuration(fast ? 100 : 200); + animator.start(); + } else { + state.translate(dx, dy); + state.scale(targetScale / scale, 0, 0); + updateMatrix(); + } + } + + public void rotate90Degrees() { + areaView.resetAnimator(); + + resetRotationStartScale(); + + float orientation = (state.getOrientation() - state.getBaseRotation() - 90.0f) % 360; + + boolean fform = freeform; + if (freeform && areaView.getLockAspectRatio() > 0) { + areaView.setLockedAspectRatio(1.0f / areaView.getLockAspectRatio()); + areaView.setActualRect(areaView.getLockAspectRatio()); + fform = false; + } else { + areaView.setBitmap(bitmap, (orientation + state.getBaseRotation()) % 180 != 0, freeform); + } + + state.reset(areaView, orientation, fform); + updateMatrix(); + + if (listener != null) + listener.onChange(orientation == 0 && areaView.getLockAspectRatio() == 0); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (animating) { + return true; + } + boolean result = false; + if (areaView.onTouchEvent(event)) + return true; + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + onScrollChangeBegan(); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + onScrollChangeEnded(); + break; + } + try { + result = detector.onTouchEvent(event); + } catch (Exception ignore) { + } + return result; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return true; + } + + @Override + public void onAreaChangeBegan() { + areaView.getCropRect(previousAreaRect); + resetRotationStartScale(); + + if (listener != null) { + listener.onChange(false); + } + } + + @Override + public void onAreaChange() { + areaView.setGridType(CropAreaView.GridType.MAJOR, false); + + float x = previousAreaRect.centerX() - areaView.getCropCenterX(); + float y = previousAreaRect.centerY() - areaView.getCropCenterY(); + state.translate(x, y); + updateMatrix(); + + areaView.getCropRect(previousAreaRect); + + fitContentInBounds(true, false, false); + } + + @Override + public void onAreaChangeEnded() { + areaView.setGridType(CropAreaView.GridType.NONE, true); + fillAreaView(areaView.getTargetRectToFill(), false); + } + + public void onDrag(float dx, float dy) { + if (animating) { + return; + } + + state.translate(dx, dy); + updateMatrix(); + } + + public void onFling(float startX, float startY, float velocityX, float velocityY) { + } + + public void onScrollChangeBegan() { + if (animating) { + return; + } + + areaView.setGridType(CropAreaView.GridType.MAJOR, true); + resetRotationStartScale(); + + if (listener != null) { + listener.onChange(false); + } + } + + public void onScrollChangeEnded() { + areaView.setGridType(CropAreaView.GridType.NONE, true); + fitContentInBounds(true, false, true); + } + + public void onScale(float scale, float x, float y) { + if (animating) { + return; + } + + float newScale = state.getScale() * scale; + if (newScale > MAX_SCALE) + scale = MAX_SCALE / state.getScale(); + + float statusBarHeight = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + + float pivotX = (x - imageView.getWidth() / 2) / areaView.getCropWidth() * state.getOrientedWidth(); + float pivotY = (y - (imageView.getHeight() - bottomPadding - statusBarHeight) / 2) / areaView.getCropHeight() * state.getOrientedHeight(); + + state.scale(scale, pivotX, pivotY); + updateMatrix(); + } + + public void onRotationBegan() { + areaView.setGridType(CropAreaView.GridType.MINOR, false); + if (rotationStartScale < 0.00001f) { + rotationStartScale = state.getScale(); + } + } + + public void onRotationEnded() { + areaView.setGridType(CropAreaView.GridType.NONE, true); + } + + private void resetRotationStartScale() { + rotationStartScale = 0.0f; + } + + public void setRotation(float angle) { + float deltaAngle = angle - state.getRotation(); + state.rotate(deltaAngle, 0, 0); + fitContentInBounds(true, true, false); + } + + public Bitmap getResult() { + if (!state.hasChanges() && state.getBaseRotation() < EPSILON && freeform) { + return bitmap; + } + + RectF cropRect = new RectF(); + areaView.getCropRect(cropRect); + RectF sizeRect = new RectF(0, 0, RESULT_SIDE, RESULT_SIDE); + + float w = scaleWidthToMaxSize(cropRect, sizeRect); + int width = (int) Math.ceil(w); + int height = (int) (Math.ceil(width / areaView.getAspectRatio())); + + Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + Matrix matrix = new Matrix(); + matrix.postTranslate(-state.getWidth() / 2, -state.getHeight() / 2); + + matrix.postRotate(state.getOrientation()); + state.getConcatMatrix(matrix); + + float scale = width / areaView.getCropWidth(); + matrix.postScale(scale, scale); + matrix.postTranslate(width / 2, height / 2); + + new Canvas(resultBitmap).drawBitmap(bitmap, matrix, new Paint(FILTER_BITMAP_FLAG)); + return resultBitmap; + } + + private void setLockedAspectRatio(float aspectRatio) { + areaView.setLockedAspectRatio(aspectRatio); + RectF targetRect = new RectF(); + areaView.calculateRect(targetRect, aspectRatio); + fillAreaView(targetRect, true); + + if (listener != null) { + listener.onChange(false); + listener.onAspectLock(true); + } + } + + public void showAspectRatioDialog() { + if (areaView.getLockAspectRatio() > 0) { + areaView.setLockedAspectRatio(0); + + if (listener != null) { + listener.onAspectLock(false); + } + + return; + } + + if (hasAspectRatioDialog) { + return; + } + + hasAspectRatioDialog = true; + + String[] actions = new String[8]; + + final Integer[][] ratios = new Integer[][]{ + new Integer[]{3, 2}, + new Integer[]{5, 3}, + new Integer[]{4, 3}, + new Integer[]{5, 4}, + new Integer[]{7, 5}, + new Integer[]{16, 9} + }; + + actions[0] = LocaleController.getString("CropOriginal", R.string.CropOriginal); + actions[1] = LocaleController.getString("CropSquare", R.string.CropSquare); + + int i = 2; + for (Integer[] ratioPair : ratios) { + if (areaView.getAspectRatio() > 1.0f) { + actions[i] = String.format("%d:%d", ratioPair[0], ratioPair[1]); + } else { + actions[i] = String.format("%d:%d", ratioPair[1], ratioPair[0]); + } + i++; + } + + AlertDialog dialog = new AlertDialog.Builder(getContext()) + .setItems(actions, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + hasAspectRatioDialog = false; + switch (which) { + case 0: { + float w = state.getBaseRotation() % 180 != 0 ? state.getHeight() : state.getWidth(); + float h = state.getBaseRotation() % 180 != 0 ? state.getWidth() : state.getHeight(); + setLockedAspectRatio(w / h); + } + break; + + case 1: { + setLockedAspectRatio(1.0f); + } + break; + + default: { + Integer[] ratioPair = ratios[which - 2]; + + if (areaView.getAspectRatio() > 1.0f) { + setLockedAspectRatio(ratioPair[0] / (float) ratioPair[1]); + } else { + setLockedAspectRatio(ratioPair[1] / (float) ratioPair[0]); + } + } + break; + } + } + }) + .create(); + dialog.setCanceledOnTouchOutside(true); + dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + hasAspectRatioDialog = false; + } + }); + dialog.show(); + } + + public void updateLayout() { + float w = areaView.getCropWidth(); + areaView.calculateRect(initialAreaRect, state.getWidth() / state.getHeight()); + areaView.setActualRect(areaView.getAspectRatio()); + areaView.getCropRect(previousAreaRect); + + float ratio = areaView.getCropWidth() / w; + state.scale(ratio, 0, 0); + updateMatrix(); + } + + public float getCropLeft() { + return areaView.getCropLeft(); + } + + public float getCropTop() { + return areaView.getCropTop(); + } + + public float getCropWidth() { + return areaView.getCropWidth(); + } + + public float getCropHeight() { + return areaView.getCropHeight(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CubicBezierInterpolator.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CubicBezierInterpolator.java new file mode 100644 index 00000000000..4fb365a53be --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CubicBezierInterpolator.java @@ -0,0 +1,73 @@ +package org.telegram.ui.Components; + +import android.graphics.PointF; +import android.view.animation.Interpolator; + +public class CubicBezierInterpolator implements Interpolator { + + public static final CubicBezierInterpolator DEFAULT=new CubicBezierInterpolator(0.25, 0.1, 0.25, 1); + public static final CubicBezierInterpolator EASE_OUT=new CubicBezierInterpolator(0, 0, .58, 1); + public static final CubicBezierInterpolator EASE_IN=new CubicBezierInterpolator(.42, 0, 1, 1); + public static final CubicBezierInterpolator EASE_BOTH=new CubicBezierInterpolator(.42, 0, .58, 1); + + protected PointF start; + protected PointF end; + protected PointF a = new PointF(); + protected PointF b = new PointF(); + protected PointF c = new PointF(); + + public CubicBezierInterpolator(PointF start, PointF end) throws IllegalArgumentException { + if (start.x < 0 || start.x > 1) { + throw new IllegalArgumentException("startX value must be in the range [0, 1]"); + } + if (end.x < 0 || end.x > 1) { + throw new IllegalArgumentException("endX value must be in the range [0, 1]"); + } + this.start = start; + this.end = end; + } + + public CubicBezierInterpolator(float startX, float startY, float endX, float endY) { + this(new PointF(startX, startY), new PointF(endX, endY)); + } + + public CubicBezierInterpolator(double startX, double startY, double endX, double endY) { + this((float) startX, (float) startY, (float) endX, (float) endY); + } + + @Override + public float getInterpolation(float time) { + return getBezierCoordinateY(getXForTime(time)); + } + + protected float getBezierCoordinateY(float time) { + c.y = 3 * start.y; + b.y = 3 * (end.y - start.y) - c.y; + a.y = 1 - c.y - b.y; + return time * (c.y + time * (b.y + time * a.y)); + } + + protected float getXForTime(float time) { + float x = time; + float z; + for (int i = 1; i < 14; i++) { + z = getBezierCoordinateX(x) - time; + if (Math.abs(z) < 1e-3) { + break; + } + x -= z / getXDerivate(x); + } + return x; + } + + private float getXDerivate(float t) { + return c.x + t * (2 * b.x + 3 * a.x * t); + } + + private float getBezierCoordinateX(float time) { + c.x = 3 * start.x; + b.x = 3 * (end.x - start.x) - c.x; + a.x = 1 - c.x - b.x; + return time * (c.x + time * (b.x + time * a.x)); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java new file mode 100644 index 00000000000..8744a2f72ca --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextBoldCursor.java @@ -0,0 +1,233 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.Rect; +import android.os.SystemClock; +import android.text.Layout; +import android.text.StaticLayout; +import android.view.Gravity; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.R; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class EditTextBoldCursor extends EditText { + + private Object editor; + private static Field mEditor; + private static Field mShowCursorField; + private static Field mCursorDrawableField; + private static Field mScrollYField; + private static Method getVerticalOffsetMethod; + private static Field mCursorDrawableResField; + private Drawable[] mCursorDrawable; + private GradientDrawable gradientDrawable; + private int cursorSize; + private int ignoreTopCount; + private int ignoreBottomCount; + private int scrollY; + private float lineSpacingExtra; + private Rect rect = new Rect(); + private StaticLayout hintLayout; + private int hintColor; + private boolean hintVisible = true; + private float hintAlpha = 1.0f; + private long lastUpdateTime; + private boolean allowDrawCursor = true; + + public EditTextBoldCursor(Context context) { + super(context); + + if (mCursorDrawableField == null) { + try { + mScrollYField = View.class.getDeclaredField("mScrollY"); + mScrollYField.setAccessible(true); + mCursorDrawableResField = TextView.class.getDeclaredField("mCursorDrawableRes"); + mCursorDrawableResField.setAccessible(true); + mEditor = TextView.class.getDeclaredField("mEditor"); + mEditor.setAccessible(true); + Class editorClass = Class.forName("android.widget.Editor"); + mShowCursorField = editorClass.getDeclaredField("mShowCursor"); + mShowCursorField.setAccessible(true); + mCursorDrawableField = editorClass.getDeclaredField("mCursorDrawable"); + mCursorDrawableField.setAccessible(true); + getVerticalOffsetMethod = TextView.class.getDeclaredMethod("getVerticalOffset", boolean.class); + getVerticalOffsetMethod.setAccessible(true); + } catch (Throwable e) { + // + } + } + try { + gradientDrawable = new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[] {0xff54a1db, 0xff54a1db}); + editor = mEditor.get(this); + mCursorDrawable = (Drawable[]) mCursorDrawableField.get(editor); + mCursorDrawableResField.set(this, R.drawable.field_carret_empty); + } catch (Exception e) { + FileLog.e(e); + } + cursorSize = AndroidUtilities.dp(24); + } + + public void setAllowDrawCursor(boolean value) { + allowDrawCursor = value; + } + + public void setCursorColor(int color) { + gradientDrawable.setColor(color); + invalidate(); + } + + public void setCursorSize(int value) { + cursorSize = value; + } + + public void setHintVisible(boolean value) { + if (hintVisible == value) { + return; + } + lastUpdateTime = System.currentTimeMillis(); + hintVisible = value; + invalidate(); + } + + public void setHintColor(int value) { + hintColor = value; + invalidate(); + } + + public void setHintText(String value) { + hintLayout = new StaticLayout(value, getPaint(), AndroidUtilities.dp(1000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + } + + @Override + public void setLineSpacing(float add, float mult) { + super.setLineSpacing(add, mult); + lineSpacingExtra = add; + } + + @Override + public int getExtendedPaddingTop() { + if (ignoreTopCount != 0) { + ignoreTopCount--; + return 0; + } + return super.getExtendedPaddingTop(); + } + + @Override + public int getExtendedPaddingBottom() { + if (ignoreBottomCount != 0) { + ignoreBottomCount--; + return scrollY != Integer.MAX_VALUE ? -scrollY : 0; + } + return super.getExtendedPaddingBottom(); + } + + @Override + protected void onDraw(Canvas canvas) { + int topPadding = getExtendedPaddingTop(); + scrollY = Integer.MAX_VALUE; + try { + scrollY = mScrollYField.getInt(this); + mScrollYField.set(this, 0); + } catch (Exception e) { + // + } + ignoreTopCount = 1; + ignoreBottomCount = 1; + canvas.save(); + canvas.translate(0, topPadding); + try { + super.onDraw(canvas); + } catch (Exception e) { + // + } + if (scrollY != Integer.MAX_VALUE) { + try { + mScrollYField.set(this, scrollY); + } catch (Exception e) { + // + } + } + canvas.restore(); + if (length() == 0 && hintLayout != null && (hintVisible || hintAlpha != 0)) { + if (hintVisible && hintAlpha != 1.0f || !hintVisible && hintAlpha != 0.0f) { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + if (dt < 0 || dt > 17) { + dt = 17; + } + lastUpdateTime = newTime; + if (hintVisible) { + hintAlpha += dt / 150.0f; + if (hintAlpha > 1.0f) { + hintAlpha = 1.0f; + } + } else { + hintAlpha -= dt / 150.0f; + if (hintAlpha < 0.0f) { + hintAlpha = 0.0f; + } + } + invalidate(); + } + int oldColor = getPaint().getColor(); + getPaint().setColor(hintColor); + getPaint().setAlpha((int) (255 * hintAlpha)); + canvas.save(); + canvas.translate(0.0f, (getMeasuredHeight() - hintLayout.getHeight()) / 2.0f); + hintLayout.draw(canvas); + getPaint().setColor(oldColor); + canvas.restore(); + } + try { + if (allowDrawCursor && mShowCursorField != null && mCursorDrawable != null && mCursorDrawable[0] != null) { + long mShowCursor = mShowCursorField.getLong(editor); + boolean showCursor = (SystemClock.uptimeMillis() - mShowCursor) % (2 * 500) < 500; + if (showCursor) { + canvas.save(); + int voffsetCursor = 0; + if ((getGravity() & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + voffsetCursor = (int) getVerticalOffsetMethod.invoke(this, true); + } + canvas.translate(getPaddingLeft(), getExtendedPaddingTop() + voffsetCursor); + Layout layout = getLayout(); + int line = layout.getLineForOffset(getSelectionStart()); + int lineCount = layout.getLineCount(); + Rect bounds = mCursorDrawable[0].getBounds(); + rect.left = bounds.left; + rect.right = bounds.left + AndroidUtilities.dp(2); + rect.bottom = bounds.bottom; + rect.top = bounds.top; + if (lineSpacingExtra != 0 && line < lineCount - 1) { + rect.bottom -= lineSpacingExtra; + } + rect.top = rect.centerY() - cursorSize / 2; + rect.bottom = rect.top + cursorSize; + gradientDrawable.setBounds(rect); + gradientDrawable.draw(canvas); + canvas.restore(); + } + } + } catch (Throwable e) { + //ignore + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java new file mode 100644 index 00000000000..ee3598a3354 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java @@ -0,0 +1,222 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Build; +import android.text.Editable; +import android.text.Layout; +import android.text.Spanned; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.R; + +public class EditTextCaption extends EditTextBoldCursor { + + private String caption; + private StaticLayout captionLayout; + private int userNameLength; + private int xOffset; + private int yOffset; + private int triesCount = 0; + private boolean copyPasteShowed; + private int hintColor; + + public EditTextCaption(Context context) { + super(context); + } + + public void setCaption(String value) { + if ((caption == null || caption.length() == 0) && (value == null || value.length() == 0) || caption != null && value != null && caption.equals(value)) { + return; + } + caption = value; + if (caption != null) { + caption = caption.replace('\n', ' '); + } + requestLayout(); + } + + private void makeSelectedBold() { + applyTextStyleToSelection(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"))); + } + + private void makeSelectedItalic() { + applyTextStyleToSelection(new TypefaceSpan(AndroidUtilities.getTypeface("fonts/ritalic.ttf"))); + } + + private void makeSelectedRegular() { + applyTextStyleToSelection(null); + } + + private void applyTextStyleToSelection(TypefaceSpan span) { + int start = getSelectionStart(); + int end = getSelectionEnd(); + Editable editable = getText(); + + URLSpanUserMention spansMentions[] = editable.getSpans(start, end, URLSpanUserMention.class); + if (spansMentions != null && spansMentions.length > 0) { + return; + } + + TypefaceSpan spans[] = editable.getSpans(start, end, TypefaceSpan.class); + if (spans != null && spans.length > 0) { + for (int a = 0; a < spans.length; a++) { + TypefaceSpan oldSpan = spans[a]; + int spanStart = editable.getSpanStart(oldSpan); + int spanEnd = editable.getSpanEnd(oldSpan); + editable.removeSpan(oldSpan); + if (spanStart < start) { + editable.setSpan(new TypefaceSpan(oldSpan.getTypeface()), spanStart, start, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (spanEnd > end) { + editable.setSpan(new TypefaceSpan(oldSpan.getTypeface()), end, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + if (span != null) { + editable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + if (Build.VERSION.SDK_INT < 23 && !hasWindowFocus && copyPasteShowed) { + return; + } + super.onWindowFocusChanged(hasWindowFocus); + } + + private ActionMode.Callback overrideCallback(final ActionMode.Callback callback) { + return new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + copyPasteShowed = true; + return callback.onCreateActionMode(mode, menu); + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return callback.onPrepareActionMode(mode, menu); + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getItemId() == R.id.menu_regular) { + makeSelectedRegular(); + mode.finish(); + return true; + } else if (item.getItemId() == R.id.menu_bold) { + makeSelectedBold(); + mode.finish(); + return true; + } else if (item.getItemId() == R.id.menu_italic) { + makeSelectedItalic(); + mode.finish(); + return true; + } + return callback.onActionItemClicked(mode, item); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + copyPasteShowed = false; + callback.onDestroyActionMode(mode); + } + }; + } + + @Override + public ActionMode startActionMode(final ActionMode.Callback callback, int type) { + return super.startActionMode(overrideCallback(callback), type); + } + + @Override + public ActionMode startActionMode(final ActionMode.Callback callback) { + return super.startActionMode(overrideCallback(callback)); + } + + @SuppressLint("DrawAllocation") + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + try { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } catch (Exception e) { + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(51)); + FileLog.e(e); + } + + captionLayout = null; + + if (caption != null && caption.length() > 0) { + CharSequence text = getText(); + if (text.length() > 1 && text.charAt(0) == '@') { + int index = TextUtils.indexOf(text, ' '); + if (index != -1) { + TextPaint paint = getPaint(); + CharSequence str = text.subSequence(0, index + 1); + int size = (int) Math.ceil(paint.measureText(text, 0, index + 1)); + int width = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + userNameLength = str.length(); + CharSequence captionFinal = TextUtils.ellipsize(caption, paint, width - size, TextUtils.TruncateAt.END); + xOffset = size; + try { + captionLayout = new StaticLayout(captionFinal, getPaint(), width - size, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (captionLayout.getLineCount() > 0) { + xOffset += -captionLayout.getLineLeft(0); + } + yOffset = (getMeasuredHeight() - captionLayout.getLineBottom(0)) / 2 + AndroidUtilities.dp(0.5f); + } catch (Exception e) { + FileLog.e(e); + } + } + } + } + } + + public String getCaption() { + return caption; + } + + @Override + public void setHintColor(int value) { + super.setHintColor(value); + hintColor = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + try { + if (captionLayout != null && userNameLength == length()) { + Paint paint = getPaint(); + int oldColor = getPaint().getColor(); + paint.setColor(hintColor); + canvas.save(); + canvas.translate(xOffset, yOffset); + captionLayout.draw(canvas); + canvas.restore(); + paint.setColor(oldColor); + } + } catch (Exception e) { + FileLog.e(e); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java new file mode 100644 index 00000000000..8983e5d84e5 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmbedBottomSheet.java @@ -0,0 +1,815 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Canvas; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.OrientationEventListener; +import android.view.Surface; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.webkit.CookieManager; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BringAppForegroundService; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.browser.Browser; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; + +import java.util.HashMap; + +@TargetApi(16) +public class EmbedBottomSheet extends BottomSheet { + + private WebView webView; + private WebPlayerView videoView; + private View customView; + private FrameLayout fullscreenVideoContainer; + private WebChromeClient.CustomViewCallback customViewCallback; + private RadialProgressView progressBar; + private Activity parentActivity; + private PipVideoView pipVideoView; + + private int[] position = new int[2]; + + private OrientationEventListener orientationEventListener; + private int lastOrientation = -1; + + private int width; + private int height; + private String openUrl; + private boolean hasDescription; + private String embedUrl; + private int prevOrientation = -2; + private boolean fullscreenedByButton; + private boolean wasInLandscape; + private boolean animationInProgress; + + private int waitingForDraw; + + private OnShowListener onShowListener = new OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + if (pipVideoView != null && videoView.isInline()) { + videoView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + videoView.getViewTreeObserver().removeOnPreDrawListener(this); + /*AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() {*/ + + /*} + }, 100);*/ + return true; + } + }); + } + } + }; + + @SuppressLint("StaticFieldLeak") + private static EmbedBottomSheet instance; + + public static void show(Context context, String title, String description, String originalUrl, final String url, int w, int h) { + if (instance != null) { + instance.destroy(); + } + new EmbedBottomSheet(context, title, description, originalUrl, url, w, h).show(); + } + + @SuppressLint("SetJavaScriptEnabled") + private EmbedBottomSheet(Context context, String title, String description, String originalUrl, final String url, int w, int h) { + super(context, false); + fullWidth = true; + setApplyTopPadding(false); + setApplyBottomPadding(false); + + if (context instanceof Activity) { + parentActivity = (Activity) context; + } + + embedUrl = url; + hasDescription = description != null && description.length() > 0; + openUrl = originalUrl; + width = w; + height = h; + if (width == 0 || height == 0) { + width = AndroidUtilities.displaySize.x; + height = AndroidUtilities.displaySize.y / 2; + } + + fullscreenVideoContainer = new FrameLayout(context); + fullscreenVideoContainer.setBackgroundColor(0xff000000); + if (Build.VERSION.SDK_INT >= 21) { + fullscreenVideoContainer.setFitsSystemWindows(true); + } + + container.addView(fullscreenVideoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + fullscreenVideoContainer.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + + FrameLayout containerLayout = new FrameLayout(context) { + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + try { + if (webView.getParent() != null) { + removeView(webView); + webView.stopLoading(); + webView.loadUrl("about:blank"); + webView.destroy(); + } + + if (!videoView.isInline()) { + if (instance == EmbedBottomSheet.this) { + instance = null; + } + + videoView.destroy(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int parentWidth = MeasureSpec.getSize(widthMeasureSpec); + float scale = width / (float) parentWidth; + int h = (int) Math.min(height / scale, AndroidUtilities.displaySize.y / 2); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h + AndroidUtilities.dp(48 + 36 + (hasDescription ? 22 : 0)) + 1, MeasureSpec.EXACTLY)); + } + }; + containerLayout.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + setCustomView(containerLayout); + + webView = new WebView(context); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setDomStorageEnabled(true); + if (Build.VERSION.SDK_INT >= 17) { + webView.getSettings().setMediaPlaybackRequiresUserGesture(false); + } + + if (Build.VERSION.SDK_INT >= 21) { + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptThirdPartyCookies(webView, true); + } + + webView.setWebChromeClient(new WebChromeClient() { + + @Override + public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { + onShowCustomView(view, callback); + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) { + if (customView != null) { + callback.onCustomViewHidden(); + return; + } + customView = view; + getSheetContainer().setVisibility(View.INVISIBLE); + fullscreenVideoContainer.setVisibility(View.VISIBLE); + fullscreenVideoContainer.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + customViewCallback = callback; + } + + @Override + public void onHideCustomView() { + super.onHideCustomView(); + if (customView == null) { + return; + } + + getSheetContainer().setVisibility(View.VISIBLE); + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + fullscreenVideoContainer.removeView(customView); + + if (customViewCallback != null && !customViewCallback.getClass().getName().contains(".chromium.")) { + customViewCallback.onCustomViewHidden(); + } + customView = null; + } + }); + + webView.setWebViewClient(new WebViewClient() { + @Override + public void onLoadResource(WebView view, String url) { + super.onLoadResource(view, url); + } + + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + progressBar.setVisibility(View.INVISIBLE); + } + }); + + containerLayout.addView(webView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0))); + + videoView = new WebPlayerView(context, true, false, new WebPlayerView.WebPlayerViewDelegate() { + @Override + public void onInitFailed() { + webView.setVisibility(View.VISIBLE); + videoView.setVisibility(View.INVISIBLE); + videoView.getControlsView().setVisibility(View.INVISIBLE); + videoView.getTextureView().setVisibility(View.INVISIBLE); + if (videoView.getTextureImageView() != null) { + videoView.getTextureImageView().setVisibility(View.INVISIBLE); + } + videoView.loadVideo(null, null, null, false); + HashMap args = new HashMap<>(); + args.put("Referer", "http://youtube.com"); + try { + webView.loadUrl(embedUrl, args); + } catch (Exception e) { + FileLog.e(e); + } + } + + @Override + public TextureView onSwitchToFullscreen(View controlsView, boolean fullscreen, float aspectRatio, int rotation, boolean byButton) { + if (fullscreen) { + fullscreenVideoContainer.setVisibility(View.VISIBLE); + fullscreenVideoContainer.setAlpha(1.0f); + fullscreenVideoContainer.addView(videoView.getAspectRatioView()); + wasInLandscape = false; + + fullscreenedByButton = byButton; + if (parentActivity != null) { + try { + prevOrientation = parentActivity.getRequestedOrientation(); + if (byButton) { + WindowManager manager = (WindowManager) parentActivity.getSystemService(Activity.WINDOW_SERVICE); + int displayRotation = manager.getDefaultDisplay().getRotation(); + if (displayRotation == Surface.ROTATION_270) { + parentActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + } else { + parentActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + } + containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN); + } catch (Exception e) { + FileLog.e(e); + } + } + } else { + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + fullscreenedByButton = false; + + if (parentActivity != null) { + try { + containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + parentActivity.setRequestedOrientation(prevOrientation); + } catch (Exception e) { + FileLog.e(e); + } + } + } + return null; + } + + @Override + public void onVideoSizeChanged(float aspectRatio, int rotation) { + + } + + @Override + public void onInlineSurfaceTextureReady() { + if (videoView.isInline()) { + dismissInternal(); + } + } + + @Override + public void prepareToSwitchInlineMode(boolean inline, final Runnable switchInlineModeRunnable, float aspectRatio, boolean animated) { + if (inline) { + if (parentActivity != null) { + try { + containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + if (prevOrientation != -2) { + parentActivity.setRequestedOrientation(prevOrientation); + } + } catch (Exception e) { + FileLog.e(e); + } + } + + if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { + containerView.setTranslationY(containerView.getMeasuredHeight() + AndroidUtilities.dp(10)); + backDrawable.setAlpha(0); + } + + setOnShowListener(null); + if (animated) { + TextureView textureView = videoView.getTextureView(); + View controlsView = videoView.getControlsView(); + ImageView textureImageView = videoView.getTextureImageView(); + + Rect rect = PipVideoView.getPipRect(aspectRatio); + + float scale = rect.width / textureView.getWidth(); + if (Build.VERSION.SDK_INT >= 21) { + rect.y += AndroidUtilities.statusBarHeight; + } + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(textureImageView, "scaleX", scale), + ObjectAnimator.ofFloat(textureImageView, "scaleY", scale), + ObjectAnimator.ofFloat(textureImageView, "translationX", rect.x), + ObjectAnimator.ofFloat(textureImageView, "translationY", rect.y), + ObjectAnimator.ofFloat(textureView, "scaleX", scale), + ObjectAnimator.ofFloat(textureView, "scaleY", scale), + ObjectAnimator.ofFloat(textureView, "translationX", rect.x), + ObjectAnimator.ofFloat(textureView, "translationY", rect.y), + ObjectAnimator.ofFloat(containerView, "translationY", containerView.getMeasuredHeight() + AndroidUtilities.dp(10)), + ObjectAnimator.ofInt(backDrawable, "alpha", 0), + ObjectAnimator.ofFloat(fullscreenVideoContainer, "alpha", 0), + ObjectAnimator.ofFloat(controlsView, "alpha", 0) + ); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(250); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { + fullscreenVideoContainer.setAlpha(1.0f); + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + } + switchInlineModeRunnable.run(); + } + }); + animatorSet.start(); + } else { + if (fullscreenVideoContainer.getVisibility() == View.VISIBLE) { + fullscreenVideoContainer.setAlpha(1.0f); + fullscreenVideoContainer.setVisibility(View.INVISIBLE); + } + switchInlineModeRunnable.run(); + dismissInternal(); + } + } else { + if (ApplicationLoader.mainInterfacePaused) { + parentActivity.startService(new Intent(ApplicationLoader.applicationContext, BringAppForegroundService.class)); + } + + if (animated) { + setOnShowListener(onShowListener); + Rect rect = PipVideoView.getPipRect(aspectRatio); + + TextureView textureView = videoView.getTextureView(); + ImageView textureImageView = videoView.getTextureImageView(); + float scale = rect.width / textureView.getLayoutParams().width; + if (Build.VERSION.SDK_INT >= 21) { + rect.y += AndroidUtilities.statusBarHeight; + } + textureImageView.setScaleX(scale); + textureImageView.setScaleY(scale); + textureImageView.setTranslationX(rect.x); + textureImageView.setTranslationY(rect.y); + textureView.setScaleX(scale); + textureView.setScaleY(scale); + textureView.setTranslationX(rect.x); + textureView.setTranslationY(rect.y); + } else { + pipVideoView.close(); + pipVideoView = null; + } + setShowWithoutAnimation(true); + show(); + if (animated) { + waitingForDraw = 4; + backDrawable.setAlpha(1); + containerView.setTranslationY(containerView.getMeasuredHeight() + AndroidUtilities.dp(10)); + } + } + } + + @Override + public TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated) { + if (inline) { + controlsView.setTranslationY(0); + pipVideoView = new PipVideoView(); + return pipVideoView.show(parentActivity, EmbedBottomSheet.this, controlsView, aspectRatio, rotation); + } + + if (animated) { + animationInProgress = true; + + View view = videoView.getAspectRatioView(); + view.getLocationInWindow(position); + position[0] -= getLeftInset(); + position[1] -= containerView.getTranslationY(); + + TextureView textureView = videoView.getTextureView(); + ImageView textureImageView = videoView.getTextureImageView(); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(textureImageView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(textureImageView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(textureImageView, "translationX", position[0]), + ObjectAnimator.ofFloat(textureImageView, "translationY", position[1]), + ObjectAnimator.ofFloat(textureView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(textureView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(textureView, "translationX", position[0]), + ObjectAnimator.ofFloat(textureView, "translationY", position[1]), + ObjectAnimator.ofFloat(containerView, "translationY", 0), + ObjectAnimator.ofInt(backDrawable, "alpha", 51) + ); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(250); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + animationInProgress = false; + } + }); + animatorSet.start(); + } else { + containerView.setTranslationY(0); + } + return null; + } + + @Override + public void onSharePressed() { + + } + + @Override + public void onPlayStateChanged(WebPlayerView playerView, boolean playing) { + if (playing) { + try { + parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } else { + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + @Override + public boolean checkInlinePermissons() { + if (parentActivity == null) { + return false; + } + if (Build.VERSION.SDK_INT < 23 || Settings.canDrawOverlays(parentActivity)) { + return true; + } else { + new AlertDialog.Builder(parentActivity).setTitle(LocaleController.getString("AppName", R.string.AppName)) + .setMessage(LocaleController.getString("PermissionDrawAboveOtherApps", R.string.PermissionDrawAboveOtherApps)) + .setPositiveButton(LocaleController.getString("PermissionOpenSettings", R.string.PermissionOpenSettings), new DialogInterface.OnClickListener() { + @TargetApi(Build.VERSION_CODES.M) + @Override + public void onClick(DialogInterface dialog, int which) { + if (parentActivity != null) { + parentActivity.startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + parentActivity.getPackageName()))); + } + } + }).show(); + } + return false; + } + + @Override + public ViewGroup getTextureViewContainer() { + return container; + } + }); + videoView.setVisibility(View.INVISIBLE); + containerLayout.addView(videoView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0) - 10)); + + progressBar = new RadialProgressView(context); + progressBar.setVisibility(View.INVISIBLE); + containerLayout.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 0, 0, (48 + 36 + (hasDescription ? 22 : 0)) / 2)); + + TextView textView; + + if (hasDescription) { + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + textView.setText(description); + textView.setSingleLine(true); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + containerLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48 + 9 + 20)); + } + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextGray)); + textView.setText(title); + textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + containerLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48 + 9)); + + View lineView = new View(context); + lineView.setBackgroundColor(Theme.getColor(Theme.key_dialogGrayLine)); + containerLayout.addView(lineView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + ((FrameLayout.LayoutParams) lineView.getLayoutParams()).bottomMargin = AndroidUtilities.dp(48); + + FrameLayout frameLayout = new FrameLayout(context); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); + containerLayout.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); + textView.setGravity(Gravity.CENTER); + textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + textView.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + frameLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); + textView.setGravity(Gravity.CENTER); + textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + textView.setText(LocaleController.getString("Copy", R.string.Copy).toUpperCase()); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", openUrl); + clipboard.setPrimaryClip(clip); + } catch (Exception e) { + FileLog.e(e); + } + Toast.makeText(getContext(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); + dismiss(); + } + }); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue4)); + textView.setGravity(Gravity.CENTER); + textView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); + textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + textView.setText(LocaleController.getString("OpenInBrowser", R.string.OpenInBrowser).toUpperCase()); + textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Browser.openUrl(parentActivity, openUrl); + dismiss(); + } + }); + + setDelegate(new BottomSheet.BottomSheetDelegate() { + @Override + public void onOpenAnimationEnd() { + boolean handled = videoView.loadVideo(embedUrl, null, openUrl, true); + if (handled) { + progressBar.setVisibility(View.INVISIBLE); + webView.setVisibility(View.INVISIBLE); + videoView.setVisibility(View.VISIBLE); + } else { + progressBar.setVisibility(View.VISIBLE); + webView.setVisibility(View.VISIBLE); + videoView.setVisibility(View.INVISIBLE); + videoView.getControlsView().setVisibility(View.INVISIBLE); + videoView.getTextureView().setVisibility(View.INVISIBLE); + if (videoView.getTextureImageView() != null) { + videoView.getTextureImageView().setVisibility(View.INVISIBLE); + } + videoView.loadVideo(null, null, null, false); + HashMap args = new HashMap<>(); + args.put("Referer", "http://youtube.com"); + try { + webView.loadUrl(embedUrl, args); + } catch (Exception e) { + FileLog.e(e); + } + } + } + + @Override + public boolean canDismiss() { + if (videoView.isInFullscreen()) { + videoView.exitFullscreen(); + return false; + } + try { + parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + return true; + } + }); + + orientationEventListener = new OrientationEventListener(ApplicationLoader.applicationContext) { + @Override + public void onOrientationChanged(int orientation) { + if (orientationEventListener == null || videoView.getVisibility() != View.VISIBLE) { + return; + } + if (parentActivity != null && videoView.isInFullscreen() && fullscreenedByButton) { + if (orientation >= 270 - 30 && orientation <= 270 + 30) { + wasInLandscape = true; + } else if (wasInLandscape && (orientation >= 330 || orientation <= 30)) { + parentActivity.setRequestedOrientation(prevOrientation); + fullscreenedByButton = false; + wasInLandscape = false; + } + } + } + }; + + if (orientationEventListener.canDetectOrientation()) { + orientationEventListener.enable(); + } else { + orientationEventListener.disable(); + orientationEventListener = null; + } + instance = this; + } + + @Override + protected boolean canDismissWithSwipe() { + return videoView.getVisibility() != View.VISIBLE || !videoView.isInFullscreen(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (videoView.getVisibility() == View.VISIBLE && videoView.isInitied() && !videoView.isInline()) { + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + if (!videoView.isInFullscreen()) { + videoView.enterFullscreen(); + } + } else { + if (videoView.isInFullscreen()) { + videoView.exitFullscreen(); + } + } + } + if (pipVideoView != null) { + pipVideoView.onConfigurationChanged(); + } + } + + public void destroy() { + if (pipVideoView != null) { + pipVideoView.close(); + pipVideoView = null; + } + if (videoView != null) { + videoView.destroy(); + } + instance = null; + dismissInternal(); + } + + public static EmbedBottomSheet getInstance() { + return instance; + } + + public void updateTextureViewPosition() { + View view = videoView.getAspectRatioView(); + view.getLocationInWindow(position); + position[0] -= getLeftInset(); + + if (!videoView.isInline() && !animationInProgress) { + TextureView textureView = videoView.getTextureView(); + textureView.setTranslationX(position[0]); + textureView.setTranslationY(position[1]); + View textureImageView = videoView.getTextureImageView(); + if (textureImageView != null) { + textureImageView.setTranslationX(position[0]); + textureImageView.setTranslationY(position[1]); + } + } + View controlsView = videoView.getControlsView(); + if (controlsView.getParent() == container) { + controlsView.setTranslationY(position[1]); + } else { + controlsView.setTranslationY(0); + } + } + + @Override + protected void onContainerTranslationYChanged(float translationY) { + updateTextureViewPosition(); + } + + @Override + protected boolean onCustomMeasure(View view, int width, int height) { + if (view == videoView.getControlsView()) { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + layoutParams.width = videoView.getMeasuredWidth(); + layoutParams.height = videoView.getAspectRatioView().getMeasuredHeight() + (videoView.isInFullscreen() ? 0 : AndroidUtilities.dp(10)); + } + return false; + } + + @Override + protected boolean onCustomLayout(View view, int left, int top, int right, int bottom) { + if (view == videoView.getControlsView()) { + updateTextureViewPosition(); + } + return false; + } + + public void pause() { + if (videoView != null && videoView.isInitied()) { + videoView.pause(); + } + } + + @Override + public void onContainerDraw(Canvas canvas) { + if (waitingForDraw != 0) { + waitingForDraw--; + if (waitingForDraw == 0) { + videoView.updateTextureImageView(); + pipVideoView.close(); + pipVideoView = null; + } else { + container.invalidate(); + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java index adecc9ac552..4d3d8024e5d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -18,6 +17,8 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; @@ -53,6 +54,8 @@ import org.telegram.messenger.support.widget.GridLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.ContextLinkCell; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.FeaturedStickerSetInfoCell; @@ -76,11 +79,44 @@ public interface Listener { void onGifTab(boolean opened); void onStickersTab(boolean opened); void onClearEmojiRecent(); - void onShowStickerSet(TLRPC.StickerSetCovered stickerSet); + void onShowStickerSet(TLRPC.StickerSet stickerSet, TLRPC.InputStickerSet inputStickerSet); void onStickerSetAdd(TLRPC.StickerSetCovered stickerSet); void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet); } + private StickerPreviewViewer.StickerPreviewViewerDelegate stickerPreviewViewerDelegate = new StickerPreviewViewer.StickerPreviewViewerDelegate() { + @Override + public void sentSticker(TLRPC.Document sticker) { + listener.onStickerSelected(sticker); + } + + @Override + public void openSet(TLRPC.InputStickerSet set) { + if (set == null) { + return; + } + TLRPC.TL_messages_stickerSet stickerSet; + if (set.id != 0) { + stickerSet = StickersQuery.getStickerSetById(set.id); + } else if (set.short_name != null) { + stickerSet = StickersQuery.getStickerSetByName(set.short_name); + } else { + stickerSet = null; + } + int position; + if (stickerSet != null) { + position = stickersGridAdapter.getPositionForPack(stickerSet); + } else { + position = -1; + } + if (position != -1) { + stickersLayoutManager.scrollToPositionWithOffset(position, 0); + } else { + listener.onShowStickerSet(null, set); + } + } + }; + private static final Field superListenerField; static { Field f = null; @@ -101,9 +137,13 @@ public void onScrollChanged() { private static String addColorToCode(String code, String color) { String end = null; - if (code.endsWith("\u200D\u2640") || code.endsWith("\u200D\u2642")) { + int lenght = code.length(); + if (lenght > 2 && code.charAt(code.length() - 2) == '\u200D') { end = code.substring(code.length() - 2); code = code.substring(0, code.length() - 2); + } else if (lenght > 3 && code.charAt(code.length() - 3) == '\u200D') { + end = code.substring(code.length() - 3); + code = code.substring(0, code.length() - 3); } code += color; if (end != null) { @@ -181,7 +221,7 @@ public boolean onLongClick(View view) { return false; } }); - setBackgroundResource(R.drawable.list_selector); + setBackgroundDrawable(Theme.getSelectorDrawable(false)); setScaleType(ImageView.ScaleType.CENTER); } @@ -378,7 +418,7 @@ public void showAsDropDown(View anchor, int xoff, int yoff) { super.showAsDropDown(anchor, xoff, yoff); registerListener(anchor); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -512,16 +552,9 @@ protected void onDraw(Canvas canvas) { private ArrayList recentGifs = new ArrayList<>(); private ArrayList recentStickers = new ArrayList<>(); - private Drawable dotDrawable; + private Paint dotPaint; - private int[] icons = { - R.drawable.ic_emoji_recent, - R.drawable.ic_emoji_smile, - R.drawable.ic_emoji_flower, - R.drawable.ic_emoji_bell, - R.drawable.ic_emoji_car, - R.drawable.ic_emoji_symbol, - R.drawable.ic_smiles2_stickers}; + private Drawable[] icons; private Listener listener; private ViewPager pager; @@ -547,6 +580,7 @@ protected void onDraw(Canvas canvas) { private HashMap installingStickerSets = new HashMap<>(); private HashMap removingStickerSets = new HashMap<>(); private boolean trendingLoaded; + private int featuredStickersHash; private int currentPage; @@ -578,9 +612,22 @@ protected void onDraw(Canvas canvas) { public EmojiView(boolean needStickers, boolean needGif, final Context context) { super(context); + Drawable stickersDrawable = context.getResources().getDrawable(R.drawable.ic_smiles2_stickers); + Theme.setDrawableColorByKey(stickersDrawable, Theme.key_chat_emojiPanelIcon); + icons = new Drawable[] { + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_recent, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_smile, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_nature, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_food, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_car, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + Theme.createEmojiIconSelectorDrawable(context, R.drawable.ic_smiles2_objects, Theme.getColor(Theme.key_chat_emojiPanelIcon), Theme.getColor(Theme.key_chat_emojiPanelIconSelected)), + stickersDrawable + }; + showGifs = needGif; - dotDrawable = context.getResources().getDrawable(R.drawable.bluecircle); + dotPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + dotPaint.setColor(Theme.getColor(Theme.key_chat_emojiPanelNewTrending)); if (Build.VERSION.SDK_INT >= 21) { outlineProvider = new ViewOutlineProvider() { @@ -601,7 +648,7 @@ public void getOutline(View view, Outline outline) { } gridView.setNumColumns(-1); EmojiGridAdapter emojiGridAdapter = new EmojiGridAdapter(i - 1); - AndroidUtilities.setListViewEdgeEffectColor(gridView, 0xfff5f6f7); + //AndroidUtilities.setListViewEdgeEffectColor(gridView, Theme.getColor(Theme.key_chat_emojiPanelBackground)); TODO gridView.setAdapter(emojiGridAdapter); adapters.add(emojiGridAdapter); emojiGrids.add(gridView); @@ -619,7 +666,7 @@ public void getOutline(View view, Outline outline) { stickersGridView = new RecyclerListView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersGridView, EmojiView.this.getMeasuredHeight()); + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersGridView, EmojiView.this.getMeasuredHeight(), stickerPreviewViewerDelegate); return super.onInterceptTouchEvent(event) || result; } @@ -650,7 +697,7 @@ public int getSpanSize(int position) { stickersGridView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - return StickerPreviewViewer.getInstance().onTouch(event, stickersGridView, EmojiView.this.getMeasuredHeight(), stickersOnItemClickListener); + return StickerPreviewViewer.getInstance().onTouch(event, stickersGridView, EmojiView.this.getMeasuredHeight(), stickersOnItemClickListener, stickerPreviewViewerDelegate); } }); stickersOnItemClickListener = new RecyclerListView.OnItemClickListener() { @@ -669,7 +716,7 @@ public void onItemClick(View view, int position) { } }; stickersGridView.setOnItemClickListener(stickersOnItemClickListener); - stickersGridView.setGlowColor(0xfff5f6f7); + stickersGridView.setGlowColor(Theme.getColor(Theme.key_chat_emojiPanelBackground)); stickersWrap.addView(stickersGridView); trendingGridView = new RecyclerListView(context); @@ -704,12 +751,12 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { public void onItemClick(View view, int position) { TLRPC.StickerSetCovered pack = trendingGridAdapter.positionsToSets.get(position); if (pack != null) { - listener.onShowStickerSet(pack); + listener.onShowStickerSet(pack.set, null); } } }); trendingGridAdapter.notifyDataSetChanged(); - trendingGridView.setGlowColor(0xfff5f6f7); + trendingGridView.setGlowColor(Theme.getColor(Theme.key_chat_emojiPanelBackground)); trendingGridView.setVisibility(GONE); stickersWrap.addView(trendingGridView); @@ -808,7 +855,7 @@ public void onClick(DialogInterface dialogInterface, int i) { stickersEmptyView = new TextView(context); stickersEmptyView.setText(LocaleController.getString("NoStickers", R.string.NoStickers)); stickersEmptyView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - stickersEmptyView.setTextColor(0xff888888); + stickersEmptyView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelEmptyText)); stickersWrap.addView(stickersEmptyView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 48, 0, 0)); stickersGridView.setEmptyView(stickersEmptyView); @@ -859,7 +906,7 @@ public boolean onTouchEvent(MotionEvent ev) { //don't promt } startedScroll = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } lastX = ev.getX(); @@ -874,9 +921,9 @@ public boolean onTouchEvent(MotionEvent ev) { } }; stickersTab.setUnderlineHeight(AndroidUtilities.dp(1)); - stickersTab.setIndicatorColor(0xffe2e5e7); - stickersTab.setUnderlineColor(0xffe2e5e7); - stickersTab.setBackgroundColor(0xfff5f6f7); + stickersTab.setIndicatorColor(Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelector)); + stickersTab.setUnderlineColor(Theme.getColor(Theme.key_chat_emojiPanelStickerPackSelector)); + stickersTab.setBackgroundColor(Theme.getColor(Theme.key_chat_emojiPanelBackground)); stickersTab.setVisibility(INVISIBLE); addView(stickersTab, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP)); stickersTab.setTranslationX(AndroidUtilities.displaySize.x); @@ -900,6 +947,7 @@ public void onPageSelected(int page) { gifsGridView.setVisibility(GONE); stickersGridView.setVisibility(VISIBLE); stickersEmptyView.setVisibility(stickersGridAdapter.getItemCount() != 0 ? GONE : VISIBLE); + checkScroll(); saveNewPage(); } else if (trendingGridView.getVisibility() == VISIBLE) { trendingGridView.setVisibility(GONE); @@ -976,8 +1024,8 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { pagerSlidingTabStrip.setShouldExpand(true); pagerSlidingTabStrip.setIndicatorHeight(AndroidUtilities.dp(2)); pagerSlidingTabStrip.setUnderlineHeight(AndroidUtilities.dp(1)); - pagerSlidingTabStrip.setIndicatorColor(0xff2b96e2); - pagerSlidingTabStrip.setUnderlineColor(0xffe2e5e7); + pagerSlidingTabStrip.setIndicatorColor(Theme.getColor(Theme.key_chat_emojiPanelIconSelector)); + pagerSlidingTabStrip.setUnderlineColor(Theme.getColor(Theme.key_chat_emojiPanelShadowLine)); emojiTab.addView(pagerSlidingTabStrip, LayoutHelper.createLinear(0, 48, 1.0f)); pagerSlidingTabStrip.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override @@ -1019,18 +1067,18 @@ public boolean onTouchEvent(MotionEvent event) { } }; backspaceButton.setImageResource(R.drawable.ic_smiles_backspace); - backspaceButton.setBackgroundResource(R.drawable.ic_emoji_backspace); + backspaceButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_emojiPanelBackspace), PorterDuff.Mode.MULTIPLY)); backspaceButton.setScaleType(ImageView.ScaleType.CENTER); frameLayout.addView(backspaceButton, LayoutHelper.createFrame(52, 48)); View view = new View(context); - view.setBackgroundColor(0xffe2e5e7); + view.setBackgroundColor(Theme.getColor(Theme.key_chat_emojiPanelShadowLine)); frameLayout.addView(view, LayoutHelper.createFrame(52, 1, Gravity.LEFT | Gravity.BOTTOM)); TextView textView = new TextView(context); textView.setText(LocaleController.getString("NoRecent", R.string.NoRecent)); - textView.setTextSize(18); - textView.setTextColor(0xff888888); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + textView.setTextColor(Theme.getColor(Theme.key_chat_emojiPanelEmptyText)); textView.setGravity(Gravity.CENTER); textView.setClickable(false); textView.setFocusable(false); @@ -1083,7 +1131,20 @@ private void checkScroll() { if (firstVisibleItem == RecyclerView.NO_POSITION) { return; } - checkStickersScroll(firstVisibleItem); + if (stickersGridView == null) { + return; + } + if (stickersGridView.getVisibility() != VISIBLE) { + if (gifsGridView != null && gifsGridView.getVisibility() != VISIBLE) { + gifsGridView.setVisibility(VISIBLE); + } + if (stickersEmptyView != null && stickersEmptyView.getVisibility() == VISIBLE) { + stickersEmptyView.setVisibility(GONE); + } + stickersTab.onPageScrolled(gifTabNum + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); + return; + } + stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(firstVisibleItem) + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); } private void saveNewPage() { @@ -1130,23 +1191,6 @@ private void showGifTab() { saveNewPage(); } - private void checkStickersScroll(int firstVisibleItem) { - if (stickersGridView == null) { - return; - } - if (stickersGridView.getVisibility() != VISIBLE) { - if (gifsGridView != null && gifsGridView.getVisibility() != VISIBLE) { - gifsGridView.setVisibility(VISIBLE); - } - if (stickersEmptyView != null && stickersEmptyView.getVisibility() == VISIBLE) { - stickersEmptyView.setVisibility(GONE); - } - stickersTab.onPageScrolled(gifTabNum + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); - return; - } - stickersTab.onPageScrolled(stickersGridAdapter.getTabForPosition(firstVisibleItem) + 1, (recentTabBum > 0 ? recentTabBum : stickersTabOffset) + 1); - } - private void onPageScrolled(int position, int width, int positionOffsetPixels) { if (stickersTab == null) { return; @@ -1196,19 +1240,6 @@ public void run() { }, time); } - private String convert(long paramLong) { - String str = ""; - for (int i = 0; ; i++) { - if (i >= 4) { - return str; - } - int j = (int) (0xFFFF & paramLong >> 16 * (3 - i)); - if (j != 0) { - str = str + (char) j; - } - } - } - private void saveRecentEmoji() { SharedPreferences preferences = getContext().getSharedPreferences("emoji", Activity.MODE_PRIVATE); StringBuilder stringBuilder = new StringBuilder(); @@ -1286,10 +1317,14 @@ private void updateStickerTabs() { stickersTabOffset = 0; int lastPosition = stickersTab.getCurrentPosition(); stickersTab.removeTabs(); - stickersTab.addIconTab(R.drawable.ic_smiles2_smile); + Drawable drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles2_smile); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersTab.addIconTab(drawable); if (showGifs && !recentGifs.isEmpty()) { - stickersTab.addIconTab(R.drawable.ic_smiles_gif); + drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles_gif); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersTab.addIconTab(drawable); gifTabNum = stickersTabOffset; stickersTabOffset++; } @@ -1298,7 +1333,9 @@ private void updateStickerTabs() { TextView stickersCounter; if (trendingGridAdapter != null && trendingGridAdapter.getItemCount() != 0 && !unread.isEmpty()) { - stickersCounter = stickersTab.addIconTabWithCounter(R.drawable.ic_smiles_trend); + drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles_trend); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersCounter = stickersTab.addIconTabWithCounter(drawable); trendingTabNum = stickersTabOffset; stickersTabOffset++; stickersCounter.setText(String.format("%d", unread.size())); @@ -1307,7 +1344,9 @@ private void updateStickerTabs() { if (!recentStickers.isEmpty()) { recentTabBum = stickersTabOffset; stickersTabOffset++; - stickersTab.addIconTab(R.drawable.ic_smiles2_recent); + drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles2_recent); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersTab.addIconTab(drawable); } stickerSets.clear(); @@ -1323,10 +1362,14 @@ private void updateStickerTabs() { stickersTab.addStickerTab(stickerSets.get(a).documents.get(0)); } if (trendingGridAdapter != null && trendingGridAdapter.getItemCount() != 0 && unread.isEmpty()) { + drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles_trend); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); trendingTabNum = stickersTabOffset + stickerSets.size(); - stickersTab.addIconTab(R.drawable.ic_smiles_trend); + stickersTab.addIconTab(drawable); } - stickersTab.addIconTab(R.drawable.ic_smiles_settings); + drawable = getContext().getResources().getDrawable(R.drawable.ic_smiles_settings); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + stickersTab.addIconTab(drawable); stickersTab.updateTabStyles(); if (lastPosition != 0) { stickersTab.onPageScrolled(lastPosition, lastPosition); @@ -1389,7 +1432,6 @@ public void addRecentGif(TLRPC.Document document) { if (document == null) { return; } - StickersQuery.addRecentGif(document, (int) (System.currentTimeMillis() / 1000)); boolean wasEmpty = recentGifs.isEmpty(); recentGifs = StickersQuery.getRecentGifs(); if (gifsAdapter != null) { @@ -1458,7 +1500,7 @@ public void loadRecents() { sortEmoji(); adapters.get(0).notifyDataSetChanged(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { @@ -1472,7 +1514,7 @@ public void loadRecents() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1495,6 +1537,7 @@ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setElevation(AndroidUtilities.dp(2)); } setBackgroundResource(R.drawable.smiles_popup); + getBackground().setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chat_emojiPanelBackground), PorterDuff.Mode.MULTIPLY)); emojiTab.setBackgroundDrawable(null); currentBackgroundType = 1; } @@ -1505,8 +1548,8 @@ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setClipToOutline(false); setElevation(0); } - setBackgroundColor(0xfff5f6f7); - emojiTab.setBackgroundColor(0xfff5f6f7); + setBackgroundColor(Theme.getColor(Theme.key_chat_emojiPanelBackground)); + emojiTab.setBackgroundColor(Theme.getColor(Theme.key_chat_emojiPanelBackground)); currentBackgroundType = 0; } } @@ -1684,15 +1727,43 @@ private void checkDocuments(boolean isGif) { } private void updateVisibleTrendingSets() { - int first = trendingLayoutManager.findFirstVisibleItemPosition(); - if (first == RecyclerView.NO_POSITION) { + if (trendingGridAdapter == null || trendingGridAdapter == null) { return; } - int last = trendingLayoutManager.findLastVisibleItemPosition(); - if (last == RecyclerView.NO_POSITION) { - return; + try { + int count = trendingGridView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = trendingGridView.getChildAt(a); + if (child instanceof FeaturedStickerSetInfoCell) { + RecyclerListView.Holder holder = (RecyclerListView.Holder) trendingGridView.getChildViewHolder(child); + if (holder == null) { + continue; + } + FeaturedStickerSetInfoCell cell = (FeaturedStickerSetInfoCell) child; + ArrayList unreadStickers = StickersQuery.getUnreadStickerSets(); + TLRPC.StickerSetCovered stickerSetCovered = cell.getStickerSet(); + boolean unread = unreadStickers != null && unreadStickers.contains(stickerSetCovered.set.id); + cell.setStickerSet(stickerSetCovered, unread); + if (unread) { + StickersQuery.markFaturedStickersByIdAsRead(stickerSetCovered.set.id); + } + boolean installing = installingStickerSets.containsKey(stickerSetCovered.set.id); + boolean removing = removingStickerSets.containsKey(stickerSetCovered.set.id); + if (installing || removing) { + if (installing && cell.isInstalled()) { + installingStickerSets.remove(stickerSetCovered.set.id); + installing = false; + } else if (removing && !cell.isInstalled()) { + removingStickerSets.remove(stickerSetCovered.set.id); + removing = false; + } + } + cell.setDrawProgress(installing || removing); + } + } + } catch (Exception e) { + FileLog.e(e); } - trendingGridAdapter.notifyItemRangeChanged(first, last - first + 1); } @SuppressWarnings("unchecked") @@ -1718,6 +1789,9 @@ public void didReceivedNotification(int id, Object... args) { } } else if (id == NotificationCenter.featuredStickersDidLoaded) { if (trendingGridAdapter != null) { + if (featuredStickersHash != StickersQuery.getFeaturesStickersHashWithoutUnread()) { + trendingLoaded = false; + } if (trendingLoaded) { updateVisibleTrendingSets(); } else { @@ -1734,7 +1808,7 @@ public void didReceivedNotification(int id, Object... args) { } } - private class TrendingGridAdapter extends RecyclerView.Adapter { + private class TrendingGridAdapter extends RecyclerListView.SelectionAdapter { private Context context; private int stickersPerRow; @@ -1756,6 +1830,11 @@ public Object getItem(int i) { return cache.get(i); } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public int getItemViewType(int position) { Object object = cache.get(position); @@ -1806,7 +1885,7 @@ public void onClick(View v) { break; } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -1846,15 +1925,24 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { @Override public void notifyDataSetChanged() { - if (trendingLoaded) { - return; - } int width = getMeasuredWidth(); if (width == 0) { - width = AndroidUtilities.displaySize.x; + if (AndroidUtilities.isTablet()) { + int smallSide = AndroidUtilities.displaySize.x; + int leftSide = smallSide * 35 / 100; + if (leftSide < AndroidUtilities.dp(320)) { + leftSide = AndroidUtilities.dp(320); + } + width = smallSide - leftSide; + } else { + width = AndroidUtilities.displaySize.x; + } } stickersPerRow = width / AndroidUtilities.dp(72); trendingLayoutManager.setSpanCount(stickersPerRow); + if (trendingLoaded) { + return; + } cache.clear(); positionsToSets.clear(); sets.clear(); @@ -1889,12 +1977,13 @@ public void notifyDataSetChanged() { } if (totalItems != 0) { trendingLoaded = true; + featuredStickersHash = StickersQuery.getFeaturesStickersHashWithoutUnread(); } super.notifyDataSetChanged(); } } - private class StickersGridAdapter extends RecyclerView.Adapter { + private class StickersGridAdapter extends RecyclerListView.SelectionAdapter { private Context context; private int stickersPerRow; @@ -1907,6 +1996,11 @@ public StickersGridAdapter(Context context) { this.context = context; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public int getItemCount() { return totalItems != 0 ? totalItems + 1 : 0; @@ -1917,7 +2011,11 @@ public Object getItem(int i) { } public int getPositionForPack(TLRPC.TL_messages_stickerSet stickerSet) { - return packStartRow.get(stickerSet) * stickersPerRow; + Integer pos = packStartRow.get(stickerSet); + if (pos == null) { + return -1; + } + return pos * stickersPerRow; } @Override @@ -1960,7 +2058,7 @@ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { break; } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -1969,6 +2067,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { case 0: TLRPC.Document sticker = cache.get(position); ((StickerEmojiCell) holder.itemView).setSticker(sticker, false); + ((StickerEmojiCell) holder.itemView).setRecent(recentStickers.contains(sticker)); break; case 1: if (position == totalItems) { @@ -2099,17 +2198,16 @@ public int getCount() { return views.size(); } - public int getPageIconResId(int paramInt) { - return icons[paramInt]; + public Drawable getPageIconDrawable(int position) { + return icons[position]; } @Override public void customOnDraw(Canvas canvas, int position) { - if (position == 6 && !StickersQuery.getUnreadStickerSets().isEmpty() && dotDrawable != null) { - int x = canvas.getWidth() / 2 + AndroidUtilities.dp(4); - int y = canvas.getHeight() / 2 - AndroidUtilities.dp(13); - dotDrawable.setBounds(x, y, x + dotDrawable.getIntrinsicWidth(), y + dotDrawable.getIntrinsicHeight()); - dotDrawable.draw(canvas); + if (position == 6 && !StickersQuery.getUnreadStickerSets().isEmpty() && dotPaint != null) { + int x = canvas.getWidth() / 2 + AndroidUtilities.dp(4 + 5); + int y = canvas.getHeight() / 2 - AndroidUtilities.dp(13 - 5); + canvas.drawCircle(x, y, AndroidUtilities.dp(5), dotPaint); } } @@ -2136,14 +2234,7 @@ public void unregisterDataSetObserver(DataSetObserver observer) { } } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - - private class GifsAdapter extends RecyclerView.Adapter { + private class GifsAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; @@ -2151,6 +2242,11 @@ public GifsAdapter(Context context) { mContext = context; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public int getItemCount() { return recentGifs.size(); @@ -2164,7 +2260,7 @@ public long getItemId(int i) { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { ContextLinkCell view = new ContextLinkCell(mContext); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java index f08d7585e88..fd7b8ab37ee 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmptyTextProgressView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -14,30 +14,30 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; -import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; public class EmptyTextProgressView extends FrameLayout { private TextView textView; - private ProgressBar progressBar; + private RadialProgressView progressBar; private boolean inLayout; private boolean showAtCenter; public EmptyTextProgressView(Context context) { super(context); - progressBar = new ProgressBar(context); + progressBar = new RadialProgressView(context); progressBar.setVisibility(INVISIBLE); addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - textView.setTextColor(0xff808080); + textView.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); textView.setGravity(Gravity.CENTER); textView.setVisibility(INVISIBLE); textView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); @@ -66,6 +66,14 @@ public void setText(String text) { textView.setText(text); } + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public void setProgressBarColor(int color) { + progressBar.setProgressColor(color); + } + public void setTextSize(int size) { textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, size); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java index f5cb849a966..8cdb7bd9ad7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ExtendedGridLayoutManager.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ForegroundDetector.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ForegroundDetector.java index b6cffc56ff7..8a79805930e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ForegroundDetector.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ForegroundDetector.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -63,12 +63,12 @@ public void onActivityStarted(Activity activity) { if (System.currentTimeMillis() - enterBackgroundTime < 200) { wasInBackground = false; } - FileLog.e("tmessages", "switch to foreground"); + FileLog.e("switch to foreground"); for (Listener listener : listeners) { try { listener.onBecameForeground(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -90,12 +90,12 @@ public void onActivityStopped(Activity activity) { if (--refs == 0) { enterBackgroundTime = System.currentTimeMillis(); wasInBackground = true; - FileLog.e("tmessages", "switch to background"); + FileLog.e("switch to background"); for (Listener listener : listeners) { try { listener.onBecameBackground(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java new file mode 100644 index 00000000000..b478e0cf950 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/FragmentContextView.java @@ -0,0 +1,386 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Typeface; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.voip.VoIPService; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.AudioPlayerActivity; +import org.telegram.ui.VoIPActivity; + +public class FragmentContextView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { + + private ImageView playButton; + private TextView titleTextView; + private MessageObject lastMessageObject; + private AnimatorSet animatorSet; + private float yPosition; + private BaseFragment fragment; + private float topPadding; + private boolean visible; + private FrameLayout frameLayout; + private ImageView closeButton; + private int currentStyle = -1; + + public FragmentContextView(Context context, BaseFragment parentFragment) { + super(context); + + fragment = parentFragment; + visible = true; + ((ViewGroup) fragment.getFragmentView()).setClipToPadding(false); + + setTag(1); + frameLayout = new FrameLayout(context); + addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + + View shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow); + addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.TOP, 0, 36, 0, 0)); + + playButton = new ImageView(context); + playButton.setScaleType(ImageView.ScaleType.CENTER); + playButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_inappPlayerPlayPause), PorterDuff.Mode.MULTIPLY)); + addView(playButton, LayoutHelper.createFrame(36, 36, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + playButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (MediaController.getInstance().isAudioPaused()) { + MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject()); + } else { + MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); + } + } + }); + + titleTextView = new TextView(context); + titleTextView.setMaxLines(1); + titleTextView.setLines(1); + titleTextView.setSingleLine(true); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + titleTextView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); + addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); + + closeButton = new ImageView(context); + closeButton.setImageResource(R.drawable.miniplayer_close); + closeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_inappPlayerClose), PorterDuff.Mode.MULTIPLY)); + closeButton.setScaleType(ImageView.ScaleType.CENTER); + addView(closeButton, LayoutHelper.createFrame(36, 36, Gravity.RIGHT | Gravity.TOP)); + closeButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + MediaController.getInstance().cleanupPlayer(true, true); + } + }); + + setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (currentStyle == 0) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + if (messageObject != null && messageObject.isMusic() && fragment != null) { + fragment.presentFragment(new AudioPlayerActivity()); + } + } else if (currentStyle == 1) { + Intent intent = new Intent(getContext(), VoIPActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + getContext().startActivity(intent); + } + } + }); + } + + public float getTopPadding() { + return topPadding; + } + + public void setTopPadding(float value) { + topPadding = value; + if (fragment != null) { + View view = fragment.getFragmentView(); + if (view != null) { + view.setPadding(0, (int) topPadding, 0, 0); + } + } + } + + private void updateStyle(int style) { + if (currentStyle == style) { + return; + } + currentStyle = style; + if (style == 0) { + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_inappPlayerBackground)); + titleTextView.setTextColor(Theme.getColor(Theme.key_inappPlayerTitle)); + closeButton.setVisibility(VISIBLE); + playButton.setVisibility(VISIBLE); + titleTextView.setTypeface(Typeface.DEFAULT); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); + } else if (style == 1) { + titleTextView.setText(LocaleController.getString("ReturnToCall", R.string.ReturnToCall)); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_returnToCallBackground)); + titleTextView.setTextColor(Theme.getColor(Theme.key_returnToCallText)); + closeButton.setVisibility(GONE); + playButton.setVisibility(GONE); + titleTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + titleTextView.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 0, 0, 2)); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + topPadding = 0; + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didStartedCall); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didEndedCall); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didStartedCall); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didEndedCall); + boolean callAvailable = VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING; + if (callAvailable) { + checkCall(true); + } else { + checkPlayer(true); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, AndroidUtilities.dp2(39)); + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.audioDidStarted || id == NotificationCenter.audioPlayStateChanged || id == NotificationCenter.audioDidReset || id == NotificationCenter.didEndedCall) { + checkPlayer(false); + } else if (id == NotificationCenter.didStartedCall) { + checkCall(false); + } else { + checkPlayer(false); + } + } + + private void checkPlayer(boolean create) { + MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); + View fragmentView = fragment.getFragmentView(); + if (!create && fragmentView != null) { + if (fragmentView.getParent() == null || ((View) fragmentView.getParent()).getVisibility() != VISIBLE) { + create = true; + } + } + if (messageObject == null || messageObject.getId() == 0/* || !messageObject.isMusic()*/) { + lastMessageObject = null; + if (visible) { + visible = false; + if (create) { + if (getVisibility() != GONE) { + setVisibility(GONE); + } + setTopPadding(0); + } else { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36)), + ObjectAnimator.ofFloat(this, "topPadding", 0)); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + setVisibility(GONE); + animatorSet = null; + } + } + }); + animatorSet.start(); + } + } + } else { + int prevStyle = currentStyle; + updateStyle(0); + if (create && topPadding == 0) { + setTopPadding(AndroidUtilities.dp2(36)); + setTranslationY(0); + yPosition = 0; + } + if (!visible) { + if (!create) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36), 0), + ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp2(36))); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + animatorSet = null; + } + } + }); + animatorSet.start(); + } + visible = true; + setVisibility(VISIBLE); + } + if (MediaController.getInstance().isAudioPaused()) { + playButton.setImageResource(R.drawable.miniplayer_play); + } else { + playButton.setImageResource(R.drawable.miniplayer_pause); + } + if (lastMessageObject != messageObject || prevStyle != 0) { + lastMessageObject = messageObject; + SpannableStringBuilder stringBuilder; + if (lastMessageObject.isVoice()) { + stringBuilder = new SpannableStringBuilder(String.format("%s %s", messageObject.getMusicAuthor(), messageObject.getMusicTitle())); + titleTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE); + } else { + stringBuilder = new SpannableStringBuilder(String.format("%s - %s", messageObject.getMusicAuthor(), messageObject.getMusicTitle())); + titleTextView.setEllipsize(TextUtils.TruncateAt.END); + } + TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, Theme.getColor(Theme.key_inappPlayerPerformer)); + stringBuilder.setSpan(span, 0, messageObject.getMusicAuthor().length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); + titleTextView.setText(stringBuilder); + } + } + } + + private void checkCall(boolean create) { + View fragmentView = fragment.getFragmentView(); + if (!create && fragmentView != null) { + if (fragmentView.getParent() == null || ((View) fragmentView.getParent()).getVisibility() != VISIBLE) { + create = true; + } + } + boolean callAvailable = VoIPService.getSharedInstance() != null && VoIPService.getSharedInstance().getCallState() != VoIPService.STATE_WAITING_INCOMING; + if (!callAvailable) { + if (visible) { + visible = false; + if (create) { + if (getVisibility() != GONE) { + setVisibility(GONE); + } + setTopPadding(0); + } else { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36)), + ObjectAnimator.ofFloat(this, "topPadding", 0)); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + setVisibility(GONE); + animatorSet = null; + } + } + }); + animatorSet.start(); + } + } + } else { + updateStyle(1); + if (create && topPadding == 0) { + setTopPadding(AndroidUtilities.dp2(36)); + setTranslationY(0); + yPosition = 0; + } + if (!visible) { + if (!create) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp2(36), 0), + ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp2(36))); + animatorSet.setDuration(200); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animatorSet != null && animatorSet.equals(animation)) { + animatorSet = null; + } + } + }); + animatorSet.start(); + } + visible = true; + setVisibility(VISIBLE); + } + } + } + + @Override + public void setTranslationY(float translationY) { + super.setTranslationY(translationY); + yPosition = translationY; + invalidate(); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + int restoreToCount = canvas.save(); + if (yPosition < 0) { + canvas.clipRect(0, (int) -yPosition, child.getMeasuredWidth(), AndroidUtilities.dp2(39)); + } + final boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restoreToCount(restoreToCount); + return result; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateCheckBox.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateCheckBox.java new file mode 100644 index 00000000000..6fb17c7b1fc --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateCheckBox.java @@ -0,0 +1,174 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; + +public class GroupCreateCheckBox extends View { + + private static Paint backgroundPaint; + private static Paint backgroundInnerPaint; + private static Paint checkPaint; + private static Paint eraser; + private static Paint eraser2; + + private Bitmap drawBitmap; + private Canvas bitmapCanvas; + + private float progress; + private ObjectAnimator checkAnimator; + private boolean isCheckAnimation = true; + + private boolean attachedToWindow; + private boolean isChecked; + + private final static float progressBounceDiff = 0.2f; + + public GroupCreateCheckBox(Context context) { + super(context); + if (backgroundPaint == null) { + backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + backgroundInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + checkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + checkPaint.setStyle(Paint.Style.STROKE); + eraser = new Paint(Paint.ANTI_ALIAS_FLAG); + eraser.setColor(0); + eraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + eraser2 = new Paint(Paint.ANTI_ALIAS_FLAG); + eraser2.setColor(0); + eraser2.setStyle(Paint.Style.STROKE); + eraser2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } + + checkPaint.setStrokeWidth(AndroidUtilities.dp(1.5f)); + eraser2.setStrokeWidth(AndroidUtilities.dp(28)); + + drawBitmap = Bitmap.createBitmap(AndroidUtilities.dp(24), AndroidUtilities.dp(24), Bitmap.Config.ARGB_4444); + bitmapCanvas = new Canvas(drawBitmap); + updateColors(); + } + + public void updateColors() { + backgroundInnerPaint.setColor(Theme.getColor(Theme.key_groupcreate_checkbox)); + backgroundPaint.setColor(Theme.getColor(Theme.key_groupcreate_checkboxCheck)); + checkPaint.setColor(Theme.getColor(Theme.key_groupcreate_checkboxCheck)); + invalidate(); + } + + public void setProgress(float value) { + if (progress == value) { + return; + } + progress = value; + invalidate(); + } + + public float getProgress() { + return progress; + } + + private void cancelCheckAnimator() { + if (checkAnimator != null) { + checkAnimator.cancel(); + } + } + + private void animateToCheckedState(boolean newCheckedState) { + isCheckAnimation = newCheckedState; + checkAnimator = ObjectAnimator.ofFloat(this, "progress", newCheckedState ? 1 : 0); + checkAnimator.setDuration(300); + checkAnimator.start(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + attachedToWindow = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + attachedToWindow = false; + } + + public void setChecked(boolean checked, boolean animated) { + if (checked == isChecked) { + return; + } + isChecked = checked; + + if (attachedToWindow && animated) { + animateToCheckedState(checked); + } else { + cancelCheckAnimator(); + setProgress(checked ? 1.0f : 0.0f); + } + } + + public boolean isChecked() { + return isChecked; + } + + @Override + protected void onDraw(Canvas canvas) { + if (getVisibility() != VISIBLE) { + return; + } + if (progress != 0) { + int cx = getMeasuredWidth() / 2; + int cy = getMeasuredHeight() / 2; + eraser2.setStrokeWidth(AndroidUtilities.dp(24 + 6)); + + drawBitmap.eraseColor(0); + + float roundProgress = progress >= 0.5f ? 1.0f : progress / 0.5f; + float checkProgress = progress < 0.5f ? 0.0f : (progress - 0.5f) / 0.5f; + + float roundProgressCheckState = isCheckAnimation ? progress : (1.0f - progress); + float radDiff; + if (roundProgressCheckState < progressBounceDiff) { + radDiff = AndroidUtilities.dp(2) * roundProgressCheckState / progressBounceDiff; + } else if (roundProgressCheckState < progressBounceDiff * 2) { + radDiff = AndroidUtilities.dp(2) - AndroidUtilities.dp(2) * (roundProgressCheckState - progressBounceDiff) / progressBounceDiff; + } else { + radDiff = 0; + } + + if (checkProgress != 0) { + canvas.drawCircle(cx, cy, (cx - AndroidUtilities.dp(2)) + AndroidUtilities.dp(2) * checkProgress - radDiff, backgroundPaint); + } + + float innerRad = cx - AndroidUtilities.dp(2) - radDiff; + bitmapCanvas.drawCircle(cx, cy, innerRad, backgroundInnerPaint); + bitmapCanvas.drawCircle(cx, cy, innerRad * (1 - roundProgress), eraser); + canvas.drawBitmap(drawBitmap, 0, 0, null); + + float checkSide = AndroidUtilities.dp(10) * checkProgress; + float smallCheckSide = AndroidUtilities.dp(5) * checkProgress; + int x = cx - AndroidUtilities.dp(1); + int y = cy + AndroidUtilities.dp(4); + float side = (float) Math.sqrt(smallCheckSide * smallCheckSide / 2.0f); + canvas.drawLine(x, y, x - side, y - side, checkPaint); + side = (float) Math.sqrt(checkSide * checkSide / 2.0f); + x -= AndroidUtilities.dp(1.2f); + canvas.drawLine(x, y, x + side, y - side, checkPaint); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java new file mode 100644 index 00000000000..c203ea9fa52 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateDividerItemDecoration.java @@ -0,0 +1,52 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.graphics.*; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.Theme; + +public class GroupCreateDividerItemDecoration extends RecyclerView.ItemDecoration { + + private boolean searching; + + public void setSearching(boolean value) { + searching = value; + } + + @Override + public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { + int width = parent.getWidth(); + int top; + int childCount = parent.getChildCount(); + for (int i = 0; i < childCount - 1; i++) { + View child = parent.getChildAt(i); + int position = parent.getChildAdapterPosition(child); + if (position == 0) { + continue; + } + top = child.getBottom(); + canvas.drawLine(LocaleController.isRTL ? 0 : AndroidUtilities.dp(72), top, width - (LocaleController.isRTL ? AndroidUtilities.dp(72) : 0), top, Theme.dividerPaint); + } + } + + @Override + public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int position = parent.getChildAdapterPosition(view); + if (position == 0 || !searching && position == 1) { + return; + } + outRect.top = 1; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java new file mode 100644 index 00000000000..1b7153703c3 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/GroupCreateSpan.java @@ -0,0 +1,175 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.R; +import org.telegram.messenger.UserObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; + +public class GroupCreateSpan extends View { + + private int uid; + private static TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + private static Paint backPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Drawable deleteDrawable; + private RectF rect = new RectF(); + private ImageReceiver imageReceiver; + private StaticLayout nameLayout; + private AvatarDrawable avatarDrawable; + private int textWidth; + private float textX; + private float progress; + private boolean deleting; + private long lastUpdateTime; + private int[] colors = new int[6]; + + public GroupCreateSpan(Context context, TLRPC.User user) { + super(context); + + deleteDrawable = getResources().getDrawable(R.drawable.delete); + textPaint.setTextSize(AndroidUtilities.dp(14)); + + avatarDrawable = new AvatarDrawable(); + avatarDrawable.setTextSize(AndroidUtilities.dp(12)); + avatarDrawable.setInfo(user); + + imageReceiver = new ImageReceiver(); + imageReceiver.setRoundRadius(AndroidUtilities.dp(16)); + imageReceiver.setParentView(this); + imageReceiver.setImageCoords(0, 0, AndroidUtilities.dp(32), AndroidUtilities.dp(32)); + + uid = user.id; + + int maxNameWidth; + if (AndroidUtilities.isTablet()) { + maxNameWidth = AndroidUtilities.dp(530 - 32 - 18 - 57 * 2) / 2; + } else { + maxNameWidth = (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(32 + 18 + 57 * 2)) / 2; + } + CharSequence name = TextUtils.ellipsize(UserObject.getFirstName(user).replace('\n', ' '), textPaint, maxNameWidth, TextUtils.TruncateAt.END); + nameLayout = new StaticLayout(name, textPaint, 1000, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (nameLayout.getLineCount() > 0) { + textWidth = (int) Math.ceil(nameLayout.getLineWidth(0)); + textX = -nameLayout.getLineLeft(0); + } + + TLRPC.FileLocation photo = null; + if (user.photo != null) { + photo = user.photo.photo_small; + } + imageReceiver.setImage(photo, null, "50_50", avatarDrawable, null, null, 0, null, true); + updateColors(); + } + + public void updateColors() { + int color = Theme.getColor(Theme.key_avatar_backgroundGroupCreateSpanBlue); + int back = Theme.getColor(Theme.key_groupcreate_spanBackground); + int text = Theme.getColor(Theme.key_groupcreate_spanText); + colors[0] = Color.red(back); + colors[1] = Color.red(color); + colors[2] = Color.green(back); + colors[3] = Color.green(color); + colors[4] = Color.blue(back); + colors[5] = Color.blue(color); + textPaint.setColor(text); + deleteDrawable.setColorFilter(new PorterDuffColorFilter(text, PorterDuff.Mode.MULTIPLY)); + backPaint.setColor(back); + avatarDrawable.setColor(AvatarDrawable.getColorForId(5)); + } + + public boolean isDeleting() { + return deleting; + } + + public void startDeleteAnimation() { + if (deleting) { + return; + } + deleting = true; + lastUpdateTime = System.currentTimeMillis(); + invalidate(); + } + + public void cancelDeleteAnimation() { + if (!deleting) { + return; + } + deleting = false; + lastUpdateTime = System.currentTimeMillis(); + invalidate(); + } + + public int getUid() { + return uid; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(AndroidUtilities.dp(32 + 25) + textWidth, AndroidUtilities.dp(32)); + } + + @Override + protected void onDraw(Canvas canvas) { + if (deleting && progress != 1.0f || !deleting && progress != 0.0f) { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + if (dt < 0 || dt > 17) { + dt = 17; + } + if (deleting) { + progress += dt / 120.0f; + if (progress >= 1.0f) { + progress = 1.0f; + } + } else { + progress -= dt / 120.0f; + if (progress < 0.0f) { + progress = 0.0f; + } + } + invalidate(); + } + canvas.save(); + rect.set(0, 0, getMeasuredWidth(), AndroidUtilities.dp(32)); + backPaint.setColor(Color.argb(255, colors[0] + (int) ((colors[1] - colors[0]) * progress), colors[2] + (int) ((colors[3] - colors[2]) * progress), colors[4] + (int) ((colors[5] - colors[4]) * progress))); + canvas.drawRoundRect(rect, AndroidUtilities.dp(16), AndroidUtilities.dp(16), backPaint); + imageReceiver.draw(canvas); + if (progress != 0) { + backPaint.setColor(avatarDrawable.getColor()); + backPaint.setAlpha((int) (255 * progress)); + canvas.drawCircle(AndroidUtilities.dp(16), AndroidUtilities.dp(16), AndroidUtilities.dp(16), backPaint); + canvas.save(); + canvas.rotate(45 * (1.0f - progress), AndroidUtilities.dp(16), AndroidUtilities.dp(16)); + deleteDrawable.setBounds(AndroidUtilities.dp(11), AndroidUtilities.dp(11), AndroidUtilities.dp(21), AndroidUtilities.dp(21)); + deleteDrawable.setAlpha((int) (255 * progress)); + deleteDrawable.draw(canvas); + canvas.restore(); + } + canvas.translate(textX + AndroidUtilities.dp(32 + 9), AndroidUtilities.dp(8)); + nameLayout.draw(canvas); + canvas.restore(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java index fc8d7154eae..efdcc42dbe5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/HintEditText.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -15,6 +15,7 @@ import android.widget.EditText; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; public class HintEditText extends EditText { @@ -27,7 +28,7 @@ public class HintEditText extends EditText { public HintEditText(Context context) { super(context); - paint.setColor(0xff979797); + paint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); } public String getHintText() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/IdenticonDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/IdenticonDrawable.java index 5625ed373c6..ea2f4cfae6e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/IdenticonDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/IdenticonDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -39,6 +39,14 @@ public void setEncryptedChat(TLRPC.EncryptedChat encryptedChat) { invalidateSelf(); } + public void setColors(int[] value) { + if (colors.length != 4) { + throw new IllegalArgumentException("colors must have length of 4"); + } + colors = value; + invalidateSelf(); + } + @Override public void draw(Canvas canvas) { if (data == null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java new file mode 100644 index 00000000000..e0937523c2b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/InstantCameraView.java @@ -0,0 +1,308 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.ColorDrawable; +import android.hardware.Camera; +import android.os.Build; +import android.os.Vibrator; +import android.view.Gravity; +import android.view.View; +import android.view.ViewOutlineProvider; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.VideoEditedInfo; +import org.telegram.messenger.camera.CameraController; +import org.telegram.messenger.camera.CameraView; +import org.telegram.ui.ChatActivity; + +import java.io.File; + +public class InstantCameraView extends FrameLayout { + + private FrameLayout cameraContainer; + private CameraView cameraView; + private ChatActivity baseFragment; + private View actionBar; + + private int[] position = new int[2]; + + private AnimatorSet animatorSet; + + private boolean deviceHasGoodCamera; + private boolean requestingPermissions; + private File cameraFile; + private long recordStartTime; + private boolean recording; + + private Runnable timerRunnable = new Runnable() { + @Override + public void run() { + if (!recording) { + return; + } + NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordProgressChanged, System.currentTimeMillis() - recordStartTime, 0.0); + AndroidUtilities.runOnUIThread(timerRunnable, 50); + } + }; + + public InstantCameraView(Context context, ChatActivity parentFragment, View actionBarOverlay) { + super(context); + actionBar = actionBarOverlay; + setBackgroundColor(0x7f000000); + baseFragment = parentFragment; + + if (Build.VERSION.SDK_INT >= 21) { + cameraContainer = new FrameLayout(context); + } else { + final Path path = new Path(); + final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + paint.setColor(0xff000000); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + cameraContainer = new FrameLayout(context) { + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + path.reset(); + path.addCircle(w / 2, h / 2, w / 2, Path.Direction.CW); + path.toggleInverseFillType(); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + canvas.drawPath(path, paint); + } + }; + } + final int size; + if (AndroidUtilities.isTablet()) { + size = AndroidUtilities.dp(100); + } else { + size = Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) / 2; + } + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(size, size, Gravity.CENTER); + layoutParams.bottomMargin = AndroidUtilities.dp(48); + addView(cameraContainer, layoutParams); + if (Build.VERSION.SDK_INT >= 21) { + cameraContainer.setOutlineProvider(new ViewOutlineProvider() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, size, size); + } + }); + cameraContainer.setClipToOutline(true); + } else { + cameraContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + setVisibility(GONE); + } + + public void checkCamera(boolean request) { + if (baseFragment == null) { + return; + } + boolean old = deviceHasGoodCamera; + if (Build.VERSION.SDK_INT >= 23) { + if (baseFragment.getParentActivity().checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + if (request) { + baseFragment.getParentActivity().requestPermissions(new String[]{Manifest.permission.CAMERA}, 17); + } + deviceHasGoodCamera = false; + } else { + CameraController.getInstance().initCamera(); + deviceHasGoodCamera = CameraController.getInstance().isCameraInitied(); + } + } else if (Build.VERSION.SDK_INT >= 16) { + CameraController.getInstance().initCamera(); + deviceHasGoodCamera = CameraController.getInstance().isCameraInitied(); + } + if (deviceHasGoodCamera && baseFragment != null) { + showCamera(); + } + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + actionBar.setVisibility(visibility); + setAlpha(0.0f); + actionBar.setAlpha(0.0f); + cameraContainer.setAlpha(0.0f); + cameraContainer.setScaleX(0.1f); + cameraContainer.setScaleY(0.1f); + if (cameraContainer.getMeasuredWidth() != 0) { + cameraContainer.setPivotX(cameraContainer.getMeasuredWidth() / 2); + cameraContainer.setPivotY(cameraContainer.getMeasuredHeight() / 2); + } + } + + @TargetApi(16) + public void showCamera() { + if (cameraView != null) { + return; + } + setVisibility(VISIBLE); + cameraView = new CameraView(getContext(), true); + cameraView.setMirror(true); + cameraContainer.addView(cameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + cameraView.setDelegate(new CameraView.CameraViewDelegate() { + @Override + public void onCameraCreated(Camera camera) { + + } + + @Override + public void onCameraInit() { + if (Build.VERSION.SDK_INT >= 23) { + if (baseFragment.getParentActivity().checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestingPermissions = true; + baseFragment.getParentActivity().requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 21); + return; + } + } + try { + Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); + v.vibrate(50); + } catch (Exception e) { + FileLog.e(e); + } + AndroidUtilities.lockOrientation(baseFragment.getParentActivity()); + cameraFile = AndroidUtilities.generateVideoPath(); + CameraController.getInstance().recordVideo(cameraView.getCameraSession(), cameraFile, new CameraController.VideoTakeCallback() { + @Override + public void onFinishVideoRecording(final Bitmap thumb) { + if (cameraFile == null || baseFragment == null) { + return; + } + AndroidUtilities.addMediaToGallery(cameraFile.getAbsolutePath()); + VideoEditedInfo videoEditedInfo = new VideoEditedInfo(); + videoEditedInfo.bitrate = -1; + videoEditedInfo.originalPath = cameraFile.getAbsolutePath(); + videoEditedInfo.startTime = videoEditedInfo.endTime = -1; + videoEditedInfo.estimatedSize = cameraFile.length(); + //videoEditedInfo.estimatedDuration = cameraFile.length(); + + baseFragment.sendMedia(new MediaController.PhotoEntry(0, 0, 0, cameraFile.getAbsolutePath(), 0, true), null); + } + }, new Runnable() { + @Override + public void run() { + recording = true; + recordStartTime = System.currentTimeMillis(); + AndroidUtilities.runOnUIThread(timerRunnable); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStarted); + startAnimation(true); + } + }, true); + } + }); + } + + public FrameLayout getCameraContainer() { + return cameraContainer; + } + + public void startAnimation(boolean open) { + if (animatorSet != null) { + animatorSet.cancel(); + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(actionBar, "alpha", open ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(this, "alpha", open ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(cameraContainer, "alpha", open ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(cameraContainer, "scaleX", open ? 1.0f : 0.1f), + ObjectAnimator.ofFloat(cameraContainer, "scaleY", open ? 1.0f : 0.1f), + ObjectAnimator.ofFloat(cameraContainer, "translationY", open ? getMeasuredHeight() / 2 : 0, open ? 0 : getMeasuredHeight() / 2) + ); + if (!open) { + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(animatorSet)) { + hideCamera(true); + setVisibility(GONE); + } + } + }); + } + animatorSet.setDuration(180); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.start(); + } + + public Rect getCameraRect() { + cameraContainer.getLocationOnScreen(position); + return new Rect(position[0], position[1], cameraContainer.getWidth(), cameraContainer.getHeight()); + } + + public void send() { + if (cameraView == null || cameraFile == null) { + return; + } + recording = false; + AndroidUtilities.cancelRunOnUIThread(timerRunnable); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStopped); + CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), false); + //startAnimation(false); + } + + public void cancel() { + if (cameraView == null || cameraFile == null) { + return; + } + recording = false; + AndroidUtilities.cancelRunOnUIThread(timerRunnable); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.recordStopped); + CameraController.getInstance().stopVideoRecording(cameraView.getCameraSession(), true); + cameraFile.delete(); + cameraFile = null; + startAnimation(false); + } + + @Override + public void setAlpha(float alpha) { + ColorDrawable colorDrawable = (ColorDrawable) getBackground(); + colorDrawable.setAlpha((int) (0x7f * alpha)); + } + + public void hideCamera(boolean async) { + if (/*!deviceHasGoodCamera || */cameraView == null) { + return; + } + cameraView.destroy(async, null); + cameraContainer.removeView(cameraView); + cameraView = null; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinGroupAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinGroupAlert.java index 33fb19e73b3..040f454f5fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinGroupAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/JoinGroupAlert.java @@ -3,12 +3,11 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; -import android.app.AlertDialog; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; @@ -85,7 +84,7 @@ public JoinGroupAlert(final Context context, TLRPC.ChatInvite invite, String gro TextView textView = new TextView(context); textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); - textView.setTextColor(Theme.JOIN_SHEET_NAME_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); textView.setText(title); textView.setSingleLine(true); textView.setEllipsize(TextUtils.TruncateAt.END); @@ -94,7 +93,7 @@ public JoinGroupAlert(final Context context, TLRPC.ChatInvite invite, String gro if (participants_count > 0) { textView = new TextView(context); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(Theme.JOIN_SHEET_COUNT_TEXT_COLOR); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextGray3)); textView.setSingleLine(true); textView.setEllipsize(TextUtils.TruncateAt.END); textView.setText(LocaleController.formatPluralString("Members", participants_count)); @@ -110,7 +109,7 @@ public JoinGroupAlert(final Context context, TLRPC.ChatInvite invite, String gro listView.setHorizontalScrollBarEnabled(false); listView.setVerticalScrollBarEnabled(false); listView.setAdapter(new UsersAdapter(context)); - listView.setGlowColor(0x01ffffff); + listView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); linearLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 90, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 0, 0, 0)); } @@ -121,7 +120,7 @@ public JoinGroupAlert(final Context context, TLRPC.ChatInvite invite, String gro PickerBottomLayout pickerBottomLayout = new PickerBottomLayout(context, false); linearLayout.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - pickerBottomLayout.cancelButton.setTextColor(Theme.STICKERS_SHEET_CLOSE_TEXT_COLOR); + pickerBottomLayout.cancelButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); pickerBottomLayout.cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { @Override @@ -132,13 +131,13 @@ public void onClick(View view) { pickerBottomLayout.doneButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); pickerBottomLayout.doneButton.setVisibility(View.VISIBLE); pickerBottomLayout.doneButtonBadgeTextView.setVisibility(View.GONE); - pickerBottomLayout.doneButtonTextView.setTextColor(Theme.STICKERS_SHEET_CLOSE_TEXT_COLOR); + pickerBottomLayout.doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); pickerBottomLayout.doneButtonTextView.setText(LocaleController.getString("JoinGroup", R.string.JoinGroup)); pickerBottomLayout.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dismiss(); - TLRPC.TL_messages_importChatInvite req = new TLRPC.TL_messages_importChatInvite(); + final TLRPC.TL_messages_importChatInvite req = new TLRPC.TL_messages_importChatInvite(); req.hash = hash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override @@ -169,17 +168,7 @@ public void run() { } } } else { - AlertDialog.Builder builder = new AlertDialog.Builder(fragment.getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - if (error.text.startsWith("FLOOD_WAIT")) { - builder.setMessage(LocaleController.getString("FloodWait", R.string.FloodWait)); - } else if (error.text.equals("USERS_TOO_MUCH")) { - builder.setMessage(LocaleController.getString("JoinToGroupErrorFull", R.string.JoinToGroupErrorFull)); - } else { - builder.setMessage(LocaleController.getString("JoinToGroupErrorNotExist", R.string.JoinToGroupErrorNotExist)); - } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - fragment.showDialog(builder.create()); + AlertsCreator.processError(error, fragment, req); } } }); @@ -189,14 +178,7 @@ public void run() { }); } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - - private class UsersAdapter extends RecyclerView.Adapter { + private class UsersAdapter extends RecyclerListView.SelectionAdapter { private Context context; @@ -224,11 +206,16 @@ public long getItemId(int i) { return i; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = new JoinSheetUserCell(context); view.setLayoutParams(new RecyclerView.LayoutParams(AndroidUtilities.dp(100), AndroidUtilities.dp(90))); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java index b4551abdd6e..db1f968305a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java index a8555a6685d..63fe4270e8a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -20,6 +20,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; +import org.telegram.ui.ActionBar.Theme; public class LetterDrawable extends Drawable { @@ -36,13 +37,21 @@ public LetterDrawable() { super(); if (namePaint == null) { - paint.setColor(0xfff0f0f0); + paint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholder)); namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - namePaint.setColor(0xffffffff); + namePaint.setColor(Theme.getColor(Theme.key_sharedMedia_linkPlaceholderText)); } namePaint.setTextSize(AndroidUtilities.dp(28)); } + public void setBackgroundColor(int value) { + paint.setColor(value); + } + + public void setColor(int value) { + namePaint.setColor(value); + } + public void setTitle(String title) { stringBuilder.setLength(0); if (title != null && title.length() > 0) { @@ -59,7 +68,7 @@ public void setTitle(String title) { textHeight = textLayout.getLineBottom(0); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { textLayout = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterSectionsListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterSectionsListView.java deleted file mode 100644 index 74e797303e1..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LetterSectionsListView.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.ListAdapter; -import android.widget.ListView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.FileLog; -import org.telegram.ui.Adapters.BaseSectionsAdapter; - -import java.util.ArrayList; - -public class LetterSectionsListView extends ListView implements AbsListView.OnScrollListener { - - private ArrayList headers = new ArrayList<>(); - private ArrayList headersCache = new ArrayList<>(); - private OnScrollListener mOnScrollListener; - private BaseSectionsAdapter mAdapter; - private int currentFirst = -1; - private int currentVisible = -1; - private int startSection; - private int sectionsCount; - - public LetterSectionsListView(Context context) { - super(context); - super.setOnScrollListener(this); - } - - public LetterSectionsListView(Context context, AttributeSet attrs) { - super(context, attrs); - super.setOnScrollListener(this); - } - - public LetterSectionsListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - super.setOnScrollListener(this); - } - - @Override - public void setAdapter(ListAdapter adapter) { - if (mAdapter == adapter) { - return; - } - headers.clear(); - headersCache.clear(); - if (adapter instanceof BaseSectionsAdapter) { - mAdapter = (BaseSectionsAdapter) adapter; - } else { - mAdapter = null; - } - super.setAdapter(adapter); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (mOnScrollListener != null) { - mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); - } - if (mAdapter == null) { - return; - } - - headersCache.addAll(headers); - headers.clear(); - - if (mAdapter.getCount() == 0) { - return; - } - - if (currentFirst != firstVisibleItem || currentVisible != visibleItemCount) { - currentFirst = firstVisibleItem; - currentVisible = visibleItemCount; - - sectionsCount = 1; - startSection = mAdapter.getSectionForPosition(firstVisibleItem); - int itemNum = firstVisibleItem + mAdapter.getCountForSection(startSection) - mAdapter.getPositionInSectionForPosition(firstVisibleItem); - while (true) { - if (itemNum >= firstVisibleItem + visibleItemCount) { - break; - } - itemNum += mAdapter.getCountForSection(startSection + sectionsCount); - sectionsCount++; - } - } - - int itemNum = firstVisibleItem; - for (int a = startSection; a < startSection + sectionsCount; a++) { - View header = null; - if (!headersCache.isEmpty()) { - header = headersCache.get(0); - headersCache.remove(0); - } - header = getSectionHeaderView(a, header); - headers.add(header); - int count = mAdapter.getCountForSection(a); - if (a == startSection) { - int pos = mAdapter.getPositionInSectionForPosition(itemNum); - if (pos == count - 1) { - header.setTag(-header.getHeight()); - } else if (pos == count - 2) { - View child = getChildAt(itemNum - firstVisibleItem); - int headerTop; - if (child != null) { - headerTop = child.getTop(); - } else { - headerTop = -AndroidUtilities.dp(100); - } - if (headerTop < 0) { - header.setTag(headerTop); - } else { - header.setTag(0); - } - } else { - header.setTag(0); - } - itemNum += count - mAdapter.getPositionInSectionForPosition(firstVisibleItem); - } else { - View child = getChildAt(itemNum - firstVisibleItem); - if (child != null) { - header.setTag(child.getTop()); - } else { - header.setTag(-AndroidUtilities.dp(100)); - } - itemNum += count; - } - } - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (mOnScrollListener != null) { - mOnScrollListener.onScrollStateChanged(view, scrollState); - } - } - - private View getSectionHeaderView(int section, View oldView) { - boolean shouldLayout = oldView == null; - View view = mAdapter.getSectionHeaderView(section, oldView, this); - if (shouldLayout) { - ensurePinnedHeaderLayout(view, false); - } - return view; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mAdapter == null || headers.isEmpty()) { - return; - } - for (View header : headers) { - ensurePinnedHeaderLayout(header, true); - } - } - - private void ensurePinnedHeaderLayout(View header, boolean forceLayout) { - if (header.isLayoutRequested() || forceLayout) { - ViewGroup.LayoutParams layoutParams = header.getLayoutParams(); - int heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); - int widthSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); - try { - header.measure(widthSpec, heightSpec); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (mAdapter == null || headers.isEmpty()) { - return; - } - for (View header : headers) { - int saveCount = canvas.save(); - int top = (Integer)header.getTag(); - canvas.translate(LocaleController.isRTL ? getWidth() - header.getWidth() : 0, top); - canvas.clipRect(0, 0, getWidth(), header.getMeasuredHeight()); - if (top < 0) { - canvas.saveLayerAlpha(0, top, header.getWidth(), top + canvas.getHeight(), (int)(255 * (1.0f + (float)top / (float)header.getMeasuredHeight())), Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); - } - header.draw(canvas); - canvas.restoreToCount(saveCount); - } - } - - @Override - public void setOnScrollListener(OnScrollListener l) { - mOnScrollListener = l; - } - - public void setOnItemClickListener(LetterSectionsListView.OnItemClickListener listener) { - super.setOnItemClickListener(listener); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java index 70961a7af1e..2e6f37cfa88 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LineProgressView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -26,7 +26,7 @@ public class LineProgressView extends View { private float animatedAlphaValue = 1.0f; private int backColor; - private int progressColor = 0xff36a2ee; + private int progressColor; private static DecelerateInterpolator decelerateInterpolator = null; private static Paint progressPaint = null; @@ -37,7 +37,6 @@ public LineProgressView(Context context) { if (decelerateInterpolator == null) { decelerateInterpolator = new DecelerateInterpolator(); progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - progressPaint.setStyle(Paint.Style.STROKE); progressPaint.setStrokeCap(Paint.Cap.ROUND); progressPaint.setStrokeWidth(AndroidUtilities.dp(2)); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java index f7203005e52..25722586062 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LinkPath.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -46,6 +46,6 @@ public void addRect(float left, float top, float right, float bottom, Direction if (left < lineLeft) { left = lineLeft; } - super.addRect(left, top, right, bottom, dir); + super.addRect(left, top, right, bottom - (bottom != currentLayout.getHeight() ? currentLayout.getSpacingAdd() : 0), dir); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MapPlaceholderDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MapPlaceholderDrawable.java index e9ffa4b7067..3d2ad33f54e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MapPlaceholderDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MapPlaceholderDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java index 10b9309ae05..92fcbe990c5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberPicker.java @@ -22,7 +22,6 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; -import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.SparseArray; @@ -38,7 +37,7 @@ import android.widget.LinearLayout; import android.widget.TextView; -import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; import java.util.Locale; @@ -76,7 +75,6 @@ public class NumberPicker extends LinearLayout { private final SparseArray mSelectorIndexToStringCache = new SparseArray<>(); private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT]; private Paint mSelectorWheelPaint; - private Drawable mVirtualButtonPressedDrawable; private int mSelectorElementHeight; private int mInitialScrollOffset = Integer.MIN_VALUE; private int mCurrentScrollOffset; @@ -93,7 +91,7 @@ public class NumberPicker extends LinearLayout { private int mMaximumFlingVelocity; private boolean mWrapSelectorWheel; private int mSolidColor; - private Drawable mSelectionDivider; + private Paint mSelectionDivider; private int mSelectionDividerHeight; private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; private boolean mIngonreMoveEvents; @@ -123,7 +121,8 @@ public interface Formatter { private void init() { mSolidColor = 0; - mSelectionDivider = getResources().getDrawable(R.drawable.numberpicker_selection_divider); + mSelectionDivider = new Paint(); + mSelectionDivider.setColor(Theme.getColor(Theme.key_dialogButton)); mSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, getResources().getDisplayMetrics()); mSelectionDividersDistance = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE, getResources().getDisplayMetrics()); @@ -144,19 +143,17 @@ private void init() { mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED); - mVirtualButtonPressedDrawable = getResources().getDrawable(R.drawable.item_background_holo_light); - mPressedStateHelper = new PressedStateHelper(); setWillNotDraw(false); mInputText = new TextView(getContext()); - addView(mInputText); - mInputText.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); mInputText.setGravity(Gravity.CENTER); mInputText.setSingleLine(true); + mInputText.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); mInputText.setBackgroundResource(0); - mInputText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); + mInputText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + addView(mInputText, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); @@ -651,19 +648,6 @@ protected void onDraw(Canvas canvas) { float x = (getRight() - getLeft()) / 2; float y = mCurrentScrollOffset; - if (mVirtualButtonPressedDrawable != null && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) { - if (mDecrementVirtualButtonPressed) { - mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); - mVirtualButtonPressedDrawable.setBounds(0, 0, getRight(), mTopSelectionDividerTop); - mVirtualButtonPressedDrawable.draw(canvas); - } - if (mIncrementVirtualButtonPressed) { - mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET); - mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, getRight(), getBottom()); - mVirtualButtonPressedDrawable.draw(canvas); - } - } - // draw the selector wheel int[] selectorIndices = mSelectorIndices; for (int i = 0; i < selectorIndices.length; i++) { @@ -680,20 +664,13 @@ protected void onDraw(Canvas canvas) { y += mSelectorElementHeight; } - // draw the selection dividers - if (mSelectionDivider != null) { - // draw the top divider - int topOfTopDivider = mTopSelectionDividerTop; - int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight; - mSelectionDivider.setBounds(0, topOfTopDivider, getRight(), bottomOfTopDivider); - mSelectionDivider.draw(canvas); + int topOfTopDivider = mTopSelectionDividerTop; + int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight; + canvas.drawRect(0, topOfTopDivider, getRight(), bottomOfTopDivider, mSelectionDivider); - // draw the bottom divider - int bottomOfBottomDivider = mBottomSelectionDividerBottom; - int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight; - mSelectionDivider.setBounds(0, topOfBottomDivider, getRight(), bottomOfBottomDivider); - mSelectionDivider.draw(canvas); - } + int bottomOfBottomDivider = mBottomSelectionDividerBottom; + int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight; + canvas.drawRect(0, topOfBottomDivider, getRight(), bottomOfBottomDivider, mSelectionDivider); } private int makeMeasureSpec(int measureSpec, int maxSize) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberTextView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberTextView.java index 991b3448cce..99eb71888df 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberTextView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/NumberTextView.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Canvas; @@ -20,7 +21,6 @@ import android.view.View; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import java.util.ArrayList; import java.util.Locale; @@ -80,7 +80,7 @@ public void setNumber(int number, boolean animated) { if (animated && !oldLetters.isEmpty()) { animator = ObjectAnimator.ofFloat(this, "progress", forwardAnimation ? -1 : 1, 0); animator.setDuration(150); - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { animator = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PagerSlidingTabStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PagerSlidingTabStrip.java index 8e15564a850..336f36a7fd1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PagerSlidingTabStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PagerSlidingTabStrip.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -12,6 +12,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.graphics.drawable.Drawable; import android.os.Build; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; @@ -27,7 +28,7 @@ public class PagerSlidingTabStrip extends HorizontalScrollView { public interface IconTabProvider { - int getPageIconResId(int position); + Drawable getPageIconDrawable(int position); void customOnDraw(Canvas canvas, int position); } @@ -95,7 +96,7 @@ public void notifyDataSetChanged() { tabCount = pager.getAdapter().getCount(); for (int i = 0; i < tabCount; i++) { if (pager.getAdapter() instanceof IconTabProvider) { - addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i)); + addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconDrawable(i)); } } updateTabStyles(); @@ -113,7 +114,7 @@ public void onGlobalLayout() { }); } - private void addIconTab(final int position, int resId) { + private void addIconTab(final int position, Drawable drawable) { ImageView tab = new ImageView(getContext()) { @Override protected void onDraw(Canvas canvas) { @@ -124,7 +125,7 @@ protected void onDraw(Canvas canvas) { } }; tab.setFocusable(true); - tab.setImageResource(resId); + tab.setImageDrawable(drawable); tab.setScaleType(ImageView.ScaleType.CENTER); tab.setOnClickListener(new OnClickListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java index b7fcbf0c266..5071be08c04 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/RenderView.java @@ -299,14 +299,14 @@ private boolean initGL() { eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - FileLog.e("tmessages", "eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } int[] version = new int[2]; if (!egl10.eglInitialize(eglDisplay, version)) { - FileLog.e("tmessages", "eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -324,13 +324,13 @@ private boolean initGL() { EGL10.EGL_NONE }; if (!egl10.eglChooseConfig(eglDisplay, configSpec, configs, 1, configsCount)) { - FileLog.e("tmessages", "eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } else if (configsCount[0] > 0) { eglConfig = configs[0]; } else { - FileLog.e("tmessages", "eglConfig not initialized"); + FileLog.e("eglConfig not initialized"); finish(); return false; } @@ -338,7 +338,7 @@ private boolean initGL() { int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); if (eglContext == null) { - FileLog.e("tmessages", "eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -351,12 +351,12 @@ private boolean initGL() { } if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { - FileLog.e("tmessages", "createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - FileLog.e("tmessages", "eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -519,7 +519,7 @@ public void run() { }); semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return object[0]; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shader.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shader.java index 6282ca703b7..a513e525ef5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shader.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Shader.java @@ -22,14 +22,14 @@ public Shader(String vertexShader, String fragmentShader, String attributes[], S CompilationResult vResult = compileShader(GLES20.GL_VERTEX_SHADER, vertexShader); if (vResult.status == GLES20.GL_FALSE) { - FileLog.e("tmessages", "Vertex shader compilation failed"); + FileLog.e("Vertex shader compilation failed"); destroyShader(vResult.shader, 0, program); return; } CompilationResult fResult = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShader); if (fResult.status == GLES20.GL_FALSE) { - FileLog.e("tmessages", "Fragment shader compilation failed"); + FileLog.e("Fragment shader compilation failed"); destroyShader(vResult.shader, fResult.shader, program); return; } @@ -88,7 +88,7 @@ private CompilationResult compileShader(int type, String shaderCode) { int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] == GLES20.GL_FALSE) { - FileLog.e("tmessages", GLES20.glGetShaderInfoLog(shader)); + FileLog.e(GLES20.glGetShaderInfoLog(shader)); } return new CompilationResult(shader, compileStatus[0]); @@ -100,7 +100,7 @@ private int linkProgram(int program) { int[] linkStatus = new int[1]; GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] == GLES20.GL_FALSE) { - FileLog.e("tmessages", GLES20.glGetProgramInfoLog(program)); + FileLog.e(GLES20.glGetProgramInfoLog(program)); } return linkStatus[0]; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Slice.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Slice.java index eaf21be21de..5546900465e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Slice.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Paint/Slice.java @@ -28,7 +28,7 @@ public Slice(final ByteBuffer data, RectF rect, DispatchQueue queue) { File outputDir = ApplicationLoader.applicationContext.getCacheDir(); file = File.createTempFile("paint", ".bin", outputDir); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (file == null) @@ -62,7 +62,7 @@ private void storeData(ByteBuffer data) { fos.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -101,7 +101,7 @@ else if (inflater.needsInput()) { return result; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java index 3b68e7110bc..39ef8ba55fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PasscodeView.java @@ -3,16 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; @@ -24,7 +24,6 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Vibrator; -import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import android.support.v4.os.CancellationSignal; import android.text.Editable; import android.text.InputFilter; @@ -54,7 +53,9 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; -import org.telegram.messenger.AnimatorListenerAdapterProxy; +import org.telegram.messenger.support.fingerprint.FingerprintManagerCompat; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; import java.util.ArrayList; import java.util.Locale; @@ -125,7 +126,7 @@ public void appendCharacter(String c) { try { performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } @@ -186,7 +187,7 @@ public void run() { currentAnimation = new AnimatorSet(); currentAnimation.setDuration(150); currentAnimation.playTogether(animators); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentAnimation != null && currentAnimation.equals(animation)) { @@ -220,7 +221,7 @@ public void onAnimationEnd(Animator animation) { currentAnimation = new AnimatorSet(); currentAnimation.setDuration(150); currentAnimation.playTogether(animators); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentAnimation != null && currentAnimation.equals(animation)) { @@ -246,7 +247,7 @@ public void eraseLastCharacter() { try { performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } ArrayList animators = new ArrayList<>(); @@ -297,7 +298,7 @@ public void eraseLastCharacter() { currentAnimation = new AnimatorSet(); currentAnimation.setDuration(150); currentAnimation.playTogether(animators); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentAnimation != null && currentAnimation.equals(animation)) { @@ -343,7 +344,7 @@ private void eraseAllCharacters(final boolean animated) { currentAnimation = new AnimatorSet(); currentAnimation.setDuration(150); currentAnimation.playTogether(animators); - currentAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentAnimation != null && currentAnimation.equals(animation)) { @@ -756,7 +757,7 @@ private void processDone(boolean fingerprint) { AnimatorSet.playTogether( ObjectAnimator.ofFloat(this, "translationY", AndroidUtilities.dp(20)), ObjectAnimator.ofFloat(this, "alpha", AndroidUtilities.dp(0.0f))); - AnimatorSet.addListener(new AnimatorListenerAdapterProxy() { + AnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { setVisibility(View.GONE); @@ -780,7 +781,7 @@ private void shakeTextView(final float x, final int num) { AnimatorSet AnimatorSet = new AnimatorSet(); AnimatorSet.playTogether(ObjectAnimator.ofFloat(passcodeTextView, "translationX", AndroidUtilities.dp(x))); AnimatorSet.setDuration(50); - AnimatorSet.addListener(new AnimatorListenerAdapterProxy() { + AnimatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { shakeTextView(num == 5 ? 0 : -x, num + 1); @@ -824,7 +825,7 @@ public void onPause() { } fingerprintDialog = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } try { @@ -833,7 +834,7 @@ public void onPause() { cancellationSignal = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -845,13 +846,13 @@ private void checkFingerprint() { return; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(ApplicationLoader.applicationContext); if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) { RelativeLayout relativeLayout = new RelativeLayout(getContext()); - relativeLayout.setPadding(AndroidUtilities.dp(24), AndroidUtilities.dp(16), AndroidUtilities.dp(24), AndroidUtilities.dp(8)); + relativeLayout.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); TextView fingerprintTextView = new TextView(getContext()); fingerprintTextView.setTextColor(0xff939393); @@ -902,7 +903,7 @@ public void onDismiss(DialogInterface dialog) { fingerprintDialog.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } fingerprintDialog = builder.show(); @@ -934,7 +935,7 @@ public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationRes fingerprintDialog.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fingerprintDialog = null; processDone(true); @@ -974,7 +975,7 @@ public void onShow() { if (selectedBackground == 1000001) { backgroundFrameLayout.setBackgroundColor(0xff517c9e); } else { - backgroundDrawable = ApplicationLoader.getCachedWallpaper(); + backgroundDrawable = Theme.getCachedWallpaper(); if (backgroundDrawable != null) { backgroundFrameLayout.setBackgroundColor(0xbf000000); } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java index b65f90ce182..569e7dd562a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoCropView.java @@ -3,36 +3,32 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.RectF; import android.os.Build; -import android.view.MotionEvent; +import android.view.Gravity; import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.FileLog; +import org.telegram.ui.Components.Crop.CropRotationWheel; +import org.telegram.ui.Components.Crop.CropView; public class PhotoCropView extends FrameLayout { public interface PhotoCropViewDelegate { void needMoveImageTo(float x, float y, float s, boolean animated); Bitmap getBitmap(); + + void onChange(boolean reset); } private boolean freeformCrop = true; - private Paint rectPaint; - private Paint circlePaint; - private Paint halfPaint; - private Paint shadowPaint; private float rectSizeX = 600; private float rectSizeY = 600; private int draggingState = 0; @@ -45,25 +41,17 @@ public interface PhotoCropViewDelegate { private float bitmapGlobalY = 0; private PhotoCropViewDelegate delegate; private Bitmap bitmapToEdit; + private boolean showOnSetBitmap; private RectF animationStartValues; private RectF animationEndValues; private Runnable animationRunnable; + private CropView cropView; + private CropRotationWheel wheelView; + public PhotoCropView(Context context) { super(context); - - rectPaint = new Paint(); - rectPaint.setColor(0xb2ffffff); - rectPaint.setStrokeWidth(AndroidUtilities.dp(2)); - rectPaint.setStyle(Paint.Style.STROKE); - circlePaint = new Paint(); - circlePaint.setColor(0xffffffff); - halfPaint = new Paint(); - halfPaint.setColor(0x7f000000); - shadowPaint = new Paint(); - shadowPaint.setColor(0x1a000000); - setWillNotDraw(false); } public void setBitmap(Bitmap bitmap, int rotation, boolean freeform) { @@ -80,6 +68,69 @@ public void setBitmap(Bitmap bitmap, int rotation, boolean freeform) { freeformCrop = freeform; orientation = rotation; requestLayout(); + + if (cropView == null) { + cropView = new CropView(getContext()); + cropView.setListener(new CropView.CropViewListener() { + @Override + public void onChange(boolean reset) { + if (delegate != null) { + delegate.onChange(reset); + } + } + + @Override + public void onAspectLock(boolean enabled) { + wheelView.setAspectLock(enabled); + } + }); + cropView.setBottomPadding(AndroidUtilities.dp(64)); + addView(cropView); + + wheelView = new CropRotationWheel(getContext()); + wheelView.setListener(new CropRotationWheel.RotationWheelListener() { + @Override + public void onStart() { + cropView.onRotationBegan(); + } + + @Override + public void onChange(float angle) { + cropView.setRotation(angle); + if (delegate != null) { + delegate.onChange(false); + } + } + + @Override + public void onEnd(float angle) { + cropView.onRotationEnded(); + } + + @Override + public void aspectRatioPressed() { + cropView.showAspectRatioDialog(); + } + + @Override + public void rotate90Pressed() { + wheelView.reset(); + cropView.rotate90Degrees(); + } + }); + addView(wheelView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER | Gravity.BOTTOM, 0, 0, 0, 0)); + } + + cropView.setVisibility(VISIBLE); + cropView.setBitmap(bitmap, rotation, freeform); + + if (showOnSetBitmap) { + showOnSetBitmap = false; + cropView.show(); + } + + wheelView.setFreeform(freeform); + wheelView.reset(); } public void setOrientation(int rotation) { @@ -92,247 +143,47 @@ public void setOrientation(int rotation) { requestLayout(); } - public boolean onTouch(MotionEvent motionEvent) { - if (motionEvent == null) { - draggingState = 0; - return false; + public boolean isReady() { + return cropView.isReady(); + } + + public void reset() { + wheelView.reset(); + cropView.reset(); + } + + public void onAppear() { + if (cropView != null) { + cropView.willShow(); } - float x = motionEvent.getX(); - float y = motionEvent.getY(); - int cornerSide = AndroidUtilities.dp(20); - if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) { - if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 1; - } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 2; - } else if (rectX - cornerSide < x && rectX + cornerSide > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 3; - } else if (rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 4; - } else { - if (freeformCrop) { - if (rectX + cornerSide < x && rectX - cornerSide + rectSizeX > x && rectY - cornerSide < y && rectY + cornerSide > y) { - draggingState = 5; - } else if (rectY + cornerSide < y && rectY - cornerSide + rectSizeY > y && rectX - cornerSide + rectSizeX < x && rectX + cornerSide + rectSizeX > x) { - draggingState = 6; - } else if (rectY + cornerSide < y && rectY - cornerSide + rectSizeY > y && rectX - cornerSide < x && rectX + cornerSide > x) { - draggingState = 7; - } else if (rectX + cornerSide < x && rectX - cornerSide + rectSizeX > x && rectY - cornerSide + rectSizeY < y && rectY + cornerSide + rectSizeY > y) { - draggingState = 8; - } - } else { - draggingState = 0; - } - } - if (draggingState != 0) { - cancelAnimationRunnable(); - PhotoCropView.this.requestDisallowInterceptTouchEvent(true); - } - oldX = x; - oldY = y; - } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) { - if (draggingState != 0) { - draggingState = 0; - startAnimationRunnable(); - return true; - } - } else if (motionEvent.getAction() == MotionEvent.ACTION_MOVE && draggingState != 0) { - float diffX = x - oldX; - float diffY = y - oldY; - float bitmapScaledWidth = bitmapWidth * bitmapGlobalScale; - float bitmapScaledHeight = bitmapHeight * bitmapGlobalScale; - float additionalY = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - float bitmapStartX = (getWidth() - bitmapScaledWidth) / 2 + bitmapGlobalX; - float bitmapStartY = (getHeight() - bitmapScaledHeight + additionalY) / 2 + bitmapGlobalY; - float bitmapEndX = bitmapStartX + bitmapScaledWidth; - float bitmapEndY = bitmapStartY + bitmapScaledHeight; - - float minSide = AndroidUtilities.getPixelsInCM(0.9f, true); - - if (draggingState == 1 || draggingState == 5) { - if (draggingState != 5) { - if (rectSizeX - diffX < minSide) { - diffX = rectSizeX - minSide; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectX + diffX < bitmapStartX) { - bitmapGlobalX -= bitmapStartX - rectX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - } - if (!freeformCrop) { - if (rectY + diffX < bitmapY) { - diffX = bitmapY - rectY; - } - if (rectY + diffX < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectX += diffX; - rectY += diffX; - rectSizeX -= diffX; - rectSizeY -= diffX; - } else { - if (rectSizeY - diffY < minSide) { - diffY = rectSizeY - minSide; - } - if (rectY + diffY < bitmapY) { - diffY = bitmapY - rectY; - } - if (rectY + diffY < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (draggingState != 5) { - rectX += diffX; - rectSizeX -= diffX; - } - rectY += diffY; - rectSizeY -= diffY; - } - } else if (draggingState == 2 || draggingState == 6) { - if (rectSizeX + diffX < minSide) { - diffX = -(rectSizeX - minSide); - } - if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSizeX; - } - if (rectX + rectSizeX + diffX > bitmapEndX) { - bitmapGlobalX -= bitmapEndX - rectX - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (!freeformCrop) { - if (rectY - diffX < bitmapY) { - diffX = rectY - bitmapY; - } - if (rectY - diffX < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY + diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectY -= diffX; - rectSizeX += diffX; - rectSizeY += diffX; - } else { - if (draggingState != 6) { - if (rectSizeY - diffY < minSide) { - diffY = rectSizeY - minSide; - } - if (rectY + diffY < bitmapY) { - diffY = bitmapY - rectY; - } - if (rectY + diffY < bitmapStartY) { - bitmapGlobalY -= bitmapStartY - rectY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectY += diffY; - rectSizeY -= diffY; - } - rectSizeX += diffX; - } - } else if (draggingState == 3 || draggingState == 7) { - if (rectSizeX - diffX < minSide) { - diffX = rectSizeX - minSide; - } - if (rectX + diffX < bitmapX) { - diffX = bitmapX - rectX; - } - if (rectX + diffX < bitmapStartX) { - bitmapGlobalX -= bitmapStartX - rectX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (!freeformCrop) { - if (rectY + rectSizeX - diffX > bitmapY + bitmapHeight) { - diffX = rectY + rectSizeX - bitmapY - bitmapHeight; - } - if (rectY + rectSizeX - diffX > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeX + diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectX += diffX; - rectSizeX -= diffX; - rectSizeY -= diffX; - } else { - if (draggingState != 7) { - if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { - diffY = bitmapY + bitmapHeight - rectY - rectSizeY; - } - if (rectY + rectSizeY + diffY > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectSizeY += diffY; - if (rectSizeY < minSide) { - rectSizeY = minSide; - } - } - rectX += diffX; - rectSizeX -= diffX; - } - } else if (draggingState == 4 || draggingState == 8) { - if (draggingState != 8) { - if (rectX + rectSizeX + diffX > bitmapX + bitmapWidth) { - diffX = bitmapX + bitmapWidth - rectX - rectSizeX; - } - if (rectX + rectSizeX + diffX > bitmapEndX) { - bitmapGlobalX -= bitmapEndX - rectX - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - } - if (!freeformCrop) { - if (rectY + rectSizeX + diffX > bitmapY + bitmapHeight) { - diffX = bitmapY + bitmapHeight - rectY - rectSizeX; - } - if (rectY + rectSizeX + diffX > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeX - diffX; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - rectSizeX += diffX; - rectSizeY += diffX; - } else { - if (rectY + rectSizeY + diffY > bitmapY + bitmapHeight) { - diffY = bitmapY + bitmapHeight - rectY - rectSizeY; - } - if (rectY + rectSizeY + diffY > bitmapEndY) { - bitmapGlobalY -= bitmapEndY - rectY - rectSizeY - diffY; - delegate.needMoveImageTo(bitmapGlobalX, bitmapGlobalY, bitmapGlobalScale, false); - } - if (draggingState != 8) { - rectSizeX += diffX; - } - rectSizeY += diffY; - } - if (rectSizeX < minSide) { - rectSizeX = minSide; - } - if (rectSizeY < minSide) { - rectSizeY = minSide; - } - } + } - oldX = x; - oldY = y; - invalidate(); + public void onAppeared() { + if (cropView != null) { + cropView.show(); + } else { + showOnSetBitmap = true; } - return draggingState != 0; + } + + public void onDisappear() { + cropView.hide(); } public float getRectX() { - return rectX - AndroidUtilities.dp(14); + return cropView.getCropLeft() - AndroidUtilities.dp(14); } public float getRectY() { - float additionalY = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - return rectY - AndroidUtilities.dp(14) - additionalY; + return cropView.getCropTop() - AndroidUtilities.dp(14) - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); } public float getRectSizeX() { - return rectSizeX; + return cropView.getCropWidth(); } public float getRectSizeY() { - return rectSizeY; + return cropView.getCropHeight(); } public float getBitmapX() { @@ -362,124 +213,13 @@ public float getLimitHeight() { return getHeight() - AndroidUtilities.dp(14) - additionalY - rectY - (int) Math.max(0, Math.ceil((getHeight() - AndroidUtilities.dp(28) - bitmapHeight * bitmapGlobalScale - additionalY) / 2)) - rectSizeY; } - private Bitmap createBitmap(int x, int y, int w, int h) { - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); - - Matrix matrix = new Matrix(); - matrix.setTranslate(-bitmapToEdit.getWidth() / 2, -bitmapToEdit.getHeight() / 2); - matrix.postRotate(orientation); - if (orientation % 360 == 90 || orientation % 360 == 270) { - matrix.postTranslate(bitmapToEdit.getHeight() / 2 - x, bitmapToEdit.getWidth() / 2 - y); - } else { - matrix.postTranslate(bitmapToEdit.getWidth() / 2 - x, bitmapToEdit.getHeight() / 2 - y); - } - canvas.drawBitmap(bitmapToEdit, matrix, paint); - try { - canvas.setBitmap(null); - } catch (Exception e) { - //don't promt, this will crash on 2.x - } - - return bitmap; - } - public Bitmap getBitmap() { - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - float bitmapScaledWidth = bitmapWidth * bitmapGlobalScale; - float bitmapScaledHeight = bitmapHeight * bitmapGlobalScale; - float additionalY = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - float bitmapStartX = (getWidth() - bitmapScaledWidth) / 2 + bitmapGlobalX; - float bitmapStartY = (getHeight() - bitmapScaledHeight + additionalY) / 2 + bitmapGlobalY; - - float percX = (rectX - bitmapStartX) / bitmapScaledWidth; - float percY = (rectY - bitmapStartY) / bitmapScaledHeight; - float percSizeX = rectSizeX / bitmapScaledWidth; - float percSizeY = rectSizeY / bitmapScaledHeight; - - int width; - int height; - if (orientation % 360 == 90 || orientation % 360 == 270) { - width = bitmapToEdit.getHeight(); - height = bitmapToEdit.getWidth(); - } else { - width = bitmapToEdit.getWidth(); - height = bitmapToEdit.getHeight(); - } - - int x = (int) (percX * width); - int y = (int) (percY * height); - int sizeX = (int) (percSizeX * width); - int sizeY = (int) (percSizeY * height); - if (x < 0) { - x = 0; - } - if (y < 0) { - y = 0; - } - if (x + sizeX > width) { - sizeX = width - x; - } - if (y + sizeY > height) { - sizeY = height - y; - } - try { - return createBitmap(x, y, sizeX, sizeY); - } catch (Throwable e) { - FileLog.e("tmessags", e); - System.gc(); - try { - return createBitmap(x, y, sizeX, sizeY); - } catch (Throwable e2) { - FileLog.e("tmessages", e2); - } + if (cropView != null) { + return cropView.getResult(); } return null; } - @Override - protected void onDraw(Canvas canvas) { - canvas.drawRect(0, 0, getWidth(), rectY, halfPaint); - canvas.drawRect(0, rectY, rectX, rectY + rectSizeY, halfPaint); - canvas.drawRect(rectX + rectSizeX, rectY, getWidth(), rectY + rectSizeY, halfPaint); - canvas.drawRect(0, rectY + rectSizeY, getWidth(), getHeight(), halfPaint); - - int side = AndroidUtilities.dp(1); - canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX - side * 2 + AndroidUtilities.dp(20), rectY, circlePaint); - canvas.drawRect(rectX - side * 2, rectY - side * 2, rectX, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); - - canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY - side * 2, rectX + rectSizeX + side * 2, rectY, circlePaint); - canvas.drawRect(rectX + rectSizeX, rectY - side * 2, rectX + rectSizeX + side * 2, rectY - side * 2 + AndroidUtilities.dp(20), circlePaint); - - canvas.drawRect(rectX - side * 2, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX, rectY + rectSizeY + side * 2, circlePaint); - canvas.drawRect(rectX - side * 2, rectY + rectSizeY, rectX - side * 2 + AndroidUtilities.dp(20), rectY + rectSizeY + side * 2, circlePaint); - - canvas.drawRect(rectX + rectSizeX + side * 2 - AndroidUtilities.dp(20), rectY + rectSizeY, rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); - canvas.drawRect(rectX + rectSizeX, rectY + rectSizeY + side * 2 - AndroidUtilities.dp(20), rectX + rectSizeX + side * 2, rectY + rectSizeY + side * 2, circlePaint); - - for (int a = 1; a < 3; a++) { - canvas.drawRect(rectX + rectSizeX / 3 * a - side, rectY, rectX + side * 2 + rectSizeX / 3 * a, rectY + rectSizeY, shadowPaint); - canvas.drawRect(rectX, rectY + rectSizeY / 3 * a - side, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side * 2, shadowPaint); - } - - for (int a = 1; a < 3; a++) { - canvas.drawRect(rectX + rectSizeX / 3 * a, rectY, rectX + side + rectSizeX / 3 * a, rectY + rectSizeY, circlePaint); - canvas.drawRect(rectX, rectY + rectSizeY / 3 * a, rectX + rectSizeX, rectY + rectSizeY / 3 * a + side, circlePaint); - } - - canvas.drawRect(rectX, rectY, rectX + rectSizeX, rectY + rectSizeY, rectPaint); - } - public void setBitmapParams(float scale, float x, float y) { bitmapGlobalScale = scale; bitmapGlobalX = x; @@ -561,71 +301,13 @@ public void setDelegate(PhotoCropViewDelegate delegate) { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - Bitmap newBimap = delegate.getBitmap(); - if (newBimap != null) { - bitmapToEdit = newBimap; - } - - if (bitmapToEdit == null) { - return; + Bitmap newBitmap = delegate.getBitmap(); + if (newBitmap != null) { + bitmapToEdit = newBitmap; } - int viewWidth = getWidth() - AndroidUtilities.dp(28); - int viewHeight = getHeight() - AndroidUtilities.dp(28) - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - - float bitmapW; - float bitmapH; - if (orientation % 360 == 90 || orientation % 360 == 270) { - bitmapW = bitmapToEdit.getHeight(); - bitmapH = bitmapToEdit.getWidth(); - } else { - bitmapW = bitmapToEdit.getWidth(); - bitmapH = bitmapToEdit.getHeight(); - } - float scaleX = viewWidth / bitmapW; - float scaleY = viewHeight / bitmapH; - if (scaleX > scaleY) { - bitmapH = viewHeight; - bitmapW = (int) Math.ceil(bitmapW * scaleY); - } else { - bitmapW = viewWidth; - bitmapH = (int) Math.ceil(bitmapH * scaleX); - } - - float percX = (rectX - bitmapX) / bitmapWidth; - float percY = (rectY - bitmapY) / bitmapHeight; - float percSizeX = rectSizeX / bitmapWidth; - float percSizeY = rectSizeY / bitmapHeight; - bitmapWidth = (int) bitmapW; - bitmapHeight = (int) bitmapH; - - bitmapX = (int) Math.ceil((viewWidth - bitmapWidth) / 2 + AndroidUtilities.dp(14)); - bitmapY = (int) Math.ceil((viewHeight - bitmapHeight) / 2 + AndroidUtilities.dp(14) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)); - - if (rectX == -1 && rectY == -1) { - if (freeformCrop) { - rectY = bitmapY; - rectX = bitmapX; - rectSizeX = bitmapWidth; - rectSizeY = bitmapHeight; - } else { - if (bitmapWidth > bitmapHeight) { - rectY = bitmapY; - rectX = (viewWidth - bitmapHeight) / 2 + AndroidUtilities.dp(14); - rectSizeX = bitmapHeight; - rectSizeY = bitmapHeight; - } else { - rectX = bitmapX; - rectY = (viewHeight - bitmapWidth) / 2 + AndroidUtilities.dp(14) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); - rectSizeX = bitmapWidth; - rectSizeY = bitmapWidth; - } - } - } else { - rectX = percX * bitmapWidth + bitmapX; - rectY = percY * bitmapHeight + bitmapY; - rectSizeX = percSizeX * bitmapWidth; - rectSizeY = percSizeY * bitmapHeight; + if (cropView != null) { + cropView.updateLayout(); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoEditorSeekBar.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoEditorSeekBar.java index 9f3e4ae493c..cf0f28a5137 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoEditorSeekBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoEditorSeekBar.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterBlurControl.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterBlurControl.java index 1c81798dada..141a699f664 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterBlurControl.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterBlurControl.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -12,6 +12,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; +import android.os.Build; import android.view.MotionEvent; import android.widget.FrameLayout; @@ -82,6 +83,7 @@ public PhotoFilterBlurControl(Context context) { public void setType(int blurType) { type = blurType; + invalidate(); } public void setDelegate(PhotoFilterLinearBlurControlDelegate delegate) { @@ -486,7 +488,7 @@ protected void onDraw(Canvas canvas) { } private Point getActualCenterPoint() { - return new Point((getWidth() - actualAreaSize.width) / 2 + centerPoint.x * actualAreaSize.width, (getHeight() - actualAreaSize.height) / 2 - (actualAreaSize.width - actualAreaSize.height) / 2 + centerPoint.y * actualAreaSize.width); + return new Point((getWidth() - actualAreaSize.width) / 2 + centerPoint.x * actualAreaSize.width, (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0) + (getHeight() - actualAreaSize.height) / 2 - (actualAreaSize.width - actualAreaSize.height) / 2 + centerPoint.y * actualAreaSize.width); } private float getActualInnerRadius() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterCurvesControl.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterCurvesControl.java index 8946a24f067..8ae45273d3b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterCurvesControl.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterCurvesControl.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java index 7e913f89295..4ac5e21096f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoFilterView.java @@ -3,18 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.opengl.GLUtils; @@ -39,7 +43,6 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.PhotoEditToolCell; @@ -158,6 +161,8 @@ public class PhotoFilterView extends FrameLayout { private Bitmap bitmapToEdit; private int orientation; + private int toolCellWidth; + public static class CurvesValue { public float blacksLevel = 0.0f; @@ -874,7 +879,7 @@ private int loadShader(int type, String shaderCode) { int[] compileStatus = new int[1]; GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] == 0) { - FileLog.e("tmessages", GLES20.glGetShaderInfoLog(shader)); + FileLog.e(GLES20.glGetShaderInfoLog(shader)); GLES20.glDeleteShader(shader); shader = 0; } @@ -886,14 +891,14 @@ private boolean initGL() { eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - FileLog.e("tmessages", "eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } int[] version = new int[2]; if (!egl10.eglInitialize(eglDisplay, version)) { - FileLog.e("tmessages", "eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -911,13 +916,13 @@ private boolean initGL() { EGL10.EGL_NONE }; if (!egl10.eglChooseConfig(eglDisplay, configSpec, configs, 1, configsCount)) { - FileLog.e("tmessages", "eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } else if (configsCount[0] > 0) { eglConfig = configs[0]; } else { - FileLog.e("tmessages", "eglConfig not initialized"); + FileLog.e("eglConfig not initialized"); finish(); return false; } @@ -925,7 +930,7 @@ private boolean initGL() { int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); if (eglContext == null) { - FileLog.e("tmessages", "eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -938,12 +943,12 @@ private boolean initGL() { } if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { - FileLog.e("tmessages", "createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - FileLog.e("tmessages", "eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); finish(); return false; } @@ -1005,7 +1010,7 @@ private boolean initGL() { GLES20.glGetProgramiv(toolsShaderProgram, GLES20.GL_LINK_STATUS, linkStatus, 0); if (linkStatus[0] == 0) { /*String infoLog = GLES20.glGetProgramInfoLog(toolsShaderProgram); - FileLog.e("tmessages", "link error = " + infoLog);*/ + FileLog.e("link error = " + infoLog);*/ GLES20.glDeleteProgram(toolsShaderProgram); toolsShaderProgram = 0; } else { @@ -1286,7 +1291,7 @@ private void drawEnhancePass() { buffer = ByteBuffer.allocateDirect(PGPhotoEnhanceSegments * PGPhotoEnhanceSegments * PGPhotoEnhanceHistogramBins * 4); Utilities.calcCDT(hsvBuffer, renderBufferWidth, renderBufferHeight, buffer); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } GLES20.glBindTexture(GL10.GL_TEXTURE_2D, enhanceTextures[1]); @@ -1488,7 +1493,7 @@ public void run() { if (!eglContext.equals(egl10.eglGetCurrentContext()) || !eglSurface.equals(egl10.eglGetCurrentSurface(EGL10.EGL_DRAW))) { if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - FileLog.e("tmessages", "eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); return; } } @@ -1546,7 +1551,7 @@ public void run() { }); semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return object[0]; } @@ -1748,7 +1753,7 @@ public void valueChanged() { cancelTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); cancelTextView.setTextColor(0xffffffff); cancelTextView.setGravity(Gravity.CENTER); - cancelTextView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + cancelTextView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); cancelTextView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); cancelTextView.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); cancelTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -1758,17 +1763,20 @@ public void valueChanged() { doneTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); doneTextView.setTextColor(0xff51bdf3); doneTextView.setGravity(Gravity.CENTER); - doneTextView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + doneTextView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); doneTextView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); doneTextView.setText(LocaleController.getString("Done", R.string.Done).toUpperCase()); doneTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); frameLayout.addView(doneTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + toolCellWidth = calculateMaxToolCellWidth(); + recyclerListView = new RecyclerListView(context); LinearLayoutManager layoutManager = new LinearLayoutManager(context); layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); recyclerListView.setLayoutManager(layoutManager); recyclerListView.setClipToPadding(false); + recyclerListView.setPadding(AndroidUtilities.dp(15), 0, 0, 0); recyclerListView.setTag(12); recyclerListView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); recyclerListView.setAdapter(toolsAdapter = new ToolsAdapter(context)); @@ -1843,12 +1851,12 @@ public void onItemClick(View view, int position) { addView(editView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 126, Gravity.LEFT | Gravity.BOTTOM)); frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xff1a1a1a); + frameLayout.setBackgroundColor(0xff000000); editView.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); ImageView imageView = new ImageView(context); imageView.setImageResource(R.drawable.edit_cancel); - imageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + imageView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); imageView.setPadding(AndroidUtilities.dp(22), 0, AndroidUtilities.dp(22), 0); frameLayout.addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); imageView.setOnClickListener(new OnClickListener() { @@ -1895,8 +1903,9 @@ public void onClick(View v) { }); imageView = new ImageView(context); - imageView.setImageResource(R.drawable.edit_doneblue); - imageView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + imageView.setImageResource(R.drawable.edit_done); + imageView.setColorFilter(new PorterDuffColorFilter(0xff51bdf3, PorterDuff.Mode.MULTIPLY)); + imageView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); imageView.setPadding(AndroidUtilities.dp(22), AndroidUtilities.dp(1), AndroidUtilities.dp(22), 0); frameLayout.addView(imageView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); imageView.setOnClickListener(new OnClickListener() { @@ -2126,6 +2135,37 @@ public void onClick(View v) { } } + private int calculateMaxToolCellWidth() { + String titles[] = { + LocaleController.getString("Enhance", R.string.Enhance), + LocaleController.getString("Exposure", R.string.Exposure), + LocaleController.getString("Contrast", R.string.Contrast), + LocaleController.getString("Warmth", R.string.Warmth), + LocaleController.getString("Saturation", R.string.Saturation), + LocaleController.getString("Tint", R.string.Tint), + LocaleController.getString("Fade", R.string.Fade), + LocaleController.getString("Highlights", R.string.Highlights), + LocaleController.getString("Shadows", R.string.Shadows), + LocaleController.getString("Vignette", R.string.Vignette), + LocaleController.getString("Grain", R.string.Grain), + LocaleController.getString("Blur", R.string.Blur), + LocaleController.getString("Sharpen", R.string.Sharpen), + LocaleController.getString("Curves", R.string.Curves) + }; + + Paint paint = new Paint(); + paint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 10, getResources().getDisplayMetrics())); + + float maxWidth = 0; + for (String title : titles) { + maxWidth = Math.max(paint.measureText(title), maxWidth); + } + + return (int)Math.max(AndroidUtilities.dp(56), maxWidth + AndroidUtilities.dp(30)); + } + private void updateSelectedBlurType() { if (blurType == 0) { blurOffButton.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.blur_off_active, 0, 0); @@ -2282,7 +2322,7 @@ public void switchToOrFromEditMode() { animatorSet.playTogether( ObjectAnimator.ofFloat(viewFrom, "translationY", 0, AndroidUtilities.dp(126)) ); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { viewFrom.setVisibility(GONE); @@ -2293,7 +2333,7 @@ public void onAnimationEnd(Animator animation) { animatorSet.playTogether( ObjectAnimator.ofFloat(viewTo, "translationY", 0) ); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (selectedTool == enhanceTool) { @@ -2331,7 +2371,7 @@ private void fixLayout(int viewWidth, int viewHeight) { } viewWidth -= AndroidUtilities.dp(28); - viewHeight -= AndroidUtilities.dp(14 + 140) + AndroidUtilities.statusBarHeight; + viewHeight -= AndroidUtilities.dp(14 + 140) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); float bitmapW; float bitmapH; @@ -2353,14 +2393,14 @@ private void fixLayout(int viewWidth, int viewHeight) { } int bitmapX = (int) Math.ceil((viewWidth - bitmapW) / 2 + AndroidUtilities.dp(14)); - int bitmapY = (int) Math.ceil((viewHeight - bitmapH) / 2 + AndroidUtilities.dp(14) + AndroidUtilities.statusBarHeight); + int bitmapY = (int) Math.ceil((viewHeight - bitmapH) / 2 + AndroidUtilities.dp(14) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)); LayoutParams layoutParams = (LayoutParams) textureView.getLayoutParams(); layoutParams.leftMargin = bitmapX; layoutParams.topMargin = bitmapY; layoutParams.width = (int) bitmapW; layoutParams.height = (int) bitmapH; - curvesControl.setActualArea(bitmapX, bitmapY, layoutParams.width, layoutParams.height); + curvesControl.setActualArea(bitmapX, bitmapY - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0), layoutParams.width, layoutParams.height); blurControl.setActualAreaSize(layoutParams.width, layoutParams.height); layoutParams = (LayoutParams) blurControl.getLayoutParams(); @@ -2483,17 +2523,10 @@ private void checkEnhance() { } } - public class ToolsAdapter extends RecyclerView.Adapter { + public class ToolsAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - public ToolsAdapter(Context context) { mContext = context; } @@ -2510,39 +2543,43 @@ public long getItemId(int i) { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { - PhotoEditToolCell view = new PhotoEditToolCell(mContext); - return new Holder(view); + PhotoEditToolCell view = new PhotoEditToolCell(mContext, toolCellWidth); + return new RecyclerListView.Holder(view); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - Holder holder = (Holder) viewHolder; if (i == enhanceTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_enhance, LocaleController.getString("Enhance", R.string.Enhance), enhanceValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_enhance, LocaleController.getString("Enhance", R.string.Enhance), enhanceValue); } else if (i == highlightsTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_highlights, LocaleController.getString("Highlights", R.string.Highlights), highlightsValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_highlights, LocaleController.getString("Highlights", R.string.Highlights), highlightsValue); } else if (i == contrastTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_contrast, LocaleController.getString("Contrast", R.string.Contrast), contrastValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_contrast, LocaleController.getString("Contrast", R.string.Contrast), contrastValue); } else if (i == exposureTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_brightness, LocaleController.getString("Exposure", R.string.Exposure), exposureValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_brightness, LocaleController.getString("Exposure", R.string.Exposure), exposureValue); } else if (i == warmthTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_warmth, LocaleController.getString("Warmth", R.string.Warmth), warmthValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_warmth, LocaleController.getString("Warmth", R.string.Warmth), warmthValue); } else if (i == saturationTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_saturation, LocaleController.getString("Saturation", R.string.Saturation), saturationValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_saturation, LocaleController.getString("Saturation", R.string.Saturation), saturationValue); } else if (i == vignetteTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_vignette, LocaleController.getString("Vignette", R.string.Vignette), vignetteValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_vignette, LocaleController.getString("Vignette", R.string.Vignette), vignetteValue); } else if (i == shadowsTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_shadows, LocaleController.getString("Shadows", R.string.Shadows), shadowsValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_shadows, LocaleController.getString("Shadows", R.string.Shadows), shadowsValue); } else if (i == grainTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_grain, LocaleController.getString("Grain", R.string.Grain), grainValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_grain, LocaleController.getString("Grain", R.string.Grain), grainValue); } else if (i == sharpenTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_details, LocaleController.getString("Sharpen", R.string.Sharpen), sharpenValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_details, LocaleController.getString("Sharpen", R.string.Sharpen), sharpenValue); } else if (i == tintTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_tint, LocaleController.getString("Tint", R.string.Tint), tintHighlightsColor != 0 || tintShadowsColor != 0 ? "◆" : ""); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_tint, LocaleController.getString("Tint", R.string.Tint), tintHighlightsColor != 0 || tintShadowsColor != 0 ? "◆" : ""); } else if (i == fadeTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_fade, LocaleController.getString("Fade", R.string.Fade), fadeValue); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_fade, LocaleController.getString("Fade", R.string.Fade), fadeValue); } else if (i == curvesTool) { - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_curve, LocaleController.getString("Curves", R.string.Curves), curvesToolValue.shouldBeSkipped() ? "" : "◆"); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_curve, LocaleController.getString("Curves", R.string.Curves), curvesToolValue.shouldBeSkipped() ? "" : "◆"); } else if (i == blurTool) { String value = ""; if (blurType == 1) { @@ -2550,7 +2587,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { } else if (blurType == 2) { value = "L"; } - ((PhotoEditToolCell) holder.itemView).setIconAndTextAndValue(R.drawable.tool_blur, LocaleController.getString("Blur", R.string.Blur), value); + ((PhotoEditToolCell) viewHolder.itemView).setIconAndTextAndValue(R.drawable.tool_blur, LocaleController.getString("Blur", R.string.Blur), value); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java index 6c7ab39ba6e..ce8be6529ba 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoPaintView.java @@ -1,10 +1,10 @@ package org.telegram.ui.Components; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.*; @@ -31,7 +31,6 @@ import com.google.android.gms.vision.face.FaceDetector; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.Bitmaps; import org.telegram.messenger.DispatchQueue; @@ -45,6 +44,7 @@ import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.ActionBarPopupWindow; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.Paint.PhotoFace; import org.telegram.ui.Components.Paint.Views.EditTextOutline; @@ -266,7 +266,7 @@ public void onSettingsPressed() { cancelTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); cancelTextView.setTextColor(0xffffffff); cancelTextView.setGravity(Gravity.CENTER); - cancelTextView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + cancelTextView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); cancelTextView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); cancelTextView.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); cancelTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -276,7 +276,7 @@ public void onSettingsPressed() { doneTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); doneTextView.setTextColor(0xff51bdf3); doneTextView.setGravity(Gravity.CENTER); - doneTextView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + doneTextView.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); doneTextView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); doneTextView.setText(LocaleController.getString("Done", R.string.Done).toUpperCase()); doneTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -285,7 +285,7 @@ public void onSettingsPressed() { ImageView stickerButton = new ImageView(context); stickerButton.setScaleType(ImageView.ScaleType.CENTER); stickerButton.setImageResource(R.drawable.photo_sticker); - stickerButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + stickerButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); toolsView.addView(stickerButton, LayoutHelper.createFrame(54, LayoutHelper.MATCH_PARENT, Gravity.CENTER, 0, 0, 56, 0)); stickerButton.setOnClickListener(new View.OnClickListener() { @Override @@ -297,7 +297,7 @@ public void onClick(View v) { paintButton = new ImageView(context); paintButton.setScaleType(ImageView.ScaleType.CENTER); paintButton.setImageResource(R.drawable.photo_paint); - paintButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + paintButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); toolsView.addView(paintButton, LayoutHelper.createFrame(54, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); paintButton.setOnClickListener(new View.OnClickListener() { @Override @@ -309,7 +309,7 @@ public void onClick(View v) { ImageView textButton = new ImageView(context); textButton.setScaleType(ImageView.ScaleType.CENTER); textButton.setImageResource(R.drawable.photo_paint_text); - textButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + textButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); toolsView.addView(textButton, LayoutHelper.createFrame(54, LayoutHelper.MATCH_PARENT, Gravity.CENTER, 56, 0, 0, 0)); textButton.setOnClickListener(new View.OnClickListener() { @Override @@ -321,7 +321,8 @@ public void onClick(View v) { actionBar = new ActionBar(context); actionBar.setBackgroundColor(Theme.ACTION_BAR_PHOTO_VIEWER_COLOR); actionBar.setOccupyStatusBar(Build.VERSION.SDK_INT >= 21); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR); + actionBar.setTitleColor(0xffffffff); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(LocaleController.getString("PaintDraw", R.string.PaintDraw)); addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); @@ -482,7 +483,7 @@ public Bitmap getBitmap() { try { c.setBitmap(null); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } b.recycle(); } else { @@ -548,7 +549,7 @@ private void setColorPickerVisibilityFade(boolean visible) { colorPickerAnimator = ObjectAnimator.ofFloat(colorPicker, "alpha", colorPicker.getAlpha(), 1.0f); colorPickerAnimator.setStartDelay(200); colorPickerAnimator.setDuration(200); - colorPickerAnimator.addListener(new AnimatorListenerAdapterProxy() { + colorPickerAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (colorPickerAnimator != null) { @@ -574,7 +575,7 @@ private void setDimVisibility(final boolean visible) { } else { animator = ObjectAnimator.ofFloat(dimView, "alpha", 1.0f, 0.0f); } - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (!visible) { @@ -605,7 +606,7 @@ private void setTextDimVisibility(final boolean visible, EntityView view) { } else { animator = ObjectAnimator.ofFloat(textDimView, "alpha", 1.0f, 0.0f); } - animator.addListener(new AnimatorListenerAdapterProxy() { + animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (!visible) { @@ -915,7 +916,7 @@ private void closeStickersView() { Animator a = ObjectAnimator.ofFloat(stickersView, "alpha", 1.0f, 0.0f); a.setDuration(200); - a.addListener(new AnimatorListenerAdapterProxy() { + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { stickersView.setVisibility(GONE); @@ -1081,8 +1082,8 @@ public void run() { parent.setOrientation(LinearLayout.HORIZONTAL); TextView deleteView = new TextView(getContext()); - deleteView.setTextColor(0xff212121); - deleteView.setBackgroundResource(R.drawable.list_selector); + deleteView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem)); + deleteView.setBackgroundDrawable(Theme.getSelectorDrawable(false)); deleteView.setGravity(Gravity.CENTER_VERTICAL); deleteView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(14), 0); deleteView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -1102,8 +1103,8 @@ public void onClick(View v) { if (entityView instanceof TextPaintView) { TextView editView = new TextView(getContext()); - editView.setTextColor(0xff212121); - editView.setBackgroundResource(R.drawable.list_selector); + editView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem)); + editView.setBackgroundDrawable(Theme.getSelectorDrawable(false)); editView.setGravity(Gravity.CENTER_VERTICAL); editView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); editView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -1123,8 +1124,8 @@ public void onClick(View v) { } TextView duplicateView = new TextView(getContext()); - duplicateView.setTextColor(0xff212121); - duplicateView.setBackgroundResource(R.drawable.list_selector); + duplicateView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubmenuItem)); + duplicateView.setBackgroundDrawable(Theme.getSelectorDrawable(false)); duplicateView.setGravity(Gravity.CENTER_VERTICAL); duplicateView.setPadding(AndroidUtilities.dp(14), 0, AndroidUtilities.dp(16), 0); duplicateView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -1154,7 +1155,7 @@ public void onClick(View v) { private FrameLayout buttonForBrush(final int brush, int resource, boolean selected) { FrameLayout button = new FrameLayout(getContext()); - button.setBackgroundResource(R.drawable.list_selector); + button.setBackgroundDrawable(Theme.getSelectorDrawable(false)); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -1172,7 +1173,7 @@ public void onClick(View v) { if (selected) { ImageView check = new ImageView(getContext()); - check.setImageResource(R.drawable.ic_ab_done_gray); + check.setImageResource(R.drawable.ic_ab_done);//TODO color check.setScaleType(ImageView.ScaleType.CENTER); button.addView(check, LayoutHelper.createFrame(50, LayoutHelper.MATCH_PARENT)); } @@ -1218,7 +1219,7 @@ public boolean onInterceptTouchEvent(MotionEvent ev) { return true; } }; - button.setBackgroundResource(R.drawable.list_selector); + button.setBackgroundDrawable(Theme.getSelectorDrawable(false)); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -1245,7 +1246,7 @@ public void onClick(View v) { if (selected) { ImageView check = new ImageView(getContext()); - check.setImageResource(R.drawable.ic_ab_done_gray); + check.setImageResource(R.drawable.ic_ab_done); //TODO color check.setScaleType(ImageView.ScaleType.CENTER); button.addView(check, LayoutHelper.createFrame(50, LayoutHelper.MATCH_PARENT)); } @@ -1363,13 +1364,14 @@ private void detectFaces() { queue.postRunnable(new Runnable() { @Override public void run() { + FaceDetector faceDetector = null; try { - FaceDetector faceDetector = new FaceDetector.Builder(getContext()) + faceDetector = new FaceDetector.Builder(getContext()) .setMode(FaceDetector.ACCURATE_MODE) .setLandmarkType(FaceDetector.ALL_LANDMARKS) .setTrackingEnabled(false).build(); if (!faceDetector.isOperational()) { - FileLog.e("tmessages", "face detection is not operational"); + FileLog.e("face detection is not operational"); return; } @@ -1378,7 +1380,7 @@ public void run() { try { faces = faceDetector.detect(frame); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); return; } ArrayList result = new ArrayList<>(); @@ -1391,12 +1393,13 @@ public void run() { result.add(face); } } - PhotoPaintView.this.faces = result; - - faceDetector.release(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } finally { + if (faceDetector != null) { + faceDetector.release(); + } } } }); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java index d9c291d5227..842764b63ad 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PhotoViewerCaptionEnterView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -22,6 +22,7 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.inputmethod.EditorInfo; import android.widget.EditText; @@ -37,6 +38,7 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -70,6 +72,8 @@ public interface PhotoViewerCaptionEnterViewDelegate { private boolean keyboardVisible; private int emojiPadding; + private boolean forceFloatingEmoji; + private boolean innerTextChange; private final int captionMaxLength = 200; @@ -117,7 +121,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } catch (Exception e) { setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(51)); - FileLog.e("tmessages", e); + FileLog.e(e); } } }; @@ -256,6 +260,24 @@ public void afterTextChanged(Editable editable) { } } }); + + ImageView doneButton = new ImageView(context); + doneButton.setScaleType(ImageView.ScaleType.CENTER); + doneButton.setImageResource(R.drawable.ic_done); + textFieldContainer.addView(doneButton, LayoutHelper.createLinear(48, 48, Gravity.BOTTOM)); + if (Build.VERSION.SDK_INT >= 21) { + doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + } + doneButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + delegate.onCaptionEnter(); + } + }); + } + + public void setForceFloatingEmoji(boolean value) { + forceFloatingEmoji = value; } public boolean hideActionMode() { @@ -263,7 +285,7 @@ public boolean hideActionMode() { try { currentActionMode.finish(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } currentActionMode = null; return true; @@ -299,7 +321,7 @@ private void fixActionMode(ActionMode mode) { method.invoke(mode); } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -363,7 +385,7 @@ public void replaceWithText(int start, int len, String text) { messageEditText.setSelection(messageEditText.length()); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -380,7 +402,7 @@ public void run() { try { messageEditText.requestFocus(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -433,7 +455,7 @@ public void onEmojiSelected(String symbol) { int j = i + localCharSequence.length(); messageEditText.setSelection(j, j); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { innerTextChange = false; } @@ -469,7 +491,7 @@ public void onClearEmojiRecent() { } @Override - public void onShowStickerSet(TLRPC.StickerSetCovered stickerSet) { + public void onShowStickerSet(TLRPC.StickerSet stickerSet, TLRPC.InputStickerSet inputStickerSet) { } @@ -500,7 +522,7 @@ public void onStickerSetRemove(TLRPC.StickerSetCovered stickerSet) { layoutParams.width = AndroidUtilities.displaySize.x; layoutParams.height = currentHeight; emojiView.setLayoutParams(layoutParams); - if (!AndroidUtilities.isInMultiwindow) { + if (!AndroidUtilities.isInMultiwindow && !forceFloatingEmoji) { AndroidUtilities.hideKeyboard(messageEditText); } if (sizeNotifierLayout != null) { @@ -534,12 +556,29 @@ public void hidePopup() { private void openKeyboardInternal() { showPopup(AndroidUtilities.usingHardwareInput ? 0 : 2); - AndroidUtilities.showKeyboard(messageEditText); + openKeyboard(); } public void openKeyboard() { - messageEditText.requestFocus(); + int currentSelection; + try { + currentSelection = messageEditText.getSelectionStart(); + } catch (Exception e) { + currentSelection = messageEditText.length(); + FileLog.e(e); + } + MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + messageEditText.onTouchEvent(event); + event.recycle(); + event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + messageEditText.onTouchEvent(event); + event.recycle(); AndroidUtilities.showKeyboard(messageEditText); + try { + messageEditText.setSelection(currentSelection); + } catch (Exception e) { + FileLog.e(e); + } } public boolean isPopupShowing() { @@ -551,12 +590,12 @@ public void closeKeyboard() { } public boolean isKeyboardVisible() { - return AndroidUtilities.usingHardwareInput && getLayoutParams() != null && ((FrameLayout.LayoutParams) getLayoutParams()).bottomMargin == 0 || keyboardVisible; + return AndroidUtilities.usingHardwareInput && getTag() != null || keyboardVisible; } @Override public void onSizeChanged(int height, boolean isWidthGreater) { - if (height > AndroidUtilities.dp(50) && keyboardVisible && !AndroidUtilities.isInMultiwindow) { + if (height > AndroidUtilities.dp(50) && keyboardVisible && !AndroidUtilities.isInMultiwindow && !forceFloatingEmoji) { if (isWidthGreater) { keyboardHeightLand = height; ApplicationLoader.applicationContext.getSharedPreferences("emoji", 0).edit().putInt("kbd_height_land3", keyboardHeightLand).commit(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java index bae9c2afb6e..4eb7ae60502 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayout.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -38,13 +39,13 @@ public PickerBottomLayout(Context context, boolean darkTheme) { super(context); isDarkTheme = darkTheme; - setBackgroundColor(isDarkTheme ? 0xff1a1a1a : 0xffffffff); + setBackgroundColor(isDarkTheme ? 0xff1a1a1a : Theme.getColor(Theme.key_windowBackgroundWhite)); cancelButton = new TextView(context); cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - cancelButton.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); + cancelButton.setTextColor(isDarkTheme ? 0xffffffff : Theme.getColor(Theme.key_picker_enabledButton)); cancelButton.setGravity(Gravity.CENTER); - cancelButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); + cancelButton.setBackgroundDrawable(Theme.createSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); cancelButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -52,23 +53,29 @@ public PickerBottomLayout(Context context, boolean darkTheme) { doneButton = new LinearLayout(context); doneButton.setOrientation(LinearLayout.HORIZONTAL); - doneButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); + doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); doneButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); addView(doneButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); doneButtonBadgeTextView = new TextView(context); doneButtonBadgeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); doneButtonBadgeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - doneButtonBadgeTextView.setTextColor(0xffffffff); + doneButtonBadgeTextView.setTextColor(isDarkTheme ? 0xffffffff : Theme.getColor(Theme.key_picker_badgeText)); doneButtonBadgeTextView.setGravity(Gravity.CENTER); - doneButtonBadgeTextView.setBackgroundResource(isDarkTheme ? R.drawable.photobadge : R.drawable.bluecounter); + Drawable drawable; + if (isDarkTheme) { + drawable = getResources().getDrawable(R.drawable.photobadge); + } else { + drawable = Theme.createRoundRectDrawable(AndroidUtilities.dp(11), Theme.getColor(Theme.key_picker_badge)); + } + doneButtonBadgeTextView.setBackgroundDrawable(drawable); doneButtonBadgeTextView.setMinWidth(AndroidUtilities.dp(23)); doneButtonBadgeTextView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); doneButton.addView(doneButtonBadgeTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 23, Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); doneButtonTextView = new TextView(context); doneButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); + doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : Theme.getColor(Theme.key_picker_enabledButton)); doneButtonTextView.setGravity(Gravity.CENTER); doneButtonTextView.setCompoundDrawablePadding(AndroidUtilities.dp(8)); doneButtonTextView.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); @@ -81,16 +88,19 @@ public void updateSelectedCount(int count, boolean disable) { doneButtonBadgeTextView.setVisibility(View.GONE); if (disable) { - doneButtonTextView.setTextColor(0xff999999); + doneButtonTextView.setTag(isDarkTheme ? null : Theme.key_picker_disabledButton); + doneButtonTextView.setTextColor(isDarkTheme ? 0xff999999 : Theme.getColor(Theme.key_picker_disabledButton)); doneButton.setEnabled(false); } else { - doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); + doneButtonTextView.setTag(isDarkTheme ? null : Theme.key_picker_enabledButton); + doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : Theme.getColor(Theme.key_picker_enabledButton)); } } else { doneButtonBadgeTextView.setVisibility(View.VISIBLE); doneButtonBadgeTextView.setText(String.format("%d", count)); - doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); + doneButtonTextView.setTag(isDarkTheme ? null : Theme.key_picker_enabledButton); + doneButtonTextView.setTextColor(isDarkTheme ? 0xffffffff : Theme.getColor(Theme.key_picker_enabledButton)); if (disable) { doneButton.setEnabled(true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayoutViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayoutViewer.java index b4aeb5c1477..b88380bded9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayoutViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PickerBottomLayoutViewer.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -42,7 +42,7 @@ public PickerBottomLayoutViewer(Context context, boolean darkTheme) { cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); cancelButton.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); cancelButton.setGravity(Gravity.CENTER); - cancelButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); + cancelButton.setBackgroundDrawable(Theme.createSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); cancelButton.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -52,7 +52,7 @@ public PickerBottomLayoutViewer(Context context, boolean darkTheme) { doneButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); doneButton.setTextColor(isDarkTheme ? 0xffffffff : 0xff19a7e8); doneButton.setGravity(Gravity.CENTER); - doneButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); + doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(isDarkTheme ? Theme.ACTION_BAR_PICKER_SELECTOR_COLOR : Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); doneButton.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); doneButton.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); doneButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java new file mode 100644 index 00000000000..e8cd4cbbd49 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PipVideoView.java @@ -0,0 +1,332 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.PixelFormat; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.TextureView; +import android.view.View; +import android.view.ViewParent; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; +import org.telegram.ui.ActionBar.ActionBar; + +import java.util.ArrayList; + +public class PipVideoView { + + private FrameLayout windowView; + private EmbedBottomSheet parentSheet; + private Activity parentActivity; + private View controlsView; + private int videoWidth; + private int videoHeight; + + private WindowManager.LayoutParams windowLayoutParams; + private WindowManager windowManager; + private SharedPreferences preferences; + private DecelerateInterpolator decelerateInterpolator; + + public TextureView show(Activity activity, EmbedBottomSheet sheet, View controls, float aspectRatio, int rotation) { + windowView = new FrameLayout(activity) { + + private float startX; + private float startY; + private boolean dragging; + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + float x = event.getRawX(); + float y = event.getRawY(); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + startX = x; + startY = y; + } else if (event.getAction() == MotionEvent.ACTION_MOVE && !dragging) { + if (Math.abs(startX - x) >= AndroidUtilities.getPixelsInCM(0.3f, true) || Math.abs(startY - y) >= AndroidUtilities.getPixelsInCM(0.3f, false)) { + dragging = true; + startX = x; + startY = y; + ((ViewParent) controlsView).requestDisallowInterceptTouchEvent(true); + return true; + } + } + return super.onInterceptTouchEvent(event); + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!dragging) { + return false; + } + float x = event.getRawX(); + float y = event.getRawY(); + if (event.getAction() == MotionEvent.ACTION_MOVE) { + float dx = (x - startX); + float dy = (y - startY); + windowLayoutParams.x += dx; + windowLayoutParams.y += dy; + int maxDiff = videoWidth / 2; + if (windowLayoutParams.x < -maxDiff) { + windowLayoutParams.x = -maxDiff; + } else if (windowLayoutParams.x > AndroidUtilities.displaySize.x - windowLayoutParams.width + maxDiff) { + windowLayoutParams.x = AndroidUtilities.displaySize.x - windowLayoutParams.width + maxDiff; + } + float alpha = 1.0f; + if (windowLayoutParams.x < 0) { + alpha = 1.0f + windowLayoutParams.x / (float) maxDiff * 0.5f; + } else if (windowLayoutParams.x > AndroidUtilities.displaySize.x - windowLayoutParams.width) { + alpha = 1.0f - (windowLayoutParams.x - AndroidUtilities.displaySize.x + windowLayoutParams.width) / (float) maxDiff * 0.5f; + } + if (windowView.getAlpha() != alpha) { + windowView.setAlpha(alpha); + } + maxDiff = 0; + if (windowLayoutParams.y < -maxDiff) { + windowLayoutParams.y = -maxDiff; + } else if (windowLayoutParams.y > AndroidUtilities.displaySize.y - windowLayoutParams.height + maxDiff) { + windowLayoutParams.y = AndroidUtilities.displaySize.y - windowLayoutParams.height + maxDiff; + } + windowManager.updateViewLayout(windowView, windowLayoutParams); + startX = x; + startY = y; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + dragging = false; + animateToBoundsMaybe(); + } + return true; + } + }; + + if (aspectRatio > 1) { + videoWidth = AndroidUtilities.dp(192); + videoHeight = (int) (videoWidth / aspectRatio); + } else { + videoHeight = AndroidUtilities.dp(192); + videoWidth = (int) (videoHeight * aspectRatio); + } + + AspectRatioFrameLayout aspectRatioFrameLayout = new AspectRatioFrameLayout(activity); + aspectRatioFrameLayout.setAspectRatio(aspectRatio, rotation); + windowView.addView(aspectRatioFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + TextureView textureView = new TextureView(activity); + aspectRatioFrameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + controlsView = controls; + windowView.addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + windowManager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE); + + preferences = ApplicationLoader.applicationContext.getSharedPreferences("pipconfig", Context.MODE_PRIVATE); + + int sidex = preferences.getInt("sidex", 1); + int sidey = preferences.getInt("sidey", 0); + float px = preferences.getFloat("px", 0); + float py = preferences.getFloat("py", 0); + + try { + windowLayoutParams = new WindowManager.LayoutParams(); + windowLayoutParams.width = videoWidth; + windowLayoutParams.height = videoHeight; + windowLayoutParams.x = getSideCoord(true, sidex, px, videoWidth); + windowLayoutParams.y = getSideCoord(false, sidey, py, videoHeight); + windowLayoutParams.format = PixelFormat.TRANSLUCENT; + windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; + windowLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; + windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + windowManager.addView(windowView, windowLayoutParams); + } catch (Exception e) { + FileLog.e(e); + return null; + } + parentSheet = sheet; + parentActivity = activity; + + return textureView; + } + + private static int getSideCoord(boolean isX, int side, float p, int sideSize) { + int total; + if (isX) { + total = AndroidUtilities.displaySize.x - sideSize; + } else { + total = AndroidUtilities.displaySize.y - sideSize - ActionBar.getCurrentActionBarHeight(); + } + int result; + if (side == 0) { + result = AndroidUtilities.dp(10); + } else if (side == 1) { + result = total - AndroidUtilities.dp(10); + } else { + result = Math.round((total - AndroidUtilities.dp(20)) * p) + AndroidUtilities.dp(10); + } + if (!isX) { + result += ActionBar.getCurrentActionBarHeight(); + } + return result; + } + + public void close() { + try { + windowManager.removeView(windowView); + } catch (Exception e) { + //don't promt + } + parentSheet = null; + parentActivity = null; + } + + public void onConfigurationChanged() { + int sidex = preferences.getInt("sidex", 1); + int sidey = preferences.getInt("sidey", 0); + float px = preferences.getFloat("px", 0); + float py = preferences.getFloat("py", 0); + windowLayoutParams.x = getSideCoord(true, sidex, px, videoWidth); + windowLayoutParams.y = getSideCoord(false, sidey, py, videoHeight); + windowManager.updateViewLayout(windowView, windowLayoutParams); + } + + private void animateToBoundsMaybe() { + int startX = getSideCoord(true, 0, 0, videoWidth); + int endX = getSideCoord(true, 1, 0, videoWidth); + int startY = getSideCoord(false, 0, 0, videoHeight); + int endY = getSideCoord(false, 1, 0, videoHeight); + ArrayList animators = null; + SharedPreferences.Editor editor = preferences.edit(); + int maxDiff = AndroidUtilities.dp(20); + boolean slideOut = false; + if (Math.abs(startX - windowLayoutParams.x) <= maxDiff || windowLayoutParams.x < 0 && windowLayoutParams.x > -videoWidth / 4) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidex", 0); + if (windowView.getAlpha() != 1.0f) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 1.0f)); + } + animators.add(ObjectAnimator.ofInt(this, "x", startX)); + } else if (Math.abs(endX - windowLayoutParams.x) <= maxDiff || windowLayoutParams.x > AndroidUtilities.displaySize.x - videoWidth && windowLayoutParams.x < AndroidUtilities.displaySize.x - videoWidth / 4 * 3) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidex", 1); + if (windowView.getAlpha() != 1.0f) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 1.0f)); + } + animators.add(ObjectAnimator.ofInt(this, "x", endX)); + } else if (windowView.getAlpha() != 1.0f) { + if (animators == null) { + animators = new ArrayList<>(); + } + if (windowLayoutParams.x < 0) { + animators.add(ObjectAnimator.ofInt(this, "x", -videoWidth)); + } else { + animators.add(ObjectAnimator.ofInt(this, "x", AndroidUtilities.displaySize.x)); + } + slideOut = true; + } else { + editor.putFloat("px", (windowLayoutParams.x - startX) / (float) (endX - startX)); + editor.putInt("sidex", 2); + } + if (!slideOut) { + if (Math.abs(startY - windowLayoutParams.y) <= maxDiff || windowLayoutParams.y <= ActionBar.getCurrentActionBarHeight()) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidey", 0); + animators.add(ObjectAnimator.ofInt(this, "y", startY)); + } else if (Math.abs(endY - windowLayoutParams.y) <= maxDiff) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidey", 1); + animators.add(ObjectAnimator.ofInt(this, "y", endY)); + } else { + editor.putFloat("py", (windowLayoutParams.y - startY) / (float) (endY - startY)); + editor.putInt("sidey", 2); + } + editor.commit(); + } + if (animators != null) { + if (decelerateInterpolator == null) { + decelerateInterpolator = new DecelerateInterpolator(); + } + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.setDuration(150); + if (slideOut) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 0.0f)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + parentSheet.destroy(); + } + }); + } + animatorSet.playTogether(animators); + animatorSet.start(); + } + } + + public static Rect getPipRect(float aspectRatio) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("pipconfig", Context.MODE_PRIVATE); + int sidex = preferences.getInt("sidex", 1); + int sidey = preferences.getInt("sidey", 0); + float px = preferences.getFloat("px", 0); + float py = preferences.getFloat("py", 0); + + int videoWidth; + int videoHeight; + if (aspectRatio > 1) { + videoWidth = AndroidUtilities.dp(192); + videoHeight = (int) (videoWidth / aspectRatio); + } else { + videoHeight = AndroidUtilities.dp(192); + videoWidth = (int) (videoHeight * aspectRatio); + } + + return new Rect(getSideCoord(true, sidex, px, videoWidth), getSideCoord(false, sidey, py, videoHeight), videoWidth, videoHeight); + } + + public int getX() { + return windowLayoutParams.x; + } + + public int getY() { + return windowLayoutParams.y; + } + + public void setX(int value) { + windowLayoutParams.x = value; + windowManager.updateViewLayout(windowView, windowLayoutParams); + } + + public void setY(int value) { + windowLayoutParams.y = value; + windowManager.updateViewLayout(windowView, windowLayoutParams); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayerView.java deleted file mode 100644 index c25a465aa6f..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayerView.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.text.SpannableStringBuilder; -import android.text.Spanned; -import android.text.TextUtils; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; -import org.telegram.messenger.MediaController; -import org.telegram.messenger.MessageObject; -import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.R; -import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.AudioPlayerActivity; - -public class PlayerView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { - - private ImageView playButton; - private TextView titleTextView; - private MessageObject lastMessageObject; - private AnimatorSet animatorSet; - private float yPosition; - private BaseFragment fragment; - private float topPadding; - private boolean visible; - - public PlayerView(Context context, BaseFragment parentFragment) { - super(context); - - fragment = parentFragment; - visible = true; - ((ViewGroup) fragment.getFragmentView()).setClipToPadding(false); - - setTag(1); - FrameLayout frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(Theme.INAPP_PLAYER_BACKGROUND_COLOR); - addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); - - View shadow = new View(context); - shadow.setBackgroundResource(R.drawable.header_shadow); - addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.TOP, 0, 36, 0, 0)); - - playButton = new ImageView(context); - playButton.setScaleType(ImageView.ScaleType.CENTER); - addView(playButton, LayoutHelper.createFrame(36, 36, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); - playButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (MediaController.getInstance().isAudioPaused()) { - MediaController.getInstance().playAudio(MediaController.getInstance().getPlayingMessageObject()); - } else { - MediaController.getInstance().pauseAudio(MediaController.getInstance().getPlayingMessageObject()); - } - } - }); - - titleTextView = new TextView(context); - titleTextView.setTextColor(Theme.INAPP_PLAYER_TITLE_TEXT_COLOR); - titleTextView.setMaxLines(1); - titleTextView.setLines(1); - titleTextView.setSingleLine(true); - titleTextView.setEllipsize(TextUtils.TruncateAt.END); - titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - titleTextView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); - addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.LEFT | Gravity.TOP, 35, 0, 36, 0)); - - ImageView closeButton = new ImageView(context); - closeButton.setImageResource(R.drawable.miniplayer_close); - closeButton.setScaleType(ImageView.ScaleType.CENTER); - addView(closeButton, LayoutHelper.createFrame(36, 36, Gravity.RIGHT | Gravity.TOP)); - closeButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - MediaController.getInstance().cleanupPlayer(true, true); - } - }); - - setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); - if (messageObject != null && messageObject.isMusic() && fragment != null) { - fragment.presentFragment(new AudioPlayerActivity()); - } - } - }); - } - - public float getTopPadding() { - return topPadding; - } - - public void setTopPadding(float value) { - topPadding = value; - if (fragment != null) { - View view = fragment.getFragmentView(); - if (view != null) { - view.setPadding(0, (int) topPadding, 0, 0); - } - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - topPadding = 0; - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidReset); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioPlayStateChanged); - NotificationCenter.getInstance().removeObserver(this, NotificationCenter.audioDidStarted); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidReset); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioPlayStateChanged); - NotificationCenter.getInstance().addObserver(this, NotificationCenter.audioDidStarted); - checkPlayer(true); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, AndroidUtilities.dp(39)); - } - - @Override - public void didReceivedNotification(int id, Object... args) { - if (id == NotificationCenter.audioDidStarted || id == NotificationCenter.audioPlayStateChanged || id == NotificationCenter.audioDidReset) { - checkPlayer(false); - } - } - - private void checkPlayer(boolean create) { - MessageObject messageObject = MediaController.getInstance().getPlayingMessageObject(); - View fragmentView = fragment.getFragmentView(); - if (!create && fragmentView != null) { - if (fragmentView.getParent() == null || ((View) fragmentView.getParent()).getVisibility() != VISIBLE) { - create = true; - } - } - if (messageObject == null || messageObject.getId() == 0/* || !messageObject.isMusic()*/) { - lastMessageObject = null; - if (visible) { - visible = false; - if (create) { - if (getVisibility() != GONE) { - setVisibility(GONE); - } - setTopPadding(0); - } else { - if (animatorSet != null) { - animatorSet.cancel(); - animatorSet = null; - } - animatorSet = new AnimatorSet(); - animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp(36)), - ObjectAnimator.ofFloat(this, "topPadding", 0)); - animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Animator animation) { - if (animatorSet != null && animatorSet.equals(animation)) { - setVisibility(GONE); - animatorSet = null; - } - } - }); - animatorSet.start(); - } - } - } else { - if (create && topPadding == 0) { - setTopPadding(AndroidUtilities.dp(36)); - setTranslationY(0); - yPosition = 0; - } - if (!visible) { - if (!create) { - if (animatorSet != null) { - animatorSet.cancel(); - animatorSet = null; - } - animatorSet = new AnimatorSet(); - animatorSet.playTogether(ObjectAnimator.ofFloat(this, "translationY", -AndroidUtilities.dp(36), 0), - ObjectAnimator.ofFloat(this, "topPadding", AndroidUtilities.dp(36))); - animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { - @Override - public void onAnimationEnd(Animator animation) { - if (animatorSet != null && animatorSet.equals(animation)) { - animatorSet = null; - } - } - }); - animatorSet.start(); - } - visible = true; - setVisibility(VISIBLE); - } - if (MediaController.getInstance().isAudioPaused()) { - playButton.setImageResource(R.drawable.miniplayer_play); - } else { - playButton.setImageResource(R.drawable.miniplayer_pause); - } - if (lastMessageObject != messageObject) { - lastMessageObject = messageObject; - SpannableStringBuilder stringBuilder; - if (lastMessageObject.isVoice()) { - stringBuilder = new SpannableStringBuilder(String.format("%s %s", messageObject.getMusicAuthor(), messageObject.getMusicTitle())); - titleTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE); - } else { - stringBuilder = new SpannableStringBuilder(String.format("%s - %s", messageObject.getMusicAuthor(), messageObject.getMusicTitle())); - titleTextView.setEllipsize(TextUtils.TruncateAt.END); - } - TypefaceSpan span = new TypefaceSpan(AndroidUtilities.getTypeface("fonts/rmedium.ttf"), 0, Theme.INAPP_PLAYER_PERFORMER_TEXT_COLOR); - stringBuilder.setSpan(span, 0, messageObject.getMusicAuthor().length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); - titleTextView.setText(stringBuilder); - } - } - } - - @Override - public void setTranslationY(float translationY) { - super.setTranslationY(translationY); - yPosition = translationY; - invalidate(); - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - int restoreToCount = canvas.save(); - if (yPosition < 0) { - canvas.clipRect(0, (int) -yPosition, child.getMeasuredWidth(), AndroidUtilities.dp(39)); - } - final boolean result = super.drawChild(canvas, child, drawingTime); - canvas.restoreToCount(restoreToCount); - return result; - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayingGameDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayingGameDrawable.java new file mode 100644 index 00000000000..c371012510d --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PlayingGameDrawable.java @@ -0,0 +1,146 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.view.animation.DecelerateInterpolator; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.NotificationCenter; +import org.telegram.ui.ActionBar.Theme; + +public class PlayingGameDrawable extends Drawable { + + private boolean isChat = false; + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private long lastUpdateTime = 0; + private boolean started = false; + private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); + private RectF rect = new RectF(); + private float progress; + + public void setIsChat(boolean value) { + isChat = value; + } + + private void update() { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + if (dt > 16) { + dt = 16; + } + if (progress >= 1.0f) { + progress = 0.0f; + } + progress += dt / 300.0f; + if (progress > 1.0f) { + progress = 1.0f; + } + invalidateSelf(); + } + + public void start() { + lastUpdateTime = System.currentTimeMillis(); + started = true; + invalidateSelf(); + } + + public void stop() { + progress = 0.0f; + started = false; + } + + @Override + public void draw(Canvas canvas) { + int size = AndroidUtilities.dp(10); + int y = getBounds().top + (getIntrinsicHeight() - size) / 2; + if (isChat) { + //y = AndroidUtilities.dp(8.5f) + getBounds().top; + } else { + y += AndroidUtilities.dp(1); + //y = AndroidUtilities.dp(9.3f) + getBounds().top; + } + + paint.setColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); + rect.set(0, y, size, y + size); + int rad; + if (progress < 0.5f) { + rad = (int) (35 * (1.0f - progress / 0.5f)); + } else { + rad = (int) (35 * (progress - 0.5f) / 0.5f); + } + for (int a = 0; a < 3; a++) { + float x = a * AndroidUtilities.dp(5) + AndroidUtilities.dp(9.2f) - AndroidUtilities.dp(5) * progress; + if (a == 2) { + paint.setAlpha(Math.min(255, (int) (255 * progress / 0.5f))); + } else if (a == 0) { + if (progress > 0.5f) { + paint.setAlpha((int) (255 * (1.0f - (progress - 0.5f) / 0.5f))); + } else { + paint.setAlpha(255); + } + } else { + paint.setAlpha(255); + } + canvas.drawCircle(x, y + size / 2, AndroidUtilities.dp(1.2f), paint); + } + paint.setAlpha(255); + canvas.drawArc(rect, rad, 360 - rad * 2, true, paint); + paint.setColor(Theme.getColor(Theme.key_actionBarDefault)); + canvas.drawCircle(AndroidUtilities.dp(4), y + size / 2 - AndroidUtilities.dp(2), AndroidUtilities.dp(1), paint); + + checkUpdate(); + } + + private void checkUpdate() { + if (started) { + if (!NotificationCenter.getInstance().isAnimationInProgress()) { + update(); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + checkUpdate(); + } + }, 100); + } + } + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter cf) { + + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + public int getIntrinsicWidth() { + return AndroidUtilities.dp(20); + } + + @Override + public int getIntrinsicHeight() { + return AndroidUtilities.dp(18); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Point.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Point.java index a508a581686..81e03f1b226 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Point.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Point.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java index ada577d77a1..6ae2c1fae39 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/PopupAudioView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -22,7 +22,6 @@ import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.FileLoader; -import org.telegram.messenger.R; import org.telegram.messenger.MessageObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.Theme; @@ -35,10 +34,7 @@ public class PopupAudioView extends BaseCell implements SeekBar.SeekBarDelegate, private boolean wasLayout = false; protected MessageObject currentMessageObject; - private static Drawable backgroundMediaDrawableIn; - - private static Drawable[][] statesDrawable = new Drawable[8][2]; - private static TextPaint timePaint; + private TextPaint timePaint; private SeekBar seekBar; private ProgressView progressView; @@ -59,29 +55,7 @@ public class PopupAudioView extends BaseCell implements SeekBar.SeekBarDelegate, public PopupAudioView(Context context) { super(context); - if (backgroundMediaDrawableIn == null) { - backgroundMediaDrawableIn = getResources().getDrawable(R.drawable.msg_in_photo); - statesDrawable[0][0] = getResources().getDrawable(R.drawable.play_g); - statesDrawable[0][1] = getResources().getDrawable(R.drawable.play_g_s); - statesDrawable[1][0] = getResources().getDrawable(R.drawable.pause_g); - statesDrawable[1][1] = getResources().getDrawable(R.drawable.pause_g_s); - statesDrawable[2][0] = getResources().getDrawable(R.drawable.file_g_load); - statesDrawable[2][1] = getResources().getDrawable(R.drawable.file_g_load_s); - statesDrawable[3][0] = getResources().getDrawable(R.drawable.file_g_cancel); - statesDrawable[3][1] = getResources().getDrawable(R.drawable.file_g_cancel_s); - - statesDrawable[4][0] = getResources().getDrawable(R.drawable.play_b); - statesDrawable[4][1] = getResources().getDrawable(R.drawable.play_b_s); - statesDrawable[5][0] = getResources().getDrawable(R.drawable.pause_b); - statesDrawable[5][1] = getResources().getDrawable(R.drawable.pause_b_s); - statesDrawable[6][0] = getResources().getDrawable(R.drawable.file_b_load); - statesDrawable[6][1] = getResources().getDrawable(R.drawable.file_b_load_s); - statesDrawable[7][0] = getResources().getDrawable(R.drawable.file_b_cancel); - statesDrawable[7][1] = getResources().getDrawable(R.drawable.file_b_cancel_s); - - timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); - } - + timePaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG); timePaint.setTextSize(AndroidUtilities.dp(16)); TAG = MediaController.getInstance().generateObserverTag(); @@ -93,8 +67,8 @@ public PopupAudioView(Context context) { public void setMessageObject(MessageObject messageObject) { if (currentMessageObject != messageObject) { - seekBar.setColors(Theme.MSG_IN_AUDIO_SEEKBAR_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_FILL_COLOR, Theme.MSG_IN_AUDIO_SEEKBAR_SELECTED_COLOR); - progressView.setProgressColors(0xffd9e2eb, 0xff86c5f8); + seekBar.setColors(Theme.getColor(Theme.key_chat_inAudioSeekbar), Theme.getColor(Theme.key_chat_inAudioSeekbarFill), Theme.getColor(Theme.key_chat_inAudioSeekbarSelected)); + progressView.setProgressColors(0xffd9e2eb, 0xff86c5f8); //TODO currentMessageObject = messageObject; wasLayout = false; @@ -149,8 +123,8 @@ protected void onDraw(Canvas canvas) { return; } - setDrawableBounds(backgroundMediaDrawableIn, 0, 0, getMeasuredWidth(), getMeasuredHeight()); - backgroundMediaDrawableIn.draw(canvas); + setDrawableBounds(Theme.chat_msgInMediaDrawable, 0, 0, getMeasuredWidth(), getMeasuredHeight()); + Theme.chat_msgInMediaDrawable.draw(canvas); if (currentMessageObject == null) { return; @@ -166,9 +140,9 @@ protected void onDraw(Canvas canvas) { } canvas.restore(); - int state = buttonState + 4; + int state = buttonState + 5; timePaint.setColor(0xffa1aab3); - Drawable buttonDrawable = statesDrawable[state][buttonPressed]; + Drawable buttonDrawable = Theme.chat_fileStatesDrawable[state][buttonPressed]; int side = AndroidUtilities.dp(36); int x = (side - buttonDrawable.getIntrinsicWidth()) / 2; int y = (side - buttonDrawable.getIntrinsicHeight()) / 2; @@ -250,7 +224,7 @@ private void didPressedButton() { } } else if (buttonState == 2) { FileLoader.getInstance().loadFile(currentMessageObject.getDocument(), true, false); - buttonState = 3; + buttonState = 4; invalidate(); } else if (buttonState == 3) { FileLoader.getInstance().cancelLoadFile(currentMessageObject.getDocument()); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java deleted file mode 100644 index 6080201c382..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressCircleView.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.view.View; - -public class ProgressCircleView extends View { - - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - - public ProgressCircleView(Context context) { - super(context); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressView.java index b652dcff8f4..7dc13d889ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ProgressView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java index a21a27f36dd..282d3cf123e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgress.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -131,7 +131,7 @@ public void setProgress(float value, boolean animated) { private void invalidateParent() { int offset = AndroidUtilities.dp(2); - parent.invalidate((int)progressRect.left - offset, (int)progressRect.top - offset, (int)progressRect.right + offset * 2, (int)progressRect.bottom + offset * 2); + parent.invalidate((int) progressRect.left - offset, (int) progressRect.top - offset, (int) progressRect.right + offset * 2, (int) progressRect.bottom + offset * 2); } public void setBackground(Drawable drawable, boolean withRound, boolean animated) { @@ -173,17 +173,17 @@ public void draw(Canvas canvas) { } else { previousDrawable.setAlpha(255); } - previousDrawable.setBounds((int)progressRect.left, (int)progressRect.top, (int)progressRect.right, (int)progressRect.bottom); + previousDrawable.setBounds((int) progressRect.left, (int) progressRect.top, (int) progressRect.right, (int) progressRect.bottom); previousDrawable.draw(canvas); } if (!hideCurrentDrawable && currentDrawable != null) { if (previousDrawable != null) { - currentDrawable.setAlpha((int)(255 * (1.0f - animatedAlphaValue))); + currentDrawable.setAlpha((int) (255 * (1.0f - animatedAlphaValue))); } else { currentDrawable.setAlpha(255); } - currentDrawable.setBounds((int)progressRect.left, (int)progressRect.top, (int)progressRect.right, (int)progressRect.bottom); + currentDrawable.setBounds((int) progressRect.left, (int) progressRect.top, (int) progressRect.right, (int) progressRect.bottom); currentDrawable.draw(canvas); } @@ -191,7 +191,7 @@ public void draw(Canvas canvas) { int diff = AndroidUtilities.dp(4); progressPaint.setColor(progressColor); if (previousWithRound) { - progressPaint.setAlpha((int)(255 * animatedAlphaValue)); + progressPaint.setAlpha((int) (255 * animatedAlphaValue)); } else { progressPaint.setAlpha(255); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgressView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgressView.java new file mode 100644 index 00000000000..75b1b3dfdee --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadialProgressView.java @@ -0,0 +1,105 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.view.View; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.Theme; + +public class RadialProgressView extends View { + + private long lastUpdateTime; + private float radOffset; + private float currentCircleLength; + private boolean risingCircleLength; + private float currentProgressTime; + private RectF cicleRect = new RectF(); + + private int progressColor; + + private DecelerateInterpolator decelerateInterpolator; + private AccelerateInterpolator accelerateInterpolator; + private Paint progressPaint; + private final float rotationTime = 2000; + private final float risingTime = 500; + private int size; + + public RadialProgressView(Context context) { + super(context); + + size = AndroidUtilities.dp(40); + + progressColor = Theme.getColor(Theme.key_progressCircle); + decelerateInterpolator = new DecelerateInterpolator(); + accelerateInterpolator = new AccelerateInterpolator(); + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setStyle(Paint.Style.STROKE); + progressPaint.setStrokeCap(Paint.Cap.ROUND); + progressPaint.setStrokeWidth(AndroidUtilities.dp(3)); + progressPaint.setColor(progressColor); + } + + private void updateAnimation() { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + if (dt > 17) { + dt = 17; + } + lastUpdateTime = newTime; + + radOffset += 360 * dt / rotationTime; + int count = (int) (radOffset / 360); + radOffset -= count * 360; + + currentProgressTime += dt; + if (currentProgressTime >= risingTime) { + currentProgressTime = risingTime; + } + if (risingCircleLength) { + currentCircleLength = 4 + 266 * accelerateInterpolator.getInterpolation(currentProgressTime / risingTime); + } else { + currentCircleLength = 4 - 270 * (1.0f - decelerateInterpolator.getInterpolation(currentProgressTime / risingTime)); + } + if (currentProgressTime == risingTime) { + if (risingCircleLength) { + radOffset += 270; + currentCircleLength = -266; + } + risingCircleLength = !risingCircleLength; + currentProgressTime = 0; + } + invalidate(); + } + + public void setSize(int value) { + size = value; + invalidate(); + } + + public void setProgressColor(int color) { + progressColor = color; + progressPaint.setColor(progressColor); + } + + @Override + protected void onDraw(Canvas canvas) { + int x = (getMeasuredWidth() - size) / 2; + int y = (getMeasuredHeight() - size) / 2; + cicleRect.set(x, y, x + size, y + size); + canvas.drawArc(cicleRect, radOffset, currentCircleLength, false, progressPaint); + updateAnimation(); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadioButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadioButton.java index 8299a6fd7be..64e57889e76 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RadioButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RadioButton.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -16,6 +16,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; +import android.support.annotation.Keep; import android.view.View; import org.telegram.messenger.AndroidUtilities; @@ -30,8 +31,8 @@ public class RadioButton extends View { private static Paint eraser; private static Paint checkedPaint; - private int checkedColor = Theme.ACTION_BAR_SUBTITLE_COLOR; - private int color = Theme.ACTION_BAR_SUBTITLE_COLOR; + private int checkedColor; + private int color; private float progress; private ObjectAnimator checkAnimator; @@ -56,10 +57,11 @@ public RadioButton(Context context) { bitmap = Bitmap.createBitmap(AndroidUtilities.dp(size), AndroidUtilities.dp(size), Bitmap.Config.ARGB_4444); bitmapCanvas = new Canvas(bitmap); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } + @Keep public void setProgress(float value) { if (progress == value) { return; @@ -85,6 +87,16 @@ public void setColor(int color1, int color2) { invalidate(); } + public void setBackgroundColor(int color1) { + color = color1; + invalidate(); + } + + public void setCheckedColor(int color2) { + checkedColor = color2; + invalidate(); + } + private void cancelCheckAnimator() { if (checkAnimator != null) { checkAnimator.cancel(); @@ -137,7 +149,7 @@ protected void onDraw(Canvas canvas) { bitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888); bitmapCanvas = new Canvas(bitmap); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } float circleProgress; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecordStatusDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecordStatusDrawable.java index 6bcf6de143d..35f795a8073 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecordStatusDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecordStatusDrawable.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.RectF; import android.graphics.drawable.Drawable; @@ -20,20 +19,11 @@ public class RecordStatusDrawable extends Drawable { private boolean isChat = false; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private long lastUpdateTime = 0; private boolean started = false; private RectF rect = new RectF(); private float progress; - public RecordStatusDrawable() { - super(); - paint.setColor(Theme.ACTION_BAR_SUBTITLE_COLOR); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(AndroidUtilities.dp(2)); - paint.setStrokeCap(Paint.Cap.ROUND); - } - public void setIsChat(boolean value) { isChat = value; } @@ -68,15 +58,15 @@ public void draw(Canvas canvas) { canvas.translate(0, getIntrinsicHeight() / 2 + AndroidUtilities.dp(isChat ? 1 : 2)); for (int a = 0; a < 4; a++) { if (a == 0) { - paint.setAlpha((int) (255 * progress)); + Theme.chat_statusRecordPaint.setAlpha((int) (255 * progress)); } else if (a == 3) { - paint.setAlpha((int) (255 * (1.0f - progress))); + Theme.chat_statusRecordPaint.setAlpha((int) (255 * (1.0f - progress))); } else { - paint.setAlpha(255); + Theme.chat_statusRecordPaint.setAlpha(255); } float side = AndroidUtilities.dp(4) * a + AndroidUtilities.dp(4) * progress; rect.set(-side, -side, side, side); - canvas.drawArc(rect, -15, 30, false, paint); + canvas.drawArc(rect, -15, 30, false, Theme.chat_statusRecordPaint); } canvas.restore(); if (started) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Rect.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Rect.java index 9777cdcc8b3..e873f70141c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Rect.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Rect.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java index 8c50b5137b0..5d50b60b9dc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java @@ -3,13 +3,26 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.util.SparseArray; +import android.util.StateSet; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; @@ -19,22 +32,44 @@ import android.view.ViewGroup; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.messenger.FileLog; +import org.telegram.ui.ActionBar.Theme; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; public class RecyclerListView extends RecyclerView { private OnItemClickListener onItemClickListener; private OnItemLongClickListener onItemLongClickListener; - private RecyclerView.OnScrollListener onScrollListener; + private OnScrollListener onScrollListener; private OnInterceptTouchListener onInterceptTouchListener; private View emptyView; private Runnable selectChildRunnable; + private FastScroll fastScroll; + private SectionsAdapter sectionsAdapter; - private GestureDetector mGestureDetector; + private ArrayList headers; + private ArrayList headersCache; + private View pinnedHeader; + private int currentFirst = -1; + private int currentVisible = -1; + private int startSection; + private int sectionsCount; + private int sectionsType; + + private Drawable selectorDrawable; + private int selectorPosition; + private android.graphics.Rect selectorRect = new android.graphics.Rect(); + private boolean isChildViewEnabled; + + private boolean selfOnLayout; + + private GestureDetector gestureDetector; private View currentChildView; private int currentChildPosition; private boolean interceptedByChild; @@ -42,6 +77,7 @@ public class RecyclerListView extends RecyclerView { private boolean disallowInterceptTouchEvents; private boolean instantClick; private Runnable clickRunnable; + private boolean ignoreOnScroll; private static int[] attributes; private static boolean gotAttributes; @@ -58,18 +94,378 @@ public interface OnInterceptTouchListener { boolean onInterceptTouchEvent(MotionEvent event); } - private class RecyclerListViewItemClickListener implements RecyclerView.OnItemTouchListener { + public abstract static class SelectionAdapter extends Adapter { + public abstract boolean isEnabled(ViewHolder holder); + } + + public abstract static class FastScrollAdapter extends SelectionAdapter { + public abstract String getLetter(int position); + public abstract int getPositionForScrollProgress(float progress); + } + + public abstract static class SectionsAdapter extends FastScrollAdapter { + + private SparseArray sectionPositionCache; + private SparseArray sectionCache; + private SparseArray sectionCountCache; + private int sectionCount; + private int count; + + private void cleanupCache() { + sectionCache = new SparseArray<>(); + sectionPositionCache = new SparseArray<>(); + sectionCountCache = new SparseArray<>(); + count = -1; + sectionCount = -1; + } + + public SectionsAdapter() { + super(); + cleanupCache(); + } + + @Override + public void notifyDataSetChanged() { + cleanupCache(); + super.notifyDataSetChanged(); + } + + @Override + public boolean isEnabled(ViewHolder holder) { + int position = holder.getAdapterPosition(); + return isEnabled(getSectionForPosition(position), getPositionInSectionForPosition(position)); + } + + @Override + public final int getItemCount() { + if (count >= 0) { + return count; + } + count = 0; + for (int i = 0; i < internalGetSectionCount(); i++) { + count += internalGetCountForSection(i); + } + return count; + } + + public final Object getItem(int position) { + return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position)); + } + + public final int getItemViewType(int position) { + return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position)); + } + + @Override + public final void onBindViewHolder(ViewHolder holder, int position) { + onBindViewHolder(getSectionForPosition(position), getPositionInSectionForPosition(position), holder); + } + + private int internalGetCountForSection(int section) { + Integer cachedSectionCount = sectionCountCache.get(section); + if (cachedSectionCount != null) { + return cachedSectionCount; + } + int sectionCount = getCountForSection(section); + sectionCountCache.put(section, sectionCount); + return sectionCount; + } + + private int internalGetSectionCount() { + if (sectionCount >= 0) { + return sectionCount; + } + sectionCount = getSectionCount(); + return sectionCount; + } + + public final int getSectionForPosition(int position) { + Integer cachedSection = sectionCache.get(position); + if (cachedSection != null) { + return cachedSection; + } + int sectionStart = 0; + for (int i = 0; i < internalGetSectionCount(); i++) { + int sectionCount = internalGetCountForSection(i); + int sectionEnd = sectionStart + sectionCount; + if (position >= sectionStart && position < sectionEnd) { + sectionCache.put(position, i); + return i; + } + sectionStart = sectionEnd; + } + return -1; + } + + public int getPositionInSectionForPosition(int position) { + Integer cachedPosition = sectionPositionCache.get(position); + if (cachedPosition != null) { + return cachedPosition; + } + int sectionStart = 0; + for (int i = 0; i < internalGetSectionCount(); i++) { + int sectionCount = internalGetCountForSection(i); + int sectionEnd = sectionStart + sectionCount; + if (position >= sectionStart && position < sectionEnd) { + int positionInSection = position - sectionStart; + sectionPositionCache.put(position, positionInSection); + return positionInSection; + } + sectionStart = sectionEnd; + } + return -1; + } + + public abstract int getSectionCount(); + public abstract int getCountForSection(int section); + public abstract boolean isEnabled(int section, int row); + public abstract int getItemViewType(int section, int position); + public abstract Object getItem(int section, int position); + public abstract void onBindViewHolder(int section, int position, ViewHolder holder); + public abstract View getSectionHeaderView(int section, View view); + } + + public static class Holder extends ViewHolder { + + public Holder(View itemView) { + super(itemView); + } + } + + private class FastScroll extends View { + + private RectF rect = new RectF(); + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private float progress; + private float lastY; + private float startDy; + private boolean pressed; + private StaticLayout letterLayout; + private StaticLayout oldLetterLayout; + private TextPaint letterPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + private String currentLetter; + private Path path = new Path(); + private float[] radii = new float[8]; + private float textX; + private float textY; + private float bubbleProgress; + private long lastUpdateTime; + private int[] colors = new int[6]; + private int scrollX; + + public FastScroll(Context context) { + super(context); + + letterPaint.setTextSize(AndroidUtilities.dp(45)); + for (int a = 0; a < 8; a++) { + radii[a] = AndroidUtilities.dp(44); + } + + scrollX = LocaleController.isRTL ? AndroidUtilities.dp(10) : AndroidUtilities.dp(117); + updateColors(); + } + + private void updateColors() { + int inactive = Theme.getColor(Theme.key_fastScrollInactive); + int active = Theme.getColor(Theme.key_fastScrollActive); + paint.setColor(inactive); + letterPaint.setColor(Theme.getColor(Theme.key_fastScrollText)); + colors[0] = Color.red(inactive); + colors[1] = Color.red(active); + colors[2] = Color.green(inactive); + colors[3] = Color.green(active); + colors[4] = Color.blue(inactive); + colors[5] = Color.blue(active); + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + float x = event.getX(); + lastY = event.getY(); + float currectY = (float) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(12); + if (LocaleController.isRTL && x > AndroidUtilities.dp(25) || !LocaleController.isRTL && x < AndroidUtilities.dp(107) || lastY < currectY || lastY > currectY + AndroidUtilities.dp(30)) { + return false; + } + startDy = lastY - currectY; + pressed = true; + lastUpdateTime = System.currentTimeMillis(); + getCurrentLetter(); + invalidate(); + return true; + case MotionEvent.ACTION_MOVE: + if (!pressed) { + return true; + } + float newY = event.getY(); + float minY = AndroidUtilities.dp(12) + startDy; + float maxY = getMeasuredHeight() - AndroidUtilities.dp(12 + 30) + startDy; + if (newY < minY) { + newY = minY; + } else if (newY > maxY) { + newY = maxY; + } + float dy = newY - lastY; + lastY = newY; + progress += dy / (getMeasuredHeight() - AndroidUtilities.dp(24 + 30)); + if (progress < 0) { + progress = 0; + } else if (progress > 1) { + progress = 1; + } + getCurrentLetter(); + invalidate(); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + pressed = false; + lastUpdateTime = System.currentTimeMillis(); + invalidate(); + return true; + } + return super.onTouchEvent(event); + } + + private void getCurrentLetter() { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; + if (linearLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL) { + Adapter adapter = getAdapter(); + if (adapter instanceof FastScrollAdapter) { + FastScrollAdapter fastScrollAdapter = (FastScrollAdapter) adapter; + int position = fastScrollAdapter.getPositionForScrollProgress(progress); + linearLayoutManager.scrollToPositionWithOffset(position, 0); + String newLetter = fastScrollAdapter.getLetter(position); + if (newLetter == null) { + if (letterLayout != null) { + oldLetterLayout = letterLayout; + } + letterLayout = null; + } else if (!newLetter.equals(currentLetter)) { + letterLayout = new StaticLayout(newLetter, letterPaint, 1000, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + oldLetterLayout = null; + if (letterLayout.getLineCount() > 0) { + if (LocaleController.isRTL) { + float lWidth = letterLayout.getLineWidth(0); + float lleft = letterLayout.getLineLeft(0); + textX = AndroidUtilities.dp(10) + (AndroidUtilities.dp(88) - (letterLayout.getLineWidth(0) - letterLayout.getLineLeft(0))) / 2; + } else { + textX = (AndroidUtilities.dp(88) - (letterLayout.getLineWidth(0) - letterLayout.getLineLeft(0))) / 2; + } + textY = (AndroidUtilities.dp(88) - letterLayout.getHeight()) / 2; + } + } + } + } + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(AndroidUtilities.dp(132), MeasureSpec.getSize(heightMeasureSpec)); + } + + @Override + protected void onDraw(Canvas canvas) { + paint.setColor(Color.argb(255, colors[0] + (int) ((colors[1] - colors[0]) * bubbleProgress), colors[2] + (int) ((colors[3] - colors[2]) * bubbleProgress), colors[4] + (int) ((colors[5] - colors[4]) * bubbleProgress))); + int y = (int) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress); + rect.set(scrollX, AndroidUtilities.dp(12) + y, scrollX + AndroidUtilities.dp(5), AndroidUtilities.dp(12 + 30) + y); + canvas.drawRoundRect(rect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), paint); + if ((pressed || bubbleProgress != 0)) { + paint.setAlpha((int) (255 * bubbleProgress)); + int progressY = y + AndroidUtilities.dp(30); + y -= AndroidUtilities.dp(46); + float diff = 0; + if (y <= AndroidUtilities.dp(12)) { + diff = AndroidUtilities.dp(12) - y; + y = AndroidUtilities.dp(12); + } + float raduisTop; + float raduisBottom; + canvas.translate(AndroidUtilities.dp(10), y); + if (diff <= AndroidUtilities.dp(29)) { + raduisTop = AndroidUtilities.dp(44); + raduisBottom = AndroidUtilities.dp(4) + (diff / AndroidUtilities.dp(29)) * AndroidUtilities.dp(40); + } else { + diff -= AndroidUtilities.dp(29); + raduisBottom = AndroidUtilities.dp(44); + raduisTop = AndroidUtilities.dp(4) + (1.0f - diff / AndroidUtilities.dp(29)) * AndroidUtilities.dp(40); + } + if (LocaleController.isRTL && (radii[0] != raduisTop || radii[6] != raduisBottom) || !LocaleController.isRTL && (radii[2] != raduisTop || radii[4] != raduisBottom)) { + if (LocaleController.isRTL) { + radii[0] = radii[1] = raduisTop; + radii[6] = radii[7] = raduisBottom; + } else { + radii[2] = radii[3] = raduisTop; + radii[4] = radii[5] = raduisBottom; + } + path.reset(); + rect.set(LocaleController.isRTL ? AndroidUtilities.dp(10) : 0, 0, AndroidUtilities.dp(LocaleController.isRTL ? 98 : 88), AndroidUtilities.dp(88)); + path.addRoundRect(rect, radii, Path.Direction.CW); + path.close(); + } + StaticLayout layoutToDraw = letterLayout != null ? letterLayout : oldLetterLayout; + if (layoutToDraw != null) { + canvas.save(); + canvas.scale(bubbleProgress, bubbleProgress, scrollX, progressY - y); + canvas.drawPath(path, paint); + canvas.translate(textX, textY); + layoutToDraw.draw(canvas); + canvas.restore(); + } + } + if ((pressed && letterLayout != null && bubbleProgress < 1.0f) || (!pressed || letterLayout == null) && bubbleProgress > 0.0f) { + long newTime = System.currentTimeMillis(); + long dt = (newTime - lastUpdateTime); + if (dt < 0 || dt > 17) { + dt = 17; + } + lastUpdateTime = newTime; + invalidate(); + if (pressed && letterLayout != null) { + bubbleProgress += dt / 120.0f; + if (bubbleProgress > 1.0f) { + bubbleProgress = 1.0f; + } + } else { + bubbleProgress -= dt / 120.0f; + if (bubbleProgress < 0.0f) { + bubbleProgress = 0.0f; + } + } + } + } + + @Override + public void layout(int l, int t, int r, int b) { + if (!selfOnLayout) { + return; + } + super.layout(l, t, r, b); + } + + private void setProgress(float value) { + progress = value; + invalidate(); + } + } + + private class RecyclerListViewItemClickListener implements OnItemTouchListener { public RecyclerListViewItemClickListener(Context context) { - mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { if (currentChildView != null && onItemClickListener != null) { currentChildView.setPressed(true); final View view = currentChildView; - if (instantClick) { + final int position = currentChildPosition; + if (instantClick && position != -1) { view.playSoundEffect(SoundEffectConstants.CLICK); - onItemClickListener.onItemClick(view, currentChildPosition); + onItemClickListener.onItemClick(view, position); } AndroidUtilities.runOnUIThread(clickRunnable = new Runnable() { @Override @@ -81,8 +477,8 @@ public void run() { view.setPressed(false); if (!instantClick) { view.playSoundEffect(SoundEffectConstants.CLICK); - if (onItemClickListener != null) { - onItemClickListener.onItemClick(view, currentChildPosition); + if (onItemClickListener != null && position != -1) { + onItemClickListener.onItemClick(view, position); } } } @@ -90,10 +486,12 @@ public void run() { }, ViewConfiguration.getPressedStateDuration()); if (selectChildRunnable != null) { + View pressedChild = currentChildView; AndroidUtilities.cancelRunOnUIThread(selectChildRunnable); selectChildRunnable = null; currentChildView = null; interceptedByChild = false; + removeSelection(pressedChild, e); } } return true; @@ -104,7 +502,7 @@ public void onLongPress(MotionEvent event) { if (currentChildView != null) { View child = currentChildView; if (onItemLongClickListener != null) { - if (onItemLongClickListener.onItemClick(currentChildView, currentChildPosition)) { + if (currentChildPosition != -1 && onItemLongClickListener.onItemClick(currentChildView, currentChildPosition)) { child.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } @@ -121,9 +519,9 @@ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent event) { if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && currentChildView == null && isScrollIdle) { currentChildView = view.findChildViewUnder(event.getX(), event.getY()); if (currentChildView instanceof ViewGroup) { - ViewGroup viewGroup = (ViewGroup) currentChildView; float x = event.getX() - currentChildView.getLeft(); float y = event.getY() - currentChildView.getTop(); + ViewGroup viewGroup = (ViewGroup) currentChildView; final int count = viewGroup.getChildCount(); for (int i = count - 1; i >= 0; i--) { final View child = viewGroup.getChildAt(i); @@ -149,10 +547,10 @@ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent event) { if (currentChildView != null && !interceptedByChild) { try { if (event != null) { - mGestureDetector.onTouchEvent(event); + gestureDetector.onTouchEvent(event); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -168,15 +566,38 @@ public void run() { } }; AndroidUtilities.runOnUIThread(selectChildRunnable, ViewConfiguration.getTapTimeout()); + if (currentChildView.isEnabled()) { + positionSelector(currentChildPosition, currentChildView); + if (selectorDrawable != null) { + final Drawable d = selectorDrawable.getCurrent(); + if (d != null && d instanceof TransitionDrawable) { + if (onItemLongClickListener != null) { + ((TransitionDrawable) d).startTransition(ViewConfiguration.getLongPressTimeout()); + } else { + ((TransitionDrawable) d).resetTransition(); + } + } + if (Build.VERSION.SDK_INT >= 21) { + selectorDrawable.setHotspot(event.getX(), event.getY()); + } + } + updateSelectorState(); + } else { + selectorRect.setEmpty(); + } } - } else if (currentChildView != null && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL || !isScrollIdle)) { - if (selectChildRunnable != null) { - AndroidUtilities.cancelRunOnUIThread(selectChildRunnable); - selectChildRunnable = null; + } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL || !isScrollIdle) { + if (currentChildView != null) { + if (selectChildRunnable != null) { + AndroidUtilities.cancelRunOnUIThread(selectChildRunnable); + selectChildRunnable = null; + } + View pressedChild = currentChildView; + currentChildView.setPressed(false); + currentChildView = null; + interceptedByChild = false; + removeSelection(pressedChild, event); } - currentChildView.setPressed(false); - currentChildView = null; - interceptedByChild = false; } return false; } @@ -192,16 +613,39 @@ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } } + private void removeSelection(View pressedChild, MotionEvent event) { + if (pressedChild == null) { + return; + } + if (pressedChild != null && pressedChild.isEnabled()) { + positionSelector(currentChildPosition, pressedChild); + if (selectorDrawable != null) { + Drawable d = selectorDrawable.getCurrent(); + if (d != null && d instanceof TransitionDrawable) { + ((TransitionDrawable) d).resetTransition(); + } + if (event != null && Build.VERSION.SDK_INT >= 21) { + selectorDrawable.setHotspot(event.getX(), event.getY()); + } + } + } else { + selectorRect.setEmpty(); + } + updateSelectorState(); + } + public void cancelClickRunnables(boolean uncheck) { if (selectChildRunnable != null) { AndroidUtilities.cancelRunOnUIThread(selectChildRunnable); selectChildRunnable = null; } if (currentChildView != null) { + View child = currentChildView; if (uncheck) { currentChildView.setPressed(false); } currentChildView = null; + removeSelection(child, null); } if (clickRunnable != null) { AndroidUtilities.cancelRunOnUIThread(clickRunnable); @@ -214,6 +658,8 @@ public void cancelClickRunnables(boolean uncheck) { @Override public void onChanged() { checkIfEmpty(); + selectorRect.setEmpty(); + invalidate(); } @Override @@ -242,6 +688,10 @@ public int[] getResourceDeclareStyleableIntArray(String packageName, String name public RecyclerListView(Context context) { super(context); + setGlowColor(Theme.getColor(Theme.key_actionBarDefault)); + selectorDrawable = Theme.getSelectorDrawable(false); + selectorDrawable.setCallback(this); + try { if (!gotAttributes) { attributes = getResourceDeclareStyleableIntArray("com.android.internal", "View"); @@ -252,10 +702,12 @@ public RecyclerListView(Context context) { initializeScrollbars.invoke(this, a); a.recycle(); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - super.setOnScrollListener(new OnScrollListener() { + + boolean scrollingByUser; + @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState != SCROLL_STATE_IDLE && currentChildView != null) { @@ -265,19 +717,22 @@ public void onScrollStateChanged(RecyclerView recyclerView, int newState) { } MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); try { - mGestureDetector.onTouchEvent(event); + gestureDetector.onTouchEvent(event); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } currentChildView.onTouchEvent(event); event.recycle(); + View child = currentChildView; currentChildView.setPressed(false); currentChildView = null; + removeSelection(child, null); interceptedByChild = false; } if (onScrollListener != null) { onScrollListener.onScrollStateChanged(recyclerView, newState); } + scrollingByUser = newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_SETTLING; } @Override @@ -285,6 +740,133 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (onScrollListener != null) { onScrollListener.onScrolled(recyclerView, dx, dy); } + if (selectorPosition != NO_POSITION) { + selectorRect.offset(0, -dy); + selectorDrawable.setBounds(selectorRect); + invalidate(); + } else { + selectorRect.setEmpty(); + } + if (scrollingByUser && fastScroll != null || sectionsType != 0 && sectionsAdapter != null) { + LayoutManager layoutManager = getLayoutManager(); + if (layoutManager instanceof LinearLayoutManager) { + LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; + if (linearLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL) { + int firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition(); + if (firstVisibleItem == NO_POSITION) { + return; + } + if (scrollingByUser && fastScroll != null) { + Adapter adapter = getAdapter(); + if (adapter instanceof FastScrollAdapter) { + fastScroll.setProgress(firstVisibleItem / (float) adapter.getItemCount()); + } + } + if (sectionsAdapter != null) { + if (sectionsType == 1) { + int visibleItemCount = Math.abs(linearLayoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + headersCache.addAll(headers); + headers.clear(); + if (sectionsAdapter.getItemCount() == 0) { + return; + } + if (currentFirst != firstVisibleItem || currentVisible != visibleItemCount) { + currentFirst = firstVisibleItem; + currentVisible = visibleItemCount; + + sectionsCount = 1; + startSection = sectionsAdapter.getSectionForPosition(firstVisibleItem); + int itemNum = firstVisibleItem + sectionsAdapter.getCountForSection(startSection) - sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem); + while (true) { + if (itemNum >= firstVisibleItem + visibleItemCount) { + break; + } + itemNum += sectionsAdapter.getCountForSection(startSection + sectionsCount); + sectionsCount++; + } + } + + int itemNum = firstVisibleItem; + for (int a = startSection; a < startSection + sectionsCount; a++) { + View header = null; + if (!headersCache.isEmpty()) { + header = headersCache.get(0); + headersCache.remove(0); + } + header = getSectionHeaderView(a, header); + headers.add(header); + int count = sectionsAdapter.getCountForSection(a); + if (a == startSection) { + int pos = sectionsAdapter.getPositionInSectionForPosition(itemNum); + if (pos == count - 1) { + header.setTag(-header.getHeight()); + } else if (pos == count - 2) { + View child = getChildAt(itemNum - firstVisibleItem); + int headerTop; + if (child != null) { + headerTop = child.getTop(); + } else { + headerTop = -AndroidUtilities.dp(100); + } + if (headerTop < 0) { + header.setTag(headerTop); + } else { + header.setTag(0); + } + } else { + header.setTag(0); + } + itemNum += count - sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem); + } else { + View child = getChildAt(itemNum - firstVisibleItem); + if (child != null) { + header.setTag(child.getTop()); + } else { + header.setTag(-AndroidUtilities.dp(100)); + } + itemNum += count; + } + } + } else if (sectionsType == 2) { + if (sectionsAdapter.getItemCount() == 0) { + return; + } + int startSection = sectionsAdapter.getSectionForPosition(firstVisibleItem); + if (currentFirst != startSection || pinnedHeader == null) { + pinnedHeader = getSectionHeaderView(startSection, pinnedHeader); + currentFirst = startSection; + } + + int count = sectionsAdapter.getCountForSection(startSection); + + int pos = sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem); + if (pos == count - 1) { + View child = getChildAt(0); + int headerHeight = pinnedHeader.getHeight(); + int headerTop = 0; + if (child != null) { + int available = child.getTop() + child.getHeight(); + if (available < headerHeight) { + headerTop = available - headerHeight; + } + } else { + headerTop = -AndroidUtilities.dp(100); + } + if (headerTop < 0) { + pinnedHeader.setTag(headerTop); + } else { + pinnedHeader.setTag(0); + } + } else { + pinnedHeader.setTag(0); + } + + invalidate(); + } + } + } + } + } } }); addOnItemTouchListener(new RecyclerListViewItemClickListener(context)); @@ -297,6 +879,33 @@ public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { } } + @Override + protected void onMeasure(int widthSpec, int heightSpec) { + super.onMeasure(widthSpec, heightSpec); + if (fastScroll != null) { + fastScroll.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(132), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (fastScroll != null) { + selfOnLayout = true; + if (LocaleController.isRTL) { + fastScroll.layout(0, t, fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight()); + } else { + int x = getMeasuredWidth() - fastScroll.getMeasuredWidth(); + fastScroll.layout(x, t, x + fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight()); + } + selfOnLayout = false; + } + } + + public void setListSelectorColor(int color) { + Theme.setSelectorDrawableColor(selectorDrawable, color, true); + } + public void setOnItemClickListener(OnItemClickListener listener) { onItemClickListener = listener; } @@ -324,6 +933,12 @@ public void invalidateViews() { } } + public void updateFastScrollColors() { + if (fastScroll != null) { + fastScroll.updateColors(); + } + } + @Override public boolean onInterceptTouchEvent(MotionEvent e) { if (disallowInterceptTouchEvents) { @@ -358,12 +973,144 @@ public void setDisallowInterceptTouchEvents(boolean value) { disallowInterceptTouchEvents = value; } + public void setFastScrollEnabled() { + fastScroll = new FastScroll(getContext()); + if (getParent() != null) { + ((ViewGroup) getParent()).addView(fastScroll); + } + } + + public void setFastScrollVisible(boolean value) { + if (fastScroll == null) { + return; + } + fastScroll.setVisibility(value ? VISIBLE : GONE); + } + + public void setSectionsType(int type) { + sectionsType = type; + if (sectionsType == 1) { + headers = new ArrayList<>(); + headersCache = new ArrayList<>(); + } + } + + private void positionSelector(int position, View sel) { + positionSelector(position, sel, false, -1, -1); + } + + private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) { + if (selectorDrawable == null) { + return; + } + final boolean positionChanged = position != selectorPosition; + if (position != NO_POSITION) { + selectorPosition = position; + } + + selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom()); + + final boolean enabled = sel.isEnabled(); + if (isChildViewEnabled != enabled) { + isChildViewEnabled = enabled; + } + + if (positionChanged) { + selectorDrawable.setVisible(false, false); + selectorDrawable.setState(StateSet.NOTHING); + } + selectorDrawable.setBounds(selectorRect); + if (positionChanged) { + if (getVisibility() == VISIBLE) { + selectorDrawable.setVisible(true, false); + } + } + if (Build.VERSION.SDK_INT >= 21 && manageHotspot) { + selectorDrawable.setHotspot(x, y); + } + } + + private void updateSelectorState() { + if (selectorDrawable != null && selectorDrawable.isStateful()) { + if (currentChildView != null) { + if (selectorDrawable.setState(getDrawableStateForSelector())) { + invalidateDrawable(selectorDrawable); + } + } else { + selectorDrawable.setState(StateSet.NOTHING); + } + } + } + + private int[] getDrawableStateForSelector() { + final int[] state = onCreateDrawableState(1); + state[state.length - 1] = android.R.attr.state_pressed; + return state; + } + + @Override + public void onChildAttachedToWindow(View child) { + if (getAdapter() instanceof SelectionAdapter) { + ViewHolder holder = findContainingViewHolder(child); + if (holder != null) { + child.setEnabled(((SelectionAdapter) getAdapter()).isEnabled(holder)); + } + } else { + child.setEnabled(false); + } + super.onChildAttachedToWindow(child); + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + updateSelectorState(); + } + + @Override + public boolean verifyDrawable(Drawable drawable) { + return selectorDrawable == drawable || super.verifyDrawable(drawable); + } + + @Override + public void jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState(); + if (selectorDrawable != null) { + selectorDrawable.jumpToCurrentState(); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (fastScroll != null && fastScroll.getParent() != getParent()) { + ViewGroup parent = (ViewGroup) fastScroll.getParent(); + if (parent != null) { + parent.removeView(fastScroll); + } + parent = (ViewGroup) getParent(); + parent.addView(fastScroll); + } + } + @Override public void setAdapter(Adapter adapter) { final Adapter oldAdapter = getAdapter(); if (oldAdapter != null) { oldAdapter.unregisterAdapterDataObserver(observer); } + if (headers != null) { + headers.clear(); + headersCache.clear(); + } + selectorPosition = NO_POSITION; + selectorRect.setEmpty(); + pinnedHeader = null; + if (adapter instanceof SectionsAdapter) { + sectionsAdapter = (SectionsAdapter) adapter; + } else { + sectionsAdapter = null; + } super.setAdapter(adapter); if (adapter != null) { adapter.registerAdapterDataObserver(observer); @@ -375,12 +1122,8 @@ public void setAdapter(Adapter adapter) { public void stopScroll() { try { super.stopScroll(); - } catch (NullPointerException exception) { - /** - * The mLayout has been disposed of before the - * RecyclerView and this stops the application - * from crashing. - */ + } catch (NullPointerException ignore) { + } } @@ -388,4 +1131,149 @@ public void stopScroll() { public boolean hasOverlappingRendering() { return false; } + + private View getSectionHeaderView(int section, View oldView) { + boolean shouldLayout = oldView == null; + View view = sectionsAdapter.getSectionHeaderView(section, oldView); + if (shouldLayout) { + ensurePinnedHeaderLayout(view, false); + } + return view; + } + + private void ensurePinnedHeaderLayout(View header, boolean forceLayout) { + if (header.isLayoutRequested() || forceLayout) { + if (sectionsType == 1) { + ViewGroup.LayoutParams layoutParams = header.getLayoutParams(); + int heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY); + int widthSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY); + try { + header.measure(widthSpec, heightSpec); + } catch (Exception e) { + FileLog.e(e); + } + } else if (sectionsType == 2) { + int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); + int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + try { + header.measure(widthSpec, heightSpec); + } catch (Exception e) { + FileLog.e(e); + } + } + header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (sectionsType == 1) { + if (sectionsAdapter == null || headers.isEmpty()) { + return; + } + for (int a = 0; a < headers.size(); a++) { + View header = headers.get(a); + ensurePinnedHeaderLayout(header, true); + } + } else if (sectionsType == 2) { + if (sectionsAdapter == null || pinnedHeader == null) { + return; + } + ensurePinnedHeaderLayout(pinnedHeader, true); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (sectionsType == 1) { + if (sectionsAdapter == null || headers.isEmpty()) { + return; + } + for (int a = 0; a < headers.size(); a++) { + View header = headers.get(a); + int saveCount = canvas.save(); + int top = (Integer) header.getTag(); + canvas.translate(LocaleController.isRTL ? getWidth() - header.getWidth() : 0, top); + canvas.clipRect(0, 0, getWidth(), header.getMeasuredHeight()); + header.draw(canvas); + canvas.restoreToCount(saveCount); + } + } else if (sectionsType == 2) { + if (sectionsAdapter == null || pinnedHeader == null) { + return; + } + int saveCount = canvas.save(); + int top = (Integer) pinnedHeader.getTag(); + canvas.translate(LocaleController.isRTL ? getWidth() - pinnedHeader.getWidth() : 0, top); + canvas.clipRect(0, 0, getWidth(), pinnedHeader.getMeasuredHeight()); + pinnedHeader.draw(canvas); + canvas.restoreToCount(saveCount); + } + + if (!selectorRect.isEmpty()) { + selectorDrawable.setBounds(selectorRect); + selectorDrawable.draw(canvas); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + selectorPosition = NO_POSITION; + selectorRect.setEmpty(); + } + + public ArrayList getHeaders() { + return headers; + } + + public ArrayList getHeadersCache() { + return headersCache; + } + + public View getPinnedHeader() { + return pinnedHeader; + } + + /* + void keyPressed() { + if (!isEnabled() || !isClickable()) { + return; + } + + Drawable selector = mSelector; + Rect selectorRect = mSelectorRect; + if (selector != null && (isFocused() || touchModeDrawsInPressedState()) + && !selectorRect.isEmpty()) { + + final View v = getChildAt(mSelectedPosition - mFirstPosition); + + if (v != null) { + if (v.hasFocusable()) return; + v.setPressed(true); + } + setPressed(true); + + final boolean longClickable = isLongClickable(); + Drawable d = selector.getCurrent(); + if (d != null && d instanceof TransitionDrawable) { + if (longClickable) { + ((TransitionDrawable) d).startTransition( + ViewConfiguration.getLongPressTimeout()); + } else { + ((TransitionDrawable) d).resetTransition(); + } + } + if (longClickable && !mDataChanged) { + if (mPendingCheckForKeyLongPress == null) { + mPendingCheckForKeyLongPress = new CheckForKeyLongPress(); + } + mPendingCheckForKeyLongPress.rememberWindowAttachCount(); + postDelayed(mPendingCheckForKeyLongPress, ViewConfiguration.getLongPressTimeout()); + } + } + } + */ } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java index 47b87852420..161306b5dab 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ScrollSlidingTabStrip.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -12,6 +12,7 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; +import android.graphics.drawable.Drawable; import android.os.Build; import android.util.TypedValue; import android.view.Gravity; @@ -94,14 +95,14 @@ public void selectTab(int num) { } } - public TextView addIconTabWithCounter(int resId) { + public TextView addIconTabWithCounter(Drawable drawable) { final int position = tabCount++; FrameLayout tab = new FrameLayout(getContext()); tab.setFocusable(true); tabsContainer.addView(tab); ImageView imageView = new ImageView(getContext()); - imageView.setImageResource(resId); + imageView.setImageDrawable(drawable); imageView.setScaleType(ImageView.ScaleType.CENTER); tab.setOnClickListener(new OnClickListener() { @Override @@ -126,11 +127,11 @@ public void onClick(View v) { return textView; } - public void addIconTab(int resId) { + public void addIconTab(Drawable drawable) { final int position = tabCount++; ImageView tab = new ImageView(getContext()); tab.setFocusable(true); - tab.setImageResource(resId); + tab.setImageDrawable(drawable); tab.setScaleType(ImageView.ScaleType.CENTER); tab.setOnClickListener(new OnClickListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SectionsListView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SectionsListView.java deleted file mode 100644 index 3c199004855..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SectionsListView.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.view.View; -import android.widget.AbsListView; -import android.widget.ListAdapter; -import android.widget.ListView; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.FileLog; -import org.telegram.ui.Adapters.BaseSectionsAdapter; - -public class SectionsListView extends ListView implements AbsListView.OnScrollListener { - - private View pinnedHeader; - private OnScrollListener mOnScrollListener; - private BaseSectionsAdapter mAdapter; - private int currentStartSection = -1; - - public SectionsListView(Context context) { - super(context); - super.setOnScrollListener(this); - } - - public SectionsListView(Context context, AttributeSet attrs) { - super(context, attrs); - super.setOnScrollListener(this); - } - - public SectionsListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - super.setOnScrollListener(this); - } - - @Override - public void setAdapter(ListAdapter adapter) { - if (mAdapter == adapter) { - return; - } - pinnedHeader = null; - if (adapter instanceof BaseSectionsAdapter) { - mAdapter = (BaseSectionsAdapter) adapter; - } else { - mAdapter = null; - } - super.setAdapter(adapter); - } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (mOnScrollListener != null) { - mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); - } - if (mAdapter == null) { - return; - } - - if (mAdapter.getCount() == 0) { - return; - } - - int startSection = mAdapter.getSectionForPosition(firstVisibleItem); - if (currentStartSection != startSection || pinnedHeader == null) { - pinnedHeader = getSectionHeaderView(startSection, pinnedHeader); - currentStartSection = startSection; - } - - int count = mAdapter.getCountForSection(startSection); - - int pos = mAdapter.getPositionInSectionForPosition(firstVisibleItem); - if (pos == count - 1) { - View child = getChildAt(0); - int headerHeight = pinnedHeader.getHeight(); - int headerTop = 0; - if (child != null) { - int available = child.getTop() + child.getHeight(); - if (available < headerHeight) { - headerTop = available - headerHeight; - } - } else { - headerTop = -AndroidUtilities.dp(100); - } - if (headerTop < 0) { - pinnedHeader.setTag(headerTop); - } else { - pinnedHeader.setTag(0); - } - } else { - pinnedHeader.setTag(0); - } - - invalidate(); - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (mOnScrollListener != null) { - mOnScrollListener.onScrollStateChanged(view, scrollState); - } - } - - private View getSectionHeaderView(int section, View oldView) { - boolean shouldLayout = oldView == null; - View view = mAdapter.getSectionHeaderView(section, oldView, this); - if (shouldLayout) { - ensurePinnedHeaderLayout(view, false); - } - return view; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (mAdapter == null || pinnedHeader == null) { - return; - } - ensurePinnedHeaderLayout(pinnedHeader, true); - } - - private void ensurePinnedHeaderLayout(View header, boolean forceLayout) { - if (header.isLayoutRequested() || forceLayout) { - int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); - int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - try { - header.measure(widthSpec, heightSpec); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight()); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (mAdapter == null || pinnedHeader == null) { - return; - } - int saveCount = canvas.save(); - int top = (Integer)pinnedHeader.getTag(); - canvas.translate(LocaleController.isRTL ? getWidth() - pinnedHeader.getWidth() : 0, top); - canvas.clipRect(0, 0, getWidth(), pinnedHeader.getMeasuredHeight()); - pinnedHeader.draw(canvas); - canvas.restoreToCount(saveCount); - } - - @Override - public void setOnScrollListener(OnScrollListener l) { - mOnScrollListener = l; - } - - public void setOnItemClickListener(OnItemClickListener listener) { - super.setOnItemClickListener(listener); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java index df2110a74c0..816630b211a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBar.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -92,6 +92,10 @@ public void setProgress(float progress) { } } + public float getProgress() { + return (float) thumbX / (float) (width - thumbWidth); + } + public boolean isDragging() { return pressed; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java index 35f47941b3e..3b4bf7d744c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarWaveform.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileDrawable.java index 60c13ea6606..e3f25a8674c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileDrawable.java @@ -3,81 +3,41 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.RectF; import android.graphics.drawable.Drawable; -import android.view.animation.DecelerateInterpolator; import org.telegram.messenger.AndroidUtilities; import org.telegram.ui.ActionBar.Theme; public class SendingFileDrawable extends Drawable { - private float radOffset = 0; - private float currentProgress = 0; - private float animationProgressStart = 0; - private long currentProgressTime = 0; - private float animatedProgressValue = 0; - private RectF cicleRect = new RectF(); private boolean isChat = false; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private long lastUpdateTime = 0; private boolean started = false; - private static DecelerateInterpolator decelerateInterpolator = null; - - public SendingFileDrawable() { - super(); - paint.setColor(Theme.ACTION_BAR_SUBTITLE_COLOR); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(AndroidUtilities.dp(2)); - paint.setStrokeCap(Paint.Cap.ROUND); - decelerateInterpolator = new DecelerateInterpolator(); - } + private float progress; public void setIsChat(boolean value) { isChat = value; } - public void setProgress(float value, boolean animated) { - if (!animated) { - animatedProgressValue = value; - animationProgressStart = value; - } else { - animationProgressStart = animatedProgressValue; - } - currentProgress = value; - currentProgressTime = 0; - - invalidateSelf(); - } - private void update() { long newTime = System.currentTimeMillis(); long dt = newTime - lastUpdateTime; lastUpdateTime = newTime; - - if (animatedProgressValue != 1) { - radOffset += 360 * dt / 1000.0f; - float progressDiff = currentProgress - animationProgressStart; - if (progressDiff > 0) { - currentProgressTime += dt; - if (currentProgressTime >= 300) { - animatedProgressValue = currentProgress; - animationProgressStart = currentProgress; - currentProgressTime = 0; - } else { - animatedProgressValue = animationProgressStart + progressDiff * decelerateInterpolator.getInterpolation(currentProgressTime / 300.0f); - } - } - invalidateSelf(); + if (dt > 50) { + dt = 50; + } + progress += dt / 500.0f; + while (progress > 1.0f) { + progress -= 1.0f; } + invalidateSelf(); } public void start() { @@ -92,8 +52,18 @@ public void stop() { @Override public void draw(Canvas canvas) { - cicleRect.set(AndroidUtilities.dp(1), AndroidUtilities.dp(isChat ? 3 : 4), AndroidUtilities.dp(10), AndroidUtilities.dp(isChat ? 11 : 12)); - canvas.drawArc(cicleRect, -90 + radOffset, Math.max(60, 360 * animatedProgressValue), false, paint); + for (int a = 0; a < 3; a++) { + if (a == 0) { + Theme.chat_statusRecordPaint.setAlpha((int) (255 * progress)); + } else if (a == 2) { + Theme.chat_statusRecordPaint.setAlpha((int) (255 * (1.0f - progress))); + } else { + Theme.chat_statusRecordPaint.setAlpha(255); + } + float side = AndroidUtilities.dp(5) * a + AndroidUtilities.dp(5) * progress; + canvas.drawLine(side, AndroidUtilities.dp(isChat ? 3 : 4), side + AndroidUtilities.dp(4), AndroidUtilities.dp(isChat ? 7 : 8), Theme.chat_statusRecordPaint); + canvas.drawLine(side, AndroidUtilities.dp(isChat ? 11 : 12), side + AndroidUtilities.dp(4), AndroidUtilities.dp(isChat ? 7 : 8), Theme.chat_statusRecordPaint); + } if (started) { update(); @@ -117,7 +87,7 @@ public int getOpacity() { @Override public int getIntrinsicWidth() { - return AndroidUtilities.dp(14); + return AndroidUtilities.dp(18); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileEx2Drawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileEx2Drawable.java deleted file mode 100644 index 5770e9d0434..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileEx2Drawable.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.ui.ActionBar.Theme; - -public class SendingFileEx2Drawable extends Drawable { - - private boolean isChat = false; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private long lastUpdateTime = 0; - private boolean started = false; - private float progress; - - public SendingFileEx2Drawable() { - super(); - paint.setColor(Theme.ACTION_BAR_SUBTITLE_COLOR); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(AndroidUtilities.dp(3)); - paint.setStrokeCap(Paint.Cap.ROUND); - } - - public void setIsChat(boolean value) { - isChat = value; - } - - private void update() { - long newTime = System.currentTimeMillis(); - long dt = newTime - lastUpdateTime; - lastUpdateTime = newTime; - if (dt > 50) { - dt = 50; - } - progress += dt / 1000.0f; - while (progress > 1.0f) { - progress -= 1.0f; - } - invalidateSelf(); - } - - public void start() { - lastUpdateTime = System.currentTimeMillis(); - started = true; - invalidateSelf(); - } - - public void stop() { - started = false; - } - - @Override - public void draw(Canvas canvas) { - int start = (int) (progress <= 0.5f ? AndroidUtilities.dp(1) : AndroidUtilities.dp(11) * (progress - 0.5f) * 2); - int end = (int) (progress >= 0.5f ? AndroidUtilities.dp(11) : AndroidUtilities.dp(11) * progress * 2); - canvas.drawLine(start, AndroidUtilities.dp(isChat ? 11 : 12), end, AndroidUtilities.dp(isChat ? 11 : 12), paint); - - if (started) { - update(); - } - } - - @Override - public void setAlpha(int alpha) { - - } - - @Override - public void setColorFilter(ColorFilter cf) { - - } - - @Override - public int getOpacity() { - return 0; - } - - @Override - public int getIntrinsicWidth() { - return AndroidUtilities.dp(18); - } - - @Override - public int getIntrinsicHeight() { - return AndroidUtilities.dp(14); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileExDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileExDrawable.java deleted file mode 100644 index 9953d0bfcd7..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SendingFileExDrawable.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.graphics.Canvas; -import android.graphics.ColorFilter; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.ui.ActionBar.Theme; - -public class SendingFileExDrawable extends Drawable { - - private boolean isChat = false; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); - private long lastUpdateTime = 0; - private boolean started = false; - private float progress; - - public SendingFileExDrawable() { - super(); - paint.setColor(Theme.ACTION_BAR_SUBTITLE_COLOR); - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(AndroidUtilities.dp(2)); - paint.setStrokeCap(Paint.Cap.ROUND); - } - - public void setIsChat(boolean value) { - isChat = value; - } - - private void update() { - long newTime = System.currentTimeMillis(); - long dt = newTime - lastUpdateTime; - lastUpdateTime = newTime; - if (dt > 50) { - dt = 50; - } - progress += dt / 500.0f; - while (progress > 1.0f) { - progress -= 1.0f; - } - invalidateSelf(); - } - - public void start() { - lastUpdateTime = System.currentTimeMillis(); - started = true; - invalidateSelf(); - } - - public void stop() { - started = false; - } - - @Override - public void draw(Canvas canvas) { - - for (int a = 0; a < 3; a++) { - if (a == 0) { - paint.setAlpha((int) (255 * progress)); - } else if (a == 2) { - paint.setAlpha((int) (255 * (1.0f - progress))); - } else { - paint.setAlpha(255); - } - float side = AndroidUtilities.dp(5) * a + AndroidUtilities.dp(5) * progress; - canvas.drawLine(side, AndroidUtilities.dp(isChat ? 3 : 4), side + AndroidUtilities.dp(4), AndroidUtilities.dp(isChat ? 7 : 8), paint); - canvas.drawLine(side, AndroidUtilities.dp(isChat ? 11 : 12), side + AndroidUtilities.dp(4), AndroidUtilities.dp(isChat ? 7 : 8), paint); - } - - if (started) { - update(); - } - } - - @Override - public void setAlpha(int alpha) { - - } - - @Override - public void setColorFilter(ColorFilter cf) { - - } - - @Override - public int getOpacity() { - return 0; - } - - @Override - public int getIntrinsicWidth() { - return AndroidUtilities.dp(18); - } - - @Override - public int getIntrinsicHeight() { - return AndroidUtilities.dp(14); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java index 762e63d65dd..8d7b6668e1c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShareAlert.java @@ -3,14 +3,16 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.annotation.SuppressLint; import android.content.Context; -import android.graphics.*; +import android.graphics.Canvas; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Editable; @@ -91,10 +93,11 @@ public class ShareAlert extends BottomSheet implements NotificationCenter.Notifi private int scrollOffsetY; private int topBeforeSwitch; - public ShareAlert(final Context context, MessageObject messageObject, final String text, boolean publicChannel, final String copyLink) { + public ShareAlert(final Context context, MessageObject messageObject, final String text, boolean publicChannel, final String copyLink, boolean fullScreen) { super(context, true); - shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); linkToCopy = copyLink; sendingMessageObject = messageObject; @@ -185,7 +188,7 @@ protected void onDraw(Canvas canvas) { containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); frameLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -195,7 +198,7 @@ public boolean onTouch(View v, MotionEvent event) { doneButton = new LinearLayout(context); doneButton.setOrientation(LinearLayout.HORIZONTAL); - doneButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); + doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.getColor(Theme.key_dialogButtonSelector), 0)); doneButton.setPadding(AndroidUtilities.dp(21), 0, AndroidUtilities.dp(21), 0); frameLayout.addView(doneButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); doneButton.setOnClickListener(new View.OnClickListener() { @@ -229,9 +232,9 @@ public void onClick(View v) { doneButtonBadgeTextView = new TextView(context); doneButtonBadgeTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); doneButtonBadgeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); - doneButtonBadgeTextView.setTextColor(Theme.SHARE_SHEET_BADGE_TEXT_COLOR); + doneButtonBadgeTextView.setTextColor(Theme.getColor(Theme.key_dialogBadgeText)); doneButtonBadgeTextView.setGravity(Gravity.CENTER); - doneButtonBadgeTextView.setBackgroundResource(R.drawable.bluecounter); + doneButtonBadgeTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(12.5f), Theme.getColor(Theme.key_dialogBadgeBackground))); doneButtonBadgeTextView.setMinWidth(AndroidUtilities.dp(23)); doneButtonBadgeTextView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), AndroidUtilities.dp(1)); doneButton.addView(doneButtonBadgeTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 23, Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); @@ -244,7 +247,8 @@ public void onClick(View v) { doneButton.addView(doneButtonTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL)); ImageView imageView = new ImageView(context); - imageView.setImageResource(R.drawable.search_share); + imageView.setImageResource(R.drawable.ic_ab_search); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); imageView.setScaleType(ImageView.ScaleType.CENTER); imageView.setPadding(0, AndroidUtilities.dp(2), 0, 0); frameLayout.addView(imageView, LayoutHelper.createFrame(48, 48, Gravity.LEFT | Gravity.CENTER_VERTICAL)); @@ -256,11 +260,11 @@ public void onClick(View v) { nameTextView.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); nameTextView.setBackgroundDrawable(null); - nameTextView.setHintTextColor(Theme.SHARE_SHEET_EDIT_PLACEHOLDER_TEXT_COLOR); + nameTextView.setHintTextColor(Theme.getColor(Theme.key_dialogTextHint)); nameTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); nameTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); AndroidUtilities.clearCursorDrawable(nameTextView); - nameTextView.setTextColor(Theme.SHARE_SHEET_EDIT_TEXT_COLOR); + nameTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 48, 2, 96, 0)); nameTextView.addTextChangedListener(new TextWatcher() { @Override @@ -312,7 +316,7 @@ public void afterTextChanged(Editable s) { gridView.addItemDecoration(new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - Holder holder = (Holder) parent.getChildViewHolder(view); + RecyclerListView.Holder holder = (RecyclerListView.Holder) parent.getChildViewHolder(view); if (holder != null) { int pos = holder.getAdapterPosition(); outRect.left = pos % 4 == 0 ? 0 : AndroidUtilities.dp(4); @@ -325,7 +329,7 @@ public void getItemOffsets(android.graphics.Rect outRect, View view, RecyclerVie }); containerView.addView(gridView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 48, 0, 0)); gridView.setAdapter(listAdapter = new ShareDialogsAdapter(context)); - gridView.setGlowColor(0xfff5f6f7); + gridView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); gridView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override public void onItemClick(View view, int position) { @@ -387,7 +391,7 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { private int getCurrentTop() { if (gridView.getChildCount() != 0) { View child = gridView.getChildAt(0); - Holder holder = (Holder) gridView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) gridView.findContainingViewHolder(child); if (holder != null) { return gridView.getPaddingTop() - (holder.getAdapterPosition() == 0 && child.getTop() >= 0 ? child.getTop() : 0); } @@ -417,7 +421,7 @@ private void updateLayout() { return; } View child = gridView.getChildAt(0); - Holder holder = (Holder) gridView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) gridView.findContainingViewHolder(child); int top = child.getTop() - AndroidUtilities.dp(8); int newOffset = top > 0 && holder != null && holder.getAdapterPosition() == 0 ? top : 0; if (scrollOffsetY != newOffset) { @@ -439,7 +443,7 @@ private void copyLink(Context context) { clipboard.setPrimaryClip(clip); Toast.makeText(context, LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -447,11 +451,11 @@ public void updateSelectedCount() { if (selectedDialogs.isEmpty()) { doneButtonBadgeTextView.setVisibility(View.GONE); if (!isPublicChannel && linkToCopy == null) { - doneButtonTextView.setTextColor(Theme.SHARE_SHEET_SEND_DISABLED_TEXT_COLOR); + doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextGray4)); doneButton.setEnabled(false); doneButtonTextView.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); } else { - doneButtonTextView.setTextColor(Theme.SHARE_SHEET_COPY_TEXT_COLOR); + doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); doneButton.setEnabled(true); doneButtonTextView.setText(LocaleController.getString("CopyLink", R.string.CopyLink).toUpperCase()); } @@ -459,7 +463,7 @@ public void updateSelectedCount() { doneButtonTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); doneButtonBadgeTextView.setVisibility(View.VISIBLE); doneButtonBadgeTextView.setText(String.format("%d", selectedDialogs.size())); - doneButtonTextView.setTextColor(Theme.SHARE_SHEET_SEND_TEXT_COLOR); + doneButtonTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlue3)); doneButton.setEnabled(true); doneButtonTextView.setText(LocaleController.getString("Send", R.string.Send).toUpperCase()); } @@ -471,14 +475,7 @@ public void dismiss() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.dialogsNeedReload); } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - - private class ShareDialogsAdapter extends RecyclerView.Adapter { + private class ShareDialogsAdapter extends RecyclerListView.SelectionAdapter { private Context context; private int currentCount; @@ -522,15 +519,15 @@ public TLRPC.TL_dialog getItem(int i) { } @Override - public long getItemId(int i) { - return i; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = new ShareDialogCell(context); view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(100))); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -546,7 +543,7 @@ public int getItemViewType(int i) { } } - public class ShareSearchAdapter extends RecyclerView.Adapter { + public class ShareSearchAdapter extends RecyclerListView.SelectionAdapter { private Context context; private Timer searchTimer; @@ -759,7 +756,7 @@ public int compare(DialogSearchResult lhs, DialogSearchResult rhs) { updateSearchResults(searchResults, searchId); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -808,7 +805,7 @@ public void searchDialogs(final String query) { searchTimer = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (query == null || query.length() == 0) { searchResult.clear(); @@ -825,7 +822,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } searchDialogsInternal(query, searchId); } @@ -839,6 +836,9 @@ public int getItemCount() { } public TLRPC.TL_dialog getItem(int i) { + if (i < 0 || i >= searchResult.size()) { + return null; + } return searchResult.get(i).dialog; } @@ -847,11 +847,16 @@ public long getItemId(int i) { return i; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = new ShareDialogCell(context); view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(100))); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShutterButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShutterButton.java index 2d0b667d2b5..175af7842d4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ShutterButton.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ShutterButton.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -28,7 +28,7 @@ public enum State { RECORDING } - private final static int LONG_PRESS_TIME = 220; + private final static int LONG_PRESS_TIME = 800; private Drawable shadowDrawable; @@ -41,17 +41,20 @@ public enum State { private float redProgress; private long lastUpdateTime; private long totalTime; + private boolean processRelease; private Runnable longPressed = new Runnable() { public void run() { if (delegate != null) { - delegate.shutterLongPressed(); + if (!delegate.shutterLongPressed()) { + processRelease = false; + } } } }; public interface ShutterButtonDelegate { - void shutterLongPressed(); + boolean shutterLongPressed(); void shutterReleased(); void shutterCancel(); } @@ -150,12 +153,13 @@ public boolean onTouchEvent(MotionEvent motionEvent) { case MotionEvent.ACTION_DOWN: AndroidUtilities.runOnUIThread(longPressed, LONG_PRESS_TIME); pressed = true; + processRelease = true; setHighlighted(true); break; case MotionEvent.ACTION_UP: setHighlighted(false); AndroidUtilities.cancelRunOnUIThread(longPressed); - if (x >= 0 && y >= 0 && x <= getMeasuredWidth() && y <= getMeasuredHeight()) { + if (processRelease && x >= 0 && y >= 0 && x <= getMeasuredWidth() && y <= getMeasuredHeight()) { delegate.shutterReleased(); } break; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Size.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Size.java index 9a840098631..2cbb2e3794c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Size.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Size.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java index 0b3b2f80e9d..76642f211ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java @@ -3,23 +3,24 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.os.Build; import android.view.View; import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; +import org.telegram.ui.ActionBar.ActionBar; public class SizeNotifierFrameLayout extends FrameLayout { @@ -40,6 +41,7 @@ public SizeNotifierFrameLayout(Context context) { public void setBackgroundImage(Drawable bitmap) { backgroundDrawable = bitmap; + invalidate(); } public Drawable getBackgroundImage() { @@ -105,16 +107,18 @@ protected void onDraw(Canvas canvas) { backgroundDrawable.draw(canvas); canvas.restore(); } else { + int actionBarHeight = (isActionBarVisible() ? ActionBar.getCurrentActionBarHeight() : 0) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0); + int viewHeight = getMeasuredHeight() - actionBarHeight; float scaleX = (float) getMeasuredWidth() / (float) backgroundDrawable.getIntrinsicWidth(); - float scaleY = (float) (getMeasuredHeight() + keyboardHeight) / (float) backgroundDrawable.getIntrinsicHeight(); + float scaleY = (float) (viewHeight + keyboardHeight) / (float) backgroundDrawable.getIntrinsicHeight(); float scale = scaleX < scaleY ? scaleY : scaleX; int width = (int) Math.ceil(backgroundDrawable.getIntrinsicWidth() * scale); int height = (int) Math.ceil(backgroundDrawable.getIntrinsicHeight() * scale); int x = (getMeasuredWidth() - width) / 2; - int y = (getMeasuredHeight() - height + keyboardHeight) / 2; + int y = (viewHeight - height + keyboardHeight) / 2 + actionBarHeight; if (bottomClip != 0) { canvas.save(); - canvas.clipRect(0, 0, width, getMeasuredHeight() - bottomClip); + canvas.clipRect(0, actionBarHeight, width, getMeasuredHeight() - bottomClip); } backgroundDrawable.setBounds(x, y, x + width, y + height); backgroundDrawable.draw(canvas); @@ -127,4 +131,8 @@ protected void onDraw(Canvas canvas) { super.onDraw(canvas); } } + + protected boolean isActionBarVisible() { + return true; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java index 6eee4fa7874..cf892ae8e01 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -56,7 +56,7 @@ public int getKeyboardHeight() { int usableViewHeight = rootView.getHeight() - AndroidUtilities.getViewInset(rootView); int top = rect.top; int size = AndroidUtilities.displaySize.y - top - usableViewHeight; - if (size <= AndroidUtilities.dp(10)) { + if (size <= Math.max(AndroidUtilities.dp(10), AndroidUtilities.statusBarHeight)) { size = 0; } return size; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java index c7ad2b5be19..99fe459d238 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -22,7 +22,7 @@ public String getHeaderName() { return ""; } - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlidingTabView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlidingTabView.java index 8cb1adadbd6..d465244ea5d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlidingTabView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlidingTabView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -58,7 +58,7 @@ public void addTextTab(final int position, String title) { tab.setTextColor(0xffffffff); tab.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); tab.setTypeface(Typeface.DEFAULT_BOLD); - tab.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false)); + tab.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); tab.setOnClickListener(new OnClickListener() { @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SpannableStringLight.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SpannableStringLight.java index 2bb9985082d..c98cc3df4a0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SpannableStringLight.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SpannableStringLight.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -34,7 +34,7 @@ public SpannableStringLight(CharSequence source) { mSpanDataOverride = (int[]) mSpanDataField.get(this); mSpanCountOverride = (int) mSpanCountField.get(this); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -50,7 +50,7 @@ public void setSpansCount(int count) { mSpanDataField.set(this, mSpanDataOverride); mSpanCountField.set(this, mSpanCountOverride); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -66,7 +66,7 @@ public static boolean isFieldsAvailable() { mSpanCountField = SpannableString.class.getSuperclass().getDeclaredField("mSpanCount"); mSpanCountField.setAccessible(true); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fieldsAvailable = true; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StaticLayoutEx.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StaticLayoutEx.java index 8739fb7380b..ab01854a3ea 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StaticLayoutEx.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StaticLayoutEx.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -70,7 +70,7 @@ public static void init() { sConstructorArgs = new Object[signature.length]; initialized = true; } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -97,7 +97,7 @@ public static StaticLayout createStaticLayout(CharSequence source, int bufstart, sConstructorArgs[12] = maxLines; return sConstructor.newInstance(sConstructorArgs); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } }*/ try { @@ -105,7 +105,20 @@ public static StaticLayout createStaticLayout(CharSequence source, int bufstart, CharSequence text = TextUtils.ellipsize(source, paint, ellipsisWidth, TextUtils.TruncateAt.END); return new StaticLayout(text, 0, text.length(), paint, outerWidth, align, spacingMult, spacingAdd, includePad); } else { - StaticLayout layout = new StaticLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, includePad); + StaticLayout layout; + if (Build.VERSION.SDK_INT >= 23) { + StaticLayout.Builder builder = StaticLayout.Builder.obtain(source, 0, source.length(), paint, outerWidth) + .setAlignment(align) + .setLineSpacing(spacingAdd, spacingMult) + .setIncludePad(includePad) + .setEllipsize(null) + .setEllipsizedWidth(ellipsisWidth) + .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY) + .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL); + layout = builder.build(); + } else { + layout = new StaticLayout(source, paint, outerWidth, align, spacingMult, spacingAdd, includePad); + } if (layout.getLineCount() <= maxLines) { return layout; } else { @@ -122,7 +135,7 @@ public static StaticLayout createStaticLayout(CharSequence source, int bufstart, } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java index b503318abd0..d760e8f7c51 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickerMasksView.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.content.Context; +import android.graphics.drawable.Drawable; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; @@ -26,6 +27,7 @@ import org.telegram.messenger.support.widget.GridLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.EmptyCell; import org.telegram.ui.Cells.StickerEmojiCell; import org.telegram.ui.StickerPreviewViewer; @@ -68,7 +70,7 @@ public StickerMasksView(final Context context) { stickersGridView = new RecyclerListView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersGridView, StickerMasksView.this.getMeasuredHeight()); + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, stickersGridView, StickerMasksView.this.getMeasuredHeight(), null); return super.onInterceptTouchEvent(event) || result; } }; @@ -89,7 +91,7 @@ public int getSpanSize(int position) { stickersGridView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - return StickerPreviewViewer.getInstance().onTouch(event, stickersGridView, StickerMasksView.this.getMeasuredHeight(), stickersOnItemClickListener); + return StickerPreviewViewer.getInstance().onTouch(event, stickersGridView, StickerMasksView.this.getMeasuredHeight(), stickersOnItemClickListener, null); } }); stickersOnItemClickListener = new RecyclerListView.OnItemClickListener() { @@ -197,17 +199,21 @@ private void updateStickerTabs() { int lastPosition = scrollSlidingTabStrip.getCurrentPosition(); scrollSlidingTabStrip.removeTabs(); if (currentType == StickersQuery.TYPE_IMAGE) { - scrollSlidingTabStrip.addIconTab(R.drawable.ic_masks_msk1); + Drawable drawable = getContext().getResources().getDrawable(R.drawable.ic_masks_msk1); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + scrollSlidingTabStrip.addIconTab(drawable); stickersEmptyView.setText(LocaleController.getString("NoStickers", R.string.NoStickers)); } else { - scrollSlidingTabStrip.addIconTab(R.drawable.ic_masks_sticker1); + Drawable drawable = getContext().getResources().getDrawable(R.drawable.ic_masks_sticker1); + Theme.setDrawableColorByKey(drawable, Theme.key_chat_emojiPanelIcon); + scrollSlidingTabStrip.addIconTab(drawable); stickersEmptyView.setText(LocaleController.getString("NoMasks", R.string.NoMasks)); } if (!recentStickers[currentType].isEmpty()) { recentTabBum = stickersTabOffset; stickersTabOffset++; - scrollSlidingTabStrip.addIconTab(R.drawable.ic_masks_recent); + scrollSlidingTabStrip.addIconTab(Theme.createEmojiIconSelectorDrawable(getContext(), R.drawable.ic_masks_recent1, Theme.getColor(Theme.key_chat_emojiPanelMasksIcon), Theme.getColor(Theme.key_chat_emojiPanelMasksIconSelected))); } stickerSets[currentType].clear(); @@ -340,7 +346,7 @@ public void didReceivedNotification(int id, Object... args) { } } - private class StickersGridAdapter extends RecyclerView.Adapter { + private class StickersGridAdapter extends RecyclerListView.SelectionAdapter { private Context context; private int stickersPerRow; @@ -390,6 +396,11 @@ public int getTabForPosition(int position) { return stickerSets[currentType].indexOf(pack) + stickersTabOffset; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; @@ -406,7 +417,7 @@ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { break; } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -472,11 +483,4 @@ public void notifyDataSetChanged() { super.notifyDataSetChanged(); } } - - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java index 888a18d9393..a762a30f9d6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersAlert.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; @@ -26,12 +27,10 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; @@ -83,6 +82,7 @@ public interface StickersAlertInstallDelegate { private BaseFragment parentFragment; private GridLayoutManager layoutManager; private Activity parentActivity; + private int itemSize; private TLRPC.TL_messages_stickerSet stickerSet; private TLRPC.Document selectedSticker; @@ -100,7 +100,7 @@ public interface StickersAlertInstallDelegate { public StickersAlert(Context context, TLRPC.Photo photo) { super(context, false); parentActivity = (Activity) context; - TLRPC.TL_messages_getAttachedStickers req = new TLRPC.TL_messages_getAttachedStickers(); + final TLRPC.TL_messages_getAttachedStickers req = new TLRPC.TL_messages_getAttachedStickers(); TLRPC.TL_inputStickeredMediaPhoto inputStickeredMediaPhoto = new TLRPC.TL_inputStickeredMediaPhoto(); inputStickeredMediaPhoto.id = new TLRPC.TL_inputPhoto(); inputStickeredMediaPhoto.id.id = photo.id; @@ -134,7 +134,7 @@ public void run() { adapter.notifyDataSetChanged(); } } else { - Toast.makeText(getContext(), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text, Toast.LENGTH_SHORT).show(); + AlertsCreator.processError(error, parentFragment, req); dismiss(); } } @@ -198,7 +198,8 @@ public void run() { } private void init(Context context) { - shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow); + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogBackground), PorterDuff.Mode.MULTIPLY)); containerView = new FrameLayout(context) { @@ -224,6 +225,8 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (Build.VERSION.SDK_INT >= 21) { height -= AndroidUtilities.statusBarHeight; } + int measuredWidth = getMeasuredWidth(); + itemSize = (MeasureSpec.getSize(widthMeasureSpec) - AndroidUtilities.dp(36)) / 5; int contentSize; if (stickerSetCovereds != null) { contentSize = AndroidUtilities.dp(48 + 8) + AndroidUtilities.dp(60) * stickerSetCovereds.size() + adapter.stickersRowCount * AndroidUtilities.dp(82); @@ -281,7 +284,7 @@ protected void onDraw(Canvas canvas) { titleTextView = new TextView(context); titleTextView.setLines(1); titleTextView.setSingleLine(true); - titleTextView.setTextColor(Theme.STICKERS_SHEET_TITLE_TEXT_COLOR); + titleTextView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); titleTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE); titleTextView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); @@ -305,7 +308,7 @@ public boolean onTouch(View v, MotionEvent event) { gridView = new RecyclerListView(context) { @Override public boolean onInterceptTouchEvent(MotionEvent event) { - boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, gridView, 0); + boolean result = StickerPreviewViewer.getInstance().onInterceptTouchEvent(event, gridView, 0, null); return super.onInterceptTouchEvent(event) || result; } @@ -342,11 +345,11 @@ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, Recycle gridView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); gridView.setClipToPadding(false); gridView.setEnabled(true); - gridView.setGlowColor(0xfff5f6f7); + gridView.setGlowColor(Theme.getColor(Theme.key_dialogScrollGlow)); gridView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { - return StickerPreviewViewer.getInstance().onTouch(event, gridView, 0, stickersOnItemClickListener); + return StickerPreviewViewer.getInstance().onTouch(event, gridView, 0, stickersOnItemClickListener, null); } }); gridView.setOnScrollListener(new RecyclerView.OnScrollListener() { @@ -422,7 +425,7 @@ public boolean onTouch(View v, MotionEvent event) { } }); - ProgressBar progressView = new ProgressBar(context); + RadialProgressView progressView = new RadialProgressView(context); emptyView.addView(progressView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); shadow[1] = new View(context); @@ -430,9 +433,10 @@ public boolean onTouch(View v, MotionEvent event) { containerView.addView(shadow[1], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); pickerBottomLayout = new PickerBottomLayout(context, false); + pickerBottomLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); containerView.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); pickerBottomLayout.cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - pickerBottomLayout.cancelButton.setTextColor(Theme.STICKERS_SHEET_CLOSE_TEXT_COLOR); + pickerBottomLayout.cancelButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); pickerBottomLayout.cancelButton.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { @Override @@ -441,10 +445,10 @@ public void onClick(View view) { } }); pickerBottomLayout.doneButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - pickerBottomLayout.doneButtonBadgeTextView.setBackgroundResource(R.drawable.stickercounter); + pickerBottomLayout.doneButtonBadgeTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(12.5f), Theme.getColor(Theme.key_dialogBadgeBackground))); stickerPreviewLayout = new FrameLayout(context); - stickerPreviewLayout.setBackgroundColor(0xdfffffff); + stickerPreviewLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground) & 0xdfffffff); stickerPreviewLayout.setVisibility(View.GONE); stickerPreviewLayout.setSoundEffectsEnabled(false); containerView.addView(stickerPreviewLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -456,11 +460,9 @@ public void onClick(View v) { }); ImageView closeButton = new ImageView(context); - closeButton.setImageResource(R.drawable.delete_reply); + closeButton.setImageResource(R.drawable.msg_panel_clear); + closeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextGray3), PorterDuff.Mode.MULTIPLY)); closeButton.setScaleType(ImageView.ScaleType.CENTER); - if (Build.VERSION.SDK_INT >= 21) { - closeButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.INPUT_FIELD_SELECTOR_COLOR)); - } stickerPreviewLayout.addView(closeButton, LayoutHelper.createFrame(48, 48, Gravity.RIGHT | Gravity.TOP)); closeButton.setOnClickListener(new View.OnClickListener() { @Override @@ -480,9 +482,9 @@ public void onClick(View v) { previewSendButton = new TextView(context); previewSendButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - previewSendButton.setTextColor(Theme.STICKERS_SHEET_SEND_TEXT_COLOR); + previewSendButton.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); previewSendButton.setGravity(Gravity.CENTER); - previewSendButton.setBackgroundColor(0xffffffff); + previewSendButton.setBackgroundColor(Theme.getColor(Theme.key_dialogBackground)); previewSendButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); previewSendButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); stickerPreviewLayout.addView(previewSendButton, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); @@ -566,7 +568,7 @@ public void run() { Toast.makeText(getContext(), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred), Toast.LENGTH_SHORT).show(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } StickersQuery.loadStickers(stickerSet.set.masks ? StickersQuery.TYPE_MASK : StickersQuery.TYPE_IMAGE, false, true); } @@ -574,7 +576,7 @@ public void run() { } }); } - }, stickerSet != null && stickerSet.set.masks ? LocaleController.getString("AddMasks", R.string.AddMasks) : LocaleController.getString("AddStickers", R.string.AddStickers), Theme.STICKERS_SHEET_ADD_TEXT_COLOR, true); + }, stickerSet != null && stickerSet.set.masks ? LocaleController.getString("AddMasks", R.string.AddMasks) : LocaleController.getString("AddStickers", R.string.AddStickers), Theme.getColor(Theme.key_dialogTextBlue2), true); } else { if (stickerSet.set.official) { setRightButton(new View.OnClickListener() { @@ -586,7 +588,7 @@ public void onClick(View v) { dismiss(); StickersQuery.removeStickersSet(getContext(), stickerSet.set, 1, parentFragment, true); } - }, LocaleController.getString("StickersRemove", R.string.StickersHide), Theme.STICKERS_SHEET_REMOVE_TEXT_COLOR, false); + }, LocaleController.getString("StickersRemove", R.string.StickersHide), Theme.getColor(Theme.key_dialogTextRed), false); } else { setRightButton(new View.OnClickListener() { @Override @@ -597,12 +599,12 @@ public void onClick(View v) { dismiss(); StickersQuery.removeStickersSet(getContext(), stickerSet.set, 0, parentFragment, true); } - }, LocaleController.getString("StickersRemove", R.string.StickersRemove), Theme.STICKERS_SHEET_REMOVE_TEXT_COLOR, false); + }, LocaleController.getString("StickersRemove", R.string.StickersRemove), Theme.getColor(Theme.key_dialogTextRed), false); } } adapter.notifyDataSetChanged(); } else { - setRightButton(null, null, Theme.STICKERS_SHEET_REMOVE_TEXT_COLOR, false); + setRightButton(null, null, Theme.getColor(Theme.key_dialogTextRed), false); } } @@ -623,7 +625,7 @@ private void updateLayout() { return; } View child = gridView.getChildAt(0); - GridAdapter.Holder holder = (GridAdapter.Holder) gridView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) gridView.findContainingViewHolder(child); int top = child.getTop(); int newOffset = 0; if (top >= 0 && holder != null && holder.getAdapterPosition() == 0) { @@ -646,7 +648,7 @@ private void hidePreview() { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(ObjectAnimator.ofFloat(stickerPreviewLayout, "alpha", 0.0f)); animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { stickerPreviewLayout.setVisibility(View.GONE); @@ -670,7 +672,7 @@ private void runShadowAnimation(final int num, final boolean show) { shadowAnimation[num] = new AnimatorSet(); shadowAnimation[num].playTogether(ObjectAnimator.ofFloat(shadow[num], "alpha", show ? 1.0f : 0.0f)); shadowAnimation[num].setDuration(150); - shadowAnimation[num].addListener(new AnimatorListenerAdapterProxy() { + shadowAnimation[num].addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (shadowAnimation[num] != null && shadowAnimation[num].equals(animation)) { @@ -736,7 +738,7 @@ private void setRightButton(View.OnClickListener onClickListener, String title, } } - private class GridAdapter extends RecyclerView.Adapter { + private class GridAdapter extends RecyclerListView.SelectionAdapter { private Context context; private int stickersPerRow; @@ -754,13 +756,6 @@ public int getItemCount() { return totalItems; } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - @Override public int getItemViewType(int position) { if (stickerSetCovereds != null) { @@ -777,6 +772,11 @@ public int getItemViewType(int position) { return 0; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; @@ -784,7 +784,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType case 0: view = new StickerEmojiCell(context) { public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(82), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(itemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(82), MeasureSpec.EXACTLY)); } }; break; @@ -796,7 +796,7 @@ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { break; } - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java index 4607fa1c431..1cb45c64581 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/StickersArchiveAlert.java @@ -3,12 +3,11 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.util.TypedValue; @@ -24,7 +23,9 @@ import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Cells.ArchivedStickerSetCell; import org.telegram.ui.StickersActivity; @@ -59,7 +60,7 @@ public StickersArchiveAlert(Context context, BaseFragment baseFragment, ArrayLis setView(container); TextView textView = new TextView(context); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setPadding(AndroidUtilities.dp(23), AndroidUtilities.dp(10), AndroidUtilities.dp(23), 0); if (set.set.masks) { @@ -94,7 +95,7 @@ public void onClick(DialogInterface dialog, int which) { } } - private class ListAdapter extends RecyclerView.Adapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { Context context; @@ -107,23 +108,21 @@ public int getItemCount() { return stickerSets.size(); } - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = new ArchivedStickerSetCell(context, false); view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, AndroidUtilities.dp(82))); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - ((ArchivedStickerSetCell) holder.itemView).setStickersSet(stickerSets.get(position), position != stickerSets.size() - 1, false); + ((ArchivedStickerSetCell) holder.itemView).setStickersSet(stickerSets.get(position), position != stickerSets.size() - 1); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java index 4488dc4a885..98401439e9d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Switch.java @@ -35,6 +35,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; public class Switch extends CompoundButton { @@ -412,10 +413,19 @@ public void setChecked(boolean checked) { } if (mTrackDrawable != null) { - mTrackDrawable.setColorFilter(new PorterDuffColorFilter(checked ? 0xffa0d6fa : 0xffc7c7c7, PorterDuff.Mode.MULTIPLY)); + mTrackDrawable.setColorFilter(new PorterDuffColorFilter(checked ? Theme.getColor(Theme.key_switchTrackChecked) : Theme.getColor(Theme.key_switchTrack), PorterDuff.Mode.MULTIPLY)); } if (mThumbDrawable != null) { - mThumbDrawable.setColorFilter(new PorterDuffColorFilter(checked ? 0xff45abef : 0xffededed, PorterDuff.Mode.MULTIPLY)); + mThumbDrawable.setColorFilter(new PorterDuffColorFilter(checked ? Theme.getColor(Theme.key_switchThumbChecked) : Theme.getColor(Theme.key_switchThumb), PorterDuff.Mode.MULTIPLY)); + } + } + + public void checkColorFilters() { + if (mTrackDrawable != null) { + mTrackDrawable.setColorFilter(new PorterDuffColorFilter(isChecked() ? Theme.getColor(Theme.key_switchTrackChecked) : Theme.getColor(Theme.key_switchTrack), PorterDuff.Mode.MULTIPLY)); + } + if (mThumbDrawable != null) { + mThumbDrawable.setColorFilter(new PorterDuffColorFilter(isChecked() ? Theme.getColor(Theme.key_switchThumbChecked) : Theme.getColor(Theme.key_switchThumb), PorterDuff.Mode.MULTIPLY)); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintSpan.java new file mode 100644 index 00000000000..6a34a9a46eb --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintSpan.java @@ -0,0 +1,37 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +public class TextPaintSpan extends MetricAffectingSpan { + + private TextPaint textPaint; + private int textSize; + private int color; + + public TextPaintSpan(TextPaint paint) { + textPaint = paint; + } + + @Override + public void updateMeasureState(TextPaint p) { + p.setColor(textPaint.getColor()); + p.setTypeface(textPaint.getTypeface()); + p.setFlags(textPaint.getFlags()); + } + + @Override + public void updateDrawState(TextPaint p) { + p.setColor(textPaint.getColor()); + p.setTypeface(textPaint.getTypeface()); + p.setFlags(textPaint.getFlags()); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintUrlSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintUrlSpan.java new file mode 100644 index 00000000000..ea24122b2af --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TextPaintUrlSpan.java @@ -0,0 +1,47 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +public class TextPaintUrlSpan extends MetricAffectingSpan { + + private TextPaint textPaint; + private int textSize; + private int color; + private String currentUrl; + + public TextPaintUrlSpan(TextPaint paint, String url) { + textPaint = paint; + currentUrl = url; + } + + public String getUrl() { + return currentUrl; + } + + @Override + public void updateMeasureState(TextPaint p) { + if (textPaint != null) { + p.setColor(textPaint.getColor()); + p.setTypeface(textPaint.getTypeface()); + p.setFlags(textPaint.getFlags()); + } + } + + @Override + public void updateDrawState(TextPaint p) { + if (textPaint != null) { + p.setColor(textPaint.getColor()); + p.setTypeface(textPaint.getTypeface()); + p.setFlags(textPaint.getFlags()); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java new file mode 100644 index 00000000000..2010aaffe06 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ThemeEditorView.java @@ -0,0 +1,1239 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.RadialGradient; +import android.graphics.Shader; +import android.graphics.SweepGradient; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.annotation.Keep; +import android.text.Editable; +import android.text.InputFilter; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarLayout; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.TextColorThemeCell; +import org.telegram.ui.LaunchActivity; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +public class ThemeEditorView { + + private FrameLayout windowView; + private Activity parentActivity; + + private boolean hidden; + + private ArrayList currentThemeDesription; + private int currentThemeDesriptionPosition; + + private final int editorWidth = AndroidUtilities.dp(54); + private final int editorHeight = AndroidUtilities.dp(54); + + private WindowManager.LayoutParams windowLayoutParams; + private WindowManager windowManager; + private DecelerateInterpolator decelerateInterpolator; + private SharedPreferences preferences; + private WallpaperUpdater wallpaperUpdater; + private EditorAlert editorAlert; + + private String currentThemeName; + + @SuppressLint("StaticFieldLeak") + private static volatile ThemeEditorView Instance = null; + public static ThemeEditorView getInstance() { + return Instance; + } + + public void destroy() { + wallpaperUpdater.cleanup(); + if (parentActivity == null || windowView == null) { + return; + } + try { + windowManager.removeViewImmediate(windowView); + windowView = null; + } catch (Exception e) { + FileLog.e(e); + } + try { + if (editorAlert != null) { + editorAlert.dismiss(); + editorAlert = null; + } + } catch (Exception e) { + FileLog.e(e); + } + parentActivity = null; + Instance = null; + } + + public class EditorAlert extends BottomSheet { + + private ColorPicker colorPicker; + private RecyclerListView listView; + private LinearLayoutManager layoutManager; + private ListAdapter listAdapter; + private FrameLayout bottomSaveLayout; + private FrameLayout bottomLayout; + private View shadow; + private TextView cancelButton; + private TextView defaultButtom; + private TextView saveButton; + + private Drawable shadowDrawable; + + private int scrollOffsetY; + private int topBeforeSwitch; + private int previousScrollPosition; + + private boolean animationInProgress; + + private AnimatorSet colorChangeAnimation; + private boolean startedColorChange; + private boolean ignoreTextChange; + + private class ColorPicker extends FrameLayout { + + private LinearLayout linearLayout; + + private final int paramValueSliderWidth = AndroidUtilities.dp(20); + + private Paint colorWheelPaint; + private Paint valueSliderPaint; + private Paint circlePaint; + private Drawable circleDrawable; + + private Bitmap colorWheelBitmap; + + private EditText colorEditText[] = new EditText[4]; + + private int colorWheelRadius; + + private float[] colorHSV = new float[] { 0.0f, 0.0f, 1.0f }; + private float alpha = 1.0f; + + private float[] hsvTemp = new float[3]; + private LinearGradient colorGradient; + private LinearGradient alphaGradient; + + private boolean circlePressed; + private boolean colorPressed; + private boolean alphaPressed; + + private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); + + public ColorPicker(Context context) { + super(context); + setWillNotDraw(false); + + circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); + circleDrawable = context.getResources().getDrawable(R.drawable.knob_shadow).mutate(); + + colorWheelPaint = new Paint(); + colorWheelPaint.setAntiAlias(true); + colorWheelPaint.setDither(true); + + valueSliderPaint = new Paint(); + valueSliderPaint.setAntiAlias(true); + valueSliderPaint.setDither(true); + + linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP)); + for (int a = 0; a < 4; a++){ + colorEditText[a] = new EditText(context); + colorEditText[a].setInputType(InputType.TYPE_CLASS_NUMBER); + colorEditText[a].setTextColor(0xff212121); + AndroidUtilities.clearCursorDrawable(colorEditText[a]); + colorEditText[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + colorEditText[a].setBackgroundDrawable(Theme.createEditTextDrawable(context, true)); + colorEditText[a].setMaxLines(1); + colorEditText[a].setTag(a); + colorEditText[a].setGravity(Gravity.CENTER); + if (a == 0) { + colorEditText[a].setHint("red"); + } else if (a == 1) { + colorEditText[a].setHint("green"); + } else if (a == 2) { + colorEditText[a].setHint("blue"); + } else if (a == 3) { + colorEditText[a].setHint("alpha"); + } + colorEditText[a].setImeOptions((a == 3 ? EditorInfo.IME_ACTION_DONE : EditorInfo.IME_ACTION_NEXT) | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + InputFilter[] inputFilters = new InputFilter[1]; + inputFilters[0] = new InputFilter.LengthFilter(3); + colorEditText[a].setFilters(inputFilters); + final int num = a; + linearLayout.addView(colorEditText[a], LayoutHelper.createLinear(55, 36, 0, 0, a != 3 ? 16 : 0, 0)); + colorEditText[a].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (ignoreTextChange) { + return; + } + ignoreTextChange = true; + int color = Utilities.parseInt(editable.toString()); + if (color < 0) { + color = 0; + colorEditText[num].setText("" + color); + colorEditText[num].setSelection(colorEditText[num].length()); + } else if (color > 255) { + color = 255; + colorEditText[num].setText("" + color); + colorEditText[num].setSelection(colorEditText[num].length()); + } + int currentColor = getColor(); + if (num == 2) { + currentColor = (currentColor & 0xffffff00) | (color & 0xff); + } else if (num == 1) { + currentColor = (currentColor & 0xffff00ff) | ((color & 0xff) << 8); + } else if (num == 0) { + currentColor = (currentColor & 0xff00ffff) | ((color & 0xff) << 16); + } else if (num == 3) { + currentColor = (currentColor & 0x00ffffff) | ((color & 0xff) << 24); + } + setColor(currentColor); + for (int a = 0; a < currentThemeDesription.size(); a++) { + currentThemeDesription.get(a).setColor(getColor(), false); + } + + ignoreTextChange = false; + } + }); + colorEditText[a].setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_DONE) { + AndroidUtilities.hideKeyboard(textView); + return true; + } + return false; + } + }); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int size = Math.min(widthSize, heightSize); + measureChild(linearLayout, widthMeasureSpec, heightMeasureSpec); + setMeasuredDimension(size, size); + } + + @Override + protected void onDraw(Canvas canvas) { + int centerX = getWidth() / 2 - paramValueSliderWidth * 2; + int centerY = getHeight() / 2 - AndroidUtilities.dp(8); + + canvas.drawBitmap(colorWheelBitmap, centerX - colorWheelRadius, centerY - colorWheelRadius, null); + + float hueAngle = (float) Math.toRadians(colorHSV[0]); + int colorPointX = (int) (-Math.cos(hueAngle) * colorHSV[1] * colorWheelRadius) + centerX; + int colorPointY = (int) (-Math.sin(hueAngle) * colorHSV[1] * colorWheelRadius) + centerY; + + float pointerRadius = 0.075f * colorWheelRadius; + + hsvTemp[0] = colorHSV[0]; + hsvTemp[1] = colorHSV[1]; + hsvTemp[2] = 1.0f; + + drawPointerArrow(canvas, colorPointX, colorPointY, Color.HSVToColor(hsvTemp)); + + int x = centerX + colorWheelRadius + paramValueSliderWidth; + int y = centerY - colorWheelRadius; + int width = AndroidUtilities.dp(9); + int height = colorWheelRadius * 2; + if (colorGradient == null) { + colorGradient = new LinearGradient(x, y, x + width, y + height, new int[]{Color.BLACK, Color.HSVToColor(hsvTemp)}, null, Shader.TileMode.CLAMP); + } + valueSliderPaint.setShader(colorGradient); + canvas.drawRect(x, y, x + width, y + height, valueSliderPaint); + drawPointerArrow(canvas, x + width / 2, (int) (y + colorHSV[2] * height), Color.HSVToColor(colorHSV)); + + x += paramValueSliderWidth * 2; + if (alphaGradient == null) { + int color = Color.HSVToColor(hsvTemp); + alphaGradient = new LinearGradient(x, y, x + width, y + height, new int[]{color, color & 0x00ffffff}, null, Shader.TileMode.CLAMP); + } + valueSliderPaint.setShader(alphaGradient); + canvas.drawRect(x, y, x + width, y + height, valueSliderPaint); + drawPointerArrow(canvas, x + width / 2, (int) (y + (1.0f - alpha) * height), (Color.HSVToColor(colorHSV) & 0x00ffffff) | ((int) (255 * alpha) << 24)); + } + + private void drawPointerArrow(Canvas canvas, int x, int y, int color) { + int side = AndroidUtilities.dp(13); + circleDrawable.setBounds(x - side, y - side, x + side, y + side); + circleDrawable.draw(canvas); + + circlePaint.setColor(0xffffffff); + canvas.drawCircle(x, y, AndroidUtilities.dp(11), circlePaint); + circlePaint.setColor(color); + canvas.drawCircle(x, y, AndroidUtilities.dp(9), circlePaint); + } + + @Override + protected void onSizeChanged(int width, int height, int oldw, int oldh) { + colorWheelRadius = Math.max(1, width / 2 - paramValueSliderWidth * 2 - AndroidUtilities.dp(20)); + colorWheelBitmap = createColorWheelBitmap(colorWheelRadius * 2, colorWheelRadius * 2); + //linearLayout.setTranslationY(colorWheelRadius * 2 + AndroidUtilities.dp(20)); + colorGradient = null; + alphaGradient = null; + } + + private Bitmap createColorWheelBitmap(int width, int height) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + int colorCount = 12; + int colorAngleStep = 360 / 12; + int colors[] = new int[colorCount + 1]; + float hsv[] = new float[] { 0.0f, 1.0f, 1.0f }; + for (int i = 0; i < colors.length; i++) { + hsv[0] = (i * colorAngleStep + 180) % 360; + colors[i] = Color.HSVToColor(hsv); + } + colors[colorCount] = colors[0]; + + SweepGradient sweepGradient = new SweepGradient(width / 2, height / 2, colors, null); + RadialGradient radialGradient = new RadialGradient(width / 2, height / 2, colorWheelRadius, 0xffffffff, 0x00ffffff, Shader.TileMode.CLAMP); + ComposeShader composeShader = new ComposeShader(sweepGradient, radialGradient, PorterDuff.Mode.SRC_OVER); + + colorWheelPaint.setShader(composeShader); + + Canvas canvas = new Canvas(bitmap); + canvas.drawCircle(width / 2, height / 2, colorWheelRadius, colorWheelPaint); + + return bitmap; + } + + private void startColorChange(boolean start) { + if (startedColorChange == start) { + return; + } + if (colorChangeAnimation != null) { + colorChangeAnimation.cancel(); + } + startedColorChange = start; + colorChangeAnimation = new AnimatorSet(); + colorChangeAnimation.playTogether( + ObjectAnimator.ofInt(backDrawable, "alpha", start ? 0 : 51), + ObjectAnimator.ofFloat(containerView, "alpha", start ? 0.2f : 1.0f)); + colorChangeAnimation.setDuration(150); + colorChangeAnimation.setInterpolator(decelerateInterpolator); + colorChangeAnimation.start(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + + int x = (int) event.getX(); + int y = (int) event.getY(); + int centerX = getWidth() / 2 - paramValueSliderWidth * 2; + int centerY = getHeight() / 2 - AndroidUtilities.dp(8); + int cx = x - centerX; + int cy = y - centerY; + double d = Math.sqrt(cx * cx + cy * cy); + + if (circlePressed || !alphaPressed && !colorPressed && d <= colorWheelRadius) { + if (d > colorWheelRadius) { + d = colorWheelRadius; + } + circlePressed = true; + colorHSV[0] = (float) (Math.toDegrees(Math.atan2(cy, cx)) + 180.0f); + colorHSV[1] = Math.max(0.0f, Math.min(1.0f, (float) (d / colorWheelRadius))); + colorGradient = null; + alphaGradient = null; + } + if (colorPressed || !circlePressed && !alphaPressed && x >= centerX + colorWheelRadius + paramValueSliderWidth && x <= centerX + colorWheelRadius + paramValueSliderWidth * 2 && y >= centerY - colorWheelRadius && y <= centerY + colorWheelRadius) { + float value = (y - (centerY - colorWheelRadius)) / (colorWheelRadius * 2.0f); + if (value < 0.0f) { + value = 0.0f; + } else if (value > 1.0f) { + value = 1.0f; + } + colorHSV[2] = value; + colorPressed = true; + } + if (alphaPressed || !circlePressed && !colorPressed && x >= centerX + colorWheelRadius + paramValueSliderWidth * 3 && x <= centerX + colorWheelRadius + paramValueSliderWidth * 4 && y >= centerY - colorWheelRadius && y <= centerY + colorWheelRadius) { + alpha = 1.0f - (y - (centerY - colorWheelRadius)) / (colorWheelRadius * 2.0f); + if (alpha < 0.0f) { + alpha = 0.0f; + } else if (alpha > 1.0f) { + alpha = 1.0f; + } + alphaPressed = true; + } + if (alphaPressed || colorPressed || circlePressed) { + startColorChange(true); + int color = getColor(); + for (int a = 0; a < currentThemeDesription.size(); a++) { + currentThemeDesription.get(a).setColor(color, false); + } + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + int a = Color.alpha(color); + if (!ignoreTextChange) { + ignoreTextChange = true; + colorEditText[0].setText("" + red); + colorEditText[1].setText("" + green); + colorEditText[2].setText("" + blue); + colorEditText[3].setText("" + a); + for (int b = 0; b < 4; b++) { + colorEditText[b].setSelection(colorEditText[b].length()); + } + ignoreTextChange = false; + } + invalidate(); + } + + return true; + case MotionEvent.ACTION_UP: + alphaPressed = false; + colorPressed = false; + circlePressed = false; + startColorChange(false); + break; + } + return super.onTouchEvent(event); + } + + public void setColor(int color) { + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + int a = Color.alpha(color); + if (!ignoreTextChange) { + ignoreTextChange = true; + colorEditText[0].setText("" + red); + colorEditText[1].setText("" + green); + colorEditText[2].setText("" + blue); + colorEditText[3].setText("" + a); + for (int b = 0; b < 4; b++) { + colorEditText[b].setSelection(colorEditText[b].length()); + } + ignoreTextChange = false; + } + alphaGradient = null; + colorGradient = null; + alpha = a / 255.0f; + Color.colorToHSV(color, colorHSV); + invalidate(); + } + + public int getColor() { + return (Color.HSVToColor(colorHSV) & 0x00ffffff) | ((int) (alpha * 255) << 24); + } + } + + public EditorAlert(final Context context, ThemeDescription[] items) { + super(context, true); + + shadowDrawable = context.getResources().getDrawable(R.drawable.sheet_shadow).mutate(); + + containerView = new FrameLayout(context) { + + private boolean ignoreLayout = false; + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN && scrollOffsetY != 0 && ev.getY() < scrollOffsetY) { + dismiss(); + return true; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + return !isDismissed() && super.onTouchEvent(e); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + if (Build.VERSION.SDK_INT >= 21) { + height -= AndroidUtilities.statusBarHeight; + } + + int pickerSize = Math.min(width, height); + + int padding = height - pickerSize; + if (listView.getPaddingTop() != padding) { + ignoreLayout = true; + int previousPadding = listView.getPaddingTop(); + listView.setPadding(0, padding, 0, AndroidUtilities.dp(48)); + if (colorPicker.getVisibility() == VISIBLE) { + //previousScrollPosition += previousPadding; + scrollOffsetY = listView.getPaddingTop(); + listView.setTopGlowOffset(scrollOffsetY); + colorPicker.setTranslationY(scrollOffsetY); + previousScrollPosition = 0; + } + ignoreLayout = false; + } + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updateLayout(); + } + + @Override + public void requestLayout() { + if (ignoreLayout) { + return; + } + super.requestLayout(); + } + + @Override + protected void onDraw(Canvas canvas) { + shadowDrawable.setBounds(0, scrollOffsetY - backgroundPaddingTop, getMeasuredWidth(), getMeasuredHeight()); + shadowDrawable.draw(canvas); + } + }; + containerView.setWillNotDraw(false); + containerView.setPadding(backgroundPaddingLeft, 0, backgroundPaddingLeft, 0); + + listView = new RecyclerListView(context); + listView.setPadding(0, 0, 0, AndroidUtilities.dp(48)); + listView.setClipToPadding(false); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(getContext())); + listView.setHorizontalScrollBarEnabled(false); + listView.setVerticalScrollBarEnabled(false); + containerView.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + listView.setAdapter(listAdapter = new ListAdapter(context, items)); + listView.setGlowColor(0xfff5f6f7); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + currentThemeDesription = listAdapter.getItem(position); + currentThemeDesriptionPosition = position; + for (int a = 0; a < currentThemeDesription.size(); a++) { + ThemeDescription description = currentThemeDesription.get(a); + if (description.getCurrentKey().equals(Theme.key_chat_wallpaper)) { + wallpaperUpdater.showAlert(true); + return; + } + description.startEditing(); + if (a == 0) { + colorPicker.setColor(description.getCurrentColor()); + } + } + setColorPickerVisible(true); + } + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + updateLayout(); + } + }); + + colorPicker = new ColorPicker(context); + colorPicker.setVisibility(View.GONE); + containerView.addView(colorPicker, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER_HORIZONTAL)); + + shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow_reverse); + containerView.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 48)); + + bottomSaveLayout = new FrameLayout(context); + bottomSaveLayout.setBackgroundColor(0xffffffff); + containerView.addView(bottomSaveLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + + TextView closeButton = new TextView(context); + closeButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + closeButton.setTextColor(0xff19a7e8); + closeButton.setGravity(Gravity.CENTER); + closeButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + closeButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + closeButton.setText(LocaleController.getString("CloseEditor", R.string.CloseEditor).toUpperCase()); + closeButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomSaveLayout.addView(closeButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + closeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + dismiss(); + } + }); + + TextView saveButton = new TextView(context); + saveButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + saveButton.setTextColor(0xff19a7e8); + saveButton.setGravity(Gravity.CENTER); + saveButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + saveButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + saveButton.setText(LocaleController.getString("SaveTheme", R.string.SaveTheme).toUpperCase()); + saveButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomSaveLayout.addView(saveButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + saveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Theme.saveCurrentTheme(currentThemeName, true); + setOnDismissListener(null); + dismiss(); + close(); + } + }); + + bottomLayout = new FrameLayout(context); + bottomLayout.setVisibility(View.GONE); + bottomLayout.setBackgroundColor(0xffffffff); + containerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + + cancelButton = new TextView(context); + cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + cancelButton.setTextColor(0xff19a7e8); + cancelButton.setGravity(Gravity.CENTER); + cancelButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + cancelButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); + cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomLayout.addView(cancelButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + for (int a = 0; a < currentThemeDesription.size(); a++) { + currentThemeDesription.get(a).setPreviousColor(); + } + setColorPickerVisible(false); + } + }); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + bottomLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + + defaultButtom = new TextView(context); + defaultButtom.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + defaultButtom.setTextColor(0xff19a7e8); + defaultButtom.setGravity(Gravity.CENTER); + defaultButtom.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + defaultButtom.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + defaultButtom.setText(LocaleController.getString("Default", R.string.Default).toUpperCase()); + defaultButtom.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(defaultButtom, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + defaultButtom.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + for (int a = 0; a < currentThemeDesription.size(); a++) { + currentThemeDesription.get(a).setDefaultColor(); + } + setColorPickerVisible(false); + } + }); + + saveButton = new TextView(context); + saveButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + saveButton.setTextColor(0xff19a7e8); + saveButton.setGravity(Gravity.CENTER); + saveButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, 0)); + saveButton.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); + saveButton.setText(LocaleController.getString("Save", R.string.Save).toUpperCase()); + saveButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + linearLayout.addView(saveButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + saveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setColorPickerVisible(false); + } + }); + } + + private void setColorPickerVisible(boolean visible) { + if (visible) { + animationInProgress = true; + colorPicker.setVisibility(View.VISIBLE); + bottomLayout.setVisibility(View.VISIBLE); + colorPicker.setAlpha(0.0f); + bottomLayout.setAlpha(0.0f); + + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(colorPicker, "alpha", 1.0f), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 1.0f), + ObjectAnimator.ofFloat(listView, "alpha", 0.0f), + ObjectAnimator.ofFloat(bottomSaveLayout, "alpha", 0.0f), + ObjectAnimator.ofInt(this, "scrollOffsetY", listView.getPaddingTop())); + animatorSet.setDuration(150); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + listView.setVisibility(View.INVISIBLE); + bottomSaveLayout.setVisibility(View.INVISIBLE); + animationInProgress = false; + } + }); + animatorSet.start(); + previousScrollPosition = scrollOffsetY; + } else { + if (parentActivity != null) { + ((LaunchActivity) parentActivity).rebuildAllFragments(false); + } + Theme.saveCurrentTheme(currentThemeName, false); + AndroidUtilities.hideKeyboard(getCurrentFocus()); + animationInProgress = true; + listView.setVisibility(View.VISIBLE); + bottomSaveLayout.setVisibility(View.VISIBLE); + listView.setAlpha(0.0f); + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether( + ObjectAnimator.ofFloat(colorPicker, "alpha", 0.0f), + ObjectAnimator.ofFloat(bottomLayout, "alpha", 0.0f), + ObjectAnimator.ofFloat(listView, "alpha", 1.0f), + ObjectAnimator.ofFloat(bottomSaveLayout, "alpha", 1.0f), + ObjectAnimator.ofInt(this, "scrollOffsetY", previousScrollPosition)); + animatorSet.setDuration(150); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + colorPicker.setVisibility(View.GONE); + bottomLayout.setVisibility(View.GONE); + animationInProgress = false; + } + }); + animatorSet.start(); + listAdapter.notifyItemChanged(currentThemeDesriptionPosition); + } + } + + private int getCurrentTop() { + if (listView.getChildCount() != 0) { + View child = listView.getChildAt(0); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + if (holder != null) { + return listView.getPaddingTop() - (holder.getAdapterPosition() == 0 && child.getTop() >= 0 ? child.getTop() : 0); + } + } + return -1000; + } + + @Override + protected boolean canDismissWithSwipe() { + return false; + } + + @SuppressLint("NewApi") + private void updateLayout() { + if (listView.getChildCount() <= 0 || listView.getVisibility() != View.VISIBLE || animationInProgress) { + return; + } + View child = listView.getChildAt(0); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); + int top; + if (listView.getVisibility() != View.VISIBLE || animationInProgress) { + top = listView.getPaddingTop(); + } else { + top = child.getTop() - AndroidUtilities.dp(8); + } + int newOffset = top > 0 && holder != null && holder.getAdapterPosition() == 0 ? top : 0; + if (scrollOffsetY != newOffset) { + setScrollOffsetY(newOffset); + } + } + + public int getScrollOffsetY() { + return scrollOffsetY; + } + + @Keep + public void setScrollOffsetY(int value) { + listView.setTopGlowOffset(scrollOffsetY = value); + colorPicker.setTranslationY(scrollOffsetY); + containerView.invalidate(); + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; + private int currentCount; + private ArrayList> items = new ArrayList<>(); + private HashMap> itemsMap = new HashMap<>(); + + public ListAdapter(Context context, ThemeDescription[] descriptions) { + this.context = context; + for (int a = 0; a < descriptions.length; a++) { + ThemeDescription description = descriptions[a]; + String key = description.getCurrentKey(); + ArrayList arrayList = itemsMap.get(key); + if (arrayList == null) { + arrayList = new ArrayList<>(); + itemsMap.put(key, arrayList); + items.add(arrayList); + } + arrayList.add(description); + } + } + + @Override + public int getItemCount() { + return items.size(); + } + + public ArrayList getItem(int i) { + if (i < 0 || i >= items.size()) { + return null; + } + return items.get(i); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = new TextColorThemeCell(context); + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + ArrayList arrayList = items.get(position); + ThemeDescription description = arrayList.get(0); + int color; + if (description.getCurrentKey().equals(Theme.key_chat_wallpaper)) { + color = 0; + } else { + color = description.getSetColor(); + } + ((TextColorThemeCell) holder.itemView).setTextAndColor(description.getTitle(), color); + } + + @Override + public int getItemViewType(int i) { + return 0; + } + } + } + + public void show(Activity activity, final String themeName) { + if (Instance != null) { + Instance.destroy(); + } + hidden = false; + currentThemeName = themeName; + windowView = new FrameLayout(activity) { + + private float startX; + private float startY; + private boolean dragging; + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + return true; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getRawX(); + float y = event.getRawY(); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + startX = x; + startY = y; + } else if (event.getAction() == MotionEvent.ACTION_MOVE && !dragging) { + if (Math.abs(startX - x) >= AndroidUtilities.getPixelsInCM(0.3f, true) || Math.abs(startY - y) >= AndroidUtilities.getPixelsInCM(0.3f, false)) { + dragging = true; + startX = x; + startY = y; + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (!dragging) { + if (editorAlert == null) { + LaunchActivity launchActivity = (LaunchActivity) parentActivity; + ActionBarLayout actionBarLayout = launchActivity.getActionBarLayout(); + if (!actionBarLayout.fragmentsStack.isEmpty()) { + BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); + ThemeDescription[] items = fragment.getThemeDescriptions(); + if (items != null) { + editorAlert = new EditorAlert(parentActivity, items); + editorAlert.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + + } + }); + editorAlert.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + editorAlert = null; + show(); + } + }); + editorAlert.show(); + hide(); + } + } + } + } + } + if (dragging) { + if (event.getAction() == MotionEvent.ACTION_MOVE) { + float dx = (x - startX); + float dy = (y - startY); + windowLayoutParams.x += dx; + windowLayoutParams.y += dy; + int maxDiff = editorWidth / 2; + if (windowLayoutParams.x < -maxDiff) { + windowLayoutParams.x = -maxDiff; + } else if (windowLayoutParams.x > AndroidUtilities.displaySize.x - windowLayoutParams.width + maxDiff) { + windowLayoutParams.x = AndroidUtilities.displaySize.x - windowLayoutParams.width + maxDiff; + } + float alpha = 1.0f; + if (windowLayoutParams.x < 0) { + alpha = 1.0f + windowLayoutParams.x / (float) maxDiff * 0.5f; + } else if (windowLayoutParams.x > AndroidUtilities.displaySize.x - windowLayoutParams.width) { + alpha = 1.0f - (windowLayoutParams.x - AndroidUtilities.displaySize.x + windowLayoutParams.width) / (float) maxDiff * 0.5f; + } + if (windowView.getAlpha() != alpha) { + windowView.setAlpha(alpha); + } + maxDiff = 0; + if (windowLayoutParams.y < -maxDiff) { + windowLayoutParams.y = -maxDiff; + } else if (windowLayoutParams.y > AndroidUtilities.displaySize.y - windowLayoutParams.height + maxDiff) { + windowLayoutParams.y = AndroidUtilities.displaySize.y - windowLayoutParams.height + maxDiff; + } + windowManager.updateViewLayout(windowView, windowLayoutParams); + startX = x; + startY = y; + } else if (event.getAction() == MotionEvent.ACTION_UP) { + dragging = false; + animateToBoundsMaybe(); + } + } + return true; + } + }; + windowView.setBackgroundResource(R.drawable.theme_picker); + windowManager = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE); + + preferences = ApplicationLoader.applicationContext.getSharedPreferences("themeconfig", Context.MODE_PRIVATE); + + int sidex = preferences.getInt("sidex", 1); + int sidey = preferences.getInt("sidey", 0); + float px = preferences.getFloat("px", 0); + float py = preferences.getFloat("py", 0); + + try { + windowLayoutParams = new WindowManager.LayoutParams(); + windowLayoutParams.width = editorWidth; + windowLayoutParams.height = editorHeight; + windowLayoutParams.x = getSideCoord(true, sidex, px, editorWidth); + windowLayoutParams.y = getSideCoord(false, sidey, py, editorHeight); + windowLayoutParams.format = PixelFormat.TRANSLUCENT; + windowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; + windowLayoutParams.type = WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; + windowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + windowManager.addView(windowView, windowLayoutParams); + } catch (Exception e) { + FileLog.e(e); + return; + } + wallpaperUpdater = new WallpaperUpdater(activity, new WallpaperUpdater.WallpaperUpdaterDelegate() { + @Override + public void didSelectWallpaper(File file, Bitmap bitmap) { + Theme.setThemeWallpaper(themeName, bitmap, file); + } + + @Override + public void needOpenColorPicker() { + for (int a = 0; a < currentThemeDesription.size(); a++) { + ThemeDescription description = currentThemeDesription.get(a); + description.startEditing(); + if (a == 0) { + editorAlert.colorPicker.setColor(description.getCurrentColor()); + } + } + editorAlert.setColorPickerVisible(true); + } + }); + Instance = this; + parentActivity = activity; + showWithAnimation(); + } + + private void showWithAnimation() { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(windowView, "alpha", 0.0f, 1.0f), + ObjectAnimator.ofFloat(windowView, "scaleX", 0.0f, 1.0f), + ObjectAnimator.ofFloat(windowView, "scaleY", 0.0f, 1.0f)); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.setDuration(150); + animatorSet.start(); + } + + private static int getSideCoord(boolean isX, int side, float p, int sideSize) { + int total; + if (isX) { + total = AndroidUtilities.displaySize.x - sideSize; + } else { + total = AndroidUtilities.displaySize.y - sideSize - ActionBar.getCurrentActionBarHeight(); + } + int result; + if (side == 0) { + result = AndroidUtilities.dp(10); + } else if (side == 1) { + result = total - AndroidUtilities.dp(10); + } else { + result = Math.round((total - AndroidUtilities.dp(20)) * p) + AndroidUtilities.dp(10); + } + if (!isX) { + result += ActionBar.getCurrentActionBarHeight(); + } + return result; + } + + private void hide() { + if (parentActivity == null) { + return; + } + try { + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(windowView, "alpha", 1.0f, 0.0f), + ObjectAnimator.ofFloat(windowView, "scaleX", 1.0f, 0.0f), + ObjectAnimator.ofFloat(windowView, "scaleY", 1.0f, 0.0f)); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.setDuration(150); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (windowView != null) { + windowManager.removeView(windowView); + } + } + }); + animatorSet.start(); + hidden = true; + } catch (Exception e) { + //don't promt + } + } + + private void show() { + if (parentActivity == null) { + return; + } + try { + windowManager.addView(windowView, windowLayoutParams); + hidden = false; + showWithAnimation(); + } catch (Exception e) { + //don't promt + } + } + + public void close() { + try { + windowManager.removeView(windowView); + } catch (Exception e) { + //don't promt + } + parentActivity = null; + } + + public void onConfigurationChanged() { + int sidex = preferences.getInt("sidex", 1); + int sidey = preferences.getInt("sidey", 0); + float px = preferences.getFloat("px", 0); + float py = preferences.getFloat("py", 0); + windowLayoutParams.x = getSideCoord(true, sidex, px, editorWidth); + windowLayoutParams.y = getSideCoord(false, sidey, py, editorHeight); + try { + if (windowView.getParent() != null) { + windowManager.updateViewLayout(windowView, windowLayoutParams); + } + } catch (Exception e) { + FileLog.e(e); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (wallpaperUpdater != null) { + wallpaperUpdater.onActivityResult(requestCode, resultCode, data); + } + } + + private void animateToBoundsMaybe() { + int startX = getSideCoord(true, 0, 0, editorWidth); + int endX = getSideCoord(true, 1, 0, editorWidth); + int startY = getSideCoord(false, 0, 0, editorHeight); + int endY = getSideCoord(false, 1, 0, editorHeight); + ArrayList animators = null; + SharedPreferences.Editor editor = preferences.edit(); + int maxDiff = AndroidUtilities.dp(20); + boolean slideOut = false; + if (Math.abs(startX - windowLayoutParams.x) <= maxDiff || windowLayoutParams.x < 0 && windowLayoutParams.x > -editorWidth / 4) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidex", 0); + if (windowView.getAlpha() != 1.0f) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 1.0f)); + } + animators.add(ObjectAnimator.ofInt(this, "x", startX)); + } else if (Math.abs(endX - windowLayoutParams.x) <= maxDiff || windowLayoutParams.x > AndroidUtilities.displaySize.x - editorWidth && windowLayoutParams.x < AndroidUtilities.displaySize.x - editorWidth / 4 * 3) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidex", 1); + if (windowView.getAlpha() != 1.0f) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 1.0f)); + } + animators.add(ObjectAnimator.ofInt(this, "x", endX)); + } else if (windowView.getAlpha() != 1.0f) { + if (animators == null) { + animators = new ArrayList<>(); + } + if (windowLayoutParams.x < 0) { + animators.add(ObjectAnimator.ofInt(this, "x", -editorWidth)); + } else { + animators.add(ObjectAnimator.ofInt(this, "x", AndroidUtilities.displaySize.x)); + } + slideOut = true; + } else { + editor.putFloat("px", (windowLayoutParams.x - startX) / (float) (endX - startX)); + editor.putInt("sidex", 2); + } + if (!slideOut) { + if (Math.abs(startY - windowLayoutParams.y) <= maxDiff || windowLayoutParams.y <= ActionBar.getCurrentActionBarHeight()) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidey", 0); + animators.add(ObjectAnimator.ofInt(this, "y", startY)); + } else if (Math.abs(endY - windowLayoutParams.y) <= maxDiff) { + if (animators == null) { + animators = new ArrayList<>(); + } + editor.putInt("sidey", 1); + animators.add(ObjectAnimator.ofInt(this, "y", endY)); + } else { + editor.putFloat("py", (windowLayoutParams.y - startY) / (float) (endY - startY)); + editor.putInt("sidey", 2); + } + editor.commit(); + } + if (animators != null) { + if (decelerateInterpolator == null) { + decelerateInterpolator = new DecelerateInterpolator(); + } + AnimatorSet animatorSet = new AnimatorSet(); + animatorSet.setInterpolator(decelerateInterpolator); + animatorSet.setDuration(150); + if (slideOut) { + animators.add(ObjectAnimator.ofFloat(windowView, "alpha", 0.0f)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + Theme.saveCurrentTheme(currentThemeName, true); + destroy(); + } + }); + } + animatorSet.playTogether(animators); + animatorSet.start(); + } + } + + public int getX() { + return windowLayoutParams.x; + } + + public int getY() { + return windowLayoutParams.y; + } + + @Keep + public void setX(int value) { + windowLayoutParams.x = value; + windowManager.updateViewLayout(windowView, windowLayoutParams); + } + + @Keep + public void setY(int value) { + windowLayoutParams.y = value; + windowManager.updateViewLayout(windowView, windowLayoutParams); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java index 6acc80c347a..08bdd40ab44 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TimerDrawable.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -19,28 +19,24 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; -import org.telegram.messenger.R; +import org.telegram.ui.ActionBar.Theme; public class TimerDrawable extends Drawable { - private static Drawable emptyTimerDrawable; - private static Drawable timerDrawable; - private static TextPaint timePaint; + private TextPaint timePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + private Paint linePaint = new Paint(Paint.ANTI_ALIAS_FLAG); private StaticLayout timeLayout; private float timeWidth = 0; private int timeHeight = 0; private int time = 0; public TimerDrawable(Context context) { - if (emptyTimerDrawable == null) { - emptyTimerDrawable = context.getResources().getDrawable(R.drawable.header_timer); - timerDrawable = context.getResources().getDrawable(R.drawable.header_timer2); - timePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - timePaint.setColor(0xffffffff); - timePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - } - + timePaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); timePaint.setTextSize(AndroidUtilities.dp(11)); + + linePaint.setStrokeWidth(AndroidUtilities.dp(1)); + linePaint.setStyle(Paint.Style.STROKE); } public void setTime(int value) { @@ -82,7 +78,7 @@ public void setTime(int value) { timeHeight = timeLayout.getHeight(); } catch (Exception e) { timeLayout = null; - FileLog.e("tmessages", e); + FileLog.e(e); } invalidateSelf(); @@ -90,20 +86,28 @@ public void setTime(int value) { @Override public void draw(Canvas canvas) { - int width = timerDrawable.getIntrinsicWidth(); - int height = timerDrawable.getIntrinsicHeight(); - Drawable drawable; + int width = getIntrinsicWidth(); + int height = getIntrinsicHeight(); + + if (time == 0) { - drawable = timerDrawable; + paint.setColor(Theme.getColor(Theme.key_chat_secretTimerBackground)); + linePaint.setColor(Theme.getColor(Theme.key_chat_secretTimerText)); + + canvas.drawCircle(AndroidUtilities.dpf2(9), AndroidUtilities.dpf2(9), AndroidUtilities.dpf2(7.5f), paint); + canvas.drawCircle(AndroidUtilities.dpf2(9), AndroidUtilities.dpf2(9), AndroidUtilities.dpf2(8), linePaint); + + paint.setColor(Theme.getColor(Theme.key_chat_secretTimerText)); + canvas.drawLine(AndroidUtilities.dp(9), AndroidUtilities.dp(9), AndroidUtilities.dp(13), AndroidUtilities.dp(9), linePaint); + canvas.drawLine(AndroidUtilities.dp(9), AndroidUtilities.dp(5), AndroidUtilities.dp(9), AndroidUtilities.dp(9.5f), linePaint); + + canvas.drawRect(AndroidUtilities.dpf2(7), AndroidUtilities.dpf2(0), AndroidUtilities.dpf2(11), AndroidUtilities.dpf2(1.5f), paint); } else { - drawable = emptyTimerDrawable; + paint.setColor(Theme.getColor(Theme.key_chat_secretTimerBackground)); + timePaint.setColor(Theme.getColor(Theme.key_chat_secretTimerText)); + canvas.drawCircle(AndroidUtilities.dp(9.5f), AndroidUtilities.dp(9.5f), AndroidUtilities.dp(9.5f), paint); } - int x = (width - drawable.getIntrinsicWidth()) / 2; - int y = (height - drawable.getIntrinsicHeight()) / 2; - drawable.setBounds(x, y, x + drawable.getIntrinsicWidth(), y + drawable.getIntrinsicHeight()); - drawable.draw(canvas); - if (time != 0 && timeLayout != null) { int xOffxet = 0; if (AndroidUtilities.density == 3) { @@ -131,11 +135,11 @@ public int getOpacity() { @Override public int getIntrinsicWidth() { - return timerDrawable.getIntrinsicWidth(); + return AndroidUtilities.dp(19); } @Override public int getIntrinsicHeight() { - return timerDrawable.getIntrinsicHeight(); + return AndroidUtilities.dp(19); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypefaceSpan.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypefaceSpan.java index 447543fb102..af3d53274e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypefaceSpan.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypefaceSpan.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -13,31 +13,45 @@ import android.text.TextPaint; import android.text.style.MetricAffectingSpan; +import org.telegram.messenger.AndroidUtilities; + public class TypefaceSpan extends MetricAffectingSpan { - private Typeface mTypeface; + private Typeface typeface; private int textSize; private int color; - public TypefaceSpan(Typeface typeface) { - mTypeface = typeface; + public TypefaceSpan(Typeface tf) { + typeface = tf; } - public TypefaceSpan(Typeface typeface, int size) { - mTypeface = typeface; + public TypefaceSpan(Typeface tf, int size) { + typeface = tf; textSize = size; } - public TypefaceSpan(Typeface typeface, int size, int textColor) { - mTypeface = typeface; + public TypefaceSpan(Typeface tf, int size, int textColor) { + typeface = tf; textSize = size; color = textColor; } + public Typeface getTypeface() { + return typeface; + } + + public boolean isBold() { + return typeface == AndroidUtilities.getTypeface("fonts/rmedium.ttf"); + } + + public boolean isItalic() { + return typeface == AndroidUtilities.getTypeface("fonts/ritalic.ttf"); + } + @Override public void updateMeasureState(TextPaint p) { - if (mTypeface != null) { - p.setTypeface(mTypeface); + if (typeface != null) { + p.setTypeface(typeface); } if (textSize != 0) { p.setTextSize(textSize); @@ -47,8 +61,8 @@ public void updateMeasureState(TextPaint p) { @Override public void updateDrawState(TextPaint tp) { - if (mTypeface != null) { - tp.setTypeface(mTypeface); + if (typeface != null) { + tp.setTypeface(typeface); } if (textSize != 0) { tp.setTextSize(textSize); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java index ecdb77c92fe..2bf83d91db1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/TypingDotsDrawable.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.graphics.Canvas; import android.graphics.ColorFilter; -import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.view.animation.DecelerateInterpolator; @@ -21,7 +20,6 @@ public class TypingDotsDrawable extends Drawable { private boolean isChat = false; - private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private float[] scales = new float[3]; private float[] startTimes = new float[] {0, 150, 300}; private float[] elapsedTimes = new float[] {0, 0, 0}; @@ -29,11 +27,6 @@ public class TypingDotsDrawable extends Drawable { private boolean started = false; private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); - public TypingDotsDrawable() { - super(); - paint.setColor(Theme.ACTION_BAR_SUBTITLE_COLOR); - } - public void setIsChat(boolean value) { isChat = value; } @@ -96,9 +89,9 @@ public void draw(Canvas canvas) { } else { y = AndroidUtilities.dp(9.3f) + getBounds().top; } - canvas.drawCircle(AndroidUtilities.dp(3), y, scales[0] * AndroidUtilities.density, paint); - canvas.drawCircle(AndroidUtilities.dp(9), y, scales[1] * AndroidUtilities.density, paint); - canvas.drawCircle(AndroidUtilities.dp(15), y, scales[2] * AndroidUtilities.density, paint); + canvas.drawCircle(AndroidUtilities.dp(3), y, scales[0] * AndroidUtilities.density, Theme.chat_statusPaint); + canvas.drawCircle(AndroidUtilities.dp(9), y, scales[1] * AndroidUtilities.density, Theme.chat_statusPaint); + canvas.drawCircle(AndroidUtilities.dp(15), y, scales[2] * AndroidUtilities.density, Theme.chat_statusPaint); checkUpdate(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java index adb870543b5..be2ed6aadef 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanBotCommand.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -15,15 +15,21 @@ public class URLSpanBotCommand extends URLSpanNoUnderline { public static boolean enabled = true; + public boolean isOut; - public URLSpanBotCommand(String url) { + public URLSpanBotCommand(String url, boolean isOutOwner) { super(url); + isOut = isOutOwner; } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); - ds.setColor(enabled ? Theme.MSG_LINK_TEXT_COLOR : Theme.MSG_TEXT_COLOR); + if (isOut) { + ds.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkOut : Theme.key_chat_messageTextOut)); + } else { + ds.setColor(Theme.getColor(enabled ? Theme.key_chat_messageLinkIn : Theme.key_chat_messageTextIn)); + } ds.setUnderlineText(false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java new file mode 100644 index 00000000000..e81850221c0 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanMono.java @@ -0,0 +1,55 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.graphics.Paint; +import android.graphics.Typeface; +import android.text.TextPaint; +import android.text.style.MetricAffectingSpan; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.MessagesController; +import org.telegram.ui.ActionBar.Theme; + +public class URLSpanMono extends MetricAffectingSpan { + + private CharSequence currentMessage; + private int currentStart; + private int currentEnd; + private boolean isOut; + + public URLSpanMono(CharSequence message, int start, int end, boolean out) { + currentMessage = message; + currentStart = start; + currentEnd = end; + } + + public void copyToClipboard() { + AndroidUtilities.addToClipboard(currentMessage.subSequence(currentStart, currentEnd).toString()); + } + + @Override + public void updateMeasureState(TextPaint p) { + p.setTypeface(Typeface.MONOSPACE); + p.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize - 1)); + p.setFlags(p.getFlags() | Paint.SUBPIXEL_TEXT_FLAG); + } + + @Override + public void updateDrawState(TextPaint ds) { + ds.setTextSize(AndroidUtilities.dp(MessagesController.getInstance().fontSize - 1)); + ds.setTypeface(Typeface.MONOSPACE); + ds.setUnderlineText(false); + if (isOut) { + ds.setColor(Theme.getColor(Theme.key_chat_messageTextOut)); + } else { + ds.setColor(Theme.getColor(Theme.key_chat_messageTextIn)); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java index f6dc2ff1443..25ea5f05033 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderline.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderlineBold.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderlineBold.java index 5660d18768c..5727cae997a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderlineBold.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanNoUnderlineBold.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java index cb0b291b8a8..9cfe6e62886 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanReplacement.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java index 60d8584d89e..07949966587 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/URLSpanUserMention.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -14,14 +14,22 @@ public class URLSpanUserMention extends URLSpanNoUnderline { - public URLSpanUserMention(String url) { + private boolean isOut; + + public URLSpanUserMention(String url, boolean isOutOwner) { super(url); + isOut = isOutOwner; } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); - ds.setColor(Theme.MSG_LINK_TEXT_COLOR); + if (isOut) { + ds.setColor(Theme.getColor(Theme.key_chat_messageLinkOut)); + } else { + ds.setColor(Theme.getColor(Theme.key_chat_messageLinkIn)); + } + ds.setUnderlineText(false); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java index 0810c1421a5..0e00a802d7f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java @@ -3,365 +3,303 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; import android.annotation.SuppressLint; -import android.content.Context; -import android.media.AudioManager; -import android.media.MediaCodec; +import android.graphics.SurfaceTexture; import android.net.Uri; import android.os.Handler; -import android.os.Looper; -import android.view.Surface; - -import org.telegram.messenger.exoplayer.DummyTrackRenderer; -import org.telegram.messenger.exoplayer.ExoPlaybackException; -import org.telegram.messenger.exoplayer.ExoPlayer; -import org.telegram.messenger.exoplayer.MediaCodecAudioTrackRenderer; -import org.telegram.messenger.exoplayer.MediaCodecSelector; -import org.telegram.messenger.exoplayer.MediaCodecTrackRenderer; -import org.telegram.messenger.exoplayer.MediaCodecVideoTrackRenderer; -import org.telegram.messenger.exoplayer.MediaFormat; -import org.telegram.messenger.exoplayer.TrackRenderer; -import org.telegram.messenger.exoplayer.audio.AudioCapabilities; -import org.telegram.messenger.exoplayer.extractor.ExtractorSampleSource; -import org.telegram.messenger.exoplayer.upstream.Allocator; -import org.telegram.messenger.exoplayer.upstream.DataSource; -import org.telegram.messenger.exoplayer.upstream.DefaultAllocator; -import org.telegram.messenger.exoplayer.upstream.DefaultUriDataSource; -import org.telegram.messenger.exoplayer.util.PlayerControl; - -import java.util.concurrent.CopyOnWriteArrayList; +import android.view.TextureView; + +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.exoplayer2.DefaultLoadControl; +import org.telegram.messenger.exoplayer2.ExoPlaybackException; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ExoPlayerFactory; +import org.telegram.messenger.exoplayer2.SimpleExoPlayer; +import org.telegram.messenger.exoplayer2.Timeline; +import org.telegram.messenger.exoplayer2.extractor.DefaultExtractorsFactory; +import org.telegram.messenger.exoplayer2.source.ExtractorMediaSource; +import org.telegram.messenger.exoplayer2.source.LoopingMediaSource; +import org.telegram.messenger.exoplayer2.source.MediaSource; +import org.telegram.messenger.exoplayer2.source.MergingMediaSource; +import org.telegram.messenger.exoplayer2.source.TrackGroupArray; +import org.telegram.messenger.exoplayer2.source.dash.DashMediaSource; +import org.telegram.messenger.exoplayer2.source.dash.DefaultDashChunkSource; +import org.telegram.messenger.exoplayer2.source.hls.HlsMediaSource; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.DefaultSsChunkSource; +import org.telegram.messenger.exoplayer2.source.smoothstreaming.SsMediaSource; +import org.telegram.messenger.exoplayer2.trackselection.AdaptiveVideoTrackSelection; +import org.telegram.messenger.exoplayer2.trackselection.DefaultTrackSelector; +import org.telegram.messenger.exoplayer2.trackselection.MappingTrackSelector; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelection; +import org.telegram.messenger.exoplayer2.trackselection.TrackSelectionArray; +import org.telegram.messenger.exoplayer2.upstream.DataSource; +import org.telegram.messenger.exoplayer2.upstream.DefaultBandwidthMeter; +import org.telegram.messenger.exoplayer2.upstream.DefaultDataSourceFactory; +import org.telegram.messenger.exoplayer2.upstream.DefaultHttpDataSourceFactory; @SuppressLint("NewApi") -public class VideoPlayer implements ExoPlayer.Listener, MediaCodecVideoTrackRenderer.EventListener { +public class VideoPlayer implements ExoPlayer.EventListener, SimpleExoPlayer.VideoListener { public interface RendererBuilder { void buildRenderers(VideoPlayer player); void cancel(); } - public interface Listener { + public interface VideoPlayerDelegate { void onStateChanged(boolean playWhenReady, int playbackState); void onError(Exception e); void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio); + void onRenderedFirstFrame(); + void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture); + boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture); } - public static class ExtractorRendererBuilder implements RendererBuilder { + private SimpleExoPlayer player; + private MappingTrackSelector trackSelector; + private Handler mainHandler; + private DataSource.Factory mediaDataSourceFactory; + private TextureView textureView; + private boolean autoplay; - private static final int BUFFER_SEGMENT_SIZE = 256 * 1024; - private static final int BUFFER_SEGMENT_COUNT = 256; - - private final Context context; - private final String userAgent; - private final Uri uri; - - public ExtractorRendererBuilder(Context context, String userAgent, Uri uri) { - this.context = context; - this.userAgent = userAgent; - this.uri = uri; - } - - @Override - public void buildRenderers(VideoPlayer player) { - Allocator allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE); - Handler mainHandler = player.getMainHandler(); - - TrackRenderer[] renderers = new TrackRenderer[RENDERER_COUNT]; - DataSource dataSource = new DefaultUriDataSource(context, userAgent); - ExtractorSampleSource sampleSource = new ExtractorSampleSource(uri, dataSource, allocator, BUFFER_SEGMENT_COUNT * BUFFER_SEGMENT_SIZE, mainHandler, null, 0); - renderers[TYPE_VIDEO] = new MediaCodecVideoTrackRenderer(context, sampleSource, MediaCodecSelector.DEFAULT, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 5000, mainHandler, player, 50) { - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) throws ExoPlaybackException { - super.doSomeWork(positionUs, elapsedRealtimeUs, sourceIsReady); - } - }; - renderers[TYPE_AUDIO] = new MediaCodecAudioTrackRenderer(sampleSource, MediaCodecSelector.DEFAULT, null, true, mainHandler, null, AudioCapabilities.getCapabilities(context), AudioManager.STREAM_MUSIC); - player.onRenderers(renderers); - } - - @Override - public void cancel() { - - } - } - - public static final int STATE_IDLE = ExoPlayer.STATE_IDLE; - public static final int STATE_PREPARING = ExoPlayer.STATE_PREPARING; - public static final int STATE_BUFFERING = ExoPlayer.STATE_BUFFERING; - public static final int STATE_READY = ExoPlayer.STATE_READY; - public static final int STATE_ENDED = ExoPlayer.STATE_ENDED; - public static final int TRACK_DISABLED = ExoPlayer.TRACK_DISABLED; - public static final int TRACK_DEFAULT = ExoPlayer.TRACK_DEFAULT; - - public static final int RENDERER_COUNT = 2; - public static final int TYPE_VIDEO = 0; - public static final int TYPE_AUDIO = 1; + private VideoPlayerDelegate delegate; + private int lastReportedPlaybackState; + private boolean lastReportedPlayWhenReady; private static final int RENDERER_BUILDING_STATE_IDLE = 1; private static final int RENDERER_BUILDING_STATE_BUILDING = 2; private static final int RENDERER_BUILDING_STATE_BUILT = 3; - private final RendererBuilder rendererBuilder; - private final ExoPlayer player; - private final Handler mainHandler; - private final CopyOnWriteArrayList listeners; - private final PlayerControl playerControl; - - private int rendererBuildingState; - private int lastReportedPlaybackState; - private boolean lastReportedPlayWhenReady; - - private Surface surface; - private TrackRenderer videoRenderer; - private TrackRenderer audioRenderer; - private int videoTrackToRestore; + private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter(); - private boolean backgrounded; + public VideoPlayer() { + mediaDataSourceFactory = new DefaultDataSourceFactory(ApplicationLoader.applicationContext, BANDWIDTH_METER, new DefaultHttpDataSourceFactory("Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)", BANDWIDTH_METER)); - public VideoPlayer(RendererBuilder rendererBuilder) { - this.rendererBuilder = rendererBuilder; - player = ExoPlayer.Factory.newInstance(RENDERER_COUNT, 1000, 5000); - player.addListener(this); - playerControl = new PlayerControl(player); mainHandler = new Handler(); - listeners = new CopyOnWriteArrayList<>(); - lastReportedPlaybackState = STATE_IDLE; - rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - } - public PlayerControl getPlayerControl() { - return playerControl; - } - - public void addListener(Listener listener) { - listeners.add(listener); - } - - public void removeListener(Listener listener) { - listeners.remove(listener); - } + TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveVideoTrackSelection.Factory(BANDWIDTH_METER); + trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); - public void setSurface(Surface surface) { - this.surface = surface; - pushSurface(false); + lastReportedPlaybackState = ExoPlayer.STATE_IDLE; } - public Surface getSurface() { - return surface; - } - - public void blockingClearSurface() { - surface = null; - pushSurface(true); - } - - public int getTrackCount(int type) { - return player.getTrackCount(type); - } - - public MediaFormat getTrackFormat(int type, int index) { - return player.getTrackFormat(type, index); + private void ensurePleyaerCreated() { + if (player == null) { + player = ExoPlayerFactory.newSimpleInstance(ApplicationLoader.applicationContext, trackSelector, new DefaultLoadControl(), null, SimpleExoPlayer.EXTENSION_RENDERER_MODE_OFF); + player.addListener(this); + player.setVideoListener(this); + player.setVideoTextureView(textureView); + player.setPlayWhenReady(autoplay); + } } - public int getSelectedTrack(int type) { - return player.getSelectedTrack(type); + public void preparePlayerLoop(Uri videoUri, String videoType, Uri audioUri, String audioType) { + ensurePleyaerCreated(); + MediaSource mediaSource1 = null, mediaSource2 = null; + for (int a = 0; a < 2; a++) { + MediaSource mediaSource; + String type; + Uri uri; + if (a == 0) { + type = videoType; + uri = videoUri; + } else { + type = audioType; + uri = audioUri; + } + switch (type) { + case "dash": + mediaSource = new DashMediaSource(uri, mediaDataSourceFactory, new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + case "hls": + mediaSource = new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); + break; + case "ss": + mediaSource = new SsMediaSource(uri, mediaDataSourceFactory, new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + default: + mediaSource = new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, null); + break; + } + mediaSource = new LoopingMediaSource(mediaSource); + if (a == 0) { + mediaSource1 = mediaSource; + } else { + mediaSource2 = mediaSource; + } + } + MediaSource mediaSource = new MergingMediaSource(mediaSource1, mediaSource2); + player.prepare(mediaSource1, true, true); + } + + public void preparePlayer(Uri uri, String type) { + ensurePleyaerCreated(); + MediaSource mediaSource; + switch (type) { + case "dash": + mediaSource = new DashMediaSource(uri, mediaDataSourceFactory, new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + case "hls": + mediaSource = new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); + break; + case "ss": + mediaSource = new SsMediaSource(uri, mediaDataSourceFactory, new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); + break; + default: + mediaSource = new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), mainHandler, null); + break; + } + player.prepare(mediaSource, true, true); } - public void setSelectedTrack(int type, int index) { - player.setSelectedTrack(type, index); + public boolean isPlayerPrepared() { + return player != null; } - public boolean getBackgrounded() { - return backgrounded; + public void releasePlayer() { + if (player != null) { + player.release(); + player = null; + } } - public void setBackgrounded(boolean backgrounded) { - if (this.backgrounded == backgrounded) { + public void setTextureView(TextureView texture) { + textureView = texture; + if (player == null) { return; } - this.backgrounded = backgrounded; - if (backgrounded) { - videoTrackToRestore = getSelectedTrack(TYPE_VIDEO); - setSelectedTrack(TYPE_VIDEO, TRACK_DISABLED); - blockingClearSurface(); - } else { - setSelectedTrack(TYPE_VIDEO, videoTrackToRestore); - } + player.setVideoTextureView(textureView); } - public void prepare() { - if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT) { - player.stop(); + public void play() { + if (player == null) { + return; } - rendererBuilder.cancel(); - videoRenderer = null; - audioRenderer = null; - rendererBuildingState = RENDERER_BUILDING_STATE_BUILDING; - maybeReportPlayerState(); - rendererBuilder.buildRenderers(this); + player.setPlayWhenReady(true); } - void onRenderers(TrackRenderer[] renderers) { - for (int i = 0; i < RENDERER_COUNT; i++) { - if (renderers[i] == null) { - renderers[i] = new DummyTrackRenderer(); - } + public void pause() { + if (player == null) { + return; } - videoRenderer = renderers[TYPE_VIDEO]; - audioRenderer = renderers[TYPE_AUDIO]; - pushSurface(false); - player.prepare(renderers); - rendererBuildingState = RENDERER_BUILDING_STATE_BUILT; - } - - void onRenderersError(Exception e) { - for (Listener listener : listeners) { - listener.onError(e); - } - rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - maybeReportPlayerState(); + player.setPlayWhenReady(false); } public void setPlayWhenReady(boolean playWhenReady) { + autoplay = playWhenReady; + if (player == null) { + return; + } player.setPlayWhenReady(playWhenReady); } - public void seekTo(long positionMs) { - player.seekTo(positionMs); + public long getDuration() { + return player != null ? player.getDuration() : 0; } - public void release() { - rendererBuilder.cancel(); - rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - surface = null; - player.release(); + public long getCurrentPosition() { + return player != null ? player.getCurrentPosition() : 0; } - public int getPlaybackState() { - if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILDING) { - return STATE_PREPARING; + public void setMute(boolean value) { + if (player == null) { + return; } - int playerState = player.getPlaybackState(); - if (rendererBuildingState == RENDERER_BUILDING_STATE_BUILT && playerState == STATE_IDLE) { - return STATE_PREPARING; + if (value) { + player.setVolume(0.0f); + } else { + player.setVolume(1.0f); } - return playerState; } - public long getCurrentPosition() { - return player.getCurrentPosition(); + public void seekTo(long positionMs) { + if (player == null) { + return; + } + player.seekTo(positionMs); } - public long getDuration() { - return player.getDuration(); + public void setDelegate(VideoPlayerDelegate videoPlayerDelegate) { + delegate = videoPlayerDelegate; } public int getBufferedPercentage() { - return player.getBufferedPercentage(); + return player != null ? player.getBufferedPercentage() : 0; } - public boolean getPlayWhenReady() { - return player.getPlayWhenReady(); + public long getBufferedPosition() { + return player != null ? player.getBufferedPosition() : 0; } - Looper getPlaybackLooper() { - return player.getPlaybackLooper(); + public boolean isPlaying() { + return player != null && player.getPlayWhenReady(); } - Handler getMainHandler() { - return mainHandler; + public boolean isBuffering() { + return player != null && lastReportedPlaybackState == ExoPlayer.STATE_BUFFERING; } @Override - public void onPlayerStateChanged(boolean playWhenReady, int state) { - maybeReportPlayerState(); + public void onLoadingChanged(boolean isLoading) { + } @Override - public void onPlayerError(ExoPlaybackException exception) { - rendererBuildingState = RENDERER_BUILDING_STATE_IDLE; - for (Listener listener : listeners) { - listener.onError(exception); - } + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { + maybeReportPlayerState(); } @Override - public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - for (Listener listener : listeners) { - listener.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); - } + public void onTimelineChanged(Timeline timeline, Object manifest) { + } @Override - public void onDroppedFrames(int count, long elapsed) { - + public void onPlayerError(ExoPlaybackException error) { + delegate.onError(error); } @Override - public void onDecoderInitializationError(MediaCodecTrackRenderer.DecoderInitializationException e) { - for (Listener listener : listeners) { - listener.onError(e); - } + public void onPositionDiscontinuity() { + } @Override - public void onDecoderInitialized(String decoderName, long elapsedRealtimeMs, long initializationDurationMs) { + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { } @Override - public void onPlayWhenReadyCommitted() { - // Do nothing. + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + delegate.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); } @Override - public void onDrawnToSurface(Surface surface) { - // Do nothing. + public void onRenderedFirstFrame() { + delegate.onRenderedFirstFrame(); } @Override - public void onCryptoError(MediaCodec.CryptoException e) { - for (Listener listener : listeners) { - listener.onError(e); - } + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return delegate.onSurfaceDestroyed(surfaceTexture); } - public void setMute(boolean value) { - if (audioRenderer == null) { - return; - } - if (value) { - player.sendMessage(audioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, 0.0f); - } else { - player.sendMessage(audioRenderer, MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, 1.0f); - } + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + delegate.onSurfaceTextureUpdated(surfaceTexture); } private void maybeReportPlayerState() { boolean playWhenReady = player.getPlayWhenReady(); - int playbackState = getPlaybackState(); + int playbackState = player.getPlaybackState(); if (lastReportedPlayWhenReady != playWhenReady || lastReportedPlaybackState != playbackState) { - for (Listener listener : listeners) { - listener.onStateChanged(playWhenReady, playbackState); - } + delegate.onStateChanged(playWhenReady, playbackState); lastReportedPlayWhenReady = playWhenReady; lastReportedPlaybackState = playbackState; } } - - private void pushSurface(boolean blockForSurfacePush) { - if (videoRenderer == null) { - return; - } - - if (blockForSurfacePush) { - player.blockingSendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); - } else { - player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface); - } - } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoSeekBarView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoSeekBarView.java index 26676ec860d..68224ac05c4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoSeekBarView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoSeekBarView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoTimelineView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoTimelineView.java index fcb667b8505..8f8f3714740 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoTimelineView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoTimelineView.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui.Components; @@ -44,9 +44,11 @@ public class VideoTimelineView extends View { private int frameWidth; private int frameHeight; private int framesToLoad; + private float maxProgressDiff; public interface VideoTimelineViewDelegate { void onLeftProgressChanged(float progress); + void onRifhtProgressChanged(float progress); } @@ -66,6 +68,14 @@ public float getRightProgress() { return progressRight; } + public void setMaxProgressDiff(float value) { + maxProgressDiff = value; + if (progressRight - progressLeft > maxProgressDiff) { + progressRight = progressLeft + maxProgressDiff; + invalidate(); + } + } + @Override public boolean onTouchEvent(MotionEvent event) { if (event == null) { @@ -75,20 +85,20 @@ public boolean onTouchEvent(MotionEvent event) { float y = event.getY(); int width = getMeasuredWidth() - AndroidUtilities.dp(32); - int startX = (int)(width * progressLeft) + AndroidUtilities.dp(16); - int endX = (int)(width * progressRight) + AndroidUtilities.dp(16); + int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16); + int endX = (int) (width * progressRight) + AndroidUtilities.dp(16); if (event.getAction() == MotionEvent.ACTION_DOWN) { int additionWidth = AndroidUtilities.dp(12); if (startX - additionWidth <= x && x <= startX + additionWidth && y >= 0 && y <= getMeasuredHeight()) { pressedLeft = true; - pressDx = (int)(x - startX); + pressDx = (int) (x - startX); getParent().requestDisallowInterceptTouchEvent(true); invalidate(); return true; } else if (endX - additionWidth <= x && x <= endX + additionWidth && y >= 0 && y <= getMeasuredHeight()) { pressedRight = true; - pressDx = (int)(x - endX); + pressDx = (int) (x - endX); getParent().requestDisallowInterceptTouchEvent(true); invalidate(); return true; @@ -103,26 +113,32 @@ public boolean onTouchEvent(MotionEvent event) { } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { if (pressedLeft) { - startX = (int)(x - pressDx); + startX = (int) (x - pressDx); if (startX < AndroidUtilities.dp(16)) { startX = AndroidUtilities.dp(16); } else if (startX > endX) { startX = endX; } - progressLeft = (float)(startX - AndroidUtilities.dp(16)) / (float)width; + progressLeft = (float) (startX - AndroidUtilities.dp(16)) / (float) width; + if (progressRight - progressLeft > maxProgressDiff) { + progressRight = progressLeft + maxProgressDiff; + } if (delegate != null) { delegate.onLeftProgressChanged(progressLeft); } invalidate(); return true; } else if (pressedRight) { - endX = (int)(x - pressDx); + endX = (int) (x - pressDx); if (endX < startX) { endX = startX; } else if (endX > width + AndroidUtilities.dp(16)) { endX = width + AndroidUtilities.dp(16); } - progressRight = (float)(endX - AndroidUtilities.dp(16)) / (float)width; + progressRight = (float) (endX - AndroidUtilities.dp(16)) / (float) width; + if (progressRight - progressLeft > maxProgressDiff) { + progressLeft = progressRight - maxProgressDiff; + } if (delegate != null) { delegate.onRifhtProgressChanged(progressRight); } @@ -135,12 +151,14 @@ public boolean onTouchEvent(MotionEvent event) { public void setVideoPath(String path) { mediaMetadataRetriever = new MediaMetadataRetriever(); + progressLeft = 0.0f; + progressRight = 1.0f; try { mediaMetadataRetriever.setDataSource(path); String duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); videoLength = Long.parseLong(duration); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -155,7 +173,7 @@ private void reloadFrames(int frameNum) { if (frameNum == 0) { frameHeight = AndroidUtilities.dp(40); framesToLoad = (getMeasuredWidth() - AndroidUtilities.dp(16)) / frameHeight; - frameWidth = (int)Math.ceil((float)(getMeasuredWidth() - AndroidUtilities.dp(16)) / (float)framesToLoad); + frameWidth = (int) Math.ceil((float) (getMeasuredWidth() - AndroidUtilities.dp(16)) / (float) framesToLoad); frameTimeOffset = videoLength / framesToLoad; } currentTask = new AsyncTask() { @@ -188,7 +206,7 @@ protected Bitmap doInBackground(Integer... objects) { bitmap = result; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return bitmap; } @@ -215,7 +233,7 @@ public void destroy() { mediaMetadataRetriever = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } for (Bitmap bitmap : frames) { @@ -247,8 +265,8 @@ public void clearFrames() { @Override protected void onDraw(Canvas canvas) { int width = getMeasuredWidth() - AndroidUtilities.dp(36); - int startX = (int)(width * progressLeft) + AndroidUtilities.dp(16); - int endX = (int)(width * progressRight) + AndroidUtilities.dp(16); + int startX = (int) (width * progressLeft) + AndroidUtilities.dp(16); + int endX = (int) (width * progressRight) + AndroidUtilities.dp(16); canvas.save(); canvas.clipRect(AndroidUtilities.dp(16), 0, width + AndroidUtilities.dp(20), AndroidUtilities.dp(44)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/WallpaperUpdater.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/WallpaperUpdater.java new file mode 100644 index 00000000000..5bfa2c84b7b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/WallpaperUpdater.java @@ -0,0 +1,157 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.support.v4.content.FileProvider; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildConfig; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.AlertDialog; + +import java.io.File; +import java.io.FileOutputStream; + +public class WallpaperUpdater { + + private String currentPicturePath; + private File picturePath = null; + private Activity parentActivity; + private WallpaperUpdaterDelegate delegate; + private File currentWallpaperPath; + + public interface WallpaperUpdaterDelegate { + void didSelectWallpaper(File file, Bitmap bitmap); + void needOpenColorPicker(); + } + + public WallpaperUpdater(Activity activity, WallpaperUpdaterDelegate wallpaperUpdaterDelegate) { + parentActivity = activity; + delegate = wallpaperUpdaterDelegate; + currentWallpaperPath = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), Utilities.random.nextInt() + ".jpg"); + } + + public void showAlert(final boolean fromTheme) { + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + CharSequence[] items; + if (fromTheme) { + items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley), LocaleController.getString("SelectColor", R.string.SelectColor), LocaleController.getString("Default", R.string.Default), LocaleController.getString("Cancel", R.string.Cancel)}; + } else { + items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley), LocaleController.getString("Cancel", R.string.Cancel)}; + } + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + try { + if (i == 0) { + try { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + File image = AndroidUtilities.generatePicturePath(); + if (image != null) { + if (Build.VERSION.SDK_INT >= 24) { + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(parentActivity, BuildConfig.APPLICATION_ID + ".provider", image)); + takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } else { + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image)); + } + currentPicturePath = image.getAbsolutePath(); + } + parentActivity.startActivityForResult(takePictureIntent, 10); + } catch (Exception e) { + FileLog.e(e); + } + } else if (i == 1) { + Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); + photoPickerIntent.setType("image/*"); + parentActivity.startActivityForResult(photoPickerIntent, 11); + } else if (fromTheme) { + if (i == 2) { + delegate.needOpenColorPicker(); + } else if (i == 3) { + delegate.didSelectWallpaper(null, null); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + }); + builder.show(); + } + + public void cleanup() { + currentWallpaperPath.delete(); + } + + public File getCurrentWallpaperPath() { + return currentWallpaperPath; + } + + public String getCurrentPicturePath() { + return currentPicturePath; + } + + public void setCurrentPicturePath(String value) { + currentPicturePath = value; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == Activity.RESULT_OK) { + if (requestCode == 10) { + AndroidUtilities.addMediaToGallery(currentPicturePath); + FileOutputStream stream = null; + try { + Point screenSize = AndroidUtilities.getRealScreenSize(); + Bitmap bitmap = ImageLoader.loadBitmap(currentPicturePath, null, screenSize.x, screenSize.y, true); + stream = new FileOutputStream(currentWallpaperPath); + bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); + delegate.didSelectWallpaper(currentWallpaperPath, bitmap); + } catch (Exception e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + currentPicturePath = null; + } else if (requestCode == 11) { + if (data == null || data.getData() == null) { + return; + } + try { + Point screenSize = AndroidUtilities.getRealScreenSize(); + Bitmap bitmap = ImageLoader.loadBitmap(null, data.getData(), screenSize.x, screenSize.y, true); + FileOutputStream stream = new FileOutputStream(currentWallpaperPath); + bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); + delegate.didSelectWallpaper(currentWallpaperPath, bitmap); + } catch (Exception e) { + FileLog.e(e); + } + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java deleted file mode 100644 index 9bc32fc422c..00000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebFrameLayout.java +++ /dev/null @@ -1,374 +0,0 @@ -/* - * This is the source code of Telegram for Android v. 3.x.x. - * It is licensed under GNU GPL v. 2 or later. - * You should have received a copy of the license in this archive (see LICENSE). - * - * Copyright Nikolai Kudashov, 2013-2016. - */ - -package org.telegram.ui.Components; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.net.Uri; -import android.os.Build; -import android.text.TextUtils; -import android.util.TypedValue; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.CookieManager; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.LocaleController; -import org.telegram.messenger.R; -import org.telegram.messenger.browser.Browser; -import org.telegram.ui.ActionBar.BottomSheet; -import org.telegram.ui.ActionBar.Theme; - -import java.util.HashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class WebFrameLayout extends FrameLayout { - - private WebView webView; - private BottomSheet dialog; - private View customView; - private FrameLayout fullscreenVideoContainer; - private WebChromeClient.CustomViewCallback customViewCallback; - private ProgressBar progressBar; - - private int width; - private int height; - private String openUrl; - private boolean hasDescription; - private String embedUrl; - - final static Pattern youtubeIdRegex = Pattern.compile("(?:youtube(?:-nocookie)?\\.com\\/(?:[^\\/\\n\\s]+\\/\\S+\\/|(?:v|e(?:mbed)?)\\/|\\S*?[?&]v=)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})"); - private final String youtubeFrame = "" + - "
          " + - "
          " + - "
          " + - " " + - " " + - "" + - ""; - - @SuppressLint("SetJavaScriptEnabled") - public WebFrameLayout(Context context, final BottomSheet parentDialog, String title, String descripton, String originalUrl, final String url, int w, int h) { - super(context); - embedUrl = url; - hasDescription = descripton != null && descripton.length() > 0; - openUrl = originalUrl; - width = w; - height = h; - if (width == 0 || height == 0) { - width = AndroidUtilities.displaySize.x; - height = AndroidUtilities.displaySize.y / 2; - } - dialog = parentDialog; - - fullscreenVideoContainer = new FrameLayout(context); - fullscreenVideoContainer.setBackgroundColor(0xff000000); - if (Build.VERSION.SDK_INT >= 21) { - fullscreenVideoContainer.setFitsSystemWindows(true); - } - parentDialog.setApplyTopPadding(false); - parentDialog.setApplyBottomPadding(false); - dialog.getContainer().addView(fullscreenVideoContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - fullscreenVideoContainer.setVisibility(INVISIBLE); - - webView = new WebView(context); - webView.getSettings().setJavaScriptEnabled(true); - webView.getSettings().setDomStorageEnabled(true); - if (Build.VERSION.SDK_INT >= 17) { - webView.getSettings().setMediaPlaybackRequiresUserGesture(false); - } - - String userAgent = webView.getSettings().getUserAgentString(); - if (userAgent != null) { - userAgent = userAgent.replace("Android", ""); - webView.getSettings().setUserAgentString(userAgent); - } - if (Build.VERSION.SDK_INT >= 21) { - webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - CookieManager cookieManager = CookieManager.getInstance(); - cookieManager.setAcceptThirdPartyCookies(webView, true); - } - - webView.setWebChromeClient(new WebChromeClient() { - - @Override - public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) { - onShowCustomView(view, callback); - } - - @Override - public void onShowCustomView(View view, CustomViewCallback callback) { - if (customView != null) { - callback.onCustomViewHidden(); - return; - } - customView = view; - if (dialog != null) { - dialog.getSheetContainer().setVisibility(INVISIBLE); - fullscreenVideoContainer.setVisibility(VISIBLE); - fullscreenVideoContainer.addView(view, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - } - customViewCallback = callback; - } - - @Override - public void onHideCustomView() { - super.onHideCustomView(); - if (customView == null) { - return; - } - if (dialog != null) { - dialog.getSheetContainer().setVisibility(VISIBLE); - fullscreenVideoContainer.setVisibility(INVISIBLE); - fullscreenVideoContainer.removeView(customView); - } - if (customViewCallback != null && !customViewCallback.getClass().getName().contains(".chromium.")) { - customViewCallback.onCustomViewHidden(); - } - customView = null; - } - }); - - webView.setWebViewClient(new WebViewClient() { - @Override - public void onLoadResource(WebView view, String url) { - super.onLoadResource(view, url); - } - - - @Override - public void onPageFinished(WebView view, String url) { - super.onPageFinished(view, url); - progressBar.setVisibility(INVISIBLE); - } - }); - - addView(webView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 48 + 36 + (hasDescription ? 22 : 0))); - - progressBar = new ProgressBar(context); - addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 0, 0, (48 + 36 + (hasDescription ? 22 : 0)) / 2)); - - TextView textView; - - if (hasDescription) { - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - textView.setTextColor(0xff222222); - textView.setText(descripton); - textView.setSingleLine(true); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48 + 9 + 20)); - } - - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(0xff8a8a8a); - textView.setText(title); - textView.setSingleLine(true); - textView.setEllipsize(TextUtils.TruncateAt.END); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48 + 9)); - - View lineView = new View(context); - lineView.setBackgroundColor(0xffdbdbdb); - addView(lineView, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); - ((LayoutParams) lineView.getLayoutParams()).bottomMargin = AndroidUtilities.dp(48); - - FrameLayout frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); - addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); - - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(0xff19a7e8); - textView.setGravity(Gravity.CENTER); - textView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - textView.setText(LocaleController.getString("Close", R.string.Close).toUpperCase()); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - textView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (dialog != null) { - dialog.dismiss(); - } - } - }); - - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - frameLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); - - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(0xff19a7e8); - textView.setGravity(Gravity.CENTER); - textView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - textView.setText(LocaleController.getString("Copy", R.string.Copy).toUpperCase()); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - textView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - try { - android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", openUrl); - clipboard.setPrimaryClip(clip); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - Toast.makeText(getContext(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); - if (dialog != null) { - dialog.dismiss(); - } - } - }); - - textView = new TextView(context); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setTextColor(0xff19a7e8); - textView.setGravity(Gravity.CENTER); - textView.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_AUDIO_SELECTOR_COLOR, false)); - textView.setPadding(AndroidUtilities.dp(18), 0, AndroidUtilities.dp(18), 0); - textView.setText(LocaleController.getString("OpenInBrowser", R.string.OpenInBrowser).toUpperCase()); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - linearLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); - textView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Browser.openUrl(getContext(), openUrl); - if (dialog != null) { - dialog.dismiss(); - } - } - }); - - setOnTouchListener(new OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - parentDialog.setDelegate(new BottomSheet.BottomSheetDelegate() { - - @Override - public void onOpenAnimationEnd() { - HashMap args = new HashMap<>(); - args.put("Referer", "http://youtube.com"); - boolean ok = false; - try { - Uri uri = Uri.parse(openUrl); - String host = uri.getHost().toLowerCase(); - if (host != null && host.endsWith("youtube.com") || host.endsWith("youtu.be")) { - Matcher matcher = youtubeIdRegex.matcher(openUrl); - String id = null; - if (matcher.find()) { - id = matcher.group(1); - } - if (id != null) { - ok = true; - webView.loadDataWithBaseURL("http://youtube.com", String.format(youtubeFrame, id), "text/html", "UTF-8", "http://youtube.com"); - } - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - if (!ok) { - try { - webView.loadUrl(embedUrl, args); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } - }); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - try { - removeView(webView); - webView.stopLoading(); - webView.loadUrl("about:blank"); - webView.destroy(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int parentWidth = MeasureSpec.getSize(widthMeasureSpec); - float scale = width / parentWidth; - int h = (int) Math.min(height / scale, AndroidUtilities.displaySize.y / 2); - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(h + AndroidUtilities.dp(48 + 36 + (hasDescription ? 22 : 0)) + 1, MeasureSpec.EXACTLY)); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java new file mode 100644 index 00000000000..8b6341fcba1 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/WebPlayerView.java @@ -0,0 +1,2056 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui.Components; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.SurfaceTexture; +import android.media.AudioManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.util.Base64; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONTokener; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.Bitmaps; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.concurrent.Semaphore; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +@TargetApi(16) +public class WebPlayerView extends ViewGroup implements VideoPlayer.VideoPlayerDelegate, AudioManager.OnAudioFocusChangeListener { + + public interface WebPlayerViewDelegate { + void onInitFailed(); + TextureView onSwitchToFullscreen(View controlsView, boolean fullscreen, float aspectRatio, int rotation, boolean byButton); + TextureView onSwitchInlineMode(View controlsView, boolean inline, float aspectRatio, int rotation, boolean animated); + void onInlineSurfaceTextureReady(); + void prepareToSwitchInlineMode(boolean inline, Runnable switchInlineModeRunnable, float aspectRatio, boolean animated); + void onSharePressed(); + void onPlayStateChanged(WebPlayerView playerView, boolean playing); + void onVideoSizeChanged(float aspectRatio, int rotation); + ViewGroup getTextureViewContainer(); + boolean checkInlinePermissons(); + } + + private VideoPlayer videoPlayer; + private WebView webView; + private String interfaceName; + private AspectRatioFrameLayout aspectRatioFrameLayout; + private TextureView textureView; + private ImageView textureImageView; + private ViewGroup textureViewContainer; + private Bitmap currentBitmap; + private TextureView changedTextureView; + private int waitingForFirstTextureUpload; + private boolean isAutoplay; + private WebPlayerViewDelegate delegate; + private boolean initFailed; + private boolean initied; + private String playVideoUrl; + private String playVideoType; + private String playAudioUrl; + private String playAudioType; + + private boolean allowInlineAnimation = Build.VERSION.SDK_INT >= 21; + + private static final int AUDIO_NO_FOCUS_NO_DUCK = 0; + private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1; + private static final int AUDIO_FOCUSED = 2; + private boolean hasAudioFocus; + private int audioFocus; + private boolean resumeAudioOnFocusGain; + + private long lastUpdateTime; + private boolean firstFrameRendered; + private float currentAlpha; + + private int seekToTime; + + private boolean drawImage; + + private Paint backgroundPaint = new Paint(); + + private AsyncTask currentTask; + + private boolean changingTextureView; + private boolean inFullscreen; + private boolean isInline; + private boolean isCompleted; + private boolean isLoading; + private boolean switchingInlineMode; + + private RadialProgressView progressView; + private ImageView fullscreenButton; + private ImageView playButton; + private ImageView inlineButton; + private ImageView shareButton; + private AnimatorSet progressAnimation; + + private ControlsView controlsView; + + private Runnable progressRunnable = new Runnable() { + @Override + public void run() { + if (videoPlayer == null || !videoPlayer.isPlaying()) { + return; + } + controlsView.setProgress((int) (videoPlayer.getCurrentPosition() / 1000)); + controlsView.setBufferedProgress((int) (videoPlayer.getBufferedPosition() / 1000), videoPlayer.getBufferedPercentage()); + + AndroidUtilities.runOnUIThread(progressRunnable, 1000); + } + }; + + private static final Pattern youtubeIdRegex = Pattern.compile("(?:youtube(?:-nocookie)?\\.com/(?:[^/\\n\\s]+/\\S+/|(?:v|e(?:mbed)?)/|\\S*?[?&]v=)|youtu\\.be/)([a-zA-Z0-9_-]{11})"); + private static final Pattern vimeoIdRegex = Pattern.compile("https?://(?:(?:www|(player))\\.)?vimeo(pro)?\\.com/(?!(?:channels|album)/[^/?#]+/?(?:$|[?#])|[^/]+/review/|ondemand/)(?:.*?/)?(?:(?:play_redirect_hls|moogaloop\\.swf)\\?clip_id=)?(?:videos?/)?([0-9]+)(?:/[\\da-f]+)?/?(?:[?&].*)?(?:[#].*)?$"); + private static final Pattern coubIdRegex = Pattern.compile("(?:coub:|https?://(?:coub\\.com/(?:view|embed|coubs)/|c-cdn\\.coub\\.com/fb-player\\.swf\\?.*\\bcoub(?:ID|id)=))([\\da-z]+)"); + private static final Pattern aparatIdRegex = Pattern.compile("^https?://(?:www\\.)?aparat\\.com/(?:v/|video/video/embed/videohash/)([a-zA-Z0-9]+)"); + + private static final Pattern aparatFileListPattern = Pattern.compile("fileList\\s*=\\s*JSON\\.parse\\('([^']+)'\\)"); + + private static final Pattern stsPattern = Pattern.compile("\"sts\"\\s*:\\s*(\\d+)"); + private static final Pattern jsPattern = Pattern.compile("\"assets\":.+?\"js\":\\s*(\"[^\"]+\")"); + private static final Pattern sigPattern = Pattern.compile("\\.sig\\|\\|([a-zA-Z0-9$]+)\\("); + private static final Pattern sigPattern2 = Pattern.compile("[\"']signature[\"']\\s*,\\s*([a-zA-Z0-9$]+)\\("); + private static final Pattern stmtVarPattern = Pattern.compile("var\\s"); + private static final Pattern stmtReturnPattern = Pattern.compile("return(?:\\s+|$)"); + private static final Pattern exprParensPattern = Pattern.compile("[()]"); + private static final Pattern playerIdPattern = Pattern.compile(".*?-([a-zA-Z0-9_-]+)(?:/watch_as3|/html5player(?:-new)?|/base)?\\.([a-z]+)$"); + private static final String exprName = "[a-zA-Z_$][a-zA-Z_$0-9]*"; + + private abstract class function { + public abstract Object run(Object[] args); + } + + private class JSExtractor { + + ArrayList codeLines = new ArrayList<>(); + + private String jsCode; + private String[] operators = {"|", "^", "&", ">>", "<<", "-", "+", "%", "/", "*"}; + private String[] assign_operators = {"|=", "^=", "&=", ">>=", "<<=", "-=", "+=", "%=", "/=", "*=", "="}; + + public JSExtractor(String js) { + jsCode = js; + } + + private void interpretExpression(String expr, HashMap localVars, int allowRecursion) throws Exception { + expr = expr.trim(); + if (TextUtils.isEmpty(expr)) { + return; + } + if (expr.charAt(0) == '(') { + int parens_count = 0; + Matcher matcher = exprParensPattern.matcher(expr); + while (matcher.find()) { + String group = matcher.group(0); + if (group.indexOf('0') == '(') { + parens_count++; + } else { + parens_count--; + if (parens_count == 0) { + String sub_expr = expr.substring(1, matcher.start()); + interpretExpression(sub_expr, localVars, allowRecursion); + String remaining_expr = expr.substring(matcher.end()).trim(); + if (TextUtils.isEmpty(remaining_expr)) { + return; + } else { + expr = remaining_expr; + } + break; + } + } + } + if (parens_count != 0) { + throw new Exception(String.format("Premature end of parens in %s", expr)); + } + } + for (int a = 0; a < assign_operators.length; a++) { + String func = assign_operators[a]; + Matcher matcher = Pattern.compile(String.format(Locale.US, "(?x)(%s)(?:\\[([^\\]]+?)\\])?\\s*%s(.*)$", exprName, Pattern.quote(func))).matcher(expr); + if (!matcher.find()) { + continue; + } + interpretExpression(matcher.group(3), localVars, allowRecursion - 1); + String index = matcher.group(2); + if (!TextUtils.isEmpty(index)) { + interpretExpression(index, localVars, allowRecursion); + } else { + localVars.put(matcher.group(1), ""); + } + return; + } + + try { + Integer.parseInt(expr); + return; + } catch (Exception e) { + //ignore + } + + Matcher matcher = Pattern.compile(String.format(Locale.US, "(?!if|return|true|false)(%s)$", exprName)).matcher(expr); + if (matcher.find()) { + return; + } + + if (expr.charAt(0) == '"' && expr.charAt(expr.length() - 1) == '"') { + return; + } + try { + new JSONObject(expr).toString(); + return; + } catch (Exception e) { + //ignore + } + + matcher = Pattern.compile(String.format(Locale.US, "(%s)\\.([^(]+)(?:\\(+([^()]*)\\))?$", exprName)).matcher(expr); + if (matcher.find()) { + String variable = matcher.group(1); + String member = matcher.group(2); + String arg_str = matcher.group(3); + if (localVars.get(variable) == null) { + extractObject(variable); + } + if (arg_str == null) { + return; + } + if (expr.charAt(expr.length() - 1) != ')') { + throw new Exception("last char not ')'"); + } + String argvals[]; + if (arg_str.length() != 0) { + String[] args = arg_str.split(","); + for (int a = 0; a < args.length; a++) { + interpretExpression(args[a], localVars, allowRecursion); + } + } + return; + } + + matcher = Pattern.compile(String.format(Locale.US, "(%s)\\[(.+)\\]$", exprName)).matcher(expr); + if (matcher.find()) { + Object val = localVars.get(matcher.group(1)); + interpretExpression(matcher.group(2), localVars, allowRecursion - 1); + return; + } + + for (int a = 0; a < operators.length; a++) { + String func = operators[a]; + matcher = Pattern.compile(String.format(Locale.US, "(.+?)%s(.+)", Pattern.quote(func))).matcher(expr); + if (!matcher.find()) { + continue; + } + boolean[] abort = new boolean[1]; + interpretStatement(matcher.group(1), localVars, abort, allowRecursion - 1); + if (abort[0]) { + throw new Exception(String.format("Premature left-side return of %s in %s", func, expr)); + } + interpretStatement(matcher.group(2), localVars, abort, allowRecursion - 1); + if (abort[0]) { + throw new Exception(String.format("Premature right-side return of %s in %s", func, expr)); + } + } + + matcher = Pattern.compile(String.format(Locale.US, "^(%s)\\(([a-zA-Z0-9_$,]*)\\)$", exprName)).matcher(expr); + if (matcher.find()) { + String fname = matcher.group(1); + extractFunction(fname); + } + throw new Exception(String.format("Unsupported JS expression %s", expr)); + } + + private void interpretStatement(String stmt, HashMap localVars, boolean[] abort, int allowRecursion) throws Exception { + if (allowRecursion < 0) { + throw new Exception("recursion limit reached"); + } + abort[0] = false; + stmt = stmt.trim(); + Matcher matcher = stmtVarPattern.matcher(stmt); + String expr; + if (matcher.find()) { + expr = stmt.substring(matcher.group(0).length()); + } else { + matcher = stmtReturnPattern.matcher(stmt); + if (matcher.find()) { + expr = stmt.substring(matcher.group(0).length()); + abort[0] = true; + } else { + expr = stmt; + } + } + interpretExpression(expr, localVars, allowRecursion); + } + + private HashMap extractObject(String objname) throws Exception { + HashMap obj = new HashMap<>(); + // ?P + Matcher matcher = Pattern.compile(String.format(Locale.US, "(?:var\\s+)?%s\\s*=\\s*\\{\\s*(([a-zA-Z$0-9]+\\s*:\\s*function\\(.*?\\)\\s*\\{.*?\\}(?:,\\s*)?)*)\\}\\s*;", Pattern.quote(objname))).matcher(jsCode); + String fields = null; + while (matcher.find()) { + String code = matcher.group(); + fields = matcher.group(2); + if (TextUtils.isEmpty(fields)) { + continue; + } + if (!codeLines.contains(code)) { + codeLines.add(matcher.group()); + } + break; + } + // ?P ?P ?P + matcher = Pattern.compile("([a-zA-Z$0-9]+)\\s*:\\s*function\\(([a-z,]+)\\)\\{([^}]+)\\}").matcher(fields); + while (matcher.find()) { + String[] argnames = matcher.group(2).split(","); + buildFunction(argnames, matcher.group(3)); + } + return obj; + } + + private void buildFunction(String[] argNames, String funcCode) throws Exception { + HashMap localVars = new HashMap<>(); + for (int a = 0; a < argNames.length; a++) { + localVars.put(argNames[a], ""); + } + String stmts[] = funcCode.split(";"); + boolean abort[] = new boolean[1]; + for (int a = 0; a < stmts.length; a++) { + interpretStatement(stmts[a], localVars, abort, 100); + if (abort[0]) { + return; + } + } + } + + private String extractFunction(String funcName) throws Exception { + try { + String quote = Pattern.quote(funcName); + Pattern funcPattern = Pattern.compile(String.format(Locale.US, "(?x)(?:function\\s+%s|[{;,]\\s*%s\\s*=\\s*function|var\\s+%s\\s*=\\s*function)\\s*\\(([^)]*)\\)\\s*\\{([^}]+)\\}", quote, quote, quote)); + Matcher matcher = funcPattern.matcher(jsCode); + if (matcher.find()) { + String group = matcher.group(); + if (!codeLines.contains(group)) { + codeLines.add(group + ";"); + } + buildFunction(matcher.group(1).split(","), matcher.group(2)); + } + } catch (Exception e) { + codeLines.clear(); + FileLog.e(e); + } + return TextUtils.join("", codeLines); + } + } + + public interface CallJavaResultInterface { + void jsCallFinished(String value); + } + + public class JavaScriptInterface { + private final CallJavaResultInterface callJavaResultInterface; + + public JavaScriptInterface(CallJavaResultInterface callJavaResult) { + callJavaResultInterface = callJavaResult; + } + + @JavascriptInterface + public void returnResultToJava(String value) { + callJavaResultInterface.jsCallFinished(value); + } + } + + protected String downloadUrlContent(AsyncTask parentTask, String url) { + boolean canRetry = true; + InputStream httpConnectionStream = null; + boolean done = false; + StringBuilder result = null; + URLConnection httpConnection = null; + try { + URL downloadUrl = new URL(url); + httpConnection = downloadUrl.openConnection(); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + httpConnection.setConnectTimeout(5000); + httpConnection.setReadTimeout(5000); + if (httpConnection instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection; + httpURLConnection.setInstanceFollowRedirects(true); + int status = httpURLConnection.getResponseCode(); + if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) { + String newUrl = httpURLConnection.getHeaderField("Location"); + String cookies = httpURLConnection.getHeaderField("Set-Cookie"); + downloadUrl = new URL(newUrl); + httpConnection = downloadUrl.openConnection(); + httpConnection.setRequestProperty("Cookie", cookies); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Encoding", "gzip, deflate"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + } + } + httpConnection.connect(); + httpConnectionStream = new GZIPInputStream(httpConnection.getInputStream()); + } catch (Throwable e) { + if (e instanceof SocketTimeoutException) { + if (ConnectionsManager.isNetworkOnline()) { + canRetry = false; + } + } else if (e instanceof UnknownHostException) { + canRetry = false; + } else if (e instanceof SocketException) { + if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) { + canRetry = false; + } + } else if (e instanceof FileNotFoundException) { + canRetry = false; + } + FileLog.e(e); + } + + if (canRetry) { + try { + if (httpConnection != null && httpConnection instanceof HttpURLConnection) { + int code = ((HttpURLConnection) httpConnection).getResponseCode(); + if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { + //canRetry = false; + } + } + } catch (Exception e) { + FileLog.e(e); + } + + if (httpConnectionStream != null) { + try { + byte[] data = new byte[1024 * 32]; + while (true) { + if (parentTask.isCancelled()) { + break; + } + try { + int read = httpConnectionStream.read(data); + if (read > 0) { + if (result == null) { + result = new StringBuilder(); + } + result.append(new String(data, 0, read, "UTF-8")); + } else if (read == -1) { + done = true; + break; + } else { + break; + } + } catch (Exception e) { + FileLog.e(e); + break; + } + } + } catch (Throwable e) { + FileLog.e(e); + } + } + + try { + if (httpConnectionStream != null) { + httpConnectionStream.close(); + } + } catch (Throwable e) { + FileLog.e(e); + } + } + return done ? result.toString() : null; + } + + private class YoutubeVideoTask extends AsyncTask { + + private String videoId; + private boolean canRetry = true; + private Semaphore semaphore = new Semaphore(0); + private String[] result = new String[1]; + private String sig; + + public YoutubeVideoTask(String vid) { + videoId = vid; + } + + @Override + protected String doInBackground(Void... voids) { + Matcher matcher; + String embedCode = downloadUrlContent(this, "https://www.youtube.com/embed/" + videoId); + if (isCancelled()) { + return null; + } + String params = "video_id=" + videoId + "&ps=default&gl=US&hl=en"; + try { + params += "&eurl=" + URLEncoder.encode("https://youtube.googleapis.com/v/" + videoId, "UTF-8"); + } catch (Exception e) { + FileLog.e(e); + } + if (embedCode != null) { + matcher = stsPattern.matcher(embedCode); + if (matcher.find()) { + params += "&sts=" + embedCode.substring(matcher.start() + 6, matcher.end()); + } else { + params += "&sts="; + } + } + + boolean encrypted = false; + String extra[] = new String[] {"", "&el=info", "&el=embedded", "&el=detailpage", "&el=vevo"}; + for (int i = 0; i < extra.length; i++) { + String videoInfo = downloadUrlContent(this, "https://www.youtube.com/get_video_info?" + params + extra[i]); + if (isCancelled()) { + return null; + } + boolean exists = false; + if (videoInfo != null) { + String args[] = videoInfo.split("&"); + for (int a = 0; a < args.length; a++) { + if (args[a].startsWith("dashmpd")) { + exists = true; + String args2[] = args[a].split("="); + if (args2.length == 2) { + try { + result[0] = URLDecoder.decode(args2[1], "UTF-8"); + } catch (Exception e) { + FileLog.e(e); + } + } + } else if (args[a].startsWith("use_cipher_signature")) { + String args2[] = args[a].split("="); + if (args2.length == 2) { + if (args2[1].toLowerCase().equals("true")) { + encrypted = true; + } + } + } + } + } + if (exists) { + break; + } + } + + if (result[0] != null && (encrypted || result[0].contains("/s/")) && embedCode != null) { + encrypted = true; + int index = result[0].indexOf("/s/"); + int index2 = result[0].indexOf('/', index + 10); + if (index != -1) { + if (index2 == -1) { + index2 = result[0].length(); + } + sig = result[0].substring(index, index2); + String jsUrl = null; + matcher = jsPattern.matcher(embedCode); + if (matcher.find()) { + try { + JSONTokener tokener = new JSONTokener(matcher.group(1)); + Object value = tokener.nextValue(); + if (value instanceof String) { + jsUrl = (String) value; + } + } catch (Exception e) { + FileLog.e(e); + } + } + + if (jsUrl != null) { + matcher = playerIdPattern.matcher(jsUrl); + String playerId; + if (matcher.find()) { + playerId = matcher.group(1) + matcher.group(2); + } else { + playerId = null; + } + String functionCode = null; + String functionName = null; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("youtubecode", Activity.MODE_PRIVATE); + if (playerId != null) { + functionCode = preferences.getString(playerId, null); + functionName = preferences.getString(playerId + "n", null); + } + if (functionCode == null) { + if (jsUrl.startsWith("//")) { + jsUrl = "https:" + jsUrl; + } else if (jsUrl.startsWith("/")) { + jsUrl = "https://www.youtube.com" + jsUrl; + } + String jsCode = downloadUrlContent(this, jsUrl); + if (isCancelled()) { + return null; + } + if (jsCode != null) { + matcher = sigPattern.matcher(jsCode); + if (matcher.find()) { + functionName = matcher.group(1); + } else { + matcher = sigPattern2.matcher(jsCode); + if (matcher.find()) { + functionName = matcher.group(1); + } + } + if (functionName != null) { + try { + JSExtractor extractor = new JSExtractor(jsCode); + functionCode = extractor.extractFunction(functionName); + if (!TextUtils.isEmpty(functionCode) && playerId != null) { + preferences.edit().putString(playerId, functionCode).putString(playerId + "n", functionName).commit(); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + } + if (!TextUtils.isEmpty(functionCode)) { + if (Build.VERSION.SDK_INT >= 21) { + functionCode += functionName + "('" + sig.substring(3) + "');"; + } else { + functionCode += "window." + interfaceName + ".returnResultToJava(" + functionName + "('" + sig.substring(3) + "'));"; + } + final String functionCodeFinal = functionCode; + try { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT >= 21) { + webView.evaluateJavascript(functionCodeFinal, new ValueCallback() { + @Override + public void onReceiveValue(String value) { + result[0] = result[0].replace(sig, "/signature/" + value.substring(1, value.length() - 1)); + semaphore.release(); + } + }); + } else { + try { + String javascript = ""; + byte[] data = javascript.getBytes("UTF-8"); + final String base64 = Base64.encodeToString(data, Base64.DEFAULT); + webView.loadUrl("data:text/html;charset=utf-8;base64," + base64); + } catch (Exception e) { + FileLog.e(e); + } + } + } + }); + semaphore.acquire(); + encrypted = false; + } catch (Exception e) { + FileLog.e(e); + } + } + } + } + } + return isCancelled() || encrypted ? null : result[0]; + } + + private void onInterfaceResult(String value) { + result[0] = result[0].replace(sig, "/signature/" + value); + semaphore.release(); + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoType = "dash"; + playVideoUrl = result; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + + private class VimeoVideoTask extends AsyncTask { + + private String videoId; + private boolean canRetry = true; + private String[] results = new String[2]; + + public VimeoVideoTask(String vid) { + videoId = vid; + } + + protected String doInBackground(Void... voids) { + String playerCode = downloadUrlContent(this, String.format(Locale.US, "https://player.vimeo.com/video/%s/config", videoId)); + if (isCancelled()) { + return null; + } + try { + JSONObject json = new JSONObject(playerCode); + JSONObject files = json.getJSONObject("request").getJSONObject("files"); + if (files.has("hls")) { + JSONObject hls = files.getJSONObject("hls"); + try { + results[0] = hls.getString("url"); + } catch (Exception e) { + String defaultCdn = hls.getString("default_cdn"); + JSONObject cdns = hls.getJSONObject("cdns"); + hls = cdns.getJSONObject(defaultCdn); + results[0] = hls.getString("url"); + } + results[1] = "hls"; + } else if (files.has("progressive")) { + results[1] = "other"; + JSONArray progressive = files.getJSONArray("progressive"); + for (int i = 0; i < progressive.length(); i++) { + JSONObject format = progressive.getJSONObject(i); + results[0] = format.getString("url"); + break; + } + } + } catch (Exception e) { + FileLog.e(e); + } + return isCancelled() ? null : results[0]; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoUrl = result; + playVideoType = results[1]; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + + private class AparatVideoTask extends AsyncTask { + + private String videoId; + private boolean canRetry = true; + private String[] results = new String[2]; + + public AparatVideoTask(String vid) { + videoId = vid; + } + + protected String doInBackground(Void... voids) { + String playerCode = downloadUrlContent(this, String.format(Locale.US, "http://www.aparat.com/video/video/embed/vt/frame/showvideo/yes/videohash/%s", videoId)); + if (isCancelled()) { + return null; + } + try { + Matcher filelist = aparatFileListPattern.matcher(playerCode); + if (filelist.find()) { + String jsonCode = filelist.group(1); + JSONArray json = new JSONArray(jsonCode); + for (int a = 0; a < json.length(); a++) { + JSONArray array = json.getJSONArray(a); + if (array.length() == 0) { + continue; + } + JSONObject object = array.getJSONObject(0); + if (!object.has("file")) { + continue; + } + results[0] = object.getString("file"); + results[1] = "other"; + } + } + } catch (Exception e) { + FileLog.e(e); + } + return isCancelled() ? null : results[0]; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoUrl = result; + playVideoType = results[1]; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + + private class CoubVideoTask extends AsyncTask { + + private String videoId; + private boolean canRetry = true; + private String[] results = new String[4]; + + public CoubVideoTask(String vid) { + videoId = vid; + } + + protected String doInBackground(Void... voids) { + String playerCode = downloadUrlContent(this, String.format(Locale.US, "https://coub.com/api/v2/coubs/%s.json", videoId)); + if (isCancelled()) { + return null; + } + try { + JSONObject json = new JSONObject(playerCode); + String video = json.getString("file"); + String audio = json.getString("audio_file_url"); + if (video != null && audio != null) { + results[0] = video; + results[1] = "other"; + results[2] = audio; + results[3] = "other"; + } + } catch (Exception e) { + FileLog.e(e); + } + return isCancelled() ? null : results[0]; + } + + @Override + protected void onPostExecute(String result) { + if (result != null) { + initied = true; + playVideoUrl = result; + playVideoType = results[1]; + playAudioUrl = results[2]; + playAudioType = results[3]; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, true); + controlsView.show(true, true); + } else if (!isCancelled()) { + onInitFailed(); + } + } + } + + private TextureView.SurfaceTextureListener surfaceTextureListener = new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + if (changingTextureView) { + if (switchingInlineMode) { + waitingForFirstTextureUpload = 2; + } + textureView.setSurfaceTexture(surface); + textureView.setVisibility(VISIBLE); + changingTextureView = false; + return false; + } + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { + if (waitingForFirstTextureUpload == 1) { + changedTextureView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + changedTextureView.getViewTreeObserver().removeOnPreDrawListener(this); + if (textureImageView != null) { + textureImageView.setVisibility(INVISIBLE); + textureImageView.setImageDrawable(null); + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + } + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + delegate.onInlineSurfaceTextureReady(); + } + }); + waitingForFirstTextureUpload = 0; + return true; + } + }); + changedTextureView.invalidate(); + } + } + }; + + private Runnable switchToInlineRunnable = new Runnable() { + @Override + public void run() { + switchingInlineMode = false; + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + + changingTextureView = true; + if (textureImageView != null) { + try { + currentBitmap = Bitmaps.createBitmap(textureView.getWidth(), textureView.getHeight(), Bitmap.Config.ARGB_8888); + textureView.getBitmap(currentBitmap); + } catch (Throwable e) { + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + FileLog.e(e); + } + + if (currentBitmap != null) { + textureImageView.setVisibility(VISIBLE); + textureImageView.setImageBitmap(currentBitmap); + } else { + textureImageView.setImageDrawable(null); + } + } + + isInline = true; + updatePlayButton(); + updateShareButton(); + updateFullscreenButton(); + updateInlineButton(); + + ViewGroup viewGroup = (ViewGroup) controlsView.getParent(); + if (viewGroup != null) { + viewGroup.removeView(controlsView); + } + changedTextureView = delegate.onSwitchInlineMode(controlsView, isInline, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), allowInlineAnimation); + changedTextureView.setVisibility(INVISIBLE); + ViewGroup parent = (ViewGroup) textureView.getParent(); + if (parent != null) { + parent.removeView(textureView); + } + controlsView.show(false, false); + } + }; + + private class ControlsView extends FrameLayout { + + private ImageReceiver imageReceiver; + private boolean progressPressed; + private TextPaint textPaint; + private StaticLayout durationLayout; + private StaticLayout progressLayout; + private Paint progressPaint; + private Paint progressInnerPaint; + private Paint progressBufferedPaint; + private int durationWidth; + private int duration; + private int progress; + private int bufferedPosition; + private int bufferedPercentage; + private boolean isVisible = true; + private AnimatorSet currentAnimation; + private int lastProgressX; + private int currentProgressX; + private Runnable hideRunnable = new Runnable() { + @Override + public void run() { + show(false, true); + } + }; + + public ControlsView(Context context) { + super(context); + setWillNotDraw(false); + + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setColor(0xffffffff); + textPaint.setTextSize(AndroidUtilities.dp(12)); + + progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressPaint.setColor(0xff19a7e8); + + progressInnerPaint = new Paint(); + progressInnerPaint.setColor(0xff959197); + + progressBufferedPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + progressBufferedPaint.setColor(0xffffffff); + + imageReceiver = new ImageReceiver(this); + } + + public void setDuration(int value) { + if (duration == value || value < 0) { + return; + } + duration = value; + durationLayout = new StaticLayout(String.format(Locale.US, "%d:%02d", duration / 60, duration % 60), textPaint, AndroidUtilities.dp(1000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + if (durationLayout.getLineCount() > 0) { + durationWidth = (int) Math.ceil(durationLayout.getLineWidth(0)); + } + invalidate(); + } + + public void setBufferedProgress(int position, int percentage) { + bufferedPosition = position; + bufferedPercentage = percentage; + invalidate(); + } + + public void setProgress(int value) { + if (progressPressed || value < 0) { + return; + } + progress = value; + progressLayout = new StaticLayout(String.format(Locale.US, "%d:%02d", progress / 60, progress % 60), textPaint, AndroidUtilities.dp(1000), Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); + invalidate(); + } + + public void show(boolean value, boolean animated) { + if (isVisible == value) { + return; + } + isVisible = value; + if (currentAnimation != null) { + currentAnimation.cancel(); + } + if (isVisible) { + if (animated) { + currentAnimation = new AnimatorSet(); + currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 1.0f)); + currentAnimation.setDuration(150); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + currentAnimation = null; + } + }); + currentAnimation.start(); + } else { + setAlpha(1.0f); + } + } else { + if (animated) { + currentAnimation = new AnimatorSet(); + currentAnimation.playTogether(ObjectAnimator.ofFloat(this, "alpha", 0.0f)); + currentAnimation.setDuration(150); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + currentAnimation = null; + } + }); + currentAnimation.start(); + } else { + setAlpha(0.0f); + } + } + checkNeedHide(); + } + + private void checkNeedHide() { + AndroidUtilities.cancelRunOnUIThread(hideRunnable); + if (isVisible && videoPlayer.isPlaying()) { + AndroidUtilities.runOnUIThread(hideRunnable, 3000); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + if (!isVisible) { + show(true, true); + return true; + } + onTouchEvent(ev); + return progressPressed; + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + super.requestDisallowInterceptTouchEvent(disallowIntercept); + checkNeedHide(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + int progressLineX; + int progressLineEndX; + int progressY; + if (inFullscreen) { + progressLineX = AndroidUtilities.dp(18 + 18) + durationWidth; + progressLineEndX = getMeasuredWidth() - AndroidUtilities.dp(58 + 18) - durationWidth; + progressY = getMeasuredHeight() - AndroidUtilities.dp(7 + 21); + } else { + progressLineX = 0; + progressLineEndX = getMeasuredWidth(); + progressY = getMeasuredHeight() - AndroidUtilities.dp(2 + 10); + } + + int progressX = progressLineX + (duration != 0 ? (int) ((progressLineEndX - progressLineX) * (progress / (float) duration)) : 0); + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (isVisible && !isInline) { + if (duration != 0) { + int x = (int) event.getX(); + int y = (int) event.getY(); + if (x >= progressX - AndroidUtilities.dp(10) && x <= progressX + AndroidUtilities.dp(10) && y >= progressY - AndroidUtilities.dp(10) && y <= progressY + AndroidUtilities.dp(10)) { + progressPressed = true; + lastProgressX = x; + currentProgressX = progressX; + getParent().requestDisallowInterceptTouchEvent(true); + invalidate(); + } + } + } else { + show(true, true); + } + AndroidUtilities.cancelRunOnUIThread(hideRunnable); + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (initied && videoPlayer.isPlaying()) { + AndroidUtilities.runOnUIThread(hideRunnable, 3000); + } + if (progressPressed) { + progressPressed = false; + if (initied) { + progress = (int) (duration * ((float) (currentProgressX - progressLineX) / (progressLineEndX - progressLineX))); + videoPlayer.seekTo((long) progress * 1000); + } + } + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (progressPressed) { + int x = (int) event.getX(); + currentProgressX -= (lastProgressX - x); + lastProgressX = x; + if (currentProgressX < progressLineX) { + currentProgressX = progressLineX; + } else if (currentProgressX > progressLineEndX) { + currentProgressX = progressLineEndX; + } + setProgress((int) (duration * 1000 * ((float) (currentProgressX - progressLineX) / (progressLineEndX - progressLineX)))); + invalidate(); + } + } + super.onTouchEvent(event); + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + if (drawImage) { + if (firstFrameRendered && currentAlpha != 0) { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + currentAlpha -= dt / 150.0f; + if (currentAlpha < 0) { + currentAlpha = 0.0f; + } + invalidate(); + } + imageReceiver.setAlpha(currentAlpha); + imageReceiver.draw(canvas); + } + if (videoPlayer.isPlayerPrepared()) { + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + if (!isInline) { + if (durationLayout != null) { + canvas.save(); + canvas.translate(width - AndroidUtilities.dp(58) - durationWidth, height - AndroidUtilities.dp(29 + (inFullscreen ? 6 : 10))); + durationLayout.draw(canvas); + canvas.restore(); + } + + if (progressLayout != null) { + canvas.save(); + canvas.translate(AndroidUtilities.dp(18), height - AndroidUtilities.dp(29 + (inFullscreen ? 6 : 10))); + progressLayout.draw(canvas); + canvas.restore(); + } + } + + if (duration != 0) { + int progressLineY; + int progressLineX; + int progressLineEndX; + int cy; + + if (isInline) { + progressLineY = height - AndroidUtilities.dp(3); + progressLineX = 0; + progressLineEndX = width; + cy = height - AndroidUtilities.dp(7); + } else if (inFullscreen) { + progressLineY = height - AndroidUtilities.dp(26 + 3); + progressLineX = AndroidUtilities.dp(18 + 18) + durationWidth; + progressLineEndX = width - AndroidUtilities.dp(58 + 18) - durationWidth; + cy = height - AndroidUtilities.dp(7 + 21); + } else { + progressLineY = height - AndroidUtilities.dp(10 + 3); + progressLineX = 0; + progressLineEndX = width; + cy = height - AndroidUtilities.dp(2 + 10); + } + if (inFullscreen) { + canvas.drawRect(progressLineX, progressLineY, progressLineEndX, progressLineY + AndroidUtilities.dp(3), progressInnerPaint); + } + int progressX; + if (progressPressed) { + progressX = currentProgressX; + } else { + progressX = progressLineX + (int) ((progressLineEndX - progressLineX) * (progress / (float) duration)); + } + if (bufferedPercentage != 0 && duration != 0) { + int pxPerS = (progressLineEndX - progressLineX) / duration; + int start = progressLineX + pxPerS * bufferedPosition; + int additional = 0; + if (progressX < start) { + additional = start - progressX; + } + canvas.drawRect(start - additional, progressLineY, start + (progressLineEndX - start) * bufferedPercentage / 100.0f, progressLineY + AndroidUtilities.dp(3), inFullscreen ? progressBufferedPaint : progressInnerPaint); + } + canvas.drawRect(progressLineX, progressLineY, progressX, progressLineY + AndroidUtilities.dp(3), progressPaint); + if (!isInline) { + canvas.drawCircle(progressX, cy, AndroidUtilities.dp(progressPressed ? 7 : 5), progressPaint); + } + } + } + } + } + + @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) + public WebPlayerView(Context context, boolean allowInline, boolean allowShare, WebPlayerViewDelegate webPlayerViewDelegate) { + super(context); + setWillNotDraw(false); + delegate = webPlayerViewDelegate; + + backgroundPaint.setColor(0xff000000); + + aspectRatioFrameLayout = new AspectRatioFrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (textureViewContainer != null) { + ViewGroup.LayoutParams layoutParams = textureView.getLayoutParams(); + layoutParams.width = getMeasuredWidth(); + layoutParams.height = getMeasuredHeight(); + + if (textureImageView != null) { + layoutParams = textureImageView.getLayoutParams(); + layoutParams.width = getMeasuredWidth(); + layoutParams.height = getMeasuredHeight(); + } + } + } + }; + addView(aspectRatioFrameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + interfaceName = "JavaScriptInterface"; + webView = new WebView(context); + webView.addJavascriptInterface(new JavaScriptInterface(new CallJavaResultInterface() { + @Override + public void jsCallFinished(String value) { + if (currentTask != null && !currentTask.isCancelled()) { + if (currentTask instanceof YoutubeVideoTask) { + ((YoutubeVideoTask) currentTask).onInterfaceResult(value); + } + } + } + }), interfaceName); + final WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setDefaultTextEncodingName("utf-8"); + + textureViewContainer = delegate.getTextureViewContainer(); + + textureView = new TextureView(context); + textureView.setPivotX(0); + textureView.setPivotY(0); + if (textureViewContainer != null) { + textureViewContainer.addView(textureView); + } else { + aspectRatioFrameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + } + + if (allowInlineAnimation && textureViewContainer != null) { + textureImageView = new ImageView(context); + textureImageView.setBackgroundColor(0xffff0000); + textureImageView.setPivotX(0); + textureImageView.setPivotY(0); + textureImageView.setVisibility(INVISIBLE); + textureViewContainer.addView(textureImageView); + } + + videoPlayer = new VideoPlayer(); + videoPlayer.setDelegate(this); + videoPlayer.setTextureView(textureView); + + controlsView = new ControlsView(context); + if (textureViewContainer != null) { + textureViewContainer.addView(controlsView); + } else { + addView(controlsView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + } + + progressView = new RadialProgressView(context); + progressView.setProgressColor(0xffffffff); + addView(progressView, LayoutHelper.createFrame(48, 48, Gravity.CENTER)); + + fullscreenButton = new ImageView(context); + fullscreenButton.setScaleType(ImageView.ScaleType.CENTER); + controlsView.addView(fullscreenButton, LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 5)); + fullscreenButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (!initied || changingTextureView || switchingInlineMode || !firstFrameRendered) { + return; + } + inFullscreen = !inFullscreen; + updateFullscreenState(true); + } + }); + + playButton = new ImageView(context); + playButton.setScaleType(ImageView.ScaleType.CENTER); + controlsView.addView(playButton, LayoutHelper.createFrame(48, 48, Gravity.CENTER)); + playButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (!initied || playVideoUrl == null) { + return; + } + if (!videoPlayer.isPlayerPrepared()) { + preparePlayer(); + } + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + } else { + isCompleted = false; + videoPlayer.play(); + } + updatePlayButton(); + } + }); + + if (allowInline) { + inlineButton = new ImageView(context); + inlineButton.setScaleType(ImageView.ScaleType.CENTER); + controlsView.addView(inlineButton, LayoutHelper.createFrame(56, 48, Gravity.RIGHT | Gravity.TOP)); + inlineButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (textureView == null || !delegate.checkInlinePermissons() || changingTextureView || switchingInlineMode || !firstFrameRendered) { + return; + } + switchingInlineMode = true; + if (!isInline) { + inFullscreen = false; + delegate.prepareToSwitchInlineMode(true, switchToInlineRunnable, aspectRatioFrameLayout.getAspectRatio(), allowInlineAnimation); + } else { + ViewGroup parent = (ViewGroup) aspectRatioFrameLayout.getParent(); + if (parent != WebPlayerView.this) { + if (parent != null) { + parent.removeView(aspectRatioFrameLayout); + } + addView(aspectRatioFrameLayout, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + aspectRatioFrameLayout.measure(MeasureSpec.makeMeasureSpec(WebPlayerView.this.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(WebPlayerView.this.getMeasuredHeight() - AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); + } + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + changingTextureView = true; + + isInline = false; + updatePlayButton(); + updateShareButton(); + updateFullscreenButton(); + updateInlineButton(); + + textureView.setVisibility(INVISIBLE); + if (textureViewContainer != null) { + textureViewContainer.addView(textureView); + } else { + aspectRatioFrameLayout.addView(textureView); + } + + parent = (ViewGroup) controlsView.getParent(); + if (parent != WebPlayerView.this) { + if (parent != null) { + parent.removeView(controlsView); + } + if (textureViewContainer != null) { + textureViewContainer.addView(controlsView); + } else { + addView(controlsView, 1); + } + } + + controlsView.show(false, false); + delegate.prepareToSwitchInlineMode(false, null, aspectRatioFrameLayout.getAspectRatio(), allowInlineAnimation); + } + } + }); + } + + if (allowShare) { + shareButton = new ImageView(context); + shareButton.setScaleType(ImageView.ScaleType.CENTER); + shareButton.setImageResource(R.drawable.ic_share_video); + controlsView.addView(shareButton, LayoutHelper.createFrame(56, 48, Gravity.RIGHT | Gravity.TOP)); + shareButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (delegate != null) { + delegate.onSharePressed(); + } + } + }); + } + + updatePlayButton(); + updateFullscreenButton(); + updateInlineButton(); + updateShareButton(); + } + + private void onInitFailed() { + if (controlsView.getParent() != this) { + controlsView.setVisibility(GONE); + } + delegate.onInitFailed(); + } + + public void updateTextureImageView() { + if (textureImageView == null) { + return; + } + try { + currentBitmap = Bitmaps.createBitmap(textureView.getWidth(), textureView.getHeight(), Bitmap.Config.ARGB_8888); + changedTextureView.getBitmap(currentBitmap); + } catch (Throwable e) { + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + FileLog.e(e); + } + if (currentBitmap != null) { + textureImageView.setVisibility(VISIBLE); + textureImageView.setImageBitmap(currentBitmap); + } else { + textureImageView.setImageDrawable(null); + } + } + + @Override + public void onStateChanged(boolean playWhenReady, int playbackState) { + if (playbackState != ExoPlayer.STATE_BUFFERING) { + if (videoPlayer.getDuration() != C.TIME_UNSET) { + controlsView.setDuration((int) (videoPlayer.getDuration() / 1000)); + } else { + controlsView.setDuration(0); + } + } + if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE && videoPlayer.isPlaying()) { + delegate.onPlayStateChanged(this, true); + } else { + delegate.onPlayStateChanged(this, false); + } + if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { + updatePlayButton(); + } else { + if (playbackState == ExoPlayer.STATE_ENDED) { + isCompleted = true; + videoPlayer.pause(); + videoPlayer.seekTo(0); + updatePlayButton(); + controlsView.show(true, true); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(10), backgroundPaint); + } + + @Override + public void onError(Exception e) { + FileLog.e(e); + } + + @Override + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + if (aspectRatioFrameLayout != null) { + if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { + int temp = width; + width = height; + height = temp; + } + float ratio = height == 0 ? 1 : (width * pixelWidthHeightRatio) / height; + aspectRatioFrameLayout.setAspectRatio(ratio, unappliedRotationDegrees); + if (inFullscreen) { + delegate.onVideoSizeChanged(ratio, unappliedRotationDegrees); + } + } + } + + @Override + public void onRenderedFirstFrame() { + firstFrameRendered = true; + lastUpdateTime = System.currentTimeMillis(); + controlsView.invalidate(); + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + if (changingTextureView) { + changingTextureView = false; + if (inFullscreen || isInline) { + if (isInline) { + waitingForFirstTextureUpload = 1; + } + changedTextureView.setSurfaceTexture(surfaceTexture); + changedTextureView.setSurfaceTextureListener(surfaceTextureListener); + changedTextureView.setVisibility(VISIBLE); + return true; + } + } + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + if (waitingForFirstTextureUpload == 2) { + if (textureImageView != null) { + textureImageView.setVisibility(INVISIBLE); + textureImageView.setImageDrawable(null); + if (currentBitmap != null) { + currentBitmap.recycle(); + currentBitmap = null; + } + } + switchingInlineMode = false; + delegate.onSwitchInlineMode(controlsView, false, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), allowInlineAnimation); + waitingForFirstTextureUpload = 0; + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + int x = ((r - l) - aspectRatioFrameLayout.getMeasuredWidth()) / 2; + int y = ((b - t - AndroidUtilities.dp(10)) - aspectRatioFrameLayout.getMeasuredHeight()) / 2; + aspectRatioFrameLayout.layout(x, y, x + aspectRatioFrameLayout.getMeasuredWidth(), y + aspectRatioFrameLayout.getMeasuredHeight()); + if (controlsView.getParent() == this) { + controlsView.layout(0, 0, controlsView.getMeasuredWidth(), controlsView.getMeasuredHeight()); + } + x = ((r - l) - progressView.getMeasuredWidth()) / 2; + y = ((b - t) - progressView.getMeasuredHeight()) / 2; + progressView.layout(x, y, x + progressView.getMeasuredWidth(), y + progressView.getMeasuredHeight()); + controlsView.imageReceiver.setImageCoords(0, 0, getMeasuredWidth(), getMeasuredHeight() - AndroidUtilities.dp(10)); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + aspectRatioFrameLayout.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - AndroidUtilities.dp(10), MeasureSpec.EXACTLY)); + if (controlsView.getParent() == this) { + controlsView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } + progressView.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(44), MeasureSpec.EXACTLY)); + setMeasuredDimension(width, height); + } + + private void updatePlayButton() { + controlsView.checkNeedHide(); + AndroidUtilities.cancelRunOnUIThread(progressRunnable); + if (!videoPlayer.isPlaying()) { + if (isCompleted) { + playButton.setImageResource(isInline ? R.drawable.ic_againinline : R.drawable.ic_again); + } else { + playButton.setImageResource(isInline ? R.drawable.ic_playinline : R.drawable.ic_play); + } + } else { + playButton.setImageResource(isInline ? R.drawable.ic_pauseinline : R.drawable.ic_pause); + AndroidUtilities.runOnUIThread(progressRunnable, 500); + checkAudioFocus(); + } + } + + private void checkAudioFocus() { + if (!hasAudioFocus) { + AudioManager audioManager = (AudioManager) ApplicationLoader.applicationContext.getSystemService(Context.AUDIO_SERVICE); + hasAudioFocus = true; + if (audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { + audioFocus = 2; + } + } + } + + @Override + public void onAudioFocusChange(int focusChange) { + if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + updatePlayButton(); + } + hasAudioFocus = false; + audioFocus = AUDIO_NO_FOCUS_NO_DUCK; + } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { + audioFocus = AUDIO_FOCUSED; + if (resumeAudioOnFocusGain) { + resumeAudioOnFocusGain = false; + videoPlayer.play(); + } + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { + audioFocus = AUDIO_NO_FOCUS_CAN_DUCK; + } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { + audioFocus = AUDIO_NO_FOCUS_NO_DUCK; + if (videoPlayer.isPlaying()) { + resumeAudioOnFocusGain = true; + videoPlayer.pause(); + updatePlayButton(); + } + } + } + + private void updateFullscreenButton() { + if (!videoPlayer.isPlayerPrepared() || isInline) { + fullscreenButton.setVisibility(GONE); + return; + } + fullscreenButton.setVisibility(VISIBLE); + if (!inFullscreen) { + fullscreenButton.setImageResource(R.drawable.ic_gofullscreen); + fullscreenButton.setLayoutParams(LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 5)); + } else { + fullscreenButton.setImageResource(R.drawable.ic_outfullscreen); + fullscreenButton.setLayoutParams(LayoutHelper.createFrame(56, 56, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 1)); + } + } + + private void updateShareButton() { + if (shareButton == null) { + return; + } + shareButton.setVisibility(isInline || !videoPlayer.isPlayerPrepared() ? GONE : VISIBLE); + } + + private View getControlView() { + return controlsView; + } + + private View getProgressView() { + return progressView; + } + + private void updateInlineButton() { + if (inlineButton == null) { + return; + } + inlineButton.setImageResource(isInline ? R.drawable.ic_goinline : R.drawable.ic_outinline); + inlineButton.setVisibility(videoPlayer.isPlayerPrepared() ? VISIBLE : GONE); + if (isInline) { + inlineButton.setLayoutParams(LayoutHelper.createFrame(40, 40, Gravity.RIGHT | Gravity.TOP)); + } else { + inlineButton.setLayoutParams(LayoutHelper.createFrame(56, 50, Gravity.RIGHT | Gravity.TOP)); + } + } + + private void preparePlayer() { + if (playVideoUrl == null) { + return; + } + if (playVideoUrl != null && playAudioUrl != null) { + videoPlayer.preparePlayerLoop(Uri.parse(playVideoUrl), playVideoType, Uri.parse(playAudioUrl), playAudioType); + } else { + videoPlayer.preparePlayer(Uri.parse(playVideoUrl), playVideoType); + } + videoPlayer.setPlayWhenReady(isAutoplay); + + isLoading = false; + + if (videoPlayer.getDuration() != C.TIME_UNSET) { + controlsView.setDuration((int) (videoPlayer.getDuration() / 1000)); + } else { + controlsView.setDuration(0); + } + updateFullscreenButton(); + updateShareButton(); + updateInlineButton(); + controlsView.invalidate(); + if (seekToTime != -1) { + videoPlayer.seekTo(seekToTime * 1000); + } + } + + public void pause() { + videoPlayer.pause(); + updatePlayButton(); + controlsView.show(true, true); + } + + private void updateFullscreenState(boolean byButton) { + if (textureView == null) { + return; + } + updateFullscreenButton(); + if (textureViewContainer == null) { + changingTextureView = true; + if (!inFullscreen) { + if (textureViewContainer != null) { + textureViewContainer.addView(textureView); + } else { + aspectRatioFrameLayout.addView(textureView); + } + } + if (inFullscreen) { + ViewGroup viewGroup = (ViewGroup) controlsView.getParent(); + if (viewGroup != null) { + viewGroup.removeView(controlsView); + } + } else { + ViewGroup parent = (ViewGroup) controlsView.getParent(); + if (parent != this) { + if (parent != null) { + parent.removeView(controlsView); + } + if (textureViewContainer != null) { + textureViewContainer.addView(controlsView); + } else { + addView(controlsView, 1); + } + } + } + changedTextureView = delegate.onSwitchToFullscreen(controlsView, inFullscreen, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), byButton); + changedTextureView.setVisibility(INVISIBLE); + if (inFullscreen && changedTextureView != null) { + ViewGroup parent = (ViewGroup) textureView.getParent(); + if (parent != null) { + parent.removeView(textureView); + } + } + controlsView.checkNeedHide(); + } else { + if (inFullscreen) { + ViewGroup viewGroup = (ViewGroup) aspectRatioFrameLayout.getParent(); + if (viewGroup != null) { + viewGroup.removeView(aspectRatioFrameLayout); + } + } else { + ViewGroup parent = (ViewGroup) aspectRatioFrameLayout.getParent(); + if (parent != this) { + if (parent != null) { + parent.removeView(aspectRatioFrameLayout); + } + addView(aspectRatioFrameLayout, 0); + } + } + delegate.onSwitchToFullscreen(controlsView, inFullscreen, aspectRatioFrameLayout.getAspectRatio(), aspectRatioFrameLayout.getVideoRotation(), byButton); + } + } + + public void exitFullscreen() { + if (!inFullscreen) { + return; + } + inFullscreen = false; + updateInlineButton(); + updateFullscreenState(false); + } + + public boolean isInitied() { + return initied; + } + + public boolean isInline() { + return isInline || switchingInlineMode; + } + + public void enterFullscreen() { + if (inFullscreen) { + return; + } + inFullscreen = true; + updateInlineButton(); + updateFullscreenState(false); + } + + public boolean isInFullscreen() { + return inFullscreen; + } + + public boolean loadVideo(String url, TLRPC.Photo thumb, String originalUrl, boolean autoplay) { + String youtubeId = null; + String vimeoId = null; + String coubId = null; + String aparatId = null; + String mp4File = null; + seekToTime = -1; + if (url != null) { + if (url.endsWith(".mp4")) { + mp4File = url; + } else { + try { + if (originalUrl != null) { + try { + Uri uri = Uri.parse(originalUrl); + String t = uri.getQueryParameter("t"); + if (t != null) { + if (t.contains("m")) { + String args[] = t.split("m"); + seekToTime = Utilities.parseInt(args[0]) * 60 + Utilities.parseInt(args[1]); + } else { + seekToTime = Utilities.parseInt(t); + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + Matcher matcher = youtubeIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(1); + } + if (id != null) { + youtubeId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + if (youtubeId == null) { + try { + Matcher matcher = vimeoIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(3); + } + if (id != null) { + vimeoId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + } + if (youtubeId == null && vimeoId == null) { + try { + Matcher matcher = aparatIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(1); + } + if (id != null) { + aparatId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + } + /*if (youtubeId == null && vimeoId == null) { + try { + Matcher matcher = coubIdRegex.matcher(url); + String id = null; + if (matcher.find()) { + id = matcher.group(1); + } + if (id != null) { + coubId = id; + } + } catch (Exception e) { + FileLog.e(e); + } + }*/ + } + } + + initied = false; + isCompleted = false; + isAutoplay = autoplay; + playVideoUrl = null; + playAudioUrl = null; + destroy(); + firstFrameRendered = false; + currentAlpha = 1.0f; + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; + } + updateFullscreenButton(); + updateShareButton(); + updateInlineButton(); + updatePlayButton(); + if (thumb != null) { + TLRPC.PhotoSize photoSize = FileLoader.getClosestPhotoSizeWithSize(thumb.sizes, 80, true); + if (photoSize != null) { + controlsView.imageReceiver.setImage(null, null, thumb != null ? photoSize.location : null, thumb != null ? "80_80_b" : null, 0, null, true); + drawImage = true; + } + } else { + drawImage = false; + } + + if (progressAnimation != null) { + progressAnimation.cancel(); + progressAnimation = null; + } + isLoading = true; + controlsView.setProgress(0); + if (mp4File != null) { + initied = true; + playVideoUrl = mp4File; + playVideoType = "other"; + if (isAutoplay) { + preparePlayer(); + } + showProgress(false, false); + controlsView.show(true, true); + } else { + if (youtubeId != null) { + YoutubeVideoTask task = new YoutubeVideoTask(youtubeId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + } else if (vimeoId != null) { + VimeoVideoTask task = new VimeoVideoTask(vimeoId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + } else if (coubId != null) { + CoubVideoTask task = new CoubVideoTask(coubId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + } else if (aparatId != null) { + AparatVideoTask task = new AparatVideoTask(aparatId); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); + currentTask = task; + } + + controlsView.show(false, false); + showProgress(true, false); + } + if (youtubeId != null || vimeoId != null || coubId != null || aparatId != null || mp4File != null) { + return true; + } + controlsView.setVisibility(GONE); + return false; + } + + public View getAspectRatioView() { + return aspectRatioFrameLayout; + } + + public TextureView getTextureView() { + return textureView; + } + + public ImageView getTextureImageView() { + return textureImageView; + } + + public View getControlsView() { + return controlsView; + } + + public void destroy() { + videoPlayer.releasePlayer(); + if (currentTask != null) { + currentTask.cancel(true); + currentTask = null; + } + webView.stopLoading(); + } + + private void showProgress(boolean show, boolean animated) { + if (animated) { + if (progressAnimation != null) { + progressAnimation.cancel(); + } + progressAnimation = new AnimatorSet(); + progressAnimation.playTogether(ObjectAnimator.ofFloat(progressView, "alpha", show ? 1.0f : 0.0f)); + progressAnimation.setDuration(150); + progressAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + progressAnimation = null; + } + }); + progressAnimation.start(); + } else { + progressView.setAlpha(show ? 1.0f : 0.0f); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CallSwipeView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CallSwipeView.java new file mode 100755 index 00000000000..09a7f4ab8ee --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CallSwipeView.java @@ -0,0 +1,240 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.ui.Components.voip; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.FileLog; + +import java.util.ArrayList; + +public class CallSwipeView extends View { + + private Paint arrowsPaint, pullBgPaint; + private int[] arrowAlphas = {64, 64, 64}; + private View viewToDrag; + private boolean dragging = false, dragFromRight; + private float dragStartX; + private RectF tmpRect = new RectF(); + private Listener listener; + private Path arrow = new Path(); + private AnimatorSet arrowAnim; + private boolean animatingArrows =false; + + public CallSwipeView(Context context) { + super(context); + init(); + } + + public CallSwipeView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public CallSwipeView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + arrowsPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + arrowsPaint.setColor(0xFFFFFFFF); + arrowsPaint.setStyle(Paint.Style.STROKE); + arrowsPaint.setStrokeWidth(AndroidUtilities.dp(2.5f)); + pullBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + ArrayList anims = new ArrayList<>(); + for (int i = 0; i < arrowAlphas.length; i++) { + ArrowAnimWrapper aaw = new ArrowAnimWrapper(i); + ObjectAnimator anim = ObjectAnimator.ofInt(aaw, "arrowAlpha", 64, 255, 64); + anim.setDuration(700); + anim.setStartDelay(200 * i); + //anim.setRepeatCount(ValueAnimator.INFINITE); + anims.add(anim); + } + arrowAnim = new AnimatorSet(); + arrowAnim.playTogether(anims); + arrowAnim.addListener(new AnimatorListenerAdapter() { + private boolean canceled = false; + private long startTime; + private Runnable restarter=new Runnable(){ + @Override + public void run(){ + arrowAnim.start(); + } + }; + + @Override + public void onAnimationEnd(Animator animation) { + if(System.currentTimeMillis()-startTime getWidth() - getDraggedViewWidth())) { + dragging = true; + dragStartX = ev.getX(); + getParent().requestDisallowInterceptTouchEvent(true); + listener.onDragStart(); + stopAnimatingArrows(); + } + } else if (ev.getAction() == MotionEvent.ACTION_MOVE) { + viewToDrag.setTranslationX(Math.max(dragFromRight ? -(getWidth() - getDraggedViewWidth()) : 0, Math.min(ev.getX() - dragStartX, dragFromRight ? 0 : (getWidth() - getDraggedViewWidth())))); + invalidate(); + } else if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction()==MotionEvent.ACTION_CANCEL) { + if (Math.abs(viewToDrag.getTranslationX()) >= getWidth() - getDraggedViewWidth() && ev.getAction()==MotionEvent.ACTION_UP) { + listener.onDragComplete(); + } else { + listener.onDragCancel(); + viewToDrag.animate().translationX(0).setDuration(200).start(); + invalidate(); + startAnimatingArrows(); + dragging = false; + } + } + return dragging; + } + + public void stopAnimatingArrows() { + animatingArrows = false; + } + + public void startAnimatingArrows() { + if(animatingArrows) + return; + animatingArrows = true; + arrowAnim.start(); + } + + public void reset() { + listener.onDragCancel(); + viewToDrag.animate().translationX(0).setDuration(200).start(); + invalidate(); + startAnimatingArrows(); + dragging = false; + } + + @Override + protected void onDraw(Canvas canvas) { + if (viewToDrag.getTranslationX() != 0) { + if (dragFromRight) { + tmpRect.set(getWidth() + viewToDrag.getTranslationX() - getDraggedViewWidth(), 0, getWidth(), getHeight()); + } else { + tmpRect.set(0, 0, viewToDrag.getTranslationX() + getDraggedViewWidth(), getHeight()); + } + canvas.drawRoundRect(tmpRect, getHeight() / 2, getHeight() / 2, pullBgPaint); + } + canvas.save(); + if (dragFromRight) { + canvas.translate(getWidth() - getHeight() - AndroidUtilities.dp(12 + 6), getHeight() / 2); + } else { + canvas.translate(getHeight() + AndroidUtilities.dp(12), getHeight() / 2); + } + float offsetX = Math.abs(viewToDrag.getTranslationX()); + for (int i = 0; i < 3; i++) { + float masterAlpha = 1; + if (offsetX > AndroidUtilities.dp(16 * i)) { + masterAlpha = 1 - Math.min(1, Math.max(0, (offsetX - i * AndroidUtilities.dp(16)) / AndroidUtilities.dp(16))); + } + arrowsPaint.setAlpha(Math.round(arrowAlphas[i] * masterAlpha)); + canvas.drawPath(arrow, arrowsPaint); + canvas.translate(AndroidUtilities.dp(dragFromRight ? -16 : 16), 0); + } + canvas.restore(); + invalidate(); + } + + private void updateArrowPath() { + arrow.reset(); + int size = AndroidUtilities.dp(6); + if (dragFromRight) { + arrow.moveTo(size, -size); + arrow.lineTo(0, 0); + arrow.lineTo(size, size); + } else { + arrow.moveTo(0, -size); + arrow.lineTo(size, 0); + arrow.lineTo(0, size); + } + } + + public interface Listener { + void onDragComplete(); + void onDragStart(); + void onDragCancel(); + } + + private class ArrowAnimWrapper { + + private int index; + + public ArrowAnimWrapper(int value) { + index = value; + } + + public int getArrowAlpha() { + return arrowAlphas[index]; + } + + public void setArrowAlpha(int value) { + arrowAlphas[index] = value; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CheckableImageView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CheckableImageView.java new file mode 100755 index 00000000000..2bf15ca842f --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/CheckableImageView.java @@ -0,0 +1,59 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.ui.Components.voip; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Checkable; +import android.widget.ImageView; + +public class CheckableImageView extends ImageView implements Checkable { + + private boolean mChecked; + private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked}; + + public CheckableImageView(Context context) { + this(context, null); + } + + public CheckableImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CheckableImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + public int[] onCreateDrawableState(final int extraSpace) { + final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); + if (isChecked()) { + mergeDrawableStates(drawableState, CHECKED_STATE_SET); + } + return drawableState; + } + + @Override + public void toggle() { + setChecked(!mChecked); + } + + @Override + public boolean isChecked() { + return mChecked; + } + + @Override + public void setChecked(final boolean checked) { + if (mChecked != checked) { + mChecked = checked; + refreshDrawableState(); + } + } +} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/FabBackgroundDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/FabBackgroundDrawable.java new file mode 100755 index 00000000000..a7a4027032e --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/FabBackgroundDrawable.java @@ -0,0 +1,78 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.ui.Components.voip; + +import android.graphics.*; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +import org.telegram.messenger.AndroidUtilities; + +public class FabBackgroundDrawable extends Drawable { + + private Paint bgPaint, shadowPaint; + private Bitmap shadowBitmap; + + public FabBackgroundDrawable() { + bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + shadowPaint = new Paint(); + shadowPaint.setColor(0x4C000000); + } + + @Override + public void draw(Canvas canvas) { + if(shadowBitmap==null) + onBoundsChange(getBounds()); + int size = Math.min(getBounds().width(), getBounds().height()); + if(shadowBitmap!=null) + canvas.drawBitmap(shadowBitmap, getBounds().centerX() - shadowBitmap.getWidth() / 2, getBounds().centerY() - shadowBitmap.getHeight() / 2, shadowPaint); + canvas.drawCircle(size / 2, size / 2, size / 2 - AndroidUtilities.dp(4), bgPaint); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + + } + + @Override + public int getOpacity() { + return 0; + } + + @Override + protected void onBoundsChange(Rect bounds) { + int size = Math.min(bounds.width(), bounds.height()); + if(size<=0){ + shadowBitmap=null; + return; + } + shadowBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ALPHA_8); + Canvas c = new Canvas(shadowBitmap); + Paint p = new Paint(Paint.ANTI_ALIAS_FLAG); + p.setShadowLayer(AndroidUtilities.dp(3.33333f), 0, AndroidUtilities.dp(0.666f), 0xFFFFFFFF); + c.drawCircle(size / 2, size / 2, size / 2 - AndroidUtilities.dp(4), p); + } + + public void setColor(int color) { + bgPaint.setColor(color); + invalidateSelf(); + } + + @Override + public boolean getPadding(Rect padding) { + int pad = AndroidUtilities.dp(4); + padding.set(pad, pad, pad, pad); + return true; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java new file mode 100644 index 00000000000..662529ffd07 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/voip/VoIPHelper.java @@ -0,0 +1,141 @@ +package org.telegram.ui.Components.voip; + +import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.voip.VoIPService; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.VoIPActivity; + +public class VoIPHelper{ + + private static long lastCallRequestTime=0; + + public static void startCall(TLRPC.User user, final Activity activity, TLRPC.TL_userFull userFull){ + if(userFull!=null && userFull.phone_calls_private){ + new AlertDialog.Builder(activity) + .setTitle(LocaleController.getString("VoipFailed", R.string.VoipFailed)) + .setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("CallNotAvailable", R.string.CallNotAvailable, + ContactsController.formatName(user.first_name, user.last_name)))) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), null) + .show(); + return; + } + if (ConnectionsManager.getInstance().getConnectionState() != ConnectionsManager.ConnectionStateConnected) { + boolean isAirplaneMode = Settings.System.getInt(activity.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0; + AlertDialog.Builder bldr = new AlertDialog.Builder(activity) + .setTitle(isAirplaneMode ? LocaleController.getString("VoipOfflineAirplaneTitle", R.string.VoipOfflineAirplaneTitle) : LocaleController.getString("VoipOfflineTitle", R.string.VoipOfflineTitle)) + .setMessage(isAirplaneMode ? LocaleController.getString("VoipOfflineAirplane", R.string.VoipOfflineAirplane) : LocaleController.getString("VoipOffline", R.string.VoipOffline)) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + if (isAirplaneMode) { + final Intent settingsIntent = new Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS); + if (settingsIntent.resolveActivity(activity.getPackageManager()) != null) { + bldr.setNeutralButton(LocaleController.getString("VoipOfflineOpenSettings", R.string.VoipOfflineOpenSettings), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + activity.startActivity(settingsIntent); + } + }); + } + } + bldr.show(); + return; + } + if (Build.VERSION.SDK_INT >= 23 && activity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + activity.requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 101); + } else { + initiateCall(user, activity); + } + } + + private static void initiateCall(final TLRPC.User user, final Activity activity) { + if (activity == null || user==null) { + return; + } + if (VoIPService.getSharedInstance() != null) { + TLRPC.User callUser = VoIPService.getSharedInstance().getUser(); + if (callUser.id != user.id) { + new AlertDialog.Builder(activity) + .setTitle(LocaleController.getString("VoipOngoingAlertTitle", R.string.VoipOngoingAlertTitle)) + .setMessage(AndroidUtilities.replaceTags(LocaleController.formatString("VoipOngoingAlert", R.string.VoipOngoingAlert, + ContactsController.formatName(callUser.first_name, callUser.last_name), + ContactsController.formatName(user.first_name, user.last_name)))) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (VoIPService.getSharedInstance() != null) { + VoIPService.getSharedInstance().hangUp(new Runnable() { + @Override + public void run() { + doInitiateCall(user, activity); + } + }); + } else { + doInitiateCall(user, activity); + } + } + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null) + .show(); + } else { + activity.startActivity(new Intent(activity, VoIPActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + } + } else if(VoIPService.callIShouldHavePutIntoIntent==null) { + doInitiateCall(user, activity); + } + } + + private static void doInitiateCall(TLRPC.User user, Activity activity) { + if (activity == null || user==null) { + return; + } + if(System.currentTimeMillis()-lastCallRequestTime<1000) + return; + lastCallRequestTime=System.currentTimeMillis(); + Intent intent = new Intent(activity, VoIPService.class); + intent.putExtra("user_id", user.id); + intent.putExtra("is_outgoing", true); + intent.putExtra("start_incall_activity", true); + activity.startService(intent); + } + + @TargetApi(Build.VERSION_CODES.M) + public static void permissionDenied(final Activity activity, final Runnable onFinish){ + if(!activity.shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)){ + AlertDialog dlg=new AlertDialog.Builder(activity) + .setTitle(LocaleController.getString("AppName", R.string.AppName)) + .setMessage(LocaleController.getString("VoipNeedMicPermission", R.string.VoipNeedMicPermission)) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), null) + .setNegativeButton(LocaleController.getString("Settings", R.string.Settings), new DialogInterface.OnClickListener(){ + @Override + public void onClick(DialogInterface dialog, int which){ + Intent intent=new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri=Uri.fromParts("package", activity.getPackageName(), null); + intent.setData(uri); + activity.startActivity(intent); + } + }) + .show(); + dlg.setOnDismissListener(new DialogInterface.OnDismissListener(){ + @Override + public void onDismiss(DialogInterface dialog){ + if(onFinish!=null) + onFinish.run(); + } + }); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java index c4a0da7b5b6..51e68b85e55 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactAddActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -11,6 +11,7 @@ import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.InputType; import android.text.TextUtils; @@ -37,6 +38,8 @@ import org.telegram.messenger.R; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; @@ -50,6 +53,7 @@ public class ContactAddActivity extends BaseFragment implements NotificationCent private BackupImageView avatarImage; private TextView nameTextView; private TextView onlineTextView; + private AvatarDrawable avatarDrawable; private int user_id; private boolean addContact; @@ -101,6 +105,7 @@ public void onItemClick(int id) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); preferences.edit().putInt("spam3_" + user_id, 1).commit(); NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateInterfaces, MessagesController.UPDATE_MASK_NAME); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.peerSettingsDidLoaded, (long) user_id); } } } @@ -113,11 +118,7 @@ public void onItemClick(int id) { LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.VERTICAL); - ((ScrollView) fragmentView).addView(linearLayout); - ScrollView.LayoutParams layoutParams2 = (ScrollView.LayoutParams) linearLayout.getLayoutParams(); - layoutParams2.width = ScrollView.LayoutParams.MATCH_PARENT; - layoutParams2.height = ScrollView.LayoutParams.WRAP_CONTENT; - linearLayout.setLayoutParams(layoutParams2); + ((ScrollView) fragmentView).addView(linearLayout, LayoutHelper.createScroll(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT)); linearLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -126,26 +127,14 @@ public boolean onTouch(View v, MotionEvent event) { }); FrameLayout frameLayout = new FrameLayout(context); - linearLayout.addView(frameLayout); - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) frameLayout.getLayoutParams(); - layoutParams.topMargin = AndroidUtilities.dp(24); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - frameLayout.setLayoutParams(layoutParams); + linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 24, 24, 24, 0)); avatarImage = new BackupImageView(context); avatarImage.setRoundRadius(AndroidUtilities.dp(30)); - frameLayout.addView(avatarImage); - FrameLayout.LayoutParams layoutParams3 = (FrameLayout.LayoutParams) avatarImage.getLayoutParams(); - layoutParams3.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP; - layoutParams3.width = AndroidUtilities.dp(60); - layoutParams3.height = AndroidUtilities.dp(60); - avatarImage.setLayoutParams(layoutParams3); + frameLayout.addView(avatarImage, LayoutHelper.createFrame(60, 60, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP)); nameTextView = new TextView(context); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); nameTextView.setLines(1); nameTextView.setMaxLines(1); @@ -153,38 +142,23 @@ public boolean onTouch(View v, MotionEvent event) { nameTextView.setEllipsize(TextUtils.TruncateAt.END); nameTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - frameLayout.addView(nameTextView); - layoutParams3 = (FrameLayout.LayoutParams) nameTextView.getLayoutParams(); - layoutParams3.width = LayoutHelper.WRAP_CONTENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 0 : 80); - layoutParams3.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 80 : 0); - layoutParams3.topMargin = AndroidUtilities.dp(3); - layoutParams3.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP; - nameTextView.setLayoutParams(layoutParams3); + frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 80, 3, LocaleController.isRTL ? 80 : 0, 0)); onlineTextView = new TextView(context); - onlineTextView.setTextColor(0xff999999); + onlineTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3)); onlineTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); onlineTextView.setLines(1); onlineTextView.setMaxLines(1); onlineTextView.setSingleLine(true); onlineTextView.setEllipsize(TextUtils.TruncateAt.END); onlineTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); - frameLayout.addView(onlineTextView); - layoutParams3 = (FrameLayout.LayoutParams) onlineTextView.getLayoutParams(); - layoutParams3.width = LayoutHelper.WRAP_CONTENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? 0 : 80); - layoutParams3.rightMargin = AndroidUtilities.dp(LocaleController.isRTL ? 80 : 0); - layoutParams3.topMargin = AndroidUtilities.dp(32); - layoutParams3.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP; - onlineTextView.setLayoutParams(layoutParams3); + frameLayout.addView(onlineTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, LocaleController.isRTL ? 0 : 80, 32, LocaleController.isRTL ? 80 : 0, 0)); firstNameField = new EditText(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setMaxLines(1); firstNameField.setLines(1); firstNameField.setSingleLine(true); @@ -193,14 +167,7 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); AndroidUtilities.clearCursorDrawable(firstNameField); - linearLayout.addView(firstNameField); - layoutParams = (LinearLayout.LayoutParams) firstNameField.getLayoutParams(); - layoutParams.topMargin = AndroidUtilities.dp(24); - layoutParams.height = AndroidUtilities.dp(36); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - layoutParams.width = LayoutHelper.MATCH_PARENT; - firstNameField.setLayoutParams(layoutParams); + linearLayout.addView(firstNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 24, 24, 0)); firstNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -215,8 +182,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { lastNameField = new EditText(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - lastNameField.setHintTextColor(0xff979797); - lastNameField.setTextColor(0xff212121); + lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); lastNameField.setMaxLines(1); lastNameField.setLines(1); lastNameField.setSingleLine(true); @@ -225,14 +193,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { lastNameField.setImeOptions(EditorInfo.IME_ACTION_DONE); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); AndroidUtilities.clearCursorDrawable(lastNameField); - linearLayout.addView(lastNameField); - layoutParams = (LinearLayout.LayoutParams) lastNameField.getLayoutParams(); - layoutParams.topMargin = AndroidUtilities.dp(16); - layoutParams.height = AndroidUtilities.dp(36); - layoutParams.leftMargin = AndroidUtilities.dp(24); - layoutParams.rightMargin = AndroidUtilities.dp(24); - layoutParams.width = LayoutHelper.MATCH_PARENT; - lastNameField.setLayoutParams(layoutParams); + linearLayout.addView(lastNameField, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, 24, 16, 24, 0)); lastNameField.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -274,7 +235,7 @@ private void updateAvatarLayout() { if (user.photo != null) { photo = user.photo.photo_small; } - avatarImage.setImage(photo, "50_50", new AvatarDrawable(user)); + avatarImage.setImage(photo, "50_50", avatarDrawable = new AvatarDrawable(user)); } public void didReceivedNotification(int id, Object... args) { @@ -305,4 +266,48 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { AndroidUtilities.showKeyboard(firstNameField); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + TLRPC.User user = MessagesController.getInstance().getUser(user_id); + if (user == null) { + return; + } + avatarDrawable.setInfo(user); + avatarImage.invalidate(); + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(onlineTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java index 2c4fab51184..315946aeb39 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ContactsActivity.java @@ -3,44 +3,45 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; +import android.Manifest; +import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; +import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; -import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.SecretChatHelper; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.ContactsController; import org.telegram.messenger.FileLog; @@ -48,25 +49,32 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; -import org.telegram.ui.Adapters.BaseSectionsAdapter; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Adapters.ContactsAdapter; import org.telegram.ui.Adapters.SearchAdapter; +import org.telegram.ui.Cells.GraySectionCell; +import org.telegram.ui.Cells.LetterSectionCell; +import org.telegram.ui.Cells.ProfileSearchCell; +import org.telegram.ui.Cells.TextCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.LetterSectionsListView; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; public class ContactsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { - private BaseSectionsAdapter listViewAdapter; - private TextView emptyTextView; - private LetterSectionsListView listView; + private ContactsAdapter listViewAdapter; + private EmptyTextProgressView emptyView; + private RecyclerListView listView; private SearchAdapter searchListViewAdapter; private boolean searchWas; @@ -76,15 +84,20 @@ public class ContactsActivity extends BaseFragment implements NotificationCenter private boolean destroyAfterSelect; private boolean returnAsResult; private boolean createSecretChat; - private boolean creatingChat = false; + private boolean creatingChat; private boolean allowBots = true; private boolean needForwardCount = true; + private boolean addingToChannel; private int chat_id; private String selectAlertString = null; private HashMap ignoreUsers; private boolean allowUsernameSearch = true; private ContactsActivityDelegate delegate; + private AlertDialog permissionDialog; + + private boolean checkPermission = true; + private final static int search_button = 0; private final static int add_button = 1; @@ -113,6 +126,7 @@ public boolean onFragmentCreate() { allowUsernameSearch = arguments.getBoolean("allowUsernameSearch", true); needForwardCount = arguments.getBoolean("needForwardCount", true); allowBots = arguments.getBoolean("allowBots", true); + addingToChannel = arguments.getBoolean("addingToChannel", false); chat_id = arguments.getInt("chat_id", 0); } else { needPhonebook = true; @@ -180,10 +194,9 @@ public void onSearchCollapse() { searchWas = false; listView.setAdapter(listViewAdapter); listViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); + listView.setFastScrollVisible(true); listView.setVerticalScrollBarEnabled(false); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); } @Override @@ -197,86 +210,50 @@ public void onTextChanged(EditText editText) { if (listView != null) { listView.setAdapter(searchListViewAdapter); searchListViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(false); - listView.setFastScrollEnabled(false); + listView.setFastScrollVisible(false); listView.setVerticalScrollBarEnabled(true); } - if (emptyTextView != null) { - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + if (emptyView != null) { + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); } } searchListViewAdapter.searchDialogs(text); } }); item.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); - menu.addItem(add_button, R.drawable.add); + if (!createSecretChat && !returnAsResult) { + menu.addItem(add_button, R.drawable.add); + } searchListViewAdapter = new SearchAdapter(context, ignoreUsers, allowUsernameSearch, false, false, allowBots); listViewAdapter = new ContactsAdapter(context, onlyUsers ? 1 : 0, needPhonebook, ignoreUsers, chat_id != 0); fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; - LinearLayout emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.INVISIBLE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - ((FrameLayout) fragmentView).addView(emptyTextLayout); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) emptyTextLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - emptyTextLayout.setLayoutParams(layoutParams); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); + emptyView = new EmptyTextProgressView(context); + emptyView.setShowAtCenter(true); + emptyView.showTextView(); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - emptyTextLayout.addView(emptyTextView); - LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) emptyTextView.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - emptyTextView.setLayoutParams(layoutParams1); - - FrameLayout frameLayout = new FrameLayout(context); - emptyTextLayout.addView(frameLayout); - layoutParams1 = (LinearLayout.LayoutParams) frameLayout.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - frameLayout.setLayoutParams(layoutParams1); - - listView = new LetterSectionsListView(context); - listView.setEmptyView(emptyTextLayout); + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setSectionsType(1); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setFastScrollEnabled(true); - listView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); + listView.setFastScrollEnabled(); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(listViewAdapter); - listView.setFastScrollAlwaysVisible(true); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); - ((FrameLayout) fragmentView).addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { if (searching && searchWas) { - TLRPC.User user = (TLRPC.User) searchListViewAdapter.getItem(i); + TLRPC.User user = (TLRPC.User) searchListViewAdapter.getItem(position); if (user == null) { return; } - if (searchListViewAdapter.isGlobalSearch(i)) { + if (searchListViewAdapter.isGlobalSearch(position)) { ArrayList users = new ArrayList<>(); users.add(user); MessagesController.getInstance().putUsers(users, false); @@ -303,8 +280,8 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { } } } else { - int section = listViewAdapter.getSectionForPosition(i); - int row = listViewAdapter.getPositionInSectionForPosition(i); + int section = listViewAdapter.getSectionForPosition(position); + int row = listViewAdapter.getPositionInSectionForPosition(position); if (row < 0 || section < 0) { return; } @@ -317,7 +294,7 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("InviteFriends", R.string.InviteFriends)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } else if (chat_id != 0) { @@ -342,7 +319,7 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { return; } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (preferences.getBoolean("channel_intro", false)) { + if (!BuildVars.DEBUG_VERSION && preferences.getBoolean("channel_intro", false)) { Bundle args = new Bundle(); args.putInt("step", 0); presentFragment(new ChannelCreateActivity(args)); @@ -395,7 +372,7 @@ public void onClick(DialogInterface dialogInterface, int i) { intent.putExtra("sms_body", LocaleController.getString("InviteText", R.string.InviteText)); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -407,19 +384,17 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL && searching && searchWas) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } } @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (absListView.isFastScrollEnabled()) { - AndroidUtilities.clearDrawableAnimation(absListView); - } + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); } }); @@ -431,11 +406,11 @@ private void didSelectResult(final TLRPC.User user, boolean useAlert, String par if (getParentActivity() == null) { return; } - if (user.bot && user.bot_nochats) { + if (user.bot && user.bot_nochats && !addingToChannel) { try { Toast.makeText(getParentActivity(), LocaleController.getString("BotCantJoinGroups", R.string.BotCantJoinGroups), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return; } @@ -451,6 +426,7 @@ private void didSelectResult(final TLRPC.User user, boolean useAlert, String par editText.setGravity(Gravity.CENTER); editText.setInputType(InputType.TYPE_CLASS_NUMBER); editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + editText.setBackgroundDrawable(Theme.createEditTextDrawable(getParentActivity(), true)); final EditText editTextFinal = editText; editText.addTextChangedListener(new TextWatcher() { @Override @@ -481,7 +457,7 @@ public void afterTextChanged(Editable s) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -524,6 +500,63 @@ public void onResume() { if (listViewAdapter != null) { listViewAdapter.notifyDataSetChanged(); } + if (checkPermission && Build.VERSION.SDK_INT >= 23) { + Activity activity = getParentActivity(); + if (activity != null) { + checkPermission = false; + if (activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + if (activity.shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("PermissionContacts", R.string.PermissionContacts)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(permissionDialog = builder.create()); + } else { + askForPermissons(); + } + } + } + } + } + + @Override + protected void onDialogDismiss(Dialog dialog) { + super.onDialogDismiss(dialog); + if (permissionDialog != null && dialog == permissionDialog && getParentActivity() != null) { + askForPermissons(); + } + } + + @TargetApi(Build.VERSION_CODES.M) + private void askForPermissons() { + Activity activity = getParentActivity(); + if (activity == null) { + return; + } + ArrayList permissons = new ArrayList<>(); + if (activity.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + permissons.add(Manifest.permission.READ_CONTACTS); + permissons.add(Manifest.permission.WRITE_CONTACTS); + permissons.add(Manifest.permission.GET_ACCOUNTS); + } + String[] items = permissons.toArray(new String[permissons.size()]); + activity.requestPermissions(items, 1); + } + + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 1) { + for (int a = 0; a < permissions.length; a++) { + if (grantResults.length <= a || grantResults[a] != PackageManager.PERMISSION_GRANTED) { + continue; + } + switch (permissions[a]) { + case Manifest.permission.READ_CONTACTS: + ContactsController.getInstance().readContacts(); + break; + } + } + } } @Override @@ -579,4 +612,71 @@ public void setDelegate(ContactsActivityDelegate delegate) { public void setIgnoreUsers(HashMap users) { ignoreUsers = users; } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } else if (child instanceof ProfileSearchCell) { + ((ProfileSearchCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SECTIONS, new Class[]{LetterSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollActive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollInactive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollText), + + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_groupDrawable, Theme.dialogs_broadcastDrawable, Theme.dialogs_botDrawable}, null, Theme.key_chats_nameIcon), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedCheckDrawable}, null, Theme.key_chats_verifiedCheck), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedDrawable}, null, Theme.key_chats_verifiedBackground), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java index 3af4bb4ca09..920363ac391 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ConvertGroupActivity.java @@ -3,36 +3,39 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; public class ConvertGroupActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; + private RecyclerListView listView; private int convertInfoRow; private int convertRow; @@ -83,19 +86,17 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(final AdapterView adapterView, View view, final int i, long l) { - if (i == convertRow) { + public void onItemClick(View view, int position) { + if (position == convertRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("ConvertGroupAlert", R.string.ConvertGroupAlert)); builder.setTitle(LocaleController.getString("ConvertGroupAlertWarning", R.string.ConvertGroupAlertWarning)); @@ -129,7 +130,8 @@ public void didReceivedNotification(int id, Object... args) { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -137,60 +139,51 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return i == convertRow; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() == convertRow; } @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + default: + view = new TextInfoPrivacyCell(mContext); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == convertRow) { - textCell.setText(LocaleController.getString("ConvertGroup", R.string.ConvertGroup), false); - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == convertInfoRow) { - ((TextInfoPrivacyCell) view).setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo2", R.string.ConvertGroupInfo2))); - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == convertDetailRow) { - ((TextInfoPrivacyCell) view).setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo3", R.string.ConvertGroupInfo3))); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == convertRow) { + textCell.setText(LocaleController.getString("ConvertGroup", R.string.ConvertGroup), false); + } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == convertInfoRow) { + privacyCell.setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo2", R.string.ConvertGroupInfo2))); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == convertDetailRow) { + privacyCell.setText(AndroidUtilities.replaceTags(LocaleController.getString("ConvertGroupInfo3", R.string.ConvertGroupInfo3))); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; } - return view; } @Override @@ -202,15 +195,26 @@ public int getItemViewType(int i) { } return 0; } + } - @Override - public int getViewTypeCount() { - return 2; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CountrySelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/CountrySelectActivity.java index da75104f574..09334a89546 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CountrySelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CountrySelectActivity.java @@ -3,52 +3,70 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.content.Context; -import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; -import android.widget.AbsListView; -import android.widget.AdapterView; +import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; -import org.telegram.ui.Adapters.CountryAdapter; -import org.telegram.ui.Adapters.CountryAdapter.Country; -import org.telegram.ui.Adapters.CountrySearchAdapter; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Cells.DividerCell; +import org.telegram.ui.Cells.LetterSectionCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.LetterSectionsListView; +import org.telegram.ui.Components.RecyclerListView; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; public class CountrySelectActivity extends BaseFragment { public interface CountrySelectActivityDelegate { - void didSelectCountry(String name); + void didSelectCountry(String name, String shortName); } - private LetterSectionsListView listView; - private TextView emptyTextView; + private RecyclerListView listView; + private EmptyTextProgressView emptyView; private CountryAdapter listViewAdapter; private CountrySearchAdapter searchListViewAdapter; private boolean searchWas; private boolean searching; + private boolean needPhoneCode; private CountrySelectActivityDelegate delegate; + public CountrySelectActivity(boolean phoneCode) { + super(); + needPhoneCode = phoneCode; + } + @Override public boolean onFragmentCreate() { return super.onFragmentCreate(); @@ -87,11 +105,8 @@ public void onSearchCollapse() { searching = false; searchWas = false; listView.setAdapter(listViewAdapter); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); - listView.setVerticalScrollBarEnabled(false); - - emptyTextView.setText(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); + listView.setFastScrollVisible(true); + emptyView.setText(LocaleController.getString("ChooseCountry", R.string.ChooseCountry)); } @Override @@ -102,11 +117,9 @@ public void onTextChanged(EditText editText) { searchWas = true; if (listView != null) { listView.setAdapter(searchListViewAdapter); - listView.setFastScrollAlwaysVisible(false); - listView.setFastScrollEnabled(false); - listView.setVerticalScrollBarEnabled(true); + listView.setFastScrollVisible(false); } - if (emptyTextView != null) { + if (emptyView != null) { } } @@ -121,97 +134,55 @@ public void onTextChanged(EditText editText) { searchListViewAdapter = new CountrySearchAdapter(context, listViewAdapter.getCountries()); fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; - LinearLayout emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.INVISIBLE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - ((FrameLayout) fragmentView).addView(emptyTextLayout); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) emptyTextLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - emptyTextLayout.setLayoutParams(layoutParams); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); + emptyView = new EmptyTextProgressView(context); + emptyView.showTextView(); + emptyView.setShowAtCenter(true); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - emptyTextLayout.addView(emptyTextView); - LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) emptyTextView.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - emptyTextView.setLayoutParams(layoutParams1); - - FrameLayout frameLayout = new FrameLayout(context); - emptyTextLayout.addView(frameLayout); - layoutParams1 = (LinearLayout.LayoutParams) frameLayout.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - frameLayout.setLayoutParams(layoutParams1); - - listView = new LetterSectionsListView(context); - listView.setEmptyView(emptyTextLayout); + listView = new RecyclerListView(context); + listView.setSectionsType(1); + listView.setEmptyView(emptyView); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setFastScrollEnabled(true); - listView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); + listView.setFastScrollEnabled(); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(listViewAdapter); - listView.setFastScrollAlwaysVisible(true); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); - ((FrameLayout) fragmentView).addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { Country country; if (searching && searchWas) { - country = searchListViewAdapter.getItem(i); + country = searchListViewAdapter.getItem(position); } else { - int section = listViewAdapter.getSectionForPosition(i); - int row = listViewAdapter.getPositionInSectionForPosition(i); + int section = listViewAdapter.getSectionForPosition(position); + int row = listViewAdapter.getPositionInSectionForPosition(position); if (row < 0 || section < 0) { return; } country = listViewAdapter.getItem(section, row); } - if (i < 0) { + if (position < 0) { return; } finishFragment(); if (country != null && delegate != null) { - delegate.didSelectCountry(country.name); + delegate.didSelectCountry(country.name, country.shortname); } } }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL && searching && searchWas) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } } - - @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (absListView.isFastScrollEnabled()) { - AndroidUtilities.clearDrawableAnimation(absListView); - } - } }); return fragmentView; @@ -228,4 +199,297 @@ public void onResume() { public void setCountrySelectActivityDelegate(CountrySelectActivityDelegate delegate) { this.delegate = delegate; } + + public static class Country { + public String name; + public String code; + public String shortname; + } + + public class CountryAdapter extends RecyclerListView.SectionsAdapter { + + private Context mContext; + private HashMap> countries = new HashMap<>(); + private ArrayList sortedCountries = new ArrayList<>(); + + public CountryAdapter(Context context) { + mContext = context; + + try { + InputStream stream = ApplicationLoader.applicationContext.getResources().getAssets().open("countries.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = reader.readLine()) != null) { + String[] args = line.split(";"); + Country c = new Country(); + c.name = args[2]; + c.code = args[0]; + c.shortname = args[1]; + String n = c.name.substring(0, 1).toUpperCase(); + ArrayList arr = countries.get(n); + if (arr == null) { + arr = new ArrayList<>(); + countries.put(n, arr); + sortedCountries.add(n); + } + arr.add(c); + } + reader.close(); + stream.close(); + } catch (Exception e) { + FileLog.e(e); + } + + Collections.sort(sortedCountries, new Comparator() { + @Override + public int compare(String lhs, String rhs) { + return lhs.compareTo(rhs); + } + }); + + for (ArrayList arr : countries.values()) { + Collections.sort(arr, new Comparator() { + @Override + public int compare(Country country, Country country2) { + return country.name.compareTo(country2.name); + } + }); + } + } + + public HashMap> getCountries() { + return countries; + } + + @Override + public Country getItem(int section, int position) { + if (section < 0 || section >= sortedCountries.size()) { + return null; + } + ArrayList arr = countries.get(sortedCountries.get(section)); + if (position < 0 || position >= arr.size()) { + return null; + } + return arr.get(position); + } + + @Override + public boolean isEnabled(int section, int row) { + ArrayList arr = countries.get(sortedCountries.get(section)); + return row < arr.size(); + } + + @Override + public int getSectionCount() { + return sortedCountries.size(); + } + + @Override + public int getCountForSection(int section) { + int count = countries.get(sortedCountries.get(section)).size(); + if (section != sortedCountries.size() - 1) { + count++; + } + return count; + } + + @Override + public View getSectionHeaderView(int section, View view) { + if (view == null) { + view = new LetterSectionCell(mContext); + ((LetterSectionCell) view).setCellHeight(AndroidUtilities.dp(48)); + } + ((LetterSectionCell) view).setLetter(sortedCountries.get(section).toUpperCase()); + return view; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 16 : 54), 0, AndroidUtilities.dp(LocaleController.isRTL ? 54 : 16), 0); + break; + case 1: + default: + view = new DividerCell(mContext); + view.setPadding(AndroidUtilities.dp(LocaleController.isRTL ? 24 : 72), 0, AndroidUtilities.dp(LocaleController.isRTL ? 72 : 24), 0); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(int section, int position, RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == 0) { + ArrayList arr = countries.get(sortedCountries.get(section)); + Country c = arr.get(position); + ((TextSettingsCell) holder.itemView).setTextAndValue(c.name, needPhoneCode ? "+" + c.code : null, false); + } + } + + @Override + public int getItemViewType(int section, int position) { + ArrayList arr = countries.get(sortedCountries.get(section)); + return position < arr.size() ? 0 : 1; + } + + @Override + public String getLetter(int position) { + int section = getSectionForPosition(position); + if (section == -1) { + section = sortedCountries.size() - 1; + } + return sortedCountries.get(section); + } + + @Override + public int getPositionForScrollProgress(float progress) { + return (int) (getItemCount() * progress); + } + } + + public class CountrySearchAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + private Timer searchTimer; + private ArrayList searchResult; + private HashMap> countries; + + public CountrySearchAdapter(Context context, HashMap> countries) { + mContext = context; + this.countries = countries; + } + + public void search(final String query) { + if (query == null) { + searchResult = null; + } else { + try { + if (searchTimer != null) { + searchTimer.cancel(); + } + } catch (Exception e) { + FileLog.e(e); + } + searchTimer = new Timer(); + searchTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + searchTimer.cancel(); + searchTimer = null; + } catch (Exception e) { + FileLog.e(e); + } + processSearch(query); + } + }, 100, 300); + } + } + + private void processSearch(final String query) { + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + + String q = query.trim().toLowerCase(); + if (q.length() == 0) { + updateSearchResults(new ArrayList()); + return; + } + ArrayList resultArray = new ArrayList<>(); + + String n = query.substring(0, 1); + ArrayList arr = countries.get(n.toUpperCase()); + if (arr != null) { + for (Country c : arr) { + if (c.name.toLowerCase().startsWith(query)) { + resultArray.add(c); + } + } + } + + updateSearchResults(resultArray); + } + }); + } + + private void updateSearchResults(final ArrayList arrCounties) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchResult = arrCounties; + notifyDataSetChanged(); + } + }); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + @Override + public int getItemCount() { + if (searchResult == null) { + return 0; + } + return searchResult.size(); + } + + public Country getItem(int i) { + if (i < 0 || i >= searchResult.size()) { + return null; + } + return searchResult.get(i); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new RecyclerListView.Holder(new TextSettingsCell(mContext)); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + Country c = searchResult.get(position); + ((TextSettingsCell) holder.itemView).setTextAndValue(c.name, needPhoneCode ? "+" + c.code : null, position != searchResult.size() - 1); + } + + @Override + public int getItemViewType(int i) { + return 0; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollActive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollInactive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollText), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_SECTIONS, new Class[]{LetterSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java new file mode 100644 index 00000000000..f0c62a363e7 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataSettingsActivity.java @@ -0,0 +1,494 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.messenger.voip.VoIPController; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.CheckBoxCell; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextDetailSettingsCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +public class DataSettingsActivity extends BaseFragment { + + private ListAdapter listAdapter; + private RecyclerListView listView; + + private int mediaDownloadSectionRow; + private int mobileDownloadRow; + private int wifiDownloadRow; + private int roamingDownloadRow; + private int mediaDownloadSection2Row; + private int usageSectionRow; + private int storageUsageRow; + private int mobileUsageRow; + private int wifiUsageRow; + private int roamingUsageRow; + private int usageSection2Row; + private int callsSectionRow; + private int useLessDataForCallsRow; + private int callsSection2Row; + private int rowCount; + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + + rowCount = 0; + mediaDownloadSectionRow = rowCount++; + mobileDownloadRow = rowCount++; + wifiDownloadRow = rowCount++; + roamingDownloadRow = rowCount++; + mediaDownloadSection2Row = rowCount++; + usageSectionRow = rowCount++; + storageUsageRow = rowCount++; + mobileUsageRow = rowCount++; + wifiUsageRow = rowCount++; + roamingUsageRow = rowCount++; + if (MessagesController.getInstance().callsEnabled) { + usageSection2Row = rowCount++; + callsSectionRow = rowCount++; + useLessDataForCallsRow = rowCount++; + } else { + usageSection2Row = -1; + callsSectionRow = -1; + useLessDataForCallsRow = -1; + } + callsSection2Row = rowCount++; + + return true; + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setTitle(LocaleController.getString("DataSettings", R.string.DataSettings)); + if (AndroidUtilities.isTablet()) { + actionBar.setOccupyStatusBar(false); + } + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + listAdapter = new ListAdapter(context); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + listView = new RecyclerListView(context); + listView.setVerticalScrollBarEnabled(false); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + listView.setAdapter(listAdapter); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, final int position) { + if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow) { + if (getParentActivity() == null) { + return; + } + final boolean maskValues[] = new boolean[6]; + BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); + + int mask = 0; + if (position == mobileDownloadRow) { + mask = MediaController.getInstance().mobileDataDownloadMask; + } else if (position == wifiDownloadRow) { + mask = MediaController.getInstance().wifiDownloadMask; + } else if (position == roamingDownloadRow) { + mask = MediaController.getInstance().roamingDownloadMask; + } + + builder.setApplyTopPadding(false); + builder.setApplyBottomPadding(false); + LinearLayout linearLayout = new LinearLayout(getParentActivity()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + for (int a = 0; a < 6; a++) { + String name = null; + if (a == 0) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0; + name = LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache); + } else if (a == 1) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0; + name = LocaleController.getString("LocalAudioCache", R.string.LocalAudioCache); + } else if (a == 2) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0; + name = LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache); + } else if (a == 3) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0; + name = LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage); + } else if (a == 4) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0; + name = LocaleController.getString("AttachMusic", R.string.AttachMusic); + } else if (a == 5) { + maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0; + name = LocaleController.getString("LocalGifCache", R.string.LocalGifCache); + } + CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity(), true); + checkBoxCell.setTag(a); + checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + checkBoxCell.setText(name, "", maskValues[a], true); + checkBoxCell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + checkBoxCell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + int num = (Integer) cell.getTag(); + maskValues[num] = !maskValues[num]; + cell.setChecked(maskValues[num], true); + } + }); + } + BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + cell.setTextAndIcon(LocaleController.getString("Save", R.string.Save).toUpperCase(), 0); + cell.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try { + if (visibleDialog != null) { + visibleDialog.dismiss(); + } + } catch (Exception e) { + FileLog.e(e); + } + int newMask = 0; + for (int a = 0; a < 6; a++) { + if (maskValues[a]) { + if (a == 0) { + newMask |= MediaController.AUTODOWNLOAD_MASK_PHOTO; + } else if (a == 1) { + newMask |= MediaController.AUTODOWNLOAD_MASK_AUDIO; + } else if (a == 2) { + newMask |= MediaController.AUTODOWNLOAD_MASK_VIDEO; + } else if (a == 3) { + newMask |= MediaController.AUTODOWNLOAD_MASK_DOCUMENT; + } else if (a == 4) { + newMask |= MediaController.AUTODOWNLOAD_MASK_MUSIC; + } else if (a == 5) { + newMask |= MediaController.AUTODOWNLOAD_MASK_GIF; + } + } + } + SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); + if (position == mobileDownloadRow) { + editor.putInt("mobileDataDownloadMask", newMask); + MediaController.getInstance().mobileDataDownloadMask = newMask; + } else if (position == wifiDownloadRow) { + editor.putInt("wifiDownloadMask", newMask); + MediaController.getInstance().wifiDownloadMask = newMask; + } else if (position == roamingDownloadRow) { + editor.putInt("roamingDownloadMask", newMask); + MediaController.getInstance().roamingDownloadMask = newMask; + } + editor.commit(); + if (listAdapter != null) { + listAdapter.notifyItemChanged(position); + } + } + }); + linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + builder.setCustomView(linearLayout); + showDialog(builder.create()); + } else if (position == storageUsageRow) { + presentFragment(new CacheControlActivity()); + } else if (position == useLessDataForCallsRow) { + final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + Dialog dlg = AlertsCreator.createSingleChoiceDialog(getParentActivity(), DataSettingsActivity.this, new String[]{ + LocaleController.getString("UseLessDataNever", R.string.UseLessDataNever), + LocaleController.getString("UseLessDataOnMobile", R.string.UseLessDataOnMobile), + LocaleController.getString("UseLessDataAlways", R.string.UseLessDataAlways)}, + LocaleController.getString("VoipUseLessData", R.string.VoipUseLessData), preferences.getInt("VoipDataSaving", VoIPController.DATA_SAVING_NEVER), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + int val = -1; + switch (which) { + case 0: + val = VoIPController.DATA_SAVING_NEVER; + break; + case 1: + val = VoIPController.DATA_SAVING_MOBILE; + break; + case 2: + val = VoIPController.DATA_SAVING_ALWAYS; + break; + } + if (val != -1) { + preferences.edit().putInt("VoipDataSaving", val).commit(); + } + if (listAdapter != null) { + listAdapter.notifyItemChanged(position); + } + } + }); + setVisibleDialog(dlg); + dlg.show(); + } else if (position == mobileUsageRow) { + presentFragment(new DataUsageActivity(0)); + } else if (position == roamingUsageRow) { + presentFragment(new DataUsageActivity(2)); + } else if (position == wifiUsageRow) { + presentFragment(new DataUsageActivity(1)); + } + } + }); + + frameLayout.addView(actionBar); + + return fragmentView; + } + + @Override + protected void onDialogDismiss(Dialog dialog) { + MediaController.getInstance().checkAutodownloadSettings(); + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + if (position == callsSection2Row || position == usageSection2Row && usageSection2Row == -1) { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == storageUsageRow) { + textCell.setText(LocaleController.getString("StorageUsage", R.string.StorageUsage), true); + } else if (position == useLessDataForCallsRow) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + String value = null; + switch (preferences.getInt("VoipDataSaving", VoIPController.DATA_SAVING_NEVER)) { + case VoIPController.DATA_SAVING_NEVER: + value = LocaleController.getString("UseLessDataNever", R.string.UseLessDataNever); + break; + case VoIPController.DATA_SAVING_MOBILE: + value = LocaleController.getString("UseLessDataOnMobile", R.string.UseLessDataOnMobile); + break; + case VoIPController.DATA_SAVING_ALWAYS: + value = LocaleController.getString("UseLessDataAlways", R.string.UseLessDataAlways); + break; + } + textCell.setTextAndValue(LocaleController.getString("VoipUseLessData", R.string.VoipUseLessData), value, false); + } else if (position == mobileUsageRow) { + textCell.setText(LocaleController.getString("MobileUsage", R.string.MobileUsage), true); + } else if (position == roamingUsageRow) { + textCell.setText(LocaleController.getString("RoamingUsage", R.string.RoamingUsage), false); + } else if (position == wifiUsageRow) { + textCell.setText(LocaleController.getString("WiFiUsage", R.string.WiFiUsage), true); + } + break; + } + case 2: { + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == mediaDownloadSectionRow) { + headerCell.setText(LocaleController.getString("AutomaticMediaDownload", R.string.AutomaticMediaDownload)); + } else if (position == usageSectionRow) { + headerCell.setText(LocaleController.getString("DataUsage", R.string.DataUsage)); + } else if (position == callsSectionRow) { + headerCell.setText(LocaleController.getString("Calls", R.string.Calls)); + } + break; + } + case 3: { + TextDetailSettingsCell textCell = (TextDetailSettingsCell) holder.itemView; + + if (position == mobileDownloadRow || position == wifiDownloadRow || position == roamingDownloadRow) { + int mask; + String value; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (position == mobileDownloadRow) { + value = LocaleController.getString("WhenUsingMobileData", R.string.WhenUsingMobileData); + mask = MediaController.getInstance().mobileDataDownloadMask; + } else if (position == wifiDownloadRow) { + value = LocaleController.getString("WhenConnectedOnWiFi", R.string.WhenConnectedOnWiFi); + mask = MediaController.getInstance().wifiDownloadMask; + } else { + value = LocaleController.getString("WhenRoaming", R.string.WhenRoaming); + mask = MediaController.getInstance().roamingDownloadMask; + } + String text = ""; + if ((mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0) { + text += LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache); + } + if ((mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0) { + if (text.length() != 0) { + text += ", "; + } + text += LocaleController.getString("LocalAudioCache", R.string.LocalAudioCache); + } + if ((mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0) { + if (text.length() != 0) { + text += ", "; + } + text += LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache); + } + if ((mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0) { + if (text.length() != 0) { + text += ", "; + } + text += LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage); + } + if ((mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0) { + if (text.length() != 0) { + text += ", "; + } + text += LocaleController.getString("AttachMusic", R.string.AttachMusic); + } + if ((mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0) { + if (text.length() != 0) { + text += ", "; + } + text += LocaleController.getString("LocalGifCache", R.string.LocalGifCache); + } + if (text.length() == 0) { + text = LocaleController.getString("NoMediaAutoDownload", R.string.NoMediaAutoDownload); + } + textCell.setTextAndValue(value, text, true); + } + break; + } + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow || position == storageUsageRow || + position == useLessDataForCallsRow || position == mobileUsageRow || position == roamingUsageRow || position == wifiUsageRow; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case 0: + view = new ShadowSectionCell(mContext); + break; + case 1: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new TextDetailSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public int getItemViewType(int position) { + if (position == mediaDownloadSection2Row || position == usageSection2Row || position == callsSection2Row) { + return 0; + } else if (position == storageUsageRow || position == useLessDataForCallsRow || position == roamingUsageRow || position == wifiUsageRow || position == mobileUsageRow) { + return 1; + } else if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow) { + return 3; + } else if (position == mediaDownloadSectionRow || position == callsSectionRow || position == usageSectionRow) { + return 2; + } else { + return 1; + } + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, TextSettingsCell.class, TextDetailSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java new file mode 100644 index 00000000000..cece9aa48d6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/DataUsageActivity.java @@ -0,0 +1,421 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.content.Context; +import android.content.DialogInterface; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.messenger.StatsController; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; + +public class DataUsageActivity extends BaseFragment { + + private ListAdapter listAdapter; + private RecyclerListView listView; + + private int currentType; + + private int messagesSectionRow; + private int messagesSentRow = -1; + private int messagesReceivedRow = -1; + private int messagesBytesSentRow; + private int messagesBytesReceivedRow; + private int messagesSection2Row; + + private int photosSectionRow; + private int photosSentRow; + private int photosReceivedRow; + private int photosBytesSentRow; + private int photosBytesReceivedRow; + private int photosSection2Row; + + private int videosSectionRow; + private int videosSentRow; + private int videosReceivedRow; + private int videosBytesSentRow; + private int videosBytesReceivedRow; + private int videosSection2Row; + + private int audiosSectionRow; + private int audiosSentRow; + private int audiosReceivedRow; + private int audiosBytesSentRow; + private int audiosBytesReceivedRow; + private int audiosSection2Row; + + private int filesSectionRow; + private int filesSentRow; + private int filesReceivedRow; + private int filesBytesSentRow; + private int filesBytesReceivedRow; + private int filesSection2Row; + + private int callsSectionRow; + private int callsSentRow; + private int callsReceivedRow; + private int callsBytesSentRow; + private int callsBytesReceivedRow; + private int callsTotalTimeRow; + private int callsSection2Row; + + private int totalSectionRow; + private int totalBytesSentRow; + private int totalBytesReceivedRow; + private int totalSection2Row; + + private int resetRow; + private int resetSection2Row; + + private int rowCount; + + public DataUsageActivity(int type) { + super(); + currentType = type; + } + + @Override + public boolean onFragmentCreate() { + super.onFragmentCreate(); + + rowCount = 0; + + photosSectionRow = rowCount++; + photosSentRow = rowCount++; + photosReceivedRow = rowCount++; + photosBytesSentRow = rowCount++; + photosBytesReceivedRow = rowCount++; + photosSection2Row = rowCount++; + + videosSectionRow = rowCount++; + videosSentRow = rowCount++; + videosReceivedRow = rowCount++; + videosBytesSentRow = rowCount++; + videosBytesReceivedRow = rowCount++; + videosSection2Row = rowCount++; + + audiosSectionRow = rowCount++; + audiosSentRow = rowCount++; + audiosReceivedRow = rowCount++; + audiosBytesSentRow = rowCount++; + audiosBytesReceivedRow = rowCount++; + audiosSection2Row = rowCount++; + + filesSectionRow = rowCount++; + filesSentRow = rowCount++; + filesReceivedRow = rowCount++; + filesBytesSentRow = rowCount++; + filesBytesReceivedRow = rowCount++; + filesSection2Row = rowCount++; + + if (MessagesController.getInstance().callsEnabled) { + callsSectionRow = rowCount++; + callsSentRow = rowCount++; + callsReceivedRow = rowCount++; + callsBytesSentRow = rowCount++; + callsBytesReceivedRow = rowCount++; + callsTotalTimeRow = rowCount++; + callsSection2Row = rowCount++; + } else { + callsSectionRow = -1; + callsSentRow = -1; + callsReceivedRow = -1; + callsBytesSentRow = -1; + callsBytesReceivedRow = -1; + callsTotalTimeRow = -1; + callsSection2Row = -1; + } + + messagesSectionRow = rowCount++; + /*if (BuildVars.DEBUG_VERSION) { + messagesSentRow = rowCount++; + messagesReceivedRow = rowCount++; + }*/ + messagesBytesSentRow = rowCount++; + messagesBytesReceivedRow = rowCount++; + messagesSection2Row = rowCount++; + + totalSectionRow = rowCount++; + totalBytesSentRow = rowCount++; + totalBytesReceivedRow = rowCount++; + totalSection2Row = rowCount++; + + resetRow = rowCount++; + resetSection2Row = rowCount++; + + return true; + } + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + if (currentType == 0) { + actionBar.setTitle(LocaleController.getString("MobileUsage", R.string.MobileUsage)); + } else if (currentType == 1) { + actionBar.setTitle(LocaleController.getString("WiFiUsage", R.string.WiFiUsage)); + } else if (currentType == 2) { + actionBar.setTitle(LocaleController.getString("RoamingUsage", R.string.RoamingUsage)); + } + if (AndroidUtilities.isTablet()) { + actionBar.setOccupyStatusBar(false); + } + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + listAdapter = new ListAdapter(context); + + fragmentView = new FrameLayout(context); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + listView = new RecyclerListView(context); + listView.setVerticalScrollBarEnabled(false); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + listView.setAdapter(listAdapter); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, final int position) { + if (getParentActivity() == null) { + return; + } + if (position == resetRow) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("ResetStatisticsAlert", R.string.ResetStatisticsAlert)); + builder.setPositiveButton(LocaleController.getString("Reset", R.string.Reset), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + StatsController.getInstance().resetStats(currentType); + listAdapter.notifyDataSetChanged(); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } + }); + + frameLayout.addView(actionBar); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return rowCount; + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + if (position == resetSection2Row) { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else { + holder.itemView.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == resetRow) { + textCell.setTag(Theme.key_windowBackgroundWhiteRedText2); + textCell.setText(LocaleController.getString("ResetStatistics", R.string.ResetStatistics), false); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText2)); + } else { + int type; + textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + if (position == callsSentRow || position == callsReceivedRow || position == callsBytesSentRow || position == callsBytesReceivedRow) { + type = StatsController.TYPE_CALLS; + } else if (position == messagesSentRow || position == messagesReceivedRow || position == messagesBytesSentRow || position == messagesBytesReceivedRow) { + type = StatsController.TYPE_MESSAGES; + } else if (position == photosSentRow || position == photosReceivedRow || position == photosBytesSentRow || position == photosBytesReceivedRow) { + type = StatsController.TYPE_PHOTOS; + } else if (position == audiosSentRow || position == audiosReceivedRow || position == audiosBytesSentRow || position == audiosBytesReceivedRow) { + type = StatsController.TYPE_AUDIOS; + } else if (position == videosSentRow || position == videosReceivedRow || position == videosBytesSentRow || position == videosBytesReceivedRow) { + type = StatsController.TYPE_VIDEOS; + } else if (position == filesSentRow || position == filesReceivedRow || position == filesBytesSentRow || position == filesBytesReceivedRow) { + type = StatsController.TYPE_FILES; + } else { + type = StatsController.TYPE_TOTAL; + } + if (position == callsSentRow) { + textCell.setTextAndValue(LocaleController.getString("OutgoingCalls", R.string.OutgoingCalls), String.format("%d", StatsController.getInstance().getSentItemsCount(currentType, type)), true); + } else if (position == callsReceivedRow) { + textCell.setTextAndValue(LocaleController.getString("IncomingCalls", R.string.IncomingCalls), String.format("%d", StatsController.getInstance().getRecivedItemsCount(currentType, type)), true); + } else if (position == callsTotalTimeRow) { + int total = StatsController.getInstance().getCallsTotalTime(currentType); + int hours = total / 3600; + total -= hours * 3600; + int minutes = total / 60; + total -= minutes * 60; + String time; + if (hours != 0) { + time = String.format("%d:%02d:%02d", hours, minutes, total); + } else { + time = String.format("%d:%02d", minutes, total); + } + textCell.setTextAndValue(LocaleController.getString("CallsTotalTime", R.string.CallsTotalTime), time, false); + } else if (position == messagesSentRow || position == photosSentRow || position == videosSentRow || position == audiosSentRow || position == filesSentRow) { + textCell.setTextAndValue(LocaleController.getString("CountSent", R.string.CountSent), String.format("%d", StatsController.getInstance().getSentItemsCount(currentType, type)), true); + } else if (position == messagesReceivedRow || position == photosReceivedRow || position == videosReceivedRow || position == audiosReceivedRow || position == filesReceivedRow) { + textCell.setTextAndValue(LocaleController.getString("CountReceived", R.string.CountReceived), String.format("%d", StatsController.getInstance().getRecivedItemsCount(currentType, type)), true); + } else if (position == messagesBytesSentRow || position == photosBytesSentRow || position == videosBytesSentRow || position == audiosBytesSentRow || position == filesBytesSentRow || position == callsBytesSentRow || position == totalBytesSentRow) { + textCell.setTextAndValue(LocaleController.getString("BytesSent", R.string.BytesSent), AndroidUtilities.formatFileSize(StatsController.getInstance().getSentBytesCount(currentType, type)), true); + } else if (position == messagesBytesReceivedRow || position == photosBytesReceivedRow || position == videosBytesReceivedRow || position == audiosBytesReceivedRow || position == filesBytesReceivedRow || position == callsBytesReceivedRow || position == totalBytesReceivedRow) { + textCell.setTextAndValue(LocaleController.getString("BytesReceived", R.string.BytesReceived), AndroidUtilities.formatFileSize(StatsController.getInstance().getReceivedBytesCount(currentType, type)), position != totalBytesReceivedRow); + } + } + break; + } + case 2: { + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == totalSectionRow) { + headerCell.setText(LocaleController.getString("TotalDataUsage", R.string.TotalDataUsage)); + } else if (position == callsSectionRow) { + headerCell.setText(LocaleController.getString("CallsDataUsage", R.string.CallsDataUsage)); + } else if (position == filesSectionRow) { + headerCell.setText(LocaleController.getString("FilesDataUsage", R.string.FilesDataUsage)); + } else if (position == audiosSectionRow) { + headerCell.setText(LocaleController.getString("LocalAudioCache", R.string.LocalAudioCache)); + } else if (position == videosSectionRow) { + headerCell.setText(LocaleController.getString("LocalVideoCache", R.string.LocalVideoCache)); + } else if (position == photosSectionRow) { + headerCell.setText(LocaleController.getString("LocalPhotoCache", R.string.LocalPhotoCache)); + } else if (position == messagesSectionRow) { + headerCell.setText(LocaleController.getString("MessagesDataUsage", R.string.MessagesDataUsage)); + } + break; + } + case 3: { + TextInfoPrivacyCell cell = (TextInfoPrivacyCell) holder.itemView; + cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + cell.setText(LocaleController.formatString("NetworkUsageSince", R.string.NetworkUsageSince, LocaleController.getInstance().formatterStats.format(StatsController.getInstance().getResetStatsDate(currentType)))); + break; + } + default: + break; + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() == resetRow; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = null; + switch (viewType) { + case 0: + view = new ShadowSectionCell(mContext); + break; + case 1: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new TextInfoPrivacyCell(mContext); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public int getItemViewType(int position) { + if (position == resetSection2Row) { + return 3; + } else if (position == resetSection2Row || position == callsSection2Row || position == filesSection2Row || position == audiosSection2Row || position == videosSection2Row || position == photosSection2Row || position == messagesSection2Row || position == totalSection2Row) { + return 0; + } else if (position == totalSectionRow || position == callsSectionRow || position == filesSectionRow || position == audiosSectionRow || position == videosSectionRow || position == photosSectionRow || position == messagesSectionRow) { + return 2; + } else { + return 1; + } + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, HeaderCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText2), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 4cbce7e1029..927d05c230a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -14,15 +14,18 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Outline; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; +import android.support.annotation.Px; import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; @@ -34,8 +37,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -58,10 +59,18 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Adapters.DialogsAdapter; import org.telegram.ui.Adapters.DialogsSearchAdapter; +import org.telegram.ui.Cells.DividerCell; +import org.telegram.ui.Cells.DrawerActionCell; +import org.telegram.ui.Cells.DrawerProfileCell; +import org.telegram.ui.Cells.GraySectionCell; +import org.telegram.ui.Cells.HashtagSearchCell; import org.telegram.ui.Cells.HintDialogCell; +import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.ProfileSearchCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Cells.DialogCell; @@ -70,9 +79,11 @@ import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.MenuDrawable; -import org.telegram.ui.Components.PlayerView; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.FragmentContextView; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.ActionBar.Theme; @@ -85,10 +96,15 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. private DialogsAdapter dialogsAdapter; private DialogsSearchAdapter dialogsSearchAdapter; private EmptyTextProgressView searchEmptyView; - private ProgressBar progressView; + private RadialProgressView progressView; private LinearLayout emptyView; private ActionBarMenuItem passcodeItem; private ImageView floatingButton; + private RecyclerView sideMenu; + private FragmentContextView fragmentContextView; + + private TextView emptyTextView1; + private TextView emptyTextView2; private AlertDialog permissionDialog; @@ -159,6 +175,7 @@ public boolean onFragmentCreate() { if (!dialogsLoaded) { MessagesController.getInstance().loadDialogs(0, 100, true); ContactsController.getInstance().checkInviteText(); + MessagesController.getInstance().loadPinnedDialogs(0, null); StickersQuery.checkFeaturedStickers(); dialogsLoaded = true; } @@ -196,7 +213,7 @@ public View createView(final Context context) { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - Theme.loadRecources(context); + Theme.createChatResources(context, false); } }); @@ -317,6 +334,11 @@ public void onItemClick(int id) { } }); + if (sideMenu != null) { + sideMenu.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.setGlowColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.getAdapter().notifyDataSetChanged(); + } FrameLayout frameLayout = new FrameLayout(context); fragmentView = frameLayout; @@ -335,7 +357,7 @@ public boolean supportsPredictiveItemAnimations() { }; layoutManager.setOrientation(LinearLayoutManager.VERTICAL); listView.setLayoutManager(layoutManager); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override @@ -491,6 +513,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } dialog = dialogs.get(position); selectedDialog = dialog.id; + final boolean pinned = dialog.pinned; BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); int lower_id = (int) selectedDialog; @@ -499,54 +522,71 @@ public void onClick(DialogInterface dialogInterface, int i) { if (DialogObject.isChannel(dialog)) { final TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); CharSequence items[]; + int icons[] = new int[]{ + dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, + R.drawable.chats_clear, + R.drawable.chats_leave + }; if (chat != null && chat.megagroup) { - items = new CharSequence[]{LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), chat == null || !chat.creator ? LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu) : LocaleController.getString("DeleteMegaMenu", R.string.DeleteMegaMenu)}; + items = new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), + chat == null || !chat.creator ? LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu) : LocaleController.getString("DeleteMegaMenu", R.string.DeleteMegaMenu)}; } else { - items = new CharSequence[]{LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), chat == null || !chat.creator ? LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu) : LocaleController.getString("ChannelDeleteMenu", R.string.ChannelDeleteMenu)}; + items = new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(false) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + LocaleController.getString("ClearHistoryCache", R.string.ClearHistoryCache), + chat == null || !chat.creator ? LocaleController.getString("LeaveChannelMenu", R.string.LeaveChannelMenu) : LocaleController.getString("ChannelDeleteMenu", R.string.ChannelDeleteMenu)}; } - builder.setItems(items, new DialogInterface.OnClickListener() { + builder.setItems(items, icons, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, final int which) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (which == 0) { - if (chat != null && chat.megagroup) { - builder.setMessage(LocaleController.getString("AreYouSureClearHistorySuper", R.string.AreYouSureClearHistorySuper)); - } else { - builder.setMessage(LocaleController.getString("AreYouSureClearHistoryChannel", R.string.AreYouSureClearHistoryChannel)); + if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { + listView.smoothScrollToPosition(0); } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - MessagesController.getInstance().deleteDialog(selectedDialog, 2); - } - }); } else { - if (chat != null && chat.megagroup) { - if (!chat.creator) { - builder.setMessage(LocaleController.getString("MegaLeaveAlert", R.string.MegaLeaveAlert)); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (which == 1) { + if (chat != null && chat.megagroup) { + builder.setMessage(LocaleController.getString("AreYouSureClearHistorySuper", R.string.AreYouSureClearHistorySuper)); } else { - builder.setMessage(LocaleController.getString("MegaDeleteAlert", R.string.MegaDeleteAlert)); + builder.setMessage(LocaleController.getString("AreYouSureClearHistoryChannel", R.string.AreYouSureClearHistoryChannel)); } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().deleteDialog(selectedDialog, 2); + } + }); } else { - if (chat == null || !chat.creator) { - builder.setMessage(LocaleController.getString("ChannelLeaveAlert", R.string.ChannelLeaveAlert)); + if (chat != null && chat.megagroup) { + if (!chat.creator) { + builder.setMessage(LocaleController.getString("MegaLeaveAlert", R.string.MegaLeaveAlert)); + } else { + builder.setMessage(LocaleController.getString("MegaDeleteAlert", R.string.MegaDeleteAlert)); + } } else { - builder.setMessage(LocaleController.getString("ChannelDeleteAlert", R.string.ChannelDeleteAlert)); - } - } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, UserConfig.getCurrentUser(), null); - if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + if (chat == null || !chat.creator) { + builder.setMessage(LocaleController.getString("ChannelLeaveAlert", R.string.ChannelLeaveAlert)); + } else { + builder.setMessage(LocaleController.getString("ChannelDeleteAlert", R.string.ChannelDeleteAlert)); } } - }); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, UserConfig.getCurrentUser(), null); + if (AndroidUtilities.isTablet()) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + } + } + }); + } + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); } - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); } }); showDialog(builder.create()); @@ -557,49 +597,62 @@ public void onClick(DialogInterface dialogInterface, int i) { user = MessagesController.getInstance().getUser(lower_id); } final boolean isBot = user != null && user.bot; - builder.setItems(new CharSequence[]{LocaleController.getString("ClearHistory", R.string.ClearHistory), - isChat ? LocaleController.getString("DeleteChat", R.string.DeleteChat) : - isBot ? LocaleController.getString("DeleteAndStop", R.string.DeleteAndStop) : LocaleController.getString("Delete", R.string.Delete)}, new DialogInterface.OnClickListener() { + + builder.setItems(new CharSequence[]{ + dialog.pinned || MessagesController.getInstance().canPinDialog(lower_id == 0) ? (dialog.pinned ? LocaleController.getString("UnpinFromTop", R.string.UnpinFromTop) : LocaleController.getString("PinToTop", R.string.PinToTop)) : null, + LocaleController.getString("ClearHistory", R.string.ClearHistory), + isChat ? LocaleController.getString("DeleteChat", R.string.DeleteChat) : isBot ? LocaleController.getString("DeleteAndStop", R.string.DeleteAndStop) : LocaleController.getString("Delete", R.string.Delete) + }, new int[]{ + dialog.pinned ? R.drawable.chats_unpin : R.drawable.chats_pin, + R.drawable.chats_clear, + isChat ? R.drawable.chats_leave : R.drawable.chats_delete + }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, final int which) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); if (which == 0) { - builder.setMessage(LocaleController.getString("AreYouSureClearHistory", R.string.AreYouSureClearHistory)); + if (MessagesController.getInstance().pinDialog(selectedDialog, !pinned, null, 0) && !pinned) { + listView.smoothScrollToPosition(0); + } } else { - if (isChat) { - builder.setMessage(LocaleController.getString("AreYouSureDeleteAndExit", R.string.AreYouSureDeleteAndExit)); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + if (which == 1) { + builder.setMessage(LocaleController.getString("AreYouSureClearHistory", R.string.AreYouSureClearHistory)); } else { - builder.setMessage(LocaleController.getString("AreYouSureDeleteThisChat", R.string.AreYouSureDeleteThisChat)); + if (isChat) { + builder.setMessage(LocaleController.getString("AreYouSureDeleteAndExit", R.string.AreYouSureDeleteAndExit)); + } else { + builder.setMessage(LocaleController.getString("AreYouSureDeleteThisChat", R.string.AreYouSureDeleteThisChat)); + } } - } - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (which != 0) { - if (isChat) { - TLRPC.Chat currentChat = MessagesController.getInstance().getChat((int) -selectedDialog); - if (currentChat != null && ChatObject.isNotInChat(currentChat)) { - MessagesController.getInstance().deleteDialog(selectedDialog, 0); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (which != 1) { + if (isChat) { + TLRPC.Chat currentChat = MessagesController.getInstance().getChat((int) -selectedDialog); + if (currentChat != null && ChatObject.isNotInChat(currentChat)) { + MessagesController.getInstance().deleteDialog(selectedDialog, 0); + } else { + MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); + } } else { - MessagesController.getInstance().deleteUserFromChat((int) -selectedDialog, MessagesController.getInstance().getUser(UserConfig.getClientUserId()), null); + MessagesController.getInstance().deleteDialog(selectedDialog, 0); + } + if (isBot) { + MessagesController.getInstance().blockUser((int) selectedDialog); + } + if (AndroidUtilities.isTablet()) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); } } else { - MessagesController.getInstance().deleteDialog(selectedDialog, 0); - } - if (isBot) { - MessagesController.getInstance().blockUser((int) selectedDialog); - } - if (AndroidUtilities.isTablet()) { - NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats, selectedDialog); + MessagesController.getInstance().deleteDialog(selectedDialog, 1); } - } else { - MessagesController.getInstance().deleteDialog(selectedDialog, 1); } - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } } }); showDialog(builder.create()); @@ -626,34 +679,44 @@ public boolean onTouch(View v, MotionEvent event) { } }); - TextView textView = new TextView(context); - textView.setText(LocaleController.getString("NoChats", R.string.NoChats)); - textView.setTextColor(0xff959595); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - emptyView.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + emptyTextView1 = new TextView(context); + emptyTextView1.setText(LocaleController.getString("NoChats", R.string.NoChats)); + emptyTextView1.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); + emptyTextView1.setGravity(Gravity.CENTER); + emptyTextView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + emptyView.addView(emptyTextView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - textView = new TextView(context); + emptyTextView2 = new TextView(context); String help = LocaleController.getString("NoChatsHelp", R.string.NoChatsHelp); if (AndroidUtilities.isTablet() && !AndroidUtilities.isSmallTablet()) { help = help.replace('\n', ' '); } - textView.setText(help); - textView.setTextColor(0xff959595); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); - textView.setGravity(Gravity.CENTER); - textView.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(6), AndroidUtilities.dp(8), 0); - textView.setLineSpacing(AndroidUtilities.dp(2), 1); - emptyView.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); - - progressView = new ProgressBar(context); + emptyTextView2.setText(help); + emptyTextView2.setTextColor(Theme.getColor(Theme.key_emptyListPlaceholder)); + emptyTextView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + emptyTextView2.setGravity(Gravity.CENTER); + emptyTextView2.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(6), AndroidUtilities.dp(8), 0); + emptyTextView2.setLineSpacing(AndroidUtilities.dp(2), 1); + emptyView.addView(emptyTextView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + + progressView = new RadialProgressView(context); progressView.setVisibility(View.GONE); frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); floatingButton = new ImageView(context); floatingButton.setVisibility(onlySelect ? View.GONE : View.VISIBLE); floatingButton.setScaleType(ImageView.ScaleType.CENTER); - floatingButton.setBackgroundResource(R.drawable.floating_states); + + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + floatingButton.setBackgroundDrawable(drawable); + floatingButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); floatingButton.setImageResource(R.drawable.floating_pencil); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); @@ -668,7 +731,7 @@ public void getOutline(View view, Outline outline) { } }); } - frameLayout.addView(floatingButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + frameLayout.addView(floatingButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); floatingButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -700,7 +763,10 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } if (visibleItemCount > 0) { if (layoutManager.findLastVisibleItemPosition() >= getDialogsArray().size() - 10) { - MessagesController.getInstance().loadDialogs(-1, 100, !MessagesController.getInstance().dialogsEndReached); + boolean fromCache = !MessagesController.getInstance().dialogsEndReached; + if (fromCache || !MessagesController.getInstance().serverDialogsEndReached) { + MessagesController.getInstance().loadDialogs(-1, 100, fromCache); + } } } @@ -825,7 +891,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } if (!onlySelect && dialogsType == 0) { - frameLayout.addView(new PlayerView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + frameLayout.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); } return fragmentView; @@ -964,7 +1030,7 @@ public void didReceivedNotification(int id, Object... args) { } } } catch (Exception e) { - FileLog.e("tmessages", e); //TODO fix it in other way? + FileLog.e(e); //TODO fix it in other way? } } } else if (id == NotificationCenter.emojiDidLoaded) { @@ -999,7 +1065,7 @@ public void didReceivedNotification(int id, Object... args) { updateVisibleRows(MessagesController.UPDATE_MASK_SEND_STATE); } else if (id == NotificationCenter.didSetPasscode) { updatePasscodeButton(); - } if (id == NotificationCenter.needReloadRecentDialogsSearch) { + } else if (id == NotificationCenter.needReloadRecentDialogsSearch) { if (dialogsSearchAdapter != null) { dialogsSearchAdapter.loadRecentSearch(); } @@ -1023,6 +1089,12 @@ private ArrayList getDialogsArray() { return null; } + public void setSideMenu(RecyclerView recyclerView) { + sideMenu = recyclerView; + sideMenu.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.setGlowColor(Theme.getColor(Theme.key_chats_menuBackground)); + } + private void updatePasscodeButton() { if (passcodeItem == null) { return; @@ -1104,13 +1176,16 @@ public boolean isMainDialogList() { private void didSelectResult(final long dialog_id, boolean useAlert, final boolean param) { if (addToGroupAlertString == null) { - if ((int) dialog_id < 0 && ChatObject.isChannel(-(int) dialog_id) && (cantSendToChannels || !ChatObject.isCanWriteToChannel(-(int) dialog_id))) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setMessage(LocaleController.getString("ChannelCantSendMessage", R.string.ChannelCantSendMessage)); - builder.setNegativeButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); - return; + if ((int) dialog_id < 0) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(-(int) dialog_id); + if (ChatObject.isChannel(chat) && !chat.megagroup && (cantSendToChannels || !ChatObject.isCanWriteToChannel(-(int) dialog_id))) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("ChannelCantSendMessage", R.string.ChannelCantSendMessage)); + builder.setNegativeButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); + return; + } } } if (useAlert && (selectAlertString != null && selectAlertStringGroup != null || addToGroupAlertString != null)) { @@ -1173,4 +1248,164 @@ public void onClick(DialogInterface dialogInterface, int i) { } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof ProfileSearchCell) { + ((ProfileSearchCell) child).update(0); + } else if (child instanceof DialogCell) { + ((DialogCell) child).update(0); + } + } + RecyclerListView recyclerListView = dialogsSearchAdapter.getInnerListView(); + if (recyclerListView != null) { + count = recyclerListView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = recyclerListView.getChildAt(a); + if (child instanceof HintDialogCell) { + ((HintDialogCell) child).update(); + } + } + } + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(searchEmptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(searchEmptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(emptyTextView1, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyTextView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(floatingButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), + new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), + new ThemeDescription(floatingButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), + + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countPaint, null, null, Theme.key_chats_unreadCounter), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countGrayPaint, null, null, Theme.key_chats_unreadCounterMuted), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_countTextPaint, null, null, Theme.key_chats_unreadCounterText), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, Theme.dialogs_namePaint, null, null, Theme.key_chats_name), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, Theme.dialogs_nameEncryptedPaint, null, null, Theme.key_chats_secretName), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_lockDrawable}, null, Theme.key_chats_secretIcon), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_groupDrawable, Theme.dialogs_broadcastDrawable, Theme.dialogs_botDrawable}, null, Theme.key_chats_nameIcon), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_pinnedDrawable}, null, Theme.key_chats_pinnedIcon), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_messagePaint, null, null, Theme.key_chats_message), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_chats_nameMessage), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_chats_draft), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_chats_attachMessage), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_messagePrintingPaint, null, null, Theme.key_chats_actionMessage), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_timePaint, null, null, Theme.key_chats_date), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_pinnedPaint, null, null, Theme.key_chats_pinnedOverlay), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_tabletSeletedPaint, null, null, Theme.key_chats_tabletSelectedOverlay), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_checkDrawable, Theme.dialogs_halfCheckDrawable}, null, Theme.key_chats_sentCheck), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_clockDrawable}, null, Theme.key_chats_sentClock), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, Theme.dialogs_errorPaint, null, null, Theme.key_chats_sentError), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_errorDrawable}, null, Theme.key_chats_sentErrorIcon), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedCheckDrawable}, null, Theme.key_chats_verifiedCheck), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class, ProfileSearchCell.class}, null, new Drawable[]{Theme.dialogs_verifiedDrawable}, null, Theme.key_chats_verifiedBackground), + new ThemeDescription(listView, 0, new Class[]{DialogCell.class}, null, new Drawable[]{Theme.dialogs_muteDrawable}, null, Theme.key_chats_muteIcon), + + new ThemeDescription(sideMenu, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_chats_menuBackground), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuName), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuPhone), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuPhoneCats), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuCloudBackgroundCats), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, new String[]{"cloudDrawable"}, null, null, null, Theme.key_chats_menuCloud), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chat_serviceBackground), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerProfileCell.class}, null, null, null, Theme.key_chats_menuTopShadow), + + new ThemeDescription(sideMenu, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{DrawerActionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chats_menuItemIcon), + new ThemeDescription(sideMenu, 0, new Class[]{DrawerActionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_chats_menuItemText), + + new ThemeDescription(sideMenu, 0, new Class[]{DividerCell.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_offlinePaint, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{ProfileSearchCell.class}, Theme.dialogs_onlinePaint, null, null, Theme.key_windowBackgroundWhiteBlueText3), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{HashtagSearchCell.class}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(progressView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(dialogsSearchAdapter.getInnerListView(), 0, new Class[]{HintDialogCell.class}, Theme.dialogs_countPaint, null, null, Theme.key_chats_unreadCounter), + new ThemeDescription(dialogsSearchAdapter.getInnerListView(), 0, new Class[]{HintDialogCell.class}, Theme.dialogs_countGrayPaint, null, null, Theme.key_chats_unreadCounterMuted), + new ThemeDescription(dialogsSearchAdapter.getInnerListView(), 0, new Class[]{HintDialogCell.class}, Theme.dialogs_countTextPaint, null, null, Theme.key_chats_unreadCounterText), + new ThemeDescription(dialogsSearchAdapter.getInnerListView(), 0, new Class[]{HintDialogCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerBackground), + new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"playButton"}, null, null, null, Theme.key_inappPlayerPlayPause), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_inappPlayerTitle), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerPerformer), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"closeButton"}, null, null, null, Theme.key_inappPlayerClose), + + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_returnToCallBackground), + new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_returnToCallText), + + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlack), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextLink), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogLinkSelection), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlue), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlue2), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlue3), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextBlue4), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextRed), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextGray), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextGray2), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextGray3), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextGray4), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogIcon), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogTextHint), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogInputField), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogInputFieldActivated), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogCheckboxSquareBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogCheckboxSquareCheck), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogCheckboxSquareUnchecked), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogCheckboxSquareDisabled), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogRadioBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogRadioBackgroundChecked), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogProgressCircle), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogButton), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogButtonSelector), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogScrollGlow), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogRoundCheckBox), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogRoundCheckBoxCheck), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogBadgeBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogBadgeText), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogLineProgress), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogLineProgressBackground), + new ThemeDescription(null, 0, null, null, null, null, Theme.key_dialogGrayLine), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java index 7a36f9e218f..b45904f802a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DocumentSelectActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -12,7 +12,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; -import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -24,31 +23,35 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; +import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.SharedDocumentCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; +import org.telegram.ui.Components.RecyclerListView; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -61,10 +64,11 @@ public interface DocumentSelectActivityDelegate { void startDocumentSelectActivity(); } - private ListView listView; + private RecyclerListView listView; private ListAdapter listAdapter; private NumberTextView selectedMessagesCountTextView; - private TextView emptyView; + private EmptyTextProgressView emptyView; + private LinearLayoutManager layoutManager; private File currentDir; private ArrayList items = new ArrayList<>(); @@ -75,6 +79,7 @@ public interface DocumentSelectActivityDelegate { private HashMap selectedFiles = new HashMap<>(); private ArrayList actionModeViews = new ArrayList<>(); private boolean scrolling; + private ArrayList recentItems = new ArrayList<>(); private final static int done = 3; @@ -85,6 +90,7 @@ private class ListItem { String ext = ""; String thumb; File file; + long date; } private class HistoryEntry { @@ -105,7 +111,7 @@ public void run() { listFiles(currentDir); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }; @@ -117,6 +123,12 @@ public void run() { } }; + @Override + public boolean onFragmentCreate() { + loadRecentFiles(); + return super.onFragmentCreate(); + } + @Override public void onFragmentDestroy() { try { @@ -124,7 +136,7 @@ public void onFragmentDestroy() { ApplicationLoader.applicationContext.unregisterReceiver(receiver); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } super.onFragmentDestroy(); } @@ -157,7 +169,13 @@ public void onItemClick(int id) { if (actionBar.isActionModeShowed()) { selectedFiles.clear(); actionBar.hideActionMode(); - listView.invalidateViews(); + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof SharedDocumentCell) { + ((SharedDocumentCell) child).setChecked(false, true); + } + } } else { finishFragment(); } @@ -166,6 +184,9 @@ public void onItemClick(int id) { ArrayList files = new ArrayList<>(); files.addAll(selectedFiles.keySet()); delegate.didSelectFiles(DocumentSelectActivity.this, files); + for (ListItem item : selectedFiles.values()) { + item.date = System.currentTimeMillis(); + } } } } @@ -178,7 +199,7 @@ public void onItemClick(int id) { selectedMessagesCountTextView = new NumberTextView(actionMode.getContext()); selectedMessagesCountTextView.setTextSize(18); selectedMessagesCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - selectedMessagesCountTextView.setTextColor(0xff737373); + selectedMessagesCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); selectedMessagesCountTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -187,40 +208,38 @@ public boolean onTouch(View v, MotionEvent event) { }); actionMode.addView(selectedMessagesCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 65, 0, 0, 0)); - actionModeViews.add(actionMode.addItem(done, R.drawable.ic_ab_done_gray, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(done, R.drawable.ic_ab_done, AndroidUtilities.dp(54))); - fragmentView = getParentActivity().getLayoutInflater().inflate(R.layout.document_select_layout, null, false); - listAdapter = new ListAdapter(context); - emptyView = (TextView) fragmentView.findViewById(R.id.searchEmptyView); - emptyView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - listView = (ListView) fragmentView.findViewById(R.id.listView); - listView.setEmptyView(emptyView); - listView.setAdapter(listAdapter); + fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - scrolling = scrollState != SCROLL_STATE_IDLE; - } + emptyView = new EmptyTextProgressView(context); + emptyView.showTextView(); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView = new RecyclerListView(context); + listView.setVerticalScrollBarEnabled(false); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setEmptyView(emptyView); + listView.setAdapter(listAdapter = new ListAdapter(context)); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + scrolling = newState != RecyclerView.SCROLL_STATE_IDLE; } }); - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView parent, View view, int i, long id) { - if (actionBar.isActionModeShowed() || i < 0 || i >= items.size()) { + public boolean onItemClick(View view, int position) { + if (actionBar.isActionModeShowed()) { + return false; + } + ListItem item = listAdapter.getItem(position); + if (item == null) { return false; } - ListItem item = items.get(i); File file = item.file; if (file != null && !file.isDirectory()) { if (!file.canRead()) { @@ -258,13 +277,13 @@ public boolean onItemLongClick(AdapterView parent, View view, int i, long id) } }); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - if (i < 0 || i >= items.size()) { + public void onItemClick(View view, int position) { + ListItem item = listAdapter.getItem(position); + if (item == null) { return; } - ListItem item = items.get(i); File file = item.file; if (file == null) { if (item.icon == R.drawable.ic_storage_gallery) { @@ -280,12 +299,15 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { } else { listRoots(); } - listView.setSelectionFromTop(he.scrollItem, he.scrollOffset); + layoutManager.scrollToPositionWithOffset(he.scrollItem, he.scrollOffset); } } else if (file.isDirectory()) { HistoryEntry he = new HistoryEntry(); - he.scrollItem = listView.getFirstVisiblePosition(); - he.scrollOffset = listView.getChildAt(0).getTop(); + he.scrollItem = layoutManager.findLastVisibleItemPosition(); + View topView = layoutManager.findViewByPosition(he.scrollItem); + if (topView != null) { + he.scrollOffset = topView.getTop(); + } he.dir = currentDir; he.title = actionBar.getTitle(); history.add(he); @@ -294,7 +316,6 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { return; } actionBar.setTitle(item.title); - listView.setSelection(0); } else { if (!file.canRead()) { showErrorBox(LocaleController.getString("AccessError", R.string.AccessError)); @@ -340,6 +361,46 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { return fragmentView; } + public void loadRecentFiles() { + try { + File[] files = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).listFiles(); + for (int a = 0; a < files.length; a++) { + File file = files[a]; + if (file.isDirectory()) { + continue; + } + ListItem item = new ListItem(); + item.title = file.getName(); + item.file = file; + String fname = file.getName(); + String[] sp = fname.split("\\."); + item.ext = sp.length > 1 ? sp[sp.length - 1] : "?"; + item.subtitle = AndroidUtilities.formatFileSize(file.length()); + fname = fname.toLowerCase(); + if (fname.endsWith(".jpg") || fname.endsWith(".png") || fname.endsWith(".gif") || fname.endsWith(".jpeg")) { + item.thumb = file.getAbsolutePath(); + } + recentItems.add(item); + } + Collections.sort(recentItems, new Comparator() { + @Override + public int compare(ListItem o1, ListItem o2) { + long lm = o1.file.lastModified(); + long rm = o2.file.lastModified(); + if (lm == rm) { + return 0; + } else if (lm > rm) { + return -1; + } else { + return 1; + } + } + }); + } catch (Exception e) { + FileLog.e(e); + } + } + @Override public void onResume() { super.onResume(); @@ -386,7 +447,7 @@ public boolean onBackPressed() { } else { listRoots(); } - listView.setSelectionFromTop(he.scrollItem, he.scrollOffset); + layoutManager.scrollToPositionWithOffset(he.scrollItem, he.scrollOffset); return false; } return super.onBackPressed(); @@ -533,7 +594,7 @@ private void listRoots() { String line; while ((line = bufferedReader.readLine()) != null) { if (line.contains("vfat") || line.contains("/mnt")) { - FileLog.e("tmessages", line); + FileLog.e(line); StringTokenizer tokens = new StringTokenizer(line, " "); String unused = tokens.nextToken(); String path = tokens.nextToken(); @@ -564,20 +625,20 @@ private void listRoots() { item.file = new File(path); items.add(item); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -599,7 +660,7 @@ private void listRoots() { items.add(fs); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } fs = new ListItem(); @@ -624,12 +685,13 @@ private String getRootSubtitle(String path) { } return LocaleController.formatString("FreeOfTotal", R.string.FreeOfTotal, AndroidUtilities.formatFileSize(free), AndroidUtilities.formatFileSize(total)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return path; } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -637,47 +699,104 @@ public ListAdapter(Context context) { } @Override - public int getCount() { - return items.size(); + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() != 0; } @Override - public Object getItem(int position) { - return items.get(position); + public int getItemCount() { + int count = items.size(); + if (history.isEmpty() && !recentItems.isEmpty()) { + count += recentItems.size() + 1; + } + return count; } - @Override - public long getItemId(int position) { - return 0; + public ListItem getItem(int position) { + if (position < items.size()) { + return items.get(position); + } else if (history.isEmpty() && !recentItems.isEmpty() && position != items.size()) { + position -= items.size() + 1; + if (position < recentItems.size()) { + return recentItems.get(position); + } + } + return null; } - public int getViewTypeCount() { - return 2; + @Override + public int getItemViewType(int position) { + return getItem(position) != null ? 1 : 0; } - public int getItemViewType(int pos) { - return items.get(pos).subtitle.length() > 0 ? 0 : 1; + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new GraySectionCell(mContext); + ((GraySectionCell) view).setText(LocaleController.getString("Recent", R.string.Recent).toUpperCase()); + break; + case 1: + default: + view = new SharedDocumentCell(mContext); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new SharedDocumentCell(mContext); - } - SharedDocumentCell textDetailCell = (SharedDocumentCell) convertView; - ListItem item = items.get(position); - if (item.icon != 0) { - ((SharedDocumentCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, null, null, item.icon); - } else { - String type = item.ext.toUpperCase().substring(0, Math.min(item.ext.length(), 4)); - ((SharedDocumentCell) convertView).setTextAndValueAndTypeAndThumb(item.title, item.subtitle, type, item.thumb, 0); - } - if (item.file != null && actionBar.isActionModeShowed()) { - textDetailCell.setChecked(selectedFiles.containsKey(item.file.toString()), !scrolling); - } else { - textDetailCell.setChecked(false, !scrolling); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 1) { + ListItem item = getItem(position); + SharedDocumentCell documentCell = (SharedDocumentCell) holder.itemView; + if (item.icon != 0) { + documentCell.setTextAndValueAndTypeAndThumb(item.title, item.subtitle, null, null, item.icon); + } else { + String type = item.ext.toUpperCase().substring(0, Math.min(item.ext.length(), 4)); + documentCell.setTextAndValueAndTypeAndThumb(item.title, item.subtitle, type, item.thumb, 0); + } + if (item.file != null && actionBar.isActionModeShowed()) { + documentCell.setChecked(selectedFiles.containsKey(item.file.toString()), !scrolling); + } else { + documentCell.setChecked(false, !scrolling); + } } - return convertView; } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_BACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_TOPBACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefaultTop), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultSelector), + + new ThemeDescription(selectedMessagesCountTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"dateTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{SharedDocumentCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{SharedDocumentCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkboxCheck), + new ThemeDescription(listView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"thumbImageView"}, null, null, null, Theme.key_files_folderIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_IMAGECOLOR | ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{SharedDocumentCell.class}, new String[]{"thumbImageView"}, null, null, null, Theme.key_files_folderIconBackground), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"extTextView"}, null, null, null, Theme.key_files_iconText), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java index c2cd45546ba..e224b53bf83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FeaturedStickersActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -22,6 +22,8 @@ import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.FeaturedStickerSetCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Components.LayoutHelper; @@ -34,6 +36,7 @@ public class FeaturedStickersActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; + private RecyclerListView listView; private LinearLayoutManager layoutManager; private ArrayList unreadStickers = null; @@ -83,9 +86,9 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - RecyclerListView listView = new RecyclerListView(context); + listView = new RecyclerListView(context); listView.setItemAnimator(null); listView.setLayoutAnimation(null); listView.setFocusable(true); @@ -190,15 +193,9 @@ public void onResume() { } } - private class ListAdapter extends RecyclerListView.Adapter { - private Context mContext; - - private class Holder extends RecyclerView.ViewHolder { + private class ListAdapter extends RecyclerListView.SelectionAdapter { - public Holder(View itemView) { - super(itemView); - } - } + private Context mContext; public ListAdapter(Context context) { mContext = context; @@ -227,13 +224,18 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() == 0; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case 0: view = new FeaturedStickerSetCell(mContext); - view.setBackgroundResource(R.drawable.list_selector_white); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); ((FeaturedStickerSetCell) view).setAddOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -250,11 +252,11 @@ public void onClick(View v) { break; case 1: view = new TextInfoPrivacyCell(mContext); - view.setBackgroundResource(R.drawable.greydivider_bottom); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -267,4 +269,32 @@ public int getItemViewType(int i) { return 0; } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FeaturedStickerSetCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{FeaturedStickerSetCell.class}, new String[]{"progressPaint"}, null, null, null, Theme.key_featuredStickers_buttonProgress), + new ThemeDescription(listView, 0, new Class[]{FeaturedStickerSetCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{FeaturedStickerSetCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{FeaturedStickerSetCell.class}, new String[]{"addButton"}, null, null, null, Theme.key_featuredStickers_buttonText), + new ThemeDescription(listView, 0, new Class[]{FeaturedStickerSetCell.class}, new String[]{"checkImage"}, null, null, null, Theme.key_featuredStickers_addedIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{FeaturedStickerSetCell.class}, new String[]{"addButton"}, null, null, null, Theme.key_featuredStickers_addButton), + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{FeaturedStickerSetCell.class}, new String[]{"addButton"}, null, null, null, Theme.key_featuredStickers_addButtonPressed), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java index 9ac8a77b24a..eedf00a39ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateActivity.java @@ -3,80 +3,92 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.Activity; -import android.app.AlertDialog; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Canvas; -import android.graphics.drawable.BitmapDrawable; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.text.Editable; import android.text.InputType; -import android.text.Spannable; -import android.text.SpannableString; import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; import android.text.TextWatcher; -import android.text.style.ImageSpan; +import android.text.style.ForegroundColorSpan; import android.util.TypedValue; +import android.view.ActionMode; import android.view.Gravity; -import android.view.LayoutInflater; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; +import android.widget.ScrollView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; -import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.ChatObject; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; -import org.telegram.messenger.UserObject; -import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; -import org.telegram.messenger.FileLog; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.ui.Adapters.ContactsAdapter; -import org.telegram.ui.Adapters.SearchAdapter; -import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Cells.UserCell; -import org.telegram.ui.Components.ChipSpan; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Adapters.SearchAdapterHelper; +import org.telegram.ui.Cells.GroupCreateSectionCell; +import org.telegram.ui.Cells.GroupCreateUserCell; +import org.telegram.ui.Components.EditTextBoldCursor; +import org.telegram.ui.Components.EmptyTextProgressView; +import org.telegram.ui.Components.GroupCreateDividerItemDecoration; +import org.telegram.ui.Components.GroupCreateSpan; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.LetterSectionsListView; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; -public class GroupCreateActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { +public class GroupCreateActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, View.OnClickListener { - public interface GroupCreateActivityDelegate { - void didSelectUsers(ArrayList ids); - } + private ScrollView scrollView; + private SpansContainer spansContainer; + private EditTextBoldCursor editText; + private RecyclerListView listView; + private EmptyTextProgressView emptyView; + private GroupCreateAdapter adapter; + private GroupCreateActivityDelegate delegate; + private GroupCreateDividerItemDecoration itemDecoration; + private AnimatorSet currentDoneButtonAnimation; + private View doneButton; + private boolean doneButtonVisible; + private boolean ignoreScrollEvent; - private ContactsAdapter listViewAdapter; - private TextView emptyTextView; - private EditText userSelectEditText; - private LetterSectionsListView listView; - private SearchAdapter searchListViewAdapter; + private int containerHeight; - private GroupCreateActivityDelegate delegate; + private int chatId; - private int beforeChangeIndex; - private boolean ignoreChange; - private CharSequence changeString; private int maxCount = 5000; private int chatType = ChatObject.CHAT_TYPE_CHAT; private boolean isAlwaysShare; @@ -84,11 +96,195 @@ public interface GroupCreateActivityDelegate { private boolean searchWas; private boolean searching; private boolean isGroup; - private HashMap selectedContacts = new HashMap<>(); - private ArrayList allSpans = new ArrayList<>(); + private HashMap selectedContacts = new HashMap<>(); + private ArrayList allSpans = new ArrayList<>(); + private GroupCreateSpan currentDeletingSpan; + + private int fieldY; private final static int done_button = 1; + public interface GroupCreateActivityDelegate { + void didSelectUsers(ArrayList ids); + } + + private class SpansContainer extends ViewGroup { + + private AnimatorSet currentAnimation; + private boolean animationStarted; + private ArrayList animators = new ArrayList<>(); + private View addingSpan; + private View removingSpan; + + public SpansContainer(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int count = getChildCount(); + int width = MeasureSpec.getSize(widthMeasureSpec); + int maxWidth = width - AndroidUtilities.dp(32); + int currentLineWidth = 0; + int y = AndroidUtilities.dp(12); + int allCurrentLineWidth = 0; + int allY = AndroidUtilities.dp(12); + int x; + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + if (!(child instanceof GroupCreateSpan)) { + continue; + } + child.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); + if (child != removingSpan && currentLineWidth + child.getMeasuredWidth() > maxWidth) { + y += child.getMeasuredHeight() + AndroidUtilities.dp(12); + currentLineWidth = 0; + } + if (allCurrentLineWidth + child.getMeasuredWidth() > maxWidth) { + allY += child.getMeasuredHeight() + AndroidUtilities.dp(12); + allCurrentLineWidth = 0; + } + x = AndroidUtilities.dp(16) + currentLineWidth; + if (!animationStarted) { + if (child == removingSpan) { + child.setTranslationX(AndroidUtilities.dp(16) + allCurrentLineWidth); + child.setTranslationY(allY); + } else if (removingSpan != null) { + if (child.getTranslationX() != x) { + animators.add(ObjectAnimator.ofFloat(child, "translationX", x)); + } + if (child.getTranslationY() != y) { + animators.add(ObjectAnimator.ofFloat(child, "translationY", y)); + } + } else { + child.setTranslationX(x); + child.setTranslationY(y); + } + } + if (child != removingSpan) { + currentLineWidth += child.getMeasuredWidth() + AndroidUtilities.dp(9); + } + allCurrentLineWidth += child.getMeasuredWidth() + AndroidUtilities.dp(9); + } + int minWidth; + if (AndroidUtilities.isTablet()) { + minWidth = AndroidUtilities.dp(530 - 32 - 18 - 57 * 2) / 3; + } else { + minWidth = (Math.min(AndroidUtilities.displaySize.x, AndroidUtilities.displaySize.y) - AndroidUtilities.dp(32 + 18 + 57 * 2)) / 3; + } + if (maxWidth - currentLineWidth < minWidth) { + currentLineWidth = 0; + y += AndroidUtilities.dp(32 + 12); + } + if (maxWidth - allCurrentLineWidth < minWidth) { + allY += AndroidUtilities.dp(32 + 12); + } + editText.measure(MeasureSpec.makeMeasureSpec(maxWidth - currentLineWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(32), MeasureSpec.EXACTLY)); + if (!animationStarted) { + int currentHeight = allY + AndroidUtilities.dp(32 + 12); + int fieldX = currentLineWidth + AndroidUtilities.dp(16); + fieldY = y; + if (currentAnimation != null) { + int resultHeight = y + AndroidUtilities.dp(32 + 12); + if (containerHeight != resultHeight) { + animators.add(ObjectAnimator.ofInt(GroupCreateActivity.this, "containerHeight", resultHeight)); + } + if (editText.getTranslationX() != fieldX) { + animators.add(ObjectAnimator.ofFloat(editText, "translationX", fieldX)); + } + if (editText.getTranslationY() != fieldY) { + animators.add(ObjectAnimator.ofFloat(editText, "translationY", fieldY)); + } + editText.setAllowDrawCursor(false); + currentAnimation.playTogether(animators); + currentAnimation.start(); + animationStarted = true; + } else { + containerHeight = currentHeight; + editText.setTranslationX(fieldX); + editText.setTranslationY(fieldY); + } + } else if (currentAnimation != null) { + if (!ignoreScrollEvent && removingSpan == null) { + editText.bringPointIntoView(editText.getSelectionStart()); + } + } + setMeasuredDimension(width, containerHeight); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int count = getChildCount(); + for (int a = 0; a < count; a++) { + View child = getChildAt(a); + child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); + } + } + + public void addSpan(final GroupCreateSpan span) { + allSpans.add(span); + selectedContacts.put(span.getUid(), span); + + editText.setHintVisible(false); + if (currentAnimation != null) { + currentAnimation.setupEndValues(); + currentAnimation.cancel(); + } + animationStarted = false; + currentAnimation = new AnimatorSet(); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + addingSpan = null; + currentAnimation = null; + animationStarted = false; + editText.setAllowDrawCursor(true); + } + }); + currentAnimation.setDuration(150); + addingSpan = span; + animators.clear(); + animators.add(ObjectAnimator.ofFloat(addingSpan, "scaleX", 0.01f, 1.0f)); + animators.add(ObjectAnimator.ofFloat(addingSpan, "scaleY", 0.01f, 1.0f)); + animators.add(ObjectAnimator.ofFloat(addingSpan, "alpha", 0.0f, 1.0f)); + addView(span); + } + + public void removeSpan(final GroupCreateSpan span) { + ignoreScrollEvent = true; + selectedContacts.remove(span.getUid()); + allSpans.remove(span); + span.setOnClickListener(null); + + if (currentAnimation != null) { + currentAnimation.setupEndValues(); + currentAnimation.cancel(); + } + animationStarted = false; + currentAnimation = new AnimatorSet(); + currentAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + removeView(span); + removingSpan = null; + currentAnimation = null; + animationStarted = false; + editText.setAllowDrawCursor(true); + if (allSpans.isEmpty()) { + editText.setHintVisible(true); + } + } + }); + currentAnimation.setDuration(150); + removingSpan = span; + animators.clear(); + animators.add(ObjectAnimator.ofFloat(removingSpan, "scaleX", 1.0f, 0.01f)); + animators.add(ObjectAnimator.ofFloat(removingSpan, "scaleY", 1.0f, 0.01f)); + animators.add(ObjectAnimator.ofFloat(removingSpan, "alpha", 1.0f, 0.0f)); + requestLayout(); + } + } + public GroupCreateActivity() { super(); } @@ -99,6 +295,7 @@ public GroupCreateActivity(Bundle args) { isAlwaysShare = args.getBoolean("isAlwaysShare", false); isNeverShare = args.getBoolean("isNeverShare", false); isGroup = args.getBoolean("isGroup", false); + chatId = args.getInt("chatId"); maxCount = chatType == ChatObject.CHAT_TYPE_CHAT ? MessagesController.getInstance().maxMegagroupCount : MessagesController.getInstance().maxBroadcastCount; } @@ -118,28 +315,52 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatDidCreated); } + @Override + public void onClick(View v) { + GroupCreateSpan span = (GroupCreateSpan) v; + if (span.isDeleting()) { + currentDeletingSpan = null; + spansContainer.removeSpan(span); + updateHint(); + checkVisibleRows(); + } else { + if (currentDeletingSpan != null) { + currentDeletingSpan.cancelDeleteAnimation(); + } + currentDeletingSpan = span; + span.startDeleteAnimation(); + } + } + @Override public View createView(Context context) { searching = false; searchWas = false; + allSpans.clear(); + selectedContacts.clear(); + currentDeletingSpan = null; + doneButtonVisible = chatType == ChatObject.CHAT_TYPE_CHANNEL; actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - if (isAlwaysShare) { - if (isGroup) { - actionBar.setTitle(LocaleController.getString("AlwaysAllow", R.string.AlwaysAllow)); - } else { - actionBar.setTitle(LocaleController.getString("AlwaysShareWithTitle", R.string.AlwaysShareWithTitle)); - } - } else if (isNeverShare) { - if (isGroup) { - actionBar.setTitle(LocaleController.getString("NeverAllow", R.string.NeverAllow)); + if (chatType == ChatObject.CHAT_TYPE_CHANNEL) { + actionBar.setTitle(LocaleController.getString("ChannelAddMembers", R.string.ChannelAddMembers)); + } else { + if (isAlwaysShare) { + if (isGroup) { + actionBar.setTitle(LocaleController.getString("AlwaysAllow", R.string.AlwaysAllow)); + } else { + actionBar.setTitle(LocaleController.getString("AlwaysShareWithTitle", R.string.AlwaysShareWithTitle)); + } + } else if (isNeverShare) { + if (isGroup) { + actionBar.setTitle(LocaleController.getString("NeverAllow", R.string.NeverAllow)); + } else { + actionBar.setTitle(LocaleController.getString("NeverShareWithTitle", R.string.NeverShareWithTitle)); + } } else { - actionBar.setTitle(LocaleController.getString("NeverShareWithTitle", R.string.NeverShareWithTitle)); + actionBar.setTitle(chatType == ChatObject.CHAT_TYPE_CHAT ? LocaleController.getString("NewGroup", R.string.NewGroup) : LocaleController.getString("NewBroadcastList", R.string.NewBroadcastList)); } - } else { - actionBar.setTitle(chatType == ChatObject.CHAT_TYPE_CHAT ? LocaleController.getString("NewGroup", R.string.NewGroup) : LocaleController.getString("NewBroadcastList", R.string.NewBroadcastList)); - actionBar.setSubtitle(LocaleController.formatString("MembersCount", R.string.MembersCount, selectedContacts.size(), maxCount)); } actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @@ -148,220 +369,217 @@ public void onItemClick(int id) { if (id == -1) { finishFragment(); } else if (id == done_button) { - if (selectedContacts.isEmpty()) { - return; - } - ArrayList result = new ArrayList<>(); - result.addAll(selectedContacts.keySet()); - if (isAlwaysShare || isNeverShare) { - if (delegate != null) { - delegate.didSelectUsers(result); - } - finishFragment(); - } else { - Bundle args = new Bundle(); - args.putIntegerArrayList("result", result); - args.putInt("chatType", chatType); - presentFragment(new GroupCreateFinalActivity(args)); - } + onDonePressed(); } } }); + ActionBarMenu menu = actionBar.createMenu(); - menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); - - searchListViewAdapter = new SearchAdapter(context, null, false, false, false, false); - searchListViewAdapter.setCheckedMap(selectedContacts); - searchListViewAdapter.setUseUserCell(true); - listViewAdapter = new ContactsAdapter(context, 1, false, null, false); - listViewAdapter.setCheckedMap(selectedContacts); - - fragmentView = new LinearLayout(context); - LinearLayout linearLayout = (LinearLayout) fragmentView; - linearLayout.setOrientation(LinearLayout.VERTICAL); - - FrameLayout frameLayout = new FrameLayout(context); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); - - userSelectEditText = new EditText(context); - userSelectEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - userSelectEditText.setHintTextColor(0xff979797); - userSelectEditText.setTextColor(0xff212121); - userSelectEditText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_MULTI_LINE); - userSelectEditText.setMinimumHeight(AndroidUtilities.dp(54)); - userSelectEditText.setSingleLine(false); - userSelectEditText.setLines(2); - userSelectEditText.setMaxLines(2); - userSelectEditText.setVerticalScrollBarEnabled(true); - userSelectEditText.setHorizontalScrollBarEnabled(false); - userSelectEditText.setPadding(0, 0, 0, 0); - userSelectEditText.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); - userSelectEditText.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); - AndroidUtilities.clearCursorDrawable(userSelectEditText); - frameLayout.addView(userSelectEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 10, 0, 10, 0)); - - if (isAlwaysShare) { - if (isGroup) { - userSelectEditText.setHint(LocaleController.getString("AlwaysAllowPlaceholder", R.string.AlwaysAllowPlaceholder)); - } else { - userSelectEditText.setHint(LocaleController.getString("AlwaysShareWithPlaceholder", R.string.AlwaysShareWithPlaceholder)); + doneButton = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + if (chatType != ChatObject.CHAT_TYPE_CHANNEL) { + doneButton.setScaleX(0.0f); + doneButton.setScaleY(0.0f); + doneButton.setAlpha(0.0f); + } + + fragmentView = new ViewGroup(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + int maxSize; + if (AndroidUtilities.isTablet() || height > width) { + maxSize = AndroidUtilities.dp(144); + } else { + maxSize = AndroidUtilities.dp(56); + } + + scrollView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.AT_MOST)); + listView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - scrollView.getMeasuredHeight(), MeasureSpec.EXACTLY)); + emptyView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height - scrollView.getMeasuredHeight(), MeasureSpec.EXACTLY)); } - } else if (isNeverShare) { - if (isGroup) { - userSelectEditText.setHint(LocaleController.getString("NeverAllowPlaceholder", R.string.NeverAllowPlaceholder)); - } else { - userSelectEditText.setHint(LocaleController.getString("NeverShareWithPlaceholder", R.string.NeverShareWithPlaceholder)); + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + scrollView.layout(0, 0, scrollView.getMeasuredWidth(), scrollView.getMeasuredHeight()); + listView.layout(0, scrollView.getMeasuredHeight(), listView.getMeasuredWidth(), scrollView.getMeasuredHeight() + listView.getMeasuredHeight()); + emptyView.layout(0, scrollView.getMeasuredHeight(), emptyView.getMeasuredWidth(), scrollView.getMeasuredHeight() + emptyView.getMeasuredHeight()); } - } else { - userSelectEditText.setHint(LocaleController.getString("SendMessageTo", R.string.SendMessageTo)); - } - userSelectEditText.setTextIsSelectable(false); - userSelectEditText.addTextChangedListener(new TextWatcher() { + @Override - public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { - if (!ignoreChange) { - beforeChangeIndex = userSelectEditText.getSelectionStart(); - changeString = new SpannableString(charSequence); + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == listView || child == emptyView) { + parentLayout.drawHeaderShadow(canvas, scrollView.getMeasuredHeight()); } + return result; } + }; + ViewGroup frameLayout = (ViewGroup) fragmentView; + scrollView = new ScrollView(context) { @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { + if (ignoreScrollEvent) { + ignoreScrollEvent = false; + return false; + } + rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY()); + rectangle.top += fieldY + AndroidUtilities.dp(20); + rectangle.bottom += fieldY + AndroidUtilities.dp(50); + return super.requestChildRectangleOnScreen(child, rectangle, immediate); + } + }; + scrollView.setVerticalScrollBarEnabled(false); + AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_windowBackgroundWhite)); + frameLayout.addView(scrollView); + + spansContainer = new SpansContainer(context); + scrollView.addView(spansContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + editText = new EditTextBoldCursor(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (currentDeletingSpan != null) { + currentDeletingSpan.cancelDeleteAnimation(); + currentDeletingSpan = null; + } + return super.onTouchEvent(event); + } + }; + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + editText.setHintColor(Theme.getColor(Theme.key_groupcreate_hintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setCursorColor(Theme.getColor(Theme.key_groupcreate_cursor)); + editText.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + editText.setSingleLine(true); + editText.setBackgroundDrawable(null); + editText.setVerticalScrollBarEnabled(false); + editText.setHorizontalScrollBarEnabled(false); + editText.setTextIsSelectable(false); + editText.setPadding(0, 0, 0, 0); + editText.setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + editText.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_VERTICAL); + spansContainer.addView(editText); + if (chatType == ChatObject.CHAT_TYPE_CHANNEL) { + editText.setHintText(LocaleController.getString("AddMutual", R.string.AddMutual)); + } else { + if (isAlwaysShare) { + if (isGroup) { + editText.setHintText(LocaleController.getString("AlwaysAllowPlaceholder", R.string.AlwaysAllowPlaceholder)); + } else { + editText.setHintText(LocaleController.getString("AlwaysShareWithPlaceholder", R.string.AlwaysShareWithPlaceholder)); + } + } else if (isNeverShare) { + if (isGroup) { + editText.setHintText(LocaleController.getString("NeverAllowPlaceholder", R.string.NeverAllowPlaceholder)); + } else { + editText.setHintText(LocaleController.getString("NeverShareWithPlaceholder", R.string.NeverShareWithPlaceholder)); + } + } else { + editText.setHintText(LocaleController.getString("SendMessageTo", R.string.SendMessageTo)); + } + } + editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() { + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + + } + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; } + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + }); + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override - public void afterTextChanged(Editable editable) { - if (!ignoreChange) { - boolean search = false; - int afterChangeIndex = userSelectEditText.getSelectionEnd(); - if (editable.toString().length() < changeString.toString().length()) { - String deletedString = ""; - try { - deletedString = changeString.toString().substring(afterChangeIndex, beforeChangeIndex); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - if (deletedString.length() > 0) { - if (searching && searchWas) { - search = true; - } - Spannable span = userSelectEditText.getText(); - for (int a = 0; a < allSpans.size(); a++) { - ChipSpan sp = allSpans.get(a); - if (span.getSpanStart(sp) == -1) { - allSpans.remove(sp); - selectedContacts.remove(sp.uid); - } - } - if (!isAlwaysShare && !isNeverShare) { - actionBar.setSubtitle(LocaleController.formatString("MembersCount", R.string.MembersCount, selectedContacts.size(), maxCount)); - } - listView.invalidateViews(); - } else { - search = true; - } - } else { - search = true; - } - if (search) { - String text = userSelectEditText.getText().toString().replace("<", ""); - if (text.length() != 0) { - searching = true; - searchWas = true; - if (listView != null) { - listView.setAdapter(searchListViewAdapter); - searchListViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(false); - listView.setFastScrollEnabled(false); - listView.setVerticalScrollBarEnabled(true); - } - if (emptyTextView != null) { - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - } - searchListViewAdapter.searchDialogs(text); - } else { - searchListViewAdapter.searchDialogs(null); - searching = false; - searchWas = false; - listView.setAdapter(listViewAdapter); - listViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); - listView.setVerticalScrollBarEnabled(false); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - } - } + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + return actionId == EditorInfo.IME_ACTION_DONE && onDonePressed(); + } + }); + editText.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_UP && editText.length() == 0 && !allSpans.isEmpty()) { + spansContainer.removeSpan(allSpans.get(allSpans.size() - 1)); + updateHint(); + checkVisibleRows(); + return true; } + return false; } }); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } - LinearLayout emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.INVISIBLE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - linearLayout.addView(emptyTextLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { @Override - public boolean onTouch(View v, MotionEvent event) { - return true; + public void afterTextChanged(Editable editable) { + if (editText.length() != 0) { + searching = true; + searchWas = true; + adapter.setSearching(true); + itemDecoration.setSearching(true); + adapter.searchDialogs(editText.getText().toString()); + listView.setFastScrollVisible(false); + listView.setVerticalScrollBarEnabled(true); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + } else { + closeSearch(); + } } }); - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - emptyTextLayout.addView(emptyTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); + emptyView = new EmptyTextProgressView(context); + if (ContactsController.getInstance().isLoadingContacts()) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } + emptyView.setShowAtCenter(true); + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + frameLayout.addView(emptyView); - FrameLayout frameLayout2 = new FrameLayout(context); - emptyTextLayout.addView(frameLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); - listView = new LetterSectionsListView(context); - listView.setEmptyView(emptyTextLayout); + listView = new RecyclerListView(context); + listView.setFastScrollEnabled(); + listView.setEmptyView(emptyView); + listView.setAdapter(adapter = new GroupCreateAdapter(context)); + listView.setLayoutManager(linearLayoutManager); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setFastScrollEnabled(true); - listView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY); - listView.setAdapter(listViewAdapter); - listView.setFastScrollAlwaysVisible(true); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); - linearLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); + listView.addItemDecoration(itemDecoration = new GroupCreateDividerItemDecoration()); + frameLayout.addView(listView); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - TLRPC.User user; - if (searching && searchWas) { - user = (TLRPC.User) searchListViewAdapter.getItem(i); - } else { - int section = listViewAdapter.getSectionForPosition(i); - int row = listViewAdapter.getPositionInSectionForPosition(i); - if (row < 0 || section < 0) { - return; - } - user = (TLRPC.User) listViewAdapter.getItem(section, row); + public void onItemClick(View view, int position) { + if (!(view instanceof GroupCreateUserCell)) { + return; } + GroupCreateUserCell cell = (GroupCreateUserCell) view; + TLRPC.User user = cell.getUser(); if (user == null) { return; } - - boolean check = true; - if (selectedContacts.containsKey(user.id)) { - check = false; - try { - ChipSpan span = selectedContacts.get(user.id); - selectedContacts.remove(user.id); - SpannableStringBuilder text = new SpannableStringBuilder(userSelectEditText.getText()); - text.delete(text.getSpanStart(span), text.getSpanEnd(span)); - allSpans.remove(span); - ignoreChange = true; - userSelectEditText.setText(text); - userSelectEditText.setSelection(text.length()); - ignoreChange = false; - } catch (Exception e) { - FileLog.e("tmessages", e); - } + boolean exists; + if (exists = selectedContacts.containsKey(user.id)) { + GroupCreateSpan span = selectedContacts.get(user.id); + spansContainer.removeSpan(span); } else { if (maxCount != 0 && selectedContacts.size() == maxCount) { return; @@ -374,130 +592,530 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { showDialog(builder.create()); return; } - ignoreChange = true; - ChipSpan span = createAndPutChipForUser(user); - span.uid = user.id; - ignoreChange = false; - } - if (!isAlwaysShare && !isNeverShare) { - actionBar.setSubtitle(LocaleController.formatString("MembersCount", R.string.MembersCount, selectedContacts.size(), maxCount)); + MessagesController.getInstance().putUser(user, !searching); + GroupCreateSpan span = new GroupCreateSpan(editText.getContext(), user); + spansContainer.addSpan(span); + span.setOnClickListener(GroupCreateActivity.this); } + updateHint(); if (searching || searchWas) { - ignoreChange = true; - SpannableStringBuilder ssb = new SpannableStringBuilder(""); - for (ImageSpan sp : allSpans) { - ssb.append("<<"); - ssb.setSpan(sp, ssb.length() - 2, ssb.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); - } - userSelectEditText.setText(ssb); - userSelectEditText.setSelection(ssb.length()); - ignoreChange = false; - - searchListViewAdapter.searchDialogs(null); - searching = false; - searchWas = false; - listView.setAdapter(listViewAdapter); - listViewAdapter.notifyDataSetChanged(); - listView.setFastScrollAlwaysVisible(true); - listView.setFastScrollEnabled(true); - listView.setVerticalScrollBarEnabled(false); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + AndroidUtilities.showKeyboard(editText); } else { - if (view instanceof UserCell) { - ((UserCell) view).setChecked(check, true); - } + cell.setChecked(!exists, true); } - } - }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL) { - AndroidUtilities.hideKeyboard(userSelectEditText); - } - if (listViewAdapter != null) { - listViewAdapter.setIsScrolling(i != SCROLL_STATE_IDLE); + if (editText.length() > 0) { + editText.setText(null); } } - + }); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (absListView.isFastScrollEnabled()) { - AndroidUtilities.clearDrawableAnimation(absListView); + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + AndroidUtilities.hideKeyboard(editText); } } }); + updateHint(); return fragmentView; } + @Override + public void onResume() { + super.onResume(); + if (editText != null) { + editText.requestFocus(); + } + } + @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.contactsDidLoaded) { - if (listViewAdapter != null) { - listViewAdapter.notifyDataSetChanged(); + if (emptyView != null) { + emptyView.showTextView(); + } + if (adapter != null) { + adapter.notifyDataSetChanged(); } } else if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; - if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { - updateVisibleRows(mask); + if (listView != null) { + int mask = (Integer) args[0]; + int count = listView.getChildCount(); + if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof GroupCreateUserCell) { + ((GroupCreateUserCell) child).update(mask); + } + } + } } } else if (id == NotificationCenter.chatDidCreated) { removeSelfFromStack(); } } - private void updateVisibleRows(int mask) { - if (listView != null) { - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View child = listView.getChildAt(a); - if (child instanceof UserCell) { - ((UserCell) child).update(mask); + public void setContainerHeight(int value) { + containerHeight = value; + if (spansContainer != null) { + spansContainer.requestLayout(); + } + } + + public int getContainerHeight() { + return containerHeight; + } + + private void checkVisibleRows() { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof GroupCreateUserCell) { + GroupCreateUserCell cell = (GroupCreateUserCell) child; + TLRPC.User user = cell.getUser(); + if (user != null) { + cell.setChecked(selectedContacts.containsKey(user.id), true); } } } } - public void setDelegate(GroupCreateActivityDelegate delegate) { - this.delegate = delegate; + private boolean onDonePressed() { + if (chatType == ChatObject.CHAT_TYPE_CHANNEL) { + ArrayList result = new ArrayList<>(); + for (Integer uid : selectedContacts.keySet()) { + TLRPC.InputUser user = MessagesController.getInputUser(MessagesController.getInstance().getUser(uid)); + if (user != null) { + result.add(user); + } + } + MessagesController.getInstance().addUsersToChannel(chatId, result, null); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); + Bundle args2 = new Bundle(); + args2.putInt("chat_id", chatId); + presentFragment(new ChatActivity(args2), true); + } else { + if (!doneButtonVisible || selectedContacts.isEmpty()) { + return false; + } + ArrayList result = new ArrayList<>(); + result.addAll(selectedContacts.keySet()); + if (isAlwaysShare || isNeverShare) { + if (delegate != null) { + delegate.didSelectUsers(result); + } + finishFragment(); + } else { + Bundle args = new Bundle(); + args.putIntegerArrayList("result", result); + args.putInt("chatType", chatType); + presentFragment(new GroupCreateFinalActivity(args)); + } + } + return true; } - private ChipSpan createAndPutChipForUser(TLRPC.User user) { - LayoutInflater lf = (LayoutInflater) ApplicationLoader.applicationContext.getSystemService(Activity.LAYOUT_INFLATER_SERVICE); - View textView = lf.inflate(R.layout.group_create_bubble, null); - TextView text = (TextView)textView.findViewById(R.id.bubble_text_view); - String name = UserObject.getUserName(user); - if (name.length() == 0 && user.phone != null && user.phone.length() != 0) { - name = PhoneFormat.getInstance().format("+" + user.phone); + private void closeSearch() { + searching = false; + searchWas = false; + itemDecoration.setSearching(false); + adapter.setSearching(false); + adapter.searchDialogs(null); + listView.setFastScrollVisible(true); + listView.setVerticalScrollBarEnabled(false); + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + } + + private void updateHint() { + if (!isAlwaysShare && !isNeverShare) { + if (chatType == ChatObject.CHAT_TYPE_CHANNEL) { + actionBar.setSubtitle(LocaleController.formatPluralString("Members", selectedContacts.size())); + } else { + if (selectedContacts.isEmpty()) { + actionBar.setSubtitle(LocaleController.formatString("MembersCountZero", R.string.MembersCountZero, LocaleController.formatPluralString("Members", maxCount))); + } else { + actionBar.setSubtitle(LocaleController.formatString("MembersCount", R.string.MembersCount, selectedContacts.size(), maxCount)); + } + } } - text.setText(name + ", "); - - int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - textView.measure(spec, spec); - textView.layout(0, 0, textView.getMeasuredWidth(), textView.getMeasuredHeight()); - Bitmap b = Bitmap.createBitmap(textView.getWidth(), textView.getHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(b); - canvas.translate(-textView.getScrollX(), -textView.getScrollY()); - textView.draw(canvas); - textView.setDrawingCacheEnabled(true); - Bitmap cacheBmp = textView.getDrawingCache(); - Bitmap viewBmp = cacheBmp.copy(Bitmap.Config.ARGB_8888, true); - textView.destroyDrawingCache(); - - final BitmapDrawable bmpDrawable = new BitmapDrawable(b); - bmpDrawable.setBounds(0, 0, b.getWidth(), b.getHeight()); - - SpannableStringBuilder ssb = new SpannableStringBuilder(""); - ChipSpan span = new ChipSpan(bmpDrawable, ImageSpan.ALIGN_BASELINE); - allSpans.add(span); - selectedContacts.put(user.id, span); - for (ImageSpan sp : allSpans) { - ssb.append("<<"); - ssb.setSpan(sp, ssb.length() - 2, ssb.length(), SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); + if (chatType != ChatObject.CHAT_TYPE_CHANNEL) { + if (doneButtonVisible && allSpans.isEmpty()) { + if (currentDoneButtonAnimation != null) { + currentDoneButtonAnimation.cancel(); + } + currentDoneButtonAnimation = new AnimatorSet(); + currentDoneButtonAnimation.playTogether(ObjectAnimator.ofFloat(doneButton, "scaleX", 0.0f), + ObjectAnimator.ofFloat(doneButton, "scaleY", 0.0f), + ObjectAnimator.ofFloat(doneButton, "alpha", 0.0f)); + currentDoneButtonAnimation.setDuration(180); + currentDoneButtonAnimation.start(); + doneButtonVisible = false; + } else if (!doneButtonVisible && !allSpans.isEmpty()) { + if (currentDoneButtonAnimation != null) { + currentDoneButtonAnimation.cancel(); + } + currentDoneButtonAnimation = new AnimatorSet(); + currentDoneButtonAnimation.playTogether(ObjectAnimator.ofFloat(doneButton, "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneButton, "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneButton, "alpha", 1.0f)); + currentDoneButtonAnimation.setDuration(180); + currentDoneButtonAnimation.start(); + doneButtonVisible = true; + } } - userSelectEditText.setText(ssb); - userSelectEditText.setSelection(ssb.length()); - return span; + } + + public void setDelegate(GroupCreateActivityDelegate groupCreateActivityDelegate) { + delegate = groupCreateActivityDelegate; + } + + public class GroupCreateAdapter extends RecyclerListView.FastScrollAdapter { + + private Context context; + private ArrayList searchResult = new ArrayList<>(); + private ArrayList searchResultNames = new ArrayList<>(); + private SearchAdapterHelper searchAdapterHelper; + private Timer searchTimer; + private boolean searching; + private ArrayList contacts = new ArrayList<>(); + + public GroupCreateAdapter(Context ctx) { + context = ctx; + + ArrayList arrayList = ContactsController.getInstance().contacts; + for (int a = 0; a < arrayList.size(); a++) { + TLRPC.User user = MessagesController.getInstance().getUser(arrayList.get(a).user_id); + if (user == null || user.self || user.deleted) { + continue; + } + contacts.add(user); + } + + searchAdapterHelper = new SearchAdapterHelper(); + searchAdapterHelper.setDelegate(new SearchAdapterHelper.SearchAdapterHelperDelegate() { + @Override + public void onDataSetChanged() { + notifyDataSetChanged(); + } + + @Override + public void onSetHashtags(ArrayList arrayList, HashMap hashMap) { + + } + }); + } + + public void setSearching(boolean value) { + if (searching == value) { + return; + } + searching = value; + notifyDataSetChanged(); + } + + @Override + public String getLetter(int position) { + if (position < 0 || position >= contacts.size()) { + return null; + } + TLRPC.User user = contacts.get(position); + if (user == null) { + return null; + } + if (LocaleController.nameDisplayOrder == 1) { + if (!TextUtils.isEmpty(user.first_name)) { + return user.first_name.substring(0, 1).toUpperCase(); + } else if (!TextUtils.isEmpty(user.last_name)) { + return user.last_name.substring(0, 1).toUpperCase(); + } + } else { + if (!TextUtils.isEmpty(user.last_name)) { + return user.last_name.substring(0, 1).toUpperCase(); + } else if (!TextUtils.isEmpty(user.first_name)) { + return user.first_name.substring(0, 1).toUpperCase(); + } + } + return ""; + } + + @Override + public int getItemCount() { + if (searching) { + int count = searchResult.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); + if (globalCount != 0) { + count += globalCount + 1; + } + return count; + } + return contacts.size(); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new GroupCreateSectionCell(context); + break; + default: + view = new GroupCreateUserCell(context, true); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + GroupCreateSectionCell cell = (GroupCreateSectionCell) holder.itemView; + if (searching) { + cell.setText(LocaleController.getString("GlobalSearch", R.string.GlobalSearch)); + } + break; + } + default: { + GroupCreateUserCell cell = (GroupCreateUserCell) holder.itemView; + TLRPC.User user; + CharSequence username = null; + CharSequence name = null; + if (searching) { + int localCount = searchResult.size(); + int globalCount = searchAdapterHelper.getGlobalSearch().size(); + if (position >= 0 && position < localCount) { + user = searchResult.get(position); + } else if (position > localCount && position <= globalCount + localCount) { + user = (TLRPC.User) searchAdapterHelper.getGlobalSearch().get(position - localCount - 1); + } else { + user = null; + } + if (user != null) { + if (position < localCount) { + name = searchResultNames.get(position); + if (name != null && !TextUtils.isEmpty(user.username)) { + if (name.toString().startsWith("@" + user.username)) { + username = name; + name = null; + } + } + } else if (position > localCount && !TextUtils.isEmpty(user.username)) { + String foundUserName = searchAdapterHelper.getLastFoundUsername(); + if (foundUserName.startsWith("@")) { + foundUserName = foundUserName.substring(1); + } + try { + username = new SpannableStringBuilder(username); + ((SpannableStringBuilder) username).setSpan(new ForegroundColorSpan(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)), 0, foundUserName.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } catch (Exception e) { + username = user.username; + } + } + } + } else { + user = contacts.get(position); + } + cell.setUser(user, name, username); + cell.setChecked(selectedContacts.containsKey(user.id), false); + break; + } + } + } + + @Override + public int getItemViewType(int position) { + if (searching) { + if (position == searchResult.size()) { + return 0; + } + return 1; + } else { + return 1; + } + } + + @Override + public int getPositionForScrollProgress(float progress) { + return (int) (getItemCount() * progress); + } + + @Override + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == 1) { + ((GroupCreateUserCell) holder.itemView).recycle(); + } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + + public void searchDialogs(final String query) { + try { + if (searchTimer != null) { + searchTimer.cancel(); + } + } catch (Exception e) { + FileLog.e(e); + } + if (query == null) { + searchResult.clear(); + searchResultNames.clear(); + searchAdapterHelper.queryServerSearch(null, false, false, false); + notifyDataSetChanged(); + } else { + searchTimer = new Timer(); + searchTimer.schedule(new TimerTask() { + @Override + public void run() { + try { + searchTimer.cancel(); + searchTimer = null; + } catch (Exception e) { + FileLog.e(e); + } + + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchAdapterHelper.queryServerSearch(query, false, false, false); + Utilities.searchQueue.postRunnable(new Runnable() { + @Override + public void run() { + String search1 = query.trim().toLowerCase(); + if (search1.length() == 0) { + updateSearchResults(new ArrayList(), new ArrayList()); + return; + } + String search2 = LocaleController.getInstance().getTranslitString(search1); + if (search1.equals(search2) || search2.length() == 0) { + search2 = null; + } + String search[] = new String[1 + (search2 != null ? 1 : 0)]; + search[0] = search1; + if (search2 != null) { + search[1] = search2; + } + + ArrayList resultArray = new ArrayList<>(); + ArrayList resultArrayNames = new ArrayList<>(); + + for (int a = 0; a < contacts.size(); a++) { + TLRPC.User user = contacts.get(a); + + String name = ContactsController.formatName(user.first_name, user.last_name).toLowerCase(); + String tName = LocaleController.getInstance().getTranslitString(name); + if (name.equals(tName)) { + tName = null; + } + + int found = 0; + for (String q : search) { + if (name.startsWith(q) || name.contains(" " + q) || tName != null && (tName.startsWith(q) || tName.contains(" " + q))) { + found = 1; + } else if (user.username != null && user.username.startsWith(q)) { + found = 2; + } + + if (found != 0) { + if (found == 1) { + resultArrayNames.add(AndroidUtilities.generateSearchName(user.first_name, user.last_name, q)); + } else { + resultArrayNames.add(AndroidUtilities.generateSearchName("@" + user.username, null, "@" + q)); + } + resultArray.add(user); + break; + } + } + } + updateSearchResults(resultArray, resultArrayNames); + } + }); + } + }); + + } + }, 200, 300); + } + } + + private void updateSearchResults(final ArrayList users, final ArrayList names) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + searchResult = users; + searchResultNames = names; + notifyDataSetChanged(); + } + }); + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof GroupCreateUserCell) { + ((GroupCreateUserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollActive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollInactive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollText), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_groupcreate_hintText), + new ThemeDescription(editText, ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_groupcreate_cursor), + + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GroupCreateSectionCell.class}, null, null, null, Theme.key_graySection), + new ThemeDescription(listView, 0, new Class[]{GroupCreateSectionCell.class}, new String[]{"drawable"}, null, null, null, Theme.key_groupcreate_sectionShadow), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_groupcreate_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_groupcreate_checkboxCheck), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_onlineText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_offlineText), + new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_avatar_backgroundGroupCreateSpanBlue), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_groupcreate_spanBackground), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_groupcreate_spanText), + new ThemeDescription(spansContainer, 0, new Class[]{GroupCreateSpan.class}, null, null, null, Theme.key_avatar_backgroundBlue), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java index 3c3cef49040..9f7eabdc318 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupCreateFinalActivity.java @@ -3,17 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; -import android.app.ProgressDialog; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Vibrator; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; @@ -26,49 +31,62 @@ import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.FileLog; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.ui.Adapters.BaseFragmentAdapter; -import org.telegram.ui.Cells.GreySectionCell; -import org.telegram.ui.Cells.UserCell; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.GroupCreateSectionCell; +import org.telegram.ui.Cells.GroupCreateUserCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.GroupCreateDividerItemDecoration; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.concurrent.Semaphore; public class GroupCreateFinalActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, AvatarUpdater.AvatarUpdaterDelegate { - private ListAdapter listAdapter; - private ListView listView; - private EditText nameTextView; + private GroupCreateAdapter adapter; + private RecyclerView listView; + private EditText editText; + private BackupImageView avatarImage; + private AvatarDrawable avatarDrawable; + private ActionBarMenuItem doneItem; + private ContextProgressView progressView; + private AnimatorSet doneItemAnimation; + private FrameLayout editTextContainer; + private TLRPC.FileLocation avatar; private TLRPC.InputFile uploadedAvatar; private ArrayList selectedContacts; - private BackupImageView avatarImage; - private AvatarDrawable avatarDrawable; private boolean createAfterUpload; private boolean donePressed; private AvatarUpdater avatarUpdater = new AvatarUpdater(); - private ProgressDialog progressDialog = null; - private String nameToSet = null; + private String nameToSet; private int chatType = ChatObject.CHAT_TYPE_CHAT; + private int reqId; + private final static int done_button = 1; public GroupCreateFinalActivity(Bundle args) { @@ -87,7 +105,8 @@ public boolean onFragmentCreate() { avatarUpdater.delegate = this; selectedContacts = getArguments().getIntegerArrayList("result"); final ArrayList usersToLoad = new ArrayList<>(); - for (Integer uid : selectedContacts) { + for (int a = 0; a < selectedContacts.size(); a++) { + Integer uid = selectedContacts.get(a); if (MessagesController.getInstance().getUser(uid) == null) { usersToLoad.add(uid); } @@ -105,7 +124,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (usersToLoad.size() != users.size()) { return false; @@ -128,13 +147,16 @@ public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatDidCreated); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.chatDidFailCreate); avatarUpdater.clear(); + if (reqId != 0) { + ConnectionsManager.getInstance().cancelRequest(reqId, true); + } } @Override public void onResume() { super.onResume(); - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); + if (adapter != null) { + adapter.notifyDataSetChanged(); } } @@ -142,11 +164,7 @@ public void onResume() { public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - if (chatType == ChatObject.CHAT_TYPE_BROADCAST) { - actionBar.setTitle(LocaleController.getString("NewBroadcastList", R.string.NewBroadcastList)); - } else { - actionBar.setTitle(LocaleController.getString("NewGroup", R.string.NewGroup)); - } + actionBar.setTitle(LocaleController.getString("NewGroup", R.string.NewGroup)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override @@ -157,169 +175,146 @@ public void onItemClick(int id) { if (donePressed) { return; } - if (nameTextView.getText().length() == 0) { + if (editText.length() == 0) { + Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(editText, 2, 0); return; } donePressed = true; + AndroidUtilities.hideKeyboard(editText); + editText.setEnabled(false); - if (chatType == ChatObject.CHAT_TYPE_BROADCAST) { - MessagesController.getInstance().createChat(nameTextView.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); + if (avatarUpdater.uploadingAvatar != null) { + createAfterUpload = true; } else { - if (avatarUpdater.uploadingAvatar != null) { - createAfterUpload = true; - } else { - progressDialog = new ProgressDialog(getParentActivity()); - progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); - progressDialog.setCanceledOnTouchOutside(false); - progressDialog.setCancelable(false); - - final int reqId = MessagesController.getInstance().createChat(nameTextView.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); - - progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ConnectionsManager.getInstance().cancelRequest(reqId, true); - donePressed = false; - try { - dialog.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - progressDialog.show(); - } + showEditDoneProgress(true); + reqId = MessagesController.getInstance().createChat(editText.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); } } } }); ActionBarMenu menu = actionBar.createMenu(); - menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + doneItem = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + progressView = new ContextProgressView(context, 1); + doneItem.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + progressView.setVisibility(View.INVISIBLE); - fragmentView = new LinearLayout(context); + fragmentView = new LinearLayout(context) { + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == listView) { + parentLayout.drawHeaderShadow(canvas, editTextContainer.getMeasuredHeight()); + } + return result; + } + }; LinearLayout linearLayout = (LinearLayout) fragmentView; linearLayout.setOrientation(LinearLayout.VERTICAL); - FrameLayout frameLayout = new FrameLayout(context); - linearLayout.addView(frameLayout); - LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) frameLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.TOP | Gravity.LEFT; - frameLayout.setLayoutParams(layoutParams); + editTextContainer = new FrameLayout(context); + linearLayout.addView(editTextContainer, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); avatarImage = new BackupImageView(context); avatarImage.setRoundRadius(AndroidUtilities.dp(32)); avatarDrawable.setInfo(5, null, null, chatType == ChatObject.CHAT_TYPE_BROADCAST); avatarImage.setImageDrawable(avatarDrawable); - frameLayout.addView(avatarImage); - FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) avatarImage.getLayoutParams(); - layoutParams1.width = AndroidUtilities.dp(64); - layoutParams1.height = AndroidUtilities.dp(64); - layoutParams1.topMargin = AndroidUtilities.dp(12); - layoutParams1.bottomMargin = AndroidUtilities.dp(12); - layoutParams1.leftMargin = LocaleController.isRTL ? 0 : AndroidUtilities.dp(16); - layoutParams1.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(16) : 0; - layoutParams1.gravity = Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - avatarImage.setLayoutParams(layoutParams1); - if (chatType != ChatObject.CHAT_TYPE_BROADCAST) { - avatarDrawable.setDrawPhoto(true); - avatarImage.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + editTextContainer.addView(avatarImage, LayoutHelper.createFrame(64, 64, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), LocaleController.isRTL ? 0 : 16, 16, LocaleController.isRTL ? 16 : 0, 16)); - CharSequence[] items; + avatarDrawable.setDrawPhoto(true); + avatarImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - if (avatar != null) { - items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley), LocaleController.getString("DeletePhoto", R.string.DeletePhoto)}; - } else { - items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley)}; - } + CharSequence[] items; - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - if (i == 0) { - avatarUpdater.openCamera(); - } else if (i == 1) { - avatarUpdater.openGallery(); - } else if (i == 2) { - avatar = null; - uploadedAvatar = null; - avatarImage.setImage(avatar, "50_50", avatarDrawable); - } - } - }); - showDialog(builder.create()); + if (avatar != null) { + items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley), LocaleController.getString("DeletePhoto", R.string.DeletePhoto)}; + } else { + items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley)}; } - }); - } - nameTextView = new EditText(context); - nameTextView.setHint(chatType == ChatObject.CHAT_TYPE_CHAT ? LocaleController.getString("EnterGroupNamePlaceholder", R.string.EnterGroupNamePlaceholder) : LocaleController.getString("EnterListName", R.string.EnterListName)); + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (i == 0) { + avatarUpdater.openCamera(); + } else if (i == 1) { + avatarUpdater.openGallery(); + } else if (i == 2) { + avatar = null; + uploadedAvatar = null; + avatarImage.setImage(avatar, "50_50", avatarDrawable); + } + } + }); + showDialog(builder.create()); + } + }); + + editText = new EditText(context); + editText.setHint(chatType == ChatObject.CHAT_TYPE_CHAT ? LocaleController.getString("EnterGroupNamePlaceholder", R.string.EnterGroupNamePlaceholder) : LocaleController.getString("EnterListName", R.string.EnterListName)); if (nameToSet != null) { - nameTextView.setText(nameToSet); + editText.setText(nameToSet); nameToSet = null; } - nameTextView.setMaxLines(4); - nameTextView.setGravity(Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); - nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setHintTextColor(0xff979797); - nameTextView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); - nameTextView.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - nameTextView.setPadding(0, 0, 0, AndroidUtilities.dp(8)); + editText.setMaxLines(4); + editText.setGravity(Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + editText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + editText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + editText.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); + editText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + editText.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + editText.setPadding(0, 0, 0, AndroidUtilities.dp(8)); InputFilter[] inputFilters = new InputFilter[1]; inputFilters[0] = new InputFilter.LengthFilter(100); - nameTextView.setFilters(inputFilters); - AndroidUtilities.clearCursorDrawable(nameTextView); - nameTextView.setTextColor(0xff212121); - frameLayout.addView(nameTextView); - layoutParams1 = (FrameLayout.LayoutParams) nameTextView.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.leftMargin = LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(96); - layoutParams1.rightMargin = LocaleController.isRTL ? AndroidUtilities.dp(96) : AndroidUtilities.dp(16); - layoutParams1.gravity = Gravity.CENTER_VERTICAL; - nameTextView.setLayoutParams(layoutParams1); - if (chatType != ChatObject.CHAT_TYPE_BROADCAST) { - nameTextView.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + editText.setFilters(inputFilters); + AndroidUtilities.clearCursorDrawable(editText); + editTextContainer.addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, LocaleController.isRTL ? 16 : 96, 0, LocaleController.isRTL ? 96 : 16, 0)); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { - } + } - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { - } + } - @Override - public void afterTextChanged(Editable s) { - avatarDrawable.setInfo(5, nameTextView.length() > 0 ? nameTextView.getText().toString() : null, null, false); - avatarImage.invalidate(); - } - }); - } + @Override + public void afterTextChanged(Editable s) { + avatarDrawable.setInfo(5, editText.length() > 0 ? editText.getText().toString() : null, null, false); + avatarImage.invalidate(); + } + }); - GreySectionCell sectionCell = new GreySectionCell(context); - sectionCell.setText(LocaleController.formatPluralString("Members", selectedContacts.size())); - linearLayout.addView(sectionCell); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false); - listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setAdapter(adapter = new GroupCreateAdapter(context)); + listView.setLayoutManager(linearLayoutManager); listView.setVerticalScrollBarEnabled(false); - listView.setAdapter(listAdapter = new ListAdapter(context)); - linearLayout.addView(listView); - layoutParams = (LinearLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? View.SCROLLBAR_POSITION_LEFT : View.SCROLLBAR_POSITION_RIGHT); + listView.addItemDecoration(new GroupCreateDividerItemDecoration()); + linearLayout.addView(listView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + AndroidUtilities.hideKeyboard(editText); + } + } + }); return fragmentView; } @@ -333,8 +328,7 @@ public void run() { avatar = small.location; avatarImage.setImage(avatar, "50_50", avatarDrawable); if (createAfterUpload) { - FileLog.e("tmessages", "avatar did uploaded"); - MessagesController.getInstance().createChat(nameTextView.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); + MessagesController.getInstance().createChat(editText.getText().toString(), selectedContacts, null, chatType, GroupCreateFinalActivity.this); } } }); @@ -350,8 +344,8 @@ public void saveSelfArgs(Bundle args) { if (avatarUpdater != null && avatarUpdater.currentPicturePath != null) { args.putString("path", avatarUpdater.currentPicturePath); } - if (nameTextView != null) { - String text = nameTextView.getText().toString(); + if (editText != null) { + String text = editText.getText().toString(); if (text != null && text.length() != 0) { args.putString("nameTextView", text); } @@ -365,8 +359,8 @@ public void restoreSelfArgs(Bundle args) { } String text = args.getString("nameTextView"); if (text != null) { - if (nameTextView != null) { - nameTextView.setText(text); + if (editText != null) { + editText.setText(text); } else { nameToSet = text; } @@ -376,36 +370,37 @@ public void restoreSelfArgs(Bundle args) { @Override public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen) { - nameTextView.requestFocus(); - AndroidUtilities.showKeyboard(nameTextView); + editText.requestFocus(); + AndroidUtilities.showKeyboard(editText); } } @Override public void didReceivedNotification(int id, final Object... args) { if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; - if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { - updateVisibleRows(mask); + if (listView == null) { + return; } - } else if (id == NotificationCenter.chatDidFailCreate) { - if (progressDialog != null) { - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); + int mask = (Integer) args[0]; + if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof GroupCreateUserCell) { + ((GroupCreateUserCell) child).update(mask); + } } } + } else if (id == NotificationCenter.chatDidFailCreate) { + reqId = 0; donePressed = false; - } else if (id == NotificationCenter.chatDidCreated) { - if (progressDialog != null) { - try { - progressDialog.dismiss(); - } catch (Exception e) { - FileLog.e("tmessages", e); - } + showEditDoneProgress(false); + if (editText != null) { + editText.setEnabled(true); } - int chat_id = (Integer)args[0]; + } else if (id == NotificationCenter.chatDidCreated) { + reqId = 0; + int chat_id = (Integer) args[0]; NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeChats); Bundle args2 = new Bundle(); args2.putInt("chat_id", chat_id); @@ -416,60 +411,187 @@ public void didReceivedNotification(int id, final Object... args) { } } - private void updateVisibleRows(int mask) { - if (listView == null) { + private void showEditDoneProgress(final boolean show) { + if (doneItem == null) { return; } - int count = listView.getChildCount(); - for (int a = 0; a < count; a++) { - View child = listView.getChildAt(a); - if (child instanceof UserCell) { - ((UserCell) child).update(mask); - } + if (doneItemAnimation != null) { + doneItemAnimation.cancel(); + } + doneItemAnimation = new AnimatorSet(); + if (show) { + progressView.setVisibility(View.VISIBLE); + doneItem.setEnabled(false); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); + } else { + doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.setEnabled(true); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + } + doneItemAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + if (!show) { + progressView.setVisibility(View.INVISIBLE); + } else { + doneItem.getImageView().setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + doneItemAnimation = null; + } + } + }); + doneItemAnimation.setDuration(150); + doneItemAnimation.start(); } - private class ListAdapter extends BaseFragmentAdapter { - private Context mContext; + public class GroupCreateAdapter extends RecyclerListView.SelectionAdapter { + + private Context context; - public ListAdapter(Context context) { - mContext = context; + public GroupCreateAdapter(Context ctx) { + context = ctx; } @Override - public boolean areAllItemsEnabled() { - return false; + public int getItemCount() { + return 1 + selectedContacts.size(); } @Override - public boolean isEnabled(int position) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { return false; } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new UserCell(mContext, 1, 0, false); + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new GroupCreateSectionCell(context); + break; + default: + view = new GroupCreateUserCell(context, false); + break; } - - TLRPC.User user = MessagesController.getInstance().getUser(selectedContacts.get(i)); - ((UserCell) view).setData(user, null, null, 0); - return view; + return new RecyclerListView.Holder(view); } @Override - public int getItemViewType(int position) { - return 0; + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + GroupCreateSectionCell cell = (GroupCreateSectionCell) holder.itemView; + cell.setText(LocaleController.formatPluralString("Members", selectedContacts.size())); + break; + } + default: { + GroupCreateUserCell cell = (GroupCreateUserCell) holder.itemView; + TLRPC.User user = MessagesController.getInstance().getUser(selectedContacts.get(position - 1)); + cell.setUser(user, null, null); + break; + } + } } @Override - public int getViewTypeCount() { - return 1; + public int getItemViewType(int position) { + switch (position) { + case 0: + return 0; + default: + return 1; + } } @Override - public int getCount() { - return selectedContacts.size(); + public void onViewRecycled(RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() == 1) { + ((GroupCreateUserCell) holder.itemView).recycle(); + } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof GroupCreateUserCell) { + ((GroupCreateUserCell) child).update(0); + } + } + avatarDrawable.setInfo(5, editText.length() > 0 ? editText.getText().toString() : null, null, false); + avatarImage.invalidate(); + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollActive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollInactive), + new ThemeDescription(listView, ThemeDescription.FLAG_FASTSCROLL, null, null, null, null, Theme.key_fastScrollText), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_groupcreate_hintText), + new ThemeDescription(editText, ThemeDescription.FLAG_CURSORCOLOR, null, null, null, null, Theme.key_groupcreate_cursor), + new ThemeDescription(editText, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(editText, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GroupCreateSectionCell.class}, null, null, null, Theme.key_graySection), + new ThemeDescription(listView, 0, new Class[]{GroupCreateSectionCell.class}, new String[]{"drawable"}, null, null, null, Theme.key_groupcreate_sectionShadow), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{GroupCreateUserCell.class}, new String[]{"textView"}, null, null, null, Theme.key_groupcreate_sectionText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_onlineText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, new Class[]{GroupCreateUserCell.class}, new String[]{"statusTextView"}, null, null, null, Theme.key_groupcreate_offlineText), + new ThemeDescription(listView, 0, new Class[]{GroupCreateUserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressInner2), + new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressOuter2), + + new ThemeDescription(editText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(editText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java index a3ec2c9adb0..c13d35084a3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GroupInviteActivity.java @@ -3,22 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; @@ -29,21 +25,29 @@ import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextBlockCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; public class GroupInviteActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; + private RecyclerListView listView; + private EmptyTextProgressView emptyView; private int chat_id; private boolean loading; @@ -104,29 +108,24 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - FrameLayout progressView = new FrameLayout(context); - frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + emptyView = new EmptyTextProgressView(context); + emptyView.showProgress(); - ProgressBar progressBar = new ProgressBar(context); - progressView.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); - - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setEmptyView(progressView); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setEmptyView(emptyView); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { + public void onItemClick(View view, int position) { if (getParentActivity() == null) { return; } - if (i == copyLinkRow || i == linkRow) { + if (position == copyLinkRow || position == linkRow) { if (invite == null) { return; } @@ -136,9 +135,9 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long clipboard.setPrimaryClip(clip); Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - } else if (i == shareLinkRow) { + } else if (position == shareLinkRow) { if (invite == null) { return; } @@ -148,9 +147,9 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long intent.putExtra(Intent.EXTRA_TEXT, invite.link); getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("InviteToGroupByLink", R.string.InviteToGroupByLink)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - } else if (i == revokeLinkRow) { + } else if (position == revokeLinkRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("RevokeAlert", R.string.RevokeAlert)); builder.setTitle(LocaleController.getString("RevokeLink", R.string.RevokeLink)); @@ -239,7 +238,8 @@ public void run() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -247,97 +247,108 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == revokeLinkRow || position == copyLinkRow || position == shareLinkRow || position == linkRow; } @Override - public boolean isEnabled(int i) { - return i == revokeLinkRow || i == copyLinkRow || i == shareLinkRow || i == linkRow; - } - - @Override - public int getCount() { + public int getItemCount() { return loading ? 0 : rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + break; + case 2: + default: + view = new TextBlockCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == copyLinkRow) { - textCell.setText(LocaleController.getString("CopyLink", R.string.CopyLink), true); - } else if (i == shareLinkRow) { - textCell.setText(LocaleController.getString("ShareLink", R.string.ShareLink), false); - } else if (i == revokeLinkRow) { - textCell.setText(LocaleController.getString("RevokeLink", R.string.RevokeLink), true); - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == shadowRow) { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (i == linkInfoRow) { - TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); - if (ChatObject.isChannel(chat) && !chat.megagroup) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("ChannelLinkInfo", R.string.ChannelLinkInfo)); - } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("LinkInfo", R.string.LinkInfo)); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == copyLinkRow) { + textCell.setText(LocaleController.getString("CopyLink", R.string.CopyLink), true); + } else if (position == shareLinkRow) { + textCell.setText(LocaleController.getString("ShareLink", R.string.ShareLink), false); + } else if (position == revokeLinkRow) { + textCell.setText(LocaleController.getString("RevokeLink", R.string.RevokeLink), true); } - view.setBackgroundResource(R.drawable.greydivider); - } - } else if (type == 2) { - if (view == null) { - view = new TextBlockCell(mContext); - view.setBackgroundColor(0xffffffff); - } - ((TextBlockCell) view).setText(invite != null ? invite.link : "error", false); + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == shadowRow) { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == linkInfoRow) { + TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); + if (ChatObject.isChannel(chat) && !chat.megagroup) { + privacyCell.setText(LocaleController.getString("ChannelLinkInfo", R.string.ChannelLinkInfo)); + } else { + privacyCell.setText(LocaleController.getString("LinkInfo", R.string.LinkInfo)); + } + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + case 2: + TextBlockCell textBlockCell = (TextBlockCell) holder.itemView; + textBlockCell.setText(invite != null ? invite.link : "error", false); + break; } - return view; } @Override - public int getItemViewType(int i) { - if (i == copyLinkRow || i == shareLinkRow || i == revokeLinkRow) { + public int getItemViewType(int position) { + if (position == copyLinkRow || position == shareLinkRow || position == revokeLinkRow) { return 0; - } else if (i == shadowRow || i == linkInfoRow) { + } else if (position == shadowRow || position == linkInfoRow) { return 1; - } else if (i == linkRow) { + } else if (position == linkRow) { return 2; } return 0; } + } - @Override - public int getViewTypeCount() { - return 3; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, TextBlockCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return loading; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{TextBlockCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java index 401a1b00522..5e76bc35d41 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IdenticonActivity.java @@ -3,14 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; import android.content.Context; +import android.graphics.Typeface; import android.os.Bundle; -import android.support.annotation.NonNull; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -22,37 +26,57 @@ import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; +import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; +import org.telegram.messenger.EmojiData; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.IdenticonDrawable; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.URLSpanReplacement; -public class IdenticonActivity extends BaseFragment { +public class IdenticonActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private TextView textView; + private TextView codeTextView; + private TextView emojiTextView; + private FrameLayout container; + private LinearLayout linearLayout1; + private TextView hintTextView; + private LinearLayout linearLayout; private int chat_id; + private AnimatorSet animatorSet; + private AnimatorSet hintAnimatorSet; + + private String emojiText; + private boolean emojiSelected; + private int textWidth; + private static class LinkMovementMethodMy extends LinkMovementMethod { @Override - public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, @NonNull MotionEvent event) { + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { try { return super.onTouchEvent(widget, buffer, event); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -65,9 +89,16 @@ public IdenticonActivity(Bundle args) { @Override public boolean onFragmentCreate() { chat_id = getArguments().getInt("chat_id"); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); return super.onFragmentCreate(); } + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); + } + @Override public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); @@ -83,11 +114,17 @@ public void onItemClick(int id) { } }); - fragmentView = new LinearLayout(context); - LinearLayout linearLayout = (LinearLayout) fragmentView; - linearLayout.setOrientation(LinearLayout.VERTICAL); - linearLayout.setWeightSum(100); - linearLayout.setBackgroundColor(0xfff0f0f0); + fragmentView = new FrameLayout(context) { + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + int x = container.getLeft() + codeTextView.getLeft() + codeTextView.getMeasuredWidth() / 2 - hintTextView.getMeasuredWidth() / 2; + int y = Math.max(AndroidUtilities.dp(5), container.getTop() + codeTextView.getTop() - AndroidUtilities.dp(10)); + hintTextView.layout(x, y, x + hintTextView.getMeasuredWidth(), y + hintTextView.getMeasuredHeight()); + } + }; + FrameLayout parentFrameLayout = (FrameLayout) fragmentView; + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); fragmentView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -95,6 +132,11 @@ public boolean onTouch(View v, MotionEvent event) { } }); + linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setWeightSum(100); + parentFrameLayout.addView(linearLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + FrameLayout frameLayout = new FrameLayout(context); frameLayout.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20), AndroidUtilities.dp(20)); linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 50.0f)); @@ -103,21 +145,65 @@ public boolean onTouch(View v, MotionEvent event) { identiconView.setScaleType(ImageView.ScaleType.FIT_XY); frameLayout.addView(identiconView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - frameLayout = new FrameLayout(context); - frameLayout.setBackgroundColor(0xffffffff); - frameLayout.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), AndroidUtilities.dp(10)); - linearLayout.addView(frameLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 50.0f)); + container = new FrameLayout(context) { + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + if (codeTextView != null) { + int x = codeTextView.getLeft() + codeTextView.getMeasuredWidth() / 2 - emojiTextView.getMeasuredWidth() / 2; + int y = (codeTextView.getMeasuredHeight() - emojiTextView.getMeasuredHeight()) / 2 + linearLayout1.getTop() - AndroidUtilities.dp(16); + emojiTextView.layout(x, y, x + emojiTextView.getMeasuredWidth(), y + emojiTextView.getMeasuredHeight()); + } + } + }; + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + linearLayout.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 50.0f)); + + linearLayout1 = new LinearLayout(context); + linearLayout1.setOrientation(LinearLayout.VERTICAL); + linearLayout1.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + container.addView(linearLayout1, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + + codeTextView = new TextView(context); + codeTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + codeTextView.setGravity(Gravity.CENTER); + codeTextView.setTypeface(Typeface.MONOSPACE); + codeTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + /*codeTextView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + emojiSelected = !emojiSelected; + updateEmojiButton(true); + showHint(false); + } + });*/ + linearLayout1.addView(codeTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); - TextView textView = new TextView(context); - textView.setTextColor(0xff7f7f7f); + hintTextView = new TextView(getParentActivity()); + hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), Theme.getColor(Theme.key_chat_gifSaveHintBackground))); + hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + hintTextView.setPadding(AndroidUtilities.dp(10), 0, AndroidUtilities.dp(10), 0); + hintTextView.setText(LocaleController.getString("TapToEmojify", R.string.TapToEmojify)); + hintTextView.setGravity(Gravity.CENTER_VERTICAL); + hintTextView.setAlpha(0.0f); + parentFrameLayout.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 32)); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + textView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteLinkText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); textView.setLinksClickable(true); textView.setClickable(true); - textView.setMovementMethod(new LinkMovementMethodMy()); - //textView.setAutoLinkMask(Linkify.WEB_URLS); - textView.setLinkTextColor(Theme.MSG_LINK_TEXT_COLOR); textView.setGravity(Gravity.CENTER); - frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + textView.setMovementMethod(new LinkMovementMethodMy()); + linearLayout1.addView(textView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); + + emojiTextView = new TextView(context); + emojiTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4)); + emojiTextView.setGravity(Gravity.CENTER); + emojiTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 32); + container.addView(emojiTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat(chat_id); if (encryptedChat != null) { @@ -126,6 +212,7 @@ public boolean onTouch(View v, MotionEvent event) { drawable.setEncryptedChat(encryptedChat); TLRPC.User user = MessagesController.getInstance().getUser(encryptedChat.user_id); SpannableStringBuilder hash = new SpannableStringBuilder(); + StringBuilder emojis = new StringBuilder(); if (encryptedChat.key_hash.length > 16) { String hex = Utilities.bytesToHex(encryptedChat.key_hash); for (int a = 0; a < 32; a++) { @@ -139,8 +226,18 @@ public boolean onTouch(View v, MotionEvent event) { hash.append(hex.substring(a * 2, a * 2 + 2)); hash.append(' '); } - hash.append("\n\n"); + hash.append("\n"); + for (int a = 0; a < 5; a++) { + int num = ((encryptedChat.key_hash[16 + a * 4] & 0x7f) << 24) | ((encryptedChat.key_hash[16 + a * 4 + 1] & 0xff) << 16) | ((encryptedChat.key_hash[16 + a * 4 + 2] & 0xff) << 8) | (encryptedChat.key_hash[16 + a * 4 + 3] & 0xff); + if (a != 0) { + emojis.append(" "); + } + emojis.append(EmojiData.emojiSecret[num % EmojiData.emojiSecret.length]); + } + emojiText = emojis.toString(); } + codeTextView.setText(hash.toString()); + hash.clear(); hash.append(AndroidUtilities.replaceTags(LocaleController.formatString("EncryptionKeyDescription", R.string.EncryptionKeyDescription, user.first_name, user.first_name))); final String url = "telegram.org"; int index = hash.toString().indexOf(url); @@ -150,6 +247,8 @@ public boolean onTouch(View v, MotionEvent event) { textView.setText(hash); } + updateEmojiButton(false); + return fragmentView; } @@ -165,6 +264,50 @@ public void onResume() { fixLayout(); } + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.emojiDidLoaded) { + if (emojiTextView != null) { + emojiTextView.invalidate(); + } + } + } + + private void updateEmojiButton(boolean animated) { + if (animatorSet != null) { + animatorSet.cancel(); + animatorSet = null; + } + if (animated) { + animatorSet = new AnimatorSet(); + animatorSet.playTogether(ObjectAnimator.ofFloat(emojiTextView, "alpha", emojiSelected ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(codeTextView, "alpha", emojiSelected ? 0.0f : 1.0f), + ObjectAnimator.ofFloat(emojiTextView, "scaleX", emojiSelected ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(emojiTextView, "scaleY", emojiSelected ? 1.0f : 0.0f), + ObjectAnimator.ofFloat(codeTextView, "scaleX", emojiSelected ? 0.0f : 1.0f), + ObjectAnimator.ofFloat(codeTextView, "scaleY", emojiSelected ? 0.0f : 1.0f)); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(animatorSet)) { + animatorSet = null; + } + } + }); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.setDuration(150); + animatorSet.start(); + } else { + emojiTextView.setAlpha(emojiSelected ? 1.0f : 0.0f); + codeTextView.setAlpha(emojiSelected ? 0.0f : 1.0f); + emojiTextView.setScaleX(emojiSelected ? 1.0f : 0.0f); + emojiTextView.setScaleY(emojiSelected ? 1.0f : 0.0f); + codeTextView.setScaleX(emojiSelected ? 0.0f : 1.0f); + codeTextView.setScaleY(emojiSelected ? 0.0f : 1.0f); + } + emojiTextView.setTag(!emojiSelected ? Theme.key_chat_emojiPanelIcon : Theme.key_chat_emojiPanelIconSelected); + } + private void fixLayout() { ViewTreeObserver obs = fragmentView.getViewTreeObserver(); obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @@ -174,14 +317,13 @@ public boolean onPreDraw() { return true; } fragmentView.getViewTreeObserver().removeOnPreDrawListener(this); - LinearLayout layout = (LinearLayout) fragmentView; WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Context.WINDOW_SERVICE); int rotation = manager.getDefaultDisplay().getRotation(); if (rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90) { - layout.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); } else { - layout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setOrientation(LinearLayout.VERTICAL); } fragmentView.setPadding(fragmentView.getPaddingLeft(), 0, fragmentView.getPaddingRight(), fragmentView.getPaddingBottom()); @@ -189,4 +331,63 @@ public boolean onPreDraw() { } }); } + + private void showHint(boolean show) { + /*SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (show) { + if (preferences.getBoolean("secrethint", false)) { + return; + } + } else { + if (hintTextView.getAlpha() == 0.0f) { + return; + } + preferences.edit().putBoolean("secrethint", true).commit(); + } + if (hintAnimatorSet != null) { + hintAnimatorSet.cancel(); + } + hintAnimatorSet = new AnimatorSet(); + hintAnimatorSet.playTogether( + ObjectAnimator.ofFloat(hintTextView, "alpha", show ? 1.0f : 0.0f) + ); + hintAnimatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(hintAnimatorSet)) { + hintAnimatorSet = null; + } + } + }); + hintAnimatorSet.setDuration(300); + hintAnimatorSet.start();*/ + } + + @Override + protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + if (isOpen && !backward && emojiText != null) { + emojiTextView.setText(Emoji.replaceEmoji(emojiText, emojiTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(32), false)); + showHint(true); + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(container, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(codeTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(textView, ThemeDescription.FLAG_LINKCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + + new ThemeDescription(hintTextView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chat_gifSaveHintBackground), + new ThemeDescription(hintTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_chat_gifSaveHintText), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java index 3871a635e91..ca47031f33c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/IntroActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -53,7 +53,6 @@ public class IntroActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.Theme_TMessages); super.onCreate(savedInstanceState); - Theme.loadRecources(this); requestWindowFeature(Window.FEATURE_NO_TITLE); if (AndroidUtilities.isTablet()) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java index 1fc54b735ca..f02211aca36 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LanguageSelectActivity.java @@ -3,50 +3,50 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.Timer; import java.util.TimerTask; public class LanguageSelectActivity extends BaseFragment { - private BaseFragmentAdapter listAdapter; - private ListView listView; + + private ListAdapter listAdapter; + private RecyclerListView listView; + private ListAdapter searchListViewAdapter; + private EmptyTextProgressView emptyView; + private boolean searchWas; private boolean searching; - private BaseFragmentAdapter searchListViewAdapter; - private TextView emptyTextView; private Timer searchTimer; public ArrayList searchResult; @@ -82,7 +82,7 @@ public void onSearchCollapse() { searching = false; searchWas = false; if (listView != null) { - emptyTextView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); listView.setAdapter(listAdapter); } } @@ -101,70 +101,36 @@ public void onTextChanged(EditText editText) { }); item.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); - listAdapter = new ListAdapter(context); - searchListViewAdapter = new SearchAdapter(context); + listAdapter = new ListAdapter(context, false); + searchListViewAdapter = new ListAdapter(context, true); fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; - LinearLayout emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.INVISIBLE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - ((FrameLayout) fragmentView).addView(emptyTextLayout); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) emptyTextLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - emptyTextLayout.setLayoutParams(layoutParams); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + emptyView.showTextView(); + emptyView.setShowAtCenter(true); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - emptyTextLayout.addView(emptyTextView); - LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) emptyTextView.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - emptyTextView.setLayoutParams(layoutParams1); - - FrameLayout frameLayout = new FrameLayout(context); - emptyTextLayout.addView(frameLayout); - layoutParams1 = (LinearLayout.LayoutParams) frameLayout.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - layoutParams1.weight = 0.5f; - frameLayout.setLayoutParams(layoutParams1); - - listView = new ListView(context); - listView.setEmptyView(emptyTextLayout); + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); listView.setAdapter(listAdapter); - ((FrameLayout) fragmentView).addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { LocaleController.LocaleInfo localeInfo = null; if (searching && searchWas) { - if (i >= 0 && i < searchResult.size()) { - localeInfo = searchResult.get(i); + if (position >= 0 && position < searchResult.size()) { + localeInfo = searchResult.get(position); } } else { - if (i >= 0 && i < LocaleController.getInstance().sortedLanguages.size()) { - localeInfo = LocaleController.getInstance().sortedLanguages.get(i); + if (position >= 0 && position < LocaleController.getInstance().sortedLanguages.size()) { + localeInfo = LocaleController.getInstance().sortedLanguages.get(position); } } if (localeInfo != null) { @@ -175,17 +141,17 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { } }); - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { + public boolean onItemClick(View view, int position) { LocaleController.LocaleInfo localeInfo = null; if (searching && searchWas) { - if (i >= 0 && i < searchResult.size()) { - localeInfo = searchResult.get(i); + if (position >= 0 && position < searchResult.size()) { + localeInfo = searchResult.get(position); } } else { - if (i >= 0 && i < LocaleController.getInstance().sortedLanguages.size()) { - localeInfo = LocaleController.getInstance().sortedLanguages.get(i); + if (position >= 0 && position < LocaleController.getInstance().sortedLanguages.size()) { + localeInfo = LocaleController.getInstance().sortedLanguages.get(position); } } if (localeInfo == null || localeInfo.pathToFile == null || getParentActivity() == null) { @@ -217,17 +183,13 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL && searching && searchWas) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } } - - @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - } }); return fragmentView; @@ -250,7 +212,7 @@ public void search(final String query) { searchTimer.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } searchTimer = new Timer(); searchTimer.schedule(new TimerTask() { @@ -260,7 +222,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } processSearch(query); } @@ -302,139 +264,82 @@ public void run() { }); } - private class SearchAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; + private boolean search; - public SearchAdapter(Context context) { + public ListAdapter(Context context, boolean isSearch) { mContext = context; + search = isSearch; } @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { return true; } @Override - public int getCount() { - if (searchResult == null) { - return 0; + public int getItemCount() { + if (search) { + if (searchResult == null) { + return 0; + } + return searchResult.size(); + } else { + if (LocaleController.getInstance().sortedLanguages == null) { + return 0; + } + return LocaleController.getInstance().sortedLanguages.size(); } - return searchResult.size(); - } - - @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; } @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new RecyclerListView.Holder(new TextSettingsCell(mContext)); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new TextSettingsCell(mContext); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + TextSettingsCell textSettingsCell = (TextSettingsCell) holder.itemView; + LocaleController.LocaleInfo localeInfo; + boolean last; + if (search) { + localeInfo = searchResult.get(position); + last = position == searchResult.size() - 1; + } else { + localeInfo = LocaleController.getInstance().sortedLanguages.get(position); + last = position == LocaleController.getInstance().sortedLanguages.size() - 1; } - - LocaleController.LocaleInfo c = searchResult.get(i); - ((TextSettingsCell) view).setText(c.name, i != searchResult.size() - 1); - - return view; + textSettingsCell.setText(localeInfo.name, !last); } @Override public int getItemViewType(int i) { return 0; } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return searchResult == null || searchResult.size() == 0; - } } - private class ListAdapter extends BaseFragmentAdapter { - private Context mContext; - - public ListAdapter(Context context) { - mContext = context; - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { - return true; - } - - @Override - public int getCount() { - if (LocaleController.getInstance().sortedLanguages == null) { - return 0; - } - return LocaleController.getInstance().sortedLanguages.size(); - } - - @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new TextSettingsCell(mContext); - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), - LocaleController.LocaleInfo localeInfo = LocaleController.getInstance().sortedLanguages.get(i); - ((TextSettingsCell) view).setText(localeInfo.name, i != LocaleController.getInstance().sortedLanguages.size() - 1); + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), - return view; - } + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), - @Override - public int getItemViewType(int i) { - return 0; - } - - @Override - public int getViewTypeCount() { - return 1; - } + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), - @Override - public boolean isEmpty() { - return LocaleController.getInstance().sortedLanguages == null || LocaleController.getInstance().sortedLanguages.size() == 0; - } + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index f54fa86d866..0b3533d508a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -3,15 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.DialogInterface; import android.content.Intent; @@ -31,21 +29,20 @@ import android.text.TextUtils; import android.view.ActionMode; import android.view.KeyEvent; +import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.ChatObject; import org.telegram.messenger.ContactsController; import org.telegram.messenger.ImageLoader; @@ -63,20 +60,25 @@ import org.telegram.messenger.R; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.DraftQuery; +import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.Adapters.DrawerLayoutAdapter; import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.DrawerLayoutContainer; +import org.telegram.ui.Components.EmbedBottomSheet; import org.telegram.ui.Components.JoinGroupAlert; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PasscodeView; +import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.ThemeEditorView; import java.io.BufferedReader; import java.io.InputStream; @@ -113,7 +115,7 @@ public class LaunchActivity extends Activity implements ActionBarLayout.ActionBa private DrawerLayoutAdapter drawerLayoutAdapter; private PasscodeView passcodeView; private AlertDialog visibleDialog; - private ListView listView; + private RecyclerListView sideMenu; private Intent passcodeSaveIntent; private boolean passcodeSaveIntentIsNew; @@ -152,13 +154,19 @@ protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); setTheme(R.style.Theme_TMessages); getWindow().setBackgroundDrawableResource(R.drawable.transparent); + if (UserConfig.passcodeHash.length() > 0 && !UserConfig.allowScreenCapture) { + try { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } catch (Exception e) { + FileLog.e(e); + } + } super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= 24) { AndroidUtilities.isInMultiwindow = isInMultiWindowMode(); } - Theme.loadRecources(this); - + Theme.createChatResources(this, false); if (UserConfig.passcodeHash.length() != 0 && UserConfig.appLocked) { UserConfig.lastPauseTime = ConnectionsManager.getInstance().getCurrentTime(); } @@ -210,7 +218,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } backgroundTablet.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); shadowTablet.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - layersActionBarLayout.measure(MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), width), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(528) - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0), height), MeasureSpec.EXACTLY)); + layersActionBarLayout.measure(MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(530), width), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(528), height), MeasureSpec.EXACTLY)); inLayout = false; } @@ -232,7 +240,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { actionBarLayout.layout(0, 0, actionBarLayout.getMeasuredWidth(), actionBarLayout.getMeasuredHeight()); } int x = (width - layersActionBarLayout.getMeasuredWidth()) / 2; - int y = (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0) + (height - layersActionBarLayout.getMeasuredHeight()) / 2; + int y = (height - layersActionBarLayout.getMeasuredHeight()) / 2; layersActionBarLayout.layout(x, y, x + layersActionBarLayout.getMeasuredWidth(), y + layersActionBarLayout.getMeasuredHeight()); backgroundTablet.layout(0, 0, backgroundTablet.getMeasuredWidth(), backgroundTablet.getMeasuredHeight()); shadowTablet.layout(0, 0, shadowTablet.getMeasuredWidth(), shadowTablet.getMeasuredHeight()); @@ -259,7 +267,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { shadowTablet = new FrameLayout(this); shadowTablet.setVisibility(layerFragmentsStack.isEmpty() ? View.GONE : View.VISIBLE); - shadowTablet.setBackgroundColor(0x7F000000); + shadowTablet.setBackgroundColor(0x7f000000); launchLayout.addView(shadowTablet); shadowTablet.setOnTouchListener(new View.OnTouchListener() { @Override @@ -310,40 +318,32 @@ public void onClick(View v) { drawerLayoutContainer.addView(actionBarLayout, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } - listView = new ListView(this) { - @Override - public boolean hasOverlappingRendering() { - return false; - } - }; - listView.setBackgroundColor(0xffffffff); - listView.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this)); - listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setVerticalScrollBarEnabled(false); - drawerLayoutContainer.setDrawerLayout(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); + sideMenu = new RecyclerListView(this); + sideMenu.setBackgroundColor(Theme.getColor(Theme.key_chats_menuBackground)); + sideMenu.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); + sideMenu.setAdapter(drawerLayoutAdapter = new DrawerLayoutAdapter(this)); + drawerLayoutContainer.setDrawerLayout(sideMenu); + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) sideMenu.getLayoutParams(); Point screenSize = AndroidUtilities.getRealScreenSize(); layoutParams.width = AndroidUtilities.isTablet() ? AndroidUtilities.dp(320) : Math.min(AndroidUtilities.dp(320), Math.min(screenSize.x, screenSize.y) - AndroidUtilities.dp(56)); layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + sideMenu.setLayoutParams(layoutParams); + sideMenu.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { + public void onItemClick(View view, int position) { + int id = drawerLayoutAdapter.getId(position); if (position == 0) { Bundle args = new Bundle(); args.putInt("user_id", UserConfig.getClientUserId()); presentFragment(new ChatActivity(args)); drawerLayoutContainer.closeDrawer(false); - } else if (position == 2) { + } else if (id == 2) { if (!MessagesController.isFeatureEnabled("chat_create", actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1))) { return; } presentFragment(new GroupCreateActivity()); drawerLayoutContainer.closeDrawer(false); - } else if (position == 3) { + } else if (id == 3) { Bundle args = new Bundle(); args.putBoolean("onlyUsers", true); args.putBoolean("destroyAfterSelect", true); @@ -351,12 +351,12 @@ public void onItemClick(AdapterView parent, View view, int position, long id) args.putBoolean("allowBots", false); presentFragment(new ContactsActivity(args)); drawerLayoutContainer.closeDrawer(false); - } else if (position == 4) { + } else if (id == 4) { if (!MessagesController.isFeatureEnabled("broadcast_create", actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1))) { return; } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (preferences.getBoolean("channel_intro", false)) { + if (!BuildVars.DEBUG_VERSION && preferences.getBoolean("channel_intro", false)) { Bundle args = new Bundle(); args.putInt("step", 0); presentFragment(new ChannelCreateActivity(args)); @@ -365,25 +365,28 @@ public void onItemClick(AdapterView parent, View view, int position, long id) preferences.edit().putBoolean("channel_intro", true).commit(); } drawerLayoutContainer.closeDrawer(false); - } else if (position == 6) { + } else if (id == 6) { presentFragment(new ContactsActivity(null)); drawerLayoutContainer.closeDrawer(false); - } else if (position == 7) { + } else if (id == 7) { try { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, ContactsController.getInstance().getInviteText()); startActivityForResult(Intent.createChooser(intent, LocaleController.getString("InviteFriends", R.string.InviteFriends)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } drawerLayoutContainer.closeDrawer(false); - } else if (position == 8) { + } else if (id == 8) { presentFragment(new SettingsActivity()); drawerLayoutContainer.closeDrawer(false); - } else if (position == 9) { + } else if (id == 9) { Browser.openUrl(LaunchActivity.this, LocaleController.getString("TelegramFaqUrl", R.string.TelegramFaqUrl)); drawerLayoutContainer.closeDrawer(false); + } else if (id == 10) { + presentFragment(new CallLogActivity()); + drawerLayoutContainer.closeDrawer(false); } } }); @@ -393,14 +396,10 @@ public void onItemClick(AdapterView parent, View view, int position, long id) actionBarLayout.init(mainFragmentsStack); actionBarLayout.setDelegate(this); - ApplicationLoader.loadWallpaper(); + Theme.loadWallpaper(); passcodeView = new PasscodeView(this); - drawerLayoutContainer.addView(passcodeView); - FrameLayout.LayoutParams layoutParams1 = (FrameLayout.LayoutParams) passcodeView.getLayoutParams(); - layoutParams1.width = LayoutHelper.MATCH_PARENT; - layoutParams1.height = LayoutHelper.MATCH_PARENT; - passcodeView.setLayoutParams(layoutParams1); + drawerLayoutContainer.addView(passcodeView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); NotificationCenter.getInstance().postNotificationName(NotificationCenter.closeOtherAppActivities, this); currentConnectionState = ConnectionsManager.getInstance().getConnectionState(); @@ -412,13 +411,16 @@ public void onItemClick(AdapterView parent, View view, int position, long id) NotificationCenter.getInstance().addObserver(this, NotificationCenter.needShowAlert); NotificationCenter.getInstance().addObserver(this, NotificationCenter.wasUnableToFindCurrentLocation); NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetNewWallpapper); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetPasscode); if (actionBarLayout.fragmentsStack.isEmpty()) { if (!UserConfig.isClientActivated()) { actionBarLayout.addFragmentToStack(new LoginActivity()); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { - actionBarLayout.addFragmentToStack(new DialogsActivity(null)); + DialogsActivity dialogsActivity = new DialogsActivity(null); + dialogsActivity.setSideMenu(sideMenu); + actionBarLayout.addFragmentToStack(dialogsActivity); drawerLayoutContainer.setAllowOpenDrawer(true, false); } @@ -484,9 +486,13 @@ public void onItemClick(AdapterView parent, View view, int position, long id) } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { + BaseFragment fragment = actionBarLayout.fragmentsStack.get(0); + if (fragment instanceof DialogsActivity) { + ((DialogsActivity) fragment).setSideMenu(sideMenu); + } boolean allowOpen = true; if (AndroidUtilities.isTablet()) { allowOpen = actionBarLayout.fragmentsStack.size() <= 1 && layersActionBarLayout.fragmentsStack.isEmpty(); @@ -528,26 +534,30 @@ public void onGlobalLayout() { } if (height > AndroidUtilities.dp(100) && height < AndroidUtilities.displaySize.y && height + AndroidUtilities.dp(100) > AndroidUtilities.displaySize.y) { AndroidUtilities.displaySize.y = height; - FileLog.e("tmessages", "fix display size y to " + AndroidUtilities.displaySize.y); + FileLog.e("fix display size y to " + AndroidUtilities.displaySize.y); } } }); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } private void checkLayout() { - if (!AndroidUtilities.isTablet()) { + if (!AndroidUtilities.isTablet() || rightActionBarLayout == null) { return; } + if (!AndroidUtilities.isInMultiwindow && (!AndroidUtilities.isSmallTablet() || getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)) { tabletFullSize = false; if (actionBarLayout.fragmentsStack.size() >= 2) { for (int a = 1; a < actionBarLayout.fragmentsStack.size(); a++) { BaseFragment chatFragment = actionBarLayout.fragmentsStack.get(a); + if (chatFragment instanceof ChatActivity) { + ((ChatActivity) chatFragment).setIgnoreAttachOnPause(true); + } chatFragment.onPause(); actionBarLayout.fragmentsStack.remove(a); rightActionBarLayout.fragmentsStack.add(chatFragment); @@ -566,6 +576,9 @@ private void checkLayout() { if (!rightActionBarLayout.fragmentsStack.isEmpty()) { for (int a = 0; a < rightActionBarLayout.fragmentsStack.size(); a++) { BaseFragment chatFragment = rightActionBarLayout.fragmentsStack.get(a); + if (chatFragment instanceof ChatActivity) { + ((ChatActivity) chatFragment).setIgnoreAttachOnPause(true); + } chatFragment.onPause(); rightActionBarLayout.fragmentsStack.remove(a); actionBarLayout.fragmentsStack.add(chatFragment); @@ -588,6 +601,8 @@ private void showPasscodeActivity() { UserConfig.appLocked = true; if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(false, true); + } else if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(false, true); } passcodeView.onShow(); UserConfig.isWaitingForPasscodeEnter = true; @@ -629,7 +644,9 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool Integer push_user_id = 0; Integer push_chat_id = 0; Integer push_enc_id = 0; + Integer push_msg_id = 0; Integer open_settings = 0; + Integer open_new_dialog = 0; long dialogId = intent != null && intent.getExtras() != null ? intent.getExtras().getLong("dialogId", 0) : 0; boolean showDialogsList = false; boolean showPlayer = false; @@ -660,7 +677,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); String line; while ((line = bufferedReader.readLine()) != null) { - FileLog.e("tmessages", line); + FileLog.e(line); String[] args = line.split(":"); if (args.length != 2) { continue; @@ -717,7 +734,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool bufferedReader.close(); stream.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } for (int a = 0; a < vcardDatas.size(); a++) { VcardData vcardData = vcardDatas.get(a); @@ -741,7 +758,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool error = true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); error = true; } } else { @@ -877,7 +894,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool error = true; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); error = true; } if (error) { @@ -901,7 +918,7 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool if (scheme != null) { if ((scheme.equals("http") || scheme.equals("https"))) { String host = data.getHost().toLowerCase(); - if (host.equals("telegram.me") || host.equals("telegram.dog")) { + if (host.equals("telegram.me") || host.equals("t.me") || host.equals("telegram.dog")) { String path = data.getPath(); if (path != null && path.length() > 1) { path = path.substring(1); @@ -991,9 +1008,33 @@ private boolean handleIntent(Intent intent, boolean isNew, boolean restore, bool } else if (url.startsWith("tg:confirmphone") || url.startsWith("tg://confirmphone")) { phone = data.getQueryParameter("phone"); phoneHash = data.getQueryParameter("hash"); + } else if (url.startsWith("tg:openmessage") || url.startsWith("tg://openmessage")) { + String userID = data.getQueryParameter("user_id"); + String chatID = data.getQueryParameter("chat_id"); + String msgID = data.getQueryParameter("message_id"); + if (userID != null) { + try { + push_user_id = Integer.parseInt(userID); + } catch (NumberFormatException ignore) { + } + } else if (chatID != null) { + try { + push_chat_id = Integer.parseInt(chatID); + } catch (NumberFormatException ignore) { + } + } + if (msgID != null) { + try { + push_msg_id = Integer.parseInt(msgID); + } catch (NumberFormatException ignore) { + } + } } } } + if (message != null && message.startsWith("@")) { + message = " " + message; + } if (phone != null || phoneHash != null) { final Bundle args = new Bundle(); args.putString("phone", phone); @@ -1018,12 +1059,14 @@ public void run() { cursor.close(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } } else if (intent.getAction().equals("org.telegram.messenger.OPEN_ACCOUNT")) { open_settings = 1; + } else if (intent.getAction().equals("new_dialog")) { + open_new_dialog = 1; } else if (intent.getAction().startsWith("com.tmessages.openchat")) { int chatId = intent.getIntExtra("chatId", 0); int userId = intent.getIntExtra("userId", 0); @@ -1049,6 +1092,8 @@ public void run() { if (push_user_id != 0) { Bundle args = new Bundle(); args.putInt("user_id", push_user_id); + if (push_msg_id != 0) + args.putInt("message_id", push_msg_id); if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { ChatActivity fragment = new ChatActivity(args); if (actionBarLayout.presentFragment(fragment, false, true, true)) { @@ -1058,6 +1103,8 @@ public void run() { } else if (push_chat_id != 0) { Bundle args = new Bundle(); args.putInt("chat_id", push_chat_id); + if (push_msg_id != 0) + args.putInt("message_id", push_msg_id); if (mainFragmentsStack.isEmpty() || MessagesController.checkCanOpenChat(args, mainFragmentsStack.get(mainFragmentsStack.size() - 1))) { ChatActivity fragment = new ChatActivity(args); if (actionBarLayout.presentFragment(fragment, false, true, true)) { @@ -1135,6 +1182,8 @@ public void run() { pushOpened = true; if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(false, true); + } else if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(false, true); } drawerLayoutContainer.setAllowOpenDrawer(false, false); @@ -1157,6 +1206,18 @@ public void run() { drawerLayoutContainer.setAllowOpenDrawer(true, false); } pushOpened = true; + } else if (open_new_dialog != 0) { + Bundle args = new Bundle(); + args.putBoolean("destroyAfterSelect", true); + actionBarLayout.presentFragment(new ContactsActivity(args), false, true, true); + if (AndroidUtilities.isTablet()) { + actionBarLayout.showLastFragment(); + rightActionBarLayout.showLastFragment(); + drawerLayoutContainer.setAllowOpenDrawer(false, false); + } else { + drawerLayoutContainer.setAllowOpenDrawer(true, false); + } + pushOpened = true; } if (!pushOpened && !isNew) { @@ -1168,7 +1229,9 @@ public void run() { } } else { if (actionBarLayout.fragmentsStack.isEmpty()) { - actionBarLayout.addFragmentToStack(new DialogsActivity(null)); + DialogsActivity dialogsActivity = new DialogsActivity(null); + dialogsActivity.setSideMenu(sideMenu); + actionBarLayout.addFragmentToStack(dialogsActivity); drawerLayoutContainer.setAllowOpenDrawer(true, false); } } @@ -1178,7 +1241,9 @@ public void run() { actionBarLayout.addFragmentToStack(new LoginActivity()); drawerLayoutContainer.setAllowOpenDrawer(false, false); } else { - actionBarLayout.addFragmentToStack(new DialogsActivity(null)); + DialogsActivity dialogsActivity = new DialogsActivity(null); + dialogsActivity.setSideMenu(sideMenu); + actionBarLayout.addFragmentToStack(dialogsActivity); drawerLayoutContainer.setAllowOpenDrawer(true, false); } } @@ -1197,7 +1262,7 @@ public void run() { } private void runLinkRequest(final String username, final String group, final String sticker, final String botUser, final String botChat, final String message, final boolean hasUrl, final Integer messageId, final String game, final int state) { - final ProgressDialog progressDialog = new ProgressDialog(this); + final AlertDialog progressDialog = new AlertDialog(this, 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -1216,7 +1281,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } final TLRPC.TL_contacts_resolvedPeer res = (TLRPC.TL_contacts_resolvedPeer) response; if (error == null && actionBarLayout != null && (game == null || game != null && !res.users.isEmpty())) { @@ -1272,6 +1337,8 @@ public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean pa actionBarLayout.presentFragment(fragment, removeLast, true, true); if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(false, true); + } else if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(false, true); } drawerLayoutContainer.setAllowOpenDrawer(false, false); if (AndroidUtilities.isTablet()) { @@ -1286,7 +1353,7 @@ public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean pa try { Toast.makeText(LaunchActivity.this, LocaleController.getString("BotCantJoinGroups", R.string.BotCantJoinGroups), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return; } @@ -1342,7 +1409,7 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { try { Toast.makeText(LaunchActivity.this, LocaleController.getString("NoUsernameFound", R.string.NoUsernameFound), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1364,7 +1431,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (error == null && actionBarLayout != null) { TLRPC.ChatInvite invite = (TLRPC.ChatInvite) response; @@ -1435,7 +1502,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (error == null) { if (actionBarLayout != null) { @@ -1526,7 +1593,7 @@ public void onClick(DialogInterface dialog, int which) { try { dialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1541,7 +1608,7 @@ public AlertDialog showAlertDialog(AlertDialog.Builder builder) { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { visibleDialog = builder.show(); @@ -1554,7 +1621,7 @@ public void onDismiss(DialogInterface dialog) { }); return visibleDialog; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -1568,8 +1635,8 @@ protected void onNewIntent(Intent intent) { @Override public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boolean param) { if (dialog_id != 0) { - int lower_part = (int)dialog_id; - int high_id = (int)(dialog_id >> 32); + int lower_part = (int) dialog_id; + int high_id = (int) (dialog_id >> 32); Bundle args = new Bundle(); args.putBoolean("scrollToTopOnResume", true); @@ -1595,16 +1662,25 @@ public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boo ChatActivity fragment = new ChatActivity(args); if (videoPath != null) { - if(android.os.Build.VERSION.SDK_INT >= 16) { + if (Build.VERSION.SDK_INT >= 16) { if (AndroidUtilities.isTablet()) { - actionBarLayout.presentFragment(fragment, false, true, true); + if (tabletFullSize) { + actionBarLayout.presentFragment(fragment, false, true, false); + } else { + rightActionBarLayout.removeAllFragments(); + rightActionBarLayout.addFragmentToStack(fragment); + rightActionBarLayout.setVisibility(View.VISIBLE); + rightActionBarLayout.showLastFragment(); + } } else { - actionBarLayout.addFragmentToStack(fragment, actionBarLayout.fragmentsStack.size() - 1); + actionBarLayout.addFragmentToStack(fragment, dialogsFragment != null ? actionBarLayout.fragmentsStack.size() - 1 : actionBarLayout.fragmentsStack.size()); } - if (!fragment.openVideoEditor(videoPath, dialogsFragment != null, false) && dialogsFragment != null) { - if (!AndroidUtilities.isTablet()) { + if (!fragment.openVideoEditor(videoPath, dialogsFragment != null, false) && !AndroidUtilities.isTablet()) { + if (dialogsFragment != null) { dialogsFragment.finishFragment(true); + } else { + actionBarLayout.showLastFragment(); } } } else { @@ -1621,7 +1697,7 @@ public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boo captions.add(sendingText); sendingText = null; } - SendMessagesHelper.prepareSendingPhotos(null, photoPathsArray, dialog_id, null, captions, null); + SendMessagesHelper.prepareSendingPhotos(null, photoPathsArray, dialog_id, null, captions, null, null); } if (sendingText != null) { @@ -1629,7 +1705,7 @@ public void didSelectDialog(DialogsActivity dialogsFragment, long dialog_id, boo } if (documentsPathsArray != null || documentsUrisArray != null) { - SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, documentsMimeType, dialog_id, null); + SendMessagesHelper.prepareSendingDocuments(documentsPathsArray, documentsOriginalPathsArray, documentsUrisArray, documentsMimeType, dialog_id, null, null); } if (contactsToSend != null && !contactsToSend.isEmpty()) { for (TLRPC.User user : contactsToSend) { @@ -1663,6 +1739,7 @@ private void onFinish() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.needShowAlert); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.wasUnableToFindCurrentLocation); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetNewWallpapper); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetPasscode); } public void presentFragment(BaseFragment fragment) { @@ -1673,6 +1750,18 @@ public boolean presentFragment(final BaseFragment fragment, final boolean remove return actionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, true); } + public ActionBarLayout getActionBarLayout() { + return actionBarLayout; + } + + public ActionBarLayout getLayersActionBarLayout() { + return layersActionBarLayout; + } + + public ActionBarLayout getRightActionBarLayout() { + return rightActionBarLayout; + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (UserConfig.passcodeHash.length() != 0 && UserConfig.lastPauseTime != 0) { @@ -1680,6 +1769,10 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { UserConfig.saveConfig(false); } super.onActivityResult(requestCode, resultCode, data); + ThemeEditorView editorView = ThemeEditorView.getInstance(); + if (editorView != null) { + editorView.onActivityResult(requestCode, resultCode, data); + } if (actionBarLayout.fragmentsStack.size() != 0) { BaseFragment fragment = actionBarLayout.fragmentsStack.get(actionBarLayout.fragmentsStack.size() - 1); fragment.onActivityResultFragment(requestCode, resultCode, data); @@ -1735,7 +1828,7 @@ public void onClick(DialogInterface dialog, int which) { intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); startActivity(intent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1768,6 +1861,13 @@ public void onClick(DialogInterface dialog, int which) { protected void onPause() { super.onPause(); ApplicationLoader.mainInterfacePaused = true; + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + ApplicationLoader.mainInterfacePausedStageQueue = true; + ApplicationLoader.mainInterfacePausedStageQueueTime = 0; + } + }); onPasscodePause(); actionBarLayout.onPause(); if (AndroidUtilities.isTablet()) { @@ -1800,14 +1900,24 @@ protected void onStop() { protected void onDestroy() { PhotoViewer.getInstance().destroyPhotoViewer(); SecretPhotoViewer.getInstance().destroyPhotoViewer(); + ArticleViewer.getInstance().destroyArticleViewer(); StickerPreviewViewer.getInstance().destroy(); + Theme.destroyResources(); + EmbedBottomSheet embedBottomSheet = EmbedBottomSheet.getInstance(); + if (embedBottomSheet != null) { + embedBottomSheet.destroy(); + } + ThemeEditorView editorView = ThemeEditorView.getInstance(); + if (editorView != null) { + editorView.destroy(); + } try { if (visibleDialog != null) { visibleDialog.dismiss(); visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { if (onGlobalLayoutListener != null) { @@ -1819,7 +1929,7 @@ protected void onDestroy() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } super.onDestroy(); onFinish(); @@ -1829,6 +1939,13 @@ protected void onDestroy() { protected void onResume() { super.onResume(); ApplicationLoader.mainInterfacePaused = false; + Utilities.stageQueue.postRunnable(new Runnable() { + @Override + public void run() { + ApplicationLoader.mainInterfacePausedStageQueue = false; + ApplicationLoader.mainInterfacePausedStageQueueTime = System.currentTimeMillis(); + } + }); onPasscodeResume(); if (passcodeView.getVisibility() != View.VISIBLE) { actionBarLayout.onResume(); @@ -1858,6 +1975,14 @@ public void onConfigurationChanged(Configuration newConfig) { AndroidUtilities.checkDisplaySize(this, newConfig); super.onConfigurationChanged(newConfig); checkLayout(); + EmbedBottomSheet embedBottomSheet = EmbedBottomSheet.getInstance(); + if (embedBottomSheet != null) { + embedBottomSheet.onConfigurationChanged(newConfig); + } + ThemeEditorView editorView = ThemeEditorView.getInstance(); + if (editorView != null) { + editorView.onConfigurationChanged(); + } } @Override @@ -1899,7 +2024,7 @@ public void didReceivedNotification(int id, Object... args) { } else if (id == NotificationCenter.didUpdatedConnectionState) { int state = ConnectionsManager.getInstance().getConnectionState(); if (currentConnectionState != state) { - FileLog.d("tmessages", "switch to state " + state); + FileLog.d("switch to state " + state); currentConnectionState = state; updateCurrentConnectionState(); } @@ -1963,12 +2088,26 @@ public void didSelectLocation(TLRPC.MessageMedia location) { mainFragmentsStack.get(mainFragmentsStack.size() - 1).showDialog(builder.create()); } } else if (id == NotificationCenter.didSetNewWallpapper) { - if (listView != null) { - View child = listView.getChildAt(0); + if (sideMenu != null) { + View child = sideMenu.getChildAt(0); if (child != null) { child.invalidate(); } } + } else if (id == NotificationCenter.didSetPasscode) { + if (UserConfig.passcodeHash.length() > 0 && !UserConfig.allowScreenCapture) { + try { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } catch (Exception e) { + FileLog.e(e); + } + } else { + try { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } catch (Exception e) { + FileLog.e(e); + } + } } } @@ -1984,10 +2123,10 @@ private void onPasscodePause() { public void run() { if (lockRunnable == this) { if (AndroidUtilities.needShowPasscode(true)) { - FileLog.e("tmessages", "lock app"); + FileLog.e("lock app"); showPasscodeActivity(); } else { - FileLog.e("tmessages", "didn't pass lock check"); + FileLog.e("didn't pass lock check"); } lockRunnable = null; } @@ -2074,7 +2213,7 @@ protected void onSaveInstanceState(Bundle outState) { lastFragment.saveSelfArgs(outState); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2086,6 +2225,8 @@ public void onBackPressed() { } if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(true, false); + } else if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(true, false); } else if (drawerLayoutContainer.isDrawerOpened()) { drawerLayoutContainer.closeDrawer(false); } else if (AndroidUtilities.isTablet()) { @@ -2119,6 +2260,20 @@ public void onLowMemory() { @Override public void onActionModeStarted(ActionMode mode) { super.onActionModeStarted(mode); + try { + Menu menu = mode.getMenu(); + if (menu != null) { + boolean extended = actionBarLayout.extendActionMode(menu); + if (!extended && AndroidUtilities.isTablet()) { + extended = rightActionBarLayout.extendActionMode(menu); + if (!extended) { + layersActionBarLayout.extendActionMode(menu); + } + } + } + } catch (Exception e) { + FileLog.e(e); + } if (Build.VERSION.SDK_INT >= 23 && mode.getType() == ActionMode.TYPE_FLOATING) { return; } @@ -2147,6 +2302,9 @@ public boolean onPreIme() { if (PhotoViewer.getInstance().isVisible()) { PhotoViewer.getInstance().closePhoto(true, false); return true; + } else if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(true, false); + return true; } return false; } @@ -2156,6 +2314,8 @@ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && !UserConfig.isWaitingForPasscodeEnter) { if (PhotoViewer.getInstance().isVisible()) { return super.onKeyUp(keyCode, event); + } else if (ArticleViewer.getInstance().isVisible()) { + return super.onKeyUp(keyCode, event); } if (AndroidUtilities.isTablet()) { if (layersActionBarLayout.getVisibility() == View.VISIBLE && !layersActionBarLayout.fragmentsStack.isEmpty()) { @@ -2185,10 +2345,13 @@ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) { @Override public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, boolean forceWithoutAnimation, ActionBarLayout layout) { + if (ArticleViewer.getInstance().isVisible()) { + ArticleViewer.getInstance().close(false, true); + } if (AndroidUtilities.isTablet()) { drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true); if (fragment instanceof DialogsActivity) { - DialogsActivity dialogsActivity = (DialogsActivity)fragment; + DialogsActivity dialogsActivity = (DialogsActivity) fragment; if (dialogsActivity.isMainDialogList() && layout != actionBarLayout) { actionBarLayout.removeAllFragments(); actionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, false); @@ -2260,7 +2423,7 @@ public boolean needPresentFragment(BaseFragment fragment, boolean removeLast, bo shadowTabletSide.setVisibility(View.GONE); shadowTablet.setBackgroundColor(0x00000000); } else { - shadowTablet.setBackgroundColor(0x7F000000); + shadowTablet.setBackgroundColor(0x7f000000); } layersActionBarLayout.presentFragment(fragment, removeLast, forceWithoutAnimation, false); return false; @@ -2277,7 +2440,7 @@ public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout lay if (AndroidUtilities.isTablet()) { drawerLayoutContainer.setAllowOpenDrawer(!(fragment instanceof LoginActivity || fragment instanceof CountrySelectActivity) && layersActionBarLayout.getVisibility() != View.VISIBLE, true); if (fragment instanceof DialogsActivity) { - DialogsActivity dialogsActivity = (DialogsActivity)fragment; + DialogsActivity dialogsActivity = (DialogsActivity) fragment; if (dialogsActivity.isMainDialogList() && layout != actionBarLayout) { actionBarLayout.removeAllFragments(); actionBarLayout.addFragmentToStack(fragment); @@ -2325,7 +2488,7 @@ public boolean needAddFragmentToStack(BaseFragment fragment, ActionBarLayout lay shadowTabletSide.setVisibility(View.GONE); shadowTablet.setBackgroundColor(0x00000000); } else { - shadowTablet.setBackgroundColor(0x7F000000); + shadowTablet.setBackgroundColor(0x7f000000); } layersActionBarLayout.addFragmentToStack(fragment); return false; @@ -2359,10 +2522,23 @@ public boolean needCloseLastFragment(ActionBarLayout layout) { finish(); return false; } + if (layout.fragmentsStack.size() >= 2 && !(layout.fragmentsStack.get(0) instanceof LoginActivity)) { + drawerLayoutContainer.setAllowOpenDrawer(true, false); + } } return true; } + public void rebuildAllFragments(boolean last) { + if (layersActionBarLayout != null) { + layersActionBarLayout.rebuildAllFragmentViews(last); + layersActionBarLayout.showLastFragment(); + } else { + actionBarLayout.rebuildAllFragmentViews(last); + actionBarLayout.showLastFragment(); + } + } + @Override public void onRebuildAllFragments(ActionBarLayout layout) { if (AndroidUtilities.isTablet()) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java index f004bce6053..5a4e971cd33 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LocationActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -15,12 +15,14 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Outline; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.location.Location; import android.location.LocationManager; import android.net.Uri; @@ -32,13 +34,9 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; import com.google.android.gms.maps.CameraUpdate; @@ -58,6 +56,8 @@ import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; @@ -66,14 +66,25 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Adapters.BaseLocationAdapter; import org.telegram.ui.Adapters.LocationActivityAdapter; import org.telegram.ui.Adapters.LocationActivitySearchAdapter; +import org.telegram.ui.Cells.GraySectionCell; +import org.telegram.ui.Cells.LocationCell; +import org.telegram.ui.Cells.LocationLoadingCell; +import org.telegram.ui.Cells.LocationPoweredCell; +import org.telegram.ui.Cells.SendLocationCell; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.MapPlaceholderDrawable; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.List; @@ -86,15 +97,19 @@ public class LocationActivity extends BaseFragment implements NotificationCenter private BackupImageView avatarImageView; private TextView nameTextView; private MapView mapView; + private EmptyTextProgressView emptyView; private FrameLayout mapViewClip; private LocationActivityAdapter adapter; - private ListView listView; - private ListView searchListView; + private RecyclerListView listView; + private RecyclerListView searchListView; private LocationActivitySearchAdapter searchAdapter; - private LinearLayout emptyTextLayout; private ImageView markerImageView; private ImageView markerXImageView; private ImageView locationButton; + private ImageView routeButton; + private FrameLayout bottomView; + private LinearLayoutManager layoutManager; + private AvatarDrawable avatarDrawable; private AnimatorSet animatorSet; @@ -151,7 +166,7 @@ public void onFragmentDestroy() { mapView.onDestroy(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (adapter != null) { adapter.destroy(); @@ -193,7 +208,7 @@ public void onItemClick(int id) { double lon = messageObject.messageOwner.media.geo._long; getParentActivity().startActivity(new Intent(android.content.Intent.ACTION_VIEW, Uri.parse("geo:" + lat + "," + lon + "?q=" + lat + "," + lon))); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -220,7 +235,7 @@ public void onSearchExpand() { listView.setVisibility(View.GONE); mapViewClip.setVisibility(View.GONE); searchListView.setVisibility(View.VISIBLE); - searchListView.setEmptyView(emptyTextLayout); + searchListView.setEmptyView(emptyView); } @Override @@ -231,7 +246,7 @@ public void onSearchCollapse() { listView.setVisibility(View.VISIBLE); mapViewClip.setVisibility(View.VISIBLE); searchListView.setVisibility(View.GONE); - emptyTextLayout.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); searchAdapter.searchDelayed(null, null); } @@ -251,9 +266,9 @@ public void onTextChanged(EditText editText) { } ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_other); - item.addSubItem(map_list_menu_map, LocaleController.getString("Map", R.string.Map), 0); - item.addSubItem(map_list_menu_satellite, LocaleController.getString("Satellite", R.string.Satellite), 0); - item.addSubItem(map_list_menu_hybrid, LocaleController.getString("Hybrid", R.string.Hybrid), 0); + item.addSubItem(map_list_menu_map, LocaleController.getString("Map", R.string.Map)); + item.addSubItem(map_list_menu_satellite, LocaleController.getString("Satellite", R.string.Satellite)); + item.addSubItem(map_list_menu_hybrid, LocaleController.getString("Hybrid", R.string.Hybrid)); fragmentView = new FrameLayout(context) { private boolean first = true; @@ -270,9 +285,18 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto FrameLayout frameLayout = (FrameLayout) fragmentView; locationButton = new ImageView(context); - locationButton.setBackgroundResource(R.drawable.floating_user_states); + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_profile_actionBackground), Theme.getColor(Theme.key_profile_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow_profile).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + locationButton.setBackgroundDrawable(drawable); locationButton.setImageResource(R.drawable.myloc_on); locationButton.setScaleType(ImageView.ScaleType.CENTER); + locationButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_profile_actionIcon), PorterDuff.Mode.MULTIPLY)); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(locationButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); @@ -319,7 +343,7 @@ public void onMapReady(GoogleMap map) { mapView.onResume(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -327,8 +351,10 @@ public void onMapReady(GoogleMap map) { } }).start(); - FrameLayout bottomView = new FrameLayout(context); - bottomView.setBackgroundResource(R.drawable.location_panel); + bottomView = new FrameLayout(context); + Drawable background = context.getResources().getDrawable(R.drawable.location_panel); + background.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhite), PorterDuff.Mode.MULTIPLY)); + bottomView.setBackgroundDrawable(background); frameLayout.addView(bottomView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 60, Gravity.LEFT | Gravity.BOTTOM)); bottomView.setOnClickListener(new View.OnClickListener() { @Override @@ -349,7 +375,7 @@ public void onClick(View view) { nameTextView = new TextView(context); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); - nameTextView.setTextColor(0xff212121); + nameTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); nameTextView.setMaxLines(1); nameTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView.setEllipsize(TextUtils.TruncateAt.END); @@ -359,7 +385,7 @@ public void onClick(View view) { distanceTextView = new TextView(context); distanceTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - distanceTextView.setTextColor(0xff2f8cc9); + distanceTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteValueText)); distanceTextView.setMaxLines(1); distanceTextView.setEllipsize(TextUtils.TruncateAt.END); distanceTextView.setSingleLine(true); @@ -370,8 +396,17 @@ public void onClick(View view) { userLocation.setLatitude(messageObject.messageOwner.media.geo.lat); userLocation.setLongitude(messageObject.messageOwner.media.geo._long); - ImageView routeButton = new ImageView(context); - routeButton.setBackgroundResource(R.drawable.floating_states); + routeButton = new ImageView(context); + drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + routeButton.setBackgroundDrawable(drawable); + routeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); routeButton.setImageResource(R.drawable.navigate); routeButton.setScaleType(ImageView.ScaleType.CENTER); if (Build.VERSION.SDK_INT >= 21) { @@ -387,7 +422,7 @@ public void getOutline(View view, Outline outline) { } }); } - frameLayout.addView(routeButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 28)); + frameLayout.addView(routeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 28)); routeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -405,13 +440,13 @@ public void onClick(View v) { Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse(String.format(Locale.US, "http://maps.google.com/maps?saddr=%f,%f&daddr=%f,%f", myLocation.getLatitude(), myLocation.getLongitude(), messageObject.messageOwner.media.geo.lat, messageObject.messageOwner.media.geo._long))); getParentActivity().startActivity(intent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } }); - frameLayout.addView(locationButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 100)); + frameLayout.addView(locationButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 100)); locationButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -441,29 +476,28 @@ public void onClick(View v) { searchAdapter.destroy(); } - listView = new ListView(context); + listView = new RecyclerListView(context); listView.setAdapter(adapter = new LocationActivityAdapter(context)); listView.setVerticalScrollBarEnabled(false); - listView.setDividerHeight(0); - listView.setDivider(null); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - - } + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (totalItemCount == 0) { + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + if (adapter.getItemCount() == 0) { return; } - updateClipView(firstVisibleItem); + int position = layoutManager.findFirstVisibleItemPosition(); + if (position == RecyclerView.NO_POSITION) { + return; + } + updateClipView(position); } }); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { + public void onItemClick(View view, int position) { if (position == 1) { if (delegate != null && userLocation != null) { TLRPC.TL_messageMediaGeo location = new TLRPC.TL_messageMediaGeo(); @@ -564,7 +598,7 @@ public void onMapReady(GoogleMap map) { mapView.onResume(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -582,6 +616,7 @@ public void onMapReady(GoogleMap map) { markerXImageView = new ImageView(context); markerXImageView.setAlpha(0.0f); + markerXImageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_location_markerX), PorterDuff.Mode.MULTIPLY)); markerXImageView.setImageResource(R.drawable.place_x); mapViewClip.addView(markerXImageView, LayoutHelper.createFrame(14, 14, Gravity.TOP | Gravity.CENTER_HORIZONTAL)); @@ -611,49 +646,28 @@ public void onClick(View v) { }); locationButton.setAlpha(0.0f); - emptyTextLayout = new LinearLayout(context); - emptyTextLayout.setVisibility(View.GONE); - emptyTextLayout.setOrientation(LinearLayout.VERTICAL); - frameLayout.addView(emptyTextLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 100, 0, 0)); - emptyTextLayout.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - TextView emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - emptyTextLayout.addView(emptyTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); + emptyView = new EmptyTextProgressView(context); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + emptyView.setShowAtCenter(true); + emptyView.setVisibility(View.GONE); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - FrameLayout frameLayoutEmpty = new FrameLayout(context); - emptyTextLayout.addView(frameLayoutEmpty, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, 0.5f)); - - searchListView = new ListView(context); + searchListView = new RecyclerListView(context); searchListView.setVisibility(View.GONE); - searchListView.setDividerHeight(0); - searchListView.setDivider(null); + searchListView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); searchListView.setAdapter(searchAdapter = new LocationActivitySearchAdapter(context)); frameLayout.addView(searchListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); - searchListView.setOnScrollListener(new AbsListView.OnScrollListener() { + searchListView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (scrollState == SCROLL_STATE_TOUCH_SCROLL && searching && searchWas) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } } - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - - } }); - searchListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + searchListView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { + public void onItemClick(View view, int position) { TLRPC.TL_messageMediaVenue object = searchAdapter.getItem(position); if (object != null && delegate != null) { delegate.didSelectLocation(object); @@ -678,7 +692,7 @@ private void onMapInit() { try { googleMap.addMarker(new MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromResource(R.drawable.map_pin))); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } CameraUpdate position = CameraUpdateFactory.newLatLngZoom(latLng, googleMap.getMaxZoomLevel() - 4); googleMap.moveCamera(position); @@ -691,7 +705,7 @@ private void onMapInit() { try { googleMap.setMyLocationEnabled(true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } googleMap.getUiSettings().setMyLocationButtonEnabled(false); googleMap.getUiSettings().setZoomControlsEnabled(false); @@ -728,7 +742,7 @@ public void onClick(DialogInterface dialog, int which) { intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); getParentActivity().startActivity(intent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -745,11 +759,11 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { viewGroup.removeView(mapView); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (mapViewClip != null) { mapViewClip.addView(mapView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, overScrollHeight + AndroidUtilities.dp(10), Gravity.TOP | Gravity.LEFT)); - updateClipView(listView.getFirstVisiblePosition()); + updateClipView(layoutManager.findFirstVisibleItemPosition()); } else if (fragmentView != null) { ((FrameLayout) fragmentView).addView(mapView, 0, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); } @@ -757,6 +771,9 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { } private void updateClipView(int firstVisibleItem) { + if (firstVisibleItem == RecyclerView.NO_POSITION) { + return; + } int height = 0; int top = 0; View child = listView.getChildAt(0); @@ -827,17 +844,17 @@ private void fixLayoutInternal(final boolean resume) { adapter.notifyDataSetChanged(); if (resume) { - listView.setSelectionFromTop(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); - updateClipView(listView.getFirstVisiblePosition()); + layoutManager.scrollToPositionWithOffset(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); + updateClipView(layoutManager.findFirstVisibleItemPosition()); listView.post(new Runnable() { @Override public void run() { - listView.setSelectionFromTop(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); - updateClipView(listView.getFirstVisiblePosition()); + layoutManager.scrollToPositionWithOffset(0, -(int) (AndroidUtilities.dp(56) * 2.5f + AndroidUtilities.dp(36 + 66))); + updateClipView(layoutManager.findFirstVisibleItemPosition()); } }); } else { - updateClipView(listView.getFirstVisiblePosition()); + updateClipView(layoutManager.findFirstVisibleItemPosition()); } } } @@ -867,7 +884,7 @@ private void updateUserData() { } String name = ""; TLRPC.FileLocation photo = null; - AvatarDrawable avatarDrawable = null; + avatarDrawable = null; if (fromId > 0) { TLRPC.User user = MessagesController.getInstance().getUser(fromId); if (user != null) { @@ -948,7 +965,7 @@ public void didReceivedNotification(int id, Object... args) { try { googleMap.setMyLocationEnabled(true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -961,7 +978,7 @@ public void onPause() { try { mapView.onPause(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } onResumeCalled = false; @@ -975,7 +992,7 @@ public void onResume() { try { mapView.onResume(); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } onResumeCalled = true; @@ -983,7 +1000,7 @@ public void onResume() { try { googleMap.setMyLocationEnabled(true); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } updateUserData(); @@ -1016,4 +1033,82 @@ private void updateSearchInterface() { adapter.notifyDataSetChanged(); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + updateUserData(); + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(locationButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_profile_actionIcon), + new ThemeDescription(locationButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_profile_actionBackground), + new ThemeDescription(locationButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_profile_actionPressedBackground), + + new ThemeDescription(bottomView, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(distanceTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(routeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_chats_actionIcon), + new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_chats_actionBackground), + new ThemeDescription(routeButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_chats_actionPressedBackground), + + new ThemeDescription(markerXImageView, 0, null, null, null, null, Theme.key_location_markerX), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{SendLocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_location_sendLocationIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE, new Class[]{SendLocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_location_sendLocationBackground), + new ThemeDescription(listView, 0, new Class[]{SendLocationCell.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueText7), + new ThemeDescription(listView, 0, new Class[]{SendLocationCell.class}, new String[]{"accurateTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{LocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{LocationCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{LocationCell.class}, new String[]{"addressTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(searchListView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{LocationCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(searchListView, 0, new Class[]{LocationCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(searchListView, 0, new Class[]{LocationCell.class}, new String[]{"addressTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(listView, 0, new Class[]{LocationLoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + new ThemeDescription(listView, 0, new Class[]{LocationLoadingCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + + new ThemeDescription(listView, 0, new Class[]{LocationPoweredCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{LocationPoweredCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{LocationPoweredCell.class}, new String[]{"textView2"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java index 6e627abb22b..49151b4791b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LoginActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -12,9 +12,7 @@ import android.animation.Animator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -67,7 +65,10 @@ import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.HintEditText; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.SlideView; @@ -84,18 +85,47 @@ import java.util.Timer; import java.util.TimerTask; +@SuppressLint("HardwareIds") public class LoginActivity extends BaseFragment { - private int currentViewNum = 0; + private int currentViewNum; private SlideView[] views = new SlideView[9]; - private ProgressDialog progressDialog; + private AlertDialog progressDialog; private Dialog permissionsDialog; + private Dialog permissionsShowDialog; private ArrayList permissionsItems = new ArrayList<>(); + private ArrayList permissionsShowItems = new ArrayList<>(); private boolean checkPermissions = true; + private boolean checkShowPermissions = true; private View doneButton; private final static int done_button = 1; + private class ProgressView extends View { + + private Paint paint = new Paint(); + private Paint paint2 = new Paint(); + private float progress; + + public ProgressView(Context context) { + super(context); + paint.setColor(Theme.getColor(Theme.key_login_progressInner)); + paint2.setColor(Theme.getColor(Theme.key_login_progressOuter)); + } + + public void setProgress(float value) { + progress = value; + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + int start = (int) (getMeasuredWidth() * progress); + canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); + canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); + } + } + @Override public void onFragmentDestroy() { super.onFragmentDestroy(); @@ -108,7 +138,7 @@ public void onFragmentDestroy() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -211,7 +241,7 @@ public void onResume() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -222,6 +252,11 @@ public void onRequestPermissionsResultFragment(int requestCode, String[] permiss if (currentViewNum == 0) { views[currentViewNum].onNextPressed(); } + } else if (requestCode == 7) { + checkShowPermissions = false; + if (currentViewNum == 0) { + ((PhoneView) views[currentViewNum]).fillNumber(); + } } } @@ -255,7 +290,7 @@ private Bundle loadCurrentState() { } return bundle; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return null; } @@ -291,8 +326,20 @@ private void putBundleToEditor(Bundle bundle, SharedPreferences.Editor editor, S @Override protected void onDialogDismiss(Dialog dialog) { - if (Build.VERSION.SDK_INT >= 23 && dialog == permissionsDialog && !permissionsItems.isEmpty() && getParentActivity() != null) { - getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + if (Build.VERSION.SDK_INT >= 23) { + if (dialog == permissionsDialog && !permissionsItems.isEmpty() && getParentActivity() != null) { + try { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } catch (Exception ignore) { + + } + } else if (dialog == permissionsShowDialog && !permissionsShowItems.isEmpty() && getParentActivity() != null) { + try { + getParentActivity().requestPermissions(permissionsShowItems.toArray(new String[permissionsShowItems.size()]), 7); + } catch (Exception ignore) { + + } + } } } @@ -369,7 +416,7 @@ private void needShowProgress() { if (getParentActivity() == null || getParentActivity().isFinishing() || progressDialog != null) { return; } - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -383,7 +430,7 @@ public void needHideProgress() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -394,6 +441,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { } else { if (page == 0) { checkPermissions = true; + checkShowPermissions = true; } doneButton.setVisibility(View.VISIBLE); } @@ -403,7 +451,7 @@ public void setPage(int page, boolean animated, Bundle params, boolean back) { currentViewNum = page; actionBar.setBackButtonImage(newView.needBackButton() ? R.drawable.ic_ab_back : 0); - newView.setParams(params); + newView.setParams(params, false); actionBar.setTitle(newView.getHeaderName()); newView.onShow(); newView.setX(back ? -AndroidUtilities.displaySize.x : AndroidUtilities.displaySize.x); @@ -449,7 +497,7 @@ public void onAnimationRepeat(Animator animator) { actionBar.setBackButtonImage(views[page].needBackButton() ? R.drawable.ic_ab_back : 0); views[currentViewNum].setVisibility(View.GONE); currentViewNum = page; - views[page].setParams(params); + views[page].setParams(params, false); views[page].setVisibility(View.VISIBLE); actionBar.setTitle(views[page].getHeaderName()); views[page].onShow(); @@ -473,7 +521,7 @@ public void saveSelfArgs(Bundle outState) { putBundleToEditor(bundle, editor, null); editor.commit(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -522,6 +570,9 @@ public class PhoneView extends SlideView implements AdapterView.OnItemSelectedLi private EditText codeField; private HintEditText phoneField; private TextView countryButton; + private View view; + private TextView textView; + private TextView textView2; private int countryState = 0; @@ -543,7 +594,7 @@ public PhoneView(Context context) { countryButton = new TextView(context); countryButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); countryButton.setPadding(AndroidUtilities.dp(12), AndroidUtilities.dp(10), AndroidUtilities.dp(12), 0); - countryButton.setTextColor(0xff212121); + countryButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); countryButton.setMaxLines(1); countryButton.setSingleLine(true); countryButton.setEllipsize(TextUtils.TruncateAt.END); @@ -553,10 +604,10 @@ public PhoneView(Context context) { countryButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { - CountrySelectActivity fragment = new CountrySelectActivity(); + CountrySelectActivity fragment = new CountrySelectActivity(true); fragment.setCountrySelectActivityDelegate(new CountrySelectActivity.CountrySelectActivityDelegate() { @Override - public void didSelectCountry(String name) { + public void didSelectCountry(String name, String shortName) { selectCountry(name); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -572,24 +623,25 @@ public void run() { } }); - View view = new View(context); + view = new View(context); view.setPadding(AndroidUtilities.dp(12), 0, AndroidUtilities.dp(12), 0); - view.setBackgroundColor(0xffdbdbdb); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayLine)); addView(view, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 4, -17.5f, 4, 0)); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(HORIZONTAL); addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 20, 0, 0)); - TextView textView = new TextView(context); + textView = new TextView(context); textView.setText("+"); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); codeField = new EditText(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); AndroidUtilities.clearCursorDrawable(codeField); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -628,7 +680,6 @@ public void afterTextChanged(Editable editable) { boolean ok = false; String textToSet = null; if (text.length() > 4) { - ignoreOnTextChange = true; for (int a = 4; a >= 1; a--) { String sub = text.substring(0, a); country = codesMap.get(sub); @@ -640,7 +691,6 @@ public void afterTextChanged(Editable editable) { } } if (!ok) { - ignoreOnTextChange = true; textToSet = text.substring(1, text.length()) + phoneField.getText().toString(); codeField.setText(text = text.substring(0, 1)); } @@ -690,8 +740,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { phoneField = new HintEditText(context); phoneField.setInputType(InputType.TYPE_CLASS_PHONE); - phoneField.setTextColor(0xff212121); - phoneField.setHintTextColor(0xff979797); + phoneField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); AndroidUtilities.clearCursorDrawable(phoneField); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -784,13 +835,13 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } }); - textView = new TextView(context); - textView.setText(LocaleController.getString("StartText", R.string.StartText)); - textView.setTextColor(0xff757575); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); - textView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); - addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 28, 0, 10)); + textView2 = new TextView(context); + textView2.setText(LocaleController.getString("StartText", R.string.StartText)); + textView2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); + textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView2.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + textView2.setLineSpacing(AndroidUtilities.dp(2), 1.0f); + addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 28, 0, 10)); HashMap languageMap = new HashMap<>(); try { @@ -808,7 +859,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } reader.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Collections.sort(countriesArray, new Comparator() { @@ -826,7 +877,7 @@ public int compare(String lhs, String rhs) { country = telephonyManager.getSimCountryIso().toUpperCase(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (country != null) { @@ -895,6 +946,7 @@ public void onNextPressed() { if (Build.VERSION.SDK_INT >= 23 && simcardAvailable) { allowCall = getParentActivity().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; boolean allowSms = getParentActivity().checkSelfPermission(Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED; + boolean allowCancelCall = getParentActivity().checkSelfPermission(Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED; if (checkPermissions) { permissionsItems.clear(); if (!allowCall) { @@ -903,14 +955,22 @@ public void onNextPressed() { if (!allowSms) { permissionsItems.add(Manifest.permission.RECEIVE_SMS); } + if (!allowCancelCall) { + permissionsItems.add(Manifest.permission.CALL_PHONE); + permissionsItems.add(Manifest.permission.WRITE_CALL_LOG); + permissionsItems.add(Manifest.permission.READ_CALL_LOG); + } + boolean ok = true; if (!permissionsItems.isEmpty()) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (preferences.getBoolean("firstlogin", true) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.RECEIVE_SMS)) { + if (!allowCancelCall && allowCall) { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } else if (preferences.getBoolean("firstlogin", true) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.RECEIVE_SMS)) { preferences.edit().putBoolean("firstlogin", false).commit(); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - if (permissionsItems.size() == 2) { + if (permissionsItems.size() >= 2) { builder.setMessage(LocaleController.getString("AllowReadCallAndSms", R.string.AllowReadCallAndSms)); } else if (!allowSms) { builder.setMessage(LocaleController.getString("AllowReadSms", R.string.AllowReadSms)); @@ -919,9 +979,15 @@ public void onNextPressed() { } permissionsDialog = showDialog(builder.create()); } else { - getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + try { + getParentActivity().requestPermissions(permissionsItems.toArray(new String[permissionsItems.size()]), 6); + } catch (Exception ignore) { + ok = false; + } + } + if (ok) { + return; } - return; } } } @@ -949,19 +1015,25 @@ public void onNextPressed() { if (req.allow_flashcall) { try { String number = tm.getLine1Number(); - req.current_number = number != null && number.length() != 0 && (phone.contains(number) || number.contains(phone)); + if (!TextUtils.isEmpty(number)) { + req.current_number = phone.contains(number) || number.contains(phone); + if (!req.current_number) { + req.allow_flashcall = false; + } + } else { + req.current_number = false; + } } catch (Exception e) { req.allow_flashcall = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } - final Bundle params = new Bundle(); params.putString("phone", "+" + codeField.getText() + phoneField.getText()); try { params.putString("ephone", "+" + PhoneFormat.stripExceptNumbers(codeField.getText().toString()) + " " + PhoneFormat.stripExceptNumbers(phoneField.getText().toString())); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); params.putString("ephone", "+" + phone); } params.putString("phoneFormated", phone); @@ -980,6 +1052,8 @@ public void run() { if (error.text != null) { if (error.text.contains("PHONE_NUMBER_INVALID")) { needShowInvalidAlert(req.phone_number, false); + } else if (error.text.contains("PHONE_NUMBER_FLOOD")) { + needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("PhoneNumberFlood", R.string.PhoneNumberFlood)); } else if (error.text.contains("PHONE_NUMBER_BANNED")) { needShowInvalidAlert(req.phone_number, true); } else if (error.text.contains("PHONE_CODE_EMPTY") || error.text.contains("PHONE_CODE_INVALID")) { @@ -1000,6 +1074,73 @@ public void run() { }, ConnectionsManager.RequestFlagFailOnServerErrors | ConnectionsManager.RequestFlagWithoutLogin | ConnectionsManager.RequestFlagTryDifferentDc | ConnectionsManager.RequestFlagEnableUnauthorized); } + public void fillNumber() { + try { + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + if (tm.getSimState() != TelephonyManager.SIM_STATE_ABSENT && tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE) { + boolean allowCall = true; + boolean allowSms = true; + if (Build.VERSION.SDK_INT >= 23) { + allowCall = getParentActivity().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; + allowSms = getParentActivity().checkSelfPermission(Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED; + if (checkShowPermissions && !allowCall && !allowSms) { + permissionsShowItems.clear(); + if (!allowCall) { + permissionsShowItems.add(Manifest.permission.READ_PHONE_STATE); + } + if (!allowSms) { + permissionsShowItems.add(Manifest.permission.RECEIVE_SMS); + } + if (!permissionsShowItems.isEmpty()) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences.getBoolean("firstloginshow", true) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.READ_PHONE_STATE) || getParentActivity().shouldShowRequestPermissionRationale(Manifest.permission.RECEIVE_SMS)) { + preferences.edit().putBoolean("firstloginshow", false).commit(); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.getString("AllowFillNumber", R.string.AllowFillNumber)); + permissionsShowDialog = showDialog(builder.create()); + } else { + getParentActivity().requestPermissions(permissionsShowItems.toArray(new String[permissionsShowItems.size()]), 7); + } + } + return; + } + } + if (allowCall || allowSms) { + String number = PhoneFormat.stripExceptNumbers(tm.getLine1Number()); + String textToSet = null; + boolean ok = false; + if (!TextUtils.isEmpty(number)) { + if (number.length() > 4) { + for (int a = 4; a >= 1; a--) { + String sub = number.substring(0, a); + String country = codesMap.get(sub); + if (country != null) { + ok = true; + textToSet = number.substring(a, number.length()); + codeField.setText(sub); + break; + } + } + if (!ok) { + textToSet = number.substring(1, number.length()); + codeField.setText(number.substring(0, 1)); + } + } + if (textToSet != null) { + phoneField.requestFocus(); + phoneField.setText(textToSet); + phoneField.setSelection(phoneField.length()); + } + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + @Override public void onShow() { super.onShow(); @@ -1013,6 +1154,8 @@ public void onShow() { codeField.requestFocus(); } } + + fillNumber(); } @Override @@ -1047,31 +1190,6 @@ public void restoreStateParams(Bundle bundle) { public class LoginActivitySmsView extends SlideView implements NotificationCenter.NotificationCenterDelegate { - private class ProgressView extends View { - - private Paint paint = new Paint(); - private Paint paint2 = new Paint(); - private float progress; - - public ProgressView(Context context) { - super(context); - paint.setColor(0xffe1eaf2); - paint2.setColor(0xff62a0d0); - } - - public void setProgress(float value) { - progress = value; - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - int start = (int) (getMeasuredWidth() * progress); - canvas.drawRect(0, 0, start, getMeasuredHeight(), paint2); - canvas.drawRect(start, 0, getMeasuredWidth(), getMeasuredHeight(), paint); - } - } - private String phone; private String phoneHash; private String requestPhone; @@ -1081,7 +1199,9 @@ protected void onDraw(Canvas canvas) { private TextView timeText; private TextView problemText; private Bundle currentParams; + private TextView wrongNumber; private ProgressView progressView; + private boolean isRestored; private Timer timeTimer; private Timer codeTimer; @@ -1098,6 +1218,7 @@ protected void onDraw(Canvas canvas) { private int currentType; private int nextType; private String pattern = "*"; + private String catchedPhone; private int length; private int timeout; @@ -1108,7 +1229,7 @@ public LoginActivitySmsView(Context context, final int type) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -1131,10 +1252,11 @@ public LoginActivitySmsView(Context context, final int type) { } codeField = new EditText(context); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); codeField.setHint(LocaleController.getString("Code", R.string.Code)); AndroidUtilities.clearCursorDrawable(codeField); - codeField.setHintTextColor(0xff979797); + codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); codeField.setInputType(InputType.TYPE_CLASS_PHONE); @@ -1180,7 +1302,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { timeText = new TextView(context); timeText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - timeText.setTextColor(0xff757575); + timeText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); timeText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); timeText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); addView(timeText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 30, 0, 0)); @@ -1194,7 +1316,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { problemText.setText(LocaleController.getString("DidNotGetTheCode", R.string.DidNotGetTheCode)); problemText.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); problemText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); - problemText.setTextColor(0xff4d83b3); + problemText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); problemText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); problemText.setPadding(0, AndroidUtilities.dp(2), 0, AndroidUtilities.dp(12)); addView(problemText, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 20, 0, 0)); @@ -1230,7 +1352,7 @@ public void onClick(View v) { TextView wrongNumber = new TextView(context); wrongNumber.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_HORIZONTAL); - wrongNumber.setTextColor(0xff4d83b3); + wrongNumber.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); wrongNumber.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); wrongNumber.setLineSpacing(AndroidUtilities.dp(2), 1.0f); wrongNumber.setPadding(0, AndroidUtilities.dp(24), 0, 0); @@ -1305,10 +1427,11 @@ public String getHeaderName() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } + isRestored = restore; codeField.setText(""); waitingForEvent = true; if (currentType == 2) { @@ -1380,7 +1503,20 @@ public void setParams(Bundle params) { } else if (nextType == 2) { timeText.setText(LocaleController.formatString("SmsText", R.string.SmsText, 1, 0)); } - createTimer(); + String callLogNumber = isRestored ? AndroidUtilities.obtainLoginPhoneCall(pattern) : null; + if (callLogNumber != null) { + ignoreOnTextChange = true; + codeField.setText(callLogNumber); + ignoreOnTextChange = false; + onNextPressed(); + } else if (catchedPhone != null) { + ignoreOnTextChange = true; + codeField.setText(catchedPhone); + ignoreOnTextChange = false; + onNextPressed(); + } else { + createTimer(); + } } else if (currentType == 2 && (nextType == 4 || nextType == 3)) { timeText.setVisibility(VISIBLE); timeText.setText(LocaleController.formatString("CallText", R.string.CallText, 2, 0)); @@ -1429,7 +1565,7 @@ private void destroyCodeTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1517,7 +1653,7 @@ private void destroyTimer() { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1535,9 +1671,10 @@ public void onNextPressed() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didReceiveCall); } waitingForEvent = false; + final String code = codeField.getText().toString(); final TLRPC.TL_auth_signIn req = new TLRPC.TL_auth_signIn(); req.phone_number = requestPhone; - req.phone_code = codeField.getText().toString(); + req.phone_code = code; req.phone_code_hash = phoneHash; destroyTimer(); needShowProgress(); @@ -1548,7 +1685,9 @@ public void run(final TLObject response, final TLRPC.TL_error error) { @Override public void run() { nextPressed = false; + boolean ok = false; if (error == null) { + ok = true; needHideProgress(); TLRPC.TL_auth_authorization res = (TLRPC.TL_auth_authorization) response; ConnectionsManager.getInstance().setUserId(res.user.id); @@ -1565,11 +1704,13 @@ public void run() { MessagesController.getInstance().putUser(res.user, false); ContactsController.getInstance().checkAppAccount(); MessagesController.getInstance().getBlockedUsers(true); + ConnectionsManager.getInstance().updateDcSettings(); needFinishActivity(); } else { lastError = error.text; if (error.text.contains("PHONE_NUMBER_UNOCCUPIED")) { + ok = true; needHideProgress(); Bundle params = new Bundle(); params.putString("phoneFormated", requestPhone); @@ -1579,6 +1720,7 @@ public void run() { destroyTimer(); destroyCodeTimer(); } else if (error.text.contains("SESSION_PASSWORD_NEEDED")) { + ok = true; TLRPC.TL_account_getPassword req2 = new TLRPC.TL_account_getPassword(); ConnectionsManager.getInstance().sendRequest(req2, new RequestDelegate() { @Override @@ -1637,6 +1779,12 @@ public void run() { } } } + if (ok) { + if (currentType == 3) { + AndroidUtilities.endIncomingCall(); + AndroidUtilities.removeLoginPhoneCall(code, true); + } + } } }); } @@ -1676,7 +1824,7 @@ public void onDestroyActivity() { @Override public void onShow() { super.onShow(); - if (codeField != null) { + if (codeField != null && currentType != 3) { codeField.requestFocus(); codeField.setSelection(codeField.length()); } @@ -1694,11 +1842,13 @@ public void didReceivedNotification(int id, final Object... args) { onNextPressed(); } else if (id == NotificationCenter.didReceiveCall) { String num = "" + args[0]; + if (!AndroidUtilities.checkPhonePattern(pattern, num)) { + return; + } if (!pattern.equals("*")) { - String patternNumbers = pattern.replace("*", ""); - if (!num.contains(patternNumbers)) { - return; - } + catchedPhone = num; + AndroidUtilities.endIncomingCall(); + AndroidUtilities.removeLoginPhoneCall(num, true); } ignoreOnTextChange = true; codeField.setText(num); @@ -1713,6 +1863,9 @@ public void saveStateParams(Bundle bundle) { if (code.length() != 0) { bundle.putString("smsview_code_" + currentType, code); } + if (catchedPhone != null) { + bundle.putString("catchedPhone", catchedPhone); + } if (currentParams != null) { bundle.putBundle("smsview_params_" + currentType, currentParams); } @@ -1728,7 +1881,11 @@ public void saveStateParams(Bundle bundle) { public void restoreStateParams(Bundle bundle) { currentParams = bundle.getBundle("smsview_params_" + currentType); if (currentParams != null) { - setParams(currentParams); + setParams(currentParams, true); + } + String catched = bundle.getString("catchedPhone"); + if (catched != null) { + catchedPhone = catched; } String code = bundle.getString("smsview_code_" + currentType); if (code != null) { @@ -1751,6 +1908,7 @@ public class LoginActivityPasswordView extends SlideView { private TextView confirmTextView; private TextView resetAccountButton; private TextView resetAccountText; + private TextView cancelButton; private Bundle currentParams; private boolean nextPressed; @@ -1768,7 +1926,7 @@ public LoginActivityPasswordView(Context context) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -1776,9 +1934,10 @@ public LoginActivityPasswordView(Context context) { addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); codeField = new EditText(context); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); AndroidUtilities.clearCursorDrawable(codeField); - codeField.setHintTextColor(0xff979797); + codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setHint(LocaleController.getString("LoginPassword", R.string.LoginPassword)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -1800,9 +1959,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } }); - TextView cancelButton = new TextView(context); + cancelButton = new TextView(context); cancelButton.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - cancelButton.setTextColor(0xff4d83b3); + cancelButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); cancelButton.setText(LocaleController.getString("ForgotPassword", R.string.ForgotPassword)); cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); cancelButton.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -1868,7 +2027,7 @@ public void onClick(DialogInterface dialogInterface, int i) { resetAccountButton = new TextView(context); resetAccountButton.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - resetAccountButton.setTextColor(0xffff6666); + resetAccountButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText6)); resetAccountButton.setVisibility(GONE); resetAccountButton.setText(LocaleController.getString("ResetMyAccount", R.string.ResetMyAccount)); resetAccountButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); @@ -1930,7 +2089,7 @@ public void run() { resetAccountText = new TextView(context); resetAccountText.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); resetAccountText.setVisibility(GONE); - resetAccountText.setTextColor(0xff757575); + resetAccountText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); resetAccountText.setText(LocaleController.getString("ResetMyAccountText", R.string.ResetMyAccountText)); resetAccountText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); resetAccountText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -1943,7 +2102,7 @@ public String getHeaderName() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -2002,7 +2161,7 @@ public void onNextPressed() { try { oldPasswordBytes = oldPassword.getBytes("UTF-8"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } needShowProgress(); @@ -2035,6 +2194,7 @@ public void run() { MessagesController.getInstance().putUser(res.user, false); ContactsController.getInstance().checkAppAccount(); MessagesController.getInstance().getBlockedUsers(true); + ConnectionsManager.getInstance().updateDcSettings(); needFinishActivity(); } else { if (error.text.equals("PASSWORD_HASH_INVALID")) { @@ -2093,7 +2253,7 @@ public void saveStateParams(Bundle bundle) { public void restoreStateParams(Bundle bundle) { currentParams = bundle.getBundle("passview_params"); if (currentParams != null) { - setParams(currentParams); + setParams(currentParams, true); } String code = bundle.getString("passview_code"); if (code != null) { @@ -2107,6 +2267,7 @@ public class LoginActivityResetWaitView extends SlideView { private TextView confirmTextView; private TextView resetAccountButton; private TextView resetAccountTime; + private TextView resetAccountText; private Runnable timeRunnable; private Bundle currentParams; @@ -2122,15 +2283,15 @@ public LoginActivityResetWaitView(Context context) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); - TextView resetAccountText = new TextView(context); + resetAccountText = new TextView(context); resetAccountText.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - resetAccountText.setTextColor(0xff757575); + resetAccountText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); resetAccountText.setText(LocaleController.getString("ResetAccountStatus", R.string.ResetAccountStatus)); resetAccountText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); resetAccountText.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -2138,7 +2299,7 @@ public LoginActivityResetWaitView(Context context) { resetAccountTime = new TextView(context); resetAccountTime.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); - resetAccountTime.setTextColor(0xff757575); + resetAccountTime.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); resetAccountTime.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); resetAccountTime.setLineSpacing(AndroidUtilities.dp(2), 1.0f); addView(resetAccountTime, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), 0, 2, 0, 0)); @@ -2215,14 +2376,16 @@ private void updateTimeText() { resetAccountTime.setText(AndroidUtilities.replaceTags(LocaleController.formatPluralString("HoursBold", hours) + " " + LocaleController.formatPluralString("MinutesBold", minutes) + " " + LocaleController.formatPluralString("SecondsBold", seconds))); } if (timeLeft > 0) { - resetAccountButton.setTextColor(0x88888888); + resetAccountButton.setTag(Theme.key_windowBackgroundWhiteGrayText6); + resetAccountButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); } else { - resetAccountButton.setTextColor(0xffff6666); + resetAccountButton.setTag(Theme.key_windowBackgroundWhiteRedText6); + resetAccountButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText6)); } } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -2270,7 +2433,7 @@ public void saveStateParams(Bundle bundle) { public void restoreStateParams(Bundle bundle) { currentParams = bundle.getBundle("resetview_params"); if (currentParams != null) { - setParams(currentParams); + setParams(currentParams, true); } } } @@ -2291,7 +2454,7 @@ public LoginActivityRecoverView(Context context) { setOrientation(VERTICAL); confirmTextView = new TextView(context); - confirmTextView.setTextColor(0xff757575); + confirmTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); confirmTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); confirmTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT)); confirmTextView.setLineSpacing(AndroidUtilities.dp(2), 1.0f); @@ -2299,9 +2462,10 @@ public LoginActivityRecoverView(Context context) { addView(confirmTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT))); codeField = new EditText(context); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); AndroidUtilities.clearCursorDrawable(codeField); - codeField.setHintTextColor(0xff979797); + codeField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); codeField.setHint(LocaleController.getString("PasswordCode", R.string.PasswordCode)); codeField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -2325,7 +2489,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { cancelButton = new TextView(context); cancelButton.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.BOTTOM); - cancelButton.setTextColor(0xff4d83b3); + cancelButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); cancelButton.setLineSpacing(AndroidUtilities.dp(2), 1.0f); cancelButton.setPadding(0, AndroidUtilities.dp(14), 0, 0); @@ -2362,7 +2526,7 @@ public String getHeaderName() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -2432,6 +2596,7 @@ public void run() { MessagesController.getInstance().putUser(res.user, false); ContactsController.getInstance().checkAppAccount(); MessagesController.getInstance().getBlockedUsers(true); + ConnectionsManager.getInstance().updateDcSettings(); needFinishActivity(); } else { if (error.text.startsWith("CODE_INVALID")) { @@ -2484,7 +2649,7 @@ public void saveStateParams(Bundle bundle) { public void restoreStateParams(Bundle bundle) { currentParams = bundle.getBundle("recoveryview_params"); if (currentParams != null) { - setParams(currentParams); + setParams(currentParams, true); } String code = bundle.getString("recoveryview_code"); if (code != null) { @@ -2497,6 +2662,8 @@ public class LoginActivityRegisterView extends SlideView { private EditText firstNameField; private EditText lastNameField; + private TextView textView; + private TextView wrongNumber; private String requestPhone; private String phoneHash; private String phoneCode; @@ -2508,16 +2675,17 @@ public LoginActivityRegisterView(Context context) { setOrientation(VERTICAL); - TextView textView = new TextView(context); + textView = new TextView(context); textView.setText(LocaleController.getString("RegisterText", R.string.RegisterText)); - textView.setTextColor(0xff757575); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); textView.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT, 0, 8, 0, 0)); firstNameField = new EditText(context); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); AndroidUtilities.clearCursorDrawable(firstNameField); firstNameField.setHint(LocaleController.getString("FirstName", R.string.FirstName)); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); @@ -2538,8 +2706,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { lastNameField = new EditText(context); lastNameField.setHint(LocaleController.getString("LastName", R.string.LastName)); - lastNameField.setHintTextColor(0xff979797); - lastNameField.setTextColor(0xff212121); + lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); AndroidUtilities.clearCursorDrawable(lastNameField); lastNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -2551,10 +2720,10 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { linearLayout.setGravity(Gravity.BOTTOM | Gravity.CENTER_VERTICAL); addView(linearLayout, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - TextView wrongNumber = new TextView(context); + wrongNumber = new TextView(context); wrongNumber.setText(LocaleController.getString("CancelRegistration", R.string.CancelRegistration)); wrongNumber.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.CENTER_HORIZONTAL); - wrongNumber.setTextColor(0xff4d83b3); + wrongNumber.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); wrongNumber.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); wrongNumber.setLineSpacing(AndroidUtilities.dp(2), 1.0f); wrongNumber.setPadding(0, AndroidUtilities.dp(24), 0, 0); @@ -2598,7 +2767,7 @@ public void onShow() { } @Override - public void setParams(Bundle params) { + public void setParams(Bundle params, boolean restore) { if (params == null) { return; } @@ -2646,6 +2815,7 @@ public void run() { MessagesController.getInstance().putUser(res.user, false); ContactsController.getInstance().checkAppAccount(); MessagesController.getInstance().getBlockedUsers(true); + ConnectionsManager.getInstance().updateDcSettings(); needFinishActivity(); } else { if (error.text.contains("PHONE_NUMBER_INVALID")) { @@ -2687,7 +2857,7 @@ public void saveStateParams(Bundle bundle) { public void restoreStateParams(Bundle bundle) { currentParams = bundle.getBundle("registerview_params"); if (currentParams != null) { - setParams(currentParams); + setParams(currentParams, true); } String first = bundle.getString("registerview_first"); if (first != null) { @@ -2699,4 +2869,116 @@ public void restoreStateParams(Bundle bundle) { } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + PhoneView phoneView = (PhoneView) views[0]; + LoginActivitySmsView smsView1 = (LoginActivitySmsView) views[1]; + LoginActivitySmsView smsView2 = (LoginActivitySmsView) views[2]; + LoginActivitySmsView smsView3 = (LoginActivitySmsView) views[3]; + LoginActivitySmsView smsView4 = (LoginActivitySmsView) views[4]; + LoginActivityRegisterView registerView = (LoginActivityRegisterView) views[5]; + LoginActivityPasswordView passwordView = (LoginActivityPasswordView) views[6]; + LoginActivityRecoverView recoverView = (LoginActivityRecoverView) views[7]; + LoginActivityResetWaitView waitView = (LoginActivityResetWaitView) views[8]; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(phoneView.countryButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.view, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhiteGrayLine), + new ThemeDescription(phoneView.textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(phoneView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(phoneView.phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(phoneView.textView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + + new ThemeDescription(passwordView.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(passwordView.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(passwordView.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(passwordView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(passwordView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(passwordView.cancelButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(passwordView.resetAccountButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteRedText6), + new ThemeDescription(passwordView.resetAccountText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + + new ThemeDescription(registerView.textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(registerView.firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(registerView.firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(registerView.firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(registerView.firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(registerView.lastNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(registerView.lastNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(registerView.lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(registerView.lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(registerView.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + + new ThemeDescription(recoverView.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(recoverView.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(recoverView.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(recoverView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(recoverView.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(recoverView.cancelButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + + new ThemeDescription(waitView.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(waitView.resetAccountText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(waitView.resetAccountTime, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(waitView.resetAccountButton, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(waitView.resetAccountButton, ThemeDescription.FLAG_TEXTCOLOR | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhiteRedText6), + + new ThemeDescription(smsView1.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView1.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView1.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView1.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView1.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView1.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView2.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView2.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView2.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView2.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView2.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView2.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView3.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView3.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView3.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView3.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView3.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView3.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + + new ThemeDescription(smsView4.confirmTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(smsView4.codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(smsView4.timeText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(smsView4.problemText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView4.wrongNumber, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressInner), + new ThemeDescription(smsView4.progressView, 0, new Class[]{ProgressView.class}, new String[]{"paint"}, null, null, null, Theme.key_login_progressOuter), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ManageSpaceActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ManageSpaceActivity.java index 0b7288458e8..502cb36e5fc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ManageSpaceActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ManageSpaceActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -58,7 +58,6 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setBackgroundDrawableResource(R.drawable.transparent); super.onCreate(savedInstanceState); - Theme.loadRecources(this); int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java index b584c5d6fa9..77326ed5955 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/MediaActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -12,12 +12,14 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -32,13 +34,10 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.webkit.MimeTypeMap; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -47,11 +46,16 @@ import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesController; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; @@ -64,12 +68,12 @@ import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.ActionBarPopupWindow; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; -import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Adapters.BaseFragmentAdapter; -import org.telegram.ui.Adapters.BaseSectionsAdapter; -import org.telegram.ui.Cells.GreySectionCell; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.CheckBoxCell; +import org.telegram.ui.Cells.GraySectionCell; import org.telegram.ui.Cells.LoadingCell; import org.telegram.ui.Cells.SharedDocumentCell; import org.telegram.ui.Cells.SharedLinkCell; @@ -77,11 +81,12 @@ import org.telegram.ui.Cells.SharedPhotoVideoCell; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.EmbedBottomSheet; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberTextView; -import org.telegram.ui.Components.PlayerView; -import org.telegram.ui.Components.SectionsListView; -import org.telegram.ui.Components.WebFrameLayout; +import org.telegram.ui.Components.FragmentContextView; +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RecyclerListView; import java.io.File; import java.util.ArrayList; @@ -100,17 +105,21 @@ public class MediaActivity extends BaseFragment implements NotificationCenter.No private MediaSearchAdapter documentsSearchAdapter; private MediaSearchAdapter audioSearchAdapter; private MediaSearchAdapter linksSearchAdapter; - private SectionsListView listView; + private RecyclerListView listView; private LinearLayout progressView; private TextView emptyTextView; + private LinearLayoutManager layoutManager; private ImageView emptyImageView; private LinearLayout emptyView; private TextView dropDown; + private Drawable dropDownDrawable; + private RadialProgressView progressBar; private ActionBarMenuItem dropDownContainer; private ActionBarMenuItem searchItem; private NumberTextView selectedMessagesCountTextView; private ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout; private ArrayList cellCache = new ArrayList<>(6); + private FragmentContextView fragmentContextView; private boolean searchWas; private boolean searching; @@ -257,7 +266,19 @@ public void onItemClick(int id) { } cantDeleteMessagesCount = 0; actionBar.hideActionMode(); - listView.invalidateViews(); + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof SharedDocumentCell) { + ((SharedDocumentCell) child).setChecked(false, true); + } else if (child instanceof SharedPhotoVideoCell) { + for (int b = 0; b < 6; b++) { + ((SharedPhotoVideoCell) child).setChecked(b, false, true); + } + } else if (child instanceof SharedLinkCell) { + ((SharedLinkCell) child).setChecked(false, true); + } + } } else { finishFragment(); } @@ -292,6 +313,69 @@ public void onItemClick(int id) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.formatString("AreYouSureDeleteMessages", R.string.AreYouSureDeleteMessages, LocaleController.formatPluralString("items", selectedFiles[0].size() + selectedFiles[1].size()))); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + + final boolean deleteForAll[] = new boolean[1]; + int lower_id = (int) dialog_id; + if (lower_id != 0) { + TLRPC.Chat currentChat; + TLRPC.User currentUser; + if (lower_id > 0) { + currentUser = MessagesController.getInstance().getUser(lower_id); + currentChat = null; + } else { + currentUser = null; + currentChat = MessagesController.getInstance().getChat(-lower_id); + } + if (currentUser != null || !ChatObject.isChannel(currentChat)) { + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + if (currentUser != null && currentUser.id != UserConfig.getClientUserId() || currentChat != null) { + boolean hasOutgoing = false; + for (int a = 1; a >= 0; a--) { + int channelId = 0; + for (HashMap.Entry entry : selectedFiles[a].entrySet()) { + MessageObject msg = entry.getValue(); + if (msg.messageOwner.action != null) { + continue; + } + if (msg.isOut()) { + if ((currentDate - msg.messageOwner.date) <= 2 * 24 * 60 * 60) { + hasOutgoing = true; + } + } else { + hasOutgoing = false; + break; + } + } + if (hasOutgoing) { + break; + } + } + + if (hasOutgoing) { + FrameLayout frameLayout = new FrameLayout(getParentActivity()); + CheckBoxCell cell = new CheckBoxCell(getParentActivity(), true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (currentChat != null) { + cell.setText(LocaleController.getString("DeleteForAll", R.string.DeleteForAll), "", false, false); + } else { + cell.setText(LocaleController.formatString("DeleteForUser", R.string.DeleteForUser, UserObject.getFirstName(currentUser)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + deleteForAll[0] = !deleteForAll[0]; + cell.setChecked(deleteForAll[0], true); + } + }); + builder.setView(frameLayout); + } + } + } + } + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -318,7 +402,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } } } - MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId); + MessagesController.getInstance().deleteMessages(ids, random_ids, currentEncryptedChat, channelId, deleteForAll[0]); selectedFiles[a].clear(); } actionBar.hideActionMode(); @@ -367,7 +451,7 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { ChatActivity chatActivity = new ChatActivity(args); presentFragment(chatActivity, true); - chatActivity.showReplyPanel(true, null, fmessages, null, false, false); + chatActivity.showReplyPanel(true, null, fmessages, null, false); if (!AndroidUtilities.isTablet()) { removeSelfFromStack(); @@ -439,17 +523,17 @@ public void onTextChanged(EditText editText) { searchItem.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); searchItem.setVisibility(View.GONE); - dropDownContainer = new ActionBarMenuItem(context, menu, 0); + dropDownContainer = new ActionBarMenuItem(context, menu, 0, 0); dropDownContainer.setSubMenuOpenSide(1); - dropDownContainer.addSubItem(shared_media_item, LocaleController.getString("SharedMediaTitle", R.string.SharedMediaTitle), 0); - dropDownContainer.addSubItem(files_item, LocaleController.getString("DocumentsTitle", R.string.DocumentsTitle), 0); + dropDownContainer.addSubItem(shared_media_item, LocaleController.getString("SharedMediaTitle", R.string.SharedMediaTitle)); + dropDownContainer.addSubItem(files_item, LocaleController.getString("DocumentsTitle", R.string.DocumentsTitle)); if ((int) dialog_id != 0) { - dropDownContainer.addSubItem(links_item, LocaleController.getString("LinksTitle", R.string.LinksTitle), 0); - dropDownContainer.addSubItem(music_item, LocaleController.getString("AudioTitle", R.string.AudioTitle), 0); + dropDownContainer.addSubItem(links_item, LocaleController.getString("LinksTitle", R.string.LinksTitle)); + dropDownContainer.addSubItem(music_item, LocaleController.getString("AudioTitle", R.string.AudioTitle)); } else { TLRPC.EncryptedChat currentEncryptedChat = MessagesController.getInstance().getEncryptedChat((int) (dialog_id >> 32)); if (currentEncryptedChat != null && AndroidUtilities.getPeerLayerVersion(currentEncryptedChat.layer) >= 46) { - dropDownContainer.addSubItem(music_item, LocaleController.getString("AudioTitle", R.string.AudioTitle), 0); + dropDownContainer.addSubItem(music_item, LocaleController.getString("AudioTitle", R.string.AudioTitle)); } } actionBar.addView(dropDownContainer, 0, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, AndroidUtilities.isTablet() ? 64 : 56, 0, 40, 0)); @@ -466,9 +550,11 @@ public void onClick(View view) { dropDown.setLines(1); dropDown.setMaxLines(1); dropDown.setEllipsize(TextUtils.TruncateAt.END); - dropDown.setTextColor(0xffffffff); + dropDown.setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); dropDown.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - dropDown.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_arrow_drop_down, 0); + dropDownDrawable = context.getResources().getDrawable(R.drawable.ic_arrow_drop_down).mutate(); + dropDownDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultTitle), PorterDuff.Mode.MULTIPLY)); + dropDown.setCompoundDrawablesWithIntrinsicBounds(null, null, dropDownDrawable, null); dropDown.setCompoundDrawablePadding(AndroidUtilities.dp(4)); dropDown.setPadding(0, 0, AndroidUtilities.dp(10), 0); dropDownContainer.addView(dropDown, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 16, 0, 0, 0)); @@ -478,7 +564,7 @@ public void onClick(View view) { selectedMessagesCountTextView = new NumberTextView(actionMode.getContext()); selectedMessagesCountTextView.setTextSize(18); selectedMessagesCountTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - selectedMessagesCountTextView.setTextColor(0xff737373); + selectedMessagesCountTextView.setTextColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon)); selectedMessagesCountTextView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { @@ -488,9 +574,9 @@ public boolean onTouch(View v, MotionEvent event) { actionMode.addView(selectedMessagesCountTextView, LayoutHelper.createLinear(0, LayoutHelper.MATCH_PARENT, 1.0f, 65, 0, 0, 0)); if ((int) dialog_id != 0) { - actionModeViews.add(actionMode.addItem(forward, R.drawable.ic_ab_fwd_forward, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(forward, R.drawable.ic_ab_forward, AndroidUtilities.dp(54))); } - actionModeViews.add(actionMode.addItem(delete, R.drawable.ic_ab_fwd_delete, Theme.ACTION_BAR_MODE_SELECTOR_COLOR, null, AndroidUtilities.dp(54))); + actionModeViews.add(actionMode.addItemWithWidth(delete, R.drawable.ic_ab_delete, AndroidUtilities.dp(54))); photoVideoAdapter = new SharedPhotoVideoAdapter(context); documentsAdapter = new SharedDocumentsAdapter(context, 1); @@ -503,36 +589,55 @@ public boolean onTouch(View v, MotionEvent event) { FrameLayout frameLayout; fragmentView = frameLayout = new FrameLayout(context); - listView = new SectionsListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setDrawSelectorOnTop(true); + int scrollToPositionOnRecreate = -1; + int scrollToOffsetOnRecreate = 0; + if (layoutManager != null) { + scrollToPositionOnRecreate = layoutManager.findFirstVisibleItemPosition(); + if (scrollToPositionOnRecreate != layoutManager.getItemCount() - 1) { + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForAdapterPosition(scrollToPositionOnRecreate); + if (holder != null) { + scrollToOffsetOnRecreate = holder.itemView.getTop(); + } else { + scrollToPositionOnRecreate = -1; + } + } else { + scrollToPositionOnRecreate = -1; + } + } + + listView = new RecyclerListView(context); listView.setClipToPadding(false); + listView.setSectionsType(2); + listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { + public void onItemClick(View view, int position) { if ((selectedMode == 1 || selectedMode == 4) && view instanceof SharedDocumentCell) { - MediaActivity.this.onItemClick(i, view, ((SharedDocumentCell) view).getMessage(), 0); + MediaActivity.this.onItemClick(position, view, ((SharedDocumentCell) view).getMessage(), 0); } else if (selectedMode == 3 && view instanceof SharedLinkCell) { - MediaActivity.this.onItemClick(i, view, ((SharedLinkCell) view).getMessage(), 0); + MediaActivity.this.onItemClick(position, view, ((SharedLinkCell) view).getMessage(), 0); } } }); - listView.setOnScrollListener(new AbsListView.OnScrollListener() { + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (scrollState == SCROLL_STATE_TOUCH_SCROLL && searching && searchWas) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING && searching && searchWas) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } - scrolling = scrollState != SCROLL_STATE_IDLE; + scrolling = newState != RecyclerView.SCROLL_STATE_IDLE; } @Override - public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { if (searching && searchWas) { return; } + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(layoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + int totalItemCount = recyclerView.getAdapter().getItemCount(); + if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !sharedMediaData[selectedMode].loading) { int type; if (selectedMode == 0) { @@ -556,9 +661,9 @@ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCoun } } }); - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView parent, View view, int i, long id) { + public boolean onItemClick(View view, int position) { if ((selectedMode == 1 || selectedMode == 4) && view instanceof SharedDocumentCell) { SharedDocumentCell cell = (SharedDocumentCell) view; MessageObject message = cell.getMessage(); @@ -571,6 +676,9 @@ public boolean onItemLongClick(AdapterView parent, View view, int i, long id) return false; } }); + if (scrollToPositionOnRecreate != -1) { + layoutManager.scrollToPositionWithOffset(scrollToPositionOnRecreate, scrollToOffsetOnRecreate); + } for (int a = 0; a < 6; a++) { cellCache.add(new SharedPhotoVideoCell(context)); @@ -580,7 +688,7 @@ public boolean onItemLongClick(AdapterView parent, View view, int i, long id) emptyView.setOrientation(LinearLayout.VERTICAL); emptyView.setGravity(Gravity.CENTER); emptyView.setVisibility(View.GONE); - emptyView.setBackgroundColor(0xfff0f0f0); + emptyView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); emptyView.setOnTouchListener(new View.OnTouchListener() { @Override @@ -593,7 +701,7 @@ public boolean onTouch(View v, MotionEvent event) { emptyView.addView(emptyImageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff8a8a8a); + emptyTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); emptyTextView.setGravity(Gravity.CENTER); emptyTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); emptyTextView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), AndroidUtilities.dp(128)); @@ -603,16 +711,16 @@ public boolean onTouch(View v, MotionEvent event) { progressView.setGravity(Gravity.CENTER); progressView.setOrientation(LinearLayout.VERTICAL); progressView.setVisibility(View.GONE); - progressView.setBackgroundColor(0xfff0f0f0); + progressView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); frameLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - ProgressBar progressBar = new ProgressBar(context); + progressBar = new RadialProgressView(context); progressView.addView(progressBar, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); switchToCurrentSelectedMode(); if (!AndroidUtilities.isTablet()) { - frameLayout.addView(new PlayerView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); + frameLayout.addView(fragmentContextView = new FragmentContextView(context, this), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 39, Gravity.TOP | Gravity.LEFT, 0, -36, 0, 0)); } return fragmentView; @@ -874,7 +982,7 @@ public void setPhotoChecked(int index) { } public boolean cancelButtonPressed() { return true; } @Override - public void sendButtonPressed(int index) { } + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { } @Override public int getSelectedCount() { return 0; } @@ -1040,6 +1148,9 @@ private void onItemClick(int index, View view, MessageObject message, int a) { cantDeleteMessagesCount--; } } else { + if (selectedFiles[0].size() + selectedFiles[1].size() >= 100) { + return; + } selectedFiles[loadIndex].put(message.getId(), message); if (!message.canDeleteMessage(null)) { cantDeleteMessagesCount++; @@ -1081,50 +1192,63 @@ private void onItemClick(int index, View view, MessageObject message, int a) { f = FileLoader.getPathToMessage(message.messageOwner); } if (f != null && f.exists()) { - String realMimeType = null; - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - MimeTypeMap myMime = MimeTypeMap.getSingleton(); - int idx = fileName.lastIndexOf('.'); - if (idx != -1) { - String ext = fileName.substring(idx + 1); - realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); - if (realMimeType == null) { - realMimeType = message.getDocument().mime_type; - if (realMimeType == null || realMimeType.length() == 0) { - realMimeType = null; - } - } - } - if (Build.VERSION.SDK_INT >= 24) { - intent.setDataAndType(FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); + if (f.getName().endsWith("attheme")) { + Theme.ThemeInfo themeInfo = Theme.applyThemeFile(f, message.getDocumentName(), true); + if (themeInfo != null) { + presentFragment(new ThemePreviewActivity(f, themeInfo)); } else { - intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setMessage(LocaleController.getString("IncorrectTheme", R.string.IncorrectTheme)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + showDialog(builder.create()); } - if (realMimeType != null) { - try { - getParentActivity().startActivityForResult(intent, 500); - } catch (Exception e) { - if (Build.VERSION.SDK_INT >= 24) { - intent.setDataAndType(FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); - } else { - intent.setDataAndType(Uri.fromFile(f), "text/plain"); + } else { + String realMimeType = null; + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + MimeTypeMap myMime = MimeTypeMap.getSingleton(); + int idx = fileName.lastIndexOf('.'); + if (idx != -1) { + String ext = fileName.substring(idx + 1); + realMimeType = myMime.getMimeTypeFromExtension(ext.toLowerCase()); + if (realMimeType == null) { + realMimeType = message.getDocument().mime_type; + if (realMimeType == null || realMimeType.length() == 0) { + realMimeType = null; + } } + } + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", f), realMimeType != null ? realMimeType : "text/plain"); + } else { + intent.setDataAndType(Uri.fromFile(f), realMimeType != null ? realMimeType : "text/plain"); + } + if (realMimeType != null) { + try { + getParentActivity().startActivityForResult(intent, 500); + } catch (Exception e) { + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", f), "text/plain"); + } else { + intent.setDataAndType(Uri.fromFile(f), "text/plain"); + } + getParentActivity().startActivityForResult(intent, 500); + } + } else { getParentActivity().startActivityForResult(intent, 500); } - } else { - getParentActivity().startActivityForResult(intent, 500); - } - } catch (Exception e) { - if (getParentActivity() == null) { - return; + } catch (Exception e) { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); + builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); + showDialog(builder.create()); } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - builder.setMessage(LocaleController.formatString("NoHandleAppInstalled", R.string.NoHandleAppInstalled, message.getDocument().mime_type)); - showDialog(builder.create()); } } } else if (!cell.isLoading()) { @@ -1154,17 +1278,14 @@ private void onItemClick(int index, View view, MessageObject message, int a) { Browser.openUrl(getParentActivity(), link); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } } private void openWebView(TLRPC.WebPage webPage) { - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - builder.setCustomView(new WebFrameLayout(getParentActivity(), builder.create(), webPage.site_name, webPage.description, webPage.url, webPage.embed_url, webPage.embed_width, webPage.embed_height)); - builder.setUseFullWidth(true); - showDialog(builder.create()); + EmbedBottomSheet.show(getParentActivity(), webPage.site_name, webPage.description, webPage.url, webPage.embed_url, webPage.embed_width, webPage.embed_height); } private void fixLayoutInternal() { @@ -1209,7 +1330,8 @@ private void fixLayoutInternal() { } } - private class SharedLinksAdapter extends BaseSectionsAdapter { + private class SharedLinksAdapter extends RecyclerListView.SectionsAdapter { + private Context mContext; public SharedLinksAdapter(Context context) { @@ -1222,7 +1344,7 @@ public Object getItem(int section, int position) { } @Override - public boolean isRowEnabled(int section, int row) { + public boolean isEnabled(int section, int row) { return row != 0; } @@ -1240,60 +1362,72 @@ public int getCountForSection(int section) { } @Override - public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new GreySectionCell(mContext); + public View getSectionHeaderView(int section, View view) { + if (view == null) { + view = new GraySectionCell(mContext); } if (section < sharedMediaData[3].sections.size()) { String name = sharedMediaData[3].sections.get(section); ArrayList messageObjects = sharedMediaData[3].sectionArrays.get(name); MessageObject messageObject = messageObjects.get(0); - ((GreySectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + ((GraySectionCell) view).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); } - return convertView; + return view; } @Override - public View getItemView(int section, int position, View convertView, ViewGroup parent) { - if (section < sharedMediaData[3].sections.size()) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new GraySectionCell(mContext); + break; + case 1: + view = new SharedLinkCell(mContext); + ((SharedLinkCell) view).setDelegate(new SharedLinkCell.SharedLinkCellDelegate() { + @Override + public void needOpenWebView(TLRPC.WebPage webPage) { + MediaActivity.this.openWebView(webPage); + } + + @Override + public boolean canPerformActions() { + return !actionBar.isActionModeShowed(); + } + }); + break; + case 2: + default: + view = new LoadingCell(mContext); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(int section, int position, RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() != 2) { String name = sharedMediaData[3].sections.get(section); ArrayList messageObjects = sharedMediaData[3].sectionArrays.get(name); - if (position == 0) { - if (convertView == null) { - convertView = new GreySectionCell(mContext); - } - MessageObject messageObject = messageObjects.get(0); - ((GreySectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); - } else { - if (convertView == null) { - convertView = new SharedLinkCell(mContext); - ((SharedLinkCell) convertView).setDelegate(new SharedLinkCell.SharedLinkCellDelegate() { - @Override - public void needOpenWebView(TLRPC.WebPage webPage) { - MediaActivity.this.openWebView(webPage); - } - - @Override - public boolean canPerformActions() { - return !actionBar.isActionModeShowed(); - } - }); + switch (holder.getItemViewType()) { + case 0: { + MessageObject messageObject = messageObjects.get(0); + ((GraySectionCell) holder.itemView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + break; } - SharedLinkCell sharedLinkCell = (SharedLinkCell) convertView; - MessageObject messageObject = messageObjects.get(position - 1); - sharedLinkCell.setLink(messageObject, position != messageObjects.size() || section == sharedMediaData[3].sections.size() - 1 && sharedMediaData[3].loading); - if (actionBar.isActionModeShowed()) { - sharedLinkCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); - } else { - sharedLinkCell.setChecked(false, !scrolling); + case 1: { + SharedLinkCell sharedLinkCell = (SharedLinkCell) holder.itemView; + MessageObject messageObject = messageObjects.get(position - 1); + sharedLinkCell.setLink(messageObject, position != messageObjects.size() || section == sharedMediaData[3].sections.size() - 1 && sharedMediaData[3].loading); + if (actionBar.isActionModeShowed()) { + sharedLinkCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); + } else { + sharedLinkCell.setChecked(false, !scrolling); + } + break; } } - } else { - if (convertView == null) { - convertView = new LoadingCell(mContext); - } } - return convertView; } @Override @@ -1309,12 +1443,17 @@ public int getItemViewType(int section, int position) { } @Override - public int getViewTypeCount() { - return 3; + public String getLetter(int position) { + return null; + } + + @Override + public int getPositionForScrollProgress(float progress) { + return 0; } } - private class SharedDocumentsAdapter extends BaseSectionsAdapter { + private class SharedDocumentsAdapter extends RecyclerListView.SectionsAdapter { private Context mContext; private int currentType; @@ -1325,12 +1464,7 @@ public SharedDocumentsAdapter(Context context, int type) { } @Override - public Object getItem(int section, int position) { - return null; - } - - @Override - public boolean isRowEnabled(int section, int row) { + public boolean isEnabled(int section, int row) { return row != 0; } @@ -1339,6 +1473,11 @@ public int getSectionCount() { return sharedMediaData[currentType].sections.size() + (sharedMediaData[currentType].sections.isEmpty() || sharedMediaData[currentType].endReached[0] && sharedMediaData[currentType].endReached[1] ? 0 : 1); } + @Override + public Object getItem(int section, int position) { + return null; + } + @Override public int getCountForSection(int section) { if (section < sharedMediaData[currentType].sections.size()) { @@ -1348,49 +1487,61 @@ public int getCountForSection(int section) { } @Override - public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new GreySectionCell(mContext); + public View getSectionHeaderView(int section, View view) { + if (view == null) { + view = new GraySectionCell(mContext); } if (section < sharedMediaData[currentType].sections.size()) { String name = sharedMediaData[currentType].sections.get(section); ArrayList messageObjects = sharedMediaData[currentType].sectionArrays.get(name); MessageObject messageObject = messageObjects.get(0); - ((GreySectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + ((GraySectionCell) view).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); } - return convertView; + return view; } @Override - public View getItemView(int section, int position, View convertView, ViewGroup parent) { - if (section < sharedMediaData[currentType].sections.size()) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new GraySectionCell(mContext); + break; + case 1: + view = new SharedDocumentCell(mContext); + break; + case 2: + default: + view = new LoadingCell(mContext); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(int section, int position, RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() != 2) { String name = sharedMediaData[currentType].sections.get(section); ArrayList messageObjects = sharedMediaData[currentType].sectionArrays.get(name); - if (position == 0) { - if (convertView == null) { - convertView = new GreySectionCell(mContext); - } - MessageObject messageObject = messageObjects.get(0); - ((GreySectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); - } else { - if (convertView == null) { - convertView = new SharedDocumentCell(mContext); + switch (holder.getItemViewType()) { + case 0: { + MessageObject messageObject = messageObjects.get(0); + ((GraySectionCell) holder.itemView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + break; } - SharedDocumentCell sharedDocumentCell = (SharedDocumentCell) convertView; - MessageObject messageObject = messageObjects.get(position - 1); - sharedDocumentCell.setDocument(messageObject, position != messageObjects.size() || section == sharedMediaData[currentType].sections.size() - 1 && sharedMediaData[currentType].loading); - if (actionBar.isActionModeShowed()) { - sharedDocumentCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); - } else { - sharedDocumentCell.setChecked(false, !scrolling); + case 1: { + SharedDocumentCell sharedDocumentCell = (SharedDocumentCell) holder.itemView; + MessageObject messageObject = messageObjects.get(position - 1); + sharedDocumentCell.setDocument(messageObject, position != messageObjects.size() || section == sharedMediaData[currentType].sections.size() - 1 && sharedMediaData[currentType].loading); + if (actionBar.isActionModeShowed()) { + sharedDocumentCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); + } else { + sharedDocumentCell.setChecked(false, !scrolling); + } + break; } } - } else { - if (convertView == null) { - convertView = new LoadingCell(mContext); - } } - return convertView; } @Override @@ -1406,12 +1557,18 @@ public int getItemViewType(int section, int position) { } @Override - public int getViewTypeCount() { - return 3; + public String getLetter(int position) { + return null; + } + + @Override + public int getPositionForScrollProgress(float progress) { + return 0; } } - private class SharedPhotoVideoAdapter extends BaseSectionsAdapter { + private class SharedPhotoVideoAdapter extends RecyclerListView.SectionsAdapter { + private Context mContext; public SharedPhotoVideoAdapter(Context context) { @@ -1424,7 +1581,7 @@ public Object getItem(int section, int position) { } @Override - public boolean isRowEnabled(int section, int row) { + public boolean isEnabled(int section, int row) { return false; } @@ -1442,80 +1599,90 @@ public int getCountForSection(int section) { } @Override - public View getSectionHeaderView(int section, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = new SharedMediaSectionCell(mContext); - convertView.setBackgroundColor(0xffffffff); + public View getSectionHeaderView(int section, View view) { + if (view == null) { + view = new SharedMediaSectionCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } if (section < sharedMediaData[0].sections.size()) { String name = sharedMediaData[0].sections.get(section); ArrayList messageObjects = sharedMediaData[0].sectionArrays.get(name); MessageObject messageObject = messageObjects.get(0); - ((SharedMediaSectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + ((SharedMediaSectionCell) view).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); } - return convertView; + return view; } @Override - public View getItemView(int section, int position, View convertView, ViewGroup parent) { - if (section < sharedMediaData[0].sections.size()) { - String name = sharedMediaData[0].sections.get(section); - ArrayList messageObjects = sharedMediaData[0].sectionArrays.get(name); - if (position == 0) { - if (convertView == null) { - convertView = new SharedMediaSectionCell(mContext); + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new SharedMediaSectionCell(mContext); + break; + case 1: + if (!cellCache.isEmpty()) { + view = cellCache.get(0); + cellCache.remove(0); + } else { + view = new SharedPhotoVideoCell(mContext); } - MessageObject messageObject = messageObjects.get(0); - ((SharedMediaSectionCell) convertView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); - } else { - SharedPhotoVideoCell cell; - if (convertView == null) { - if (!cellCache.isEmpty()) { - convertView = cellCache.get(0); - cellCache.remove(0); - } else { - convertView = new SharedPhotoVideoCell(mContext); + SharedPhotoVideoCell cell = (SharedPhotoVideoCell) view; + cell.setDelegate(new SharedPhotoVideoCell.SharedPhotoVideoCellDelegate() { + @Override + public void didClickItem(SharedPhotoVideoCell cell, int index, MessageObject messageObject, int a) { + onItemClick(index, cell, messageObject, a); } - cell = (SharedPhotoVideoCell) convertView; - cell.setDelegate(new SharedPhotoVideoCell.SharedPhotoVideoCellDelegate() { - @Override - public void didClickItem(SharedPhotoVideoCell cell, int index, MessageObject messageObject, int a) { - onItemClick(index, cell, messageObject, a); - } - @Override - public boolean didLongClickItem(SharedPhotoVideoCell cell, int index, MessageObject messageObject, int a) { - return onItemLongClick(messageObject, cell, a); - } - }); - } else { - cell = (SharedPhotoVideoCell) convertView; + @Override + public boolean didLongClickItem(SharedPhotoVideoCell cell, int index, MessageObject messageObject, int a) { + return onItemLongClick(messageObject, cell, a); + } + }); + break; + case 2: + default: + view = new LoadingCell(mContext); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(int section, int position, RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() != 2) { + String name = sharedMediaData[0].sections.get(section); + ArrayList messageObjects = sharedMediaData[0].sectionArrays.get(name); + switch (holder.getItemViewType()) { + case 0: { + MessageObject messageObject = messageObjects.get(0); + ((SharedMediaSectionCell) holder.itemView).setText(LocaleController.getInstance().formatterMonthYear.format((long) messageObject.messageOwner.date * 1000).toUpperCase()); + break; } - cell.setItemsCount(columnsCount); - for (int a = 0; a < columnsCount; a++) { - int index = (position - 1) * columnsCount + a; - if (index < messageObjects.size()) { - MessageObject messageObject = messageObjects.get(index); - cell.setIsFirst(position == 1); - cell.setItem(a, sharedMediaData[0].messages.indexOf(messageObject), messageObject); - - if (actionBar.isActionModeShowed()) { - cell.setChecked(a, selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); + case 1: { + SharedPhotoVideoCell cell = (SharedPhotoVideoCell) holder.itemView; + cell.setItemsCount(columnsCount); + for (int a = 0; a < columnsCount; a++) { + int index = (position - 1) * columnsCount + a; + if (index < messageObjects.size()) { + MessageObject messageObject = messageObjects.get(index); + cell.setIsFirst(position == 1); + cell.setItem(a, sharedMediaData[0].messages.indexOf(messageObject), messageObject); + + if (actionBar.isActionModeShowed()) { + cell.setChecked(a, selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); + } else { + cell.setChecked(a, false, !scrolling); + } } else { - cell.setChecked(a, false, !scrolling); + cell.setItem(a, index, null); } - } else { - cell.setItem(a, index, null); } + cell.requestLayout(); + break; } - cell.requestLayout(); - } - } else { - if (convertView == null) { - convertView = new LoadingCell(mContext); } } - return convertView; } @Override @@ -1531,12 +1698,17 @@ public int getItemViewType(int section, int position) { } @Override - public int getViewTypeCount() { - return 3; + public String getLetter(int position) { + return null; + } + + @Override + public int getPositionForScrollProgress(float progress) { + return 0; } } - public class MediaSearchAdapter extends BaseFragmentAdapter { + public class MediaSearchAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private ArrayList searchResult = new ArrayList<>(); private Timer searchTimer; @@ -1617,7 +1789,7 @@ public void search(final String query) { searchTimer.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (query == null) { searchResult.clear(); @@ -1631,7 +1803,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } processSearch(query); } @@ -1734,17 +1906,12 @@ public void run() { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return i != searchResult.size() + globalSearch.size(); + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() != searchResult.size() + globalSearch.size(); } @Override - public int getCount() { + public int getItemCount() { int count = searchResult.size(); int globalCount = globalSearch.size(); if (globalCount != 0) { @@ -1764,7 +1931,6 @@ public boolean isGlobalSearch(int i) { return false; } - @Override public MessageObject getItem(int i) { if (i < searchResult.size()) { return searchResult.get(i); @@ -1774,69 +1940,140 @@ public MessageObject getItem(int i) { } @Override - public long getItemId(int i) { - return i; - } + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + if (currentType == 1 || currentType == 4) { + view = new SharedDocumentCell(mContext); + } else { + view = new SharedLinkCell(mContext); + ((SharedLinkCell) view).setDelegate(new SharedLinkCell.SharedLinkCellDelegate() { + @Override + public void needOpenWebView(TLRPC.WebPage webPage) { + MediaActivity.this.openWebView(webPage); + } - @Override - public boolean hasStableIds() { - return true; + @Override + public boolean canPerformActions() { + return !actionBar.isActionModeShowed(); + } + }); + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (currentType == 1 || currentType == 4) { - if (view == null) { - view = new SharedDocumentCell(mContext); - } - SharedDocumentCell sharedDocumentCell = (SharedDocumentCell) view; - MessageObject messageObject = getItem(i); - sharedDocumentCell.setDocument(messageObject, i != getCount() - 1); + SharedDocumentCell sharedDocumentCell = (SharedDocumentCell) holder.itemView; + MessageObject messageObject = getItem(position); + sharedDocumentCell.setDocument(messageObject, position != getItemCount() - 1); if (actionBar.isActionModeShowed()) { sharedDocumentCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); } else { sharedDocumentCell.setChecked(false, !scrolling); } } else if (currentType == 3) { - if (view == null) { - view = new SharedLinkCell(mContext); - ((SharedLinkCell) view).setDelegate(new SharedLinkCell.SharedLinkCellDelegate() { - @Override - public void needOpenWebView(TLRPC.WebPage webPage) { - MediaActivity.this.openWebView(webPage); - } - - @Override - public boolean canPerformActions() { - return !actionBar.isActionModeShowed(); - } - }); - } - SharedLinkCell sharedLinkCell = (SharedLinkCell) view; - MessageObject messageObject = getItem(i); - sharedLinkCell.setLink(messageObject, i != getCount() - 1); + SharedLinkCell sharedLinkCell = (SharedLinkCell) holder.itemView; + MessageObject messageObject = getItem(position); + sharedLinkCell.setLink(messageObject, position != getItemCount() - 1); if (actionBar.isActionModeShowed()) { sharedLinkCell.setChecked(selectedFiles[messageObject.getDialogId() == dialog_id ? 0 : 1].containsKey(messageObject.getId()), !scrolling); } else { sharedLinkCell.setChecked(false, !scrolling); } } - return view; } @Override public int getItemViewType(int i) { return 0; } + } - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return searchResult.isEmpty() && globalSearch.isEmpty(); - } + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof SharedPhotoVideoCell) { + ((SharedPhotoVideoCell) child).updateCheckboxColor(); + } + } + } + }; + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(emptyView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + new ThemeDescription(progressView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(dropDown, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(dropDown, 0, null, null, new Drawable[]{dropDownDrawable}, null, Theme.key_actionBarDefaultTitle), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_BACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_TOPBACKGROUND, null, null, null, null, Theme.key_actionBarActionModeDefaultTop), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_AM_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(selectedMessagesCountTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarActionModeDefaultIcon), + + new ThemeDescription(progressBar, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(emptyTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"dateTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, ThemeDescription.FLAG_PROGRESSBAR, new Class[]{SharedDocumentCell.class}, new String[]{"progressView"}, null, null, null, Theme.key_sharedMedia_startStopLoadIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"statusImageView"}, null, null, null, Theme.key_sharedMedia_startStopLoadIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{SharedDocumentCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{SharedDocumentCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkboxCheck), + new ThemeDescription(listView, ThemeDescription.FLAG_IMAGECOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"thumbImageView"}, null, null, null, Theme.key_files_folderIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{SharedDocumentCell.class}, new String[]{"extTextView"}, null, null, null, Theme.key_files_iconText), + + new ThemeDescription(listView, 0, new Class[]{GraySectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{GraySectionCell.class}, null, null, null, Theme.key_graySection), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{SharedLinkCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{SharedLinkCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_checkboxCheck), + new ThemeDescription(listView, 0, new Class[]{SharedLinkCell.class}, new String[]{"titleTextPaint"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{SharedLinkCell.class}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + new ThemeDescription(listView, 0, new Class[]{SharedLinkCell.class}, Theme.linkSelectionPaint, null, null, Theme.key_windowBackgroundWhiteLinkSelection), + new ThemeDescription(listView, 0, new Class[]{SharedLinkCell.class}, new String[]{"letterDrawable"}, null, null, null, Theme.key_sharedMedia_linkPlaceholderText), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{SharedLinkCell.class}, new String[]{"letterDrawable"}, null, null, null, Theme.key_sharedMedia_linkPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{SharedMediaSectionCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(listView, ThemeDescription.FLAG_SECTIONS, new Class[]{SharedMediaSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{SharedMediaSectionCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{SharedPhotoVideoCell.class}, null, null, сellDelegate, Theme.key_checkbox), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{SharedPhotoVideoCell.class}, null, null, сellDelegate, Theme.key_checkboxCheck), + + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerBackground), + new ThemeDescription(fragmentContextView, 0, new Class[]{FragmentContextView.class}, new String[]{"playButton"}, null, null, null, Theme.key_inappPlayerPlayPause), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"titleTextView"}, null, null, null, Theme.key_inappPlayerTitle), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{FragmentContextView.class}, new String[]{"frameLayout"}, null, null, null, Theme.key_inappPlayerPerformer), + new ThemeDescription(fragmentContextView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{FragmentContextView.class}, new String[]{"closeButton"}, null, null, null, Theme.key_inappPlayerClose), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java index 4d391aa0d8d..ba8f376f292 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NewContactActivity.java @@ -3,20 +3,21 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Vibrator; import android.telephony.TelephonyManager; @@ -40,7 +41,6 @@ import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ContactsController; import org.telegram.messenger.FileLog; @@ -54,7 +54,11 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.ContextProgressView; @@ -82,6 +86,8 @@ public class NewContactActivity extends BaseFragment implements AdapterView.OnIt private TextView countryButton; private AvatarDrawable avatarDrawable; private AnimatorSet editDoneItemAnimation; + private TextView textView; + private View lineView; private ArrayList countriesArray = new ArrayList<>(); private HashMap countriesMap = new HashMap<>(); @@ -136,7 +142,7 @@ public void onItemClick(int id) { } donePressed = true; showEditDoneProgress(true, true); - TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts(); + final TLRPC.TL_contacts_importContacts req = new TLRPC.TL_contacts_importContacts(); final TLRPC.TL_inputPhoneContact inputPhoneContact = new TLRPC.TL_inputPhoneContact(); inputPhoneContact.first_name = firstNameField.getText().toString(); inputPhoneContact.last_name = lastNameField.getText().toString(); @@ -171,7 +177,7 @@ public void onClick(DialogInterface dialog, int which) { intent.putExtra("sms_body", LocaleController.getString("InviteText", R.string.InviteText)); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -179,11 +185,7 @@ public void onClick(DialogInterface dialog, int which) { } } else { showEditDoneProgress(false, true); - if (error == null || error.text.startsWith("FLOOD_WAIT")) { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("FloodWait", R.string.FloodWait)); - } else { - needShowAlert(LocaleController.getString("AppName", R.string.AppName), LocaleController.getString("ErrorOccurred", R.string.ErrorOccurred) + "\n" + error.text); - } + AlertsCreator.processError(error, NewContactActivity.this, req); } } }); @@ -226,11 +228,12 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField = new EditText(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); firstNameField.setMaxLines(1); firstNameField.setLines(1); firstNameField.setSingleLine(true); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setGravity(Gravity.LEFT); firstNameField.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT); firstNameField.setImeOptions(EditorInfo.IME_ACTION_NEXT); @@ -268,8 +271,9 @@ public void afterTextChanged(Editable editable) { lastNameField = new EditText(context); lastNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - lastNameField.setHintTextColor(0xff979797); - lastNameField.setTextColor(0xff212121); + lastNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + lastNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + lastNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); lastNameField.setMaxLines(1); lastNameField.setLines(1); lastNameField.setSingleLine(true); @@ -311,7 +315,7 @@ public void afterTextChanged(Editable editable) { countryButton = new TextView(context); countryButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); countryButton.setPadding(AndroidUtilities.dp(6), AndroidUtilities.dp(10), AndroidUtilities.dp(6), 0); - countryButton.setTextColor(0xff212121); + countryButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); countryButton.setMaxLines(1); countryButton.setSingleLine(true); countryButton.setEllipsize(TextUtils.TruncateAt.END); @@ -321,10 +325,10 @@ public void afterTextChanged(Editable editable) { countryButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - CountrySelectActivity fragment = new CountrySelectActivity(); + CountrySelectActivity fragment = new CountrySelectActivity(true); fragment.setCountrySelectActivityDelegate(new CountrySelectActivity.CountrySelectActivityDelegate() { @Override - public void didSelectCountry(String name) { + public void didSelectCountry(String name, String shortName) { selectCountry(name); AndroidUtilities.runOnUIThread(new Runnable() { @Override @@ -340,24 +344,25 @@ public void run() { } }); - View view = new View(context); - view.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); - view.setBackgroundColor(0xffdbdbdb); - linearLayout.addView(view, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 0, -17.5f, 0, 0)); + lineView = new View(context); + lineView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); + lineView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayLine)); + linearLayout.addView(lineView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1, 0, -17.5f, 0, 0)); LinearLayout linearLayout2 = new LinearLayout(context); linearLayout2.setOrientation(HORIZONTAL); linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 20, 0, 0)); - TextView textView = new TextView(context); + textView = new TextView(context); textView.setText("+"); - textView.setTextColor(0xff212121); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); linearLayout2.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); codeField = new EditText(context); codeField.setInputType(InputType.TYPE_CLASS_PHONE); - codeField.setTextColor(0xff212121); + codeField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + codeField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); AndroidUtilities.clearCursorDrawable(codeField); codeField.setPadding(AndroidUtilities.dp(10), 0, 0, 0); codeField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -458,8 +463,9 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { phoneField = new HintEditText(context); phoneField.setInputType(InputType.TYPE_CLASS_PHONE); - phoneField.setTextColor(0xff212121); - phoneField.setHintTextColor(0xff979797); + phoneField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + phoneField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + phoneField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); phoneField.setPadding(0, 0, 0, 0); AndroidUtilities.clearCursorDrawable(phoneField); phoneField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); @@ -568,7 +574,7 @@ public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { } reader.close(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Collections.sort(countriesArray, new Comparator() { @@ -586,7 +592,7 @@ public int compare(String lhs, String rhs) { country = telephonyManager.getSimCountryIso().toUpperCase(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (country != null) { @@ -708,7 +714,7 @@ private void showEditDoneProgress(final boolean show, boolean animated) { ObjectAnimator.ofFloat(editDoneItem.getImageView(), "alpha", 1.0f)); } - editDoneItemAnimation.addListener(new AnimatorListenerAdapterProxy() { + editDoneItemAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (editDoneItemAnimation != null && editDoneItemAnimation.equals(animation)) { @@ -732,14 +738,65 @@ public void onAnimationCancel(Animator animation) { } } - private void needShowAlert(String title, String text) { - if (text == null || getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(title); - builder.setMessage(text); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), null); - showDialog(builder.create()); + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + if (avatarImage != null) { + avatarDrawable.setInfo(5, firstNameField.getText().toString(), lastNameField.getText().toString(), false); + avatarImage.invalidate(); + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(lastNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(lastNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(codeField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(codeField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(codeField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(phoneField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(phoneField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(phoneField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + + new ThemeDescription(textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(lineView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhiteGrayLine), + + new ThemeDescription(countryButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(editDoneItemProgress, 0, null, null, null, null, Theme.key_contextProgressInner2), + new ThemeDescription(editDoneItemProgress, 0, null, null, null, null, Theme.key_contextProgressOuter2), + + new ThemeDescription(null, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, сellDelegate, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + //TODO edittext + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java index 513a21ae7ff..829a4f11b56 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/NotificationsSettingsActivity.java @@ -3,13 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -19,13 +18,9 @@ import android.net.Uri; import android.os.Build; import android.provider.Settings; -import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; @@ -33,6 +28,8 @@ import org.telegram.messenger.NotificationsController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; @@ -40,7 +37,9 @@ import org.telegram.messenger.FileLog; import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.TextCheckCell; @@ -48,13 +47,16 @@ import org.telegram.ui.Cells.TextDetailSettingsCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Components.ColorPickerView; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; public class NotificationsSettingsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { - private ListView listView; + private RecyclerListView listView; private boolean reseting = false; + private ListAdapter adapter; private int notificationsServiceRow; private int notificationsServiceConnectionRow; @@ -82,6 +84,10 @@ public class NotificationsSettingsActivity extends BaseFragment implements Notif private int inappPreviewRow; private int inchatSoundRow; private int inappPriorityRow; + private int callsSectionRow2; + private int callsSectionRow; + private int callsVibrateRow; + private int callsRingtoneRow; private int eventsSectionRow2; private int eventsSectionRow; private int contactJoinedRow; @@ -134,6 +140,14 @@ public boolean onFragmentCreate() { } else { inappPriorityRow = -1; } + if (MessagesController.getInstance().callsEnabled) { + callsSectionRow2 = rowCount++; + callsSectionRow = rowCount++; + callsVibrateRow = rowCount++; + callsRingtoneRow = rowCount++; + } else { + callsSectionRow2 = callsSectionRow = callsVibrateRow = callsRingtoneRow = -1; + } eventsSectionRow2 = rowCount++; eventsSectionRow = rowCount++; contactJoinedRow = rowCount++; @@ -176,61 +190,64 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }); listView.setVerticalScrollBarEnabled(false); - frameLayout.addView(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - listView.setAdapter(new ListAdapter(context)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setAdapter(adapter = new ListAdapter(context)); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, final View view, final int i, long l) { + public void onItemClick(View view, final int position) { boolean enabled = false; - if (i == messageAlertRow || i == groupAlertRow) { + if (position == messageAlertRow || position == groupAlertRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); - if (i == messageAlertRow) { + if (position == messageAlertRow) { enabled = preferences.getBoolean("EnableAll", true); editor.putBoolean("EnableAll", !enabled); - } else if (i == groupAlertRow) { + } else if (position == groupAlertRow) { enabled = preferences.getBoolean("EnableGroup", true); editor.putBoolean("EnableGroup", !enabled); } editor.commit(); - updateServerNotificationsSettings(i == groupAlertRow); - } else if (i == messagePreviewRow || i == groupPreviewRow) { + updateServerNotificationsSettings(position == groupAlertRow); + } else if (position == messagePreviewRow || position == groupPreviewRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); - if (i == messagePreviewRow) { + if (position == messagePreviewRow) { enabled = preferences.getBoolean("EnablePreviewAll", true); editor.putBoolean("EnablePreviewAll", !enabled); - } else if (i == groupPreviewRow) { + } else if (position == groupPreviewRow) { enabled = preferences.getBoolean("EnablePreviewGroup", true); editor.putBoolean("EnablePreviewGroup", !enabled); } editor.commit(); - updateServerNotificationsSettings(i == groupPreviewRow); - } else if (i == messageSoundRow || i == groupSoundRow) { + updateServerNotificationsSettings(position == groupPreviewRow); + } else if (position == messageSoundRow || position == groupSoundRow || position == callsRingtoneRow) { try { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, position == callsRingtoneRow ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION); tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(position == callsRingtoneRow ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION)); Uri currentSound = null; String defaultPath = null; - Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + Uri defaultUri = position == callsRingtoneRow ? Settings.System.DEFAULT_RINGTONE_URI : Settings.System.DEFAULT_NOTIFICATION_URI; if (defaultUri != null) { defaultPath = defaultUri.getPath(); } - if (i == messageSoundRow) { + if (position == messageSoundRow) { String path = preferences.getString("GlobalSoundPath", defaultPath); if (path != null && !path.equals("NoSound")) { if (path.equals(defaultPath)) { @@ -239,7 +256,7 @@ public void onItemClick(AdapterView adapterView, final View view, final int i currentSound = Uri.parse(path); } } - } else if (i == groupSoundRow) { + } else if (position == groupSoundRow) { String path = preferences.getString("GroupSoundPath", defaultPath); if (path != null && !path.equals("NoSound")) { if (path.equals(defaultPath)) { @@ -248,13 +265,22 @@ public void onItemClick(AdapterView adapterView, final View view, final int i currentSound = Uri.parse(path); } } + } else if (position == callsRingtoneRow) { + String path = preferences.getString("CallsRingtonfePath", defaultPath); + if (path != null && !path.equals("NoSound")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); + } + } } tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); - startActivityForResult(tmpIntent, i); + startActivityForResult(tmpIntent, position); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } - } else if (i == resetNotificationsRow) { + } else if (position == resetNotificationsRow) { if (reseting) { return; } @@ -272,9 +298,7 @@ public void run() { SharedPreferences.Editor editor = preferences.edit(); editor.clear(); editor.commit(); - if (listView != null) { - listView.invalidateViews(); - } + adapter.notifyDataSetChanged(); if (getParentActivity() != null) { Toast toast = Toast.makeText(getParentActivity(), LocaleController.getString("ResetNotificationsText", R.string.ResetNotificationsText), Toast.LENGTH_SHORT); toast.show(); @@ -283,64 +307,64 @@ public void run() { }); } }); - } else if (i == inappSoundRow) { + } else if (position == inappSoundRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableInAppSounds", true); editor.putBoolean("EnableInAppSounds", !enabled); editor.commit(); - } else if (i == inappVibrateRow) { + } else if (position == inappVibrateRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableInAppVibrate", true); editor.putBoolean("EnableInAppVibrate", !enabled); editor.commit(); - } else if (i == inappPreviewRow) { + } else if (position == inappPreviewRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableInAppPreview", true); editor.putBoolean("EnableInAppPreview", !enabled); editor.commit(); - } else if (i == inchatSoundRow) { + } else if (position == inchatSoundRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableInChatSound", true); editor.putBoolean("EnableInChatSound", !enabled); editor.commit(); NotificationsController.getInstance().setInChatSoundEnabled(!enabled); - } else if (i == inappPriorityRow) { + } else if (position == inappPriorityRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableInAppPriority", false); editor.putBoolean("EnableInAppPriority", !enabled); editor.commit(); - } else if (i == contactJoinedRow) { + } else if (position == contactJoinedRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableContactJoined", true); MessagesController.getInstance().enableJoined = !enabled; editor.putBoolean("EnableContactJoined", !enabled); editor.commit(); - } else if (i == pinnedMessageRow) { + } else if (position == pinnedMessageRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("PinnedMessages", true); editor.putBoolean("PinnedMessages", !enabled); editor.commit(); - } else if (i == androidAutoAlertRow) { + } else if (position == androidAutoAlertRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("EnableAutoNotifications", false); editor.putBoolean("EnableAutoNotifications", !enabled); editor.commit(); - } else if (i == badgeNumberRow) { + } else if (position == badgeNumberRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); SharedPreferences.Editor editor = preferences.edit(); enabled = preferences.getBoolean("badgeNumber", true); editor.putBoolean("badgeNumber", !enabled); editor.commit(); NotificationsController.getInstance().setBadgeEnabled(!enabled); - } else if (i == notificationsServiceConnectionRow) { + } else if (position == notificationsServiceConnectionRow) { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); enabled = preferences.getBoolean("pushConnection", true); SharedPreferences.Editor editor = preferences.edit(); @@ -351,7 +375,7 @@ public void run() { } else { ConnectionsManager.getInstance().setPushConnectionEnabled(false); } - } else if (i == notificationsServiceRow) { + } else if (position == notificationsServiceRow) { final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); enabled = preferences.getBoolean("pushService", true); SharedPreferences.Editor editor = preferences.edit(); @@ -362,174 +386,52 @@ public void run() { } else { ApplicationLoader.stopPushService(); } - /*if (!enabled) { - - } else { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setMessage(LocaleController.getString("NotificationsServiceDisableInfo", R.string.NotificationsServiceDisableInfo)); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - - final SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean("pushService", false); - editor.commit(); - listView.invalidateViews(); - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ((TextCheckCell) view).setChecked(true); - } - }); - showDialog(builder.create()); - }*/ - } else if (i == messageLedRow || i == groupLedRow) { + } else if (position == messageLedRow || position == groupLedRow) { if (getParentActivity() == null) { return; } - - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - linearLayout.setOrientation(LinearLayout.VERTICAL); - final ColorPickerView colorPickerView = new ColorPickerView(getParentActivity()); - linearLayout.addView(colorPickerView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (i == messageLedRow) { - colorPickerView.setOldCenterColor(preferences.getInt("MessagesLed", 0xff00ff00)); - } else if (i == groupLedRow) { - colorPickerView.setOldCenterColor(preferences.getInt("GroupLed", 0xff00ff00)); - } - - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("LedColor", R.string.LedColor)); - builder.setView(linearLayout); - builder.setPositiveButton(LocaleController.getString("Set", R.string.Set), new DialogInterface.OnClickListener() { + showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), 0, position == groupLedRow, position == messageLedRow, new Runnable() { @Override - public void onClick(DialogInterface dialogInterface, int which) { - final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - TextColorCell textCell = (TextColorCell) view; - if (i == messageLedRow) { - editor.putInt("MessagesLed", colorPickerView.getColor()); - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), colorPickerView.getColor(), true); - } else if (i == groupLedRow) { - editor.putInt("GroupLed", colorPickerView.getColor()); - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), colorPickerView.getColor(), true); - } - editor.commit(); + public void run() { + adapter.notifyItemChanged(position); } - }); - builder.setNeutralButton(LocaleController.getString("LedDisabled", R.string.LedDisabled), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - TextColorCell textCell = (TextColorCell) view; - if (i == messageLedRow) { - editor.putInt("MessagesLed", 0); - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), 0, true); - } else if (i == groupLedRow) { - editor.putInt("GroupLed", 0); - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), 0, true); - } - editor.commit(); - listView.invalidateViews(); - } - }); - showDialog(builder.create()); - } else if (i == messagePopupNotificationRow || i == groupPopupNotificationRow) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("PopupNotification", R.string.PopupNotification)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("NoPopup", R.string.NoPopup), - LocaleController.getString("OnlyWhenScreenOn", R.string.OnlyWhenScreenOn), - LocaleController.getString("OnlyWhenScreenOff", R.string.OnlyWhenScreenOff), - LocaleController.getString("AlwaysShowPopup", R.string.AlwaysShowPopup) - }, new DialogInterface.OnClickListener() { + })); + } else if (position == messagePopupNotificationRow || position == groupPopupNotificationRow) { + if (getParentActivity() == null) { + return; + } + showDialog(AlertsCreator.createPopupSelectDialog(getParentActivity(), NotificationsSettingsActivity.this, position == groupPopupNotificationRow, position == messagePopupNotificationRow, new Runnable() { @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - if (i == messagePopupNotificationRow) { - editor.putInt("popupAll", which); - } else if (i == groupPopupNotificationRow) { - editor.putInt("popupGroup", which); - } - editor.commit(); - if (listView != null) { - listView.invalidateViews(); - } + public void run() { + adapter.notifyItemChanged(position); } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == messageVibrateRow || i == groupVibrateRow) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("Vibrate", R.string.Vibrate)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), - LocaleController.getString("VibrationDefault", R.string.VibrationDefault), - LocaleController.getString("Short", R.string.Short), - LocaleController.getString("Long", R.string.Long), - LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent) - }, new DialogInterface.OnClickListener() { + })); + } else if (position == messageVibrateRow || position == groupVibrateRow || position == callsVibrateRow) { + if (getParentActivity() == null) { + return; + } + String key = null; + if (position == messageVibrateRow) { + key = "vibrate_messages"; + } else if (position == groupVibrateRow) { + key = "vibrate_group"; + } else if (position == callsVibrateRow) { + key = "vibrate_calls"; + } + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), NotificationsSettingsActivity.this, 0, key, new Runnable() { @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - String param = "vibrate_messages"; - if (i == groupVibrateRow) { - param = "vibrate_group"; - } - if (which == 0) { - editor.putInt(param, 2); - } else if (which == 1) { - editor.putInt(param, 0); - } else if (which == 2) { - editor.putInt(param, 1); - } else if (which == 3) { - editor.putInt(param, 3); - } else if (which == 4) { - editor.putInt(param, 4); - } - editor.commit(); - if (listView != null) { - listView.invalidateViews(); - } + public void run() { + adapter.notifyItemChanged(position); } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == messagePriorityRow || i == groupPriorityRow) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), - LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), - LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax) - }, new DialogInterface.OnClickListener() { + })); + } else if (position == messagePriorityRow || position == groupPriorityRow) { + showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), NotificationsSettingsActivity.this, 0, position == groupPriorityRow, position == messagePriorityRow, new Runnable() { @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (i == messagePriorityRow) { - preferences.edit().putInt("priority_messages", which).commit(); - } else if (i == groupPriorityRow) { - preferences.edit().putInt("priority_group", which).commit(); - } - if (listView != null) { - listView.invalidateViews(); - } + public void run() { + adapter.notifyItemChanged(position); } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == repeatRow) { + })); + } else if (position == repeatRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications)); builder.setItems(new CharSequence[]{ @@ -559,9 +461,7 @@ public void onClick(DialogInterface dialog, int which) { } SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); preferences.edit().putInt("repeat_messages", minutes).commit(); - if (listView != null) { - listView.invalidateViews(); - } + adapter.notifyItemChanged(position); } }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); @@ -608,10 +508,18 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat if (ringtone != null) { Ringtone rng = RingtoneManager.getRingtone(getParentActivity(), ringtone); if (rng != null) { - if(ringtone.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { - name = LocaleController.getString("SoundDefault", R.string.SoundDefault); + if (requestCode == callsRingtoneRow) { + if (ringtone.equals(Settings.System.DEFAULT_RINGTONE_URI)) { + name = LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone); + } else { + name = rng.getTitle(getParentActivity()); + } } else { - name = rng.getTitle(getParentActivity()); + if (ringtone.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { + name = LocaleController.getString("SoundDefault", R.string.SoundDefault); + } else { + name = rng.getTitle(getParentActivity()); + } } rng.stop(); } @@ -636,20 +544,29 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat editor.putString("GroupSound", "NoSound"); editor.putString("GroupSoundPath", "NoSound"); } + } else if (requestCode == callsRingtoneRow) { + if (name != null && ringtone != null) { + editor.putString("CallsRingtone", name); + editor.putString("CallsRingtonePath", ringtone.toString()); + } else { + editor.putString("CallsRingtone", "NoSound"); + editor.putString("CallsRingtonePath", "NoSound"); + } } editor.commit(); - listView.invalidateViews(); + adapter.notifyItemChanged(requestCode); } } @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.notificationsSettingsUpdated) { - listView.invalidateViews(); + adapter.notifyDataSetChanged(); } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -657,236 +574,284 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return !(i == messageSectionRow || i == groupSectionRow || i == inappSectionRow || - i == eventsSectionRow || i == otherSectionRow || i == resetSectionRow || - i == eventsSectionRow2 || i == groupSectionRow2 || - i == inappSectionRow2 || i == otherSectionRow2 || i == resetSectionRow2); + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return !(position == messageSectionRow || position == groupSectionRow || position == inappSectionRow || + position == eventsSectionRow || position == otherSectionRow || position == resetSectionRow || + position == eventsSectionRow2 || position == groupSectionRow2 || + position == inappSectionRow2 || position == otherSectionRow2 || position == resetSectionRow2 || + position == callsSectionRow2 || position == callsSectionRow); } @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: view = new HeaderCell(mContext); - } - if (i == messageSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("MessageNotifications", R.string.MessageNotifications)); - } else if (i == groupSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("GroupNotifications", R.string.GroupNotifications)); - } else if (i == inappSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("InAppNotifications", R.string.InAppNotifications)); - } else if (i == eventsSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("Events", R.string.Events)); - } else if (i == otherSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("NotificationsOther", R.string.NotificationsOther)); - } else if (i == resetSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("Reset", R.string.Reset)); - } - } if (type == 1) { - if (view == null) { + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: view = new TextCheckCell(mContext); - } - TextCheckCell checkCell = (TextCheckCell) view; - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (i == messageAlertRow) { - checkCell.setTextAndCheck(LocaleController.getString("Alert", R.string.Alert), preferences.getBoolean("EnableAll", true), true); - } else if (i == groupAlertRow) { - checkCell.setTextAndCheck(LocaleController.getString("Alert", R.string.Alert), preferences.getBoolean("EnableGroup", true), true); - } else if (i == messagePreviewRow) { - checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("EnablePreviewAll", true), true); - } else if (i == groupPreviewRow) { - checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("EnablePreviewGroup", true), true); - } else if (i == inappSoundRow) { - checkCell.setTextAndCheck(LocaleController.getString("InAppSounds", R.string.InAppSounds), preferences.getBoolean("EnableInAppSounds", true), true); - } else if (i == inappVibrateRow) { - checkCell.setTextAndCheck(LocaleController.getString("InAppVibrate", R.string.InAppVibrate), preferences.getBoolean("EnableInAppVibrate", true), true); - } else if (i == inappPreviewRow) { - checkCell.setTextAndCheck(LocaleController.getString("InAppPreview", R.string.InAppPreview), preferences.getBoolean("EnableInAppPreview", true), true); - } else if (i == inappPriorityRow) { - checkCell.setTextAndCheck(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), preferences.getBoolean("EnableInAppPriority", false), false); - } else if (i == contactJoinedRow) { - checkCell.setTextAndCheck(LocaleController.getString("ContactJoined", R.string.ContactJoined), preferences.getBoolean("EnableContactJoined", true), true); - } else if (i == pinnedMessageRow) { - checkCell.setTextAndCheck(LocaleController.getString("PinnedMessages", R.string.PinnedMessages), preferences.getBoolean("PinnedMessages", true), false); - } else if (i == androidAutoAlertRow) { - checkCell.setTextAndCheck("Android Auto", preferences.getBoolean("EnableAutoNotifications", false), true); - } else if (i == notificationsServiceRow) { - checkCell.setTextAndValueAndCheck(LocaleController.getString("NotificationsService", R.string.NotificationsService), LocaleController.getString("NotificationsServiceInfo", R.string.NotificationsServiceInfo), preferences.getBoolean("pushService", true), true, true); - } else if (i == notificationsServiceConnectionRow) { - checkCell.setTextAndValueAndCheck(LocaleController.getString("NotificationsServiceConnection", R.string.NotificationsServiceConnection), LocaleController.getString("NotificationsServiceConnectionInfo", R.string.NotificationsServiceConnectionInfo), preferences.getBoolean("pushConnection", true), true, true); - } else if (i == badgeNumberRow) { - checkCell.setTextAndCheck(LocaleController.getString("BadgeNumber", R.string.BadgeNumber), preferences.getBoolean("badgeNumber", true), true); - } else if (i == inchatSoundRow) { - checkCell.setTextAndCheck(LocaleController.getString("InChatSound", R.string.InChatSound), preferences.getBoolean("EnableInChatSound", true), true); - } - } else if (type == 2) { - if (view == null) { + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: view = new TextDetailSettingsCell(mContext); - } - - TextDetailSettingsCell textCell = (TextDetailSettingsCell) view; - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = new TextColorCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 4: + view = new ShadowSectionCell(mContext); + break; + case 5: + default: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); + } - if (i == messageSoundRow || i == groupSoundRow) { - textCell.setMultilineDetail(false); - String value = null; - if (i == messageSoundRow) { - value = preferences.getString("GlobalSound", LocaleController.getString("SoundDefault", R.string.SoundDefault)); - } else if (i == groupSoundRow) { - value = preferences.getString("GroupSound", LocaleController.getString("SoundDefault", R.string.SoundDefault)); - } - if (value.equals("NoSound")) { - value = LocaleController.getString("NoSound", R.string.NoSound); - } - textCell.setTextAndValue(LocaleController.getString("Sound", R.string.Sound), value, true); - } else if (i == resetNotificationsRow) { - textCell.setMultilineDetail(true); - textCell.setTextAndValue(LocaleController.getString("ResetAllNotifications", R.string.ResetAllNotifications), LocaleController.getString("UndoAllCustom", R.string.UndoAllCustom), false); - } else if (i == messagePopupNotificationRow || i == groupPopupNotificationRow) { - textCell.setMultilineDetail(false); - int option = 0; - if (i == messagePopupNotificationRow) { - option = preferences.getInt("popupAll", 0); - } else if (i == groupPopupNotificationRow) { - option = preferences.getInt("popupGroup", 0); - } - String value; - if (option == 0) { - value = LocaleController.getString("NoPopup", R.string.NoPopup); - } else if (option == 1) { - value = LocaleController.getString("OnlyWhenScreenOn", R.string.OnlyWhenScreenOn); - } else if (option == 2) { - value = LocaleController.getString("OnlyWhenScreenOff", R.string.OnlyWhenScreenOff); - } else { - value = LocaleController.getString("AlwaysShowPopup", R.string.AlwaysShowPopup); - } - textCell.setTextAndValue(LocaleController.getString("PopupNotification", R.string.PopupNotification), value, true); - } else if (i == messageVibrateRow || i == groupVibrateRow) { - textCell.setMultilineDetail(false); - int value = 0; - if (i == messageVibrateRow) { - value = preferences.getInt("vibrate_messages", 0); - } else if (i == groupVibrateRow) { - value = preferences.getInt("vibrate_group", 0); + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == messageSectionRow) { + headerCell.setText(LocaleController.getString("MessageNotifications", R.string.MessageNotifications)); + } else if (position == groupSectionRow) { + headerCell.setText(LocaleController.getString("GroupNotifications", R.string.GroupNotifications)); + } else if (position == inappSectionRow) { + headerCell.setText(LocaleController.getString("InAppNotifications", R.string.InAppNotifications)); + } else if (position == eventsSectionRow) { + headerCell.setText(LocaleController.getString("Events", R.string.Events)); + } else if (position == otherSectionRow) { + headerCell.setText(LocaleController.getString("NotificationsOther", R.string.NotificationsOther)); + } else if (position == resetSectionRow) { + headerCell.setText(LocaleController.getString("Reset", R.string.Reset)); + } else if (position == callsSectionRow) { + headerCell.setText(LocaleController.getString("VoipNotificationSettings", R.string.VoipNotificationSettings)); } - if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), true); - } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), true); - } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), true); - } else if (value == 3) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), true); - } else if (value == 4) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent), true); + break; + } + case 1: { + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (position == messageAlertRow) { + checkCell.setTextAndCheck(LocaleController.getString("Alert", R.string.Alert), preferences.getBoolean("EnableAll", true), true); + } else if (position == groupAlertRow) { + checkCell.setTextAndCheck(LocaleController.getString("Alert", R.string.Alert), preferences.getBoolean("EnableGroup", true), true); + } else if (position == messagePreviewRow) { + checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("EnablePreviewAll", true), true); + } else if (position == groupPreviewRow) { + checkCell.setTextAndCheck(LocaleController.getString("MessagePreview", R.string.MessagePreview), preferences.getBoolean("EnablePreviewGroup", true), true); + } else if (position == inappSoundRow) { + checkCell.setTextAndCheck(LocaleController.getString("InAppSounds", R.string.InAppSounds), preferences.getBoolean("EnableInAppSounds", true), true); + } else if (position == inappVibrateRow) { + checkCell.setTextAndCheck(LocaleController.getString("InAppVibrate", R.string.InAppVibrate), preferences.getBoolean("EnableInAppVibrate", true), true); + } else if (position == inappPreviewRow) { + checkCell.setTextAndCheck(LocaleController.getString("InAppPreview", R.string.InAppPreview), preferences.getBoolean("EnableInAppPreview", true), true); + } else if (position == inappPriorityRow) { + checkCell.setTextAndCheck(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), preferences.getBoolean("EnableInAppPriority", false), false); + } else if (position == contactJoinedRow) { + checkCell.setTextAndCheck(LocaleController.getString("ContactJoined", R.string.ContactJoined), preferences.getBoolean("EnableContactJoined", true), true); + } else if (position == pinnedMessageRow) { + checkCell.setTextAndCheck(LocaleController.getString("PinnedMessages", R.string.PinnedMessages), preferences.getBoolean("PinnedMessages", true), false); + } else if (position == androidAutoAlertRow) { + checkCell.setTextAndCheck("Android Auto", preferences.getBoolean("EnableAutoNotifications", false), true); + } else if (position == notificationsServiceRow) { + checkCell.setTextAndValueAndCheck(LocaleController.getString("NotificationsService", R.string.NotificationsService), LocaleController.getString("NotificationsServiceInfo", R.string.NotificationsServiceInfo), preferences.getBoolean("pushService", true), true, true); + } else if (position == notificationsServiceConnectionRow) { + checkCell.setTextAndValueAndCheck(LocaleController.getString("NotificationsServiceConnection", R.string.NotificationsServiceConnection), LocaleController.getString("NotificationsServiceConnectionInfo", R.string.NotificationsServiceConnectionInfo), preferences.getBoolean("pushConnection", true), true, true); + } else if (position == badgeNumberRow) { + checkCell.setTextAndCheck(LocaleController.getString("BadgeNumber", R.string.BadgeNumber), preferences.getBoolean("badgeNumber", true), true); + } else if (position == inchatSoundRow) { + checkCell.setTextAndCheck(LocaleController.getString("InChatSound", R.string.InChatSound), preferences.getBoolean("EnableInChatSound", true), true); + } else if (position == callsVibrateRow) { + checkCell.setTextAndCheck(LocaleController.getString("Vibrate", R.string.Vibrate), preferences.getBoolean("EnableCallVibrate", true), true); } - } else if (i == repeatRow) { - textCell.setMultilineDetail(false); - int minutes = preferences.getInt("repeat_messages", 60); - String value; - if (minutes == 0) { - value = LocaleController.getString("RepeatNotificationsNever", R.string.RepeatNotificationsNever); - } else if (minutes < 60) { - value = LocaleController.formatPluralString("Minutes", minutes); + break; + } + case 2: { + TextDetailSettingsCell settingsCell = (TextDetailSettingsCell) holder.itemView; + settingsCell.setMultilineDetail(true); + settingsCell.setTextAndValue(LocaleController.getString("ResetAllNotifications", R.string.ResetAllNotifications), LocaleController.getString("UndoAllCustom", R.string.UndoAllCustom), false); + break; + } + case 3: { + TextColorCell textColorCell = (TextColorCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int color; + if (position == messageLedRow) { + color = preferences.getInt("MessagesLed", 0xff0000ff); } else { - value = LocaleController.formatPluralString("Hours", minutes / 60); + color = preferences.getInt("GroupLed", 0xff0000ff); } - textCell.setTextAndValue(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications), value, false); - } else if (i == messagePriorityRow || i == groupPriorityRow) { - textCell.setMultilineDetail(false); - int value = 0; - if (i == messagePriorityRow) { - value = preferences.getInt("priority_messages", 1); - } else if (i == groupPriorityRow) { - value = preferences.getInt("priority_group", 1); - } - if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), false); - } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), false); - } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax), false); + for (int a = 0; a < 9; a++) { + if (TextColorCell.colorsToSave[a] == color) { + color = TextColorCell.colors[a]; + break; + } } + textColorCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), color, true); + break; } - } else if (type == 3) { - if (view == null) { - view = new TextColorCell(mContext); - } - - TextColorCell textCell = (TextColorCell) view; - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (i == messageLedRow) { - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), preferences.getInt("MessagesLed", 0xff00ff00), true); - } else if (i == groupLedRow) { - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), preferences.getInt("GroupLed", 0xff00ff00), true); - } - } else if (type == 4) { - if (view == null) { - view = new ShadowSectionCell(mContext); - } + case 5: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (position == messageSoundRow || position == groupSoundRow || position == callsRingtoneRow) { + String value = null; + if (position == messageSoundRow) { + value = preferences.getString("GlobalSound", LocaleController.getString("SoundDefault", R.string.SoundDefault)); + } else if (position == groupSoundRow) { + value = preferences.getString("GroupSound", LocaleController.getString("SoundDefault", R.string.SoundDefault)); + } else if (position == callsRingtoneRow) { + value = preferences.getString("CallsRingtone", LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone)); + } + if (value.equals("NoSound")) { + value = LocaleController.getString("NoSound", R.string.NoSound); + } + if (position == callsRingtoneRow) { + textCell.setTextAndValue(LocaleController.getString("VoipSettingsRingtone", R.string.VoipSettingsRingtone), value, true); + } else { + textCell.setTextAndValue(LocaleController.getString("Sound", R.string.Sound), value, true); + } + } else if (position == messageVibrateRow || position == groupVibrateRow || position == callsVibrateRow) { + int value = 0; + if (position == messageVibrateRow) { + value = preferences.getInt("vibrate_messages", 0); + } else if (position == groupVibrateRow) { + value = preferences.getInt("vibrate_group", 0); + } else if (position == callsVibrateRow) { + value = preferences.getInt("vibrate_calls", 0); + } + if (value == 0) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), true); + } else if (value == 1) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), true); + } else if (value == 2) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), true); + } else if (value == 3) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), true); + } else if (value == 4) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("OnlyIfSilent", R.string.OnlyIfSilent), true); + } + } else if (position == repeatRow) { + int minutes = preferences.getInt("repeat_messages", 60); + String value; + if (minutes == 0) { + value = LocaleController.getString("RepeatNotificationsNever", R.string.RepeatNotificationsNever); + } else if (minutes < 60) { + value = LocaleController.formatPluralString("Minutes", minutes); + } else { + value = LocaleController.formatPluralString("Hours", minutes / 60); + } + textCell.setTextAndValue(LocaleController.getString("RepeatNotifications", R.string.RepeatNotifications), value, false); + } else if (position == messagePriorityRow || position == groupPriorityRow) { + int value = 0; + if (position == messagePriorityRow) { + value = preferences.getInt("priority_messages", 1); + } else if (position == groupPriorityRow) { + value = preferences.getInt("priority_group", 1); + } + if (value == 0) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), false); + } else if (value == 1) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), false); + } else if (value == 2) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax), false); + } + } else if (position == messagePopupNotificationRow || position == groupPopupNotificationRow) { + int option = 0; + if (position == messagePopupNotificationRow) { + option = preferences.getInt("popupAll", 0); + } else if (position == groupPopupNotificationRow) { + option = preferences.getInt("popupGroup", 0); + } + String value; + if (option == 0) { + value = LocaleController.getString("NoPopup", R.string.NoPopup); + } else if (option == 1) { + value = LocaleController.getString("OnlyWhenScreenOn", R.string.OnlyWhenScreenOn); + } else if (option == 2) { + value = LocaleController.getString("OnlyWhenScreenOff", R.string.OnlyWhenScreenOff); + } else { + value = LocaleController.getString("AlwaysShowPopup", R.string.AlwaysShowPopup); + } + textCell.setTextAndValue(LocaleController.getString("PopupNotification", R.string.PopupNotification), value, true); + } + break; } - return view; } @Override - public int getItemViewType(int i) { - if (i == messageSectionRow || i == groupSectionRow || i == inappSectionRow || - i == eventsSectionRow || i == otherSectionRow || i == resetSectionRow) { + public int getItemViewType(int position) { + if (position == messageSectionRow || position == groupSectionRow || position == inappSectionRow || + position == eventsSectionRow || position == otherSectionRow || position == resetSectionRow || + position == callsSectionRow) { return 0; - } else if (i == messageAlertRow || i == messagePreviewRow || i == groupAlertRow || - i == groupPreviewRow || i == inappSoundRow || i == inappVibrateRow || - i == inappPreviewRow || i == contactJoinedRow || i == pinnedMessageRow || - i == notificationsServiceRow || i == badgeNumberRow || i == inappPriorityRow || - i == inchatSoundRow || i == androidAutoAlertRow || i == notificationsServiceConnectionRow) { + } else if (position == messageAlertRow || position == messagePreviewRow || position == groupAlertRow || + position == groupPreviewRow || position == inappSoundRow || position == inappVibrateRow || + position == inappPreviewRow || position == contactJoinedRow || position == pinnedMessageRow || + position == notificationsServiceRow || position == badgeNumberRow || position == inappPriorityRow || + position == inchatSoundRow || position == androidAutoAlertRow || position == notificationsServiceConnectionRow) { return 1; - } else if (i == messageLedRow || i == groupLedRow) { + } else if (position == messageLedRow || position == groupLedRow) { return 3; - } else if (i == eventsSectionRow2 || i == groupSectionRow2 || - i == inappSectionRow2 || i == otherSectionRow2 || i == resetSectionRow2) { + } else if (position == eventsSectionRow2 || position == groupSectionRow2 || + position == inappSectionRow2 || position == otherSectionRow2 || position == resetSectionRow2 || + position == callsSectionRow2) { return 4; - } else { + } else if (position == resetNotificationsRow) { return 2; + } else { + return 5; } } + } - @Override - public int getViewTypeCount() { - return 5; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{HeaderCell.class, TextCheckCell.class, TextDetailSettingsCell.class, TextColorCell.class, TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(listView, 0, new Class[]{TextColorCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java index fa4904cf1e6..2ef34a1ba83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PasscodeActivity.java @@ -3,19 +3,20 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.res.Configuration; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Vibrator; -import android.support.v4.hardware.fingerprint.FingerprintManagerCompat; import android.text.Editable; import android.text.InputFilter; import android.text.InputType; @@ -33,10 +34,8 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; @@ -48,25 +47,32 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.fingerprint.FingerprintManagerCompat; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberPicker; +import org.telegram.ui.Components.RecyclerListView; public class PasscodeActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; - private ListView listView; + private RecyclerListView listView; private TextView titleTextView; private EditText passwordEditText; private TextView dropDown; private ActionBarMenuItem dropDownContainer; + private Drawable dropDownDrawable; private int type; private int currentPasswordType = 0; @@ -76,6 +82,8 @@ public class PasscodeActivity extends BaseFragment implements NotificationCenter private int passcodeRow; private int changePasscodeRow; private int passcodeDetailRow; + private int captureRow; + private int captureDetailRow; private int fingerprintRow; private int autoLockRow; private int autoLockDetailRow; @@ -143,7 +151,7 @@ public void onItemClick(int id) { menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); titleTextView = new TextView(context); - titleTextView.setTextColor(0xff757575); + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); if (type == 1) { if (UserConfig.passcodeHash.length() != 0) { titleTextView.setText(LocaleController.getString("EnterNewPasscode", R.string.EnterNewPasscode)); @@ -155,17 +163,12 @@ public void onItemClick(int id) { } titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); - frameLayout.addView(titleTextView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) titleTextView.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.CENTER_HORIZONTAL; - layoutParams.topMargin = AndroidUtilities.dp(38); - titleTextView.setLayoutParams(layoutParams); + frameLayout.addView(titleTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 38, 0, 0)); passwordEditText = new EditText(context); passwordEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - passwordEditText.setTextColor(0xff000000); + passwordEditText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + passwordEditText.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); passwordEditText.setMaxLines(1); passwordEditText.setLines(1); passwordEditText.setGravity(Gravity.CENTER_HORIZONTAL); @@ -180,15 +183,7 @@ public void onItemClick(int id) { passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); passwordEditText.setTypeface(Typeface.DEFAULT); AndroidUtilities.clearCursorDrawable(passwordEditText); - frameLayout.addView(passwordEditText); - layoutParams = (FrameLayout.LayoutParams) passwordEditText.getLayoutParams(); - layoutParams.topMargin = AndroidUtilities.dp(90); - layoutParams.height = AndroidUtilities.dp(36); - layoutParams.leftMargin = AndroidUtilities.dp(40); - layoutParams.gravity = Gravity.TOP | Gravity.LEFT; - layoutParams.rightMargin = AndroidUtilities.dp(40); - layoutParams.width = LayoutHelper.MATCH_PARENT; - passwordEditText.setLayoutParams(layoutParams); + frameLayout.addView(passwordEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 40, 90, 40, 0)); passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -247,18 +242,12 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { }); if (type == 1) { - dropDownContainer = new ActionBarMenuItem(context, menu, 0); + frameLayout.setTag(Theme.key_windowBackgroundWhite); + dropDownContainer = new ActionBarMenuItem(context, menu, 0, 0); dropDownContainer.setSubMenuOpenSide(1); - dropDownContainer.addSubItem(pin_item, LocaleController.getString("PasscodePIN", R.string.PasscodePIN), 0); - dropDownContainer.addSubItem(password_item, LocaleController.getString("PasscodePassword", R.string.PasscodePassword), 0); - actionBar.addView(dropDownContainer); - layoutParams = (FrameLayout.LayoutParams) dropDownContainer.getLayoutParams(); - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.rightMargin = AndroidUtilities.dp(40); - layoutParams.leftMargin = AndroidUtilities.isTablet() ? AndroidUtilities.dp(64) : AndroidUtilities.dp(56); - layoutParams.gravity = Gravity.TOP | Gravity.LEFT; - dropDownContainer.setLayoutParams(layoutParams); + dropDownContainer.addSubItem(pin_item, LocaleController.getString("PasscodePIN", R.string.PasscodePIN)); + dropDownContainer.addSubItem(password_item, LocaleController.getString("PasscodePassword", R.string.PasscodePassword)); + actionBar.addView(dropDownContainer, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT, AndroidUtilities.isTablet() ? 64 : 56, 0, 40, 0)); dropDownContainer.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -272,19 +261,14 @@ public void onClick(View view) { dropDown.setLines(1); dropDown.setMaxLines(1); dropDown.setEllipsize(TextUtils.TruncateAt.END); - dropDown.setTextColor(0xffffffff); + dropDown.setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); dropDown.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - dropDown.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_arrow_drop_down, 0); + dropDownDrawable = context.getResources().getDrawable(R.drawable.ic_arrow_drop_down).mutate(); + dropDownDrawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_actionBarDefaultTitle), PorterDuff.Mode.MULTIPLY)); + dropDown.setCompoundDrawablesWithIntrinsicBounds(null, null, dropDownDrawable, null); dropDown.setCompoundDrawablePadding(AndroidUtilities.dp(4)); dropDown.setPadding(0, 0, AndroidUtilities.dp(10), 0); - dropDownContainer.addView(dropDown); - layoutParams = (FrameLayout.LayoutParams) dropDown.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.leftMargin = AndroidUtilities.dp(16); - layoutParams.gravity = Gravity.CENTER_VERTICAL; - layoutParams.bottomMargin = AndroidUtilities.dp(1); - dropDown.setLayoutParams(layoutParams); + dropDownContainer.addView(dropDown, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 16, 0, 0, 1)); } else { actionBar.setTitle(LocaleController.getString("Passcode", R.string.Passcode)); } @@ -292,25 +276,29 @@ public void onClick(View view) { updateDropDownTextView(); } else { actionBar.setTitle(LocaleController.getString("Passcode", R.string.Passcode)); - frameLayout.setBackgroundColor(0xfff0f0f0); - listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + frameLayout.setTag(Theme.key_windowBackgroundGray); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); - frameLayout.addView(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - listView.setLayoutParams(layoutParams); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter = new ListAdapter(context)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == changePasscodeRow) { + public void onItemClick(View view, final int position) { + if (!view.isEnabled()) { + return; + } + if (position == changePasscodeRow) { presentFragment(new PasscodeActivity(1)); - } else if (i == passcodeRow) { + } else if (position == passcodeRow) { TextCheckCell cell = (TextCheckCell) view; if (UserConfig.passcodeHash.length() != 0) { UserConfig.passcodeHash = ""; @@ -321,7 +309,7 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long View child = listView.getChildAt(a); if (child instanceof TextSettingsCell) { TextSettingsCell textCell = (TextSettingsCell) child; - textCell.setTextColor(0xffc6c6c6); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText7)); break; } } @@ -330,7 +318,7 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long } else { presentFragment(new PasscodeActivity(1)); } - } else if (i == autoLockRow) { + } else if (position == autoLockRow) { if (getParentActivity() == null) { return; } @@ -383,15 +371,20 @@ public void onClick(DialogInterface dialog, int which) { } else if (which == 4) { UserConfig.autoLockIn = 60 * 60 * 5; } - listView.invalidateViews(); + listAdapter.notifyItemChanged(position); UserConfig.saveConfig(false); } }); showDialog(builder.create()); - } else if (i == fingerprintRow) { + } else if (position == fingerprintRow) { UserConfig.useFingerprint = !UserConfig.useFingerprint; UserConfig.saveConfig(false); ((TextCheckCell) view).setChecked(UserConfig.useFingerprint); + } else if (position == captureRow) { + UserConfig.allowScreenCapture = !UserConfig.allowScreenCapture; + UserConfig.saveConfig(false); + ((TextCheckCell) view).setChecked(UserConfig.allowScreenCapture); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didSetPasscode); } } }); @@ -446,11 +439,15 @@ private void updateRows() { } } } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } autoLockRow = rowCount++; autoLockDetailRow = rowCount++; + captureRow = rowCount++; + captureDetailRow = rowCount++; } else { + captureRow = -1; + captureDetailRow = -1; fingerprintRow = -1; autoLockRow = -1; autoLockDetailRow = -1; @@ -529,7 +526,7 @@ private void processDone() { try { Toast.makeText(getParentActivity(), LocaleController.getString("PasscodeDoNotMatch", R.string.PasscodeDoNotMatch), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } AndroidUtilities.shakeView(titleTextView, 2, 0); passwordEditText.setText(""); @@ -546,7 +543,7 @@ private void processDone() { System.arraycopy(UserConfig.passcodeSalt, 0, bytes, passcodeBytes.length + 16, 16); UserConfig.passcodeHash = Utilities.bytesToHex(Utilities.computeSHA256(bytes, 0, bytes.length)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } UserConfig.passcodeType = currentPasswordType; @@ -593,7 +590,8 @@ private void fixLayoutInternal() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -601,112 +599,150 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return i == passcodeRow || i == fingerprintRow || i == autoLockRow || UserConfig.passcodeHash.length() != 0 && i == changePasscodeRow; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == passcodeRow || position == fingerprintRow || position == autoLockRow || position == captureRow || UserConfig.passcodeHash.length() != 0 && position == changePasscodeRow; } @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextCheckCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + default: + view = new TextInfoPrivacyCell(mContext); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int viewType = getItemViewType(i); - if (viewType == 0) { - if (view == null) { - view = new TextCheckCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextCheckCell textCell = (TextCheckCell) view; - - if (i == passcodeRow) { - textCell.setTextAndCheck(LocaleController.getString("Passcode", R.string.Passcode), UserConfig.passcodeHash.length() > 0, true); - } else if (i == fingerprintRow) { - textCell.setTextAndCheck(LocaleController.getString("UnlockFingerprint", R.string.UnlockFingerprint), UserConfig.useFingerprint, true); - } - } else if (viewType == 1) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == changePasscodeRow) { - textCell.setText(LocaleController.getString("ChangePasscode", R.string.ChangePasscode), false); - textCell.setTextColor(UserConfig.passcodeHash.length() == 0 ? 0xffc6c6c6 : 0xff000000); - } else if (i == autoLockRow) { - String val; - if (UserConfig.autoLockIn == 0) { - val = LocaleController.formatString("AutoLockDisabled", R.string.AutoLockDisabled); - } else if (UserConfig.autoLockIn < 60 * 60) { - val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Minutes", UserConfig.autoLockIn / 60)); - } else if (UserConfig.autoLockIn < 60 * 60 * 24) { - val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Hours", (int) Math.ceil(UserConfig.autoLockIn / 60.0f / 60))); - } else { - val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Days", (int) Math.ceil(UserConfig.autoLockIn / 60.0f / 60 / 24))); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + TextCheckCell textCell = (TextCheckCell) holder.itemView; + if (position == passcodeRow) { + textCell.setTextAndCheck(LocaleController.getString("Passcode", R.string.Passcode), UserConfig.passcodeHash.length() > 0, true); + } else if (position == fingerprintRow) { + textCell.setTextAndCheck(LocaleController.getString("UnlockFingerprint", R.string.UnlockFingerprint), UserConfig.useFingerprint, true); + } else if (position == captureRow) { + textCell.setTextAndCheck(LocaleController.getString("ScreenCapture", R.string.ScreenCapture), UserConfig.allowScreenCapture, false); } - textCell.setTextAndValue(LocaleController.getString("AutoLock", R.string.AutoLock), val, true); - textCell.setTextColor(0xff000000); + break; } - } else if (viewType == 2) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == changePasscodeRow) { + textCell.setText(LocaleController.getString("ChangePasscode", R.string.ChangePasscode), false); + if (UserConfig.passcodeHash.length() == 0) { + textCell.setTag(Theme.key_windowBackgroundWhiteGrayText7); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText7)); + } else { + textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + } + } else if (position == autoLockRow) { + String val; + if (UserConfig.autoLockIn == 0) { + val = LocaleController.formatString("AutoLockDisabled", R.string.AutoLockDisabled); + } else if (UserConfig.autoLockIn < 60 * 60) { + val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Minutes", UserConfig.autoLockIn / 60)); + } else if (UserConfig.autoLockIn < 60 * 60 * 24) { + val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Hours", (int) Math.ceil(UserConfig.autoLockIn / 60.0f / 60))); + } else { + val = LocaleController.formatString("AutoLockInTime", R.string.AutoLockInTime, LocaleController.formatPluralString("Days", (int) Math.ceil(UserConfig.autoLockIn / 60.0f / 60 / 24))); + } + textCell.setTextAndValue(LocaleController.getString("AutoLock", R.string.AutoLock), val, true); + textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + } + break; } - if (i == passcodeDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("ChangePasscodeInfo", R.string.ChangePasscodeInfo)); - if (autoLockDetailRow != -1) { - view.setBackgroundResource(R.drawable.greydivider); - } else { - view.setBackgroundResource(R.drawable.greydivider_bottom); + case 2: { + TextInfoPrivacyCell cell = (TextInfoPrivacyCell) holder.itemView; + if (position == passcodeDetailRow) { + cell.setText(LocaleController.getString("ChangePasscodeInfo", R.string.ChangePasscodeInfo)); + if (autoLockDetailRow != -1) { + cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else { + cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + } else if (position == autoLockDetailRow) { + cell.setText(LocaleController.getString("AutoLockInfo", R.string.AutoLockInfo)); + cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == captureDetailRow) { + cell.setText(LocaleController.getString("ScreenCaptureInfo", R.string.ScreenCaptureInfo)); + cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } - } else if (i == autoLockDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("AutoLockInfo", R.string.AutoLockInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); + break; } } - return view; } @Override - public int getItemViewType(int i) { - if (i == passcodeRow || i == fingerprintRow) { + public int getItemViewType(int position) { + if (position == passcodeRow || position == fingerprintRow || position == captureRow) { return 0; - } else if (i == changePasscodeRow || i == autoLockRow) { + } else if (position == changePasscodeRow || position == autoLockRow) { return 1; - } else if (i == passcodeDetailRow || i == autoLockDetailRow) { + } else if (position == passcodeDetailRow || position == autoLockDetailRow || position == captureDetailRow) { return 2; } return 0; } + } - @Override - public int getViewTypeCount() { - return 3; - } - - @Override - public boolean isEmpty() { - return false; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextCheckCell.class, TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND | ThemeDescription.FLAG_CHECKTAG, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(titleTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + new ThemeDescription(dropDown, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(dropDown, 0, null, null, new Drawable[]{dropDownDrawable}, null, Theme.key_actionBarDefaultTitle), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText7), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java new file mode 100644 index 00000000000..4dff23c0b8a --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/PaymentFormActivity.java @@ -0,0 +1,2597 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageManager; +import android.graphics.Typeface; +import android.os.Build; +import android.os.Vibrator; +import android.telephony.TelephonyManager; +import android.text.Editable; +import android.text.InputFilter; +import android.text.InputType; +import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; +import android.text.method.PasswordTransformationMethod; +import android.text.style.ClickableSpan; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.webkit.CookieManager; +import android.webkit.JavascriptInterface; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.stripe.android.Stripe; +import com.stripe.android.TokenCallback; +import com.stripe.android.exception.APIConnectionException; +import com.stripe.android.exception.APIException; +import com.stripe.android.model.Card; +import com.stripe.android.model.Token; + +import org.json.JSONObject; +import org.telegram.PhoneFormat.PhoneFormat; +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.browser.Browser; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.PaymentInfoCell; +import org.telegram.ui.Cells.RadioCell; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextCheckCell; +import org.telegram.ui.Cells.TextDetailSettingsCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextPriceCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.AlertsCreator; +import org.telegram.ui.Components.ContextProgressView; +import org.telegram.ui.Components.HintEditText; +import org.telegram.ui.Components.LayoutHelper; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Locale; + +public class PaymentFormActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private final static int FIELD_CARD = 0; + private final static int FIELD_EXPIRE_DATE = 1; + private final static int FIELD_CARDNAME = 2; + private final static int FIELD_CVV = 3; + private final static int FIELD_CARD_COUNTRY = 4; + private final static int FIELD_CARD_POSTCODE = 5; + private final static int FIELDS_COUNT_CARD = 6; + + private final static int FIELD_STREET1 = 0; + private final static int FIELD_STREET2 = 1; + private final static int FIELD_CITY = 2; + private final static int FIELD_STATE = 3; + private final static int FIELD_COUNTRY = 4; + private final static int FIELD_POSTCODE = 5; + private final static int FIELD_NAME = 6; + private final static int FIELD_EMAIL = 7; + private final static int FIELD_PHONECODE = 8; + private final static int FIELD_PHONE = 9; + private final static int FIELDS_COUNT_ADDRESS = 10; + + private final static int FIELD_SAVEDCARD = 0; + private final static int FIELD_SAVEDPASSWORD = 1; + private final static int FIELDS_COUNT_SAVEDCARD = 2; + + private ArrayList countriesArray = new ArrayList<>(); + private HashMap countriesMap = new HashMap<>(); + private HashMap codesMap = new HashMap<>(); + private HashMap phoneFormatMap = new HashMap<>(); + +// private GoogleApiClient googleApiClient; +// private WalletFragment walletFragment; + + private EditText[] inputFields; + private RadioCell[] radioCells; + private ActionBarMenuItem doneItem; + private ContextProgressView progressView; + private AnimatorSet doneItemAnimation; + private WebView webView; + private ScrollView scrollView; + + private TextView textView; + private HeaderCell headerCell[] = new HeaderCell[3]; + private ArrayList dividers = new ArrayList<>(); + private ShadowSectionCell sectionCell[] = new ShadowSectionCell[3]; + private TextCheckCell checkCell1; + private TextInfoPrivacyCell bottomCell[] = new TextInfoPrivacyCell[2]; + private TextSettingsCell settingsCell1; + private LinearLayout linearLayout2; + + private PaymentFormActivityDelegate delegate; + + private TextView payTextView; + private FrameLayout bottomLayout; + private PaymentInfoCell paymentInfoCell; + private TextDetailSettingsCell detailSettingsCell[] = new TextDetailSettingsCell[6]; + + private boolean need_card_country; + private boolean need_card_postcode; + private boolean need_card_name; + private String stripeApiKey; + + private boolean ignoreOnTextChange; + private boolean ignoreOnPhoneChange; + private boolean ignoreOnCardChange; + + private String currentBotName; + private String currentItemName; + + private int currentStep; + private boolean passwordOk; + private String paymentJson; + private String cardName; + private boolean webviewLoading; + private String countryName; + private TLRPC.TL_payments_paymentForm paymentForm; + private TLRPC.TL_payments_validatedRequestedInfo requestedInfo; + private TLRPC.TL_shippingOption shippingOption; + private TLRPC.TL_payments_validateRequestedInfo validateRequest; + private MessageObject messageObject; + private boolean donePressed; + private boolean canceled; + + private boolean saveShippingInfo; + private boolean saveCardInfo; + + private final static int done_button = 1; + + private interface PaymentFormActivityDelegate { + void didSelectNewCard(String tokenJson, String card, boolean saveCard); + } + + private class TelegramWebviewProxy { + @JavascriptInterface + public void postEvent(final String eventName, final String eventData) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (getParentActivity() == null) { + return; + } + if (eventName.equals("payment_form_submit")) { + try { + JSONObject jsonObject = new JSONObject(eventData); + JSONObject response = jsonObject.getJSONObject("credentials"); + paymentJson = response.toString(); + cardName = jsonObject.getString("title"); + } catch (Throwable e) { + paymentJson = eventData; + FileLog.e(e); + } + goToNextStep(); + } + } + }); + } + } + + private static class LinkMovementMethodMy extends LinkMovementMethod { + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + try { + boolean result = super.onTouchEvent(widget, buffer, event); + if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + Selection.removeSelection(buffer); + } + return result; + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + } + + public class LinkSpan extends ClickableSpan { + @Override + public void updateDrawState(TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + } + + @Override + public void onClick(View widget) { + presentFragment(new TwoStepVerificationActivity(0)); + } + } + + public PaymentFormActivity(MessageObject message, TLRPC.TL_payments_paymentReceipt receipt) { + currentStep = 5; + paymentForm = new TLRPC.TL_payments_paymentForm(); + paymentForm.bot_id = receipt.bot_id; + paymentForm.invoice = receipt.invoice; + paymentForm.provider_id = receipt.provider_id; + paymentForm.users = receipt.users; + shippingOption = receipt.shipping; + messageObject = message; + TLRPC.User user = MessagesController.getInstance().getUser(receipt.bot_id); + if (user != null) { + currentBotName = user.first_name; + } else { + currentBotName = ""; + } + currentItemName = message.messageOwner.media.title; + if (receipt.info != null) { + validateRequest = new TLRPC.TL_payments_validateRequestedInfo(); + validateRequest.info = receipt.info; + } + cardName = receipt.credentials_title; + } + + public PaymentFormActivity(TLRPC.TL_payments_paymentForm form, MessageObject message) { + int step; + if (form.invoice.shipping_address_requested || form.invoice.email_requested || form.invoice.name_requested || form.invoice.phone_requested) { + step = 0; + } else if (form.saved_credentials != null) { + if (UserConfig.tmpPassword != null) { + if (UserConfig.tmpPassword.valid_until < ConnectionsManager.getInstance().getCurrentTime() + 60) { + UserConfig.tmpPassword = null; + UserConfig.saveConfig(false); + } + } + if (UserConfig.tmpPassword != null) { + step = 4; + } else { + step = 3; + } + } else { + step = 2; + } + init(form, message, step, null, null, null, null, null, false); + } + + private PaymentFormActivity(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard) { + init(form, message, step, validatedRequestedInfo, shipping, tokenJson, card, request, saveCard); + } + + private void setDelegate(PaymentFormActivityDelegate paymentFormActivityDelegate) { + delegate = paymentFormActivityDelegate; + } + + private void init(TLRPC.TL_payments_paymentForm form, MessageObject message, int step, TLRPC.TL_payments_validatedRequestedInfo validatedRequestedInfo, TLRPC.TL_shippingOption shipping, String tokenJson, String card, TLRPC.TL_payments_validateRequestedInfo request, boolean saveCard) { + currentStep = step; + paymentJson = tokenJson; + requestedInfo = validatedRequestedInfo; + paymentForm = form; + shippingOption = shipping; + messageObject = message; + saveCardInfo = saveCard; + TLRPC.User user = MessagesController.getInstance().getUser(form.bot_id); + if (user != null) { + currentBotName = user.first_name; + } else { + currentBotName = ""; + } + currentItemName = message.messageOwner.media.title; + validateRequest = request; + saveShippingInfo = true; + if (saveCard) { + saveCardInfo = saveCard; + } else { + saveCardInfo = paymentForm.saved_credentials != null; + } + if (card == null) { + if (form.saved_credentials != null) { + cardName = form.saved_credentials.title; + } + } else { + cardName = card; + } + } + + @Override + public void onResume() { + super.onResume(); + AndroidUtilities.requestAdjustResize(getParentActivity(), classGuid); + if (Build.VERSION.SDK_INT >= 23) { + try { + if (currentStep == 2) { + getParentActivity().getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); + } else if (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture) { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } catch (Throwable e) { + FileLog.e(e); + } + } +// if (googleApiClient != null) { +// googleApiClient.connect(); +// } + } + + @Override + public void onPause() { +// if (googleApiClient != null) { +// googleApiClient.disconnect(); +// } + } + + @SuppressLint({"SetJavaScriptEnabled", "AddJavascriptInterface"}) + @Override + public View createView(Context context) { + if (currentStep == 0) { + actionBar.setTitle(LocaleController.getString("PaymentShippingInfo", R.string.PaymentShippingInfo)); + } else if (currentStep == 1) { + actionBar.setTitle(LocaleController.getString("PaymentShippingMethod", R.string.PaymentShippingMethod)); + } else if (currentStep == 2) { + actionBar.setTitle(LocaleController.getString("PaymentCardInfo", R.string.PaymentCardInfo)); + } else if (currentStep == 3) { + actionBar.setTitle(LocaleController.getString("PaymentCardInfo", R.string.PaymentCardInfo)); + } else if (currentStep == 4) { + if (paymentForm.invoice.test) { + actionBar.setTitle("Test " + LocaleController.getString("PaymentCheckout", R.string.PaymentCheckout)); + } else { + actionBar.setTitle(LocaleController.getString("PaymentCheckout", R.string.PaymentCheckout)); + } + } else if (currentStep == 5) { + if (paymentForm.invoice.test) { + actionBar.setTitle("Test " + LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt)); + } else { + actionBar.setTitle(LocaleController.getString("PaymentReceipt", R.string.PaymentReceipt)); + } + } + + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(true); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + if (donePressed) { + return; + } + finishFragment(); + } else if (id == done_button) { + if (donePressed) { + return; + } + if (currentStep != 3) { + AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); + } + if (currentStep == 0) { + setDonePressed(true); + sendForm(); + } else if (currentStep == 1) { + for (int a = 0; a < radioCells.length; a++) { + if (radioCells[a].isChecked()) { + shippingOption = requestedInfo.shipping_options.get(a); + break; + } + } + goToNextStep(); + } else if (currentStep == 2) { + sendCardData(); + } else if (currentStep == 3) { + checkPassword(); + } + } + } + }); + + ActionBarMenu menu = actionBar.createMenu(); + + if (currentStep == 0 || currentStep == 1 || currentStep == 2 || currentStep == 3) { + doneItem = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); + progressView = new ContextProgressView(context, 1); + doneItem.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + progressView.setVisibility(View.INVISIBLE); + } + + fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + + scrollView = new ScrollView(context); + scrollView.setFillViewport(true); + AndroidUtilities.setScrollViewEdgeEffectColor(scrollView, Theme.getColor(Theme.key_actionBarDefault)); + frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, currentStep == 4 ? 48 : 0)); + + linearLayout2 = new LinearLayout(context); + linearLayout2.setOrientation(LinearLayout.VERTICAL); + scrollView.addView(linearLayout2, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + if (currentStep == 0) { + HashMap languageMap = new HashMap<>(); + HashMap countryMap = new HashMap<>(); + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().getAssets().open("countries.txt"))); + String line; + while ((line = reader.readLine()) != null) { + String[] args = line.split(";"); + countriesArray.add(0, args[2]); + countriesMap.put(args[2], args[0]); + codesMap.put(args[0], args[2]); + countryMap.put(args[1], args[2]); + if (args.length > 3) { + phoneFormatMap.put(args[0], args[3]); + } + languageMap.put(args[1], args[2]); + } + reader.close(); + } catch (Exception e) { + FileLog.e(e); + } + + Collections.sort(countriesArray, new Comparator() { + @Override + public int compare(String lhs, String rhs) { + return lhs.compareTo(rhs); + } + }); + + inputFields = new EditText[FIELDS_COUNT_ADDRESS]; + for (int a = 0; a < FIELDS_COUNT_ADDRESS; a++) { + if (a == FIELD_STREET1) { + headerCell[0] = new HeaderCell(context); + headerCell[0].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[0].setText(LocaleController.getString("PaymentShippingAddress", R.string.PaymentShippingAddress)); + linearLayout2.addView(headerCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (a == FIELD_NAME) { + sectionCell[0] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + headerCell[1] = new HeaderCell(context); + headerCell[1].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[1].setText(LocaleController.getString("PaymentShippingReceiver", R.string.PaymentShippingReceiver)); + linearLayout2.addView(headerCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + ViewGroup container; + if (a == FIELD_PHONECODE) { + container = new LinearLayout(context); + ((LinearLayout) container).setOrientation(LinearLayout.HORIZONTAL); + linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + } else if (a == FIELD_PHONE) { + container = (ViewGroup) inputFields[FIELD_PHONECODE].getParent(); + } else { + container = new FrameLayout(context); + linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + boolean allowDivider = a != FIELD_POSTCODE && a != FIELD_PHONE; + if (allowDivider) { + if (a == FIELD_EMAIL && !paymentForm.invoice.phone_requested) { + allowDivider = false; + } else if (a == FIELD_NAME && !paymentForm.invoice.phone_requested && !paymentForm.invoice.email_requested) { + allowDivider = false; + } + } + if (allowDivider) { + View divider = new View(context); + dividers.add(divider); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + } + } + + if (a == FIELD_PHONE) { + inputFields[a] = new HintEditText(context); + } else { + inputFields[a] = new EditText(context); + } + inputFields[a].setTag(a); + inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setBackgroundDrawable(null); + AndroidUtilities.clearCursorDrawable(inputFields[a]); + if (a == FIELD_COUNTRY) { + inputFields[a].setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (getParentActivity() == null) { + return false; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + CountrySelectActivity fragment = new CountrySelectActivity(false); + fragment.setCountrySelectActivityDelegate(new CountrySelectActivity.CountrySelectActivityDelegate() { + @Override + public void didSelectCountry(String name, String shortName) { + inputFields[FIELD_COUNTRY].setText(name); + countryName = shortName; + } + }); + presentFragment(fragment); + } + return true; + } + }); + inputFields[a].setInputType(0); + } + if (a == FIELD_PHONE || a == FIELD_PHONECODE) { + inputFields[a].setInputType(InputType.TYPE_CLASS_PHONE); + } else if (a == FIELD_EMAIL) { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT); + } else { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + } + inputFields[a].setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + switch (a) { + case FIELD_NAME: + inputFields[a].setHint(LocaleController.getString("PaymentShippingName", R.string.PaymentShippingName)); + if (paymentForm.saved_info != null && paymentForm.saved_info.name != null) { + inputFields[a].setText(paymentForm.saved_info.name); + } + break; + case FIELD_EMAIL: + inputFields[a].setHint(LocaleController.getString("PaymentShippingEmailPlaceholder", R.string.PaymentShippingEmailPlaceholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.email != null) { + inputFields[a].setText(paymentForm.saved_info.email); + } + break; + case FIELD_STREET1: + inputFields[a].setHint(LocaleController.getString("PaymentShippingAddress1Placeholder", R.string.PaymentShippingAddress1Placeholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + inputFields[a].setText(paymentForm.saved_info.shipping_address.street_line1); + } + break; + case FIELD_STREET2: + inputFields[a].setHint(LocaleController.getString("PaymentShippingAddress2Placeholder", R.string.PaymentShippingAddress2Placeholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + inputFields[a].setText(paymentForm.saved_info.shipping_address.street_line2); + } + break; + case FIELD_CITY: + inputFields[a].setHint(LocaleController.getString("PaymentShippingCityPlaceholder", R.string.PaymentShippingCityPlaceholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + inputFields[a].setText(paymentForm.saved_info.shipping_address.city); + } + break; + case FIELD_STATE: + inputFields[a].setHint(LocaleController.getString("PaymentShippingStatePlaceholder", R.string.PaymentShippingStatePlaceholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + inputFields[a].setText(paymentForm.saved_info.shipping_address.state); + } + break; + case FIELD_COUNTRY: + inputFields[a].setHint(LocaleController.getString("PaymentShippingCountry", R.string.PaymentShippingCountry)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + String value = countryMap.get(paymentForm.saved_info.shipping_address.country_iso2); + countryName = paymentForm.saved_info.shipping_address.country_iso2; + inputFields[a].setText(value != null ? value : countryName); + } + break; + case FIELD_POSTCODE: + inputFields[a].setHint(LocaleController.getString("PaymentShippingZipPlaceholder", R.string.PaymentShippingZipPlaceholder)); + if (paymentForm.saved_info != null && paymentForm.saved_info.shipping_address != null) { + inputFields[a].setText(paymentForm.saved_info.shipping_address.post_code); + } + break; + } + inputFields[a].setSelection(inputFields[a].length()); + + if (a == FIELD_PHONECODE) { + textView = new TextView(context); + textView.setText("+"); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + container.addView(textView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, 17, 12, 0, 6)); + + inputFields[a].setPadding(AndroidUtilities.dp(10), 0, 0, 0); + inputFields[a].setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + InputFilter[] inputFilters = new InputFilter[1]; + inputFilters[0] = new InputFilter.LengthFilter(5); + inputFields[a].setFilters(inputFilters); + container.addView(inputFields[a], LayoutHelper.createLinear(55, LayoutHelper.WRAP_CONTENT, 0, 12, 16, 6)); + inputFields[a].addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + if (ignoreOnTextChange) { + return; + } + ignoreOnTextChange = true; + String text = PhoneFormat.stripExceptNumbers(inputFields[FIELD_PHONECODE].getText().toString()); + inputFields[FIELD_PHONECODE].setText(text); + HintEditText phoneField = (HintEditText) inputFields[FIELD_PHONE]; + if (text.length() == 0) { + phoneField.setHintText(null); + phoneField.setHint(LocaleController.getString("PaymentShippingPhoneNumber", R.string.PaymentShippingPhoneNumber)); + } else { + String country; + boolean ok = false; + String textToSet = null; + if (text.length() > 4) { + for (int a = 4; a >= 1; a--) { + String sub = text.substring(0, a); + country = codesMap.get(sub); + if (country != null) { + ok = true; + textToSet = text.substring(a, text.length()) + inputFields[FIELD_PHONE].getText().toString(); + inputFields[FIELD_PHONECODE].setText(text = sub); + break; + } + } + if (!ok) { + textToSet = text.substring(1, text.length()) + inputFields[FIELD_PHONE].getText().toString(); + inputFields[FIELD_PHONECODE].setText(text = text.substring(0, 1)); + } + } + country = codesMap.get(text); + boolean set = false; + if (country != null) { + int index = countriesArray.indexOf(country); + if (index != -1) { + String hint = phoneFormatMap.get(text); + if (hint != null) { + set = true; + phoneField.setHintText(hint.replace('X', '–')); + phoneField.setHint(null); + } + } + } + if (!set) { + phoneField.setHintText(null); + phoneField.setHint(LocaleController.getString("PaymentShippingPhoneNumber", R.string.PaymentShippingPhoneNumber)); + } + if (!ok) { + inputFields[FIELD_PHONECODE].setSelection(inputFields[FIELD_PHONECODE].getText().length()); + } + if (textToSet != null) { + phoneField.requestFocus(); + phoneField.setText(textToSet); + phoneField.setSelection(phoneField.length()); + } + } + ignoreOnTextChange = false; + } + }); + } else if (a == FIELD_PHONE) { + inputFields[a].setPadding(0, 0, 0, 0); + inputFields[a].setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + container.addView(inputFields[a], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 12, 17, 6)); + inputFields[a].addTextChangedListener(new TextWatcher() { + private int characterAction = -1; + private int actionPosition; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + if (count == 0 && after == 1) { + characterAction = 1; + } else if (count == 1 && after == 0) { + if (s.charAt(start) == ' ' && start > 0) { + characterAction = 3; + actionPosition = start - 1; + } else { + characterAction = 2; + } + } else { + characterAction = -1; + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (ignoreOnPhoneChange) { + return; + } + HintEditText phoneField = (HintEditText) inputFields[FIELD_PHONE]; + int start = phoneField.getSelectionStart(); + String phoneChars = "0123456789"; + String str = phoneField.getText().toString(); + if (characterAction == 3) { + str = str.substring(0, actionPosition) + str.substring(actionPosition + 1, str.length()); + start--; + } + StringBuilder builder = new StringBuilder(str.length()); + for (int a = 0; a < str.length(); a++) { + String ch = str.substring(a, a + 1); + if (phoneChars.contains(ch)) { + builder.append(ch); + } + } + ignoreOnPhoneChange = true; + String hint = phoneField.getHintText(); + if (hint != null) { + for (int a = 0; a < builder.length(); a++) { + if (a < hint.length()) { + if (hint.charAt(a) == ' ') { + builder.insert(a, ' '); + a++; + if (start == a && characterAction != 2 && characterAction != 3) { + start++; + } + } + } else { + builder.insert(a, ' '); + if (start == a + 1 && characterAction != 2 && characterAction != 3) { + start++; + } + break; + } + } + } + phoneField.setText(builder); + if (start >= 0) { + phoneField.setSelection(start <= phoneField.length() ? start : phoneField.length()); + } + phoneField.onTextChange(); + ignoreOnPhoneChange = false; + } + }); + } else { + inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); + inputFields[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + container.addView(inputFields[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 17, 12, 17, 6)); + } + + inputFields[a].setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_NEXT) { + int num = (Integer) textView.getTag(); + while (num + 1 < inputFields.length) { + num++; + if (num != FIELD_COUNTRY && ((View) inputFields[num].getParent()).getVisibility() == View.VISIBLE) { + inputFields[num].requestFocus(); + break; + } + } + return true; + } else if (i == EditorInfo.IME_ACTION_DONE) { + doneItem.performClick(); + return true; + } + return false; + } + }); + if (a == FIELD_PHONE) { + sectionCell[1] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + checkCell1 = new TextCheckCell(context); + checkCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + checkCell1.setTextAndCheck(LocaleController.getString("PaymentShippingSave", R.string.PaymentShippingSave), saveShippingInfo, false); + linearLayout2.addView(checkCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + checkCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveShippingInfo = !saveShippingInfo; + checkCell1.setChecked(saveShippingInfo); + } + }); + + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + bottomCell[0].setText(LocaleController.getString("PaymentShippingSaveInfo", R.string.PaymentShippingSaveInfo)); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + } + + if (!paymentForm.invoice.name_requested) { + ((ViewGroup) inputFields[FIELD_NAME].getParent()).setVisibility(View.GONE); + } + if (!paymentForm.invoice.phone_requested) { + ((ViewGroup) inputFields[FIELD_PHONECODE].getParent()).setVisibility(View.GONE); + } + if (!paymentForm.invoice.email_requested) { + ((ViewGroup) inputFields[FIELD_EMAIL].getParent()).setVisibility(View.GONE); + } + + if (paymentForm.invoice.phone_requested) { + inputFields[FIELD_PHONE].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } else if (paymentForm.invoice.email_requested) { + inputFields[FIELD_EMAIL].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } else if (paymentForm.invoice.name_requested) { + inputFields[FIELD_NAME].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } else { + inputFields[FIELD_POSTCODE].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } + + sectionCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); + headerCell[1].setVisibility(paymentForm.invoice.name_requested || paymentForm.invoice.phone_requested || paymentForm.invoice.email_requested ? View.VISIBLE : View.GONE); + if (!paymentForm.invoice.shipping_address_requested) { + headerCell[0].setVisibility(View.GONE); + sectionCell[0].setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_STREET1].getParent()).setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_STREET2].getParent()).setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_CITY].getParent()).setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_STATE].getParent()).setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_COUNTRY].getParent()).setVisibility(View.GONE); + ((ViewGroup) inputFields[FIELD_POSTCODE].getParent()).setVisibility(View.GONE); + } + + if (paymentForm.saved_info != null && !TextUtils.isEmpty(paymentForm.saved_info.phone)) { + fillNumber(paymentForm.saved_info.phone); + } else { + fillNumber(null); + } + + if (inputFields[FIELD_PHONECODE].length() == 0 && (paymentForm.invoice.phone_requested && (paymentForm.saved_info == null || TextUtils.isEmpty(paymentForm.saved_info.phone)))) { + String country = null; + + try { + TelephonyManager telephonyManager = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + if (telephonyManager != null) { + country = telephonyManager.getSimCountryIso().toUpperCase(); + } + } catch (Exception e) { + FileLog.e(e); + } + + if (country != null) { + String countryName = languageMap.get(country); + if (countryName != null) { + int index = countriesArray.indexOf(countryName); + if (index != -1) { + inputFields[FIELD_PHONECODE].setText(countriesMap.get(countryName)); + } + } + } + } + } else if (currentStep == 2) { + if (!"stripe".equals(paymentForm.native_provider)) { + webviewLoading = true; + showEditDoneProgress(true); + progressView.setVisibility(View.VISIBLE); + doneItem.setEnabled(false); + doneItem.getImageView().setVisibility(View.INVISIBLE); + webView = new WebView(context); + webView.getSettings().setJavaScriptEnabled(true); + webView.getSettings().setDomStorageEnabled(true); + + if (Build.VERSION.SDK_INT >= 21) { + webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptThirdPartyCookies(webView, true); + webView.addJavascriptInterface(new TelegramWebviewProxy(), "TelegramWebviewProxy"); + } + + webView.setWebViewClient(new WebViewClient() { + @Override + public void onLoadResource(WebView view, String url) { + super.onLoadResource(view, url); + } + + @Override + public void onPageFinished(WebView view, String url) { + super.onPageFinished(view, url); + webviewLoading = false; + showEditDoneProgress(false); + updateSavePaymentField(); + } + }); + + linearLayout2.addView(webView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + sectionCell[2] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[2], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + checkCell1 = new TextCheckCell(context); + checkCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + checkCell1.setTextAndCheck(LocaleController.getString("PaymentCardSavePaymentInformation", R.string.PaymentCardSavePaymentInformation), saveCardInfo, false); + linearLayout2.addView(checkCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + checkCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveCardInfo = !saveCardInfo; + checkCell1.setChecked(saveCardInfo); + } + }); + + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + updateSavePaymentField(); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else { + try { + JSONObject jsonObject = new JSONObject(paymentForm.native_params.data); + try { + need_card_country = jsonObject.getBoolean("need_country"); + } catch (Exception e) { + need_card_country = false; + } + try { + need_card_postcode = jsonObject.getBoolean("need_zip"); + } catch (Exception e) { + need_card_postcode = false; + } + try { + need_card_name = jsonObject.getBoolean("need_cardholder_name"); + } catch (Exception e) { + need_card_name = false; + } + try { + stripeApiKey = jsonObject.getString("publishable_key"); + } catch (Exception e) { + stripeApiKey = ""; + } + } catch (Exception e) { + FileLog.e(e); + } + + /*googleApiClient = new GoogleApiClient.Builder(context) + .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() { + @Override + public void onConnected(Bundle bundle) { + + } + + @Override + public void onConnectionSuspended(int i) { + + } + }) + .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() { + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + + } + }) + .addApi(Wallet.API, new Wallet.WalletOptions.Builder() + .setEnvironment(WalletConstants.ENVIRONMENT_TEST) + .setTheme(WalletConstants.THEME_LIGHT) + .build()) + .build(); + + Wallet.Payments.isReadyToPay(googleApiClient).setResultCallback( + new ResultCallback() { + @Override + public void onResult(BooleanResult booleanResult) { + if (booleanResult.getStatus().isSuccess()) { + if (booleanResult.getValue()) { + showAndroidPay(false); + } + } else { + + } + } + } + );*/ + + inputFields = new EditText[FIELDS_COUNT_CARD]; + for (int a = 0; a < FIELDS_COUNT_CARD; a++) { + if (a == FIELD_CARD) { + headerCell[0] = new HeaderCell(context); + headerCell[0].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[0].setText(LocaleController.getString("PaymentCardTitle", R.string.PaymentCardTitle)); + linearLayout2.addView(headerCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (a == FIELD_CARD_COUNTRY) { + headerCell[1] = new HeaderCell(context); + headerCell[1].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[1].setText(LocaleController.getString("PaymentBillingAddress", R.string.PaymentBillingAddress)); + linearLayout2.addView(headerCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + boolean allowDivider = a != FIELD_CVV && a != FIELD_CARD_POSTCODE && !(a == FIELD_CARD_COUNTRY && !need_card_postcode); + ViewGroup container = new FrameLayout(context); + linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + if (allowDivider) { + View divider = new View(context); + dividers.add(divider); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + } + + View.OnTouchListener onTouchListener = null; + inputFields[a] = new EditText(context); + inputFields[a].setTag(a); + inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setBackgroundDrawable(null); + AndroidUtilities.clearCursorDrawable(inputFields[a]); + if (a == FIELD_CVV) { + InputFilter[] inputFilters = new InputFilter[1]; + inputFilters[0] = new InputFilter.LengthFilter(3); + inputFields[a].setFilters(inputFilters); + inputFields[a].setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_VARIATION_PASSWORD); + inputFields[a].setTypeface(Typeface.DEFAULT); + inputFields[a].setTransformationMethod(PasswordTransformationMethod.getInstance()); + } else if (a == FIELD_CARD) { + inputFields[a].setInputType(InputType.TYPE_CLASS_NUMBER); + } else if (a == FIELD_CARD_COUNTRY) { + inputFields[a].setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (getParentActivity() == null) { + return false; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + CountrySelectActivity fragment = new CountrySelectActivity(false); + fragment.setCountrySelectActivityDelegate(new CountrySelectActivity.CountrySelectActivityDelegate() { + @Override + public void didSelectCountry(String name, String shortName) { + inputFields[FIELD_CARD_COUNTRY].setText(name); + } + }); + presentFragment(fragment); + } + return true; + } + }); + inputFields[a].setInputType(0); + } else if (a == FIELD_EXPIRE_DATE) { + inputFields[a].setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + } else if (a == FIELD_CARDNAME) { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS); + } else { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + } + inputFields[a].setImeOptions(EditorInfo.IME_ACTION_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + switch (a) { + case FIELD_CARD: + inputFields[a].setHint(LocaleController.getString("PaymentCardNumber", R.string.PaymentCardNumber)); + break; + case FIELD_CVV: + inputFields[a].setHint(LocaleController.getString("PaymentCardCvv", R.string.PaymentCardCvv)); + break; + case FIELD_EXPIRE_DATE: + inputFields[a].setHint(LocaleController.getString("PaymentCardExpireDate", R.string.PaymentCardExpireDate)); + break; + case FIELD_CARDNAME: + inputFields[a].setHint(LocaleController.getString("PaymentCardName", R.string.PaymentCardName)); + break; + case FIELD_CARD_POSTCODE: + inputFields[a].setHint(LocaleController.getString("PaymentShippingZipPlaceholder", R.string.PaymentShippingZipPlaceholder)); + break; + case FIELD_CARD_COUNTRY: + inputFields[a].setHint(LocaleController.getString("PaymentShippingCountry", R.string.PaymentShippingCountry)); + break; + } + + if (a == FIELD_CARD) { + inputFields[a].addTextChangedListener(new TextWatcher() { + + public final String[] PREFIXES_15 = {"34", "37"}; + public final String[] PREFIXES_14 = {"300", "301", "302", "303", "304", "305", "309", "36", "38", "39"}; + public final String[] PREFIXES_16 = { + "2221", "2222", "2223", "2224", "2225", "2226", "2227", "2228", "2229", + "223", "224", "225", "226", "227", "228", "229", + "23", "24", "25", "26", + "270", "271", "2720", + "50", "51", "52", "53", "54", "55", + + "4", + + "60", "62", "64", "65", + + "35" + }; + + public static final int MAX_LENGTH_STANDARD = 16; + public static final int MAX_LENGTH_AMERICAN_EXPRESS = 15; + public static final int MAX_LENGTH_DINERS_CLUB = 14; + + private int characterAction = -1; + private int actionPosition; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + if (count == 0 && after == 1) { + characterAction = 1; + } else if (count == 1 && after == 0) { + if (s.charAt(start) == ' ' && start > 0) { + characterAction = 3; + actionPosition = start - 1; + } else { + characterAction = 2; + } + } else { + characterAction = -1; + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (ignoreOnCardChange) { + return; + } + EditText phoneField = inputFields[FIELD_CARD]; + int start = phoneField.getSelectionStart(); + String phoneChars = "0123456789"; + String str = phoneField.getText().toString(); + if (characterAction == 3) { + str = str.substring(0, actionPosition) + str.substring(actionPosition + 1, str.length()); + start--; + } + StringBuilder builder = new StringBuilder(str.length()); + for (int a = 0; a < str.length(); a++) { + String ch = str.substring(a, a + 1); + if (phoneChars.contains(ch)) { + builder.append(ch); + } + } + ignoreOnCardChange = true; + String hint = null; + int maxLength = 100; + if (builder.length() > 0) { + String currentString = builder.toString(); + for (int a = 0; a < 3; a++) { + String checkArr[]; + String resultHint; + int resultMaxLength; + switch (a) { + case 0: + checkArr = PREFIXES_16; + resultMaxLength = 16; + resultHint = "xxxx xxxx xxxx xxxx"; + break; + case 1: + checkArr = PREFIXES_15; + resultMaxLength = 15; + resultHint = "xxxx xxxx xxxx xxx"; + break; + case 2: + default: + checkArr = PREFIXES_14; + resultMaxLength = 14; + resultHint = "xxxx xxxx xxxx xx"; + break; + } + for (int b = 0; b < checkArr.length; b++) { + String prefix = checkArr[b]; + if (currentString.length() <= prefix.length()) { + if (prefix.startsWith(currentString)) { + hint = resultHint; + maxLength = resultMaxLength; + break; + } + } else { + if (currentString.startsWith(prefix)) { + hint = resultHint; + maxLength = resultMaxLength; + break; + } + } + } + if (hint != null) { + break; + } + } + if (maxLength != 0) { + if (builder.length() > maxLength) { + builder.setLength(maxLength); + } + } + } + if (hint != null) { + if (maxLength != 0) { + if (builder.length() == maxLength) { + inputFields[FIELD_EXPIRE_DATE].requestFocus(); + } + } + phoneField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + for (int a = 0; a < builder.length(); a++) { + if (a < hint.length()) { + if (hint.charAt(a) == ' ') { + builder.insert(a, ' '); + a++; + if (start == a && characterAction != 2 && characterAction != 3) { + start++; + } + } + } else { + builder.insert(a, ' '); + if (start == a + 1 && characterAction != 2 && characterAction != 3) { + start++; + } + break; + } + } + } else { + phoneField.setTextColor(builder.length() > 0 ? Theme.getColor(Theme.key_windowBackgroundWhiteRedText4) : Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + } + phoneField.setText(builder); + if (start >= 0) { + phoneField.setSelection(start <= phoneField.length() ? start : phoneField.length()); + } + ignoreOnCardChange = false; + } + }); + } else if (a == FIELD_EXPIRE_DATE) { + inputFields[a].addTextChangedListener(new TextWatcher() { + + private int characterAction = -1; + private boolean isYear; + private int actionPosition; + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + if (count == 0 && after == 1) { + isYear = TextUtils.indexOf(inputFields[FIELD_EXPIRE_DATE].getText(), '/') != -1; + characterAction = 1; + } else if (count == 1 && after == 0) { + if (s.charAt(start) == '/' && start > 0) { + isYear = false; + characterAction = 3; + actionPosition = start - 1; + } else { + characterAction = 2; + } + } else { + characterAction = -1; + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (ignoreOnCardChange) { + return; + } + EditText phoneField = inputFields[FIELD_EXPIRE_DATE]; + int start = phoneField.getSelectionStart(); + String phoneChars = "0123456789"; + String str = phoneField.getText().toString(); + if (characterAction == 3) { + str = str.substring(0, actionPosition) + str.substring(actionPosition + 1, str.length()); + start--; + } + StringBuilder builder = new StringBuilder(str.length()); + for (int a = 0; a < str.length(); a++) { + String ch = str.substring(a, a + 1); + if (phoneChars.contains(ch)) { + builder.append(ch); + } + } + ignoreOnCardChange = true; + inputFields[FIELD_EXPIRE_DATE].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + if (builder.length() > 4) { + builder.setLength(4); + } + if (builder.length() < 2) { + isYear = false; + } + boolean isError = false; + if (isYear) { + String[] args = new String[builder.length() > 2 ? 2 : 1]; + args[0] = builder.substring(0, 2); + if (args.length == 2) { + args[1] = builder.substring(2); + } + if (builder.length() == 4 && args.length == 2) { + int month = Utilities.parseInt(args[0]); + int year = Utilities.parseInt(args[1]) + 2000; + Calendar rightNow = Calendar.getInstance(); + int currentYear = rightNow.get(Calendar.YEAR); + int currentMonth = rightNow.get(Calendar.MONTH) + 1; + if (year < currentYear || year == currentYear && month < currentMonth) { + inputFields[FIELD_EXPIRE_DATE].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + isError = true; + } + } else { + int value = Utilities.parseInt(args[0]); + if (value > 12 || value == 0) { + inputFields[FIELD_EXPIRE_DATE].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + isError = true; + } + } + } else { + if (builder.length() == 1) { + int value = Utilities.parseInt(builder.toString()); + if (value != 1 && value != 0) { + builder.insert(0, "0"); + start++; + } + } else if (builder.length() == 2) { + int value = Utilities.parseInt(builder.toString()); + if (value > 12 || value == 0) { + inputFields[FIELD_EXPIRE_DATE].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText4)); + isError = true; + } + start++; + } + } + if (!isError && builder.length() == 4) { + inputFields[need_card_name ? FIELD_CARDNAME : FIELD_CVV].requestFocus(); + } + if (builder.length() == 2) { + builder.append('/'); + start++; + } else if (builder.length() > 2 && builder.charAt(2) != '/') { + builder.insert(2, '/'); + start++; + } + + phoneField.setText(builder); + if (start >= 0) { + phoneField.setSelection(start <= phoneField.length() ? start : phoneField.length()); + } + ignoreOnCardChange = false; + } + }); + } + inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); + inputFields[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + container.addView(inputFields[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 17, 12, 17, 6)); + + inputFields[a].setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_NEXT) { + int num = (Integer) textView.getTag(); + while (num + 1 < inputFields.length) { + num++; + if (num == FIELD_CARD_COUNTRY) { + num++; + } + if (((View) inputFields[num].getParent()).getVisibility() == View.VISIBLE) { + inputFields[num].requestFocus(); + break; + } + } + return true; + } else if (i == EditorInfo.IME_ACTION_DONE) { + doneItem.performClick(); + return true; + } + return false; + } + }); + if (a == FIELD_CVV) { + sectionCell[0] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (a == FIELD_CARD_POSTCODE) { + sectionCell[2] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[2], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + checkCell1 = new TextCheckCell(context); + checkCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + checkCell1.setTextAndCheck(LocaleController.getString("PaymentCardSavePaymentInformation", R.string.PaymentCardSavePaymentInformation), saveCardInfo, false); + linearLayout2.addView(checkCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + checkCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveCardInfo = !saveCardInfo; + checkCell1.setChecked(saveCardInfo); + } + }); + + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + updateSavePaymentField(); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + if (a == FIELD_CARD_COUNTRY && !need_card_country || a == FIELD_CARD_POSTCODE && !need_card_postcode || a == FIELD_CARDNAME && !need_card_name) { + container.setVisibility(View.GONE); + } + } + if (!need_card_country && !need_card_postcode) { + headerCell[1].setVisibility(View.GONE); + sectionCell[0].setVisibility(View.GONE); + } + if (need_card_postcode) { + inputFields[FIELD_CARD_POSTCODE].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } else { + inputFields[FIELD_CVV].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + } + + /*settingsCell1 = new TextSettingsCell(context); + settingsCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + settingsCell1.setText("Android Pay", false); + settingsCell1.setVisibility(View.GONE); + linearLayout2.addView(settingsCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + settingsCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showAndroidPay(true); + } + });*/ + } + } else if (currentStep == 1) { + int count = requestedInfo.shipping_options.size(); + radioCells = new RadioCell[count]; + for (int a = 0; a < count; a++) { + TLRPC.TL_shippingOption shippingOption = requestedInfo.shipping_options.get(a); + radioCells[a] = new RadioCell(context); + radioCells[a].setTag(a); + radioCells[a].setBackgroundDrawable(Theme.getSelectorDrawable(true)); + radioCells[a].setText(String.format("%s - %s", getTotalPriceString(shippingOption.prices), shippingOption.title), a == 0, a != count - 1); + radioCells[a].setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int num = (Integer) v.getTag(); + for (int a = 0; a < radioCells.length; a++) { + radioCells[a].setChecked(num == a, true); + } + } + }); + linearLayout2.addView(radioCells[a]); + } + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } else if (currentStep == 3) { + inputFields = new EditText[FIELDS_COUNT_SAVEDCARD]; + for (int a = 0; a < FIELDS_COUNT_SAVEDCARD; a++) { + if (a == FIELD_SAVEDCARD) { + headerCell[0] = new HeaderCell(context); + headerCell[0].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + headerCell[0].setText(LocaleController.getString("PaymentCardTitle", R.string.PaymentCardTitle)); + linearLayout2.addView(headerCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + ViewGroup container = new FrameLayout(context); + linearLayout2.addView(container, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + container.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + + boolean allowDivider = a != FIELD_SAVEDPASSWORD; + if (allowDivider) { + if (a == FIELD_EMAIL && !paymentForm.invoice.phone_requested) { + allowDivider = false; + } else if (a == FIELD_NAME && !paymentForm.invoice.phone_requested && !paymentForm.invoice.email_requested) { + allowDivider = false; + } + } + if (allowDivider) { + View divider = new View(context); + dividers.add(divider); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + container.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + } + + inputFields[a] = new EditText(context); + inputFields[a].setTag(a); + inputFields[a].setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + inputFields[a].setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + inputFields[a].setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + inputFields[a].setBackgroundDrawable(null); + AndroidUtilities.clearCursorDrawable(inputFields[a]); + if (a == FIELD_SAVEDCARD) { + inputFields[a].setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + inputFields[a].setInputType(0); + } else { + inputFields[a].setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + inputFields[a].setTypeface(Typeface.DEFAULT); + } + inputFields[a].setImeOptions(EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI); + switch (a) { + case FIELD_SAVEDCARD: + inputFields[a].setText(paymentForm.saved_credentials.title); + break; + case FIELD_SAVEDPASSWORD: + inputFields[a].setHint(LocaleController.getString("LoginPassword", R.string.LoginPassword)); + inputFields[a].requestFocus(); + break; + } + + inputFields[a].setPadding(0, 0, 0, AndroidUtilities.dp(6)); + inputFields[a].setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + container.addView(inputFields[a], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 17, 12, 17, 6)); + + inputFields[a].setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + if (i == EditorInfo.IME_ACTION_DONE) { + doneItem.performClick(); + return true; + } + return false; + } + }); + if (a == FIELD_SAVEDPASSWORD) { + bottomCell[0] = new TextInfoPrivacyCell(context); + bottomCell[0].setText(LocaleController.formatString("PaymentConfirmationMessage", R.string.PaymentConfirmationMessage, paymentForm.saved_credentials.title)); + bottomCell[0].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + settingsCell1 = new TextSettingsCell(context); + settingsCell1.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + settingsCell1.setText(LocaleController.getString("PaymentConfirmationNewCard", R.string.PaymentConfirmationNewCard), false); + linearLayout2.addView(settingsCell1, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + settingsCell1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + passwordOk = false; + goToNextStep(); + } + }); + + bottomCell[1] = new TextInfoPrivacyCell(context); + bottomCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(bottomCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + } + } else if (currentStep == 4 || currentStep == 5) { + paymentInfoCell = new PaymentInfoCell(context); + paymentInfoCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + paymentInfoCell.setInvoice((TLRPC.TL_messageMediaInvoice) messageObject.messageOwner.media, currentBotName); + linearLayout2.addView(paymentInfoCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + sectionCell[0] = new ShadowSectionCell(context); + linearLayout2.addView(sectionCell[0], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + ArrayList arrayList = new ArrayList<>(); + arrayList.addAll(paymentForm.invoice.prices); + if (shippingOption != null) { + arrayList.addAll(shippingOption.prices); + } + final String totalPrice = getTotalPriceString(arrayList); + + for (int a = 0; a < arrayList.size(); a++) { + TLRPC.TL_labeledPrice price = arrayList.get(a); + + TextPriceCell priceCell = new TextPriceCell(context); + priceCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + priceCell.setTextAndValue(price.label, LocaleController.getInstance().formatCurrencyString(price.amount, paymentForm.invoice.currency), false); + linearLayout2.addView(priceCell); + } + + TextPriceCell priceCell = new TextPriceCell(context); + priceCell.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + priceCell.setTextAndValue(LocaleController.getString("PaymentTransactionTotal", R.string.PaymentTransactionTotal), totalPrice, true); + linearLayout2.addView(priceCell); + + View divider = new View(context); + dividers.add(divider); + divider.setBackgroundColor(Theme.getColor(Theme.key_divider)); + linearLayout2.addView(divider, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1, Gravity.LEFT | Gravity.BOTTOM)); + + detailSettingsCell[0] = new TextDetailSettingsCell(context); + detailSettingsCell[0].setBackgroundDrawable(Theme.getSelectorDrawable(true)); + detailSettingsCell[0].setTextAndValue(cardName, LocaleController.getString("PaymentCheckoutMethod", R.string.PaymentCheckoutMethod), true); + linearLayout2.addView(detailSettingsCell[0]); + if (currentStep == 4) { + detailSettingsCell[0].setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + PaymentFormActivity activity = new PaymentFormActivity(paymentForm, messageObject, 2, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo); + activity.setDelegate(new PaymentFormActivityDelegate() { + @Override + public void didSelectNewCard(String tokenJson, String card, boolean saveCard) { + paymentJson = tokenJson; + saveCardInfo = saveCard; + cardName = card; + detailSettingsCell[0].setTextAndValue(cardName, LocaleController.getString("PaymentCheckoutMethod", R.string.PaymentCheckoutMethod), true); + } + }); + presentFragment(activity); + } + }); + } + + if (validateRequest != null) { + if (validateRequest.info.shipping_address != null) { + String address = String.format("%s %s, %s, %s, %s, %s", validateRequest.info.shipping_address.street_line1, validateRequest.info.shipping_address.street_line2, validateRequest.info.shipping_address.city, validateRequest.info.shipping_address.state, validateRequest.info.shipping_address.country_iso2, validateRequest.info.shipping_address.post_code); + detailSettingsCell[1] = new TextDetailSettingsCell(context); + detailSettingsCell[1].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + detailSettingsCell[1].setTextAndValue(address, LocaleController.getString("PaymentShippingAddress", R.string.PaymentShippingAddress), true); + linearLayout2.addView(detailSettingsCell[1]); + } + + if (validateRequest.info.name != null) { + detailSettingsCell[2] = new TextDetailSettingsCell(context); + detailSettingsCell[2].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + detailSettingsCell[2].setTextAndValue(validateRequest.info.name, LocaleController.getString("PaymentCheckoutName", R.string.PaymentCheckoutName), true); + linearLayout2.addView(detailSettingsCell[2]); + } + + if (validateRequest.info.phone != null) { + detailSettingsCell[3] = new TextDetailSettingsCell(context); + detailSettingsCell[3].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + detailSettingsCell[3].setTextAndValue(PhoneFormat.getInstance().format(validateRequest.info.phone), LocaleController.getString("PaymentCheckoutPhoneNumber", R.string.PaymentCheckoutPhoneNumber), true); + linearLayout2.addView(detailSettingsCell[3]); + } + + if (validateRequest.info.email != null) { + detailSettingsCell[4] = new TextDetailSettingsCell(context); + detailSettingsCell[4].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + detailSettingsCell[4].setTextAndValue(validateRequest.info.email, LocaleController.getString("PaymentCheckoutEmail", R.string.PaymentCheckoutEmail), true); + linearLayout2.addView(detailSettingsCell[4]); + } + + if (shippingOption != null) { + detailSettingsCell[5] = new TextDetailSettingsCell(context); + detailSettingsCell[5].setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + detailSettingsCell[5].setTextAndValue(shippingOption.title, LocaleController.getString("PaymentCheckoutShippingMethod", R.string.PaymentCheckoutShippingMethod), false); + linearLayout2.addView(detailSettingsCell[5]); + } + } + + if (currentStep == 4) { + bottomLayout = new FrameLayout(context); + bottomLayout.setBackgroundDrawable(Theme.getSelectorDrawable(true)); + frameLayout.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM)); + bottomLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("PaymentTransactionReview", R.string.PaymentTransactionReview)); + builder.setMessage(LocaleController.formatString("PaymentTransactionMessage", R.string.PaymentTransactionMessage, totalPrice, currentBotName, currentItemName)); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + showEditDoneProgress(true); + setDonePressed(true); + sendData(); + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + }); + + payTextView = new TextView(context); + payTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText6)); + payTextView.setText(LocaleController.formatString("PaymentCheckoutPay", R.string.PaymentCheckoutPay, totalPrice)); + payTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + payTextView.setGravity(Gravity.CENTER); + payTextView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomLayout.addView(payTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + progressView = new ContextProgressView(context, 0); + progressView.setVisibility(View.INVISIBLE); + bottomLayout.addView(progressView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + View shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow_reverse); + frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48)); + } + + sectionCell[1] = new ShadowSectionCell(context); + sectionCell[1].setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + linearLayout2.addView(sectionCell[1], LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + return fragmentView; + } + + private String getTotalPriceString(ArrayList prices) { + int amount = 0; + for (int a = 0; a < prices.size(); a++) { + amount += prices.get(a).amount; + } + return LocaleController.getInstance().formatCurrencyString(amount, paymentForm.invoice.currency); + } + + private String getTotalPriceDecimalString(ArrayList prices) { + int amount = 0; + for (int a = 0; a < prices.size(); a++) { + amount += prices.get(a).amount; + } + return LocaleController.getInstance().formatCurrencyDecimalString(amount, paymentForm.invoice.currency); + } + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didSetTwoStepPassword); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.didRemovedTwoStepPassword); + if (currentStep != 4) { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.paymentFinished); + } + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didSetTwoStepPassword); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.didRemovedTwoStepPassword); + if (currentStep != 4) { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.paymentFinished); + } + if (webView != null) { + try { + ViewParent parent = webView.getParent(); + if (parent != null) { + ((FrameLayout) parent).removeView(webView); + } + webView.stopLoading(); + webView.loadUrl("about:blank"); + webView.destroy(); + webView = null; + } catch (Exception e) { + FileLog.e(e); + } + } + try { + if (currentStep == 2 && Build.VERSION.SDK_INT >= 23 && (UserConfig.passcodeHash.length() == 0 || UserConfig.allowScreenCapture)) { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } catch (Throwable e) { + FileLog.e(e); + } + super.onFragmentDestroy(); + canceled = true; + } + + @Override + protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { + if (isOpen && !backward) { + if (webView != null) { + webView.loadUrl(paymentForm.url); + } else if (currentStep == 2) { + inputFields[FIELD_CARD].requestFocus(); + AndroidUtilities.showKeyboard(inputFields[FIELD_CARD]); + } else if (currentStep == 3) { + inputFields[FIELD_SAVEDPASSWORD].requestFocus(); + AndroidUtilities.showKeyboard(inputFields[FIELD_SAVEDPASSWORD]); + } + } + } + + @Override + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.didSetTwoStepPassword) { + paymentForm.password_missing = false; + updateSavePaymentField(); + } else if (id == NotificationCenter.didRemovedTwoStepPassword) { + paymentForm.password_missing = true; + updateSavePaymentField(); + } else if (id == NotificationCenter.paymentFinished) { + removeSelfFromStack(); + } + } + + /*private void showAndroidPay(boolean show) { + if (show) { + if (getParentActivity() == null) { + return; + } + Activity parentActivity = getParentActivity(); + AlertDialog.Builder builder = new AlertDialog.Builder(parentActivity); + FrameLayout frameLayout = new FrameLayout(parentActivity); + walletFragment.onCreate(null); + walletFragment.onCreateView(parentActivity.getLayoutInflater(), frameLayout, null); + walletFragment.onStart(); + walletFragment.onResume(); + builder.setView(frameLayout); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } else { + WalletFragmentOptions.Builder optionsBuilder = WalletFragmentOptions.newBuilder(); + optionsBuilder.setEnvironment(WalletConstants.ENVIRONMENT_TEST); + optionsBuilder.setMode(WalletFragmentMode.BUY_BUTTON); + walletFragment = WalletFragment.newInstance(optionsBuilder.build()); + + ArrayList arrayList = new ArrayList<>(); + arrayList.addAll(paymentForm.invoice.prices); + if (shippingOption != null) { + arrayList.addAll(shippingOption.prices); + } + final String totalPrice = getTotalPriceDecimalString(arrayList); + + MaskedWalletRequest maskedWalletRequest = MaskedWalletRequest.newBuilder() + .setPaymentMethodTokenizationParameters(PaymentMethodTokenizationParameters.newBuilder() + .setPaymentMethodTokenizationType(PaymentMethodTokenizationType.PAYMENT_GATEWAY) + .addParameter("gateway", "stripe") + .addParameter("stripe:publishableKey", stripeApiKey) + .addParameter("stripe:version", StripeApiHandler.VERSION) + .build()) + + //.setShippingAddressRequired(true) + .setEstimatedTotalPrice(totalPrice) + .setCurrencyCode(paymentForm.invoice.currency) + .build(); + + WalletFragmentInitParams initParams = WalletFragmentInitParams.newBuilder() + .setMaskedWalletRequest(maskedWalletRequest) + .setMaskedWalletRequestCode(91) + .build(); + + walletFragment.initialize(initParams); + settingsCell1.setVisibility(View.VISIBLE); + } + }*/ + + private void goToNextStep() { + if (currentStep == 0) { + int nextStep; + if (paymentForm.invoice.flexible) { + nextStep = 1; + } else if (paymentForm.saved_credentials != null) { + if (UserConfig.tmpPassword != null) { + if (UserConfig.tmpPassword.valid_until < ConnectionsManager.getInstance().getCurrentTime() + 60) { + UserConfig.tmpPassword = null; + UserConfig.saveConfig(false); + } + } + if (UserConfig.tmpPassword != null) { + nextStep = 4; + } else { + nextStep = 3; + } + } else { + nextStep = 2; + } + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, null, null, cardName, validateRequest, saveCardInfo)); + } else if (currentStep == 1) { + int nextStep; + if (paymentForm.saved_credentials != null) { + if (UserConfig.tmpPassword != null) { + if (UserConfig.tmpPassword.valid_until < ConnectionsManager.getInstance().getCurrentTime() + 60) { + UserConfig.tmpPassword = null; + UserConfig.saveConfig(false); + } + } + if (UserConfig.tmpPassword != null) { + nextStep = 4; + } else { + nextStep = 3; + } + } else { + nextStep = 2; + } + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, null, cardName, validateRequest, saveCardInfo)); + } else if (currentStep == 2) { + if (delegate != null) { + delegate.didSelectNewCard(paymentJson, cardName, saveCardInfo); + finishFragment(); + } else { + presentFragment(new PaymentFormActivity(paymentForm, messageObject, 4, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo)); + } + } else if (currentStep == 3) { + int nextStep; + if (passwordOk) { + nextStep = 4; + } else { + nextStep = 2; + } + presentFragment(new PaymentFormActivity(paymentForm, messageObject, nextStep, requestedInfo, shippingOption, paymentJson, cardName, validateRequest, saveCardInfo), !passwordOk); + } else if (currentStep == 4) { + NotificationCenter.getInstance().postNotificationName(NotificationCenter.paymentFinished); + finishFragment(); + } + } + + private void updateSavePaymentField() { + if (bottomCell[0] == null) { + return; + } + if (paymentForm.can_save_credentials && (webView == null || webView != null && !webviewLoading)) { + SpannableStringBuilder text = new SpannableStringBuilder(LocaleController.getString("PaymentCardSavePaymentInformationInfoLine1", R.string.PaymentCardSavePaymentInformationInfoLine1)); + if (paymentForm.password_missing) { + text.append("\n"); + int len = text.length(); + String str2 = LocaleController.getString("PaymentCardSavePaymentInformationInfoLine2", R.string.PaymentCardSavePaymentInformationInfoLine2); + int index1 = str2.indexOf('*'); + int index2 = str2.lastIndexOf('*'); + text.append(str2); + if (index1 != -1 && index2 != -1) { + index1 += len; + index2 += len; + bottomCell[0].getTextView().setMovementMethod(new LinkMovementMethodMy()); + text.replace(index2, index2 + 1, ""); + text.replace(index1, index1 + 1, ""); + text.setSpan(new LinkSpan(), index1, index2 - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + checkCell1.setEnabled(false); + } else { + checkCell1.setEnabled(true); + } + bottomCell[0].setText(text); + checkCell1.setVisibility(View.VISIBLE); + bottomCell[0].setVisibility(View.VISIBLE); + sectionCell[2].setBackgroundDrawable(Theme.getThemedDrawable(sectionCell[2].getContext(), R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else { + checkCell1.setVisibility(View.GONE); + bottomCell[0].setVisibility(View.GONE); + sectionCell[2].setBackgroundDrawable(Theme.getThemedDrawable(sectionCell[2].getContext(), R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + } + + @SuppressLint("HardwareIds") + public void fillNumber(String number) { + try { + TelephonyManager tm = (TelephonyManager) ApplicationLoader.applicationContext.getSystemService(Context.TELEPHONY_SERVICE); + boolean allowCall = true; + boolean allowSms = true; + if (number != null || tm.getSimState() != TelephonyManager.SIM_STATE_ABSENT && tm.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE) { + if (Build.VERSION.SDK_INT >= 23) { + allowCall = getParentActivity().checkSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; + allowSms = getParentActivity().checkSelfPermission(Manifest.permission.RECEIVE_SMS) == PackageManager.PERMISSION_GRANTED; + } + if (number != null || allowCall || allowSms) { + if (number == null) { + number = PhoneFormat.stripExceptNumbers(tm.getLine1Number()); + } + String textToSet = null; + boolean ok = false; + if (!TextUtils.isEmpty(number)) { + if (number.length() > 4) { + for (int a = 4; a >= 1; a--) { + String sub = number.substring(0, a); + String country = codesMap.get(sub); + if (country != null) { + ok = true; + textToSet = number.substring(a, number.length()); + inputFields[FIELD_PHONECODE].setText(sub); + break; + } + } + if (!ok) { + textToSet = number.substring(1, number.length()); + inputFields[FIELD_PHONECODE].setText(number.substring(0, 1)); + } + } + if (textToSet != null) { + inputFields[FIELD_PHONE].setText(textToSet); + inputFields[FIELD_PHONE].setSelection(inputFields[FIELD_PHONE].length()); + } + } + } + } + } catch (Exception e) { + FileLog.e(e); + } + } + + private boolean sendCardData() { + if (paymentForm.saved_credentials != null && !saveCardInfo && paymentForm.can_save_credentials) { + TLRPC.TL_payments_clearSavedInfo req = new TLRPC.TL_payments_clearSavedInfo(); + req.credentials = true; + paymentForm.saved_credentials = null; + UserConfig.tmpPassword = null; + UserConfig.saveConfig(false); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } + Integer month; + Integer year; + String date = inputFields[FIELD_EXPIRE_DATE].getText().toString(); + String args[] = date.split("/"); + if (args.length == 2) { + month = Utilities.parseInt(args[0]); + year = Utilities.parseInt(args[1]); + } else { + month = null; + year = null; + } + Card card = new Card( + inputFields[FIELD_CARD].getText().toString(), + month, + year, + inputFields[FIELD_CVV].getText().toString(), + inputFields[FIELD_CARDNAME].getText().toString(), + null, null, null, null, + inputFields[FIELD_CARD_POSTCODE].getText().toString(), + inputFields[FIELD_CARD_COUNTRY].getText().toString(), + null); + cardName = card.getType() + " *" + card.getLast4(); + if (!card.validateNumber()) { + shakeField(FIELD_CARD); + return false; + } else if (!card.validateExpMonth() || !card.validateExpYear() || !card.validateExpiryDate()) { + shakeField(FIELD_EXPIRE_DATE); + return false; + } else if (need_card_name && inputFields[FIELD_CARDNAME].length() == 0) { + shakeField(FIELD_CARDNAME); + return false; + } else if (!card.validateCVC()) { + shakeField(FIELD_CVV); + return false; + } else if (need_card_country && inputFields[FIELD_CARD_COUNTRY].length() == 0) { + shakeField(FIELD_CARD_COUNTRY); + return false; + } else if (need_card_postcode && inputFields[FIELD_CARD_POSTCODE].length() == 0) { + shakeField(FIELD_CARD_POSTCODE); + return false; + } + showEditDoneProgress(true); + try { + Stripe stripe = new Stripe(stripeApiKey); + stripe.createToken(card, new TokenCallback() { + public void onSuccess(Token token) { + if (canceled) { + return; + } + paymentJson = String.format(Locale.US, "{\"type\":\"%1$s\", \"id\":\"%2$s\"}", token.getType(), token.getId()); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + goToNextStep(); + showEditDoneProgress(false); + setDonePressed(false); + } + }); + } + + public void onError(Exception error) { + if (canceled) { + return; + } + showEditDoneProgress(false); + setDonePressed(false); + if (error instanceof APIConnectionException || error instanceof APIException) { + AlertsCreator.showSimpleToast(PaymentFormActivity.this, LocaleController.getString("PaymentConnectionFailed", R.string.PaymentConnectionFailed)); + } else { + AlertsCreator.showSimpleToast(PaymentFormActivity.this, error.getMessage()); + } + } + } + ); + } catch (Exception e) { + FileLog.e(e); + } + return true; + } + + private void sendForm() { + if (canceled) { + return; + } + showEditDoneProgress(true); + validateRequest = new TLRPC.TL_payments_validateRequestedInfo(); + validateRequest.save = saveShippingInfo; + validateRequest.msg_id = messageObject.getId(); + validateRequest.info = new TLRPC.TL_paymentRequestedInfo(); + if (paymentForm.invoice.name_requested) { + validateRequest.info.name = inputFields[FIELD_NAME].getText().toString(); + validateRequest.info.flags |= 1; + } + if (paymentForm.invoice.phone_requested) { + validateRequest.info.phone = "+" + inputFields[FIELD_PHONECODE].getText().toString() + inputFields[FIELD_PHONE].getText().toString(); + validateRequest.info.flags |= 2; + } + if (paymentForm.invoice.email_requested) { + validateRequest.info.email = inputFields[FIELD_EMAIL].getText().toString(); + validateRequest.info.flags |= 4; + } + if (paymentForm.invoice.shipping_address_requested) { + validateRequest.info.shipping_address = new TLRPC.TL_postAddress(); + validateRequest.info.shipping_address.street_line1 = inputFields[FIELD_STREET1].getText().toString(); + validateRequest.info.shipping_address.street_line2 = inputFields[FIELD_STREET2].getText().toString(); + validateRequest.info.shipping_address.city = inputFields[FIELD_CITY].getText().toString(); + validateRequest.info.shipping_address.state = inputFields[FIELD_STATE].getText().toString(); + validateRequest.info.shipping_address.country_iso2 = countryName != null ? countryName : ""; + validateRequest.info.shipping_address.post_code = inputFields[FIELD_POSTCODE].getText().toString(); + validateRequest.info.flags |= 8; + } + final TLObject req = validateRequest; + ConnectionsManager.getInstance().sendRequest(validateRequest, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_payments_validatedRequestedInfo) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + requestedInfo = (TLRPC.TL_payments_validatedRequestedInfo) response; + if (paymentForm.saved_info != null && !saveShippingInfo) { + TLRPC.TL_payments_clearSavedInfo req = new TLRPC.TL_payments_clearSavedInfo(); + req.info = true; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + + } + }); + } + goToNextStep(); + setDonePressed(false); + showEditDoneProgress(false); + } + }); + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + setDonePressed(false); + showEditDoneProgress(false); + if (error != null) { + switch (error.text) { + case "REQ_INFO_NAME_INVALID": + shakeField(FIELD_NAME); + break; + case "REQ_INFO_PHONE_INVALID": + shakeField(FIELD_PHONE); + break; + case "REQ_INFO_EMAIL_INVALID": + shakeField(FIELD_EMAIL); + break; + case "ADDRESS_COUNTRY_INVALID": + shakeField(FIELD_COUNTRY); + break; + case "ADDRESS_CITY_INVALID": + shakeField(FIELD_CITY); + break; + case "ADDRESS_POSTCODE_INVALID": + shakeField(FIELD_POSTCODE); + break; + case "ADDRESS_STATE_INVALID": + shakeField(FIELD_STATE); + break; + case "ADDRESS_STREET_LINE1_INVALID": + shakeField(FIELD_STREET1); + break; + case "ADDRESS_STREET_LINE2_INVALID": + shakeField(FIELD_STREET2); + break; + default: + AlertsCreator.processError(error, PaymentFormActivity.this, req); + break; + } + } + } + }); + } + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private TLRPC.TL_paymentRequestedInfo getRequestInfo() { + TLRPC.TL_paymentRequestedInfo info = new TLRPC.TL_paymentRequestedInfo(); + if (paymentForm.invoice.name_requested) { + info.name = inputFields[FIELD_NAME].getText().toString(); + info.flags |= 1; + } + if (paymentForm.invoice.phone_requested) { + info.phone = "+" + inputFields[FIELD_PHONECODE].getText().toString() + inputFields[FIELD_PHONE].getText().toString(); + info.flags |= 2; + } + if (paymentForm.invoice.email_requested) { + info.email = inputFields[FIELD_EMAIL].getText().toString(); + info.flags |= 4; + } + if (paymentForm.invoice.shipping_address_requested) { + info.shipping_address = new TLRPC.TL_postAddress(); + info.shipping_address.street_line1 = inputFields[FIELD_STREET1].getText().toString(); + info.shipping_address.street_line2 = inputFields[FIELD_STREET2].getText().toString(); + info.shipping_address.city = inputFields[FIELD_CITY].getText().toString(); + info.shipping_address.state = inputFields[FIELD_STATE].getText().toString(); + info.shipping_address.country_iso2 = countryName != null ? countryName : ""; + info.shipping_address.post_code = inputFields[FIELD_POSTCODE].getText().toString(); + info.flags |= 8; + } + return info; + } + + private void sendData() { + if (canceled) { + return; + } + showEditDoneProgress(true); + final TLRPC.TL_payments_sendPaymentForm req = new TLRPC.TL_payments_sendPaymentForm(); + req.msg_id = messageObject.getId(); + if (UserConfig.tmpPassword != null && paymentForm.saved_credentials != null) { + req.credentials = new TLRPC.TL_inputPaymentCredentialsSaved(); + req.credentials.id = paymentForm.saved_credentials.id; + req.credentials.tmp_password = UserConfig.tmpPassword.tmp_password; + } else { + req.credentials = new TLRPC.TL_inputPaymentCredentials(); + req.credentials.save = saveCardInfo; + req.credentials.data = new TLRPC.TL_dataJSON(); + req.credentials.data.data = paymentJson; + } + if (requestedInfo != null && requestedInfo.id != null) { + req.requested_info_id = requestedInfo.id; + req.flags |= 1; + } + if (shippingOption != null) { + req.shipping_option_id = shippingOption.id; + req.flags |= 2; + } + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + if (response != null) { + if (response instanceof TLRPC.TL_payments_paymentResult) { + MessagesController.getInstance().processUpdates(((TLRPC.TL_payments_paymentResult) response).updates, false); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + goToNextStep(); + } + }); + } else if (response instanceof TLRPC.TL_payments_paymentVerficationNeeded) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + Browser.openUrl(getParentActivity(), ((TLRPC.TL_payments_paymentVerficationNeeded) response).url, false); + goToNextStep(); + } + }); + } + } else { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + AlertsCreator.processError(error, PaymentFormActivity.this, req); + setDonePressed(false); + showEditDoneProgress(false); + } + }); + } + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private void shakeField(int field) { + Vibrator v = (Vibrator) getParentActivity().getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(inputFields[field], 2, 0); + } + + private void setDonePressed(boolean value) { + donePressed = value; + swipeBackEnabled = !value; + actionBar.getBackButton().setEnabled(!donePressed); + if (detailSettingsCell[0] != null) { + detailSettingsCell[0].setEnabled(!donePressed); + } + } + + private void checkPassword() { + if (UserConfig.tmpPassword != null) { + if (UserConfig.tmpPassword.valid_until < ConnectionsManager.getInstance().getCurrentTime() + 60) { + UserConfig.tmpPassword = null; + UserConfig.saveConfig(false); + } + } + if (UserConfig.tmpPassword != null) { + sendData(); + return; + } + if (inputFields[FIELD_SAVEDPASSWORD].length() == 0) { + Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD], 2, 0); + return; + } + final String password = inputFields[FIELD_SAVEDPASSWORD].getText().toString(); + showEditDoneProgress(true); + setDonePressed(true); + final TLRPC.TL_account_getPassword req = new TLRPC.TL_account_getPassword(); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (error == null) { + if (response instanceof TLRPC.TL_account_noPassword) { + passwordOk = false; + goToNextStep(); + } else { + TLRPC.TL_account_password currentPassword = (TLRPC.TL_account_password) response; + byte[] passwordBytes = null; + try { + passwordBytes = password.getBytes("UTF-8"); + } catch (Exception e) { + FileLog.e(e); + } + + byte[] hash = new byte[currentPassword.current_salt.length * 2 + passwordBytes.length]; + System.arraycopy(currentPassword.current_salt, 0, hash, 0, currentPassword.current_salt.length); + System.arraycopy(passwordBytes, 0, hash, currentPassword.current_salt.length, passwordBytes.length); + System.arraycopy(currentPassword.current_salt, 0, hash, hash.length - currentPassword.current_salt.length, currentPassword.current_salt.length); + + final TLRPC.TL_account_getTmpPassword req = new TLRPC.TL_account_getTmpPassword(); + req.password_hash = Utilities.computeSHA256(hash, 0, hash.length); + req.period = 60 * 30; + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(final TLObject response, final TLRPC.TL_error error) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + showEditDoneProgress(false); + setDonePressed(false); + if (response != null) { + passwordOk = true; + UserConfig.tmpPassword = (TLRPC.TL_account_tmpPassword) response; + UserConfig.saveConfig(false); + goToNextStep(); + } else { + if (error.text.equals("PASSWORD_HASH_INVALID")) { + Vibrator v = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); + if (v != null) { + v.vibrate(200); + } + AndroidUtilities.shakeView(inputFields[FIELD_SAVEDPASSWORD], 2, 0); + inputFields[FIELD_SAVEDPASSWORD].setText(""); + } else { + AlertsCreator.processError(error, PaymentFormActivity.this, req); + } + } + } + }); + + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + } else { + AlertsCreator.processError(error, PaymentFormActivity.this, req); + showEditDoneProgress(false); + setDonePressed(false); + } + } + }); + } + }, ConnectionsManager.RequestFlagFailOnServerErrors); + } + + private void showEditDoneProgress(final boolean show) { + if (doneItemAnimation != null) { + doneItemAnimation.cancel(); + } + if (doneItem != null) { + doneItemAnimation = new AnimatorSet(); + if (show) { + progressView.setVisibility(View.VISIBLE); + doneItem.setEnabled(false); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 0.1f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 0.0f), + ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); + } else { + if (webView != null) { + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f)); + } else { + doneItem.getImageView().setVisibility(View.VISIBLE); + doneItem.setEnabled(true); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleX", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "scaleY", 1.0f), + ObjectAnimator.ofFloat(doneItem.getImageView(), "alpha", 1.0f)); + } + + } + doneItemAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + if (!show) { + progressView.setVisibility(View.INVISIBLE); + } else { + doneItem.getImageView().setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + doneItemAnimation = null; + } + } + }); + doneItemAnimation.setDuration(150); + doneItemAnimation.start(); + } else if (payTextView != null) { + doneItemAnimation = new AnimatorSet(); + if (show) { + progressView.setVisibility(View.VISIBLE); + bottomLayout.setEnabled(false); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(payTextView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(payTextView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(payTextView, "alpha", 0.0f), + ObjectAnimator.ofFloat(progressView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(progressView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(progressView, "alpha", 1.0f)); + } else { + payTextView.setVisibility(View.VISIBLE); + bottomLayout.setEnabled(true); + doneItemAnimation.playTogether( + ObjectAnimator.ofFloat(progressView, "scaleX", 0.1f), + ObjectAnimator.ofFloat(progressView, "scaleY", 0.1f), + ObjectAnimator.ofFloat(progressView, "alpha", 0.0f), + ObjectAnimator.ofFloat(payTextView, "scaleX", 1.0f), + ObjectAnimator.ofFloat(payTextView, "scaleY", 1.0f), + ObjectAnimator.ofFloat(payTextView, "alpha", 1.0f)); + + } + doneItemAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + if (!show) { + progressView.setVisibility(View.INVISIBLE); + } else { + payTextView.setVisibility(View.INVISIBLE); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { + if (doneItemAnimation != null && doneItemAnimation.equals(animation)) { + doneItemAnimation = null; + } + } + }); + doneItemAnimation.setDuration(150); + doneItemAnimation.start(); + } + } + + @Override + public boolean onBackPressed() { + return !donePressed; + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ArrayList arrayList = new ArrayList<>(); + arrayList.add(new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault)); + arrayList.add(new ThemeDescription(scrollView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch)); + arrayList.add(new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder)); + arrayList.add(new ThemeDescription(linearLayout2, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider)); + arrayList.add(new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressInner2)); + arrayList.add(new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressOuter2)); + + if (inputFields != null) { + for (int a = 0; a < inputFields.length; a++) { + arrayList.add(new ThemeDescription((View) inputFields[a].getParent(), ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(inputFields[a], ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText)); + } + } else { + arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText)); + } + if (radioCells != null) { + for (int a = 0; a < radioCells.length; a++) { + arrayList.add(new ThemeDescription(radioCells[a], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(radioCells[a], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector)); + arrayList.add(new ThemeDescription(radioCells[a], 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(radioCells[a], ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground)); + arrayList.add(new ThemeDescription(radioCells[a], ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked)); + } + } else { + arrayList.add(new ThemeDescription(null, 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground)); + arrayList.add(new ThemeDescription(null, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked)); + } + for (int a = 0; a < headerCell.length; a++) { + arrayList.add(new ThemeDescription(headerCell[a], ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(headerCell[a], 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader)); + } + for (int a = 0; a < sectionCell.length; a++) { + arrayList.add(new ThemeDescription(sectionCell[a], ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow)); + } + for (int a = 0; a < bottomCell.length; a++) { + arrayList.add(new ThemeDescription(bottomCell[a], ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow)); + arrayList.add(new ThemeDescription(bottomCell[a], 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4)); + arrayList.add(new ThemeDescription(bottomCell[a], ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText)); + } + for (int a = 0; a < dividers.size(); a++) { + arrayList.add(new ThemeDescription(dividers.get(a), ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_divider)); + } + + arrayList.add(new ThemeDescription(textView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + + arrayList.add(new ThemeDescription(checkCell1, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(checkCell1, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb)); + arrayList.add(new ThemeDescription(checkCell1, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack)); + arrayList.add(new ThemeDescription(checkCell1, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked)); + arrayList.add(new ThemeDescription(checkCell1, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked)); + arrayList.add(new ThemeDescription(checkCell1, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(checkCell1, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector)); + + arrayList.add(new ThemeDescription(settingsCell1, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(settingsCell1, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector)); + arrayList.add(new ThemeDescription(settingsCell1, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + + arrayList.add(new ThemeDescription(payTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText6)); + + arrayList.add(new ThemeDescription(linearLayout2, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextPriceCell.class}, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(linearLayout2, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextPriceCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(linearLayout2, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextPriceCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(linearLayout2, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextPriceCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); + arrayList.add(new ThemeDescription(linearLayout2, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextPriceCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); + + arrayList.add(new ThemeDescription(detailSettingsCell[0], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(detailSettingsCell[0], ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector)); + + for (int a = 1; a < detailSettingsCell.length; a++) { + arrayList.add(new ThemeDescription(detailSettingsCell[a], ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(detailSettingsCell[a], 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(detailSettingsCell[a], 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); + } + + arrayList.add(new ThemeDescription(paymentInfoCell, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(paymentInfoCell, 0, new Class[]{PaymentInfoCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(paymentInfoCell, 0, new Class[]{PaymentInfoCell.class}, new String[]{"detailTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText)); + arrayList.add(new ThemeDescription(paymentInfoCell, 0, new Class[]{PaymentInfoCell.class}, new String[]{"detailExTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2)); + + arrayList.add(new ThemeDescription(bottomLayout, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_windowBackgroundWhite)); + arrayList.add(new ThemeDescription(bottomLayout, ThemeDescription.FLAG_SELECTORWHITE, null, null, null, null, Theme.key_listSelector)); + + return arrayList.toArray(new ThemeDescription[arrayList.size()]); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java index 2dfc6665a56..04946d8ce47 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoAlbumPickerActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -11,7 +11,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; -import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.text.TextUtils; import android.view.Gravity; @@ -22,8 +21,6 @@ import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -33,17 +30,21 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.R; +import org.telegram.messenger.VideoEditedInfo; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Adapters.BaseFragmentAdapter; import org.telegram.ui.Cells.PhotoPickerAlbumsCell; import org.telegram.ui.Cells.PhotoPickerSearchCell; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PickerBottomLayout; +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.HashMap; @@ -52,7 +53,7 @@ public class PhotoAlbumPickerActivity extends BaseFragment implements Notificati public interface PhotoAlbumPickerActivityDelegate { void didSelectPhotos(ArrayList photos, ArrayList captions, ArrayList> masks, ArrayList webPhotos); - boolean didSelectVideo(String path); + void didSelectVideo(String path, VideoEditedInfo info, long estimatedSize, long estimatedDuration, String caption); void startPhotoSelectActivity(); } @@ -67,7 +68,7 @@ public interface PhotoAlbumPickerActivityDelegate { private boolean loading = false; private int columnsCount = 2; - private ListView listView; + private RecyclerListView listView; private ListAdapter listAdapter; private FrameLayout progressView; private TextView emptyView; @@ -116,7 +117,8 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { actionBar.setBackgroundColor(Theme.ACTION_BAR_MEDIA_PICKER_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); + actionBar.setTitleColor(0xffffffff); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override @@ -159,10 +161,10 @@ public void onItemClick(int id) { if (!singlePhoto) { selectedMode = 0; - dropDownContainer = new ActionBarMenuItem(context, menu, 0); + dropDownContainer = new ActionBarMenuItem(context, menu, 0, 0); dropDownContainer.setSubMenuOpenSide(1); - dropDownContainer.addSubItem(item_photos, LocaleController.getString("PickerPhotos", R.string.PickerPhotos), 0); - dropDownContainer.addSubItem(item_video, LocaleController.getString("PickerVideo", R.string.PickerVideo), 0); + dropDownContainer.addSubItem(item_photos, LocaleController.getString("PickerPhotos", R.string.PickerPhotos)); + dropDownContainer.addSubItem(item_video, LocaleController.getString("PickerVideo", R.string.PickerVideo)); actionBar.addView(dropDownContainer); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) dropDownContainer.getLayoutParams(); layoutParams.height = LayoutHelper.MATCH_PARENT; @@ -201,16 +203,13 @@ public void onClick(View view) { actionBar.setTitle(LocaleController.getString("Gallery", R.string.Gallery)); } - listView = new ListView(context); + listView = new RecyclerListView(context); listView.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), AndroidUtilities.dp(4)); listView.setClipToPadding(false); listView.setHorizontalScrollBarEnabled(false); listView.setVerticalScrollBarEnabled(false); - listView.setSelector(new ColorDrawable(0)); - listView.setDividerHeight(0); - listView.setDivider(null); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setDrawingCacheEnabled(false); - listView.setScrollingCacheEnabled(false); frameLayout.addView(listView); FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); layoutParams.width = LayoutHelper.MATCH_PARENT; @@ -218,7 +217,7 @@ public void onClick(View view) { layoutParams.bottomMargin = AndroidUtilities.dp(48); listView.setLayoutParams(layoutParams); listView.setAdapter(listAdapter = new ListAdapter(context)); - AndroidUtilities.setListViewEdgeEffectColor(listView, 0xff333333); + listView.setGlowColor(0xff333333); emptyView = new TextView(context); emptyView.setTextColor(0xff808080); @@ -248,7 +247,7 @@ public boolean onTouch(View v, MotionEvent event) { layoutParams.bottomMargin = AndroidUtilities.dp(48); progressView.setLayoutParams(layoutParams); - ProgressBar progressBar = new ProgressBar(context); + RadialProgressView progressBar = new RadialProgressView(context); progressView.addView(progressBar); layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); layoutParams.width = LayoutHelper.WRAP_CONTENT; @@ -490,15 +489,16 @@ public void actionButtonPressed(boolean canceled) { } @Override - public boolean didSelectVideo(String path) { + public void didSelectVideo(String path, VideoEditedInfo info, long estimatedSize, long estimatedDuration, String caption) { removeSelfFromStack(); - return delegate.didSelectVideo(path); + delegate.didSelectVideo(path, info, estimatedSize, estimatedDuration, caption); } }); presentFragment(fragment); } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -506,17 +506,12 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int i) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { return true; } @Override - public int getCount() { + public int getItemCount() { if (singlePhoto || selectedMode == 0) { if (singlePhoto) { return albumsSorted != null ? (int) Math.ceil(albumsSorted.size() / (float) columnsCount) : 0; @@ -528,44 +523,47 @@ public int getCount() { } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - PhotoPickerAlbumsCell photoPickerAlbumsCell; - if (view == null) { - view = new PhotoPickerAlbumsCell(mContext); - photoPickerAlbumsCell = (PhotoPickerAlbumsCell) view; - photoPickerAlbumsCell.setDelegate(new PhotoPickerAlbumsCell.PhotoPickerAlbumsCellDelegate() { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: { + PhotoPickerAlbumsCell cell = new PhotoPickerAlbumsCell(mContext); + cell.setDelegate(new PhotoPickerAlbumsCell.PhotoPickerAlbumsCellDelegate() { @Override public void didSelectAlbum(MediaController.AlbumEntry albumEntry) { openPhotoPicker(albumEntry, 0); } }); - } else { - photoPickerAlbumsCell = (PhotoPickerAlbumsCell) view; + view = cell; + break; } + case 1: + default: { + PhotoPickerSearchCell cell = new PhotoPickerSearchCell(mContext, allowGifs); + cell.setDelegate(new PhotoPickerSearchCell.PhotoPickerSearchCellDelegate() { + @Override + public void didPressedSearchButton(int index) { + openPhotoPicker(null, index); + } + }); + view = cell; + break; + } + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + PhotoPickerAlbumsCell photoPickerAlbumsCell = (PhotoPickerAlbumsCell) holder.itemView; photoPickerAlbumsCell.setAlbumsCount(columnsCount); for (int a = 0; a < columnsCount; a++) { int index; if (singlePhoto || selectedMode == 1) { - index = i * columnsCount + a; + index = position * columnsCount + a; } else { - index = (i - 1) * columnsCount + a; + index = (position - 1) * columnsCount + a; } if (singlePhoto || selectedMode == 0) { if (index < albumsSorted.size()) { @@ -584,18 +582,7 @@ public void didSelectAlbum(MediaController.AlbumEntry albumEntry) { } } photoPickerAlbumsCell.requestLayout(); - } else if (type == 1) { - if (view == null) { - view = new PhotoPickerSearchCell(mContext, allowGifs); - ((PhotoPickerSearchCell) view).setDelegate(new PhotoPickerSearchCell.PhotoPickerSearchCellDelegate() { - @Override - public void didPressedSearchButton(int index) { - openPhotoPicker(null, index); - } - }); - } } - return view; } @Override @@ -608,18 +595,5 @@ public int getItemViewType(int i) { } return 0; } - - @Override - public int getViewTypeCount() { - if (singlePhoto || selectedMode == 1) { - return 1; - } - return 2; - } - - @Override - public boolean isEmpty() { - return getCount() == 0; - } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java index b3d8d659013..f3d428f485b 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoCropActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -313,7 +313,7 @@ public Bitmap getBitmap() { try { return Bitmaps.createBitmap(imageToCrop, x, y, sizeX, sizeY); } catch (Throwable e2) { - FileLog.e("tmessages", e2); + FileLog.e(e2); } } return null; @@ -326,7 +326,7 @@ protected void onDraw(Canvas canvas) { drawable.setBounds(bitmapX, bitmapY, bitmapX + bitmapWidth, bitmapY + bitmapHeight); drawable.draw(canvas); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } canvas.drawRect(bitmapX, bitmapY, bitmapX + bitmapWidth, rectY, halfPaint); @@ -419,7 +419,8 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { actionBar.setBackgroundColor(Theme.ACTION_BAR_MEDIA_PICKER_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false); + actionBar.setTitleColor(0xffffffff); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); actionBar.setTitle(LocaleController.getString("CropImage", R.string.CropImage)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java index 8c0e6a8941e..b20b76e84ec 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoPickerActivity.java @@ -3,76 +3,76 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Bitmap; +import android.graphics.Rect; +import android.os.AsyncTask; import android.os.Build; -import android.util.Base64; +import android.os.Bundle; import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; -import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.GridView; -import android.widget.ProgressBar; -import android.widget.TextView; import org.json.JSONArray; import org.json.JSONObject; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.NotificationCenter; -import org.telegram.messenger.volley.AuthFailureError; -import org.telegram.messenger.volley.Request; -import org.telegram.messenger.volley.RequestQueue; -import org.telegram.messenger.volley.Response; -import org.telegram.messenger.volley.VolleyError; -import org.telegram.messenger.volley.toolbox.JsonObjectRequest; -import org.telegram.messenger.volley.toolbox.Volley; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.GridLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.MessageObject; import org.telegram.messenger.UserConfig; -import org.telegram.messenger.Utilities; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; -import org.telegram.ui.Adapters.BaseFragmentAdapter; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Cells.PhotoPickerPhotoCell; import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PickerBottomLayout; - +import org.telegram.ui.Components.RadialProgressView; +import org.telegram.ui.Components.RecyclerListView; + +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.SocketException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; import java.net.URLEncoder; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; -import java.util.Map; public class PhotoPickerActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate, PhotoViewer.PhotoViewerProvider { @@ -81,11 +81,9 @@ public interface PhotoPickerActivityDelegate { void actionButtonPressed(boolean canceled); - boolean didSelectVideo(String path); + void didSelectVideo(String path, VideoEditedInfo info, long estimatedSize, long estimatedDuration, String caption); } - private RequestQueue requestQueue; - private int type; private HashMap selectedWebPhotos; private HashMap selectedPhotos; @@ -96,7 +94,7 @@ public interface PhotoPickerActivityDelegate { private HashMap searchResultUrls = new HashMap<>(); private boolean searching; - private String nextSearchBingString; + private boolean bingSearchEndReached = true; private boolean giphySearchEndReached = true; private String lastSearchString; private boolean loadingRecent; @@ -104,14 +102,15 @@ public interface PhotoPickerActivityDelegate { private int giphyReqId; private int lastSearchToken; private boolean allowCaption = true; + private AsyncTask currentBingTask; private MediaController.AlbumEntry selectedAlbum; - private GridView listView; + private RecyclerListView listView; private ListAdapter listAdapter; + private GridLayoutManager layoutManager; private PickerBottomLayout pickerBottomLayout; - private FrameLayout progressView; - private TextView emptyView; + private EmptyTextProgressView emptyView; private ActionBarMenuItem searchItem; private int itemWidth = 100; private boolean sendPressed; @@ -140,7 +139,6 @@ public boolean onFragmentCreate() { NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().addObserver(this, NotificationCenter.recentImagesDidLoaded); if (selectedAlbum == null) { - requestQueue = Volley.newRequestQueue(ApplicationLoader.applicationContext); if (recentImages.isEmpty()) { MessagesStorage.getInstance().loadWebRecent(type); loadingRecent = true; @@ -153,9 +151,13 @@ public boolean onFragmentCreate() { public void onFragmentDestroy() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.recentImagesDidLoaded); - if (requestQueue != null) { - requestQueue.cancelAll("search"); - requestQueue.stop(); + if (currentBingTask != null) { + currentBingTask.cancel(true); + currentBingTask = null; + } + if (giphyReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(giphyReqId, true); + giphyReqId = 0; } super.onFragmentDestroy(); } @@ -164,7 +166,8 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { actionBar.setBackgroundColor(Theme.ACTION_BAR_MEDIA_PICKER_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false); + actionBar.setTitleColor(0xffffffff); actionBar.setBackButtonImage(R.drawable.ic_ab_back); if (selectedAlbum != null) { actionBar.setTitle(selectedAlbum.bucketName); @@ -202,10 +205,17 @@ public void onTextChanged(EditText editText) { searchResult.clear(); searchResultKeys.clear(); lastSearchString = null; - nextSearchBingString = null; + bingSearchEndReached = true; giphySearchEndReached = true; searching = false; - requestQueue.cancelAll("search"); + if (currentBingTask != null) { + currentBingTask.cancel(true); + currentBingTask = null; + } + if (giphyReqId != 0) { + ConnectionsManager.getInstance().cancelRequest(giphyReqId, true); + giphyReqId = 0; + } if (type == 0) { emptyView.setText(LocaleController.getString("NoRecentPhotos", R.string.NoRecentPhotos)); } else if (type == 1) { @@ -222,7 +232,7 @@ public void onSearchPressed(EditText editText) { } searchResult.clear(); searchResultKeys.clear(); - nextSearchBingString = null; + bingSearchEndReached = true; giphySearchEndReached = true; if (type == 0) { searchBingImages(editText.getText().toString(), 0, 53); @@ -259,33 +269,75 @@ public void onSearchPressed(EditText editText) { FrameLayout frameLayout = (FrameLayout) fragmentView; frameLayout.setBackgroundColor(0xff000000); - listView = new GridView(context); + listView = new RecyclerListView(context); listView.setPadding(AndroidUtilities.dp(4), AndroidUtilities.dp(4), AndroidUtilities.dp(4), AndroidUtilities.dp(4)); listView.setClipToPadding(false); - listView.setDrawSelectorOnTop(true); - listView.setStretchMode(GridView.STRETCH_COLUMN_WIDTH); listView.setHorizontalScrollBarEnabled(false); listView.setVerticalScrollBarEnabled(false); - listView.setNumColumns(GridView.AUTO_FIT); - listView.setVerticalSpacing(AndroidUtilities.dp(4)); - listView.setHorizontalSpacing(AndroidUtilities.dp(4)); - listView.setSelector(R.drawable.list_selector); - frameLayout.addView(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.bottomMargin = singlePhoto ? 0 : AndroidUtilities.dp(48); - listView.setLayoutParams(layoutParams); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setLayoutManager(layoutManager = new GridLayoutManager(context, 4) { + @Override + public boolean supportsPredictiveItemAnimations() { + return false; + } + }); + listView.addItemDecoration(new RecyclerView.ItemDecoration() { + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + super.getItemOffsets(outRect, view, parent, state); + int total = state.getItemCount(); + int position = parent.getChildAdapterPosition(view); + int spanCount = layoutManager.getSpanCount(); + int rowsCOunt = (int) Math.ceil(total / (float) spanCount); + int row = position / spanCount; + int col = position % spanCount; + outRect.right = col != spanCount - 1 ? AndroidUtilities.dp(4) : 0; + outRect.bottom = row != rowsCOunt - 1 ? AndroidUtilities.dp(4) : 0; + } + }); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, singlePhoto ? 0 : 48)); listView.setAdapter(listAdapter = new ListAdapter(context)); - AndroidUtilities.setListViewEdgeEffectColor(listView, 0xff333333); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setGlowColor(0xff333333); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { + public void onItemClick(View view, int position) { if (selectedAlbum != null && selectedAlbum.isVideo) { - if (i < 0 || i >= selectedAlbum.photos.size()) { + if (position < 0 || position >= selectedAlbum.photos.size()) { return; } - if (delegate.didSelectVideo(selectedAlbum.photos.get(i).path)) { + + String path = selectedAlbum.photos.get(position).path; + if (Build.VERSION.SDK_INT >= 16) { + Bundle args = new Bundle(); + args.putString("videoPath", path); + VideoEditorActivity fragment = new VideoEditorActivity(args); + fragment.setDelegate(new VideoEditorActivity.VideoEditorActivityDelegate() { + @Override + public void didFinishEditVideo(String videoPath, long startTime, long endTime, int resultWidth, int resultHeight, int rotationValue, int originalWidth, int originalHeight, int bitrate, long estimatedSize, long estimatedDuration, String caption) { + removeSelfFromStack(); + VideoEditedInfo videoEditedInfo = new VideoEditedInfo(); + videoEditedInfo.startTime = startTime; + videoEditedInfo.endTime = endTime; + videoEditedInfo.rotationValue = rotationValue; + videoEditedInfo.originalWidth = originalWidth; + videoEditedInfo.originalHeight = originalHeight; + videoEditedInfo.bitrate = bitrate; + videoEditedInfo.resultWidth = resultWidth; + videoEditedInfo.resultHeight = resultHeight; + videoEditedInfo.originalPath = videoPath; + delegate.didSelectVideo(videoPath, videoEditedInfo, estimatedSize, estimatedDuration, caption); + } + }); + + if (!fragment.onFragmentCreate()) { + delegate.didSelectVideo(path, null, 0, 0, null); + finishFragment(); + } else if (parentLayout.presentFragment(fragment, false, false, true)) { + fragment.setParentChatActivity(chatActivity); + } + } else { + delegate.didSelectVideo(path, null, 0, 0, null); finishFragment(); } } else { @@ -299,22 +351,22 @@ public void onItemClick(AdapterView adapterView, View view, int i, long l) { arrayList = (ArrayList) searchResult; } } - if (i < 0 || i >= arrayList.size()) { + if (position < 0 || position >= arrayList.size()) { return; } if (searchItem != null) { AndroidUtilities.hideKeyboard(searchItem.getSearchField()); } PhotoViewer.getInstance().setParentActivity(getParentActivity()); - PhotoViewer.getInstance().openPhotoForSelect(arrayList, i, singlePhoto ? 1 : 0, PhotoPickerActivity.this, chatActivity); + PhotoViewer.getInstance().openPhotoForSelect(arrayList, position, singlePhoto ? 1 : 0, PhotoPickerActivity.this, chatActivity); } } }); if (selectedAlbum == null) { - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + public boolean onItemClick(View view, int position) { if (searchResult.isEmpty() && lastSearchString == null) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -338,11 +390,10 @@ public void onClick(DialogInterface dialogInterface, int i) { }); } - emptyView = new TextView(context); + emptyView = new EmptyTextProgressView(context); emptyView.setTextColor(0xff808080); - emptyView.setTextSize(20); - emptyView.setGravity(Gravity.CENTER); - emptyView.setVisibility(View.GONE); + emptyView.setProgressBarColor(0xffffffff); + emptyView.setShowAtCenter(true); if (selectedAlbum != null) { emptyView.setText(LocaleController.getString("NoPhotos", R.string.NoPhotos)); } else { @@ -352,67 +403,39 @@ public void onClick(DialogInterface dialogInterface, int i) { emptyView.setText(LocaleController.getString("NoRecentGIFs", R.string.NoRecentGIFs)); } } - frameLayout.addView(emptyView); - layoutParams = (FrameLayout.LayoutParams) emptyView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.bottomMargin = singlePhoto ? 0 : AndroidUtilities.dp(48); - emptyView.setLayoutParams(layoutParams); - emptyView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, singlePhoto ? 0 : 48)); if (selectedAlbum == null) { - listView.setOnScrollListener(new AbsListView.OnScrollListener() { + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onScrollStateChanged(AbsListView absListView, int i) { - if (i == SCROLL_STATE_TOUCH_SCROLL) { + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { AndroidUtilities.hideKeyboard(getParentActivity().getCurrentFocus()); } } @Override - public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) { - if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !searching) { - if (type == 0 && nextSearchBingString != null) { - searchBingImages(lastSearchString, searchResult.size(), 54); - } else if (type == 1 && !giphySearchEndReached) { - searchGiphyImages(searchItem.getSearchField().getText().toString(), nextGiphySearchOffset); + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = layoutManager.findFirstVisibleItemPosition(); + int visibleItemCount = firstVisibleItem == RecyclerView.NO_POSITION ? 0 : Math.abs(layoutManager.findLastVisibleItemPosition() - firstVisibleItem) + 1; + if (visibleItemCount > 0) { + int totalItemCount = layoutManager.getItemCount(); + if (visibleItemCount != 0 && firstVisibleItem + visibleItemCount > totalItemCount - 2 && !searching) { + if (type == 0 && !bingSearchEndReached) { + searchBingImages(lastSearchString, searchResult.size(), 54); + } else if (type == 1 && !giphySearchEndReached) { + searchGiphyImages(searchItem.getSearchField().getText().toString(), nextGiphySearchOffset); + } } } } }); - progressView = new FrameLayout(context); - progressView.setVisibility(View.GONE); - frameLayout.addView(progressView); - layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.bottomMargin = singlePhoto ? 0 : AndroidUtilities.dp(48); - progressView.setLayoutParams(layoutParams); - - ProgressBar progressBar = new ProgressBar(context); - progressView.addView(progressBar); - layoutParams = (FrameLayout.LayoutParams) progressBar.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.CENTER; - progressBar.setLayoutParams(layoutParams); - updateSearchInterface(); } pickerBottomLayout = new PickerBottomLayout(context); - frameLayout.addView(pickerBottomLayout); - layoutParams = (FrameLayout.LayoutParams) pickerBottomLayout.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = AndroidUtilities.dp(48); - layoutParams.gravity = Gravity.BOTTOM; - pickerBottomLayout.setLayoutParams(layoutParams); + frameLayout.addView(pickerBottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM)); pickerBottomLayout.cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { @@ -612,8 +635,15 @@ public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation @Override public void willHidePhotoViewer() { - if (listAdapter != null) { - listAdapter.notifyDataSetChanged(); + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View view = listView.getChildAt(a); + if (view instanceof PhotoPickerPhotoCell) { + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; + if (cell.checkBox.getVisibility() != View.VISIBLE) { + cell.checkBox.setVisibility(View.VISIBLE); + } + } } } @@ -686,7 +716,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { if (selectedAlbum != null) { if (selectedPhotos.isEmpty()) { if (index < 0 || index >= selectedAlbum.photos.size()) { @@ -728,13 +758,9 @@ private void updateSearchInterface() { listAdapter.notifyDataSetChanged(); } if (searching && searchResult.isEmpty() || loadingRecent && lastSearchString == null) { - progressView.setVisibility(View.VISIBLE); - listView.setEmptyView(null); - emptyView.setVisibility(View.GONE); + emptyView.showProgress(); } else { - progressView.setVisibility(View.GONE); - emptyView.setVisibility(View.VISIBLE); - listView.setEmptyView(emptyView); + emptyView.showTextView(); } } @@ -745,7 +771,10 @@ private void searchGiphyImages(final String query, int offset) { ConnectionsManager.getInstance().cancelRequest(giphyReqId, true); giphyReqId = 0; } - requestQueue.cancelAll("search"); + if (currentBingTask != null) { + currentBingTask.cancel(true); + currentBingTask = null; + } } searching = true; TLRPC.TL_messages_searchGifs req = new TLRPC.TL_messages_searchGifs(); @@ -761,6 +790,7 @@ public void run() { if (token != lastSearchToken) { return; } + int addedCount = 0; if (response != null) { boolean added = false; TLRPC.TL_messages_foundGifs res = (TLRPC.TL_messages_foundGifs) response; @@ -799,81 +829,27 @@ public void run() { } bingImage.type = 1; searchResult.add(bingImage); + addedCount++; searchResultKeys.put(bingImage.id, bingImage); } giphySearchEndReached = !added; } searching = false; - updateSearchInterface(); + if (addedCount != 0) { + listAdapter.notifyItemRangeInserted(searchResult.size(), addedCount); + } else if (giphySearchEndReached) { + listAdapter.notifyItemRemoved(searchResult.size() - 1); + } + if (searching && searchResult.isEmpty() || loadingRecent && lastSearchString == null) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } } }); } }); ConnectionsManager.getInstance().bindRequestToGuid(giphyReqId, classGuid); - /*try { - String url = String.format(Locale.US, "https://api.giphy.com/v1/gifs/search?q=%s&offset=%d&limit=%d&api_key=141Wa2KDAfNfxu", URLEncoder.encode(query, "UTF-8"), offset, count); - JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET, url, null, - new Response.Listener() { - @Override - public void onResponse(JSONObject response) { - try { - JSONArray result = response.getJSONArray("data"); - try { - JSONObject pagination = response.getJSONObject("pagination"); - int total_count = pagination.getInt("total_count"); - giphySearchEndReached = searchResult.size() + result.length() >= total_count; - } catch (Exception e) { - FileLog.e("tmessages", e); - } - boolean added = false; - for (int a = 0; a < result.length(); a++) { - try { - JSONObject object = result.getJSONObject(a); - String id = object.getString("id"); - if (searchResultKeys.containsKey(id)) { - continue; - } - added = true; - JSONObject images = object.getJSONObject("images"); - JSONObject thumb = images.getJSONObject("downsized_still"); - JSONObject original = images.getJSONObject("original"); - MediaController.SearchImage bingImage = new MediaController.SearchImage(); - bingImage.id = id; - bingImage.width = original.getInt("width"); - bingImage.height = original.getInt("height"); - bingImage.size = original.getInt("size"); - bingImage.imageUrl = original.getString("url"); - bingImage.thumbUrl = thumb.getString("url"); - bingImage.type = 1; - searchResult.add(bingImage); - searchResultKeys.put(id, bingImage); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - - } catch (Exception e) { - FileLog.e("tmessages", e); - } - searching = false; - updateSearchInterface(); - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - FileLog.e("tmessages", "Error: " + error.getMessage()); - giphySearchEndReached = true; - searching = false; - updateSearchInterface(); - } - }); - jsonObjReq.setShouldCache(false); - jsonObjReq.setTag("search"); - requestQueue.add(jsonObjReq); - } catch (Exception e) { - FileLog.e("tmessages", e); - }*/ } private void searchBingImages(String query, int offset, int count) { @@ -883,90 +859,202 @@ private void searchBingImages(String query, int offset, int count) { ConnectionsManager.getInstance().cancelRequest(giphyReqId, true); giphyReqId = 0; } - requestQueue.cancelAll("search"); + if (currentBingTask != null) { + currentBingTask.cancel(true); + currentBingTask = null; + } } try { searching = true; - String url; - if (nextSearchBingString != null) { - url = nextSearchBingString; - } else { - boolean adult; - String phone = UserConfig.getCurrentUser().phone; - adult = phone.startsWith("44") || phone.startsWith("49") || phone.startsWith("43") || phone.startsWith("31") || phone.startsWith("1"); - url = String.format(Locale.US, "https://api.datamarket.azure.com/Bing/Search/v1/Image?Query='%s'&$skip=%d&$top=%d&$format=json%s", URLEncoder.encode(query, "UTF-8"), offset, count, adult ? "" : "&Adult='Off'"); - } - JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET, url, null, - new Response.Listener() { - @Override - public void onResponse(JSONObject response) { - nextSearchBingString = null; - try { - JSONObject d = response.getJSONObject("d"); - JSONArray result = d.getJSONArray("results"); - try { - nextSearchBingString = d.getString("__next"); - } catch (Exception e) { - nextSearchBingString = null; - FileLog.e("tmessages", e); + + boolean adult; + String phone = UserConfig.getCurrentUser().phone; + adult = phone.startsWith("44") || phone.startsWith("49") || phone.startsWith("43") || phone.startsWith("31") || phone.startsWith("1"); + final String url = String.format(Locale.US, "https://api.cognitive.microsoft.com/bing/v5.0/images/search?q='%s'&offset=%d&count=%d&$format=json&safeSearch=%s", URLEncoder.encode(query, "UTF-8"), offset, count, adult ? "Strict" : "Off"); + + currentBingTask = new AsyncTask() { + + private boolean canRetry = true; + + private String downloadUrlContent(String url) { + boolean canRetry = true; + InputStream httpConnectionStream = null; + boolean done = false; + StringBuilder result = null; + URLConnection httpConnection = null; + try { + URL downloadUrl = new URL(url); + httpConnection = downloadUrl.openConnection(); + httpConnection.addRequestProperty("Ocp-Apim-Subscription-Key", BuildVars.BING_SEARCH_KEY); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + httpConnection.setConnectTimeout(5000); + httpConnection.setReadTimeout(5000); + if (httpConnection instanceof HttpURLConnection) { + HttpURLConnection httpURLConnection = (HttpURLConnection) httpConnection; + httpURLConnection.setInstanceFollowRedirects(true); + int status = httpURLConnection.getResponseCode(); + if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == HttpURLConnection.HTTP_SEE_OTHER) { + String newUrl = httpURLConnection.getHeaderField("Location"); + String cookies = httpURLConnection.getHeaderField("Set-Cookie"); + downloadUrl = new URL(newUrl); + httpConnection = downloadUrl.openConnection(); + httpConnection.setRequestProperty("Cookie", cookies); + httpConnection.addRequestProperty("Ocp-Apim-Subscription-Key", BuildVars.BING_SEARCH_KEY); + httpConnection.addRequestProperty("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)"); + httpConnection.addRequestProperty("Accept-Language", "en-us,en;q=0.5"); + httpConnection.addRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + httpConnection.addRequestProperty("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + } + } + httpConnection.connect(); + httpConnectionStream = httpConnection.getInputStream(); + } catch (Throwable e) { + if (e instanceof SocketTimeoutException) { + if (ConnectionsManager.isNetworkOnline()) { + canRetry = false; + } + } else if (e instanceof UnknownHostException) { + canRetry = false; + } else if (e instanceof SocketException) { + if (e.getMessage() != null && e.getMessage().contains("ECONNRESET")) { + canRetry = false; + } + } else if (e instanceof FileNotFoundException) { + canRetry = false; + } + FileLog.e(e); + } + + if (canRetry) { + try { + if (httpConnection != null && httpConnection instanceof HttpURLConnection) { + int code = ((HttpURLConnection) httpConnection).getResponseCode(); + if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_ACCEPTED && code != HttpURLConnection.HTTP_NOT_MODIFIED) { + //canRetry = false; } - for (int a = 0; a < result.length(); a++) { + } + } catch (Exception e) { + FileLog.e(e); + } + + if (httpConnectionStream != null) { + try { + byte[] data = new byte[1024 * 32]; + while (true) { + if (isCancelled()) { + break; + } try { - JSONObject object = result.getJSONObject(a); - String id = Utilities.MD5(object.getString("MediaUrl")); - if (searchResultKeys.containsKey(id)) { - continue; + int read = httpConnectionStream.read(data); + if (read > 0) { + if (result == null) { + result = new StringBuilder(); + } + result.append(new String(data, 0, read, "UTF-8")); + } else if (read == -1) { + done = true; + break; + } else { + break; } - MediaController.SearchImage bingImage = new MediaController.SearchImage(); - bingImage.id = id; - bingImage.width = object.getInt("Width"); - bingImage.height = object.getInt("Height"); - bingImage.size = object.getInt("FileSize"); - bingImage.imageUrl = object.getString("MediaUrl"); - JSONObject thumbnail = object.getJSONObject("Thumbnail"); - bingImage.thumbUrl = thumbnail.getString("MediaUrl"); - searchResult.add(bingImage); - searchResultKeys.put(id, bingImage); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + break; } } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - searching = false; - if (nextSearchBingString != null && !nextSearchBingString.contains("json")) { - nextSearchBingString += "&$format=json"; + } catch (Throwable e) { + FileLog.e(e); } - updateSearchInterface(); } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - FileLog.e("tmessages", "Error: " + error.getMessage()); - nextSearchBingString = null; - searching = false; - updateSearchInterface(); + + try { + if (httpConnectionStream != null) { + httpConnectionStream.close(); + } + } catch (Throwable e) { + FileLog.e(e); } - }) { + } + return done ? result.toString() : null; + } + + protected JSONObject doInBackground(Void... voids) { + String code = downloadUrlContent(url); + if (isCancelled()) { + return null; + } + try { + return new JSONObject(code); + } catch (Exception e) { + FileLog.e(e); + } + return null; + } @Override - public Map getHeaders() throws AuthFailureError { - Map headers = new HashMap<>(); - String auth = "Basic " + Base64.encodeToString((BuildVars.BING_SEARCH_KEY + ":" + BuildVars.BING_SEARCH_KEY).getBytes(), Base64.NO_WRAP); - headers.put("Authorization", auth); - return headers; + protected void onPostExecute(JSONObject response) { + int addedCount = 0; + if (response != null) { + try { + JSONArray result = response.getJSONArray("value"); + boolean added = false; + for (int a = 0; a < result.length(); a++) { + try { + JSONObject object = result.getJSONObject(a); + String id = Utilities.MD5(object.getString("contentUrl")); + if (searchResultKeys.containsKey(id)) { + continue; + } + MediaController.SearchImage bingImage = new MediaController.SearchImage(); + bingImage.id = id; + bingImage.width = object.getInt("width"); + bingImage.height = object.getInt("height"); + bingImage.size = Utilities.parseInt(object.getString("contentSize")); + bingImage.imageUrl = object.getString("contentUrl"); + bingImage.thumbUrl = object.getString("thumbnailUrl"); + searchResult.add(bingImage); + searchResultKeys.put(id, bingImage); + addedCount++; + added = true; + } catch (Exception e) { + FileLog.e(e); + } + } + bingSearchEndReached = !added; + } catch (Exception e) { + FileLog.e(e); + } + searching = false; + } else { + bingSearchEndReached = true; + searching = false; + } + if (addedCount != 0) { + listAdapter.notifyItemRangeInserted(searchResult.size(), addedCount); + } else if (giphySearchEndReached) { + listAdapter.notifyItemRemoved(searchResult.size() - 1); + } + if (searching && searchResult.isEmpty() || loadingRecent && lastSearchString == null) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } } }; - jsonObjReq.setShouldCache(false); - jsonObjReq.setTag("search"); - requestQueue.add(jsonObjReq); + currentBingTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null, null, null); } catch (Exception e) { - FileLog.e("tmessages", e); - nextSearchBingString = null; + FileLog.e(e); + bingSearchEndReached = true; searching = false; - updateSearchInterface(); + listAdapter.notifyItemRemoved(searchResult.size() - 1); + if (searching && searchResult.isEmpty() || loadingRecent && lastSearchString == null) { + emptyView.showProgress(); + } else { + emptyView.showTextView(); + } } } @@ -1003,7 +1091,7 @@ private void fixLayoutInternal() { if (getParentActivity() == null) { return; } - int position = listView.getFirstVisiblePosition(); + int position = layoutManager.findFirstVisibleItemPosition(); WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Activity.WINDOW_SERVICE); int rotation = manager.getDefaultDisplay().getRotation(); @@ -1017,23 +1105,23 @@ private void fixLayoutInternal() { columnsCount = 3; } } - listView.setNumColumns(columnsCount); + layoutManager.setSpanCount(columnsCount); if (AndroidUtilities.isTablet()) { itemWidth = (AndroidUtilities.dp(490) - ((columnsCount + 1) * AndroidUtilities.dp(4))) / columnsCount; } else { itemWidth = (AndroidUtilities.displaySize.x - ((columnsCount + 1) * AndroidUtilities.dp(4))) / columnsCount; } - listView.setColumnWidth(itemWidth); listAdapter.notifyDataSetChanged(); - listView.setSelection(position); + layoutManager.scrollToPosition(position); if (selectedAlbum == null) { emptyView.setPadding(0, 0, 0, (int) ((AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight()) * 0.4f)); } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -1041,29 +1129,25 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return selectedAlbum != null; - } - - @Override - public boolean isEnabled(int i) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { if (selectedAlbum == null) { + int position = holder.getAdapterPosition(); if (searchResult.isEmpty() && lastSearchString == null) { - return i < recentImages.size(); + return position < recentImages.size(); } else { - return i < searchResult.size(); + return position < searchResult.size(); } } return true; } @Override - public int getCount() { + public int getItemCount() { if (selectedAlbum == null) { if (searchResult.isEmpty() && lastSearchString == null) { return recentImages.size(); } else if (type == 0) { - return searchResult.size() + (nextSearchBingString == null ? 0 : 1); + return searchResult.size() + (bingSearchEndReached ? 0 : 1); } else if (type == 1) { return searchResult.size() + (giphySearchEndReached ? 0 : 1); } @@ -1071,29 +1155,17 @@ public int getCount() { return selectedAlbum.photos.size(); } - @Override - public Object getItem(int i) { - return null; - } - @Override public long getItemId(int i) { return i; } @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int viewType = getItemViewType(i); - if (viewType == 0) { - PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) view; - if (view == null) { - view = new PhotoPickerPhotoCell(mContext); - cell = (PhotoPickerPhotoCell) view; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + PhotoPickerPhotoCell cell = new PhotoPickerPhotoCell(mContext); cell.checkFrame.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -1133,66 +1205,83 @@ public void onClick(View v) { } }); cell.checkFrame.setVisibility(singlePhoto ? View.GONE : View.VISIBLE); - } - cell.itemWidth = itemWidth; - BackupImageView imageView = ((PhotoPickerPhotoCell) view).photoImage; - imageView.setTag(i); - view.setTag(i); - boolean showing; - imageView.setOrientation(0, true); + view = cell; + break; + case 1: + default: + FrameLayout frameLayout = new FrameLayout(mContext); + view = frameLayout; + RadialProgressView progressBar = new RadialProgressView(mContext); + progressBar.setProgressColor(0xffffffff); + frameLayout.addView(progressBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + break; + } + return new RecyclerListView.Holder(view); + } - if (selectedAlbum != null) { - MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(i); - if (photoEntry.thumbPath != null) { - imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.path != null) { - imageView.setOrientation(photoEntry.orientation, true); - if (photoEntry.isVideo) { - imageView.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + PhotoPickerPhotoCell cell = (PhotoPickerPhotoCell) holder.itemView; + cell.itemWidth = itemWidth; + BackupImageView imageView = cell.photoImage; + imageView.setTag(position); + cell.setTag(position); + boolean showing; + imageView.setOrientation(0, true); + + if (selectedAlbum != null) { + MediaController.PhotoEntry photoEntry = selectedAlbum.photos.get(position); + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.path != null) { + imageView.setOrientation(photoEntry.orientation, true); + if (photoEntry.isVideo) { + imageView.setImage("vthumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else { + imageView.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } } else { - imageView.setImage("thumb://" + photoEntry.imageId + ":" + photoEntry.path, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + imageView.setImageResource(R.drawable.nophotos); } + cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); + showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); } else { - imageView.setImageResource(R.drawable.nophotos); - } - cell.setChecked(selectedPhotos.containsKey(photoEntry.imageId), false); - showing = PhotoViewer.getInstance().isShowingImage(photoEntry.path); - } else { - MediaController.SearchImage photoEntry; - if (searchResult.isEmpty() && lastSearchString == null) { - photoEntry = recentImages.get(i); - } else { - photoEntry = searchResult.get(i); - } - if (photoEntry.thumbPath != null) { - imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { - imageView.setImage(photoEntry.thumbUrl, null, mContext.getResources().getDrawable(R.drawable.nophotos)); - } else if (photoEntry.document != null && photoEntry.document.thumb != null) { - imageView.setImage(photoEntry.document.thumb.location, null, mContext.getResources().getDrawable(R.drawable.nophotos)); - } else { - imageView.setImageResource(R.drawable.nophotos); + MediaController.SearchImage photoEntry; + if (searchResult.isEmpty() && lastSearchString == null) { + photoEntry = recentImages.get(position); + } else { + photoEntry = searchResult.get(position); + } + if (photoEntry.thumbPath != null) { + imageView.setImage(photoEntry.thumbPath, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.thumbUrl != null && photoEntry.thumbUrl.length() > 0) { + imageView.setImage(photoEntry.thumbUrl, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else if (photoEntry.document != null && photoEntry.document.thumb != null) { + imageView.setImage(photoEntry.document.thumb.location, null, mContext.getResources().getDrawable(R.drawable.nophotos)); + } else { + imageView.setImageResource(R.drawable.nophotos); + } + cell.setChecked(selectedWebPhotos.containsKey(photoEntry.id), false); + if (photoEntry.document != null) { + showing = PhotoViewer.getInstance().isShowingImage(FileLoader.getPathToAttach(photoEntry.document, true).getAbsolutePath()); + } else { + showing = PhotoViewer.getInstance().isShowingImage(photoEntry.imageUrl); + } } - cell.setChecked(selectedWebPhotos.containsKey(photoEntry.id), false); - if (photoEntry.document != null) { - showing = PhotoViewer.getInstance().isShowingImage(FileLoader.getPathToAttach(photoEntry.document, true).getAbsolutePath()); - } else { - showing = PhotoViewer.getInstance().isShowingImage(photoEntry.imageUrl); + imageView.getImageReceiver().setVisible(!showing, true); + cell.checkBox.setVisibility(singlePhoto || showing ? View.GONE : View.VISIBLE); + break; + case 1: + ViewGroup.LayoutParams params = holder.itemView.getLayoutParams(); + if (params != null) { + params.width = itemWidth; + params.height = itemWidth; + holder.itemView.setLayoutParams(params); } - } - imageView.getImageReceiver().setVisible(!showing, true); - cell.checkBox.setVisibility(singlePhoto || showing ? View.GONE : View.VISIBLE); - } else if (viewType == 1) { - if (view == null) { - LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = li.inflate(R.layout.media_loading_layout, viewGroup, false); - } - ViewGroup.LayoutParams params = view.getLayoutParams(); - params.width = itemWidth; - params.height = itemWidth; - view.setLayoutParams(params); + break; } - return view; } @Override @@ -1202,23 +1291,5 @@ public int getItemViewType(int i) { } return 1; } - - @Override - public int getViewTypeCount() { - return 2; - } - - @Override - public boolean isEmpty() { - if (selectedAlbum != null) { - return selectedAlbum.photos.isEmpty(); - } else { - if (searchResult.isEmpty() && lastSearchString == null) { - return recentImages.isEmpty(); - } else { - return searchResult.isEmpty(); - } - } - } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index b2a03fcf63c..68950692dbf 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -3,21 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.Manifest; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -29,11 +30,13 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.media.MediaCodecInfo; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.content.FileProvider; import android.text.SpannableStringBuilder; +import android.text.TextPaint; import android.text.TextUtils; import android.util.TypedValue; import android.view.ActionMode; @@ -49,23 +52,35 @@ import android.view.ViewGroup; import android.view.WindowInsets; import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.Scroller; import android.widget.TextView; +import com.coremedia.iso.IsoFile; +import com.coremedia.iso.boxes.Box; +import com.coremedia.iso.boxes.MediaBox; +import com.coremedia.iso.boxes.MediaHeaderBox; +import com.coremedia.iso.boxes.SampleSizeBox; +import com.coremedia.iso.boxes.TrackBox; +import com.coremedia.iso.boxes.TrackHeaderBox; +import com.googlecode.mp4parser.util.Matrix; +import com.googlecode.mp4parser.util.Path; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BuildConfig; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageLoader; import org.telegram.messenger.MessagesStorage; import org.telegram.messenger.UserObject; -import org.telegram.messenger.exoplayer.AspectRatioFrameLayout; -import org.telegram.messenger.exoplayer.ExoPlayer; -import org.telegram.messenger.exoplayer.util.PlayerControl; +import org.telegram.messenger.VideoEditedInfo; +import org.telegram.messenger.exoplayer2.C; +import org.telegram.messenger.exoplayer2.ExoPlayer; +import org.telegram.messenger.exoplayer2.ui.AspectRatioFrameLayout; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLoader; @@ -77,17 +92,20 @@ import org.telegram.messenger.R; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.messenger.MessageObject; import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Adapters.MentionsAdapter; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Components.AnimatedFileDrawable; +import org.telegram.ui.Components.ChatAttachAlert; import org.telegram.ui.Components.CheckBox; import org.telegram.ui.Components.ClippingImageView; import org.telegram.messenger.ImageReceiver; @@ -97,17 +115,20 @@ import org.telegram.ui.Components.PhotoPaintView; import org.telegram.ui.Components.PhotoViewerCaptionEnterView; import org.telegram.ui.Components.PickerBottomLayoutViewer; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.SeekBar; import org.telegram.ui.Components.SizeNotifierFrameLayoutPhoto; import org.telegram.ui.Components.StickersAlert; import org.telegram.ui.Components.VideoPlayer; +import org.telegram.ui.Components.VideoTimelineView; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Locale; @SuppressWarnings("unchecked") @@ -135,20 +156,22 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private TextView nameTextView; private TextView dateTextView; private ActionBarMenuItem menuItem; - private ActionBarMenuItem muteItem; private ActionBarMenuItem masksItem; - private boolean muteItemAvailable; private ImageView shareButton; private BackgroundDrawable backgroundDrawable = new BackgroundDrawable(0xff000000); private Paint blackPaint = new Paint(); private CheckBox checkImageView; private PickerBottomLayoutViewer pickerView; private PickerBottomLayoutViewer editorDoneLayout; - private RadialProgressView radialProgressViews[] = new RadialProgressView[3]; + private TextView resetButton; + private PhotoProgressView photoProgressViews[] = new PhotoProgressView[3]; private ImageView paintItem; private ImageView cropItem; private ImageView tuneItem; - private ActionBarMenuItem captionDoneItem; + private ImageView muteItem; + private ImageView captionItem; + private ImageView compressItem; + private AnimatorSet currentActionBarAnimation; private PhotoCropView photoCropView; private PhotoFilterView photoFilterView; @@ -157,6 +180,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private TextView captionTextView; private TextView captionTextViewOld; private TextView captionTextViewNew; + private ChatAttachAlert parentAlert; private PhotoViewerCaptionEnterView captionEditText; private boolean canShowBottom = true; private int sendPhotoType; @@ -166,6 +190,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private Object lastInsets; + private boolean doneButtonPressed; + private AspectRatioFrameLayout aspectRatioFrameLayout; private TextureView videoTextureView; private VideoPlayer videoPlayer; @@ -173,7 +199,6 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private ImageView videoPlayButton; private TextView videoPlayerTime; private SeekBar videoPlayerSeekbar; - private boolean playerNeedsPrepare; private boolean textureUploaded; private boolean videoCrossfadeStarted; private float videoCrossfadeAlpha; @@ -184,15 +209,32 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public void run() { if (videoPlayer != null && videoPlayerSeekbar != null) { if (!videoPlayerSeekbar.isDragging()) { - PlayerControl playerControl = videoPlayer.getPlayerControl(); - float progress = playerControl.getCurrentPosition() / (float) playerControl.getDuration(); - videoPlayerSeekbar.setProgress(progress); + float progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); + if (!inPreview && videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() == View.VISIBLE) { + if (progress >= videoTimelineView.getRightProgress()) { + videoPlayer.pause(); + videoPlayerSeekbar.setProgress(0); + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoPlayer.getDuration())); + } else { + progress -= videoTimelineView.getLeftProgress(); + if (progress < 0) { + progress = 0; + } + progress /= (videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()); + if (progress > 1) { + progress = 1; + } + videoPlayerSeekbar.setProgress(progress); + } + } else { + videoPlayerSeekbar.setProgress(progress); + } videoPlayerControlFrameLayout.invalidate(); updateVideoPlayerTime(); } } if (isPlaying) { - AndroidUtilities.runOnUIThread(updateProgressRunnable, 100); + AndroidUtilities.runOnUIThread(updateProgressRunnable); } } }; @@ -206,12 +248,12 @@ public void run() { private AnimatorSet mentionListAnimation; private boolean allowMentions; - private int animationInProgress = 0; - private long transitionAnimationStartTime = 0; - private Runnable animationEndRunnable = null; + private int animationInProgress; + private long transitionAnimationStartTime; + private Runnable animationEndRunnable; private PlaceProviderObject showAfterAnimation; private PlaceProviderObject hideAfterAnimation; - private boolean disableShowCheck = false; + private boolean disableShowCheck; private String lastTitle; @@ -222,12 +264,14 @@ public void run() { private ImageReceiver rightImage = new ImageReceiver(); private int currentIndex; private MessageObject currentMessageObject; + private File currentPlayingVideoFile; private TLRPC.BotInlineResult currentBotInlineResult; private TLRPC.FileLocation currentFileLocation; private String currentFileNames[] = new String[3]; private PlaceProviderObject currentPlaceObject; private String currentPathObject; - private Bitmap currentThumb = null; + private Bitmap currentThumb; + private boolean ignoreDidSetImage; private int avatarsDialogId; private long currentDialogId; @@ -245,7 +289,7 @@ public void run() { private boolean wasLayout; private boolean dontResetZoomOnFirstLayout; - private boolean draggingDown = false; + private boolean draggingDown; private float dragY; private float translationX; private float translationY; @@ -254,7 +298,7 @@ public void run() { private float animateToY; private float animateToScale; private float animationValue; - private int currentRotation; + private boolean applying; private long animationStartTime; private AnimatorSet imageMoveAnimation; private AnimatorSet changeModeAnimation; @@ -273,17 +317,19 @@ public void run() { private float minY; private float maxY; private boolean canZoom = true; - private boolean changingPage = false; - private boolean zooming = false; - private boolean moving = false; - private boolean doubleTap = false; - private boolean invalidCoords = false; + private boolean changingPage; + private boolean zooming; + private boolean moving; + private boolean doubleTap; + private boolean invalidCoords; private boolean canDragDown = true; - private boolean zoomAnimation = false; - private boolean discardTap = false; - private int switchImageAfterAnimation = 0; - private VelocityTracker velocityTracker = null; - private Scroller scroller = null; + private boolean zoomAnimation; + private boolean discardTap; + private int switchImageAfterAnimation; + private VelocityTracker velocityTracker; + private Scroller scroller; + + private boolean bottomTouchEnabled = true; private ArrayList imagesArrTemp = new ArrayList<>(); private HashMap[] imagesByIdsTemp = new HashMap[] {new HashMap<>(), new HashMap<>()}; @@ -299,18 +345,17 @@ public void run() { private final static int gallery_menu_showall = 2; private final static int gallery_menu_send = 3; private final static int gallery_menu_delete = 6; - private final static int gallery_menu_caption_done = 9; private final static int gallery_menu_share = 10; private final static int gallery_menu_openin = 11; - private final static int gallery_menu_mute = 12; private final static int gallery_menu_masks = 13; - private static DecelerateInterpolator decelerateInterpolator = null; - private static Paint progressPaint = null; + private static DecelerateInterpolator decelerateInterpolator; + private static Paint progressPaint; private class BackgroundDrawable extends ColorDrawable { private Runnable drawRunnable; + private boolean allowDrawContent; public BackgroundDrawable(int color) { super(color); @@ -319,7 +364,24 @@ public BackgroundDrawable(int color) { @Override public void setAlpha(int alpha) { if (parentActivity instanceof LaunchActivity) { - ((LaunchActivity) parentActivity).drawerLayoutContainer.setAllowDrawContent(!isVisible || alpha != 255); + allowDrawContent = !isVisible || alpha != 255; + ((LaunchActivity) parentActivity).drawerLayoutContainer.setAllowDrawContent(allowDrawContent); + if (parentAlert != null) { + if (!allowDrawContent) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + if (parentAlert != null) { + parentAlert.setAllowDrawContent(allowDrawContent); + } + } + }, 50); + } else { + if (parentAlert != null) { + parentAlert.setAllowDrawContent(allowDrawContent); + } + } + } } super.setAlpha(alpha); } @@ -336,7 +398,7 @@ public void draw(Canvas canvas) { } } - private class RadialProgressView { + private class PhotoProgressView { private long lastUpdateTime = 0; private float radOffset = 0; @@ -353,7 +415,7 @@ private class RadialProgressView { private float alpha = 1.0f; private float scale = 1.0f; - public RadialProgressView(Context context, View parentView) { + public PhotoProgressView(Context context, View parentView) { if (decelerateInterpolator == null) { decelerateInterpolator = new DecelerateInterpolator(1.5f); progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -519,7 +581,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { } @@ -559,7 +621,7 @@ public interface PhotoViewerProvider { boolean cancelButtonPressed(); - void sendButtonPressed(int index); + void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo); int getSelectedCount(); @@ -596,7 +658,10 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (child.getVisibility() == GONE || child == captionEditText) { continue; } - if (captionEditText.isPopupView(child)) { + if (child == aspectRatioFrameLayout) { + int heightSpec = MeasureSpec.makeMeasureSpec(AndroidUtilities.displaySize.y + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0), MeasureSpec.EXACTLY); + measureChildWithMargins(child, widthMeasureSpec, 0, heightSpec, 0); + } else if (captionEditText.isPopupView(child)) { if (AndroidUtilities.isInMultiwindow) { if (AndroidUtilities.isTablet()) { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(320), heightSize - inputFieldHeight - AndroidUtilities.statusBarHeight), MeasureSpec.EXACTLY)); @@ -643,7 +708,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { childLeft = (r - l - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: - childLeft = r - width - lp.rightMargin; + childLeft = (r - l - width) - lp.rightMargin; break; case Gravity.LEFT: default: @@ -665,19 +730,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { } if (child == mentionListView) { - if (!captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { - childTop += AndroidUtilities.dp(400); - } else { - childTop -= captionEditText.getMeasuredHeight(); - } - } else if (child == captionEditText) { - if (!captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { - childTop += AndroidUtilities.dp(400); - } - } else if (child == pickerView || child == captionTextViewNew || child == captionTextViewOld) { - if (captionEditText.isPopupShowing() || captionEditText.isKeyboardVisible()) { - childTop += AndroidUtilities.dp(400); - } + childTop -= captionEditText.getMeasuredHeight(); } else if (captionEditText.isPopupView(child)) { if (AndroidUtilities.isInMultiwindow) { childTop = captionEditText.getTop() - child.getMeasuredHeight() + AndroidUtilities.dp(1); @@ -693,7 +746,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { @Override protected void onDraw(Canvas canvas) { - getInstance().onDraw(canvas); + PhotoViewer.this.onDraw(canvas); if (Build.VERSION.SDK_INT >= 21 && AndroidUtilities.statusBarHeight != 0) { canvas.drawRect(0, 0, getMeasuredWidth(), AndroidUtilities.statusBarHeight, paint); @@ -702,10 +755,24 @@ protected void onDraw(Canvas canvas) { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + if (child == mentionListView || child == captionEditText) { + if (!captionEditText.isPopupShowing() && captionEditText.getEmojiPadding() == 0 && (AndroidUtilities.usingHardwareInput && getTag() == null || getKeyboardHeight() == 0)) { + return false; + } + } else if (child == pickerView || child == captionTextViewNew || child == captionTextViewOld || child == checkImageView || child == videoTimelineViewContainer || muteItem.getVisibility() == VISIBLE && child == bottomLayout) { + int paddingBottom = getKeyboardHeight() <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow ? captionEditText.getEmojiPadding() : 0; + if (captionEditText.isPopupShowing() || AndroidUtilities.usingHardwareInput && getTag() != null || getKeyboardHeight() > 0 || paddingBottom != 0) { + bottomTouchEnabled = false; + return false; + } else { + bottomTouchEnabled = true; + } + } return child != aspectRatioFrameLayout && super.drawChild(canvas, child, drawingTime); } } + @SuppressLint("StaticFieldLeak") private static volatile PhotoViewer Instance = null; public static PhotoViewer getInstance() { @@ -732,7 +799,7 @@ public void didReceivedNotification(int id, Object... args) { String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { - radialProgressViews[a].setProgress(1.0f, true); + photoProgressViews[a].setProgress(1.0f, true); checkProgress(a, true); break; } @@ -741,7 +808,7 @@ public void didReceivedNotification(int id, Object... args) { String location = (String) args[0]; for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { - radialProgressViews[a].setProgress(1.0f, true); + photoProgressViews[a].setProgress(1.0f, true); checkProgress(a, true); if (Build.VERSION.SDK_INT >= 16 && a == 0 && (currentMessageObject != null && currentMessageObject.isVideo() || currentBotInlineResult != null && (currentBotInlineResult.type.equals("video") || MessageObject.isVideoDocument(currentBotInlineResult.document)))) { onActionClick(false); @@ -754,7 +821,7 @@ public void didReceivedNotification(int id, Object... args) { for (int a = 0; a < 3; a++) { if (currentFileNames[a] != null && currentFileNames[a].equals(location)) { Float progress = (Float) args[1]; - radialProgressViews[a].setProgress(progress, true); + photoProgressViews[a].setProgress(progress, true); } } } else if (id == NotificationCenter.dialogPhotosLoaded) { @@ -957,6 +1024,30 @@ public void didReceivedNotification(int id, Object... args) { if (captionTextView != null) { captionTextView.invalidate(); } + } else if (id == NotificationCenter.FilePreparingFailed) { + MessageObject messageObject = (MessageObject) args[0]; + if (loadInitialVideo) { + loadInitialVideo = false; + progressView.setVisibility(View.INVISIBLE); + preparePlayer(currentPlayingVideoFile, false, false); + } else if (tryStartRequestPreviewOnFinish) { + releasePlayer(); + tryStartRequestPreviewOnFinish = !MediaController.getInstance().scheduleVideoConvert(videoPreviewMessageObject, true); + } else if (messageObject == videoPreviewMessageObject) { + requestingPreview = false; + progressView.setVisibility(View.INVISIBLE); + } + } else if (id == NotificationCenter.FileNewChunkAvailable) { + MessageObject messageObject = (MessageObject) args[0]; + if (messageObject == videoPreviewMessageObject) { + String finalPath = (String) args[1]; + long finalSize = (Long) args[2]; + if (finalSize != 0) { + requestingPreview = false; + progressView.setVisibility(View.INVISIBLE); + preparePlayer(new File(finalPath), false, true); + } + } } } @@ -974,7 +1065,15 @@ private void onSharePressed() { AndroidUtilities.openUrl(parentActivity, currentMessageObject.messageOwner.media.webpage.url); return; }*/ - f = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + if (!TextUtils.isEmpty(currentMessageObject.messageOwner.attachPath)) { + f = new File(currentMessageObject.messageOwner.attachPath); + if (!f.exists()) { + f = null; + } + } + if (f == null) { + f = FileLoader.getPathToMessage(currentMessageObject.messageOwner); + } } else if (currentFileLocation != null) { f = FileLoader.getPathToAttach(currentFileLocation, avatarsDialogId != 0); } @@ -984,7 +1083,11 @@ private void onSharePressed() { if (isVideo) { intent.setType("video/mp4"); } else { - intent.setType("image/jpeg"); + if (currentMessageObject != null) { + intent.setType(currentMessageObject.getMimeType()); + } else { + intent.setType("image/jpeg"); + } } intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(f)); @@ -997,7 +1100,7 @@ private void onSharePressed() { showAlertDialog(builder); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1013,6 +1116,10 @@ private void setScaleToFill() { updateMinMax(scale); } + public void setParentAlert(ChatAttachAlert alert) { + parentAlert = alert; + } + public void setParentActivity(final Activity activity) { if (parentActivity == activity) { return; @@ -1074,6 +1181,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { } } setMeasuredDimension(widthSize, heightSize); + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + widthSize -= ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); + } ViewGroup.LayoutParams layoutParams = animatingImageView.getLayoutParams(); animatingImageView.measure(MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.AT_MOST)); containerView.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)); @@ -1082,8 +1192,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @SuppressWarnings("DrawAllocation") @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - animatingImageView.layout(0, 0, animatingImageView.getMeasuredWidth(), animatingImageView.getMeasuredHeight()); - containerView.layout(0, 0, containerView.getMeasuredWidth(), containerView.getMeasuredHeight()); + int x = 0; + if (Build.VERSION.SDK_INT >= 21 && lastInsets != null) { + x += ((WindowInsets) lastInsets).getSystemWindowInsetLeft(); + } + animatingImageView.layout(x, 0, x + animatingImageView.getMeasuredWidth(), animatingImageView.getMeasuredHeight()); + containerView.layout(x, 0, x + containerView.getMeasuredWidth(), containerView.getMeasuredHeight()); wasLayout = true; if (changed) { if (!dontResetZoomOnFirstLayout) { @@ -1146,7 +1260,7 @@ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback try { return ((ViewGroup) view).startActionModeForChild(originalView, callback, type); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -1165,17 +1279,20 @@ public ActionMode startActionModeForChild(View originalView, ActionMode.Callback containerView.setFocusable(false); windowView.addView(containerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); if (Build.VERSION.SDK_INT >= 21) { - //containerView.setFitsSystemWindows(true); + containerView.setFitsSystemWindows(true); containerView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() { @SuppressLint("NewApi") @Override public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + WindowInsets oldInsets = (WindowInsets) lastInsets; lastInsets = insets; - windowView.requestLayout(); + if (oldInsets == null || !oldInsets.toString().equals(insets.toString())) { + windowView.requestLayout(); + } return insets.consumeSystemWindowInsets(); } }); - //containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);*/ + containerView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); } windowLayoutParams = new WindowManager.LayoutParams(); @@ -1194,9 +1311,11 @@ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { } actionBar = new ActionBar(activity); + actionBar.setTitleColor(0xffffffff); + actionBar.setSubtitleColor(0xffffffff); actionBar.setBackgroundColor(Theme.ACTION_BAR_PHOTO_VIEWER_COLOR); actionBar.setOccupyStatusBar(Build.VERSION.SDK_INT >= 21); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); containerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); @@ -1292,6 +1411,49 @@ public void onItemClick(int id) { builder.setMessage(LocaleController.formatString("AreYouSureDeletePhoto", R.string.AreYouSureDeletePhoto)); } builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + + final boolean deleteForAll[] = new boolean[1]; + if (currentMessageObject != null) { + int lower_id = (int) currentMessageObject.getDialogId(); + if (lower_id != 0) { + TLRPC.Chat currentChat; + TLRPC.User currentUser; + if (lower_id > 0) { + currentUser = MessagesController.getInstance().getUser(lower_id); + currentChat = null; + } else { + currentUser = null; + currentChat = MessagesController.getInstance().getChat(-lower_id); + } + if (currentUser != null || !ChatObject.isChannel(currentChat)) { + boolean hasOutgoing = false; + int currentDate = ConnectionsManager.getInstance().getCurrentTime(); + if (currentUser != null && currentUser.id != UserConfig.getClientUserId() || currentChat != null) { + if ((currentMessageObject.messageOwner.action == null || currentMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty) && currentMessageObject.isOut() && (currentDate - currentMessageObject.messageOwner.date) <= 2 * 24 * 60 * 60) { + FrameLayout frameLayout = new FrameLayout(parentActivity); + CheckBoxCell cell = new CheckBoxCell(parentActivity, true); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + if (currentChat != null) { + cell.setText(LocaleController.getString("DeleteForAll", R.string.DeleteForAll), "", false, false); + } else { + cell.setText(LocaleController.formatString("DeleteForUser", R.string.DeleteForUser, UserObject.getFirstName(currentUser)), "", false, false); + } + cell.setPadding(LocaleController.isRTL ? AndroidUtilities.dp(16) : AndroidUtilities.dp(8), 0, LocaleController.isRTL ? AndroidUtilities.dp(8) : AndroidUtilities.dp(16), 0); + frameLayout.addView(cell, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.TOP | Gravity.LEFT, 0, 0, 0, 0)); + cell.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CheckBoxCell cell = (CheckBoxCell) v; + deleteForAll[0] = !deleteForAll[0]; + cell.setChecked(deleteForAll[0], true); + } + }); + builder.setView(frameLayout); + } + } + } + } + } builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -1313,7 +1475,7 @@ public void onClick(DialogInterface dialogInterface, int i) { encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (obj.getDialogId() >> 32)); } - MessagesController.getInstance().deleteMessages(arr, random_ids, encryptedChat, obj.messageOwner.to_id.channel_id); + MessagesController.getInstance().deleteMessages(arr, random_ids, encryptedChat, obj.messageOwner.to_id.channel_id, deleteForAll[0]); } } else if (!avatarsArr.isEmpty()) { if (currentIndex < 0 || currentIndex >= avatarsArr.size()) { @@ -1365,8 +1527,6 @@ public void onClick(DialogInterface dialogInterface, int i) { }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showAlertDialog(builder); - } else if (id == gallery_menu_caption_done) { - closeCaptionEnter(true); } else if (id == gallery_menu_share) { onSharePressed(); } else if (id == gallery_menu_openin) { @@ -1374,19 +1534,7 @@ public void onClick(DialogInterface dialogInterface, int i) { AndroidUtilities.openForView(currentMessageObject, parentActivity); closePhoto(false, false); } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else if (id == gallery_menu_mute) { - muteVideo = !muteVideo; - if (videoPlayer != null) { - videoPlayer.setMute(muteVideo); - } - if (muteVideo) { - actionBar.setTitle(LocaleController.getString("AttachGif", R.string.AttachGif)); - muteItem.setIcon(R.drawable.volume_off); - } else { - actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); - muteItem.setIcon(R.drawable.volume_on); + FileLog.e(e); } } else if (id == gallery_menu_masks) { if (parentActivity == null || currentMessageObject == null || currentMessageObject.messageOwner.media == null || currentMessageObject.messageOwner.media.photo == null) { @@ -1417,22 +1565,24 @@ public boolean canOpenMenu() { ActionBarMenu menu = actionBar.createMenu(); masksItem = menu.addItem(gallery_menu_masks, R.drawable.ic_masks_msk1); - muteItem = menu.addItem(gallery_menu_mute, R.drawable.volume_on); menuItem = menu.addItem(0, R.drawable.ic_ab_other); - menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp), 0); - menuItem.addSubItem(gallery_menu_showall, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia), 0); - menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile), 0); - menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery), 0); - menuItem.addSubItem(gallery_menu_delete, LocaleController.getString("Delete", R.string.Delete), 0); - - captionDoneItem = menu.addItemWithWidth(gallery_menu_caption_done, R.drawable.ic_done, AndroidUtilities.dp(56)); + menuItem.addSubItem(gallery_menu_openin, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); + menuItem.addSubItem(gallery_menu_showall, LocaleController.getString("ShowAllMedia", R.string.ShowAllMedia)); + menuItem.addSubItem(gallery_menu_share, LocaleController.getString("ShareFile", R.string.ShareFile)); + menuItem.addSubItem(gallery_menu_save, LocaleController.getString("SaveToGallery", R.string.SaveToGallery)); + menuItem.addSubItem(gallery_menu_delete, LocaleController.getString("Delete", R.string.Delete)); bottomLayout = new FrameLayout(actvityContext); bottomLayout.setBackgroundColor(0x7f000000); containerView.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); - captionTextViewOld = new TextView(actvityContext); + captionTextViewOld = new TextView(actvityContext) { + @Override + public boolean onTouchEvent(MotionEvent event) { + return bottomTouchEnabled && super.onTouchEvent(event); + } + }; captionTextViewOld.setMaxLines(10); captionTextViewOld.setBackgroundColor(0x7f000000); captionTextViewOld.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); @@ -1451,7 +1601,12 @@ public void onClick(View v) { } }); - captionTextView = captionTextViewNew = new TextView(actvityContext); + captionTextView = captionTextViewNew = new TextView(actvityContext) { + @Override + public boolean onTouchEvent(MotionEvent event) { + return bottomTouchEnabled && super.onTouchEvent(event); + } + }; captionTextViewNew.setMaxLines(10); captionTextViewNew.setBackgroundColor(0x7f000000); captionTextViewNew.setPadding(AndroidUtilities.dp(20), AndroidUtilities.dp(8), AndroidUtilities.dp(20), AndroidUtilities.dp(8)); @@ -1470,17 +1625,17 @@ public void onClick(View v) { } }); - radialProgressViews[0] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[0].setBackgroundState(0, false); - radialProgressViews[1] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[1].setBackgroundState(0, false); - radialProgressViews[2] = new RadialProgressView(containerView.getContext(), containerView); - radialProgressViews[2].setBackgroundState(0, false); + photoProgressViews[0] = new PhotoProgressView(containerView.getContext(), containerView); + photoProgressViews[0].setBackgroundState(0, false); + photoProgressViews[1] = new PhotoProgressView(containerView.getContext(), containerView); + photoProgressViews[1].setBackgroundState(0, false); + photoProgressViews[2] = new PhotoProgressView(containerView.getContext(), containerView); + photoProgressViews[2].setBackgroundState(0, false); shareButton = new ImageView(containerView.getContext()); shareButton.setImageResource(R.drawable.share); shareButton.setScaleType(ImageView.ScaleType.CENTER); - shareButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + shareButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); bottomLayout.addView(shareButton, LayoutHelper.createFrame(50, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); shareButton.setOnClickListener(new View.OnClickListener() { @Override @@ -1516,7 +1671,10 @@ public void onClick(View v) { @Override public void onSeekBarDrag(float progress) { if (videoPlayer != null) { - videoPlayer.getPlayerControl().seekTo((int) (progress * videoPlayer.getDuration())); + if (!inPreview && videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() == View.VISIBLE) { + progress = videoTimelineView.getLeftProgress() + (videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()) * progress; + } + videoPlayer.seekTo((int) (progress * videoPlayer.getDuration())); } } }); @@ -1541,7 +1699,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { long duration; if (videoPlayer != null) { duration = videoPlayer.getDuration(); - if (duration == ExoPlayer.UNKNOWN_TIME) { + if (duration == C.TIME_UNSET) { duration = 0; } } else { @@ -1557,8 +1715,17 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto super.onLayout(changed, left, top, right, bottom); float progress = 0; if (videoPlayer != null) { - PlayerControl playerControl = videoPlayer.getPlayerControl(); - progress = playerControl.getCurrentPosition() / (float) playerControl.getDuration(); + progress = videoPlayer.getCurrentPosition() / (float) videoPlayer.getDuration(); + if (!inPreview && videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() == View.VISIBLE) { + progress -= videoTimelineView.getLeftProgress(); + if (progress < 0) { + progress = 0; + } + progress /= (videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()); + if (progress > 1) { + progress = 1; + } + } } videoPlayerSeekbar.setProgress(progress); } @@ -1580,12 +1747,16 @@ protected void onDraw(Canvas canvas) { videoPlayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (videoPlayer != null) { - if (isPlaying) { - videoPlayer.getPlayerControl().pause(); - } else { - videoPlayer.getPlayerControl().start(); + if (videoPlayer == null) { + return; + } + if (isPlaying) { + videoPlayer.pause(); + } else { + if (Math.abs(videoPlayerSeekbar.getProgress() - 1.0f) < 0.01f || videoPlayer.getCurrentPosition() == videoPlayer.getDuration()) { + videoPlayer.seekTo(0); } + videoPlayer.play(); } } }); @@ -1595,9 +1766,93 @@ public void onClick(View v) { videoPlayerTime.setGravity(Gravity.CENTER_VERTICAL); videoPlayerTime.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); videoPlayerControlFrameLayout.addView(videoPlayerTime, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 8, 0)); + + videoTimelineViewContainer = new FrameLayout(parentActivity); + videoTimelineViewContainer.setBackgroundColor(0x7f000000); + containerView.addView(videoTimelineViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 52, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 96)); + + videoTimelineView = new VideoTimelineView(parentActivity); + videoTimelineView.setDelegate(new VideoTimelineView.VideoTimelineViewDelegate() { + @Override + public void onLeftProgressChanged(float progress) { + if (videoPlayer == null) { + return; + } + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + } + videoPlayer.seekTo((int) (videoDuration * progress)); + videoPlayerSeekbar.setProgress(0); + updateVideoInfo(); + } + + @Override + public void onRifhtProgressChanged(float progress) { + if (videoPlayer == null) { + return; + } + if (videoPlayer.isPlaying()) { + videoPlayer.pause(); + } + videoPlayer.seekTo((int) (videoDuration * progress)); + videoPlayerSeekbar.setProgress(0); + updateVideoInfo(); + } + }); + videoTimelineViewContainer.addView(videoTimelineView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 44, Gravity.LEFT | Gravity.TOP, 0, 8, 0, 0)); + + progressView = new RadialProgressView(parentActivity); + progressView.setProgressColor(0xffffffff); + progressView.setBackgroundResource(R.drawable.circle_big); + progressView.setVisibility(View.INVISIBLE); + containerView.addView(progressView, LayoutHelper.createFrame(54, 54, Gravity.CENTER, 0, 0, 0, 60)); + + qualityPicker = new PickerBottomLayoutViewer(parentActivity); + qualityPicker.setBackgroundColor(0x7f000000); + qualityPicker.updateSelectedCount(0, false); + qualityPicker.setTranslationY(AndroidUtilities.dp(120)); + qualityPicker.doneButton.setText(LocaleController.getString("Done", R.string.Done).toUpperCase()); + containerView.addView(qualityPicker, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); + qualityPicker.cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + selectedCompression = previousCompression; + didChangedCompressionLevel(false); + showQualityView(false); + requestVideoPreview(2); + } + }); + qualityPicker.doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showQualityView(false); + requestVideoPreview(2); + } + }); + + qualityChooseView = new QualityChooseView(parentActivity); + qualityChooseView.setTranslationY(AndroidUtilities.dp(120)); + qualityChooseView.setVisibility(View.INVISIBLE); + qualityChooseView.setBackgroundColor(0x7f000000); + containerView.addView(qualityChooseView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 70, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 96)); } - pickerView = new PickerBottomLayoutViewer(actvityContext); + pickerView = new PickerBottomLayoutViewer(actvityContext) { + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + return bottomTouchEnabled && super.dispatchTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return bottomTouchEnabled && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return bottomTouchEnabled && super.onTouchEvent(event); + } + }; pickerView.setBackgroundColor(0x7f000000); containerView.addView(pickerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); pickerView.cancelButton.setOnClickListener(new View.OnClickListener() { @@ -1613,8 +1868,37 @@ public void onClick(View view) { pickerView.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (placeProvider != null) { - placeProvider.sendButtonPressed(currentIndex); + if (placeProvider != null && !doneButtonPressed) { + + VideoEditedInfo videoEditedInfo = null; + if (captionItem.getVisibility() == View.VISIBLE) { + videoEditedInfo = new VideoEditedInfo(); + videoEditedInfo.startTime = startTime; + videoEditedInfo.endTime = endTime; + videoEditedInfo.rotationValue = rotationValue; + videoEditedInfo.originalWidth = originalWidth; + videoEditedInfo.originalHeight = originalHeight; + videoEditedInfo.bitrate = bitrate; + videoEditedInfo.originalPath = currentPlayingVideoFile.getAbsolutePath(); + videoEditedInfo.estimatedSize = estimatedSize; + videoEditedInfo.estimatedDuration = estimatedDuration; + + if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && selectedCompression == compressionsCount - 1) { + videoEditedInfo.resultWidth = originalWidth; + videoEditedInfo.resultHeight = originalHeight; + videoEditedInfo.bitrate = muteVideo ? -1 : originalBitrate; + } else { + if (muteVideo) { + selectedCompression = 1; + updateWidthHeightBitrateForCompression(); + } + videoEditedInfo.resultWidth = resultWidth; + videoEditedInfo.resultHeight = resultHeight; + videoEditedInfo.bitrate = muteVideo ? -1 : bitrate; + } + } + placeProvider.sendButtonPressed(currentIndex, videoEditedInfo); + doneButtonPressed = true; closePhoto(false, false); } } @@ -1627,7 +1911,7 @@ public void onClick(View view) { tuneItem = new ImageView(parentActivity); tuneItem.setScaleType(ImageView.ScaleType.CENTER); tuneItem.setImageResource(R.drawable.photo_tools); - tuneItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + tuneItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); itemsLayout.addView(tuneItem, LayoutHelper.createLinear(56, 48)); tuneItem.setOnClickListener(new View.OnClickListener() { @Override @@ -1639,7 +1923,7 @@ public void onClick(View v) { paintItem = new ImageView(parentActivity); paintItem.setScaleType(ImageView.ScaleType.CENTER); paintItem.setImageResource(R.drawable.photo_paint); - paintItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + paintItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); itemsLayout.addView(paintItem, LayoutHelper.createLinear(56, 48)); paintItem.setOnClickListener(new View.OnClickListener() { @Override @@ -1651,7 +1935,7 @@ public void onClick(View v) { cropItem = new ImageView(parentActivity); cropItem.setScaleType(ImageView.ScaleType.CENTER); cropItem.setImageResource(R.drawable.photo_crop); - cropItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + cropItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); itemsLayout.addView(cropItem, LayoutHelper.createLinear(56, 48)); cropItem.setOnClickListener(new View.OnClickListener() { @Override @@ -1660,6 +1944,41 @@ public void onClick(View v) { } }); + captionItem = new ImageView(parentActivity); + captionItem.setScaleType(ImageView.ScaleType.CENTER); + captionItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + itemsLayout.addView(captionItem, LayoutHelper.createLinear(56, 48)); + captionItem.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + openCaptionEnter(); + } + }); + + compressItem = new ImageView(parentActivity); + compressItem.setScaleType(ImageView.ScaleType.CENTER); + compressItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + itemsLayout.addView(compressItem, LayoutHelper.createLinear(56, 48)); + compressItem.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showQualityView(true); + requestVideoPreview(1); + } + }); + + muteItem = new ImageView(parentActivity); + muteItem.setScaleType(ImageView.ScaleType.CENTER); + muteItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + itemsLayout.addView(muteItem, LayoutHelper.createLinear(56, 48)); + muteItem.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + muteVideo = !muteVideo; + updateMuteButton(); + } + }); + editorDoneLayout = new PickerBottomLayoutViewer(actvityContext); editorDoneLayout.setBackgroundColor(0x7f000000); editorDoneLayout.updateSelectedCount(0, false); @@ -1677,28 +1996,28 @@ public void onClick(View view) { editorDoneLayout.doneButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - if (currentEditMode == 1) { - photoCropView.cancelAnimationRunnable(); - if (imageMoveAnimation != null) { - return; - } + if (currentEditMode == 1 && !photoCropView.isReady()) { + return; } applyCurrentEditMode(); switchToEditMode(0); } }); - ImageView rotateButton = new ImageView(actvityContext); - rotateButton.setScaleType(ImageView.ScaleType.CENTER); - rotateButton.setImageResource(R.drawable.tool_rotate); - rotateButton.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); - editorDoneLayout.addView(rotateButton, LayoutHelper.createFrame(48, 48, Gravity.CENTER)); - rotateButton.setOnClickListener(new View.OnClickListener() { + resetButton = new TextView(actvityContext); + resetButton.setVisibility(View.GONE); + resetButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + resetButton.setTextColor(0xffffffff); + resetButton.setGravity(Gravity.CENTER); + resetButton.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, 0)); + resetButton.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); + resetButton.setText(LocaleController.getString("Reset", R.string.CropReset).toUpperCase()); + resetButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + editorDoneLayout.addView(resetButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.CENTER)); + resetButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - centerImage.setOrientation(centerImage.getOrientation() - 90, false); - photoCropView.setOrientation(centerImage.getOrientation()); - containerView.invalidate(); + photoCropView.reset(); } }); @@ -1708,7 +2027,13 @@ public void onClick(View v) { ImageReceiver.ImageReceiverDelegate imageReceiverDelegate = new ImageReceiver.ImageReceiverDelegate() { @Override public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) { - if (imageReceiver == centerImage && set && placeProvider != null && placeProvider.scaleToFill()) { + if (imageReceiver == centerImage && set && !thumb && currentEditMode == 1 && photoCropView != null) { + Bitmap bitmap = imageReceiver.getBitmap(); + if (bitmap != null) { + photoCropView.setBitmap(bitmap, imageReceiver.getOrientation(), sendPhotoType != 1); + } + } + if (imageReceiver == centerImage && set && placeProvider != null && placeProvider.scaleToFill() && !ignoreDidSetImage) { if (!wasLayout) { dontResetZoomOnFirstLayout = true; } else { @@ -1734,11 +2059,16 @@ public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb) WindowManager manager = (WindowManager) ApplicationLoader.applicationContext.getSystemService(Activity.WINDOW_SERVICE); int rotation = manager.getDefaultDisplay().getRotation(); - checkImageView = new CheckBox(containerView.getContext(), R.drawable.selectphoto_large); + checkImageView = new CheckBox(containerView.getContext(), R.drawable.selectphoto_large) { + @Override + public boolean onTouchEvent(MotionEvent event) { + return bottomTouchEnabled && super.onTouchEvent(event); + } + }; checkImageView.setDrawBackground(true); checkImageView.setSize(45); checkImageView.setCheckOffset(AndroidUtilities.dp(1)); - checkImageView.setColor(0xff3ccaef); + checkImageView.setColor(0xff3ccaef, 0xffffffff); checkImageView.setVisibility(View.GONE); containerView.addView(checkImageView, LayoutHelper.createFrame(45, 45, Gravity.RIGHT | Gravity.TOP, 0, rotation == Surface.ROTATION_270 || rotation == Surface.ROTATION_90 ? 58 : 68, 10, 0)); if (Build.VERSION.SDK_INT >= 21) { @@ -1755,7 +2085,32 @@ public void onClick(View v) { } }); - captionEditText = new PhotoViewerCaptionEnterView(actvityContext, containerView, windowView); + captionEditText = new PhotoViewerCaptionEnterView(actvityContext, containerView, windowView) { + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + try { + return !bottomTouchEnabled && super.dispatchTouchEvent(ev); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + try { + return !bottomTouchEnabled && super.onInterceptTouchEvent(ev); + } catch (Exception e) { + FileLog.e(e); + } + return false; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return !bottomTouchEnabled && super.onTouchEvent(event); + } + }; captionEditText.setDelegate(new PhotoViewerCaptionEnterView.PhotoViewerCaptionEnterViewDelegate() { @Override public void onCaptionEnter() { @@ -1785,9 +2140,24 @@ public void onWindowSizeChanged(int size) { } } }); - containerView.addView(captionEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, -400)); + containerView.addView(captionEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); + + mentionListView = new RecyclerListView(actvityContext) { + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + return !bottomTouchEnabled && super.dispatchTouchEvent(ev); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return !bottomTouchEnabled && super.onInterceptTouchEvent(ev); + } - mentionListView = new RecyclerListView(actvityContext); + @Override + public boolean onTouchEvent(MotionEvent event) { + return !bottomTouchEnabled && super.onTouchEvent(event); + } + }; mentionListView.setTag(5); mentionLayoutManager = new LinearLayoutManager(actvityContext) { @Override @@ -1800,7 +2170,7 @@ public boolean supportsPredictiveItemAnimations() { mentionListView.setBackgroundColor(0x7f000000); mentionListView.setVisibility(View.GONE); mentionListView.setClipToPadding(true); - mentionListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER); + mentionListView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); containerView.addView(mentionListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 110, Gravity.LEFT | Gravity.BOTTOM)); mentionListView.setAdapter(mentionsAdapter = new MentionsAdapter(actvityContext, true, 0, new MentionsAdapter.MentionsAdapterDelegate() { @@ -1830,7 +2200,7 @@ public void needChangePanelVisibility(boolean show) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionListView, "alpha", 0.0f, 1.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -1858,7 +2228,7 @@ public void onAnimationEnd(Animator animation) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionListView, "alpha", 0.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -1928,25 +2298,55 @@ public void onClick(DialogInterface dialogInterface, int i) { } private void openCaptionEnter() { - if (imageMoveAnimation != null || changeModeAnimation != null) { + if (imageMoveAnimation != null || changeModeAnimation != null || currentEditMode != 0) { return; } - paintItem.setVisibility(View.GONE); - cropItem.setVisibility(View.GONE); - tuneItem.setVisibility(View.GONE); - checkImageView.setVisibility(View.GONE); - captionDoneItem.setVisibility(View.VISIBLE); - pickerView.setVisibility(View.GONE); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) captionEditText.getLayoutParams(); - layoutParams.bottomMargin = 0; - captionEditText.setLayoutParams(layoutParams); - layoutParams = (FrameLayout.LayoutParams) mentionListView.getLayoutParams(); - layoutParams.bottomMargin = 0; - mentionListView.setLayoutParams(layoutParams); - captionTextView.setVisibility(View.INVISIBLE); + captionEditText.setTag(1); captionEditText.openKeyboard(); lastTitle = actionBar.getTitle(); - actionBar.setTitle(LocaleController.getString("PhotoCaption", R.string.PhotoCaption)); + if (captionItem.getVisibility() == View.VISIBLE) { + actionBar.setTitle(muteVideo ? LocaleController.getString("GifCaption", R.string.GifCaption) : LocaleController.getString("VideoCaption", R.string.VideoCaption)); + actionBar.setSubtitle(null); + } else { + actionBar.setTitle(LocaleController.getString("PhotoCaption", R.string.PhotoCaption)); + } + } + + private void closeCaptionEnter(boolean apply) { + if (currentIndex < 0 || currentIndex >= imagesArrLocals.size()) { + return; + } + Object object = imagesArrLocals.get(currentIndex); + if (apply) { + if (object instanceof MediaController.PhotoEntry) { + ((MediaController.PhotoEntry) object).caption = captionEditText.getFieldCharSequence(); + } else if (object instanceof MediaController.SearchImage) { + ((MediaController.SearchImage) object).caption = captionEditText.getFieldCharSequence(); + } + + if (captionEditText.getFieldCharSequence().length() != 0 && !placeProvider.isPhotoChecked(currentIndex)) { + placeProvider.setPhotoChecked(currentIndex); + checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); + updateSelectedCount(); + } + } + captionEditText.setTag(null); + if (lastTitle != null) { + actionBar.setTitle(lastTitle); + lastTitle = null; + } + if (captionItem.getVisibility() == View.VISIBLE) { + actionBar.setTitle(muteVideo ? LocaleController.getString("AttachGif", R.string.AttachGif) : LocaleController.getString("AttachVideo", R.string.AttachVideo)); + actionBar.setSubtitle(muteVideo ? null : currentSubtitle); + captionItem.setImageResource(captionEditText.getFieldCharSequence().length() == 0 ? R.drawable.photo_text : R.drawable.photo_text2); + } + + updateCaptionTextForCurrentPhoto(object); + setCurrentCaption(captionEditText.getFieldCharSequence()); + if (captionEditText.isPopupShowing()) { + captionEditText.hidePopup(); + } + captionEditText.closeKeyboard(); } private void updateVideoPlayerTime() { @@ -1954,21 +2354,18 @@ private void updateVideoPlayerTime() { if (videoPlayer == null) { newText = "00:00 / 00:00"; } else { - long current = videoPlayer.getCurrentPosition() / 1000; + long current = videoPlayer.getCurrentPosition(); long total = videoPlayer.getDuration(); - if (muteItemAvailable) { - if (total >= 30000) { - if (muteItem.getVisibility() == View.VISIBLE) { - muteItem.setVisibility(View.GONE); - } - } else { - if (muteItem.getVisibility() != View.VISIBLE) { - muteItem.setVisibility(View.VISIBLE); + if (total != C.TIME_UNSET && current != C.TIME_UNSET) { + if (!inPreview && videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() == View.VISIBLE) { + total *= (videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()); + current -= videoTimelineView.getLeftProgress() * total; + if (current > total) { + current = total; } } - } - total /= 1000; - if (total != ExoPlayer.UNKNOWN_TIME && current != ExoPlayer.UNKNOWN_TIME) { + current /= 1000; + total /= 1000; newText = String.format("%02d:%02d / %02d:%02d", current / 60, current % 60, total / 60, total % 60); } else { newText = "00:00 / 00:00"; @@ -1980,10 +2377,14 @@ private void updateVideoPlayerTime() { } @SuppressLint("NewApi") - private void preparePlayer(File file, boolean playWhenReady) { + private void preparePlayer(File file, boolean playWhenReady, boolean preview) { if (parentActivity == null) { return; } + if (!preview) { + currentPlayingVideoFile = file; + } + inPreview = preview; releasePlayer(); if (videoTextureView == null) { aspectRatioFrameLayout = new AspectRatioFrameLayout(parentActivity); @@ -1992,35 +2393,6 @@ private void preparePlayer(File file, boolean playWhenReady) { videoTextureView = new TextureView(parentActivity); videoTextureView.setOpaque(false); - videoTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { - @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - if (videoPlayer != null) { - videoPlayer.setSurface(new Surface(videoTextureView.getSurfaceTexture())); - } - } - - @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - - } - - @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - if (videoPlayer != null) { - videoPlayer.blockingClearSurface(); - } - return true; - } - - @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - if (!textureUploaded) { - textureUploaded = true; - containerView.invalidate(); - } - } - }); aspectRatioFrameLayout.addView(videoTextureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); } textureUploaded = false; @@ -2028,30 +2400,31 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { videoTextureView.setAlpha(videoCrossfadeAlpha = 0.0f); videoPlayButton.setImageResource(R.drawable.inline_video_play); if (videoPlayer == null) { - videoPlayer = new VideoPlayer(new VideoPlayer.ExtractorRendererBuilder(parentActivity, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", Uri.fromFile(file))); - videoPlayer.addListener(new VideoPlayer.Listener() { + videoPlayer = new VideoPlayer(); + videoPlayer.setTextureView(videoTextureView); + videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { @Override public void onStateChanged(boolean playWhenReady, int playbackState) { if (videoPlayer == null) { return; } - if (playbackState != VideoPlayer.STATE_ENDED && playbackState != VideoPlayer.STATE_IDLE) { + if (playbackState != ExoPlayer.STATE_ENDED && playbackState != ExoPlayer.STATE_IDLE) { try { parentActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } - if (playbackState == VideoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { + if (playbackState == ExoPlayer.STATE_READY && aspectRatioFrameLayout.getVisibility() != View.VISIBLE) { aspectRatioFrameLayout.setVisibility(View.VISIBLE); } - if (videoPlayer.getPlayerControl().isPlaying() && playbackState != VideoPlayer.STATE_ENDED) { + if (videoPlayer.isPlaying() && playbackState != ExoPlayer.STATE_ENDED) { if (!isPlaying) { isPlaying = true; videoPlayButton.setImageResource(R.drawable.inline_video_pause); @@ -2061,12 +2434,16 @@ public void onStateChanged(boolean playWhenReady, int playbackState) { isPlaying = false; videoPlayButton.setImageResource(R.drawable.inline_video_play); AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); - if (playbackState == VideoPlayer.STATE_ENDED) { + if (playbackState == ExoPlayer.STATE_ENDED) { if (!videoPlayerSeekbar.isDragging()) { videoPlayerSeekbar.setProgress(0.0f); videoPlayerControlFrameLayout.invalidate(); - videoPlayer.seekTo(0); - videoPlayer.getPlayerControl().pause(); + if (!inPreview && videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() == View.VISIBLE) { + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoPlayer.getDuration())); + } else { + videoPlayer.seekTo(0); + } + videoPlayer.pause(); } } } @@ -2075,7 +2452,7 @@ public void onStateChanged(boolean playWhenReady, int playbackState) { @Override public void onError(Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } @Override @@ -2089,11 +2466,29 @@ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegre aspectRatioFrameLayout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height, unappliedRotationDegrees); } } + + @Override + public void onRenderedFirstFrame() { + if (!textureUploaded) { + textureUploaded = true; + containerView.invalidate(); + } + } + + @Override + public boolean onSurfaceDestroyed(SurfaceTexture surfaceTexture) { + return false; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + + } }); long duration; if (videoPlayer != null) { duration = videoPlayer.getDuration(); - if (duration == ExoPlayer.UNKNOWN_TIME) { + if (duration == C.TIME_UNSET) { duration = 0; } } else { @@ -2101,13 +2496,8 @@ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegre } duration /= 1000; int size = (int) Math.ceil(videoPlayerTime.getPaint().measureText(String.format("%02d:%02d / %02d:%02d", duration / 60, duration % 60, duration / 60, duration % 60))); - - playerNeedsPrepare = true; - } - if (playerNeedsPrepare) { - videoPlayer.prepare(); - playerNeedsPrepare = false; } + videoPlayer.preparePlayer(Uri.fromFile(file), "other"); if (videoPlayerControlFrameLayout != null) { if (currentBotInlineResult != null && (currentBotInlineResult.type.equals("video") || MessageObject.isVideoDocument(currentBotInlineResult.document))) { bottomLayout.setVisibility(View.VISIBLE); @@ -2121,21 +2511,19 @@ public void onVideoSizeChanged(int width, int height, int unappliedRotationDegre menuItem.showSubItem(gallery_menu_share); } } - if (videoTextureView.getSurfaceTexture() != null) { - videoPlayer.setSurface(new Surface(videoTextureView.getSurfaceTexture())); - } videoPlayer.setPlayWhenReady(playWhenReady); + inPreview = preview; } private void releasePlayer() { if (videoPlayer != null) { - videoPlayer.release(); + videoPlayer.releasePlayer(); videoPlayer = null; } try { parentActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (aspectRatioFrameLayout != null) { containerView.removeView(aspectRatioFrameLayout); @@ -2149,7 +2537,7 @@ private void releasePlayer() { videoPlayButton.setImageResource(R.drawable.inline_video_play); AndroidUtilities.cancelRunOnUIThread(updateProgressRunnable); } - if (videoPlayerControlFrameLayout != null) { + if (!inPreview && !requestingPreview && videoPlayerControlFrameLayout != null) { videoPlayerControlFrameLayout.setVisibility(View.GONE); dateTextView.setVisibility(View.VISIBLE); nameTextView.setVisibility(View.VISIBLE); @@ -2176,57 +2564,6 @@ private void updateCaptionTextForCurrentPhoto(Object object) { } } - private void closeCaptionEnter(boolean apply) { - if (currentIndex < 0 || currentIndex >= imagesArrLocals.size()) { - return; - } - Object object = imagesArrLocals.get(currentIndex); - if (apply) { - if (object instanceof MediaController.PhotoEntry) { - ((MediaController.PhotoEntry) object).caption = captionEditText.getFieldCharSequence(); - } else if (object instanceof MediaController.SearchImage) { - ((MediaController.SearchImage) object).caption = captionEditText.getFieldCharSequence(); - } - - if (captionEditText.getFieldCharSequence().length() != 0 && !placeProvider.isPhotoChecked(currentIndex)) { - placeProvider.setPhotoChecked(currentIndex); - checkImageView.setChecked(placeProvider.isPhotoChecked(currentIndex), true); - updateSelectedCount(); - } - } - cropItem.setVisibility(View.VISIBLE); - if (Build.VERSION.SDK_INT >= 16) { - paintItem.setVisibility(View.VISIBLE); - tuneItem.setVisibility(View.VISIBLE); - } - if (sendPhotoType == 0) { - checkImageView.setVisibility(View.VISIBLE); - } - captionDoneItem.setVisibility(View.GONE); - pickerView.setVisibility(View.VISIBLE); - - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) captionEditText.getLayoutParams(); - layoutParams.bottomMargin = -AndroidUtilities.dp(400); - captionEditText.setLayoutParams(layoutParams); - - layoutParams = (FrameLayout.LayoutParams) mentionListView.getLayoutParams(); - layoutParams.bottomMargin = -AndroidUtilities.dp(400); - mentionListView.setLayoutParams(layoutParams); - - if (lastTitle != null) { - actionBar.setTitle(lastTitle); - lastTitle = null; - } - - updateCaptionTextForCurrentPhoto(object); - setCurrentCaption(captionEditText.getFieldCharSequence()); - if (captionEditText.isPopupShowing()) { - captionEditText.hidePopup(); - } else { - captionEditText.closeKeyboard(); - } - } - public void showAlertDialog(AlertDialog.Builder builder) { if (parentActivity == null) { return; @@ -2237,7 +2574,7 @@ public void showAlertDialog(AlertDialog.Builder builder) { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } try { visibleDialog = builder.show(); @@ -2249,7 +2586,7 @@ public void onDismiss(DialogInterface dialog) { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -2304,17 +2641,22 @@ private void applyCurrentEditMode() { translationX = photoCropView.getRectX() + photoCropView.getRectSizeX() / 2 - getContainerViewWidth() / 2; translationY = photoCropView.getRectY() + photoCropView.getRectSizeY() / 2 - getContainerViewHeight() / 2; zoomAnimation = true; + applying = true; + + photoCropView.onDisappear(); } centerImage.setParentView(null); centerImage.setOrientation(0, true); + ignoreDidSetImage = true; centerImage.setImageBitmap(bitmap); + ignoreDidSetImage = false; centerImage.setParentView(containerView); } } } private void switchToEditMode(final int mode) { - if (currentEditMode == mode || centerImage.getBitmap() == null || changeModeAnimation != null || imageMoveAnimation != null || radialProgressViews[0].backgroundState != -1) { + if (currentEditMode == mode || centerImage.getBitmap() == null || changeModeAnimation != null || imageMoveAnimation != null || photoProgressViews[0].backgroundState != -1 || captionEditText.getTag() != null) { return; } if (mode == 0) { @@ -2336,10 +2678,21 @@ private void switchToEditMode(final int mode) { float scale = scaleX > scaleY ? scaleY : scaleX; float newScale = newScaleX > newScaleY ? newScaleY : newScaleX; - animateToScale = newScale / scale; + if (sendPhotoType == 1 && !applying) { + float minSide = Math.min(getContainerViewWidth(), getContainerViewHeight()); + scaleX = minSide / (float) bitmapWidth; + scaleY = minSide / (float) bitmapHeight; + float fillScale = scaleX > scaleY ? scaleX : scaleY; + + this.scale = fillScale / scale; + animateToScale = this.scale * newScale / fillScale; + } else { + animateToScale = newScale / scale; + } + animateToX = 0; if (currentEditMode == 1) { - animateToY = AndroidUtilities.dp(24); + animateToY = AndroidUtilities.dp(24 + 34); } else if (currentEditMode == 2) { animateToY = AndroidUtilities.dp(62); } else if (currentEditMode == 3) { @@ -2375,7 +2728,7 @@ private void switchToEditMode(final int mode) { ); } imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentEditMode == 1) { @@ -2390,6 +2743,7 @@ public void onAnimationEnd(Animator animation) { } imageMoveAnimation = null; currentEditMode = mode; + applying = false; animateToScale = 1; animateToX = 0; animateToY = 0; @@ -2409,7 +2763,7 @@ public void onAnimationEnd(Animator animation) { } animatorSet.playTogether(arrayList); animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { pickerView.setVisibility(View.VISIBLE); @@ -2448,10 +2802,18 @@ public void needMoveImageTo(float x, float y, float s, boolean animated) { public Bitmap getBitmap() { return centerImage.getBitmap(); } + + @Override + public void onChange(boolean reset) { + resetButton.setVisibility(reset ? View.GONE : View.VISIBLE); + } }); } + photoCropView.onAppear(); editorDoneLayout.doneButton.setText(LocaleController.getString("Crop", R.string.Crop)); + editorDoneLayout.doneButton.setTextColor(0xff51bdf3); + changeModeAnimation = new AnimatorSet(); ArrayList arrayList = new ArrayList<>(); arrayList.add(ObjectAnimator.ofFloat(pickerView, "translationY", 0, AndroidUtilities.dp(96))); @@ -2464,7 +2826,7 @@ public Bitmap getBitmap() { } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); - changeModeAnimation.addListener(new AnimatorListenerAdapterProxy() { + changeModeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { changeModeAnimation = null; @@ -2476,7 +2838,7 @@ public void onAnimationEnd(Animator animation) { checkImageView.setVisibility(View.GONE); } - Bitmap bitmap = centerImage.getBitmap(); + final Bitmap bitmap = centerImage.getBitmap(); if (bitmap != null) { photoCropView.setBitmap(bitmap, centerImage.getOrientation(), sendPhotoType != 1); int bitmapWidth = centerImage.getBitmapWidth(); @@ -2488,10 +2850,16 @@ public void onAnimationEnd(Animator animation) { float newScaleY = (float) getContainerViewHeight(1) / (float) bitmapHeight; float scale = scaleX > scaleY ? scaleY : scaleX; float newScale = newScaleX > newScaleY ? newScaleY : newScaleX; + if (sendPhotoType == 1) { + float minSide = Math.min(getContainerViewWidth(1), getContainerViewHeight(1)); + newScaleX = minSide / (float) bitmapWidth; + newScaleY = minSide / (float) bitmapHeight; + newScale = newScaleX > newScaleY ? newScaleX : newScaleY; + } animateToScale = newScale / scale; animateToX = 0; - animateToY = -AndroidUtilities.dp(24) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight / 2 : 0); + animateToY = -AndroidUtilities.dp(24 + 32) + (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight / 2 : 0); animationStartTime = System.currentTimeMillis(); zoomAnimation = true; } @@ -2503,7 +2871,7 @@ public void onAnimationEnd(Animator animation) { ObjectAnimator.ofFloat(photoCropView, "alpha", 0, 1) ); imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { editorDoneLayout.setVisibility(View.VISIBLE); @@ -2512,6 +2880,8 @@ public void onAnimationStart(Animator animation) { @Override public void onAnimationEnd(Animator animation) { + photoCropView.onAppeared(); + imageMoveAnimation = null; currentEditMode = mode; animateToScale = 1; @@ -2575,7 +2945,7 @@ public void onClick(DialogInterface dialogInterface, int i) { } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); - changeModeAnimation.addListener(new AnimatorListenerAdapterProxy() { + changeModeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { changeModeAnimation = null; @@ -2613,7 +2983,7 @@ public void onAnimationEnd(Animator animation) { ObjectAnimator.ofFloat(photoFilterView.getToolsView(), "translationY", AndroidUtilities.dp(126), 0) ); imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -2676,7 +3046,7 @@ public void run() { } changeModeAnimation.playTogether(arrayList); changeModeAnimation.setDuration(200); - changeModeAnimation.addListener(new AnimatorListenerAdapterProxy() { + changeModeAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { changeModeAnimation = null; @@ -2715,7 +3085,7 @@ public void onAnimationEnd(Animator animation) { ObjectAnimator.ofFloat(photoPaintView.getActionBar(), "translationY", -ActionBar.getCurrentActionBarHeight() - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0), 0) ); imageMoveAnimation.setDuration(200); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -2780,7 +3150,7 @@ private void toggleActionBar(boolean show, final boolean animated) { currentActionBarAnimation = new AnimatorSet(); currentActionBarAnimation.playTogether(arrayList); if (!show) { - currentActionBarAnimation.addListener(new AnimatorListenerAdapterProxy() { + currentActionBarAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (currentActionBarAnimation != null && currentActionBarAnimation.equals(animation)) { @@ -2867,7 +3237,7 @@ private String getFileName(int index) { return null; } - private TLRPC.FileLocation getFileLocation(int index, int size[]) { + private TLObject getFileLocation(int index, int size[]) { if (index < 0) { return null; } @@ -2908,6 +3278,8 @@ private TLRPC.FileLocation getFileLocation(int index, int size[]) { } else { size[0] = -1; } + } else if (message.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) { + return ((TLRPC.TL_messageMediaInvoice) message.messageOwner.media).photo; } else if (message.getDocument() != null && message.getDocument().thumb != null) { size[0] = message.getDocument().thumb.size; if (size[0] == 0) { @@ -2964,7 +3336,19 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca menuItem.setVisibility(View.VISIBLE); bottomLayout.setVisibility(View.VISIBLE); bottomLayout.setTranslationY(0); + captionTextViewOld.setTranslationY(0); + captionTextViewNew.setTranslationY(0); + bottomLayout.setTranslationY(0); shareButton.setVisibility(View.GONE); + if (qualityChooseView != null) { + qualityChooseView.setVisibility(View.INVISIBLE); + qualityPicker.setVisibility(View.INVISIBLE); + qualityChooseView.setTag(null); + } + if (qualityChooseViewAnimation != null) { + qualityChooseViewAnimation.cancel(); + qualityChooseViewAnimation = null; + } allowShare = false; menuItem.hideSubItem(gallery_menu_showall); menuItem.hideSubItem(gallery_menu_share); @@ -2978,14 +3362,19 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca paintItem.setVisibility(View.GONE); cropItem.setVisibility(View.GONE); tuneItem.setVisibility(View.GONE); - captionDoneItem.setVisibility(View.GONE); + if (videoTimelineViewContainer != null) { + videoTimelineViewContainer.setVisibility(View.GONE); + } + captionItem.setVisibility(View.GONE); + captionItem.setImageResource(R.drawable.photo_text); + compressItem.setVisibility(View.GONE); captionEditText.setVisibility(View.GONE); mentionListView.setVisibility(View.GONE); muteItem.setVisibility(View.GONE); + actionBar.setSubtitle(null); masksItem.setVisibility(View.GONE); - muteItemAvailable = false; muteVideo = false; - muteItem.setIcon(R.drawable.volume_on); + muteItem.setImageResource(R.drawable.volume_on); editorDoneLayout.setVisibility(View.GONE); captionTextView.setTag(null); captionTextView.setVisibility(View.INVISIBLE); @@ -2997,8 +3386,8 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca } for (int a = 0; a < 3; a++) { - if (radialProgressViews[a] != null) { - radialProgressViews[a].setBackgroundState(-1, false); + if (photoProgressViews[a] != null) { + photoProgressViews[a].setBackgroundState(-1, false); } } @@ -3006,7 +3395,7 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca imagesArr.add(messageObject); if (currentAnimation != null) { needSearchImageInArr = false; - } else if (!(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty)) { + } else if (!(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaInvoice) && !(messageObject.messageOwner.media instanceof TLRPC.TL_messageMediaWebPage) && (messageObject.messageOwner.action == null || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionEmpty)) { needSearchImageInArr = true; imagesByIds[0].put(messageObject.getId(), messageObject); menuItem.showSubItem(gallery_menu_showall); @@ -3051,6 +3440,7 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca bottomLayout.setVisibility(View.GONE); canShowBottom = false; Object obj = imagesArrLocals.get(index); + boolean allowCaption; if (obj instanceof MediaController.PhotoEntry) { if (((MediaController.PhotoEntry) obj).isVideo) { cropItem.setVisibility(View.GONE); @@ -3059,16 +3449,19 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca } else { cropItem.setVisibility(View.VISIBLE); } + allowCaption = true; } else if (obj instanceof TLRPC.BotInlineResult) { cropItem.setVisibility(View.GONE); + allowCaption = false; } else { cropItem.setVisibility(obj instanceof MediaController.SearchImage && ((MediaController.SearchImage) obj).type == 0 ? View.VISIBLE : View.GONE); + allowCaption = cropItem.getVisibility() == View.VISIBLE; } if (parentChatActivity != null && (parentChatActivity.currentEncryptedChat == null || AndroidUtilities.getPeerLayerVersion(parentChatActivity.currentEncryptedChat.layer) >= 46)) { mentionsAdapter.setChatInfo(parentChatActivity.info); mentionsAdapter.setNeedUsernames(parentChatActivity.currentChat != null); mentionsAdapter.setNeedBotContext(false); - needCaptionLayout = cropItem.getVisibility() == View.VISIBLE && (placeProvider == null || placeProvider != null && placeProvider.allowCaption()); + needCaptionLayout = allowCaption && (placeProvider == null || placeProvider != null && placeProvider.allowCaption()); captionEditText.setVisibility(needCaptionLayout ? View.VISIBLE : View.GONE); if (captionTextView.getTag() == null && needCaptionLayout) { captionTextView.setText(LocaleController.getString("AddCaption", R.string.AddCaption)); @@ -3107,7 +3500,7 @@ private void onPhotoShow(final MessageObject messageObject, final TLRPC.FileLoca if (entry instanceof MediaController.PhotoEntry) { MediaController.PhotoEntry photoEntry = ((MediaController.PhotoEntry) entry); if (photoEntry.isVideo) { - preparePlayer(new File(photoEntry.path), false); + preparePlayer(new File(photoEntry.path), false, false); } } } @@ -3140,6 +3533,7 @@ private void setImageIndex(int index, boolean init) { currentIndex = index; boolean isVideo = false; boolean sameImage = false; + boolean isInvoice; if (!imagesArr.isEmpty()) { if (currentIndex < 0 || currentIndex >= imagesArr.size()) { @@ -3150,41 +3544,53 @@ private void setImageIndex(int index, boolean init) { sameImage = currentMessageObject != null && currentMessageObject.getId() == newMessageObject.getId(); currentMessageObject = newMessageObject; isVideo = currentMessageObject.isVideo(); - masksItem.setVisibility(currentMessageObject.hasPhotoStickers() && (int) currentMessageObject.getDialogId() != 0 ? View.VISIBLE : View.INVISIBLE); - if (currentMessageObject.canDeleteMessage(null)) { - menuItem.showSubItem(gallery_menu_delete); - } else { + isInvoice = currentMessageObject.isInvoice(); + if (isInvoice) { + masksItem.setVisibility(View.GONE); menuItem.hideSubItem(gallery_menu_delete); - } - if (isVideo && Build.VERSION.SDK_INT >= 16) { - menuItem.showSubItem(gallery_menu_openin); - } else { menuItem.hideSubItem(gallery_menu_openin); - } - if (currentMessageObject.isFromUser()) { - TLRPC.User user = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); - if (user != null) { - nameTextView.setText(UserObject.getUserName(user)); + setCurrentCaption(currentMessageObject.messageOwner.media.description); + allowShare = false; + bottomLayout.setTranslationY(AndroidUtilities.dp(48)); + captionTextViewOld.setTranslationY(AndroidUtilities.dp(48)); + captionTextViewNew.setTranslationY(AndroidUtilities.dp(48)); + } else { + masksItem.setVisibility(currentMessageObject.hasPhotoStickers() && (int) currentMessageObject.getDialogId() != 0 ? View.VISIBLE : View.INVISIBLE); + if (currentMessageObject.canDeleteMessage(null)) { + menuItem.showSubItem(gallery_menu_delete); } else { - nameTextView.setText(""); + menuItem.hideSubItem(gallery_menu_delete); } - } else { - TLRPC.Chat chat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); - if (chat != null) { - nameTextView.setText(chat.title); + if (isVideo && Build.VERSION.SDK_INT >= 16) { + menuItem.showSubItem(gallery_menu_openin); } else { - nameTextView.setText(""); + menuItem.hideSubItem(gallery_menu_openin); } + if (currentMessageObject.isFromUser()) { + TLRPC.User user = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); + if (user != null) { + nameTextView.setText(UserObject.getUserName(user)); + } else { + nameTextView.setText(""); + } + } else { + TLRPC.Chat chat = MessagesController.getInstance().getChat(currentMessageObject.messageOwner.to_id.channel_id); + if (chat != null) { + nameTextView.setText(chat.title); + } else { + nameTextView.setText(""); + } + } + long date = (long) currentMessageObject.messageOwner.date * 1000; + String dateString = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(new Date(date)), LocaleController.getInstance().formatterDay.format(new Date(date))); + if (currentFileNames[0] != null && isVideo) { + dateTextView.setText(String.format("%s (%s)", dateString, AndroidUtilities.formatFileSize(currentMessageObject.getDocument().size))); + } else { + dateTextView.setText(dateString); + } + CharSequence caption = currentMessageObject.caption; + setCurrentCaption(caption); } - long date = (long) currentMessageObject.messageOwner.date * 1000; - String dateString = LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, LocaleController.getInstance().formatterYear.format(new Date(date)), LocaleController.getInstance().formatterDay.format(new Date(date))); - if (currentFileNames[0] != null && isVideo) { - dateTextView.setText(String.format("%s (%s)", dateString, AndroidUtilities.formatFileSize(currentMessageObject.getDocument().size))); - } else { - dateTextView.setText(dateString); - } - CharSequence caption = currentMessageObject.caption; - setCurrentCaption(caption); if (currentAnimation != null) { menuItem.hideSubItem(gallery_menu_save); @@ -3234,8 +3640,10 @@ private void setImageIndex(int index, boolean init) { } else { actionBar.setTitle(LocaleController.getString("AttachPhoto", R.string.AttachPhoto)); } + } else if (isInvoice) { + actionBar.setTitle(currentMessageObject.messageOwner.media.title); } - if (currentMessageObject.messageOwner.ttl != 0) { + if (currentMessageObject.messageOwner.ttl != 0 && currentMessageObject.messageOwner.ttl < 60 * 60) { allowShare = false; menuItem.hideSubItem(gallery_menu_save); shareButton.setVisibility(View.GONE); @@ -3315,7 +3723,12 @@ private void setImageIndex(int index, boolean init) { } if (fromCamera) { if (isVideo) { - muteItemAvailable = true; + muteItem.setVisibility(View.VISIBLE); + captionItem.setVisibility(View.VISIBLE); + captionTextViewNew.setTranslationY(AndroidUtilities.dp(96)); + captionTextViewOld.setTranslationY(AndroidUtilities.dp(96)); + videoTimelineViewContainer.setVisibility(View.VISIBLE); + processOpenVideo(currentPathObject); actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); } else { actionBar.setTitle(LocaleController.getString("AttachPhoto", R.string.AttachPhoto)); @@ -3379,7 +3792,7 @@ private void setImageIndex(int index, boolean init) { canDragDown = true; changingPage = false; switchImageAfterAnimation = 0; - canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); + canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo && photoProgressViews[0].backgroundState != 0); updateMinMax(scale); } @@ -3397,9 +3810,9 @@ private void setImageIndex(int index, boolean init) { centerImage = leftImage; leftImage = temp; - RadialProgressView tempProgress = radialProgressViews[0]; - radialProgressViews[0] = radialProgressViews[2]; - radialProgressViews[2] = tempProgress; + PhotoProgressView tempProgress = photoProgressViews[0]; + photoProgressViews[0] = photoProgressViews[2]; + photoProgressViews[2] = tempProgress; setIndexToImage(leftImage, currentIndex - 1); checkProgress(1, false); @@ -3410,9 +3823,9 @@ private void setImageIndex(int index, boolean init) { centerImage = rightImage; rightImage = temp; - RadialProgressView tempProgress = radialProgressViews[0]; - radialProgressViews[0] = radialProgressViews[1]; - radialProgressViews[1] = tempProgress; + PhotoProgressView tempProgress = photoProgressViews[0]; + photoProgressViews[0] = photoProgressViews[1]; + photoProgressViews[1] = tempProgress; setIndexToImage(rightImage, currentIndex + 1); checkProgress(1, false); @@ -3426,9 +3839,14 @@ private void setCurrentCaption(final CharSequence caption) { captionTextView = captionTextViewOld; captionTextViewOld = captionTextViewNew; captionTextViewNew = captionTextView; - CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), MessageObject.getTextPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + Theme.createChatResources(null, true); + CharSequence str = Emoji.replaceEmoji(new SpannableStringBuilder(caption.toString()), captionTextView.getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); captionTextView.setTag(str); - captionTextView.setText(str); + try { + captionTextView.setText(str); + } catch (Exception e) { + FileLog.e(e); + } captionTextView.setTextColor(0xffffffff); captionTextView.setAlpha(bottomLayout.getVisibility() == View.VISIBLE || pickerView.getVisibility() == View.VISIBLE ? 1.0f : 0.0f); AndroidUtilities.runOnUIThread(new Runnable() { @@ -3441,7 +3859,11 @@ public void run() { }); } else { if (needCaptionLayout) { - captionTextView.setText(LocaleController.getString("AddCaption", R.string.AddCaption)); + try { + captionTextView.setText(LocaleController.getString("AddCaption", R.string.AddCaption)); + } catch (Exception e) { + FileLog.e(e); + } captionTextView.setTag("empty"); captionTextView.setVisibility(View.VISIBLE); captionTextView.setTextColor(0xb2ffffff); @@ -3503,31 +3925,31 @@ private void checkProgress(int a, boolean animated) { } if (f != null && f.exists()) { if (isVideo) { - radialProgressViews[a].setBackgroundState(3, animated); + photoProgressViews[a].setBackgroundState(3, animated); } else { - radialProgressViews[a].setBackgroundState(-1, animated); + photoProgressViews[a].setBackgroundState(-1, animated); } } else { if (isVideo) { if (!FileLoader.getInstance().isLoadingFile(currentFileNames[a])) { - radialProgressViews[a].setBackgroundState(2, false); + photoProgressViews[a].setBackgroundState(2, false); } else { - radialProgressViews[a].setBackgroundState(1, false); + photoProgressViews[a].setBackgroundState(1, false); } } else { - radialProgressViews[a].setBackgroundState(0, animated); + photoProgressViews[a].setBackgroundState(0, animated); } Float progress = ImageLoader.getInstance().getFileProgress(currentFileNames[a]); if (progress == null) { progress = 0.0f; } - radialProgressViews[a].setProgress(progress, false); + photoProgressViews[a].setProgress(progress, false); } if (a == 0) { - canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo && radialProgressViews[0].backgroundState != 0); + canZoom = !imagesArrLocals.isEmpty() || (currentFileNames[0] != null && !isVideo && photoProgressViews[0].backgroundState != 0); } } else { - radialProgressViews[a].setBackgroundState(-1, animated); + photoProgressViews[a].setBackgroundState(-1, animated); } } @@ -3561,7 +3983,7 @@ private void setIndexToImage(ImageReceiver imageReceiver, int index) { } filter = String.format(Locale.US, "%d_%d", size, size); } - } else if (object instanceof TLRPC.BotInlineResult) { //TODO ? + } else if (object instanceof TLRPC.BotInlineResult) { TLRPC.BotInlineResult botInlineResult = ((TLRPC.BotInlineResult) object); if (botInlineResult.type.equals("video") || MessageObject.isVideoDocument(botInlineResult.document)) { if (botInlineResult.document != null) { @@ -3611,7 +4033,7 @@ private void setIndexToImage(ImageReceiver imageReceiver, int index) { } } else { int size[] = new int[1]; - TLRPC.FileLocation fileLocation = getFileLocation(index, size); + TLObject fileLocation = getFileLocation(index, size); if (fileLocation != null) { MessageObject messageObject = null; @@ -3720,7 +4142,7 @@ public boolean openPhoto(final MessageObject messageObject, final TLRPC.FileLoca if (object == null && photos == null) { return false; } - + lastInsets = null; WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); if (attachedToWindow) { try { @@ -3746,10 +4168,11 @@ public boolean openPhoto(final MessageObject messageObject, final TLRPC.FileLoca containerView.setFocusable(false); wm.addView(windowView, windowLayoutParams); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return false; } + doneButtonPressed = false; parentChatActivity = chatActivity; actionBar.setTitle(LocaleController.formatString("Of", R.string.Of, 1, 1)); @@ -3760,6 +4183,8 @@ public boolean openPhoto(final MessageObject messageObject, final TLRPC.FileLoca NotificationCenter.getInstance().addObserver(this, NotificationCenter.mediaDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.dialogPhotosLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FilePreparingFailed); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileNewChunkAvailable); placeProvider = provider; mergeDialogId = mDialogId; @@ -3896,7 +4321,7 @@ public void run() { }; animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -3970,13 +4395,17 @@ public void run() { switchToEditMode(0); return; } + if (Build.VERSION.SDK_INT >= 16 && qualityChooseView != null && qualityChooseView.getTag() != null) { + qualityPicker.cancelButton.callOnClick(); + return; + } try { if (visibleDialog != null) { visibleDialog.dismiss(); visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentEditMode != 0) { @@ -4008,6 +4437,8 @@ public void run() { NotificationCenter.getInstance().removeObserver(this, NotificationCenter.mediaDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.dialogPhotosLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FilePreparingFailed); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileNewChunkAvailable); ConnectionsManager.getInstance().cancelRequestsForGuid(classGuid); isActionBarVisible = false; @@ -4128,7 +4559,7 @@ public void run() { }; animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -4172,7 +4603,7 @@ public void run() { } }; animatorSet.setDuration(200); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (animationEndRunnable != null) { @@ -4209,7 +4640,7 @@ public void destroyPhotoViewer() { } windowView = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (captionEditText != null) { captionEditText.onDestroy(); @@ -4225,15 +4656,20 @@ private void onPhotoClosed(PlaceProviderObject object) { currentFileLocation = null; currentPathObject = null; currentThumb = null; + parentAlert = null; if (currentAnimation != null) { currentAnimation.setSecondParentView(null); currentAnimation = null; } for (int a = 0; a < 3; a++) { - if (radialProgressViews[a] != null) { - radialProgressViews[a].setBackgroundState(-1, false); + if (photoProgressViews[a] != null) { + photoProgressViews[a].setBackgroundState(-1, false); } } + requestVideoPreview(0); + if (videoTimelineView != null) { + videoTimelineView.destroy(); + } centerImage.setImageBitmap((Bitmap) null); leftImage.setImageBitmap((Bitmap) null); rightImage.setImageBitmap((Bitmap) null); @@ -4247,7 +4683,7 @@ public void run() { wm.removeView(windowView); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -4277,6 +4713,9 @@ public void run() { public void onResume() { redraw(0); //workaround for camera bug + if (videoPlayer != null) { + videoPlayer.seekTo(videoPlayer.getCurrentPosition() + 1); + } } public void onPause() { @@ -4284,7 +4723,7 @@ public void onPause() { closePhoto(false, false); return; } - if (captionDoneItem.getVisibility() != View.GONE) { + if (lastTitle != null) { closeCaptionEnter(true); } } @@ -4354,7 +4793,7 @@ private int getContainerViewHeight(int mode) { height += AndroidUtilities.statusBarHeight; } if (mode == 1) { - height -= AndroidUtilities.dp(76); + height -= AndroidUtilities.dp(48 + 32 + 64); } else if (mode == 2) { height -= AndroidUtilities.dp(154); } else if (mode == 3) { @@ -4374,14 +4813,7 @@ private boolean onTouchEvent(MotionEvent ev) { } if (currentEditMode == 1) { - if (ev.getPointerCount() == 1) { - if (photoCropView.onTouch(ev)) { - updateMinMax(scale); - return true; - } - } else { - photoCropView.onTouch(null); - } + return true; } if (captionEditText.isPopupShowing() || captionEditText.isKeyboardVisible()) { @@ -4642,7 +5074,7 @@ private void animateTo(float newScale, float newTx, float newTy, boolean isZoom, ); imageMoveAnimation.setInterpolator(interpolator); imageMoveAnimation.setDuration(duration); - imageMoveAnimation.addListener(new AnimatorListenerAdapterProxy() { + imageMoveAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { imageMoveAnimation = null; @@ -4782,9 +5214,9 @@ private void onDraw(Canvas canvas) { canvas.save(); canvas.translate(tranlateX, currentTranslationY / currentScale); canvas.translate((canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); - radialProgressViews[1].setScale(1.0f - scaleDiff); - radialProgressViews[1].setAlpha(alpha); - radialProgressViews[1].onDraw(canvas); + photoProgressViews[1].setScale(1.0f - scaleDiff); + photoProgressViews[1].setAlpha(alpha); + photoProgressViews[1].onDraw(canvas); canvas.restore(); } @@ -4855,9 +5287,9 @@ private void onDraw(Canvas canvas) { if (!drawTextureView && (videoPlayerControlFrameLayout == null || videoPlayerControlFrameLayout.getVisibility() != View.VISIBLE)) { canvas.save(); canvas.translate(translateX, currentTranslationY / currentScale); - radialProgressViews[0].setScale(1.0f - scaleDiff); - radialProgressViews[0].setAlpha(alpha); - radialProgressViews[0].onDraw(canvas); + photoProgressViews[0].setScale(1.0f - scaleDiff); + photoProgressViews[0].setAlpha(alpha); + photoProgressViews[0].onDraw(canvas); canvas.restore(); } @@ -4884,9 +5316,9 @@ private void onDraw(Canvas canvas) { canvas.save(); canvas.translate(currentTranslationX, currentTranslationY / currentScale); canvas.translate(-(canvas.getWidth() * (scale + 1) + AndroidUtilities.dp(30)) / 2, -currentTranslationY / currentScale); - radialProgressViews[2].setScale(1.0f); - radialProgressViews[2].setAlpha(1.0f); - radialProgressViews[2].onDraw(canvas); + photoProgressViews[2].setScale(1.0f); + photoProgressViews[2].setAlpha(1.0f); + photoProgressViews[2].onDraw(canvas); canvas.restore(); } } @@ -4948,7 +5380,7 @@ private void onActionClick(boolean download) { } } else { if (Build.VERSION.SDK_INT >= 16) { - preparePlayer(file, true); + preparePlayer(file, true, false); } else { Intent intent = new Intent(Intent.ACTION_VIEW); if (Build.VERSION.SDK_INT >= 24) { @@ -5004,8 +5436,8 @@ public boolean onSingleTapConfirmed(MotionEvent e) { } if (canShowBottom) { boolean drawTextureView = Build.VERSION.SDK_INT >= 16 && aspectRatioFrameLayout != null && aspectRatioFrameLayout.getVisibility() == View.VISIBLE; - if (radialProgressViews[0] != null && containerView != null && !drawTextureView) { - int state = radialProgressViews[0].backgroundState; + if (photoProgressViews[0] != null && containerView != null && !drawTextureView) { + int state = photoProgressViews[0].backgroundState; if (state > 0 && state <= 3) { float x = e.getX(); float y = e.getY(); @@ -5021,7 +5453,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { } else if (sendPhotoType == 0) { checkImageView.performClick(); } else if (currentBotInlineResult != null && (currentBotInlineResult.type.equals("video") || MessageObject.isVideoDocument(currentBotInlineResult.document))) { - int state = radialProgressViews[0].backgroundState; + int state = photoProgressViews[0].backgroundState; if (state > 0 && state <= 3) { float x = e.getX(); float y = e.getY(); @@ -5070,4 +5502,564 @@ public boolean onDoubleTap(MotionEvent e) { public boolean onDoubleTapEvent(MotionEvent e) { return false; } + + // video edit start + private QualityChooseView qualityChooseView; + private PickerBottomLayoutViewer qualityPicker; + private RadialProgressView progressView; + private VideoTimelineView videoTimelineView; + private FrameLayout videoTimelineViewContainer; + private AnimatorSet qualityChooseViewAnimation; + + private int selectedCompression; + private int compressionsCount = -1; + private int previousCompression; + + private int rotationValue; + private int originalWidth; + private int originalHeight; + private int resultWidth; + private int resultHeight; + private int bitrate; + private int originalBitrate; + private float videoDuration; + private long startTime; + private long endTime; + private long audioFramesSize; + private long videoFramesSize; + private int estimatedSize; + private long estimatedDuration; + private long originalSize; + + private MessageObject videoPreviewMessageObject; + private boolean tryStartRequestPreviewOnFinish; + private boolean loadInitialVideo; + private boolean inPreview; + private int previewViewEnd; + private boolean requestingPreview; + + private String currentSubtitle; + + private class QualityChooseView extends View { + + private Paint paint; + private TextPaint textPaint; + + private int circleSize; + private int gapSize; + private int sideSide; + private int lineSize; + + private boolean moving; + private boolean startMoving; + private float startX; + + private int startMovingQuality; + + public QualityChooseView(Context context) { + super(context); + + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setTextSize(AndroidUtilities.dp(12)); + textPaint.setColor(0xffcdcdcd); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + getParent().requestDisallowInterceptTouchEvent(true); + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (x > cx - AndroidUtilities.dp(15) && x < cx + AndroidUtilities.dp(15)) { + startMoving = a == selectedCompression; + startX = x; + startMovingQuality = selectedCompression; + break; + } + } + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (startMoving) { + if (Math.abs(startX - x) >= AndroidUtilities.getPixelsInCM(0.5f, true)) { + moving = true; + startMoving = false; + } + } else if (moving) { + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + int diff = lineSize / 2 + circleSize / 2 + gapSize; + if (x > cx - diff && x < cx + diff) { + if (selectedCompression != a) { + selectedCompression = a; + didChangedCompressionLevel(false); + invalidate(); + } + break; + } + } + } + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (!moving) { + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (x > cx - AndroidUtilities.dp(15) && x < cx + AndroidUtilities.dp(15)) { + if (selectedCompression != a) { + selectedCompression = a; + didChangedCompressionLevel(true); + invalidate(); + } + break; + } + } + } else { + if (selectedCompression != startMovingQuality) { + requestVideoPreview(1); + } + } + startMoving = false; + moving = false; + } + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + circleSize = AndroidUtilities.dp(12); + gapSize = AndroidUtilities.dp(2); + sideSide = AndroidUtilities.dp(18); + lineSize = (getMeasuredWidth() - circleSize * compressionsCount - gapSize * 8 - sideSide * 2) / (compressionsCount - 1); + } + + @Override + protected void onDraw(Canvas canvas) { + int cy = getMeasuredHeight() / 2 + AndroidUtilities.dp(6); + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (a <= selectedCompression) { + paint.setColor(0xff53aeef); + } else { + paint.setColor(0x66ffffff); + } + String text; + if (a == compressionsCount - 1) { + text = originalHeight + "p"; + } else if (a == 0) { + text = "240p"; + } else if (a == 1) { + text = "360p"; + } else if (a == 2) { + text = "480p"; + } else { + text = "720p"; + } + float width = textPaint.measureText(text); + canvas.drawCircle(cx, cy, a == selectedCompression ? AndroidUtilities.dp(8) : circleSize / 2, paint); + canvas.drawText(text, cx - width / 2, cy - AndroidUtilities.dp(16), textPaint); + if (a != 0) { + int x = cx - circleSize / 2 - gapSize - lineSize; + canvas.drawRect(x, cy - AndroidUtilities.dp(1), x + lineSize, cy + AndroidUtilities.dp(2), paint); + } + } + } + } + + public void updateMuteButton() { + if (videoPlayer != null) { + videoPlayer.setMute(muteVideo); + } + if (muteVideo) { + actionBar.setTitle(LocaleController.getString("AttachGif", R.string.AttachGif)); + actionBar.setSubtitle(null); + muteItem.setImageResource(R.drawable.volume_off); + if (compressItem.getVisibility() == View.VISIBLE) { + compressItem.setClickable(false); + compressItem.setAlpha(0.5f); + compressItem.setEnabled(false); + } + videoTimelineView.setMaxProgressDiff(30000.0f / videoDuration); + } else { + actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); + actionBar.setSubtitle(currentSubtitle); + muteItem.setImageResource(R.drawable.volume_on); + if (compressItem.getVisibility() == View.VISIBLE) { + compressItem.setClickable(true); + compressItem.setAlpha(1.0f); + compressItem.setEnabled(true); + } + videoTimelineView.setMaxProgressDiff(1.0f); + } + } + + private void didChangedCompressionLevel(boolean request) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("compress_video2", selectedCompression); + editor.commit(); + updateWidthHeightBitrateForCompression(); + updateVideoInfo(); + if (request) { + requestVideoPreview(1); + } + } + + private void updateVideoInfo() { + if (actionBar == null) { + return; + } + + if (selectedCompression == 0) { + compressItem.setImageResource(R.drawable.video_240); + } else if (selectedCompression == 1) { + compressItem.setImageResource(R.drawable.video_360); + } else if (selectedCompression == 2) { + compressItem.setImageResource(R.drawable.video_480); + } else if (selectedCompression == 3) { + compressItem.setImageResource(R.drawable.video_720); + } else if (selectedCompression == 4) { + compressItem.setImageResource(R.drawable.video_1080); + } + + estimatedDuration = (long) Math.ceil((videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()) * videoDuration); + + int width; + int height; + + if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && selectedCompression == compressionsCount - 1) { + width = rotationValue == 90 || rotationValue == 270 ? originalHeight : originalWidth; + height = rotationValue == 90 || rotationValue == 270 ? originalWidth : originalHeight; + estimatedSize = (int) (originalSize * ((float) estimatedDuration / videoDuration)); + } else { + width = rotationValue == 90 || rotationValue == 270 ? resultHeight : resultWidth; + height = rotationValue == 90 || rotationValue == 270 ? resultWidth : resultHeight; + + estimatedSize = (int) ((audioFramesSize + videoFramesSize) * ((float) estimatedDuration / videoDuration)); + estimatedSize += estimatedSize / (32 * 1024) * 16; + } + + if (videoTimelineView.getLeftProgress() == 0) { + startTime = -1; + } else { + startTime = (long) (videoTimelineView.getLeftProgress() * videoDuration) * 1000; + } + if (videoTimelineView.getRightProgress() == 1) { + endTime = -1; + } else { + endTime = (long) (videoTimelineView.getRightProgress() * videoDuration) * 1000; + } + + String videoDimension = String.format("%dx%d", width, height); + int minutes = (int) (estimatedDuration / 1000 / 60); + int seconds = (int) Math.ceil(estimatedDuration / 1000) - minutes * 60; + String videoTimeSize = String.format("%d:%02d, ~%s", minutes, seconds, AndroidUtilities.formatFileSize(estimatedSize)); + currentSubtitle = String.format("%s, %s", videoDimension, videoTimeSize); + actionBar.setSubtitle(muteVideo ? null : currentSubtitle); + } + + private void requestVideoPreview(int request) { + if (Build.VERSION.SDK_INT < 16) { + return; + } + if (videoPreviewMessageObject != null) { + MediaController.getInstance().cancelVideoConvert(videoPreviewMessageObject); + } + boolean wasRequestingPreview = requestingPreview && !tryStartRequestPreviewOnFinish; + requestingPreview = false; + loadInitialVideo = false; + progressView.setVisibility(View.INVISIBLE); + if (request == 1) { + if (selectedCompression == compressionsCount - 1) { + tryStartRequestPreviewOnFinish = false; + if (!wasRequestingPreview) { + preparePlayer(currentPlayingVideoFile, false, false); + } else { + progressView.setVisibility(View.VISIBLE); + loadInitialVideo = true; + } + } else { + requestingPreview = true; + releasePlayer(); + if (videoPreviewMessageObject == null) { + TLRPC.TL_message message = new TLRPC.TL_message(); + message.id = 0; + message.message = ""; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.action = new TLRPC.TL_messageActionEmpty(); + videoPreviewMessageObject = new MessageObject(message, null, false); + videoPreviewMessageObject.messageOwner.attachPath = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "video_preview.mp4").getAbsolutePath(); + videoPreviewMessageObject.videoEditedInfo = new VideoEditedInfo(); + videoPreviewMessageObject.videoEditedInfo.rotationValue = rotationValue; + videoPreviewMessageObject.videoEditedInfo.originalWidth = originalWidth; + videoPreviewMessageObject.videoEditedInfo.originalHeight = originalHeight; + videoPreviewMessageObject.videoEditedInfo.originalPath = currentPlayingVideoFile.getAbsolutePath(); + } + long start = videoPreviewMessageObject.videoEditedInfo.startTime = startTime; + long end = videoPreviewMessageObject.videoEditedInfo.endTime = endTime; + if (start == -1) { + start = 0; + } + if (end == -1) { + end = (long) (videoDuration * 1000); + } + if (end - start > 5000000) { + videoPreviewMessageObject.videoEditedInfo.endTime = start + 5000000; + } + videoPreviewMessageObject.videoEditedInfo.bitrate = bitrate; + videoPreviewMessageObject.videoEditedInfo.resultWidth = resultWidth; + videoPreviewMessageObject.videoEditedInfo.resultHeight = resultHeight; + if (!MediaController.getInstance().scheduleVideoConvert(videoPreviewMessageObject, true)) { + tryStartRequestPreviewOnFinish = true; + } + requestingPreview = true; + progressView.setVisibility(View.VISIBLE); + } + } else { + tryStartRequestPreviewOnFinish = false; + if (request == 2) { + preparePlayer(currentPlayingVideoFile, false, false); + } + } + } + + private void updateWidthHeightBitrateForCompression() { + if (selectedCompression >= compressionsCount) { + selectedCompression = compressionsCount - 1; + } + if (selectedCompression != compressionsCount - 1) { + float maxSize; + int targetBitrate; + switch (selectedCompression) { + case 0: + maxSize = 432.0f; + targetBitrate = 400000; + break; + case 1: + maxSize = 640.0f; + targetBitrate = 900000; + break; + case 2: + maxSize = 848.0f; + targetBitrate = 1100000; + break; + case 3: + default: + targetBitrate = 1600000; + maxSize = 1280.0f; + break; + } + float scale = originalWidth > originalHeight ? maxSize / originalWidth : maxSize / originalHeight; + resultWidth = Math.round(originalWidth * scale / 2) * 2; + resultHeight = Math.round(originalHeight * scale / 2) * 2; + if (bitrate != 0) { + bitrate = Math.min(targetBitrate, (int) (originalBitrate / scale)); + videoFramesSize = (long) (bitrate / 8 * videoDuration / 1000); + } + } + } + + private void showQualityView(final boolean show) { + if (show) { + previousCompression = selectedCompression; + } + if (qualityChooseViewAnimation != null) { + qualityChooseViewAnimation.cancel(); + } + qualityChooseViewAnimation = new AnimatorSet(); + if (show) { + qualityChooseView.setTag(1); + qualityChooseViewAnimation.playTogether( + ObjectAnimator.ofFloat(pickerView, "translationY", 0, AndroidUtilities.dp(152)), + ObjectAnimator.ofFloat(videoTimelineViewContainer, "translationY", 0, AndroidUtilities.dp(152)), + ObjectAnimator.ofFloat(bottomLayout, "translationY", -AndroidUtilities.dp(48), AndroidUtilities.dp(104)) + ); + } else { + qualityChooseView.setTag(null); + qualityChooseViewAnimation.playTogether( + ObjectAnimator.ofFloat(qualityChooseView, "translationY", 0, AndroidUtilities.dp(166)), + ObjectAnimator.ofFloat(qualityPicker, "translationY", 0, AndroidUtilities.dp(166)), + ObjectAnimator.ofFloat(bottomLayout, "translationY", -AndroidUtilities.dp(48), AndroidUtilities.dp(118)) + ); + } + qualityChooseViewAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (!animation.equals(qualityChooseViewAnimation)) { + return; + } + qualityChooseViewAnimation = new AnimatorSet(); + if (show) { + qualityChooseView.setVisibility(View.VISIBLE); + qualityPicker.setVisibility(View.VISIBLE); + qualityChooseViewAnimation.playTogether( + ObjectAnimator.ofFloat(qualityChooseView, "translationY", 0), + ObjectAnimator.ofFloat(qualityPicker, "translationY", 0), + ObjectAnimator.ofFloat(bottomLayout, "translationY", -AndroidUtilities.dp(48)) + ); + } else { + qualityChooseView.setVisibility(View.INVISIBLE); + qualityPicker.setVisibility(View.INVISIBLE); + qualityChooseViewAnimation.playTogether( + ObjectAnimator.ofFloat(pickerView, "translationY", 0), + ObjectAnimator.ofFloat(videoTimelineViewContainer, "translationY", 0), + ObjectAnimator.ofFloat(bottomLayout, "translationY", -AndroidUtilities.dp(48)) + ); + } + qualityChooseViewAnimation.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (animation.equals(qualityChooseViewAnimation)) { + qualityChooseViewAnimation = null; + } + } + }); + qualityChooseViewAnimation.setDuration(200); + qualityChooseViewAnimation.setInterpolator(new AccelerateInterpolator()); + qualityChooseViewAnimation.start(); + } + + @Override + public void onAnimationCancel(Animator animation) { + qualityChooseViewAnimation = null; + } + }); + qualityChooseViewAnimation.setDuration(200); + qualityChooseViewAnimation.setInterpolator(new DecelerateInterpolator()); + qualityChooseViewAnimation.start(); + } + + private boolean processOpenVideo(String videoPath) { + try { + videoPreviewMessageObject = null; + compressItem.setVisibility(View.GONE); + muteVideo = false; + videoTimelineView.setVideoPath(videoPath); + compressionsCount = -1; + File file = new File(videoPath); + originalSize = file.length(); + + IsoFile isoFile = new IsoFile(videoPath); + List boxes = Path.getPaths(isoFile, "/moov/trak/"); + TrackHeaderBox trackHeaderBox = null; + boolean isAvc = true; + boolean isMp4A = true; + + Box boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/mp4a/"); + if (boxTest == null) { + isMp4A = false; + } + + if (!isMp4A) { + return false; + } + + boxTest = Path.getPath(isoFile, "/moov/trak/mdia/minf/stbl/stsd/avc1/"); + if (boxTest == null) { + isAvc = false; + } + + for (int b = 0; b < boxes.size(); b++) { + Box box = boxes.get(b); + TrackBox trackBox = (TrackBox) box; + long sampleSizes = 0; + long trackBitrate = 0; + try { + MediaBox mediaBox = trackBox.getMediaBox(); + MediaHeaderBox mediaHeaderBox = mediaBox.getMediaHeaderBox(); + SampleSizeBox sampleSizeBox = mediaBox.getMediaInformationBox().getSampleTableBox().getSampleSizeBox(); + long[] sizes = sampleSizeBox.getSampleSizes(); + for (int a = 0; a < sizes.length; a++) { + sampleSizes += sizes[a]; + } + videoDuration = (float) mediaHeaderBox.getDuration() / (float) mediaHeaderBox.getTimescale(); + trackBitrate = (int) (sampleSizes * 8 / videoDuration); + } catch (Exception e) { + FileLog.e(e); + } + TrackHeaderBox headerBox = trackBox.getTrackHeaderBox(); + if (headerBox.getWidth() != 0 && headerBox.getHeight() != 0) { + trackHeaderBox = headerBox; + originalBitrate = bitrate = (int) (trackBitrate / 100000 * 100000); + if (bitrate > 900000) { + bitrate = 900000; + } + videoFramesSize += sampleSizes; + } else { + audioFramesSize += sampleSizes; + } + } + if (trackHeaderBox == null) { + return false; + } + + Matrix matrix = trackHeaderBox.getMatrix(); + if (matrix.equals(Matrix.ROTATE_90)) { + rotationValue = 90; + } else if (matrix.equals(Matrix.ROTATE_180)) { + rotationValue = 180; + } else if (matrix.equals(Matrix.ROTATE_270)) { + rotationValue = 270; + } + resultWidth = originalWidth = (int) trackHeaderBox.getWidth(); + resultHeight = originalHeight = (int) trackHeaderBox.getHeight(); + + videoDuration *= 1000; + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + selectedCompression = preferences.getInt("compress_video2", 1); + if (originalWidth > 1280 || originalHeight > 1280) { + compressionsCount = 5; + } else if (originalWidth > 848 || originalHeight > 848) { + compressionsCount = 4; + } else if (originalWidth > 640 || originalHeight > 640) { + compressionsCount = 3; + } else if (originalWidth > 480 || originalHeight > 480) { + compressionsCount = 2; + } else { + compressionsCount = 1; + } + updateWidthHeightBitrateForCompression(); + + if (!isAvc && (resultWidth == originalWidth || resultHeight == originalHeight)) { + return false; + } + } catch (Exception e) { + FileLog.e(e); + return false; + } + + compressItem.setVisibility(compressionsCount > 1 ? View.VISIBLE : View.GONE); + if (Build.VERSION.SDK_INT >= 16 && Build.VERSION.SDK_INT < 18 && compressItem.getVisibility() == View.VISIBLE) { + try { + MediaCodecInfo codecInfo = MediaController.selectCodec(MediaController.MIME_TYPE); + if (codecInfo == null) { + compressItem.setVisibility(View.GONE); + } else { + String name = codecInfo.getName(); + if (name.equals("OMX.google.h264.encoder") || + name.equals("OMX.ST.VFM.H264Enc") || + name.equals("OMX.Exynos.avc.enc") || + name.equals("OMX.MARVELL.VIDEO.HW.CODA7542ENCODER") || + name.equals("OMX.MARVELL.VIDEO.H264ENCODER") || + name.equals("OMX.k3.video.encoder.avc") || + name.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { + compressItem.setVisibility(View.GONE); + } else { + if (MediaController.selectColorFormat(codecInfo, MediaController.MIME_TYPE) == 0) { + compressItem.setVisibility(View.GONE); + } + } + } + } catch (Exception e) { + compressItem.setVisibility(View.GONE); + FileLog.e(e); + } + } + + updateVideoInfo(); + updateMuteButton(); + + return true; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java index 03d70443827..f3d4c6e8c43 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PopupNotificationActivity.java @@ -3,14 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; @@ -34,7 +33,9 @@ import android.view.WindowManager; import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; +import android.widget.LinearLayout; import android.widget.RelativeLayout; +import android.widget.ScrollView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -55,12 +56,16 @@ import org.telegram.messenger.MessageObject; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.Components.ChatActivityEnterView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.PlayingGameDrawable; import org.telegram.ui.Components.PopupAudioView; import org.telegram.ui.Components.RecordStatusDrawable; +import org.telegram.ui.Components.SendingFileDrawable; import org.telegram.ui.Components.SizeNotifierFrameLayout; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.TypingDotsDrawable; @@ -89,6 +94,8 @@ public class PopupNotificationActivity extends Activity implements NotificationC private VelocityTracker velocityTracker = null; private TypingDotsDrawable typingDotsDrawable; private RecordStatusDrawable recordStatusDrawable; + private SendingFileDrawable sendingFileDrawable; + private PlayingGameDrawable playingGameDrawable; private int classGuid; private TLRPC.User currentUser; @@ -131,7 +138,7 @@ public boolean onTouchEvent(MotionEvent ev) { @Override public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - ((PopupNotificationActivity)getContext()).onTouchEventMy(null); + ((PopupNotificationActivity) getContext()).onTouchEventMy(null); super.requestDisallowInterceptTouchEvent(disallowIntercept); } } @@ -162,7 +169,7 @@ protected void onAnimationEnd() { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Theme.loadRecources(this); + Theme.createChatResources(this, false); int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { @@ -180,6 +187,8 @@ protected void onCreate(Bundle savedInstanceState) { typingDotsDrawable = new TypingDotsDrawable(); recordStatusDrawable = new RecordStatusDrawable(); + sendingFileDrawable = new SendingFileDrawable(); + playingGameDrawable = new PlayingGameDrawable(); SizeNotifierFrameLayout contentView = new SizeNotifierFrameLayout(this) { @Override @@ -284,7 +293,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { contentView.addView(relativeLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); popupContainer = new RelativeLayout(this); - popupContainer.setBackgroundColor(0xffffffff); + popupContainer.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); relativeLayout.addView(popupContainer, LayoutHelper.createRelative(LayoutHelper.MATCH_PARENT, 240, 12, 0, 12, 0, RelativeLayout.CENTER_IN_PARENT)); if (chatActivityEnterView != null) { @@ -342,6 +351,16 @@ public void onWindowSizeChanged(int size) { public void onStickersTab(boolean opened) { } + + @Override + public void didPressedAttachButton() { + + } + + @Override + public void needStartRecordVideo(int state) { + + } }); messageContainer = new FrameLayoutTouch(this); @@ -349,17 +368,21 @@ public void onStickersTab(boolean opened) { actionBar = new ActionBar(this); actionBar.setOccupyStatusBar(false); - actionBar.setBackButtonImage(R.drawable.ic_ab_back); - actionBar.setBackgroundColor(Theme.ACTION_BAR_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_SELECTOR_COLOR); + actionBar.setBackButtonImage(R.drawable.ic_close_white); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_actionBarDefault)); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarDefaultSelector), false); popupContainer.addView(actionBar); ViewGroup.LayoutParams layoutParams = actionBar.getLayoutParams(); layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; actionBar.setLayoutParams(layoutParams); ActionBarMenu menu = actionBar.createMenu(); - View view = menu.addItemResource(2, R.layout.popup_count_layout); - countText = (TextView) view.findViewById(R.id.count_text); + ActionBarMenuItem view = menu.addItemWithWidth(2, 0, AndroidUtilities.dp(56)); + countText = new TextView(this); + countText.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); + countText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + countText.setGravity(Gravity.CENTER); + view.addView(countText, LayoutHelper.createFrame(56, LayoutHelper.MATCH_PARENT)); avatarContainer = new FrameLayout(this); avatarContainer.setPadding(AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4), 0); @@ -382,7 +405,7 @@ public void onStickersTab(boolean opened) { avatarImageView.setLayoutParams(layoutParams2); nameTextView = new TextView(this); - nameTextView.setTextColor(0xffffffff); + nameTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); nameTextView.setLines(1); nameTextView.setMaxLines(1); @@ -400,7 +423,7 @@ public void onStickersTab(boolean opened) { nameTextView.setLayoutParams(layoutParams2); onlineTextView = new TextView(this); - onlineTextView.setTextColor(Theme.ACTION_BAR_SUBTITLE_COLOR); + onlineTextView.setTextColor(Theme.getColor(Theme.key_actionBarDefaultSubtitle)); onlineTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); onlineTextView.setLines(1); onlineTextView.setMaxLines(1); @@ -469,7 +492,7 @@ public void onClick(DialogInterface dialog, int which) { intent.setData(Uri.parse("package:" + ApplicationLoader.applicationContext.getPackageName())); startActivity(intent); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -523,7 +546,7 @@ public boolean onTouchEventMy(MotionEvent motionEvent) { moveStartX = motionEvent.getX(); } else if (motionEvent != null && motionEvent.getAction() == MotionEvent.ACTION_MOVE) { float x = motionEvent.getX(); - int diff = (int)(x - moveStartX); + int diff = (int) (x - moveStartX); if (moveStartX != -1 && !startedMoving) { if (Math.abs(diff) > AndroidUtilities.dp(10)) { startedMoving = true; @@ -552,7 +575,7 @@ public boolean onTouchEventMy(MotionEvent motionEvent) { } else if (motionEvent == null || motionEvent.getAction() == MotionEvent.ACTION_UP || motionEvent.getAction() == MotionEvent.ACTION_CANCEL) { if (motionEvent != null && startedMoving) { FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) centerView.getLayoutParams(); - int diff = (int)(motionEvent.getX() - moveStartX); + int diff = (int) (motionEvent.getX() - moveStartX); int width = AndroidUtilities.displaySize.x - AndroidUtilities.dp(24); int moveDiff = 0; int forceMove = 0; @@ -600,7 +623,7 @@ public void run() { }; } if (moveDiff != 0) { - int time = (int)(Math.abs((float)moveDiff / (float)width) * 200); + int time = (int) (Math.abs((float) moveDiff / (float) width) * 200); TranslateAnimation animation = new TranslateAnimation(0, moveDiff, 0, 0); animation.setDuration(time); centerView.startAnimation(animation); @@ -672,7 +695,23 @@ private ViewGroup getViewForMessage(int num, boolean applyOffset) { imageViews.remove(0); } else { view = new FrameLayoutAnimationListener(this); - view.addView(getLayoutInflater().inflate(R.layout.popup_image_layout, null)); + + FrameLayout frameLayout = new FrameLayout(this); + frameLayout.setPadding(AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10)); + frameLayout.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + view.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + BackupImageView backupImageView = new BackupImageView(this); + backupImageView.setTag(311); + frameLayout.addView(backupImageView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + TextView textView = new TextView(this); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setGravity(Gravity.CENTER); + textView.setTag(312); + frameLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + view.setTag(2); view.setOnClickListener(new View.OnClickListener() { @@ -683,8 +722,8 @@ public void onClick(View v) { }); } - TextView messageText = (TextView)view.findViewById(R.id.message_text); - BackupImageView imageView = (BackupImageView) view.findViewById(R.id.message_image); + TextView messageText = (TextView) view.findViewWithTag(312); + BackupImageView imageView = (BackupImageView) view.findViewWithTag(311); imageView.setAspectFit(true); if (messageObject.type == 1) { @@ -724,7 +763,7 @@ public void onClick(View v) { imageView.setVisibility(View.VISIBLE); double lat = messageObject.messageOwner.media.geo.lat; double lon = messageObject.messageOwner.media.geo._long; - String currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=13&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int)Math.ceil(AndroidUtilities.density)), lat, lon); + String currentUrl = String.format(Locale.US, "https://maps.googleapis.com/maps/api/staticmap?center=%f,%f&zoom=13&size=100x100&maptype=roadmap&scale=%d&markers=color:red|size:big|%f,%f&sensor=false", lat, lon, Math.min(2, (int) Math.ceil(AndroidUtilities.density)), lat, lon); imageView.setImage(currentUrl, null, null); } } else if (messageObject.type == 2) { @@ -732,10 +771,21 @@ public void onClick(View v) { if (audioViews.size() > 0) { view = audioViews.get(0); audioViews.remove(0); - cell = (PopupAudioView)view.findViewWithTag(300); + cell = (PopupAudioView) view.findViewWithTag(300); } else { view = new FrameLayoutAnimationListener(this); - view.addView(getLayoutInflater().inflate(R.layout.popup_audio_layout, null)); + + FrameLayout frameLayout = new FrameLayout(this); + frameLayout.setPadding(AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10)); + frameLayout.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + view.addView(frameLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + FrameLayout frameLayout1 = new FrameLayout(this); + frameLayout.addView(frameLayout1, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 20, 0, 20, 0)); + cell = new PopupAudioView(this); + cell.setTag(300); + frameLayout1.addView(cell); + view.setTag(3); view.setOnClickListener(new View.OnClickListener() { @@ -744,11 +794,6 @@ public void onClick(View v) { openCurrentMessage(); } }); - - ViewGroup audioContainer = (ViewGroup)view.findViewById(R.id.audio_container); - cell = new PopupAudioView(this); - cell.setTag(300); - audioContainer.addView(cell); } cell.setMessageObject(messageObject); @@ -761,19 +806,34 @@ public void onClick(View v) { textViews.remove(0); } else { view = new FrameLayoutAnimationListener(this); - view.addView(getLayoutInflater().inflate(R.layout.popup_text_layout, null)); - view.setTag(1); - View textContainer = view.findViewById(R.id.text_container); - textContainer.setOnClickListener(new View.OnClickListener() { + ScrollView scrollView = new ScrollView(this); + scrollView.setFillViewport(true); + view.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + LinearLayout linearLayout = new LinearLayout(this); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + linearLayout.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + scrollView.addView(linearLayout, LayoutHelper.createScroll(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL)); + linearLayout.setPadding(AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10)); + linearLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { openCurrentMessage(); } }); + + TextView textView = new TextView(this); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTag(301); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setLinkTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textView.setGravity(Gravity.CENTER); + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); + + view.setTag(1); } - TextView messageText = (TextView)view.findViewById(R.id.message_text); - messageText.setTag(301); + TextView messageText = (TextView) view.findViewWithTag(301); messageText.setTextSize(TypedValue.COMPLEX_UNIT_SP, MessagesController.getInstance().fontSize); messageText.setText(messageObject.messageText); } @@ -806,7 +866,7 @@ private void reuseView(ViewGroup view) { if (view == null) { return; } - int tag = (Integer)view.getTag(); + int tag = (Integer) view.getTag(); view.setVisibility(View.GONE); if (tag == 1) { textViews.add(view); @@ -918,13 +978,13 @@ private void handleIntent(Intent intent) { KeyguardManager km = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); if (km.inKeyguardRestrictedInputMode() || !ApplicationLoader.isScreenOn) { getWindow().addFlags( - WindowManager.LayoutParams.FLAG_DIM_BEHIND | + WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); } else { getWindow().addFlags( - WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); @@ -973,15 +1033,15 @@ private void openCurrentMessage() { } Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); long dialog_id = currentMessageObject.getDialogId(); - if ((int)dialog_id != 0) { - int lower_id = (int)dialog_id; + if ((int) dialog_id != 0) { + int lower_id = (int) dialog_id; if (lower_id < 0) { intent.putExtra("chatId", -lower_id); } else { intent.putExtra("userId", lower_id); } } else { - intent.putExtra("encId", (int)(dialog_id >> 32)); + intent.putExtra("encId", (int) (dialog_id >> 32)); } intent.setAction("com.tmessages.openchat" + Math.random() + Integer.MAX_VALUE); intent.setFlags(0x00008000); @@ -998,8 +1058,8 @@ private void updateInterfaceForCurrentMessage(int move) { currentUser = null; long dialog_id = currentMessageObject.getDialogId(); chatActivityEnterView.setDialogId(dialog_id); - if ((int)dialog_id != 0) { - int lower_id = (int)dialog_id; + if ((int) dialog_id != 0) { + int lower_id = (int) dialog_id; if (lower_id > 0) { currentUser = MessagesController.getInstance().getUser(lower_id); } else { @@ -1007,7 +1067,7 @@ private void updateInterfaceForCurrentMessage(int move) { currentUser = MessagesController.getInstance().getUser(currentMessageObject.messageOwner.from_id); } } else { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int)(dialog_id >> 32)); + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (dialog_id >> 32)); currentUser = MessagesController.getInstance().getUser(encryptedChat.user_id); } @@ -1018,7 +1078,7 @@ private void updateInterfaceForCurrentMessage(int move) { nameTextView.setCompoundDrawablePadding(0); } else if (currentUser != null) { nameTextView.setText(UserObject.getUserName(currentUser)); - if ((int)dialog_id == 0) { + if ((int) dialog_id == 0) { nameTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_lock_white, 0, 0, 0); nameTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); } else { @@ -1106,20 +1166,40 @@ private void setTypingAnimation(boolean start) { onlineTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); typingDotsDrawable.start(); recordStatusDrawable.stop(); + sendingFileDrawable.stop(); + playingGameDrawable.stop(); } else if (type == 1) { onlineTextView.setCompoundDrawablesWithIntrinsicBounds(recordStatusDrawable, null, null, null); onlineTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); recordStatusDrawable.start(); typingDotsDrawable.stop(); + sendingFileDrawable.stop(); + playingGameDrawable.stop(); + } else if (type == 2) { + onlineTextView.setCompoundDrawablesWithIntrinsicBounds(sendingFileDrawable, null, null, null); + onlineTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); + sendingFileDrawable.start(); + typingDotsDrawable.stop(); + recordStatusDrawable.stop(); + playingGameDrawable.stop(); + } else if (type == 3) { + onlineTextView.setCompoundDrawablesWithIntrinsicBounds(playingGameDrawable, null, null, null); + onlineTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); + playingGameDrawable.start(); + typingDotsDrawable.stop(); + recordStatusDrawable.stop(); + sendingFileDrawable.stop(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else { onlineTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); onlineTextView.setCompoundDrawablePadding(0); typingDotsDrawable.stop(); recordStatusDrawable.stop(); + recordStatusDrawable.stop(); + sendingFileDrawable.stop(); } } @@ -1166,7 +1246,7 @@ public void didReceivedNotification(int id, Object... args) { if (currentMessageObject == null) { return; } - int updateMask = (Integer)args[0]; + int updateMask = (Integer) args[0]; if ((updateMask & MessagesController.UPDATE_MASK_NAME) != 0 || (updateMask & MessagesController.UPDATE_MASK_STATUS) != 0 || (updateMask & MessagesController.UPDATE_MASK_CHAT_NAME) != 0 || (updateMask & MessagesController.UPDATE_MASK_CHAT_MEMBERS) != 0) { updateSubtitle(); } @@ -1180,13 +1260,13 @@ public void didReceivedNotification(int id, Object... args) { } } } else if (id == NotificationCenter.audioDidReset) { - Integer mid = (Integer)args[0]; + Integer mid = (Integer) args[0]; if (messageContainer != null) { int count = messageContainer.getChildCount(); for (int a = 0; a < count; a++) { View view = messageContainer.getChildAt(a); - if ((Integer)view.getTag() == 3) { - PopupAudioView cell = (PopupAudioView)view.findViewWithTag(300); + if ((Integer) view.getTag() == 3) { + PopupAudioView cell = (PopupAudioView) view.findViewWithTag(300); if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) { cell.updateButtonState(); break; @@ -1195,13 +1275,13 @@ public void didReceivedNotification(int id, Object... args) { } } } else if (id == NotificationCenter.audioProgressDidChanged) { - Integer mid = (Integer)args[0]; + Integer mid = (Integer) args[0]; if (messageContainer != null) { int count = messageContainer.getChildCount(); for (int a = 0; a < count; a++) { View view = messageContainer.getChildAt(a); - if ((Integer)view.getTag() == 3) { - PopupAudioView cell = (PopupAudioView)view.findViewWithTag(300); + if ((Integer) view.getTag() == 3) { + PopupAudioView cell = (PopupAudioView) view.findViewWithTag(300); if (cell.getMessageObject() != null && cell.getMessageObject().getId() == mid) { cell.updateProgress(); break; @@ -1214,8 +1294,8 @@ public void didReceivedNotification(int id, Object... args) { int count = messageContainer.getChildCount(); for (int a = 0; a < count; a++) { View view = messageContainer.getChildAt(a); - if ((Integer)view.getTag() == 1) { - TextView textView = (TextView)view.findViewWithTag(301); + if ((Integer) view.getTag() == 1) { + TextView textView = (TextView) view.findViewWithTag(301); if (textView != null) { textView.invalidate(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyControlActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyControlActivity.java index 79377a65662..040457f0ccc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyControlActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyControlActivity.java @@ -3,27 +3,22 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; import android.os.Bundle; import android.text.Spannable; import android.text.method.LinkMovementMethod; -import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; @@ -34,19 +29,24 @@ import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.RadioCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; @@ -54,13 +54,14 @@ public class PrivacyControlActivity extends BaseFragment implements Notification private ListAdapter listAdapter; private View doneButton; + private RecyclerListView listView; - private int currentType = 0; + private int rulesType; private ArrayList currentPlus; private ArrayList currentMinus; private int lastCheckedType = -1; - private boolean isGroup; + private int currentType; private boolean enableAnimation; @@ -83,15 +84,15 @@ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event try { return super.onTouchEvent(widget, buffer, event); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } } - public PrivacyControlActivity(boolean group) { + public PrivacyControlActivity(int type) { super(); - isGroup = group; + rulesType = type; } @Override @@ -113,7 +114,9 @@ public void onFragmentDestroy() { public View createView(Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - if (isGroup) { + if (rulesType == 2) { + actionBar.setTitle(LocaleController.getString("Calls", R.string.Calls)); + } else if (rulesType == 1) { actionBar.setTitle(LocaleController.getString("GroupsAndChannels", R.string.GroupsAndChannels)); } else { actionBar.setTitle(LocaleController.getString("PrivacyLastSeen", R.string.PrivacyLastSeen)); @@ -128,12 +131,12 @@ public void onItemClick(int id) { return; } - if (currentType != 0 && !isGroup) { + if (currentType != 0 && rulesType == 0) { final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); boolean showed = preferences.getBoolean("privacyAlertShowed", false); if (!showed) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - if (isGroup) { + if (rulesType == 1) { builder.setMessage(LocaleController.getString("WhoCanAddMeInfo", R.string.WhoCanAddMeInfo)); } else { builder.setMessage(LocaleController.getString("CustomHelp", R.string.CustomHelp)); @@ -164,30 +167,23 @@ public void onClick(DialogInterface dialogInterface, int i) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); - frameLayout.addView(listView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - listView.setLayoutParams(layoutParams); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == nobodyRow || i == everybodyRow || i == myContactsRow) { + public void onItemClick(View view, final int position) { + if (position == nobodyRow || position == everybodyRow || position == myContactsRow) { int newType = currentType; - if (i == nobodyRow) { + if (position == nobodyRow) { newType = 1; - } else if (i == everybodyRow) { + } else if (position == everybodyRow) { newType = 0; - } else if (i == myContactsRow) { + } else if (position == myContactsRow) { newType = 2; } if (newType == currentType) { @@ -198,22 +194,22 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long lastCheckedType = currentType; currentType = newType; updateRows(); - } else if (i == neverShareRow || i == alwaysShareRow) { + } else if (position == neverShareRow || position == alwaysShareRow) { ArrayList createFromArray; - if (i == neverShareRow) { + if (position == neverShareRow) { createFromArray = currentMinus; } else { createFromArray = currentPlus; } if (createFromArray.isEmpty()) { Bundle args = new Bundle(); - args.putBoolean(i == neverShareRow ? "isNeverShare" : "isAlwaysShare", true); - args.putBoolean("isGroup", isGroup); + args.putBoolean(position == neverShareRow ? "isNeverShare" : "isAlwaysShare", true); + args.putBoolean("isGroup", rulesType != 0); GroupCreateActivity fragment = new GroupCreateActivity(args); fragment.setDelegate(new GroupCreateActivity.GroupCreateActivityDelegate() { @Override public void didSelectUsers(ArrayList ids) { - if (i == neverShareRow) { + if (position == neverShareRow) { currentMinus = ids; for (int a = 0; a < currentMinus.size(); a++) { currentPlus.remove(currentMinus.get(a)); @@ -231,11 +227,11 @@ public void didSelectUsers(ArrayList ids) { }); presentFragment(fragment); } else { - PrivacyUsersActivity fragment = new PrivacyUsersActivity(createFromArray, isGroup, i == alwaysShareRow); + PrivacyUsersActivity fragment = new PrivacyUsersActivity(createFromArray, rulesType != 0, position == alwaysShareRow); fragment.setDelegate(new PrivacyUsersActivity.PrivacyActivityDelegate() { @Override public void didUpdatedUserList(ArrayList ids, boolean added) { - if (i == neverShareRow) { + if (position == neverShareRow) { currentMinus = ids; if (added) { for (int a = 0; a < currentMinus.size(); a++) { @@ -272,7 +268,9 @@ public void didReceivedNotification(int id, Object... args) { private void applyCurrentPrivacySettings() { TLRPC.TL_account_setPrivacy req = new TLRPC.TL_account_setPrivacy(); - if (isGroup) { + if (rulesType == 2) { + req.key = new TLRPC.TL_inputPrivacyKeyPhoneCall(); + } else if (rulesType == 1) { req.key = new TLRPC.TL_inputPrivacyKeyChatInvite(); } else { req.key = new TLRPC.TL_inputPrivacyKeyStatusTimestamp(); @@ -310,15 +308,15 @@ private void applyCurrentPrivacySettings() { } else if (currentType == 2) { req.rules.add(new TLRPC.TL_inputPrivacyValueAllowContacts()); } - ProgressDialog progressDialog = null; + AlertDialog progressDialog = null; if (getParentActivity() != null) { - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); progressDialog.show(); } - final ProgressDialog progressDialogFinal = progressDialog; + final AlertDialog progressDialogFinal = progressDialog; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @Override public void run(final TLObject response, final TLRPC.TL_error error) { @@ -330,13 +328,13 @@ public void run() { progressDialogFinal.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (error == null) { finishFragment(); TLRPC.TL_account_privacyRules rules = (TLRPC.TL_account_privacyRules) response; MessagesController.getInstance().putUsers(rules.users, false); - ContactsController.getInstance().setPrivacyRules(rules.rules, isGroup); + ContactsController.getInstance().setPrivacyRules(rules.rules, rulesType); } else { showErrorAlert(); } @@ -360,8 +358,8 @@ private void showErrorAlert() { private void checkPrivacy() { currentPlus = new ArrayList<>(); currentMinus = new ArrayList<>(); - ArrayList privacyRules = ContactsController.getInstance().getPrivacyRules(isGroup); - if (privacyRules.size() == 0) { + ArrayList privacyRules = ContactsController.getInstance().getPrivacyRules(rulesType); + if (privacyRules == null || privacyRules.size() == 0) { currentType = 1; return; } @@ -398,7 +396,7 @@ private void updateRows() { sectionRow = rowCount++; everybodyRow = rowCount++; myContactsRow = rowCount++; - if (isGroup) { + if (rulesType != 0 && rulesType != 2) { nobodyRow = -1; } else { nobodyRow = rowCount++; @@ -428,7 +426,7 @@ public void onResume() { enableAnimation = false; } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; public ListAdapter(Context context) { @@ -436,151 +434,172 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == nobodyRow || position == everybodyRow || position == myContactsRow || position == neverShareRow || position == alwaysShareRow; } @Override - public boolean isEnabled(int i) { - return i == nobodyRow || i == everybodyRow || i == myContactsRow || i == neverShareRow || i == alwaysShareRow; - } - - @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + break; + case 2: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + default: + view = new RadioCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == alwaysShareRow) { - String value; - if (currentPlus.size() != 0) { - value = LocaleController.formatPluralString("Users", currentPlus.size()); - } else { - value = LocaleController.getString("EmpryUsersPlaceholder", R.string.EmpryUsersPlaceholder); - } - if (isGroup) { - textCell.setTextAndValue(LocaleController.getString("AlwaysAllow", R.string.AlwaysAllow), value, neverShareRow != -1); - } else { - textCell.setTextAndValue(LocaleController.getString("AlwaysShareWith", R.string.AlwaysShareWith), value, neverShareRow != -1); - } - } else if (i == neverShareRow) { - String value; - if (currentMinus.size() != 0) { - value = LocaleController.formatPluralString("Users", currentMinus.size()); - } else { - value = LocaleController.getString("EmpryUsersPlaceholder", R.string.EmpryUsersPlaceholder); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == alwaysShareRow) { + String value; + if (currentPlus.size() != 0) { + value = LocaleController.formatPluralString("Users", currentPlus.size()); + } else { + value = LocaleController.getString("EmpryUsersPlaceholder", R.string.EmpryUsersPlaceholder); + } + if (rulesType != 0) { + textCell.setTextAndValue(LocaleController.getString("AlwaysAllow", R.string.AlwaysAllow), value, neverShareRow != -1); + } else { + textCell.setTextAndValue(LocaleController.getString("AlwaysShareWith", R.string.AlwaysShareWith), value, neverShareRow != -1); + } + } else if (position == neverShareRow) { + String value; + if (currentMinus.size() != 0) { + value = LocaleController.formatPluralString("Users", currentMinus.size()); + } else { + value = LocaleController.getString("EmpryUsersPlaceholder", R.string.EmpryUsersPlaceholder); + } + if (rulesType != 0) { + textCell.setTextAndValue(LocaleController.getString("NeverAllow", R.string.NeverAllow), value, false); + } else { + textCell.setTextAndValue(LocaleController.getString("NeverShareWith", R.string.NeverShareWith), value, false); + } } - if (isGroup) { - textCell.setTextAndValue(LocaleController.getString("NeverAllow", R.string.NeverAllow), value, false); - } else { - textCell.setTextAndValue(LocaleController.getString("NeverShareWith", R.string.NeverShareWith), value, false); + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == detailRow) { + if (rulesType == 2) { + privacyCell.setText(LocaleController.getString("WhoCanCallMeInfo", R.string.WhoCanCallMeInfo)); + } else if (rulesType == 1) { + privacyCell.setText(LocaleController.getString("WhoCanAddMeInfo", R.string.WhoCanAddMeInfo)); + } else { + privacyCell.setText(LocaleController.getString("CustomHelp", R.string.CustomHelp)); + } + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == shareDetailRow) { + if (rulesType == 2) { + privacyCell.setText(LocaleController.getString("CustomCallInfo", R.string.CustomCallInfo)); + } else if (rulesType == 1) { + privacyCell.setText(LocaleController.getString("CustomShareInfo", R.string.CustomShareInfo)); + } else { + privacyCell.setText(LocaleController.getString("CustomShareSettingsHelp", R.string.CustomShareSettingsHelp)); + } + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - view.setBackgroundColor(0xffffffff); - } - if (i == detailRow) { - if (isGroup) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("WhoCanAddMeInfo", R.string.WhoCanAddMeInfo)); - } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("CustomHelp", R.string.CustomHelp)); + break; + case 2: + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == sectionRow) { + if (rulesType == 2) { + headerCell.setText(LocaleController.getString("WhoCanCallMe", R.string.WhoCanCallMe)); + } else if (rulesType == 1) { + headerCell.setText(LocaleController.getString("WhoCanAddMe", R.string.WhoCanAddMe)); + } else { + headerCell.setText(LocaleController.getString("LastSeenTitle", R.string.LastSeenTitle)); + } + } else if (position == shareSectionRow) { + headerCell.setText(LocaleController.getString("AddExceptions", R.string.AddExceptions)); } - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == shareDetailRow) { - if (isGroup) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("CustomShareInfo", R.string.CustomShareInfo)); - } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("CustomShareSettingsHelp", R.string.CustomShareSettingsHelp)); + break; + case 3: + RadioCell radioCell = (RadioCell) holder.itemView; + int checkedType = 0; + if (position == everybodyRow) { + radioCell.setText(LocaleController.getString("LastSeenEverybody", R.string.LastSeenEverybody), lastCheckedType == 0, true); + checkedType = 0; + } else if (position == myContactsRow) { + radioCell.setText(LocaleController.getString("LastSeenContacts", R.string.LastSeenContacts), lastCheckedType == 2, nobodyRow != -1); + checkedType = 2; + } else if (position == nobodyRow) { + radioCell.setText(LocaleController.getString("LastSeenNobody", R.string.LastSeenNobody), lastCheckedType == 1, false); + checkedType = 1; } - view.setBackgroundResource(R.drawable.greydivider_bottom); - } - } else if (type == 2) { - if (view == null) { - view = new HeaderCell(mContext); - view.setBackgroundColor(0xffffffff); - } - if (i == sectionRow) { - if (isGroup) { - ((HeaderCell) view).setText(LocaleController.getString("WhoCanAddMe", R.string.WhoCanAddMe)); - } else { - ((HeaderCell) view).setText(LocaleController.getString("LastSeenTitle", R.string.LastSeenTitle)); + if (lastCheckedType == checkedType) { + radioCell.setChecked(false, enableAnimation); + } else if (currentType == checkedType) { + radioCell.setChecked(true, enableAnimation); } - } else if (i == shareSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("AddExceptions", R.string.AddExceptions)); - } - } else if (type == 3) { - if (view == null) { - view = new RadioCell(mContext); - view.setBackgroundColor(0xffffffff); - } - RadioCell textCell = (RadioCell) view; - int checkedType = 0; - if (i == everybodyRow) { - textCell.setText(LocaleController.getString("LastSeenEverybody", R.string.LastSeenEverybody), lastCheckedType == 0, true); - checkedType = 0; - } else if (i == myContactsRow) { - textCell.setText(LocaleController.getString("LastSeenContacts", R.string.LastSeenContacts), lastCheckedType == 2, nobodyRow != -1); - checkedType = 2; - } else if (i == nobodyRow) { - textCell.setText(LocaleController.getString("LastSeenNobody", R.string.LastSeenNobody), lastCheckedType == 1, false); - checkedType = 1; - } - if (lastCheckedType == checkedType) { - textCell.setChecked(false, enableAnimation); - } else if (currentType == checkedType) { - textCell.setChecked(true, enableAnimation); - } + break; } - return view; } @Override - public int getItemViewType(int i) { - if (i == alwaysShareRow || i == neverShareRow) { + public int getItemViewType(int position) { + if (position == alwaysShareRow || position == neverShareRow) { return 0; - } else if (i == shareDetailRow || i == detailRow) { + } else if (position == shareDetailRow || position == detailRow) { return 1; - } else if (i == sectionRow || i == shareSectionRow) { + } else if (position == sectionRow || position == shareSectionRow) { return 2; - } else if (i == everybodyRow || i == myContactsRow || i == nobodyRow) { + } else if (position == everybodyRow || position == myContactsRow || position == nobodyRow) { return 3; } return 0; } + } - @Override - public int getViewTypeCount() { - return 4; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, HeaderCell.class, RadioCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java index b4d5fa086d3..503aea66bf3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacySettingsActivity.java @@ -3,21 +3,17 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; @@ -27,29 +23,36 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; public class PrivacySettingsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; + private RecyclerListView listView; private int privacySectionRow; private int blockedRow; private int lastSeenRow; + private int callsRow; private int groupsRow; private int groupsDetailRow; private int securitySectionRow; @@ -75,6 +78,11 @@ public boolean onFragmentCreate() { privacySectionRow = rowCount++; blockedRow = rowCount++; lastSeenRow = rowCount++; + if (MessagesController.getInstance().callsEnabled) { + callsRow = rowCount++; + } else { + callsRow = -1; + } groupsRow = rowCount++; groupsDetailRow = rowCount++; securitySectionRow = rowCount++; @@ -124,23 +132,24 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == blockedRow) { + public void onItemClick(View view, int position) { + if (!view.isEnabled()) { + return; + } + if (position == blockedRow) { presentFragment(new BlockedUsersActivity()); - } else if (i == sessionsRow) { + } else if (position == sessionsRow) { presentFragment(new SessionsActivity()); - } else if (i == deleteAccountRow) { + } else if (position == deleteAccountRow) { if (getParentActivity() == null) { return; } @@ -164,7 +173,7 @@ public void onClick(DialogInterface dialog, int which) { } else if (which == 3) { value = 365; } - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -182,7 +191,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (response instanceof TLRPC.TL_boolTrue) { ContactsController.getInstance().setDeleteAccountTTL(req.ttl.days); @@ -196,19 +205,21 @@ public void run() { }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); - } else if (i == lastSeenRow) { - presentFragment(new PrivacyControlActivity(false)); - } else if (i == groupsRow) { - presentFragment(new PrivacyControlActivity(true)); - } else if (i == passwordRow) { + } else if (position == lastSeenRow) { + presentFragment(new PrivacyControlActivity(0)); + } else if (position == callsRow) { + presentFragment(new PrivacyControlActivity(2)); + } else if (position == groupsRow) { + presentFragment(new PrivacyControlActivity(1)); + } else if (position == passwordRow) { presentFragment(new TwoStepVerificationActivity(0)); - } else if (i == passcodeRow) { + } else if (position == passcodeRow) { if (UserConfig.passcodeHash.length() > 0) { presentFragment(new PasscodeActivity(2)); } else { presentFragment(new PasscodeActivity(0)); } - } else if (i == secretWebpageRow) { + } else if (position == secretWebpageRow) { if (MessagesController.getInstance().secretWebpagePreview == 1) { MessagesController.getInstance().secretWebpagePreview = 0; } else { @@ -234,8 +245,8 @@ public void didReceivedNotification(int id, Object... args) { } } - private String formatRulesString(boolean isGroup) { - ArrayList privacyRules = ContactsController.getInstance().getPrivacyRules(isGroup); + private String formatRulesString(int rulesType) { + ArrayList privacyRules = ContactsController.getInstance().getPrivacyRules(rulesType); if (privacyRules.size() == 0) { return LocaleController.getString("LastSeenNobody", R.string.LastSeenNobody); } @@ -292,7 +303,8 @@ public void onResume() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -300,153 +312,180 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == passcodeRow || position == passwordRow || position == blockedRow || position == sessionsRow || position == secretWebpageRow || + position == groupsRow && !ContactsController.getInstance().getLoadingGroupInfo() || + position == lastSeenRow && !ContactsController.getInstance().getLoadingLastSeenInfo() || + position == callsRow && !ContactsController.getInstance().getLoadingCallsInfo() || + position == deleteAccountRow && !ContactsController.getInstance().getLoadingDeleteInfo(); } @Override - public boolean isEnabled(int i) { - return i == passcodeRow || i == passwordRow || i == blockedRow || i == sessionsRow || i == secretWebpageRow || - i == groupsRow && !ContactsController.getInstance().getLoadingGroupInfo() || - i == lastSeenRow && !ContactsController.getInstance().getLoadingLastSeenInfo() || - i == deleteAccountRow && !ContactsController.getInstance().getLoadingDeleteInfo(); - } - - @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + break; + case 2: + view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + default: + view = new TextCheckCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == blockedRow) { - textCell.setText(LocaleController.getString("BlockedUsers", R.string.BlockedUsers), true); - } else if (i == sessionsRow) { - textCell.setText(LocaleController.getString("SessionsTitle", R.string.SessionsTitle), false); - } else if (i == passwordRow) { - textCell.setText(LocaleController.getString("TwoStepVerification", R.string.TwoStepVerification), true); - } else if (i == passcodeRow) { - textCell.setText(LocaleController.getString("Passcode", R.string.Passcode), true); - } else if (i == lastSeenRow) { - String value; - if (ContactsController.getInstance().getLoadingLastSeenInfo()) { - value = LocaleController.getString("Loading", R.string.Loading); - } else { - value = formatRulesString(false); - } - textCell.setTextAndValue(LocaleController.getString("PrivacyLastSeen", R.string.PrivacyLastSeen), value, true); - } else if (i == groupsRow) { - String value; - if (ContactsController.getInstance().getLoadingGroupInfo()) { - value = LocaleController.getString("Loading", R.string.Loading); - } else { - value = formatRulesString(true); - } - textCell.setTextAndValue(LocaleController.getString("GroupsAndChannels", R.string.GroupsAndChannels), value, false); - } else if (i == deleteAccountRow) { - String value; - if (ContactsController.getInstance().getLoadingDeleteInfo()) { - value = LocaleController.getString("Loading", R.string.Loading); - } else { - int ttl = ContactsController.getInstance().getDeleteAccountTTL(); - if (ttl <= 182) { - value = LocaleController.formatPluralString("Months", ttl / 30); - } else if (ttl == 365) { - value = LocaleController.formatPluralString("Years", ttl / 365); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == blockedRow) { + textCell.setText(LocaleController.getString("BlockedUsers", R.string.BlockedUsers), true); + } else if (position == sessionsRow) { + textCell.setText(LocaleController.getString("SessionsTitle", R.string.SessionsTitle), false); + } else if (position == passwordRow) { + textCell.setText(LocaleController.getString("TwoStepVerification", R.string.TwoStepVerification), true); + } else if (position == passcodeRow) { + textCell.setText(LocaleController.getString("Passcode", R.string.Passcode), true); + } else if (position == lastSeenRow) { + String value; + if (ContactsController.getInstance().getLoadingLastSeenInfo()) { + value = LocaleController.getString("Loading", R.string.Loading); + } else { + value = formatRulesString(0); + } + textCell.setTextAndValue(LocaleController.getString("PrivacyLastSeen", R.string.PrivacyLastSeen), value, true); + } else if (position == callsRow) { + String value; + if (ContactsController.getInstance().getLoadingCallsInfo()) { + value = LocaleController.getString("Loading", R.string.Loading); + } else { + value = formatRulesString(2); + } + textCell.setTextAndValue(LocaleController.getString("Calls", R.string.Calls), value, true); + } else if (position == groupsRow) { + String value; + if (ContactsController.getInstance().getLoadingGroupInfo()) { + value = LocaleController.getString("Loading", R.string.Loading); } else { - value = LocaleController.formatPluralString("Days", ttl); + value = formatRulesString(1); } + textCell.setTextAndValue(LocaleController.getString("GroupsAndChannels", R.string.GroupsAndChannels), value, false); + } else if (position == deleteAccountRow) { + String value; + if (ContactsController.getInstance().getLoadingDeleteInfo()) { + value = LocaleController.getString("Loading", R.string.Loading); + } else { + int ttl = ContactsController.getInstance().getDeleteAccountTTL(); + if (ttl <= 182) { + value = LocaleController.formatPluralString("Months", ttl / 30); + } else if (ttl == 365) { + value = LocaleController.formatPluralString("Years", ttl / 365); + } else { + value = LocaleController.formatPluralString("Days", ttl); + } + } + textCell.setTextAndValue(LocaleController.getString("DeleteAccountIfAwayFor", R.string.DeleteAccountIfAwayFor), value, false); } - textCell.setTextAndValue(LocaleController.getString("DeleteAccountIfAwayFor", R.string.DeleteAccountIfAwayFor), value, false); - } - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == deleteAccountDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("DeleteAccountHelp", R.string.DeleteAccountHelp)); - view.setBackgroundResource(secretSectionRow == -1 ? R.drawable.greydivider_bottom : R.drawable.greydivider); - } else if (i == groupsDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("GroupsAndChannelsHelp", R.string.GroupsAndChannelsHelp)); - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == sessionsDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("SessionsInfo", R.string.SessionsInfo)); - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == secretDetailRow) { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } - } else if (type == 2) { - if (view == null) { - view = new HeaderCell(mContext); - view.setBackgroundColor(0xffffffff); - } - if (i == privacySectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("PrivacyTitle", R.string.PrivacyTitle)); - } else if (i == securitySectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("SecurityTitle", R.string.SecurityTitle)); - } else if (i == deleteAccountSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("DeleteAccountTitle", R.string.DeleteAccountTitle)); - } else if (i == secretSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("SecretChat", R.string.SecretChat)); - } - } else if (type == 3) { - if (view == null) { - view = new TextCheckCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextCheckCell textCell = (TextCheckCell) view; - if (i == secretWebpageRow) { - textCell.setTextAndCheck(LocaleController.getString("SecretWebPage", R.string.SecretWebPage), MessagesController.getInstance().secretWebpagePreview == 1, true); - } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == deleteAccountDetailRow) { + privacyCell.setText(LocaleController.getString("DeleteAccountHelp", R.string.DeleteAccountHelp)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, secretSectionRow == -1 ? R.drawable.greydivider_bottom : R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == groupsDetailRow) { + privacyCell.setText(LocaleController.getString("GroupsAndChannelsHelp", R.string.GroupsAndChannelsHelp)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == sessionsDetailRow) { + privacyCell.setText(LocaleController.getString("SessionsInfo", R.string.SessionsInfo)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == secretDetailRow) { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; + case 2: + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == privacySectionRow) { + headerCell.setText(LocaleController.getString("PrivacyTitle", R.string.PrivacyTitle)); + } else if (position == securitySectionRow) { + headerCell.setText(LocaleController.getString("SecurityTitle", R.string.SecurityTitle)); + } else if (position == deleteAccountSectionRow) { + headerCell.setText(LocaleController.getString("DeleteAccountTitle", R.string.DeleteAccountTitle)); + } else if (position == secretSectionRow) { + headerCell.setText(LocaleController.getString("SecretChat", R.string.SecretChat)); + } + break; + case 3: + TextCheckCell textCheckCell = (TextCheckCell) holder.itemView; + if (position == secretWebpageRow) { + textCheckCell.setTextAndCheck(LocaleController.getString("SecretWebPage", R.string.SecretWebPage), MessagesController.getInstance().secretWebpagePreview == 1, true); + } + break; } - return view; } @Override - public int getItemViewType(int i) { - if (i == lastSeenRow || i == blockedRow || i == deleteAccountRow || i == sessionsRow || i == passwordRow || i == passcodeRow || i == groupsRow) { + public int getItemViewType(int position) { + if (position == lastSeenRow || position == blockedRow || position == deleteAccountRow || position == sessionsRow || position == passwordRow || position == passcodeRow || position == groupsRow) { return 0; - } else if (i == deleteAccountDetailRow || i == groupsDetailRow || i == sessionsDetailRow || i == secretDetailRow) { + } else if (position == deleteAccountDetailRow || position == groupsDetailRow || position == sessionsDetailRow || position == secretDetailRow) { return 1; - } else if (i == securitySectionRow || i == deleteAccountSectionRow || i == privacySectionRow || i == secretSectionRow) { + } else if (position == securitySectionRow || position == deleteAccountSectionRow || position == privacySectionRow || position == secretSectionRow) { return 2; - } else if (i == secretWebpageRow) { + } else if (position == secretWebpageRow) { return 3; } return 0; } + } - @Override - public int getViewTypeCount() { - return 4; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, HeaderCell.class, TextCheckCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java index 9c7412527ac..7aedbf63331 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PrivacyUsersActivity.java @@ -3,37 +3,38 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.TextView; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextInfoCell; import org.telegram.ui.Cells.UserCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; @@ -43,8 +44,10 @@ public interface PrivacyActivityDelegate { void didUpdatedUserList(ArrayList ids, boolean added); } - private ListView listView; + private RecyclerListView listView; private ListAdapter listViewAdapter; + private EmptyTextProgressView emptyView; + private int selectedUserId; private boolean isGroup; @@ -129,56 +132,36 @@ public void didSelectUsers(ArrayList ids) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - TextView emptyTextView = new TextView(context); - emptyTextView.setTextColor(0xff808080); - emptyTextView.setTextSize(20); - emptyTextView.setGravity(Gravity.CENTER); - emptyTextView.setVisibility(View.INVISIBLE); - emptyTextView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); - frameLayout.addView(emptyTextView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) emptyTextView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - emptyTextView.setLayoutParams(layoutParams); - emptyTextView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); + emptyView = new EmptyTextProgressView(context); + emptyView.showTextView(); + emptyView.setText(LocaleController.getString("NoContacts", R.string.NoContacts)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - listView = new ListView(context); - listView.setEmptyView(emptyTextView); + listView = new RecyclerListView(context); + listView.setEmptyView(emptyView); listView.setVerticalScrollBarEnabled(false); - listView.setDivider(null); - listView.setDividerHeight(0); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setAdapter(listViewAdapter = new ListAdapter(context)); - listView.setVerticalScrollbarPosition(LocaleController.isRTL ? ListView.SCROLLBAR_POSITION_LEFT : ListView.SCROLLBAR_POSITION_RIGHT); - frameLayout.addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - if (i < uidArray.size()) { + public void onItemClick(View view, int position) { + if (position < uidArray.size()) { Bundle args = new Bundle(); - args.putInt("user_id", uidArray.get(i)); + args.putInt("user_id", uidArray.get(position)); presentFragment(new ProfileActivity(args)); } } }); - - listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + listView.setOnItemLongClickListener(new RecyclerListView.OnItemLongClickListener() { @Override - public boolean onItemLongClick(AdapterView adapterView, View view, int i, long l) { - if (i < 0 || i >= uidArray.size() || getParentActivity() == null) { - return true; + public boolean onItemClick(View view, int position) { + if (position < 0 || position >= uidArray.size() || getParentActivity() == null) { + return false; } - selectedUserId = uidArray.get(i); + selectedUserId = uidArray.get(position); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); CharSequence[] items = new CharSequence[]{LocaleController.getString("Delete", R.string.Delete)}; @@ -205,7 +188,7 @@ public void onClick(DialogInterface dialogInterface, int i) { @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.updateInterfaces) { - int mask = (Integer)args[0]; + int mask = (Integer) args[0]; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0) { updateVisibleRows(mask); } @@ -237,7 +220,8 @@ public void onResume() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -245,17 +229,12 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getAdapterPosition() != uidArray.size(); } @Override - public boolean isEnabled(int i) { - return i != uidArray.size(); - } - - @Override - public int getCount() { + public int getItemCount() { if (uidArray.isEmpty()) { return 0; } @@ -263,54 +242,78 @@ public int getCount() { } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: view = new UserCell(mContext, 1, 0, false); - } - TLRPC.User user = MessagesController.getInstance().getUser(uidArray.get(i)); - ((UserCell)view).setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); - } else if (type == 1) { - if (view == null) { + break; + case 1: + default: view = new TextInfoCell(mContext); ((TextInfoCell) view).setText(LocaleController.getString("RemoveFromListText", R.string.RemoveFromListText)); - } + break; } - return view; + return new RecyclerListView.Holder(view); } @Override - public int getItemViewType(int i) { - if(i == uidArray.size()) { - return 1; + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + TLRPC.User user = MessagesController.getInstance().getUser(uidArray.get(position)); + ((UserCell) holder.itemView).setData(user, null, user.phone != null && user.phone.length() != 0 ? PhoneFormat.getInstance().format("+" + user.phone) : LocaleController.getString("NumberUnknown", R.string.NumberUnknown), 0); } - return 0; } @Override - public int getViewTypeCount() { - return 2; + public int getItemViewType(int i) { + if (i == uidArray.size()) { + return 1; + } + return 0; } + } - @Override - public boolean isEmpty() { - return uidArray.isEmpty(); - } + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(listView, 0, new Class[]{TextInfoCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText5), + + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index 47b8e2d9d22..265cbf465b4 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -3,32 +3,40 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.StateListAnimator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.annotation.Keep; +import android.text.TextUtils; +import android.util.TypedValue; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -39,16 +47,19 @@ import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; import org.telegram.PhoneFormat.PhoneFormat; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ChatObject; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesStorage; +import org.telegram.messenger.NotificationsController; import org.telegram.messenger.SecretChatHelper; import org.telegram.messenger.SendMessagesHelper; import org.telegram.messenger.UserObject; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.query.BotQuery; import org.telegram.messenger.query.SharedMediaQuery; import org.telegram.messenger.ApplicationLoader; @@ -66,8 +77,10 @@ import org.telegram.messenger.MessageObject; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BackDrawable; import org.telegram.ui.ActionBar.SimpleTextView; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.AboutLinkCell; import org.telegram.ui.Cells.DividerCell; import org.telegram.ui.Cells.EmptyCell; @@ -85,10 +98,12 @@ import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.IdenticonDrawable; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.voip.VoIPHelper; import java.util.ArrayList; import java.util.Collections; @@ -108,6 +123,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private AnimatorSet writeButtonAnimation; private AvatarDrawable avatarDrawable; private ActionBarMenuItem animatingItem; + private ActionBarMenuItem callItem; private TopView topView; private int user_id; private int chat_id; @@ -121,6 +137,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private boolean usersEndReached; private boolean openAnimationInProgress; + private boolean recreateMenuAfterAnimation; private boolean playProfileAnimation; private boolean allowProfileAnimation = true; private int extraHeight; @@ -153,6 +170,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private final static int edit_channel = 12; private final static int convert_to_supergroup = 13; private final static int add_shortcut = 14; + private final static int call_item = 15; private int emptyRow; private int emptyRowChat; @@ -171,6 +189,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int managementRow; private int blockedUsersRow; private int leaveChannelRow; + private int groupsInCommonRow; private int startSecretChatRow; private int sectionRow; private int userSectionRow; @@ -260,7 +279,7 @@ public void run() { try { semaphore.acquire(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (currentChat != null) { MessagesController.getInstance().putChat(currentChat, true); @@ -347,7 +366,9 @@ public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); } }; - actionBar.setItemsBackgroundColor(AvatarDrawable.getButtonColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id)); + actionBar.setItemsBackgroundColor(AvatarDrawable.getButtonColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id), false); + actionBar.setItemsColor(Theme.getColor(Theme.key_actionBarDefaultIcon), false); + actionBar.setItemsColor(Theme.getColor(Theme.key_actionBarActionModeDefaultIcon), true); actionBar.setBackButtonDrawable(new BackDrawable(false)); actionBar.setCastShadows(false); actionBar.setAddToContainer(false); @@ -357,6 +378,8 @@ public boolean onTouchEvent(MotionEvent event) { @Override public View createView(Context context) { + Theme.createProfileResources(context); + hasOwnBackground = true; extraHeight = AndroidUtilities.dp(88); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @@ -486,15 +509,15 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { } Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); - String about = MessagesController.getInstance().getUserAbout(botInfo.user_id); - if (botInfo != null && about != null) { - intent.putExtra(Intent.EXTRA_TEXT, String.format("%s https://telegram.me/%s", about, user.username)); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(botInfo.user_id); + if (botInfo != null && userFull != null && !TextUtils.isEmpty(userFull.about)) { + intent.putExtra(Intent.EXTRA_TEXT, String.format("%s https://" + MessagesController.getInstance().linkPrefix + "/%s", userFull.about, user.username)); } else { - intent.putExtra(Intent.EXTRA_TEXT, String.format("https://telegram.me/%s", user.username)); + intent.putExtra(Intent.EXTRA_TEXT, String.format("https://" + MessagesController.getInstance().linkPrefix + "/%s", user.username)); } startActivityForResult(Intent.createChooser(intent, LocaleController.getString("BotShare", R.string.BotShare)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (id == set_admins) { Bundle args = new Bundle(); @@ -522,10 +545,15 @@ public void didSelectDialog(DialogsActivity fragment, long did, boolean param) { /*try { Toast.makeText(getParentActivity(), LocaleController.getString("ShortcutAdded", R.string.ShortcutAdded), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); }*/ } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + } + } else if (id == call_item) { + TLRPC.User user = MessagesController.getInstance().getUser(user_id); + if (user != null) { + VoIPHelper.startCall(user, getParentActivity(), MessagesController.getInstance().getUserFull(user.id)); } } } @@ -559,7 +587,7 @@ public boolean hasOverlappingRendering() { }; listView.setTag(6); listView.setPadding(0, AndroidUtilities.dp(88), 0, 0); - listView.setBackgroundColor(0xffffffff); + listView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); listView.setVerticalScrollBarEnabled(false); listView.setItemAnimator(null); listView.setLayoutAnimation(null); @@ -592,20 +620,119 @@ public void onItemClick(View view, final int position) { MediaActivity fragment = new MediaActivity(args); fragment.setChatInfo(info); presentFragment(fragment); + } else if (position == groupsInCommonRow) { + presentFragment(new CommonGroupsActivity(user_id)); } else if (position == settingsKeyRow) { Bundle args = new Bundle(); args.putInt("chat_id", (int) (dialog_id >> 32)); presentFragment(new IdenticonActivity(args)); } else if (position == settingsTimerRow) { - showDialog(AndroidUtilities.buildTTLAlert(getParentActivity(), currentEncryptedChat).create()); + showDialog(AlertsCreator.createTTLAlert(getParentActivity(), currentEncryptedChat).create()); } else if (position == settingsNotificationsRow) { - Bundle args = new Bundle(); - if (user_id != 0) { - args.putLong("dialog_id", dialog_id == 0 ? user_id : dialog_id); - } else if (chat_id != 0) { - args.putLong("dialog_id", -chat_id); + final long did; + boolean enabled; + if (dialog_id != 0) { + did = dialog_id; + } else if (user_id != 0) { + did = user_id; + } else { + did = -chat_id; } - presentFragment(new ProfileNotificationsActivity(args)); + + String[] descriptions = new String[]{ + LocaleController.getString("NotificationsTurnOn", R.string.NotificationsTurnOn), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Hours", 1)), + LocaleController.formatString("MuteFor", R.string.MuteFor, LocaleController.formatPluralString("Days", 2)), + LocaleController.getString("NotificationsCustomize", R.string.NotificationsCustomize), + LocaleController.getString("NotificationsTurnOff", R.string.NotificationsTurnOff) + }; + + int[] icons = new int[]{ + R.drawable.notifications_s_on, + R.drawable.notifications_s_1h, + R.drawable.notifications_s_2d, + R.drawable.notifications_s_custom, + R.drawable.notifications_s_off + }; + + final LinearLayout linearLayout = new LinearLayout(getParentActivity()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + + for (int a = 0; a < descriptions.length; a++) { + TextView textView = new TextView(getParentActivity()); + textView.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setLines(1); + textView.setMaxLines(1); + Drawable drawable = getParentActivity().getResources().getDrawable(icons[a]); + drawable.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogIcon), PorterDuff.Mode.MULTIPLY)); + textView.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); + textView.setTag(a); + textView.setBackgroundDrawable(Theme.getSelectorDrawable(false)); + textView.setPadding(AndroidUtilities.dp(24), 0, AndroidUtilities.dp(24), 0); + textView.setSingleLine(true); + textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); + textView.setCompoundDrawablePadding(AndroidUtilities.dp(26)); + textView.setText(descriptions[a]); + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.TOP)); + textView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int i = (Integer) v.getTag(); + if (i == 0) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("notify2_" + did, 0); + MessagesStorage.getInstance().setDialogFlags(did, 0); + editor.commit(); + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + } + NotificationsController.updateServerNotificationsSettings(did); + } else if (i == 3) { + Bundle args = new Bundle(); + args.putLong("dialog_id", did); + presentFragment(new ProfileNotificationsActivity(args)); + } else { + int untilTime = ConnectionsManager.getInstance().getCurrentTime(); + if (i == 1) { + untilTime += 60 * 60; + } else if (i == 2) { + untilTime += 60 * 60 * 48; + } else if (i == 4) { + untilTime = Integer.MAX_VALUE; + } + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + long flags; + if (i == 4) { + editor.putInt("notify2_" + did, 2); + flags = 1; + } else { + editor.putInt("notify2_" + did, 3); + editor.putInt("notifyuntil_" + did, untilTime); + flags = ((long) untilTime << 32) | 1; + } + NotificationsController.getInstance().removeNotificationsForDialog(did); + MessagesStorage.getInstance().setDialogFlags(did, flags); + editor.commit(); + TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(did); + if (dialog != null) { + dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); + dialog.notify_settings.mute_until = untilTime; + } + NotificationsController.updateServerNotificationsSettings(did); + } + listAdapter.notifyItemChanged(settingsNotificationsRow); + dismissCurrentDialig(); + } + }); + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("Notifications", R.string.Notifications)); + builder.setView(linearLayout); + showDialog(builder.create()); } else if (position == startSecretChatRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("AreYouSureSecretChat", R.string.AreYouSureSecretChat)); @@ -639,13 +766,13 @@ public void onClick(DialogInterface dialogInterface, int i) { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); if (info.about != null && info.about.length() > 0) { - intent.putExtra(Intent.EXTRA_TEXT, currentChat.title + "\n" + info.about + "\nhttps://telegram.me/" + currentChat.username); + intent.putExtra(Intent.EXTRA_TEXT, currentChat.title + "\n" + info.about + "\nhttps://" + MessagesController.getInstance().linkPrefix + "/" + currentChat.username); } else { - intent.putExtra(Intent.EXTRA_TEXT, currentChat.title + "\nhttps://telegram.me/" + currentChat.username); + intent.putExtra(Intent.EXTRA_TEXT, currentChat.title + "\nhttps://" + MessagesController.getInstance().linkPrefix + "/" + currentChat.username); } getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("BotShare", R.string.BotShare)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (position == leaveChannelRow) { leaveChatPressed(); @@ -737,7 +864,7 @@ public void onClick(DialogInterface dialogInterface, int i) { channelParticipant.channelParticipant.user_id = user.user_id; channelParticipant.channelParticipant.date = user.date; - TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); + final TLRPC.TL_channels_editAdmin req = new TLRPC.TL_channels_editAdmin(); req.channel = MessagesController.getInputChannel(chat_id); req.user_id = MessagesController.getInputUser(selectedUser); req.role = new TLRPC.TL_channelRoleEditor(); @@ -756,7 +883,7 @@ public void run() { AndroidUtilities.runOnUIThread(new Runnable() { @Override public void run() { - AlertsCreator.showAddUserAlert(error.text, ProfileActivity.this, false); + AlertsCreator.processError(error, ProfileActivity.this, req, false); } }); } @@ -821,31 +948,41 @@ public void onClick(View v) { continue; } nameTextView[a] = new SimpleTextView(context); - nameTextView[a].setTextColor(0xffffffff); + if (a == 1) { + nameTextView[a].setTextColor(Theme.getColor(Theme.key_profile_title)); + } else { + nameTextView[a].setTextColor(Theme.getColor(Theme.key_actionBarDefaultTitle)); + } nameTextView[a].setTextSize(18); nameTextView[a].setGravity(Gravity.LEFT); nameTextView[a].setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); nameTextView[a].setLeftDrawableTopPadding(-AndroidUtilities.dp(1.3f)); - nameTextView[a].setRightDrawableTopPadding(-AndroidUtilities.dp(1.3f)); nameTextView[a].setPivotX(0); nameTextView[a].setPivotY(0); + nameTextView[a].setAlpha(a == 0 ? 0.0f : 1.0f); frameLayout.addView(nameTextView[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, a == 0 ? 48 : 0, 0)); onlineTextView[a] = new SimpleTextView(context); onlineTextView[a].setTextColor(AvatarDrawable.getProfileTextColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id)); onlineTextView[a].setTextSize(14); onlineTextView[a].setGravity(Gravity.LEFT); + onlineTextView[a].setAlpha(a == 0 ? 0.0f : 1.0f); frameLayout.addView(onlineTextView[a], LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, a == 0 ? 48 : 8, 0)); } - if (user_id != 0 || chat_id >= 0 && !ChatObject.isLeftFromChat(currentChat)) { + if (user_id != 0 || chat_id >= 0 && (!ChatObject.isLeftFromChat(currentChat) || ChatObject.isChannel(currentChat))) { writeButton = new ImageView(context); - try { - writeButton.setBackgroundResource(R.drawable.floating_user_states); - } catch (Throwable e) { - FileLog.e("tmessages", e); - } + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_profile_actionBackground), Theme.getColor(Theme.key_profile_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow_profile).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + writeButton.setBackgroundDrawable(drawable); writeButton.setScaleType(ImageView.ScaleType.CENTER); + writeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_profile_actionIcon), PorterDuff.Mode.MULTIPLY)); if (user_id != 0) { writeButton.setImageResource(R.drawable.floating_message); writeButton.setPadding(0, AndroidUtilities.dp(3), 0, 0); @@ -858,7 +995,7 @@ public void onClick(View v) { writeButton.setImageResource(R.drawable.floating_camera); } } - frameLayout.addView(writeButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 16, 0)); + frameLayout.addView(writeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, Gravity.RIGHT | Gravity.TOP, 0, 0, 16, 0)); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(writeButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); @@ -954,10 +1091,20 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } private boolean processOnClickOrPress(final int position) { - if (position == usernameRow) { - final TLRPC.User user = MessagesController.getInstance().getUser(user_id); - if (user == null || user.username == null) { - return false; + if (position == usernameRow || position == channelNameRow) { + final String username; + if (position == usernameRow) { + final TLRPC.User user = MessagesController.getInstance().getUser(user_id); + if (user == null || user.username == null) { + return false; + } + username = user.username; + } else { + final TLRPC.Chat chat = MessagesController.getInstance().getChat(chat_id); + if (chat == null || chat.username == null) { + return false; + } + username = chat.username; } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setItems(new CharSequence[]{LocaleController.getString("Copy", R.string.Copy)}, new DialogInterface.OnClickListener() { @@ -966,10 +1113,10 @@ public void onClick(DialogInterface dialogInterface, int i) { if (i == 0) { try { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", "@" + user.username); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", "@" + username); clipboard.setPrimaryClip(clip); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -983,16 +1130,28 @@ public void onClick(DialogInterface dialogInterface, int i) { } AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setItems(new CharSequence[]{LocaleController.getString("Call", R.string.Call), LocaleController.getString("Copy", R.string.Copy)}, new DialogInterface.OnClickListener() { + ArrayList items = new ArrayList<>(); + final ArrayList actions = new ArrayList<>(); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user.id); + if (MessagesController.getInstance().callsEnabled && userFull != null && userFull.phone_calls_available) { + items.add(LocaleController.getString("CallViaTelegram", R.string.CallViaTelegram)); + actions.add(2); + } + items.add(LocaleController.getString("Call", R.string.Call)); + actions.add(0); + items.add(LocaleController.getString("Copy", R.string.Copy)); + actions.add(1); + builder.setItems(items.toArray(new CharSequence[items.size()]), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { + i = actions.get(i); if (i == 0) { try { Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:+" + user.phone)); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getParentActivity().startActivityForResult(intent, 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (i == 1) { try { @@ -1000,8 +1159,10 @@ public void onClick(DialogInterface dialogInterface, int i) { android.content.ClipData clip = android.content.ClipData.newPlainText("label", "+" + user.phone); clipboard.setPrimaryClip(clip); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } + } else if (i == 2) { + VoIPHelper.startCall(user, getParentActivity(), MessagesController.getInstance().getUserFull(user.id)); } } }); @@ -1017,7 +1178,8 @@ public void onClick(DialogInterface dialogInterface, int i) { if (position == channelInfoRow) { about = info.about; } else { - about = MessagesController.getInstance().getUserAbout(botInfo.user_id); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(botInfo.user_id); + about = userFull != null ? userFull.about : null; } if (about == null) { return; @@ -1026,7 +1188,7 @@ public void onClick(DialogInterface dialogInterface, int i) { android.content.ClipData clip = android.content.ClipData.newPlainText("label", about); clipboard.setPrimaryClip(clip); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1171,7 +1333,7 @@ private void checkListViewScroll() { } View child = listView.getChildAt(0); - ListAdapter.Holder holder = (ListAdapter.Holder) listView.findContainingViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findContainingViewHolder(child); int top = child.getTop(); int newOffset = 0; if (top >= 0 && holder != null && holder.getAdapterPosition() == 0) { @@ -1236,7 +1398,7 @@ private void needLayout() { ); } writeButtonAnimation.setDuration(150); - writeButtonAnimation.addListener(new AnimatorListenerAdapterProxy() { + writeButtonAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (writeButtonAnimation != null && writeButtonAnimation.equals(animation)) { @@ -1271,7 +1433,7 @@ public void onAnimationEnd(Animator animation) { } else { width = AndroidUtilities.displaySize.x; } - width = (int) (width - AndroidUtilities.dp(118 + 8 + 40 * (1.0f - diff)) - nameTextView[a].getTranslationX()); + width = (int) (width - AndroidUtilities.dp(118 + 8 + (40 + (callItem != null ? 48 : 0)) * (1.0f - diff)) - nameTextView[a].getTranslationX()); float width2 = nameTextView[a].getPaint().measureText(nameTextView[a].getText().toString()) * nameTextView[a].getScaleX() + nameTextView[a].getSideDrawablesSize(); layoutParams = (FrameLayout.LayoutParams) nameTextView[a].getLayoutParams(); if (width < width2) { @@ -1323,7 +1485,7 @@ public void didReceivedNotification(int id, final Object... args) { } if ((mask & MessagesController.UPDATE_MASK_PHONE) != 0) { if (listView != null) { - ListAdapter.Holder holder = (ListAdapter.Holder) listView.findViewHolderForPosition(phoneRow); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.findViewHolderForPosition(phoneRow); if (holder != null) { listAdapter.onBindViewHolder(holder, phoneRow); } @@ -1385,7 +1547,7 @@ public void didReceivedNotification(int id, final Object... args) { int count = listView.getChildCount(); for (int a = 0; a < count; a++) { View child = listView.getChildAt(a); - ListAdapter.Holder holder = (ListAdapter.Holder) listView.getChildViewHolder(child); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); if (holder.getAdapterPosition() == sharedMediaRow) { listAdapter.onBindViewHolder(holder, sharedMediaRow); break; @@ -1466,6 +1628,11 @@ public void run() { } else if (id == NotificationCenter.userInfoDidLoaded) { int uid = (Integer) args[0]; if (uid == user_id) { + if (!openAnimationInProgress && callItem == null) { + createActionBarMenu(); + } else { + recreateMenuAfterAnimation = true; + } updateRowsIds(); if (listAdapter != null) { listAdapter.notifyDataSetChanged(); @@ -1518,6 +1685,9 @@ protected void onTransitionAnimationStart(boolean isOpen, boolean backward) { protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (!backward && playProfileAnimation && allowProfileAnimation) { openAnimationInProgress = false; + if (recreateMenuAfterAnimation) { + createActionBarMenu(); + } } NotificationCenter.getInstance().setAnimationInProgress(false); } @@ -1526,6 +1696,7 @@ public float getAnimationProgress() { return animationProgress; } + @Keep public void setAnimationProgress(float progress) { animationProgress = progress; listView.setAlpha(progress); @@ -1533,28 +1704,63 @@ public void setAnimationProgress(float progress) { listView.setTranslationX(AndroidUtilities.dp(48) - AndroidUtilities.dp(48) * progress); int color = AvatarDrawable.getProfileBackColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id); - int r = Color.red(Theme.ACTION_BAR_COLOR); - int g = Color.green(Theme.ACTION_BAR_COLOR); - int b = Color.blue(Theme.ACTION_BAR_COLOR); + int actionBarColor = Theme.getColor(Theme.key_actionBarDefault); + int r = Color.red(actionBarColor); + int g = Color.green(actionBarColor); + int b = Color.blue(actionBarColor); + int a; int rD = (int) ((Color.red(color) - r) * progress); int gD = (int) ((Color.green(color) - g) * progress); int bD = (int) ((Color.blue(color) - b) * progress); + int aD; topView.setBackgroundColor(Color.rgb(r + rD, g + gD, b + bD)); - color = AvatarDrawable.getProfileTextColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id); - r = Color.red(Theme.ACTION_BAR_SUBTITLE_COLOR); - g = Color.green(Theme.ACTION_BAR_SUBTITLE_COLOR); - b = Color.blue(Theme.ACTION_BAR_SUBTITLE_COLOR); + color = AvatarDrawable.getIconColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id); + int iconColor = Theme.getColor(Theme.key_actionBarDefaultIcon); + r = Color.red(iconColor); + g = Color.green(iconColor); + b = Color.blue(iconColor); rD = (int) ((Color.red(color) - r) * progress); gD = (int) ((Color.green(color) - g) * progress); bD = (int) ((Color.blue(color) - b) * progress); - for (int a = 0; a < 2; a++) { - if (onlineTextView[a] == null) { + actionBar.setItemsColor(Color.rgb(r + rD, g + gD, b + bD), false); + + color = Theme.getColor(Theme.key_profile_title); + int titleColor = Theme.getColor(Theme.key_actionBarDefaultTitle); + r = Color.red(titleColor); + g = Color.green(titleColor); + b = Color.blue(titleColor); + a = Color.alpha(titleColor); + + rD = (int) ((Color.red(color) - r) * progress); + gD = (int) ((Color.green(color) - g) * progress); + bD = (int) ((Color.blue(color) - b) * progress); + aD = (int) ((Color.alpha(color) - a) * progress); + for (int i = 0; i < 2; i++) { + if (nameTextView[i] == null) { + continue; + } + nameTextView[i].setTextColor(Color.argb(a + aD, r + rD, g + gD, b + bD)); + } + + color = AvatarDrawable.getProfileTextColorForId(user_id != 0 || ChatObject.isChannel(chat_id) && !currentChat.megagroup ? 5 : chat_id); + int subtitleColor = Theme.getColor(Theme.key_actionBarDefaultSubtitle); + r = Color.red(subtitleColor); + g = Color.green(subtitleColor); + b = Color.blue(subtitleColor); + a = Color.alpha(subtitleColor); + + rD = (int) ((Color.red(color) - r) * progress); + gD = (int) ((Color.green(color) - g) * progress); + bD = (int) ((Color.blue(color) - b) * progress); + aD = (int) ((Color.alpha(color) - a) * progress); + for (int i = 0; i < 2; i++) { + if (onlineTextView[i] == null) { continue; } - onlineTextView[a].setTextColor(Color.rgb(r + rD, g + gD, b + bD)); + onlineTextView[i].setTextColor(Color.argb(a + aD, r + rD, g + gD, b + bD)); } extraHeight = (int) (initialAnimationExtraHeight * progress); color = AvatarDrawable.getProfileColorForId(user_id != 0 ? user_id : chat_id); @@ -1622,6 +1828,10 @@ protected AnimatorSet onCustomTransitionAnimation(final boolean isOpen, final Ru animatingItem.setAlpha(1.0f); animators.add(ObjectAnimator.ofFloat(animatingItem, "alpha", 0.0f)); } + if (callItem != null) { + callItem.setAlpha(0.0f); + animators.add(ObjectAnimator.ofFloat(callItem, "alpha", 1.0f)); + } animatorSet.playTogether(animators); } else { initialAnimationExtraHeight = extraHeight; @@ -1640,9 +1850,13 @@ protected AnimatorSet onCustomTransitionAnimation(final boolean isOpen, final Ru animatingItem.setAlpha(0.0f); animators.add(ObjectAnimator.ofFloat(animatingItem, "alpha", 1.0f)); } + if (callItem != null) { + callItem.setAlpha(1.0f); + animators.add(ObjectAnimator.ofFloat(callItem, "alpha", 0.0f)); + } animatorSet.playTogether(animators); } - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (Build.VERSION.SDK_INT > 15) { @@ -1732,7 +1946,8 @@ public Bitmap getThumbForPhoto(MessageObject messageObject, TLRPC.FileLocation f } @Override - public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { } + public void willSwitchFromPhoto(MessageObject messageObject, TLRPC.FileLocation fileLocation, int index) { + } @Override public void willHidePhotoViewer() { @@ -1740,19 +1955,27 @@ public void willHidePhotoViewer() { } @Override - public boolean isPhotoChecked(int index) { return false; } + public boolean isPhotoChecked(int index) { + return false; + } @Override - public void setPhotoChecked(int index) { } + public void setPhotoChecked(int index) { + } @Override - public boolean cancelButtonPressed() { return true; } + public boolean cancelButtonPressed() { + return true; + } @Override - public void sendButtonPressed(int index) { } + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { + } @Override - public int getSelectedCount() { return 0; } + public int getSelectedCount() { + return 0; + } private void updateOnlineCount() { onlineCount = 0; @@ -1813,7 +2036,7 @@ public int compare(Integer lhs, Integer rhs) { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (listAdapter != null) { @@ -1913,6 +2136,7 @@ private void updateRowsIds() { managementRow = -1; leaveChannelRow = -1; loadMoreMembersRow = -1; + groupsInCommonRow = -1; blockedUsersRow = -1; rowCount = 0; @@ -1925,7 +2149,8 @@ private void updateRowsIds() { if (user != null && user.username != null && user.username.length() > 0) { usernameRow = rowCount++; } - String about = MessagesController.getInstance().getUserAbout(user.id); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user.id); + String about = userFull != null ? userFull.about : null; if (about != null) { userSectionRow = rowCount++; userInfoRow = rowCount++; @@ -1942,6 +2167,9 @@ private void updateRowsIds() { settingsTimerRow = rowCount++; settingsKeyRow = rowCount++; } + if (userFull != null && userFull.common_chats_count != 0) { + groupsInCommonRow = rowCount++; + } if (user != null && !user.bot && currentEncryptedChat == null && user.id != UserConfig.getClientUserId()) { startSecretChatRow = rowCount++; } @@ -2067,12 +2295,12 @@ private void updateProfileData() { if (!onlineTextView[a].getText().equals(newString2)) { onlineTextView[a].setText(newString2); } - int leftIcon = currentEncryptedChat != null ? R.drawable.ic_lock_header : 0; - int rightIcon = 0; + Drawable leftIcon = currentEncryptedChat != null ? Theme.chat_lockIconDrawable : null; + Drawable rightIcon = null; if (a == 0) { - rightIcon = MessagesController.getInstance().isDialogMuted(dialog_id != 0 ? dialog_id : (long) user_id) ? R.drawable.mute_fixed : 0; + rightIcon = MessagesController.getInstance().isDialogMuted(dialog_id != 0 ? dialog_id : (long) user_id) ? Theme.chat_muteIconDrawable : null; } else if (user.verified) { - rightIcon = R.drawable.check_profile_fixed; + rightIcon = new CombinedDrawable(Theme.profile_verifiedDrawable, Theme.profile_verifiedCheckDrawable); } nameTextView[a].setLeftDrawable(leftIcon); nameTextView[a].setRightDrawable(rightIcon); @@ -2134,12 +2362,12 @@ private void updateProfileData() { nameTextView[a].setLeftDrawable(null); if (a != 0) { if (chat.verified) { - nameTextView[a].setRightDrawable(R.drawable.check_profile_fixed); + nameTextView[a].setRightDrawable(new CombinedDrawable(Theme.profile_verifiedDrawable, Theme.profile_verifiedCheckDrawable)); } else { nameTextView[a].setRightDrawable(null); } } else { - nameTextView[a].setRightDrawable(MessagesController.getInstance().isDialogMuted((long) -chat_id) ? R.drawable.mute_fixed : 0); + nameTextView[a].setRightDrawable(MessagesController.getInstance().isDialogMuted((long) -chat_id) ? Theme.chat_muteIconDrawable : null); } if (currentChat.megagroup && info != null && info.participants_count <= 200 && onlineCount > 0) { if (!onlineTextView[a].getText().equals(newString)) { @@ -2176,6 +2404,10 @@ private void createActionBarMenu() { ActionBarMenuItem item = null; if (user_id != 0) { if (UserConfig.getClientUserId() != user_id) { + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); + if (MessagesController.getInstance().callsEnabled && userFull != null && userFull.phone_calls_available) { + callItem = menu.addItem(call_item, R.drawable.ic_call_white_24dp); + } if (ContactsController.getInstance().contactsDict.get(user_id) == null) { TLRPC.User user = MessagesController.getInstance().getUser(user_id); if (user == null) { @@ -2184,32 +2416,32 @@ private void createActionBarMenu() { item = menu.addItem(10, R.drawable.ic_ab_other); if (user.bot) { if (!user.bot_nochats) { - item.addSubItem(invite_to_group, LocaleController.getString("BotInvite", R.string.BotInvite), 0); + item.addSubItem(invite_to_group, LocaleController.getString("BotInvite", R.string.BotInvite)); } - item.addSubItem(share, LocaleController.getString("BotShare", R.string.BotShare), 0); + item.addSubItem(share, LocaleController.getString("BotShare", R.string.BotShare)); } if (user.phone != null && user.phone.length() != 0) { - item.addSubItem(add_contact, LocaleController.getString("AddContact", R.string.AddContact), 0); - item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact), 0); - item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock), 0); + item.addSubItem(add_contact, LocaleController.getString("AddContact", R.string.AddContact)); + item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact)); + item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); } else { if (user.bot) { - item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BotStop", R.string.BotStop) : LocaleController.getString("BotRestart", R.string.BotRestart), 0); + item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BotStop", R.string.BotStop) : LocaleController.getString("BotRestart", R.string.BotRestart)); } else { - item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock), 0); + item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); } } } else { item = menu.addItem(10, R.drawable.ic_ab_other); - item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact), 0); - item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock), 0); - item.addSubItem(edit_contact, LocaleController.getString("EditContact", R.string.EditContact), 0); - item.addSubItem(delete_contact, LocaleController.getString("DeleteContact", R.string.DeleteContact), 0); + item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact)); + item.addSubItem(block_contact, !userBlocked ? LocaleController.getString("BlockContact", R.string.BlockContact) : LocaleController.getString("Unblock", R.string.Unblock)); + item.addSubItem(edit_contact, LocaleController.getString("EditContact", R.string.EditContact)); + item.addSubItem(delete_contact, LocaleController.getString("DeleteContact", R.string.DeleteContact)); } } else { item = menu.addItem(10, R.drawable.ic_ab_other); - item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact), 0); + item.addSubItem(share_contact, LocaleController.getString("ShareContact", R.string.ShareContact)); } } else if (chat_id != 0) { if (chat_id > 0) { @@ -2227,36 +2459,36 @@ private void createActionBarMenu() { if (ChatObject.isChannel(chat)) { if (chat.creator || chat.megagroup && chat.editor) { item = menu.addItem(10, R.drawable.ic_ab_other); - item.addSubItem(edit_channel, LocaleController.getString("ChannelEdit", R.string.ChannelEdit), 0); + item.addSubItem(edit_channel, LocaleController.getString("ChannelEdit", R.string.ChannelEdit)); } if (!chat.creator && !chat.left && !chat.kicked && chat.megagroup) { if (item == null) { item = menu.addItem(10, R.drawable.ic_ab_other); } - item.addSubItem(leave_group, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu), 0); + item.addSubItem(leave_group, LocaleController.getString("LeaveMegaMenu", R.string.LeaveMegaMenu)); } } else { item = menu.addItem(10, R.drawable.ic_ab_other); if (chat.creator && chat_id > 0) { - item.addSubItem(set_admins, LocaleController.getString("SetAdmins", R.string.SetAdmins), 0); + item.addSubItem(set_admins, LocaleController.getString("SetAdmins", R.string.SetAdmins)); } if (!chat.admins_enabled || chat.creator || chat.admin) { - item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName), 0); + item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName)); } - if (chat.creator && (info == null || info.participants.participants.size() > 1)) { - item.addSubItem(convert_to_supergroup, LocaleController.getString("ConvertGroupMenu", R.string.ConvertGroupMenu), 0); + if (chat.creator && (info == null || info.participants.participants.size() > 0)) { + item.addSubItem(convert_to_supergroup, LocaleController.getString("ConvertGroupMenu", R.string.ConvertGroupMenu)); } - item.addSubItem(leave_group, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit), 0); + item.addSubItem(leave_group, LocaleController.getString("DeleteAndExit", R.string.DeleteAndExit)); } } else { item = menu.addItem(10, R.drawable.ic_ab_other); - item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName), 0); + item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName)); } } if (item == null) { item = menu.addItem(10, R.drawable.ic_ab_other); } - item.addSubItem(add_shortcut, LocaleController.getString("AddShortcut", R.string.AddShortcut), 0); + item.addSubItem(add_shortcut, LocaleController.getString("AddShortcut", R.string.AddShortcut)); } @Override @@ -2294,15 +2526,24 @@ public void didSelectDialog(DialogsActivity fragment, long dialog_id, boolean pa } } - private class ListAdapter extends RecyclerListView.Adapter { - private Context mContext; - - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); + @Override + public void onRequestPermissionsResultFragment(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 101) { + final TLRPC.User user = MessagesController.getInstance().getUser(user_id); + if (user == null) { + return; + } + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + VoIPHelper.startCall(user, getParentActivity(), MessagesController.getInstance().getUserFull(user.id)); + } else { + VoIPHelper.permissionDenied(getParentActivity(), null); } } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; public ListAdapter(Context context) { mContext = context; @@ -2320,53 +2561,32 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType view.setPadding(AndroidUtilities.dp(72), 0, 0, 0); break; case 2: - view = new TextDetailCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextDetailCell(mContext); break; case 3: - view = new TextCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextCell(mContext); break; case 4: - view = new UserCell(mContext, 61, 0, true) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new UserCell(mContext, 61, 0, true); break; - case 5: + case 5: { view = new ShadowSectionCell(mContext); + Drawable drawable = Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow); + CombinedDrawable combinedDrawable = new CombinedDrawable(new ColorDrawable(Theme.getColor(Theme.key_windowBackgroundGray)), drawable); + combinedDrawable.setFullsize(true); + view.setBackgroundDrawable(combinedDrawable); break; - case 6: + } + case 6: { view = new TextInfoPrivacyCell(mContext); TextInfoPrivacyCell cell = (TextInfoPrivacyCell) view; - cell.setBackgroundResource(R.drawable.greydivider); + Drawable drawable = Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow); + CombinedDrawable combinedDrawable = new CombinedDrawable(new ColorDrawable(Theme.getColor(Theme.key_windowBackgroundGray)), drawable); + combinedDrawable.setFullsize(true); + cell.setBackgroundDrawable(combinedDrawable); cell.setText(AndroidUtilities.replaceTags(LocaleController.formatString("ConvertGroupInfo", R.string.ConvertGroupInfo, LocaleController.formatPluralString("Members", MessagesController.getInstance().maxMegagroupCount)))); break; + } case 7: view = new LoadingCell(mContext); break; @@ -2395,7 +2615,7 @@ public void didPressUrl(String url) { break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -2419,7 +2639,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { } else { text = LocaleController.getString("NumberUnknown", R.string.NumberUnknown); } - textDetailCell.setTextAndValueAndIcon(text, LocaleController.getString("PhoneMobile", R.string.PhoneMobile), R.drawable.phone_grey); + textDetailCell.setTextAndValueAndIcon(text, LocaleController.getString("PhoneMobile", R.string.PhoneMobile), R.drawable.profile_phone); } else if (i == usernameRow) { String text; final TLRPC.User user = MessagesController.getInstance().getUser(user_id); @@ -2436,12 +2656,13 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { } else { text = "-"; } - textDetailCell.setTextAndValue(text, "telegram.me/" + currentChat.username); + textDetailCell.setTextAndValue(text, MessagesController.getInstance().linkPrefix + "/" + currentChat.username); } break; case 3: TextCell textCell = (TextCell) holder.itemView; - textCell.setTextColor(0xff212121); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); if (i == sharedMediaRow) { String value; @@ -2455,31 +2676,98 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { } else { textCell.setTextAndValue(LocaleController.getString("SharedMedia", R.string.SharedMedia), value); } + } else if (i == groupsInCommonRow) { + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); + textCell.setTextAndValue(LocaleController.getString("GroupsInCommon", R.string.GroupsInCommon), String.format("%d", userFull != null ? userFull.common_chats_count : 0)); } else if (i == settingsTimerRow) { - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int)(dialog_id >> 32)); + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (dialog_id >> 32)); String value; if (encryptedChat.ttl == 0) { value = LocaleController.getString("ShortMessageLifetimeForever", R.string.ShortMessageLifetimeForever); } else { - value = AndroidUtilities.formatTTLString(encryptedChat.ttl); + value = LocaleController.formatTTLString(encryptedChat.ttl); } textCell.setTextAndValue(LocaleController.getString("MessageLifetime", R.string.MessageLifetime), value); } else if (i == settingsNotificationsRow) { - textCell.setTextAndIcon(LocaleController.getString("NotificationsAndSounds", R.string.NotificationsAndSounds), R.drawable.profile_list); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + boolean enabled; + long did; + if (dialog_id != 0) { + did = dialog_id; + } else if (user_id != 0) { + did = user_id; + } else { + did = -chat_id; + } + + boolean custom = preferences.getBoolean("custom_" + did, false); + boolean hasOverride = preferences.contains("notify2_" + did); + int value = preferences.getInt("notify2_" + did, 0); + int delta = preferences.getInt("notifyuntil_" + did, 0); + String val; + if (value == 3 && delta != Integer.MAX_VALUE) { + delta -= ConnectionsManager.getInstance().getCurrentTime(); + if (delta <= 0) { + if (custom) { + val = LocaleController.getString("NotificationsCustom", R.string.NotificationsCustom); + } else { + val = LocaleController.getString("NotificationsOn", R.string.NotificationsOn); + } + } else if (delta < 60 * 60) { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Minutes", delta / 60)); + } else if (delta < 60 * 60 * 24) { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Hours", (int) Math.ceil(delta / 60.0f / 60))); + } else if (delta < 60 * 60 * 24 * 365) { + val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Days", (int) Math.ceil(delta / 60.0f / 60 / 24))); + } else { + val = null; + } + } else { + if (value == 0) { + if (hasOverride) { + enabled = true; + } else { + if ((int) did < 0) { + enabled = preferences.getBoolean("EnableGroup", true); + } else { + enabled = preferences.getBoolean("EnableAll", true); + } + } + } else if (value == 1) { + enabled = true; + } else if (value == 2) { + enabled = false; + } else { + enabled = false; + } + if (enabled && custom) { + val = LocaleController.getString("NotificationsCustom", R.string.NotificationsCustom); + } else { + val = enabled ? LocaleController.getString("NotificationsOn", R.string.NotificationsOn) : LocaleController.getString("NotificationsOff", R.string.NotificationsOff); + } + } + if (val != null) { + textCell.setTextAndValueAndIcon(LocaleController.getString("Notifications", R.string.Notifications), val, R.drawable.profile_list); + } else { + textCell.setTextAndValueAndIcon(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("NotificationsOff", R.string.NotificationsOff), R.drawable.profile_list); + } } else if (i == startSecretChatRow) { textCell.setText(LocaleController.getString("StartEncryptedChat", R.string.StartEncryptedChat)); - textCell.setTextColor(0xff37a919); + textCell.setTag(Theme.key_windowBackgroundWhiteGreenText2); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText2)); } else if (i == settingsKeyRow) { IdenticonDrawable identiconDrawable = new IdenticonDrawable(); - TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int)(dialog_id >> 32)); + TLRPC.EncryptedChat encryptedChat = MessagesController.getInstance().getEncryptedChat((int) (dialog_id >> 32)); identiconDrawable.setEncryptedChat(encryptedChat); textCell.setTextAndValueDrawable(LocaleController.getString("EncryptionKey", R.string.EncryptionKey), identiconDrawable); } else if (i == leaveChannelRow) { - textCell.setTextColor(0xffed3d39); + textCell.setTag(Theme.key_windowBackgroundWhiteRedText5); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText5)); textCell.setText(LocaleController.getString("LeaveChannel", R.string.LeaveChannel)); } else if (i == convertRow) { textCell.setText(LocaleController.getString("UpgradeGroup", R.string.UpgradeGroup)); - textCell.setTextColor(0xff37a919); + textCell.setTag(Theme.key_windowBackgroundWhiteGreenText2); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGreenText2)); } else if (i == membersRow) { if (info != null) { textCell.setTextAndValue(LocaleController.getString("ChannelMembers", R.string.ChannelMembers), String.format("%d", info.participants_count)); @@ -2539,39 +2827,32 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { case 8: AboutLinkCell aboutLinkCell = (AboutLinkCell) holder.itemView; if (i == userInfoRow) { - String about = MessagesController.getInstance().getUserAbout(user_id); - aboutLinkCell.setTextAndIcon(about, R.drawable.bot_info); + TLRPC.TL_userFull userFull = MessagesController.getInstance().getUserFull(user_id); + String about = userFull != null ? userFull.about : null; + aboutLinkCell.setTextAndIcon(about, R.drawable.profile_info); } else if (i == channelInfoRow) { String text = info.about; while (text.contains("\n\n\n")) { text = text.replace("\n\n\n", "\n\n"); } - aboutLinkCell.setTextAndIcon(text, R.drawable.bot_info); + aboutLinkCell.setTextAndIcon(text, R.drawable.profile_info); } break; - default: - checkBackground = false; } - if (checkBackground) { - boolean enabled = false; - if (user_id != 0) { - enabled = i == phoneRow || i == settingsTimerRow || i == settingsKeyRow || i == settingsNotificationsRow || - i == sharedMediaRow || i == startSecretChatRow || i == usernameRow || i == userInfoRow; - } else if (chat_id != 0) { - enabled = i == convertRow || i == settingsNotificationsRow || i == sharedMediaRow || i > emptyRowChat2 && i < membersEndRow || - i == addMemberRow || i == channelNameRow || i == leaveChannelRow || i == membersRow || i == managementRow || - i == blockedUsersRow || i == channelInfoRow; - } - if (enabled) { - if (holder.itemView.getBackground() == null) { - holder.itemView.setBackgroundResource(R.drawable.list_selector); - } - } else { - if (holder.itemView.getBackground() != null) { - holder.itemView.setBackgroundDrawable(null); - } - } + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int i = holder.getAdapterPosition(); + if (user_id != 0) { + return i == phoneRow || i == settingsTimerRow || i == settingsKeyRow || i == settingsNotificationsRow || + i == sharedMediaRow || i == startSecretChatRow || i == usernameRow || i == userInfoRow || i == groupsInCommonRow; + } else if (chat_id != 0) { + return i == convertRow || i == settingsNotificationsRow || i == sharedMediaRow || i > emptyRowChat2 && i < membersEndRow || + i == addMemberRow || i == channelNameRow || i == leaveChannelRow || i == membersRow || i == managementRow || + i == blockedUsersRow || i == channelInfoRow; } + return false; } @Override @@ -2587,7 +2868,7 @@ public int getItemViewType(int i) { return 1; } else if (i == phoneRow || i == usernameRow || i == channelNameRow) { return 2; - } else if (i == leaveChannelRow || i == sharedMediaRow || i == settingsTimerRow || i == settingsNotificationsRow || i == startSecretChatRow || i == settingsKeyRow || i == membersRow || i == managementRow || i == blockedUsersRow || i == convertRow || i == addMemberRow) { + } else if (i == leaveChannelRow || i == sharedMediaRow || i == settingsTimerRow || i == settingsNotificationsRow || i == startSecretChatRow || i == settingsKeyRow || i == membersRow || i == managementRow || i == blockedUsersRow || i == convertRow || i == addMemberRow || i == groupsInCommonRow) { return 3; } else if (i > emptyRowChat2 && i < membersEndRow) { return 4; @@ -2603,4 +2884,134 @@ public int getItemViewType(int i) { return 0; } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } + } + } + }; + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorBlue), + new ThemeDescription(nameTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_profile_title), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileBlue), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarRed), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarRed), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarRed), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorRed), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileRed), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconRed), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarOrange), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarOrange), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarOrange), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorOrange), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileOrange), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconOrange), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarViolet), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarViolet), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarViolet), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorViolet), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileViolet), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconViolet), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarGreen), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarGreen), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarGreen), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorGreen), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileGreen), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconGreen), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarCyan), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarCyan), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarCyan), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorCyan), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileCyan), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconCyan), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarPink), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarPink), + new ThemeDescription(topView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarPink), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorPink), + new ThemeDescription(onlineTextView[1], ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfilePink), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconPink), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileRed), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileOrange), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileViolet), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileGreen), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileCyan), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileBlue), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfilePink), + + new ThemeDescription(writeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_profile_actionIcon), + new ThemeDescription(writeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_profile_actionBackground), + new ThemeDescription(writeButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_profile_actionPressedBackground), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGreenText2), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText5), + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + new ThemeDescription(listView, 0, new Class[]{TextCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + + new ThemeDescription(listView, 0, new Class[]{TextDetailCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextDetailCell.class}, new String[]{"valueImageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + new ThemeDescription(listView, 0, new Class[]{TextDetailCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{UserCell.class}, new String[]{"adminImage"}, null, null, null, Theme.key_profile_creatorIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{UserCell.class}, new String[]{"adminImage"}, null, null, null, Theme.key_profile_adminIcon), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + + new ThemeDescription(listView, 0, new Class[]{LoadingCell.class}, new String[]{"progressBar"}, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{AboutLinkCell.class}, new String[]{"imageView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayIcon), + new ThemeDescription(listView, ThemeDescription.FLAG_TEXTCOLOR, new Class[]{AboutLinkCell.class}, Theme.profile_aboutTextPaint, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{AboutLinkCell.class}, Theme.profile_aboutTextPaint, null, null, Theme.key_windowBackgroundWhiteLinkText), + new ThemeDescription(listView, 0, new Class[]{AboutLinkCell.class}, Theme.linkSelectionPaint, null, null, Theme.key_windowBackgroundWhiteLinkSelection), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGray), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(nameTextView[1], 0, null, null, new Drawable[]{Theme.profile_verifiedCheckDrawable}, null, Theme.key_profile_verifiedCheck), + new ThemeDescription(nameTextView[1], 0, null, null, new Drawable[]{Theme.profile_verifiedDrawable}, null, Theme.key_profile_verifiedBackground), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java index 6331dc07fe9..8bdf12bbe36 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileNotificationsActivity.java @@ -3,13 +3,15 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -20,49 +22,73 @@ import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.text.TextUtils; import android.util.TypedValue; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; import android.widget.TextView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.MessagesController; -import org.telegram.messenger.MessagesStorage; -import org.telegram.messenger.NotificationsController; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; -import org.telegram.tgnet.ConnectionsManager; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Cells.RadioCell; +import org.telegram.ui.Cells.TextCheckBoxCell; import org.telegram.ui.Cells.TextColorCell; -import org.telegram.ui.Cells.TextDetailSettingsCell; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Components.AvatarDrawable; -import org.telegram.ui.Components.ColorPickerView; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.AlertsCreator; import org.telegram.ui.Components.LayoutHelper; -import org.telegram.ui.Components.NumberPicker; +import org.telegram.ui.Components.RecyclerListView; + +import java.util.ArrayList; public class ProfileNotificationsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { - private ListView listView; + private RecyclerListView listView; + private ListAdapter adapter; + private AnimatorSet animatorSet; + private long dialog_id; - private int settingsNotificationsRow; - private int settingsVibrateRow; - private int settingsSoundRow; - private int settingsPriorityRow; - private int settingsLedRow; + private boolean customEnabled; + private boolean notificationsEnabled; + + private int customRow; + private int customInfoRow; + private int generalRow; + private int soundRow; + private int vibrateRow; private int smartRow; - private int rowCount = 0; + private int priorityRow; + private int priorityInfoRow; + private int popupRow; + private int popupEnabledRow; + private int popupDisabledRow; + private int popupInfoRow; + private int callsRow; + private int ringtoneRow; + private int callsVibrateRow; + private int ringtoneInfoRow; + private int ledRow; + private int colorRow; + private int ledInfoRow; + private int rowCount; public ProfileNotificationsActivity(Bundle args) { super(args); @@ -71,21 +97,82 @@ public ProfileNotificationsActivity(Bundle args) { @Override public boolean onFragmentCreate() { - settingsNotificationsRow = rowCount++; - settingsVibrateRow = rowCount++; - settingsSoundRow = rowCount++; + rowCount = 0; + customRow = rowCount++; + customInfoRow = rowCount++; + generalRow = rowCount++; + soundRow = rowCount++; + vibrateRow = rowCount++; + if ((int) dialog_id < 0) { + smartRow = rowCount++; + } else { + smartRow = -1; + } if (Build.VERSION.SDK_INT >= 21) { - settingsPriorityRow = rowCount++; + priorityRow = rowCount++; } else { - settingsPriorityRow = -1; + priorityRow = -1; } + priorityInfoRow = rowCount++; + boolean isChannel; int lower_id = (int) dialog_id; if (lower_id < 0) { - smartRow = rowCount++; + TLRPC.Chat chat = MessagesController.getInstance().getChat(-lower_id); + isChannel = chat != null && ChatObject.isChannel(chat) && !chat.megagroup; } else { - smartRow = 1; + isChannel = false; } - settingsLedRow = rowCount++; + if (lower_id != 0 && !isChannel) { + popupRow = rowCount++; + popupEnabledRow = rowCount++; + popupDisabledRow = rowCount++; + popupInfoRow = rowCount++; + } else { + popupRow = -1; + popupEnabledRow = -1; + popupDisabledRow = -1; + popupInfoRow = -1; + } + + if (lower_id > 0 && MessagesController.getInstance().callsEnabled) { + callsRow = rowCount++; + callsVibrateRow = rowCount++; + ringtoneRow = rowCount++; + ringtoneInfoRow = rowCount++; + } else { + callsRow = -1; + callsVibrateRow = -1; + ringtoneRow = -1; + ringtoneInfoRow = -1; + } + + ledRow = rowCount++; + colorRow = rowCount++; + ledInfoRow = rowCount++; + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + customEnabled = preferences.getBoolean("custom_" + dialog_id, false); + + boolean hasOverride = preferences.contains("notify2_" + dialog_id); + int value = preferences.getInt("notify2_" + dialog_id, 0); + if (value == 0) { + if (hasOverride) { + notificationsEnabled = true; + } else { + if ((int) dialog_id < 0) { + notificationsEnabled = preferences.getBoolean("EnableGroup", true); + } else { + notificationsEnabled = preferences.getBoolean("EnableAll", true); + } + } + } else if (value == 1) { + notificationsEnabled = true; + } else if (value == 2) { + notificationsEnabled = false; + } else { + notificationsEnabled = false; + } + NotificationCenter.getInstance().addObserver(this, NotificationCenter.notificationsSettingsUpdated); return super.onFragmentCreate(); } @@ -97,14 +184,18 @@ public void onFragmentDestroy() { } @Override - public View createView(Context context) { + public View createView(final Context context) { actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); - actionBar.setTitle(LocaleController.getString("NotificationsAndSounds", R.string.NotificationsAndSounds)); + actionBar.setTitle(LocaleController.getString("CustomNotifications", R.string.CustomNotifications)); actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { @Override public void onItemClick(int id) { if (id == -1) { + if (notificationsEnabled && customEnabled) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("notify2_" + dialog_id, 0).commit(); + } finishFragment(); } } @@ -112,314 +203,281 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - - listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); - listView.setVerticalScrollBarEnabled(false); - AndroidUtilities.setListViewEdgeEffectColor(listView, AvatarDrawable.getProfileBackColorForId(5)); - frameLayout.addView(listView); - final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - listView.setLayoutParams(layoutParams); - listView.setAdapter(new ListAdapter(context)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + + listView = new RecyclerListView(context); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setAdapter(adapter = new ListAdapter(context)); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setLayoutManager(new LinearLayoutManager(context) { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == settingsVibrateRow) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("Vibrate", R.string.Vibrate)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), - LocaleController.getString("SettingsDefault", R.string.SettingsDefault), - LocaleController.getString("SystemDefault", R.string.SystemDefault), - LocaleController.getString("Short", R.string.Short), - LocaleController.getString("Long", R.string.Long) - }, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - if (which == 0) { - editor.putInt("vibrate_" + dialog_id, 2); - } else if (which == 1) { - editor.putInt("vibrate_" + dialog_id, 0); - } else if (which == 2) { - editor.putInt("vibrate_" + dialog_id, 4); - } else if (which == 3) { - editor.putInt("vibrate_" + dialog_id, 1); - } else if (which == 4) { - editor.putInt("vibrate_" + dialog_id, 3); - } - editor.commit(); - if (listView != null) { - listView.invalidateViews(); + public boolean supportsPredictiveItemAnimations() { + return false; + } + }); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == customRow && view instanceof TextCheckBoxCell) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + customEnabled = !customEnabled; + notificationsEnabled = customEnabled; + preferences.edit().putBoolean("custom_" + dialog_id, customEnabled).commit(); + TextCheckBoxCell cell = (TextCheckBoxCell) view; + cell.setChecked(customEnabled); + int count = listView.getChildCount(); + ArrayList animators = new ArrayList<>(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + RecyclerListView.Holder holder = (RecyclerListView.Holder) listView.getChildViewHolder(child); + int type = holder.getItemViewType(); + if (holder.getAdapterPosition() != customRow && type != 0) { + switch (type) { + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setEnabled(customEnabled, animators); + break; + } + case 2: { + TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; + textCell.setEnabled(customEnabled, animators); + break; + } + case 3: { + TextColorCell textCell = (TextColorCell) holder.itemView; + textCell.setEnabled(customEnabled, animators); + break; + } + case 4: { + RadioCell radioCell = (RadioCell) holder.itemView; + radioCell.setEnabled(customEnabled, animators); + break; + } } } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == settingsNotificationsRow) { - if (getParentActivity() == null) { - return; } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("Default", R.string.Default), - LocaleController.getString("Enabled", R.string.Enabled), - LocaleController.getString("NotificationsDisabled", R.string.NotificationsDisabled) - }, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface d, int which) { + if (!animators.isEmpty()) { + if (animatorSet != null) { + animatorSet.cancel(); + } + animatorSet = new AnimatorSet(); + animatorSet.playTogether(animators); + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + if (animator.equals(animatorSet)) { + animatorSet = null; + } + } + }); + animatorSet.setDuration(150); + animatorSet.start(); + } + } else if (customEnabled) { + if (position == soundRow) { + try { + Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt("notify2_" + dialog_id, which); - if (which == 2) { - NotificationsController.getInstance().removeNotificationsForDialog(dialog_id); + Uri currentSound = null; + + String defaultPath = null; + Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + if (defaultUri != null) { + defaultPath = defaultUri.getPath(); } - MessagesStorage.getInstance().setDialogFlags(dialog_id, which == 2 ? 1 : 0); - editor.commit(); - TLRPC.TL_dialog dialog = MessagesController.getInstance().dialogs_dict.get(dialog_id); - if (dialog != null) { - dialog.notify_settings = new TLRPC.TL_peerNotifySettings(); - if (which == 2) { - dialog.notify_settings.mute_until = Integer.MAX_VALUE; + + String path = preferences.getString("sound_path_" + dialog_id, defaultPath); + if (path != null && !path.equals("NoSound")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); } } - if (listView != null) { - listView.invalidateViews(); - } - NotificationsController.updateServerNotificationsSettings(dialog_id); - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == settingsSoundRow) { - try { - Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - Uri currentSound = null; - String defaultPath = null; - Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; - if (defaultUri != null) { - defaultPath = defaultUri.getPath(); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); + startActivityForResult(tmpIntent, 12); + } catch (Exception e) { + FileLog.e(e); } + } else if (position == ringtoneRow) { + try { + Intent tmpIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + Uri currentSound = null; - String path = preferences.getString("sound_path_" + dialog_id, defaultPath); - if (path != null && !path.equals("NoSound")) { - if (path.equals(defaultPath)) { - currentSound = defaultUri; - } else { - currentSound = Uri.parse(path); + String defaultPath = null; + Uri defaultUri = Settings.System.DEFAULT_NOTIFICATION_URI; + if (defaultUri != null) { + defaultPath = defaultUri.getPath(); } - } - tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); - startActivityForResult(tmpIntent, 12); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else if (i == settingsLedRow) { - if (getParentActivity() == null) { - return; - } - - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - linearLayout.setOrientation(LinearLayout.VERTICAL); - final ColorPickerView colorPickerView = new ColorPickerView(getParentActivity()); - linearLayout.addView(colorPickerView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER)); - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - if (preferences.contains("color_" + dialog_id)) { - colorPickerView.setOldCenterColor(preferences.getInt("color_" + dialog_id, 0xff00ff00)); - } else { - if ((int) dialog_id < 0) { - colorPickerView.setOldCenterColor(preferences.getInt("GroupLed", 0xff00ff00)); - } else { - colorPickerView.setOldCenterColor(preferences.getInt("MessagesLed", 0xff00ff00)); - } - } + String path = preferences.getString("ringtone_path_" + dialog_id, defaultPath); + if (path != null && !path.equals("NoSound")) { + if (path.equals(defaultPath)) { + currentSound = defaultUri; + } else { + currentSound = Uri.parse(path); + } + } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("LedColor", R.string.LedColor)); - builder.setView(linearLayout); - builder.setPositiveButton(LocaleController.getString("Set", R.string.Set), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int which) { - final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt("color_" + dialog_id, colorPickerView.getColor()); - editor.commit(); - listView.invalidateViews(); + tmpIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentSound); + startActivityForResult(tmpIntent, 13); + } catch (Exception e) { + FileLog.e(e); } - }); - builder.setNeutralButton(LocaleController.getString("LedDisabled", R.string.LedDisabled), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putInt("color_" + dialog_id, 0); - editor.commit(); - listView.invalidateViews(); + } else if (position == vibrateRow) { + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), ProfileNotificationsActivity.this, dialog_id, false, false, new Runnable() { + @Override + public void run() { + if (adapter != null) { + adapter.notifyItemChanged(vibrateRow); + } + } + })); + } else if (position == callsVibrateRow) { + showDialog(AlertsCreator.createVibrationSelectDialog(getParentActivity(), ProfileNotificationsActivity.this, dialog_id, "calls_vibrate_", new Runnable() { + @Override + public void run() { + if (adapter != null) { + adapter.notifyItemChanged(callsVibrateRow); + } + } + })); + } else if (position == priorityRow) { + showDialog(AlertsCreator.createPrioritySelectDialog(getParentActivity(), ProfileNotificationsActivity.this, dialog_id, false, false, new Runnable() { + @Override + public void run() { + if (adapter != null) { + adapter.notifyItemChanged(priorityRow); + } + } + })); + } else if (position == smartRow) { + if (getParentActivity() == null) { + return; } - }); - builder.setNegativeButton(LocaleController.getString("Default", R.string.Default), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.remove("color_" + dialog_id); - editor.commit(); - listView.invalidateViews(); + final Context context1 = getParentActivity(); + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); + int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); + if (notifyMaxCount == 0) { + notifyMaxCount = 2; } - }); - showDialog(builder.create()); - } else if (i == settingsPriorityRow) { - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority)); - builder.setItems(new CharSequence[]{ - LocaleController.getString("SettingsDefault", R.string.SettingsDefault), - LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), - LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), - LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax) - }, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - which = 3; - } else { - which--; + final int selected = (notifyDelay / 60 - 1) * 10 + notifyMaxCount - 1; + + RecyclerListView list = new RecyclerListView(getParentActivity()); + list.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + list.setClipToPadding(true); + list.setAdapter(new RecyclerListView.SelectionAdapter() { + @Override + public int getItemCount() { + return 100; } - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putInt("priority_" + dialog_id, which).commit(); - if (listView != null) { - listView.invalidateViews(); + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; } - } - }); - builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); - showDialog(builder.create()); - } else if (i == smartRow) { - if (getParentActivity() == null) { - return; - } - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); - int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); - if (notifyMaxCount == 0) { - notifyMaxCount = 2; - } - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - LinearLayout linearLayout2 = new LinearLayout(getParentActivity()); - linearLayout2.setOrientation(LinearLayout.HORIZONTAL); - linearLayout.addView(linearLayout2); - LinearLayout.LayoutParams layoutParams1 = (LinearLayout.LayoutParams) linearLayout2.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - linearLayout2.setLayoutParams(layoutParams1); - - TextView textView = new TextView(getParentActivity()); - textView.setText(LocaleController.getString("SmartNotificationsSoundAtMost", R.string.SmartNotificationsSoundAtMost)); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - linearLayout2.addView(textView); - layoutParams1 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; - textView.setLayoutParams(layoutParams1); - - final NumberPicker numberPickerTimes = new NumberPicker(getParentActivity()); - numberPickerTimes.setMinValue(1); - numberPickerTimes.setMaxValue(10); - numberPickerTimes.setValue(notifyMaxCount); - linearLayout2.addView(numberPickerTimes); - layoutParams1 = (LinearLayout.LayoutParams) numberPickerTimes.getLayoutParams(); - layoutParams1.width = AndroidUtilities.dp(50); - numberPickerTimes.setLayoutParams(layoutParams1); - - textView = new TextView(getParentActivity()); - textView.setText(LocaleController.getString("SmartNotificationsTimes", R.string.SmartNotificationsTimes)); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - linearLayout2.addView(textView); - layoutParams1 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; - textView.setLayoutParams(layoutParams1); - - linearLayout2 = new LinearLayout(getParentActivity()); - linearLayout2.setOrientation(LinearLayout.HORIZONTAL); - linearLayout.addView(linearLayout2); - layoutParams1 = (LinearLayout.LayoutParams) linearLayout2.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP; - linearLayout2.setLayoutParams(layoutParams1); - - textView = new TextView(getParentActivity()); - textView.setText(LocaleController.getString("SmartNotificationsWithin", R.string.SmartNotificationsWithin)); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - linearLayout2.addView(textView); - layoutParams1 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; - textView.setLayoutParams(layoutParams1); - - final NumberPicker numberPickerMinutes = new NumberPicker(getParentActivity()); - numberPickerMinutes.setMinValue(1); - numberPickerMinutes.setMaxValue(10); - numberPickerMinutes.setValue(notifyDelay / 60); - linearLayout2.addView(numberPickerMinutes); - layoutParams1 = (LinearLayout.LayoutParams) numberPickerMinutes.getLayoutParams(); - layoutParams1.width = AndroidUtilities.dp(50); - numberPickerMinutes.setLayoutParams(layoutParams1); - - textView = new TextView(getParentActivity()); - textView.setText(LocaleController.getString("SmartNotificationsMinutes", R.string.SmartNotificationsMinutes)); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - linearLayout2.addView(textView); - layoutParams1 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams1.width = LayoutHelper.WRAP_CONTENT; - layoutParams1.height = LayoutHelper.WRAP_CONTENT; - layoutParams1.gravity = Gravity.CENTER_VERTICAL | Gravity.LEFT; - textView.setLayoutParams(layoutParams1); - - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle(LocaleController.getString("SmartNotifications", R.string.SmartNotifications)); - builder.setView(linearLayout); - builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putInt("smart_max_count_" + dialog_id, numberPickerTimes.getValue()).commit(); - preferences.edit().putInt("smart_delay_" + dialog_id, numberPickerMinutes.getValue() * 60).commit(); - if (listView != null) { - listView.invalidateViews(); + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = new TextView(context1) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(widthMeasureSpec, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(48), MeasureSpec.EXACTLY)); + } + }; + TextView textView = (TextView) view; + textView.setGravity(Gravity.CENTER); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); + textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + TextView textView = (TextView) holder.itemView; + textView.setTextColor(Theme.getColor(position == selected ? Theme.key_dialogTextGray : Theme.key_dialogTextBlack)); + int notifyMaxCount = position % 10; + int notifyDelay = position / 10; + String times = LocaleController.formatPluralString("Times", notifyMaxCount + 1); + String minutes = LocaleController.formatPluralString("Minutes", notifyDelay + 1); + textView.setText(LocaleController.formatString("SmartNotificationsDetail", R.string.SmartNotificationsDetail, times, minutes)); + } + }); + list.setPadding(0, AndroidUtilities.dp(12), 0, AndroidUtilities.dp(8)); + list.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position < 0 || position >= 100) { + return; + } + int notifyMaxCount = position % 10 + 1; + int notifyDelay = position / 10 + 1; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("smart_max_count_" + dialog_id, notifyMaxCount).commit(); + preferences.edit().putInt("smart_delay_" + dialog_id, notifyDelay * 60).commit(); + if (adapter != null) { + adapter.notifyItemChanged(smartRow); + } + dismissCurrentDialig(); + } + }); + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("SmartNotificationsAlert", R.string.SmartNotificationsAlert)); + builder.setView(list); + builder.setPositiveButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.setNegativeButton(LocaleController.getString("SmartNotificationsDisabled", R.string.SmartNotificationsDisabled), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("smart_max_count_" + dialog_id, 0).commit(); + if (adapter != null) { + adapter.notifyItemChanged(smartRow); + } + dismissCurrentDialig(); } + }); + showDialog(builder.create()); + } else if (position == colorRow) { + if (getParentActivity() == null) { + return; } - }); - builder.setNegativeButton(LocaleController.getString("SmartNotificationsDisabled", R.string.SmartNotificationsDisabled), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - preferences.edit().putInt("smart_max_count_" + dialog_id, 0).commit(); - if (listView != null) { - listView.invalidateViews(); + showDialog(AlertsCreator.createColorSelectDialog(getParentActivity(), dialog_id, false, false, new Runnable() { + @Override + public void run() { + if (adapter != null) { + adapter.notifyItemChanged(colorRow); + } } + })); + } else if (position == popupEnabledRow) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("popup_" + dialog_id, 1).commit(); + ((RadioCell) view).setChecked(true, true); + view = listView.findViewWithTag(2); + if (view != null) { + ((RadioCell) view).setChecked(false, true); + } + } else if (position == popupDisabledRow) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + preferences.edit().putInt("popup_" + dialog_id, 2).commit(); + ((RadioCell) view).setChecked(true, true); + view = listView.findViewWithTag(1); + if (view != null) { + ((RadioCell) view).setChecked(false, true); } - }); - showDialog(builder.create()); + } } } }); @@ -438,10 +496,18 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat if (ringtone != null) { Ringtone rng = RingtoneManager.getRingtone(ApplicationLoader.applicationContext, ringtone); if (rng != null) { - if(ringtone.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { - name = LocaleController.getString("SoundDefault", R.string.SoundDefault); + if (requestCode == 13) { + if (ringtone.equals(Settings.System.DEFAULT_RINGTONE_URI)) { + name = LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone); + } else { + name = rng.getTitle(getParentActivity()); + } } else { - name = rng.getTitle(getParentActivity()); + if (ringtone.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { + name = LocaleController.getString("SoundDefault", R.string.SoundDefault); + } else { + name = rng.getTitle(getParentActivity()); + } } rng.stop(); } @@ -458,177 +524,310 @@ public void onActivityResultFragment(int requestCode, int resultCode, Intent dat editor.putString("sound_" + dialog_id, "NoSound"); editor.putString("sound_path_" + dialog_id, "NoSound"); } + } else if (requestCode == 13) { + if (name != null) { + editor.putString("ringtone_" + dialog_id, name); + editor.putString("ringtone_path_" + dialog_id, ringtone.toString()); + } else { + editor.putString("ringtone_" + dialog_id, "NoSound"); + editor.putString("ringtone_path_" + dialog_id, "NoSound"); + } } editor.commit(); - listView.invalidateViews(); + if (adapter != null) { + adapter.notifyItemChanged(requestCode == 13 ? ringtoneRow : soundRow); + } } } @Override public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.notificationsSettingsUpdated) { - listView.invalidateViews(); + adapter.notifyDataSetChanged(); } } - private class ListAdapter extends BaseFragmentAdapter { - private Context mContext; + private class ListAdapter extends RecyclerView.Adapter { - public ListAdapter(Context context) { - mContext = context; - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } + private Context context; - @Override - public boolean isEnabled(int i) { - return true; + public ListAdapter(Context ctx) { + context = ctx; } @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new HeaderCell(context); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextSettingsCell(context); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new TextInfoPrivacyCell(context); + break; + case 3: + view = new TextColorCell(context); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 4: + view = new RadioCell(context); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 5: + default: + view = new TextCheckBoxCell(context); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextDetailSettingsCell(mContext); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: { + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == generalRow) { + headerCell.setText(LocaleController.getString("General", R.string.General)); + } else if (position == popupRow) { + headerCell.setText(LocaleController.getString("ProfilePopupNotification", R.string.ProfilePopupNotification)); + } else if (position == ledRow) { + headerCell.setText(LocaleController.getString("NotificationsLed", R.string.NotificationsLed)); + } else if (position == callsRow) { + headerCell.setText(LocaleController.getString("VoipNotificationSettings", R.string.VoipNotificationSettings)); + } + break; } - - TextDetailSettingsCell textCell = (TextDetailSettingsCell) view; - - SharedPreferences preferences = mContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - - if (i == settingsVibrateRow) { - int value = preferences.getInt("vibrate_" + dialog_id, 0); - if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("SettingsDefault", R.string.SettingsDefault), true); - } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), true); - } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), true); - } else if (value == 3) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), true); - } else if (value == 4) { - textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("SystemDefault", R.string.SystemDefault), true); + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + if (position == soundRow) { + String value = preferences.getString("sound_" + dialog_id, LocaleController.getString("SoundDefault", R.string.SoundDefault)); + if (value.equals("NoSound")) { + value = LocaleController.getString("NoSound", R.string.NoSound); + } + textCell.setTextAndValue(LocaleController.getString("Sound", R.string.Sound), value, true); + } else if (position == ringtoneRow) { + String value = preferences.getString("ringtone_" + dialog_id, LocaleController.getString("DefaultRingtone", R.string.DefaultRingtone)); + if (value.equals("NoSound")) { + value = LocaleController.getString("NoSound", R.string.NoSound); + } + textCell.setTextAndValue(LocaleController.getString("VoipSettingsRingtone", R.string.VoipSettingsRingtone), value, true); + } else if (position == vibrateRow) { + int value = preferences.getInt("vibrate_" + dialog_id, 0); + if (value == 0 || value == 4) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), smartRow != -1 || priorityRow != -1); + } else if (value == 1) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), smartRow != -1 || priorityRow != -1); + } else if (value == 2) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), smartRow != -1 || priorityRow != -1); + } else if (value == 3) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), smartRow != -1 || priorityRow != -1); + } + } else if (position == priorityRow) { + int value = preferences.getInt("priority_" + dialog_id, 3); + if (value == 0) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), false); + } else if (value == 1) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), false); + } else if (value == 2) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax), false); + } else if (value == 3) { + textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPrioritySettings", R.string.NotificationsPrioritySettings), false); + } + } else if (position == smartRow) { + int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); + int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); + if (notifyMaxCount == 0) { + textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.getString("SmartNotificationsDisabled", R.string.SmartNotificationsDisabled), priorityRow != -1); + } else { + String minutes = LocaleController.formatPluralString("Minutes", notifyDelay / 60); + textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.formatString("SmartNotificationsInfo", R.string.SmartNotificationsInfo, notifyMaxCount, minutes), priorityRow != -1); + } + } else if (position == callsVibrateRow) { + int value = preferences.getInt("calls_vibrate_" + dialog_id, 0); + if (value == 0 || value == 4) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDefault", R.string.VibrationDefault), true); + } else if (value == 1) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Short", R.string.Short), true); + } else if (value == 2) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("VibrationDisabled", R.string.VibrationDisabled), true); + } else if (value == 3) { + textCell.setTextAndValue(LocaleController.getString("Vibrate", R.string.Vibrate), LocaleController.getString("Long", R.string.Long), true); + } } - } else if (i == settingsNotificationsRow) { - int value = preferences.getInt("notify2_" + dialog_id, 0); - if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("Default", R.string.Default), true); - } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("Enabled", R.string.Enabled), true); - } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("NotificationsDisabled", R.string.NotificationsDisabled), true); - } else if (value == 3) { - int delta = preferences.getInt("notifyuntil_" + dialog_id, 0) - ConnectionsManager.getInstance().getCurrentTime(); - String val; - if (delta <= 0) { - val = LocaleController.getString("Enabled", R.string.Enabled); - } else if (delta < 60 * 60) { - val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Minutes", delta / 60)); - } else if (delta < 60 * 60 * 24) { - val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Hours", (int) Math.ceil(delta / 60.0f / 60))); - } else if (delta < 60 * 60 * 24 * 365) { - val = LocaleController.formatString("WillUnmuteIn", R.string.WillUnmuteIn, LocaleController.formatPluralString("Days", (int) Math.ceil(delta / 60.0f / 60 / 24))); + break; + } + case 2: { + TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; + if (position == popupInfoRow) { + textCell.setText(LocaleController.getString("ProfilePopupNotificationInfo", R.string.ProfilePopupNotificationInfo)); + textCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == ledInfoRow) { + textCell.setText(LocaleController.getString("NotificationsLedInfo", R.string.NotificationsLedInfo)); + textCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == priorityInfoRow) { + if (priorityRow == -1) { + textCell.setText(""); } else { - val = null; + textCell.setText(LocaleController.getString("PriorityInfo", R.string.PriorityInfo)); } - if (val != null) { - textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), val, true); + textCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == customInfoRow) { + textCell.setText(null); + textCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == ringtoneInfoRow) { + textCell.setText(LocaleController.getString("VoipRingtoneInfo", R.string.VoipRingtoneInfo)); + textCell.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } + break; + } + case 3: { + TextColorCell textCell = (TextColorCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int color; + if (preferences.contains("color_" + dialog_id)) { + color = preferences.getInt("color_" + dialog_id, 0xff0000ff); + } else { + if ((int) dialog_id < 0) { + color = preferences.getInt("GroupLed", 0xff0000ff); } else { - textCell.setTextAndValue(LocaleController.getString("Notifications", R.string.Notifications), LocaleController.getString("NotificationsDisabled", R.string.NotificationsDisabled), true); + color = preferences.getInt("MessagesLed", 0xff0000ff); } } - } else if (i == settingsSoundRow) { - String value = preferences.getString("sound_" + dialog_id, LocaleController.getString("SoundDefault", R.string.SoundDefault)); - if (value.equals("NoSound")) { - value = LocaleController.getString("NoSound", R.string.NoSound); + for (int a = 0; a < 9; a++) { + if (TextColorCell.colorsToSave[a] == color) { + color = TextColorCell.colors[a]; + break; + } } - textCell.setTextAndValue(LocaleController.getString("Sound", R.string.Sound), value, true); - } else if (i == settingsPriorityRow) { - int value = preferences.getInt("priority_" + dialog_id, 3); - if (value == 0) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityDefault", R.string.NotificationsPriorityDefault), true); - } else if (value == 1) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityHigh", R.string.NotificationsPriorityHigh), true); - } else if (value == 2) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("NotificationsPriorityMax", R.string.NotificationsPriorityMax), true); - } else if (value == 3) { - textCell.setTextAndValue(LocaleController.getString("NotificationsPriority", R.string.NotificationsPriority), LocaleController.getString("SettingsDefault", R.string.SettingsDefault), true); + textCell.setTextAndColor(LocaleController.getString("NotificationsLedColor", R.string.NotificationsLedColor), color, false); + break; + } + case 4: { + RadioCell radioCell = (RadioCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + int popup = preferences.getInt("popup_" + dialog_id, 0); + if (popup == 0) { + popup = preferences.getInt((int) dialog_id < 0 ? "popupGroup" : "popupAll", 0); + if (popup != 0) { + popup = 1; + } else { + popup = 2; + } } - } else if (i == smartRow) { - int notifyMaxCount = preferences.getInt("smart_max_count_" + dialog_id, 2); - int notifyDelay = preferences.getInt("smart_delay_" + dialog_id, 3 * 60); - if (notifyMaxCount == 0) { - textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.getString("SmartNotificationsDisabled", R.string.SmartNotificationsDisabled), true); - } else { - String times = LocaleController.formatPluralString("Times", notifyMaxCount); - String minutes = LocaleController.formatPluralString("Minutes", notifyDelay / 60); - textCell.setTextAndValue(LocaleController.getString("SmartNotifications", R.string.SmartNotifications), LocaleController.formatString("SmartNotificationsInfo", R.string.SmartNotificationsInfo, times, minutes), true); + if (position == popupEnabledRow) { + radioCell.setText(LocaleController.getString("PopupEnabled", R.string.PopupEnabled), popup == 1, true); + radioCell.setTag(1); + } else if (position == popupDisabledRow) { + radioCell.setText(LocaleController.getString("PopupDisabled", R.string.PopupDisabled), popup == 2, false); + radioCell.setTag(2); } + break; } - } else if (type == 1) { - if (view == null) { - view = new TextColorCell(mContext); + case 5: { + TextCheckBoxCell cell = (TextCheckBoxCell) holder.itemView; + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); + cell.setTextAndCheck(LocaleController.getString("NotificationsEnableCustom", R.string.NotificationsEnableCustom), customEnabled && notificationsEnabled, false); + break; } + } + } - TextColorCell textCell = (TextColorCell) view; - - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("Notifications", Activity.MODE_PRIVATE); - - if (preferences.contains("color_" + dialog_id)) { - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), preferences.getInt("color_" + dialog_id, 0xff00ff00), false); - } else { - if ((int)dialog_id < 0) { - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), preferences.getInt("GroupLed", 0xff00ff00), false); - } else { - textCell.setTextAndColor(LocaleController.getString("LedColor", R.string.LedColor), preferences.getInt("MessagesLed", 0xff00ff00), false); + @Override + public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { + if (holder.getItemViewType() != 0) { + switch (holder.getItemViewType()) { + case 1: { + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 2: { + TextInfoPrivacyCell textCell = (TextInfoPrivacyCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 3: { + TextColorCell textCell = (TextColorCell) holder.itemView; + textCell.setEnabled(customEnabled && notificationsEnabled, null); + break; + } + case 4: { + RadioCell radioCell = (RadioCell) holder.itemView; + radioCell.setEnabled(customEnabled && notificationsEnabled, null); + break; } } } - return view; } @Override - public int getItemViewType(int i) { - if (i == settingsNotificationsRow || i == settingsVibrateRow || i == settingsSoundRow || i == settingsPriorityRow || i == smartRow) { + public int getItemViewType(int position) { + if (position == generalRow || position == popupRow || position == ledRow || position == callsRow) { return 0; - } else if (i == settingsLedRow) { + } else if (position == soundRow || position == vibrateRow || position == priorityRow || position == smartRow || position == ringtoneRow || position == callsVibrateRow) { return 1; + } else if (position == popupInfoRow || position == ledInfoRow || position == priorityInfoRow || position == customInfoRow || position == ringtoneInfoRow) { + return 2; + } else if (position == colorRow) { + return 3; + } else if (position == popupEnabledRow || position == popupDisabledRow) { + return 4; + } else if (position == customRow) { + return 5; } return 0; } + } - @Override - public int getViewTypeCount() { - return 2; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{HeaderCell.class, TextSettingsCell.class, TextColorCell.class, RadioCell.class, TextCheckBoxCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return false; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{TextColorCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + + new ThemeDescription(listView, 0, new Class[]{RadioCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOX, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackground), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKBOXCHECK, new Class[]{RadioCell.class}, new String[]{"radioButton"}, null, null, null, Theme.key_radioBackgroundChecked), + + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareUnchecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareDisabled), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareBackground), + new ThemeDescription(listView, 0, new Class[]{TextCheckBoxCell.class}, null, null, null, Theme.key_checkboxSquareCheck), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java index e169186424b..0927726c3dc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ReportOtherActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -36,6 +36,8 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.LayoutHelper; public class ReportOtherActivity extends BaseFragment { @@ -96,8 +98,9 @@ public boolean onTouch(View v, MotionEvent event) { firstNameField = new EditText(context); firstNameField.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); - firstNameField.setHintTextColor(0xff979797); - firstNameField.setTextColor(0xff212121); + firstNameField.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + firstNameField.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + firstNameField.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); firstNameField.setMaxLines(3); firstNameField.setPadding(0, 0, 0, 0); firstNameField.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); @@ -148,4 +151,21 @@ public void run() { }, 100); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(firstNameField, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(firstNameField, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java index 3125447a198..aa4646dfa84 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SecretPhotoViewer.java @@ -3,11 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; @@ -144,6 +145,7 @@ protected void onDraw(Canvas canvas) { private MessageObject currentMessageObject = null; + @SuppressLint("StaticFieldLeak") private static volatile SecretPhotoViewer Instance = null; public static SecretPhotoViewer getInstance() { SecretPhotoViewer localInstance = Instance; @@ -274,7 +276,7 @@ public void openPhoto(MessageObject messageObject) { try { bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options); } catch (Throwable e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (bitmap != null) { drawable = new BitmapDrawable(bitmap); @@ -297,7 +299,7 @@ public void openPhoto(MessageObject messageObject) { wm.removeView(windowView); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); @@ -331,7 +333,7 @@ public void run() { wm.removeView(windowView); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -350,7 +352,7 @@ public void destroyPhotoViewer() { } windowView = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Instance = null; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java index 283b8d675bd..5d48aa4296e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SessionsActivity.java @@ -3,28 +3,24 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.os.Build; import android.util.TypedValue; import android.view.Gravity; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; -import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -34,25 +30,37 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.messenger.UserConfig; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.HeaderCell; import org.telegram.ui.Cells.SessionCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; public class SessionsActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; + private RecyclerListView listView; + private ImageView imageView; + private TextView textView1; + private TextView textView2; + private EmptyTextProgressView emptyView; + private ArrayList sessions = new ArrayList<>(); private TLRPC.TL_authorization currentSession = null; private boolean loading; @@ -102,88 +110,49 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); emptyLayout = new LinearLayout(context); emptyLayout.setOrientation(LinearLayout.VERTICAL); emptyLayout.setGravity(Gravity.CENTER); - emptyLayout.setBackgroundResource(R.drawable.greydivider_bottom); + emptyLayout.setBackgroundDrawable(Theme.getThemedDrawable(context, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); emptyLayout.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight())); - ImageView imageView = new ImageView(context); + imageView = new ImageView(context); imageView.setImageResource(R.drawable.devices); - emptyLayout.addView(imageView); - LinearLayout.LayoutParams layoutParams2 = (LinearLayout.LayoutParams) imageView.getLayoutParams(); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - imageView.setLayoutParams(layoutParams2); - - TextView textView = new TextView(context); - textView.setTextColor(0xff8a8a8a); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); - textView.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); - textView.setText(LocaleController.getString("NoOtherSessions", R.string.NoOtherSessions)); - emptyLayout.addView(textView); - layoutParams2 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams2.topMargin = AndroidUtilities.dp(16); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - layoutParams2.gravity = Gravity.CENTER; - textView.setLayoutParams(layoutParams2); - - textView = new TextView(context); - textView.setTextColor(0xff8a8a8a); - textView.setGravity(Gravity.CENTER); - textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); - textView.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); - textView.setText(LocaleController.getString("NoOtherSessionsInfo", R.string.NoOtherSessionsInfo)); - emptyLayout.addView(textView); - layoutParams2 = (LinearLayout.LayoutParams) textView.getLayoutParams(); - layoutParams2.topMargin = AndroidUtilities.dp(14); - layoutParams2.width = LayoutHelper.WRAP_CONTENT; - layoutParams2.height = LayoutHelper.WRAP_CONTENT; - layoutParams2.gravity = Gravity.CENTER; - textView.setLayoutParams(layoutParams2); - - FrameLayout progressView = new FrameLayout(context); - frameLayout.addView(progressView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - progressView.setLayoutParams(layoutParams); - progressView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - ProgressBar progressBar = new ProgressBar(context); - progressView.addView(progressBar); - layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.CENTER; - progressView.setLayoutParams(layoutParams); - - ListView listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_sessions_devicesImage), PorterDuff.Mode.MULTIPLY)); + emptyLayout.addView(imageView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT)); + + textView1 = new TextView(context); + textView1.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + textView1.setGravity(Gravity.CENTER); + textView1.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + textView1.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + textView1.setText(LocaleController.getString("NoOtherSessions", R.string.NoOtherSessions)); + emptyLayout.addView(textView1, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 16, 0, 0)); + + textView2 = new TextView(context); + textView2.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2)); + textView2.setGravity(Gravity.CENTER); + textView2.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + textView2.setPadding(AndroidUtilities.dp(20), 0, AndroidUtilities.dp(20), 0); + textView2.setText(LocaleController.getString("NoOtherSessionsInfo", R.string.NoOtherSessionsInfo)); + emptyLayout.addView(textView2, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 0, 14, 0, 0)); + + emptyView = new EmptyTextProgressView(context); + emptyView.showProgress(); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); + + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); - listView.setEmptyView(progressView); - frameLayout.addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - listView.setLayoutParams(layoutParams); + listView.setEmptyView(emptyView); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == terminateAllSessionsRow) { + public void onItemClick(View view, final int position) { + if (position == terminateAllSessionsRow) { if (getParentActivity() == null) { return; } @@ -223,20 +192,23 @@ public void run() { }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); - } else if (i >= otherSessionsStartRow && i < otherSessionsEndRow) { + } else if (position >= otherSessionsStartRow && position < otherSessionsEndRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("TerminateSessionQuestion", R.string.TerminateSessionQuestion)); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int option) { - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + if (getParentActivity() == null) { + return; + } + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); progressDialog.show(); - final TLRPC.TL_authorization authorization = sessions.get(i - otherSessionsStartRow); + final TLRPC.TL_authorization authorization = sessions.get(position - otherSessionsStartRow); TLRPC.TL_account_resetAuthorization req = new TLRPC.TL_account_resetAuthorization(); req.hash = authorization.hash; ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -248,7 +220,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (error == null) { sessions.remove(authorization); @@ -357,122 +329,147 @@ private void updateRows() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { mContext = context; } - + @Override - public boolean areAllItemsEnabled() { - return false; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == terminateAllSessionsRow || position >= otherSessionsStartRow && position < otherSessionsEndRow; } @Override - public boolean isEnabled(int i) { - return i == terminateAllSessionsRow || i >= otherSessionsStartRow && i < otherSessionsEndRow; - } - - @Override - public int getCount() { + public int getItemCount() { return loading ? 0 : rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - if (i == terminateAllSessionsRow) { - textCell.setTextColor(0xffdb5151); - textCell.setText(LocaleController.getString("TerminateAllSessions", R.string.TerminateAllSessions), false); - } - } else if (type == 1) { - if (view == null) { + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: view = new TextInfoPrivacyCell(mContext); - } - if (i == terminateAllSessionsDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("ClearOtherSessionsHelp", R.string.ClearOtherSessionsHelp)); - view.setBackgroundResource(R.drawable.greydivider); - } else if (i == otherSessionsTerminateDetail) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("TerminateSessionInfo", R.string.TerminateSessionInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } - } else if (type == 2) { - if (view == null) { + break; + case 2: view = new HeaderCell(mContext); - view.setBackgroundColor(0xffffffff); - } - if (i == currentSessionSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("CurrentSession", R.string.CurrentSession)); - } else if (i == otherSessionsSectionRow) { - ((HeaderCell) view).setText(LocaleController.getString("OtherSessions", R.string.OtherSessions)); - } - } else if (type == 3) { - ViewGroup.LayoutParams layoutParams = emptyLayout.getLayoutParams(); - if (layoutParams != null) { - layoutParams.height = Math.max(AndroidUtilities.dp(220), AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(128) - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)); - emptyLayout.setLayoutParams(layoutParams); - } - return emptyLayout; - } else if (type == 4) { - if (view == null) { + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 3: + view = emptyLayout; + break; + default: view = new SessionCell(mContext); - view.setBackgroundColor(0xffffffff); - } - if (i == currentSessionRow) { - ((SessionCell) view).setSession(currentSession, !sessions.isEmpty()); - } else { - ((SessionCell) view).setSession(sessions.get(i - otherSessionsStartRow), i != otherSessionsEndRow - 1); - } + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; } - return view; + return new RecyclerListView.Holder(view); } @Override - public int getItemViewType(int i) { - if (i == terminateAllSessionsRow) { + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + if (position == terminateAllSessionsRow) { + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText2)); + textCell.setText(LocaleController.getString("TerminateAllSessions", R.string.TerminateAllSessions), false); + } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == terminateAllSessionsDetailRow) { + privacyCell.setText(LocaleController.getString("ClearOtherSessionsHelp", R.string.ClearOtherSessionsHelp)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else if (position == otherSessionsTerminateDetail) { + privacyCell.setText(LocaleController.getString("TerminateSessionInfo", R.string.TerminateSessionInfo)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; + case 2: + HeaderCell headerCell = (HeaderCell) holder.itemView; + if (position == currentSessionSectionRow) { + headerCell.setText(LocaleController.getString("CurrentSession", R.string.CurrentSession)); + } else if (position == otherSessionsSectionRow) { + headerCell.setText(LocaleController.getString("OtherSessions", R.string.OtherSessions)); + } + break; + case 3: + ViewGroup.LayoutParams layoutParams = emptyLayout.getLayoutParams(); + if (layoutParams != null) { + layoutParams.height = Math.max(AndroidUtilities.dp(220), AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(128) - (Build.VERSION.SDK_INT >= 21 ? AndroidUtilities.statusBarHeight : 0)); + emptyLayout.setLayoutParams(layoutParams); + } + break; + default: + SessionCell sessionCell = (SessionCell) holder.itemView; + if (position == currentSessionRow) { + sessionCell.setSession(currentSession, !sessions.isEmpty()); + } else { + sessionCell.setSession(sessions.get(position - otherSessionsStartRow), position != otherSessionsEndRow - 1); + } + break; + } + } + + @Override + public int getItemViewType(int position) { + if (position == terminateAllSessionsRow) { return 0; - } else if (i == terminateAllSessionsDetailRow || i == otherSessionsTerminateDetail) { + } else if (position == terminateAllSessionsDetailRow || position == otherSessionsTerminateDetail) { return 1; - } else if (i == currentSessionSectionRow || i == otherSessionsSectionRow) { + } else if (position == currentSessionSectionRow || position == otherSessionsSectionRow) { return 2; - } else if (i == noOtherSessionsRow) { + } else if (position == noOtherSessionsRow) { return 3; - } else if (i == currentSessionRow || i >= otherSessionsStartRow && i < otherSessionsEndRow) { + } else if (position == currentSessionRow || position >= otherSessionsStartRow && position < otherSessionsEndRow) { return 4; } return 0; } + } - @Override - public int getViewTypeCount() { - return 5; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class, HeaderCell.class, SessionCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return loading; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(imageView, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_sessions_devicesImage), + new ThemeDescription(textView1, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(textView2, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText2), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{SessionCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{SessionCell.class}, new String[]{"onlineTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{SessionCell.class}, new String[]{"onlineTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + new ThemeDescription(listView, 0, new Class[]{SessionCell.class}, new String[]{"detailTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{SessionCell.class}, new String[]{"detailExTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText3), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java index c5cdcaf675d..508df4d545c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SetAdminsActivity.java @@ -3,19 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.content.Context; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.ListView; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ContactsController; @@ -26,17 +25,21 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextCheckCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.UserCell; import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; import java.util.ArrayList; import java.util.Collections; @@ -48,8 +51,8 @@ public class SetAdminsActivity extends BaseFragment implements NotificationCente private ListAdapter listAdapter; private SearchAdapter searchAdapter; - private EmptyTextProgressView searchEmptyView; - private ListView listView; + private EmptyTextProgressView emptyView; + private RecyclerListView listView; private TLRPC.ChatFull info; private ArrayList participants = new ArrayList<>(); private int chat_id; @@ -106,7 +109,7 @@ public void onItemClick(int id) { @Override public void onSearchExpand() { searching = true; - listView.setEmptyView(searchEmptyView); + listView.setEmptyView(emptyView); } @Override @@ -115,10 +118,9 @@ public void onSearchCollapse() { searchWas = false; if (listView != null) { listView.setEmptyView(null); - searchEmptyView.setVisibility(View.GONE); + emptyView.setVisibility(View.GONE); if (listView.getAdapter() != listAdapter) { listView.setAdapter(listAdapter); - fragmentView.setBackgroundColor(0xfff0f0f0); } } if (searchAdapter != null) { @@ -133,11 +135,11 @@ public void onTextChanged(EditText editText) { searchWas = true; if (searchAdapter != null && listView.getAdapter() != searchAdapter) { listView.setAdapter(searchAdapter); - fragmentView.setBackgroundColor(0xffffffff); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); } - if (searchEmptyView != null && listView.getEmptyView() != searchEmptyView) { - searchEmptyView.showTextView(); - listView.setEmptyView(searchEmptyView); + if (emptyView != null && listView.getEmptyView() != emptyView) { + emptyView.showTextView(); + listView.setEmptyView(emptyView); } } if (searchAdapter != null) { @@ -152,25 +154,23 @@ public void onTextChanged(EditText editText) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - fragmentView.setBackgroundColor(0xfff0f0f0); + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); - listView = new ListView(context); - listView.setDivider(null); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (listView.getAdapter() == searchAdapter || i >= usersStartRow && i < usersEndRow) { + public void onItemClick(View view, int position) { + if (listView.getAdapter() == searchAdapter || position >= usersStartRow && position < usersEndRow) { UserCell userCell = (UserCell) view; chat = MessagesController.getInstance().getChat(chat_id); TLRPC.ChatParticipant participant; int index = -1; if (listView.getAdapter() == searchAdapter) { - participant = searchAdapter.getItem(i); + participant = searchAdapter.getItem(position); for (int a = 0; a < participants.size(); a++) { TLRPC.ChatParticipant p = participants.get(a); if (p.user_id == participant.user_id) { @@ -179,7 +179,7 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long } } } else { - participant = participants.get(index = i - usersStartRow); + participant = participants.get(index = position - usersStartRow); } if (index != -1 && !(participant instanceof TLRPC.TL_chatParticipantCreator)) { TLRPC.ChatParticipant newParticipant; @@ -200,7 +200,7 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long info.participants.participants.set(index, newParticipant); } if (listView.getAdapter() == searchAdapter) { - searchAdapter.searchResult.set(i, newParticipant); + searchAdapter.searchResult.set(position, newParticipant); } participant = newParticipant; userCell.setChecked(!(participant instanceof TLRPC.TL_chatParticipant) || chat != null && !chat.admins_enabled, true); @@ -209,7 +209,7 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long } } } else { - if (i == allAdminsRow) { + if (position == allAdminsRow) { chat = MessagesController.getInstance().getChat(chat_id); if (chat != null) { chat.admins_enabled = !chat.admins_enabled; @@ -221,12 +221,12 @@ public void onItemClick(AdapterView adapterView, View view, final int i, long } }); - searchEmptyView = new EmptyTextProgressView(context); - searchEmptyView.setVisibility(View.GONE); - searchEmptyView.setShowAtCenter(true); - searchEmptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); - frameLayout.addView(searchEmptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); - searchEmptyView.showTextView(); + emptyView = new EmptyTextProgressView(context); + emptyView.setVisibility(View.GONE); + emptyView.setShowAtCenter(true); + emptyView.setText(LocaleController.getString("NoResult", R.string.NoResult)); + frameLayout.addView(emptyView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + emptyView.showTextView(); updateRowsIds(); @@ -242,7 +242,7 @@ public void didReceivedNotification(int id, Object... args) { updateChatParticipants(); updateRowsIds(); } - } if (id == NotificationCenter.updateInterfaces) { + } else if (id == NotificationCenter.updateInterfaces) { int mask = (Integer) args[0]; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 || (mask & MessagesController.UPDATE_MASK_NAME) != 0 || (mask & MessagesController.UPDATE_MASK_STATUS) != 0) { if (listView != null) { @@ -333,7 +333,7 @@ public int compare(TLRPC.ChatParticipant lhs, TLRPC.ChatParticipant rhs) { } }); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -361,7 +361,8 @@ private void updateRowsIds() { } } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -369,16 +370,12 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - if (i == allAdminsRow) { + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + if (position == allAdminsRow) { return true; - } else if (i >= usersStartRow && i < usersEndRow) { - TLRPC.ChatParticipant participant = participants.get(i - usersStartRow); + } else if (position >= usersStartRow && position < usersEndRow) { + TLRPC.ChatParticipant participant = participants.get(position - usersStartRow); if (!(participant instanceof TLRPC.TL_chatParticipantCreator)) { return true; } @@ -387,69 +384,66 @@ public boolean isEnabled(int i) { } @Override - public int getCount() { + public int getItemCount() { return rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextCheckCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + view = new TextInfoPrivacyCell(mContext); + break; + case 2: + default: + view = new UserCell(mContext, 1, 2, false); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int type = getItemViewType(i); - if (type == 0) { - if (view == null) { - view = new TextCheckCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextCheckCell checkCell = (TextCheckCell) view; - chat = MessagesController.getInstance().getChat(chat_id); - checkCell.setTextAndCheck(LocaleController.getString("SetAdminsAll", R.string.SetAdminsAll), chat != null && !chat.admins_enabled, false); - } else if (type == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == allAdminsInfoRow) { - if (chat.admins_enabled) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("SetAdminsNotAllInfo", R.string.SetAdminsNotAllInfo)); - } else { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("SetAdminsAllInfo", R.string.SetAdminsAllInfo)); - } - if (usersStartRow != -1) { - view.setBackgroundResource(R.drawable.greydivider); - } else { - view.setBackgroundResource(R.drawable.greydivider_bottom); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextCheckCell checkCell = (TextCheckCell) holder.itemView; + chat = MessagesController.getInstance().getChat(chat_id); + checkCell.setTextAndCheck(LocaleController.getString("SetAdminsAll", R.string.SetAdminsAll), chat != null && !chat.admins_enabled, false); + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == allAdminsInfoRow) { + if (chat.admins_enabled) { + privacyCell.setText(LocaleController.getString("SetAdminsNotAllInfo", R.string.SetAdminsNotAllInfo)); + } else { + privacyCell.setText(LocaleController.getString("SetAdminsAllInfo", R.string.SetAdminsAllInfo)); + } + if (usersStartRow != -1) { + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + } else { + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + } else if (position == usersEndRow) { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); } - } else if (i == usersEndRow) { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } - } else if (type == 2) { - if (view == null) { - view = new UserCell(mContext, 1, 2, false); - view.setBackgroundColor(0xffffffff); - } - UserCell userCell = (UserCell) view; - TLRPC.ChatParticipant part = participants.get(i - usersStartRow); - TLRPC.User user = MessagesController.getInstance().getUser(part.user_id); - userCell.setData(user, null, null, 0); - chat = MessagesController.getInstance().getChat(chat_id); - userCell.setChecked(!(part instanceof TLRPC.TL_chatParticipant) || chat != null && !chat.admins_enabled, false); - userCell.setCheckDisabled(chat == null || !chat.admins_enabled || part.user_id == UserConfig.getClientUserId()); + break; + case 2: + UserCell userCell = (UserCell) holder.itemView; + TLRPC.ChatParticipant part = participants.get(position - usersStartRow); + TLRPC.User user = MessagesController.getInstance().getUser(part.user_id); + userCell.setData(user, null, null, 0); + chat = MessagesController.getInstance().getChat(chat_id); + userCell.setChecked(!(part instanceof TLRPC.TL_chatParticipant) || chat != null && !chat.admins_enabled, false); + userCell.setCheckDisabled(chat == null || !chat.admins_enabled || part.user_id == UserConfig.getClientUserId()); + break; } - return view; } @Override @@ -462,19 +456,9 @@ public int getItemViewType(int i) { return 2; } } - - @Override - public int getViewTypeCount() { - return 3; - } - - @Override - public boolean isEmpty() { - return false; - } } - public class SearchAdapter extends BaseFragmentAdapter { + public class SearchAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; private ArrayList searchResult = new ArrayList<>(); @@ -491,7 +475,7 @@ public void search(final String query) { searchTimer.cancel(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } if (query == null) { searchResult.clear(); @@ -506,7 +490,7 @@ public void run() { searchTimer.cancel(); searchTimer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } processSearch(query); } @@ -593,49 +577,34 @@ public void run() { } @Override - public boolean areAllItemsEnabled() { + public boolean isEnabled(RecyclerView.ViewHolder holder) { return true; } @Override - public boolean isEnabled(int i) { - return true; - } - - @Override - public int getCount() { + public int getItemCount() { return searchResult.size(); } - @Override public TLRPC.ChatParticipant getItem(int i) { return searchResult.get(i); } @Override - public long getItemId(int i) { - return i; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new RecyclerListView.Holder(new UserCell(mContext, 1, 2, false)); } @Override - public boolean hasStableIds() { - return true; - } - - @Override - public View getView(int i, View view, ViewGroup viewGroup) { - if (view == null) { - view = new UserCell(mContext, 1, 2, false); - } - - TLRPC.ChatParticipant participant = getItem(i); + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + TLRPC.ChatParticipant participant = getItem(position); TLRPC.User user = MessagesController.getInstance().getUser(participant.user_id); String un = user.username; CharSequence username = null; CharSequence name = null; - if (i < searchResult.size()) { - name = searchResultNames.get(i); + if (position < searchResult.size()) { + name = searchResultNames.get(position); if (name != null && un != null && un.length() > 0) { if (name.toString().startsWith("@" + un)) { username = name; @@ -643,27 +612,76 @@ public View getView(int i, View view, ViewGroup viewGroup) { } } } - UserCell userCell = (UserCell) view; + UserCell userCell = (UserCell) holder.itemView; userCell.setData(user, name, username, 0); chat = MessagesController.getInstance().getChat(chat_id); userCell.setChecked(!(participant instanceof TLRPC.TL_chatParticipant) || chat != null && !chat.admins_enabled, false); userCell.setCheckDisabled(chat == null || !chat.admins_enabled || participant.user_id == UserConfig.getClientUserId()); - return view; } @Override public int getItemViewType(int i) { return 0; } + } - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean isEmpty() { - return searchResult.isEmpty(); - } + @Override + public ThemeDescription[] getThemeDescriptions() { + ThemeDescription.ThemeDescriptionDelegate сellDelegate = new ThemeDescription.ThemeDescriptionDelegate() { + @Override + public void didSetColor(int color) { + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof UserCell) { + ((UserCell) child).update(0); + } + } + } + }; + + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextCheckCell.class, UserCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCH, null, null, null, null, Theme.key_actionBarDefaultSearch), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SEARCHPLACEHOLDER, null, null, null, null, Theme.key_actionBarDefaultSearchPlaceholder), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_emptyListPlaceholder), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, null, null, Theme.key_checkboxSquareUnchecked), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, null, null, Theme.key_checkboxSquareDisabled), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, null, null, Theme.key_checkboxSquareBackground), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, null, null, Theme.key_checkboxSquareCheck), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"nameTextView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteGrayText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, new String[]{"statusOnlineColor"}, null, null, сellDelegate, Theme.key_windowBackgroundWhiteBlueText), + new ThemeDescription(listView, 0, new Class[]{UserCell.class}, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundRed), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundOrange), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundViolet), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundGreen), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundCyan), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundBlue), + new ThemeDescription(null, 0, null, null, null, сellDelegate, Theme.key_avatar_backgroundPink), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java index c0447792a04..e5bd4559f62 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SettingsActivity.java @@ -3,20 +3,18 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.StateListAnimator; import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -26,14 +24,19 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Outline; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.text.Html; import android.text.Spannable; +import android.text.SpannableString; import android.text.TextUtils; import android.text.method.LinkMovementMethod; +import android.text.style.URLSpan; import android.util.Base64; import android.util.TypedValue; import android.view.Gravity; @@ -51,7 +54,6 @@ import android.widget.Toast; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.PhoneFormat.PhoneFormat; import org.telegram.messenger.ContactsController; import org.telegram.messenger.MediaController; @@ -60,6 +62,7 @@ import org.telegram.messenger.BuildVars; import org.telegram.messenger.LocaleController; import org.telegram.messenger.FileLoader; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.browser.Browser; import org.telegram.messenger.query.StickersQuery; import org.telegram.messenger.support.widget.LinearLayoutManager; @@ -76,7 +79,9 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.MessageObject; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.CheckBoxCell; import org.telegram.ui.Cells.TextInfoCell; import org.telegram.ui.Cells.EmptyCell; @@ -92,10 +97,12 @@ import org.telegram.ui.Components.AvatarUpdater; import org.telegram.ui.Components.BackupImageView; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.Components.CombinedDrawable; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.NumberPicker; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.URLSpanNoUnderline; import java.io.File; import java.util.ArrayList; @@ -114,6 +121,7 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private AvatarUpdater avatarUpdater = new AvatarUpdater(); private View extraHeightView; private View shadowView; + private AvatarDrawable avatarDrawable; private int extraHeight; @@ -127,13 +135,10 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private int enableAnimationsRow; private int notificationRow; private int backgroundRow; + private int themeRow; private int languageRow; private int privacyRow; - private int mediaDownloadSection; - private int mediaDownloadSection2; - private int mobileDownloadRow; - private int wifiDownloadRow; - private int roamingDownloadRow; + private int dataRow; private int saveToGalleryRow; private int messagesSectionRow; private int messagesSectionRow2; @@ -142,7 +147,6 @@ public class SettingsActivity extends BaseFragment implements NotificationCenter private int textSizeRow; private int stickersRow; private int emojiRow; - private int cacheRow; private int raiseToSpeakRow; private int sendByEnterRow; private int supportSectionRow; @@ -169,7 +173,7 @@ public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, try { return super.onTouchEvent(widget, buffer, event); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } return false; } @@ -242,16 +246,11 @@ public void run() { settingsSectionRow2 = rowCount++; notificationRow = rowCount++; privacyRow = rowCount++; + dataRow = rowCount++; backgroundRow = rowCount++; + themeRow = rowCount++; languageRow = rowCount++; enableAnimationsRow = rowCount++; - mediaDownloadSection = rowCount++; - mediaDownloadSection2 = rowCount++; - mobileDownloadRow = rowCount++; - wifiDownloadRow = rowCount++; - roamingDownloadRow = rowCount++; - autoplayGifsRow = rowCount++; - saveToGalleryRow = rowCount++; messagesSectionRow = rowCount++; messagesSectionRow2 = rowCount++; customTabsRow = rowCount++; @@ -261,9 +260,10 @@ public void run() { stickersRow = rowCount++; //emojiRow = rowCount++; textSizeRow = rowCount++; - cacheRow = rowCount++; raiseToSpeakRow = rowCount++; sendByEnterRow = rowCount++; + autoplayGifsRow = rowCount++; + saveToGalleryRow = rowCount++; supportSectionRow = rowCount++; supportSectionRow2 = rowCount++; askQuestionRow = rowCount++; @@ -299,8 +299,9 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { - actionBar.setBackgroundColor(AvatarDrawable.getProfileBackColorForId(5)); - actionBar.setItemsBackgroundColor(AvatarDrawable.getButtonColorForId(5)); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_avatar_backgroundActionBarBlue)); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_avatar_actionBarSelectorBlue), false); + actionBar.setItemsColor(Theme.getColor(Theme.key_avatar_actionBarIconBlue), false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAddToContainer(false); extraHeight = 88; @@ -334,8 +335,8 @@ public void onClick(DialogInterface dialogInterface, int i) { }); ActionBarMenu menu = actionBar.createMenu(); ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_other); - item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName), 0); - item.addSubItem(logout, LocaleController.getString("LogOut", R.string.LogOut), 0); + item.addSubItem(edit_name, LocaleController.getString("EditName", R.string.EditName)); + item.addSubItem(logout, LocaleController.getString("LogOut", R.string.LogOut)); listAdapter = new ListAdapter(context); @@ -367,12 +368,13 @@ protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long dr } } }; + fragmentView.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); FrameLayout frameLayout = (FrameLayout) fragmentView; listView = new RecyclerListView(context); listView.setVerticalScrollBarEnabled(false); listView.setLayoutManager(layoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); - listView.setGlowColor(AvatarDrawable.getProfileBackColorForId(5)); + listView.setGlowColor(Theme.getColor(Theme.key_avatar_backgroundActionBarBlue)); frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); listView.setAdapter(listAdapter); listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @@ -421,14 +423,27 @@ public void onClick(DialogInterface dialog, int which) { return; } final TextView message = new TextView(getParentActivity()); - message.setText(Html.fromHtml(LocaleController.getString("AskAQuestionInfo", R.string.AskAQuestionInfo))); - message.setTextSize(18); - message.setLinkTextColor(Theme.MSG_LINK_TEXT_COLOR); - message.setPadding(AndroidUtilities.dp(8), AndroidUtilities.dp(5), AndroidUtilities.dp(8), AndroidUtilities.dp(6)); + Spannable spanned = new SpannableString(Html.fromHtml(LocaleController.getString("AskAQuestionInfo", R.string.AskAQuestionInfo))); + URLSpan[] spans = spanned.getSpans(0, spanned.length(), URLSpan.class); + for (int a = 0; a < spans.length; a++) { + URLSpan span = spans[a]; + int start = spanned.getSpanStart(span); + int end = spanned.getSpanEnd(span); + spanned.removeSpan(span); + span = new URLSpanNoUnderline(span.getURL()); + spanned.setSpan(span, start, end, 0); + } + message.setText(spanned); + message.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + message.setLinkTextColor(Theme.getColor(Theme.key_dialogTextLink)); + message.setHighlightColor(Theme.getColor(Theme.key_dialogLinkSelection)); + message.setPadding(AndroidUtilities.dp(23), 0, AndroidUtilities.dp(23), 0); message.setMovementMethod(new LinkMovementMethodMy()); + message.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setView(message); + builder.setTitle(LocaleController.getString("AskAQuestion", R.string.AskAQuestion)); builder.setPositiveButton(LocaleController.getString("AskButton", R.string.AskButton), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -477,8 +492,12 @@ public void onClick(DialogInterface dialogInterface, int i) { } } else if (position == privacyRow) { presentFragment(new PrivacySettingsActivity()); + } else if (position == dataRow) { + presentFragment(new DataSettingsActivity()); } else if (position == languageRow) { presentFragment(new LanguageSelectActivity()); + } else if (position == themeRow) { + presentFragment(new ThemeActivity()); } else if (position == switchBackendButtonRow) { if (getParentActivity() == null) { return; @@ -524,122 +543,12 @@ public void onClick(DialogInterface dialog, int which) { }); builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); showDialog(builder.create()); - } else if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow) { - if (getParentActivity() == null) { - return; - } - final boolean maskValues[] = new boolean[6]; - BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); - - int mask = 0; - if (position == mobileDownloadRow) { - mask = MediaController.getInstance().mobileDataDownloadMask; - } else if (position == wifiDownloadRow) { - mask = MediaController.getInstance().wifiDownloadMask; - } else if (position == roamingDownloadRow) { - mask = MediaController.getInstance().roamingDownloadMask; - } - - builder.setApplyTopPadding(false); - builder.setApplyBottomPadding(false); - LinearLayout linearLayout = new LinearLayout(getParentActivity()); - linearLayout.setOrientation(LinearLayout.VERTICAL); - for (int a = 0; a < 6; a++) { - String name = null; - if (a == 0) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0; - name = LocaleController.getString("AttachPhoto", R.string.AttachPhoto); - } else if (a == 1) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0; - name = LocaleController.getString("AttachAudio", R.string.AttachAudio); - } else if (a == 2) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0; - name = LocaleController.getString("AttachVideo", R.string.AttachVideo); - } else if (a == 3) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0; - name = LocaleController.getString("AttachDocument", R.string.AttachDocument); - } else if (a == 4) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0; - name = LocaleController.getString("AttachMusic", R.string.AttachMusic); - } else if (a == 5) { - maskValues[a] = (mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0; - name = LocaleController.getString("AttachGif", R.string.AttachGif); - } - CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity()); - checkBoxCell.setTag(a); - checkBoxCell.setBackgroundResource(R.drawable.list_selector); - linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - checkBoxCell.setText(name, "", maskValues[a], true); - checkBoxCell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - CheckBoxCell cell = (CheckBoxCell) v; - int num = (Integer) cell.getTag(); - maskValues[num] = !maskValues[num]; - cell.setChecked(maskValues[num], true); - } - }); - } - BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); - cell.setBackgroundResource(R.drawable.list_selector); - cell.setTextAndIcon(LocaleController.getString("Save", R.string.Save).toUpperCase(), 0); - cell.setTextColor(Theme.AUTODOWNLOAD_SHEET_SAVE_TEXT_COLOR); - cell.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - try { - if (visibleDialog != null) { - visibleDialog.dismiss(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - int newMask = 0; - for (int a = 0; a < 6; a++) { - if (maskValues[a]) { - if (a == 0) { - newMask |= MediaController.AUTODOWNLOAD_MASK_PHOTO; - } else if (a == 1) { - newMask |= MediaController.AUTODOWNLOAD_MASK_AUDIO; - } else if (a == 2) { - newMask |= MediaController.AUTODOWNLOAD_MASK_VIDEO; - } else if (a == 3) { - newMask |= MediaController.AUTODOWNLOAD_MASK_DOCUMENT; - } else if (a == 4) { - newMask |= MediaController.AUTODOWNLOAD_MASK_MUSIC; - } else if (a == 5) { - newMask |= MediaController.AUTODOWNLOAD_MASK_GIF; - } - } - } - SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); - if (position == mobileDownloadRow) { - editor.putInt("mobileDataDownloadMask", newMask); - MediaController.getInstance().mobileDataDownloadMask = newMask; - } else if (position == wifiDownloadRow) { - editor.putInt("wifiDownloadMask", newMask); - MediaController.getInstance().wifiDownloadMask = newMask; - } else if (position == roamingDownloadRow) { - editor.putInt("roamingDownloadMask", newMask); - MediaController.getInstance().roamingDownloadMask = newMask; - } - editor.commit(); - if (listAdapter != null) { - listAdapter.notifyItemChanged(position); - } - } - }); - linearLayout.addView(cell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); - builder.setCustomView(linearLayout); - showDialog(builder.create()); } else if (position == usernameRow) { presentFragment(new ChangeUsernameActivity()); } else if (position == numberRow) { presentFragment(new ChangePhoneHelpActivity()); } else if (position == stickersRow) { presentFragment(new StickersActivity(StickersQuery.TYPE_IMAGE)); - } else if (position == cacheRow) { - presentFragment(new CacheControlActivity()); } else if (position == emojiRow) { if (getParentActivity() == null) { return; @@ -660,11 +569,12 @@ public void onClick(View v) { maskValues[a] = MessagesController.getInstance().useSystemEmoji; name = LocaleController.getString("EmojiUseDefault", R.string.EmojiUseDefault); } - CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity()); + CheckBoxCell checkBoxCell = new CheckBoxCell(getParentActivity(), true); checkBoxCell.setTag(a); - checkBoxCell.setBackgroundResource(R.drawable.list_selector); + checkBoxCell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); linearLayout.addView(checkBoxCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); checkBoxCell.setText(name, "", maskValues[a], true); + checkBoxCell.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); checkBoxCell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -676,9 +586,9 @@ public void onClick(View v) { }); } BottomSheet.BottomSheetCell cell = new BottomSheet.BottomSheetCell(getParentActivity(), 1); - cell.setBackgroundResource(R.drawable.list_selector); + cell.setBackgroundDrawable(Theme.getSelectorDrawable(false)); cell.setTextAndIcon(LocaleController.getString("Save", R.string.Save).toUpperCase(), 0); - cell.setTextColor(Theme.AUTODOWNLOAD_SHEET_SAVE_TEXT_COLOR); + cell.setTextColor(Theme.getColor(Theme.key_dialogTextBlue2)); cell.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -687,7 +597,7 @@ public void onClick(View v) { visibleDialog.dismiss(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } SharedPreferences.Editor editor = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE).edit(); editor.putBoolean("allowBigEmoji", MessagesController.getInstance().allowBigEmoji = maskValues[0]); @@ -715,10 +625,10 @@ public boolean onItemClick(View view, int position) { pressCount++; if (pressCount >= 2) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - builder.setTitle("Debug Menu"); + builder.setTitle(LocaleController.getString("DebugMenu", R.string.DebugMenu)); builder.setItems(new CharSequence[]{ - "Import Contacts", - "Reload Contacts" + LocaleController.getString("DebugMenuImportContacts", R.string.DebugMenuImportContacts), + LocaleController.getString("DebugMenuReloadContacts", R.string.DebugMenuReloadContacts) }, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -735,7 +645,7 @@ public void onClick(DialogInterface dialog, int which) { try { Toast.makeText(getParentActivity(), "¯\\_(ツ)_/¯", Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } return true; @@ -748,7 +658,7 @@ public void onClick(DialogInterface dialog, int which) { extraHeightView = new View(context); extraHeightView.setPivotY(0); - extraHeightView.setBackgroundColor(AvatarDrawable.getProfileBackColorForId(5)); + extraHeightView.setBackgroundColor(Theme.getColor(Theme.key_avatar_backgroundActionBarBlue)); frameLayout.addView(extraHeightView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 88)); shadowView = new View(context); @@ -772,7 +682,7 @@ public void onClick(View v) { }); nameTextView = new TextView(context); - nameTextView.setTextColor(0xffffffff); + nameTextView.setTextColor(Theme.getColor(Theme.key_profile_title)); nameTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); nameTextView.setLines(1); nameTextView.setMaxLines(1); @@ -785,7 +695,7 @@ public void onClick(View v) { frameLayout.addView(nameTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, 48, 0)); onlineTextView = new TextView(context); - onlineTextView.setTextColor(AvatarDrawable.getProfileTextColorForId(5)); + onlineTextView.setTextColor(Theme.getColor(Theme.key_avatar_subtitleInProfileBlue)); onlineTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); onlineTextView.setLines(1); onlineTextView.setMaxLines(1); @@ -795,8 +705,17 @@ public void onClick(View v) { frameLayout.addView(onlineTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 118, 0, 48, 0)); writeButton = new ImageView(context); - writeButton.setBackgroundResource(R.drawable.floating_user_states); + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_profile_actionBackground), Theme.getColor(Theme.key_profile_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow_profile).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + writeButton.setBackgroundDrawable(drawable); writeButton.setImageResource(R.drawable.floating_camera); + writeButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_profile_actionIcon), PorterDuff.Mode.MULTIPLY)); writeButton.setScaleType(ImageView.ScaleType.CENTER); if (Build.VERSION.SDK_INT >= 21) { StateListAnimator animator = new StateListAnimator(); @@ -811,7 +730,7 @@ public void getOutline(View view, Outline outline) { } }); } - frameLayout.addView(writeButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.RIGHT | Gravity.TOP, 0, 0, 16, 0)); + frameLayout.addView(writeButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, Gravity.RIGHT | Gravity.TOP, 0, 0, 16, 0)); writeButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -879,11 +798,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { return fragmentView; } - @Override - protected void onDialogDismiss(Dialog dialog) { - MediaController.getInstance().checkAutodownloadSettings(); - } - @Override public void updatePhotoAtIndex(int index) { @@ -955,7 +869,7 @@ public boolean cancelButtonPressed() { } @Override - public void sendButtonPressed(int index) { + public void sendButtonPressed(int index, VideoEditedInfo videoEditedInfo) { } @Override @@ -983,14 +897,14 @@ private void performAskAQuestion() { data.cleanup(); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); supportUser = null; } } } } if (supportUser == null) { - final ProgressDialog progressDialog = new ProgressDialog(getParentActivity()); + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -1015,7 +929,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } ArrayList users = new ArrayList<>(); users.add(res.user); @@ -1033,7 +947,7 @@ public void run() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } }); @@ -1148,7 +1062,7 @@ private void needLayout() { ); } writeButtonAnimation.setDuration(150); - writeButtonAnimation.addListener(new AnimatorListenerAdapterProxy() { + writeButtonAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (writeButtonAnimation != null && writeButtonAnimation.equals(animation)) { @@ -1198,9 +1112,9 @@ private void updateUserData() { photo = user.photo.photo_small; photoBig = user.photo.photo_big; } - AvatarDrawable avatarDrawable = new AvatarDrawable(user, true); + avatarDrawable = new AvatarDrawable(user, true); - avatarDrawable.setColor(Theme.ACTION_BAR_MAIN_AVATAR_COLOR); + avatarDrawable.setColor(Theme.getColor(Theme.key_avatar_backgroundInProfileBlue)); if (avatarImage != null) { avatarImage.setImage(photo, "50_50", avatarDrawable); avatarImage.getImageReceiver().setVisible(!PhotoViewer.getInstance().isShowingImage(photoBig), false); @@ -1227,7 +1141,7 @@ private void sendLogs() { } Intent i = new Intent(Intent.ACTION_SEND_MULTIPLE); i.setType("message/rfc822"); - i.putExtra(Intent.EXTRA_EMAIL, new String[]{BuildVars.SEND_LOGS_EMAIL}); + i.putExtra(Intent.EXTRA_EMAIL, ""); i.putExtra(Intent.EXTRA_SUBJECT, "last logs"); i.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); getParentActivity().startActivityForResult(Intent.createChooser(i, "Select email application."), 500); @@ -1236,17 +1150,10 @@ private void sendLogs() { } } - private class ListAdapter extends RecyclerView.Adapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } - public ListAdapter(Context context) { mContext = context; } @@ -1258,7 +1165,6 @@ public int getItemCount() { @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - boolean checkBackground = true; switch (holder.getItemViewType()) { case 0: { if (position == overscrollRow) { @@ -1266,7 +1172,6 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else { ((EmptyCell) holder.itemView).setHeight(AndroidUtilities.dp(16)); } - checkBackground = false; break; } case 2: { @@ -1277,6 +1182,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { textCell.setTextAndValue(LocaleController.getString("TextSize", R.string.TextSize), String.format("%d", size), true); } else if (position == languageRow) { textCell.setTextAndValue(LocaleController.getString("Language", R.string.Language), LocaleController.getCurrentLanguageName(), true); + } else if (position == themeRow) { + textCell.setTextAndValue(LocaleController.getString("Theme", R.string.Theme), Theme.getCurrentThemeName(), true); } else if (position == contactsSortRow) { String value; SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); @@ -1301,6 +1208,8 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { textCell.setText(LocaleController.getString("AskAQuestion", R.string.AskAQuestion), true); } else if (position == privacyRow) { textCell.setText(LocaleController.getString("PrivacySettings", R.string.PrivacySettings), true); + } else if (position == dataRow) { + textCell.setText(LocaleController.getString("DataSettings", R.string.DataSettings), true); } else if (position == switchBackendButtonRow) { textCell.setText("Switch Backend", true); } else if (position == telegramFaqRow) { @@ -1310,8 +1219,6 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } else if (position == stickersRow) { int count = StickersQuery.getUnreadStickerSets().size(); textCell.setTextAndValue(LocaleController.getString("Stickers", R.string.Stickers), count != 0 ? String.format("%d", count) : "", true); - } else if (position == cacheRow) { - textCell.setText(LocaleController.getString("CacheSettings", R.string.CacheSettings), true); } else if (position == privacyPolicyRow) { textCell.setText(LocaleController.getString("PrivacyPolicy", R.string.PrivacyPolicy), true); } else if (position == emojiRow) { @@ -1325,7 +1232,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position == enableAnimationsRow) { textCell.setTextAndCheck(LocaleController.getString("EnableAnimations", R.string.EnableAnimations), preferences.getBoolean("view_animations", true), false); } else if (position == sendByEnterRow) { - textCell.setTextAndCheck(LocaleController.getString("SendByEnter", R.string.SendByEnter), preferences.getBoolean("send_by_enter", false), false); + textCell.setTextAndCheck(LocaleController.getString("SendByEnter", R.string.SendByEnter), preferences.getBoolean("send_by_enter", false), true); } else if (position == saveToGalleryRow) { textCell.setTextAndCheck(LocaleController.getString("SaveToGallerySettings", R.string.SaveToGallerySettings), MediaController.getInstance().canSaveToGallery(), false); } else if (position == autoplayGifsRow) { @@ -1346,8 +1253,6 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { ((HeaderCell) holder.itemView).setText(LocaleController.getString("Support", R.string.Support)); } else if (position == messagesSectionRow2) { ((HeaderCell) holder.itemView).setText(LocaleController.getString("MessagesSettings", R.string.MessagesSettings)); - } else if (position == mediaDownloadSection2) { - ((HeaderCell) holder.itemView).setText(LocaleController.getString("AutomaticMediaDownload", R.string.AutomaticMediaDownload)); } else if (position == numberSectionRow) { ((HeaderCell) holder.itemView).setText(LocaleController.getString("Info", R.string.Info)); } @@ -1356,59 +1261,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { case 6: { TextDetailSettingsCell textCell = (TextDetailSettingsCell) holder.itemView; - if (position == mobileDownloadRow || position == wifiDownloadRow || position == roamingDownloadRow) { - int mask; - String value; - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - if (position == mobileDownloadRow) { - value = LocaleController.getString("WhenUsingMobileData", R.string.WhenUsingMobileData); - mask = MediaController.getInstance().mobileDataDownloadMask; - } else if (position == wifiDownloadRow) { - value = LocaleController.getString("WhenConnectedOnWiFi", R.string.WhenConnectedOnWiFi); - mask = MediaController.getInstance().wifiDownloadMask; - } else { - value = LocaleController.getString("WhenRoaming", R.string.WhenRoaming); - mask = MediaController.getInstance().roamingDownloadMask; - } - String text = ""; - if ((mask & MediaController.AUTODOWNLOAD_MASK_PHOTO) != 0) { - text += LocaleController.getString("AttachPhoto", R.string.AttachPhoto); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_AUDIO) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachAudio", R.string.AttachAudio); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_VIDEO) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachVideo", R.string.AttachVideo); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_DOCUMENT) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachDocument", R.string.AttachDocument); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_MUSIC) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachMusic", R.string.AttachMusic); - } - if ((mask & MediaController.AUTODOWNLOAD_MASK_GIF) != 0) { - if (text.length() != 0) { - text += ", "; - } - text += LocaleController.getString("AttachGif", R.string.AttachGif); - } - if (text.length() == 0) { - text = LocaleController.getString("NoMediaAutoDownload", R.string.NoMediaAutoDownload); - } - textCell.setTextAndValue(value, text, true); - } else if (position == numberRow) { + if (position == numberRow) { TLRPC.User user = UserConfig.getCurrentUser(); String value; if (user != null && user.phone != null && user.phone.length() != 0) { @@ -1429,79 +1282,46 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { } break; } - default: - checkBackground = false; - break; - } - if (checkBackground) { - if (position == textSizeRow || position == enableAnimationsRow || position == notificationRow || position == backgroundRow || position == numberRow || - position == askQuestionRow || position == sendLogsRow || position == sendByEnterRow || position == autoplayGifsRow || position == privacyRow || position == wifiDownloadRow || - position == mobileDownloadRow || position == clearLogsRow || position == roamingDownloadRow || position == languageRow || position == usernameRow || - position == switchBackendButtonRow || position == telegramFaqRow || position == contactsSortRow || position == contactsReimportRow || position == saveToGalleryRow || - position == stickersRow || position == cacheRow || position == raiseToSpeakRow || position == privacyPolicyRow || position == customTabsRow || position == directShareRow || position == versionRow || - position == emojiRow) { - if (holder.itemView.getBackground() == null) { - holder.itemView.setBackgroundResource(R.drawable.list_selector); - } - } else { - if (holder.itemView.getBackground() != null) { - holder.itemView.setBackgroundDrawable(null); - } - } } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position == textSizeRow || position == enableAnimationsRow || position == notificationRow || position == backgroundRow || position == numberRow || + position == askQuestionRow || position == sendLogsRow || position == sendByEnterRow || position == autoplayGifsRow || position == privacyRow || + position == clearLogsRow || position == languageRow || position == usernameRow || + position == switchBackendButtonRow || position == telegramFaqRow || position == contactsSortRow || position == contactsReimportRow || position == saveToGalleryRow || + position == stickersRow || position == raiseToSpeakRow || position == privacyPolicyRow || position == customTabsRow || position == directShareRow || position == versionRow || + position == emojiRow || position == dataRow || position == themeRow; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case 0: view = new EmptyCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 1: view = new ShadowSectionCell(mContext); break; case 2: - view = new TextSettingsCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 3: - view = new TextCheckCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextCheckCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 4: view = new HeaderCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 5: - view = new TextInfoCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextInfoCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); try { PackageInfo pInfo = ApplicationLoader.applicationContext.getPackageManager().getPackageInfo(ApplicationLoader.applicationContext.getPackageName(), 0); int code = pInfo.versionCode / 10; @@ -1511,36 +1331,29 @@ public boolean onTouchEvent(MotionEvent event) { abi = "arm"; break; case 1: + case 3: abi = "arm-v7a"; break; case 2: + case 4: abi = "x86"; break; - case 3: + case 5: abi = "universal"; break; } - ((TextInfoCell) view).setText(String.format(Locale.US, "Telegram for Android v%s (%d) %s", pInfo.versionName, code, abi)); + ((TextInfoCell) view).setText(LocaleController.formatString("TelegramVersion", R.string.TelegramVersion, String.format(Locale.US, "v%s (%d) %s", pInfo.versionName, code, abi))); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } break; case 6: - view = new TextDetailSettingsCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; + view = new TextDetailSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -1548,21 +1361,70 @@ public int getItemViewType(int position) { if (position == emptyRow || position == overscrollRow) { return 0; } - if (position == settingsSectionRow || position == supportSectionRow || position == messagesSectionRow || position == mediaDownloadSection || position == contactsSectionRow) { + if (position == settingsSectionRow || position == supportSectionRow || position == messagesSectionRow || position == contactsSectionRow) { return 1; } else if (position == enableAnimationsRow || position == sendByEnterRow || position == saveToGalleryRow || position == autoplayGifsRow || position == raiseToSpeakRow || position == customTabsRow || position == directShareRow) { return 3; - } else if (position == notificationRow || position == backgroundRow || position == askQuestionRow || position == sendLogsRow || position == privacyRow || position == clearLogsRow || position == switchBackendButtonRow || position == telegramFaqRow || position == contactsReimportRow || position == textSizeRow || position == languageRow || position == contactsSortRow || position == stickersRow || position == cacheRow || position == privacyPolicyRow || position == emojiRow) { + } else if (position == notificationRow || position == themeRow || position == backgroundRow || position == askQuestionRow || position == sendLogsRow || position == privacyRow || position == clearLogsRow || position == switchBackendButtonRow || position == telegramFaqRow || position == contactsReimportRow || position == textSizeRow || position == languageRow || position == contactsSortRow || position == stickersRow || position == privacyPolicyRow || position == emojiRow || position == dataRow) { return 2; } else if (position == versionRow) { return 5; - } else if (position == wifiDownloadRow || position == mobileDownloadRow || position == roamingDownloadRow || position == numberRow || position == usernameRow) { + } else if (position == numberRow || position == usernameRow) { return 6; - } else if (position == settingsSectionRow2 || position == messagesSectionRow2 || position == supportSectionRow2 || position == numberSectionRow || position == mediaDownloadSection2) { + } else if (position == settingsSectionRow2 || position == messagesSectionRow2 || position == supportSectionRow2 || position == numberSectionRow) { return 4; } else { return 2; } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{EmptyCell.class, TextSettingsCell.class, TextCheckCell.class, HeaderCell.class, TextInfoCell.class, TextDetailSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(extraHeightView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_avatar_backgroundActionBarBlue), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_avatar_actionBarIconBlue), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_avatar_actionBarSelectorBlue), + new ThemeDescription(nameTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_profile_title), + new ThemeDescription(onlineTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_avatar_subtitleInProfileBlue), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumb), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrack), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchThumbChecked), + new ThemeDescription(listView, 0, new Class[]{TextCheckCell.class}, new String[]{"checkBox"}, null, null, null, Theme.key_switchTrackChecked), + + new ThemeDescription(listView, 0, new Class[]{HeaderCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlueHeader), + + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextDetailSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + + new ThemeDescription(listView, 0, new Class[]{TextInfoCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText5), + + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{Theme.avatar_photoDrawable, Theme.avatar_broadcastDrawable}, null, Theme.key_avatar_text), + new ThemeDescription(avatarImage, 0, null, null, new Drawable[]{avatarDrawable}, null, Theme.key_avatar_backgroundInProfileBlue), + + new ThemeDescription(writeButton, ThemeDescription.FLAG_IMAGECOLOR, null, null, null, null, Theme.key_profile_actionIcon), + new ThemeDescription(writeButton, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_profile_actionBackground), + new ThemeDescription(writeButton, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_profile_actionPressedBackground), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java index fdc40664e30..946fd982db3 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ShareActivity.java @@ -3,7 +3,7 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; @@ -74,7 +74,7 @@ protected void onCreate(Bundle savedInstanceState) { messageObject.messageOwner.with_my_score = true; try { - visibleDialog = new ShareAlert(this, messageObject, null, false, link); + visibleDialog = new ShareAlert(this, messageObject, null, false, link, false); visibleDialog.setCanceledOnTouchOutside(true); visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override @@ -87,7 +87,7 @@ public void onDismiss(DialogInterface dialog) { }); visibleDialog.show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); finish(); } } @@ -101,7 +101,7 @@ public void onPause() { visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java index 2e898ba8830..fe7bd8a32ad 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickerPreviewViewer.java @@ -3,18 +3,26 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; +import android.annotation.SuppressLint; import android.app.Activity; +import android.app.Dialog; import android.content.Context; +import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.drawable.ColorDrawable; import android.os.Build; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -24,9 +32,13 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Emoji; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.BottomSheet; import org.telegram.ui.Cells.ContextLinkCell; import org.telegram.ui.Cells.StickerCell; import org.telegram.ui.Cells.StickerEmojiCell; @@ -47,10 +59,19 @@ protected void onDraw(Canvas canvas) { } } + public interface StickerPreviewViewerDelegate { + void sentSticker(TLRPC.Document sticker); + void openSet(TLRPC.InputStickerSet set); + } + + private static TextPaint textPaint; + private int startX; private int startY; private View currentStickerPreviewCell; private Runnable openStickerPreviewRunnable; + private Dialog visibleDialog; + private StickerPreviewViewerDelegate delegate; private ColorDrawable backgroundDrawable = new ColorDrawable(0x71000000); private Activity parentActivity; @@ -60,11 +81,49 @@ protected void onDraw(Canvas canvas) { private ImageReceiver centerImage = new ImageReceiver(); private boolean isVisible = false; private float showProgress; + private StaticLayout stickerEmojiLayout; private long lastUpdateTime; private int keyboardHeight = AndroidUtilities.dp(200); + private Runnable showSheetRunnable = new Runnable() { + @Override + public void run() { + if (parentActivity == null || currentSet == null) { + return; + } + BottomSheet.Builder builder = new BottomSheet.Builder(parentActivity); + builder.setItems(new CharSequence[]{ + LocaleController.getString("SendStickerPreview", R.string.SendStickerPreview), + LocaleController.formatString("ViewPackPreview", R.string.ViewPackPreview)}, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, final int which) { + if (parentActivity == null) { + return; + } + if (delegate != null) { + if (which == 0) { + delegate.sentSticker(currentSticker); + } else if (which == 1) { + delegate.openSet(currentSet); + } + } + } + }); + visibleDialog = builder.create(); + visibleDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + visibleDialog = null; + close(); + } + }); + visibleDialog.show(); + } + }; - private TLRPC.Document currentSticker = null; + private TLRPC.Document currentSticker; + private TLRPC.InputStickerSet currentSet; + @SuppressLint("StaticFieldLeak") private static volatile StickerPreviewViewer Instance = null; public static StickerPreviewViewer getInstance() { StickerPreviewViewer localInstance = Instance; @@ -96,7 +155,8 @@ public void reset() { } } - public boolean onTouch(MotionEvent event, final View listView, final int height, final Object listener) { + public boolean onTouch(MotionEvent event, final View listView, final int height, final Object listener, StickerPreviewViewerDelegate stickerPreviewViewerDelegate) { + delegate = stickerPreviewViewerDelegate; if (openStickerPreviewRunnable != null || isVisible()) { if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_POINTER_UP) { AndroidUtilities.runOnUIThread(new Runnable() { @@ -174,13 +234,13 @@ public void run() { currentStickerPreviewCell = view; setKeyboardHeight(height); if (currentStickerPreviewCell instanceof StickerEmojiCell) { - open(((StickerEmojiCell) currentStickerPreviewCell).getSticker()); + open(((StickerEmojiCell) currentStickerPreviewCell).getSticker(), ((StickerEmojiCell) currentStickerPreviewCell).isRecent()); ((StickerEmojiCell) currentStickerPreviewCell).setScaled(true); } else if (currentStickerPreviewCell instanceof StickerCell) { - open(((StickerCell) currentStickerPreviewCell).getSticker()); + open(((StickerCell) currentStickerPreviewCell).getSticker(), false); ((StickerCell) currentStickerPreviewCell).setScaled(true); } else if (currentStickerPreviewCell instanceof ContextLinkCell) { - open(((ContextLinkCell) currentStickerPreviewCell).getDocument()); + open(((ContextLinkCell) currentStickerPreviewCell).getDocument(), false); ((ContextLinkCell) currentStickerPreviewCell).setScaled(true); } return true; @@ -203,7 +263,8 @@ public void run() { return false; } - public boolean onInterceptTouchEvent(MotionEvent event, final View listView, final int height) { + public boolean onInterceptTouchEvent(MotionEvent event, final View listView, final int height, StickerPreviewViewerDelegate stickerPreviewViewerDelegate) { + delegate = stickerPreviewViewerDelegate; if (event.getAction() == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); @@ -262,13 +323,13 @@ public void run() { setParentActivity((Activity) listView.getContext()); setKeyboardHeight(height); if (currentStickerPreviewCell instanceof StickerEmojiCell) { - open(((StickerEmojiCell) currentStickerPreviewCell).getSticker()); + open(((StickerEmojiCell) currentStickerPreviewCell).getSticker(), ((StickerEmojiCell) currentStickerPreviewCell).isRecent()); ((StickerEmojiCell) currentStickerPreviewCell).setScaled(true); } else if (currentStickerPreviewCell instanceof StickerCell) { - open(((StickerCell) currentStickerPreviewCell).getSticker()); + open(((StickerCell) currentStickerPreviewCell).getSticker(), false); ((StickerCell) currentStickerPreviewCell).setScaled(true); } else if (currentStickerPreviewCell instanceof ContextLinkCell) { - open(((ContextLinkCell) currentStickerPreviewCell).getDocument()); + open(((ContextLinkCell) currentStickerPreviewCell).getDocument(), false); ((ContextLinkCell) currentStickerPreviewCell).setScaled(true); } } @@ -280,6 +341,10 @@ public void run() { return false; } + public void setDelegate(StickerPreviewViewerDelegate stickerPreviewViewerDelegate) { + delegate = stickerPreviewViewerDelegate; + } + public void setParentActivity(Activity activity) { if (parentActivity == activity) { return; @@ -326,12 +391,51 @@ public void setKeyboardHeight(int height) { keyboardHeight = height; } - public void open(TLRPC.Document sticker) { + public void open(TLRPC.Document sticker, boolean isRecent) { if (parentActivity == null || sticker == null) { return; } + if (textPaint == null) { + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setTextSize(AndroidUtilities.dp(24)); + } + TLRPC.InputStickerSet newSet = null; + if (isRecent) { + for (int a = 0; a < sticker.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = sticker.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeSticker && attribute.stickerset != null) { + newSet = attribute.stickerset; + break; + } + } + if (newSet != null && currentSet != newSet) { + try { + if (visibleDialog != null) { + visibleDialog.setOnDismissListener(null); + visibleDialog.dismiss(); + } + } catch (Exception e) { + FileLog.e(e); + } + AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); + AndroidUtilities.runOnUIThread(showSheetRunnable, 2000); + } + } + currentSet = newSet; centerImage.setImage(sticker, null, sticker.thumb.location, null, "webp", true); + stickerEmojiLayout = null; + for (int a = 0; a < sticker.attributes.size(); a++) { + TLRPC.DocumentAttribute attribute = sticker.attributes.get(a); + if (attribute instanceof TLRPC.TL_documentAttributeSticker) { + if (!TextUtils.isEmpty(attribute.alt)) { + CharSequence emoji = Emoji.replaceEmoji(attribute.alt, textPaint.getFontMetricsInt(), AndroidUtilities.dp(24), false); + stickerEmojiLayout = new StaticLayout(emoji, textPaint, AndroidUtilities.dp(100), Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + break; + } + } + } + currentSticker = sticker; containerView.invalidate(); @@ -343,7 +447,7 @@ public void open(TLRPC.Document sticker) { wm.removeView(windowView); } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); wm.addView(windowView, windowLayoutParams); @@ -358,32 +462,40 @@ public boolean isVisible() { } public void close() { - if (parentActivity == null) { + if (parentActivity == null || visibleDialog != null) { return; } + AndroidUtilities.cancelRunOnUIThread(showSheetRunnable); showProgress = 1.0f; - currentSticker = null; - isVisible = false; - AndroidUtilities.unlockOrientation(parentActivity); - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - centerImage.setImageBitmap((Bitmap)null); - } - }); + lastUpdateTime = System.currentTimeMillis(); + containerView.invalidate(); try { - if (windowView.getParent() != null) { - WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); - wm.removeView(windowView); + if (visibleDialog != null) { + visibleDialog.dismiss(); + visibleDialog = null; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } + currentSticker = null; + currentSet = null; + delegate = null; + isVisible = false; } public void destroy() { isVisible = false; + delegate = null; currentSticker = null; + currentSet = null; + try { + if (visibleDialog != null) { + visibleDialog.dismiss(); + visibleDialog = null; + } + } catch (Exception e) { + FileLog.e(e); + } if (parentActivity == null || windowView == null) { return; } @@ -394,11 +506,12 @@ public void destroy() { } windowView = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } Instance = null; } + @SuppressLint("DrawAllocation") private void onDraw(Canvas canvas) { if (containerView == null || backgroundDrawable == null) { return; @@ -418,15 +531,47 @@ private void onDraw(Canvas canvas) { centerImage.setImageCoords(-size / 2, -size / 2, size, size); centerImage.draw(canvas); } + if (stickerEmojiLayout != null) { + canvas.translate(-AndroidUtilities.dp(50), -centerImage.getImageHeight() / 2 - AndroidUtilities.dp(30)); + stickerEmojiLayout.draw(canvas); + } canvas.restore(); - if (showProgress != 1) { + if (isVisible) { + if (showProgress != 1) { + long newTime = System.currentTimeMillis(); + long dt = newTime - lastUpdateTime; + lastUpdateTime = newTime; + showProgress += dt / 120.0f; + containerView.invalidate(); + if (showProgress > 1.0f) { + showProgress = 1.0f; + } + } + } else if (showProgress != 0) { long newTime = System.currentTimeMillis(); long dt = newTime - lastUpdateTime; lastUpdateTime = newTime; - showProgress += dt / 150.0f; + showProgress -= dt / 120.0f; containerView.invalidate(); - if (showProgress > 1.0f) { - showProgress = 1.0f; + if (showProgress < 0.0f) { + showProgress = 0.0f; + } + if (showProgress == 0) { + AndroidUtilities.unlockOrientation(parentActivity); + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + centerImage.setImageBitmap((Bitmap) null); + } + }); + try { + if (windowView.getParent() != null) { + WindowManager wm = (WindowManager) parentActivity.getSystemService(Context.WINDOW_SERVICE); + wm.removeView(windowView); + } + } catch (Exception e) { + FileLog.e(e); + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java index b6682199069..6e2a76183fe 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StickersActivity.java @@ -3,20 +3,17 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Canvas; -import android.os.Build; import android.text.SpannableStringBuilder; import android.text.Spanned; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -37,7 +34,10 @@ import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.ShadowSectionCell; import org.telegram.ui.Cells.StickerSetCell; import org.telegram.ui.Cells.TextInfoPrivacyCell; @@ -132,6 +132,7 @@ public boolean onFragmentCreate() { StickersQuery.checkFeaturedStickers(); } NotificationCenter.getInstance().addObserver(this, NotificationCenter.stickersDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.archivedStickersCountDidLoaded); NotificationCenter.getInstance().addObserver(this, NotificationCenter.featuredStickersDidLoaded); updateRows(); return true; @@ -141,6 +142,7 @@ public boolean onFragmentCreate() { public void onFragmentDestroy() { super.onFragmentDestroy(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.stickersDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.archivedStickersCountDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.featuredStickersDidLoaded); sendReorder(); } @@ -167,7 +169,7 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); listView = new RecyclerListView(context); listView.setFocusable(true); @@ -192,8 +194,10 @@ public void onItemClick(View view, int position) { } showDialog(new StickersAlert(getParentActivity(), StickersActivity.this, null, stickerSet, null)); } else if (position == featuredRow) { + sendReorder(); presentFragment(new FeaturedStickersActivity()); } else if (position == archivedRow) { + sendReorder(); presentFragment(new ArchivedStickersActivity(currentType)); } else if (position == masksRow) { presentFragment(new StickersActivity(StickersQuery.TYPE_MASK)); @@ -214,6 +218,10 @@ public void didReceivedNotification(int id, Object... args) { if (listAdapter != null) { listAdapter.notifyItemChanged(0); } + } else if (id == NotificationCenter.archivedStickersCountDidLoaded) { + if ((Integer) args[0] == currentType) { + updateRows(); + } } } @@ -224,6 +232,7 @@ private void sendReorder() { StickersQuery.calcNewHash(currentType); needReorder = false; TLRPC.TL_messages_reorderStickerSets req = new TLRPC.TL_messages_reorderStickerSets(); + req.masks = currentType == StickersQuery.TYPE_MASK; ArrayList arrayList = StickersQuery.getStickerSets(currentType); for (int a = 0; a < arrayList.size(); a++) { req.order.add(arrayList.get(a).set.id); @@ -244,15 +253,18 @@ private void updateRows() { featuredInfoRow = rowCount++; masksRow = rowCount++; masksInfoRow = rowCount++; - archivedRow = rowCount++; - archivedInfoRow = rowCount++; } else { featuredRow = -1; featuredInfoRow = -1; masksRow = -1; masksInfoRow = -1; + } + if (StickersQuery.getArchivedStickersCount(currentType) != 0) { archivedRow = rowCount++; archivedInfoRow = rowCount++; + } else { + archivedRow = -1; + archivedInfoRow = -1; } ArrayList stickerSets = StickersQuery.getStickerSets(currentType); if (!stickerSets.isEmpty()) { @@ -278,15 +290,9 @@ public void onResume() { } } - private class ListAdapter extends RecyclerListView.Adapter { - private Context mContext; - - private class Holder extends RecyclerView.ViewHolder { + private class ListAdapter extends RecyclerListView.SelectionAdapter { - public Holder(View itemView) { - super(itemView); - } - } + private Context mContext; public ListAdapter(Context context) { mContext = context; @@ -317,19 +323,19 @@ private void processSelectionOption(int which, TLRPC.TL_messages_stickerSet stic try { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, String.format(Locale.US, "https://telegram.me/addstickers/%s", stickerSet.set.short_name)); + intent.putExtra(Intent.EXTRA_TEXT, String.format(Locale.US, "https://" + MessagesController.getInstance().linkPrefix + "/addstickers/%s", stickerSet.set.short_name)); getParentActivity().startActivityForResult(Intent.createChooser(intent, LocaleController.getString("StickersShare", R.string.StickersShare)), 500); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } else if (which == 3) { try { android.content.ClipboardManager clipboard = (android.content.ClipboardManager) ApplicationLoader.applicationContext.getSystemService(Context.CLIPBOARD_SERVICE); - android.content.ClipData clip = android.content.ClipData.newPlainText("label", String.format(Locale.US, "https://telegram.me/addstickers/%s", stickerSet.set.short_name)); + android.content.ClipData clip = android.content.ClipData.newPlainText("label", String.format(Locale.US, "https://" + MessagesController.getInstance().linkPrefix + "/addstickers/%s", stickerSet.set.short_name)); clipboard.setPrimaryClip(clip); Toast.makeText(getParentActivity(), LocaleController.getString("LinkCopied", R.string.LinkCopied), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -359,7 +365,7 @@ public void onClick(View widget) { stringBuilder.setSpan(spanNoUnderline, index, index + botName.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); ((TextInfoPrivacyCell) holder.itemView).setText(stringBuilder); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); ((TextInfoPrivacyCell) holder.itemView).setText(text); } } else { @@ -392,13 +398,19 @@ public void onClick(View widget) { } } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 0 || type == 2; + } + @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = null; switch (viewType) { case 0: view = new StickerSetCell(mContext); - view.setBackgroundResource(R.drawable.list_selector_white); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); ((StickerSetCell) view).setOnOptionsClick(new View.OnClickListener() { @Override public void onClick(View v) { @@ -453,29 +465,19 @@ public void onClick(DialogInterface dialog, int which) { break; case 1: view = new TextInfoPrivacyCell(mContext); - view.setBackgroundResource(R.drawable.greydivider_bottom); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); break; case 2: - view = new TextSettingsCell(mContext) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (Build.VERSION.SDK_INT >= 21 && getBackground() != null) { - if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { - getBackground().setHotspot(event.getX(), event.getY()); - } - } - return super.onTouchEvent(event); - } - }; - view.setBackgroundResource(R.drawable.list_selector_white); + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); break; case 3: view = new ShadowSectionCell(mContext); - view.setBackgroundResource(R.drawable.greydivider_bottom); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); break; } view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override @@ -503,4 +505,36 @@ public void swapElements(int fromIndex, int toIndex) { notifyItemMoved(fromIndex, toIndex); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{StickerSetCell.class, TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + new ThemeDescription(listView, ThemeDescription.FLAG_LINKCOLOR, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteLinkText), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteValueText), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"valueTextView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText2), + new ThemeDescription(listView, ThemeDescription.FLAG_USEBACKGROUNDDRAWABLE | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, new Class[]{StickerSetCell.class}, new String[]{"optionsButton"}, null, null, null, Theme.key_stickers_menuSelector), + new ThemeDescription(listView, 0, new Class[]{StickerSetCell.class}, new String[]{"optionsButton"}, null, null, null, Theme.key_stickers_menu), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java new file mode 100644 index 00000000000..eee21c07370 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemeActivity.java @@ -0,0 +1,398 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Vibrator; +import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.FileLoader; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; +import org.telegram.ui.Cells.ShadowSectionCell; +import org.telegram.ui.Cells.TextInfoPrivacyCell; +import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Cells.ThemeCell; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.ThemeEditorView; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.HashMap; + +public class ThemeActivity extends BaseFragment { + + private ListAdapter listAdapter; + private RecyclerListView listView; + + @Override + public View createView(Context context) { + actionBar.setBackButtonImage(R.drawable.ic_ab_back); + actionBar.setAllowOverlayTitle(false); + actionBar.setTitle(LocaleController.getString("Theme", R.string.Theme)); + + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + + listAdapter = new ListAdapter(context); + + FrameLayout frameLayout = new FrameLayout(context); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); + fragmentView = frameLayout; + + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setVerticalScrollBarEnabled(false); + listView.setAdapter(listAdapter); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + if (position == 0) { + if (getParentActivity() == null) { + return; + } + final EditText editText = new EditText(getParentActivity()); + editText.setBackgroundDrawable(Theme.createEditTextDrawable(getParentActivity(), true)); + + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setTitle(LocaleController.getString("NewTheme", R.string.NewTheme)); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + builder.setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, int which) { + + } + }); + + LinearLayout linearLayout = new LinearLayout(getParentActivity()); + linearLayout.setOrientation(LinearLayout.VERTICAL); + builder.setView(linearLayout); + + final TextView message = new TextView(getParentActivity()); + message.setText(LocaleController.formatString("EnterThemeName", R.string.EnterThemeName)); + message.setTextSize(16); + message.setPadding(AndroidUtilities.dp(23), AndroidUtilities.dp(12), AndroidUtilities.dp(23), AndroidUtilities.dp(6)); + message.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + linearLayout.addView(message, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + editText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + editText.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + editText.setMaxLines(1); + editText.setLines(1); + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + editText.setGravity(Gravity.LEFT | Gravity.TOP); + editText.setSingleLine(true); + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + AndroidUtilities.clearCursorDrawable(editText); + editText.setPadding(0, AndroidUtilities.dp(4), 0, 0); + linearLayout.addView(editText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 24, 6, 24, 0)); + editText.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { + AndroidUtilities.hideKeyboard(textView); + return false; + } + }); + final AlertDialog alertDialog = builder.create(); + alertDialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialog) { + AndroidUtilities.runOnUIThread(new Runnable() { + @Override + public void run() { + editText.requestFocus(); + AndroidUtilities.showKeyboard(editText); + } + }); + } + }); + showDialog(alertDialog); + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (editText.length() == 0) { + Vibrator vibrator = (Vibrator) ApplicationLoader.applicationContext.getSystemService(Context.VIBRATOR_SERVICE); + if (vibrator != null) { + vibrator.vibrate(200); + } + AndroidUtilities.shakeView(editText, 2, 0); + return; + } + ThemeEditorView themeEditorView = new ThemeEditorView(); + String name = editText.getText().toString() + ".attheme"; + themeEditorView.show(getParentActivity(), name); + Theme.saveCurrentTheme(name, true); + listAdapter.notifyDataSetChanged(); + alertDialog.dismiss(); + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + if (preferences.getBoolean("themehint", false)) { + return; + } + preferences.edit().putBoolean("themehint", true).commit(); + try { + Toast.makeText(getParentActivity(), LocaleController.getString("CreateNewThemeHelp", R.string.CreateNewThemeHelp), Toast.LENGTH_LONG).show(); + } catch (Exception e) { + FileLog.e(e); + } + } + }); + } else { + position -= 2; + if (position >= 0 && position < Theme.themes.size()) { + Theme.ThemeInfo themeInfo = Theme.themes.get(position); + Theme.applyTheme(themeInfo); + if (parentLayout != null) { + parentLayout.rebuildAllFragmentViews(false); + } + finishFragment(); + } + } + } + }); + + return fragmentView; + } + + @Override + public void onResume() { + super.onResume(); + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + + private class ListAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + public ListAdapter(Context context) { + mContext = context; + } + + @Override + public int getItemCount() { + return Theme.themes.size() + 3; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int type = holder.getItemViewType(); + return type == 0 || type == 1; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new ThemeCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + ((ThemeCell) view).setOnOptionsClick(new View.OnClickListener() { + @Override + public void onClick(View v) { + final Theme.ThemeInfo themeInfo = ((ThemeCell) v.getParent()).getCurrentThemeInfo(); + if (getParentActivity() == null) { + return; + } + + BottomSheet.Builder builder = new BottomSheet.Builder(getParentActivity()); + CharSequence[] items; + if (themeInfo.pathToFile == null) { + items = new CharSequence[]{ + LocaleController.getString("ShareFile", R.string.ShareFile) + }; + } else { + items = new CharSequence[]{ + LocaleController.getString("ShareFile", R.string.ShareFile), + LocaleController.getString("Edit", R.string.Edit), + LocaleController.getString("Delete", R.string.Delete)}; + } + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, final int which) { + if (which == 0) { + File currentFile; + if (themeInfo.pathToFile == null && themeInfo.assetName == null) { + StringBuilder result = new StringBuilder(); + for (HashMap.Entry entry : Theme.getDefaultColors().entrySet()) { + result.append(entry.getKey()).append("=").append(entry.getValue()).append("\n"); + } + currentFile = new File(ApplicationLoader.getFilesDirFixed(), "default_theme.attheme"); + FileOutputStream stream = null; + try { + stream = new FileOutputStream(currentFile); + stream.write(result.toString().getBytes()); + } catch (Exception e) { + FileLog.e(e); + } finally { + try { + if (stream != null) { + stream.close(); + } + } catch (Exception e) { + FileLog.e("tmessage", e); + } + } + } else if (themeInfo.assetName != null) { + currentFile = Theme.getAssetFile(themeInfo.assetName); + } else { + currentFile = new File(themeInfo.pathToFile); + } + File finalFile = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), currentFile.getName()); + try { + if (!AndroidUtilities.copyFile(currentFile, finalFile)) { + return; + } + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/xml"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(finalFile)); + startActivityForResult(Intent.createChooser(intent, LocaleController.getString("ShareFile", R.string.ShareFile)), 500); + } catch (Exception e) { + FileLog.e(e); + } + } else if (which == 1) { + if (parentLayout != null) { + Theme.applyTheme(themeInfo); + parentLayout.rebuildAllFragmentViews(true); + parentLayout.showLastFragment(); + new ThemeEditorView().show(getParentActivity(), themeInfo.name); + } + } else { + if (getParentActivity() == null) { + return; + } + AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); + builder.setMessage(LocaleController.getString("DeleteThemeAlert", R.string.DeleteThemeAlert)); + builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); + builder.setPositiveButton(LocaleController.getString("Delete", R.string.Delete), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + if (Theme.deleteTheme(themeInfo)) { + parentLayout.rebuildAllFragmentViews(true); + parentLayout.showLastFragment(); + } + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } + } + }); + builder.setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), null); + showDialog(builder.create()); + } + } + }); + showDialog(builder.create()); + } + }); + break; + case 1: + view = new TextSettingsCell(mContext); + ((TextSettingsCell) view).setText(LocaleController.getString("CreateNewTheme", R.string.CreateNewTheme), false); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 2: + view = new TextInfoPrivacyCell(mContext); + ((TextInfoPrivacyCell) view).setText(LocaleController.getString("CreateNewThemeInfo", R.string.CreateNewThemeInfo)); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider, Theme.key_windowBackgroundGrayShadow)); + break; + case 3: + default: + view = new ShadowSectionCell(mContext); + view.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + break; + } + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if (holder.getItemViewType() == 0) { + position -= 2; + Theme.ThemeInfo themeInfo = Theme.themes.get(position); + ((ThemeCell) holder.itemView).setTheme(themeInfo, position != Theme.themes.size() - 1); + } + } + + @Override + public int getItemViewType(int i) { + if (i == 0) { + return 1; + } else if (i == 1) { + return 2; + } else if (i == Theme.themes.size() + 2) { + return 3; + } + return 0; + } + } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(listView, 0, new Class[]{ThemeCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, 0, new Class[]{ThemeCell.class}, new String[]{"checkImage"}, null, null, null, Theme.key_featuredStickers_addedIcon), + new ThemeDescription(listView, 0, new Class[]{ThemeCell.class}, new String[]{"optionsButton"}, null, null, null, Theme.key_stickers_menu), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{ShadowSectionCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(listView, 0, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + }; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java new file mode 100644 index 00000000000..0d4490dba40 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/ThemePreviewActivity.java @@ -0,0 +1,885 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Nikolai Kudashov, 2013-2017. + */ + +package org.telegram.ui; + +import android.animation.ObjectAnimator; +import android.animation.StateListAnimator; +import android.annotation.SuppressLint; +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Canvas; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.text.style.CharacterStyle; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessageObject; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.MenuDrawable; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.ChatActionCell; +import org.telegram.ui.Cells.ChatMessageCell; +import org.telegram.ui.Cells.DialogCell; +import org.telegram.ui.Cells.LoadingCell; +import org.telegram.ui.Components.CombinedDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.SizeNotifierFrameLayout; + +import java.io.File; +import java.util.ArrayList; + +public class ThemePreviewActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private FrameLayout page1; + private RecyclerListView listView; + private DialogsAdapter dialogsAdapter; + private ImageView floatingButton; + private View dotsContainer; + private ActionBar actionBar2; + + private SizeNotifierFrameLayout page2; + private RecyclerListView listView2; + private MessagesAdapter messagesAdapter; + + private Theme.ThemeInfo applyingTheme; + private File themeFile; + private boolean applied; + + public ThemePreviewActivity(File file, Theme.ThemeInfo themeInfo) { + super(); + swipeBackEnabled = false; + applyingTheme = themeInfo; + themeFile = file; + } + + @Override + public View createView(Context context) { + page1 = new FrameLayout(context); + ActionBarMenu menu = actionBar.createMenu(); + final ActionBarMenuItem item = menu.addItem(0, R.drawable.ic_ab_search).setIsSearchField(true).setActionBarMenuItemSearchListener(new ActionBarMenuItem.ActionBarMenuItemSearchListener() { + @Override + public void onSearchExpand() { + + } + + @Override + public boolean canCollapseSearch() { + return true; + } + + @Override + public void onSearchCollapse() { + + } + + @Override + public void onTextChanged(EditText editText) { + + } + }); + item.getSearchField().setHint(LocaleController.getString("Search", R.string.Search)); + + actionBar.setBackButtonDrawable(new MenuDrawable()); + actionBar.setAddToContainer(false); + actionBar.setTitle(LocaleController.getString("ThemePreview", R.string.ThemePreview)); + + page1 = new FrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(widthSize, heightSize); + + measureChildWithMargins(actionBar, widthMeasureSpec, 0, heightMeasureSpec, 0); + int actionBarHeight = actionBar.getMeasuredHeight(); + if (actionBar.getVisibility() == VISIBLE) { + heightSize -= actionBarHeight; + } + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); + layoutParams.topMargin = actionBarHeight; + listView.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)); + + measureChildWithMargins(floatingButton, widthMeasureSpec, 0, heightMeasureSpec, 0); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == actionBar && parentLayout != null) { + parentLayout.drawHeaderShadow(canvas, actionBar.getVisibility() == VISIBLE ? actionBar.getMeasuredHeight() : 0); + } + return result; + } + }; + page1.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + listView = new RecyclerListView(context); + listView.setVerticalScrollBarEnabled(true); + listView.setItemAnimator(null); + listView.setLayoutAnimation(null); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + page1.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + + floatingButton = new ImageView(context); + floatingButton.setScaleType(ImageView.ScaleType.CENTER); + + Drawable drawable = Theme.createSimpleSelectorCircleDrawable(AndroidUtilities.dp(56), Theme.getColor(Theme.key_chats_actionBackground), Theme.getColor(Theme.key_chats_actionPressedBackground)); + if (Build.VERSION.SDK_INT < 21) { + Drawable shadowDrawable = context.getResources().getDrawable(R.drawable.floating_shadow).mutate(); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(0xff000000, PorterDuff.Mode.MULTIPLY)); + CombinedDrawable combinedDrawable = new CombinedDrawable(shadowDrawable, drawable, 0, 0); + combinedDrawable.setIconSize(AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + drawable = combinedDrawable; + } + floatingButton.setBackgroundDrawable(drawable); + floatingButton.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_chats_actionIcon), PorterDuff.Mode.MULTIPLY)); + floatingButton.setImageResource(R.drawable.floating_pencil); + if (Build.VERSION.SDK_INT >= 21) { + StateListAnimator animator = new StateListAnimator(); + animator.addState(new int[]{android.R.attr.state_pressed}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(2), AndroidUtilities.dp(4)).setDuration(200)); + animator.addState(new int[]{}, ObjectAnimator.ofFloat(floatingButton, "translationZ", AndroidUtilities.dp(4), AndroidUtilities.dp(2)).setDuration(200)); + floatingButton.setStateListAnimator(animator); + floatingButton.setOutlineProvider(new ViewOutlineProvider() { + @SuppressLint("NewApi") + @Override + public void getOutline(View view, Outline outline) { + outline.setOval(0, 0, AndroidUtilities.dp(56), AndroidUtilities.dp(56)); + } + }); + } + page1.addView(floatingButton, LayoutHelper.createFrame(Build.VERSION.SDK_INT >= 21 ? 56 : 60, Build.VERSION.SDK_INT >= 21 ? 56 : 60, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.BOTTOM, LocaleController.isRTL ? 14 : 0, 0, LocaleController.isRTL ? 0 : 14, 14)); + + dialogsAdapter = new DialogsAdapter(context); + listView.setAdapter(dialogsAdapter); + + page2 = new SizeNotifierFrameLayout(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + + setMeasuredDimension(widthSize, heightSize); + + measureChildWithMargins(actionBar2, widthMeasureSpec, 0, heightMeasureSpec, 0); + int actionBarHeight = actionBar2.getMeasuredHeight(); + if (actionBar2.getVisibility() == VISIBLE) { + heightSize -= actionBarHeight; + } + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) listView2.getLayoutParams(); + layoutParams.topMargin = actionBarHeight; + listView2.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean result = super.drawChild(canvas, child, drawingTime); + if (child == actionBar2 && parentLayout != null) { + parentLayout.drawHeaderShadow(canvas, actionBar2.getVisibility() == VISIBLE ? actionBar2.getMeasuredHeight() : 0); + } + return result; + } + }; + page2.setBackgroundImage(Theme.getCachedWallpaper()); + + actionBar2 = createActionBar(context); + actionBar2.setBackButtonDrawable(new BackDrawable(false)); + actionBar2.setTitle("Reinhardt"); + actionBar2.setSubtitle(LocaleController.formatDateOnline(System.currentTimeMillis() / 1000 - 60 * 60)); + page2.addView(actionBar2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + + listView2 = new RecyclerListView(context); + listView2.setVerticalScrollBarEnabled(true); + listView2.setItemAnimator(null); + listView2.setLayoutAnimation(null); + listView2.setPadding(0, AndroidUtilities.dp(4), 0, AndroidUtilities.dp(4)); + listView2.setClipToPadding(false); + listView2.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, true)); + listView2.setVerticalScrollbarPosition(LocaleController.isRTL ? RecyclerListView.SCROLLBAR_POSITION_LEFT : RecyclerListView.SCROLLBAR_POSITION_RIGHT); + page2.addView(listView2, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + + messagesAdapter = new MessagesAdapter(context); + listView2.setAdapter(messagesAdapter); + + fragmentView = new FrameLayout(context); + FrameLayout frameLayout = (FrameLayout) fragmentView; + + final ViewPager viewPager = new ViewPager(context); + viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + + } + + @Override + public void onPageSelected(int position) { + dotsContainer.invalidate(); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }); + viewPager.setAdapter(new PagerAdapter() { + + @Override + public int getCount() { + return 2; + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return object == view; + } + + @Override + public int getItemPosition(Object object) { + return POSITION_UNCHANGED; + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + View view = position == 0 ? page1 : page2; + container.addView(view); + return view; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + container.removeView((View) object); + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + if (observer != null) { + super.unregisterDataSetObserver(observer); + } + } + }); + AndroidUtilities.setViewPagerEdgeEffectColor(viewPager, Theme.getColor(Theme.key_actionBarDefault)); + frameLayout.addView(viewPager, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 48)); + + View shadow = new View(context); + shadow.setBackgroundResource(R.drawable.header_shadow_reverse); + frameLayout.addView(shadow, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 3, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 48)); + + FrameLayout bottomLayout = new FrameLayout(context); + bottomLayout.setBackgroundColor(0xffffffff); + frameLayout.addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.LEFT | Gravity.BOTTOM)); + + dotsContainer = new View(context) { + + private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + + @Override + protected void onDraw(Canvas canvas) { + int selected = viewPager.getCurrentItem(); + for (int a = 0; a < 2; a++) { + paint.setColor(a == selected ? 0xff999999 : 0xffcccccc); + canvas.drawCircle(AndroidUtilities.dp(3 + 15 * a), AndroidUtilities.dp(4), AndroidUtilities.dp(3), paint); + } + } + }; + bottomLayout.addView(dotsContainer, LayoutHelper.createFrame(22, 8, Gravity.CENTER)); + + TextView cancelButton = new TextView(context); + cancelButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + cancelButton.setTextColor(0xff19a7e8); + cancelButton.setGravity(Gravity.CENTER); + cancelButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x2f000000, 0)); + cancelButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); + cancelButton.setText(LocaleController.getString("Cancel", R.string.Cancel).toUpperCase()); + cancelButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomLayout.addView(cancelButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.LEFT)); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Theme.applyPreviousTheme(); + parentLayout.rebuildAllFragmentViews(false); + finishFragment(); + } + }); + + TextView doneButton = new TextView(context); + doneButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + doneButton.setTextColor(0xff19a7e8); + doneButton.setGravity(Gravity.CENTER); + doneButton.setBackgroundDrawable(Theme.createSelectorDrawable(0x2f000000, 0)); + doneButton.setPadding(AndroidUtilities.dp(29), 0, AndroidUtilities.dp(29), 0); + doneButton.setText(LocaleController.getString("ApplyTheme", R.string.ApplyTheme).toUpperCase()); + doneButton.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + bottomLayout.addView(doneButton, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.MATCH_PARENT, Gravity.TOP | Gravity.RIGHT)); + doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + applied = true; + parentLayout.rebuildAllFragmentViews(false); + Theme.applyThemeFile(themeFile, applyingTheme.name, false); + finishFragment(); + } + }); + + return fragmentView; + } + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); + super.onFragmentDestroy(); + } + + @Override + public void onResume() { + super.onResume(); + if (dialogsAdapter != null) { + dialogsAdapter.notifyDataSetChanged(); + } + if (messagesAdapter != null) { + messagesAdapter.notifyDataSetChanged(); + } + } + + @Override + public boolean onBackPressed() { + Theme.applyPreviousTheme(); + parentLayout.rebuildAllFragmentViews(false); + return super.onBackPressed(); + } + + @Override + @SuppressWarnings("unchecked") + public void didReceivedNotification(int id, Object... args) { + if (id == NotificationCenter.emojiDidLoaded) { + if (listView == null) { + return; + } + int count = listView.getChildCount(); + for (int a = 0; a < count; a++) { + View child = listView.getChildAt(a); + if (child instanceof DialogCell) { + DialogCell cell = (DialogCell) child; + cell.update(0); + } + } + } + } + + public class DialogsAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + private ArrayList dialogs; + + public DialogsAdapter(Context context) { + mContext = context; + dialogs = new ArrayList<>(); + + int date = (int) (System.currentTimeMillis() / 1000); + DialogCell.CustomDialog customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Eva Summer"; + customDialog.message = "Reminds me of a Chinese prove..."; + customDialog.id = 0; + customDialog.unread_count = 0; + customDialog.pinned = true; + customDialog.muted = false; + customDialog.type = 0; + customDialog.date = date; + customDialog.verified = false; + customDialog.isMedia = false; + customDialog.sent = true; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Alexandra Smith"; + customDialog.message = "Reminds me of a Chinese prove..."; + customDialog.id = 1; + customDialog.unread_count = 2; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 0; + customDialog.date = date - 60 * 60; + customDialog.verified = false; + customDialog.isMedia = false; + customDialog.sent = false; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Make Apple"; + customDialog.message = "\uD83E\uDD37\u200D♂️ Sticker"; + customDialog.id = 2; + customDialog.unread_count = 3; + customDialog.pinned = false; + customDialog.muted = true; + customDialog.type = 0; + customDialog.date = date - 60 * 60 * 2; + customDialog.verified = false; + customDialog.isMedia = true; + customDialog.sent = false; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Paul Newman"; + customDialog.message = "Any ideas?"; + customDialog.id = 3; + customDialog.unread_count = 0; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 2; + customDialog.date = date - 60 * 60 * 3; + customDialog.verified = false; + customDialog.isMedia = false; + customDialog.sent = false; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Old Pirates"; + customDialog.message = "Yo-ho-ho!"; + customDialog.id = 4; + customDialog.unread_count = 0; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 1; + customDialog.date = date - 60 * 60 * 4; + customDialog.verified = false; + customDialog.isMedia = false; + customDialog.sent = true; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Kate Bright"; + customDialog.message = "Hola!"; + customDialog.id = 5; + customDialog.unread_count = 0; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 0; + customDialog.date = date - 60 * 60 * 5; + customDialog.verified = false; + customDialog.isMedia = false; + customDialog.sent = false; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Nick K"; + customDialog.message = "These are not the droids you are looking for"; + customDialog.id = 6; + customDialog.unread_count = 0; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 0; + customDialog.date = date - 60 * 60 * 6; + customDialog.verified = true; + customDialog.isMedia = false; + customDialog.sent = false; + dialogs.add(customDialog); + + customDialog = new DialogCell.CustomDialog(); + customDialog.name = "Adler Toberg"; + customDialog.message = "Did someone say peanut butter?"; + customDialog.id = 0; + customDialog.unread_count = 0; + customDialog.pinned = false; + customDialog.muted = false; + customDialog.type = 0; + customDialog.date = date - 60 * 60 * 7; + customDialog.verified = true; + customDialog.isMedia = false; + customDialog.sent = false; + dialogs.add(customDialog); + } + + @Override + public int getItemCount() { + return dialogs.size() + 1; + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return holder.getItemViewType() != 1; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View view = null; + if (viewType == 0) { + view = new DialogCell(mContext); + } else if (viewType == 1) { + view = new LoadingCell(mContext); + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { + if (viewHolder.getItemViewType() == 0) { + DialogCell cell = (DialogCell) viewHolder.itemView; + cell.useSeparator = (i != getItemCount() - 1); + cell.setDialog(dialogs.get(i)); + } + } + + @Override + public int getItemViewType(int i) { + if (i == dialogs.size()) { + return 1; + } + return 0; + } + } + + public class MessagesAdapter extends RecyclerListView.SelectionAdapter { + + private Context mContext; + + private ArrayList messages; + + public MessagesAdapter(Context context) { + mContext = context; + messages = new ArrayList<>(); + + int date = (int) (System.currentTimeMillis() / 1000) - 60 * 60; + + TLRPC.Message message; + + message = new TLRPC.TL_message(); + message.message = "Reinhardt, we need to find you some new tunes \uD83C\uDFB6."; + message.date = date + 60; + message.dialog_id = 1; + message.flags = 259; + message.from_id = UserConfig.getClientUserId(); + message.id = 1; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.out = true; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = 0; + MessageObject replyMessageObject = new MessageObject(message, null, true); + + message = new TLRPC.TL_message(); + message.message = "I can't even take you seriously right now."; + message.date = date + 960; + message.dialog_id = 1; + message.flags = 259; + message.from_id = UserConfig.getClientUserId(); + message.id = 1; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.out = true; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = 0; + MessageObject messageObject; + messages.add(new MessageObject(message, null, true)); + + message = new TLRPC.TL_message(); + message.date = date + 130; + message.dialog_id = 1; + message.flags = 259; + message.from_id = 0; + message.id = 5; + message.media = new TLRPC.TL_messageMediaDocument(); + message.media.document = new TLRPC.TL_document(); + message.media.document.mime_type = "audio/mp4"; + message.media.document.thumb = new TLRPC.TL_photoSizeEmpty(); + message.media.document.thumb.type = "s"; + TLRPC.TL_documentAttributeAudio audio = new TLRPC.TL_documentAttributeAudio(); + audio.duration = 243; + audio.performer = "David Hasselhoff"; + audio.title = "True Survivor"; + message.media.document.attributes.add(audio); + message.out = false; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = UserConfig.getClientUserId(); + messages.add(new MessageObject(message, null, true)); + + message = new TLRPC.TL_message(); + message.message = "Ah, you kids today with techno music! You should enjoy the classics, like Hasselhoff!"; + message.date = date + 60; + message.dialog_id = 1; + message.flags = 257 + 8; + message.from_id = 0; + message.id = 1; + message.reply_to_msg_id = 5; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.out = false; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = UserConfig.getClientUserId(); + messageObject = new MessageObject(message, null, true); + messageObject.customReplyName = "Lucio"; + messageObject.replyMessageObject = replyMessageObject; + messages.add(messageObject); + + message = new TLRPC.TL_message(); + message.date = date + 120; + message.dialog_id = 1; + message.flags = 259; + message.from_id = UserConfig.getClientUserId(); + message.id = 1; + message.media = new TLRPC.TL_messageMediaDocument(); + message.media.document = new TLRPC.TL_document(); + message.media.document.mime_type = "audio/ogg"; + message.media.document.thumb = new TLRPC.TL_photoSizeEmpty(); + message.media.document.thumb.type = "s"; + audio = new TLRPC.TL_documentAttributeAudio(); + audio.flags = 1028; + audio.duration = 3; + audio.voice = true; + audio.waveform = new byte[]{0, 4, 17, -50, -93, 86, -103, -45, -12, -26, 63, -25, -3, 109, -114, -54, -4, -1, + -1, -1, -1, -29, -1, -1, -25, -1, -1, -97, -43, 57, -57, -108, 1, -91, -4, -47, 21, 99, 10, 97, 43, + 45, 115, -112, -77, 51, -63, 66, 40, 34, -122, -116, 48, -124, 16, 66, -120, 16, 68, 16, 33, 4, 1}; + message.media.document.attributes.add(audio); + message.out = true; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = 0; + messageObject = new MessageObject(message, null, true); + messageObject.audioProgressSec = 1; + messageObject.audioProgress = 0.3f; + messageObject.useCustomPhoto = true; + messages.add(messageObject); + + messages.add(replyMessageObject); + + message = new TLRPC.TL_message(); + message.date = date + 10; + message.dialog_id = 1; + message.flags = 257; + message.from_id = 0; + message.id = 1; + message.media = new TLRPC.TL_messageMediaPhoto(); + message.media.photo = new TLRPC.TL_photo(); + message.media.photo.has_stickers = false; + message.media.photo.id = 1; + message.media.photo.access_hash = 0; + message.media.photo.date = date; + TLRPC.TL_photoSize photoSize = new TLRPC.TL_photoSize(); + photoSize.size = 0; + photoSize.w = 500; + photoSize.h = 302; + photoSize.type = "s"; + photoSize.location = new TLRPC.TL_fileLocationUnavailable(); + message.media.photo.sizes.add(photoSize); + message.media.caption = "Bring it on! I LIVE for this!"; + message.out = false; + message.to_id = new TLRPC.TL_peerUser(); + message.to_id.user_id = UserConfig.getClientUserId(); + messageObject = new MessageObject(message, null, true); + messageObject.useCustomPhoto = true; + messages.add(messageObject); + + message = new TLRPC.Message(); + message.message = LocaleController.formatDateChat(date); + message.id = 0; + message.date = date; + messageObject = new MessageObject(message, null, false); + messageObject.type = 10; + messageObject.contentType = 1; + messageObject.isDateObject = true; + messages.add(messageObject); + } + + @Override + public int getItemCount() { + return messages.size(); + } + + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return false; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { + View view = null; + if (viewType == 0) { + view = new ChatMessageCell(mContext); + ChatMessageCell chatMessageCell = (ChatMessageCell) view; + chatMessageCell.setDelegate(new ChatMessageCell.ChatMessageCellDelegate() { + @Override + public void didPressedShare(ChatMessageCell cell) { + + } + + @Override + public boolean needPlayAudio(MessageObject messageObject) { + return false; + } + + @Override + public void didPressedChannelAvatar(ChatMessageCell cell, TLRPC.Chat chat, int postId) { + + } + + @Override + public void didPressedOther(ChatMessageCell cell) { + + } + + @Override + public void didPressedUserAvatar(ChatMessageCell cell, TLRPC.User user) { + + } + + @Override + public void didPressedBotButton(ChatMessageCell cell, TLRPC.KeyboardButton button) { + + } + + @Override + public void didPressedCancelSendButton(ChatMessageCell cell) { + + } + + @Override + public void didLongPressed(ChatMessageCell cell) { + + } + + @Override + public boolean canPerformActions() { + return false; + } + + @Override + public void didPressedUrl(MessageObject messageObject, final CharacterStyle url, boolean longPress) { + + } + + @Override + public void needOpenWebView(String url, String title, String description, String originalUrl, int w, int h) { + + } + + @Override + public void didPressedReplyMessage(ChatMessageCell cell, int id) { + + } + + @Override + public void didPressedViaBot(ChatMessageCell cell, String username) { + + } + + @Override + public void didPressedImage(ChatMessageCell cell) { + + } + + @Override + public void didPressedInstantButton(ChatMessageCell cell) { + + } + }); + } else if (viewType == 1) { + view = new ChatActionCell(mContext); + ((ChatActionCell) view).setDelegate(new ChatActionCell.ChatActionCellDelegate() { + @Override + public void didClickedImage(ChatActionCell cell) { + + } + + @Override + public void didLongPressed(ChatActionCell cell) { + + } + + @Override + public void needOpenUserProfile(int uid) { + + } + + @Override + public void didPressedReplyMessage(ChatActionCell cell, int id) { + + } + + @Override + public void didPressedBotButton(MessageObject messageObject, TLRPC.KeyboardButton button) { + + } + }); + } + view.setLayoutParams(new RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)); + return new RecyclerListView.Holder(view); + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + MessageObject message = messages.get(position); + View view = holder.itemView; + + if (view instanceof ChatMessageCell) { + ChatMessageCell messageCell = (ChatMessageCell) view; + messageCell.isChat = false; + int nextType = getItemViewType(position - 1); + int prevType = getItemViewType(position + 1); + boolean pinnedBotton; + boolean pinnedTop; + if (!(message.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && nextType == holder.getItemViewType()) { + MessageObject nextMessage = messages.get(position - 1); + pinnedBotton = nextMessage.isOutOwner() == message.isOutOwner() && Math.abs(nextMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + } else { + pinnedBotton = false; + } + if (prevType == holder.getItemViewType()) { + MessageObject prevMessage = messages.get(position + 1); + pinnedTop = !(prevMessage.messageOwner.reply_markup instanceof TLRPC.TL_replyInlineMarkup) && prevMessage.isOutOwner() == message.isOutOwner() && Math.abs(prevMessage.messageOwner.date - message.messageOwner.date) <= 5 * 60; + } else { + pinnedTop = false; + } + messageCell.setFullyDraw(true); + messageCell.setMessageObject(message, pinnedBotton, pinnedTop); + } else if (view instanceof ChatActionCell) { + ChatActionCell actionCell = (ChatActionCell) view; + actionCell.setMessageObject(message); + actionCell.setAlpha(1.0f); + } + } + + @Override + public int getItemViewType(int i) { + if (i >= 0 && i < messages.size()) { + return messages.get(i).contentType; + } + return 4; + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java index 63e638ee6a3..be0e75eecc5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TwoStepVerificationActivity.java @@ -3,14 +3,12 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Typeface; @@ -23,16 +21,12 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; -import android.widget.AdapterView; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; @@ -42,6 +36,8 @@ import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.support.widget.LinearLayoutManager; +import org.telegram.messenger.support.widget.RecyclerView; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.RequestDelegate; import org.telegram.tgnet.TLObject; @@ -50,22 +46,26 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; -import org.telegram.ui.Adapters.BaseFragmentAdapter; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.TextInfoPrivacyCell; import org.telegram.ui.Cells.TextSettingsCell; +import org.telegram.ui.Components.EmptyTextProgressView; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; public class TwoStepVerificationActivity extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { private ListAdapter listAdapter; - private ListView listView; + private RecyclerListView listView; private TextView titleTextView; private TextView bottomTextView; private TextView bottomButton; private EditText passwordEditText; - private ProgressDialog progressDialog; - private FrameLayout progressView; + private AlertDialog progressDialog; + private EmptyTextProgressView emptyView; private ActionBarMenuItem doneItem; private ScrollView scrollView; @@ -131,7 +131,7 @@ public void onFragmentDestroy() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -155,42 +155,30 @@ public void onItemClick(int id) { fragmentView = new FrameLayout(context); FrameLayout frameLayout = (FrameLayout) fragmentView; - frameLayout.setBackgroundColor(0xfff0f0f0); + frameLayout.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundGray)); ActionBarMenu menu = actionBar.createMenu(); doneItem = menu.addItemWithWidth(done_button, R.drawable.ic_done, AndroidUtilities.dp(56)); scrollView = new ScrollView(context); scrollView.setFillViewport(true); - frameLayout.addView(scrollView); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) scrollView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - scrollView.setLayoutParams(layoutParams); + frameLayout.addView(scrollView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); LinearLayout linearLayout = new LinearLayout(context); linearLayout.setOrientation(LinearLayout.VERTICAL); - scrollView.addView(linearLayout); - ScrollView.LayoutParams layoutParams2 = (ScrollView.LayoutParams) linearLayout.getLayoutParams(); - layoutParams2.width = ScrollView.LayoutParams.MATCH_PARENT; - layoutParams2.height = ScrollView.LayoutParams.WRAP_CONTENT; - linearLayout.setLayoutParams(layoutParams2); + scrollView.addView(linearLayout, LayoutHelper.createScroll(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP)); titleTextView = new TextView(context); - titleTextView.setTextColor(0xff757575); + titleTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); titleTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); titleTextView.setGravity(Gravity.CENTER_HORIZONTAL); - linearLayout.addView(titleTextView); - LinearLayout.LayoutParams layoutParams3 = (LinearLayout.LayoutParams) titleTextView.getLayoutParams(); - layoutParams3.width = LayoutHelper.WRAP_CONTENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.gravity = Gravity.CENTER_HORIZONTAL; - layoutParams3.topMargin = AndroidUtilities.dp(38); - titleTextView.setLayoutParams(layoutParams3); + linearLayout.addView(titleTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 38, 0, 0)); passwordEditText = new EditText(context); passwordEditText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); - passwordEditText.setTextColor(0xff000000); + passwordEditText.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + passwordEditText.setHintTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteHintText)); + passwordEditText.setBackgroundDrawable(Theme.createEditTextDrawable(context, false)); passwordEditText.setMaxLines(1); passwordEditText.setLines(1); passwordEditText.setGravity(Gravity.CENTER_HORIZONTAL); @@ -199,15 +187,7 @@ public void onItemClick(int id) { passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance()); passwordEditText.setTypeface(Typeface.DEFAULT); AndroidUtilities.clearCursorDrawable(passwordEditText); - linearLayout.addView(passwordEditText); - layoutParams3 = (LinearLayout.LayoutParams) passwordEditText.getLayoutParams(); - layoutParams3.topMargin = AndroidUtilities.dp(32); - layoutParams3.height = AndroidUtilities.dp(36); - layoutParams3.leftMargin = AndroidUtilities.dp(40); - layoutParams3.rightMargin = AndroidUtilities.dp(40); - layoutParams3.gravity = Gravity.TOP | Gravity.LEFT; - layoutParams3.width = LayoutHelper.MATCH_PARENT; - passwordEditText.setLayoutParams(layoutParams3); + linearLayout.addView(passwordEditText, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 36, Gravity.TOP | Gravity.LEFT, 40, 32, 40, 0)); passwordEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) { @@ -236,43 +216,23 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { }); bottomTextView = new TextView(context); - bottomTextView.setTextColor(0xff757575); + bottomTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText6)); bottomTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); bottomTextView.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP); bottomTextView.setText(LocaleController.getString("YourEmailInfo", R.string.YourEmailInfo)); - linearLayout.addView(bottomTextView); - layoutParams3 = (LinearLayout.LayoutParams) bottomTextView.getLayoutParams(); - layoutParams3.width = LayoutHelper.WRAP_CONTENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP; - layoutParams3.topMargin = AndroidUtilities.dp(30); - layoutParams3.leftMargin = AndroidUtilities.dp(40); - layoutParams3.rightMargin = AndroidUtilities.dp(40); - bottomTextView.setLayoutParams(layoutParams3); + linearLayout.addView(bottomTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.TOP, 40, 30, 40, 0)); LinearLayout linearLayout2 = new LinearLayout(context); linearLayout2.setGravity(Gravity.BOTTOM | Gravity.CENTER_VERTICAL); - linearLayout.addView(linearLayout2); - layoutParams3 = (LinearLayout.LayoutParams) linearLayout2.getLayoutParams(); - layoutParams3.width = LayoutHelper.MATCH_PARENT; - layoutParams3.height = LayoutHelper.MATCH_PARENT; - linearLayout2.setLayoutParams(layoutParams3); + linearLayout.addView(linearLayout2, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); bottomButton = new TextView(context); - bottomButton.setTextColor(0xff4d83b3); + bottomButton.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlueText4)); bottomButton.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); bottomButton.setGravity((LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.BOTTOM); bottomButton.setText(LocaleController.getString("YourEmailSkip", R.string.YourEmailSkip)); bottomButton.setPadding(0, AndroidUtilities.dp(10), 0, 0); - linearLayout2.addView(bottomButton); - layoutParams3 = (LinearLayout.LayoutParams) bottomButton.getLayoutParams(); - layoutParams3.width = LayoutHelper.WRAP_CONTENT; - layoutParams3.height = LayoutHelper.WRAP_CONTENT; - layoutParams3.gravity = (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.BOTTOM; - layoutParams3.bottomMargin = AndroidUtilities.dp(14); - layoutParams3.leftMargin = AndroidUtilities.dp(40); - layoutParams3.rightMargin = AndroidUtilities.dp(40); - bottomButton.setLayoutParams(layoutParams3); + linearLayout2.addView(bottomButton, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT) | Gravity.BOTTOM, 40, 0, 40, 14)); bottomButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -350,56 +310,31 @@ public void onClick(DialogInterface dialogInterface, int i) { }); if (type == 0) { - progressView = new FrameLayout(context); - frameLayout.addView(progressView); - layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - progressView.setLayoutParams(layoutParams); - progressView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - return true; - } - }); - - ProgressBar progressBar = new ProgressBar(context); - progressView.addView(progressBar); - layoutParams = (FrameLayout.LayoutParams) progressView.getLayoutParams(); - layoutParams.width = LayoutHelper.WRAP_CONTENT; - layoutParams.height = LayoutHelper.WRAP_CONTENT; - layoutParams.gravity = Gravity.CENTER; - progressView.setLayoutParams(layoutParams); + emptyView = new EmptyTextProgressView(context); + emptyView.showProgress(); - listView = new ListView(context); - listView.setDivider(null); - listView.setEmptyView(progressView); - listView.setDividerHeight(0); + listView = new RecyclerListView(context); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)); + listView.setEmptyView(emptyView); listView.setVerticalScrollBarEnabled(false); - listView.setDrawSelectorOnTop(true); - frameLayout.addView(listView); - layoutParams = (FrameLayout.LayoutParams) listView.getLayoutParams(); - layoutParams.width = LayoutHelper.MATCH_PARENT; - layoutParams.height = LayoutHelper.MATCH_PARENT; - layoutParams.gravity = Gravity.TOP; - listView.setLayoutParams(layoutParams); + frameLayout.addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); listView.setAdapter(listAdapter = new ListAdapter(context)); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + listView.setOnItemClickListener(new RecyclerListView.OnItemClickListener() { @Override - public void onItemClick(AdapterView adapterView, View view, final int i, long l) { - if (i == setPasswordRow || i == changePasswordRow) { + public void onItemClick(View view, int position) { + if (position == setPasswordRow || position == changePasswordRow) { TwoStepVerificationActivity fragment = new TwoStepVerificationActivity(1); fragment.currentPasswordHash = currentPasswordHash; fragment.currentPassword = currentPassword; presentFragment(fragment); - } else if (i == setRecoveryEmailRow || i == changeRecoveryEmailRow) { + } else if (position == setRecoveryEmailRow || position == changeRecoveryEmailRow) { TwoStepVerificationActivity fragment = new TwoStepVerificationActivity(1); fragment.currentPasswordHash = currentPasswordHash; fragment.currentPassword = currentPassword; fragment.emailOnly = true; fragment.passwordSetState = 3; presentFragment(fragment); - } else if (i == turnPasswordOffRow || i == abortPasswordRow) { + } else if (position == turnPasswordOffRow || position == abortPasswordRow) { AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); builder.setMessage(LocaleController.getString("TurnPasswordOffQuestion", R.string.TurnPasswordOffQuestion)); builder.setTitle(LocaleController.getString("AppName", R.string.AppName)); @@ -464,6 +399,9 @@ public void onTransitionAnimationEnd(boolean isOpen, boolean backward) { private void loadPasswordInfo(final boolean silent) { if (!silent) { loading = true; + if (listAdapter != null) { + listAdapter.notifyDataSetChanged(); + } } TLRPC.TL_account_getPassword req = new TLRPC.TL_account_getPassword(); ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { @@ -602,8 +540,8 @@ private void updateRows() { if (listView != null) { listView.setVisibility(View.VISIBLE); scrollView.setVisibility(View.INVISIBLE); - progressView.setVisibility(View.VISIBLE); - listView.setEmptyView(progressView); + emptyView.setVisibility(View.VISIBLE); + listView.setEmptyView(emptyView); } if (passwordEditText != null) { doneItem.setVisibility(View.GONE); @@ -617,7 +555,7 @@ private void updateRows() { listView.setEmptyView(null); listView.setVisibility(View.INVISIBLE); scrollView.setVisibility(View.VISIBLE); - progressView.setVisibility(View.INVISIBLE); + emptyView.setVisibility(View.INVISIBLE); } if (passwordEditText != null) { doneItem.setVisibility(View.VISIBLE); @@ -648,7 +586,7 @@ private void needShowProgress() { if (getParentActivity() == null || getParentActivity().isFinishing() || progressDialog != null) { return; } - progressDialog = new ProgressDialog(getParentActivity()); + progressDialog = new AlertDialog(getParentActivity(), 1); progressDialog.setMessage(LocaleController.getString("Loading", R.string.Loading)); progressDialog.setCanceledOnTouchOutside(false); progressDialog.setCancelable(false); @@ -662,7 +600,7 @@ private void needHideProgress() { try { progressDialog.dismiss(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } progressDialog = null; } @@ -706,7 +644,7 @@ private void setNewPassword(final boolean clear) { try { newPasswordBytes = firstPassword.getBytes("UTF-8"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } byte[] new_salt = currentPassword.new_salt; @@ -737,6 +675,7 @@ public void run() { currentPassword = null; currentPasswordHash = new byte[0]; loadPasswordInfo(false); + NotificationCenter.getInstance().postNotificationName(NotificationCenter.didRemovedTwoStepPassword); updateRows(); } else { if (getParentActivity() == null) { @@ -810,7 +749,7 @@ private void processDone() { try { oldPasswordBytes = oldPassword.getBytes("UTF-8"); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } needShowProgress(); @@ -868,7 +807,7 @@ public void run() { try { Toast.makeText(getParentActivity(), LocaleController.getString("PasswordDoNotMatch", R.string.PasswordDoNotMatch), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } onPasscodeError(true); return; @@ -880,7 +819,7 @@ public void run() { try { Toast.makeText(getParentActivity(), LocaleController.getString("PasswordAsHintError", R.string.PasswordAsHintError), Toast.LENGTH_SHORT).show(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } onPasscodeError(false); return; @@ -966,7 +905,8 @@ private void onPasscodeError(boolean clear) { AndroidUtilities.shakeView(titleTextView, 2, 0); } - private class ListAdapter extends BaseFragmentAdapter { + private class ListAdapter extends RecyclerListView.SelectionAdapter { + private Context mContext; public ListAdapter(Context context) { @@ -974,99 +914,117 @@ public ListAdapter(Context context) { } @Override - public boolean areAllItemsEnabled() { - return false; - } - - @Override - public boolean isEnabled(int i) { - return i != setPasswordDetailRow && i != shadowRow && i != passwordSetupDetailRow && i != passwordEmailVerifyDetailRow && i != passwordEnabledDetailRow; + public boolean isEnabled(RecyclerView.ViewHolder holder) { + int position = holder.getAdapterPosition(); + return position != setPasswordDetailRow && position != shadowRow && position != passwordSetupDetailRow && position != passwordEmailVerifyDetailRow && position != passwordEnabledDetailRow; } @Override - public int getCount() { + public int getItemCount() { return loading || currentPassword == null ? 0 : rowCount; } @Override - public Object getItem(int i) { - return null; - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public boolean hasStableIds() { - return false; + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view; + switch (viewType) { + case 0: + view = new TextSettingsCell(mContext); + view.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + break; + case 1: + default: + view = new TextInfoPrivacyCell(mContext); + break; + } + return new RecyclerListView.Holder(view); } @Override - public View getView(int i, View view, ViewGroup viewGroup) { - int viewType = getItemViewType(i); - if (viewType == 0) { - if (view == null) { - view = new TextSettingsCell(mContext); - view.setBackgroundColor(0xffffffff); - } - TextSettingsCell textCell = (TextSettingsCell) view; - textCell.setTextColor(0xff212121); - if (i == changePasswordRow) { - textCell.setText(LocaleController.getString("ChangePassword", R.string.ChangePassword), true); - } else if (i == setPasswordRow) { - textCell.setText(LocaleController.getString("SetAdditionalPassword", R.string.SetAdditionalPassword), true); - } else if (i == turnPasswordOffRow) { - textCell.setText(LocaleController.getString("TurnPasswordOff", R.string.TurnPasswordOff), true); - } else if (i == changeRecoveryEmailRow) { - textCell.setText(LocaleController.getString("ChangeRecoveryEmail", R.string.ChangeRecoveryEmail), abortPasswordRow != -1); - } else if (i == setRecoveryEmailRow) { - textCell.setText(LocaleController.getString("SetRecoveryEmail", R.string.SetRecoveryEmail), false); - } else if (i == abortPasswordRow) { - textCell.setTextColor(0xffd24949); - textCell.setText(LocaleController.getString("AbortPassword", R.string.AbortPassword), false); - } - } else if (viewType == 1) { - if (view == null) { - view = new TextInfoPrivacyCell(mContext); - } - if (i == setPasswordDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("SetAdditionalPasswordInfo", R.string.SetAdditionalPasswordInfo)); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (i == shadowRow) { - ((TextInfoPrivacyCell) view).setText(""); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (i == passwordSetupDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.formatString("EmailPasswordConfirmText", R.string.EmailPasswordConfirmText, currentPassword.email_unconfirmed_pattern)); - view.setBackgroundResource(R.drawable.greydivider_top); - } else if (i == passwordEnabledDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.getString("EnabledPasswordText", R.string.EnabledPasswordText)); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } else if (i == passwordEmailVerifyDetailRow) { - ((TextInfoPrivacyCell) view).setText(LocaleController.formatString("PendingEmailText", R.string.PendingEmailText, currentPassword.email_unconfirmed_pattern)); - view.setBackgroundResource(R.drawable.greydivider_bottom); - } + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + switch (holder.getItemViewType()) { + case 0: + TextSettingsCell textCell = (TextSettingsCell) holder.itemView; + textCell.setTag(Theme.key_windowBackgroundWhiteBlackText); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + if (position == changePasswordRow) { + textCell.setText(LocaleController.getString("ChangePassword", R.string.ChangePassword), true); + } else if (position == setPasswordRow) { + textCell.setText(LocaleController.getString("SetAdditionalPassword", R.string.SetAdditionalPassword), true); + } else if (position == turnPasswordOffRow) { + textCell.setText(LocaleController.getString("TurnPasswordOff", R.string.TurnPasswordOff), true); + } else if (position == changeRecoveryEmailRow) { + textCell.setText(LocaleController.getString("ChangeRecoveryEmail", R.string.ChangeRecoveryEmail), abortPasswordRow != -1); + } else if (position == setRecoveryEmailRow) { + textCell.setText(LocaleController.getString("SetRecoveryEmail", R.string.SetRecoveryEmail), false); + } else if (position == abortPasswordRow) { + textCell.setTag(Theme.key_windowBackgroundWhiteRedText3); + textCell.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteRedText3)); + textCell.setText(LocaleController.getString("AbortPassword", R.string.AbortPassword), false); + } + break; + case 1: + TextInfoPrivacyCell privacyCell = (TextInfoPrivacyCell) holder.itemView; + if (position == setPasswordDetailRow) { + privacyCell.setText(LocaleController.getString("SetAdditionalPasswordInfo", R.string.SetAdditionalPasswordInfo)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == shadowRow) { + privacyCell.setText(""); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == passwordSetupDetailRow) { + privacyCell.setText(LocaleController.formatString("EmailPasswordConfirmText", R.string.EmailPasswordConfirmText, currentPassword.email_unconfirmed_pattern)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_top, Theme.key_windowBackgroundGrayShadow)); + } else if (position == passwordEnabledDetailRow) { + privacyCell.setText(LocaleController.getString("EnabledPasswordText", R.string.EnabledPasswordText)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } else if (position == passwordEmailVerifyDetailRow) { + privacyCell.setText(LocaleController.formatString("PendingEmailText", R.string.PendingEmailText, currentPassword.email_unconfirmed_pattern)); + privacyCell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, Theme.key_windowBackgroundGrayShadow)); + } + break; } - return view; } @Override - public int getItemViewType(int i) { - if (i == setPasswordDetailRow || i == shadowRow || i == passwordSetupDetailRow || i == passwordEnabledDetailRow || i == passwordEmailVerifyDetailRow) { + public int getItemViewType(int position) { + if (position == setPasswordDetailRow || position == shadowRow || position == passwordSetupDetailRow || position == passwordEnabledDetailRow || position == passwordEmailVerifyDetailRow) { return 1; } return 0; } + } - @Override - public int getViewTypeCount() { - return 2; - } + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(listView, ThemeDescription.FLAG_CELLBACKGROUNDCOLOR, new Class[]{TextSettingsCell.class}, null, null, null, Theme.key_windowBackgroundWhite), + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundGray), - @Override - public boolean isEmpty() { - return loading || currentPassword == null; - } + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + + new ThemeDescription(listView, 0, new Class[]{View.class}, Theme.dividerPaint, null, null, Theme.key_divider), + + new ThemeDescription(emptyView, ThemeDescription.FLAG_PROGRESSBAR, null, null, null, null, Theme.key_progressCircle), + + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(listView, ThemeDescription.FLAG_CHECKTAG, new Class[]{TextSettingsCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteRedText3), + + new ThemeDescription(listView, ThemeDescription.FLAG_BACKGROUNDFILTER, new Class[]{TextInfoPrivacyCell.class}, null, null, null, Theme.key_windowBackgroundGrayShadow), + new ThemeDescription(listView, 0, new Class[]{TextInfoPrivacyCell.class}, new String[]{"textView"}, null, null, null, Theme.key_windowBackgroundWhiteGrayText4), + + new ThemeDescription(titleTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(bottomTextView, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteGrayText6), + new ThemeDescription(bottomButton, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlueText4), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_TEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteBlackText), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_HINTTEXTCOLOR, null, null, null, null, Theme.key_windowBackgroundWhiteHintText), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_BACKGROUNDFILTER, null, null, null, null, Theme.key_windowBackgroundWhiteInputField), + new ThemeDescription(passwordEditText, ThemeDescription.FLAG_BACKGROUNDFILTER | ThemeDescription.FLAG_DRAWABLESELECTEDSTATE, null, null, null, null, Theme.key_windowBackgroundWhiteInputFieldActivated), + }; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/VideoEditorActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/VideoEditorActivity.java index c0d85e39272..4c9f3f02879 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/VideoEditorActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/VideoEditorActivity.java @@ -3,35 +3,41 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.SurfaceTexture; import android.media.MediaCodecInfo; import android.media.MediaPlayer; import android.os.Build; import android.os.Bundle; +import android.text.TextPaint; import android.text.TextUtils; import android.view.Gravity; import android.view.KeyEvent; +import android.view.MotionEvent; import android.view.Surface; import android.view.TextureView; import android.view.View; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ListView; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.Box; @@ -44,24 +50,26 @@ import com.googlecode.mp4parser.util.Path; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; +import org.telegram.messenger.MessageObject; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.R; +import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarMenu; -import org.telegram.ui.ActionBar.ActionBarMenuItem; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.Adapters.MentionsAdapter; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.PhotoViewerCaptionEnterView; import org.telegram.ui.Components.PickerBottomLayoutViewer; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; import org.telegram.ui.Components.SizeNotifierFrameLayoutPhoto; import org.telegram.ui.Components.VideoSeekBarView; @@ -81,23 +89,37 @@ public class VideoEditorActivity extends BaseFragment implements NotificationCen private ImageView captionItem; private ImageView compressItem; private ImageView playButton; - private ActionBarMenuItem captionDoneItem; + private RadialProgressView progressView; private PhotoViewerCaptionEnterView captionEditText; private PickerBottomLayoutViewer pickerView; + private MessageObject videoPreviewMessageObject; + private boolean tryStartRequestPreviewOnFinish; + private boolean loadInitialVideo; + private boolean inPreview; + private int previewViewEnd; + private boolean requestingPreview; + + private QualityChooseView qualityChooseView; + private PickerBottomLayoutViewer qualityPicker; + private ChatActivity parentChatActivity; private MentionsAdapter mentionsAdapter; private RecyclerListView mentionListView; private LinearLayoutManager mentionLayoutManager; private AnimatorSet mentionListAnimation; + private boolean firstCaptionLayout; private boolean allowMentions; private boolean created; private boolean playerPrepared; private boolean muteVideo; - private boolean needCompressVideo; - private String oldTitle; + private int selectedCompression; + private int compressionsCount = -1; + private int previousCompression; + + private String currentSubtitle; private String videoPath; private float lastProgress; @@ -139,7 +161,7 @@ public void run() { playerCheck = videoPlayer != null && videoPlayer.isPlaying(); } catch (Exception e) { playerCheck = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } if (!playerCheck) { @@ -149,24 +171,41 @@ public void run() { @Override public void run() { if (videoPlayer != null && videoPlayer.isPlaying()) { - float startTime = videoTimelineView.getLeftProgress() * videoDuration; - float endTime = videoTimelineView.getRightProgress() * videoDuration; + float startTime; + float endTime; + float lrdiff; + if (inPreview) { + startTime = 0; + endTime = previewViewEnd; + lrdiff = 1.0f; + } else { + startTime = videoTimelineView.getLeftProgress() * videoDuration; + endTime = videoTimelineView.getRightProgress() * videoDuration; + lrdiff = videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress(); + } if (startTime == endTime) { startTime = endTime - 0.01f; } float progress = (videoPlayer.getCurrentPosition() - startTime) / (endTime - startTime); - float lrdiff = videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress(); - progress = videoTimelineView.getLeftProgress() + lrdiff * progress; + if (!inPreview) { + progress = videoTimelineView.getLeftProgress() + lrdiff * progress; + } if (progress > lastProgress) { videoSeekBarView.setProgress(progress); lastProgress = progress; } + int position = videoPlayer.getCurrentPosition(); if (videoPlayer.getCurrentPosition() >= endTime) { try { videoPlayer.pause(); onPlayComplete(); + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -175,7 +214,7 @@ public void run() { try { Thread.sleep(50); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } synchronized (sync) { @@ -184,49 +223,217 @@ public void run() { } }; + private class QualityChooseView extends View { + + private Paint paint; + private TextPaint textPaint; + + private int circleSize; + private int gapSize; + private int sideSide; + private int lineSize; + + private boolean moving; + private boolean startMoving; + private float startX; + + private int startMovingQuality; + + public QualityChooseView(Context context) { + super(context); + + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + textPaint.setTextSize(AndroidUtilities.dp(12)); + textPaint.setColor(0xffcdcdcd); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + float x = event.getX(); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + getParent().requestDisallowInterceptTouchEvent(true); + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (x > cx - AndroidUtilities.dp(15) && x < cx + AndroidUtilities.dp(15)) { + startMoving = a == selectedCompression; + startX = x; + startMovingQuality = selectedCompression; + break; + } + } + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (startMoving) { + if (Math.abs(startX - x) >= AndroidUtilities.getPixelsInCM(0.5f, true)) { + moving = true; + startMoving = false; + } + } else if (moving) { + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + int diff = lineSize / 2 + circleSize / 2 + gapSize; + if (x > cx - diff && x < cx + diff) { + if (selectedCompression != a) { + selectedCompression = a; + didChangedCompressionLevel(false); + invalidate(); + } + break; + } + } + } + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (!moving) { + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (x > cx - AndroidUtilities.dp(15) && x < cx + AndroidUtilities.dp(15)) { + if (selectedCompression != a) { + selectedCompression = a; + didChangedCompressionLevel(true); + invalidate(); + } + break; + } + } + } else { + if (selectedCompression != startMovingQuality) { + requestVideoPreview(1); + } + } + startMoving = false; + moving = false; + } + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + circleSize = AndroidUtilities.dp(12); + gapSize = AndroidUtilities.dp(2); + sideSide = AndroidUtilities.dp(18); + lineSize = (getMeasuredWidth() - circleSize * compressionsCount - gapSize * 8 - sideSide * 2) / (compressionsCount - 1); + } + + @Override + protected void onDraw(Canvas canvas) { + int cy = getMeasuredHeight() / 2 + AndroidUtilities.dp(6); + for (int a = 0; a < compressionsCount; a++) { + int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; + if (a <= selectedCompression) { + paint.setColor(0xff53aeef); + } else { + paint.setColor(0xff222222); + } + String text; + if (a == compressionsCount - 1) { + text = originalHeight + "p"; + } else if (a == 0) { + text = "240p"; + } else if (a == 1) { + text = "360p"; + } else if (a == 2) { + text = "480p"; + } else { + text = "720p"; + } + float width = textPaint.measureText(text); + canvas.drawCircle(cx, cy, a == selectedCompression ? AndroidUtilities.dp(8) : circleSize / 2, paint); + canvas.drawText(text, cx - width / 2, cy - AndroidUtilities.dp(16), textPaint); + if (a != 0) { + int x = cx - circleSize / 2 - gapSize - lineSize; + canvas.drawRect(x, cy - AndroidUtilities.dp(1), x + lineSize, cy + AndroidUtilities.dp(2), paint); + } + } + } + } + public VideoEditorActivity(Bundle args) { super(args); videoPath = args.getString("videoPath"); } - @Override - public boolean onFragmentCreate() { - if (created) { - return true; + private void destroyPlayer() { + if (videoPlayer != null) { + try { + if (videoPlayer != null) { + videoPlayer.stop(); + } + } catch (Exception ignore) { + + } + try { + if (videoPlayer != null) { + videoPlayer.release(); + } + } catch (Exception ignore) { + + } + videoPlayer = null; } - if (videoPath == null || !processOpenVideo()) { - return false; + } + + private boolean reinitPlayer(String path) { + destroyPlayer(); + if (playButton != null) { + playButton.setImageResource(R.drawable.video_edit_play); } + lastProgress = 0; videoPlayer = new MediaPlayer(); - videoPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - AndroidUtilities.runOnUIThread(new Runnable() { - @Override - public void run() { - onPlayComplete(); - } - }); - } - }); videoPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { playerPrepared = true; + previewViewEnd = videoPlayer.getDuration(); if (videoTimelineView != null && videoPlayer != null) { - videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + if (inPreview) { + videoPlayer.seekTo(0); + } else { + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + } } } }); try { - videoPlayer.setDataSource(videoPath); + videoPlayer.setDataSource(path); videoPlayer.prepareAsync(); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); + return false; + } + float volume = muteVideo ? 0.0f : 1.0f; + if (videoPlayer != null) { + videoPlayer.setVolume(volume, volume); + } + inPreview = !path.equals(videoPath); + if (textureView != null) { + try { + Surface s = new Surface(textureView.getSurfaceTexture()); + videoPlayer.setSurface(s); + } catch (Exception e) { + FileLog.e(e); + } + } + return true; + } + + @Override + public boolean onFragmentCreate() { + if (created) { + return true; + } + if (videoPath == null || !processOpenVideo()) { + return false; + } + if (!reinitPlayer(videoPath)) { return false; } NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeChats); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FilePreparingFailed); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.FileNewChunkAvailable); + created = true; return super.onFragmentCreate(); @@ -234,6 +441,11 @@ public void onPrepared(MediaPlayer mp) { @Override public void onFragmentDestroy() { + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } if (videoTimelineView != null) { videoTimelineView.destroy(); } @@ -243,23 +455,24 @@ public void onFragmentDestroy() { videoPlayer.release(); videoPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } if (captionEditText != null) { captionEditText.onDestroy(); } + requestVideoPreview(0); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeChats); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FilePreparingFailed); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileNewChunkAvailable); super.onFragmentDestroy(); } @Override public View createView(Context context) { - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - needCompressVideo = preferences.getBoolean("compress_video", true); - actionBar.setBackgroundColor(Theme.ACTION_BAR_VIDEO_EDIT_COLOR); - actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR); + actionBar.setTitleColor(0xffffffff); + actionBar.setItemsBackgroundColor(Theme.ACTION_BAR_PICKER_SELECTOR_COLOR, false); actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); actionBar.setSubtitleColor(0xffffffff); @@ -267,7 +480,7 @@ public View createView(Context context) { @Override public void onItemClick(int id) { if (id == -1) { - if (captionEditText.isPopupShowing() || captionEditText.isKeyboardVisible()) { + if (pickerView.getVisibility() != View.VISIBLE) { closeCaptionEnter(false); return; } @@ -278,10 +491,6 @@ public void onItemClick(int id) { } }); - ActionBarMenu menu = actionBar.createMenu(); - captionDoneItem = menu.addItemWithWidth(1, R.drawable.ic_done, AndroidUtilities.dp(56)); - captionDoneItem.setVisibility(View.GONE); - fragmentView = new SizeNotifierFrameLayoutPhoto(context) { int lastWidth; @@ -302,7 +511,11 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); - heightSize = AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight(); + if (!AndroidUtilities.isTablet()) { + heightSize = AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight(); + } else { + heightSize = AndroidUtilities.dp(424); + } measureChildWithMargins(captionEditText, widthMeasureSpec, 0, heightMeasureSpec, 0); int inputFieldHeight = captionEditText.getMeasuredHeight(); @@ -314,9 +527,9 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { continue; } if (captionEditText.isPopupView(child)) { - if (AndroidUtilities.isInMultiwindow) { + if (AndroidUtilities.isInMultiwindow || AndroidUtilities.isTablet()) { if (AndroidUtilities.isTablet()) { - child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(320), heightSize - inputFieldHeight - AndroidUtilities.statusBarHeight), MeasureSpec.EXACTLY)); + child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(320), MeasureSpec.getSize(heightMeasureSpec) - inputFieldHeight), MeasureSpec.EXACTLY)); } else { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize - inputFieldHeight - AndroidUtilities.statusBarHeight, MeasureSpec.EXACTLY)); } @@ -354,9 +567,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); - int paddingBottom = getKeyboardHeight() <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow ? captionEditText.getEmojiPadding() : 0; + int paddingBottom = getKeyboardHeight() <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isTablet() ? captionEditText.getEmojiPadding() : 0; - int heightSize = AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight(); + int heightSize; + if (!AndroidUtilities.isTablet()) { + heightSize = AndroidUtilities.displaySize.y - ActionBar.getCurrentActionBarHeight(); + } else { + heightSize = AndroidUtilities.dp(424); + } for (int i = 0; i < count; i++) { final View child = getChildAt(i); @@ -407,29 +625,31 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { if (child == mentionListView) { childTop = ((b - paddingBottom) - t) - height - lp.bottomMargin; - if (!captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { + if (pickerView.getVisibility() == VISIBLE || firstCaptionLayout && !captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { childTop += AndroidUtilities.dp(400); } else { childTop -= captionEditText.getMeasuredHeight(); } } else if (child == captionEditText) { childTop = ((b - paddingBottom) - t) - height - lp.bottomMargin; - if (!captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { - childTop += AndroidUtilities.dp(400); - } - } else if (child == pickerView) { - if (captionEditText.isPopupShowing() || captionEditText.isKeyboardVisible()) { + if (pickerView.getVisibility() == VISIBLE || firstCaptionLayout && !captionEditText.isPopupShowing() && !captionEditText.isKeyboardVisible() && captionEditText.getEmojiPadding() == 0) { childTop += AndroidUtilities.dp(400); + } else { + firstCaptionLayout = false; } } else if (captionEditText.isPopupView(child)) { - if (AndroidUtilities.isInMultiwindow) { + if (AndroidUtilities.isInMultiwindow || AndroidUtilities.isTablet()) { childTop = captionEditText.getTop() - child.getMeasuredHeight() + AndroidUtilities.dp(1); } else { childTop = captionEditText.getBottom(); } } else if (child == textureView) { childLeft = (r - l - textureView.getMeasuredWidth()) / 2; - childTop = AndroidUtilities.dp(14); + if (AndroidUtilities.isTablet()) { + childTop = (heightSize - AndroidUtilities.dp(14 + 152) - textureView.getMeasuredHeight()) / 2 + AndroidUtilities.dp(14); + } else { + childTop = (heightSize - AndroidUtilities.dp(14 + 152) - textureView.getMeasuredHeight()) / 2 + AndroidUtilities.dp(14); + } } child.layout(childLeft, childTop, childLeft + width, childTop + height); } @@ -440,6 +660,12 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) { fragmentView.setBackgroundColor(0xff000000); SizeNotifierFrameLayoutPhoto frameLayout = (SizeNotifierFrameLayoutPhoto) fragmentView; frameLayout.setWithoutWindow(true); + fragmentView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); pickerView = new PickerBottomLayoutViewer(context); pickerView.setBackgroundColor(0); @@ -461,14 +687,21 @@ public void onClick(View view) { videoPlayer.release(); videoPlayer = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } if (delegate != null) { - if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && !needCompressVideo) { + if (muteVideo) { + + } + if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && selectedCompression == compressionsCount - 1) { delegate.didFinishEditVideo(videoPath, startTime, endTime, originalWidth, originalHeight, rotationValue, originalWidth, originalHeight, muteVideo ? -1 : originalBitrate, estimatedSize, esimatedDuration, currentCaption != null ? currentCaption.toString() : null); } else { + if (muteVideo) { + selectedCompression = 1; + updateWidthHeightBitrateForCompression(); + } delegate.didFinishEditVideo(videoPath, startTime, endTime, resultWidth, resultHeight, rotationValue, originalWidth, originalHeight, muteVideo ? -1 : bitrate, estimatedSize, esimatedDuration, currentCaption != null ? currentCaption.toString() : null); } } @@ -483,45 +716,34 @@ public void onClick(View view) { captionItem = new ImageView(context); captionItem.setScaleType(ImageView.ScaleType.CENTER); captionItem.setImageResource(TextUtils.isEmpty(currentCaption) ? R.drawable.photo_text : R.drawable.photo_text2); - captionItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + captionItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); itemsLayout.addView(captionItem, LayoutHelper.createLinear(56, 48)); captionItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { captionEditText.setFieldText(currentCaption); - captionDoneItem.setVisibility(View.VISIBLE); - videoSeekBarView.setVisibility(View.GONE); - videoTimelineView.setVisibility(View.GONE); pickerView.setVisibility(View.GONE); - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) captionEditText.getLayoutParams(); - layoutParams.bottomMargin = 0; - captionEditText.setLayoutParams(layoutParams); - layoutParams = (FrameLayout.LayoutParams) mentionListView.getLayoutParams(); - layoutParams.bottomMargin = 0; - mentionListView.setLayoutParams(layoutParams); + firstCaptionLayout = true; + if (!AndroidUtilities.isTablet()) { + videoSeekBarView.setVisibility(View.GONE); + videoTimelineView.setVisibility(View.GONE); + } captionEditText.openKeyboard(); - oldTitle = actionBar.getSubtitle(); - actionBar.setTitle(LocaleController.getString("VideoCaption", R.string.VideoCaption)); + actionBar.setTitle(muteVideo ? LocaleController.getString("GifCaption", R.string.GifCaption) : LocaleController.getString("VideoCaption", R.string.VideoCaption)); actionBar.setSubtitle(null); } }); compressItem = new ImageView(context); compressItem.setScaleType(ImageView.ScaleType.CENTER); - compressItem.setImageResource(needCompressVideo ? R.drawable.hd_off : R.drawable.hd_on); - compressItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); - compressItem.setVisibility(originalHeight != resultHeight || originalWidth != resultWidth ? View.VISIBLE : View.GONE); + compressItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + compressItem.setVisibility(compressionsCount > 1 ? View.VISIBLE : View.GONE); itemsLayout.addView(compressItem, LayoutHelper.createLinear(56, 48)); compressItem.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - needCompressVideo = !needCompressVideo; - SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); - SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean("compress_video", needCompressVideo); - editor.commit(); - compressItem.setImageResource(needCompressVideo ? R.drawable.hd_off : R.drawable.hd_on); - updateVideoInfo(); + showQualityView(true); + requestVideoPreview(1); } }); if (Build.VERSION.SDK_INT < 18) { @@ -547,13 +769,14 @@ public void onClick(View v) { } } catch (Exception e) { compressItem.setVisibility(View.GONE); - FileLog.e("tmessages", e); + FileLog.e(e); } } muteItem = new ImageView(context); muteItem.setScaleType(ImageView.ScaleType.CENTER); - muteItem.setBackgroundDrawable(Theme.createBarSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); + muteItem.setBackgroundDrawable(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR)); +// muteItem.setVisibility(videoDuration >= 30000 ? View.GONE : View.VISIBLE); itemsLayout.addView(muteItem, LayoutHelper.createLinear(56, 48)); muteItem.setOnClickListener(new View.OnClickListener() { @Override @@ -575,11 +798,16 @@ public void onLeftProgressChanged(float progress) { if (videoPlayer.isPlaying()) { videoPlayer.pause(); playButton.setImageResource(R.drawable.video_edit_play); + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } } videoPlayer.setOnSeekCompleteListener(null); videoPlayer.seekTo((int) (videoDuration * progress)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } needSeek = true; videoSeekBarView.setProgress(videoTimelineView.getLeftProgress()); @@ -595,11 +823,16 @@ public void onRifhtProgressChanged(float progress) { if (videoPlayer.isPlaying()) { videoPlayer.pause(); playButton.setImageResource(R.drawable.video_edit_play); + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } } videoPlayer.setOnSeekCompleteListener(null); videoPlayer.seekTo((int) (videoDuration * progress)); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } needSeek = true; videoSeekBarView.setProgress(videoTimelineView.getLeftProgress()); @@ -622,16 +855,11 @@ public void onSeekBarDrag(float progress) { if (videoPlayer == null || !playerPrepared) { return; } - if (videoPlayer.isPlaying()) { - try { - videoPlayer.seekTo((int) (videoDuration * progress)); - lastProgress = progress; - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } else { + try { + videoPlayer.seekTo((int) (videoDuration * progress)); lastProgress = progress; - needSeek = true; + } catch (Exception e) { + FileLog.e(e); } } }); @@ -648,10 +876,14 @@ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int hei Surface s = new Surface(textureView.getSurfaceTexture()); videoPlayer.setSurface(s); if (playerPrepared) { - videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + if (inPreview) { + videoPlayer.seekTo(0); + } else { + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -676,18 +908,29 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { }); frameLayout.addView(textureView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 14, 0, 140)); + progressView = new RadialProgressView(context); + progressView.setProgressColor(0xffffffff); + progressView.setBackgroundResource(R.drawable.circle_big); + progressView.setVisibility(View.INVISIBLE); + frameLayout.addView(progressView, LayoutHelper.createFrame(54, 54, Gravity.CENTER, 0, 0, 0, 70)); + playButton = new ImageView(context); playButton.setScaleType(ImageView.ScaleType.CENTER); playButton.setImageResource(R.drawable.video_edit_play); playButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - if (videoPlayer == null || !playerPrepared) { + if (videoPlayer == null || !playerPrepared || requestingPreview || loadInitialVideo) { return; } if (videoPlayer.isPlaying()) { videoPlayer.pause(); playButton.setImageResource(R.drawable.video_edit_play); + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } } else { try { playButton.setImageDrawable(null); @@ -699,18 +942,40 @@ public void onClick(View v) { videoPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() { @Override public void onSeekComplete(MediaPlayer mp) { - float startTime = videoTimelineView.getLeftProgress() * videoDuration; - float endTime = videoTimelineView.getRightProgress() * videoDuration; - if (startTime == endTime) { - startTime = endTime - 0.01f; + if (inPreview) { + float startTime = 0; + float endTime = 1.0f; + lastProgress = (videoPlayer.getCurrentPosition() - startTime) / (endTime - startTime); + } else { + float startTime = videoTimelineView.getLeftProgress() * videoDuration; + float endTime = videoTimelineView.getRightProgress() * videoDuration; + if (startTime == endTime) { + startTime = endTime - 0.01f; + } + lastProgress = (videoPlayer.getCurrentPosition() - startTime) / (endTime - startTime); + float lrdiff = videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress(); + lastProgress = videoTimelineView.getLeftProgress() + lrdiff * lastProgress; + videoSeekBarView.setProgress(lastProgress); } - lastProgress = (videoPlayer.getCurrentPosition() - startTime) / (endTime - startTime); - float lrdiff = videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress(); - lastProgress = videoTimelineView.getLeftProgress() + lrdiff * lastProgress; - videoSeekBarView.setProgress(lastProgress); + } + }); + videoPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + try { + getParentActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } + onPlayComplete(); } }); videoPlayer.start(); + try { + getParentActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } catch (Exception e) { + FileLog.e(e); + } synchronized (sync) { if (thread == null) { thread = new Thread(progressRunnable); @@ -718,7 +983,7 @@ public void onSeekComplete(MediaPlayer mp) { } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } } @@ -729,7 +994,13 @@ public void onSeekComplete(MediaPlayer mp) { captionEditText.onDestroy(); } captionEditText = new PhotoViewerCaptionEnterView(context, frameLayout, null); + captionEditText.setForceFloatingEmoji(AndroidUtilities.isTablet()); captionEditText.setDelegate(new PhotoViewerCaptionEnterView.PhotoViewerCaptionEnterViewDelegate() { + + private int previousSize; + private int[] location = new int[2]; + private int previousY; + @Override public void onCaptionEnter() { closeCaptionEnter(true); @@ -756,10 +1027,15 @@ public void onWindowSizeChanged(int size) { mentionListView.setVisibility(View.VISIBLE); } } - fragmentView.requestLayout(); + fragmentView.getLocationInWindow(location); + if (previousSize != size || previousY != location[1]) { + fragmentView.requestLayout(); + previousSize = size; + previousY = location[1]; + } } }); - frameLayout.addView(captionEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, -400)); + frameLayout.addView(captionEditText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT)); captionEditText.onCreate(); mentionListView = new RecyclerListView(context); @@ -775,7 +1051,7 @@ public boolean supportsPredictiveItemAnimations() { mentionListView.setBackgroundColor(0x7f000000); mentionListView.setVisibility(View.GONE); mentionListView.setClipToPadding(true); - mentionListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER); + mentionListView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); frameLayout.addView(mentionListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 110, Gravity.LEFT | Gravity.BOTTOM)); mentionListView.setAdapter(mentionsAdapter = new MentionsAdapter(context, true, 0, new MentionsAdapter.MentionsAdapterDelegate() { @@ -805,7 +1081,7 @@ public void needChangePanelVisibility(boolean show) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionListView, "alpha", 0.0f, 1.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -833,7 +1109,7 @@ public void onAnimationEnd(Animator animation) { mentionListAnimation.playTogether( ObjectAnimator.ofFloat(mentionListView, "alpha", 0.0f) ); - mentionListAnimation.addListener(new AnimatorListenerAdapterProxy() { + mentionListAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mentionListAnimation != null && mentionListAnimation.equals(animation)) { @@ -904,20 +1180,119 @@ public void onClick(DialogInterface dialogInterface, int i) { } }); + if (compressionsCount > 1) { + qualityPicker = new PickerBottomLayoutViewer(context); + qualityPicker.setBackgroundColor(0); + qualityPicker.updateSelectedCount(0, false); + qualityPicker.setTranslationY(AndroidUtilities.dp(120)); + qualityPicker.doneButton.setText(LocaleController.getString("Done", R.string.Done).toUpperCase()); + frameLayout.addView(qualityPicker, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); + qualityPicker.cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + selectedCompression = previousCompression; + didChangedCompressionLevel(false); + showQualityView(false); + requestVideoPreview(2); + } + }); + qualityPicker.doneButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + showQualityView(false); + requestVideoPreview(2); + } + }); + + qualityChooseView = new QualityChooseView(context); + qualityChooseView.setTranslationY(AndroidUtilities.dp(120)); + qualityChooseView.setVisibility(View.INVISIBLE); + frameLayout.addView(qualityChooseView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 90, Gravity.LEFT | Gravity.BOTTOM, 0, 0, 0, 44)); + } + updateVideoInfo(); updateMuteButton(); return fragmentView; } + private void didChangedCompressionLevel(boolean request) { + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt("compress_video2", selectedCompression); + editor.commit(); + updateWidthHeightBitrateForCompression(); + updateVideoInfo(); + if (request) { + requestVideoPreview(1); + } + } + @Override public void onPause() { super.onPause(); - if (captionDoneItem.getVisibility() != View.GONE) { + if (pickerView.getVisibility() == View.GONE) { closeCaptionEnter(true); } } + @Override + public void onResume() { + super.onResume(); + if (textureView != null) { + try { + if (playerPrepared && !videoPlayer.isPlaying()) { + videoPlayer.seekTo((int) (videoSeekBarView.getProgress() * videoDuration)); + } + } catch (Exception e) { + FileLog.e(e); + } + } + } + + private void showQualityView(final boolean show) { + if (show) { + previousCompression = selectedCompression; + } + AnimatorSet animatorSet = new AnimatorSet(); + if (show) { + animatorSet.playTogether( + ObjectAnimator.ofFloat(pickerView, "translationY", 0, AndroidUtilities.dp(152)), + ObjectAnimator.ofFloat(videoTimelineView, "translationY", 0, AndroidUtilities.dp(152)), + ObjectAnimator.ofFloat(videoSeekBarView, "translationY", 0, AndroidUtilities.dp(152))); + } else { + animatorSet.playTogether( + ObjectAnimator.ofFloat(qualityChooseView, "translationY", 0, AndroidUtilities.dp(120)), + ObjectAnimator.ofFloat(qualityPicker, "translationY", 0, AndroidUtilities.dp(120))); + } + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + AnimatorSet animatorSet = new AnimatorSet(); + if (show) { + qualityChooseView.setVisibility(View.VISIBLE); + qualityPicker.setVisibility(View.VISIBLE); + animatorSet.playTogether( + ObjectAnimator.ofFloat(qualityChooseView, "translationY", 0), + ObjectAnimator.ofFloat(qualityPicker, "translationY", 0)); + } else { + qualityChooseView.setVisibility(View.INVISIBLE); + qualityPicker.setVisibility(View.INVISIBLE); + animatorSet.playTogether( + ObjectAnimator.ofFloat(pickerView, "translationY", 0), + ObjectAnimator.ofFloat(videoTimelineView, "translationY", 0), + ObjectAnimator.ofFloat(videoSeekBarView, "translationY", 0)); + } + animatorSet.setDuration(200); + animatorSet.setInterpolator(new AccelerateInterpolator()); + animatorSet.start(); + } + }); + animatorSet.setDuration(200); + animatorSet.setInterpolator(new DecelerateInterpolator()); + animatorSet.start(); + } + public void setParentChatActivity(ChatActivity chatActivity) { parentChatActivity = chatActivity; } @@ -926,26 +1301,83 @@ private void closeCaptionEnter(boolean apply) { if (apply) { currentCaption = captionEditText.getFieldCharSequence(); } - actionBar.setSubtitle(oldTitle); - captionDoneItem.setVisibility(View.GONE); pickerView.setVisibility(View.VISIBLE); - videoSeekBarView.setVisibility(View.VISIBLE); - videoTimelineView.setVisibility(View.VISIBLE); - - FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) captionEditText.getLayoutParams(); - layoutParams.bottomMargin = -AndroidUtilities.dp(400); - captionEditText.setLayoutParams(layoutParams); - - layoutParams = (FrameLayout.LayoutParams) mentionListView.getLayoutParams(); - layoutParams.bottomMargin = -AndroidUtilities.dp(400); - mentionListView.setLayoutParams(layoutParams); + if (!AndroidUtilities.isTablet()) { + videoSeekBarView.setVisibility(View.VISIBLE); + videoTimelineView.setVisibility(View.VISIBLE); + } actionBar.setTitle(muteVideo ? LocaleController.getString("AttachGif", R.string.AttachGif) : LocaleController.getString("AttachVideo", R.string.AttachVideo)); + actionBar.setSubtitle(muteVideo ? null : currentSubtitle); captionItem.setImageResource(TextUtils.isEmpty(currentCaption) ? R.drawable.photo_text : R.drawable.photo_text2); if (captionEditText.isPopupShowing()) { captionEditText.hidePopup(); + } + captionEditText.closeKeyboard(); + } + + private void requestVideoPreview(int request) { + if (videoPreviewMessageObject != null) { + MediaController.getInstance().cancelVideoConvert(videoPreviewMessageObject); + } + boolean wasRequestingPreview = requestingPreview && !tryStartRequestPreviewOnFinish; + requestingPreview = false; + loadInitialVideo = false; + progressView.setVisibility(View.INVISIBLE); + if (request == 1) { + if (selectedCompression == compressionsCount - 1) { + tryStartRequestPreviewOnFinish = false; + if (!wasRequestingPreview) { + reinitPlayer(videoPath); + } else { + playButton.setImageDrawable(null); + progressView.setVisibility(View.VISIBLE); + loadInitialVideo = true; + } + } else { + destroyPlayer(); + if (videoPreviewMessageObject == null) { + TLRPC.TL_message message = new TLRPC.TL_message(); + message.id = 0; + message.message = ""; + message.media = new TLRPC.TL_messageMediaEmpty(); + message.action = new TLRPC.TL_messageActionEmpty(); + videoPreviewMessageObject = new MessageObject(message, null, false); + videoPreviewMessageObject.messageOwner.attachPath = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), "video_preview.mp4").getAbsolutePath(); + videoPreviewMessageObject.videoEditedInfo = new VideoEditedInfo(); + videoPreviewMessageObject.videoEditedInfo.rotationValue = rotationValue; + videoPreviewMessageObject.videoEditedInfo.originalWidth = originalWidth; + videoPreviewMessageObject.videoEditedInfo.originalHeight = originalHeight; + videoPreviewMessageObject.videoEditedInfo.originalPath = videoPath; + } + long start = videoPreviewMessageObject.videoEditedInfo.startTime = startTime; + long end = videoPreviewMessageObject.videoEditedInfo.endTime = endTime; + if (start == -1) { + start = 0; + } + if (end == -1) { + end = (long) (videoDuration * 1000); + } + if (end - start > 5000000) { + videoPreviewMessageObject.videoEditedInfo.endTime = start + 5000000; + } + videoPreviewMessageObject.videoEditedInfo.bitrate = bitrate; + videoPreviewMessageObject.videoEditedInfo.resultWidth = resultWidth; + videoPreviewMessageObject.videoEditedInfo.resultHeight = resultHeight; + if (!MediaController.getInstance().scheduleVideoConvert(videoPreviewMessageObject, true)) { + tryStartRequestPreviewOnFinish = true; + } + if (videoPlayer == null) { + requestingPreview = true; + playButton.setImageDrawable(null); + progressView.setVisibility(View.VISIBLE); + } + } } else { - captionEditText.closeKeyboard(); + tryStartRequestPreviewOnFinish = false; + if (request == 2) { + reinitPlayer(videoPath); + } } } @@ -953,6 +1385,31 @@ private void closeCaptionEnter(boolean apply) { public void didReceivedNotification(int id, Object... args) { if (id == NotificationCenter.closeChats) { removeSelfFromStack(); + } else if (id == NotificationCenter.FilePreparingFailed) { + MessageObject messageObject = (MessageObject) args[0]; + if (loadInitialVideo) { + loadInitialVideo = false; + progressView.setVisibility(View.INVISIBLE); + reinitPlayer(videoPath); + } else if (tryStartRequestPreviewOnFinish) { + destroyPlayer(); + tryStartRequestPreviewOnFinish = !MediaController.getInstance().scheduleVideoConvert(videoPreviewMessageObject, true); + } else if (messageObject == videoPreviewMessageObject) { + requestingPreview = false; + progressView.setVisibility(View.INVISIBLE); + playButton.setImageResource(R.drawable.video_edit_play); + } + } else if (id == NotificationCenter.FileNewChunkAvailable) { + MessageObject messageObject = (MessageObject) args[0]; + if (messageObject == videoPreviewMessageObject) { + String finalPath = (String) args[1]; + long finalSize = (Long) args[2]; + if (finalSize != 0) { + requestingPreview = false; + progressView.setVisibility(View.INVISIBLE); + reinitPlayer(finalPath); + } + } } } @@ -965,22 +1422,24 @@ public void updateMuteButton() { } if (muteVideo) { actionBar.setTitle(LocaleController.getString("AttachGif", R.string.AttachGif)); + actionBar.setSubtitle(null); muteItem.setImageResource(R.drawable.volume_off); - if (captionItem.getVisibility() == View.VISIBLE) { - needCompressVideo = true; - compressItem.setImageResource(R.drawable.hd_off); + if (compressItem.getVisibility() == View.VISIBLE) { compressItem.setClickable(false); - compressItem.setAlpha(0.8f); + compressItem.setAlpha(0.5f); compressItem.setEnabled(false); } + videoTimelineView.setMaxProgressDiff(30000.0f / videoDuration); } else { actionBar.setTitle(LocaleController.getString("AttachVideo", R.string.AttachVideo)); + actionBar.setSubtitle(currentSubtitle); muteItem.setImageResource(R.drawable.volume_on); - if (captionItem.getVisibility() == View.VISIBLE) { + if (compressItem.getVisibility() == View.VISIBLE) { compressItem.setClickable(true); compressItem.setAlpha(1.0f); compressItem.setEnabled(true); } + videoTimelineView.setMaxProgressDiff(1.0f); } } @@ -989,16 +1448,24 @@ private void onPlayComplete() { playButton.setImageResource(R.drawable.video_edit_play); } if (videoSeekBarView != null && videoTimelineView != null) { - videoSeekBarView.setProgress(videoTimelineView.getLeftProgress()); + if (inPreview) { + videoSeekBarView.setProgress(0); + } else { + videoSeekBarView.setProgress(videoTimelineView.getLeftProgress()); + } } try { if (videoPlayer != null) { if (videoTimelineView != null) { - videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + if (inPreview) { + videoPlayer.seekTo(0); + } else { + videoPlayer.seekTo((int) (videoTimelineView.getLeftProgress() * videoDuration)); + } } } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -1006,12 +1473,25 @@ private void updateVideoInfo() { if (actionBar == null) { return; } + + if (selectedCompression == 0) { + compressItem.setImageResource(R.drawable.video_240); + } else if (selectedCompression == 1) { + compressItem.setImageResource(R.drawable.video_360); + } else if (selectedCompression == 2) { + compressItem.setImageResource(R.drawable.video_480); + } else if (selectedCompression == 3) { + compressItem.setImageResource(R.drawable.video_720); + } else if (selectedCompression == 4) { + compressItem.setImageResource(R.drawable.video_1080); + } + esimatedDuration = (long) Math.ceil((videoTimelineView.getRightProgress() - videoTimelineView.getLeftProgress()) * videoDuration); int width; int height; - if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && !needCompressVideo) { + if (compressItem.getVisibility() == View.GONE || compressItem.getVisibility() == View.VISIBLE && selectedCompression == compressionsCount - 1) { width = rotationValue == 90 || rotationValue == 270 ? originalHeight : originalWidth; height = rotationValue == 90 || rotationValue == 270 ? originalWidth : originalHeight; estimatedSize = (int) (originalSize * ((float) esimatedDuration / videoDuration)); @@ -1038,13 +1518,63 @@ private void updateVideoInfo() { int minutes = (int) (esimatedDuration / 1000 / 60); int seconds = (int) Math.ceil(esimatedDuration / 1000) - minutes * 60; String videoTimeSize = String.format("%d:%02d, ~%s", minutes, seconds, AndroidUtilities.formatFileSize(estimatedSize)); - actionBar.setSubtitle(String.format("%s, %s", videoDimension, videoTimeSize)); + currentSubtitle = String.format("%s, %s", videoDimension, videoTimeSize); + actionBar.setSubtitle(muteVideo ? null : currentSubtitle); } public void setDelegate(VideoEditorActivityDelegate videoEditorActivityDelegate) { delegate = videoEditorActivityDelegate; } + private void updateWidthHeightBitrateForCompression() { + if (compressionsCount == -1) { + if (originalWidth > 1280 || originalHeight > 1280) { + compressionsCount = 5; + } else if (originalWidth > 848 || originalHeight > 848) { + compressionsCount = 4; + } else if (originalWidth > 640 || originalHeight > 640) { + compressionsCount = 3; + } else if (originalWidth > 480 || originalHeight > 480) { + compressionsCount = 2; + } else { + compressionsCount = 1; + } + } + if (selectedCompression >= compressionsCount) { + selectedCompression = compressionsCount - 1; + } + if (selectedCompression != compressionsCount - 1) { + float maxSize; + int targetBitrate; + switch (selectedCompression) { + case 0: + maxSize = 432.0f; + targetBitrate = 400000; + break; + case 1: + maxSize = 640.0f; + targetBitrate = 900000; + break; + case 2: + maxSize = 848.0f; + targetBitrate = 1100000; + break; + case 3: + default: + targetBitrate = 1600000; + maxSize = 1280.0f; + break; + } + float scale = originalWidth > originalHeight ? maxSize / originalWidth : maxSize / originalHeight; + resultWidth = Math.round(originalWidth * scale / 2) * 2; + resultHeight = Math.round(originalHeight * scale / 2) * 2; + if (bitrate != 0) { + bitrate = Math.min(targetBitrate, (int) (originalBitrate / scale)); + videoFramesSize = (long) (bitrate / 8 * videoDuration / 1000); + } + } + } + private boolean processOpenVideo() { try { File file = new File(videoPath); @@ -1070,7 +1600,8 @@ private boolean processOpenVideo() { isAvc = false; } - for (Box box : boxes) { + for (int b = 0; b < boxes.size(); b++) { + Box box = boxes.get(b); TrackBox trackBox = (TrackBox) box; long sampleSizes = 0; long trackBitrate = 0; @@ -1078,13 +1609,14 @@ private boolean processOpenVideo() { MediaBox mediaBox = trackBox.getMediaBox(); MediaHeaderBox mediaHeaderBox = mediaBox.getMediaHeaderBox(); SampleSizeBox sampleSizeBox = mediaBox.getMediaInformationBox().getSampleTableBox().getSampleSizeBox(); - for (long size : sampleSizeBox.getSampleSizes()) { - sampleSizes += size; + long[] sizes = sampleSizeBox.getSampleSizes(); + for (int a = 0; a < sizes.length; a++) { + sampleSizes += sizes[a]; } videoDuration = (float) mediaHeaderBox.getDuration() / (float) mediaHeaderBox.getTimescale(); trackBitrate = (int) (sampleSizes * 8 / videoDuration); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } TrackHeaderBox headerBox = trackBox.getTrackHeaderBox(); if (headerBox.getWidth() != 0 && headerBox.getHeight() != 0) { @@ -1113,25 +1645,20 @@ private boolean processOpenVideo() { resultWidth = originalWidth = (int) trackHeaderBox.getWidth(); resultHeight = originalHeight = (int) trackHeaderBox.getHeight(); - if (resultWidth > 640 || resultHeight > 640) { - float scale = resultWidth > resultHeight ? 640.0f / resultWidth : 640.0f / resultHeight; - resultWidth *= scale; - resultHeight *= scale; - if (bitrate != 0) { - bitrate *= Math.max(0.5f, scale); - videoFramesSize = (long) (bitrate / 8 * videoDuration); - } - } + videoDuration *= 1000; + + SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); + selectedCompression = preferences.getInt("compress_video2", 1); + updateWidthHeightBitrateForCompression(); if (!isAvc && (resultWidth == originalWidth || resultHeight == originalHeight)) { return false; } } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); return false; } - videoDuration *= 1000; updateVideoInfo(); return true; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java new file mode 100755 index 00000000000..c155dede57c --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/VoIPActivity.java @@ -0,0 +1,1430 @@ +/* + * This is the source code of Telegram for Android v. 3.x.x. + * It is licensed under GNU GPL v. 2 or later. + * You should have received a copy of the license in this archive (see LICENSE). + * + * Copyright Grishka, 2013-2016. + */ + +package org.telegram.ui; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.media.AudioManager; +import android.os.Build; +import android.os.Bundle; +import android.support.v7.graphics.Palette; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.CharacterStyle; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ApplicationLoader; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.ContactsController; +import org.telegram.messenger.Emoji; +import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.voip.EncryptionKeyEmojifier; +import org.telegram.messenger.voip.VoIPController; +import org.telegram.messenger.voip.VoIPService; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.CorrectlyMeasuringTextView; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.IdenticonDrawable; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.voip.CallSwipeView; +import org.telegram.ui.Components.voip.CheckableImageView; +import org.telegram.ui.Components.voip.FabBackgroundDrawable; +import org.telegram.ui.Components.voip.VoIPHelper; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; + +public class VoIPActivity extends Activity implements VoIPService.StateListener, NotificationCenter.NotificationCenterDelegate{ + + private static final String TAG = "tg-voip-ui"; + private TextView stateText, nameText, stateText2; + private TextView durationText; + private View endBtn, acceptBtn, declineBtn, endBtnIcon, cancelBtn; + private CheckableImageView spkToggle, micToggle; + private ImageView chatBtn; + private FabBackgroundDrawable endBtnBg; + private CallSwipeView acceptSwipe, declineSwipe; + private LinearLayout swipeViewsWrap; + private BackupImageView photoView; + private boolean isIncomingWaiting; + private boolean firstStateChange = true; + private Animator currentDeclineAnim, currentAcceptAnim, textChangingAnim; + private TLRPC.User user; + private boolean didAcceptFromHere = false; + private int callState; + private TextAlphaSpan[] ellSpans; + private AnimatorSet ellAnimator; + private String lastStateText; + private ImageView[] keyEmojiViews=new ImageView[4]; + private boolean keyEmojiVisible; + private AnimatorSet emojiAnimator; + private TextView hintTextView; + private Animator tooltipAnim; + private Runnable tooltipHider; + private LinearLayout emojiWrap; + boolean emojiTooltipVisible; + boolean emojiExpanded; + private Bitmap blurredPhoto1, blurredPhoto2; + private ImageView blurOverlayView1, blurOverlayView2; + private TextView emojiExpandedText; + private FrameLayout content; + private boolean retrying; + private AnimatorSet retryAnim; + + private int audioBitrate = 25; + private int packetLossPercent = 5; + + @Override + protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + super.onCreate(savedInstanceState); + + if (VoIPService.getSharedInstance() == null) { + finish(); + return; + } + + if ((getResources().getConfiguration().screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) < Configuration.SCREENLAYOUT_SIZE_LARGE) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + View contentView; + setContentView(contentView = createContentView()); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + //getWindow().setStatusBarColor(0x88000000); + getWindow().setStatusBarColor(0xFF000000); + //getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + + user = VoIPService.getSharedInstance().getUser(); + if (user.photo != null) { + photoView.getImageReceiver().setDelegate(new ImageReceiver.ImageReceiverDelegate(){ + @Override + public void didSetImage(ImageReceiver imageReceiver, boolean set, boolean thumb){ + Bitmap bmp=imageReceiver.getBitmap(); + if(bmp!=null){ + updateBlurredPhotos(bmp); + } + } + }); + photoView.setImage(user.photo.photo_big, null, new ColorDrawable(0xFF000000)); + } else { + photoView.setVisibility(View.GONE); + contentView.setBackgroundDrawable(new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int[]{0xFF1b354e, 0xFF255b7d})); + } + + setVolumeControlStream(AudioManager.STREAM_VOICE_CALL); + + nameText.setOnClickListener(new View.OnClickListener() { + private int tapCount=0; + @Override + public void onClick(View v) { + if(BuildVars.DEBUG_VERSION || tapCount==9){ + showDebugAlert(); + tapCount=0; + }else{ + tapCount++; + } + } + }); + /*nameText.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + showDebugCtlAlert(); + return true; + } + });*/ + + endBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + endBtn.setEnabled(false); + if(retrying){ + Intent intent = new Intent(VoIPActivity.this, VoIPService.class); + intent.putExtra("user_id", user.id); + intent.putExtra("is_outgoing", true); + intent.putExtra("start_incall_activity", false); + startService(intent); + hideRetry(); + endBtn.postDelayed(new Runnable(){ + @Override + public void run(){ + if(VoIPService.getSharedInstance()==null && !isFinishing()){ + endBtn.postDelayed(this, 100); + return; + } + if(VoIPService.getSharedInstance()!=null) + VoIPService.getSharedInstance().registerStateListener(VoIPActivity.this); + } + }, 100); + return; + } + if (VoIPService.getSharedInstance() != null) + VoIPService.getSharedInstance().hangUp(); + } + }); + + spkToggle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + VoIPService svc=VoIPService.getSharedInstance(); + if (svc == null) + return; + if(svc.isBluetoothHeadsetConnected() && svc.hasEarpiece()){ + BottomSheet.Builder bldr=new BottomSheet.Builder(VoIPActivity.this) + .setItems(new CharSequence[]{LocaleController.getString("VoipAudioRoutingBluetooth", R.string.VoipAudioRoutingBluetooth), + LocaleController.getString("VoipAudioRoutingEarpiece", R.string.VoipAudioRoutingEarpiece), + LocaleController.getString("VoipAudioRoutingSpeaker", R.string.VoipAudioRoutingSpeaker)}, + new int[]{R.drawable.ic_bluetooth_white_24dp, + R.drawable.ic_phone_in_talk_white_24dp, + R.drawable.ic_volume_up_white_24dp}, new DialogInterface.OnClickListener(){ + @Override + public void onClick(DialogInterface dialog, int which){ + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + if(VoIPService.getSharedInstance()==null) + return; + switch(which){ + case 0: + am.setBluetoothScoOn(true); + am.setSpeakerphoneOn(false); + break; + case 1: + am.setBluetoothScoOn(false); + am.setSpeakerphoneOn(false); + break; + case 2: + am.setBluetoothScoOn(false); + am.setSpeakerphoneOn(true); + break; + } + onAudioSettingsChanged(); + } + }); + bldr.show(); + return; + } + boolean checked = !spkToggle.isChecked(); + spkToggle.setChecked(checked); + AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE); + if(svc.hasEarpiece()){ + am.setSpeakerphoneOn(checked); + }else{ + am.setBluetoothScoOn(checked); + } + } + }); + micToggle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (VoIPService.getSharedInstance() == null) { + finish(); + return; + } + boolean checked = !micToggle.isChecked(); + micToggle.setChecked(checked); + VoIPService.getSharedInstance().setMicMute(checked); + } + }); + chatBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(ApplicationLoader.applicationContext, LaunchActivity.class); + intent.setAction("com.tmessages.openchat" + Math.random() + Integer.MAX_VALUE); + intent.setFlags(32768); + intent.putExtra("userId", user.id); + startActivity(intent); + finish(); + } + }); + + spkToggle.setChecked(((AudioManager) getSystemService(AUDIO_SERVICE)).isSpeakerphoneOn()); + micToggle.setChecked(VoIPService.getSharedInstance().isMicMute()); + + onAudioSettingsChanged(); + + nameText.setText(ContactsController.formatName(user.first_name, user.last_name)); + + VoIPService.getSharedInstance().registerStateListener(this); + + acceptSwipe.setListener(new CallSwipeView.Listener() { + @Override + public void onDragComplete() { + acceptSwipe.setEnabled(false); + declineSwipe.setEnabled(false); + if(VoIPService.getSharedInstance()==null){ + finish(); + return; + } + didAcceptFromHere = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 101); + } else { + VoIPService.getSharedInstance().acceptIncomingCall(); + callAccepted(); + } + } + + @Override + public void onDragStart() { + if (currentDeclineAnim != null) { + currentDeclineAnim.cancel(); + } + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(declineSwipe, "alpha", .2f), + ObjectAnimator.ofFloat(declineBtn, "alpha", .2f) + ); + set.setDuration(200); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentDeclineAnim = null; + } + }); + currentDeclineAnim = set; + set.start(); + declineSwipe.stopAnimatingArrows(); + } + + @Override + public void onDragCancel() { + if (currentDeclineAnim != null) { + currentDeclineAnim.cancel(); + } + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(declineSwipe, "alpha", 1), + ObjectAnimator.ofFloat(declineBtn, "alpha", 1) + ); + set.setDuration(200); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentDeclineAnim = null; + } + }); + currentDeclineAnim = set; + set.start(); + declineSwipe.startAnimatingArrows(); + } + }); + declineSwipe.setListener(new CallSwipeView.Listener() { + @Override + public void onDragComplete() { + acceptSwipe.setEnabled(false); + declineSwipe.setEnabled(false); + if(VoIPService.getSharedInstance()!=null) + VoIPService.getSharedInstance().declineIncomingCall(VoIPService.DISCARD_REASON_LINE_BUSY, null); + else + finish(); + } + + @Override + public void onDragStart() { + if (currentAcceptAnim != null) { + currentAcceptAnim.cancel(); + } + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(acceptSwipe, "alpha", .2f), + ObjectAnimator.ofFloat(acceptBtn, "alpha", .2f) + ); + set.setDuration(200); + set.setInterpolator(new DecelerateInterpolator()); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentAcceptAnim = null; + } + }); + currentAcceptAnim = set; + set.start(); + acceptSwipe.stopAnimatingArrows(); + } + + @Override + public void onDragCancel() { + if (currentAcceptAnim != null) { + currentAcceptAnim.cancel(); + } + AnimatorSet set = new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(acceptSwipe, "alpha", 1), + ObjectAnimator.ofFloat(acceptBtn, "alpha", 1) + ); + set.setDuration(200); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + currentAcceptAnim = null; + } + }); + currentAcceptAnim = set; + set.start(); + acceptSwipe.startAnimatingArrows(); + } + }); + cancelBtn.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v){ + finish(); + } + }); + getWindow().getDecorView().setKeepScreenOn(true); + + NotificationCenter.getInstance().addObserver(this, NotificationCenter.emojiDidLoaded); + NotificationCenter.getInstance().addObserver(this, NotificationCenter.closeInCallActivity); + hintTextView.setText(LocaleController.formatString("CallEmojiKeyTooltip", R.string.CallEmojiKeyTooltip, user.first_name)); + emojiExpandedText.setText(LocaleController.formatString("CallEmojiKeyTooltip", R.string.CallEmojiKeyTooltip, user.first_name)); + } + + private View createContentView(){ + FrameLayout content=new FrameLayout(this); + content.setBackgroundColor(0); + + BackupImageView photo=new BackupImageView(this){ + private Drawable topGradient=getResources().getDrawable(R.drawable.gradient_top); + private Drawable bottomGradient=getResources().getDrawable(R.drawable.gradient_bottom); + private Paint paint=new Paint(); + + @Override + protected void onDraw(Canvas canvas){ + super.onDraw(canvas); + paint.setColor(0x4C000000); + canvas.drawRect(0, 0, getWidth(), getHeight(), paint); + topGradient.setBounds(0, 0, getWidth(), AndroidUtilities.dp(170)); + topGradient.setAlpha(128); + topGradient.draw(canvas); + bottomGradient.setBounds(0, getHeight()-AndroidUtilities.dp(220), getWidth(), getHeight()); + bottomGradient.setAlpha(178); + bottomGradient.draw(canvas); + } + }; + content.addView(photoView=photo); + blurOverlayView1=new ImageView(this); + blurOverlayView1.setScaleType(ImageView.ScaleType.CENTER_CROP); + blurOverlayView1.setAlpha(0f); + content.addView(blurOverlayView1); + blurOverlayView2=new ImageView(this); + blurOverlayView2.setScaleType(ImageView.ScaleType.CENTER_CROP); + blurOverlayView2.setAlpha(0f); + content.addView(blurOverlayView2); + + TextView branding=new TextView(this); + branding.setTextColor(0xCCFFFFFF); + branding.setText(LocaleController.getString("VoipInCallBranding", R.string.VoipInCallBranding)); + Drawable logo=getResources().getDrawable(R.drawable.notification).mutate(); + logo.setAlpha(0xCC); + logo.setBounds(0, 0, AndroidUtilities.dp(15), AndroidUtilities.dp(15)); + branding.setCompoundDrawables(LocaleController.isRTL ? null : logo, null, LocaleController.isRTL ? logo : null, null); + branding.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + branding.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + branding.setCompoundDrawablePadding(AndroidUtilities.dp(5)); + branding.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + content.addView(branding, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT, 18, 18, 18, 0)); + + TextView name=new TextView(this); + name.setSingleLine(); + name.setTextColor(0xFFFFFFFF); + name.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40); + name.setEllipsize(TextUtils.TruncateAt.END); + name.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + name.setShadowLayer(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(.666666667f), 0x4C000000); + name.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL)); + content.addView(nameText=name, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT, 18, 43, 18, 0)); + + TextView state=new TextView(this); + state.setTextColor(0xCCFFFFFF); + state.setSingleLine(); + state.setEllipsize(TextUtils.TruncateAt.END); + state.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + state.setShadowLayer(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(.666666667f), 0x4C000000); + state.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + state.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + //state.setAllCaps(true); + content.addView(stateText=state, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT, 18, 98, 18, 0)); + durationText=state; + + state=new TextView(this); + state.setTextColor(0xCCFFFFFF); + state.setSingleLine(); + state.setEllipsize(TextUtils.TruncateAt.END); + state.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf")); + state.setShadowLayer(AndroidUtilities.dp(3), 0, AndroidUtilities.dp(.666666667f), 0x4C000000); + state.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + state.setGravity(LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT); + //state.setAllCaps(true); + state.setVisibility(View.GONE); + content.addView(stateText2=state, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.LEFT, 18, 98, 18, 0)); + + ellSpans=new TextAlphaSpan[]{new TextAlphaSpan(), new TextAlphaSpan(), new TextAlphaSpan()}; + + CheckableImageView mic=new CheckableImageView(this); + mic.setBackgroundResource(R.drawable.bg_voip_icon_btn); + Drawable micIcon=getResources().getDrawable(R.drawable.ic_mic_off_white_24dp).mutate(); + micIcon.setAlpha(204); + mic.setImageDrawable(micIcon); + mic.setScaleType(ImageView.ScaleType.CENTER); + content.addView(micToggle=mic, LayoutHelper.createFrame(38, 38, Gravity.BOTTOM|Gravity.LEFT, 16, 0, 0, 10)); + + CheckableImageView speaker=new CheckableImageView(this); + speaker.setBackgroundResource(R.drawable.bg_voip_icon_btn); + Drawable speakerIcon=getResources().getDrawable(R.drawable.ic_volume_up_white_24dp).mutate(); + speakerIcon.setAlpha(204); + speaker.setImageDrawable(speakerIcon); + speaker.setScaleType(ImageView.ScaleType.CENTER); + content.addView(spkToggle=speaker, LayoutHelper.createFrame(38, 38, Gravity.BOTTOM|Gravity.RIGHT, 0, 0, 16, 10)); + + ImageView chat=new ImageView(this); + Drawable chatIcon=getResources().getDrawable(R.drawable.ic_chat_bubble_white_24dp).mutate(); + chatIcon.setAlpha(204); + chat.setImageDrawable(chatIcon); + chat.setScaleType(ImageView.ScaleType.CENTER); + content.addView(chatBtn=chat, LayoutHelper.createFrame(38, 38, Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0, 0, 10)); + + LinearLayout swipesWrap=new LinearLayout(this); + swipesWrap.setOrientation(LinearLayout.HORIZONTAL); + + CallSwipeView acceptSwipe=new CallSwipeView(this); + acceptSwipe.setColor(0xFF45bc4d); + swipesWrap.addView(this.acceptSwipe=acceptSwipe, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 70, 1f, 4, 4, -35, 4)); + + CallSwipeView declineSwipe=new CallSwipeView(this); + declineSwipe.setColor(0xFFe61e44); + swipesWrap.addView(this.declineSwipe=declineSwipe, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 70, 1f, -35, 4, 4, 4)); + + content.addView(swipeViewsWrap=swipesWrap, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 20, 0, 20, 68)); + + ImageView acceptBtn=new ImageView(this); + FabBackgroundDrawable acceptBtnBg=new FabBackgroundDrawable(); + acceptBtnBg.setColor(0xFF45bc4d); + acceptBtn.setBackgroundDrawable(acceptBtnBg); + acceptBtn.setImageResource(R.drawable.ic_call_end_white_36dp); + acceptBtn.setScaleType(ImageView.ScaleType.MATRIX); + Matrix matrix=new Matrix(); + matrix.setTranslate(AndroidUtilities.dp(17), AndroidUtilities.dp(17)); + matrix.postRotate(-135, AndroidUtilities.dp(35), AndroidUtilities.dp(35)); + acceptBtn.setImageMatrix(matrix); + content.addView(this.acceptBtn=acceptBtn, LayoutHelper.createFrame(78, 78, Gravity.BOTTOM|Gravity.LEFT, 20, 0, 0, 68)); + + ImageView declineBtn=new ImageView(this); + FabBackgroundDrawable rejectBtnBg=new FabBackgroundDrawable(); + rejectBtnBg.setColor(0xFFe61e44); + declineBtn.setBackgroundDrawable(rejectBtnBg); + declineBtn.setImageResource(R.drawable.ic_call_end_white_36dp); + declineBtn.setScaleType(ImageView.ScaleType.CENTER); + content.addView(this.declineBtn=declineBtn, LayoutHelper.createFrame(78, 78, Gravity.BOTTOM|Gravity.RIGHT, 0, 0, 20, 68)); + + acceptSwipe.setViewToDrag(acceptBtn, false); + declineSwipe.setViewToDrag(declineBtn, true); + + FrameLayout end=new FrameLayout(this); + FabBackgroundDrawable endBtnBg=new FabBackgroundDrawable(); + endBtnBg.setColor(0xFFe61e44); + end.setBackgroundDrawable(this.endBtnBg=endBtnBg); + ImageView endInner=new ImageView(this); + endInner.setImageResource(R.drawable.ic_call_end_white_36dp); + endInner.setScaleType(ImageView.ScaleType.CENTER); + end.addView(endBtnIcon=endInner, LayoutHelper.createFrame(70, 70)); + end.setForeground(getResources().getDrawable(R.drawable.fab_highlight_dark)); + content.addView(endBtn=end, LayoutHelper.createFrame(78, 78, Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL, 0, 0, 0, 68)); + + ImageView cancelBtn=new ImageView(this); + FabBackgroundDrawable cancelBtnBg=new FabBackgroundDrawable(); + cancelBtnBg.setColor(0xFFFFFFFF); + cancelBtn.setBackgroundDrawable(cancelBtnBg); + cancelBtn.setImageResource(R.drawable.edit_cancel); + cancelBtn.setColorFilter(0x89000000); + cancelBtn.setScaleType(ImageView.ScaleType.CENTER); + cancelBtn.setVisibility(View.GONE); + content.addView(this.cancelBtn=cancelBtn, LayoutHelper.createFrame(78, 78, Gravity.BOTTOM|Gravity.LEFT, 52, 0, 0, 68)); + + + emojiWrap=new LinearLayout(this); + emojiWrap.setOrientation(LinearLayout.HORIZONTAL); + emojiWrap.setClipToPadding(false); + emojiWrap.setPivotX(0); + emojiWrap.setPivotY(0); + emojiWrap.setPadding(AndroidUtilities.dp(14), AndroidUtilities.dp(10), AndroidUtilities.dp(14), AndroidUtilities.dp(10)); + for(int i=0;i<4;i++){ + ImageView emoji=new ImageView(this); + emoji.setScaleType(ImageView.ScaleType.FIT_XY); + emojiWrap.addView(emoji, LayoutHelper.createLinear(22, 22, i==0 ? 0 : 4, 0, 0, 0)); + keyEmojiViews[i]=emoji; + } + emojiWrap.setOnClickListener(new View.OnClickListener(){ + @Override + public void onClick(View v){ + if(emojiTooltipVisible){ + setEmojiTooltipVisible(false); + if(tooltipHider!=null){ + hintTextView.removeCallbacks(tooltipHider); + tooltipHider=null; + } + } + setEmojiExpanded(!emojiExpanded); + } + }); + //keyEmojiText=new TextView(this); + //keyEmojiText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + content.addView(emojiWrap, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP|Gravity.RIGHT)); + emojiWrap.setOnLongClickListener(new View.OnLongClickListener(){ + @Override + public boolean onLongClick(View v){ + if(emojiExpanded) + return false; + if(tooltipHider!=null){ + hintTextView.removeCallbacks(tooltipHider); + tooltipHider=null; + } + setEmojiTooltipVisible(!emojiTooltipVisible); + if(emojiTooltipVisible){ + hintTextView.postDelayed(tooltipHider=new Runnable(){ + @Override + public void run(){ + tooltipHider=null; + setEmojiTooltipVisible(false); + } + }, 5000); + } + return true; + } + }); + emojiExpandedText=new TextView(this); + emojiExpandedText.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + emojiExpandedText.setTextColor(0xFFFFFFFF); + emojiExpandedText.setGravity(Gravity.CENTER); + emojiExpandedText.setAlpha(0); + content.addView(emojiExpandedText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER, 10, 32, 10, 0)); + + hintTextView = new CorrectlyMeasuringTextView(this); + hintTextView.setBackgroundDrawable(Theme.createRoundRectDrawable(AndroidUtilities.dp(3), 0xf2333333)); + hintTextView.setTextColor(Theme.getColor(Theme.key_chat_gifSaveHintText)); + hintTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + hintTextView.setPadding(AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10), AndroidUtilities.dp(10)); + hintTextView.setGravity(Gravity.CENTER); + hintTextView.setMaxWidth(AndroidUtilities.dp(300)); + hintTextView.setAlpha(0.0f); + content.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.RIGHT, 0, 42, 10, 0)); + + int ellMaxAlpha=stateText.getPaint().getAlpha(); + ellAnimator=new AnimatorSet(); + ellAnimator.playTogether( + createAlphaAnimator(ellSpans[0], 0, ellMaxAlpha, 0, 300), + createAlphaAnimator(ellSpans[1], 0, ellMaxAlpha, 150, 300), + createAlphaAnimator(ellSpans[2], 0, ellMaxAlpha, 300, 300), + createAlphaAnimator(ellSpans[0], ellMaxAlpha, 0, 1000, 400), + createAlphaAnimator(ellSpans[1], ellMaxAlpha, 0, 1000, 400), + createAlphaAnimator(ellSpans[2], ellMaxAlpha, 0, 1000, 400) + ); + ellAnimator.addListener(new AnimatorListenerAdapter(){ + private Runnable restarter=new Runnable(){ + @Override + public void run(){ + if(!isFinishing()) + ellAnimator.start(); + } + }; + @Override + public void onAnimationEnd(Animator animation){ + if(!isFinishing()){ + VoIPActivity.this.content.postDelayed(restarter, 300); + } + } + }); + content.setClipChildren(false); + this.content=content; + + return content; + } + + @SuppressLint("ObjectAnimatorBinding") + private ObjectAnimator createAlphaAnimator(Object target, int startVal, int endVal, int startDelay, int duration){ + ObjectAnimator a=ObjectAnimator.ofInt(target, "alpha", startVal, endVal); + a.setDuration(duration); + a.setStartDelay(startDelay); + a.setInterpolator(CubicBezierInterpolator.DEFAULT); + return a; + } + + @Override + protected void onDestroy() { + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.emojiDidLoaded); + NotificationCenter.getInstance().removeObserver(this, NotificationCenter.closeInCallActivity); + if (VoIPService.getSharedInstance() != null) { + VoIPService.getSharedInstance().unregisterStateListener(this); + } + super.onDestroy(); + } + + @Override + public void onBackPressed() { + if(emojiExpanded){ + setEmojiExpanded(false); + return; + } + if (!isIncomingWaiting) { + super.onBackPressed(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (VoIPService.getSharedInstance() != null) + VoIPService.getSharedInstance().onUIForegroundStateChanged(true); + } + + @Override + protected void onPause() { + super.onPause(); + if(retrying) + finish(); + if (VoIPService.getSharedInstance() != null) + VoIPService.getSharedInstance().onUIForegroundStateChanged(false); + } + + @TargetApi(Build.VERSION_CODES.M) + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == 101) { + if (grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + VoIPService.getSharedInstance().acceptIncomingCall(); + callAccepted(); + } else { + if(!shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)){ + VoIPService.getSharedInstance().declineIncomingCall(); + VoIPHelper.permissionDenied(this, new Runnable(){ + @Override + public void run(){ + finish(); + } + }); + return; + } + acceptSwipe.reset(); + } + } + + } + + private void updateKeyView() { + if(VoIPService.getSharedInstance()==null) + return; + IdenticonDrawable img = new IdenticonDrawable(); + img.setColors(new int[]{0x00FFFFFF, 0xFFFFFFFF, 0x99FFFFFF, 0x33FFFFFF}); + TLRPC.EncryptedChat encryptedChat = new TLRPC.EncryptedChat(); + try{ + ByteArrayOutputStream buf=new ByteArrayOutputStream(); + buf.write(VoIPService.getSharedInstance().getEncryptionKey()); + buf.write(VoIPService.getSharedInstance().getGA()); + encryptedChat.auth_key = buf.toByteArray(); + }catch(Exception checkedExceptionsAreBad){} + byte[] sha256 = Utilities.computeSHA256(encryptedChat.auth_key, 0, encryptedChat.auth_key.length); + String[] emoji=EncryptionKeyEmojifier.emojifyForCall(sha256); + //keyEmojiText.setText(Emoji.replaceEmoji(TextUtils.join(" ", emoji), keyEmojiText.getPaint().getFontMetricsInt(), AndroidUtilities.dp(32), false)); + for(int i=0;i<4;i++){ + Drawable drawable=Emoji.getEmojiDrawable(emoji[i]); + drawable.setBounds(0, 0, AndroidUtilities.dp(22), AndroidUtilities.dp(22)); + keyEmojiViews[i].setImageDrawable(drawable); + } + } + + private void showDebugAlert() { + if(VoIPService.getSharedInstance()==null) + return; + final AlertDialog dlg = new AlertDialog.Builder(this) + .setTitle("libtgvoip v" + VoIPController.getVersion() + " debug") + .setMessage(VoIPService.getSharedInstance().getDebugString()) + .setPositiveButton("Close", null) + .create(); + final Runnable r = new Runnable() { + @Override + public void run() { + if (!dlg.isShowing() || isFinishing() || VoIPService.getSharedInstance() == null) { + return; + } + dlg.setMessage(VoIPService.getSharedInstance().getDebugString()); + dlg.getWindow().getDecorView().postDelayed(this, 500); + } + }; + dlg.show(); + dlg.getWindow().getDecorView().postDelayed(r, 500); + } + + private void showDebugCtlAlert() { + new AlertDialog.Builder(this) + .setItems(new String[]{"Set audio bitrate", "Set expect packet loss %", "Disable p2p", "Enable p2p", "Disable AEC", "Enable AEC"}, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + showNumberPickerDialog(8, 32, audioBitrate, "Audio bitrate (kbit/s)", new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + audioBitrate = newVal; + VoIPService.getSharedInstance().debugCtl(1, newVal * 1000); + } + }); + break; + case 1: + showNumberPickerDialog(0, 100, packetLossPercent, "Expected packet loss %", new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + packetLossPercent = newVal; + VoIPService.getSharedInstance().debugCtl(2, newVal); + } + }); + break; + case 2: + VoIPService.getSharedInstance().debugCtl(3, 0); + break; + case 3: + VoIPService.getSharedInstance().debugCtl(3, 1); + break; + case 4: + VoIPService.getSharedInstance().debugCtl(4, 0); + break; + case 5: + VoIPService.getSharedInstance().debugCtl(4, 1); + break; + } + } + }) + .show(); + } + + private void showNumberPickerDialog(int min, int max, int value, String title, NumberPicker.OnValueChangeListener listener) { + NumberPicker picker = new NumberPicker(this); + picker.setMinValue(min); + picker.setMaxValue(max); + picker.setValue(value); + picker.setOnValueChangedListener(listener); + new AlertDialog.Builder(this) + .setTitle(title) + .setView(picker) + .setPositiveButton("Done", null) + .show(); + } + + private void startUpdatingCallDuration() { + Runnable r = new Runnable() { + @Override + public void run() { + if (isFinishing() || VoIPService.getSharedInstance() == null) { + return; + } + if(callState==VoIPService.STATE_ESTABLISHED){ + long duration=VoIPService.getSharedInstance().getCallDuration()/1000; + durationText.setText(duration>3600 ? String.format("%d:%02d:%02d", duration/3600, duration%3600/60, duration%60) : String.format("%d:%02d", duration/60, duration%60)); + durationText.postDelayed(this, 500); + } + } + }; + r.run(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (isIncomingWaiting && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) { + if(VoIPService.getSharedInstance()!=null) + VoIPService.getSharedInstance().stopRinging(); + else + finish(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + private void callAccepted() { + endBtn.setVisibility(View.VISIBLE); + micToggle.setVisibility(View.VISIBLE); + if(VoIPService.getSharedInstance().hasEarpiece()) + spkToggle.setVisibility(View.VISIBLE); + chatBtn.setVisibility(View.VISIBLE); + if (didAcceptFromHere) { + acceptBtn.setVisibility(View.GONE); + ObjectAnimator colorAnim; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAnim = ObjectAnimator.ofArgb(endBtnBg, "color", 0xFF45bc4d, 0xFFe61e44); + } else { + colorAnim = ObjectAnimator.ofInt(endBtnBg, "color", 0xFF45bc4d, 0xFFe61e44); + colorAnim.setEvaluator(new ArgbEvaluator()); + } + AnimatorSet set = new AnimatorSet(); + AnimatorSet decSet = new AnimatorSet(); + decSet.playTogether( + ObjectAnimator.ofFloat(micToggle, "alpha", 0, 1), + ObjectAnimator.ofFloat(spkToggle, "alpha", 0, 1), + ObjectAnimator.ofFloat(chatBtn, "alpha", 0, 1), + ObjectAnimator.ofFloat(endBtnIcon, "rotation", -135, 0), + colorAnim + ); + decSet.setInterpolator(CubicBezierInterpolator.EASE_OUT); + decSet.setDuration(500); + AnimatorSet accSet = new AnimatorSet(); + accSet.playTogether( + ObjectAnimator.ofFloat(swipeViewsWrap, "alpha", 1, 0), + ObjectAnimator.ofFloat(declineBtn, "alpha", 0) + ); + accSet.setInterpolator(CubicBezierInterpolator.EASE_IN); + accSet.setDuration(125); + set.playTogether( + decSet, + accSet + ); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + swipeViewsWrap.setVisibility(View.GONE); + declineBtn.setVisibility(View.GONE); + } + }); + set.start(); + } else { + AnimatorSet set = new AnimatorSet(); + AnimatorSet decSet = new AnimatorSet(); + decSet.playTogether( + ObjectAnimator.ofFloat(micToggle, "alpha", 0, 1), + ObjectAnimator.ofFloat(spkToggle, "alpha", 0, 1), + ObjectAnimator.ofFloat(chatBtn, "alpha", 0, 1) + ); + decSet.setInterpolator(CubicBezierInterpolator.EASE_OUT); + decSet.setDuration(500); + AnimatorSet accSet = new AnimatorSet(); + accSet.playTogether( + ObjectAnimator.ofFloat(swipeViewsWrap, "alpha", 1, 0), + ObjectAnimator.ofFloat(declineBtn, "alpha", 0), + ObjectAnimator.ofFloat(acceptBtn, "alpha", 0) + ); + accSet.setInterpolator(CubicBezierInterpolator.EASE_IN); + accSet.setDuration(125); + set.playTogether( + decSet, + accSet + ); + set.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + swipeViewsWrap.setVisibility(View.GONE); + declineBtn.setVisibility(View.GONE); + acceptBtn.setVisibility(View.GONE); + } + }); + set.start(); + } + } + + private void showRetry(){ + if(retryAnim!=null) + retryAnim.cancel(); + endBtn.setEnabled(false); + retrying=true; + cancelBtn.setVisibility(View.VISIBLE); + cancelBtn.setAlpha(0); + AnimatorSet set=new AnimatorSet(); + ObjectAnimator colorAnim; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAnim = ObjectAnimator.ofArgb(endBtnBg, "color", 0xFFe61e44, 0xFF45bc4d); + } else { + colorAnim = ObjectAnimator.ofInt(endBtnBg, "color", 0xFFe61e44, 0xFF45bc4d); + colorAnim.setEvaluator(new ArgbEvaluator()); + } + set.playTogether( + ObjectAnimator.ofFloat(cancelBtn, "alpha", 0, 1), + ObjectAnimator.ofFloat(endBtn, "translationX", 0, content.getWidth()/2-AndroidUtilities.dp(52)-endBtn.getWidth()/2), + colorAnim, + ObjectAnimator.ofFloat(endBtnIcon, "rotation", 0, -135), + ObjectAnimator.ofFloat(spkToggle, "alpha", 0), + ObjectAnimator.ofFloat(micToggle, "alpha", 0), + ObjectAnimator.ofFloat(chatBtn, "alpha", 0) + ); + set.setStartDelay(200); + set.setDuration(300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + spkToggle.setVisibility(View.GONE); + micToggle.setVisibility(View.GONE); + chatBtn.setVisibility(View.GONE); + retryAnim=null; + endBtn.setEnabled(true); + } + }); + retryAnim=set; + set.start(); + } + + private void hideRetry(){ + if(retryAnim!=null) + retryAnim.cancel(); + retrying=false; + spkToggle.setVisibility(View.VISIBLE); + micToggle.setVisibility(View.VISIBLE); + chatBtn.setVisibility(View.VISIBLE); + ObjectAnimator colorAnim; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAnim = ObjectAnimator.ofArgb(endBtnBg, "color", 0xFF45bc4d, 0xFFe61e44); + } else { + colorAnim = ObjectAnimator.ofInt(endBtnBg, "color", 0xFF45bc4d, 0xFFe61e44); + colorAnim.setEvaluator(new ArgbEvaluator()); + } + AnimatorSet set=new AnimatorSet(); + set.playTogether( + colorAnim, + ObjectAnimator.ofFloat(endBtnIcon, "rotation", -135, 0), + ObjectAnimator.ofFloat(endBtn, "translationX", 0), + ObjectAnimator.ofFloat(cancelBtn, "alpha", 0), + ObjectAnimator.ofFloat(spkToggle, "alpha", 1), + ObjectAnimator.ofFloat(micToggle, "alpha", 1), + ObjectAnimator.ofFloat(chatBtn, "alpha", 1) + ); + set.setStartDelay(200); + set.setDuration(300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + cancelBtn.setVisibility(View.GONE); + endBtn.setEnabled(true); + retryAnim=null; + } + }); + retryAnim=set; + set.start(); + } + + @Override + public void onStateChanged(final int state) { + callState=state; + runOnUiThread(new Runnable() { + @Override + public void run() { + boolean wasFirstStateChange=firstStateChange; + if (firstStateChange) { + if (isIncomingWaiting = state == VoIPService.STATE_WAITING_INCOMING) { + swipeViewsWrap.setVisibility(View.VISIBLE); + endBtn.setVisibility(View.GONE); + micToggle.setVisibility(View.GONE); + spkToggle.setVisibility(View.GONE); + chatBtn.setVisibility(View.GONE); + AndroidUtilities.runOnUIThread(new Runnable(){ + @Override + public void run(){ + acceptSwipe.startAnimatingArrows(); + declineSwipe.startAnimatingArrows(); + } + }, 500); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } else { + swipeViewsWrap.setVisibility(View.GONE); + acceptBtn.setVisibility(View.GONE); + declineBtn.setVisibility(View.GONE); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } + if (state != VoIPService.STATE_ESTABLISHED) + emojiWrap.setVisibility(View.GONE); + firstStateChange = false; + } + + if (isIncomingWaiting && state != VoIPService.STATE_WAITING_INCOMING && state!=VoIPService.STATE_ENDED && state!=VoIPService.STATE_HANGING_UP) { + isIncomingWaiting = false; + if (!didAcceptFromHere) + callAccepted(); + } + + if (state == VoIPService.STATE_WAITING_INCOMING) { + setStateTextAnimated(LocaleController.getString("VoipIncoming", R.string.VoipIncoming), false); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + } else if (state == VoIPService.STATE_WAIT_INIT || state == VoIPService.STATE_WAIT_INIT_ACK) { + setStateTextAnimated(LocaleController.getString("VoipConnecting", R.string.VoipConnecting), true); + } else if (state == VoIPService.STATE_EXCHANGING_KEYS) { + setStateTextAnimated(LocaleController.getString("VoipExchangingKeys", R.string.VoipExchangingKeys), true); + } else if (state == VoIPService.STATE_WAITING) { + setStateTextAnimated(LocaleController.getString("VoipWaiting", R.string.VoipWaiting), true); + } else if (state == VoIPService.STATE_RINGING) { + setStateTextAnimated(LocaleController.getString("VoipRinging", R.string.VoipRinging), true); + } else if (state == VoIPService.STATE_REQUESTING) { + setStateTextAnimated(LocaleController.getString("VoipRequesting", R.string.VoipRequesting), true); + } else if (state == VoIPService.STATE_HANGING_UP) { + setStateTextAnimated(LocaleController.getString("VoipHangingUp", R.string.VoipHangingUp), true); + } else if (state == VoIPService.STATE_ENDED) { + setStateTextAnimated(LocaleController.getString("VoipCallEnded", R.string.VoipCallEnded), false); + stateText.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 200); + } else if (state == VoIPService.STATE_BUSY) { + //endBtn.setEnabled(false); + setStateTextAnimated(LocaleController.getString("VoipBusy", R.string.VoipBusy), false); + /*stateText.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 2000);*/ + showRetry(); + } else if (state == VoIPService.STATE_ESTABLISHED) { + if(!wasFirstStateChange){ + int count=getSharedPreferences("mainconfig", MODE_PRIVATE).getInt("call_emoji_tooltip_count", 0); + if(count<3){ + setEmojiTooltipVisible(true); + hintTextView.postDelayed(tooltipHider=new Runnable(){ + @Override + public void run(){ + tooltipHider=null; + setEmojiTooltipVisible(false); + } + }, 5000); + getSharedPreferences("mainconfig", MODE_PRIVATE).edit().putInt("call_emoji_tooltip_count", count+1).apply(); + } + } + setStateTextAnimated("0:00", false); + startUpdatingCallDuration(); + updateKeyView(); + if (emojiWrap.getVisibility() != View.VISIBLE) { + emojiWrap.setVisibility(View.VISIBLE); + emojiWrap.setAlpha(0f); + emojiWrap.animate().alpha(1).setDuration(200).setInterpolator(new DecelerateInterpolator()).start(); + } + } else if (state == VoIPService.STATE_FAILED) { + setStateTextAnimated(LocaleController.getString("VoipFailed", R.string.VoipFailed), false); + int lastError=VoIPService.getSharedInstance()!=null ? VoIPService.getSharedInstance().getLastError() : VoIPController.ERROR_UNKNOWN; + if (lastError== VoIPController.ERROR_INCOMPATIBLE) { + showErrorDialog(AndroidUtilities.replaceTags(LocaleController.formatString("VoipPeerIncompatible", R.string.VoipPeerIncompatible, + ContactsController.formatName(user.first_name, user.last_name)))); + }else if (lastError== VoIPController.ERROR_PEER_OUTDATED) { + showErrorDialog(AndroidUtilities.replaceTags(LocaleController.formatString("VoipPeerOutdated", R.string.VoipPeerOutdated, + ContactsController.formatName(user.first_name, user.last_name)))); + }else if(lastError==VoIPController.ERROR_PRIVACY){ + showErrorDialog(AndroidUtilities.replaceTags(LocaleController.formatString("CallNotAvailable", R.string.CallNotAvailable, + ContactsController.formatName(user.first_name, user.last_name)))); + }else if(lastError==VoIPController.ERROR_AUDIO_IO){ + showErrorDialog("Error initializing audio hardware"); + }else if(lastError==VoIPController.ERROR_LOCALIZED){ + finish(); + } else { + stateText.postDelayed(new Runnable() { + @Override + public void run() { + finish(); + } + }, 1000); + } + } + } + }); + } + + private void showErrorDialog(CharSequence message){ + AlertDialog dlg = new AlertDialog.Builder(VoIPActivity.this) + .setTitle(LocaleController.getString("VoipFailed", R.string.VoipFailed)) + .setMessage(message) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), null) + .show(); + dlg.setCanceledOnTouchOutside(true); + dlg.setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } + }); + } + + @Override + public void onAudioSettingsChanged() { + if(VoIPService.getSharedInstance()==null) + return; + micToggle.setChecked(VoIPService.getSharedInstance().isMicMute()); + if(!VoIPService.getSharedInstance().hasEarpiece() && !VoIPService.getSharedInstance().isBluetoothHeadsetConnected()){ + spkToggle.setVisibility(View.INVISIBLE); + }else{ + spkToggle.setVisibility(View.VISIBLE); + AudioManager am=(AudioManager)getSystemService(AUDIO_SERVICE); + if(!VoIPService.getSharedInstance().hasEarpiece()){ + spkToggle.setImageResource(R.drawable.ic_bluetooth_white_24dp); + spkToggle.setChecked(am.isBluetoothScoOn()); + }else if(VoIPService.getSharedInstance().isBluetoothHeadsetConnected()){ + if(am.isBluetoothScoOn()){ + spkToggle.setImageResource(R.drawable.ic_bluetooth_white_24dp); + }else if(am.isSpeakerphoneOn()){ + spkToggle.setImageResource(R.drawable.ic_volume_up_white_24dp); + }else{ + spkToggle.setImageResource(R.drawable.ic_phone_in_talk_white_24dp); + } + spkToggle.setChecked(false); + }else{ + spkToggle.setImageResource(R.drawable.ic_volume_up_white_24dp); + spkToggle.setChecked(am.isSpeakerphoneOn()); + } + } + } + + private void setStateTextAnimated(String _newText, boolean ellipsis){ + if(_newText.equals(lastStateText)) + return; + lastStateText=_newText; + if(textChangingAnim!=null) + textChangingAnim.cancel(); + CharSequence newText; + if(ellipsis){ + if(!ellAnimator.isRunning()) + ellAnimator.start(); + SpannableStringBuilder ssb=new SpannableStringBuilder(_newText.toUpperCase()); + for(TextAlphaSpan s:ellSpans) + s.setAlpha(0); + SpannableString ell=new SpannableString("..."); + ell.setSpan(ellSpans[0], 0, 1, 0); + ell.setSpan(ellSpans[1], 1, 2, 0); + ell.setSpan(ellSpans[2], 2, 3, 0); + ssb.append(ell); + newText=ssb; + }else{ + if(ellAnimator.isRunning()) + ellAnimator.cancel(); + newText=_newText.toUpperCase(); + } + stateText2.setText(newText); + stateText2.setVisibility(View.VISIBLE); + stateText.setPivotX(LocaleController.isRTL ? stateText.getWidth() : 0); + stateText.setPivotY(stateText.getHeight()/2); + stateText2.setPivotX(LocaleController.isRTL ? stateText.getWidth() : 0); + stateText2.setPivotY(stateText.getHeight()/2); + durationText=stateText2; + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(stateText2, "alpha", 0, 1), + ObjectAnimator.ofFloat(stateText2, "translationY", stateText.getHeight()/2, 0), + ObjectAnimator.ofFloat(stateText2, "scaleX", 0.7f, 1), + ObjectAnimator.ofFloat(stateText2, "scaleY", 0.7f, 1), + ObjectAnimator.ofFloat(stateText, "alpha", 1, 0), + ObjectAnimator.ofFloat(stateText, "translationY", 0, -stateText.getHeight()/2), + ObjectAnimator.ofFloat(stateText, "scaleX", 1, 0.7f), + ObjectAnimator.ofFloat(stateText, "scaleY", 1, 0.7f) + ); + set.setDuration(200); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + textChangingAnim=null; + stateText2.setVisibility(View.GONE); + durationText=stateText; + stateText.setTranslationY(0); + stateText.setScaleX(1); + stateText.setScaleY(1); + stateText.setAlpha(1); + stateText.setText(stateText2.getText()); + } + }); + textChangingAnim=set; + set.start(); + } + + @Override + public void didReceivedNotification(int id, Object... args){ + if(id==NotificationCenter.emojiDidLoaded){ + for(ImageView iv:keyEmojiViews){ + iv.invalidate(); + } + } + if(id==NotificationCenter.closeInCallActivity){ + finish(); + } + } + + private void setEmojiTooltipVisible(boolean visible){ + emojiTooltipVisible=visible; + if(tooltipAnim!=null) + tooltipAnim.cancel(); + hintTextView.setVisibility(View.VISIBLE); + ObjectAnimator oa=ObjectAnimator.ofFloat(hintTextView, "alpha", visible ? 1 : 0); + oa.setDuration(300); + oa.setInterpolator(CubicBezierInterpolator.DEFAULT); + oa.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + tooltipAnim=null; + } + }); + tooltipAnim=oa; + oa.start(); + } + + private void setEmojiExpanded(boolean expanded){ + if(emojiExpanded==expanded) + return; + emojiExpanded=expanded; + if(emojiAnimator!=null) + emojiAnimator.cancel(); + if(expanded){ + int[] loc={0, 0}, loc2={0, 0}; + emojiWrap.getLocationInWindow(loc); + emojiExpandedText.getLocationInWindow(loc2); + Rect rect=new Rect(); + getWindow().getDecorView().getGlobalVisibleRect(rect); + int offsetY=loc2[1]-(loc[1]+emojiWrap.getHeight())-AndroidUtilities.dp(32)-emojiWrap.getHeight(); + int firstOffsetX=(rect.width()/2-Math.round(emojiWrap.getWidth()*2.5f)/2)-loc[0]; + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(emojiWrap, "translationY", offsetY), + ObjectAnimator.ofFloat(emojiWrap, "translationX", firstOffsetX), + ObjectAnimator.ofFloat(emojiWrap, "scaleX", 2.5f), + ObjectAnimator.ofFloat(emojiWrap, "scaleY", 2.5f), + ObjectAnimator.ofFloat(blurOverlayView1, "alpha", blurOverlayView1.getAlpha(), 1, 1), + ObjectAnimator.ofFloat(blurOverlayView2, "alpha", blurOverlayView2.getAlpha(), blurOverlayView2.getAlpha(), 1), + ObjectAnimator.ofFloat(emojiExpandedText, "alpha", 1) + ); + set.setDuration(300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + emojiAnimator=set; + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + emojiAnimator=null; + } + }); + set.start(); + }else{ + AnimatorSet set=new AnimatorSet(); + set.playTogether( + ObjectAnimator.ofFloat(emojiWrap, "translationX", 0), + ObjectAnimator.ofFloat(emojiWrap, "translationY", 0), + ObjectAnimator.ofFloat(emojiWrap, "scaleX", 1), + ObjectAnimator.ofFloat(emojiWrap, "scaleY", 1), + ObjectAnimator.ofFloat(blurOverlayView1, "alpha", blurOverlayView1.getAlpha(), blurOverlayView1.getAlpha(), 0), + ObjectAnimator.ofFloat(blurOverlayView2, "alpha", blurOverlayView2.getAlpha(), 0, 0), + ObjectAnimator.ofFloat(emojiExpandedText, "alpha", 0) + ); + set.setDuration(300); + set.setInterpolator(CubicBezierInterpolator.DEFAULT); + emojiAnimator=set; + set.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation){ + emojiAnimator=null; + } + }); + set.start(); + } + } + + private void updateBlurredPhotos(final Bitmap src){ + new Thread(new Runnable(){ + @Override + public void run(){ + Bitmap blur1=Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888); + Canvas canvas=new Canvas(blur1); + canvas.drawBitmap(src, null, new Rect(0, 0, 150, 150), new Paint(Paint.FILTER_BITMAP_FLAG)); + Utilities.blurBitmap(blur1, 3, 0, blur1.getWidth(), blur1.getHeight(), blur1.getRowBytes()); + final Palette palette=Palette.from(src).generate(); + Paint paint=new Paint(); + paint.setColor((palette.getDarkMutedColor(0xFF547499) & 0x00FFFFFF) | 0x44000000); + canvas.drawColor(0x26000000); + canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint); + Bitmap blur2=Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888); + canvas=new Canvas(blur2); + canvas.drawBitmap(src, null, new Rect(0, 0, 50, 50), new Paint(Paint.FILTER_BITMAP_FLAG)); + Utilities.blurBitmap(blur2, 3, 0, blur2.getWidth(), blur2.getHeight(), blur2.getRowBytes()); + paint.setAlpha(0x66); + canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint); + blurredPhoto1=blur1; + blurredPhoto2=blur2; + runOnUiThread(new Runnable(){ + @Override + public void run(){ + blurOverlayView1.setImageBitmap(blurredPhoto1); + blurOverlayView2.setImageBitmap(blurredPhoto2); + } + }); + } + }).start(); + } + + private class TextAlphaSpan extends CharacterStyle{ + private int alpha; + + public TextAlphaSpan(){ + this.alpha=0; + } + + public int getAlpha(){ + return alpha; + } + + public void setAlpha(int alpha){ + this.alpha=alpha; + stateText.invalidate(); + stateText2.invalidate(); + } + + @Override + public void updateDrawState(TextPaint tp){ + tp.setAlpha(alpha); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/VoIPFeedbackActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/VoIPFeedbackActivity.java new file mode 100644 index 00000000000..c3b6a0b7a83 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/VoIPFeedbackActivity.java @@ -0,0 +1,123 @@ +package org.telegram.ui; + +import android.app.Activity; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.InputType; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.R; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.RequestDelegate; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.BetterRatingView; +import org.telegram.ui.Components.LayoutHelper; + +public class VoIPFeedbackActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + super.onCreate(savedInstanceState); + + overridePendingTransition(0, 0); + + setContentView(new View(this)); + + LinearLayout ll = new LinearLayout(this); + ll.setOrientation(LinearLayout.VERTICAL); + int pad = AndroidUtilities.dp(16); + ll.setPadding(pad, pad, pad, pad); + + TextView text = new TextView(this); + text.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + text.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + text.setGravity(Gravity.CENTER); + text.setText(LocaleController.getString("VoipRateCallAlert", R.string.VoipRateCallAlert)); + ll.addView(text); + + final BetterRatingView bar = new BetterRatingView(this); + ll.addView(bar, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 16, 0, 0)); + + final EditText commentBox = new EditText(this); + commentBox.setHint(LocaleController.getString("VoipFeedbackCommentHint", R.string.VoipFeedbackCommentHint)); + commentBox.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + commentBox.setVisibility(View.GONE); + commentBox.setTextColor(Theme.getColor(Theme.key_dialogTextBlack)); + commentBox.setHintTextColor(Theme.getColor(Theme.key_dialogTextHint)); + commentBox.setBackgroundDrawable(Theme.createEditTextDrawable(this, true)); + ll.addView(commentBox, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 16, 0, 0)); + + AlertDialog alert = new AlertDialog.Builder(this) + .setTitle(LocaleController.getString("AppName", R.string.AppName)) + .setView(ll) + .setPositiveButton(LocaleController.getString("OK", R.string.OK), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + TLRPC.TL_phone_setCallRating req = new TLRPC.TL_phone_setCallRating(); + req.rating = bar.getRating(); + if (req.rating < 5) + req.comment = commentBox.getText().toString(); + else + req.comment=""; + req.peer = new TLRPC.TL_inputPhoneCall(); + req.peer.access_hash = getIntent().getLongExtra("call_access_hash", 0); + req.peer.id = getIntent().getLongExtra("call_id", 0); + ConnectionsManager.getInstance().sendRequest(req, new RequestDelegate() { + @Override + public void run(TLObject response, TLRPC.TL_error error) { + if (response instanceof TLRPC.TL_updates) { + TLRPC.TL_updates updates = (TLRPC.TL_updates) response; + MessagesController.getInstance().processUpdates(updates, false); + } + } + }); + finish(); + } + }) + .setNegativeButton(LocaleController.getString("Cancel", R.string.Cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } + }) + .show(); + alert.setCanceledOnTouchOutside(true); + alert.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + final View btn = alert.getButton(DialogInterface.BUTTON_POSITIVE); + btn.setEnabled(false); + bar.setOnRatingChangeListener(new BetterRatingView.OnRatingChangeListener() { + @Override + public void onRatingChanged(int rating) { + btn.setEnabled(rating > 0); + commentBox.setVisibility(rating < 5 && rating > 0 ? View.VISIBLE : View.GONE); + if (commentBox.getVisibility() == View.GONE) { + ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(commentBox.getWindowToken(), 0); + } + } + }); + } + + @Override + public void finish() { + super.finish(); + overridePendingTransition(0, 0); + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/VoIPPermissionActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/VoIPPermissionActivity.java new file mode 100644 index 00000000000..1c1b9f32776 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/VoIPPermissionActivity.java @@ -0,0 +1,49 @@ +package org.telegram.ui; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; + +import org.telegram.messenger.voip.VoIPService; +import org.telegram.ui.Components.voip.VoIPHelper; + +/** + * Created by grishka on 22.11.16. + */ + +public class VoIPPermissionActivity extends Activity{ + @Override + protected void onCreate(Bundle savedInstanceState){ + super.onCreate(savedInstanceState); + + requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, 101); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults){ + if(requestCode==101){ + if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ + if(VoIPService.getSharedInstance()!=null) + VoIPService.getSharedInstance().acceptIncomingCall(); + finish(); + startActivity(new Intent(this, VoIPActivity.class)); + }else{ + if(!shouldShowRequestPermissionRationale(Manifest.permission.RECORD_AUDIO)){ + if(VoIPService.getSharedInstance()!=null) + VoIPService.getSharedInstance().declineIncomingCall(); + VoIPHelper.permissionDenied(this, new Runnable(){ + @Override + public void run(){ + finish(); + } + }); + return; + }else{ + finish(); + } + } + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java index 6a0c4a5d9b0..c90880c93b1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WallpapersActivity.java @@ -3,38 +3,29 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.app.Activity; -import android.app.AlertDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; -import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.Build; import android.os.Bundle; -import android.provider.MediaStore; -import android.support.v4.content.FileProvider; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.ProgressBar; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.BuildConfig; -import org.telegram.messenger.ImageLoader; import org.telegram.messenger.LocaleController; import org.telegram.messenger.support.widget.LinearLayoutManager; import org.telegram.messenger.support.widget.RecyclerView; @@ -51,13 +42,16 @@ import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Cells.WallpaperCell; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.WallpaperUpdater; import java.io.File; -import java.io.FileOutputStream; import java.util.ArrayList; import java.util.HashMap; @@ -67,15 +61,22 @@ public class WallpapersActivity extends BaseFragment implements NotificationCent private ImageView backgroundImage; private FrameLayout progressView; private View progressViewBackground; + private View doneButton; + private RadialProgressView progressBar; + private RecyclerListView listView; + private WallpaperUpdater updater; + private File wallpaperFile; + private Drawable themedWallpaper; + private int selectedBackground; + private boolean overrideThemeWallpaper; private int selectedColor; private ArrayList wallPapers = new ArrayList<>(); private HashMap wallpappersByIds = new HashMap<>(); - private View doneButton; + private String loadingFile = null; private File loadingFileObject = null; private TLRPC.PhotoSize loadingSize = null; - private String currentPicturePath; private final static int done_button = 1; @@ -89,16 +90,16 @@ public boolean onFragmentCreate() { SharedPreferences preferences = ApplicationLoader.applicationContext.getSharedPreferences("mainconfig", Activity.MODE_PRIVATE); selectedBackground = preferences.getInt("selectedBackground", 1000001); + overrideThemeWallpaper = preferences.getBoolean("overrideThemeWallpaper", false); selectedColor = preferences.getInt("selectedColor", 0); MessagesStorage.getInstance().getWallpapers(); - File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper-temp.jpg"); - toFile.delete(); return true; } @Override public void onFragmentDestroy() { super.onFragmentDestroy(); + updater.cleanup(); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidFailedLoad); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.FileDidLoaded); NotificationCenter.getInstance().removeObserver(this, NotificationCenter.wallpapersDidLoaded); @@ -106,6 +107,24 @@ public void onFragmentDestroy() { @Override public View createView(Context context) { + themedWallpaper = Theme.getThemedWallpaper(true); + updater = new WallpaperUpdater(getParentActivity(), new WallpaperUpdater.WallpaperUpdaterDelegate() { + @Override + public void didSelectWallpaper(File file, Bitmap bitmap) { + selectedBackground = -1; + overrideThemeWallpaper = true; + selectedColor = 0; + wallpaperFile = file; + Drawable drawable = backgroundImage.getDrawable(); + backgroundImage.setImageBitmap(bitmap); + } + + @Override + public void needOpenColorPicker() { + + } + }); + actionBar.setBackButtonImage(R.drawable.ic_ab_back); actionBar.setAllowOverlayTitle(true); actionBar.setTitle(LocaleController.getString("ChatBackground", R.string.ChatBackground)); @@ -133,13 +152,18 @@ public void onItemClick(int id) { done = AndroidUtilities.copyFile(f, toFile); } catch (Exception e) { done = false; - FileLog.e("tmessages", e); + FileLog.e(e); } } else { if (selectedBackground == -1) { - File fromFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper-temp.jpg"); + File fromFile = updater.getCurrentWallpaperPath(); File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper.jpg"); - done = fromFile.renameTo(toFile); + try { + done = AndroidUtilities.copyFile(fromFile, toFile); + } catch (Exception e) { + done = false; + FileLog.e(e); + } } else { done = true; } @@ -150,8 +174,9 @@ public void onItemClick(int id) { SharedPreferences.Editor editor = preferences.edit(); editor.putInt("selectedBackground", selectedBackground); editor.putInt("selectedColor", selectedColor); + editor.putBoolean("overrideThemeWallpaper", Theme.hasWallpaperFromTheme() && overrideThemeWallpaper); editor.commit(); - ApplicationLoader.reloadWallpaper(); + Theme.reloadWallpaper(); } finishFragment(); } @@ -182,17 +207,12 @@ public boolean onTouch(View v, MotionEvent event) { progressViewBackground.setBackgroundResource(R.drawable.system_loader); progressView.addView(progressViewBackground, LayoutHelper.createFrame(36, 36, Gravity.CENTER)); - ProgressBar progressBar = new ProgressBar(context); - try { - progressBar.setIndeterminateDrawable(context.getResources().getDrawable(R.drawable.loading_animation)); - } catch (Exception e) { - //don't promt - } - progressBar.setIndeterminate(true); - AndroidUtilities.setProgressBarAnimationDuration(progressBar, 1500); + progressBar = new RadialProgressView(context); + progressBar.setSize(AndroidUtilities.dp(28)); + progressBar.setProgressColor(0xffffffff); progressView.addView(progressBar, LayoutHelper.createFrame(32, 32, Gravity.CENTER)); - RecyclerListView listView = new RecyclerListView(context); + listView = new RecyclerListView(context); listView.setClipToPadding(false); listView.setTag(8); listView.setPadding(AndroidUtilities.dp(40), 0, AndroidUtilities.dp(40), 0); @@ -207,48 +227,24 @@ public boolean onTouch(View v, MotionEvent event) { @Override public void onItemClick(View view, int position) { if (position == 0) { - if (getParentActivity() == null) { - return; - } - AlertDialog.Builder builder = new AlertDialog.Builder(getParentActivity()); - - CharSequence[] items = new CharSequence[]{LocaleController.getString("FromCamera", R.string.FromCamera), LocaleController.getString("FromGalley", R.string.FromGalley), LocaleController.getString("Cancel", R.string.Cancel)}; - - builder.setItems(items, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - try { - if (i == 0) { - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - File image = AndroidUtilities.generatePicturePath(); - if (image != null) { - if (Build.VERSION.SDK_INT >= 24) { - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(getParentActivity(), BuildConfig.APPLICATION_ID + ".provider", image)); - takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image)); - } - currentPicturePath = image.getAbsolutePath(); - } - startActivityForResult(takePictureIntent, 10); - } else if (i == 1) { - Intent photoPickerIntent = new Intent(Intent.ACTION_PICK); - photoPickerIntent.setType("image/*"); - startActivityForResult(photoPickerIntent, 11); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - }); - showDialog(builder.create()); + updater.showAlert(false); } else { - if (position - 1 < 0 || position - 1 >= wallPapers.size()) { - return; + if (Theme.hasWallpaperFromTheme()) { + if (position == 1) { + selectedBackground = -2; + overrideThemeWallpaper = false; + listAdapter.notifyDataSetChanged(); + processSelectedBackground(); + return; + } else { + position -= 2; + } + } else { + position--; } - TLRPC.WallPaper wallPaper = wallPapers.get(position - 1); + TLRPC.WallPaper wallPaper = wallPapers.get(position); selectedBackground = wallPaper.id; + overrideThemeWallpaper = true; listAdapter.notifyDataSetChanged(); processSelectedBackground(); } @@ -262,55 +258,12 @@ public void onClick(DialogInterface dialogInterface, int i) { @Override public void onActivityResultFragment(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == 10) { - AndroidUtilities.addMediaToGallery(currentPicturePath); - FileOutputStream stream = null; - try { - Point screenSize = AndroidUtilities.getRealScreenSize(); - Bitmap bitmap = ImageLoader.loadBitmap(currentPicturePath, null, screenSize.x, screenSize.y, true); - File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper-temp.jpg"); - stream = new FileOutputStream(toFile); - bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); - selectedBackground = -1; - selectedColor = 0; - Drawable drawable = backgroundImage.getDrawable(); - backgroundImage.setImageBitmap(bitmap); - } catch (Exception e) { - FileLog.e("tmessages", e); - } finally { - try { - if (stream != null) { - stream.close(); - } - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - currentPicturePath = null; - } else if (requestCode == 11) { - if (data == null || data.getData() == null) { - return; - } - try { - Point screenSize = AndroidUtilities.getRealScreenSize(); - Bitmap bitmap = ImageLoader.loadBitmap(null, data.getData(), screenSize.x, screenSize.y, true); - File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper-temp.jpg"); - FileOutputStream stream = new FileOutputStream(toFile); - bitmap.compress(Bitmap.CompressFormat.JPEG, 87, stream); - selectedBackground = -1; - selectedColor = 0; - Drawable drawable = backgroundImage.getDrawable(); - backgroundImage.setImageBitmap(bitmap); - } catch (Exception e) { - FileLog.e("tmessages", e); - } - } - } + updater.onActivityResult(requestCode, resultCode, data); } @Override public void saveSelfArgs(Bundle args) { + String currentPicturePath = updater.getCurrentPicturePath(); if (currentPicturePath != null) { args.putString("path", currentPicturePath); } @@ -318,88 +271,95 @@ public void saveSelfArgs(Bundle args) { @Override public void restoreSelfArgs(Bundle args) { - currentPicturePath = args.getString("path"); + updater.setCurrentPicturePath(args.getString("path")); } private void processSelectedBackground() { - TLRPC.WallPaper wallPaper = wallpappersByIds.get(selectedBackground); - if (selectedBackground != -1 && selectedBackground != 1000001 && wallPaper != null && wallPaper instanceof TLRPC.TL_wallPaper) { - int width = AndroidUtilities.displaySize.x; - int height = AndroidUtilities.displaySize.y; - if (width > height) { - int temp = width; - width = height; - height = temp; - } - TLRPC.PhotoSize size = FileLoader.getClosestPhotoSizeWithSize(wallPaper.sizes, Math.min(width, height)); - if (size == null) { - return; - } - String fileName = size.location.volume_id + "_" + size.location.local_id + ".jpg"; - File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); - if (!f.exists()) { - int result[] = AndroidUtilities.calcDrawableColor(backgroundImage.getDrawable()); - progressViewBackground.getBackground().setColorFilter(new PorterDuffColorFilter(result[0], PorterDuff.Mode.MULTIPLY)); - loadingFile = fileName; - loadingFileObject = f; - doneButton.setEnabled(false); - progressView.setVisibility(View.VISIBLE); - loadingSize = size; - selectedColor = 0; - FileLoader.getInstance().loadFile(size, null, true); - backgroundImage.setBackgroundColor(0); + if (Theme.hasWallpaperFromTheme() && !overrideThemeWallpaper) { + backgroundImage.setImageDrawable(Theme.getThemedWallpaper(false)); + } else { + TLRPC.WallPaper wallPaper = wallpappersByIds.get(selectedBackground); + if (selectedBackground != -1 && selectedBackground != 1000001 && wallPaper != null && wallPaper instanceof TLRPC.TL_wallPaper) { + int width = AndroidUtilities.displaySize.x; + int height = AndroidUtilities.displaySize.y; + if (width > height) { + int temp = width; + width = height; + height = temp; + } + TLRPC.PhotoSize size = FileLoader.getClosestPhotoSizeWithSize(wallPaper.sizes, Math.min(width, height)); + if (size == null) { + return; + } + String fileName = size.location.volume_id + "_" + size.location.local_id + ".jpg"; + File f = new File(FileLoader.getInstance().getDirectory(FileLoader.MEDIA_DIR_CACHE), fileName); + if (!f.exists()) { + int result[] = AndroidUtilities.calcDrawableColor(backgroundImage.getDrawable()); + progressViewBackground.getBackground().setColorFilter(new PorterDuffColorFilter(result[0], PorterDuff.Mode.MULTIPLY)); + loadingFile = fileName; + loadingFileObject = f; + doneButton.setEnabled(false); + progressView.setVisibility(View.VISIBLE); + loadingSize = size; + selectedColor = 0; + FileLoader.getInstance().loadFile(size, null, true); + backgroundImage.setBackgroundColor(0); + } else { + if (loadingFile != null) { + FileLoader.getInstance().cancelLoadFile(loadingSize); + } + loadingFileObject = null; + loadingFile = null; + loadingSize = null; + try { + backgroundImage.setImageURI(Uri.fromFile(f)); + } catch (Throwable e) { + FileLog.e(e); + } + backgroundImage.setBackgroundColor(0); + selectedColor = 0; + doneButton.setEnabled(true); + progressView.setVisibility(View.GONE); + } } else { if (loadingFile != null) { FileLoader.getInstance().cancelLoadFile(loadingSize); } + if (selectedBackground == 1000001) { + backgroundImage.setImageResource(R.drawable.background_hd); + backgroundImage.setBackgroundColor(0); + selectedColor = 0; + } else if (selectedBackground == -1) { + File toFile; + if (wallpaperFile != null) { + toFile = wallpaperFile; + } else { + toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper.jpg"); + } + if (toFile.exists()) { + backgroundImage.setImageURI(Uri.fromFile(toFile)); + } else { + selectedBackground = 1000001; + overrideThemeWallpaper = true; + processSelectedBackground(); + } + } else { + if (wallPaper == null) { + return; + } + if (wallPaper instanceof TLRPC.TL_wallPaperSolid) { + Drawable drawable = backgroundImage.getDrawable(); + backgroundImage.setImageBitmap(null); + selectedColor = 0xff000000 | wallPaper.bg_color; + backgroundImage.setBackgroundColor(selectedColor); + } + } loadingFileObject = null; loadingFile = null; loadingSize = null; - try { - backgroundImage.setImageURI(Uri.fromFile(f)); - } catch (Throwable e) { - FileLog.e("tmessages", e); - } - backgroundImage.setBackgroundColor(0); - selectedColor = 0; doneButton.setEnabled(true); progressView.setVisibility(View.GONE); } - } else { - if (loadingFile != null) { - FileLoader.getInstance().cancelLoadFile(loadingSize); - } - if (selectedBackground == 1000001) { - backgroundImage.setImageResource(R.drawable.background_hd); - backgroundImage.setBackgroundColor(0); - selectedColor = 0; - } else if (selectedBackground == -1) { - File toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper-temp.jpg"); - if (!toFile.exists()) { - toFile = new File(ApplicationLoader.getFilesDirFixed(), "wallpaper.jpg"); - } - if (toFile.exists()) { - backgroundImage.setImageURI(Uri.fromFile(toFile)); - } else { - selectedBackground = 1000001; - processSelectedBackground(); - } - } else { - if (wallPaper == null) { - return; - } - if (wallPaper instanceof TLRPC.TL_wallPaperSolid) { - Drawable drawable = backgroundImage.getDrawable(); - backgroundImage.setImageBitmap(null); - selectedColor = 0xff000000 | wallPaper.bg_color; - backgroundImage.setBackgroundColor(selectedColor); - } - } - loadingFileObject = null; - loadingFile = null; - loadingSize = null; - doneButton.setEnabled(true); - progressView.setVisibility(View.GONE); } } @@ -483,14 +443,7 @@ public void onResume() { processSelectedBackground(); } - private class ListAdapter extends RecyclerView.Adapter { - - private class Holder extends RecyclerView.ViewHolder { - - public Holder(View itemView) { - super(itemView); - } - } + private class ListAdapter extends RecyclerListView.SelectionAdapter { private Context mContext; @@ -498,9 +451,18 @@ public ListAdapter(Context context) { mContext = context; } + @Override + public boolean isEnabled(RecyclerView.ViewHolder holder) { + return true; + } + @Override public int getItemCount() { - return 1 + wallPapers.size(); + int count = 1 + wallPapers.size(); + if (Theme.hasWallpaperFromTheme()) { + count++; + } + return count; } @Override @@ -511,12 +473,42 @@ public long getItemId(int i) { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { WallpaperCell view = new WallpaperCell(mContext); - return new Holder(view); + return new RecyclerListView.Holder(view); } @Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) { - ((WallpaperCell) viewHolder.itemView).setWallpaper(i == 0 ? null : wallPapers.get(i - 1), selectedBackground); + WallpaperCell wallpaperCell = (WallpaperCell) viewHolder.itemView; + if (i == 0) { + wallpaperCell.setWallpaper(null, !Theme.hasWallpaperFromTheme() || overrideThemeWallpaper ? selectedBackground : -2, null, false); + } else { + if (Theme.hasWallpaperFromTheme()) { + if (i == 1) { + wallpaperCell.setWallpaper(null, overrideThemeWallpaper ? -1 : -2, themedWallpaper, true); + return; + } else { + i -= 2; + } + } else { + i--; + } + wallpaperCell.setWallpaper(wallPapers.get(i), !Theme.hasWallpaperFromTheme() || overrideThemeWallpaper ? selectedBackground : -2, null, false); + } } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(listView, ThemeDescription.FLAG_LISTGLOWCOLOR, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + + new ThemeDescription(listView, ThemeDescription.FLAG_SELECTOR, null, null, null, null, Theme.key_listSelector), + }; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java index 60b6a5f4b0b..714ec6937e0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/WebviewActivity.java @@ -3,12 +3,13 @@ * It is licensed under GNU GPL v. 2 or later. * You should have received a copy of the license in this archive (see LICENSE). * - * Copyright Nikolai Kudashov, 2013-2016. + * Copyright Nikolai Kudashov, 2013-2017. */ package org.telegram.ui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; @@ -27,11 +28,11 @@ import android.widget.FrameLayout; import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.AnimatorListenerAdapterProxy; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessageObject; +import org.telegram.messenger.MessagesController; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; import org.telegram.messenger.browser.Browser; @@ -40,6 +41,8 @@ import org.telegram.ui.ActionBar.ActionBarMenu; import org.telegram.ui.ActionBar.ActionBarMenuItem; import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.ActionBar.ThemeDescription; import org.telegram.ui.Components.ContextProgressView; import org.telegram.ui.Components.LayoutHelper; import org.telegram.ui.Components.ShareAlert; @@ -51,6 +54,7 @@ public class WebviewActivity extends BaseFragment { private WebView webView; private ActionBarMenuItem progressItem; private ContextProgressView progressView; + private String currentUrl; private String currentBot; private String currentGame; @@ -70,7 +74,7 @@ public void run() { if (getParentActivity() == null) { return; } - FileLog.e("tmessages", eventName); + FileLog.e(eventName); switch (eventName) { case "share_game": currentMessageObject.messageOwner.with_my_score = false; @@ -79,12 +83,23 @@ public void run() { currentMessageObject.messageOwner.with_my_score = true; break; } - showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy)); + showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); } }); } } + public Runnable typingRunnable = new Runnable() { + @Override + public void run() { + if (currentMessageObject == null || getParentActivity() == null || typingRunnable == null) { + return; + } + MessagesController.getInstance().sendTyping(currentMessageObject.getDialogId(), 6, 0); + AndroidUtilities.runOnUIThread(typingRunnable, 25000); + } + }; + public WebviewActivity(String url, String botName, String gameName, String startParam, MessageObject messageObject) { super(); currentUrl = url; @@ -92,12 +107,14 @@ public WebviewActivity(String url, String botName, String gameName, String start currentGame = gameName; currentMessageObject = messageObject; short_param = startParam; - linkToCopy = "https://telegram.me/" + currentBot + (TextUtils.isEmpty(startParam) ? "" : "?game=" + startParam); + linkToCopy = "https://" + MessagesController.getInstance().linkPrefix + "/" + currentBot + (TextUtils.isEmpty(startParam) ? "" : "?game=" + startParam); } @Override public void onFragmentDestroy() { super.onFragmentDestroy(); + AndroidUtilities.cancelRunOnUIThread(typingRunnable); + typingRunnable = null; try { ViewParent parent = webView.getParent(); if (parent != null) { @@ -108,7 +125,7 @@ public void onFragmentDestroy() { webView.destroy(); webView = null; } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } @@ -127,7 +144,7 @@ public void onItemClick(int id) { finishFragment(); } else if (id == share) { currentMessageObject.messageOwner.with_my_score = false; - showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy)); + showDialog(new ShareAlert(getParentActivity(), currentMessageObject, null, false, linkToCopy, false)); } else if (id == open_in) { openGameInBrowser(currentUrl, currentMessageObject, getParentActivity(), short_param, currentBot); } @@ -140,7 +157,7 @@ public void onItemClick(int id) { progressItem.getImageView().setVisibility(View.INVISIBLE); ActionBarMenuItem menuItem = menu.addItem(0, R.drawable.ic_ab_other); - menuItem.addSubItem(open_in, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp), 0); + menuItem.addSubItem(open_in, LocaleController.getString("OpenInExternalApp", R.string.OpenInExternalApp)); webView = new WebView(context); webView.getSettings().setJavaScriptEnabled(true); @@ -176,7 +193,7 @@ public void onPageFinished(WebView view, String url) { ObjectAnimator.ofFloat(progressItem.getImageView(), "scaleX", 0.0f, 1.0f), ObjectAnimator.ofFloat(progressItem.getImageView(), "scaleY", 0.0f, 1.0f), ObjectAnimator.ofFloat(progressItem.getImageView(), "alpha", 0.0f, 1.0f)); - animatorSet.addListener(new AnimatorListenerAdapterProxy() { + animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animator) { progressView.setVisibility(View.INVISIBLE); @@ -192,6 +209,13 @@ public void onAnimationEnd(Animator animator) { return fragmentView; } + @Override + public void onResume() { + super.onResume(); + AndroidUtilities.cancelRunOnUIThread(typingRunnable); + typingRunnable.run(); + } + @Override protected void onTransitionAnimationEnd(boolean isOpen, boolean backward) { if (isOpen && !backward && webView != null) { @@ -244,11 +268,28 @@ public static void openGameInBrowser(String urlStr, MessageObject messageObject, SerializedData serializedData = new SerializedData(messageObject.messageOwner.getObjectSize()); messageObject.messageOwner.serializeToStream(serializedData); editor.putString(hash + "_m", Utilities.bytesToHex(serializedData.toByteArray())); - editor.putString(hash + "_link", "https://telegram.me/" + username + (TextUtils.isEmpty(short_name) ? "" : "?game=" + short_name)); + editor.putString(hash + "_link", "https://" + MessagesController.getInstance().linkPrefix + "/" + username + (TextUtils.isEmpty(short_name) ? "" : "?game=" + short_name)); editor.commit(); Browser.openUrl(parentActivity, url, false); } catch (Exception e) { - FileLog.e("tmessages", e); + FileLog.e(e); } } + + @Override + public ThemeDescription[] getThemeDescriptions() { + return new ThemeDescription[]{ + new ThemeDescription(fragmentView, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_windowBackgroundWhite), + + new ThemeDescription(actionBar, ThemeDescription.FLAG_BACKGROUND, null, null, null, null, Theme.key_actionBarDefault), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_ITEMSCOLOR, null, null, null, null, Theme.key_actionBarDefaultIcon), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_TITLECOLOR, null, null, null, null, Theme.key_actionBarDefaultTitle), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SELECTORCOLOR, null, null, null, null, Theme.key_actionBarDefaultSelector), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUBACKGROUND, null, null, null, null, Theme.key_actionBarDefaultSubmenuBackground), + new ThemeDescription(actionBar, ThemeDescription.FLAG_AB_SUBMENUITEM, null, null, null, null, Theme.key_actionBarDefaultSubmenuItem), + + new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressInner2), + new ThemeDescription(progressView, 0, null, null, null, null, Theme.key_contextProgressOuter2), + }; + } } diff --git a/TMessagesProj/src/main/res/drawable-hdpi/add_btn.9.png b/TMessagesProj/src/main/res/drawable-hdpi/add_btn.9.png deleted file mode 100644 index 273cf0c6452..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/add_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/add_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-hdpi/add_btn_pressed.9.png deleted file mode 100644 index 57f723a4da9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/add_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/addmember.png b/TMessagesProj/src/main/res/drawable-hdpi/addmember.png deleted file mode 100755 index 166473d7bcd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/addmember.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png index 2f6db99edf0..13fc8c6ce6f 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png and b/TMessagesProj/src/main/res/drawable-hdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/admin_star2.png b/TMessagesProj/src/main/res/drawable-hdpi/admin_star2.png deleted file mode 100755 index 6d9f3a89265..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/admin_star2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/assign_manager.png b/TMessagesProj/src/main/res/drawable-hdpi/assign_manager.png deleted file mode 100755 index 153a7fb1fcd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/assign_manager.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/audiosend_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/audiosend_pause.png index 3900d81514c..4a8c89084cf 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/audiosend_pause.png and b/TMessagesProj/src/main/res/drawable-hdpi/audiosend_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/audiosend_play.png b/TMessagesProj/src/main/res/drawable-hdpi/audiosend_play.png index 4b13c02e6a9..f032aeb3841 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/audiosend_play.png and b/TMessagesProj/src/main/res/drawable-hdpi/audiosend_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bluecircle.png b/TMessagesProj/src/main/res/drawable-hdpi/bluecircle.png deleted file mode 100644 index 1cc7e9f99e5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bluecircle.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_file.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_file.png index 81a867e92f4..ce32afeeeb5 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_file.png and b/TMessagesProj/src/main/res/drawable-hdpi/bot_file.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_info.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_info.png deleted file mode 100644 index 6b5a4a2bad3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_info.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard.png old mode 100644 new mode 100755 index e045972d8e6..68ba9865a7e Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard.png and b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard2.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard2.png index d43df46ce8f..5783d098e64 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard2.png and b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard2.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button.9.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button.9.png deleted file mode 100644 index 55777ab32b8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button_pressed.9.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button_pressed.9.png deleted file mode 100644 index 15a03df75e7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_keyboard_button_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_list.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_list.png deleted file mode 100755 index cbcfa1f250f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_location.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_location.png index e587748fe63..8dffe45049c 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_location.png and b/TMessagesProj/src/main/res/drawable-hdpi/bot_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/bot_music.png b/TMessagesProj/src/main/res/drawable-hdpi/bot_music.png index 03857ec841c..4cccd6ed2c4 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/bot_music.png and b/TMessagesProj/src/main/res/drawable-hdpi/bot_music.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/broadcast2.png b/TMessagesProj/src/main/res/drawable-hdpi/broadcast2.png deleted file mode 100644 index 9c4837b895f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/broadcast2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/broadcast3.png b/TMessagesProj/src/main/res/drawable-hdpi/broadcast3.png old mode 100644 new mode 100755 index d296975976b..4c8a3328da4 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/broadcast3.png and b/TMessagesProj/src/main/res/drawable-hdpi/broadcast3.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/broadcast4.png b/TMessagesProj/src/main/res/drawable-hdpi/broadcast4.png deleted file mode 100644 index ed908d80417..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/broadcast4.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chat_badge.9.png b/TMessagesProj/src/main/res/drawable-hdpi/chat_badge.9.png deleted file mode 100755 index 50fd7f07d8e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/chat_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chats_clear.png b/TMessagesProj/src/main/res/drawable-hdpi/chats_clear.png new file mode 100755 index 00000000000..82f293b65eb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/chats_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chats_delete.png b/TMessagesProj/src/main/res/drawable-hdpi/chats_delete.png new file mode 100755 index 00000000000..421a99fef49 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/chats_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chats_leave.png b/TMessagesProj/src/main/res/drawable-hdpi/chats_leave.png new file mode 100755 index 00000000000..2391fc9d16c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/chats_leave.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chats_pin.png b/TMessagesProj/src/main/res/drawable-hdpi/chats_pin.png new file mode 100755 index 00000000000..f9ccd7ae746 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/chats_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/chats_unpin.png b/TMessagesProj/src/main/res/drawable-hdpi/chats_unpin.png new file mode 100755 index 00000000000..5fb6cb33b61 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/chats_unpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/check_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/check_blue.png deleted file mode 100755 index 80ade6ab1d0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/check_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/check_list.png b/TMessagesProj/src/main/res/drawable-hdpi/check_list.png deleted file mode 100644 index e739bea2f80..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/check_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/check_profile.png b/TMessagesProj/src/main/res/drawable-hdpi/check_profile.png deleted file mode 100644 index 380a8ebff12..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/check_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/cloud.png b/TMessagesProj/src/main/res/drawable-hdpi/cloud.png old mode 100644 new mode 100755 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/compose_panel.9.png b/TMessagesProj/src/main/res/drawable-hdpi/compose_panel.9.png deleted file mode 100644 index 19f2a5a702d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/compose_panel.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/compose_panel_shadow.png b/TMessagesProj/src/main/res/drawable-hdpi/compose_panel_shadow.png new file mode 100755 index 00000000000..afde14d30ac Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/compose_panel_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/contact_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/contact_blue.png deleted file mode 100755 index 0f52e878104..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/contact_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/contact_green.png b/TMessagesProj/src/main/res/drawable-hdpi/contact_green.png deleted file mode 100755 index 1b3c67d76ed..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/contact_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/del_btn.9.png b/TMessagesProj/src/main/res/drawable-hdpi/del_btn.9.png deleted file mode 100644 index 656cb06b080..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/del_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/del_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-hdpi/del_btn_pressed.9.png deleted file mode 100644 index f8a6e064421..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/del_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/delete.png b/TMessagesProj/src/main/res/drawable-hdpi/delete.png new file mode 100644 index 00000000000..79c04e5f2c0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/delete_reply.png b/TMessagesProj/src/main/res/drawable-hdpi/delete_reply.png deleted file mode 100755 index d225e7e22a2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/delete_reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/devices.png b/TMessagesProj/src/main/res/drawable-hdpi/devices.png index 76870496865..71ffe85e05c 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/devices.png and b/TMessagesProj/src/main/res/drawable-hdpi/devices.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge.9.png b/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge.9.png deleted file mode 100755 index 3d15a62bc9d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge2.9.png b/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge2.9.png deleted file mode 100755 index 3a857ff2366..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_badge2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_check.png b/TMessagesProj/src/main/res/drawable-hdpi/dialogs_check.png deleted file mode 100755 index 11380095e33..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_check.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_halfcheck.png b/TMessagesProj/src/main/res/drawable-hdpi/dialogs_halfcheck.png deleted file mode 100755 index f75f9ccb2bf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_halfcheck.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_warning.png b/TMessagesProj/src/main/res/drawable-hdpi/dialogs_warning.png deleted file mode 100755 index c1474e23b39..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/dialogs_warning.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b.png deleted file mode 100755 index 7d13ee813d1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b_s.png deleted file mode 100755 index f107194d733..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_g.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_g.png deleted file mode 100755 index 09dea5d2aab..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_actions_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_big.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_big.png new file mode 100755 index 00000000000..5368ac95f44 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/doc_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_blue.png deleted file mode 100755 index 2e5b702f534..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_blue_s.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_blue_s.png deleted file mode 100755 index 0484f441a6f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_blue_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doc_green.png b/TMessagesProj/src/main/res/drawable-hdpi/doc_green.png deleted file mode 100755 index 9bc676c9936..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doc_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b.png b/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b.png deleted file mode 100644 index 897ac3a3faf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b_s.png deleted file mode 100755 index 0f607d71737..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_g.png b/TMessagesProj/src/main/res/drawable-hdpi/doccancel_g.png deleted file mode 100644 index b53eebb552c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/doccancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docload_b.png b/TMessagesProj/src/main/res/drawable-hdpi/docload_b.png deleted file mode 100644 index e27febd6a23..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docload_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/docload_b_s.png deleted file mode 100755 index e5bbd51f058..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docload_g.png b/TMessagesProj/src/main/res/drawable-hdpi/docload_g.png deleted file mode 100644 index f93437c168d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docpause_b.png b/TMessagesProj/src/main/res/drawable-hdpi/docpause_b.png deleted file mode 100644 index d7c483a0250..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docpause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docpause_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/docpause_b_s.png deleted file mode 100755 index 3bc4621ec4f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docpause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/docpause_g.png b/TMessagesProj/src/main/res/drawable-hdpi/docpause_g.png deleted file mode 100644 index 35ca985fb22..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/docpause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/edit_done.png b/TMessagesProj/src/main/res/drawable-hdpi/edit_done.png new file mode 100755 index 00000000000..9dd3864b4ca Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/edit_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/edit_doneblue.png b/TMessagesProj/src/main/res/drawable-hdpi/edit_doneblue.png deleted file mode 100755 index ad5f280d117..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/edit_doneblue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b.png deleted file mode 100755 index b9b9fdbc1e5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel.png deleted file mode 100755 index 76fccb3beb6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel_s.png deleted file mode 100755 index 212a68f2949..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b_load.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b_load.png deleted file mode 100755 index d704af86ea5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b_load_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b_load_s.png deleted file mode 100755 index 23a413daa4a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_b_s.png deleted file mode 100755 index b682a368c63..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g.png deleted file mode 100755 index 6b805748cff..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel.png deleted file mode 100755 index 5fbc87d9b74..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel_s.png deleted file mode 100755 index 07b8de1536e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g_load.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g_load.png deleted file mode 100755 index 741b6986461..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g_load_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g_load_s.png deleted file mode 100755 index 2449001207d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/file_g_s.png b/TMessagesProj/src/main/res/drawable-hdpi/file_g_s.png deleted file mode 100755 index 41228655bf4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/file_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating.png b/TMessagesProj/src/main/res/drawable-hdpi/floating.png deleted file mode 100755 index 5215c11d398..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png deleted file mode 100755 index de9e52d626e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png deleted file mode 100755 index 834d0195a91..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png deleted file mode 100755 index 82f31bdfa5d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating3_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png index 0980e9e08db..7d1701ef8a1 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png deleted file mode 100755 index be31f6d4cf6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png index c6743f12a44..7fb41f8bc4e 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png and b/TMessagesProj/src/main/res/drawable-hdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png deleted file mode 100755 index b4579d8bb36..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/floating_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow.png new file mode 100755 index 00000000000..25e06ca7c0a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow_profile.png b/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow_profile.png new file mode 100755 index 00000000000..5cece640199 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/floating_shadow_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/forward_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/forward_blue.png deleted file mode 100755 index c39ac4ef781..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/forward_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/foursquare.png b/TMessagesProj/src/main/res/drawable-hdpi/foursquare.png old mode 100644 new mode 100755 index 64783f7ec59..ca3a16b3adc Binary files a/TMessagesProj/src/main/res/drawable-hdpi/foursquare.png and b/TMessagesProj/src/main/res/drawable-hdpi/foursquare.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/greydivider.9.png b/TMessagesProj/src/main/res/drawable-hdpi/greydivider.9.png old mode 100644 new mode 100755 index 9a69cf43db3..7d2ced6074d Binary files a/TMessagesProj/src/main/res/drawable-hdpi/greydivider.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/greydivider.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/greydivider_bottom.9.png b/TMessagesProj/src/main/res/drawable-hdpi/greydivider_bottom.9.png old mode 100644 new mode 100755 index 64d23a36cd9..900fd1b0848 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/greydivider_bottom.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/greydivider_bottom.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/greydivider_top.9.png b/TMessagesProj/src/main/res/drawable-hdpi/greydivider_top.9.png old mode 100644 new mode 100755 index 4523e953c81..7505f015007 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/greydivider_top.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/greydivider_top.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/hd_off.png b/TMessagesProj/src/main/res/drawable-hdpi/hd_off.png deleted file mode 100755 index 9926b18ba54..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/hd_off.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/hd_on.png b/TMessagesProj/src/main/res/drawable-hdpi/hd_on.png deleted file mode 100755 index db05c9c1045..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/hd_on.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/header_timer.png b/TMessagesProj/src/main/res/drawable-hdpi/header_timer.png deleted file mode 100755 index 0c17b9bbad9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/header_timer.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/header_timer2.png b/TMessagesProj/src/main/res/drawable-hdpi/header_timer2.png deleted file mode 100755 index 019ad9c9ab3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/header_timer2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach.png old mode 100644 new mode 100755 index 7f1bacc6b36..4a2855b21f3 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach3.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach3.png deleted file mode 100755 index 7529e81e68b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_attach3.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_back_grey.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_back_grey.png deleted file mode 100755 index 0ca6612a917..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_back_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_copy.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_copy.png new file mode 100755 index 00000000000..761096b4770 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_delete.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_delete.png new file mode 100755 index 00000000000..257f5d4e996 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_doc.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_doc.png deleted file mode 100755 index 25e2c036b8c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_doc.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done.png new file mode 100755 index 00000000000..7152d573940 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done_gray.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done_gray.png deleted file mode 100755 index 99240881de7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_done_gray.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_forward.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_forward.png new file mode 100755 index 00000000000..474b0fd33ea Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_copy.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_copy.png deleted file mode 100755 index 64ed7cd057e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_copy.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_delete.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_delete.png deleted file mode 100755 index 9af1eb095f4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_delete.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_forward.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_forward.png deleted file mode 100755 index bbe96ec85f3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_fwd_forward.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_new.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_new.png index c06f2c593bd..890c3367ce8 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_new.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_new.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_reply.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_reply.png index 2d78652e737..0b4e6a813d0 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_reply.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_ab_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_again.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_again.png new file mode 100644 index 00000000000..e8116a335f9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_again.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_againinline.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_againinline.png new file mode 100644 index 00000000000..391b7b5f16c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_againinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_bluetooth_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_bluetooth_white_24dp.png new file mode 100755 index 00000000000..fce1884000f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_bluetooth_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png new file mode 100755 index 00000000000..625b827c44e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_36dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_36dp.png new file mode 100755 index 00000000000..51456d3d5d3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_end_white_36dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_call_made_green_18dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_made_green_18dp.png new file mode 100755 index 00000000000..0c96e2e94a9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_made_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_call_received_green_18dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_received_green_18dp.png new file mode 100755 index 00000000000..49daf602ae9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_received_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_call_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_white_24dp.png new file mode 100755 index 00000000000..4dc5065155b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_call_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_chat_bubble_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_chat_bubble_white_24dp.png new file mode 100755 index 00000000000..6e4e14e91da Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_chat_bubble_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_create.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_create.png deleted file mode 100644 index 82966ed287b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_create.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png index f0cc429d5a8..a13bd156957 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png index bbf0414c0f1..b9b8af36a17 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_gofullscreen.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_gofullscreen.png new file mode 100644 index 00000000000..a77ad403abd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_gofullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_goinline.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_goinline.png new file mode 100644 index 00000000000..e7ddb90a67f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_goinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_mic_off_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_mic_off_white_24dp.png new file mode 100755 index 00000000000..6fccf5d09f0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_mic_off_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png new file mode 100644 index 00000000000..c6e1eab3796 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_more.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_gif.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_gif.png index 309ad0832ca..b7b3569ceaa 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_gif.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_kb.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_kb.png index c6985f3577d..f5dd8c7d7c7 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_kb.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_kb.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_smiles.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_smiles.png index 8b55b056fe9..94ef04311ab 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_smiles.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_smiles.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_stickers.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_stickers.png new file mode 100755 index 00000000000..c481140e269 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_video.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_video.png new file mode 100755 index 00000000000..6929559330c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_msg_panel_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_outfullscreen.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_outfullscreen.png new file mode 100644 index 00000000000..d27b1101d4b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_outfullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_outinline.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_outinline.png new file mode 100644 index 00000000000..43d65043423 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_outinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_pause.png new file mode 100644 index 00000000000..630f0e5dfb8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_pauseinline.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_pauseinline.png new file mode 100644 index 00000000000..6efd8647221 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_pauseinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_play.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_play.png new file mode 100644 index 00000000000..e04ea8a3af1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_playinline.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_playinline.png new file mode 100644 index 00000000000..a2d21e6b3cd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_playinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star.png new file mode 100644 index 00000000000..dc6a99f91e3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star_filled.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star_filled.png new file mode 100644 index 00000000000..b4a6325d10f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_rating_star_filled.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_send.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_send.png index 30feca08dba..ffa715e3281 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_send.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_share_article.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_share_article.png new file mode 100644 index 00000000000..e23af5fcb23 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_share_article.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_share_video.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_share_video.png new file mode 100644 index 00000000000..9a90837c215 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_share_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car.png index d069347777a..8648c6e84ff 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car_active.png deleted file mode 100755 index 954d316b4b7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_car_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food.png index bf53fa2a1f5..b0e92a31422 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food_active.png deleted file mode 100755 index b0ba3f0e2c1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_food_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature.png index e93740eaf63..3fd0a3ef094 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature_active.png deleted file mode 100755 index 28fbe328c3b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_nature_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects.png index 66786a22fa6..f834a4242be 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects_active.png deleted file mode 100755 index 2e0e9d05975..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_objects_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent.png index 104d17cbfb4..63d98b60356 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent_active.png deleted file mode 100755 index 216ed10b89d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_recent_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile.png index 950116479e9..3411fd33c8e 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile_active.png deleted file mode 100755 index 75fa7d62062..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_smile_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers.png index ac1cd3e63d4..7f0ac891ff4 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers_active.png deleted file mode 100755 index ff8b99f15e1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles2_stickers_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace.png index 50c5312f2d1..19eb21d5ee9 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace_active.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace_active.png deleted file mode 100755 index b02ed7c29b6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_backspace_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_gif.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_gif.png index bae6ea9413f..016963ba6ef 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_gif.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_settings.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_settings.png index d97d21f6ca4..0c1b57df316 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_settings.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_trend.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_trend.png index c47d3c197a9..30c53cfaa9c 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_trend.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_smiles_trend.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png index 6c2c8e92700..014219a1949 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png index 7cd764da3ba..01e504f4277 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png and b/TMessagesProj/src/main/res/drawable-hdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png new file mode 100755 index 00000000000..57d787163e9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_volume_up_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png new file mode 100755 index 00000000000..0b6a1dfd0e8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/ic_vpn_key_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/link.png b/TMessagesProj/src/main/res/drawable-hdpi/link.png deleted file mode 100755 index 987f4cc5216..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/link.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_bot.png b/TMessagesProj/src/main/res/drawable-hdpi/list_bot.png new file mode 100755 index 00000000000..4e161651262 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_bot.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_broadcast.png b/TMessagesProj/src/main/res/drawable-hdpi/list_broadcast.png index 54a56f821dd..46876a5e149 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_broadcast.png and b/TMessagesProj/src/main/res/drawable-hdpi/list_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_check.png b/TMessagesProj/src/main/res/drawable-hdpi/list_check.png new file mode 100755 index 00000000000..3b578669b44 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_group.png b/TMessagesProj/src/main/res/drawable-hdpi/list_group.png index bde0ddcb3e3..4b2e05477c7 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_group.png and b/TMessagesProj/src/main/res/drawable-hdpi/list_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_halfcheck.png b/TMessagesProj/src/main/res/drawable-hdpi/list_halfcheck.png new file mode 100755 index 00000000000..dc9b8f2795e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_longpressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-hdpi/list_longpressed_holo_light.9.png deleted file mode 100644 index e9afcc9248a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_longpressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_mute.png b/TMessagesProj/src/main/res/drawable-hdpi/list_mute.png new file mode 100755 index 00000000000..4835db6f546 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_mute.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_pin.png b/TMessagesProj/src/main/res/drawable-hdpi/list_pin.png new file mode 100755 index 00000000000..9a1030ff6ee Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_pressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-hdpi/list_pressed_holo_light.9.png deleted file mode 100644 index 2054530ed28..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_pressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_secret.png b/TMessagesProj/src/main/res/drawable-hdpi/list_secret.png index 54ed6bf2882..80d702f864f 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_secret.png and b/TMessagesProj/src/main/res/drawable-hdpi/list_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_selector_disabled_holo_light.9.png b/TMessagesProj/src/main/res/drawable-hdpi/list_selector_disabled_holo_light.9.png deleted file mode 100644 index ca8e9a2778f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/list_selector_disabled_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/list_warning_sign.png b/TMessagesProj/src/main/res/drawable-hdpi/list_warning_sign.png new file mode 100755 index 00000000000..0a0ac17b645 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/list_warning_sign.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/location2.png b/TMessagesProj/src/main/res/drawable-hdpi/location2.png deleted file mode 100644 index 84e92b27b99..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/location2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/location_b.9.png b/TMessagesProj/src/main/res/drawable-hdpi/location_b.9.png deleted file mode 100755 index 28f77558be0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/location_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/location_g.9.png b/TMessagesProj/src/main/res/drawable-hdpi/location_g.9.png deleted file mode 100755 index 8f2c9c0c783..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/location_g.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/managers.png b/TMessagesProj/src/main/res/drawable-hdpi/managers.png index 0cad1f458bd..ccf2c72fa02 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/managers.png and b/TMessagesProj/src/main/res/drawable-hdpi/managers.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png index 35678c12d43..5ddef7dd315 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png index 364c6d99050..561f81051cb 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png and b/TMessagesProj/src/main/res/drawable-hdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_broadcast.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_broadcast.png index 439cbb7cd3a..e3d63728b2e 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_broadcast.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_calls.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_calls.png new file mode 100755 index 00000000000..2cb0de149e0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/menu_calls.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_contacts.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_contacts.png index d2ce6e6454e..8ee5e0ba875 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_contacts.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_contacts.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_help.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_help.png index 3acaa23eee9..eb3f753a202 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_help.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_help.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_invite.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_invite.png index eee0ec8f13d..f746b62ab39 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_invite.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_invite.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_newgroup.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_newgroup.png index 09dec72f627..502be61fff1 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_newgroup.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_newgroup.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_secret.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_secret.png index 60c65a29ceb..e9143ffd463 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_secret.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_settings.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_settings.png index 01277467599..520c2c4651f 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/menu_settings.png and b/TMessagesProj/src/main/res/drawable-hdpi/menu_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mic.png b/TMessagesProj/src/main/res/drawable-hdpi/mic.png index 75e7bbf8374..6bbf1f4fbfb 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/mic.png and b/TMessagesProj/src/main/res/drawable-hdpi/mic.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mic_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/mic_pressed.png deleted file mode 100644 index ae9ea3bf424..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/mic_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png index ee4972c7a07..95ef2b917d7 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png and b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_pause.png index f173ae63ab6..612b8704de9 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_pause.png and b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_play.png b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_play.png index 7dd0d068817..9e90258b775 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_play.png and b/TMessagesProj/src/main/res/drawable-hdpi/miniplayer_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_actions.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_actions.png new file mode 100755 index 00000000000..11878ff287c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_actions.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_check.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_check.png index 80e7278e621..9d19836637e 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_check.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_check_w.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_check_w.png deleted file mode 100755 index be83f2bf2a6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_check_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_clock.png index 94b24e32b82..1587746c41a 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_clock.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2.png deleted file mode 100755 index 68c4f355555..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2_s.png deleted file mode 100755 index 74166457069..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock2_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock_photo.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_clock_photo.png deleted file mode 100755 index a7f059cb460..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_clock_photo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_contact.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_contact.png new file mode 100755 index 00000000000..b49b48171e5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_contact.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck.png index f568503fdd3..e3b19944e8c 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck_w.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck_w.png deleted file mode 100755 index 1cb63687512..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_halfcheck_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_in.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_in.9.png index 984a1871c3a..50df48361db 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_in.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_in.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo.9.png deleted file mode 100755 index 5bded8c16c5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo_selected.9.png deleted file mode 100755 index e279dc81e99..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_selected.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_in_selected.9.png deleted file mode 100755 index 8ddd4059e69..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_in_shadow.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_in_shadow.9.png new file mode 100755 index 00000000000..c97fb2a18e6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_in_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_instant.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_instant.png new file mode 100755 index 00000000000..0d44d5ac620 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_instant.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_location.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_location.png new file mode 100755 index 00000000000..81fe1629a2a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_out.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_out.9.png index 5034ea1f06d..9087ef0c2cd 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_out.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_out.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo.9.png deleted file mode 100755 index a6604011a55..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo_selected.9.png deleted file mode 100755 index 8a4bd61f39b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_selected.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_out_selected.9.png deleted file mode 100755 index 82b6bc2db28..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_out_shadow.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_out_shadow.9.png new file mode 100755 index 00000000000..9899e38da6b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_out_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_clear.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_clear.png new file mode 100755 index 00000000000..d576ba038b6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_forward.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_forward.png new file mode 100755 index 00000000000..426bb558816 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_link.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_link.png new file mode 100755 index 00000000000..b7220e36a86 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_link.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_reply.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_reply.png new file mode 100755 index 00000000000..a23cabdb4ff Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_panel_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_photo.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_photo.9.png new file mode 100755 index 00000000000..dfa1687435e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_photo.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_photo_shadow.9.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_photo_shadow.9.png new file mode 100755 index 00000000000..4c080fa118b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_photo_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png new file mode 100755 index 00000000000..253eecc90a6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_m.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_m.png new file mode 100755 index 00000000000..0ddb348118c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png new file mode 100755 index 00000000000..aac1d5fa63e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_cancel_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_file_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_file_s.png new file mode 100755 index 00000000000..d9a07fbe949 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_file_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_gif_m.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_gif_m.png new file mode 100755 index 00000000000..c2fc9129ea3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_gif_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png new file mode 100755 index 00000000000..9224129fb34 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_m.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_m.png new file mode 100755 index 00000000000..3eca4141350 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png new file mode 100755 index 00000000000..07f0fea6be3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_load_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_m.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_m.png new file mode 100755 index 00000000000..e136413f6d2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png new file mode 100755 index 00000000000..fe038fd26f3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_pause_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png new file mode 100755 index 00000000000..6fcf16da9ad Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_m.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_m.png new file mode 100755 index 00000000000..56e77657d5c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png new file mode 100755 index 00000000000..0f9dbe8dedb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_round_play_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_views.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_views.png new file mode 100755 index 00000000000..69fe2c37aff Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/msg_views.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/msg_warning.png b/TMessagesProj/src/main/res/drawable-hdpi/msg_warning.png old mode 100644 new mode 100755 index 8c3a54caca5..c52eebd9380 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/msg_warning.png and b/TMessagesProj/src/main/res/drawable-hdpi/msg_warning.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png deleted file mode 100755 index 27af0fa13df..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/mute_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png deleted file mode 100755 index 57a0acb9f76..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/mute_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/myloc_on.png b/TMessagesProj/src/main/res/drawable-hdpi/myloc_on.png old mode 100644 new mode 100755 index 0e93b8072ca..ad517c3a746 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/myloc_on.png and b/TMessagesProj/src/main/res/drawable-hdpi/myloc_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/nocover.png b/TMessagesProj/src/main/res/drawable-hdpi/nocover.png old mode 100644 new mode 100755 index 4c0748a947f..27d1739436e Binary files a/TMessagesProj/src/main/res/drawable-hdpi/nocover.png and b/TMessagesProj/src/main/res/drawable-hdpi/nocover.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_1h.png b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_1h.png new file mode 100755 index 00000000000..9ed6a44d4f4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_1h.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_2d.png b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_2d.png new file mode 100755 index 00000000000..4d47f1ec397 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_2d.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_custom.png b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_custom.png new file mode 100755 index 00000000000..8c0df989ea5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_custom.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_off.png b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_off.png new file mode 100755 index 00000000000..71297115eee Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_on.png b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_on.png new file mode 100755 index 00000000000..a88eb01137f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/notifications_s_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-hdpi/notify_members_off.png index 6f77fbdcf0f..4f8ac8214cb 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-hdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-hdpi/notify_members_on.png index cd6f422979d..d6bd8e531ee 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-hdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/numberpicker_selection_divider.9.png b/TMessagesProj/src/main/res/drawable-hdpi/numberpicker_selection_divider.9.png deleted file mode 100644 index c9c72ba6194..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/numberpicker_selection_divider.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pagedown.png b/TMessagesProj/src/main/res/drawable-hdpi/pagedown.png index 0142cce7d3f..18c03ae07d6 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pagedown.png and b/TMessagesProj/src/main/res/drawable-hdpi/pagedown.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pagedown_shadow.png b/TMessagesProj/src/main/res/drawable-hdpi/pagedown_shadow.png new file mode 100755 index 00000000000..e823700ccc8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/pagedown_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pause_b.png b/TMessagesProj/src/main/res/drawable-hdpi/pause_b.png deleted file mode 100755 index d8547fe069a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pause_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/pause_b_s.png deleted file mode 100755 index fe250126eb2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pause_g.png b/TMessagesProj/src/main/res/drawable-hdpi/pause_g.png deleted file mode 100755 index 1f1f85d824a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pause_g_s.png b/TMessagesProj/src/main/res/drawable-hdpi/pause_g_s.png deleted file mode 100755 index f9b9d4285ac..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pause_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-hdpi/phone_change.png old mode 100644 new mode 100755 index dfd3a8e4bbe..592645f6c68 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/phone_change.png and b/TMessagesProj/src/main/res/drawable-hdpi/phone_change.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/phone_grey.png b/TMessagesProj/src/main/res/drawable-hdpi/phone_grey.png deleted file mode 100755 index e1d32825a71..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/phone_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel.png deleted file mode 100755 index 06eae455312..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b.png deleted file mode 100755 index 780ecf6ac28..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b_s.png deleted file mode 100755 index 6c2bc841fb9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g.png deleted file mode 100755 index 23200be5d8f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g_s.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g_s.png deleted file mode 100755 index a8aad3a0e08..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/photocancel_pressed.png deleted file mode 100755 index 3aa1632d844..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photocancel_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photogif.png b/TMessagesProj/src/main/res/drawable-hdpi/photogif.png deleted file mode 100755 index 2c71a642027..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photogif.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photogif_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/photogif_pressed.png deleted file mode 100755 index ec339890ce7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photogif_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload.png deleted file mode 100755 index 57c2e5240f8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload_b.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload_b.png deleted file mode 100755 index f78f534d838..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload_b_s.png deleted file mode 100755 index 563dd17c5fa..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload_g.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload_g.png deleted file mode 100755 index bb7ba472c22..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload_g_s.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload_g_s.png deleted file mode 100755 index 027d0da085a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/photoload_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/photoload_pressed.png deleted file mode 100755 index 588a4fa7b63..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/photoload_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pin.png b/TMessagesProj/src/main/res/drawable-hdpi/pin.png old mode 100644 new mode 100755 index 386acb2284f..e3a4f796204 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pin.png and b/TMessagesProj/src/main/res/drawable-hdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png old mode 100644 new mode 100755 index c6581812412..6617d44de38 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_next_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_next_pressed.png deleted file mode 100644 index 5a01fac92c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_next_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png old mode 100644 new mode 100755 index c3cbab41697..c8313750111 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_pause_pressed.png deleted file mode 100644 index 4ec1ead1a5f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png old mode 100644 new mode 100755 index 434039be989..1d2312f70e2 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_play_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_play_pressed.png deleted file mode 100644 index 836f92d592f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png old mode 100644 new mode 100755 index b9d6787256e..d9662db4a05 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_previous_pressed.png deleted file mode 100644 index 4ef7d468b7d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_previous_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png old mode 100644 new mode 100755 index 492637b717b..4450600ced1 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png new file mode 100755 index 00000000000..30357ce8484 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1_active.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1_active.png deleted file mode 100644 index 4379bbd2cef..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat1_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat_active.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat_active.png deleted file mode 100644 index 432f02870c8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_repeat_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png old mode 100644 new mode 100755 index 0389f00b90c..fcd1cac3e50 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle_active.png b/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle_active.png deleted file mode 100644 index c1c5ab850ed..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/pl_shuffle_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/place_x.png b/TMessagesProj/src/main/res/drawable-hdpi/place_x.png old mode 100644 new mode 100755 index c4ee065d322..734a1466a23 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/place_x.png and b/TMessagesProj/src/main/res/drawable-hdpi/place_x.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/play_b.png b/TMessagesProj/src/main/res/drawable-hdpi/play_b.png deleted file mode 100755 index 702c57a9a3e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/play_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/play_b_s.png b/TMessagesProj/src/main/res/drawable-hdpi/play_b_s.png deleted file mode 100755 index fd1dc122802..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/play_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/play_g.png b/TMessagesProj/src/main/res/drawable-hdpi/play_g.png deleted file mode 100755 index 6b61de3ffe3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/play_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/play_g_s.png b/TMessagesProj/src/main/res/drawable-hdpi/play_g_s.png deleted file mode 100755 index 0cffb436ff5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/play_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/playvideo.png b/TMessagesProj/src/main/res/drawable-hdpi/playvideo.png deleted file mode 100755 index dc0e8a04dce..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/playvideo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/playvideo_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/playvideo_pressed.png deleted file mode 100755 index 63b03ebb4b3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/playvideo_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/plus.png b/TMessagesProj/src/main/res/drawable-hdpi/plus.png index 0c206326496..1bc967dfe63 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/plus.png and b/TMessagesProj/src/main/res/drawable-hdpi/plus.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/post_views.png b/TMessagesProj/src/main/res/drawable-hdpi/post_views.png deleted file mode 100755 index ec2add861d8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/post_views.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/post_views_s.png b/TMessagesProj/src/main/res/drawable-hdpi/post_views_s.png deleted file mode 100755 index ab68d75a098..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/post_views_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/post_views_w.png b/TMessagesProj/src/main/res/drawable-hdpi/post_views_w.png deleted file mode 100755 index a224f5961bd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/post_views_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/post_viewsg.png b/TMessagesProj/src/main/res/drawable-hdpi/post_viewsg.png deleted file mode 100755 index ff153d19841..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/post_viewsg.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/profile_info.png b/TMessagesProj/src/main/res/drawable-hdpi/profile_info.png new file mode 100755 index 00000000000..7d731cdfbfc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/profile_info.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/profile_list.png b/TMessagesProj/src/main/res/drawable-hdpi/profile_list.png index c201168e4a8..56a4d89336b 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/profile_list.png and b/TMessagesProj/src/main/res/drawable-hdpi/profile_list.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/profile_phone.png b/TMessagesProj/src/main/res/drawable-hdpi/profile_phone.png new file mode 100755 index 00000000000..02a43d7fa69 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/profile_phone.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/publish.png b/TMessagesProj/src/main/res/drawable-hdpi/publish.png deleted file mode 100755 index 97802475883..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/publish.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/publish_active.png b/TMessagesProj/src/main/res/drawable-hdpi/publish_active.png deleted file mode 100755 index 9354ce65f20..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/publish_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/recorded.9.png b/TMessagesProj/src/main/res/drawable-hdpi/recorded.9.png deleted file mode 100644 index 5709cf5751b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/recorded.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/reply.png b/TMessagesProj/src/main/res/drawable-hdpi/reply.png deleted file mode 100755 index f9285320001..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/s_pause_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/s_pause_pressed.png deleted file mode 100755 index 1965bd3bed0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/s_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/s_play_pressed.png b/TMessagesProj/src/main/res/drawable-hdpi/s_play_pressed.png deleted file mode 100755 index 56284c35a42..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/s_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_calendar.png b/TMessagesProj/src/main/res/drawable-hdpi/search_calendar.png new file mode 100755 index 00000000000..81d8fdf6d94 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/search_calendar.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_dark.9.png b/TMessagesProj/src/main/res/drawable-hdpi/search_dark.9.png index a178a67f0b6..cbe5823ba6f 100644 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_dark.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/search_dark.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_dark_activated.9.png b/TMessagesProj/src/main/res/drawable-hdpi/search_dark_activated.9.png index d5d4c78b0c5..5fb492d87e9 100644 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_dark_activated.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/search_dark_activated.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_down.png b/TMessagesProj/src/main/res/drawable-hdpi/search_down.png old mode 100644 new mode 100755 index 756bae2fd95..59b3e1960a7 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_down.png and b/TMessagesProj/src/main/res/drawable-hdpi/search_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_share.png b/TMessagesProj/src/main/res/drawable-hdpi/search_share.png deleted file mode 100755 index e5bf1d95cc9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_share.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/search_up.png b/TMessagesProj/src/main/res/drawable-hdpi/search_up.png old mode 100644 new mode 100755 index e19ca0cd36a..cfb07e55d2f Binary files a/TMessagesProj/src/main/res/drawable-hdpi/search_up.png and b/TMessagesProj/src/main/res/drawable-hdpi/search_up.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/shadowdown.png b/TMessagesProj/src/main/res/drawable-hdpi/shadowdown.png new file mode 100644 index 00000000000..1b591ce3b79 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/shadowdown.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/shortcut_compose.png b/TMessagesProj/src/main/res/drawable-hdpi/shortcut_compose.png new file mode 100755 index 00000000000..a6e9202eb47 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/shortcut_compose.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/shortcut_user.png b/TMessagesProj/src/main/res/drawable-hdpi/shortcut_user.png new file mode 100755 index 00000000000..c898b5cb0ac Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/shortcut_user.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_big.png b/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_big.png new file mode 100755 index 00000000000..5802f30d697 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_small.png b/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_small.png new file mode 100755 index 00000000000..0ddb0e59f75 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/slide_dot_small.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/slidearrow.png b/TMessagesProj/src/main/res/drawable-hdpi/slidearrow.png index b04164901d6..f7c95e8e364 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/slidearrow.png and b/TMessagesProj/src/main/res/drawable-hdpi/slidearrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/smiles_popup.9.png b/TMessagesProj/src/main/res/drawable-hdpi/smiles_popup.9.png old mode 100644 new mode 100755 index 104e781c639..ce757c86784 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/smiles_popup.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/smiles_popup.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/sticker_added.png b/TMessagesProj/src/main/res/drawable-hdpi/sticker_added.png index 9788cfe7d52..8ee9dabed42 100755 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/sticker_added.png and b/TMessagesProj/src/main/res/drawable-hdpi/sticker_added.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickercounter.9.png b/TMessagesProj/src/main/res/drawable-hdpi/stickercounter.9.png deleted file mode 100755 index e17364d60c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/stickercounter.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_left.9.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_left.9.png index c50efac83e1..6afd3266af1 100644 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_left.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_left.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_right.9.png b/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_right.9.png index 068039da0f5..c87f02450e2 100644 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_right.9.png and b/TMessagesProj/src/main/res/drawable-hdpi/stickers_back_right.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/theme_picker.png b/TMessagesProj/src/main/res/drawable-hdpi/theme_picker.png new file mode 100755 index 00000000000..289abe83ab1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/theme_picker.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/tool_cropfix_active.png b/TMessagesProj/src/main/res/drawable-hdpi/tool_cropfix_active.png new file mode 100644 index 00000000000..d551d6f7d95 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/tool_cropfix_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/tooltip.9.png b/TMessagesProj/src/main/res/drawable-hdpi/tooltip.9.png deleted file mode 100644 index dba0f15985b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-hdpi/tooltip.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/verified_area.png b/TMessagesProj/src/main/res/drawable-hdpi/verified_area.png new file mode 100755 index 00000000000..ad8dc9f0679 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/verified_area.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/verified_check.png b/TMessagesProj/src/main/res/drawable-hdpi/verified_check.png new file mode 100755 index 00000000000..105abc93dc3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/verified_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_1080.png b/TMessagesProj/src/main/res/drawable-hdpi/video_1080.png new file mode 100755 index 00000000000..0851e53cd5b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_1080.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_240.png b/TMessagesProj/src/main/res/drawable-hdpi/video_240.png new file mode 100755 index 00000000000..31396f872b4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_240.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_360.png b/TMessagesProj/src/main/res/drawable-hdpi/video_360.png new file mode 100755 index 00000000000..f3a34f9f0ef Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_360.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_480.png b/TMessagesProj/src/main/res/drawable-hdpi/video_480.png new file mode 100755 index 00000000000..6f2c5c1c144 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_480.png differ diff --git a/TMessagesProj/src/main/res/drawable-hdpi/video_720.png b/TMessagesProj/src/main/res/drawable-hdpi/video_720.png new file mode 100755 index 00000000000..f2f6bf1c875 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-hdpi/video_720.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/add_btn.9.png b/TMessagesProj/src/main/res/drawable-mdpi/add_btn.9.png deleted file mode 100644 index 6cac0bf80f4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/add_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/add_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-mdpi/add_btn_pressed.9.png deleted file mode 100644 index 0945c91ba05..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/add_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/addmember.png b/TMessagesProj/src/main/res/drawable-mdpi/addmember.png deleted file mode 100755 index 439b82d4116..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/addmember.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png index d676d01e480..23e2a094b80 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png and b/TMessagesProj/src/main/res/drawable-mdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/admin_star2.png b/TMessagesProj/src/main/res/drawable-mdpi/admin_star2.png deleted file mode 100755 index 150fb215963..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/admin_star2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/assign_manager.png b/TMessagesProj/src/main/res/drawable-mdpi/assign_manager.png deleted file mode 100755 index 40800d94b31..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/assign_manager.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/audiosend_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/audiosend_pause.png index df9cc8f14f4..cc46f565391 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/audiosend_pause.png and b/TMessagesProj/src/main/res/drawable-mdpi/audiosend_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/audiosend_play.png b/TMessagesProj/src/main/res/drawable-mdpi/audiosend_play.png index b2352c30aa1..ec37d365f6f 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/audiosend_play.png and b/TMessagesProj/src/main/res/drawable-mdpi/audiosend_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bluecircle.png b/TMessagesProj/src/main/res/drawable-mdpi/bluecircle.png deleted file mode 100644 index 18642388b7d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bluecircle.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_file.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_file.png index 44b53c44273..38f4b9d00ec 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_file.png and b/TMessagesProj/src/main/res/drawable-mdpi/bot_file.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_info.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_info.png deleted file mode 100644 index d25ea6e3e45..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_info.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard.png old mode 100644 new mode 100755 index 0e9f59a4cb4..cf775a2d814 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard.png and b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard2.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard2.png index 200acbe00cf..4631330cbb8 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard2.png and b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard2.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button.9.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button.9.png deleted file mode 100644 index d84e518813f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button_pressed.9.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button_pressed.9.png deleted file mode 100644 index 09472087016..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_keyboard_button_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_list.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_list.png deleted file mode 100755 index 41a7ae2f029..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_location.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_location.png index e18474ab1e8..f2b4113de09 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_location.png and b/TMessagesProj/src/main/res/drawable-mdpi/bot_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/bot_music.png b/TMessagesProj/src/main/res/drawable-mdpi/bot_music.png index 2a9f2e53222..36c1bb4ada7 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/bot_music.png and b/TMessagesProj/src/main/res/drawable-mdpi/bot_music.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/broadcast2.png b/TMessagesProj/src/main/res/drawable-mdpi/broadcast2.png deleted file mode 100644 index 57e7d3b31c8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/broadcast2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/broadcast3.png b/TMessagesProj/src/main/res/drawable-mdpi/broadcast3.png old mode 100644 new mode 100755 index fb81e5ebf9d..4024613dbc1 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/broadcast3.png and b/TMessagesProj/src/main/res/drawable-mdpi/broadcast3.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/broadcast4.png b/TMessagesProj/src/main/res/drawable-mdpi/broadcast4.png deleted file mode 100644 index dda48af2bab..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/broadcast4.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chat_badge.9.png b/TMessagesProj/src/main/res/drawable-mdpi/chat_badge.9.png deleted file mode 100755 index 493995bcd17..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/chat_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chats_clear.png b/TMessagesProj/src/main/res/drawable-mdpi/chats_clear.png new file mode 100755 index 00000000000..cfe723c4c9c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/chats_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chats_delete.png b/TMessagesProj/src/main/res/drawable-mdpi/chats_delete.png new file mode 100755 index 00000000000..92f536ddf89 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/chats_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chats_leave.png b/TMessagesProj/src/main/res/drawable-mdpi/chats_leave.png new file mode 100755 index 00000000000..78778c504f7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/chats_leave.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chats_pin.png b/TMessagesProj/src/main/res/drawable-mdpi/chats_pin.png new file mode 100755 index 00000000000..2953e933af7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/chats_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/chats_unpin.png b/TMessagesProj/src/main/res/drawable-mdpi/chats_unpin.png new file mode 100755 index 00000000000..f579db21922 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/chats_unpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/check_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/check_blue.png deleted file mode 100755 index c37aa71b2fd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/check_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/check_list.png b/TMessagesProj/src/main/res/drawable-mdpi/check_list.png deleted file mode 100644 index 4061ffae9c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/check_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/check_profile.png b/TMessagesProj/src/main/res/drawable-mdpi/check_profile.png deleted file mode 100644 index ada9e496ddd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/check_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/cloud.png b/TMessagesProj/src/main/res/drawable-mdpi/cloud.png old mode 100644 new mode 100755 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/compose_panel.9.png b/TMessagesProj/src/main/res/drawable-mdpi/compose_panel.9.png deleted file mode 100644 index d01a64efb69..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/compose_panel.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/compose_panel_shadow.png b/TMessagesProj/src/main/res/drawable-mdpi/compose_panel_shadow.png new file mode 100755 index 00000000000..5a5b8e5e785 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/compose_panel_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/contact_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/contact_blue.png deleted file mode 100755 index 1b2beced2f1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/contact_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/contact_green.png b/TMessagesProj/src/main/res/drawable-mdpi/contact_green.png deleted file mode 100755 index 0cb733438d5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/contact_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/del_btn.9.png b/TMessagesProj/src/main/res/drawable-mdpi/del_btn.9.png deleted file mode 100644 index f16fbbb54b3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/del_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/del_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-mdpi/del_btn_pressed.9.png deleted file mode 100644 index 96701643e5e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/del_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/delete.png b/TMessagesProj/src/main/res/drawable-mdpi/delete.png new file mode 100644 index 00000000000..08aacdbaa03 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/delete_reply.png b/TMessagesProj/src/main/res/drawable-mdpi/delete_reply.png deleted file mode 100755 index 8808768e4ea..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/delete_reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/devices.png b/TMessagesProj/src/main/res/drawable-mdpi/devices.png index b6bd7a72c0f..9422ede4bed 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/devices.png and b/TMessagesProj/src/main/res/drawable-mdpi/devices.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge.9.png b/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge.9.png deleted file mode 100755 index bf14151a654..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge2.9.png b/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge2.9.png deleted file mode 100755 index 26856e0f327..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_badge2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_check.png b/TMessagesProj/src/main/res/drawable-mdpi/dialogs_check.png deleted file mode 100755 index 072cce7cd9a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_check.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_halfcheck.png b/TMessagesProj/src/main/res/drawable-mdpi/dialogs_halfcheck.png deleted file mode 100755 index 0a0d29d9f5f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_halfcheck.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_warning.png b/TMessagesProj/src/main/res/drawable-mdpi/dialogs_warning.png deleted file mode 100755 index 0c0f3fb5f7b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/dialogs_warning.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b.png deleted file mode 100755 index a65aa9da9fa..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b_s.png deleted file mode 100755 index 0d86fadaca9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_g.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_g.png deleted file mode 100755 index 1c1921a82ba..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_actions_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_big.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_big.png new file mode 100755 index 00000000000..71c5a475610 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/doc_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_blue.png deleted file mode 100755 index 6412768dfab..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_blue_s.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_blue_s.png deleted file mode 100755 index 361da2e7fd4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_blue_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doc_green.png b/TMessagesProj/src/main/res/drawable-mdpi/doc_green.png deleted file mode 100755 index d9bbb09b21f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doc_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b.png b/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b.png deleted file mode 100644 index 984b0f36f40..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b_s.png deleted file mode 100755 index fdf95793c96..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_g.png b/TMessagesProj/src/main/res/drawable-mdpi/doccancel_g.png deleted file mode 100644 index d33f5d0bd13..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/doccancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docload_b.png b/TMessagesProj/src/main/res/drawable-mdpi/docload_b.png deleted file mode 100644 index b6f8de393f8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docload_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/docload_b_s.png deleted file mode 100755 index 3b7fa820d01..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docload_g.png b/TMessagesProj/src/main/res/drawable-mdpi/docload_g.png deleted file mode 100644 index bd998016d25..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docpause_b.png b/TMessagesProj/src/main/res/drawable-mdpi/docpause_b.png deleted file mode 100644 index 485fbaca904..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docpause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docpause_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/docpause_b_s.png deleted file mode 100755 index dfe863db538..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docpause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/docpause_g.png b/TMessagesProj/src/main/res/drawable-mdpi/docpause_g.png deleted file mode 100644 index c909c00be35..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/docpause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/edit_done.png b/TMessagesProj/src/main/res/drawable-mdpi/edit_done.png new file mode 100755 index 00000000000..d1de075dbc1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/edit_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/edit_doneblue.png b/TMessagesProj/src/main/res/drawable-mdpi/edit_doneblue.png deleted file mode 100755 index 494f4f85a70..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/edit_doneblue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b.png deleted file mode 100755 index 463d64525b5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel.png deleted file mode 100755 index d2e69537563..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel_s.png deleted file mode 100755 index 0535435fbfb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b_load.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b_load.png deleted file mode 100755 index 4038ef607c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b_load_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b_load_s.png deleted file mode 100755 index 68251f82f69..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_b_s.png deleted file mode 100755 index 970f88a98ab..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g.png deleted file mode 100755 index 7e3ba259495..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel.png deleted file mode 100755 index 8697a7efda0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel_s.png deleted file mode 100755 index 16c52423f81..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g_load.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g_load.png deleted file mode 100755 index 9a760469d9a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g_load_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g_load_s.png deleted file mode 100755 index dd804226778..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/file_g_s.png b/TMessagesProj/src/main/res/drawable-mdpi/file_g_s.png deleted file mode 100755 index e72c909d9cc..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/file_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating.png b/TMessagesProj/src/main/res/drawable-mdpi/floating.png deleted file mode 100755 index 465b28eb795..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png deleted file mode 100755 index 35eb5bbb678..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png deleted file mode 100755 index 652630fc41d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png deleted file mode 100755 index fbcb4da6bd2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating3_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png index 0e74dc260b5..0f3911b1778 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png deleted file mode 100755 index 650c4626eda..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png index 8d5f07d4ef5..f841074500c 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png and b/TMessagesProj/src/main/res/drawable-mdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png deleted file mode 100755 index d013320f123..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/floating_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow.png new file mode 100755 index 00000000000..719152f8592 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow_profile.png b/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow_profile.png new file mode 100755 index 00000000000..d30c7a0eb66 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/floating_shadow_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/forward_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/forward_blue.png deleted file mode 100755 index 992a34f5be9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/forward_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/foursquare.png b/TMessagesProj/src/main/res/drawable-mdpi/foursquare.png old mode 100644 new mode 100755 index e13d176419f..845b3c9ad7c Binary files a/TMessagesProj/src/main/res/drawable-mdpi/foursquare.png and b/TMessagesProj/src/main/res/drawable-mdpi/foursquare.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/greydivider.9.png b/TMessagesProj/src/main/res/drawable-mdpi/greydivider.9.png old mode 100644 new mode 100755 index 48a2125970f..f37c794c8be Binary files a/TMessagesProj/src/main/res/drawable-mdpi/greydivider.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/greydivider.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/greydivider_bottom.9.png b/TMessagesProj/src/main/res/drawable-mdpi/greydivider_bottom.9.png old mode 100644 new mode 100755 index d8fc75bc81c..87c76fba21a Binary files a/TMessagesProj/src/main/res/drawable-mdpi/greydivider_bottom.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/greydivider_bottom.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/greydivider_top.9.png b/TMessagesProj/src/main/res/drawable-mdpi/greydivider_top.9.png old mode 100644 new mode 100755 index 1faeb263600..997f15345aa Binary files a/TMessagesProj/src/main/res/drawable-mdpi/greydivider_top.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/greydivider_top.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/hd_off.png b/TMessagesProj/src/main/res/drawable-mdpi/hd_off.png deleted file mode 100755 index fdde410c153..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/hd_off.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/hd_on.png b/TMessagesProj/src/main/res/drawable-mdpi/hd_on.png deleted file mode 100755 index 7476a43dfa3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/hd_on.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/header_timer.png b/TMessagesProj/src/main/res/drawable-mdpi/header_timer.png deleted file mode 100755 index 66aee6c2e3a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/header_timer.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/header_timer2.png b/TMessagesProj/src/main/res/drawable-mdpi/header_timer2.png deleted file mode 100755 index 5b63eb2ad98..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/header_timer2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach.png old mode 100644 new mode 100755 index bcd6ffceecd..5d25c50babf Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach3.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach3.png deleted file mode 100755 index b18e0362b17..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_attach3.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_back_grey.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_back_grey.png deleted file mode 100755 index 44e1c11bb60..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_back_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_copy.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_copy.png new file mode 100755 index 00000000000..661cd9a1754 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_delete.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_delete.png new file mode 100755 index 00000000000..9d8104c01f6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_doc.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_doc.png deleted file mode 100755 index ce1100b80b4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_doc.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done.png new file mode 100755 index 00000000000..428dc8454f3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done_gray.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done_gray.png deleted file mode 100755 index 47beed22b8d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_done_gray.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_forward.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_forward.png new file mode 100755 index 00000000000..6d24be15d95 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_copy.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_copy.png deleted file mode 100755 index f8bfd187fd1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_copy.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_delete.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_delete.png deleted file mode 100755 index e2370b3b46b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_delete.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_forward.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_forward.png deleted file mode 100755 index 5a0f86d7390..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_fwd_forward.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_new.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_new.png index 5d8ee2cc94b..a877390bd94 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_new.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_new.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_reply.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_reply.png index a8d82941166..5e66fa9695e 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_reply.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_ab_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_again.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_again.png new file mode 100644 index 00000000000..cb77bb1f6e6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_again.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_againinline.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_againinline.png new file mode 100644 index 00000000000..0d1d8e73de9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_againinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_bluetooth_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_bluetooth_white_24dp.png new file mode 100755 index 00000000000..27a8a719f45 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_bluetooth_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png new file mode 100755 index 00000000000..378272ffc15 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_36dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_36dp.png new file mode 100755 index 00000000000..625b827c44e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_end_white_36dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_call_made_green_18dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_made_green_18dp.png new file mode 100755 index 00000000000..68dd5ef7a21 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_made_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_call_received_green_18dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_received_green_18dp.png new file mode 100755 index 00000000000..dbd3e206963 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_received_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_call_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_white_24dp.png new file mode 100755 index 00000000000..77f9de5e3cc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_call_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_chat_bubble_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_chat_bubble_white_24dp.png new file mode 100755 index 00000000000..317959bdef3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_chat_bubble_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_create.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_create.png deleted file mode 100644 index 289f7933e46..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_create.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png index 3000004c811..4d64f01e046 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png index 97bd0b6155f..903927e720f 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_gofullscreen.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_gofullscreen.png new file mode 100644 index 00000000000..678d86c0646 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_gofullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_goinline.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_goinline.png new file mode 100644 index 00000000000..ab8b13d87c6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_goinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_mic_off_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_mic_off_white_24dp.png new file mode 100755 index 00000000000..15094d88424 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_mic_off_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png new file mode 100644 index 00000000000..141a0fe3336 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_more.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_gif.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_gif.png index fa4dd605291..b994a09e91d 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_gif.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_kb.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_kb.png index 57919b9acef..50d1439d67b 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_kb.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_kb.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_smiles.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_smiles.png index 3ea0ee195be..c533531437d 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_smiles.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_smiles.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_stickers.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_stickers.png index 814f345f5b8..9ec2f8599c7 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_stickers.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_video.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_video.png new file mode 100755 index 00000000000..dfa24c677ee Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_msg_panel_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_outfullscreen.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_outfullscreen.png new file mode 100644 index 00000000000..9ac83671528 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_outfullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_outinline.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_outinline.png new file mode 100644 index 00000000000..59ffad30cec Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_outinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_pause.png new file mode 100644 index 00000000000..a96530f537b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_pauseinline.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_pauseinline.png new file mode 100644 index 00000000000..e37cba7a869 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_pauseinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_phone_in_talk_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_phone_in_talk_white_24dp.png new file mode 100755 index 00000000000..e6f98af95bf Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_phone_in_talk_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_play.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_play.png new file mode 100644 index 00000000000..458981e7349 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_playinline.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_playinline.png new file mode 100644 index 00000000000..9e903a4b450 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_playinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star.png new file mode 100644 index 00000000000..c80fa717dfe Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star_filled.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star_filled.png new file mode 100644 index 00000000000..b509c0ba4f1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_rating_star_filled.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_send.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_send.png index 312ce750da2..982a7e52257 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_send.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_share_article.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_share_article.png new file mode 100644 index 00000000000..046f3461c52 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_share_article.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_share_video.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_share_video.png new file mode 100644 index 00000000000..ed27722cf06 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_share_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car.png index 7c0aab37bea..1750f01c5d1 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car_active.png deleted file mode 100755 index 1093eb6609a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_car_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food.png index f8e35bfbe0d..a11434a35c2 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food_active.png deleted file mode 100755 index 503e2f22c43..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_food_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature.png index bdf0cfcd51d..1e7bbf40665 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature_active.png deleted file mode 100755 index 11bc8d5c390..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_nature_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects.png index 305f174cecf..a31f51e1091 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects_active.png deleted file mode 100755 index 7c4cb83acd6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_objects_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent.png index f789cf27743..492879ca2ed 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent_active.png deleted file mode 100755 index e6ef6872a6e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_recent_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile.png index dd0f19bd34a..ef295e9e65a 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile_active.png deleted file mode 100755 index 7fac7931b1b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_smile_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers.png index 6d42e6aa1c9..7796db2f399 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers_active.png deleted file mode 100755 index e4916a0b935..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles2_stickers_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace.png index d63ba596abd..3ca28d42985 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace_active.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace_active.png deleted file mode 100755 index 45140c69f7f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_backspace_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_gif.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_gif.png index 4cc8179c193..2e5e499d180 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_gif.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_settings.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_settings.png index 5a877b4e887..b5057c249ca 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_settings.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_trend.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_trend.png index 22865acf9ed..1b736185624 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_trend.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_smiles_trend.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png index dcef21d24c6..6b7f4214920 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png index 3dc4bd980b4..69faf7c210d 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png and b/TMessagesProj/src/main/res/drawable-mdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png new file mode 100755 index 00000000000..7cfd4c7b88b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_volume_up_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png new file mode 100755 index 00000000000..21ba9217902 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/ic_vpn_key_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/link.png b/TMessagesProj/src/main/res/drawable-mdpi/link.png deleted file mode 100755 index 96b098ffff6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/link.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_bot.png b/TMessagesProj/src/main/res/drawable-mdpi/list_bot.png new file mode 100755 index 00000000000..a563200809b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_bot.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_broadcast.png b/TMessagesProj/src/main/res/drawable-mdpi/list_broadcast.png index f772b53ecb5..72e0fa3e8a7 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_broadcast.png and b/TMessagesProj/src/main/res/drawable-mdpi/list_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_check.png b/TMessagesProj/src/main/res/drawable-mdpi/list_check.png new file mode 100755 index 00000000000..da4b4c83231 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_group.png b/TMessagesProj/src/main/res/drawable-mdpi/list_group.png index 3d5818c4a44..896d41b5653 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_group.png and b/TMessagesProj/src/main/res/drawable-mdpi/list_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_halfcheck.png b/TMessagesProj/src/main/res/drawable-mdpi/list_halfcheck.png new file mode 100755 index 00000000000..6fde716cb7e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_longpressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-mdpi/list_longpressed_holo_light.9.png deleted file mode 100644 index 3226ab760aa..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_longpressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_mute.png b/TMessagesProj/src/main/res/drawable-mdpi/list_mute.png new file mode 100755 index 00000000000..756851163f9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_mute.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_pin.png b/TMessagesProj/src/main/res/drawable-mdpi/list_pin.png new file mode 100755 index 00000000000..1d0b1680638 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_pressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-mdpi/list_pressed_holo_light.9.png deleted file mode 100644 index 061904c42c1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_pressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_secret.png b/TMessagesProj/src/main/res/drawable-mdpi/list_secret.png index 4fc91b4d95e..123d3a8d642 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_secret.png and b/TMessagesProj/src/main/res/drawable-mdpi/list_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_selector_disabled_holo_light.9.png b/TMessagesProj/src/main/res/drawable-mdpi/list_selector_disabled_holo_light.9.png deleted file mode 100644 index 42cb6463e4c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/list_selector_disabled_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/list_warning_sign.png b/TMessagesProj/src/main/res/drawable-mdpi/list_warning_sign.png new file mode 100755 index 00000000000..6b2b30e5723 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/list_warning_sign.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/location2.png b/TMessagesProj/src/main/res/drawable-mdpi/location2.png deleted file mode 100644 index 3f0abf8b467..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/location2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/location_b.9.png b/TMessagesProj/src/main/res/drawable-mdpi/location_b.9.png deleted file mode 100644 index c3f9b241779..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/location_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/location_g.9.png b/TMessagesProj/src/main/res/drawable-mdpi/location_g.9.png deleted file mode 100644 index b84039f32c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/location_g.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/managers.png b/TMessagesProj/src/main/res/drawable-mdpi/managers.png index 71c809f14bd..b669878840d 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/managers.png and b/TMessagesProj/src/main/res/drawable-mdpi/managers.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png index 6ea401f9c6d..49b64c18552 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png index e6c16824574..a87d0cf920d 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png and b/TMessagesProj/src/main/res/drawable-mdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_broadcast.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_broadcast.png index ff619d43b6b..9a4acf68a04 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_broadcast.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_calls.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_calls.png new file mode 100755 index 00000000000..909fcccea89 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/menu_calls.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_contacts.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_contacts.png index d068de48351..1e6013d6827 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_contacts.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_contacts.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_help.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_help.png index 73fa5a6b5b2..a3c0e40baab 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_help.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_help.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_invite.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_invite.png index 6f2d41a505a..89e67e2e815 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_invite.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_invite.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_newgroup.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_newgroup.png index 7b5185f955b..dc51fb9fbc0 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_newgroup.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_newgroup.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_secret.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_secret.png index c2417590153..0c6cc42dbc7 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_secret.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_settings.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_settings.png index c3d57c2109a..8a9422d3f83 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/menu_settings.png and b/TMessagesProj/src/main/res/drawable-mdpi/menu_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mic.png b/TMessagesProj/src/main/res/drawable-mdpi/mic.png index 2b9dec25860..ab5f4c3eb10 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/mic.png and b/TMessagesProj/src/main/res/drawable-mdpi/mic.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mic_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/mic_pressed.png deleted file mode 100644 index ed471652ed3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/mic_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_close.png index 1f5816b6ffa..7f74b015644 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_close.png and b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_pause.png index 295f9addc97..0f608a01435 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_pause.png and b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_play.png b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_play.png index 10b948e60dd..28116e88cce 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_play.png and b/TMessagesProj/src/main/res/drawable-mdpi/miniplayer_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_actions.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_actions.png new file mode 100755 index 00000000000..b683c96de0f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_actions.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_check.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_check.png index 2fdad7a389c..f56fd66a33c 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_check.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_check_w.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_check_w.png deleted file mode 100755 index f2448a0a5a0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_check_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_clock.png index 4bb0fe3df48..2182fb4e140 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_clock.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2.png deleted file mode 100755 index 94a136ebd34..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2_s.png deleted file mode 100755 index 11805a2e62c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock2_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock_photo.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_clock_photo.png deleted file mode 100755 index 0c3c94421de..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_clock_photo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_contact.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_contact.png new file mode 100755 index 00000000000..90d9388e10e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_contact.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck.png index 010da7c5243..23ed5e7ce28 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck_w.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck_w.png deleted file mode 100755 index d5c1888a9a4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_halfcheck_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_in.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_in.9.png index 979a146be5f..1b993976d38 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_in.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_in.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo.9.png deleted file mode 100755 index 5ac724e541f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo_selected.9.png deleted file mode 100755 index 324c656711f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_selected.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_in_selected.9.png deleted file mode 100755 index 7425774eab9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_in_shadow.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_in_shadow.9.png new file mode 100755 index 00000000000..6a780bdbc49 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_in_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_instant.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_instant.png new file mode 100755 index 00000000000..1cacca81cdb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_instant.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_location.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_location.png new file mode 100755 index 00000000000..176d9a099e1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_out.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_out.9.png index 4e1c17baee3..869bf1479d3 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_out.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_out.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo.9.png deleted file mode 100755 index 1a759a33e45..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo_selected.9.png deleted file mode 100755 index 030a8a0a11b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_selected.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_out_selected.9.png deleted file mode 100755 index 388972c2209..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_out_shadow.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_out_shadow.9.png new file mode 100755 index 00000000000..047d9dd7b58 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_out_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_clear.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_clear.png new file mode 100755 index 00000000000..20f4fc40e09 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_forward.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_forward.png new file mode 100755 index 00000000000..ec68481927d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_link.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_link.png new file mode 100755 index 00000000000..be6d04d9eee Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_link.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_reply.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_reply.png new file mode 100755 index 00000000000..4cd49dd5f23 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_panel_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_photo.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_photo.9.png new file mode 100755 index 00000000000..d8c59ffe424 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_photo.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_photo_shadow.9.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_photo_shadow.9.png new file mode 100755 index 00000000000..50d35cdf9af Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_photo_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png new file mode 100755 index 00000000000..920a8969cb9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_m.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_m.png new file mode 100755 index 00000000000..668dc2cc642 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png new file mode 100755 index 00000000000..775a201e775 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_cancel_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_file_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_file_s.png new file mode 100755 index 00000000000..2666ccc289e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_file_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_gif_m.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_gif_m.png new file mode 100755 index 00000000000..a82b2fdd934 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_gif_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png new file mode 100755 index 00000000000..fb790662b25 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_m.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_m.png new file mode 100755 index 00000000000..2e8cb31ba95 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png new file mode 100755 index 00000000000..ce4f7148011 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_load_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_m.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_m.png new file mode 100755 index 00000000000..b8361c70a96 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png new file mode 100755 index 00000000000..f95f61f402d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_pause_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png new file mode 100755 index 00000000000..cbb4158614d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_m.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_m.png new file mode 100755 index 00000000000..922864b5254 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png new file mode 100755 index 00000000000..0a40021c2df Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/msg_round_play_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/post_views_w.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_views.png similarity index 100% rename from TMessagesProj/src/main/res/drawable-mdpi/post_views_w.png rename to TMessagesProj/src/main/res/drawable-mdpi/msg_views.png diff --git a/TMessagesProj/src/main/res/drawable-mdpi/msg_warning.png b/TMessagesProj/src/main/res/drawable-mdpi/msg_warning.png old mode 100644 new mode 100755 index 968a0d27c4d..0c868b862e2 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/msg_warning.png and b/TMessagesProj/src/main/res/drawable-mdpi/msg_warning.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png deleted file mode 100755 index 365d4cc78ed..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/mute_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png deleted file mode 100755 index fb6674649d2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/mute_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/myloc_on.png b/TMessagesProj/src/main/res/drawable-mdpi/myloc_on.png old mode 100644 new mode 100755 index a86dd157011..5484c902dd4 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/myloc_on.png and b/TMessagesProj/src/main/res/drawable-mdpi/myloc_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/nocover.png b/TMessagesProj/src/main/res/drawable-mdpi/nocover.png old mode 100644 new mode 100755 index abbedfc21be..30db7ed46a8 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/nocover.png and b/TMessagesProj/src/main/res/drawable-mdpi/nocover.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_1h.png b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_1h.png new file mode 100755 index 00000000000..6e828659757 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_1h.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_2d.png b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_2d.png new file mode 100755 index 00000000000..1b3f8356b86 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_2d.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_custom.png b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_custom.png new file mode 100755 index 00000000000..dfa28dd0c6d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_custom.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_off.png b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_off.png new file mode 100755 index 00000000000..bf3a95e613e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_on.png b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_on.png new file mode 100755 index 00000000000..d0e495615c8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/notifications_s_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-mdpi/notify_members_off.png index d6bb409e842..947ddd999a0 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-mdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-mdpi/notify_members_on.png index 001f109bd80..48c52c5f27c 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-mdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/numberpicker_selection_divider.9.png b/TMessagesProj/src/main/res/drawable-mdpi/numberpicker_selection_divider.9.png deleted file mode 100644 index 076fc166421..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/numberpicker_selection_divider.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pagedown.png b/TMessagesProj/src/main/res/drawable-mdpi/pagedown.png index 98e51e823af..ed6168eac10 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pagedown.png and b/TMessagesProj/src/main/res/drawable-mdpi/pagedown.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pagedown_shadow.png b/TMessagesProj/src/main/res/drawable-mdpi/pagedown_shadow.png new file mode 100755 index 00000000000..7d9d6470aef Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/pagedown_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pause_b.png b/TMessagesProj/src/main/res/drawable-mdpi/pause_b.png deleted file mode 100755 index 8608ecd68b9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pause_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/pause_b_s.png deleted file mode 100755 index 80fafabb376..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pause_g.png b/TMessagesProj/src/main/res/drawable-mdpi/pause_g.png deleted file mode 100755 index 08127935924..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pause_g_s.png b/TMessagesProj/src/main/res/drawable-mdpi/pause_g_s.png deleted file mode 100755 index b711a313830..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pause_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png old mode 100644 new mode 100755 index 99e036d89d0..20ba8c38a44 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png and b/TMessagesProj/src/main/res/drawable-mdpi/phone_change.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/phone_grey.png b/TMessagesProj/src/main/res/drawable-mdpi/phone_grey.png deleted file mode 100755 index 4d0d08081eb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/phone_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel.png deleted file mode 100755 index 6014372e594..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b.png deleted file mode 100755 index be189093f86..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b_s.png deleted file mode 100755 index 2d4328698d8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g.png deleted file mode 100755 index 9493aa82155..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g_s.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g_s.png deleted file mode 100755 index 7d4af0439d4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/photocancel_pressed.png deleted file mode 100755 index 491b3d43468..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photocancel_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photogif.png b/TMessagesProj/src/main/res/drawable-mdpi/photogif.png deleted file mode 100755 index 2ee7a49d2d7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photogif.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photogif_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/photogif_pressed.png deleted file mode 100755 index 64af18ac8c7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photogif_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload.png deleted file mode 100755 index 1f320b2d8b6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload_b.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload_b.png deleted file mode 100755 index fb97fc7c9d0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload_b_s.png deleted file mode 100755 index be2dfb74fbd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload_g.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload_g.png deleted file mode 100755 index 7321550e4e7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload_g_s.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload_g_s.png deleted file mode 100755 index 2411e380e07..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/photoload_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/photoload_pressed.png deleted file mode 100755 index 87810072f20..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/photoload_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pin.png b/TMessagesProj/src/main/res/drawable-mdpi/pin.png old mode 100644 new mode 100755 index ce126ca8c43..8aba83c90ec Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pin.png and b/TMessagesProj/src/main/res/drawable-mdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png old mode 100644 new mode 100755 index 6702c4be695..4a9ebd1933f Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_back.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png old mode 100644 new mode 100755 index 08df043ef20..2252cc911d6 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_next_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_next_pressed.png deleted file mode 100644 index fc645c14778..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_next_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png old mode 100644 new mode 100755 index 1bf3d4abf14..f2e357b1b2d Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_pause_pressed.png deleted file mode 100644 index b8e61746fa3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png old mode 100644 new mode 100755 index 31fce9bc1a0..330e5f4f2a4 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_play_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_play_pressed.png deleted file mode 100644 index d8ca3eb74c9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png old mode 100644 new mode 100755 index 1b52aca086b..13bd58e9e5b Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_previous_pressed.png deleted file mode 100644 index 1b34ae6fe58..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_previous_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png old mode 100644 new mode 100755 index c62fb6c859e..5c196f2697d Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png new file mode 100755 index 00000000000..36d40fa18e2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1_active.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1_active.png deleted file mode 100644 index 0b1a47433ef..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat1_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat_active.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat_active.png deleted file mode 100644 index 140c797bf0c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_repeat_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png old mode 100644 new mode 100755 index c22ab8e3fc4..11a6e7d6f37 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle_active.png b/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle_active.png deleted file mode 100644 index 8e3888ddc70..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/pl_shuffle_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/place_x.png b/TMessagesProj/src/main/res/drawable-mdpi/place_x.png old mode 100644 new mode 100755 index 3e4869bf6d8..bf940d8cf24 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/place_x.png and b/TMessagesProj/src/main/res/drawable-mdpi/place_x.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/play_b.png b/TMessagesProj/src/main/res/drawable-mdpi/play_b.png deleted file mode 100755 index 247246ed331..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/play_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/play_b_s.png b/TMessagesProj/src/main/res/drawable-mdpi/play_b_s.png deleted file mode 100755 index c3e88682fb6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/play_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/play_g.png b/TMessagesProj/src/main/res/drawable-mdpi/play_g.png deleted file mode 100755 index 10f5caf0dae..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/play_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/play_g_s.png b/TMessagesProj/src/main/res/drawable-mdpi/play_g_s.png deleted file mode 100755 index 761fa5188cd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/play_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/playvideo.png b/TMessagesProj/src/main/res/drawable-mdpi/playvideo.png deleted file mode 100755 index f3a3a6c7e15..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/playvideo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/playvideo_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/playvideo_pressed.png deleted file mode 100755 index 5a361fe42d5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/playvideo_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/plus.png b/TMessagesProj/src/main/res/drawable-mdpi/plus.png index 2bb1bf4fe83..1a1d05e2a86 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/plus.png and b/TMessagesProj/src/main/res/drawable-mdpi/plus.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/post_views.png b/TMessagesProj/src/main/res/drawable-mdpi/post_views.png deleted file mode 100755 index 7004aa51f06..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/post_views.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/post_views_s.png b/TMessagesProj/src/main/res/drawable-mdpi/post_views_s.png deleted file mode 100755 index 2ecc48092ef..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/post_views_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/post_viewsg.png b/TMessagesProj/src/main/res/drawable-mdpi/post_viewsg.png deleted file mode 100755 index f59e64f881a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/post_viewsg.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/profile_info.png b/TMessagesProj/src/main/res/drawable-mdpi/profile_info.png new file mode 100755 index 00000000000..999d4a785bd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/profile_info.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/profile_list.png b/TMessagesProj/src/main/res/drawable-mdpi/profile_list.png index b27a67622a8..0d808d0ea8b 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/profile_list.png and b/TMessagesProj/src/main/res/drawable-mdpi/profile_list.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/profile_phone.png b/TMessagesProj/src/main/res/drawable-mdpi/profile_phone.png new file mode 100755 index 00000000000..031f1823e2f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/profile_phone.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/publish.png b/TMessagesProj/src/main/res/drawable-mdpi/publish.png deleted file mode 100755 index 4864c24db4a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/publish.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/publish_active.png b/TMessagesProj/src/main/res/drawable-mdpi/publish_active.png deleted file mode 100755 index bd5ce853c5c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/publish_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/recorded.9.png b/TMessagesProj/src/main/res/drawable-mdpi/recorded.9.png deleted file mode 100644 index efe1f2a7f4d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/recorded.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/reply.png b/TMessagesProj/src/main/res/drawable-mdpi/reply.png deleted file mode 100755 index 8e718b4f3d3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/s_pause_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/s_pause_pressed.png deleted file mode 100755 index 40e04fc77b2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/s_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/s_play_pressed.png b/TMessagesProj/src/main/res/drawable-mdpi/s_play_pressed.png deleted file mode 100755 index f5a735089c7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/s_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_calendar.png b/TMessagesProj/src/main/res/drawable-mdpi/search_calendar.png new file mode 100755 index 00000000000..a6f7bb7122a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/search_calendar.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_dark.9.png b/TMessagesProj/src/main/res/drawable-mdpi/search_dark.9.png index e7ae604062d..19ca07db034 100644 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_dark.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/search_dark.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_dark_activated.9.png b/TMessagesProj/src/main/res/drawable-mdpi/search_dark_activated.9.png index cf31f1c66f4..3617983da4d 100644 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_dark_activated.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/search_dark_activated.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_down.png b/TMessagesProj/src/main/res/drawable-mdpi/search_down.png old mode 100644 new mode 100755 index 405e4c6d377..7cabdcc9f71 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_down.png and b/TMessagesProj/src/main/res/drawable-mdpi/search_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_share.png b/TMessagesProj/src/main/res/drawable-mdpi/search_share.png deleted file mode 100755 index 9e0e2da3393..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_share.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/search_up.png b/TMessagesProj/src/main/res/drawable-mdpi/search_up.png old mode 100644 new mode 100755 index 5c2152e517f..50353171f97 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/search_up.png and b/TMessagesProj/src/main/res/drawable-mdpi/search_up.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/shadowdown.png b/TMessagesProj/src/main/res/drawable-mdpi/shadowdown.png new file mode 100644 index 00000000000..f2be9d95b40 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/shadowdown.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/shortcut_compose.png b/TMessagesProj/src/main/res/drawable-mdpi/shortcut_compose.png new file mode 100755 index 00000000000..b9c413abe6f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/shortcut_compose.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/shortcut_user.png b/TMessagesProj/src/main/res/drawable-mdpi/shortcut_user.png new file mode 100755 index 00000000000..b3c1325ffb7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/shortcut_user.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_big.png b/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_big.png new file mode 100755 index 00000000000..c9d55e980a3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_small.png b/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_small.png new file mode 100755 index 00000000000..975b49d87db Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/slide_dot_small.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/slidearrow.png b/TMessagesProj/src/main/res/drawable-mdpi/slidearrow.png index 9206c25524f..c796dddc436 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/slidearrow.png and b/TMessagesProj/src/main/res/drawable-mdpi/slidearrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/smiles_popup.9.png b/TMessagesProj/src/main/res/drawable-mdpi/smiles_popup.9.png old mode 100644 new mode 100755 index 70bf937283f..c3b212f57b8 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/smiles_popup.9.png and b/TMessagesProj/src/main/res/drawable-mdpi/smiles_popup.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/sticker_added.png b/TMessagesProj/src/main/res/drawable-mdpi/sticker_added.png index ebb63c0f12b..15d5a445fc9 100755 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/sticker_added.png and b/TMessagesProj/src/main/res/drawable-mdpi/sticker_added.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/stickercounter.9.png b/TMessagesProj/src/main/res/drawable-mdpi/stickercounter.9.png deleted file mode 100755 index 217e9f16c18..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/stickercounter.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/theme_picker.png b/TMessagesProj/src/main/res/drawable-mdpi/theme_picker.png new file mode 100755 index 00000000000..e7573e4a768 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/theme_picker.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/tool_cropfix_active.png b/TMessagesProj/src/main/res/drawable-mdpi/tool_cropfix_active.png new file mode 100644 index 00000000000..983953f21c9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/tool_cropfix_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/tooltip.9.png b/TMessagesProj/src/main/res/drawable-mdpi/tooltip.9.png deleted file mode 100644 index a0d9bd3511a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-mdpi/tooltip.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/verified_area.png b/TMessagesProj/src/main/res/drawable-mdpi/verified_area.png new file mode 100755 index 00000000000..e6e1c192f7f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/verified_area.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/verified_check.png b/TMessagesProj/src/main/res/drawable-mdpi/verified_check.png new file mode 100755 index 00000000000..5339caa1b98 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/verified_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_1080.png b/TMessagesProj/src/main/res/drawable-mdpi/video_1080.png new file mode 100755 index 00000000000..d82cbfecb9d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_1080.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_240.png b/TMessagesProj/src/main/res/drawable-mdpi/video_240.png new file mode 100755 index 00000000000..8c144c0e31e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_240.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_360.png b/TMessagesProj/src/main/res/drawable-mdpi/video_360.png new file mode 100755 index 00000000000..8bae8b4cc76 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_360.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_480.png b/TMessagesProj/src/main/res/drawable-mdpi/video_480.png new file mode 100755 index 00000000000..8dd87fbda93 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_480.png differ diff --git a/TMessagesProj/src/main/res/drawable-mdpi/video_720.png b/TMessagesProj/src/main/res/drawable-mdpi/video_720.png new file mode 100755 index 00000000000..1204f56c089 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-mdpi/video_720.png differ diff --git a/TMessagesProj/src/main/res/drawable-v21/fab_highlight_dark.xml b/TMessagesProj/src/main/res/drawable-v21/fab_highlight_dark.xml new file mode 100644 index 00000000000..61464aa894a --- /dev/null +++ b/TMessagesProj/src/main/res/drawable-v21/fab_highlight_dark.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-v21/floating_states.xml b/TMessagesProj/src/main/res/drawable-v21/floating_states.xml deleted file mode 100644 index f92baa8aa44..00000000000 --- a/TMessagesProj/src/main/res/drawable-v21/floating_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml b/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml deleted file mode 100644 index 41f9bd33219..00000000000 --- a/TMessagesProj/src/main/res/drawable-v21/floating_user_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable-v21/list_selector.xml b/TMessagesProj/src/main/res/drawable-v21/list_selector_ex.xml similarity index 100% rename from TMessagesProj/src/main/res/drawable-v21/list_selector.xml rename to TMessagesProj/src/main/res/drawable-v21/list_selector_ex.xml diff --git a/TMessagesProj/src/main/res/drawable-v21/list_selector_white.xml b/TMessagesProj/src/main/res/drawable-v21/list_selector_white.xml deleted file mode 100644 index a7802617f2d..00000000000 --- a/TMessagesProj/src/main/res/drawable-v21/list_selector_white.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/add_btn.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/add_btn.9.png deleted file mode 100644 index 337aabdd3c5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/add_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/add_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/add_btn_pressed.9.png deleted file mode 100644 index 9499d0ae651..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/add_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/addmember.png b/TMessagesProj/src/main/res/drawable-xhdpi/addmember.png deleted file mode 100755 index 8440ca18ff0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/addmember.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png index 370787e2fc0..3f67c008956 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png and b/TMessagesProj/src/main/res/drawable-xhdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/admin_star2.png b/TMessagesProj/src/main/res/drawable-xhdpi/admin_star2.png deleted file mode 100755 index 6afc194ecc9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/admin_star2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/assign_manager.png b/TMessagesProj/src/main/res/drawable-xhdpi/assign_manager.png deleted file mode 100755 index 114615b19aa..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/assign_manager.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_pause.png index 8078779dfaa..db13c0430e8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_pause.png and b/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_play.png index bfd90992e3b..b4764fc7f49 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_play.png and b/TMessagesProj/src/main/res/drawable-xhdpi/audiosend_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bluecircle.png b/TMessagesProj/src/main/res/drawable-xhdpi/bluecircle.png deleted file mode 100644 index 93c4e092bd8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bluecircle.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_file.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_file.png index 2191a418d68..35879ffb771 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_file.png and b/TMessagesProj/src/main/res/drawable-xhdpi/bot_file.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_info.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_info.png deleted file mode 100644 index 69c0388ef8a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_info.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard.png old mode 100644 new mode 100755 index af078978d58..5a46d4ce814 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard.png and b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard2.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard2.png index 6a672a795af..8503952930a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard2.png and b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard2.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button.9.png deleted file mode 100644 index 7f5fd2a7a1b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button_pressed.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button_pressed.9.png deleted file mode 100644 index 66fe8b30ee6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_keyboard_button_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_list.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_list.png deleted file mode 100755 index f8ed9caa5df..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_location.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_location.png index f66cdbd7b18..984b1c74902 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_location.png and b/TMessagesProj/src/main/res/drawable-xhdpi/bot_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/bot_music.png b/TMessagesProj/src/main/res/drawable-xhdpi/bot_music.png index e1aebb69f7e..262bb197509 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/bot_music.png and b/TMessagesProj/src/main/res/drawable-xhdpi/bot_music.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast2.png b/TMessagesProj/src/main/res/drawable-xhdpi/broadcast2.png deleted file mode 100644 index 38524b5782f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast3.png b/TMessagesProj/src/main/res/drawable-xhdpi/broadcast3.png old mode 100644 new mode 100755 index c95c69020ce..ddfd4b155dc Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast3.png and b/TMessagesProj/src/main/res/drawable-xhdpi/broadcast3.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast4.png b/TMessagesProj/src/main/res/drawable-xhdpi/broadcast4.png deleted file mode 100644 index 727ee6f411d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/broadcast4.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chat_badge.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/chat_badge.9.png deleted file mode 100755 index 51610d0c20d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/chat_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chats_clear.png b/TMessagesProj/src/main/res/drawable-xhdpi/chats_clear.png new file mode 100755 index 00000000000..a055aef09b6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/chats_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chats_delete.png b/TMessagesProj/src/main/res/drawable-xhdpi/chats_delete.png new file mode 100755 index 00000000000..b06fbc4a0af Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/chats_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chats_leave.png b/TMessagesProj/src/main/res/drawable-xhdpi/chats_leave.png new file mode 100755 index 00000000000..2ea2dfce3c6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/chats_leave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chats_pin.png b/TMessagesProj/src/main/res/drawable-xhdpi/chats_pin.png new file mode 100755 index 00000000000..bb657230673 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/chats_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/chats_unpin.png b/TMessagesProj/src/main/res/drawable-xhdpi/chats_unpin.png new file mode 100755 index 00000000000..161515bb830 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/chats_unpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/check_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/check_blue.png deleted file mode 100755 index 37b2d0472ee..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/check_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/check_list.png b/TMessagesProj/src/main/res/drawable-xhdpi/check_list.png deleted file mode 100644 index 7bc4bc303bd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/check_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/check_profile.png b/TMessagesProj/src/main/res/drawable-xhdpi/check_profile.png deleted file mode 100644 index 36efe5482fb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/check_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/cloud.png b/TMessagesProj/src/main/res/drawable-xhdpi/cloud.png old mode 100644 new mode 100755 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel.9.png deleted file mode 100644 index 559747bea6b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel_shadow.png b/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel_shadow.png new file mode 100755 index 00000000000..ea53d2d4d63 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/compose_panel_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/contact_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/contact_blue.png deleted file mode 100755 index 0691158e3e9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/contact_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/contact_green.png b/TMessagesProj/src/main/res/drawable-xhdpi/contact_green.png deleted file mode 100755 index d50586ac10b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/contact_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/del_btn.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/del_btn.9.png deleted file mode 100644 index 09fb4092174..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/del_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/del_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/del_btn_pressed.9.png deleted file mode 100644 index 280a2812250..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/del_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/delete.png b/TMessagesProj/src/main/res/drawable-xhdpi/delete.png new file mode 100644 index 00000000000..ff7e84c400d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/delete_reply.png b/TMessagesProj/src/main/res/drawable-xhdpi/delete_reply.png deleted file mode 100755 index b1378a6bb67..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/delete_reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/devices.png b/TMessagesProj/src/main/res/drawable-xhdpi/devices.png index 9b5663ff931..c2a90afc6d5 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/devices.png and b/TMessagesProj/src/main/res/drawable-xhdpi/devices.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge.9.png deleted file mode 100755 index 8dcdfe626ff..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge2.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge2.9.png deleted file mode 100755 index 6ddebe88102..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_badge2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_check.png b/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_check.png deleted file mode 100755 index 83e79ba58ab..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_check.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_halfcheck.png b/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_halfcheck.png deleted file mode 100755 index 4756cbf5cdc..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_halfcheck.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_warning.png b/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_warning.png deleted file mode 100755 index 83fd07ad736..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/dialogs_warning.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b.png deleted file mode 100755 index d36cc837568..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b_s.png deleted file mode 100755 index ac076eff89c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_g.png deleted file mode 100755 index bc4b1d285db..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_actions_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_big.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_big.png new file mode 100755 index 00000000000..c25d6a02b0a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/doc_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue.png deleted file mode 100755 index 6931af1d442..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue_s.png deleted file mode 100755 index ae8dcf51777..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_blue_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doc_green.png b/TMessagesProj/src/main/res/drawable-xhdpi/doc_green.png deleted file mode 100755 index 05380876acf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doc_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b.png deleted file mode 100644 index fefafdca4cb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b_s.png deleted file mode 100755 index c157d8543ae..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_g.png deleted file mode 100644 index d43a5fe19c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/doccancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docload_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/docload_b.png deleted file mode 100644 index 9a6d2f9afc1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docload_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/docload_b_s.png deleted file mode 100755 index 13fbdb3d09d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docload_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/docload_g.png deleted file mode 100644 index c80fb6b0d62..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b.png deleted file mode 100644 index b665d65b8c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b_s.png deleted file mode 100755 index 98445c9ae93..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/docpause_g.png deleted file mode 100644 index da750eba013..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/docpause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/edit_done.png b/TMessagesProj/src/main/res/drawable-xhdpi/edit_done.png new file mode 100755 index 00000000000..0041ebb993b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/edit_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/edit_doneblue.png b/TMessagesProj/src/main/res/drawable-xhdpi/edit_doneblue.png deleted file mode 100755 index 301434f89f0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/edit_doneblue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b.png deleted file mode 100755 index 85c47ecf76f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel.png deleted file mode 100755 index ed4c2381d53..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel_s.png deleted file mode 100755 index 0efa019d1bb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load.png deleted file mode 100755 index f9412a04f38..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load_s.png deleted file mode 100755 index 50cc62b0b9c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_b_s.png deleted file mode 100755 index da8180c0388..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g.png deleted file mode 100755 index 7b1911ab270..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel.png deleted file mode 100755 index 376d89f64e5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel_s.png deleted file mode 100755 index 4a8e785fff3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load.png deleted file mode 100755 index 8ef2faff736..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load_s.png deleted file mode 100755 index 6eb2518ebfd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/file_g_s.png deleted file mode 100755 index aaae696a71e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/file_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating.png deleted file mode 100755 index 7333b5429b2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png deleted file mode 100755 index c838c5412ed..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png deleted file mode 100755 index 5a1e0bae6cf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png deleted file mode 100755 index 5ae6d6fc216..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating3_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png index aa5089de59c..253e7503d83 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png deleted file mode 100755 index 3e00bc95669..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png index 914283172c7..4aefc95d744 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png deleted file mode 100755 index 98ce443c33e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/floating_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow.png new file mode 100755 index 00000000000..5f9ceafc910 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow_profile.png b/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow_profile.png new file mode 100755 index 00000000000..f5d5c8994bb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/floating_shadow_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/forward_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/forward_blue.png deleted file mode 100755 index 78a131b4360..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/forward_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/foursquare.png b/TMessagesProj/src/main/res/drawable-xhdpi/foursquare.png old mode 100644 new mode 100755 index 2b5bb36dc82..a59c7757e37 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/foursquare.png and b/TMessagesProj/src/main/res/drawable-xhdpi/foursquare.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider.9.png old mode 100644 new mode 100755 index f14b4733744..de385834c18 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_bottom.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_bottom.9.png old mode 100644 new mode 100755 index e1043341db3..4caba0c7a6a Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_bottom.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_bottom.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_top.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_top.9.png old mode 100644 new mode 100755 index aeb9cc803f9..7c10419726e Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_top.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/greydivider_top.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/hd_off.png b/TMessagesProj/src/main/res/drawable-xhdpi/hd_off.png deleted file mode 100755 index 2a63bce2f15..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/hd_off.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/hd_on.png b/TMessagesProj/src/main/res/drawable-xhdpi/hd_on.png deleted file mode 100755 index 4326d57a635..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/hd_on.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/header_timer.png b/TMessagesProj/src/main/res/drawable-xhdpi/header_timer.png deleted file mode 100755 index 7bdc8d56945..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/header_timer.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/header_timer2.png b/TMessagesProj/src/main/res/drawable-xhdpi/header_timer2.png deleted file mode 100755 index 37c985e6520..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/header_timer2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach.png old mode 100644 new mode 100755 index 403b24dd976..ed39a9bab70 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach3.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach3.png deleted file mode 100755 index 5b7b0f652db..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_attach3.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_back_grey.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_back_grey.png deleted file mode 100755 index acb9af9557e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_back_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_copy.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_copy.png new file mode 100755 index 00000000000..5bb7599e53f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_delete.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_delete.png new file mode 100755 index 00000000000..4696f96441e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_doc.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_doc.png deleted file mode 100755 index 245a76f03ff..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_doc.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done.png new file mode 100755 index 00000000000..d949f3a4be1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done_gray.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done_gray.png deleted file mode 100755 index 41671b4fb93..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_done_gray.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_forward.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_forward.png new file mode 100755 index 00000000000..d9e605ab58b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_copy.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_copy.png deleted file mode 100755 index 4bc6df880e3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_copy.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_delete.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_delete.png deleted file mode 100755 index ac3d1e98028..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_delete.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_forward.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_forward.png deleted file mode 100755 index 519de969be2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_fwd_forward.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_new.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_new.png index 71a5fc29630..efb7d89494e 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_new.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_new.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_reply.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_reply.png index 1b6cbc5b401..258cd5a2805 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_reply.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_ab_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_again.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_again.png new file mode 100644 index 00000000000..1c691837780 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_again.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_againinline.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_againinline.png new file mode 100644 index 00000000000..cda2c5e4bb6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_againinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_bluetooth_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_bluetooth_white_24dp.png new file mode 100755 index 00000000000..920f5cae72f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_bluetooth_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png new file mode 100755 index 00000000000..a4fe6889d15 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_36dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_36dp.png new file mode 100755 index 00000000000..e1831d7afd0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_end_white_36dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_made_green_18dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_made_green_18dp.png new file mode 100755 index 00000000000..403be791754 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_made_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_received_green_18dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_received_green_18dp.png new file mode 100755 index 00000000000..3f54e505185 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_received_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_white_24dp.png new file mode 100755 index 00000000000..ef45e933a99 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_call_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_chat_bubble_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_chat_bubble_white_24dp.png new file mode 100755 index 00000000000..ea41d0647ef Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_chat_bubble_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_create.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_create.png deleted file mode 100644 index 27579452dd1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_create.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png index f91f66653e4..dcbfcc9d9c7 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png index 41ea21d87b5..511cbbc746f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_gofullscreen.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_gofullscreen.png new file mode 100644 index 00000000000..8ccff7d13f7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_gofullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_goinline.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_goinline.png new file mode 100644 index 00000000000..f4204b8e574 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_goinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_mic_off_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_mic_off_white_24dp.png new file mode 100755 index 00000000000..bb7915f33df Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_mic_off_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png new file mode 100644 index 00000000000..fc78d7e3fd8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_more.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_gif.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_gif.png index 1fb2cbe60df..c391538fe15 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_gif.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_kb.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_kb.png index 55722091a81..81ad38432f5 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_kb.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_kb.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_smiles.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_smiles.png old mode 100644 new mode 100755 index 35d3ee54414..e1665974303 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_smiles.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_smiles.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_stickers.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_stickers.png index 542f1c7f99f..4eb1ab1d1cb 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_stickers.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_video.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_video.png new file mode 100755 index 00000000000..a6cb272c807 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_msg_panel_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_outfullscreen.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_outfullscreen.png new file mode 100644 index 00000000000..af5e2e275c8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_outfullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_outinline.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_outinline.png new file mode 100644 index 00000000000..3ca7fca4716 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_outinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_pause.png new file mode 100644 index 00000000000..7e1600c15ac Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_pauseinline.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_pauseinline.png new file mode 100644 index 00000000000..bf835605bfa Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_pauseinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_phone_in_talk_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_phone_in_talk_white_24dp.png new file mode 100755 index 00000000000..a2d78b22172 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_phone_in_talk_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_play.png new file mode 100644 index 00000000000..248c87e5ba9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_playinline.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_playinline.png new file mode 100644 index 00000000000..f653a3305f0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_playinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star.png new file mode 100644 index 00000000000..6bacf57635c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star_filled.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star_filled.png new file mode 100644 index 00000000000..e447c80cf12 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_rating_star_filled.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_send.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_send.png index 5413204cffa..79e85a8eb20 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_send.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_article.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_article.png new file mode 100644 index 00000000000..5c6cf3c376f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_article.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_video.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_video.png new file mode 100644 index 00000000000..a62366c5cb5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_share_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car.png index 772df38a3cf..8feaca01a56 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car_active.png deleted file mode 100755 index fe26abc5130..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_car_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food.png index c6aa99f8abc..b78e5f1bd7f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food_active.png deleted file mode 100755 index 0494b456fc5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_food_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature.png index a5c958f57b2..f64082875c8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature_active.png deleted file mode 100755 index f2b57bf566e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_nature_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects.png index ca6ea1e79e0..52e307f0b60 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects_active.png deleted file mode 100755 index 46a59bc5689..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_objects_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent.png index 4c562e0015b..f4691b546b5 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent_active.png deleted file mode 100755 index 40283e4c357..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_recent_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile.png index c562e241e05..12d99d09162 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile_active.png deleted file mode 100755 index 6a3c0a6067f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_smile_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers.png index 288723cf3a3..f72c63ed61f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers_active.png deleted file mode 100755 index 2b6917aebb5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles2_stickers_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace.png index 3d4d64c15a6..5365790245e 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace_active.png deleted file mode 100755 index 6f76811cbf0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_backspace_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_gif.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_gif.png index d7764aea944..1de26dbda36 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_gif.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_settings.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_settings.png index 3741ce25363..27ee73f2231 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_settings.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_trend.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_trend.png index 1935671d40f..6c9b2562554 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_trend.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_smiles_trend.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png index 7d8f350ab48..e7913dd2fcc 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png index 89685206c20..aa2a87ddce2 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png new file mode 100755 index 00000000000..2ed00343b80 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_volume_up_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png new file mode 100755 index 00000000000..93e7b071259 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/ic_vpn_key_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/link.png b/TMessagesProj/src/main/res/drawable-xhdpi/link.png deleted file mode 100755 index d526eee2c6b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/link.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_bot.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_bot.png new file mode 100755 index 00000000000..2b7763e0883 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_bot.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_broadcast.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_broadcast.png index d4fae87b228..9c9220c3118 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_broadcast.png and b/TMessagesProj/src/main/res/drawable-xhdpi/list_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_check.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_check.png new file mode 100755 index 00000000000..b861ff7341b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_group.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_group.png index 98e1e2be89c..185c653ea86 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_group.png and b/TMessagesProj/src/main/res/drawable-xhdpi/list_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_halfcheck.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_halfcheck.png new file mode 100755 index 00000000000..c61e9df1159 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_longpressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_longpressed_holo_light.9.png deleted file mode 100644 index 5532e88c2c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_longpressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_mute.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_mute.png new file mode 100755 index 00000000000..fe772f354de Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_mute.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_pin.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_pin.png new file mode 100755 index 00000000000..60dfe7cd496 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_pressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_pressed_holo_light.9.png deleted file mode 100644 index f4af9265719..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_pressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_secret.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_secret.png index f4ebc8b5717..03d8c9d53f6 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_secret.png and b/TMessagesProj/src/main/res/drawable-xhdpi/list_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png deleted file mode 100644 index c6a7d4d87c0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/list_selector_disabled_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/list_warning_sign.png b/TMessagesProj/src/main/res/drawable-xhdpi/list_warning_sign.png new file mode 100755 index 00000000000..c5d384b1d0f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/list_warning_sign.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/location2.png b/TMessagesProj/src/main/res/drawable-xhdpi/location2.png deleted file mode 100644 index ade7b9ef908..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/location2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/location_b.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/location_b.9.png deleted file mode 100755 index 3a59e957916..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/location_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/location_g.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/location_g.9.png deleted file mode 100755 index 8934991096d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/location_g.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/managers.png b/TMessagesProj/src/main/res/drawable-xhdpi/managers.png index cdcbc9baafc..f57278000c4 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/managers.png and b/TMessagesProj/src/main/res/drawable-xhdpi/managers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png index f701d7d94ca..2e3afc94c73 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png index 78b4017c926..eb6dd4c26a5 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png and b/TMessagesProj/src/main/res/drawable-xhdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_broadcast.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_broadcast.png index 1a2a61a0c26..14ac878f266 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_broadcast.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_calls.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_calls.png new file mode 100755 index 00000000000..b3b1cca27fe Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_calls.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_contacts.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_contacts.png index a7cc40dffbc..be1eba57f7a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_contacts.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_contacts.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_help.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_help.png index 256a4fc1560..8b41728185f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_help.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_help.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_invite.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_invite.png index 67ff57b1993..84fece051ab 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_invite.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_invite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_newgroup.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_newgroup.png index a55f0aa5f6a..49f33b655d2 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_newgroup.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_newgroup.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_secret.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_secret.png index ffb647b0be0..01867174d8a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_secret.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_settings.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_settings.png index 972c2ab6b82..d631f43e26b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/menu_settings.png and b/TMessagesProj/src/main/res/drawable-xhdpi/menu_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mic.png b/TMessagesProj/src/main/res/drawable-xhdpi/mic.png index f8b45380474..3de143d147a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/mic.png and b/TMessagesProj/src/main/res/drawable-xhdpi/mic.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mic_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/mic_pressed.png deleted file mode 100644 index bebe9361120..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/mic_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_close.png index 07d83314b4e..37f544ec028 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_close.png and b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_pause.png index 556aec50007..4b1840d21ad 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_pause.png and b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_play.png index fd6c00ec91f..3967d3725ca 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_play.png and b/TMessagesProj/src/main/res/drawable-xhdpi/miniplayer_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_actions.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_actions.png new file mode 100755 index 00000000000..58ccde2d608 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_actions.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_check.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_check.png index d214b024002..e869b16ba20 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_check.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_check_w.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_check_w.png deleted file mode 100755 index 9e797a5c310..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_check_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock.png index 2719ac9e4da..e38d7f9fdf6 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2.png deleted file mode 100755 index 334d7aa0911..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2_s.png deleted file mode 100755 index f2303b7ced3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock2_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock_photo.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock_photo.png deleted file mode 100755 index 50d10cbeb25..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_clock_photo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_contact.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_contact.png new file mode 100755 index 00000000000..7d622cbbc45 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_contact.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck.png index 7ae6e79c73e..f357df4f6d3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck_w.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck_w.png deleted file mode 100755 index dd289160721..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_halfcheck_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in.9.png index 42b96820bf6..63fee107e3d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo.9.png deleted file mode 100755 index 2e6c9da2390..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo_selected.9.png deleted file mode 100755 index a6a5f615a2c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_selected.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_selected.9.png deleted file mode 100755 index 3137acd5212..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_shadow.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_shadow.9.png new file mode 100755 index 00000000000..e7286533d6c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_in_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_instant.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_instant.png new file mode 100755 index 00000000000..f03df3f07c9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_instant.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_location.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_location.png new file mode 100755 index 00000000000..a23c6f9b20f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out.9.png index 306d56769a8..34de041e904 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo.9.png deleted file mode 100755 index 1018fe16a5a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo_selected.9.png deleted file mode 100755 index c76a79c921e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_selected.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_selected.9.png deleted file mode 100755 index 7f7551686d2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_shadow.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_shadow.9.png new file mode 100755 index 00000000000..dc71a23f90e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_out_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_clear.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_clear.png new file mode 100755 index 00000000000..d6e1f472573 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_forward.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_forward.png new file mode 100755 index 00000000000..beb9a83ddcd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_link.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_link.png new file mode 100755 index 00000000000..4cd2021e5a1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_link.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_reply.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_reply.png new file mode 100755 index 00000000000..33c847c2b3d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_panel_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo.9.png new file mode 100755 index 00000000000..fc7d664029b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo_shadow.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo_shadow.9.png new file mode 100755 index 00000000000..b8a548157ca Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_photo_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png new file mode 100755 index 00000000000..07cbd880de7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_m.png new file mode 100755 index 00000000000..3dca5e1270b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png new file mode 100755 index 00000000000..7ea1aa6d726 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_cancel_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_file_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_file_s.png new file mode 100755 index 00000000000..a96ebcf2014 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_file_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_gif_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_gif_m.png new file mode 100755 index 00000000000..fe04dc322d0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_gif_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png new file mode 100755 index 00000000000..913074707a6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_m.png new file mode 100755 index 00000000000..926e76fdb50 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png new file mode 100755 index 00000000000..4215c60cba2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_load_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_m.png new file mode 100755 index 00000000000..575c0ef589c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png new file mode 100755 index 00000000000..f948b7aabfd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_pause_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png new file mode 100755 index 00000000000..6944b3fd607 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_m.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_m.png new file mode 100755 index 00000000000..2c237ddce12 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png new file mode 100755 index 00000000000..9549ed9b0de Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_round_play_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_views.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_views.png new file mode 100755 index 00000000000..f6eb7815d44 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_views.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/msg_warning.png b/TMessagesProj/src/main/res/drawable-xhdpi/msg_warning.png old mode 100644 new mode 100755 index ca7d471e8fe..e0d2c59990c Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/msg_warning.png and b/TMessagesProj/src/main/res/drawable-xhdpi/msg_warning.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png deleted file mode 100755 index e5e62eaec78..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/mute_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png deleted file mode 100755 index 73b1e397610..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/mute_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/myloc_on.png b/TMessagesProj/src/main/res/drawable-xhdpi/myloc_on.png old mode 100644 new mode 100755 index 0ec99ef2a43..0ebd3417375 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/myloc_on.png and b/TMessagesProj/src/main/res/drawable-xhdpi/myloc_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/nocover.png b/TMessagesProj/src/main/res/drawable-xhdpi/nocover.png old mode 100644 new mode 100755 index 0e916f3761d..6d655f83323 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/nocover.png and b/TMessagesProj/src/main/res/drawable-xhdpi/nocover.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_1h.png b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_1h.png new file mode 100755 index 00000000000..e8698ee69bc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_1h.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_2d.png b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_2d.png new file mode 100755 index 00000000000..1adb89713cd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_2d.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_custom.png b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_custom.png new file mode 100755 index 00000000000..20f0292e603 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_custom.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_off.png b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_off.png new file mode 100755 index 00000000000..0c719f18129 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_on.png b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_on.png new file mode 100755 index 00000000000..a4e3d053895 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/notifications_s_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png index 7fcaef929dd..0343bd5834d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png index 74270aae750..c34b8563076 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-xhdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/numberpicker_selection_divider.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/numberpicker_selection_divider.9.png deleted file mode 100644 index 97eb5fe801e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/numberpicker_selection_divider.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pagedown.png b/TMessagesProj/src/main/res/drawable-xhdpi/pagedown.png index 685d8306a10..6e7cfa11b02 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pagedown.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pagedown.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pagedown_shadow.png b/TMessagesProj/src/main/res/drawable-xhdpi/pagedown_shadow.png new file mode 100755 index 00000000000..89486c7fbef Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/pagedown_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pause_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/pause_b.png deleted file mode 100755 index ae9b038134e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pause_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/pause_b_s.png deleted file mode 100755 index 6c796c01121..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pause_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/pause_g.png deleted file mode 100755 index ec2d1ed0504..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pause_g_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/pause_g_s.png deleted file mode 100755 index a7bb4434ae1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pause_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png old mode 100644 new mode 100755 index 770c9bd6722..4b39140b9b1 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png and b/TMessagesProj/src/main/res/drawable-xhdpi/phone_change.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/phone_grey.png b/TMessagesProj/src/main/res/drawable-xhdpi/phone_grey.png deleted file mode 100755 index 981ed79f0f1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/phone_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel.png deleted file mode 100755 index fccbbd7f361..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b.png deleted file mode 100755 index 7fef9dc1d09..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b_s.png deleted file mode 100755 index 81fcc3a9728..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g.png deleted file mode 100755 index e61452ac483..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g_s.png deleted file mode 100755 index 6600032fc14..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_pressed.png deleted file mode 100755 index 99ff3c7ec7d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photocancel_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photogif.png b/TMessagesProj/src/main/res/drawable-xhdpi/photogif.png deleted file mode 100755 index cfa6ce73504..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photogif.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photogif_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/photogif_pressed.png deleted file mode 100755 index 4c9032569ce..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photogif_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload.png deleted file mode 100755 index e214790e263..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b.png deleted file mode 100755 index 08d8ba1e14f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b_s.png deleted file mode 100755 index 9b860d1ee69..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g.png deleted file mode 100755 index 9ffbdfdd409..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g_s.png deleted file mode 100755 index c22b11e896a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/photoload_pressed.png deleted file mode 100755 index 3fc49d39873..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/photoload_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pin.png b/TMessagesProj/src/main/res/drawable-xhdpi/pin.png old mode 100644 new mode 100755 index 1fa36f89211..46300d17d9f Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pin.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png old mode 100644 new mode 100755 index a9b7bb55925..2471be4ec62 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_next_pressed.png deleted file mode 100644 index adfa9150262..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_next_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png old mode 100644 new mode 100755 index 88901a49ee6..98e88278a75 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause_pressed.png deleted file mode 100644 index 95d5b12241c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png old mode 100644 new mode 100755 index 337bf8a37a9..5d23c88b89e Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_play_pressed.png deleted file mode 100644 index adc3c6fe0b7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png old mode 100644 new mode 100755 index 1dbd201bdfb..293d9fc1193 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous_pressed.png deleted file mode 100644 index 6979d0dd2a7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_previous_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png old mode 100644 new mode 100755 index f0ebf49875a..2f36cc7b478 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png new file mode 100755 index 00000000000..785e0d71244 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1_active.png deleted file mode 100644 index 9b37d2dd301..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat1_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat_active.png deleted file mode 100644 index da9e63bc137..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_repeat_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png old mode 100644 new mode 100755 index 00293fad0db..e7af0c0313f Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle_active.png deleted file mode 100644 index 1cba5d2f563..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/pl_shuffle_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/place_x.png b/TMessagesProj/src/main/res/drawable-xhdpi/place_x.png old mode 100644 new mode 100755 index 3d95f25f9e8..be7548eb323 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/place_x.png and b/TMessagesProj/src/main/res/drawable-xhdpi/place_x.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/play_b.png b/TMessagesProj/src/main/res/drawable-xhdpi/play_b.png deleted file mode 100755 index c2f1803c710..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/play_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/play_b_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/play_b_s.png deleted file mode 100755 index 9ab9ba848c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/play_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/play_g.png b/TMessagesProj/src/main/res/drawable-xhdpi/play_g.png deleted file mode 100755 index 6c442666bca..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/play_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/play_g_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/play_g_s.png deleted file mode 100755 index 95b40bc0e5c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/play_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/playvideo.png b/TMessagesProj/src/main/res/drawable-xhdpi/playvideo.png deleted file mode 100755 index 91825e84f3f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/playvideo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/playvideo_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/playvideo_pressed.png deleted file mode 100755 index 1f3374c2ea2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/playvideo_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/plus.png b/TMessagesProj/src/main/res/drawable-xhdpi/plus.png index e29a055637e..0451c72bf94 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/plus.png and b/TMessagesProj/src/main/res/drawable-xhdpi/plus.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/post_views.png b/TMessagesProj/src/main/res/drawable-xhdpi/post_views.png deleted file mode 100755 index 9cf0d5bdf6e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/post_views.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/post_views_s.png b/TMessagesProj/src/main/res/drawable-xhdpi/post_views_s.png deleted file mode 100755 index 87af7233242..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/post_views_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/post_views_w.png b/TMessagesProj/src/main/res/drawable-xhdpi/post_views_w.png deleted file mode 100755 index 2de06dd70b5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/post_views_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/post_viewsg.png b/TMessagesProj/src/main/res/drawable-xhdpi/post_viewsg.png deleted file mode 100755 index fc3764cc96a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/post_viewsg.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/profile_info.png b/TMessagesProj/src/main/res/drawable-xhdpi/profile_info.png new file mode 100755 index 00000000000..26cd7be6575 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/profile_info.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/profile_list.png b/TMessagesProj/src/main/res/drawable-xhdpi/profile_list.png index 1383d6bc616..ad39ad088b3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/profile_list.png and b/TMessagesProj/src/main/res/drawable-xhdpi/profile_list.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/profile_phone.png b/TMessagesProj/src/main/res/drawable-xhdpi/profile_phone.png new file mode 100755 index 00000000000..b8c3072f8b6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/profile_phone.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/publish.png b/TMessagesProj/src/main/res/drawable-xhdpi/publish.png deleted file mode 100755 index e48445bc660..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/publish.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/publish_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/publish_active.png deleted file mode 100755 index d273752707f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/publish_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/recorded.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/recorded.9.png deleted file mode 100644 index af215a34c34..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/recorded.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/reply.png b/TMessagesProj/src/main/res/drawable-xhdpi/reply.png deleted file mode 100755 index c7226c4f5d4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/s_pause_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/s_pause_pressed.png deleted file mode 100755 index 83e9e8201c2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/s_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/s_play_pressed.png b/TMessagesProj/src/main/res/drawable-xhdpi/s_play_pressed.png deleted file mode 100755 index f0993b74231..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/s_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_calendar.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_calendar.png new file mode 100755 index 00000000000..8c7d29406fd Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/search_calendar.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_dark.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_dark.9.png index fcb8ced747d..cc5dfb27dd4 100644 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_dark.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/search_dark.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_dark_activated.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_dark_activated.9.png index cd2456c1635..71aa58d743c 100644 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_dark_activated.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/search_dark_activated.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_down.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_down.png old mode 100644 new mode 100755 index 0c6e1e02944..5b07c1dffb8 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_down.png and b/TMessagesProj/src/main/res/drawable-xhdpi/search_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_share.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_share.png deleted file mode 100755 index c8c15c21b26..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_share.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/search_up.png b/TMessagesProj/src/main/res/drawable-xhdpi/search_up.png old mode 100644 new mode 100755 index 6cded70aad4..8ff662fdfe8 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/search_up.png and b/TMessagesProj/src/main/res/drawable-xhdpi/search_up.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/shadowdown.png b/TMessagesProj/src/main/res/drawable-xhdpi/shadowdown.png new file mode 100644 index 00000000000..0e78f86e642 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/shadowdown.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_compose.png b/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_compose.png new file mode 100755 index 00000000000..3cc65ac9477 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_compose.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_user.png b/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_user.png new file mode 100755 index 00000000000..1261b387a3d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/shortcut_user.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_big.png b/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_big.png new file mode 100755 index 00000000000..e73a8fc44d7 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_small.png b/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_small.png new file mode 100755 index 00000000000..0ee66007c50 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/slide_dot_small.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/slidearrow.png b/TMessagesProj/src/main/res/drawable-xhdpi/slidearrow.png index 519e2b1730b..aea2017d68a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/slidearrow.png and b/TMessagesProj/src/main/res/drawable-xhdpi/slidearrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/smiles_popup.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/smiles_popup.9.png old mode 100644 new mode 100755 index 1fb62779631..29d4408eb91 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/smiles_popup.9.png and b/TMessagesProj/src/main/res/drawable-xhdpi/smiles_popup.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/sticker_added.png b/TMessagesProj/src/main/res/drawable-xhdpi/sticker_added.png index 65bc52a2887..1a7b7837054 100755 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/sticker_added.png and b/TMessagesProj/src/main/res/drawable-xhdpi/sticker_added.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/stickercounter.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/stickercounter.9.png deleted file mode 100755 index 7f1f17fbd7c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/stickercounter.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/theme_picker.png b/TMessagesProj/src/main/res/drawable-xhdpi/theme_picker.png new file mode 100755 index 00000000000..d1c3618e7ba Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/theme_picker.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/tool_cropfix_active.png b/TMessagesProj/src/main/res/drawable-xhdpi/tool_cropfix_active.png new file mode 100644 index 00000000000..2cdc13f119a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/tool_cropfix_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/tooltip.9.png b/TMessagesProj/src/main/res/drawable-xhdpi/tooltip.9.png deleted file mode 100644 index ab5eb8075eb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xhdpi/tooltip.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/verified_area.png b/TMessagesProj/src/main/res/drawable-xhdpi/verified_area.png new file mode 100755 index 00000000000..11c954ab82b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/verified_area.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/verified_check.png b/TMessagesProj/src/main/res/drawable-xhdpi/verified_check.png new file mode 100755 index 00000000000..aa22dc8ce0f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/verified_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_1080.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_1080.png new file mode 100755 index 00000000000..f6df8f95f95 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_1080.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_240.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_240.png new file mode 100755 index 00000000000..6ca42671a42 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_240.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_360.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_360.png new file mode 100755 index 00000000000..cd6375e3bd1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_360.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_480.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_480.png new file mode 100755 index 00000000000..a6633e92f73 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_480.png differ diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/video_720.png b/TMessagesProj/src/main/res/drawable-xhdpi/video_720.png new file mode 100755 index 00000000000..9dc84f250bc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xhdpi/video_720.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn.9.png deleted file mode 100644 index 9ce02468210..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn_pressed.9.png deleted file mode 100644 index 11273de1462..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/add_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/addmember.png b/TMessagesProj/src/main/res/drawable-xxhdpi/addmember.png deleted file mode 100755 index b4a2620834e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/addmember.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png b/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png index 8724c05b1e3..fd56eb4066f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star2.png deleted file mode 100755 index 6dac9318e59..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/admin_star2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/assign_manager.png b/TMessagesProj/src/main/res/drawable-xxhdpi/assign_manager.png deleted file mode 100755 index fcad7d9e8cf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/assign_manager.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_pause.png index 6a227efc217..a7beb78c0ff 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_play.png index 40478b91705..b4362fa0733 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_play.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/audiosend_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bluecircle.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bluecircle.png deleted file mode 100644 index 36d790a8f49..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bluecircle.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_file.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_file.png index 3fdbe9907e6..9cd059b8c7a 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_file.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_file.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_info.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_info.png deleted file mode 100644 index c620bbaf6e3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_info.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard.png old mode 100644 new mode 100755 index 67b0fa4debf..f57e1bc6e7f Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard2.png index b75abe5c3fa..7991e46a660 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard2.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard2.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button.9.png deleted file mode 100644 index 58fccc6e9de..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button_pressed.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button_pressed.9.png deleted file mode 100644 index dba2a36bc01..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_keyboard_button_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_list.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_list.png deleted file mode 100755 index 3391bb61a6b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_location.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_location.png index 6c75f1deb1f..21652a6bd04 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_location.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_music.png b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_music.png index 16bf5bc9e74..ce6c20adc4b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/bot_music.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/bot_music.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast2.png deleted file mode 100644 index 9b686f34727..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast3.png b/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast3.png old mode 100644 new mode 100755 index f89697b731e..a4fca086480 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast3.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast3.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast4.png b/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast4.png deleted file mode 100644 index 9a1b9b2abfc..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/broadcast4.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chat_badge.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chat_badge.9.png deleted file mode 100755 index 501e2eb61b6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/chat_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chats_clear.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_clear.png new file mode 100755 index 00000000000..e351ac61c18 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chats_delete.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_delete.png new file mode 100755 index 00000000000..fe0c730e77d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chats_leave.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_leave.png new file mode 100755 index 00000000000..d682143d35f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_leave.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chats_pin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_pin.png new file mode 100755 index 00000000000..8ef9bbd8f3f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/chats_unpin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_unpin.png new file mode 100755 index 00000000000..3b1c76e9965 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/chats_unpin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/check_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/check_blue.png deleted file mode 100755 index d01961facce..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/check_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/check_list.png b/TMessagesProj/src/main/res/drawable-xxhdpi/check_list.png deleted file mode 100644 index fd5a56b4414..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/check_list.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/check_profile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/check_profile.png deleted file mode 100644 index 30bfdb4824c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/check_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/cloud.png b/TMessagesProj/src/main/res/drawable-xxhdpi/cloud.png old mode 100644 new mode 100755 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel.9.png deleted file mode 100644 index e2ee05beea9..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel_shadow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel_shadow.png new file mode 100755 index 00000000000..a84e83515d9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/compose_panel_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/contact_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/contact_blue.png deleted file mode 100755 index aa2f30e3766..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/contact_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/contact_green.png b/TMessagesProj/src/main/res/drawable-xxhdpi/contact_green.png deleted file mode 100755 index 1fb42bf4051..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/contact_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn.9.png deleted file mode 100644 index 0268e62017a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn_pressed.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn_pressed.9.png deleted file mode 100644 index 48c60796e36..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/del_btn_pressed.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/delete.png b/TMessagesProj/src/main/res/drawable-xxhdpi/delete.png new file mode 100644 index 00000000000..6c87bf13923 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/delete_reply.png b/TMessagesProj/src/main/res/drawable-xxhdpi/delete_reply.png deleted file mode 100755 index f2f98a68ef8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/delete_reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/devices.png b/TMessagesProj/src/main/res/drawable-xxhdpi/devices.png index 3afa7788da5..e767cc0f24c 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/devices.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/devices.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge.9.png deleted file mode 100755 index 1c07f6c9c1a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge2.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge2.9.png deleted file mode 100755 index 7facfe30e13..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_badge2.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_check.png b/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_check.png deleted file mode 100755 index a3c0f9cec58..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_check.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_halfcheck.png b/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_halfcheck.png deleted file mode 100755 index 5f5a7b1e1b4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_halfcheck.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_warning.png b/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_warning.png deleted file mode 100755 index 8b1332493c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/dialogs_warning.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b.png deleted file mode 100755 index 3b169ff048a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b_s.png deleted file mode 100755 index 34f1206fafd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_g.png deleted file mode 100755 index 109c13fd7aa..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_actions_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_big.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_big.png new file mode 100755 index 00000000000..4fe1a5013a8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue.png deleted file mode 100755 index ac033bc15e5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue_s.png deleted file mode 100755 index 466b3cb3d8c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_blue_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_green.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doc_green.png deleted file mode 100755 index 33f1a5315a7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doc_green.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b.png deleted file mode 100644 index fad332f1e2e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b_s.png deleted file mode 100755 index 4eae493c8c5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_g.png deleted file mode 100644 index c31c831eb96..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/doccancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b.png deleted file mode 100644 index a54e9deacb2..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b_s.png deleted file mode 100755 index e86d9347a46..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docload_g.png deleted file mode 100644 index 5da89c7836d..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b.png deleted file mode 100644 index 370878820bb..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b_s.png deleted file mode 100755 index fd117934c45..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_g.png deleted file mode 100644 index 18dcd57d18a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/docpause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/edit_done.png b/TMessagesProj/src/main/res/drawable-xxhdpi/edit_done.png new file mode 100755 index 00000000000..a1efc36a663 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/edit_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/edit_doneblue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/edit_doneblue.png deleted file mode 100755 index 643fa3c6e81..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/edit_doneblue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b.png deleted file mode 100755 index 1c8598cd710..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel.png deleted file mode 100755 index 9d48270d976..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel_s.png deleted file mode 100755 index 5e323f92664..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load.png deleted file mode 100755 index dc4dae1fb85..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load_s.png deleted file mode 100755 index 044304d96b5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_s.png deleted file mode 100755 index 925351b0207..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g.png deleted file mode 100755 index 7dd85a3b527..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel.png deleted file mode 100755 index 1ccbe9d93ae..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel_s.png deleted file mode 100755 index de91f915809..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_cancel_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load.png deleted file mode 100755 index bcddac3bcd1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load_s.png deleted file mode 100755 index 174947d793c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_load_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_s.png deleted file mode 100755 index 8395b5ac857..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/file_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png deleted file mode 100755 index 76ea18c9301..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png deleted file mode 100755 index 3715273b0bf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png deleted file mode 100755 index f9b8bc1f563..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png deleted file mode 100755 index 291e574fd49..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating3_profile_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png index ca6d5ef98e2..fbb08d56596 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_camera.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png deleted file mode 100755 index 598b8953a5c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_m.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png index 40451f2988d..0bb02efaaa0 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_message.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png deleted file mode 100755 index 0904520c9ed..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow.png new file mode 100755 index 00000000000..e69ef7e246b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow_profile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow_profile.png new file mode 100755 index 00000000000..9f1ffc94bd5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/floating_shadow_profile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/forward_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/forward_blue.png deleted file mode 100755 index 44c2b085ab5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/forward_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/foursquare.png b/TMessagesProj/src/main/res/drawable-xxhdpi/foursquare.png old mode 100644 new mode 100755 index 0104f7dc9cc..baeaca21e70 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/foursquare.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/foursquare.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider.9.png old mode 100644 new mode 100755 index ef001a3d52c..c88965c287d Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_bottom.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_bottom.9.png old mode 100644 new mode 100755 index 1d72587a8d8..89206a6ef5c Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_bottom.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_bottom.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_top.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_top.9.png old mode 100644 new mode 100755 index 4c3d0b3160d..54a14ea8efc Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_top.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/greydivider_top.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/hd_off.png b/TMessagesProj/src/main/res/drawable-xxhdpi/hd_off.png deleted file mode 100755 index 744159db8fd..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/hd_off.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/hd_on.png b/TMessagesProj/src/main/res/drawable-xxhdpi/hd_on.png deleted file mode 100755 index 38bff0ca880..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/hd_on.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer.png b/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer.png deleted file mode 100755 index 9d27a863f42..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer2.png deleted file mode 100755 index aeaa2455b0f..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/header_timer2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach.png old mode 100644 new mode 100755 index 5f2d555b448..22fca80dece Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach3.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach3.png deleted file mode 100755 index 39b7c7d15a5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_attach3.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_back_grey.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_back_grey.png deleted file mode 100755 index 06104d817f5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_back_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_copy.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_copy.png new file mode 100755 index 00000000000..a9129dd513c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_copy.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_delete.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_delete.png new file mode 100755 index 00000000000..b51e9267deb Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_delete.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_doc.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_doc.png deleted file mode 100755 index 7a86b6f08e4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_doc.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done.png new file mode 100755 index 00000000000..25c54ef7550 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done_gray.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done_gray.png deleted file mode 100755 index 4ff3b63bbb0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_done_gray.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_forward.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_forward.png new file mode 100755 index 00000000000..22b3daf3082 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_copy.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_copy.png deleted file mode 100755 index 31054c20597..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_copy.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_delete.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_delete.png deleted file mode 100755 index 74398b6cb8e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_delete.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_forward.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_forward.png deleted file mode 100755 index 3861355bf89..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_fwd_forward.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_new.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_new.png index 57e0563e31b..0c668fdc8b3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_new.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_new.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_reply.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_reply.png index 2af3db4e545..2e74dd013e0 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_reply.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_ab_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_again.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_again.png new file mode 100644 index 00000000000..8164863e557 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_again.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_againinline.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_againinline.png new file mode 100644 index 00000000000..781e2e87180 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_againinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_bluetooth_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_bluetooth_white_24dp.png new file mode 100755 index 00000000000..860c7586420 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_bluetooth_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png new file mode 100755 index 00000000000..e1831d7afd0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_36dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_36dp.png new file mode 100755 index 00000000000..13ffc2ad75f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_end_white_36dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_made_green_18dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_made_green_18dp.png new file mode 100755 index 00000000000..14b481cf664 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_made_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_received_green_18dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_received_green_18dp.png new file mode 100755 index 00000000000..a290ac3190d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_received_green_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png new file mode 100755 index 00000000000..6f4dcea1f3c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_call_white_18dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_chat_bubble_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_chat_bubble_white_24dp.png new file mode 100755 index 00000000000..948b11055bf Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_chat_bubble_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_create.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_create.png deleted file mode 100644 index 54d4e808f88..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_create.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png index 591886a3c11..2254d8106a7 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_directory.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png index f1971d6be16..dc7764cb976 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_external_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_gofullscreen.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_gofullscreen.png new file mode 100644 index 00000000000..4a501c587d1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_gofullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_goinline.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_goinline.png new file mode 100644 index 00000000000..c8e9661329d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_goinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_mic_off_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_mic_off_white_24dp.png new file mode 100755 index 00000000000..7a15a9ea9e9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_mic_off_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png new file mode 100644 index 00000000000..49fba0ce0a0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_more.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_gif.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_gif.png index a09e973d7c9..e09e846173f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_gif.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_kb.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_kb.png index 863d5e3846c..e82bd3c214f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_kb.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_kb.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_smiles.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_smiles.png old mode 100644 new mode 100755 index b47f91717e7..be0f86f0204 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_smiles.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_smiles.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_stickers.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_stickers.png index fc689a86233..f3a80f97512 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_stickers.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_video.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_video.png new file mode 100755 index 00000000000..d8a957dc08e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_msg_panel_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outfullscreen.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outfullscreen.png new file mode 100644 index 00000000000..28e150e1986 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outfullscreen.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outinline.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outinline.png new file mode 100644 index 00000000000..de3c110214d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_outinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pause.png new file mode 100644 index 00000000000..8167aee492b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pauseinline.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pauseinline.png new file mode 100644 index 00000000000..bb6504d7c3b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_pauseinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_phone_in_talk_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_phone_in_talk_white_24dp.png new file mode 100755 index 00000000000..9c002da0aaa Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_phone_in_talk_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_play.png new file mode 100644 index 00000000000..ce34b24d829 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_playinline.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_playinline.png new file mode 100644 index 00000000000..5b0ed00f5f9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_playinline.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star.png new file mode 100644 index 00000000000..f70a7a94979 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star_filled.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star_filled.png new file mode 100644 index 00000000000..af437f9d06c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_rating_star_filled.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_send.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_send.png index c64dbb21d11..69c6f9978ee 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_send.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_send.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_article.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_article.png new file mode 100644 index 00000000000..1cbbb26153c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_article.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_video.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_video.png new file mode 100644 index 00000000000..6494c1755c3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_share_video.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car.png index 4a0177503c7..3cbef4aa007 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car_active.png deleted file mode 100755 index 417c46f1586..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_car_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food.png index 0b1a1a1b31c..d0cbece34b8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food_active.png deleted file mode 100755 index e10e3c32d96..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_food_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature.png index 2558d32e409..77f38356118 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature_active.png deleted file mode 100755 index e7adec7e108..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_nature_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects.png index afa8a949bb5..34591ad86b1 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects_active.png deleted file mode 100755 index f159f569d23..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_objects_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent.png index 20fb6df5762..2f83e451e96 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent_active.png deleted file mode 100755 index ed16a56eb51..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_recent_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile.png index 4fb73ab1255..d1539aa1aed 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile_active.png deleted file mode 100755 index 61aa5421ff7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_smile_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers.png index e5d036ede7b..25a14e39025 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers_active.png deleted file mode 100755 index 925c02c34f4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles2_stickers_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace.png index 8580a8b6733..15486e1005d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace_active.png deleted file mode 100755 index eb046dea7b5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_backspace_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_gif.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_gif.png index c4ef4615fe7..35d51242069 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_gif.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_gif.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_settings.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_settings.png index 68278c84d1b..23e77a55fc9 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_settings.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_trend.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_trend.png index 81fd6f8c5c8..dcf17cc4250 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_trend.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_smiles_trend.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png index 3522701328c..7759760e6b2 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png index 5ec0a940e91..bdb8630098d 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_storage_gallery.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png new file mode 100755 index 00000000000..2e751a40f53 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_volume_up_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png new file mode 100755 index 00000000000..54c92ace4d2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/ic_vpn_key_white_24dp.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/link.png b/TMessagesProj/src/main/res/drawable-xxhdpi/link.png deleted file mode 100755 index c3edde91b91..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/link.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_bot.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_bot.png new file mode 100755 index 00000000000..0596f8f1217 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_bot.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_broadcast.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_broadcast.png index 3118410ebb6..adf93a84672 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_broadcast.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_check.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_check.png new file mode 100755 index 00000000000..30f01c6d90f Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_group.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_group.png index 8113ffea000..14c071e6752 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_group.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_group.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_halfcheck.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_halfcheck.png new file mode 100755 index 00000000000..2c8e85fe01a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_longpressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_longpressed_holo_light.9.png deleted file mode 100644 index 230d649bf73..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_longpressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_mute.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_mute.png new file mode 100755 index 00000000000..a0e5efc6f76 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_mute.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_pin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_pin.png new file mode 100755 index 00000000000..b72568bbfe3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_pressed_holo_light.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_pressed_holo_light.9.png deleted file mode 100644 index 1352a1702a7..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_pressed_holo_light.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_secret.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_secret.png index fd1e9f59fd7..35a15b3bfb6 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/list_secret.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/list_warning_sign.png b/TMessagesProj/src/main/res/drawable-xxhdpi/list_warning_sign.png new file mode 100755 index 00000000000..c0f5b2dd10a Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/list_warning_sign.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/location2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/location2.png deleted file mode 100644 index 01ea9f4dd5c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/location2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/location_b.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/location_b.9.png deleted file mode 100755 index ab744516b28..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/location_b.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/location_g.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/location_g.9.png deleted file mode 100755 index 09fc04cd955..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/location_g.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png b/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png index e777cb3d2c2..f58abc4ae08 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/managers.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png index 1381ece387b..44a6b9c6cee 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_load.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png index c4d4125d72e..402a45525ce 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/media_doc_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_broadcast.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_broadcast.png index cf8e5baa90a..9c5e3b7300f 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_broadcast.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_broadcast.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_calls.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_calls.png new file mode 100755 index 00000000000..2155b11df19 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_calls.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_contacts.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_contacts.png index 483e8669b7a..3a87eadef32 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_contacts.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_contacts.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_help.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_help.png index 6d46163daee..4d9ca592c38 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_help.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_help.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_invite.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_invite.png index e7e1adbeb8f..d1afc6be72b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_invite.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_invite.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_newgroup.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_newgroup.png index d83702f7bcf..9f995094208 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_newgroup.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_newgroup.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_secret.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_secret.png index 9c1b7903f7d..8566c70764c 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_secret.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_secret.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_settings.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_settings.png index bcd8ec6b97f..5c9c7388f76 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_settings.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_settings.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mic.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mic.png index ba25d26129d..42fd1218554 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/mic.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/mic.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mic_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mic_pressed.png deleted file mode 100644 index e7ca004270b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/mic_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png index 909458acfb1..650dc6fc44b 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_close.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_pause.png index b3e388b96eb..df49d651aa3 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_play.png index e8a04810e22..0926c432650 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_play.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/miniplayer_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_actions.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_actions.png new file mode 100755 index 00000000000..2c70066caa6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_actions.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check.png index 5a38cea3e6b..7c78b851e54 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check_w.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check_w.png deleted file mode 100755 index 7a6da6cc52a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_check_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock.png index 6c28c8e0333..c7b96ea3a3c 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2.png deleted file mode 100755 index 5130d83de12..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2_s.png deleted file mode 100755 index af7cba16a79..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock2_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock_photo.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock_photo.png deleted file mode 100755 index 4bf6b168b16..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_clock_photo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_contact.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_contact.png new file mode 100755 index 00000000000..738a3816ea0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_contact.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck.png index 4dbbf75b8a7..9e52c5bf730 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck_w.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck_w.png deleted file mode 100755 index 841c77bcc98..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_halfcheck_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in.9.png index 54475d909fb..9288fdb23a8 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo.9.png deleted file mode 100755 index f1d14e0b5e1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo_selected.9.png deleted file mode 100755 index 2f3076fc231..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_selected.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_selected.9.png deleted file mode 100755 index 0502f0dd7e8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_shadow.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_shadow.9.png new file mode 100755 index 00000000000..00ccf246c45 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_in_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_instant.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_instant.png new file mode 100755 index 00000000000..59968c26cef Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_instant.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_location.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_location.png new file mode 100755 index 00000000000..fc080b46d2c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_location.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out.9.png index ee6f2df36f6..f2115074c62 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo.9.png deleted file mode 100755 index 6498fa28c10..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo_selected.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo_selected.9.png deleted file mode 100755 index 8c8d11d0dc6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_photo_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_selected.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_selected.9.png deleted file mode 100755 index 64cf8e69de5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_selected.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_shadow.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_shadow.9.png new file mode 100755 index 00000000000..fc5612f08f8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_out_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_clear.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_clear.png new file mode 100755 index 00000000000..a495f8369d1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_clear.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_forward.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_forward.png new file mode 100755 index 00000000000..0a06cb529af Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_forward.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_link.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_link.png new file mode 100755 index 00000000000..b9f19cc0f5e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_link.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_reply.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_reply.png new file mode 100755 index 00000000000..0979942a471 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_panel_reply.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo.9.png new file mode 100755 index 00000000000..001cea3de3b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo_shadow.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo_shadow.9.png new file mode 100755 index 00000000000..e03c0c11382 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_photo_shadow.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png new file mode 100755 index 00000000000..a3eea7bda53 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_m.png new file mode 100755 index 00000000000..7ffc2d6b0d3 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png new file mode 100755 index 00000000000..6ec74f45148 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_cancel_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_file_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_file_s.png new file mode 100755 index 00000000000..68d16e1122c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_file_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_gif_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_gif_m.png new file mode 100755 index 00000000000..9d517d4f9c2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_gif_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png new file mode 100755 index 00000000000..31cf7dfada9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_m.png new file mode 100755 index 00000000000..f32acf3f49d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png new file mode 100755 index 00000000000..e88961eaee8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_load_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_m.png new file mode 100755 index 00000000000..738f5b648b0 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png new file mode 100755 index 00000000000..63d1849d9e1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_pause_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png new file mode 100755 index 00000000000..4d875efd8f6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_l.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_m.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_m.png new file mode 100755 index 00000000000..449657696e5 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_m.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png new file mode 100755 index 00000000000..84285003b32 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_round_play_s.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_views.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_views.png new file mode 100755 index 00000000000..fb6825ae92b Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_views.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_warning.png b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_warning.png old mode 100644 new mode 100755 index ac1c5f33113..8a7d22f3529 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/msg_warning.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/msg_warning.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png deleted file mode 100755 index 4860d2f26ca..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_blue.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png b/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png deleted file mode 100755 index e6f3ded37d6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/mute_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/myloc_on.png b/TMessagesProj/src/main/res/drawable-xxhdpi/myloc_on.png old mode 100644 new mode 100755 index 1b40c0f1b03..92fbeda7136 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/myloc_on.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/myloc_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/nocover.png b/TMessagesProj/src/main/res/drawable-xxhdpi/nocover.png old mode 100644 new mode 100755 index 61aab196af2..ae86c205bc8 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/nocover.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/nocover.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_1h.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_1h.png new file mode 100755 index 00000000000..97bece13bdc Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_1h.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_2d.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_2d.png new file mode 100755 index 00000000000..5fbedb5b0f6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_2d.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_custom.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_custom.png new file mode 100755 index 00000000000..6d375414658 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_custom.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_off.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_off.png new file mode 100755 index 00000000000..0205230fc7e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_on.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_on.png new file mode 100755 index 00000000000..d86fe0fe4b1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/notifications_s_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png index 3097e16f154..229c3ea6956 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_off.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png index b468befd5c7..6427a9fe299 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/notify_members_on.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/numberpicker_selection_divider.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/numberpicker_selection_divider.9.png deleted file mode 100644 index b7a99402eb5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/numberpicker_selection_divider.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown.png index 2d58a5f8036..ace471404a1 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown_shadow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown_shadow.png new file mode 100755 index 00000000000..98603e32c1e Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/pagedown_shadow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b.png deleted file mode 100755 index 091b06c6713..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b_s.png deleted file mode 100755 index 3b056929be0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g.png deleted file mode 100755 index d2ed392ad07..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g_s.png deleted file mode 100755 index ce7328a18c4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pause_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png old mode 100644 new mode 100755 index 169a5216485..2133e16040b Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_change.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_grey.png b/TMessagesProj/src/main/res/drawable-xxhdpi/phone_grey.png deleted file mode 100755 index a32cf0173b5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/phone_grey.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel.png deleted file mode 100755 index 2a9105b1495..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b.png deleted file mode 100755 index 80d4d90327a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b_s.png deleted file mode 100755 index 6d29a268198..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g.png deleted file mode 100755 index d42899e9cbc..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g_s.png deleted file mode 100755 index bfa63bb483e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_pressed.png deleted file mode 100755 index 1477b2476d0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photocancel_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photogif.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photogif.png deleted file mode 100755 index ad4cd9c65e5..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photogif.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photogif_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photogif_pressed.png deleted file mode 100755 index 365f8f595d8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photogif_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload.png deleted file mode 100755 index efae871de6c..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b.png deleted file mode 100755 index 314b0241002..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b_s.png deleted file mode 100755 index 8b30d79ef23..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g.png deleted file mode 100755 index 5f85ecced7a..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g_s.png deleted file mode 100755 index fdd610942ba..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_pressed.png deleted file mode 100755 index df021f02429..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/photoload_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png old mode 100644 new mode 100755 index 8580d9a2f3b..3eded6c8283 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pin.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png old mode 100644 new mode 100755 index 7895ed90f69..8cf29a4aa90 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next_pressed.png deleted file mode 100644 index fa5646ca3f3..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_next_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png old mode 100644 new mode 100755 index 10dac92ff4a..8808ce1378a Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause_pressed.png deleted file mode 100644 index 1eb9c4eb482..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png old mode 100644 new mode 100755 index 41d57747cd3..55c92dbd778 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play_pressed.png deleted file mode 100644 index bc1b06969df..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png old mode 100644 new mode 100755 index 6d45e33cad0..c2e3c33fa3d Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous_pressed.png deleted file mode 100644 index 92baf19cdd4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_previous_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png old mode 100644 new mode 100755 index a0404007d1e..d50ffdef18c Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png new file mode 100755 index 00000000000..6ba0fe87bc9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1_active.png deleted file mode 100644 index 49caea60036..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat1_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat_active.png deleted file mode 100644 index 38266b9d1b0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_repeat_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png old mode 100644 new mode 100755 index 59108a061e9..00e3337e692 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle_active.png deleted file mode 100644 index 1ca88b2e77b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/pl_shuffle_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/place_x.png b/TMessagesProj/src/main/res/drawable-xxhdpi/place_x.png old mode 100644 new mode 100755 index aa7aebba001..f0d94e6df2b Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/place_x.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/place_x.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/play_b.png b/TMessagesProj/src/main/res/drawable-xxhdpi/play_b.png deleted file mode 100755 index 99e2c15c3c6..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/play_b.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/play_b_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/play_b_s.png deleted file mode 100755 index c5c551054cf..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/play_b_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/play_g.png b/TMessagesProj/src/main/res/drawable-xxhdpi/play_g.png deleted file mode 100755 index d4461d6579b..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/play_g.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/play_g_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/play_g_s.png deleted file mode 100755 index d471364b462..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/play_g_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo.png b/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo.png deleted file mode 100755 index e056d93f828..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo_pressed.png deleted file mode 100755 index 6d98bdf7fd0..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/playvideo_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/plus.png b/TMessagesProj/src/main/res/drawable-xxhdpi/plus.png index d2515016c93..10b5f8f1747 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/plus.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/plus.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views.png b/TMessagesProj/src/main/res/drawable-xxhdpi/post_views.png deleted file mode 100755 index c8601721600..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_s.png b/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_s.png deleted file mode 100755 index 7839b3d3954..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_s.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_w.png b/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_w.png deleted file mode 100755 index 7ddf4de6374..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/post_views_w.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/post_viewsg.png b/TMessagesProj/src/main/res/drawable-xxhdpi/post_viewsg.png deleted file mode 100755 index b8889aa3fb1..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/post_viewsg.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/profile_info.png b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_info.png new file mode 100755 index 00000000000..c3b7e55674d Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_info.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/profile_list.png b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_list.png index b07f5de4dfb..7d87c637394 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/profile_list.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_list.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/profile_phone.png b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_phone.png new file mode 100755 index 00000000000..64472781f82 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/profile_phone.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/publish.png b/TMessagesProj/src/main/res/drawable-xxhdpi/publish.png deleted file mode 100755 index 6c02add3713..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/publish.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/publish_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/publish_active.png deleted file mode 100755 index 2d74763e889..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/publish_active.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/recorded.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/recorded.9.png deleted file mode 100644 index 0c72100abad..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/recorded.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/reply.png b/TMessagesProj/src/main/res/drawable-xxhdpi/reply.png deleted file mode 100755 index d7b1f3354f8..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/reply.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause.png b/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause.png index 07deeb1803e..48279a1c2c2 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause_pressed.png deleted file mode 100755 index 29423e28330..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/s_pause_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/s_play.png b/TMessagesProj/src/main/res/drawable-xxhdpi/s_play.png index 4757ae74d92..ec1dd17519e 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/s_play.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/s_play.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/s_play_pressed.png b/TMessagesProj/src/main/res/drawable-xxhdpi/s_play_pressed.png deleted file mode 100755 index a9575a4c336..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/s_play_pressed.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_calendar.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_calendar.png new file mode 100755 index 00000000000..23485e842d8 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/search_calendar.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark.9.png index 6df53875db0..df1a121bc4a 100644 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark_activated.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark_activated.9.png index 3a49d9baaaa..bebe13be533 100644 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark_activated.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/search_dark_activated.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_down.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_down.png old mode 100644 new mode 100755 index 3b6909c7e91..71c3667e757 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_down.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/search_down.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_share.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_share.png deleted file mode 100755 index 60007340af4..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_share.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/search_up.png b/TMessagesProj/src/main/res/drawable-xxhdpi/search_up.png old mode 100644 new mode 100755 index fd54f5b41bb..ec9efb265fc Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/search_up.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/search_up.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/shadowdown.png b/TMessagesProj/src/main/res/drawable-xxhdpi/shadowdown.png new file mode 100644 index 00000000000..b9527e16426 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/shadowdown.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_compose.png b/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_compose.png new file mode 100755 index 00000000000..caebb609e00 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_compose.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_user.png b/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_user.png new file mode 100755 index 00000000000..21cd3725294 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/shortcut_user.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_big.png b/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_big.png new file mode 100755 index 00000000000..edc3b7ad996 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_big.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_small.png b/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_small.png new file mode 100755 index 00000000000..5be0e81f9f4 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/slide_dot_small.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/slidearrow.png b/TMessagesProj/src/main/res/drawable-xxhdpi/slidearrow.png index f60d77275b5..6fb78051e05 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/slidearrow.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/slidearrow.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/smiles_popup.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/smiles_popup.9.png old mode 100644 new mode 100755 index fdc40071a9e..585a098c7f6 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/smiles_popup.9.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/smiles_popup.9.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/sticker_added.png b/TMessagesProj/src/main/res/drawable-xxhdpi/sticker_added.png index 8f31dc4762d..1e9ad687813 100755 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/sticker_added.png and b/TMessagesProj/src/main/res/drawable-xxhdpi/sticker_added.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/stickercounter.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/stickercounter.9.png deleted file mode 100755 index a9ff07a7fcc..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/stickercounter.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/theme_picker.png b/TMessagesProj/src/main/res/drawable-xxhdpi/theme_picker.png new file mode 100755 index 00000000000..c742274b739 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/theme_picker.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/tool_cropfix_active.png b/TMessagesProj/src/main/res/drawable-xxhdpi/tool_cropfix_active.png new file mode 100644 index 00000000000..0ee059e2e46 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/tool_cropfix_active.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/tooltip.9.png b/TMessagesProj/src/main/res/drawable-xxhdpi/tooltip.9.png deleted file mode 100644 index 263f788fd2e..00000000000 Binary files a/TMessagesProj/src/main/res/drawable-xxhdpi/tooltip.9.png and /dev/null differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/verified_area.png b/TMessagesProj/src/main/res/drawable-xxhdpi/verified_area.png new file mode 100755 index 00000000000..356bf24ac08 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/verified_area.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/verified_check.png b/TMessagesProj/src/main/res/drawable-xxhdpi/verified_check.png new file mode 100755 index 00000000000..7e37cd3cbd2 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/verified_check.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_1080.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_1080.png new file mode 100755 index 00000000000..c3f130075d6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_1080.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_240.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_240.png new file mode 100755 index 00000000000..fa9e097285c Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_240.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_360.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_360.png new file mode 100755 index 00000000000..1ec1b8aa837 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_360.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_480.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_480.png new file mode 100755 index 00000000000..a3f37d6f5f1 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_480.png differ diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/video_720.png b/TMessagesProj/src/main/res/drawable-xxhdpi/video_720.png new file mode 100755 index 00000000000..84bca9b05c6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable-xxhdpi/video_720.png differ diff --git a/TMessagesProj/src/main/res/drawable/add_states.xml b/TMessagesProj/src/main/res/drawable/add_states.xml deleted file mode 100644 index 9e986b3fae0..00000000000 --- a/TMessagesProj/src/main/res/drawable/add_states.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/bar_selector_lock.xml b/TMessagesProj/src/main/res/drawable/bar_selector_lock.xml index 76518274140..4e92d7d889d 100644 --- a/TMessagesProj/src/main/res/drawable/bar_selector_lock.xml +++ b/TMessagesProj/src/main/res/drawable/bar_selector_lock.xml @@ -3,7 +3,7 @@ ~ It is licensed under GNU GPL v. 2 or later. ~ You should have received a copy of the license in this archive (see LICENSE). ~ - ~ Copyright Nikolai Kudashov, 2013-2016. + ~ Copyright Nikolai Kudashov, 2013-2017. --> + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/bot_keyboard_states.xml b/TMessagesProj/src/main/res/drawable/bot_keyboard_states.xml deleted file mode 100644 index 3f0757f53f5..00000000000 --- a/TMessagesProj/src/main/res/drawable/bot_keyboard_states.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/check_profile_fixed.xml b/TMessagesProj/src/main/res/drawable/check_profile_fixed.xml deleted file mode 100644 index 591ea9d5d55..00000000000 --- a/TMessagesProj/src/main/res/drawable/check_profile_fixed.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/del_states.xml b/TMessagesProj/src/main/res/drawable/del_states.xml deleted file mode 100644 index f82a1dffc11..00000000000 --- a/TMessagesProj/src/main/res/drawable/del_states.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/edit_text.xml b/TMessagesProj/src/main/res/drawable/edit_text.xml deleted file mode 100644 index b9d7fc5439e..00000000000 --- a/TMessagesProj/src/main/res/drawable/edit_text.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/fab_highlight_dark.xml b/TMessagesProj/src/main/res/drawable/fab_highlight_dark.xml new file mode 100644 index 00000000000..d790fb543e1 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/fab_highlight_dark.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/field_carret_empty.xml b/TMessagesProj/src/main/res/drawable/field_carret_empty.xml new file mode 100644 index 00000000000..8138321f50e --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/field_carret_empty.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/TMessagesProj/src/main/res/drawable/floating_states.xml b/TMessagesProj/src/main/res/drawable/floating_states.xml deleted file mode 100644 index 55cb84eb484..00000000000 --- a/TMessagesProj/src/main/res/drawable/floating_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/floating_user_states.xml b/TMessagesProj/src/main/res/drawable/floating_user_states.xml deleted file mode 100644 index 1e8f2ff8e6c..00000000000 --- a/TMessagesProj/src/main/res/drawable/floating_user_states.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/gradient_bottom.png b/TMessagesProj/src/main/res/drawable/gradient_bottom.png new file mode 100755 index 00000000000..ecee422a3b6 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable/gradient_bottom.png differ diff --git a/TMessagesProj/src/main/res/drawable/gradient_top.png b/TMessagesProj/src/main/res/drawable/gradient_top.png new file mode 100755 index 00000000000..22b720270b9 Binary files /dev/null and b/TMessagesProj/src/main/res/drawable/gradient_top.png differ diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_backspace.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_backspace.xml deleted file mode 100644 index 51a217e25df..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_backspace.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_bell.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_bell.xml deleted file mode 100644 index 7d42e318d34..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_bell.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_car.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_car.xml deleted file mode 100644 index 0d6e59f9ffa..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_car.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_flower.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_flower.xml deleted file mode 100644 index 0a21067f142..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_flower.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_recent.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_recent.xml deleted file mode 100644 index e3c60248fdc..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_recent.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_smile.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_smile.xml deleted file mode 100644 index 71c0563d83d..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_smile.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/ic_emoji_symbol.xml b/TMessagesProj/src/main/res/drawable/ic_emoji_symbol.xml deleted file mode 100644 index 60c0d3c4f6b..00000000000 --- a/TMessagesProj/src/main/res/drawable/ic_emoji_symbol.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - diff --git a/TMessagesProj/src/main/res/drawable/item_background_holo_light.xml b/TMessagesProj/src/main/res/drawable/item_background_holo_light.xml deleted file mode 100644 index 652dc8a4c6a..00000000000 --- a/TMessagesProj/src/main/res/drawable/item_background_holo_light.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/list_selector_background_transition_holo_light.xml b/TMessagesProj/src/main/res/drawable/list_selector_background_transition_holo_light.xml deleted file mode 100644 index 41cae09a3ca..00000000000 --- a/TMessagesProj/src/main/res/drawable/list_selector_background_transition_holo_light.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TMessagesProj/src/main/res/drawable/list_selector.xml b/TMessagesProj/src/main/res/drawable/list_selector_ex.xml similarity index 100% rename from TMessagesProj/src/main/res/drawable/list_selector.xml rename to TMessagesProj/src/main/res/drawable/list_selector_ex.xml diff --git a/TMessagesProj/src/main/res/drawable/list_selector_white.xml b/TMessagesProj/src/main/res/drawable/list_selector_white.xml deleted file mode 100644 index 160ae131aaf..00000000000 --- a/TMessagesProj/src/main/res/drawable/list_selector_white.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/mute_fixed.xml b/TMessagesProj/src/main/res/drawable/mute_fixed.xml deleted file mode 100644 index 77b676a9480..00000000000 --- a/TMessagesProj/src/main/res/drawable/mute_fixed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/player_next_states.xml b/TMessagesProj/src/main/res/drawable/player_next_states.xml deleted file mode 100644 index b3fe031297c..00000000000 --- a/TMessagesProj/src/main/res/drawable/player_next_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/player_pause_states.xml b/TMessagesProj/src/main/res/drawable/player_pause_states.xml deleted file mode 100644 index 713cd0bc371..00000000000 --- a/TMessagesProj/src/main/res/drawable/player_pause_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/player_play_states.xml b/TMessagesProj/src/main/res/drawable/player_play_states.xml deleted file mode 100644 index 92447f398be..00000000000 --- a/TMessagesProj/src/main/res/drawable/player_play_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/player_prev_states.xml b/TMessagesProj/src/main/res/drawable/player_prev_states.xml deleted file mode 100644 index e34bbba26d5..00000000000 --- a/TMessagesProj/src/main/res/drawable/player_prev_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/s_player_pause_states.xml b/TMessagesProj/src/main/res/drawable/s_player_pause_states.xml deleted file mode 100644 index 8d9ed544834..00000000000 --- a/TMessagesProj/src/main/res/drawable/s_player_pause_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/s_player_play_states.xml b/TMessagesProj/src/main/res/drawable/s_player_play_states.xml deleted file mode 100644 index d5e885d1646..00000000000 --- a/TMessagesProj/src/main/res/drawable/s_player_play_states.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/theme_preview_image.jpg b/TMessagesProj/src/main/res/drawable/theme_preview_image.jpg new file mode 100644 index 00000000000..a929ceeeacf Binary files /dev/null and b/TMessagesProj/src/main/res/drawable/theme_preview_image.jpg differ diff --git a/TMessagesProj/src/main/res/layout/document_select_layout.xml b/TMessagesProj/src/main/res/layout/document_select_layout.xml deleted file mode 100644 index 9c9329f6154..00000000000 --- a/TMessagesProj/src/main/res/layout/document_select_layout.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - diff --git a/TMessagesProj/src/main/res/layout/group_create_bubble.xml b/TMessagesProj/src/main/res/layout/group_create_bubble.xml deleted file mode 100644 index 2f932acff7b..00000000000 --- a/TMessagesProj/src/main/res/layout/group_create_bubble.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - diff --git a/TMessagesProj/src/main/res/layout/media_loading_layout.xml b/TMessagesProj/src/main/res/layout/media_loading_layout.xml deleted file mode 100644 index 435491b6dfd..00000000000 --- a/TMessagesProj/src/main/res/layout/media_loading_layout.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/TMessagesProj/src/main/res/layout/popup_audio_layout.xml b/TMessagesProj/src/main/res/layout/popup_audio_layout.xml deleted file mode 100644 index cbd48a1f83d..00000000000 --- a/TMessagesProj/src/main/res/layout/popup_audio_layout.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/layout/popup_count_layout.xml b/TMessagesProj/src/main/res/layout/popup_count_layout.xml deleted file mode 100644 index 6011dc4cb9c..00000000000 --- a/TMessagesProj/src/main/res/layout/popup_count_layout.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/layout/popup_image_layout.xml b/TMessagesProj/src/main/res/layout/popup_image_layout.xml deleted file mode 100644 index e1fbaccd126..00000000000 --- a/TMessagesProj/src/main/res/layout/popup_image_layout.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/layout/popup_text_layout.xml b/TMessagesProj/src/main/res/layout/popup_text_layout.xml deleted file mode 100644 index 02358aace3a..00000000000 --- a/TMessagesProj/src/main/res/layout/popup_text_layout.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/voip_busy.mp3 b/TMessagesProj/src/main/res/raw/voip_busy.mp3 new file mode 100644 index 00000000000..1d6741e5eae Binary files /dev/null and b/TMessagesProj/src/main/res/raw/voip_busy.mp3 differ diff --git a/TMessagesProj/src/main/res/raw/voip_connecting.mp3 b/TMessagesProj/src/main/res/raw/voip_connecting.mp3 new file mode 100644 index 00000000000..fc425bab976 Binary files /dev/null and b/TMessagesProj/src/main/res/raw/voip_connecting.mp3 differ diff --git a/TMessagesProj/src/main/res/raw/voip_end.mp3 b/TMessagesProj/src/main/res/raw/voip_end.mp3 new file mode 100644 index 00000000000..1d6741e5eae Binary files /dev/null and b/TMessagesProj/src/main/res/raw/voip_end.mp3 differ diff --git a/TMessagesProj/src/main/res/raw/voip_failed.mp3 b/TMessagesProj/src/main/res/raw/voip_failed.mp3 new file mode 100644 index 00000000000..3b596dbcb14 Binary files /dev/null and b/TMessagesProj/src/main/res/raw/voip_failed.mp3 differ diff --git a/TMessagesProj/src/main/res/raw/voip_ringback.mp3 b/TMessagesProj/src/main/res/raw/voip_ringback.mp3 new file mode 100644 index 00000000000..a92325ba31b Binary files /dev/null and b/TMessagesProj/src/main/res/raw/voip_ringback.mp3 differ diff --git a/TMessagesProj/src/main/res/values-ar/strings.xml b/TMessagesProj/src/main/res/values-ar/strings.xml index d091e22a93a..5691df2ace7 100644 --- a/TMessagesProj/src/main/res/values-ar/strings.xml +++ b/TMessagesProj/src/main/res/values-ar/strings.xml @@ -41,7 +41,56 @@ الاسم الأول اسم العائلة إلغاء التسجيل + + لقد قمت بنجاح تحويل %1$s إلى %2$s لـ %3$s + لقد قمت بنجاح تحويل %1$s إلى %2$s + الدفع + طرق الشحن + المعذرة، لا يمكن التوصيل لعنوانك. + معلومات الشحن + عنوان الشحن + العنوان ١ (الشارع) + العنوان ٢ (الشارع) + المدينة + الولاية + الدولة + الرمز البريدي + المستقبل + الاسم الكامل + رقم الهاتف + البريد الالكتروني + حفظ معلومات الشحن + يمكنك حفظ معلومات الشحن لاستخدامها لاحقًا. + معلومات الشحن + بطاقة الدفع + رقم البطاقة + رمز الأمان (CVV) + MM/YY + عنوان الدفع + صاحب البطاقة + الاسم + حفظ معلومات الدفع + يمكنك حفظ معلومات الدفع لاستخدامها لاحقًا. + فضلًا قم بتفعيل *التحقق بخطوتين* للتفعيل. + مراجعة العملية + هل حقًا ترغب في تحويل %1$s إلى البوت %2$s لـ %3$s؟ + الإجمالي + الفاتورة + الفاتورة التجريبية + إدفع %1$s + وسيلة الدفع + الاسم + رقم الهاتف + عنوان التواصل + طريقة الشحن + الفاتورة + اختر بطاقة أخرى + بطاقتك %1$s مسجلة مسبقًا. لتتمكن من استخدامها، يرجى إدخال كلمة مرور التحقق بخطوتين. + المعذرة، تم إلغاء عملية الدفع من البوت. + المعذرة، تم رفض عملية الدفع. + تعذر الوصور إلى خادم الدفع. فضلًا تحقق من اتصال الانترنت وحاول مرة أخرى. + محادثات جديدة الإعدادات جهات الاتصال مجموعة جديدة @@ -80,6 +129,16 @@ معاينة الرابط مسودة تم مسح السجل + من %1$s + %1$s من %2$s + اكتب رأيك بخصوص هذا الإستعراض + أرسل ملصق + استعرض الحزمة + ثبت في الأعلى + إلغاء التثبيت + عريض + مائل + طبيعي نوع المجموعة نوع القناة @@ -110,7 +169,7 @@ un1 ثبت ملصق un1 ثبت رسالة صوتية un1 ثبت جهة اتصال - un1 ثبت a %1$s + un1 ثبت %1$s un1 ثبت خريطة un1 ثبت صورة متحركة un1 ثبت مقطع صوتي @@ -173,7 +232,7 @@ تم تغيير صورة القناة تم حذف صورة القناة تم تغيير اسم القناة إلى un2 - المعذرة، قمت بحجز معرفات عامة كثيرة .يمكنك إلغاء بعض روابط مجموعاتك وقنواتك القديمة، أو قم بجعلها خاصة. + المعذرة، قمت بحجز معرفات عامة كثيرة. يمكنك إلغاء بعض روابط مجموعاتك وقنواتك القديمة، أو قم بجعلها خاصة. المراقب المنشئ إداري @@ -267,11 +326,14 @@ %1$s يقوم بتسجيل رسالة صوتية... %1$s يقوم بإرسال مقطع صوتي... %1$s يقوم بإرسال صورة... + استعراض لحظي + %1$s يلعب لعبة... %1$s يقوم بإرسال مقطع مرئي... %1$s يقوم بإرسال ملف... جاري تسجيل الرسالة الصوتية... جاري إرسال المقطع الصوتي... جاري إرسال الصورة... + يلعب لعبة... جاري إرسال المقطع المرئي... جاري إرسال الملف... هل يوجد لديك سؤال\nحول تيليجرام؟ @@ -307,6 +369,7 @@ احفظ في الموسيقى مشاركة تطبيق ملف التعريب + تطبيق ملف النمط المرفق غير مدعوم عداد التدمير الذاتي إشعارات الخدمة @@ -323,8 +386,10 @@ هل أنت متأكد من رغبتك في الإبلاغ عن هذا المستخدم كغير مرغوب به؟ هل أنت متأكد من رغبتك في الإبلاغ عن هذه المجموعة كغير مرغوب بها؟ هل أنت متأكد من رغبتك في الإبلاغ عن هذه القناة كغير مرغوب بها؟ - المعذرة، يمكنك فقط إرسال رسائل لمن يمتلك رقمك وتمتلك رقمه في الوقت الحالي. - المعذرة، يمكنك فقط إضافة من يمتلك رقمك وتمتلك رقمه للمجموعة في الوقت الحالي. + المعذرة، فقط يمكنك مراسلة جهات الاتصال المشتركة في الوقت الحالي. + المعذرة، فقط يمكنك إضافة جهات الاتصال المشتركة للمجموعات في الوقت الحالي. + لقد قمت بمراسلة أشخاص كثيرين اليوم من خارج جهات الاتصال لديك. فضلًا حاول مرة أخرى غدًا. ستتمكن من الرد اليوم إذا قام المستخدم بمراسلتك أولًا. + لا يمكنك إضافة هذا المستخدم لأنك قمت بمراسلة أشخاص كثيرين اليوم من خارج جهات الاتصال لديك. فضلًا حاول مرة أخرى غدًا. يمكنك الطلب من عضو آخر إضافة هذا المستخدم للمجموعة. https://telegram.org/faq/can-39t-send-messages-to-non-contacts ملعومات إضافية أرسل إلى... @@ -358,6 +423,10 @@ افتح هذه المحادثة من أي جهاز من أجهزتك استخدم البحث لتجد الأشياء بسرعة مساحتك السحابية + اذهب لهذا التاريخ + حذف لـ%1$s + حذف لكافة الأعضاء + تم نسخ النص للحافظة %1$s قام بتعيين عداد التدمير الذاتي إلى to %2$s لقد قمت بتعيين التدمير الذاتي إلى %1$s @@ -451,11 +520,12 @@ آخر ظهور خلال فترة طويلة رسالة جديدة - إرسال الرسالة إلى... + أضف أشخاص... ستتمكن من إضافة أعضاء أكثر إذا انتهيت من إنشاء المجموعة وقمت بتحويلها إلى مجموعة خارقة. أدخل اسم للمجموعة اسم المجموعة - %1$d/%2$d عضو + تم اختيار %1$d من %2$d + حتى %1$s هل ترغب في الدخول للمحادثة \'%1$s\'؟ المعذرة، هذه المجموعة ممتلئة. المعذرة، هذه المحادثة غير موجودة. @@ -507,12 +577,16 @@ آخر الرئيسية إبدأ محادثة سرية + المجموعات المشتركة + المجموعات المشتركة + لا توجد مجموعات مشتركة بعد حدث خطأ. مفتاح التشفير عداد التدمير الذاتي إيقاف هذه الصورة والنص تم اشتقاقهم من مفتاح التشفير لهذه المحادثة السرية مع ]]>%1$s]]>.
          ]]>إذا كانت مطابقة لما يظهر على جهاز ]]>%2$s]]> ، التشفير من البداية للنهاية مضمون.
          ]]>للإستزادة، إطلع على telegram.org
          https://telegram.org/faq#secret-chats + اضغط للتحويل لإيموجي غير معروف معلومات هاتف @@ -525,6 +599,7 @@ اسم المستخدم يجب ألا يتخطى ٣٢ حرف كحد أقصى. المعذرة، اسم المستخدم لا يمكن أن يبدأ برقم. يمكنك اختيار اسم مستخدم في ]]>تيليجرام]]>. إذا قمت بذلك، سيستطيع الناس إيجادك باستخدام الاسم المستخدم والتواصل معك من دون معرفة رقمك.
          ]]>يمكنك استخدام ]]>حروف اللغة الإنجليزية]]>, ]]>وأرقامها]]> و كذلك الخط. لا بد من استخدام ]]>٥]]> حروف على الأقل.
          + هذا الرابط يقوم بفتح محادثة معك في تيليجرام:\n%1$s جارٍ مراجعة اسم المستخدم... %1$s متاح. لا يوجد @@ -561,6 +636,20 @@ تمت أرشفة بعض من حزم الملصقات الخاصة بك. يمكنك تفعيل هذه الحزم ان طريق إعدادات الملصقات. الأقنعة المؤرشفة تمت أرشفة بعض من حزم الأقنعة الخاصة بك. يمكنك تفعيل هذه الحزم ان طريق إعدادات الأقنعة. + + نمط + هل أنت متأكد من رغبتك في حذف النمط؟ + ملف النمط غير صحيح + أدخل اسم النمط + أغلق المحرر + حفظ النمط + نمط جديد + تطبيق + معاينة النمط + اختر لون + إنشاء نمط جديد + اضغط على أيقونة الألوان لاستعراض قائمة الخيارات في كل شاشة - ثم قم بتعديلهم. + يمكنك إنشاء النمط الخاص بك بتغيير الألوان داخل التطبيق. يمكنك دائمًا العودة لنمط تيليجرام الأصلي من هنا. تم تعيين كافة الإشعارات افتراضيا حجم نص الرسائل @@ -583,6 +672,27 @@ إعادة تعيين كافة الإشعارات إعادة تعيين كافة إعدادات الإشعارات لجميع جهات الاتصال والمجموعات الإشعارات والأصوات + الإشعارات مخصصة + الإشعارات المنبثقة + الرسائل الجديدة من هذا المستخدم ستظهر على شاشتك حتى عند عدم استخدامك لتيليجرام. + LED + اللون + أزرق + أحمر + أصفر + أخضر + سماوي + أبيض + وردي + بنفسجي + برتقالي + الـ LED هي إضاءة صغيرة تومض في بعض الأجهزة كإشعار برسالة جديدة. + الإشعارات الأكثر أهمية ستعمل حتى عندما يكون جهازك في وضعية عدم الإزعاج. + عام + تشغيل + إيقاف + تشغيل + إيقاف جهات الاتصال المحظورة تسجيل خروج لا يوجد صوت @@ -597,7 +707,7 @@ اشترك صديق في تيليجرام رسائل مثبتة اللغة - نرجو الأخذ بالعلم أن الدعم الفني في تيليجرام يقوم به مجموعة من المتطوعين. نحن نحاول الرد بسرعة قدر المستطاع، لكن ربما نستغرق القليل من الوقت.
          ]]> صفحة الأسئلة الأكثر شيوعًا]]>: يوجد بها حلول للمشاكل وإجابات لمعظم الأسئلة.
          + نرجو الأخذ بالعلم أن الدعم الفني في تيليجرام يقوم به مجموعة من المتطوعين. نحن نحاول الرد بسرعة قدر المستطاع، لكن ربما نستغرق القليل من الوقت.
          ]]> صفحة الأسئلة الأكثر شيوعًا]]>: يوجد بها حلول للمشاكل وإجابات لمعظم الأسئلة.
          اسأل أحد المتطوعين الأسئلة الشائعة عن تيليجرام https://telegram.org/faq/ar @@ -624,8 +734,6 @@ عداد الشارات قصير طويل - النظام الإفتراضي - الإعدادات الإفتراضية تنزيل تلقائي للوسائط عند استخدام البيانات الخلوية عند الاتصال بالشبكة اللاسلكية @@ -635,7 +743,11 @@ ارفع الجهاز للتحدث حفظ في الجهاز تعديل الاسم + تخصيص + مخصص + تفعيل الإشعارات مخصصة الأولوية + كما في الإعدادات افتراضي منخفض مرتفع @@ -651,29 +763,32 @@ آخر تعطيل تعطيل + مفعل + معطل تعطيل إيقاف الأصوات داخل المحادثات افتراضي تلقائي إشعارات ذكية + %1$d / %2$s تعطيل - أعلى صوت %1$s خلال %2$s - أعلى صوت - الأوقات - خلال - دقائق + تردد صوت التنبيه + %1$s خلال %2$s معاينة الرابط المحادثات السرية علامات تبويب كروم المخصصة فتح الروابط الخارجية داخل التطبيق مشاركة مباشرة اظهار المحادثات الأخيرة في قائمة المشاركة - Emoji - Draw single big emoji - Use system default emoji + ايموجي + ارسم ايموجي واحد كبير + استخدم نظام الايموجي الافتراضي + تيليجرام للأندرويد %1$s + قائمة المعالجة + استيراد جهات الاتصال + إعادة تنزيل جهات الاتصال - إعدادات الذاكرة المخبئية قاعدة البيانات على الجهاز هل ترغب في مسح الرسائل المحفوظة في الذاكرة المخبئية؟ مسح قاعدة البيانات على الجهاز سيحذف الرسائل التي تم تنزيلها على جهازك ويقوم بضغط قاعدة البيانات لتوفير مساحة على جهازك. تيليجرام يحتاج لبعض البيانات ليعمل، لذلك حجم قاعدة البيانات لن يصل إلى صفر.\n\nهذه العملية ربما تأخذ عدة دقائق لتتم. @@ -685,6 +800,7 @@ الرسائل الصوتية المقاطع المرئية الموسيقى + الصور المتحركة الملفات الأخرى إفراغ الإحتفاظ بالوسائط @@ -722,6 +838,8 @@ قم بتأكيد البصمة للإستمرار حساس اللمس لم يتم التعرف على البصمة. حاول مرة أخرى + اسمح بتصوير الشاشة + إذا تم التفعيل، ستتمكن من التقاط صور للشاشة داخل التطبيق، لكن النظام سيعرض محادثاتك في شاشة تعدد المهام حتى في حال تفعيل رمز المرور.\n\nربما تحتاج لإعادة تشغيل التطبيق لتفعيل هذا الخيار. الملفات المشاركة الوسائط المشتركة @@ -798,6 +916,7 @@ أضف تعليق... تعليق الصورة تعليق المقطع المرئي + تعليق الصورة المتحركة تعليق ارسم ملصقات @@ -807,6 +926,9 @@ تكرار مرسوم طبيعي + إعادة تعيين + الأصل + مربع التحقق بخطوتين تعيين كلمة مرور إضافية @@ -857,6 +979,27 @@ تم تعطيل كلمة المرور لقد قمت بتفعيل التحقق بخطوتين.\nعند محاولة تسجيل الدخول على حساب تيليجرام الخاص بك من جهاز جديد، سيتم طلب كلمة المرور التي اخترتها هنا منك. البريد الإلكتروني لإسترداد الحساب %1$s غير فعّال بعد ويلزم تفعيله + + البيانات والتخزين + استخدام الشبكة والتخزين + استخدام التخزين + استخدام بيانات الجوال + استخدام تجوال البيانات + استخدام بيانات الواي فاي + الرسائل والبيانات الأخرى + الصادر + الوارد + بايتات مرسلة + بايتات مستقبلة + ملفات + مكالمات + مكالمات صادرة + مكالمات واردة + إجمالي الوقت + إجمالي + مسح الإحصائيات + استخدام خلوي منذ %1$s + هل ترغب في مسح إحصائيات استخدامك؟ الخصوصية والأمان الخصوصية @@ -904,7 +1047,7 @@ تحرير الفيديو جارٍ إرسال المقطع المرئي... - جاري إرسال الصورة المتحركة... + جاري إرسال صورة متحركة... بوت مشاركة @@ -941,6 +1084,8 @@ تعيين موافق قطع + نعم + لا لقد قمت بالدخول للمجموعة باستخدام رابط الدعوة un1 قام بالدخول للمجموعة باستخدام رابط الدعوة @@ -987,6 +1132,7 @@ محاولات كثيرة خاطئة، نرجو المحاولة لاحقًا محاولات كثيرة خاطئة، يرجى المحاولة خلال %1$s الرمز غير صحيح + المعذرة، قمت بحذف حسابك وإعادة إنشاؤه عدة مرات مؤخرًا. فضلًا انتظر لعدة أيام قبل محاولة إنشاء الحساب من جديد. الاسم الأول غير صحيح اسم العائلة غير صحيح جاري التحميل ... @@ -1038,16 +1184,20 @@ المعذرة، لا يمكنك التعديل على هذه الرسالة. فصلًا قم بالسماح لتيليجرام باستقبال رسائل قصيرة ليتمكن من إدخال الرمز لك تلقائيًا. فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ليتمكن من إدخال الرمز لك تلقائيًا. + فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ورسائل قصيرة ليتمكن من إدخال رقمك، إرسال رمز التفعيل إليك، وإدخال الرمز لك تلقائيًا. فصلًا قم بالسماح لتيليجرام باستقبال اتصالات ورسائل قصيرة ليتمكن من إدخال الرمز لك تلقائيًا. المعذرة، لا يمكنك القيام بذلك. + المعذرة، لا يمكنك إضافة هذا المستخدم أو البوت للمجموعات بسبب أنك قمت بحظره. فضلًا قم بإزالة الحظر للإستمرار. انضم للمجموعة تيليجرام يحتاج للسماح له بالوصول لجهات الاتصال الخاصة بك لتتمكن من محادثة أصدقائك من كافة أجهزتك. تيليجرام يحتاج للسماح له بالوصول للذاكرة الخاصة بك لتتمكن من إرسال وحفظ الصور، المقاطع المرئية، الموسيقى وغيرها من الوسائط. تيليجرام يحتاج للسماح له بالوصول للمايكروفون الخاص بك لتتمكن من إرسال رسائل صوتية. + تيليجرام يحتاج للسماح له بالوصول للمايكروفون الخاص بك لتتمكن من إرسال فيديوهات. تيليجرام يحتاج للسماح له بالوصول للكاميرا لتتمكن من التقاط صور ومقاطع مرئية. تيليجرام يحتاج للسماح له بالوصول لمكانك لتتمكن من مشاركته مع أصدقائك من خلاله. تيليجرام يحتاج صلاحية الحصول على موقعك. + تيليجرام يحتاج الصلاحية ليقوم بتشغيل الفيديو داخل الشاشة بعد الخروج من التطبيق. الإعدادات تيليجرام @@ -1065,6 +1215,66 @@ تيليجرام]]> يمكنك الوصول إلى الرسائل الخاصة بك من أجهزة متعددة. تيليجرام]]> الرسائل مشفرة بشكل قوي وتستطيع تدمير ذاتها إبدأ المراسلة + + إعدادات الحساب + استخدم بيانات أقل للاتصال. + مكالمات واردة + جاري الإتصال + جاري مبادلة مفاتيح التشفير + انتظار + جاري الطلب + جاري إنهاء المكالمة + تم إنهاء المكالمة + فشل في الاتصال + يتم الاتصال + الخط مشغول + مكالمة تيليجرام + مكالمة تيليجرام الحالية + إنهاء المكالمة + مكالمة أخرى نشطة + لديك حاليًا مكالمة نشطة مع %1$s]]>. هل ترغب في إنهاء المكالمة وبدء مكالمة جديدة مع %2$s]]>؟ + مكالمات صوتية + النغمة + يمكنك تخصيص نغمة الاتصال عندما يتصل بك هذا المعرف من خلال تيليجرام. + مكالمات + من يستطيع الاتصال بي؟ + يمكنك تحديد من يستطيع الاتصال بك. + هؤلاء المستخدمين بإمكانهم أو ليس بإمكانهم الاتصال بك بغض النظر عن الإعدادات أعلاه. + أبدًا + فقط على البيانات الخلوية + دائمًا + رد + رفض + أنت حاليًا غير متصل. يرجى الاتصال بالانترنت لتتمكن من إجراء مكالمات. + لقد قمت بتفعيل وضع الطيران. يرجى تشغيل الواي فاي لتتمكن من إجراء اتصالات. + غير متصل + وضع الطيران + الإعدادات + مكالمات صادرة + مكالمات واردة + مكالمات فائتة + مكالمات ملغاة + مكالمة مرفوضة + %1$s (%2$s) + هذه الصورة والنص تم اشتقاقهم من مفتاح التشفير لهذه المحادثة السرية مع ]]>%1$s]]>.
          ]]>إذا كانت مطابقة لما يظهر على جهاز ]]>%2$s]]> ، التشفير من البداية للنهاية مضمون.
          + لم تقم بإجراء أية مكالمة بعد. + إصدار تيليجرام الخاص بـ]]>%1$s]]> غير متوافق. ينبغي عليه تحديث إصدارهم لتتمكن من الاتصال بهم. + تطبيق تيليجرام الخاص بـ]]>%1$s]]> لا يدعم المكالمات. ينبغي عليه تحديث تطبيقهم لتتمكن من الاتصال بهم. + فضلًا قم بتقييم جودة الاتصال في تيليجرام + هل ترغب في ترك أية ملاحظات لتطوير خدمة المكالمات لدينا؟ + تيليجرام يحتاج للسماح له بالوصول للمايكروفون الخاص بك لتتمكن من إجراء مكالمات. + أضف تعليق اختياري + أعد الاتصال بالرقم + اتصل مرة أخرى + افتراضي + هل أنت متأكد من رغبتك في حذف المكالمة من سجل المكالمات؟ + مكالمة تيليجرام + سماعة الأذن + سماعة + بلوتوث + الرجوع للمكالمة + المعذرة، ]]>%1$s]]> لا يستقبل مكالمات. + إذا كانت هذه الايموجيات مطابقة للتي تظهر على شاشة %1$s، فالمكالمة 100%% آمنة. %1$d متصل %1$d متصل @@ -1284,6 +1494,8 @@ و %1$d غيرهم و %1$d غيرهم + MMM dd yyyy, h:mm a + MMM dd yyyy, HH:mm MMMM yyyy MMM dd dd.MM.yy diff --git a/TMessagesProj/src/main/res/values-de/strings.xml b/TMessagesProj/src/main/res/values-de/strings.xml index 89754dae45c..4a5133a0ab9 100644 --- a/TMessagesProj/src/main/res/values-de/strings.xml +++ b/TMessagesProj/src/main/res/values-de/strings.xml @@ -41,7 +41,56 @@ Vorname (erforderlich) Nachname (optional) Registrierung abbrechen + + Du hast erfolgreich %1$s an %2$s für %3$s übermittelt. + Du hast erfolgreich %1$s an %2$s übermittelt. + Kasse + Versandarten + Tut uns sehr leid, leider kann an deine Adresse nichts geliefert werden. + Versandinfo + Versandadresse + Adresse 1 (Straße) + Adresse 2 (Straße) + Stadt + Bundesland + Land + PLZ + Empfänger + Vor- und Nachname + Telefonnummer + E-Mail + Versandinformationen speichern + Du kannst deine Versandinformationen für zukünftige Bestellungen speichern. + Zahlungsinformationen + Zahlungskarte + Kartennummer + Kartenprüfnummer (CVV) + MM/JJ + Rechnungsadresse + Karteninhaber + Vollständiger Name + Zahlungsinformationen speichern + Du kannst deine Zahlungsinformationen für zukünftige Bestellungen speichern. + Bitte *schalte die zweistufige Bestätigung ein*, um es zu aktivieren. + Überprüfung der Transaktion + Möchtest du wirklich %1$s an den %2$s Bot für %3$s übermitteln? + Gesamtsumme + RECHNUNG + TEST-RECHNUNG + %1$s BEZAHLEN + Zahlungsart + Name + Telefonnummer + Kontaktadresse + Versandart + Beleg + Wähle eine andere Karte + Deine Karte %1$s ist bereits gespeichert. Um mit dieser Karte zu bezahlen, tippe dein Kennwort der zweistufigen Bestätigung ein. + Tut uns sehr leid. Die Zahlung wurde durch den Bot abgebrochen. + Tut uns sehr leid. Die Zahlung wurde abgelehnt. + Kann den Zahlungsserver nicht erreichen. Bitte prüfe deine Internetverbindung und versuche es erneut. + Neue Unterhaltung Einstellungen Kontakte Neue Gruppe @@ -50,12 +99,12 @@ Noch keine Chats… Tippe unten auf den Stift für deine erste\nChatnachricht oder auf den Menüknopf\num die restlichen Optionen zu öffnen. Warte auf Netzwerk... - Verbinde… - Aktualisiere… + Verbinde... + Aktualisiere... Neuer Geheimer Chat - Warte, bis %s online geht… + Warte, bis %s online geht... Geheimer Chat beendet - Tausche Schlüssel aus… + Tausche Schlüssel aus... %s ist deinem geheimen Chat beigetreten. Du bist dem geheimen Chat beigetreten. Verlauf löschen @@ -80,6 +129,16 @@ Linkvorschau Entwurf Verlauf wurde gelöscht + von %1$s + %1$s von %2$s + Rückmeldung zu dieser Vorschau hinterlassen + Sticker senden + Paket anzeigen + Anheften + Entfernen + Fett + Kursiv + Normal Gruppenart Kanalart @@ -92,7 +151,7 @@ Gruppe verlassen Gruppe löschen Du verlierst alle Nachrichten der Gruppe. - Administratoren helfen dir, deinen Kanal zu verwalten. Tippen und halten um sie zu löschen. + Administratoren helfen dir, deine Gruppe zu verwalten. Tippen und halten um sie zu löschen. Wenn du diese Gruppe löschst, werden alle Mitglieder und alle Nachrichten entfernt. Wirklich löschen? Gruppe erstellt un1 hat dich hinzugefügt @@ -110,7 +169,7 @@ un1 hat einen Sticker angeheftet un1 hat eine Sprachnachricht angeheftet un1 hat einen Kontakt angeheftet - un1 hat ein %1$s angeheftet + un1 hat %1$s angeheftet un1 hat einen Standort angeheftet un1 hat ein GIF angeheftet un1 hat ein Musikstück angeheftet @@ -135,7 +194,7 @@ privat Private Gruppe Kann man nur per Einladungslink finden - Private Gruppen können nur nur durch direkte Einladungen oder über einen Einladungslink betreten werden. + Private Gruppen können nur durch direkte Einladungen oder über einen Einladungslink betreten werden. Link Einladungslink Mitglieder hinzufügen @@ -267,11 +326,14 @@ %1$s nimmt etwas auf... %1$s schickt Audio... %1$s schickt Bild... + SCHNELLANSICHT + %1$s spielt ein Spiel... %1$s schickt Video... %1$s schickt Datei... nimmt etwas auf... schickt Audio... schickt Bild... + spielt ein Spiel... schickt Video... schickt Datei... Hast du eine Frage\nzu Telegram? @@ -281,7 +343,7 @@ Video Datei Kamera - Noch keine Nachrichten… + Noch keine Nachrichten... Weitergeleitete Nachricht Von Keine aktuellen @@ -307,6 +369,7 @@ Musik speichern Teilen Sprachdatei benutzen + Thema benutzen Nicht unterstützte Datei Selbstzerstörungs-Timer setzen Servicemeldungen @@ -323,8 +386,10 @@ Sicher, dass du Spam von diesem Nutzer melden willst? Sicher, dass du Spam von dieser Gruppe melden willst? Sicher. dass du Spam von diesem Kanal melden möchtest? - Derzeit kannst du nur Kontakten schreiben, die auch deine Nummer haben. - Derzeit kannst du nur Kontakte hinzufügen, die auch deine Nummer haben. + Du kannst im Moment nur Kontakten schreiben, die auch deine Nummer haben. + Derzeit kannst du nur gemeinsame Kontakte Gruppen hinzufügen. + Du hast heute zu viele nicht-gemeinsame Kontakte angeschrieben, bitte probiere es morgen wieder. Du kannst auf Nachrichten antworten, wenn dir die Person zuerst geschrieben hat. + Du kannst diesen Nutzer nicht hinzufügen, weil du heute zu viele nicht-gemeinsame Kontakte angeschrieben hast. Bitte morgen wieder probieren oder einen anderen Teilnehmer der Gruppe fragen, den Nutzer hinzuzufügen. https://telegram.org/faq/de#kann-keine-nachrichten-an-nicht-kontakte-senden Mehr Infos Sende an... @@ -358,6 +423,10 @@ Synchronisiert auf allen Geräten Cloud-basierte Suchfunktion Dein Cloud-Speicher + Gehe zu Datum + Bei %1$s löschen + Bei allen Mitgliedern löschen + Text in die Zwischenablage kopiert %1$s hat den Selbstzerstörungs-Timer auf %2$s gesetzt Du hast den Selbstzerstörungs-Timer auf %1$s gesetzt @@ -451,11 +520,12 @@ vor langer Zeit gesehen Neue Nachricht - Sende Nachricht an… + Leute hinzufügen... Sobald du diese Gruppe zu einer Supergruppe erweitert hast, kannst du mehr Nutzer einladen. Gruppenname Gruppenname - %1$d/%2$d Mitglieder + %1$d von %2$d ausgewählt + bis zu %1$s Möchtest du dem Chat \'%1$s\' beitreten? Leider ist diese Gruppe schon voll. Leider gibt es diesen Chat nicht. @@ -507,12 +577,16 @@ Sonstiges Hauptnummer Geheimen Chat starten + Gemeinsame Gruppen + Gemeinsame Gruppen + Noch keine gemeinsamen Gruppen Es ist ein Fehler aufgetreten. Geheimer Schlüssel Selbstzerstörungs-Timer Aus Bild und Text zeigen den aktuellen Schlüssel dieses geheimen Chats mit ]]>%1$s]]>.
          ]]>Sehen sie auf dem Gerät von ]]>%2$s]]> genau so aus, ist eure Sicherheit garantiert.
          ]]>Erfahre mehr unter telegram.org
          https://telegram.org/faq/de#geheime-chats + Tippen für Emoji-Ansicht Unbekannt Info Telefon @@ -525,6 +599,7 @@ Ein Benutzername darf maximal 32 Zeichen haben. Benutzernamen dürfen leider nicht mit einer Zahl anfangen. Wähle einen öffentlichen Benutzernamen, wenn du von anderen bei ]]>Telegram]]> gefunden werden willst — ohne, dass sie deine Nummer kennen müssen.
          ]]>Erlaubt sind ]]>a-z]]>, ]]>0-9]]> und Unterstriche. Die Mindestlänge beträgt ]]>5]]> Zeichen.
          + Dieser Link öffnet einen Chat mit dir:\n%1$s Prüfe Benutzername... %1$s ist verfügbar. Keiner @@ -561,6 +636,20 @@ Einige deiner Sticker wurden archiviert. Du kannst sie in den Sticker-Einstellungen erneut hinzufügen. Archivierte Masken Einige deiner Masken wurden archiviert. Du kannst sie in den Masken-Einstellungen erneut hinzufügen. + + Thema + Möchtest du wirklich dieses Thema löschen? + Themen-Datei ist fehlerhaft. + Einen Namen festlegen + EDITOR SCHLIESSEN + THEMA SPEICHERN + Neues Thema + ANWENDEN + Vorschau + Farbe wählen + Neues Thema erstellen + Tippe auf das kleine Palettensymbol um eine Auswahl des jeweiligen Bildschirms anzuzeigen und wähle deine Wunschfarben. + Erstelle dein eigenes Thema, indem du die Farben innerhalb der App änderst. Hier kannst du jederzeit zum vorgegebenen Telegram Thema zurückwechseln. Alle Einstellungen für Mitteilungen zurücksetzen Textgröße für Nachrichten @@ -583,6 +672,27 @@ Mitteilungseinstellungen zurücksetzen Setzt alle benutzerdefinierten Einstellungen für Mitteilungen zurück Mitteilungen und Töne + Eigene Benachrichtigungen + Popup Mitteilungen + Neue Nachrichten von diesem Kontakt erscheinen auf deinem Bildschirm, wenn du Telegram nicht benutzt. + LED + Farbe + Blau + Rot + Gelb + Grün + Cyan + Weiß + Pink + Violett + Orange + Einige Geräte besitzen ein kleines Licht, welches farbig leuchten oder blinken kann, um dich über neue Nachrichten zu informieren. + Mitteilungen höherer Priorität funktionieren auch, wenn sich dein Telefon im \"Bitte nicht stören\"-Modus befindet. + Allgemein + An + Aus + Einschalten + Ausschalten Blockierte Benutzer Abmelden Kein Ton @@ -592,12 +702,12 @@ Chat-Hintergrundbild Nachrichten Mit Enter senden - Alle anderen Geräte abmelden + Alle anderen Sitzungen abmelden Ereignisse Kontakt ist Telegram beigetreten Angeheftete Nachrichten Sprache - Bedenke bitte, dass der Telegram Support von ehrenamtlichen Helfern betreut wird. Wir versuchen so schnell wie möglich zu antworten, dies kann jedoch manchmal ein bisschen dauern.
          ]]>Bitte schau auch in den Fragen und Antworten ]]> nach. Dort findest du Antworten auf die meisten Fragen und wichtige Tipps zur Problembehandlung]]>.
          + Bedenke bitte, dass der Telegram Support von ehrenamtlichen Helfern betreut wird. Wir versuchen so schnell wie möglich zu antworten, dies kann jedoch manchmal ein bisschen dauern.
          ]]>Bitte schau auch in den Fragen und Antworten ]]> nach. Dort findest du Antworten auf die meisten Fragen und wichtige Tipps zur Problembehandlung]]>.
          Eine Frage stellen Fragen und Antworten https://telegram.org/faq/de @@ -624,8 +734,6 @@ Kennzeichensymbol Kurz Lang - Systemvorgabe - Telegramvorgabe Automatischer Mediendownload Über Mobilfunk Über W-LAN @@ -635,14 +743,18 @@ Zum Sprechen ans Ohr In der Galerie speichern Name bearbeiten + Anpassen + Eigene + Eigene Benachrichtigungen Priorität + Wie in Einstellungen Standard Niedrig Hoch Max. Niemals Erneut benachrichtigen - Du kannst deine Telefonnummer hier ändern. Dein Konto und alle Daten in der Telegram-Cloud, also Nachrichten, Medien, Kontakte, etc. werden auf das neue Konto übertragen.\n\nWichtig:]]> Alle deine Kontakte erhalten deine neue Nummer]]> ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. + Du kannst deine Telefonnummer hier ändern. Dein Konto und alle Daten in der Telegram-Cloud, also Nachrichten, Medien, Kontakte, etc. werden auf das neue Konto übertragen.\n\nWichtig:]]> Alle deine Kontakte erhalten deine neue Nummer]]> ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. Deinen Kontakten wird deine neue Nummer ihrem Telefonbuch hinzugefügt, sofern sie deine alte Nummer gespeichert hatten und du sie nicht blockiert hattest. NUMMER ÄNDERN Neue Nummer @@ -651,18 +763,18 @@ Sonstige Deaktiviert Deaktiviert + Aktiviert + Deaktiviert Deaktiviert Aus In-Chat-Töne Standard Standard Intelligente Benachrichtigungen + %1$d / %2$s Deaktiviert - Höchstens %1$s innerhalb von %2$s - Höchstens - Mal - innerhalb von - Minuten + Anzahl Benachrichtigungen + %1$s innerhalb %2$s Linkvorschau Geheime Chats In-App Browser @@ -671,9 +783,12 @@ Letzte Chats im Teilen-Menü anzeigen Emoji Draw single big emoji - Use system default emoji + Benutze System-Emoji + Telegram für Android %1$s + Debug-Menü + Kontakte importieren + Kontakte neu laden - Cache-Einstellungen Lokale Datenbank Textnachrichten-Cache leeren? Zwischengespeicherte Textnachrichten werden entfernt und die Datenbank optimiert um Speicherplatz zurückzuerhalten. Auf Null lässt sich die Größe jedoch nicht reduzieren, da die App einige Daten für den laufenden Betrieb benötigt.\n\nHinweis: Der Vorgang kann mehrere Minuten dauern. @@ -685,6 +800,7 @@ Sprachnachrichten Videos Musik + GIFs Sonstige Dateien Leer Medien behalten @@ -692,13 +808,13 @@ Dauerhaft Sitzungen - Aktuelles Gerät - Keine anderen Geräte + Aktuelle Sitzung + Keine anderen Sitzungen Du kannst dich von jedem Handy, Tablet und Computer bei Telegram mit derselben Telefonnummer anmelden. Alles wird immer sofort synchronisiert. - Andere Geräte - Überprüfe alle deine angemeldeten Geräte. + Andere Sitzungen + Überprüfe alle deine angemeldeten Sitzungen. Tippe auf eine Sitzung um sie zu beenden. - Dieses Gerät abmelden? + Diese Sitzung abmelden? inoffizielle Version Pincode-Sperre @@ -722,6 +838,8 @@ Fingerabdruck bestätigen Berührungssensor Abdruck nicht erkannt; erneut versuchen + Bildschirmfotos erlauben + Aktivierst du diese Funktion, so kannst du Screenshots in der App aufnehmen. Wenn du einen Pincode festgelegt hast, zeigt das System jedoch weiterhin deine Chats in der Liste der geöffneten App an.\n\nDu musst die App nach der Änderung neu starten. Geteilte Dateien Geteilte Medien @@ -798,6 +916,7 @@ Beschriftung... Bildbeschriftung Videobeschriftung + GIF Beschriftung Beschriftung Zeichnen Sticker @@ -807,6 +926,9 @@ Klonen Kontur Normal + Zurücksetzen + Original + Quadrat Zweistufige Bestätigung Zusätzliches Kennwort festlegen @@ -818,7 +940,7 @@ Bitte erneut dein Kennwort eingeben Wiederherstellung Deine E-Mail - Falls du dein Kennwort vergisst, benötigen wir deine richtige Email Adresse. + Falls du dein Kennwort vergisst, benötigen wir deine richtige E-Mail Adresse. Überspringen Warnung Keine gute Idee.\n\nWenn du dein Passwort vergisst, verlierst du den Zugang zu deinem Telegram Konto. Für immer, ohne Ausnahme. @@ -839,11 +961,11 @@ Der Hinweis darf nicht das Kennwort sein. Ungültige E-Mail Tut uns leid - Da du für diesen Fall keine Email Adresse hinterlegt hast, kannst du nur noch hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. + Da du für diesen Fall keine E-Mail Adresse hinterlegt hast, kannst du nur noch hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. Wir haben den Wiederherstellungscode an diese Adresse geschickt:\n\n%1$s - Überprüfe deine Mails und gib den 6-stelligen Code aus userer Email ein. + Überprüfe deine Mails und gib den 6-stelligen Code aus userer E-Mail ein. Du hast keinen Zugang zu deiner Adresse %1$s? - Wenn du nicht in deine Emails kommst, kannst du nur hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. + Wenn du nicht in deine E-Mails kommst, kannst du nur hoffen, dass dir dein Kennwort wieder einfällt oder du musst dein Telegram Konto zurückzusetzen. KONTO ZURÜCKSETZEN Wenn du dein Konto zurücksetzt, verlierst du alle Chats und Nachrichten, ebenso deine geteilten Bilder und Videos. Warnung @@ -857,6 +979,27 @@ Kennwort deaktiviert Du hast die zweistufige Bestätigung aktiviert.\nWenn du dich bei Telegram anmeldest, brauchst du dein Kennwort. Deine E-Mail Adresse %1$s wurde noch verifiziert und ist daher noch nicht aktiv. + + Daten und Speicher + Daten- und Speichernutzung + Speichernutzung + Mobile Datennutzung + Roaming Datennutzung + WLAN Datennutzung + Nachrichten und sonstige Daten + Gesendet + Empfangen + Bytes gesendet + Bytes empfangen + Dateien + Anrufe + Ausgehende Anrufe + Eingehende Anrufe + Zeit insgesamt + Insgesamt + Statistik zurücksetzen + Netzwerk-Nutzung seit %1$s + Möchtest du die Nutzungsstatistiken zurücksetzen? Privatsphäre und Sicherheit Privatsphäre @@ -904,7 +1047,7 @@ Video bearbeiten Sende Video... - Sende gif... + Sende GIF... Bot Teilen @@ -916,7 +1059,7 @@ Was kann dieser Bot? STARTEN NEU STARTEN - Bot Anhalten + Bot anhalten Bot neu starten Weiter @@ -932,7 +1075,7 @@ Anrufen Kopieren Löschen - Löschen und Anhalten + Löschen und anhalten Weiterleiten Erneut versuchen Von der Kamera @@ -941,6 +1084,8 @@ Wählen OK SCHNEIDEN + Ja + Nein Du bist der Gruppe per Link beigetreten un1 ist der Gruppe per Link beigetreten @@ -987,11 +1132,12 @@ Zu viele Versuche in zu kurzer Zeit, versuche es bitte später erneut. Zu viele Versuche, bitte erneut in %1$s versuchen Ungültiger Code + Du hast dein Konto leider zu oft gelöscht. Bitte warte einige Tage, erst dann kannst du dich erneut registrieren. Ungültiger Vorname Ungültiger Nachname Lädt… Du hast keinen Videoplayer. Bitte installiere einen um fortzufahren. - Bitte sende eine Email an sms@stel.com mit einer Beschreibung des Problems. + Bitte sende eine E-Mail an sms@stel.com mit einer Beschreibung des Problems. Du hast keine Applikationen, die den Dateityp \'%1$s\' öffnen könnten. Bitte installiere eine entsprechende Anwendung um fortzufahren. Dieser Benutzer hat noch kein Telegram. Möchtest du ihn einladen? Bist du sicher? @@ -1004,7 +1150,7 @@ Spiel mit %1$s teilen? Kontakt senden an %1$s? Wirklich abmelden?\n\nDu kannst Telegram von all deinen Geräten gleichzeitig nutzen.\n\nWichtig: Abmelden löscht deine Geheimen Chats. - Sicher, dass du alle anderen Geräte abmelden möchtest? + Sicher, dass du alle anderen Sitzungen abmelden möchtest? Gruppe löschen und verlassen? Möchtest du wirklich diesen Chat löschen? Deinen Standort teilen? @@ -1038,16 +1184,20 @@ Du kannst diese Nachricht nicht bearbeiten. Bitte erlaube Telegram den Zugriff auf SMS, so dass wir den Code automatisch in der App für dich eingeben können. Bitte erlaube Telegram den Zugriff auf Anrufe, so dass wir den Code automatisch in der App eingeben können. + Bitte erlaube Telegram Anrufe und SMS zu empfangen, so dass wir deine Handynummer eintragen, einen Code senden und diesen automatisch eintragen können. Bitte erlaube Telegram den Zugriff auf SMS und Anrufe, so dass wir den Code automatisch in der App eingeben können. Du kannst diese Aktion nicht durchführen. + Du kannst diesen Nutzer oder Bot Gruppen leider nicht hinzufügen, da du ihn blockiert hast. Gebe ihn wieder frei, um fortzufahren. GRUPPE BEITRETEN Telegram benötigt Zugriff auf deine Kontakte um dich auf all denen Geräten mit deinen Freunden zu verbinden. Telegram benötigt Zugriff auf deinen Speicher, damit du Bilder, Videos und Musik senden und speichern kannst. Telegram benötigt Zugriff auf dein Mikrofon, damit du Sprachnachrichten senden kannst. + Telegram benötigt Zugriff auf dein Mikrofon, damit du Videos aufnehmen kannst. Telegram benötigt Zugriff auf deine Kamera, damit du Bilder und Videos aufnehmen kannst. Telegram benötigt Zugriff auf deinen Standort, damit du ihn mit Freunden teilen kannst. Telegram benötigt Zugriff auf deinen Standort. + Telegram braucht Zugriff auf die Funktion \'Über andere Apps einblenden\'. Nur so können Videos im Bild in Bild Modus wiedergegeben werden. EINSTELLUNGEN Telegram @@ -1065,6 +1215,66 @@ Telegram]]> lässt sich von verschiedenen Geräten]]>gleichzeitig nutzen. Telegram]]>-Nachrichten sind stark verschlüsselt]]>und können sich selbst zerstören. Jetzt beginnen + + Kontoeinstellungen + Weniger Daten benutzen + Eingehender Anruf + Verbinde + Tausche Schlüssel aus + Warte + Wird angefordert + Anruf wird beendet + Anruf beendet + Keine Verbindung möglich + Es klingelt + Besetzt + Telegram Anruf + Laufender Telegram Anruf + Anruf beenden + Ein anderer Anruf ist bereits aktiv + Du bist mit %1$s]]> bereits im Gespräch. Möchtest du den Anruf beenden und einen neuen mit %2$s]]> starten? + Sprachanrufe + Klingelton + Hier kannst du den Klingelton für Sprachanrufe festlegen. + Anrufe + Wer kann mich anrufen? + Du kannst bestimmen, wer dich anrufen darf. + Hier kannst du Nutzer hinzufügen, für die eine Ausnahme gemacht werden soll. + Niemals + Nur über Mobilfunk + Immer + Annehmen + Ablehnen + Du bist derzeit offline. Bitte verbinde dich mit dem Internet und versuche es erneut. + Du hast derzeit den Flugmodus aktiviert. Deaktiviere den Flugmodus oder verbinde dich per W-LAN um jemanden anzurufen. + Offline + Flugmodus + Einstellungen + Ausgehender Anruf + Eingehender Anruf + Verpasster Anruf + Abgebrochener Anruf + Abgelehnter Anruf + %1$s (%2$s) + Bild und Text zeigen den Schlüssel dieses Anrufs mit ]]>%1$s]]>.
          ]]>Sehen sie auf dem Gerät von ]]>%2$s\'s]]> genau so aus, ist Ende-zu-Ende Verschlüsselung garantiert.
          + Du hast noch keine Anrufe geführt. + ]]>%1$s]]>s App ist nicht mit unserem Protokoll kompatibel. Dein Chatpartner muss seine App aktualisieren, bevor du anrufen kannst. + ]]>%1$s]]> unterstützt keine Anrufe. Dein Chatpartner muss seine App aktualisieren, bevor du ihn anrufen kannst. + Bewerte bitte die Qualität deines Telegram-Anrufs + Möchtest du eine Rückmeldung zu unseren Sprachanrufen hinterlassen, damit wir den Dienst optimieren können? + Telegram benötigt für Sprachanrufe Zugriff auf dein Mikrofon. + Optionalen Kommentar hinzufügen + Zurückrufen + Erneut anrufen + Standard + Möchtest du wirklich diesen Eintrag aus der Anrufliste löschen? + Telegram Anruf + Ohrhörer + Lautsprecher + Bluetooth + ZURÜCK ZUM ANRUF + ]]>%1$s]]> akzeptiert leider keine Anrufe. + Wenn diese Emoji genau so bei %1$s aussehen, ist euer Anruf 100%% sicher. %1$d online %1$d online @@ -1284,6 +1494,8 @@ und %1$d andere und %1$d andere + dd. MMM yyyy, h:mm a + dd. MMM yyyy, HH:mm MMMM yyyy dd MMM dd.MM.yy diff --git a/TMessagesProj/src/main/res/values-es/strings.xml b/TMessagesProj/src/main/res/values-es/strings.xml index c652039eae0..8d2237fce31 100644 --- a/TMessagesProj/src/main/res/values-es/strings.xml +++ b/TMessagesProj/src/main/res/values-es/strings.xml @@ -41,13 +41,62 @@ Nombre (requerido) Apellidos (opcional) Cancelar registro + + Has transferido satisfactoriamente %1$s a %2$s por %3$s + Has transferido satisfactoriamente %1$s a %2$s + Caja + Forma de envío + Lo sentimos, pero no es posible hacer envíos a tu dirección. + Información de envío + Dirección de envío + Dirección 1 (calle) + Dirección 2 (calle) + Ciudad + Estado + País + Código postal + Destinatario + Nombre completo + Número de teléfono + E-mail + Guardar información de envío + Puedes guardar la información de envío para usarla más adelante. + Información de pago + Tarjeta de pago + Número de tarjeta + Código de seguridad (CVV) + MM/AA + Dirección de facturación + Dueño de la tarjeta + Nombre Apellidos + Guardar información de pago + Puedes guardar la información de pago para usarla más adelante. + Por favor, *activa la verificación en dos pasos* para activar esto. + Repaso de la transacción + ¿Quieres transferir %1$s al bot %2$s por %3$s? + Total + FACTURA + PROBAR FACTURA + PAGAR %1$s + Forma de pago + Nombre + Número de teléfono + Dirección de contacto + Forma de envío + Recibo + Elige una tarjeta diferente + Tu tarjeta %1$s está archivada. Para pagar con esta tarjeta, por favor, pon tu contraseña de la verificación en dos pasos. + Lo sentimos, el pago fue cancelado por el bot. + Lo sentimos, el pago fue rechazado. + No es posible acceder al servidor de pago. Por favor, revisa tu conexión a internet y reinténtalo. + Nuevo chat Ajustes Contactos Nuevo grupo ayer Sin resultados - Aún sin chats... + Aún no hay chats... Envía mensajes tocando el botón para\nredactar, en la parte inferior derecha,\no pulsa el botón menú para más opciones. Esperando red... Conectando... @@ -58,7 +107,7 @@ Intercambiando claves de cifrado... %s se unió a tu chat secreto. Te uniste al chat secreto. - Borrar historial + Eliminar historial Eliminar de la caché Eliminar y salir Eliminar chat @@ -80,6 +129,16 @@ Vista previa del enlace Borrador Historial borrado + por %1$s + %1$s por %2$s + Enviar comentarios sobre esta vista previa + Enviar sticker + Ver pack + Anclar + Desanclar + Negrita + Cursiva + Normal Tipo de grupo Tipo de canal @@ -87,19 +146,19 @@ Privado Nombrar como administrador Puedes poner una descripción para tu grupo. - Dejar el grupo + Salir del grupo Eliminar grupo - Dejar el grupo + Salir del grupo Eliminar grupo Perderás todos los mensajes en este grupo. Puedes añadir administradores para que te ayuden en el grupo. Mantén pulsado para eliminarlos. ¡Espera! Al eliminar este grupo, todos los miembros y los mensajes se perderán. ¿Quieres eliminarlo? Grupo creado un1 te añadió a este grupo - ¿Quieres dejar el grupo? + ¿Quieres salir del grupo? Lo sentimos, no puedes añadir este usuario a grupos. Lo sentimos, el grupo está lleno. - Lo sentimos, este usuario decidió dejar el grupo, así que no puedes invitarlo otra vez. + Lo sentimos, este usuario decidió salir del grupo, así que no puedes invitarlo otra vez. Hay demasiados administradores en el grupo. Lo sentimos, hay demasiados bots en el grupo. un1 ancló \"%1$s\" @@ -110,13 +169,13 @@ un1 ancló un sticker un1 ancló un mensaje de voz un1 ancló un contacto - un1 ancló el %1$s + un1 ancló %1$s un1 ancló un mapa un1 ancló un GIF un1 ancló una pista Este grupo fue convertido en un supergrupo %1$s fue convertido en un supergrupo - Los usuarios bloqueados son eliminados del grupo y sólo pueden volver si son invitados por un administrador. Los enlaces de invitación no funcionan para ellos. + Los usuarios suspendidos son eliminados del grupo y sólo pueden volver si son invitados por un administrador. Los enlaces de invitación no funcionan para ellos. Nuevo canal Nombre del canal Añadir contactos a tu canal @@ -139,8 +198,8 @@ Enlace Enlace de invitación Añadir miembros - Dejar canal - Dejar el canal + Salir del canal + Salir del canal Ajustes UNIRME Difundir @@ -158,12 +217,12 @@ Verificando nombre... %1$s está disponible. Miembros - Bloqueados + Suspendidos Administradores Eliminar canal Eliminar canal ¡Espera! Al eliminar este canal, todos los miembros y los mensajes se perderán. ¿Quieres eliminarlo? - ¿Quieres dejar este canal? + ¿Quieres salir de este canal? Perderás todos los mensajes en este canal. Editar Ten en cuenta que, si eliges un enlace público para tu grupo, cualquiera podrá encontrarlo en la búsqueda y unirse.\n\nNo crees este enlace si quieres que tu supergrupo sea privado. @@ -192,7 +251,7 @@ Lamentablemente, fuiste suspendido de participar en grupos públicos. Lo sentimos, este chat ya no es accesible. ¿Añadir a %1$s al canal? - Lo sentimos, este usuario decidió dejar el canal, así que no puedes invitarlo otra vez. + Lo sentimos, este usuario decidió salir del canal, así que no puedes invitarlo otra vez. Lo sentimos, no puedes añadir a este usuario a canales. Lo sentimos, hay demasiados administradores en el canal. Lo sentimos, hay demasiados bots en el canal. @@ -249,7 +308,7 @@ %1$s de %2$s libres Error desconocido Error de acceso - Aún sin archivos... + Aún no hay archivos... El archivo no debe superar los %1$s Almacenamiento no montado Transferencia USB activa @@ -267,11 +326,14 @@ %1$s está grabando un mensaje de voz... %1$s está enviando un audio... %1$s está enviando una foto... + VISTA RÁPIDA + %1$s está jugando... %1$s está enviando un vídeo... %1$s está enviando un archivo... grabando mensaje de voz... enviando audio... enviando foto... + jugando... enviando vídeo... enviando archivo... ¿Tienes preguntas\nsobre Telegram? @@ -281,7 +343,7 @@ Vídeo Archivo Cámara - Aún sin mensajes... + Aún no hay mensajes... Mensaje reenviado De No hay recientes @@ -307,6 +369,7 @@ Guardar en música Compartir Aplicar traducción + Aplicar tema Adjunto no soportado Establecer autodestrucción Servicio de notificaciones @@ -324,7 +387,9 @@ ¿Quieres reportar a este grupo como spam? ¿Quieres reportar spam de este canal? Lo sentimos, por ahora puedes enviar mensajes sólo a contactos mutuos. - Lo sentimos, por ahora sólo puedes añadir contactos mutuos a un grupo. + Lo sentimos, por ahora sólo puedes añadir contactos mutuos a grupos. + Escribiste a demasiados usuarios que no son tus contactos hoy. Inténtalo de nuevo mañana. Podrás responder si ellos te escriben primero. + No puedes añadir a este usuario porque escribiste a demasiadas personas que no son tus contactos hoy. Inténtalo de nuevo mañana. Puedes pedir a otro miembro del grupo que lo añada. https://telegram.org/faq/es#no-puedo-enviar-mensajes-a-quienes-no-son-mis-contactos Más información Enviar a... @@ -337,7 +402,7 @@ Suspender usuario Reportar spam Eliminar todo lo de %1$s - ¿Borrar los emojis recientes? + ¿Eliminar los emojis recientes? Reportar Spam Violencia @@ -358,12 +423,16 @@ Accede desde cualquier dispositivo Encuentra tus cosas con la búsqueda Almacenamiento en la nube + Ir a la fecha + Eliminar para %1$s + Eliminar para todos + Texto copiado al portapapeles %1$s activó la autodestrucción en %2$s Activaste la autodestrucción en %1$s %1$s desactivó la autodestrucción Desactivaste la autodestrucción - Tienes un nuevo mensaje + Tienes un mensaje nuevo %1$s: %2$s %1$s te envió un mensaje %1$s te envió una foto @@ -435,7 +504,7 @@ %1$s ancló un juego Elegir contacto - Aún sin contactos + Aún no hay contactos ¡Oye! Cambiémonos a Telegram: https://telegram.org/dl a las ayer a las @@ -449,13 +518,14 @@ últ. vez hace unos días últ. vez hace unas semanas últ. vez hace mucho tiempo - Nuevo mensaje + Mensaje nuevo - Invitar a... + Añadir personas... Podrás añadir más usuarios después de crear el grupo y convertirlo en un supergrupo. Nombre del grupo Nombre del grupo - %1$d/%2$d miembros + %1$d de %2$d seleccionados + hasta %1$s ¿Quieres unirte al chat “%1$s”? Lo sentimos. Este grupo está lleno. Lo sentimos, este chat no existe. @@ -481,7 +551,7 @@ Ajustes Añadir miembro Nombrar administradores - Eliminar y dejar el grupo + Eliminar y salir del grupo Notificaciones Eliminar del grupo Convertir en supergrupo @@ -507,12 +577,16 @@ Otro Principal Iniciar chat secreto + Grupos en común + Grupos en común + Aún no hay grupos en común Ocurrió un error. Clave de cifrado Autodestrucción Apagada El texto e imagen derivan de la clave de cifrado para el chat secreto creado con ]]>%1$s]]>.
          ]]>Si se ven igual en el dispositivo de ]]>%2$s]]>, el cifrado end-to-end está garantizado.
          ]]>Conoce más en telegram.org
          https://telegram.org/faq/es#chats-secretos + Toca para ver emojis Desconocido Información Teléfono @@ -525,6 +599,7 @@ El alias no debe exceder los 32 caracteres. Lo sentimos, un alias no puede comenzar con un número. Puedes elegir un alias en ]]>Telegram]]>. Si lo haces, otras personas te podrán encontrar por ese alias y contactarte sin saber tu número de teléfono.
          ]]>Puedes usar ]]>a–z]]>, ]]>0–9]]> y guiones bajos. La longitud mínima es de ]]>5]]> caracteres.
          + Este enlace abre un chat contigo en Telegram:\n%1$s Verificando alias... %1$s está disponible. Ninguno @@ -544,8 +619,8 @@ Compartir Copiar enlace Eliminar - Aún sin stickers - Aún sin máscaras + Aún no hay stickers + Aún no hay máscaras Máscaras Puedes añadir máscaras a las fotos que envías. Para hacerlo, abre el editor de fotos antes de enviar una foto. Stickers destacados @@ -553,7 +628,7 @@ Stickers archivados Máscaras archivadas Stickers no archivados - Máscaras no archivadas + Sin máscaras archivadas Puedes instalar hasta 200 packs de stickers. Cuando instales más, se archivarán los que no usas. Puedes instalar hasta 200 packs de máscaras. Cuando instales más, se archivarán los que no usas. ENVIAR STICKER @@ -561,6 +636,20 @@ Algunos de tus packs de stickers más viejos fueron archivados. Puedes reactivarlos desde los ajustes de Stickers. Máscaras archivadas Algunos de tus packs de máscaras más viejos fueron archivados. Puedes reactivarlos desde los ajustes de máscaras. + + Tema + ¿Quieres eliminar este tema? + Archivo de tema incorrecto + Pon el nombre del tema + CERRAR EDITOR + GUARDAR TEMA + Nuevo tema + APLICAR + Vista previa del tema + Elegir color + Crear nuevo tema + Toca sobre el icono de la paleta de colores para ver la lista de elementos en cada ventana y editarlos. + Puedes crear tu propio tema cambiando los colores en la aplicación. Siempre puedes volver al tema por defecto de Telegram aquí. Restablecer las notificaciones Tamaño del texto @@ -568,7 +657,7 @@ Activar animaciones Desbloquear Mantén pulsado sobre un usuario para desbloquearlo. - Sin usuarios bloqueados + No hay usuarios bloqueados Notificación de mensajes Alerta Vista previa del mensaje @@ -583,6 +672,27 @@ Restablecer las notificaciones Deshacer las notificaciones personalizadas para todos tus usuarios y grupos Notificaciones y sonidos + Notificaciones personalizadas + Notificaciones emergentes + Los mensajes nuevos de este contacto aparecerán en tu pantalla cuando no estés usando Telegram. + LED + Color + Azul + Rojo + Amarillo + Verde + Cian + Blanco + Rosa + Violeta + Naranja + LED es una pequeña luz que parpadea en algunos dispositivos para señalar los mensajes nuevos. + Las notificaciones con prioridad alta funcionarán aunque tu teléfono esté en modo no molestar. + General + Activadas + Desactivadas + Activar + Desactivar Usuarios bloqueados Cerrar sesión Sin sonido @@ -597,7 +707,7 @@ Un contacto se unió a Telegram Mensajes anclados Idioma - Por favor, considera que el soporte de Telegram está hecho por voluntarios. Respondemos lo antes posible, pero puede tomar tiempo.
          ]]>Por favor, mira las preguntas frecuentes de Telegram]]>: tienen respuestas para la mayoría de las preguntas y soluciones a problemas]]>.
          + Por favor, considera que el soporte de Telegram está hecho por voluntarios. Respondemos lo antes posible, pero puede tomar tiempo.
          ]]>Por favor, mira las preguntas frecuentes de Telegram]]>: tienen respuestas para la mayoría de las preguntas y soluciones a problemas]]>.
          Preguntar Preguntas frecuentes https://telegram.org/faq/es @@ -624,8 +734,6 @@ Globo en el ícono Cortas Largas - Según el sistema - Según Telegram Descarga automática de multimedia Con uso de datos móviles Con conexión a Wi-Fi @@ -635,7 +743,11 @@ Elevar para hablar Guardar en galería Editar nombre + Personalizar + Personalizadas + Activar notificaciones personalizadas Prioridad + Como en Ajustes Por defecto Baja Alta @@ -651,18 +763,18 @@ Otras Desactivadas Desactivadas + Activadas + Desactivadas Desactivado Apagado Sonidos en el chat Por defecto Por defecto Notificaciones inteligentes + %1$d / %2$s Desactivadas - Sonar como máximo %1$s en %2$s - Sonar como máximo - veces - en - minutos + Frecuencia de alertas + %1$s dentro de %2$s Vistas previas de enlaces Chats secretos Usar navegador interno @@ -670,21 +782,25 @@ Direct Share Mostrar los chats recientes al compartir Emoji - Draw single big emoji - Use system default emoji + Usar un emoji grande + Usar emoji predeterminado + Telegram para Android %1$s + Menú de depuración + Importar contactos + Recargar contactos - Ajustes de caché Base de datos local - ¿Borrar los mensajes en la caché? - Al borrar la base de datos se eliminarán los mensajes en la caché y se comprimirá la base de datos para liberar espacio de almacenamiento. Telegram requiere algunos datos para funcionar, así que la base de datos nunca podrá llegar a cero.\n\nEsto puede tardar algunos minutos. - Borrar caché - Borrar + ¿Eliminar los mensajes en la caché? + Al eliminar la base de datos se eliminarán los mensajes en la caché y se comprimirá la base de datos para liberar espacio de almacenamiento. Telegram requiere algunos datos para funcionar, así que la base de datos nunca podrá llegar a cero.\n\nEsto puede tardar algunos minutos. + Eliminar caché + Eliminar Calculando... Archivos Fotos Mensajes de voz Vídeos Música + GIF Otros archivos Vacío Conservar multimedia @@ -722,6 +838,8 @@ Confirma la huella digital para continuar Sensor táctil Huella digital no reconocida. Reinténtalo + Permitir captura de pantalla + Si está activada, puedes hacer capturas de pantalla de la aplicación, pero el sistema mostrará tus chats en la multitarea aunque tengas activo el código de acceso.\n\nPuede ser necesario reiniciar la aplicación para que tenga efecto. Archivos Multimedia @@ -753,7 +871,7 @@ Galería Todas las fotos Todos los vídeos - Aún sin fotos + Aún no hay fotos Aún sin vídeos Por favor, primero descarga la multimedia No hay fotos recientes @@ -791,13 +909,14 @@ ¿Quieres eliminar esta foto? ¿Quieres eliminar este vídeo? ¿Descartar cambios? - ¿Quieres borrar el historial de búsqueda? - Borrar + ¿Quieres eliminar el historial de búsqueda? + Eliminar Fotos Vídeo Añadir un comentario... Comentario de foto Comentario de vídeo + Comentario en el GIF Comentario Dibujar Stickers @@ -807,6 +926,9 @@ Duplicar Contorno Normal + Restablecer + Original + 1:1 Verificación en dos pasos Poner contraseña adicional @@ -857,6 +979,27 @@ Contraseña desactivada Tienes activada la verificación en dos pasos.\nNecesitarás la contraseña que configuraste para iniciar tu sesión en Telegram. Tu e-mail de recuperación %1$s aún no está activo y su confirmación está pendiente. + + Datos y almacenamiento + Uso de almacenamiento y red + Uso de almacenamiento + Uso de datos móviles + Uso de datos de roaming + Uso de datos Wi-Fi + Mensajes y otros datos + Enviados + Recibidos + Bytes enviados + Bytes recibidos + Archivos + Llamadas + Llamadas salientes + Llamadas entrantes + Tiempo total + Total + Restablecer estadísticas + Uso de la red desde el %1$s + ¿Quieres restablecer las estadísticas de uso? Privacidad y seguridad Privacidad @@ -872,7 +1015,7 @@ Seguridad Autodestrucción de la cuenta Si estoy fuera - Si no inicias sesión durante este tiempo, al menos una vez, tu cuenta se eliminará con todos tus grupos, mensajes y contactos. + Si no estás en línea durante este tiempo, al menos una vez, tu cuenta se eliminará con todos tus grupos, mensajes y contactos. ¿Queres eliminar tu cuenta? Elige quién puede ver tu última conexión. ¿Quién puede ver tu última conexión? @@ -941,6 +1084,8 @@ OK OK RECORTAR + + No Te uniste al grupo con un enlace de invitación un1 se unió al grupo con un enlace de invitación @@ -987,6 +1132,7 @@ Muchos intentos. Por favor, prueba de nuevo más tarde. Demasiados intentos. Por favor, reinténtalo en %1$s Código inválido + Has eliminado y vuelto a crear tu cuenta muchas veces recientemente. Por favor, espera algunos días antes de inscribirte de nuevo. Nombre inválido Apellidos inválidos Cargando... @@ -1005,7 +1151,7 @@ ¿Enviar contacto a %1$s? ¿Quieres cerrar sesión?\n\nConsidera que puedes usar Telegram en todos tus dispositivos a la vez.\n\nRecuerda que, al cerrar sesión, eliminas todos tus chats secretos. ¿Quieres terminar todas las otras sesiones? - ¿Quieres eliminar y dejar el grupo? + ¿Quieres eliminar y salir del grupo? ¿Quieres eliminar este chat? ¿Compartir tu ubicación? Esto enviará tu ubicación actual al bot. @@ -1038,16 +1184,20 @@ No puedes editar este mensaje. Por favor, permite a Telegram recibir SMS, para ingresar el código automáticamente. Por favor, permite a Telegram recibir llamadas, para ingresar el código automáticamente. + Por favor, permite a Telegram recibir llamadas y SMS, para que podamos ingresar tu número, enviarte un código e ingresarlo por ti. Por favor, permite a Telegram recibir llamadas y SMS, para ingresar el código automáticamente. Lo sentimos, no estás autorizado para hacer esto. + No puedes añadir a este usuario o bot al grupo porque lo has bloqueado. Por favor, desbloquéalo para continuar. UNIRME AL GRUPO Telegram necesita el acceso a tus contactos, para que puedas comunicarte con ellos en todos tus dispositivos. Telegram necesita acceso a tu almacenamiento, para que puedas enviar y guardar fotos, vídeos, música y otros archivos. Telegram necesita acceso a tu micrófono, para que puedas enviar mensajes de voz. + Telegram necesita acceso a tu micrófono, para que puedas grabar vídeos. Telegram necesita acceso a tu cámara, para que puedas hacer fotos y vídeos. Telegram necesita acceso a tu ubicación, para que puedas compartirla con tus amigos. Telegram necesita acceso a tu ubicación. + Telegram necesita acceso a mostrarse sobre otras aplicaciones para usar el modo Picture-in-Picture. AJUSTES Telegram @@ -1065,6 +1215,66 @@ Telegram]]> te permite acceder a tus]]>mensajes desde múltiples dispositivos. Telegram]]> posee mensajes fuertemente]]>cifrados y se pueden autodestruir. Empieza a conversar + + Ajustes de la cuenta + Usar menos datos + Llamada entrante + Conectando + Intercambiando claves de cifrado + Esperando + Solicitando + Colgando + Llamada terminada + Conexión fallida + Llamando + Línea ocupada + Llamada de Telegram + Llamada de Telegram en curso + Finalizar llamada + Otra llamada en curso + Actualmente tienes una llamada en curso con %1$s]]>. ¿Quieres colgar esa llamada y empezar una nueva con %2$s]]>? + Llamadas de voz + Tono de llamada + Puedes personalizar el tono de llamada desde este contacto en Telegram. + Llamadas + ¿Quién puede llamarme? + Puedes restringir quién puede llamarte. + Estos usuarios podrán o no podrán llamarte, a partir de los ajustes de arriba. + Nunca + Sólo datos móviles + Siempre + Contestar + Rechazar + Estás sin conexión. Por favor, conéctate a internet para realizar llamadas. + Tienes activado el modo avión. Por favor, desactívalo o conéctate a una red Wi-Fi para hacer llamadas. + Fuera de línea + Modo avión + Ajustes + Llamada saliente + Llamada entrante + Llamada perdida + Llamada cancelada + Llamada rechazada + %1$s (%2$s) + El texto e imagen derivan de la clave de cifrado para la llamada con ]]>%1$s]]>.
          ]]>Si se ven igual en el dispositivo de ]]>%2$s\'s]]> el cifrado end-to-end está garantizado.
          + Aún no has realizado llamadas. + La app de ]]>%1$s]]> está usando un protocolo incompatible. Necesita actualizar la app para poder llamar. + La app de ]]>%1$s]]> no soporta llamadas. Necesita actualizar la app para poder llamar. + Por favor, evalúa la calidad de tu llamada de Telegram + ¿Quieres dejar algún comentario para ayudarnos a mejorar las llamadas? + Telegram necesita acceso a tu micrófono para poder realizar llamadas. + Añade un comentario opcional + Devolver llamada + Llamar de nuevo + Por defecto + ¿Quieres eliminar este elemento del registro de llamadas? + Llamada de Telegram + Auricular + Altavoz + Bluetooth + VOLVER A LA LLAMADA + Lo sentimos, ]]>%1$s]]> no acepta llamadas. + Si estos emojis son los mismos en la pantalla de %1$s, esta llamada es 100%% segura. %1$d en línea %1$d en línea @@ -1085,11 +1295,11 @@ y %1$d más están escribiendo y %1$d más están escribiendo Sin mensajes nuevos - %1$d nuevo mensaje - %1$d nuevos mensajes - %1$d nuevos mensajes - %1$d nuevos mensajes - %1$d nuevos mensajes + %1$d mensaje nuevo + %1$d mensajes nuevos + %1$d mensajes nuevos + %1$d mensajes nuevos + %1$d mensajes nuevos Sin mensajes %1$d mensaje %1$d mensajes @@ -1284,6 +1494,8 @@ y otros %1$d y otros %1$d + dd MMM yyyy, h:mm a + dd MMM yyyy, HH:mm MMMM \'de\' yyyy dd \'de\' MMM dd.MM.yy diff --git a/TMessagesProj/src/main/res/values-it/strings.xml b/TMessagesProj/src/main/res/values-it/strings.xml index f1b6a31c662..bac63eccc53 100644 --- a/TMessagesProj/src/main/res/values-it/strings.xml +++ b/TMessagesProj/src/main/res/values-it/strings.xml @@ -16,7 +16,7 @@ Verifica numero Abbiamo inviato un SMS con un codice di attivazione al tuo numero ]]>%1$s]]>. - Abbiamo inviato il codice su ]]>Telegram]]> nell\'altro tuo dispositivo. + Abbiamo inviato il codice su ]]>Telegram]]> nel tuo altro dispositivo. Abbiamo inviato una chiamata di attivazione al tuo numero ]]>%1$s]]>.\n\nNon rispondere, Telegram farà tutto in automatico. Stiamo chiamando il tuo numero ]]>%1$s]]> per dettarti un codice. Ti telefoneremo tra %1$d:%2$02d @@ -29,7 +29,7 @@ Qualcuno con accesso al tuo numero di telefono ]]>%1$s ha richiesto l\'eliminazione del tuo account Telegram e il ripristino della password della verifica in due passaggi.\n\nSe non sei stato tu, per favore inserisci il codice che abbiamo appena inviato tramite SMS al tuo numero. Ripristino account Dato che l\'account ]]>%1$s è attivo e protetto da una password, lo elimineremo tra 1 settimana per motivi di sicurezza.\n\nPuoi annullare questo processo in qualsiasi momento. - Sarai in grado di ripristinare il tuo account tra: + Potrai ripristinare il tuo account tra: I tuoi tentativi recenti di ripristinare questo account sono stati annullati dal suo utente attivo. Per favore riprova tra 7 giorni. RIPRISTINA Link non valido o scaduto. @@ -40,20 +40,69 @@ Nome (richiesto) Cognome (facoltativo) - Annulla registrazione + Annulla iscrizione + + Hai appena trasferito con successo %1$s a %2$s per %3$s + Hai appena trasferito con successo %1$s a %2$s + Cassa + Metodi di spedizione + Spiacenti, non è possibile consegnare al tuo indirizzo. + Info di spedizione + Indirizzo di spedizione + Indirizzo 1 (Via) + Indirizzo 2 (Via) + Città + Provincia + Paese + Codice postale + Destinatario + Nome completo + Phone Number + E-Mail + Salva info di spedizione + Puoi salvare le tue informazioni di spedizione per uso futuro. + Informazioni di pagamento + Carta di credito + Numero della carta + Codice di sicurezza (CVV) + MM/YY + Indirizzo di fatturazione + Titolare della carta + Nome e cognome + Salva informazioni di pagamento + Puoi salvare le tue informazioni di pagamento per uso futuro. + Per favore *attiva la verifica in due passaggi* per abilitare questo. + Riepilogo transazione + Vuoi davvero trasferire %1$s al bot %2$s per %3$s? + Totale + FATTURA + FATTURA DI TEST + PAGA %1$s + Metodo di pagamento + Nome + Numero di telefono + Indirizzo di contatto + Metodo di spedizione + Ricevuta + Scegli una carta differente + La tua carta %1$s è registrata. Per pagare con questa carta, per favore inserisci la tua password della verifica in due passaggi. + Spiacenti, il pagamento è stato annullato dal bot. + Spiacenti, il pagamento è stato rifiutato. + Impossibile raggiungere il server di pagamento. Per favore controlla la tua connessione a Internet e riprova. + Nuova chat Impostazioni Contatti Nuovo gruppo ieri Nessun risultato Ancora nessuna chat... - Inizia a chattare premendo il tasto\nnuovo messaggio nell\'angolo in basso a destra\no apri il menu per avere più opzioni. + Inizia a messaggiare premendo il tasto\nnuovo messaggio in basso a destra\no apri il menù per avere più opzioni. Attendo la rete... Connetto... Aggiorno... Nuova chat segreta - In attesa che %s si colleghi... + Attendo che %s si colleghi... Chat segreta annullata Scambio chiavi di crittografia... %s si è unito alla tua chat segreta. @@ -80,6 +129,16 @@ Anteprima link Bozza Cronologia eliminata + di %1$s + %1$s di %2$s + Lascia un feedback su questa anteprima + Invia sticker + Visualizza pacchetto + Fissa in alto + Togli dall\'alto + Grassetto + Corsivo + Normale Tipo di gruppo Tipo di canale @@ -116,9 +175,9 @@ un1 ha fissato una traccia Questo gruppo è stato aggiornato a supergruppo %1$s è stato aggiornato a supergruppo. - Gli utenti in lista nera sono rimossi dal gruppo e possono tornare solo se invitati da un amministratore. I link di invito non funzionano per loro. + Gli utenti in lista nera sono rimossi dal gruppo e possono tornare solo se invitati da un amministratore. I link d\'invito non funzionano per loro. Nuovo canale - Nome canale + Nome del canale Aggiungi contatti al tuo canale Le persone possono condividere questo link con gli altri e trovare il tuo canale usando la ricerca di Telegram. Le persone possono condividere questo link con gli altri e trovare il tuo gruppo usando la ricerca di Telegram. @@ -134,10 +193,10 @@ I gruppi pubblici possono essere trovati nella ricerca, la cronologia è disponibile per tutti e chiunque può unirsi. Canale privato Gruppo privato - È possibile unirsi ai canali privati solo tramite link di invito. - È possibile unirsi ai gruppi privati solo se sei stato invitato o se hai un link di invito. + È possibile unirsi ai canali privati solo tramite link d\'invito. + È possibile unirsi ai gruppi privati solo se sei stato invitato o se hai un link d\'invito. Link - Link di invito + Link d\'invito Aggiungi membri Lascia il canale Lascia il canale @@ -166,8 +225,8 @@ Sei sicuro di voler lasciare il canale? Perderai tutti i messaggi in questo canale. Modifica - Per favore nota che se scegli un link pubblico per il tuo gruppo, chiunque sarà in grado di trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo supergruppo rimanga privato. - Per favore nota che se scegli un link pubblico per il tuo canale, chiunque sarà in grado di trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo canale rimanga privato. + Per favore nota che se scegli un link pubblico per il tuo gruppo, chiunque potrà trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo supergruppo rimanga privato. + Per favore nota che se scegli un link pubblico per il tuo canale, chiunque potrà trovarlo nella ricerca e unirsi.\n\nNon creare questo link se vuoi che il tuo canale rimanga privato. Per favore scegli un link per il tuo canale pubblico, in modo che possa essere trovato nella ricerca e condiviso con altri.\n\nSe non sei interessato, ti consigliamo di creare un canale privato. Canale creato Foto del canale cambiata @@ -185,7 +244,7 @@ Rimuovi Solo gli amministratori del canale possono vedere questa lista. Questo utente non si è ancora unito al canale. Vuoi invitarlo? - Chiunque abbia Telegram installato sarà in grado di aggiungersi al tuo canale seguendo questo link. + Chiunque abbia Telegram installato potrà aggiungersi al tuo canale aprendo questo link. Puoi aggiungere amministratori per farti aiutare a gestire il tuo canale. Tieni premuto per rimuovere gli amministratori. Vuoi unirti al canale \'%1$s\'? Spiacenti, questa chat non è più accessibile. @@ -248,13 +307,13 @@ Seleziona file Liberi %1$s di %2$s Errore sconosciuto - Errore durante l\'accesso + Errore di accesso Ancora nessun file... La dimensione del file non dovrebbe superare i %1$s - Archiviazione non montata + Archivio non montato Trasferimento USB attivo - Archiviazione interna - Archiviazione esterna + Archivio interno + Archivio esterno Root di sistema Scheda SD Cartella @@ -267,11 +326,14 @@ %1$s sta registrando un audio... %1$s sta inviando un audio... %1$s sta inviando una foto... + APERTURA RAPIDA + %1$s sta giocando a un gioco... %1$s sta inviando un video... %1$s sta inviando un file... sta registrando un audio... sta inviando un audio... sta inviando una foto... + sta giocando a un gioco... sta inviando un video... sta inviando un file... Hai una domanda\nsu Telegram? @@ -307,6 +369,7 @@ Salva nella musica Condividi Applica traduzione + Applica tema Allegato non supportato Timer di autodistruzione Notifiche di servizio @@ -323,12 +386,14 @@ Sei sicuro di voler segnalare questo utente come spam? Sei sicuro di voler segnalare dello spam da questo gruppo? Sei sicuro di voler segnalare dello spam da questo canale? - Spiacenti, ma al momento puoi scrivere solo ai contatti reciproci. - Spiacenti, ma al momento puoi aggiungere ai gruppi solo contatti reciproci. + Spiacenti, al momento puoi scrivere solo ai contatti reciproci. + Spiacenti, al momento puoi aggiungere ai gruppi solo i contatti reciproci . + Oggi hai scritto a troppi non-contatti, per favore riprova domani. Potrai rispondere oggi se questo utente ti scrive per primo. + Non puoi aggiungere questo utente perché hai scritto a troppi non-contatti oggi. Per favore riprova domani. Puoi chiedere a un altro membro di aggiungere questo utente al gruppo. https://telegram.org/faq/it#non-posso-inviare-messaggi-a-chi-non-far-parte-dei-miei-contatti Maggiori info Invia a... - Premi qui per accedere alle GIF salvate + Tocca qui per vedere le GIF salvate Fissa Notifica tutti i membri Togli @@ -358,6 +423,10 @@ Accedi alla chat da ogni dispositivo Trova le tue cose con la ricerca Il tuo archivio cloud + Vai alla data + Elimina per %1$s + Elimina per tutti i membri + Testo copiato negli appunti %1$s ha impostato il timer di autodistruzione a %2$s Hai impostato il timer di autodistruzione a %1$s @@ -400,9 +469,9 @@ %1$s ti ha rimosso dal gruppo %2$s %1$s ha lasciato il gruppo %2$s %1$s si è unito a Telegram! - %1$s,\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo il %2$s\n\nDispositivo: %3$s\nPosizione: %4$s\n\nSe non sei stato tu, puoi andare su Impostazioni - Privacy e sicurezza - Sessioni - Termina tutte le sessioni.\n\nSe pensi che qualcuno si sia collegato al tuo account contro il tuo volere, ti raccomandiamo di attivare la verifica in due passaggi nelle impostazioni di Privacy e Sicurezza.\n\nGrazie,\nil team di Telegram + %1$s,\nAbbiamo rilevato un accesso al tuo account da un nuovo dispositivo il %2$s\n\nDispositivo: %3$s\nPosizione: %4$s\n\nSe non sei stato tu, puoi andare nelle Impostazioni - Privacy e sicurezza - Sessioni - Termina tutte le sessioni.\n\nSe pensi che qualcuno si sia collegato al tuo account contro il tuo volere, puoi attivare la verifica in due passaggi nelle impostazioni di Privacy e sicurezza.\n\nSinceramente\nIl Team di Telegram %1$s ha aggiornato la foto del profilo - %1$s si è unito al gruppo %2$s tramite link di invito + %1$s si è unito al gruppo %2$s tramite link d\'invito Rispondi Rispondi a %1$s Rispondi a %1$s @@ -451,26 +520,27 @@ ultimo accesso molto tempo fa Nuovo messaggio - Invia messaggio a... - Sarai in grado di aggiungere più utenti dopo aver creato il gruppo e averlo convertito in supergruppo. - Immetti il nome del gruppo - Nome gruppo - %1$d/%2$d membri + Aggiungi persone... + Potrai aggiungere più utenti dopo aver creato il gruppo e averlo convertito in supergruppo. + Inserisci il nome del gruppo + Nome del gruppo + %1$d di %2$d + fino a %1$s Vuoi unirti alla chat \'%1$s\'? Spiacenti, questo gruppo è già pieno. Spiacenti, sembra che questa chat non esista. Link copiato negli appunti Invita nel gruppo tramite link - Link di invito + Link d\'invito Sei sicuro di voler revocare questo link? Una volta fatto, nessuno potrà unirsi utilizzandolo. - Il precedente link di invito è inattivo. Ne è appena stato creato uno nuovo. + Il precedente link d\'invito è ora inattivo. Ne è stato creato uno nuovo. Revoca Revoca link Sei sicuro di voler revocare il link ]]>%1$s]]>?\n\nIl gruppo \"]]>%2$s]]>\" diventerà privato. Sei sicuro di voler revocare il link ]]>%1$s]]>?\n\nIl canale \"]]>%2$s]]>\" diventerà privato. Copia link Condividi link - Chiunque abbia Telegram installato, sarà in grado di aggiungersi al tuo gruppo aprendo il link. + Chiunque abbia Telegram installato, potrà aggiungersi al tuo gruppo aprendo questo link. Amministratori Tutti sono amministratori @@ -507,12 +577,16 @@ Altro Principale Inizia chat segreta + Gruppi in comune + Gruppi in comune + Ancora nessun gruppo in comune Si è verificato un errore. Chiave di crittografia Timer di autodistruzione Spento - Questa immagine e il testo sono derivati dalla chiave di crittografia per questa chat segreta con ]]>%1$s]]>.
          ]]>Se sono uguali sul dispositivo di ]]>%2$s]]>, la crittografia end-to-end è garantita.
          ]]>Ulteriori informazioni su telegram.org
          + L\'immagine e il testo sono derivati dalla chiave di crittografia di questa chat segreta con ]]>%1$s]]>.
          ]]>Se sono uguali sul dispositivo di ]]>%2$s]]>, la crittografia end-to-end è garantita.
          ]]>Ulteriori informazioni su telegram.org
          https://telegram.org/faq/it#chat-segrete + Tocca per mostrare le emoji Sconosciuto Info Telefono @@ -525,6 +599,7 @@ Il massimo per un username è 32 caratteri. Spiacenti, un username non può iniziare con un numero. Puoi scegliere un username su ]]>Telegram]]>. Se lo fai, le altre persone potranno trovarti tramite questo username e contattarti senza conoscere il tuo numero di telefono.
          ]]>Puoi usare ]]>a–z]]>, ]]>0–9]]> e underscore. La lunghezza minima è di ]]>5]]> caratteri.
          + Questo link apre una chat con te su Telegram:\n%1$s Controllo l\'username... %1$s è disponibile. Nessuno @@ -549,7 +624,7 @@ Maschere Puoi aggiungere maschere alle foto che invii. Per farlo, apri l\'editor fotografico prima di inviare una foto. Sticker in primo piano - Questi sticker sono attualmente in primo piano su Telegram. Puoi aggiungere sticker personalizzati tramite il bot @stickers. + Questi sticker sono al momento in primo piano su Telegram. Puoi aggiungere sticker personalizzati tramite il bot @stickers. Sticker archiviati Maschere archiviate Nessuno sticker archiviato @@ -561,6 +636,20 @@ Alcuni dei tuoi set di sticker più vecchi sono stati stati archiviati. Puoi riattivarli nelle impostazioni degli sticker. Maschere archiviate Alcuni dei tuoi set di maschere più vecchi sono stati stati archiviati. Puoi riattivarli nelle impostazioni delle maschere. + + Tema + Sei sicuro di voler eliminare questo tema? + File tema non valido + Inserisci il nome del tema + CHIUDI EDITOR + SALVA TEMA + Nuovo tema + APPLICA + Anteprima tema + Seleziona colore + Crea nuovo tema + Tocca sull\'icona della tavolozza per vedere la lista degli elementi in ogni schermata - e modificarli. + Puoi creare il tuo tema cambiando i colori all\'interno dell\'app. Puoi sempre tornare al tema predefinito di Telegram qui. Ripristina tutte le impostazioni di notifica predefinite Dimensione testo messaggi @@ -583,21 +672,42 @@ Ripristina tutte le notifiche Annulla tutte le impostazioni di notifica personalizzate per tutti i tuoi contatti e gruppi Notifiche e suoni + Notifiche personalizzate + Notifiche popup + I nuovi messaggi da questo contatto appariranno sul tuo schermo quando non stai usando Telegram. + LED + Colore + Blu + Rosso + Giallo + Verde + Ciano + Bianco + Rosa + Viola + Arancione + Il led è una piccola luce lampeggiante usata in alcuni dispositivi per indicare nuovi messaggi. + Le notifiche con priorità più alta funzioneranno anche se il telefono è in modalità Non disturbare. + Generali + + No + Attiva + Disattiva Utenti bloccati Esci Nessun suono - Predefinite - Supporto + Predefinito + Assistenza Solo se silenzioso Sfondo chat Messaggi - Invia con tasto invio + Invia con tasto Invio Termina le altre sessioni Eventi Un contatto si è unito a Telegram Messaggi fissati Lingua - Nota che il supporto di Telegram è fornito da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.
          ]]>Dai un\'occhiata alle Domande frequenti di Telegram]]>: contengono suggerimenti importanti per risolvere i problemi]]> e risposte a quasi tutte le domande.
          + Per favore nota che l\'assistenza di Telegram è fornita da volontari. Proviamo a rispondere quanto prima, ma potrebbe volerci del tempo.
          ]]>Dai un\'occhiata alle domande frequenti di Telegram]]>: contengono suggerimenti importanti per risolvere i problemi]]> e risposte a quasi tutte le domande.
          Chiedi a un volontario Domande frequenti https://telegram.org/faq/it @@ -624,18 +734,20 @@ Contatore badge Breve Lunga - Predefinito di sistema - Impostazioni predefinite Download automatico media Quando utilizzi la rete mobile Quando connesso tramite Wi-Fi In roaming Nessun media Autoriproduzione GIF - Alza per parlare + Alza per registrare Salva nella galleria Modifica nome + Personalizza + Personalizzate + Attiva notifiche personalizzate Priorità + Come nelle Impostazioni Predefinita Bassa Alta @@ -651,40 +763,44 @@ Altro Disabilitate Disabilitata + Abilitate + Disabilitate Disabilitato No Suoni in-chat Predefinito Predefinita Notifiche intelligenti + %1$d / %2$s Disabilitate - Suona al massimo %1$s in %2$s - Suona al massimo - volte - in - minuti + Frequenza avviso sonoro + %1$s entro %2$s Anteprime link Chat segrete Browser in-app Apri i link esterni all\'interno dell\'app Condivisione diretta - Mostra le chat recenti nel menu condividi + Mostra le chat recenti nel menù di condivisione Emoji - Draw single big emoji - Use system default emoji + Disegna una singola emoji grande + Usa emoji predefinite + Telegram per Android %1$s + Menù debug + Importa contatti + Ricarica contatti - Impostazioni cache Database locale Cancellare i messaggi salvati nella cache? Svuotando il database locale, verranno cancellati i messaggi nella cache e il database verrà compresso per risparmiare spazio. Telegram ha bisogno di alcuni dati per funzionare, quindi il database non sarà azzerato.\n\nQuesta operazione può richiedere alcuni minuti. Vuota la cache Svuota - Calcolando... + Calcolo... Documenti Foto Messaggi vocali Video Musica + GIF Altri file Vuota Mantieni media @@ -701,9 +817,9 @@ Terminare questa sessione? app non ufficiale - Blocco con codice + Codice di blocco Cambia codice - Quando imposti un codice, un\'icona col lucchetto apparirà nella pagina delle chat. Premi su di essa per bloccare e sbloccare l\'app.\n\nNota: se ti dimentichi il codice, dovrai disinstallare e reinstallare l\'app. Tutte le chat segrete verranno perse. + Quando imposti un codice, apparirà un\'icona col lucchetto nella pagina delle chat. Premila per bloccare e sbloccare l\'app.\n\nNota: se ti dimentichi il codice, dovrai disinstallare e reinstallare l\'app. Tutte le chat segrete verranno perse. Ora visualizzerai un\'icona col lucchetto nella pagina delle chat. Premi su di essa per bloccare la tua app Telegram con il codice. PIN Password @@ -722,6 +838,8 @@ Conferma impronta digitale per continuare Sensore touch Impronta digitale non riconosciuta. Riprova + Consenti cattura schermo + Se attivata, puoi fare screenshot dell\'app, ma il sistema mostrerà le tue chat nel task switcher anche quando il codice di blocco è attivo.\n\nPotresti dover riavviare l\'app per rendere effettive le modifiche. File condivisi Media condivisi @@ -747,7 +865,7 @@ Precisione di %1$s O SELEZIONA UN LUOGO - Mostra tutti i file media + Mostra tutti i media Salva nella galleria %1$d di %2$d Galleria @@ -798,6 +916,7 @@ Aggiungi una didascalia... Didascalia foto Didascalia video + Didascalia GIF Didascalia Disegno Sticker @@ -807,6 +926,9 @@ Duplica Contornato Normale + Ripristina + Originale + Quadrato Verifica in due passaggi Imposta password aggiuntiva @@ -857,6 +979,27 @@ Password disattivata Hai attivato la verifica in due passaggi.\nAvrai bisogno della password che hai impostato per accedere al tuo account Telegram. La tua e-mail di recupero %1$s non è ancora attiva e attende la conferma. + + Dati e archivio + Utilizzo disco e rete + Utilizzo archivio + Utilizzo dati mobile + Utilizzo dati roaming + Utilizzo dati Wi-Fi + Messaggi e altri dati + Inviati + Ricevuti + Byte inviati + Byte ricevuti + File + Chiamate + Chiamate in uscita + Chiamata in entrata + Tempo totale + Totale + Azzera statistiche + Utilizzo rete da %1$s + Vuoi ripristinare le tue statistiche di utilizzo? Privacy e sicurezza Privacy @@ -872,12 +1015,12 @@ Sicurezza Elimina il mio account Se lontano per - Se non ti connetti almeno una volta in questo periodo, il tuo account verrà eliminato insieme a tutti i gruppi, messaggi e contatti. + Se non ti connetti almeno una volta in questo periodo, il tuo account verrà eliminato insieme a tutti i gruppi, i messaggi e i contatti. Eliminare il tuo account? Cambia chi può vedere il tuo ultimo accesso. Chi può vedere il tuo ultimo accesso? Aggiungi eccezioni - Importante: non sarai in grado di vedere l\'ultimo accesso delle persone con le quali non condividi l\'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese). + Importante: non potrai vedere l\'ultimo accesso delle persone con cui non condividi l\'ultimo accesso. Verrà mostrato un orario approssimativo (di recente, entro una settimana, entro un mese). Condividi con Non condividere con Queste impostazioni annulleranno i valori precedenti. @@ -896,7 +1039,7 @@ Non consentire mai Consenti sempre... Non consentire mai... - Questi utenti saranno o non saranno in grado di aggiungerti a gruppi e canali indipendentemente dalle impostazioni precedenti. + Questi utenti potranno o non potranno aggiungerti a gruppi e canali indipendentemente dalle impostazioni precedenti. Cambia chi può aggiungerti a gruppi e canali. Spiacenti, non puoi aggiungere questo utente al gruppo a causa delle sue impostazioni di privacy. Spiacenti, non puoi aggiungere questo utente al canale a causa delle sue impostazioni di privacy. @@ -904,7 +1047,7 @@ Modifica video Invio video... - Invio gif... + Invio GIF... bot Condividi @@ -941,9 +1084,11 @@ Imposta OK RITAGLIA + + No - Ti sei unito al gruppo tramite link di invito - un1 si è unito al gruppo tramite link di invito + Ti sei unito al gruppo tramite link d\'invito + un1 si è unito al gruppo tramite link d\'invito un1 ha rimosso un2 un1 ha lasciato il gruppo un1 ha aggiunto un2 @@ -987,6 +1132,7 @@ Troppi tentativi, riprova più tardi Troppi tentativi, per favore riprova di nuovo tra %1$s Codice non valido + Spiacenti, hai eliminato e ricreato il tuo account troppe volte di recente. Per favore attendi alcuni giorni prima di iscriverti di nuovo. Nome non valido Cognome non valido Carico... @@ -1034,20 +1180,24 @@ Questo bot non può unirsi ai gruppi. Vuoi attivare le anteprime estese per i link nelle Chat Segrete? Nota che le anteprime dei link sono generate sui server di Telegram. Per favore nota che i bot inline sono forniti da sviluppatori di terze parti. Per far funzionare il bot, i simboli che digiti dopo l\'username del bot sono inviati al rispettivo sviluppatore. - Vuoi attivare \"Alza per parlare\" per i messaggi vocali? + Vuoi attivare \"Alza per registrare\" per i messaggi vocali? Spiacenti, non puoi modificare questo messaggio. Per favore consenti a Telegram di ricevere SMS così potremo inserire in automatico il codice per te. Per favore consenti a Telegram di ricevere chiamate così potremo inserire in automatico il codice per te. + Per favore consenti a Telegram di ricevere chiamate ed SMS così potremo inserire il tuo numero di telefono, inviarti un codice ed inserirlo per te. Per favore consenti a Telegram di ricevere chiamate ed SMS così potremo inserire in automatico il codice per te. Spiacenti, questa azione non ti è permessa. + Spiacenti, non puoi aggiungere questo utente o bot al gruppo perchè lo hai bloccato. Per favore sbloccalo per procedere. UNISCITI AL GRUPPO Telegram deve accedere ai tuoi contatti per poterti connettere con i tuoi amici su tutti i tuoi dispositivi. Telegram deve accedere alla tua memoria per poter inviare e salvare foto,video, musica e altri media. Telegram deve accedere al microfono per poter inviare messaggi vocali. + Telegram deve accedere al microfono per poter registrare video. Telegram deve accedere alla tua fotocamera per scattare foto e registrare video. Telegram deve accedere alla tua posizione per poterla condividere con i tuoi amici. Telegram deve accedere alla tua posizione. + Telegram deve accedere allo spostamento su altre app per riprodurre i video in modalità PiP. IMPOSTAZIONI Telegram @@ -1064,7 +1214,67 @@ Telegram]]> non ha limiti di dimensione]]>per le tue chat e i file multimediali. Telegram]]> ti consente di accedere]]>ai tuoi messaggi da più dispositivi. I messaggi di Telegram]]> sono fortemente]]>criptati e possono autodistruggersi. - Inizia a chattare + Inizia a messaggiare + + Impostazioni account + Utilizza meno dati per le chiamate + Chiamata in entrata + Connetto + Scambio delle chiavi di crittografia + Attendo + Richiedo + Riattacco + Chiamata terminata + Connessione fallita + Squillo + Linea occupata + Chiamata Telegram + Chiamata Telegram in corso + Termina chiamata + Altra chiamata in corso + Al momento hai una chiamata in corso con %1$s]]>. Vuoi terminare quella chiamata e iniziarne una nuova con %2$s]]>? + Chiamate vocali + Suoneria + Puoi personalizzare la suoneria usata quando questo contatto ti chiama su Telegram. + Chiamate + Chi può chiamarmi? + Puoi decidere chi può chiamarti. + Questi utenti potranno o non potranno chiamarti indipendentemente dalle impostazioni precedenti. + Mai + Solo con i dati mobile + Sempre + Rispondi + Rifiuta + Al momento non sei in linea. Per favore connettiti a Internet per chiamare. + Al momento hai la modalità aereo attivata. Per favore disattivala o connettiti ad una rete Wi-Fi per chiamare. + Non in linea + Modalità aereo + Impostazioni + Chiamata in uscita + Chiamata in entrata + Chiamata persa + Chiamata annullata + Chiamata rifiutata + %1$s (%2$s) + L\'immagine e il testo sono derivati dalla chiave di crittografia di questa chiamata con ]]>%1$s]]>.
          ]]>Se sono uguali sul dispositivo di ]]>%2$s]]>, la crittografia end-to-end è garantita.
          + Non hai ancora effettuato alcuna chiamata. + L\'app di ]]>%1$s]]> sta usando un protocollo non compatibile. Deve aggiornare la sua app prima che tu possa chiamarlo. + L\'app di ]]>%1$s]]> non supporta le chiamate. Deve aggiornare la sua app prima che tu possa chiamarlo. + Per favore valuta la qualità della tua chiamata Telegram + Vuoi lasciare un feedback per aiutarci a migliorare le chiamate? + Telegram deve accedere al microfono per poter effettuare chiamate. + Aggiungi un commento opzionale + Richiama + Richiama + Predefinita + Sei sicuro di voler eliminare questa voce dal registro delle chiamate? + Chiamata Telegram + Auricolare + Altoparlante + Bluetooth + RITORNA ALLA CHIAMATA + Spiacenti, ]]>%1$s]]> non accetta le chiamate. + Se le emoji sono uguali sullo schermo di %1$s, la chiamata è sicura al 100%%. %1$d in linea %1$d in linea @@ -1284,6 +1494,8 @@ e altri %1$d e altri %1$d + dd MMM yyyy, h:mm a + dd MMM yyyy, HH:mm MMMM yyyy dd MMM dd.MM.yy diff --git a/TMessagesProj/src/main/res/values-ko/strings.xml b/TMessagesProj/src/main/res/values-ko/strings.xml index 8794b81fe67..8dd8fc0c12a 100644 --- a/TMessagesProj/src/main/res/values-ko/strings.xml +++ b/TMessagesProj/src/main/res/values-ko/strings.xml @@ -41,7 +41,56 @@ 이름(필수) 성(선택) 가입 취소 + + %3$s에 대한 %1$s 금액을 %2$s 에게 성공적으로 전송하였습니다. + %1$s 금액을 %2$s 에게 성공적으로 전송하였습니다. + 체크아웃 + 배송방법 + 죄송합니다. 이 주소로 배송이 가능하지 않습니다. + 배송정보 + 배송주소 + 주소 1 (Street) + 주소 2 (Street) + + + 국가 + 우편번호 + 수신자 + 이름 + 전화번호 + 이메일 + 배송정보 저장 + 향후 사용을 위하여 배송정보를 저장 할 수 있습니다. + 결제정보 + 결제카드 + 카드번호 + 보안코드 (CVV) + MM/YY + 결제주소 + 카드소유자 + + 결제정보 저장 + 향후 사용을 위하여 결제정보를 저장 할 수 있습니다. + 이 기능을 사용하시려면 2단계 인증 비밀번호를 사용해야합니다. + 거래내역 확인 + %3$s에 대한 %1$s 금액을 %2$s 봇에게 전송하시겠습니까? + 전체 + 인보이스 + 테스트 인보이스 + %1$s 결제 + 결제방법 + 이름 + 전화번호 + 연락주소 + 배송방법 + 영수증 + 다른 카드를 선택해주세요 + %1$s 카드정보가 파일로 있습니다. 이 카드로 결제하시려면 2단계 비밀번호를 입력해주세요 + 죄송합니다, 결제가 봇에 의하여 거절되었습니다. + 죄송합니다, 결제가 거절되었습니다. + 결제 서버로 연결을 할 수 없습니다. 인터넷 연결을 확인해주시고 다시 시도해주세요. + 새로운 대화 설정 주소록 새 그룹 @@ -80,6 +129,16 @@ 링크 미리복 임시저장 내역이 초기화 됨 + %1$s 이내 + %1$s by %2$s + 미리보기에 대한 의견 남기기 + 스티커 전송 + 팩 보기 + 맨 위로 고정 + 맨 위 고정 해제 + 굵게 + 기울림 + 일반 그룹 종류 채널 종류 @@ -267,11 +326,14 @@ %1$s님이 음성메시지를 녹음중입니다... %1$s님이 음성파일을 전송중입니다... %1$s님이 사진 보내는 중... + 즉시 보기 + %1$s님이 게임을 플레이중입니다. %1$s님이 동영상 보내는 중... %1$s님이 파일 보내는 중... 음성메시지를 녹음 중입니다.. 음성파일 전송 중.. 사진 전송 중.. + 게임 플레이중... 동영상 전송 중.. 파일 전송 중... 텔레그램에 관해\n궁금한 사항이 있나요? @@ -307,6 +369,7 @@ 음악으로 저장 공유 언어 파일 적용 + 테마 파일 적용 지원하지 않는 형식입니다 자동삭제 타이머 설정 서비스 알림 @@ -325,6 +388,8 @@ 이 채널을 스팸신고 하시겠습니까? 죄송합니다, 서로 연락처가 추가된 경우에만 메시지 전송이 가능합니다. 죄송합니다, 서로 연락처가 추가된 경우에만 그룹에 구성원을 추가 할 수 있습니다. + 오늘 연락처가 없는 분들과 너무 많은 연락을 하셨습니다. 내일 다시 시도해주세요. 오늘은 메시지가 오는 분들하고만 대화가 가능합니다. + 오늘 연락처가 없는 분들과 너무 많은 연락을 하셨습니다. 내일 다시 시도해주세요. 그룹에 추가하시려면 다른 분이 초대해주셔야합니다. https://telegram.org/faq#can-39t-send-messages-to-non-contacts 더 보기 다음에게 보내기.. @@ -358,6 +423,10 @@ 모든 기기에서 이 대화를 공유 빠른검색을 위하여 검색사용 클라우드 저장소 + 날자로 이동 + %1$s 에게 삭제 + 모두에게 삭제 + 클립보드로 텍스트가 복하되었습니다. %1$s님이 자동삭제를 %2$s 후로 설정했습니다 자동삭제를 %1$s 후로 설정했습니다 @@ -451,11 +520,12 @@ 마지막으로 접속한 지 오래됨 새 메시지 - 메시지 보내기... + 초대하기.. 이 그룹 생성을 완료하시고 슈퍼그룹으로 전환하시면 더 많은 구성원을 추가 할 수 있습니다. 그룹 이름 입력 그룹 이름 - 대화상대 %1$d/%2$d + %2$d 중 %1$d 선택됨 + 최대 %1$s \'%1$s\' 채널에 참여하시겠습니까? 죄송합니다, 그룹방의 인원이 최대치입니다. 죄송합니다, 채팅방이 더이상 존재하지 않습니다. @@ -507,12 +577,16 @@ 기타 비밀대화 시작 + 공통 그룹 + 공통 그룹 + 아직 공통 그룹이 없습니다 오류가 발생했습니다. 암호화 키 자동삭제 타이머 해제 이 이미지와 텍스트는 ]]>%1$s]]>.
          ]]> 님과의 현재 비밀대화에 대한 비밀키에서 파생된 것입니다. 이 이미지와 텍스트가 ]]>%2$s\'s]]> 님의 휴대전화와 동일하다면 단말기간(end-to-end)의 암호화가 정상적으로 진행되고 있음을 보장합니다.
          ]]> 자세한 사항은 telegram.org를 참고해 주세요.
          https://telegram.org/faq#secret-chats + 탭을 하여 이모티콘화 알 수 없음 정보 전화번호 @@ -525,6 +599,7 @@ 아이디는 최대 32자까지만 가능합니다. 아이디는 숫자로 시작할 수 없습니다. 텔레그램 아이디를 설정할 수 있습니다. 아이디를 설정하면 회원님의 전화번호를 몰라도 아이디로 회원님을 찾아 대화를 나눌 수 있습니다.
          ]]>아이디는 영문, 밑줄, 숫자로 (]]>a~z]]>, ]]>_]]>, ]]>0~9]]>) ]]>다섯 글자]]> 이상으로 설정해 주세요.
          + 이 링크는 다음 상대와 텔레그램 대화를 열게 됩니다:\n%1$s 아이디 확인 중... %1$s: 사용 가능합니다. 없음 @@ -561,6 +636,20 @@ 오래된 스티커중 일부가 보관되어집니다. 스티커 설정에서 다시 재설정이 가능합니다. 보관된 마스크 오래된 마스크중 일부가 보관되어집니다. 마스크 설정에서 다시 재설정이 가능합니다. + + 테마 + 이 테마를 삭제하시겠습니까? + 올바르지 않은 테마 파일 + 테마명을 입력하세요 + 에디터 닫기 + 테마 저장 + 새로운 테마 + 적용 + 테마 미리보기 + 색상 선택 + 새로운 테마 생성 + 팔레트 아이콘을 클릭하여 화면 구성 리스트를 볼 수 있으며 - 편집을 할 수 있습니다. + 앱내에 색상을 변경하여 원하는 테마를 생성할 수 있습니다. 여기에서 언제든지 기존 텔레그램 테마로 복원이 가능합니다. 모든 알림 설정이 초기화되었습니다 채팅 글자 크기 @@ -583,6 +672,27 @@ 모든 알림 설정 초기화 연락처와 그룹에 대한 모든 알림 설정을 처음 상태로 되돌립니다. 알림 및 소리 + 개별 알림 + 알림 팝업 + 이 연락처로 부터 수신되는 메시지는 텔레그램 미사용 중일시 화면에 뜨게 됩니다. + LED + 색깔 + 파란색 + 빨간색 + 노란색 + 초록색 + 청록색 + 하얀색 + 핑크색 + 보라색 + 주황색 + LED는 일부 기기에서 새로운 메시지가 수신될때 반짝입니는 빛입니다 + 우선수위가 높은 알림은 휴대폰 기기가 무음이더라도 알림이 작동합니다. + 일반 + 켜기 + 끄기 + 켜기 + 끄기 차단 목록 로그아웃 알림음 없음 @@ -597,7 +707,7 @@ 친구의 텔레그램 가입 알림 고정된 메시지 언어 - 텔레그램에 관한 질문은 자원봉사자들이 답변해 드립니다. 신속한 답변을 위해 노력하지만 답변이 다소 늦을 수 있습니다.
          ]]>일반적인 문제와 해결방법]]>에 대해서는 \'자주 묻는 질문]]>\'을 확인해 보세요.
          + 텔레그램에 관한 질문은 자원봉사자들이 답변해 드립니다. 신속한 답변을 위해 노력하지만 답변이 다소 늦을 수 있습니다.
          ]]>일반적인 문제와 해결방법]]>에 대해서는 \'자주 묻는 질문]]>\'을 확인해 보세요.
          질문하기 자주 묻는 질문 https://telegram.org/faq/ko @@ -624,8 +734,6 @@ 앱 아이콘에 알림 개수 표시 짧게 길게 - 시스템 기본값 - 설정 기본값 사진/동영상 자동 다운로드 모바일 데이터를 사용 중일 때 Wi-Fi에 연결 중일 때 @@ -635,7 +743,11 @@ 기기를 들어 말하기 앨범에 자동 저장 이름 편집 + 개인화 + 개별 + 개별 알림 활성화 우선순위 + 설정과 동일 기본 낮음 높음 @@ -651,29 +763,32 @@ 기타 비활성화됨 비활성화됨 + 활성화 + 비활성화 비활성화됨 채팅중 소리 설정 기본값 기본값 스마트 알림 + %1$d / %2$s 비활성화됨 - 최대 %1$s번, %2$s번 이내 알림 - 알림 최대치 - - 이내 - + 소리 알림 간격 + %2$s 이내 %1$s 링크 프리뷰 비밀대화 크롬 커스텀 탭 앱내에서 외부 링크 열기 직접 공유 공유 메뉴에서 최근 대화 보기 - Emoji - Draw single big emoji - Use system default emoji + 이모티콘 + 한개의 큰 이모티콘 그리기 + 기본 시스템 이모티콘 사용 + 텔레그램 안드로이드 %1$s + 디버그 메뉴 + 연락처 가져오기 + 연락처 재동기화 - 캐시 설정 로컬 데이터베이스 캐시된 텍스트 메시지를 삭제하시겠습니까? 압축된 데이터베이스 및 캐시에 저장된 메시지를 로컬 데이터베이스에서 삭제하면 내부 저장공간이 증가합니다. 데이터베이스는 Telegram이 작동하는데 어느정도 필요함으로 완전히 삭제가 되지는 않습니다.\n\n이 작업은 완료되기까지 몇분정도 소요가 될 수 있습니다. @@ -685,6 +800,7 @@ 음성 메시지 동영상 음악 + GIF파일 다른 파일 없음 미디어 저장 @@ -722,6 +838,8 @@ 지문인식 후 진행해주세요 터치 센서 지문인식이 실패하였습니다. 다시 시도해주세요. + 화면 캡춰 허용 + 활성화시 앱내에서 화면 캡춰가 가능합니다. 다만, 화면전환시 잠금코드가 활성화 되어져 있더라도 대화내용이 뜰 수 있습니다.\n\n적용이 안될시, 앱 재시작이 필요할 수도 있습니다. 공유한 파일 공유된 미디어 @@ -798,6 +916,7 @@ 설명 추가... 사진 설명 동영상 설명 + GIF 설명 설명 그리기 스티커 @@ -807,6 +926,9 @@ 복제 아웃라인 레귤러 + 초기화 + 원본 + 정사각형 2단계 인증 개별 비밀번호 설정 @@ -857,6 +979,27 @@ 비밀번호 비활성화 2단계 인증을 활성화 하였습니다.\n설정된 개별 비밀번호를 사용하여 텔레그램 계정에 로그인 할 수 있습니다. 복구 이메일 %1$s 이 아직 활성화 되지 않았으며 미승인 상태입니다. + + 데이터 및 저장소 + 디스크 및 네트워크 사용량 + 저장소 사용량 + 모바일 데이터 사용량 + 로밍 데이터 사용량 + WIFI 데이터 사용량 + 메시지 및 기타 데이터 + 보냄 + 받음 + 보낸 바이트 + 받은 바이트 + 파일 + 전화 + 발신 전화 + 수신 전화 + 전체 시간 + 전체 + 통계 초기화 + %1$s 부터 사용된 네트워크 + 사용량 통계를 초기화 하시겠습니까? 개인정보 및 보안 개인정보 @@ -872,7 +1015,7 @@ 보안 회원 탈퇴 자동 회원 탈퇴 - 이 기간 동안 최소 한 번 이상 로그인을 하지 않으면 자동으로 모든 메시지와 대화상대를 삭제하고 텔레그램을 탈퇴합니다. + 이 기간 동안 최소 한 번 이상 로그인을 하지 않으면 자동으로 모든 메시지, 연락처와 대화상대를 삭제하고 텔레그램을 탈퇴합니다. 텔레그램을 탈퇴할까요? 누가 마지막 접속 시간을 볼 수 있는지 변경 마지막 접속 시간을 누구에게 공개할까요? @@ -904,7 +1047,7 @@ 동영상 편집 동영상 보내는 중... - gif 전송 중... + GIF 전송중... 공유 @@ -941,6 +1084,8 @@ 설정 확인 자르기 + + 아니오 초대링크를 타고 그룹에 참여하였습니다. 초대링크를 타고 그룹에 un1님이 참여하였습니다. @@ -987,6 +1132,7 @@ 너무 많이 시도하셨습니다. 나중에 다시 시도하세요 너무 많이 시도하셨습니다. %1$s 초 후에 다시 시도하세요. 올바른 코드를 입력해 주세요 + 죄송합니다, 최근에 계정 재가입을 너무 많이 시도하였습니다. 몇일 후에 다시 시도해주세요. 올바른 이름을 입력해 주세요 올바른 성을 입력해 주세요 불러오는 중... @@ -1038,16 +1184,20 @@ 죄송합니다, 메시지 수정을 할 수 없습니다. 텔레그램이 SMS를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 텔레그램이 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. + 텔레그램이 SMS 및 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 텔레그램이 SMS 및 전화를 수신 할 수 있도록 설정해주셔야 자동으로 코드 입력이 가능합니다. 죄송합니다, 이 작업이 제한되었습니다. + 죄송합니다, 그룹에 이 이용자나 봇을 차단하였기 대문에 추가를 할 수 없습니다. 초대하시렴녀 차단해제를 부탁드립니다. 그룹방 입장 Telegram은 여러 기기에서 친구와 메시지를 주고받을 수 있도록 회원님의 연락처 접근이 필요합니다. Telegram은 사진, 비디오, 음악 및 다양한 미디어를 공유 및 저장하기 위하여 스토리지 접근이 필요합니다. Telegram이 음성 메시지를 보내기 위하여 마이크에 대한 접근이 필요합니다. + Telegram이 비디오 녹화를 위하여 마이크에 대한 접근이 필요합니다. Telegram이 사진 및 비디오 촬영을 하기 위하여 카메라 접근 권한을 필요로 합니다. Telegram이 위치를 친구분들과 공유하기 위해 위치에 대한 접근 권한을 필요로 합니다. 텔레그램이 위치에 대한 권한이 필요합니다. + Telegram이 화면속화면 기능을 위하여 화면위 그리기에 대한 접근이 필요합니다. 설정 텔레그램 @@ -1065,6 +1215,66 @@ 텔레그램]]>은 다른 기기에서도]]>동시에 사용할 수 있습니다. 텔레그램]]>은 메시지를 강력하게 암호화하며]]>자동으로 삭제되게 할 수 있습니다. 시작하기 + + 계정설정 + 전화에 적은 데이터 사용 + 수신 전화 + 연결 중 + 암호화 키 교환 중 + 대기 중 + 요청 중 + 전화 끊는 중 + 전화 종료 + 연결 실패 + 전화 거는 중 + 통화 중 + 텔레그램 전화 + 진행중인 텔레그램 전화 + 전화 종료 + 다른 전화가 진행중입니다 + 현재 %1$s]]>와 통화중입니다. 전화를 끊고 %2$s]]>와 새로운 통화를 하시겠습니까? + 음성 전화 + 통화음 + 텔레그램으로 전화 수신시 이 연락처에 대한 개별 통화음을 설정할 수 있습니다. + 전화 + 나에게 전화가 가능한 사람은? + 전화차단 설정이 가능합니다. + 이 사용자들은 위의 설정과 무관하게 전화가 되거나 안될 수 있습니다. + 거부 + 모바일 데이터로만 가능 + 항상 + 받기 + 거절 + 현재 오프라인입니다. 인터넷에 접속하여 전화를 시도해주세요 + 현재 비행기 모드가 활성화 되어져 있습니다. 모드를 끄시거나 WIFI를 연결하여 전화를 시도해주세요 + 오프라인 + 비행기 모드 + 설정 + 발신 전화 + 수신 전화 + 부재중 전화 + 취소된 전화 + 거절한 전화 + %1$s (%2$s) + 이 이미지와 텍스트는 ]]>%1$s]]>.
          ]]> 님과의 현재 전화를 위하여 파생된 것입니다. 이 이미지와 텍스트가 ]]>%2$s\'s]]> 님의 휴대전화와 동일하다면 단말기간(end-to-end)의 암호화가 정상적으로 진행되고 있음을 보장합니다.
          + 아직 전화 내역이 없습니다. + ]]>%1$s]]>의 앱은 현재 호환되지 않은 프로토콜을 사용중입니다. 앱 업데이트가 필요합니다. + ]]>%1$s]]>은 현재 전화기능을 지원하고 있지 않습니다. 앱 업데이트 후에 전화가 가능합니다. + 텔레그램 전화의 품질을 평가해주세요. + 전화서비스 품질개선을 위하여 피드백을 남겨주시겠나요? + Telegram이전화를 걸기 위하여 마이크에 대한 접근이 필요합니다. + 코멘트 추가 + 전화 회신 + 다시 전화걸기 + 기본값 + 이 통화내역을 삭제하시겠습니까? + 텔레그램 전화 + 이어폰 + 스피커 + 블루투스 + 전화로 이동 + 죄송합니다, ]]>%1$s]]> 님은 수신거부 상태입니다. + %1$s님과 이모티콘이 일치한다면, 이 통화는 100%% 안전합니다 온라인 %1$d명 온라인 %1$d명 @@ -1284,6 +1494,8 @@ 및 %1$d 개 및 %1$d 개 + MMM dd yyyy, h:mm a + MMM dd yyyy, HH:mm MMMM yyyy M\'월\' d\'일\' yyyy.MM.dd. diff --git a/TMessagesProj/src/main/res/values-nl/strings.xml b/TMessagesProj/src/main/res/values-nl/strings.xml index f894abcddf8..fcf54fec602 100644 --- a/TMessagesProj/src/main/res/values-nl/strings.xml +++ b/TMessagesProj/src/main/res/values-nl/strings.xml @@ -41,7 +41,56 @@ Voornaam (verplicht) Achternaam (optioneel) Registratie annuleren + + %1$s is succesvol overgemaakt naar %2$s voor %3$s + %1$s is succesvol overgemaakt naar %2$s + Afrekenen + Verzendmethodes + Sorry, afleveren op jouw adres is niet mogelijk. + Verzendinformatie + Verzendadres + Adres 1 (Straat) + Adres 2 (Straat) + Stad + Provincie + Land + Postcode + Ontvanger + Naam + Telefoonnummer + E-mail + Verzendinformatie opslaan + Je kunt je verzendinformatie opslaan voor toekomstig gebruik. + Betaalinformatie + Betaalkaart + Kaartnummer + Veiligheidscode (CVV) + MM/YY + Factuuradres + Kaarthouder + Achternaam + Betalingsinformatie opslaan + Je kunt je betalingsinformatie voor toekomstig gebruik opslaan. + Activeer \"twee-staps-verificatie\" om deze functie te gebruiken. + Transactie-overzicht + Echt %1$s overmaken naar de bot %2$s voor %3$s? + Totaal + FACTUUR + TEST FACTUUR + %1$s BETALEN + Betalingsmethode + Naam + Telefoonnummer + Contactadres + Verzendmethode + Betalingsbewijs + Kies een andere kaart + Kaartinformatie %1$s is opgeslagen. Geef je 2-staps-verificatie-wachtwoord in om met deze kaart te betalen. + Sorry, de bot heeft je betaling geannuleerd. + Sorry, de betaling werd geweigerd. + Betalingsprovider kon niet worden bereikt, controleer je internetverbinding en probeer het opnieuw. + NIeuwe chat Instellingen Contacten Nieuwe groep @@ -80,6 +129,16 @@ Link-voorvertoning Concept Geschiedenis gewist + door %1$s + %1$s door %2$s + Feedback over deze voorvertoning + Sticker sturen + Bundel bekijken + Vastzetten + Losmaken + Vet + Cursief + Normaal Groepsvorm Kanaaltype @@ -110,7 +169,7 @@ un1 heeft sticker vastgezet un1 heeft spraakbericht vastgezet un1 heeft contact vastgezet - un1 heeft een %1$s vastgezet + un1 heeft %1$s vastgezet un1 heeft locatie vastgezet un1 heeft GIF vastgezet un1 heeft muziekbestand vastgezet @@ -267,11 +326,14 @@ %1$s neemt een spraakbericht op... %1$s verstuurt een geluid... %1$s verstuurt een foto + SNELLE WEERGAVE + %1$s speelt een spel %1$s verstuurt een video %1$s verstuurt een bestand neemt een spraakbericht op... audio versturen... foto versturen + speelt een spel video versturen bestand versturen Heb je een vraag\nover Telegram? @@ -307,6 +369,7 @@ Opslaan in muziek Delen Vertaling toepassen + Thema toepassen Bestandstype niet ondersteund Zelfvernietigingstimer instellen Servicemeldingen @@ -325,6 +388,8 @@ Spam van dit kanaal echt melden? Je kunt momenteel alleen berichten sturen aan onderlingen contacten. Je kunt momenteel alleen onderlinge contacten aan groepen toevoegen + Je hebt vandaag teveel mensen aangeschreven die niet in je contacten staan, probeer het morgen nog eens. Je kunt wel antwoorden als deze gebruiker eerst contact met jou opneemt, + Je kunt deze gebruiker niet toevoegen omdat je vandaag teveel mensen hebt aangeschreven die niet in je contacten staan, probeer het morgen nog eens. Je kunt een ander lid vragen om deze gebruiker aan de groep toe te voegen. https://telegram.org/faq#can-39t-send-messages-to-non-contacts Meer informatie Versturen naar... @@ -358,6 +423,10 @@ • Te gebruiken op al je apparaten • Snel dingen in op te zoeken Je Cloudopslag is er om: + Ga naar datum + Verwijder voor %1$s + Verwijder voor iedereen + Tekst gekopieerd naar klembord. %1$s heeft de zelfvernietigingstimer ingesteld op %2$s Je hebt de zelfvernietigingstimer ingesteld op %1$s @@ -451,11 +520,12 @@ lang geleden gezien Nieuw bericht - Bericht versturen naar + Mensen toevoegen Nadat je de groep hebt aangemaakt kun je meer gebruikers toevoegen en omzetten naar een supergroep. Groepsnaam Groepsnaam - %1$d/%2$d leden + %1$d van %2$d geselecteerd + tot aan %1$s Wil je lid worden van de groep \"%1$s\"? Sorry, deze groep is al vol. Sorry, deze groep bestaat niet. @@ -507,12 +577,16 @@ Overig Hoofd Geheime chat starten + Gedeelde groepen + Gedeelde groepen + Nog geen gedeelde groepen Er is een fout opgetreden. Encryptiesleutel Zelfvernietigingstimer Uit Deze afbeelding en tekst zijn afgeleid van de encryptiesleutel voor deze geheime chat met ]]>%1$s]]>.
          ]]>Als dit er hetzelfde uitziet op het apparaat van ]]>%2$s]]>, dan is end-to-end-encryptie gegarandeerd.
          ]]>Lees meer op telegram.org
          https://telegram.org/faq#secret-chats + Tik voor emoji-weergave Onbekend Info Telefoon @@ -525,6 +599,7 @@ Je naam mag niet langer zijn dan 32 tekens. Sorry, begincijfers zijn niet toegestaan. Je kan een gebruikersnaam kiezen voor ]]>Telegram]]>. Hiermee kunnen anderen je vinden en contact met je opnemen zonder je telefoonnummer te weten.
          ]]>Je mag ]]>a–z]]>, ]]>0–9]]> en liggend streepje gebruiken. De minimale lengte is ]]>5]]> tekens.
          + Deze link opent een chat met je in Telegram:\n%1$s Gebruikersnaam controleren. %1$s is beschikbaar. Geen @@ -561,6 +636,20 @@ Een aantal ongebruikte stickerbundels zijn gearchiveerd. Je deze opnieuw activeren via de stickersinstellingen. Gearchiveerde maskers Een aantal ongebruikte maskerbundels zijn gearchiveerd. Je kunt deze opnieuw activeren via de maskerinstellingen. + + Thema + Thema echt verwijderen? + Themabestand is ongeldig + Geef een naam aan je thema + BEWERKER SLUITEN + THEME OPSLAAN + Nieuw Thema + TOEPASSEN + Thema-voorvertoning + Selecteer kleur + Nieuw thema maken + Tik op het palet-icoon om een lijst met elementen voor elk scherm te zien en deze te bewerken. + Door het veranderen van de kleuren in de app kun je je eigen thema maken. Je kunt hier altijd terugschakelen naar het standaardthema. Meldingen gereset Tekstgrootte berichten @@ -583,6 +672,27 @@ Meldingen resetten Aangepaste meldingsinstellingen wissen voor contacten en groepen. Meldingen en geluiden + Aangepaste meldingen + Pop-up meldingen + Nieuwe berichten van deze persoon worden weergegeven als je Telegram niet gebruikt. + LED + Kleur + Blauw + Rood + Geel + Groen + Cyaan + Wit + Roze + Paars + Oranje + Sommige apparaten hebben een LED-functie, om d.m.v. licht aan te geven dat er nieuwe berichten zijn. + Meldingen met hoge prioriteit werken ook als je telefoon op \"Niet storen\" staat. + Algemeen + Aan + Uit + Inschakelen + Uitschakelen Geblokkeerd Uitloggen Geen geluid @@ -597,7 +707,7 @@ Contact lid van Telegram Vastgezette berichten Taal - De ondersteuning van Telegram wordt gedaan door vrijwilligers. We doen ons best om zo snel mogelijk te antwoorden.
          ]]>Bekijk ook de veelgestelde vragen]]>: Hier staan de antwoorden op de meeste vragen en belangrijke tips voor het oplossen van problemen]]>.
          + De ondersteuning van Telegram wordt gedaan door vrijwilligers. We doen ons best om zo snel mogelijk te antwoorden.
          ]]>Bekijk ook de veelgestelde vragen]]>: Hier staan de antwoorden op de meeste vragen en belangrijke tips voor het oplossen van problemen]]>.
          Vraag een vrijwilliger Veelgestelde vragen https://telegram.org/faq @@ -624,8 +734,6 @@ Badgenummer Kort Lang - Systeeminstelling - Standaardinstelling Automatisch media downloaden Bij mobiele verbinding Bij Wi-Fi-verbinding @@ -635,7 +743,11 @@ Houd bij oor Opslaan in galerij Naam wijzigen + Aanpassen + Aangepast + Aangepaste meldingen inschakelen Prioriteit + Systeeminstelling gebruiken Standaard Laag Hoog @@ -651,18 +763,18 @@ Overig Uitgeschakeld Uitgeschakeld + Ingeschakeld + Uitgeschakeld Uitgeschakeld Uit Chatgeluiden Standaard Standaard Slimme meldingen + %1$d / %2$s Uitgeschakeld - Geluid maximaal %1$s per %2$s - Geluid maximaal - keer - binnen - minuten + Aantal geluidsmeldingen + %1$s binnen %2$s Linkvoorvertoningen Geheime chats In-app browser @@ -670,10 +782,13 @@ Snel delen Recente chats in snel delen weergeven Emoji - Draw single big emoji - Use system default emoji + Grote emoji gebruiken + Systeem-emoji gebruiken + Telegram voor Android %1$s + Debug-menu + Contacten importeren + Contacten verversen - Cache-instellingen Lokale database Gecachet tekstberichten wissen? Het opschonen van de lokale database zal de tekst van gecachet berichten verwijderen en de database comprimeren om ruimte te besparen. Telegram heeft wat ruimte nodig om te kunnen functioneren, dus de databasegrootte zal nooit nul worden.\n\nDit kan enkele minuten duren. @@ -685,6 +800,7 @@ Spraakberichten Video\'s Muziek + GIFs Overige bestanden Leeg Media bewaren @@ -722,6 +838,8 @@ Vingerafdruk bevestigen Vingerafdruksensor Vingerafdruk niet herkend, probeer opnieuw + Schermafdruk toestaan + Met deze functie kun je in-app schermafdrukken maken, wel zijn chats dan zichtbaar in taakbeheer, ook met een ingeschakelde toegangscode.\n\nHerstart de app om deze optie actief te maken. Gedeelde bestanden Gedeelde media @@ -798,6 +916,7 @@ Onderschrift toevoegen Foto-onderschrift Video-onderschrift + GIF-Onderschrift Onderschrift Tekenen Stickers @@ -807,6 +926,9 @@ Klonen Omlijnd Normaal + Resetten + Origineel + Vierkant Twee-staps-verificatie Extra wachtwoord instellen @@ -857,6 +979,27 @@ Wachtwoord uitgeschakeld Je hebt twee-staps-verificatie ingeschakeld.\nAls je inlogt op je Telegram-account heb je het ingestelde wachtwoord nodig. Je herstel-e-mailadres %1$s is nog niet actief en in afwachting van bevestiging. + + Data en opslag + Opslag- en datagebruik + Opslaggebruik + Mobiel datagebruik + Roamen datagebruik + Wi-Fi datagebruik + Berichten en andere gegevens + Verzonden + Ontvangen + Verzonden bytes + Ontvangen bytes + Bestanden + Oproepen + Uitgaande oproepen + Inkomende oproepen + Totale oproeptijd + Totaal + Gegevens opnieuw instellen + Datagebruik sinds %1$s + Gebruiksgegevens opnieuw instellen? Privacy en veiligheid Privacy @@ -872,7 +1015,7 @@ Veiligheid Account verwijderen Indien afwezig voor - Als je binnen deze periode niet minimaal één keer ingelogd bent geweest zal je account worden verwijderd, inclusief alle data. + Als je binnen deze periode niet minimaal één keer ingelogd bent geweest zal je account worden verwijderd, inclusief alle groepen, berichten en contacten. Je account verwijderen? Wijzig wie je laatst gezien tijd kan zien. Wie kan mijn laatst gezien tijd zien? @@ -891,7 +1034,7 @@ Gebruiker vasthouden om te verwijderen. Groepen Wie kan me toevoegen aan groepchats? - Je kunt met precisie bepalen wie je aan groepen en kanalen mag toevoegen. + Je kunt nauwkeurig bepalen wie je aan groepen en kanalen mag toevoegen. Altijd toestaan Nooit toestaan Altijd toestaan... @@ -904,7 +1047,7 @@ Video bewerken Video versturen - Gif versturen... + GIF versturen... bot Delen @@ -941,6 +1084,8 @@ Stel in OK BIJSNIJDEN + Ja + Nee Je bent nu lid van de groep via uitnodigingslink un1 is nu lid van de groep via uitnodigingslink @@ -987,6 +1132,7 @@ Te veel pogingen. Probeer het later opnieuw. Te veel pogingen, probeer het over %1$s opnieuw Ongeldige code + Je hebt in korte tijd je account veelvuldig verwijderd en opnieuw aangemaakt. Probeer het over een aantal dagen nog eens. Ongeldige voornaam Ongeldige achternaam Bezig met laden @@ -1038,16 +1184,20 @@ Je mag dit bericht niet wijzigen. Sta het ontvangen van SMS toe zodat we automatisch je inlogcode kunnen invoeren. Sta het ontvangen van oproepen toe zodat we automatisch je inlogcode kunnen invoeren. + Sta het ontvangen van oproepen en SMS toe zodat we je nummer kunnen invullen en automatisch je inlogcode kunnen invoeren. Sta het ontvangen van oproepen en SMS toe zodat we automatisch je inlogcode kunnen invoeren. Je mag deze actie niet uitvoeren. + Je hebt deze gebruiker of bot geblokkeerd, toevoegen aan een groep kan pas na deblokkeren. LID WORDEN Telegram heeft toegang tot je contacten nodig zodat je kan chatten met je vrienden vanaf al je apparaten. Telegram heeft toegang tot je opslaggeheugen nodig zodat je foto\'s, video\'s, muziek en andere media kunt opslaan en versturen. Telegram heeft toegang tot je microfoon nodig om spraakberichten te kunnen verzenden. + Telegram heeft toegang tot je microfoon nodig om video op te nemen. Telegram heeft toegang tot je camera nodig zodat je foto\'s en video\'s kunt maken. Telegram heeft toegang tot je locatie nodig om deze te kunnen delen met je vrienden. Telegram heeft toegang tot je locatie nodig. + Telegram heeft de toestemming \'over andere apps tekenen\' nodig om video\'s af te spelen in Picture-in-Picture-modus. INSTELLINGEN Telegram @@ -1065,6 +1215,66 @@ Telegram]]> biedt toegang tot je berichten]]>vanaf meerdere apparaten. Telegram]]> berichten zijn sterk versleuteld]]>en kunnen zichzelf vernietigen. Begin met chatten + + Accountinstellingen + Minder data voor oproepen gebruiken + Inkomende oproep + Verbinden + Encryptiesleutels uitwisselen + Wachten + Aanvragen + Ophangen + Oproep beëindigd + Verbinden mislukt + Gaat over + In gesprek + Telegram-oproep + Telegram-oproep bezig + Oproep beëindigen + Er is al een oproep actief + Er is al een oproep actief met %1$s]]>. Wil je deze oproep beëindigen en een nieuwe oproep starten met %2$s]]>? + Spraakoproepen + Beltoon + Je kunt een aangepaste beltoon instellen voor Telegram-oproepen van dit contact. + Oproepen + Wie kan mij bellen? + Je kunt inperken wie jou mag bellen. + Deze gebruikers altijd toestaan of verbieden om je te bellen. + Nooit + Alleen bij mobiele verbinding + Altijd + Opnemen + Weigeren + Je bent offline, maak verbinding met het internet om een oproep te starten. + Vliegtuigmodus is actief, schakel vliegtuigmodus uit of verbind met Wi-Fi om een oproep te starten + Offline + Vliegtuigmodus + Instellingen + Uitgaande oproep + Inkomende oproep + Gemiste oproep + Oproep geannuleerd + Geweigerde oproep + %1$s (%2$s) + Deze afbeelding en tekst zijn afgeleid van de encryptiesleutel voor deze oproep met ]]>%1$s]]>.
          ]]>Als dit er hetzelfde uitziet op het apparaat van ]]>%2$s]]>, dan is end-to-end-encryptie gegarandeerd.
          + Je hebt nog geen oproepen + ]]>%1$s]]> maakt gebruik van een niet-compatibel protocol voor spraakoproepen en moet eerst een update uitvoeren. + ]]>%1$s]]> heeft nog geen ondersteuning voor spraakoproepen en zal eerst een update moeten uitvoeren. + Beoordeel de kwaliteit van je Telegram-oproep. + Wil je feedback geven zodat we onze oproepfunctie kunnen verbeteren? + Telegram heeft toegang tot je microfoon nodig zodat je kunt bellen. + Opmerking toevoegen + Terugbellen + Herhaal + Standaard + Gesprek echt uit oproepgeschiedenis wissen? + Telegram-oproep + Oorstuk + Luidspreker + Bluetooth + TERUG NAAR GESPREK + Sorry, ]]>%1$s]]> neemt geen oproepen aan. + Als deze emoji\'s er bij %1$s hetzelfde uitzien is deze oproep 100%% veilig. %1$d online %1$d online @@ -1284,6 +1494,8 @@ en %1$d anderen en %1$d anderen + dd MMM yyyy, h:mm a + dd MMM yyyy, HH:mm MMMM yyyy dd MMM dd-MM-yy diff --git a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml index 30cf235c61a..44f02ce2a51 100644 --- a/TMessagesProj/src/main/res/values-pt-rBR/strings.xml +++ b/TMessagesProj/src/main/res/values-pt-rBR/strings.xml @@ -41,7 +41,56 @@ Nome (obrigatório) Sobrenome (opcional) Cancelar registro + + Você transferiu com sucesso %1$s para %2$s por %3$s + Você transferiu com sucesso %1$s para %2$s + Finalizar + Métodos de envio + Desculpe, não é possível entregar neste endereço. + Informação de envio + Endereço de envio + Endereço 1 (Rua) + Endereço 2 (Rua) + Cidade + Estado + País + Código Postal + Destinatário + Nome Completo + Número de Telefone + E-Mail + Salvar Informação de Envio + Você pode salvar sua informação de envio para uso futuro. + Informação de pagamento + Cartão de pagamento + Número do Cartão + Código de Segurança (CVV) + MM/YY + Endereço de cobrança + Titular do cartão + Nome Sobrenome + Salvar Informação de Pagamento + Você pode salvar sua informação de pagamento para uso futuro. + Por favor, *ative a Verificação em Duas Etapas* para habilitar isso. + Revisão da Transação + Você realmente deseja transferir %1$s para o bot %2$s por %3$s? + Total + FATURA + FATURA DE TESTES + PAGAR %1$s + Método de pagamento + Nome + Número de telefone + Endereço de contato + Método de envio + Recibo + Escolher um cartão diferente + Seu cartão %1$s está no arquivo. Para pagar com este cartão, insira sua senha da Verificação em Duas Etapas. + Desculpe, o pagamento foi cancelado pelo bot. + Desculpe, o pagamento foi recusado. + Não foi possível encontrar o servidor de pagamento. Verifique sua conexão e tente novamente. + Nova conversa Configurações Contatos Novo Grupo @@ -80,6 +129,16 @@ Prévia do link Rascunho O histórico foi apagado + por %1$s + %1$s por %2$s + Deixar um comentário sobre essa pré-visualização + Enviar Sticker + Ver Pacote + Fixar no topo + Desafixar do topo + Negrito + Itálico + Regular Tipo de Grupo Tipo de canal @@ -110,7 +169,7 @@ un1 fixou um sticker un1 fixou uma mensagem de voz un1 fixou um contato - un1 fixou um %1$s + un1 fixou %1$s un1 fixou um mapa un1 fixou um GIF un1 fixou uma música @@ -267,11 +326,14 @@ %1$s está gravando uma mensagem de voz... %1$s está enviando um áudio... %1$s está enviando uma foto... + LEITURA RÁPIDA + %1$s está jogando... %1$s está enviando um vídeo... %1$s está enviando um arquivo... gravando mensagem de voz... enviando áudio... enviando foto... + jogando... enviando vídeo... enviando arquivo... Tem alguma dúvida\nsobre o Telegram? @@ -307,6 +369,7 @@ Salvar em músicas Compartilhar Aplicar arquivo de localização + Aplicar tema Anexo não suportado Definir timer de autodestruição Notificações de serviço @@ -323,8 +386,10 @@ Você tem certeza que deseja reportar esse usuário por spam? Você tem certeza que deseja reportar esse grupo por spam? Você tem certeza que deseja reportar esse canal por spam? - Desculpe, você pode enviar mensagens somente para contatos mútuos no momento. + Desculpe, você só pode enviar mensagens para contatos mútuos no momento. Desculpe, você só pode adicionar contatos mútuos à grupos no momento. + Você contatou muitos não-contatos hoje, tente novamente amanhã. Você poderá responder hoje se esse usuário te enviar uma mensagem primeiro. + Você não pode adicionar esse usuário porque você contatou muitos não-contatos hoje. Tente novamente amanhã. Você pode pedir para outro membro adicionar este usuário ao grupo. https://telegram.org/faq/br#no-consigo-enviar-mensagens-para-no-contatos Mais informações Enviar para... @@ -358,6 +423,10 @@ Acesse esse chat de qualquer dispositivo Use a busca para encontrar suas coisas Sua nuvem de armazenamento + Ir para Data + Apagar para %1$s + Apagar para todos os membros + Texto copiado para área de transferência %1$s estabeleceu o tempo de autodestruição para %2$s Você estabeleceu o tempo de autodestruição para %1$s @@ -451,11 +520,12 @@ visto há muito tempo Nova Mensagem - Enviar mensagem para... + Adicionar pessoas... Você poderá adicionar mais usuários após finalizar a criação do grupo e convertê-lo em um supergrupo. Digite o nome do grupo Nome do grupo - %1$d/%2$d membros + %1$d de %2$d selecionados + até %1$s Você deseja entrar no chat \'%1$s\'? Desculpe, este grupo já está lotado. Desculpe, esse chat não existe. @@ -507,12 +577,16 @@ Outro Principal Iniciar Chat Secreto + Grupos em Comum + Grupos em Comum + Nenhum grupo em comum Ocorreu um erro. Chave criptográfica Tempo de autodestruição Desativado Essa imagem e texto foram derivadas da chave criptográfica para este chat secreto com ]]>%1$s]]>.
          ]]>Se você vê o mesmo no dispositivo de ]]>%2$s\'s]]>, a criptografia ponta a ponta está garantida.
          ]]>Leia mais em telegram.org
          https://telegram.org/faq/br#chats-secretos + Toque para emojizar Desconhecido Info Telefone @@ -524,7 +598,8 @@ O nome de usuário deve ter pelo menos 5 caracteres. O nome de usuário não pode exceder 32 caracteres. Desculpe, o nome de usuário não pode começar com um número. - Você pode escolher um nome de usuário no ]]>Telegram]]>. Assim, outras pessoas poderão te encontrar pelo nome de usuário e entrar em contato sem precisar saber seu telefone.
          ]]>Você pode usar ]]>a–z]]>, ]]>0–9]]> e underline. O tamanho mínimo é ]]>5]]> caracteres.
          + Você pode escolher um nome de usuário no ]]>Telegram]]>. Assim, outras pessoas poderão te encontrar pelo nome de usuário e entrar em contato sem precisar saber seu telefone.
          ]]>Você pode usar ]]>a–z]]>, ]]>0–9]]> e underline. O tamanho mínimo é de ]]>5]]> caracteres.
          + Esse link abre um chat com você mesmo no Telegram:\n%1$s Verificando nome de usuário... %1$s está disponível. Nenhum @@ -561,6 +636,20 @@ Alguns de seus pacotes de sticker antigos foram arquivados. Você pode reativá-los nas Configurações de Sticker. Máscaras arquivadas Alguns de seus pacotes de máscaras antigos foram arquivados. Você pode reativá-los nas Configurações de Máscaras. + + Tema + Você tem certeza que deseja apagar esse tema? + Arquivo de tema incorreto + Insira o nome do tema + FECHAR EDITOR + SALVAR TEMA + Novo Tema + APLICAR + Preview do Tema + Selecionar cor + Criar Novo Tema + Toque no ícone da paleta para visualizar a lista de elementos de cada tela - e editá-los. + Você pode criar seu próprio tema alterando as cores do app. Você pode voltar ao tema padrão do Telegram aqui. Restaurar todas as configurações de notificação Tamanho do Texto @@ -577,12 +666,33 @@ Notificações no aplicativo Sons no Aplicativo Vibração no Aplicativo - Vibrar + Vibração Visualização no Aplicativo Limpar Restaurar Configurações Desfazer todas as configurações de notificação para todos os seus contatos e grupos Notificações e Sons + Notificações Personalizadas + Notificações popup + Novas mensagens desse contato aparecerão em sua tela quando você não estiver no Telegram. + LED + Cor + Azul + Vermelho + Amarelo + Verde + Ciano + Branco + Rosa + Violeta + Laranja + O LED é uma pequena luz piscante em alguns dispositivos, utilizada para indicar novas mensagens. + Nas prioridades mais altas as notificações irão funcionar mesmo que o seu celular esteja no modo Não Perturbe. + Geral + Ligado + Desligado + Ativar + Desativar Usuários Bloqueados Sair Sem som @@ -597,7 +707,7 @@ Contato entrou para o Telegram Mensagens Fixadas Idioma - Por favor entenda que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco.
          ]]>Por favor verifique a página de perguntas frequentes do Telegram]]>: há dicas e respostas para a maioria dos problemas]]>.
          + Por favor entenda que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco.
          ]]>Por favor verifique a página de perguntas frequentes do Telegram]]>: há dicas e respostas para a maioria dos problemas]]>.
          Pergunte a um voluntário Perguntas Frequentes https://telegram.org/faq/br @@ -624,8 +734,6 @@ Contador no Ícone Curta Longa - Padrão do sistema - Configurações padrão Download automático de mídia Ao usar dados móveis Quando conectado ao Wi-Fi @@ -635,7 +743,11 @@ Levantar para Falar Salvar na galeria Editar nome + Personalizar + Personalizado + Ativar Notificações Personalizadas Prioridade + Assim como em Configurações Padrão Baixa Alta @@ -651,18 +763,18 @@ Outro Desativado Desativado + Ativado + Desativado Desativado Desativado Sons no Chat Padrão Padrão Notificações Inteligentes + %1$d / %2$s Desativado - Tocar no máximo %1$s a cada %2$s - Tocar no máximo - vezes - a cada - minutos + Frequência do Alerta de Som + %1$s em %2$s Pré-visualização de Link Chats secretos Navegador Interno @@ -670,10 +782,13 @@ Compartilhamento Direto Mostrar chats recentes no menu compartilhar Emoji - Draw single big emoji - Use system default emoji + Desenhe um único emoji grande + Usar emoji padrão do sistema + Telegram para Android %1$s + Debug Menu + Importar Contatos + Recarregar Contatos - Configurações de Cache Banco de Dados Local Limpar todos os textos em cache? Limpar o banco de dados local apagará todos os textos das mensagens em cache e compactará o banco de dados para economizar espaço. O Telegram precisa de alguns dados para trabalhar, então o tamanho do banco não vai chegar a zero.\n\nEssa operação pode demorar alguns minutos para ser concluída. @@ -685,6 +800,7 @@ Mensagens de voz Vídeos Música + GIFs Outros arquivos Vazio Manter Mídias @@ -722,6 +838,8 @@ Confirme a impressão digital para continuar Toque o sensor Impressão digital não reconhecida. + Permitir Captura de Tela + Se ativado, você pode tirar capturas de tela do app, mas o sistema irá mostrar suas conversas ao alternar entre janelas mesmo que a senha no app esteja habilitada.\n\nTalvez você precise reiniciar o app para que isso tenha efeito. Arquivos Compartilhados Mídia Compartilhada @@ -798,6 +916,7 @@ Adicionar legenda... Legenda da Foto Legenda do Vídeo + Legenda do GIF Legenda Desenhar Stickers @@ -807,6 +926,9 @@ Duplicar Delineado Regular + Reiniciar + Original + Quadrado Verificação em Duas Etapas Configurar senha adicional @@ -857,6 +979,27 @@ Senha desativada Você habilitou a verificação em duas etapas. Toda vez que você entrar na sua conta em um novo aparelho, será preciso digitar a senha que você configurar aqui. O seu e-mail de recuperação %1$s ainda não está ativo e aguarda confirmação. + + Dados e Armazenamento + Uso do disco e da rede + Uso do Armazenamento + Uso de Dados Móveis + Uso de Dados em Roaming + Uso de Dados no Wi-Fi + Mensagens e outros dados + Enviados + Recebidos + Bytes enviados + Bytes recebidos + Arquivos + Chamadas + Chamadas Efetuadas + Chamadas Recebidas + Tempo total + Total + Reiniciar Estatísticas + Uso da rede desde %1$s + Você deseja reiniciar suas estatísticas de uso? Privacidade e Segurança Privacidade @@ -872,7 +1015,7 @@ Segurança Auto-destruição da conta Se você estiver inativo por - Se você não acessar sua conta ao menos uma vez neste período, sua conta será excluída com seus grupos, mensagens e contatos. + Se você não ficar online ao menos uma vez nesse período, sua conta será apagada junto com seus grupos, mensagens e contatos. Excluir sua conta? Alterar quem pode ver o seu Último Acesso. Quem pode ver o seu Último Acesso? @@ -904,7 +1047,7 @@ Editar Vídeo Enviando vídeo... - Enviando gif... + Enviando GIF... bot Compartilhar @@ -941,6 +1084,8 @@ Aplicar OK CORTAR + Sim + Não Você entrou para o grupo via link de convite un1 entrou para o grupo via link de convite @@ -987,6 +1132,7 @@ Muitas tentativas. Por favor, tente novamente mais tarde. Muitas tentativas, por favor tente novamente em %1$s Código inválido + Desculpe, você apagou e recriou sua conta muitas vezes recentemente. Aguarde alguns dias antes de tentar novamente. Nome inválido Sobrenome inválido Carregando... @@ -1038,16 +1184,20 @@ Desculpe, você não pode editar essa mensagem. Permita o acesso às SMS ao Telegram, assim podemos automaticamente adicionar o código para você. Permita o acesso às ligações ao Telegram, assim podemos automaticamente adicionar o código para você. + Permita o acesso às ligações e SMS ao Telegram, assim podemos automaticamente adicionar o seu número e te enviar o código. Permita o acesso às SMS e ligações ao Telegram, assim podemos automaticamente adicionar o código para você. Você não tem permissão para isso. + Desculpe, você não pode adicionar esse usuário ou bot à grupos por tê-lo bloqueado. Desbloqueie para prosseguir. ENTRAR NO GRUPO Telegram precisa acessar seus contatos para que você possa se conectar aos seus amigos em todos os seus dispositivos. Telegram precisa acessar seu armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. Telegram precisa acessar seu microfone para que você possa enviar mensagens de voz. + Telegram precisa acessar seu microfone para que você possa gravar vídeos. Telegram precisa acessar sua câmera para que você possa capturar fotos e vídeos. Telegram precisa acessar sua localização para que você possa compartilhar com seus amigos. O Telegram precisa acessar sua localização + Telegram precisa de acesso para sobrescrever outros apps para reproduzir vídeos no modo PiP. CONFIGURAÇÕES Telegram @@ -1065,6 +1215,66 @@ O Telegram]]> permite você acessar suas]]> mensagens de múltiplos dispositivos. O Telegram]]> possui mensagens fortemente]]>encriptadas e podem se auto-destruir. Comece a conversar + + Configurações de Conta + Usar menos dados para chamadas + Recebendo chamada + Conectando + Trocando chaves criptográficas + Aguardando + Solicitando + Desligando + Chamada encerrada + Falha ao conectar + Tocando + Linha ocupada + Chamada do Telegram + Realizando chamada do Telegram + Encerrar chamada + Outra chamada em andamento + Você está em uma chamada com %1$s]]>. Gostaria de encerrar e iniciar uma nova chamada com %2$s]]>? + Chamadas de voz + Toque + Você pode personalizar o toque usado quando esse contato te chamar no Telegram + Chamadas + Quem pode me ligar? + Você pode restringir quem pode te ligar. + Esses usuários poderão ou não te ligar dependendo de suas configurações. + Nunca + Somente em redes móveis + Sempre + Atender + Rejeitar + Você está offline. Conecte à internet para poder realizar chamadas. + Você está com o modo avião habilitado. Desligue-o ou se conecte ao Wi-Fi para realizar chamadas. + Desconectado + Modo Avião + Configurações + Chamada Efetuada + Chamada Recebida + Chamada Perdida + Chamada Cancelada + Chamada Rejeitada + %1$s (%2$s) + Essa imagem e texto foram derivadas da chave criptográfica para essa chamada com ]]>%1$s]]>.
          ]]>Se você vê o mesmo no dispositivo de ]]>%2$s]]>, a criptografia ponta a ponta está garantida.
          + Você ainda não realizou chamadas. + O aplicativo de ]]>%1$s]]> está usando um protocolo incompatível. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. + O aplicativo de ]]>%1$s]]> não suporta chamadas. O aplicativo desse usuário precisa ser atualizado para que você possa chamá-lo. + Avalie a qualidade de sua chamada do Telegram + Você gostaria de deixar algum feedback para nos ajudar a melhorar o serviço de chamadas? + O Telegram precisa acessar seu microfone para poder fazer chamadas. + Adicionar um comentário opcional + Ligar de Volta + Ligar Novamente + Padrão + Você tem certeza que deseja apagar essa entrada do registro de chamadas? + Chamada do Telegram + Auricular + Alto-falante + Bluetooth + RETORNAR À LIGAÇÃO + Desculpe, ]]>%1$s]]> não aceita chamadas. + Se os emojis na tela de %1$s são os mesmos, essa chamada é 100%% segura. %1$d online %1$d online @@ -1284,6 +1494,8 @@ e %1$d outros e %1$d outros + dd MMM yyyy, h:mm a + dd MMM yyyy, h:mm a MMMM yyyy dd MMM dd.MM.yy diff --git a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml b/TMessagesProj/src/main/res/values-pt-rPT/strings.xml deleted file mode 100644 index e64b4eb7646..00000000000 --- a/TMessagesProj/src/main/res/values-pt-rPT/strings.xml +++ /dev/null @@ -1,1297 +0,0 @@ - - - - - - Telegram - Telegram Beta - Português (Brasil) - Português (Brasil) - pt_BR - - Seu número - Confirme o código de seu país e preencha seu número de telefone. - Escolha um país - Código do país incorreto - - Verificação do tel. - Enviamos um SMS com um código de ativação para ]]>%1$s]]>. - Nós enviamos o código para o aplicativo do ]]>Telegram]]> em seu outro dispositivo. - Enviamos uma ligação de ativação para ]]>%1$s]]>.\n\nNão atenda, o Telegram processará automaticamente. - Nós te ligaremos em ]]>%1$s]]> para ditar o código. - Vamos te ligar em %1$d:%2$02d - Vamos te enviar uma SMS em %1$d:%2$02d - Estamos te ligando... - Código - Número incorreto? - Não recebeu o código? - Cancelar exclusão da conta - Alguém com acesso ao seu número de telefone ]]>%1$s]]> solicitou a exclusão de sua conta do Telegram e redefiniu sua senha de Verificação em Duas Etapas.\n\nSe não foi você, por favor insira o código que te enviamos via SMS. - Apagar conta - Uma vez que a conta ]]>%1$s]]> está ativa e protegida por senha, nós iremos desativá-la em 1 semana, por questões de segurança.\n\nVocê pode cancelar esse processo a qualquer momento. - Você poderá restaurar sua conta em: - Suas tentativas recentes de restaurar essa conta foram canceladas pelo usuário ativo. Tente novamente em 7 dias. - APAGAR - O link expirou ou está inválido. - O processo de exclusão foi cancelado em sua conta %1$s. Você pode fechar essa janela agora. - - Seu nome - Configure seu nome e sobrenome - - Nome (obrigatório) - Sobrenome (opcional) - Cancelar registro - - Configurações - Contatos - Novo Grupo - ontem - Nenhum resultado - Ainda não há chats... - Comece a conversar pressionando o\nbotão \'Nova Mensagem\' no canto inferior direito\nou vá para a seção \'Contatos\'. - Aguardando rede... - Conectando... - Atualizando... - Novo Chat Secreto - Esperando %s se conectar... - Chat secreto cancelado - Trocando chaves de criptografia... - %s entrou no chat secreto - Você entrou no chat secreto - Limpar histórico - Apagar do cache - Apagar e sair - Apagar conversa - Conta Excluída - Selecione um Chat - Toque e segure para ver - %1$s está usando uma versão mais antiga do Telegram, por isso fotos secretas serão mostradas em modo de compatibilidade.\n\nAssim que %2$s atualizar o Telegram, fotos com timers de 1 minuto ou menos passarão a funcionar no modo ‘Toque e segure para ver’, e você será notificado caso a outra pessoa salve a tela. - MENSAGENS - Busca - Silenciar notificações - Silenciar por %1$s - Restaurar Som - Em %1$s - Desativar - HASHTAGS - RECENTE - CONVERSAS - Apagar %1$s das sugestões? - Prévia do link - Rascunho - O histórico foi apagado - - Tipo de Grupo - Tipo de canal - Público - Privado - Promover a administrador - Você pode fornecer uma descrição opcional para seu grupo. - Sair do Grupo - Apagar Grupo - Sair do Grupo - Apagar Grupo - Você perderá todas as mensagens neste grupo. - Você pode adicionar administradores para ajudar você a gerenciar seu grupo. Toque e segure para removê-los. - Espere! Apagar este grupo removerá todos os membros e todas as mensagens serão perdidas. Apagar o grupo mesmo assim? - Grupo criado - un1 adicionou você ao grupo - Você tem certeza que deseja sair do grupo? - Desculpe, você não pode adicionar este usuário a grupos. - Desculpe, este grupo está cheio. - Desculpe, este usuário decidiu sair deste grupo, de maneira que você não pode convidá-lo de volta. - Desculpe, há administradores demais neste grupo. - Desculpe, há bots demais neste grupo. - un1 fixou \"%1$s\" - un1 fixou uma mensagem - un1 fixou uma foto - un1 fixou um vídeo - un1 fixou um arquivo - un1 fixou um sticker - un1 fixou uma mensagem de voz - un1 fixou um contato - un1 fixou um %1$s - un1 fixou um mapa - un1 fixou um GIF - un1 fixou uma música - Esse grupo foi convertido para um supergrupo - %1$s foi convertido a um supergrupo - Usuários bloqueados são removidos do grupo e só podem voltar se convidados por um administrador. Convites por link não funcionam para eles. - Novo Canal - Nome do canal - Adicionar contatos no canal - Pessoas podem compartilhar esse link com outros e encontrar seu canal usando a busca do Telegram. - Pessoas podem compartilhar esse link com outros e encontrar o seu grupo usando a busca do Telegram. - link - Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. - Pessoas podem entrar em seu canal com este link. Você pode desativar o link quando quiser. - Descrição (opcional) - Descrição - Você pode providenciar uma descrição opcional para o seu canal. - Canal Público - Grupo Público - Canais públicos podem ser encontrados na busca, qualquer um pode entrar. - Grupos públicos podem ser encontrados na busca, o histórico é disponível para todos e qualquer um pode entrar. - Canal Privado - Grupo Privado - Canais privados só podem ser aderidos através de um link de convite. - Grupos privados só podem ser aderidos através de um link de convite. - Link - Link de Convite - Adicionar membros - Sair do Canal - Sair do Canal - Configurações - ENTRAR - Transmissão - Mensagem Silenciosa - O que é um Canal? - Canais são uma nova ferramenta para transmissão de suas mensagens para grandes audiências. - CRIAR CANAL - Desculpe, esse nome já está em uso. - Desculpe, esse nome é inválido. - Nome do canal deve ter pelo menos 5 caracteres. - O nome não pode exceder 32 caracteres. - Nome do canal não pode iniciar com número. - Nome do grupo deve ter pelo menos 5 caracteres. - Nome do grupo não pode iniciar com número. - Verificando nome... - %1$s está disponível. - Membros - Usuários bloqueados - Administradores - Apagar Canal - Apagar Canal - Espere! Apagando esse canal removerá todos os membros e todas as mensagens serão perdidas. Apagar assim mesmo? - Você tem certeza que deseja sair do canal? - Você perderá todas as mensagens desse canal. - Editar - Por favor, note que ao escolher um link público para o seu grupo, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu supergrupo seja privado. - Por favor, note que ao escolher um link público para o seu canal, qualquer um poderá encontrá-lo na busca e entrar.\n\nNão crie um link se você deseja que seu canal seja privado. - Por favor, escolha um link para o seu canal público, assim as pessoas poderão encontrá-lo na busca e compartilhar com outros.\n\nSe não estiver interessado, sugerimos que crie um canal privado. - Canal criado - Foto do canal alterada - Foto do canal removida - Nome do canal alterado para un2 - Desculpe, você reservou muitos nomes públicos. Você pode remover o link público de um de seus grupos ou canais, ou criar de forma privada. - Moderador - Criador - Administrador - SILENCIAR - RESTAURAR SOM - Adicionar Administrador - Convidar via Link - Você tem certeza que deseja colocar %1$s como adiministrador? - Remover - Somente os administradores do canal podem ver essa lista. - Esse usuário não entrou no seu canal ainda. Você deseja enviar um convite? - Qualquer um com Telegram instalado poderá entrar no seu canal abrindo este link. - Você pode adicionar administradores para ajudar você a gerenciar seu canal. Aperte e segure para removê-los. - Você deseja entrar no canal \'%1$s\'? - Desculpe, esta conversa não pode mais ser acessada. - Infelizmente você foi banido de participar de grupos públicos. - Desculpe, esse chat não está mais acessível. - Adicionar %1$s ao canal? - Desculpe, este usuário decidiu sair deste canal, então você não pode convidá-lo de volta. - Desculpe, você não pode adicionar esse usuário em canais. - Desculpe, muitos administradores nesse canal. - Desculpe, há bots demais neste canal. - Desculpe, você só pode adicionar os primeiros 200 membros ao canal. Note que um número ilimitado de pessoas podem entrar via link do canal. - un1 adicionou você ao canal - Você entrou nesse canal - Você entrou nesse grupo - Remover do canal - Desculp, você não pode enviar mensagens para esse canal. - %1$s adicionou você ao canal %2$s - Canal %1$s atualizou a foto - %1$s enviou uma mensagem ao canal %2$s - %1$s enviou uma foto para o canal %2$s - %1$s enviou um vídeo ao canal %2$s - %1$s enviou um contato ao canal %2$s - %1$s enviou uma localização ao canal %2$s - %1$s enviou um arquivo ao canal %2$s - %1$s enviou um GIF ao canal %2$s - %1$s enviou uma mensagem ao canal %2$s - %1$s enviou uma música ao canal %2$s - %1$s enviou um sticker ao canal %2$s - %1$s enviou um %3$s sticker ao canal %2$s - %1$s postou uma mensagem - %1$s postou uma foto - %1$s postou um vídeo - %1$s postou um contato - %1$s postou uma foto - %1$s postou um arquivo - %1$s postou um GIF - %1$s postou uma mensagem de voz - %1$s postou uma música - %1$s postou um sticker - %1$s postou um %2$s sticker - Quem pode adicionar novos membros? - Todos os Membros - Somente Administradores - Os membros serão notificados quando você postar - Os membros não serão notificados quando você postar - Assinar Mensagens - Adicionar nomes dos administradores nas mensagens postadas. - - Nova Lista de Transmissão - Digite o nome da lista - Você criou uma lista de transmissão - Adicionar destinatário - Remover da lista de transmissão - - Por favor, adicione arquivos à biblioteca de música de seu dispositivo para vê-los aqui. - Música - Artista desconhecido - Título desconhecido - - Selecione um Arquivo - Disponível %1$s de %2$s - Erro desconhecido - Erro de acesso - Ainda não há arquivos - Tamanho do arquivo não deve ser maior que %1$s - Armazenamento não está montado - Transferência USB ativa - Armazenamento Interno - Armazenamento Externo - Administrador do Sistema - Cartão SD - Pasta - Para enviar imagens sem compressão - - invisível - escrevendo... - está escrevendo... - estão escrevendo... - %1$s está gravando uma mensagem de voz... - %1$s está enviando um áudio... - %1$s está enviando uma foto... - %1$s está enviando um vídeo... - %1$s está enviando um arquivo... - gravando mensagem de voz... - enviando áudio... - enviando foto... - enviando vídeo... - enviando arquivo... - Tem alguma dúvida\nsobre o Telegram? - Tirar foto - Galeria - Localização - Vídeo - Arquivo - Câmera - Ainda não há mensagens aqui... - Mensagem encaminhada - De - Nada recente - Mensagem - Mensagem - Compartilhar meu contato - Adicionar aos contatos - %s convidou você para um chat secreto - Você convidou %s para um chat secreto. - Chats Secretos: - Criptografia de ponta-a-ponta - Sem rastros nos servidores - Timer de autodestruição - Encaminhamento desativado - Você foi removido deste grupo - Você saiu deste grupo - Apagar este grupo - Apagar este chat - DESLIZE PARA CANCELAR - Salvar em downloads - Salvar em GIFs - Apagar GIF? - Salvar em músicas - Compartilhar - Aplicar arquivo de localização - Anexo não suportado - Definir timer de autodestruição - Notificações de serviço - Obtendo informações... - ABRIR EM... - Abrir em... - Copiar URL - Enviar %1$s - Abrir URL em %1$s? - Permitir ao %1$s passar seu nome do Telegram e id (não o seu número de telefone) para páginas que você abrir com esse bot? - REPORTAR SPAM - REPORTAR SPAM E SAIR - ADICIONAR CONTATO - Você tem certeza que deseja reportar esse usuário por spam? - Você tem certeza que deseja reportar esse grupo por spam? - Você tem certeza que deseja reportar esse canal por spam? - Desculpe, você pode enviar mensagens somente para contatos mútuos no momento. - Desculpe, você só pode adicionar contatos mútuos à grupos no momento. - https://telegram.org/faq/br#no-consigo-enviar-mensagens-para-no-contatos - Mais informações - Enviar para... - Toque aqui para acessar os GIFs salvos - Fixar - Notificar todos os membros - Desafixar - Você deseja fixar essa mensagem no grupo? - Você deseja desafixar essa mensagem? - Banir usuário - Reportar spam - Apagar tudo de %1$s - Limpar emojis recentes? - Reportar - Spam - Violência - Pornografia - Outro - Descrição - Mensagem Fixada - editado - Desça para ver os bots - %1$s - Desculpe, o tempo para editar expirou. - Adicionar atalho - Atalho adicionado à tela de início - chat consigo mesmo - Você - Encaminhe mensagens aqui para salvá-las - Envie mídias e arquivos aqui para salvá-los - Acesse esse chat de qualquer dispositivo - Use a busca para encontrar suas coisas - Sua nuvem de armazenamento - - %1$s estabeleceu o tempo de autodestruição para %2$s - Você estabeleceu o tempo de autodestruição para %1$s - %1$s desativou o temporizador de autodestruição - Você desativou o temporizador de autodestruição - Você tem uma nova mensagem - %1$s: %2$s - %1$s te enviou uma mensagem - %1$s te enviou uma foto - %1$s te enviou um vídeo - %1$s compartilhou um contato com você - %1$s enviou uma localização - %1$s te convidou para jogar %2$s - %1$s lhe enviou um arquivo - %1$s te enviou um GIF - %1$s enviou uma mensagem de voz - %1$s enviou uma música - %1$s lhe enviou um sticker - %1$s lhe enviou um %2$s sticker - %1$s @ %2$s: %3$s - %1$s enviou uma mensagem para o grupo %2$s - %1$s enviou uma foto para o grupo %2$s - %1$s enviou um vídeo para o grupo %2$s - %1$s compartilhou um contato para o grupo %2$s - %1$s enviou uma localização para o grupo %2$s - %1$s convidou o grupo %2$s para jogar %3$s - %1$s enviou um arquivo para o grupo %2$s - %1$s enviou um GIF para o grupo %2$s - %1$s enviou uma mensagem para o grupo %2$s - %1$s enviou uma música ao grupo %2$s - %1$s enviou um sticker ao grupo %2$s - %1$s enviou um %3$s sticker ao grupo %2$s - %1$s convidou você para o grupo %2$s - %1$s editou o nome do grupo %2$s - %1$s editou a foto do grupo %2$s - %1$s convidou %3$s para o grupo %2$s - %1$s retornou ao grupo %2$s - %1$s entrou no grupo %2$s - %1$s removeu %3$s do grupo %2$s - %1$s removeu você do grupo %2$s - %1$s saiu do grupo %2$s - %1$s entrou para o Telegram! - %1$s,\nNós detectamos que alguém acessou a sua conta a partir de um novo aparelho em %2$s\n\nAparelho: %3$s\nLocalização: %4$s\n\nSe não foi você, você pode ir em Configurações - Privacidade e Segurança - Sessões, e terminar aquela sessão.\n\nSe você acha que alguém acessou a sua conta contra a sua vontade, você pode habilitar a verificação em duas etapas nas configurações de Privacidade e Segurança.\n\nAtenciosamente,\nEquipe Telegram - %1$s atualizou a foto do perfil - %1$s entrou para o grupo %2$s via link de convite - Responder - Responder para %1$s - Responder para %1$s - %1$s %2$s - %1$s fixou \"%2$s\" no grupo %3$s - %1$s fixou uma mensagem no grupo %2$s - %1$s fixou uma foto no grupo %2$s - %1$s fixou um jogo no grupo %2$s - %1$s fixou um vídeo no grupo %2$s - %1$s fixou um arquivo no grupo %2$s - %1$s fixou um sticker no grupo %2$s - %1$s fixou um %3$s sticker no grupo %2$s - %1$s fixou uma mensagem de voz no grupo %2$s - %1$s fixou um contato no grupo %2$s - %1$s fixou um mapa no grupo %2$s - %1$s fixou um GIF no grupo %2$s - %1$s fixou uma música no grupo %2$s - %1$s fixou \"%2$s\" - %1$s fixou uma mensagem - %1$s fixou uma foto - %1$s fixou um vídeo - %1$s fixou um arquivo - %1$s fixou um sticker - %1$s fixou um %2$s sticker - %1$s fixou uma mensagem de voz - %1$s fixou um contato - %1$s fixou um mapa - %1$s fixou um GIF - %1$s fixou uma música - %1$s fixou um jogo - - Selecionar Contato - Ainda não há contatos - Ei, vamos mudar para o Telegram: https://telegram.org/dl - às - ontem às - online - visto - visto - visto agora mesmo - Convidar Amigos - BUSCA GLOBAL - visto recentemente - visto na última semana - visto no último mês - visto há muito tempo - Nova Mensagem - - Enviar mensagem para... - Você poderá adicionar mais usuários após finalizar a criação do grupo e convertê-lo em um supergrupo. - Digite o nome do grupo - Nome do grupo - %1$d/%2$d membros - Você deseja entrar no chat \'%1$s\'? - Desculpe, este grupo já está lotado. - Desculpe, esse chat não existe. - Link copiado para área de transferência - Convidar para o Grupo via Link - Link de Convite - Você tem certeza que deseja desativar o link? Uma vez feito, ninguém conseguirá entrar usando-o. - Este link de convite está inativo. Um novo link foi gerado. - Desativar - Desativar Link - Você tem certeza que deseja remover o link ]]>%1$s]]>?\n\nO grupo \"]]>%2$s]]>\" se tornará privado. - Você tem certeza que deseja remover o link ]]>%1$s]]>?\n\nO canal \"]]>%2$s]]>\" se tornará privado. - Copiar Link - Compartilhar Link - Qualquer um com Telegram instalado poderá entrar no seu grupo abrindo este link. - - Administradores de Conversas - Todos São Administradores - Todos os membros podem adicionar novos membros, editar o nome e a foto do grupo. - Somente administradores podem adicionar e remover membros, editar nome foto do grupo. - - Mídia Compartilhada - Configurações - Adicionar membro - Definir administradores - Apagar e sair do grupo - Notificações - Remover do grupo - Converter a Supergrupo - Converter a Supergrupo - Converter a supergrupo - Atenção - Essa ação é irreversível. Não é possível voltar de um supergrupo para um grupo normal. - ]]>Limite de membros atingido.]]>\n\nPara ir além do limite e ter funções adcionais, converta para um supergrupo:\n\n• Supergrupos podem ter até %1$s\n• Novos membros veêm todo o histórico de conversas\n• Mensagens apagadas desaparecem para todos\n• Administradores podem fixar mensagens importantes\n• O criador pode definir um link público para o grupo - ]]>Em supergrupos:]]>\n\n• Novos membros podem visualizar todo o histórico de mensagens\n• Mensagens apagadas desaparecerão para todos\n• Administradores podem fixar mensagens importantes\n• O criador pode definir um link público para o grupo - ]]>Nota:]]> essa ação não pode ser desfeita. - - Compartilhar - Adicionar - Adicionar contato - %1$s ainda não está no Telegram, deseja lhe enviar um convite para entrar? - Convidar - Bloquear - Editar - Apagar - Início - Celular - Trabalho - Outro - Principal - Iniciar Chat Secreto - Ocorreu um erro. - Chave criptográfica - Tempo de autodestruição - Desativado - Essa imagem e texto foram derivadas da chave criptográfica para este chat secreto com ]]>%1$s]]>.
          ]]>Se você vê o mesmo no dispositivo de ]]>%2$s\'s]]>, a criptografia ponta a ponta está garantida.
          ]]>Leia mais em telegram.org
          - https://telegram.org/faq/br#chats-secretos - Desconhecido - Info - Telefone - - Nome de Usuário - Seu nome de usuário - Desculpe, este usuário já existe. - Desculpe, este usuário é inválido. - O nome de usuário deve ter pelo menos 5 caracteres. - O nome de usuário não pode exceder 32 caracteres. - Desculpe, o nome de usuário não pode começar com um número. - Você pode escolher um nome de usuário no ]]>Telegram]]>. Assim, outras pessoas poderão te encontrar pelo nome de usuário e entrar em contato sem precisar saber seu telefone.
          ]]>Você pode usar ]]>a–z]]>, ]]>0–9]]> e underline. O tamanho mínimo é ]]>5]]> caracteres.
          - Verificando nome de usuário... - %1$s está disponível. - Nenhum - Ocorreu um erro. - - Stickers - Adicionar Stickers - Adicionar Máscaras - Adicionar aos Stickers - Adicionar Máscaras - Stickers não encontrados - Stickers removidos - Máscaras removidas - Novos stickers adicionados - Novas máscaras adicionadas - Arquivar - Compartilhar - Copiar link - Remover - Nenhum sticker - Nenhuma máscara - Máscaras - Você pode adicionar máscaras em fotos que você envia. Para isso, abra o editor de fotos antes de enviar uma foto. - Stickers Populares - Esses são os stickers do momento no Telegram. Você pode adicionar stickers customizados através do bot @stickers. - Stickers Arquivados - Máscaras Arquivadas - Nenhum sticker arquivado - Nenhuma máscara arquivada - Você pode ter até 200 pacotes de sticker instalados. Stickers não usados são arquivados quando você adicionar mais. - Você pode ter até 200 pacotes de máscaras instalados. Máscaras não usadas são arquivadas quando você adicionar mais. - ENVIAR STICKER - Stickers arquivados - Alguns de seus pacotes de sticker antigos foram arquivados. Você pode reativá-los nas Configurações de Sticker. - Máscaras arquivadas - Alguns de seus pacotes de máscaras antigos foram arquivados. Você pode reativá-los nas Configurações de Máscaras. - - Restaurar todas as configurações de notificação - Tamanho do Texto - Fazer uma Pergunta - Permitir Animações - Desbloquear - Toque e segure no usuário para desbloquear - Nenhum usuário bloqueado - Notificações de mensagens - Alerta - Prévia da Mensagem - Notificações de grupo - Som - Notificações no aplicativo - Sons no Aplicativo - Vibração no Aplicativo - Vibrar - Visualização no Aplicativo - Limpar - Restaurar Configurações - Desfazer todas as configurações de notificação para todos os seus contatos e grupos - Notificações e Sons - Usuários Bloqueados - Sair - Sem som - Padrão - Suporte - Somente no silencioso - Papel de Parede - Mensagens - Enviar usando \'Enter\' - Terminar todas as outras sessões - Eventos - Contato entrou para o Telegram - Mensagens Fixadas - Idioma - Por favor entenda que o suporte do Telegram é feito por voluntários. Tentaremos responder o mais rápido possível, mas poderemos demorar um pouco.
          ]]>Por favor verifique a página de perguntas frequentes do Telegram]]>: há dicas e respostas para a maioria dos problemas]]>.
          - Pergunte a um voluntário - Perguntas Frequentes - https://telegram.org/faq/br - Política de Privacidade - https://telegram.org/privacy - Apagar localização? - Arquivo de localização incorreto - Ativado - Desativado - Manter Serviço Ativo - Reiniciar o app quando desligado pelo usuário ou pelo sistema. Isso irá garantir que o app mostre as notificações. - Conexão em Segundo Plano - Manter uma conexão de baixo impacto para o Telegram receber notificações. Habilite para notificações mais confiáveis. - Ordenar Por - Importar Contatos - Primeiro nome - Sobrenome - Cor do LED - Notificações Pop-up - Sem pop-up - Somente com a tela ligada - Somente com a tela desligada - Sempre mostrar pop-up - Contador no Ícone - Curta - Longa - Padrão do sistema - Configurações padrão - Download automático de mídia - Ao usar dados móveis - Quando conectado ao Wi-Fi - Quando em roaming - Sem mídia - Auto reproduzir GIFs - Levantar para Falar - Salvar na galeria - Editar nome - Prioridade - Padrão - Baixa - Alta - Máxima - Nunca - Repetir Notificações - Você pode alterar seu número do Telegram aqui. Sua conta e todos os seus dados — mensagens, mídia, contatos, etc. serão movidos para o novo número.\n\nImportante:]]> todos os contatos do Telegram terão seu novo número]]> adicionado às suas lista de contatos, desde que eles tenham seu número antigo e você não os tenha bloqueado no Telegram. - Todos os seus contatos do Telegram terão seu novo número adicionado às suas listas de contatos, desde que eles tenham seu antigo número e você não os tenha bloqueado no Telegram. - ALTERAR NÚMERO - Novo número - Vamos enviar uma SMS com um código de confirmação para o seu novo número. - O número %1$s já possui uma conta do Telegram. Por favor, exclua esta conta antes de migrar para o novo número. - Outro - Desativado - Desativado - Desativado - Desativado - Sons no Chat - Padrão - Padrão - Notificações Inteligentes - Desativado - Tocar no máximo %1$s a cada %2$s - Tocar no máximo - vezes - a cada - minutos - Pré-visualização de Link - Chats secretos - Navegador Interno - Abrir links externos com o aplicativo - Compartilhamento Direto - Mostrar chats recentes no menu compartilhar - Emoji - Draw single big emoji - Use system default emoji - - Configurações de Cache - Banco de Dados Local - Limpar todos os textos em cache? - Limpar o banco de dados local apagará todos os textos das mensagens em cache e compactará o banco de dados para economizar espaço. O Telegram precisa de alguns dados para trabalhar, então o tamanho do banco não vai chegar a zero.\n\nEssa operação pode demorar alguns minutos para ser concluída. - Limpar Cache - Limpar - Calculando... - Documentos - Fotos - Mensagens de voz - Vídeos - Música - Outros arquivos - Vazio - Manter Mídias - Fotos, vídeos e outros arquivos da nuvem que você não acessou]]> durante esse período serão removidos deste dispositivo para economizar espaço em disco.\n\nTodas as mídias permanecerão na nuvem do Telegram e poderão ser baixadas novamente conforme necessário. - Permanentemente - - Sessões Ativas - Sessão atual - Nenhuma outra sessão ativa - Você pode entrar no Telegram a partir de outro celular, tablet ou computador usando o mesmo número de telefone. Todos os seus dados serão sincronizados instantaneamente. - Sessões Ativas - Controle suas sessões em outros aparelhos. - Toque em uma sessão para terminá-la. - Encerrar essa sessão? - aplicativo não oficial - - Senha de Bloqueio - Alterar Senha - Quando você define uma senha adicional, um ícone de cadeado aparece na página de chats. Clique para bloquear e desbloquear o app.\n\nNota: se você esquecer a sua senha, terá de excluir e reinstalar o app. Todos os chats secretos serão perdidos. - Você verá o ícone do cadeado na página de chats. Clique para bloquear seu app do Telegram com a sua nova senha. - PIN - Senha - Insira sua senha atual - Insira uma senha - Insira sua nova senha - Insira sua senha - Re-insira sua nova senha - Senha inválida - As senhas não são iguais - Auto-bloquear - Requisitar senha se estiver ausente por muito tempo. - em %1$s - Desativado - Desbloquear com Impressão Digital - Confirme a impressão digital para continuar - Toque o sensor - Impressão digital não reconhecida. - - Arquivos Compartilhados - Mídia Compartilhada - Links Compartilhados - Música Compartilhada - Compartilhar fotos e vídeos no chat e acessá-los em qualquer um de seus dispositivos. - Compartilhe músicas nesse chat e os acesse de qualquer um de seus dispositivos. - Compartilhar arquivos e documentos no chat e acessá-los de qualquer um de seus dispositivos. - Compartilhe links nesse chat e os acesse de qualquer um de seus dispositivos - Fotos e vídeos desse chat serão mostrados aqui. - Músicas desse chat serão mostradas aqui. - Arquivos e documentos desse chat serão mostradas aqui. - Links compartilhados desse chat serão mostrados aqui. - - Mapa - Satélite - Híbrido - m de distância - km de distância - Enviar sua localização atual - Enviar localização selecionada - Localização - Precisão de %1$s - OU ESCOLHA UM LUGAR - - Mostrar todas as mídias - Salvar na galeria - %1$d de %2$d - Galeria - Todas as Fotos - Todos os Vídeos - Ainda não há fotos - Nenhum vídeo ainda - Baixar o vídeo primeiro - Nenhuma foto recente - Nenhum GIF recente - BUSCAR IMAGENS - BUSCA GLOBAL - BUSCAR GIFS - Procurar na web - Procurar GIFs - Recortar imagem - Editar imagem - Realçar - Luzes - Contraste - Exposição - Calor - Saturação - Vignette - Sombras - Granulado - Nitidez - Fade - Matiz - SOMBRAS - LUZES - Curvas - TUDO - VERMELHO - VERDE - AZUL - Desfoque - Desativado - Linear - Radial - Você tem certeza que deseja apagar esta foto? - Você tem certeza que deseja apagar este vídeo? - Descartar mudanças? - Limpar histórico de busca? - Limpar - Fotos - Vídeo - Adicionar legenda... - Legenda da Foto - Legenda do Vídeo - Legenda - Desenhar - Stickers - Texto - Apagar - Editar - Duplicar - Delineado - Regular - - Verificação em Duas Etapas - Configurar senha adicional - Você pode configurar uma senha que será requisitada quando você entrar em um novo aparelho, além do código que você receberá por SMS. - Sua senha - Por favor, digite a sua senha - Insira uma senha - Por favor, digite a sua nova senha - Por favor, digite sua senha novamente - E-mail de recuperação - Seu e-mail - Por favor, adicione um e-mail válido. Essa é a única forma de recuperar uma senha esquecida. - Pular - Atenção - É sério!\n\nSe você esquecer a sua senha, você perderá o acesso a sua conta do Telegram. Não há nenhuma forma de recuperá-la. - Quase lá! - Por favor, verifique o seu e-mail (não esqueça da pasta spam) para completar a configuração da verificação em duas etapas. - Pronto! - A sua senha para a verificação em duas etapas foi ativada. - Alterar senha - Desabilitar senha - Configurar e-mail de recuperação - Alterar e-mail de recuperação. - Você tem certeza que quer desabilitar a sua senha? - Dica da senha - Por favor, crie uma dica para a sua senha - As senhas não são iguais - Cancelar a configuração da verificação em duas etapas - Por favor, siga os seguintes passos para completar a configuração da autenticação em duas etapas:\n\n1. Verifique seu e-mail ( não esqueça da pasta spam)\n%1$s\n\n2. Clique no link de validação. - A dica deve ser diferente da sua senha - E-mail inválido - Desculpe - Como você não indicou um e-mail de recuperação quando configurou a sua senha, as únicas opções restantes são lembrar a senha ou apagar a sua conta. - O código de recuperação foi enviado para o e-mail fornecido: \n\n%1$s - Por favor, verifique o seu e-mail e digite aqui o código de 6 dígitos recebido. - Está tendo problemas para acessar seu e-mail %1$s? - Se você não puder acessar o seu e-mail, as suas únicas opções são lembrar a senha ou apagar a sua conta. - APAGAR MINHA CONTA - Se você prosseguir e apagar a sua conta, você perderá todos os seus chats e mensagens, assim como todas as suas mídias e arquivos compartilhados. - Aviso - Essa ação não pode ser desfeita.\n\nSe você apagar a sua conta, todas as suas mensagens e chats serão apagados. - Apagar - Senha - Você habilitou a verificação em duas etapas, a sua conta está protegida com uma senha adicional. - Esqueceu a senha? - Recuperação de senha - Código - Senha desativada - Você habilitou a verificação em duas etapas. Toda vez que você entrar na sua conta em um novo aparelho, será preciso digitar a senha que você configurar aqui. - O seu e-mail de recuperação %1$s ainda não está ativo e aguarda confirmação. - - Privacidade e Segurança - Privacidade - Último Acesso - Todos - Meus Contatos - Nenhum - Todos (-%1$d) - Meus Contatos (+%1$d) - Meus Contatos (-%1$d) - Meus Contatos (-%1$d, +%2$d) - Nenhum (+%1$d) - Segurança - Auto-destruição da conta - Se você estiver inativo por - Se você não acessar sua conta ao menos uma vez neste período, sua conta será excluída com seus grupos, mensagens e contatos. - Excluir sua conta? - Alterar quem pode ver o seu Último Acesso. - Quem pode ver o seu Último Acesso? - Adicionar exceções - Importante: você não poderá ver quando foi o Último Acesso das pessoas com quem você não compartilha o seu Último Acesso. Você visualizará o Último acesso aproximado (recentemente, dentro de uma semana, dentro de um mês). - Sempre Mostrar Para - Nunca Mostrar Para - Estas configurações irão substituir os valores anteriores. - Sempre Mostrar - Sempre compartilhar para os usuários... - Nunca Mostrar - Nunca mostrar para os usuários... - Adicionar Usuários - Desculpe, muitas solicitações. Impossível alterar os ajustes de privacidade agora, por favor aguarde. - Sair de todos os dispositivos, exceto este. - Toque e segure no usuário para remover. - Grupos - Quem pode me adicionar em grupos? - Você pode restringir quem pode te adicionar em grupos ou canais com precisão. - Sempre Permitir - Nunca Permitir - Sempre permitir... - Nunca permitir... - Esses usuários poderão ou não te adicionar em grupos e canais, dependendo de suas configurações. - Alterar quem pode te adicionar em grupos ou canais. - Desculpe, você não pode adicionar esse usuário a grupos devido às configurações de privacidade dele. - Desculpe, você não pode adicionar esse usuário a canais devido às configurações de privacidade dele. - Você não pode criar um grupo com esses usuários devido as configurações de privacidade deles. - - Editar Vídeo - Enviando vídeo... - Enviando gif... - - bot - Compartilhar - Adicionar Ao Grupo - Configurações - Ajuda - tem acesso às mensagens - não tem acesso às mensagens - O que esse bot pode fazer? - COMEÇAR - REINICIAR - Parar bot - Reiniciar bot - - Próximo - Voltar - Concluído - Abrir - Salvar - Cancelar - Fechar - Adicionar - Editar - Enviar - Ligar - Copiar - Apagar - Apagar e parar - Encaminhar - Tentar novamente - Câmera - Galeria - Apagar foto - Aplicar - OK - CORTAR - - Você entrou para o grupo via link de convite - un1 entrou para o grupo via link de convite - un1 removeu un2 - un1 deixou o grupo - un1 adicionou un2 - un1 removeu foto do grupo - un1 alterou a foto do grupo - un1 alterou o nome do grupo para un2 - un1 criou o grupo - Você removeu un2 - Você deixou o grupo - Você adicionou un2 - Você marcou %1$s - un1 marcou %1$s - Você marcou %1$s em un2 - un1 marcou %1$s em un2 - Você removeu a foto do grupo - Você alterou a foto do grupo - Você alterou o nome do grupo para un2 - Você criou o grupo - un1 removeu você - un1 adicionou você - un1 retornou ao grupo - un1 entrou no grupo - Você retornou ao grupo - Esta mensagem não é suportada na sua versão do Telegram. Para visualizá-la atualize seu aplicativo em https://telegram.org/update - Foto - Vídeo - GIF - Localização - Contato - Arquivo - Sticker - Mensagem de voz - Jogar - Você - Você realizou uma captura da tela! - un1 realizou uma captura da tela! - - Número de telefone inválido - Número de telefone banido - O código expirou. Por favor, identifique-se novamente. - Muitas tentativas. Por favor, tente novamente mais tarde. - Muitas tentativas, por favor tente novamente em %1$s - Código inválido - Nome inválido - Sobrenome inválido - Carregando... - Você não possui um reprodutor de vídeo, instale um para continuar - Por favor, envie um email para sms@stel.com e conte-nos sobre seu problema. - Você não possui um aplicativo que suporte o tipo de arquivo \'%1$s\', por favor instale um para continuar - Este usuário ainda não possui Telegram, deseja enviar um convite? - Você tem certeza? - Adcione %1$s ao chat %2$s? - Número de mensagens antigas para encaminhar: - Adicionar %1$s no grupo? - Este usuário já está neste grupo - Encaminhar mensagem para %1$s? - Enviar mensagens para %1$s? - Compartilhar jogo com %1$s? - Enviar contato para %1$s? - Você tem certeza que desejar sair?\n\nSaiba que você pode usar o Telegram em vários dispositivos de uma vez.\n\nLembre-se, sair apaga todos os seus Chats Secretos. - Você tem certeza que deseja terminar todas as outras sessões? - Você tem certeza que apagar e sair do grupo? - Você tem certeza que deseja apagar esta conversa? - Compartilhar sua localização? - Isso irá enviar sua localização atual ao bot. - O aplicativo foi impossibilitado de determinar sua localização atual. - Escolher manualmente - Esse bot gostaria de saber sua localização todas as vezes que você enviá-lo uma mensagem. Isso pode ser utilizado para providenciar resultados específicos de localização. - Compartilhar seu número de telefone? - O bot saberá seu número de telefone. Isso pode ser útil para a integração com outros serviços. - Tem certeza que deseja compartilhar seu número de telefone %1$s com ]]>%2$s]]>? - Tem certeza que deseja compartilhar seu número de telefone? - Você tem certeza que deseja bloquear este contato? - Você tem certeza que deseja desbloquear este contato? - Você tem certeza que deseja apagar este contato? - Você tem certeza que deseja começar um chat secreto? - Você tem certeza que deseja cancelar o registro? - Você tem certeza que deseja limpar o histórico? - Apagar todos os textos e mídias em cache desse canal? - Apagar todos os textos e mídias em cache desse grupo? - Você tem certeza que deseja apagar %1$s? - Enviar mensagens para %1$s? - Compartilhar jogo com %1$s? - Enviar contato para %1$s? - Encaminhar mensagem para %1$s? - Desculpe, esta funcionalidade não está disponível para seu país. - Não há conta do Telegram com esse nome de usuário - Esse bot não pode entrar em grupos. - Você gostaria de ativar a pré-visualização estendida de links em Chats Secretos? Note que a pré-visualização é gerada nos servidores do Telegram. - Os bots integrados são fornecidos por desenvolvedores terceiros. Para o bot funcionar, os símbolos que você digita depois do nome de usuário do bot são enviados para o respectivo desenvolvedor. - Gostaria de habilitar o \"Levantar para Falar\" para mensagens de voz? - Desculpe, você não pode editar essa mensagem. - Permita o acesso às SMS ao Telegram, assim podemos automaticamente adicionar o código para você. - Permita o acesso às ligações ao Telegram, assim podemos automaticamente adicionar o código para você. - Permita o acesso às SMS e ligações ao Telegram, assim podemos automaticamente adicionar o código para você. - Você não tem permissão para isso. - ENTRAR NO GRUPO - - Telegram precisa acessar seus contatos para que você possa se conectar aos seus amigos em todos os seus dispositivos. - Telegram precisa acessar seu armazenamento para que você possa enviar e salvar fotos, vídeos, músicas e outras mídias. - Telegram precisa acessar seu microfone para que você possa enviar mensagens de voz. - Telegram precisa acessar sua câmera para que você possa capturar fotos e vídeos. - Telegram precisa acessar sua localização para que você possa compartilhar com seus amigos. - O Telegram precisa acessar sua localização - CONFIGURAÇÕES - - Telegram - Rápido - Gratuito - Seguro - Poderoso - Baseado na nuvem - Privado - O mais rápido]]> aplicativo de mensagem do mundo. ]]>É gratuito]]> e seguro]]>. - O Telegram]]> envia mensagens mais rápido]]>que qualquer outro aplicativo. - O Telegram]]> é grátis para sempre. ]]>Sem propagandas. Sem taxas. - O Telegram]]> mantém suas mensagens]]>seguras de ataques de hackers. - O Telegram]]> não possui limites no tamanho]]>de seus arquivos e conversas. - O Telegram]]> permite você acessar suas]]> mensagens de múltiplos dispositivos. - O Telegram]]> possui mensagens fortemente]]>encriptadas e podem se auto-destruir. - Comece a conversar - - %1$d online - %1$d online - %1$d online - %1$d online - %1$d online - %1$d online - %1$d membros - %1$d membro - %1$d membros - %1$d membros - %1$d membros - %1$d membros - e mais %1$d estão escrevendo - e mais %1$d estão escrevendo - e mais %1$d estão escrevendo - e mais %1$d estão escrevendo - e mais %1$d estão escrevendo - e mais %1$d estão escrevendo - sem novas mensagens - %1$d nova mensagem - %1$d novas mensagens - %1$d novas mensagens - %1$d novas mensagens - %1$d novas mensagens - sem mensagens - %1$d mensagem - %1$d mensagens - %1$d mensagens - %1$d mensagens - %1$d mensagens - nenhum item - %1$d item - %1$d itens - %1$d itens - %1$d itens - %1$d itens - de nenhum chat - de %1$d chat - de %1$d chats - de %1$d chats - de %1$d chats - de %1$d chats - %1$d segundos - %1$d segundo - %1$d segundos - %1$d segundos - %1$d segundos - %1$d segundos - %1$d minutos - %1$d minuto - %1$d minutos - %1$d minutos - %1$d minutos - %1$d minutos - %1$d horas - %1$d hora - %1$d horas - %1$d horas - %1$d horas - %1$d horas - %1$d dias - %1$d dia - %1$d dias - %1$d dias - %1$d dias - %1$d dias - %1$d semanas - %1$d semana - %1$d semanas - %1$d semanas - %1$d semanas - %1$d semanas - %1$d meses - %1$d mês - %1$d meses - %1$d meses - %1$d meses - %1$d meses - %1$d anos - %1$d ano - %1$d anos - %1$d anos - %1$d anos - %1$d anos - %1$d usuários - %1$d usuário - %1$d usuários - %1$d usuários - %1$d usuários - %1$d usuários - %1$d vezes - %1$d vez - %1$d vezes - %1$d vezes - %1$d vezes - %1$d vezes - %1$d metros - %1$d metro - %1$d metros - %1$d metros - %1$d metros - %1$d metros - %1$d stickers - %1$d sticker - %1$d stickers - %1$d stickers - %1$d stickers - %1$d stickers - %1$d fotos - %1$d foto - %1$d fotos - %1$d fotos - %1$d fotos - %1$d fotos - %1$d ponto - %1$d ponto - %1$d pontos - %1$d pontos - %1$d pontos - %1$d pontos - visto há %1$d minutos - visto há %1$d minuto - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d minutos - visto há %1$d horas - visto há %1$d hora - visto há %1$d horas - visto há %1$d horas - visto há %1$d horas - visto há %1$d horas - %1$d]]> segundos - %1$d]]> segundo - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> segundos - %1$d]]> minutos - %1$d]]> minuto - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> minutos - %1$d]]> horas - %1$d]]> hora - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> horas - %1$d]]> dias - %1$d]]> dia - %1$d]]> dias - %1$d]]> dias - %1$d]]> dias - %1$d]]> dias - - %1$d mensagens encaminhadas - Mensagem encaminhada - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas - %1$d mensagens encaminhadas - %1$d arquivos encaminhados - Arquivo encaminhado - %1$d arquivos encaminhados - %1$d arquivos encaminhados - %1$d arquivos encaminhados - %1$d arquivos encaminhados - %1$d fotos encaminhadas - Foto encaminhada - %1$d fotos encaminhadas - %1$d fotos encaminhadas - %1$d fotos encaminhadas - %1$d fotos encaminhadas - %1$d vídeos encaminhados - Vídeo encaminhado - %1$d vídeos encaminhados - %1$d vídeos encaminhados - %1$d vídeos encaminhados - %1$d vídeos encaminhados - %1$d músicas encaminhadas - Música encaminhada - %1$d músicas encaminhadas - %1$d músicas encaminhadas - %1$d músicas encaminhadas - %1$d músicas encaminhadas - %1$d mensagens de voz encaminhadas - Mensagem de voz encaminhada - %1$d mensagens de voz encaminhadas - %1$d mensagens de voz encaminhadas - %1$d mensagens de voz encaminhadas - %1$d mensagens de voz encaminhadas - %1$d localizações encaminhadas - Localização encaminhada - %1$d localizações encaminhadas - %1$d localizações encaminhadas - %1$d localizações encaminhadas - %1$d localizações encaminhadas - %1$d contatos encaminhados - Contato encaminhado - %1$d contatos encaminhados - %1$d contatos encaminhados - %1$d contatos encaminhados - %1$d contatos encaminhados - %1$d stickers encaminhados - Sticker encaminhado - %1$d stickers encaminhados - %1$d stickers encaminhados - %1$d stickers encaminhados - %1$d stickers encaminhados - e %1$d outros - e %1$d outro - e %1$d outros - e %1$d outros - e %1$d outros - e %1$d outros - - MMMM yyyy - dd MMM - dd.MM.yy - dd.MM.yyyy - d MMMM - d MMMM, yyyy - EEE - HH:mm - h:mm a - %1$s às %2$s -
          diff --git a/TMessagesProj/src/main/res/values-v21/styles.xml b/TMessagesProj/src/main/res/values-v21/styles.xml index 0e719cf4212..f98bc604412 100644 --- a/TMessagesProj/src/main/res/values-v21/styles.xml +++ b/TMessagesProj/src/main/res/values-v21/styles.xml @@ -3,7 +3,7 @@ ~ It is licensed under GNU GPL v. 2 or later. ~ You should have received a copy of the license in this archive (see LICENSE). ~ - ~ Copyright Nikolai Kudashov, 2013-2016. + ~ Copyright Nikolai Kudashov, 2013-2017. --> @android:color/white @style/ActionBar.Transparent.TMessages.Item @style/Theme.TMessages.ListView - @drawable/list_selector - @style/Theme.TMessages.EditText + @drawable/list_selector_ex @drawable/bar_selector_style #33000000 #527da3 @style/Theme.TMessages.Dialog.Alert #4991cc + @style/Theme.TMessages.DatePicker + @style/Theme.TMessages.Dialog.Alert + @style/Theme.TMessages.CalendarView + + + + + + + + + + - - - - @@ -45,6 +44,15 @@ false + + - - - -